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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +168 -0
  4. data/Rakefile +15 -0
  5. data/app/assets/images/smriti/android-chrome-192x192.png +0 -0
  6. data/app/assets/images/smriti/android-chrome-512x512.png +0 -0
  7. data/app/assets/images/smriti/apple-touch-icon.png +0 -0
  8. data/app/assets/images/smriti/favicon-16x16.png +0 -0
  9. data/app/assets/images/smriti/favicon-32x32.png +0 -0
  10. data/app/assets/images/smriti/favicon-48x48.png +0 -0
  11. data/app/assets/images/smriti/favicon.ico +0 -0
  12. data/app/assets/images/smriti/favicon.svg +18 -0
  13. data/app/assets/images/smriti/logo.svg +18 -0
  14. data/app/assets/images/smriti/mask-icon.svg +5 -0
  15. data/app/assets/stylesheets/smriti/application.css +1040 -0
  16. data/app/controllers/smriti/admin/application_controller.rb +135 -0
  17. data/app/controllers/smriti/admin/dashboard_controller.rb +32 -0
  18. data/app/controllers/smriti/admin/mat_view_definitions_controller.rb +372 -0
  19. data/app/controllers/smriti/admin/mat_view_runs_controller.rb +185 -0
  20. data/app/controllers/smriti/admin/preferences_controller.rb +91 -0
  21. data/app/helpers/smriti/admin/datatable_helper.rb +249 -0
  22. data/app/helpers/smriti/admin/localized_digit_helper.rb +70 -0
  23. data/app/helpers/smriti/admin/ui_helper.rb +539 -0
  24. data/app/javascript/smriti/application.js +8 -0
  25. data/app/javascript/smriti/controllers/application.js +10 -0
  26. data/app/javascript/smriti/controllers/body_setup_controller.js +120 -0
  27. data/app/javascript/smriti/controllers/datatable_controller.js +351 -0
  28. data/app/javascript/smriti/controllers/details_controller.js +200 -0
  29. data/app/javascript/smriti/controllers/drawer_controller.js +470 -0
  30. data/app/javascript/smriti/controllers/flash_controller.js +112 -0
  31. data/app/javascript/smriti/controllers/index.js +10 -0
  32. data/app/javascript/smriti/controllers/mv_confirm_controller.js +435 -0
  33. data/app/javascript/smriti/controllers/tabs_controller.js +184 -0
  34. data/app/javascript/smriti/controllers/tooltip_controller.js +525 -0
  35. data/app/javascript/smriti/controllers/turbo_frame_lifecycle_controller.js +342 -0
  36. data/app/jobs/smriti/application_job.rb +144 -0
  37. data/app/jobs/smriti/create_view_job.rb +87 -0
  38. data/app/jobs/smriti/delete_view_job.rb +89 -0
  39. data/app/jobs/smriti/refresh_view_job.rb +94 -0
  40. data/app/models/concerns/smriti_i18n.rb +139 -0
  41. data/app/models/concerns/smriti_paginate.rb +70 -0
  42. data/app/models/concerns/smriti_query_helper.rb +36 -0
  43. data/app/models/smriti/application_record.rb +39 -0
  44. data/app/models/smriti/mat_view_definition.rb +254 -0
  45. data/app/models/smriti/mat_view_run.rb +275 -0
  46. data/app/views/layouts/smriti/_footer.html.erb +47 -0
  47. data/app/views/layouts/smriti/_header.html.erb +25 -0
  48. data/app/views/layouts/smriti/admin.html.erb +47 -0
  49. data/app/views/layouts/smriti/turbo_frame.html.erb +3 -0
  50. data/app/views/smriti/admin/dashboard/index.html.erb +38 -0
  51. data/app/views/smriti/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
  52. data/app/views/smriti/admin/mat_view_definitions/_dt-index-empty-row.html.erb +11 -0
  53. data/app/views/smriti/admin/mat_view_definitions/_dt-index-row.html.erb +27 -0
  54. data/app/views/smriti/admin/mat_view_definitions/empty.html.erb +1 -0
  55. data/app/views/smriti/admin/mat_view_definitions/form.html.erb +79 -0
  56. data/app/views/smriti/admin/mat_view_definitions/index.html.erb +10 -0
  57. data/app/views/smriti/admin/mat_view_definitions/show.html.erb +40 -0
  58. data/app/views/smriti/admin/mat_view_runs/_dt-index-empty-row.html.erb +11 -0
  59. data/app/views/smriti/admin/mat_view_runs/_dt-index-row.html.erb +41 -0
  60. data/app/views/smriti/admin/mat_view_runs/index.html.erb +1 -0
  61. data/app/views/smriti/admin/mat_view_runs/show.html.erb +64 -0
  62. data/app/views/smriti/admin/preferences/show.html.erb +49 -0
  63. data/app/views/smriti/admin/ui/_card.html.erb +15 -0
  64. data/app/views/smriti/admin/ui/_datatable.html.erb +34 -0
  65. data/app/views/smriti/admin/ui/_datatable_filters.html.erb +45 -0
  66. data/app/views/smriti/admin/ui/_datatable_tbody.html.erb +11 -0
  67. data/app/views/smriti/admin/ui/_datatable_tfoot.html.erb +70 -0
  68. data/app/views/smriti/admin/ui/_datatable_thead.html.erb +105 -0
  69. data/app/views/smriti/admin/ui/_details.html.erb +10 -0
  70. data/app/views/smriti/admin/ui/_flash.html.erb +6 -0
  71. data/app/views/smriti/admin/ui/_table.html.erb +8 -0
  72. data/config/importmap.rb +9 -0
  73. data/config/locales/ar.yml +223 -0
  74. data/config/locales/de.yml +230 -0
  75. data/config/locales/en-AU-ocker.yml +223 -0
  76. data/config/locales/en-AU.yml +202 -0
  77. data/config/locales/en-BORK.yml +225 -0
  78. data/config/locales/en-CA.yml +223 -0
  79. data/config/locales/en-GB.yml +223 -0
  80. data/config/locales/en-LOL.yml +219 -0
  81. data/config/locales/en-SCOT.yml +223 -0
  82. data/config/locales/en-SHAKESPEARE.yml +225 -0
  83. data/config/locales/en-US-pirate.yml +222 -0
  84. data/config/locales/en-US.yml +225 -0
  85. data/config/locales/en-YODA.yml +221 -0
  86. data/config/locales/en.yml +223 -0
  87. data/config/locales/es.yml +226 -0
  88. data/config/locales/fa.yml +223 -0
  89. data/config/locales/fr-CA.yml +227 -0
  90. data/config/locales/fr.yml +227 -0
  91. data/config/locales/he.yml +218 -0
  92. data/config/locales/hi.yml +223 -0
  93. data/config/locales/it.yml +225 -0
  94. data/config/locales/ja-JP.yml +215 -0
  95. data/config/locales/pt.yml +225 -0
  96. data/config/locales/ru.yml +228 -0
  97. data/config/locales/ur.yml +225 -0
  98. data/config/locales/zh-CN.yml +214 -0
  99. data/config/locales/zh-TW.yml +214 -0
  100. data/config/routes.rb +36 -0
  101. data/lib/ext/exception.rb +20 -0
  102. data/lib/generators/smriti/install/install_generator.rb +86 -0
  103. data/lib/generators/smriti/install/templates/create_mat_view_definitions.rb +29 -0
  104. data/lib/generators/smriti/install/templates/create_mat_view_runs.rb +32 -0
  105. data/lib/generators/smriti/install/templates/smriti_initializer.rb +23 -0
  106. data/lib/smriti/admin/auth_bridge.rb +93 -0
  107. data/lib/smriti/admin/default_auth.rb +62 -0
  108. data/lib/smriti/configuration.rb +58 -0
  109. data/lib/smriti/engine.rb +82 -0
  110. data/lib/smriti/helpers/ui_test_ids.rb +49 -0
  111. data/lib/smriti/jobs/adapter.rb +81 -0
  112. data/lib/smriti/service_response.rb +75 -0
  113. data/lib/smriti/services/base_service.rb +471 -0
  114. data/lib/smriti/services/check_matview_exists.rb +76 -0
  115. data/lib/smriti/services/concurrent_refresh.rb +94 -0
  116. data/lib/smriti/services/create_view.rb +173 -0
  117. data/lib/smriti/services/delete_view.rb +111 -0
  118. data/lib/smriti/services/regular_refresh.rb +90 -0
  119. data/lib/smriti/services/swap_refresh.rb +181 -0
  120. data/lib/smriti/version.rb +21 -0
  121. data/lib/smriti.rb +64 -0
  122. data/lib/tasks/helpers.rb +185 -0
  123. data/lib/tasks/smriti_tasks.rake +151 -0
  124. metadata +206 -0
@@ -0,0 +1,135 @@
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
+ module Smriti
9
+ module Admin
10
+ # Smriti::Admin::ApplicationController
11
+ # --------------------------------------
12
+ # Base controller for the Smriti admin interface.
13
+ #
14
+ # Responsibilities:
15
+ # - Provides authentication and authorization via Smriti::Admin::AuthBridge.
16
+ # - Applies the `smriti/admin` layout and includes UI helpers.
17
+ # - Manages locale (`I18n.locale`) and enforces language parameter consistency.
18
+ # - Sets the browser time zone around each request when provided via cookies.
19
+ # - Exposes `smriti_data_theme` (light/dark) for theming via cookies.
20
+ # - Provides frame helpers (`render_frame`, `ensure_frame`) to support Turbo-driven
21
+ # admin UI navigation.
22
+ #
23
+ # Filters:
24
+ # - `before_action`: sets locale and redirects to enforce `lang` consistency.
25
+ # - `around_action`: wraps requests in the browser’s time zone if valid.
26
+ #
27
+ # Methods:
28
+ # - {#default_url_options} ensures `lang` param is included in generated URLs.
29
+ # - {#set_time_zone} runs the request in the cookie-provided time zone if valid.
30
+ # - {#render_frame} renders a UI frame partial given `frame_id`.
31
+ # - {#ensure_frame} requires a `frame_id` param for frame-only actions.
32
+ # - {#redirect_to_lang} redirects when the URL `lang` param differs from `I18n.locale`.
33
+ # - {#set_smriti_locale} sets the session-defined or default locale.
34
+ # - {#smriti_data_theme} returns `light`, `dark`, or `nil` for theming.
35
+ #
36
+ class ApplicationController < ActionController::Base
37
+ include Smriti::Admin::AuthBridge
38
+
39
+ helper Smriti::Admin::UiHelper
40
+ helper Smriti::Admin::LocalizedDigitHelper
41
+ helper Smriti::Admin::DatatableHelper
42
+ helper Smriti::Helpers::UiTestIds
43
+ layout 'smriti/admin'
44
+
45
+ before_action :set_smriti_locale, :redirect_to_lang
46
+ helper_method :smriti_data_theme
47
+ around_action :set_time_zone
48
+
49
+ private
50
+
51
+ # Default URL options, ensuring `lang` is always included.
52
+ #
53
+ # @api private
54
+ #
55
+ # @return [Hash{Symbol => String}]
56
+ def default_url_options
57
+ { lang: params[:lang].presence || I18n.locale }
58
+ end
59
+
60
+ # Wraps the request in the browser’s time zone if one is set in cookies.
61
+ #
62
+ # @api private
63
+ #
64
+ # @yield the block representing the request lifecycle
65
+ # @return [void]
66
+ def set_time_zone(&)
67
+ browser_tz = cookies[:browser_tz]
68
+ if browser_tz.present? && ActiveSupport::TimeZone[browser_tz]
69
+ Time.use_zone(browser_tz, &)
70
+ else
71
+ yield
72
+ end
73
+ end
74
+
75
+ # Ensures a `frame_id` param is present.
76
+ # If missing, redirects to the admin root with an alert.
77
+ #
78
+ # @api private
79
+ #
80
+ # @return [void]
81
+ def ensure_frame
82
+ @frame_id = params[:frame_id]
83
+ return if @frame_id.present?
84
+
85
+ redirect_to admin_root_path, alert: I18n.t('smriti.errors.frame_only')
86
+ end
87
+
88
+ # Redirects to enforce that the `lang` param matches `I18n.locale`.
89
+ #
90
+ # @api private
91
+ #
92
+ # @return [void]
93
+ def redirect_to_lang
94
+ locale_str = locale.to_s
95
+ return if params[:lang] == locale_str
96
+
97
+ lang = locale_str
98
+ redirect_to url_for(params.permit!.to_h.merge(lang: lang)), status: :see_other
99
+ end
100
+
101
+ # Sets the locale for Smriti admin requests.
102
+ # Falls back to default locale if session value is invalid.
103
+ #
104
+ # @api private
105
+ #
106
+ # @return [void]
107
+ def set_smriti_locale
108
+ I18n.locale = if (loc = session[:smriti_locale]).present? && Smriti::Engine.available_locales.map(&:to_s).include?(loc)
109
+ loc
110
+ else
111
+ Smriti::Engine.default_locale
112
+ end
113
+ end
114
+
115
+ # Returns the current theme for the admin UI.
116
+ #
117
+ # @api private
118
+ #
119
+ # @return ["light", "dark", nil] the theme stored in cookies, or `nil` if invalid
120
+ def smriti_data_theme
121
+ theme = cookies[:theme].to_s
122
+ %w[light dark].include?(theme) ? theme : nil
123
+ end
124
+
125
+ # Returns the current locale.
126
+ #
127
+ # @api private
128
+ #
129
+ # @return [Symbol] the current I18n locale
130
+ def locale
131
+ I18n.locale
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,32 @@
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
+ module Smriti
9
+ module Admin
10
+ # Smriti::Admin::DashboardController
11
+ # ------------------------------------
12
+ # Controller for the Smriti admin dashboard.
13
+ #
14
+ # Responsibilities:
15
+ # - Provides the landing page (`index`) for the admin interface.
16
+ # - Authorizes access via {ApplicationController#authorize_smriti!}.
17
+ # - Prepares placeholder metrics content (future: aggregated refresh metrics).
18
+ #
19
+ class DashboardController < ApplicationController
20
+ # GET /:lang/admin
21
+ #
22
+ # Renders the admin dashboard. Currently sets a placeholder message
23
+ # until metric aggregation is implemented.
24
+ #
25
+ # @return [void]
26
+ def index
27
+ authorize_smriti!(:view, :smriti_dashboard)
28
+ @metrics_note = 'Metrics coming soon (see: Aggregate refresh metrics for reporting).'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,372 @@
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
+ module Smriti
9
+ module Admin
10
+ # Smriti::Admin::MatViewDefinitionsController
11
+ # ---------------------------------------------
12
+ # Admin CRUD controller for {Smriti::MatViewDefinition} records.
13
+ #
14
+ # Responsibilities:
15
+ # - Full CRUD lifecycle: index, show, new, create, edit, update, destroy.
16
+ # - Admin-only actions to trigger materialised view operations:
17
+ # - {#create_now} → enqueues {Smriti::CreateViewJob}
18
+ # - {#refresh} → enqueues {Smriti::RefreshViewJob}
19
+ # - {#delete_now} → enqueues {Smriti::DeleteViewJob}
20
+ # - Integrates with Turbo Frames (uses frame-aware redirects/responses).
21
+ # - Normalizes array fields (`unique_index_columns`, `dependencies`) from
22
+ # comma-separated params into arrays.
23
+ #
24
+ # Filters:
25
+ # - `before_action :set_definition` for member actions.
26
+ # - `before_action :normalize_array_fields` for create/update.
27
+ # - `before_action :ensure_frame` to enforce frame context.
28
+ #
29
+ class MatViewDefinitionsController < ApplicationController
30
+ include Smriti::Admin::DatatableHelper
31
+
32
+ before_action :set_definition, only: %i[show edit update destroy create_now refresh delete_now]
33
+ before_action :normalize_array_fields, only: %i[create update]
34
+ before_action :parse_headers_to_params, :ensure_frame
35
+
36
+ # GET /:lang/admin/definitions
37
+ #
38
+ # Two part rendering:
39
+ # - Full page load when no `stream` param: renders index with datatable frame. This is
40
+ # essentially shell of the datatable for initial load.
41
+ # - When shell is loaded, it requests the `stream` version which renders just the datatable rows
42
+ # and pagination controls. This allows for dynamic updates via Turbo Streams.
43
+ #
44
+ # @return [void]
45
+ def index
46
+ authorize_smriti!(:read, :smriti_definitions)
47
+
48
+ assign_index_state
49
+
50
+ if params[:stream].present?
51
+ render_dt_turbo_streams
52
+ return
53
+ end
54
+ render 'index', formats: :html, layout: 'smriti/turbo_frame', locals: { row_meta: @row_meta }
55
+ end
56
+
57
+ # GET /:lang/admin/definitions/:id
58
+ #
59
+ # Shows a single definition, including run history.
60
+ #
61
+ # @return [void]
62
+ def show
63
+ authorize_smriti!(:read, :smriti_definition, @definition)
64
+ @mv_exists = Smriti::Services::CheckMatviewExists.new(@definition).call.response[:exists]
65
+ @runs = @definition.mat_view_runs.order(created_at: :desc).to_a
66
+ render 'show', formats: :html, layout: 'smriti/turbo_frame'
67
+ end
68
+
69
+ # GET /:lang/admin/definitions/new
70
+ #
71
+ # Renders the new definition form.
72
+ #
73
+ # @return [void]
74
+ def new
75
+ authorize_smriti!(:create, :smriti_definition)
76
+ @definition = Smriti::MatViewDefinition.new
77
+ render 'form', formats: :html, layout: 'smriti/turbo_frame'
78
+ end
79
+
80
+ # POST /:lang/admin/definitions
81
+ #
82
+ # Creates a new definition from params.
83
+ #
84
+ # @return [void]
85
+ def create
86
+ authorize_smriti!(:create, :smriti_definition)
87
+ @definition = Smriti::MatViewDefinition.new(definition_params)
88
+ if @definition.save
89
+ handle_frame_response(status: 298)
90
+ else
91
+ render 'form', formats: :html, layout: 'smriti/turbo_frame', status: :unprocessable_content
92
+ end
93
+ end
94
+
95
+ # GET /:lang/admin/definitions/:id/edit
96
+ #
97
+ # Renders the edit form for an existing definition.
98
+ #
99
+ # @return [void]
100
+ def edit
101
+ authorize_smriti!(:update, :smriti_definition, @definition)
102
+ render 'form', formats: :html, layout: 'smriti/turbo_frame'
103
+ end
104
+
105
+ # PATCH/PUT /:lang/admin/definitions/:id
106
+ #
107
+ # Updates an existing definition.
108
+ #
109
+ # @return [void]
110
+ def update
111
+ authorize_smriti!(:update, :smriti_definition, @definition)
112
+ if @definition.update(definition_params)
113
+ handle_frame_response(status: 298)
114
+ else
115
+ render 'form', formats: :html, layout: 'smriti/turbo_frame', status: :unprocessable_content
116
+ end
117
+ end
118
+
119
+ # DELETE /:lang/admin/definitions/:id
120
+ #
121
+ # Destroys the definition. Frame-specific redirect/empty response.
122
+ #
123
+ # @return [void]
124
+ def destroy
125
+ authorize_smriti!(:destroy, :smriti_definition, @definition)
126
+ @definition.destroy!
127
+ if @frame_id == 'dash-definitions'
128
+ redirect_to admin_mat_view_definitions_path(frame_id: @frame_id), status: :see_other
129
+ else
130
+ render 'empty', formats: :html, layout: 'smriti/turbo_frame', status: 298
131
+ end
132
+ end
133
+
134
+ # POST /:lang/admin/definitions/:id/create_now
135
+ #
136
+ # Immediately enqueues a background job to create the materialised view.
137
+ #
138
+ # @return [void]
139
+ def create_now
140
+ authorize_smriti!(:create, :smriti_definition_view, @definition)
141
+
142
+ force = params[:force].to_s.downcase == 'true'
143
+ Smriti::Jobs::Adapter.enqueue(
144
+ Smriti::CreateViewJob,
145
+ queue: Smriti.configuration.job_queue,
146
+ args: [@definition.id, force, row_count_strategy]
147
+ )
148
+ handle_frame_response
149
+ end
150
+
151
+ # POST /:lang/admin/definitions/:id/refresh
152
+ #
153
+ # Immediately enqueues a background job to refresh the materialised view.
154
+ #
155
+ # @return [void]
156
+ def refresh
157
+ authorize_smriti!(:update, :smriti_definition_view, @definition)
158
+ Smriti::Jobs::Adapter.enqueue(
159
+ Smriti::RefreshViewJob,
160
+ queue: Smriti.configuration.job_queue,
161
+ args: [@definition.id, row_count_strategy]
162
+ )
163
+ handle_frame_response
164
+ end
165
+
166
+ # POST /:lang/admin/definitions/:id/delete_now
167
+ #
168
+ # Immediately enqueues a background job to delete the materialised view.
169
+ #
170
+ # @return [void]
171
+ def delete_now
172
+ authorize_smriti!(:destroy, :smriti_definition_view, @definition)
173
+
174
+ cascade = params[:cascade].to_s.downcase == 'true'
175
+ Smriti::Jobs::Adapter.enqueue(
176
+ Smriti::DeleteViewJob,
177
+ queue: Smriti.configuration.job_queue,
178
+ args: [@definition.id, cascade, row_count_strategy]
179
+ )
180
+ handle_frame_response
181
+ end
182
+
183
+ private
184
+
185
+ # Returns the configured row count strategy for admin UI operations.
186
+ #
187
+ # @api private
188
+ #
189
+ # @return [Symbol, nil] row count strategy (e.g., :estimated, :exact, :none)
190
+ def row_count_strategy
191
+ Smriti.configuration.admin_ui[:row_count_strategy] || :none
192
+ end
193
+
194
+ # Handles redirect/response after a frame-based action.
195
+ #
196
+ # @api private
197
+ #
198
+ # @param status [Symbol,Integer] the HTTP status for redirect
199
+ # @return [void]
200
+ def handle_frame_response(status: :see_other)
201
+ if @frame_id == 'dash-definitions'
202
+ dtsort = params[:dtsort]
203
+ dtfilter = params[:dtfilter]
204
+ dtsearch = params[:dtsearch]
205
+ redirect_to admin_mat_view_definitions_path(frame_id: @frame_id, stream: true, dtsort:, dtfilter:, dtsearch:), status: status
206
+ else
207
+ redirect_to admin_mat_view_definition_path(@definition, frame_id: @frame_id), status: status
208
+ end
209
+ end
210
+
211
+ # Loads a definition by `params[:id]`.
212
+ #
213
+ # @api private
214
+ #
215
+ # @return [void]
216
+ def set_definition
217
+ @definition = Smriti::MatViewDefinition.find(params[:id])
218
+ end
219
+
220
+ # Normalizes array fields (unique_index_columns, dependencies) from
221
+ # comma-separated strings into arrays.
222
+ #
223
+ # @api private
224
+ #
225
+ # @return [void]
226
+ def normalize_array_fields
227
+ normalize_array_field(:mat_view_definition, :unique_index_columns)
228
+ normalize_array_field(:mat_view_definition, :dependencies)
229
+ end
230
+
231
+ # Normalizes a specific array field from a comma-separated string into an array.
232
+ #
233
+ # @api private
234
+ #
235
+ # @param object_key [Symbol] the params object key (e.g., :mat_view_definition)
236
+ # @param array_key [Symbol] the specific array field key (e.g., :unique_index_columns)
237
+ #
238
+ # @return [void]
239
+ def normalize_array_field(object_key, array_key)
240
+ object = params[object_key]
241
+
242
+ values = object[array_key]
243
+ return if values.nil?
244
+
245
+ object[array_key] = values.split(',').map(&:strip).reject(&:blank?)
246
+ end
247
+
248
+ # Strong params for mat view definitions.
249
+ #
250
+ # @api private
251
+ #
252
+ # @return [ActionController::Parameters]
253
+ def definition_params
254
+ params.require(:mat_view_definition).permit(
255
+ :name, :sql, :refresh_strategy, :schedule_cron,
256
+ unique_index_columns: [], dependencies: []
257
+ )
258
+ end
259
+
260
+ # Loads data for the index datatable with filtering, searching, sorting, and pagination.
261
+ # sets @data.
262
+ #
263
+ # @api private
264
+ #
265
+ # @return [void]
266
+ def index_dt_load_data
267
+ rel = Smriti::MatViewDefinition
268
+ rel = dt_apply_filter(rel, index_dt_columns)
269
+ rel = dt_apply_search(rel, index_dt_columns)
270
+ rel = dt_apply_sort(rel, index_dt_columns)
271
+ @data = dt_apply_pagination(rel, @dt_config[:pagination][:per_page_default])
272
+ end
273
+
274
+ # Configuration for the index datatable.
275
+ #
276
+ # @api private
277
+ #
278
+ # @return [Hash] datatable configuration
279
+ def index_dt_config
280
+ columns = index_dt_columns
281
+ {
282
+ id: 'mv-definitions-table',
283
+ index_url: admin_mat_view_definitions_path(frame_id: @frame_id),
284
+ frame_id: 'mv-definitions-datatable',
285
+ columns: columns,
286
+ dt_humanize_ref: 'Smriti::MatViewDefinition',
287
+ empty_row_partial_name: 'dt-index-empty-row',
288
+ row_partial_name: 'dt-index-row',
289
+ search_enabled: columns.any? { |_, col| col[:search].present? },
290
+ filter_enabled: columns.any? { |_, col| col[:filter].present? },
291
+ pagination: { per_page_default: 10, per_page_options: [10, 25, 50, 100] }
292
+ }
293
+ end
294
+
295
+ # Column definitions for the index datatable.
296
+ #
297
+ # @api private
298
+ #
299
+ # @return [Hash] column definitions
300
+ def index_dt_columns
301
+ {
302
+ name: {
303
+ label_ref: 'name',
304
+ label_type: 'humanize_attr',
305
+ sort: 'name',
306
+ filter: 'name',
307
+ search: 'name'
308
+ },
309
+ refresh_strategy: {
310
+ label_ref: 'refresh_strategy',
311
+ label_type: 'humanize_attr',
312
+ sort: 'refresh_strategy',
313
+ filter: 'refresh_strategy',
314
+ search: 'refresh_strategy'
315
+ },
316
+ schedule_cron: {
317
+ label_ref: 'schedule_cron',
318
+ label_type: 'humanize_attr',
319
+ sort: 'schedule_cron',
320
+ filter: 'schedule_cron',
321
+ search: 'schedule_cron'
322
+ },
323
+ last_run_at: {
324
+ label_ref: 'last_run_at',
325
+ label_type: 'humanize_attr',
326
+ sort: 'last_run_at',
327
+ filter: nil,
328
+ search: 'last_run_at'
329
+ },
330
+ actions: {
331
+ label_ref: 'actions',
332
+ label_type: 'i18n',
333
+ th_style: 'justify-content: end;',
334
+ filter: nil,
335
+ sort: nil,
336
+ search: nil
337
+ }
338
+ }
339
+ end
340
+
341
+ # Builds a map of definition names to their existence status in the database.
342
+ # If definitions is nil or empty, returns an empty hash.
343
+ #
344
+ # @api private
345
+ #
346
+ # @param definitions [Array<Smriti::MatViewDefinition>] the definitions to check
347
+ # @return [Hash{String => Boolean}] map of definition names to existence status
348
+ # e.g., { "my_view" => true, "other_view" => false }
349
+ def build_matview_exists_map(definitions)
350
+ return {} if definitions.blank?
351
+
352
+ definitions.to_h do |definition|
353
+ exists = Smriti::Services::CheckMatviewExists.new(definition).call.response[:exists]
354
+ [definition.name, exists]
355
+ end
356
+ end
357
+
358
+ # Assigns instance variables for the index action.
359
+ #
360
+ # @api private
361
+ #
362
+ # @return [void]
363
+ def assign_index_state
364
+ @dt_config = index_dt_config
365
+ @data = []
366
+
367
+ index_dt_load_data
368
+ @row_meta = { mv_exists_map: build_matview_exists_map(@data) }
369
+ end
370
+ end
371
+ end
372
+ end