smriti 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +168 -0
- data/Rakefile +15 -0
- data/app/assets/images/smriti/android-chrome-192x192.png +0 -0
- data/app/assets/images/smriti/android-chrome-512x512.png +0 -0
- data/app/assets/images/smriti/apple-touch-icon.png +0 -0
- data/app/assets/images/smriti/favicon-16x16.png +0 -0
- data/app/assets/images/smriti/favicon-32x32.png +0 -0
- data/app/assets/images/smriti/favicon-48x48.png +0 -0
- data/app/assets/images/smriti/favicon.ico +0 -0
- data/app/assets/images/smriti/favicon.svg +18 -0
- data/app/assets/images/smriti/logo.svg +18 -0
- data/app/assets/images/smriti/mask-icon.svg +5 -0
- data/app/assets/stylesheets/smriti/application.css +1040 -0
- data/app/controllers/smriti/admin/application_controller.rb +135 -0
- data/app/controllers/smriti/admin/dashboard_controller.rb +32 -0
- data/app/controllers/smriti/admin/mat_view_definitions_controller.rb +372 -0
- data/app/controllers/smriti/admin/mat_view_runs_controller.rb +185 -0
- data/app/controllers/smriti/admin/preferences_controller.rb +91 -0
- data/app/helpers/smriti/admin/datatable_helper.rb +249 -0
- data/app/helpers/smriti/admin/localized_digit_helper.rb +70 -0
- data/app/helpers/smriti/admin/ui_helper.rb +539 -0
- data/app/javascript/smriti/application.js +8 -0
- data/app/javascript/smriti/controllers/application.js +10 -0
- data/app/javascript/smriti/controllers/body_setup_controller.js +120 -0
- data/app/javascript/smriti/controllers/datatable_controller.js +351 -0
- data/app/javascript/smriti/controllers/details_controller.js +200 -0
- data/app/javascript/smriti/controllers/drawer_controller.js +470 -0
- data/app/javascript/smriti/controllers/flash_controller.js +112 -0
- data/app/javascript/smriti/controllers/index.js +10 -0
- data/app/javascript/smriti/controllers/mv_confirm_controller.js +435 -0
- data/app/javascript/smriti/controllers/tabs_controller.js +184 -0
- data/app/javascript/smriti/controllers/tooltip_controller.js +525 -0
- data/app/javascript/smriti/controllers/turbo_frame_lifecycle_controller.js +342 -0
- data/app/jobs/smriti/application_job.rb +144 -0
- data/app/jobs/smriti/create_view_job.rb +87 -0
- data/app/jobs/smriti/delete_view_job.rb +89 -0
- data/app/jobs/smriti/refresh_view_job.rb +94 -0
- data/app/models/concerns/smriti_i18n.rb +139 -0
- data/app/models/concerns/smriti_paginate.rb +70 -0
- data/app/models/concerns/smriti_query_helper.rb +36 -0
- data/app/models/smriti/application_record.rb +39 -0
- data/app/models/smriti/mat_view_definition.rb +254 -0
- data/app/models/smriti/mat_view_run.rb +275 -0
- data/app/views/layouts/smriti/_footer.html.erb +47 -0
- data/app/views/layouts/smriti/_header.html.erb +25 -0
- data/app/views/layouts/smriti/admin.html.erb +47 -0
- data/app/views/layouts/smriti/turbo_frame.html.erb +3 -0
- data/app/views/smriti/admin/dashboard/index.html.erb +38 -0
- data/app/views/smriti/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
- data/app/views/smriti/admin/mat_view_definitions/_dt-index-empty-row.html.erb +11 -0
- data/app/views/smriti/admin/mat_view_definitions/_dt-index-row.html.erb +27 -0
- data/app/views/smriti/admin/mat_view_definitions/empty.html.erb +1 -0
- data/app/views/smriti/admin/mat_view_definitions/form.html.erb +79 -0
- data/app/views/smriti/admin/mat_view_definitions/index.html.erb +10 -0
- data/app/views/smriti/admin/mat_view_definitions/show.html.erb +40 -0
- data/app/views/smriti/admin/mat_view_runs/_dt-index-empty-row.html.erb +11 -0
- data/app/views/smriti/admin/mat_view_runs/_dt-index-row.html.erb +41 -0
- data/app/views/smriti/admin/mat_view_runs/index.html.erb +1 -0
- data/app/views/smriti/admin/mat_view_runs/show.html.erb +64 -0
- data/app/views/smriti/admin/preferences/show.html.erb +49 -0
- data/app/views/smriti/admin/ui/_card.html.erb +15 -0
- data/app/views/smriti/admin/ui/_datatable.html.erb +34 -0
- data/app/views/smriti/admin/ui/_datatable_filters.html.erb +45 -0
- data/app/views/smriti/admin/ui/_datatable_tbody.html.erb +11 -0
- data/app/views/smriti/admin/ui/_datatable_tfoot.html.erb +70 -0
- data/app/views/smriti/admin/ui/_datatable_thead.html.erb +105 -0
- data/app/views/smriti/admin/ui/_details.html.erb +10 -0
- data/app/views/smriti/admin/ui/_flash.html.erb +6 -0
- data/app/views/smriti/admin/ui/_table.html.erb +8 -0
- data/config/importmap.rb +9 -0
- data/config/locales/ar.yml +223 -0
- data/config/locales/de.yml +230 -0
- data/config/locales/en-AU-ocker.yml +223 -0
- data/config/locales/en-AU.yml +202 -0
- data/config/locales/en-BORK.yml +225 -0
- data/config/locales/en-CA.yml +223 -0
- data/config/locales/en-GB.yml +223 -0
- data/config/locales/en-LOL.yml +219 -0
- data/config/locales/en-SCOT.yml +223 -0
- data/config/locales/en-SHAKESPEARE.yml +225 -0
- data/config/locales/en-US-pirate.yml +222 -0
- data/config/locales/en-US.yml +225 -0
- data/config/locales/en-YODA.yml +221 -0
- data/config/locales/en.yml +223 -0
- data/config/locales/es.yml +226 -0
- data/config/locales/fa.yml +223 -0
- data/config/locales/fr-CA.yml +227 -0
- data/config/locales/fr.yml +227 -0
- data/config/locales/he.yml +218 -0
- data/config/locales/hi.yml +223 -0
- data/config/locales/it.yml +225 -0
- data/config/locales/ja-JP.yml +215 -0
- data/config/locales/pt.yml +225 -0
- data/config/locales/ru.yml +228 -0
- data/config/locales/ur.yml +225 -0
- data/config/locales/zh-CN.yml +214 -0
- data/config/locales/zh-TW.yml +214 -0
- data/config/routes.rb +36 -0
- data/lib/ext/exception.rb +20 -0
- data/lib/generators/smriti/install/install_generator.rb +86 -0
- data/lib/generators/smriti/install/templates/create_mat_view_definitions.rb +29 -0
- data/lib/generators/smriti/install/templates/create_mat_view_runs.rb +32 -0
- data/lib/generators/smriti/install/templates/smriti_initializer.rb +23 -0
- data/lib/smriti/admin/auth_bridge.rb +93 -0
- data/lib/smriti/admin/default_auth.rb +62 -0
- data/lib/smriti/configuration.rb +58 -0
- data/lib/smriti/engine.rb +82 -0
- data/lib/smriti/helpers/ui_test_ids.rb +49 -0
- data/lib/smriti/jobs/adapter.rb +81 -0
- data/lib/smriti/service_response.rb +75 -0
- data/lib/smriti/services/base_service.rb +471 -0
- data/lib/smriti/services/check_matview_exists.rb +76 -0
- data/lib/smriti/services/concurrent_refresh.rb +94 -0
- data/lib/smriti/services/create_view.rb +173 -0
- data/lib/smriti/services/delete_view.rb +111 -0
- data/lib/smriti/services/regular_refresh.rb +90 -0
- data/lib/smriti/services/swap_refresh.rb +181 -0
- data/lib/smriti/version.rb +21 -0
- data/lib/smriti.rb +64 -0
- data/lib/tasks/helpers.rb +185 -0
- data/lib/tasks/smriti_tasks.rake +151 -0
- metadata +206 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
|
4
|
+
#
|
|
5
|
+
# This source code is licensed under the MIT license found in the
|
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Top-level namespace for the smriti engine.
|
|
10
|
+
module Smriti
|
|
11
|
+
##
|
|
12
|
+
# ActiveRecord model that tracks the lifecycle of *runs* for
|
|
13
|
+
# materialised views.
|
|
14
|
+
#
|
|
15
|
+
# Each record corresponds to a single attempt to mutate a materialised view
|
|
16
|
+
# from a {Smriti::MatViewDefinition}, storing its status, timing, and
|
|
17
|
+
# any associated error or metadata.
|
|
18
|
+
#
|
|
19
|
+
# This model provides an auditable history of view provisioning across
|
|
20
|
+
# environments, useful for telemetry, dashboards, and debugging.
|
|
21
|
+
#
|
|
22
|
+
# @see Smriti::MatViewDefinition
|
|
23
|
+
# @see Smriti::CreateViewJob
|
|
24
|
+
#
|
|
25
|
+
# @example Query recent successful runs
|
|
26
|
+
# Smriti::MatViewRun.status_success.order(created_at: :desc).limit(10)
|
|
27
|
+
#
|
|
28
|
+
# @example Check if a definition has any failed runs
|
|
29
|
+
# definition.mat_view_runs.status_failed.any?
|
|
30
|
+
#
|
|
31
|
+
class MatViewRun < ApplicationRecord
|
|
32
|
+
##
|
|
33
|
+
# Underlying database table name.
|
|
34
|
+
self.table_name = 'mat_view_runs'
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# The definition this run belongs to.
|
|
38
|
+
#
|
|
39
|
+
# @return [Smriti::MatViewDefinition]
|
|
40
|
+
#
|
|
41
|
+
belongs_to :mat_view_definition, class_name: 'Smriti::MatViewDefinition'
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Status of the create run.
|
|
45
|
+
#
|
|
46
|
+
# @!attribute [r] status
|
|
47
|
+
# @return [Symbol] One of:
|
|
48
|
+
# - `:running` - currently executing
|
|
49
|
+
# - `:success` - completed successfully
|
|
50
|
+
# - `:failed` - encountered an error
|
|
51
|
+
#
|
|
52
|
+
enum :status, {
|
|
53
|
+
running: 0,
|
|
54
|
+
success: 1,
|
|
55
|
+
failed: 2
|
|
56
|
+
}, prefix: :status
|
|
57
|
+
|
|
58
|
+
# Operation type of the run.
|
|
59
|
+
#
|
|
60
|
+
# @!attribute [r] operation
|
|
61
|
+
# @return [Symbol] One of:
|
|
62
|
+
# - `:create` - initial creation of the materialised view
|
|
63
|
+
# - `:refresh` - refreshing an existing view
|
|
64
|
+
# - `:drop` - dropping the materialised view
|
|
65
|
+
enum :operation, {
|
|
66
|
+
create: 0,
|
|
67
|
+
refresh: 1,
|
|
68
|
+
drop: 2
|
|
69
|
+
}, prefix: :operation
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Validations
|
|
73
|
+
#
|
|
74
|
+
# Ensures that a status is always present.
|
|
75
|
+
validates :status, presence: true
|
|
76
|
+
|
|
77
|
+
# ───────────────────────────────────────────────────────────────
|
|
78
|
+
# Scopes for runs, ordering, searching, filtering
|
|
79
|
+
# ───────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Scope create runs
|
|
83
|
+
# All runs with `operation: :create`.
|
|
84
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
85
|
+
scope :create_runs, -> { where(operation: :create) }
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Scope refresh runs
|
|
89
|
+
# All runs with `operation: :refresh`.
|
|
90
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
91
|
+
scope :refresh_runs, -> { where(operation: :refresh) }
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Scope drop runs
|
|
95
|
+
# All runs with `operation: :drop`.
|
|
96
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
97
|
+
scope :drop_runs, -> { where(operation: :drop) }
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# Scope ordered by operation
|
|
101
|
+
# Orders by the `operation` attribute using humanized enum labels.
|
|
102
|
+
#
|
|
103
|
+
# @param dir [Symbol, String] `:asc` or `:desc`
|
|
104
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
105
|
+
scope :ordered_by_operation, ->(dir) { ordered_by_enum(enum_values: operations, enum_name: :operation, direction: dir) }
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Scope ordered by definition name
|
|
109
|
+
# Orders by the associated definition's `name` attribute.
|
|
110
|
+
#
|
|
111
|
+
# @param dir [Symbol, String] `:asc` or `:desc`
|
|
112
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
113
|
+
scope :ordered_by_definition, ->(dir) { left_joins(:mat_view_definition).order("mat_view_definitions.name #{dir.to_s.upcase}") }
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Scope ordered by started_at
|
|
117
|
+
# Orders by the `started_at` attribute.
|
|
118
|
+
#
|
|
119
|
+
# @param dir [Symbol, String] `:asc` or `:desc`
|
|
120
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
121
|
+
scope :ordered_by_started_at, ->(dir) { order("started_at #{dir.to_s.upcase}") }
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# Scope ordered by the 'status' attribute using humanized enum labels.
|
|
125
|
+
#
|
|
126
|
+
# @param dir [Symbol, String] `:asc` or `:desc`
|
|
127
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
128
|
+
scope :ordered_by_status, ->(dir) { ordered_by_enum(enum_values: statuses, enum_name: :status, direction: dir) }
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Scope ordered by duration_ms
|
|
132
|
+
# Orders by the `duration_ms` attribute.
|
|
133
|
+
#
|
|
134
|
+
# @param dir [Symbol, String] `:asc` or `:desc`
|
|
135
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
136
|
+
scope :ordered_by_duration_ms, ->(dir) { order("duration_ms #{dir.to_s.upcase}") }
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Scope search by operation
|
|
140
|
+
# Searches the `operation` attribute using humanized enum labels.
|
|
141
|
+
#
|
|
142
|
+
# @param term [String] search term
|
|
143
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
144
|
+
scope :search_by_operation, ->(term) { search_by_enum(enum_values: operations, enum_name: :operation, term: term) }
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
# Scope search by definition name
|
|
148
|
+
# Searches by the associated definition's `name` attribute using ILIKE.
|
|
149
|
+
#
|
|
150
|
+
# @param term [String] search term
|
|
151
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
152
|
+
scope :search_by_definition, lambda { |term|
|
|
153
|
+
where(<<~SQL, like: "%#{term}%")
|
|
154
|
+
EXISTS (
|
|
155
|
+
SELECT 1
|
|
156
|
+
FROM mat_view_definitions d
|
|
157
|
+
WHERE d.id = mat_view_runs.mat_view_definition_id
|
|
158
|
+
AND d.name ILIKE :like
|
|
159
|
+
)
|
|
160
|
+
SQL
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
# Scope search by status
|
|
165
|
+
# Searches the `status` attribute using humanized enum labels.
|
|
166
|
+
#
|
|
167
|
+
# @param term [String] search term
|
|
168
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
169
|
+
scope :search_by_status, ->(term) { search_by_enum(enum_values: statuses, enum_name: :status, term: term) }
|
|
170
|
+
|
|
171
|
+
# Scope search by duration_ms
|
|
172
|
+
# Searches the `duration_ms` attribute by casting to text and using ILIKE.
|
|
173
|
+
# Also supports searching with localized "X milliseconds" format.
|
|
174
|
+
#
|
|
175
|
+
# @param term [String] search term
|
|
176
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
177
|
+
scope :search_by_duration_ms, lambda { |term|
|
|
178
|
+
term_with_ms = I18n.t('smriti.x_miliseconds', count: term)
|
|
179
|
+
where('CAST(duration_ms AS TEXT) ILIKE ?', "%#{term}%")
|
|
180
|
+
.or(where('CAST(duration_ms AS TEXT) ILIKE ?', "%#{term_with_ms}%"))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
##
|
|
184
|
+
# Scope filter by operation
|
|
185
|
+
# Filters by the `operation` attribute.
|
|
186
|
+
#
|
|
187
|
+
# @param operation [String, Symbol] operation value
|
|
188
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
189
|
+
scope :filtered_by_operation, ->(operation) { where(operation:) }
|
|
190
|
+
|
|
191
|
+
##
|
|
192
|
+
# Scope filter by definition
|
|
193
|
+
# Filters by the associated definition's ID.
|
|
194
|
+
#
|
|
195
|
+
# @param definition_id [Integer] definition ID
|
|
196
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
197
|
+
scope :filtered_by_definition, lambda { |definition_id|
|
|
198
|
+
where(mat_view_definition_id: definition_id)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
##
|
|
202
|
+
# Scope filter by status
|
|
203
|
+
# Filters by the `status` attribute.
|
|
204
|
+
#
|
|
205
|
+
# @param status [String, Symbol] status value
|
|
206
|
+
# @return [ActiveRecord::Relation<Smriti::MatViewRun>]
|
|
207
|
+
scope :filtered_by_status, ->(status) { where(status:) }
|
|
208
|
+
|
|
209
|
+
# ──────────────────────────────────────────────────────────────
|
|
210
|
+
# Class methods
|
|
211
|
+
# ──────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
class << self
|
|
214
|
+
# Options for filtering by operation
|
|
215
|
+
#
|
|
216
|
+
# @return [Array<Array(String, String)>] array of `[label, value]` pairs
|
|
217
|
+
def filter_options_for_operation
|
|
218
|
+
order(:operation).distinct.pluck(:operation).compact.map { |operation| [human_enum_name(:operation, operation), operation] }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Options for filtering by definition
|
|
222
|
+
#
|
|
223
|
+
# @return [Array<Array(String, Integer)>] array of `[name, id]` pairs
|
|
224
|
+
def filter_options_for_definition
|
|
225
|
+
Smriti::MatViewDefinition.order(:name).pluck(:name, :id)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Options for filtering by status
|
|
229
|
+
#
|
|
230
|
+
# @return [Array<Array(String, String)>] array of `[label, value]` pairs
|
|
231
|
+
def filter_options_for_status
|
|
232
|
+
order(:status).distinct.pluck(:status).compact.map { |status| [human_enum_name(:status, status), status] }
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# ──────────────────────────────────────────────────────────────
|
|
237
|
+
# Instance methods
|
|
238
|
+
# ──────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
##
|
|
241
|
+
# Metadata associated with the run.
|
|
242
|
+
#
|
|
243
|
+
# This is a JSONB column storing arbitrary structured data about the run,
|
|
244
|
+
# such as database responses, row counts, etc.
|
|
245
|
+
#
|
|
246
|
+
# @return [Hash] Parsed JSON metadata.
|
|
247
|
+
def meta
|
|
248
|
+
self[:meta] || {}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
##
|
|
252
|
+
# Error message if the run failed.
|
|
253
|
+
#
|
|
254
|
+
# Extracted from `meta['error']['message']` if present.
|
|
255
|
+
#
|
|
256
|
+
# @return [String, nil] Error message or `nil` if none.
|
|
257
|
+
def error_message
|
|
258
|
+
meta.dig('error', 'message')
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
##
|
|
262
|
+
|
|
263
|
+
# row count before the operation, if applicable
|
|
264
|
+
# @return [Integer, nil]
|
|
265
|
+
def row_count_before
|
|
266
|
+
meta.dig('response', 'row_count_before')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# row count after the operation, if applicable
|
|
270
|
+
# @return [Integer, nil]
|
|
271
|
+
def row_count_after
|
|
272
|
+
meta.dig('response', 'row_count_after')
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<footer class="mv-footer">
|
|
2
|
+
<div class="mv-container mv-footer-row">
|
|
3
|
+
<div>
|
|
4
|
+
<%= t("smriti.footer.tagline") %><br>
|
|
5
|
+
<%= t("smriti.footer.copyright", year: Time.current.year, company: Smriti::Engine.company_name) %>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="mv-text-right">
|
|
8
|
+
<%= mv_link_to Smriti::Engine.rubygems_uri, tooltip: t("smriti.footer.tooltip.gem_version"), testid: 'GEM_LINK' do %>
|
|
9
|
+
<strong><%= Smriti::Engine.project_name %></strong>
|
|
10
|
+
<%= t("smriti.footer.version", version: Smriti::Engine.project_version) %><br>
|
|
11
|
+
<% end %>
|
|
12
|
+
<%= mv_link_to t("smriti.footer.project_homepage"),
|
|
13
|
+
Smriti::Engine.project_homepage,
|
|
14
|
+
tooltip: t("smriti.footer.tooltip.project_homepage"),
|
|
15
|
+
testid: "PROJECT_HOMEPAGE_LINK" %>
|
|
16
|
+
<%= mv_link_to t("smriti.footer.open_issue"), Smriti::Engine.bug_tracker_uri, tooltip: t("smriti.footer.tooltip.open_issue"), testid: "OPEN_ISSUE_LINK" %>
|
|
17
|
+
<%= mv_link_to t("smriti.footer.documentation"),
|
|
18
|
+
Smriti::Engine.documentation_uri,
|
|
19
|
+
tooltip: t("smriti.footer.tooltip.documentation"),
|
|
20
|
+
testid: "DOCUMENTATION_LINK" %>
|
|
21
|
+
<br/>
|
|
22
|
+
<span class="text-rose-700"><%= t("smriti.footer.need_help") %></span>
|
|
23
|
+
<%= mv_link_to t("smriti.footer.support"), Smriti::Engine.support_uri, tooltip: t("smriti.footer.tooltip.support"), testid: "SUPPORT_LINK" %>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</footer>
|
|
27
|
+
<div class="mv-drawer-root" aria-hidden="true" data-drawer-target="root">
|
|
28
|
+
<div data-drawer-target="overlay" class="mv-drawer-overlay" data-action="click->drawer#close"></div>
|
|
29
|
+
<aside data-drawer-target="panel" class="mv-drawer" role="dialog" aria-modal="true">
|
|
30
|
+
<div class="mv-drawer-head" aria-label="<%= t('smriti.details') %>" data-drawer-target="header">
|
|
31
|
+
<h2 id="mv-drawer-title" data-drawer-target="title">
|
|
32
|
+
<%= t("smriti.details") %>
|
|
33
|
+
</h2>
|
|
34
|
+
<div class='row-item'>
|
|
35
|
+
<%= mv_drawer_action_button(t("smriti.refresh"), "refresh", t("smriti.refresh_contents"), "left", testid: "DRAWER_REFRESH_LINK") { mv_icon(:refresh) } %>
|
|
36
|
+
<%= mv_drawer_action_button(t("smriti.close"), "close", t("smriti.close_window"), "left", testid: "DRAWER_CLOSE_LINK") { mv_icon(:x_circle) } %>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="mv-drawer-body">
|
|
40
|
+
<turbo-frame id="mv-drawer" data-drawer-target="frame" data-turbo-frame-lifecycle-target="frame">
|
|
41
|
+
<div class="mv-card">
|
|
42
|
+
<div class="mv-card-b"><%= t("smriti.loading") %></div>
|
|
43
|
+
</div>
|
|
44
|
+
</turbo-frame>
|
|
45
|
+
</div>
|
|
46
|
+
</aside>
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<header class="mv-header">
|
|
2
|
+
<div class="mv-container mv-header-row">
|
|
3
|
+
<div class="row-item">
|
|
4
|
+
<%= mv_link_to admin_root_path, class: "mv-brand", underline: false, testid: 'HEADER_LINK' do %>
|
|
5
|
+
<%= image_tag "smriti/logo.svg", alt: t("smriti.title"), class: "mv-logo" %>
|
|
6
|
+
<span><%= t("smriti.title") %></span>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
9
|
+
<div class='row-item'>
|
|
10
|
+
<% if user %>
|
|
11
|
+
<span>
|
|
12
|
+
<%= t("smriti.header.signed_in_as", email: user.respond_to?(:email) ? user.email : user.to_s) %>
|
|
13
|
+
</span>
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= mv_drawer_link(
|
|
16
|
+
admin_preferences_path(frame_id: "mv-drawer"),
|
|
17
|
+
t("smriti.settings.title"),
|
|
18
|
+
variant: :ghost,
|
|
19
|
+
tooltip: t("smriti.settings.title"),
|
|
20
|
+
tooltip_placement: "bottom",
|
|
21
|
+
testid: "PREFERENCES_LINK",
|
|
22
|
+
) { mv_icon(:gear) } %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= I18n.locale %>" dir="<%= %i(ar he fa ur).include?(I18n.locale.to_sym) ? 'rtl' : 'ltr' %>" data-theme="<%= smriti_data_theme || 'auto' %>">
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= t("smriti.title") %><%= content_for?(:page_title) ? " | #{content_for(:page_title)}" : "" %></title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
<meta charset="utf-8">
|
|
8
|
+
<meta name="description" content="<%= t('smriti.project_description') %>">
|
|
9
|
+
<meta name="author" content="<%= t('smriti.project_author') %>">
|
|
10
|
+
<meta name="keywords" content="<%= t('smriti.project_tags') %>">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<meta name="color-scheme" content="<%= %w[light dark].include?(smriti_data_theme) ? smriti_data_theme : 'light dark' %>">
|
|
13
|
+
<meta http-equiv="Content-Language" content="<%= I18n.locale %>">
|
|
14
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
16
|
+
<link
|
|
17
|
+
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Space+Grotesk:wght@300..700&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap"
|
|
18
|
+
rel="stylesheet"
|
|
19
|
+
>
|
|
20
|
+
<%= favicon_link_tag "smriti/favicon.svg", type: "image/svg+xml" %>
|
|
21
|
+
<%= favicon_link_tag "smriti/favicon.ico", rel: "icon", sizes: "16x16 32x32 48x48" %>
|
|
22
|
+
<%= favicon_link_tag "smriti/favicon-16x16.png", rel: "icon", type: "image/png", sizes: "16x16" %>
|
|
23
|
+
<%= favicon_link_tag "smriti/favicon-32x32.png", rel: "icon", type: "image/png", sizes: "32x32" %>
|
|
24
|
+
<%= favicon_link_tag "smriti/favicon-48x48.png", rel: "icon", type: "image/png", sizes: "48x48" %>
|
|
25
|
+
<%= favicon_link_tag "smriti/apple-touch-icon.png", rel: "apple-touch-icon" %>
|
|
26
|
+
<%= favicon_link_tag "smriti/mask-icon.svg", rel: "mask-icon", color: "#0F172A" %>
|
|
27
|
+
<%= favicon_link_tag "smriti/android-chrome-192x192.png", rel: "icon", type: "image/png", sizes: "192x192" %>
|
|
28
|
+
<%= favicon_link_tag "smriti/android-chrome-512x512.png", rel: "icon", type: "image/png", sizes: "512x512" %>
|
|
29
|
+
<%= tag.meta name: "theme-color", content: "#0F172A" %>
|
|
30
|
+
<%= stylesheet_link_tag "smriti/application.css", media: "all", "data-turbo-track": "reload" %>
|
|
31
|
+
<%= javascript_importmap_tags "smriti/application", importmap: Smriti.importmap %>
|
|
32
|
+
|
|
33
|
+
<%= javascript_tag do %>
|
|
34
|
+
window.SmritiRoutes = { definitionsPath: "<%= admin_mat_view_definitions_path %>", runsPath: "<%= admin_mat_view_runs_path %>", preferencesPath: "<%= admin_preferences_path %>" }
|
|
35
|
+
<% end %>
|
|
36
|
+
</head>
|
|
37
|
+
<body class="mv-shell" data-controller="body-setup turbo-frame-lifecycle drawer mv-confirm" data-drawer-open-class="is-open">
|
|
38
|
+
<%= render "layouts/smriti/header" %>
|
|
39
|
+
<main class="mv-main">
|
|
40
|
+
<div class="mv-container">
|
|
41
|
+
<%= render "smriti/admin/ui/flash" %>
|
|
42
|
+
<%= yield %>
|
|
43
|
+
</div>
|
|
44
|
+
</main>
|
|
45
|
+
<%= render "layouts/smriti/footer" %>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<h1><%= t("smriti.dashboard.title") %></h1>
|
|
2
|
+
<div class="mv-card" style="margin-bottom:12px;">
|
|
3
|
+
<div class="mv-card-h"><%= t("smriti.dashboard.metrics.title") %></div>
|
|
4
|
+
<div class="mv-card-b">
|
|
5
|
+
<div class="mv-flash mv-flash--ok">
|
|
6
|
+
<%= @metrics_note %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div data-controller="tabs" data-tabs-active-link-class="mv-tab--on" data-tabs-default-tab-value="definitions">
|
|
11
|
+
<nav class="mv-tabs" style="margin-bottom:1rem;">
|
|
12
|
+
<%= mv_tab_link 'definitions', selected: true, testid: 'DEFINITIONS_TAB_LINK' do %>
|
|
13
|
+
<%= t("smriti.definitions") %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= mv_tab_link 'runs', selected: false, testid: 'RUNS_TAB_LINK' do %>
|
|
16
|
+
<%= t("smriti.runs") %>
|
|
17
|
+
<% end %>
|
|
18
|
+
</nav>
|
|
19
|
+
<div data-tabs-target="panel" data-name="definitions">
|
|
20
|
+
<turbo-frame
|
|
21
|
+
id="dash-definitions"
|
|
22
|
+
data-src="<%= admin_mat_view_definitions_path(frame_id: 'dash-definitions') %>"
|
|
23
|
+
data-turbo-frame-lifecycle-target="frame"
|
|
24
|
+
data-turbo-temporary
|
|
25
|
+
>
|
|
26
|
+
<div class="mv-card">
|
|
27
|
+
<div class="mv-card-b"><%= t("smriti.loading_definitions") %></div>
|
|
28
|
+
</div>
|
|
29
|
+
</turbo-frame>
|
|
30
|
+
</div>
|
|
31
|
+
<div data-tabs-target="panel" data-name="runs" hidden>
|
|
32
|
+
<turbo-frame id="dash-runs" data-src="<%= admin_mat_view_runs_path(frame_id: 'dash-runs') %>" data-turbo-frame-lifecycle-target="frame" data-turbo-temporary>
|
|
33
|
+
<div class="mv-card">
|
|
34
|
+
<div class="mv-card-b"><%= t("smriti.loading_runs") %></div>
|
|
35
|
+
</div>
|
|
36
|
+
</turbo-frame>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<div class="mv-actions">
|
|
2
|
+
<!-- GROUP: Definition -->
|
|
3
|
+
<div class="mv-action-group">
|
|
4
|
+
<div class="mv-action-title"><%= mv_icon(:layers) %>
|
|
5
|
+
<%= t("smriti.definition") %></div>
|
|
6
|
+
<div class="mv-buttons">
|
|
7
|
+
<%= mv_button_link admin_root_path(tab: 'runs', dtfilter: "definition:#{defn.id}"),
|
|
8
|
+
variant: :ghost,
|
|
9
|
+
testid: 'VIEW_HISTORY_LINK',
|
|
10
|
+
testid_identifier: "defn-#{defn.id}",
|
|
11
|
+
data: { turbo: false, turbo_frame: "_top" } do %>
|
|
12
|
+
<%= mv_icon(:history) %>
|
|
13
|
+
<%= t("smriti.history") %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= mv_drawer_link(edit_admin_mat_view_definition_path(defn, frame_id: 'mv-drawer'),
|
|
16
|
+
t("smriti.edit_var", name: defn.name),
|
|
17
|
+
testid: 'EDIT_LINK',
|
|
18
|
+
testid_identifier: "defn-#{defn.id}",
|
|
19
|
+
tooltip: t("smriti.mat_view_definition.edit_tooltip")) do %>
|
|
20
|
+
<%= mv_icon(:edit) %>
|
|
21
|
+
<%= t("smriti.edit") %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<%= mv_button_to admin_mat_view_definition_path(defn, frame_id: frame_id),
|
|
24
|
+
method: :delete,
|
|
25
|
+
variant: :negative,
|
|
26
|
+
testid: 'DELETE_LINK',
|
|
27
|
+
testid_identifier: "defn-#{defn.id}",
|
|
28
|
+
tooltip: t("smriti.mat_view_definition.delete_tooltip"), tooltip_placement: "bottom",
|
|
29
|
+
confirm: t("smriti.mat_view_definition.delete_confirm", name: defn.name) do %>
|
|
30
|
+
<%= mv_icon(:trash) %>
|
|
31
|
+
<%= t("smriti.delete") %>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<!-- GROUP: Materialized View -->
|
|
36
|
+
<div class="mv-action-group">
|
|
37
|
+
<div class="mv-action-title">
|
|
38
|
+
<%= mv_icon(:database) %>
|
|
39
|
+
<%= t("smriti.mat_view_definition.materialized_view") %>
|
|
40
|
+
<% if mv_exists %>
|
|
41
|
+
<span class="mv-status-icon" data-controller="tooltip" data-tooltip-text-value="<%= t("smriti.mat_view_definition.materialized_view_exists") %>">
|
|
42
|
+
<%= mv_icon(:check_circle, class_name: "mv-status-ok") %>
|
|
43
|
+
</span>
|
|
44
|
+
<% else %>
|
|
45
|
+
<span class="mv-status-icon" data-controller="tooltip" data-tooltip-text-value="<%= t("smriti.mat_view_definition.materialized_view_not_exists") %>">
|
|
46
|
+
<%= mv_icon(:x_circle, class_name: "mv-status-missing") %>
|
|
47
|
+
</span>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="mv-buttons">
|
|
51
|
+
<% if mv_exists %>
|
|
52
|
+
<%= mv_button_to refresh_admin_mat_view_definition_path(defn, frame_id: frame_id),
|
|
53
|
+
variant: :primary,
|
|
54
|
+
method: :post,
|
|
55
|
+
testid: 'REFRESH_LINK',
|
|
56
|
+
testid_identifier: "defn-#{defn.id}",
|
|
57
|
+
tooltip: t("smriti.mat_view_definition.refresh_tooltip") do %>
|
|
58
|
+
<%= mv_icon(:refresh) %>
|
|
59
|
+
<%= t("smriti.refresh") %>
|
|
60
|
+
<% end %>
|
|
61
|
+
<%= mv_button_to delete_now_admin_mat_view_definition_path(defn, frame_id: frame_id),
|
|
62
|
+
method: :post,
|
|
63
|
+
variant: :negative,
|
|
64
|
+
testid: 'DROP_LINK',
|
|
65
|
+
testid_identifier: "defn-#{defn.id}",
|
|
66
|
+
tooltip: t("smriti.mat_view_definition.drop_mv_tooltip"), tooltip_placement: "bottom",
|
|
67
|
+
confirm: t("smriti.mat_view_definition.drop_mv_confirm", name: defn.name) do %>
|
|
68
|
+
<%= mv_icon(:x_circle) %>
|
|
69
|
+
<%= t("smriti.mat_view_definition.drop_mv") %>
|
|
70
|
+
<% end %>
|
|
71
|
+
<%= mv_button_to delete_now_admin_mat_view_definition_path(defn, frame_id: frame_id, cascade: true),
|
|
72
|
+
method: :post,
|
|
73
|
+
variant: :negative,
|
|
74
|
+
testid: 'DROP_CASCADE_LINK',
|
|
75
|
+
testid_identifier: "defn-#{defn.id}",
|
|
76
|
+
tooltip: t("smriti.mat_view_definition.drop_mv_cascade_tooltip"), tooltip_placement: "bottom",
|
|
77
|
+
confirm: t("smriti.mat_view_definition.drop_mv_cascade_confirm", name: defn.name) do %>
|
|
78
|
+
<%= mv_icon(:x_circle) %>
|
|
79
|
+
<%= t("smriti.mat_view_definition.drop_mv_cascade") %>
|
|
80
|
+
<% end %>
|
|
81
|
+
<% else %>
|
|
82
|
+
<%= mv_button_to create_now_admin_mat_view_definition_path(defn, frame_id: frame_id),
|
|
83
|
+
method: :post,
|
|
84
|
+
variant: :secondary,
|
|
85
|
+
testid: 'CREATE_MV_LINK',
|
|
86
|
+
testid_identifier: "defn-#{defn.id}",
|
|
87
|
+
tooltip: t("smriti.mat_view_definition.create_mv_tooltip"), tooltip_placement: "bottom" do %>
|
|
88
|
+
<%= mv_icon(:hammer) %>
|
|
89
|
+
<%= t("smriti.mat_view_definition.create_mv") %>
|
|
90
|
+
<% end %>
|
|
91
|
+
<% end %>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<tr class="mv-tr">
|
|
2
|
+
<td class="mv-td" colspan="<%= dt_config[:columns].size %>">
|
|
3
|
+
<div class="mv-cell">
|
|
4
|
+
<% if params[:dtfilter] || params[:dtsearch] %>
|
|
5
|
+
<%= t("smriti.mat_view_definition.no_definitions_if_filtered") %>
|
|
6
|
+
<% else %>
|
|
7
|
+
<%= t("smriti.mat_view_definition.no_definitions") %>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
</td>
|
|
11
|
+
</tr>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<% dt_humanize_ref = dt_config[:dt_humanize_ref]
|
|
2
|
+
dt_humanize_ref_klass = dt_humanize_ref.constantize
|
|
3
|
+
humanize_attr = ->(attr) { dt_humanize_ref_klass.human_attribute_name(attr) }
|
|
4
|
+
humanize_enum_attr = ->(col, val) { dt_humanize_ref_klass.human_enum_name(col, val) }
|
|
5
|
+
mv_exists_map ||= {} %>
|
|
6
|
+
<tr class="mv-tr">
|
|
7
|
+
<td class="mv-td">
|
|
8
|
+
<div class="mv-cell">
|
|
9
|
+
<%= mv_drawer_link(admin_mat_view_definition_path(row_value, frame_id: 'mv-drawer'), t("smriti.view_var", name: row_value.name),
|
|
10
|
+
variant: :ghost,
|
|
11
|
+
testid: 'VIEW_LINK',
|
|
12
|
+
testid_identifier: "defn-#{row_value.id}",
|
|
13
|
+
underline: true,
|
|
14
|
+
tooltip: t("smriti.mat_view_definition.view_tooltip")) do %>
|
|
15
|
+
<%= row_value.name %>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</td>
|
|
19
|
+
<td class="mv-td"><div class="mv-cell"><%= humanize_enum_attr.call(:refresh_strategy, row_value.refresh_strategy) %></div></td>
|
|
20
|
+
<td class="mv-td"><div class="mv-cell"><%= row_value.schedule_cron.presence || "-" %></div></td>
|
|
21
|
+
<td class="mv-td"><div class="mv-cell"><%= row_value.last_run ? l_with_digits(row_value.last_run.started_at.in_time_zone, format: :datetime12hour) : "-" %></div></td>
|
|
22
|
+
<td class="mv-td mv-td-actions">
|
|
23
|
+
<div class="mv-cell">
|
|
24
|
+
<%= render "definition_actions", defn: row_value, mv_exists: mv_exists_map[row_value.name], frame_id: "dash-definitions" %>
|
|
25
|
+
</div>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1><%= t("smriti.reloading") %></h1>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<% action = @definition.new_record? ? admin_mat_view_definitions_path(frame_id: "mv-drawer") : admin_mat_view_definition_path(@definition, frame_id: "mv-drawer") %>
|
|
2
|
+
<% method = @definition.new_record? ? :post : :patch %>
|
|
3
|
+
|
|
4
|
+
<input
|
|
5
|
+
type="hidden"
|
|
6
|
+
id="mv-drawer-title-text"
|
|
7
|
+
value="<%= @definition.new_record? ? t('smriti.mat_view_definition.new_definition') : t("smriti.edit_var", name: @definition.name) %>"
|
|
8
|
+
/>
|
|
9
|
+
|
|
10
|
+
<input type="hidden" id="mv-drawer-open-url-identifier" value="definitions_<%= @definition.new_record? ? 'new' : "edit_#{@definition.id}" %>"/>
|
|
11
|
+
|
|
12
|
+
<%= form_with model: @definition, url: action, method: method, class: "mv-form" do |f| %>
|
|
13
|
+
<% if @definition.errors.any? %>
|
|
14
|
+
<div class="mv-flash mv-flash--err">
|
|
15
|
+
<strong><%= t("smriti.errors.prevented_saving", count: @definition.errors.count) %></strong>
|
|
16
|
+
<ul style="margin:.25rem 1rem;">
|
|
17
|
+
<% @definition.errors.full_messages.each do |msg| %>
|
|
18
|
+
<li><%= msg %></li>
|
|
19
|
+
<% end %>
|
|
20
|
+
</ul>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
24
|
+
<div class="mv-field">
|
|
25
|
+
<%= f.label :name, class: "mv-label" %>
|
|
26
|
+
<%= f.text_field :name, class: "mv-input", required: true, placeholder: Smriti::MatViewDefinition.placeholder_for(:name) %>
|
|
27
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:name), class: "mv-hint" %>
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="mv-field">
|
|
32
|
+
<%= f.label :sql, class: "mv-label" %>
|
|
33
|
+
<%= f.text_area :sql, class: "mv-textarea", placeholder: Smriti::MatViewDefinition.placeholder_for(:sql), required: true %>
|
|
34
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:sql), class: "mv-hint" %>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="mv-field">
|
|
38
|
+
<%= f.label :refresh_strategy, class: "mv-label" %>
|
|
39
|
+
<%= f.select :refresh_strategy,
|
|
40
|
+
options_for_select(Smriti::MatViewDefinition.human_enum_options(:refresh_strategy), @definition.refresh_strategy),
|
|
41
|
+
{},
|
|
42
|
+
class: "mv-select" %>
|
|
43
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:refresh_strategy), class: "mv-hint" %>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mv-field">
|
|
47
|
+
<%= f.label :schedule_cron, class: "mv-label" %>
|
|
48
|
+
<%= f.text_field :schedule_cron, class: "mv-input", placeholder: Smriti::MatViewDefinition.placeholder_for(:schedule_cron) %>
|
|
49
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:schedule_cron), class: "mv-hint" %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="mv-field">
|
|
53
|
+
<%= f.label :unique_index_columns, class: "mv-label" %>
|
|
54
|
+
<% current_uic = Array(@definition.unique_index_columns).join(", ") %>
|
|
55
|
+
<%= f.text_field :unique_index_columns, value: current_uic, class: "mv-input", placeholder: Smriti::MatViewDefinition.placeholder_for(:unique_index_columns) %>
|
|
56
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:unique_index_columns), class: "mv-hint" %>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="mv-field">
|
|
60
|
+
<%= f.label :dependencies, class: "mv-label" %>
|
|
61
|
+
<% current_dep = Array(@definition.dependencies).join(", ") %>
|
|
62
|
+
<%= f.text_field :dependencies, value: current_dep, class: "mv-input", placeholder: Smriti::MatViewDefinition.placeholder_for(:dependencies) %>
|
|
63
|
+
<%= content_tag :small, Smriti::MatViewDefinition.hint_for(:dependencies), class: "mv-hint" %>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div style="display:flex;gap:.5rem;margin-top:.75rem;">
|
|
67
|
+
<%= mv_submit_button class: "mv-btn mv-btn--primary",
|
|
68
|
+
testid: 'SUBMIT_BUTTON',
|
|
69
|
+
testid_identifier: "defn-#{@definition.id || 'new'}" do %>
|
|
70
|
+
<%= @definition.new_record? ? t("smriti.create") : t("smriti.save_changes") %>
|
|
71
|
+
<% end %>
|
|
72
|
+
|
|
73
|
+
<%= mv_cancel_button variant: :ghost, data: { action: "click->drawer#close" },
|
|
74
|
+
testid: 'CANCEL_BUTTON',
|
|
75
|
+
testid_identifier: "defn-#{@definition.id || 'new'}" do %>
|
|
76
|
+
<%= t("smriti.cancel") %>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|