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,185 @@
|
|
|
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::MatViewRunsController
|
|
11
|
+
# -------------------------------
|
|
12
|
+
# Controller for viewing materialised view run history in the admin UI.
|
|
13
|
+
#
|
|
14
|
+
# Responsibilities:
|
|
15
|
+
# - Provides a list of recent runs (create/refresh/delete) for all definitions.
|
|
16
|
+
# - Shows details for an individual run in a Turbo frame/drawer context.
|
|
17
|
+
#
|
|
18
|
+
# Filters:
|
|
19
|
+
# - `before_action :ensure_frame` → enforces frame-only access.
|
|
20
|
+
# - `before_action :set_mat_view_run` → loads and authorizes a single run.
|
|
21
|
+
#
|
|
22
|
+
# Views:
|
|
23
|
+
# - Index renders `smriti/admin/runs/embed_frame` partial.
|
|
24
|
+
# - Show renders `smriti/admin/runs/embed_show_drawer` partial.
|
|
25
|
+
#
|
|
26
|
+
class MatViewRunsController < ApplicationController
|
|
27
|
+
include Smriti::Admin::DatatableHelper
|
|
28
|
+
|
|
29
|
+
before_action :ensure_frame, :parse_headers_to_params
|
|
30
|
+
helper_method :definition
|
|
31
|
+
before_action :ensure_frame, only: %i[index show]
|
|
32
|
+
before_action :set_mat_view_run, only: %i[show]
|
|
33
|
+
|
|
34
|
+
# GET /:lang/admin/runs
|
|
35
|
+
#
|
|
36
|
+
# Two part rendering:
|
|
37
|
+
# - Full page load when no `stream` param: renders index with datatable frame. This is
|
|
38
|
+
# essentially shell of the datatable for initial load.
|
|
39
|
+
# - When shell is loaded, it requests the `stream` version which renders just the datatable rows
|
|
40
|
+
# and pagination controls. This allows for dynamic updates via Turbo Streams.
|
|
41
|
+
#
|
|
42
|
+
# @return [void]
|
|
43
|
+
def index
|
|
44
|
+
authorize_smriti!(:read, :smriti_runs)
|
|
45
|
+
|
|
46
|
+
assign_index_state
|
|
47
|
+
|
|
48
|
+
if params[:stream].present?
|
|
49
|
+
render_dt_turbo_streams
|
|
50
|
+
else
|
|
51
|
+
render 'index', formats: :html, layout: 'smriti/turbo_frame', locals: { row_meta: @row_meta }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# GET /:lang/admin/runs/:id
|
|
56
|
+
#
|
|
57
|
+
# Displays details for a single run.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def show
|
|
61
|
+
authorize_smriti!(:read, :smriti_run, @run)
|
|
62
|
+
|
|
63
|
+
render 'show', formats: :html, layout: 'smriti/turbo_frame'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Loads the requested run and checks authorization.
|
|
69
|
+
#
|
|
70
|
+
# @api private
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def set_mat_view_run
|
|
74
|
+
@run = Smriti::MatViewRun.find(params[:id])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Loads data for the index datatable with filtering, searching, sorting, and pagination.
|
|
78
|
+
# sets @data.
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
#
|
|
82
|
+
# @return [void]
|
|
83
|
+
def index_dt_load_data
|
|
84
|
+
rel = Smriti::MatViewRun
|
|
85
|
+
rel = dt_apply_filter(rel, index_dt_columns)
|
|
86
|
+
rel = dt_apply_search(rel, index_dt_columns)
|
|
87
|
+
rel = dt_apply_sort(rel, index_dt_columns)
|
|
88
|
+
@data = dt_apply_pagination(rel, @dt_config[:pagination][:per_page_default])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Configuration for the index datatable.
|
|
92
|
+
#
|
|
93
|
+
# @api private
|
|
94
|
+
#
|
|
95
|
+
# @return [Hash] datatable configuration
|
|
96
|
+
def index_dt_config
|
|
97
|
+
columns = index_dt_columns
|
|
98
|
+
{
|
|
99
|
+
id: 'mv-runs-table',
|
|
100
|
+
index_url: admin_mat_view_runs_path(frame_id: @frame_id),
|
|
101
|
+
frame_id: 'mv-runs-datatable',
|
|
102
|
+
columns: columns,
|
|
103
|
+
dt_humanize_ref: 'Smriti::MatViewRun',
|
|
104
|
+
empty_row_partial_name: 'dt-index-empty-row',
|
|
105
|
+
row_partial_name: 'dt-index-row',
|
|
106
|
+
search_enabled: columns.any? { |_, col| col[:search].present? },
|
|
107
|
+
filter_enabled: columns.any? { |_, col| col[:filter].present? },
|
|
108
|
+
pagination: { per_page_default: 10, per_page_options: [10, 25, 50, 100] }
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Column definitions for the index datatable.
|
|
113
|
+
#
|
|
114
|
+
# @api private
|
|
115
|
+
#
|
|
116
|
+
# @return [Hash] column definitions
|
|
117
|
+
def index_dt_columns
|
|
118
|
+
{
|
|
119
|
+
operation: {
|
|
120
|
+
label_ref: 'operation',
|
|
121
|
+
label_type: 'humanize_attr',
|
|
122
|
+
sort: 'operation',
|
|
123
|
+
filter: 'operation',
|
|
124
|
+
search: 'operation'
|
|
125
|
+
},
|
|
126
|
+
definition: {
|
|
127
|
+
label_ref: 'definition',
|
|
128
|
+
label_type: 'i18n',
|
|
129
|
+
sort: 'definition',
|
|
130
|
+
filter: 'definition',
|
|
131
|
+
search: 'definition'
|
|
132
|
+
},
|
|
133
|
+
started_at: {
|
|
134
|
+
label_ref: 'started_at',
|
|
135
|
+
label_type: 'humanize_attr',
|
|
136
|
+
sort: 'started_at',
|
|
137
|
+
filter: nil,
|
|
138
|
+
search: nil
|
|
139
|
+
},
|
|
140
|
+
status: {
|
|
141
|
+
label_ref: 'status',
|
|
142
|
+
label_type: 'humanize_attr',
|
|
143
|
+
sort: 'status',
|
|
144
|
+
filter: 'status',
|
|
145
|
+
search: 'status'
|
|
146
|
+
},
|
|
147
|
+
duration: {
|
|
148
|
+
label_ref: 'duration_ms',
|
|
149
|
+
label_type: 'humanize_attr',
|
|
150
|
+
filter: nil,
|
|
151
|
+
sort: 'duration_ms',
|
|
152
|
+
search: 'duration_ms'
|
|
153
|
+
},
|
|
154
|
+
rows_before_after: {
|
|
155
|
+
label_ref: 'rows_before_after',
|
|
156
|
+
label_type: 'humanize_attr',
|
|
157
|
+
filter: nil,
|
|
158
|
+
sort: nil,
|
|
159
|
+
search: nil
|
|
160
|
+
},
|
|
161
|
+
details: {
|
|
162
|
+
label_ref: 'details',
|
|
163
|
+
label_type: 'humanize_attr',
|
|
164
|
+
filter: nil,
|
|
165
|
+
sort: nil,
|
|
166
|
+
search: nil
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Assigns instance variables for the index action.
|
|
172
|
+
#
|
|
173
|
+
# @api private
|
|
174
|
+
#
|
|
175
|
+
# @return [void]
|
|
176
|
+
def assign_index_state
|
|
177
|
+
@dt_config = index_dt_config
|
|
178
|
+
@data = []
|
|
179
|
+
|
|
180
|
+
index_dt_load_data
|
|
181
|
+
@row_meta = {}
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
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::PreferencesController
|
|
11
|
+
# --------------------------------------
|
|
12
|
+
# Controller for managing user preferences in the Smriti admin UI.
|
|
13
|
+
#
|
|
14
|
+
# Responsibilities:
|
|
15
|
+
# - Allows users to view and update UI preferences such as theme and locale.
|
|
16
|
+
# - Stores theme in cookies and locale in session.
|
|
17
|
+
# - Provides a force-reload response option to update Turbo frames dynamically.
|
|
18
|
+
#
|
|
19
|
+
# Filters:
|
|
20
|
+
# - `before_action :authorize!` → ensures user can access preferences.
|
|
21
|
+
# - `before_action :ensure_frame` → requires Turbo frame context for `show`.
|
|
22
|
+
#
|
|
23
|
+
class PreferencesController < ApplicationController
|
|
24
|
+
before_action :authorize!
|
|
25
|
+
before_action :ensure_frame
|
|
26
|
+
|
|
27
|
+
# GET /:lang/admin/preferences
|
|
28
|
+
#
|
|
29
|
+
# Displays the current preferences (theme + locale) and available locales.
|
|
30
|
+
# If `force_reload=1` is passed, sets a non-standard status code (299) and
|
|
31
|
+
# a custom header to signal the client to reload.
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
34
|
+
def show
|
|
35
|
+
@theme = read_theme
|
|
36
|
+
@locale = I18n.locale.to_s
|
|
37
|
+
@locales = Smriti::Engine.locale_code_mapping.sort_by { |_key, name| name }.map { |code, _name| code.to_s }.uniq
|
|
38
|
+
|
|
39
|
+
# force reload frame if requested
|
|
40
|
+
if params[:force_reload].to_s == '1'
|
|
41
|
+
response.status = 299
|
|
42
|
+
response.set_header('X-Status-Name', 'Success force reload')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
render 'show', formats: :html, layout: 'smriti/turbo_frame'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# PATCH/PUT /:lang/admin/preferences
|
|
49
|
+
#
|
|
50
|
+
# Updates preferences:
|
|
51
|
+
# - Theme (`light`, `dark`, or deleted if invalid) stored in cookies.
|
|
52
|
+
# - Locale stored in session if valid.
|
|
53
|
+
# Redirects back to preferences with `force_reload=1` to trigger a refresh.
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
def update
|
|
57
|
+
theme_param = params[:theme].to_s
|
|
58
|
+
case theme_param
|
|
59
|
+
when 'light', 'dark' then cookies[:theme] = { value: theme_param, expires: 1.year.from_now, httponly: false }
|
|
60
|
+
else cookies.delete(:theme)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
locale = params[:locale].to_s.presence || Smriti::Engine.default_locale.to_s
|
|
64
|
+
session[:smriti_locale] = locale if Smriti::Engine.available_locales.map(&:to_s).include?(locale)
|
|
65
|
+
|
|
66
|
+
redirect_to "#{admin_preferences_path}?force_reload=1&frame_id=#{@frame_id}", status: :see_other
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Authorizes access to preferences.
|
|
72
|
+
#
|
|
73
|
+
# @api private
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
def authorize!
|
|
77
|
+
authorize_smriti!(:view, :smriti_dashboard)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Reads the theme from cookies.
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
#
|
|
84
|
+
# @return ["light", "dark", "auto"] theme preference or "auto" if unset/invalid
|
|
85
|
+
def read_theme
|
|
86
|
+
t = cookies[:theme].to_s
|
|
87
|
+
%w[light dark].include?(t) ? t : 'auto'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,249 @@
|
|
|
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::DatatableHelper
|
|
11
|
+
# ---------------------------
|
|
12
|
+
# Helper module providing methods to manage datatable functionalities
|
|
13
|
+
# such as sorting, searching, filtering, and pagination.
|
|
14
|
+
#
|
|
15
|
+
# Responsibilities:
|
|
16
|
+
# - Apply sorting based on request parameters.
|
|
17
|
+
# - Apply searching across multiple columns.
|
|
18
|
+
# - Apply filtering based on specified criteria.
|
|
19
|
+
# - Handle pagination with customizable page size.
|
|
20
|
+
# - Generate pagination window for UI display.
|
|
21
|
+
# - Parse custom headers to parameters for datatable requests.
|
|
22
|
+
# - Render Turbo Stream responses for dynamic datatable updates.
|
|
23
|
+
#
|
|
24
|
+
# Methods:
|
|
25
|
+
# - dt_apply_sort: applies sorting to a relation based on parameters.
|
|
26
|
+
# - dt_apply_search: applies search filtering to a relation.
|
|
27
|
+
# - dt_apply_filter: applies column-based filtering to a relation.
|
|
28
|
+
# - dt_apply_pagination: paginates a relation.
|
|
29
|
+
# - pagination_window: generates page numbers for pagination UI.
|
|
30
|
+
# - parse_headers_to_params: parses custom headers into params.
|
|
31
|
+
# - render_dt_turbo_streams: renders Turbo Stream responses for datatables.
|
|
32
|
+
# - param_dtfilter: accessor for dtfilter param.
|
|
33
|
+
# - param_dtsearch: accessor for dtsearch param.
|
|
34
|
+
# - param_dtsort: accessor for dtsort param.
|
|
35
|
+
module DatatableHelper
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Applies sorting to the given ActiveRecord relation based on the provided columns and request parameters.
|
|
39
|
+
#
|
|
40
|
+
# @api private
|
|
41
|
+
#
|
|
42
|
+
# @param rel [ActiveRecord::Relation] the relation to sort
|
|
43
|
+
# @param columns [Hash] a hash defining the columns and their sort attributes
|
|
44
|
+
#
|
|
45
|
+
# @return [ActiveRecord::Relation] the sorted relation
|
|
46
|
+
def dt_apply_sort(rel, columns)
|
|
47
|
+
return rel unless param_dtsort.present?
|
|
48
|
+
|
|
49
|
+
param_dtsort.split(',').each do |clause|
|
|
50
|
+
col, dir = clause.split(':')
|
|
51
|
+
dir = dir&.downcase == 'desc' ? :desc : :asc
|
|
52
|
+
col_def = columns[col.to_sym]
|
|
53
|
+
col_def_sort = col_def[:sort] if col_def
|
|
54
|
+
rel = rel.send("ordered_by_#{col_def_sort}", dir) if col_def_sort
|
|
55
|
+
end
|
|
56
|
+
rel
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Applies search filtering to the given ActiveRecord relation based on the provided columns and request parameters.
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
#
|
|
63
|
+
# @param rel [ActiveRecord::Relation] the relation to search
|
|
64
|
+
# @param columns [Hash] a hash defining the columns and their search attributes
|
|
65
|
+
#
|
|
66
|
+
# @return [ActiveRecord::Relation] the filtered relation
|
|
67
|
+
def dt_apply_search(rel, columns)
|
|
68
|
+
return rel unless param_dtsearch.present?
|
|
69
|
+
|
|
70
|
+
scopes = columns.values.filter_map do |col_def|
|
|
71
|
+
col_def_search = col_def[:search]
|
|
72
|
+
rel.send("search_by_#{col_def_search}", param_dtsearch) if col_def_search
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
rel = scopes.reduce { |acc, scope| acc.or(scope) } if scopes.any?
|
|
76
|
+
|
|
77
|
+
rel
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Applies column-based filtering to the given ActiveRecord relation based on the provided columns and request parameters.
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
#
|
|
84
|
+
# @param rel [ActiveRecord::Relation] the relation to filter
|
|
85
|
+
# @param columns [Hash] a hash defining the columns and their filter attributes
|
|
86
|
+
#
|
|
87
|
+
# @return [ActiveRecord::Relation] the filtered relation
|
|
88
|
+
def dt_apply_filter(rel, columns)
|
|
89
|
+
return rel unless param_dtfilter.present?
|
|
90
|
+
|
|
91
|
+
param_dtfilter.split(',').each do |clause|
|
|
92
|
+
col, val = clause.split(':')
|
|
93
|
+
col_def = columns[col.to_sym]
|
|
94
|
+
col_def_f = col_def[:filter] if col_def
|
|
95
|
+
rel = rel.send("filtered_by_#{col_def_f}", val) if col_def_f && val.present? && rel.respond_to?("filtered_by_#{col_def_f}")
|
|
96
|
+
end
|
|
97
|
+
rel
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Applies pagination to the given ActiveRecord relation based on request parameters.
|
|
101
|
+
#
|
|
102
|
+
# @api private
|
|
103
|
+
#
|
|
104
|
+
# @param rel [ActiveRecord::Relation] the relation to paginate
|
|
105
|
+
# @param default_per_page [Integer] the default number of items per page
|
|
106
|
+
#
|
|
107
|
+
# @return [ActiveRecord::Relation] the paginated relation
|
|
108
|
+
def dt_apply_pagination(rel, default_per_page)
|
|
109
|
+
@dt_page = (params[:dtpage] || 1).to_i
|
|
110
|
+
@dt_per_page = (params[:dtperpage] || default_per_page).to_i
|
|
111
|
+
total = rel.count
|
|
112
|
+
@dt_total_pages = rel.total_pages(total: total, per_page: @dt_per_page)
|
|
113
|
+
rel.paginate(total: total, page: @dt_page, per_page: @dt_per_page)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns an array of page numbers and :gap symbols for pagination display
|
|
117
|
+
#
|
|
118
|
+
# @api private
|
|
119
|
+
#
|
|
120
|
+
# Example:
|
|
121
|
+
# pagination_window(current_page: 6, total_pages: 20)
|
|
122
|
+
# => [1, :gap, 4, 5, 6, 7, 8, :gap, 20]
|
|
123
|
+
#
|
|
124
|
+
# Use :gap to render "..." in your view.
|
|
125
|
+
#
|
|
126
|
+
# @param current_page [Integer] the current page number
|
|
127
|
+
# @param total_pages [Integer] the total number of pages
|
|
128
|
+
# @param window [Integer] the number of pages to show on each side of the current page
|
|
129
|
+
#
|
|
130
|
+
# @return [Array<Integer, Symbol>] array of page numbers and :gap symbols
|
|
131
|
+
def pagination_window(current_page:, total_pages:, window: 2)
|
|
132
|
+
return [] if total_pages < 1
|
|
133
|
+
|
|
134
|
+
pages = []
|
|
135
|
+
left = [1, current_page - window].max
|
|
136
|
+
right = [total_pages, current_page + window].min
|
|
137
|
+
|
|
138
|
+
pages << 1 unless left == 1
|
|
139
|
+
pages << :gap if left > 2
|
|
140
|
+
|
|
141
|
+
(left..right).each { |page| pages << page }
|
|
142
|
+
|
|
143
|
+
pages << :gap if right < total_pages - 1
|
|
144
|
+
pages << total_pages unless right == total_pages
|
|
145
|
+
|
|
146
|
+
pages
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Parses custom headers into params for datatable requests.
|
|
150
|
+
#
|
|
151
|
+
# @api private
|
|
152
|
+
#
|
|
153
|
+
# This allows clients to send datatable parameters via headers instead of query parameters.
|
|
154
|
+
# In this application, this is used to append headers when making Turbo Frame requests.
|
|
155
|
+
#
|
|
156
|
+
# Headers parsed:
|
|
157
|
+
# - X-Dtsearch -> params[:dtsearch]
|
|
158
|
+
# - X-Dtsort -> params[:dtsort]
|
|
159
|
+
# - X-Dtfilter -> params[:dtfilter]
|
|
160
|
+
# - X-DtPage -> params[:dtpage]
|
|
161
|
+
# - X-DtPerPage -> params[:dtperpage]
|
|
162
|
+
#
|
|
163
|
+
# @return [void]
|
|
164
|
+
def parse_headers_to_params
|
|
165
|
+
parse_header_to_params(:dtsearch, 'X-Dtsearch')
|
|
166
|
+
parse_header_to_params(:dtsort, 'X-Dtsort')
|
|
167
|
+
parse_header_to_params(:dtfilter, 'X-Dtfilter')
|
|
168
|
+
parse_header_to_params(:dtpage, 'X-DtPage')
|
|
169
|
+
parse_header_to_params(:dtperpage, 'X-DtPerPage')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Helper method to parse a single header into a parameter if the parameter is not already set.
|
|
173
|
+
#
|
|
174
|
+
# @api private
|
|
175
|
+
#
|
|
176
|
+
# @param param_key [Symbol] the parameter key to set
|
|
177
|
+
# @param header_key [String] the header key to read from
|
|
178
|
+
#
|
|
179
|
+
# @return [void]
|
|
180
|
+
def parse_header_to_params(param_key, header_key)
|
|
181
|
+
return unless request.headers[header_key].present?
|
|
182
|
+
return if params[param_key].present?
|
|
183
|
+
|
|
184
|
+
values = request.headers[header_key].split(',').map(&:strip).reject(&:empty?)
|
|
185
|
+
params[param_key] = values.join(',')
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Renders Turbo Stream responses for datatable updates.
|
|
189
|
+
#
|
|
190
|
+
# @api private
|
|
191
|
+
#
|
|
192
|
+
# This method is used to dynamically update the datatable rows, pagination controls,
|
|
193
|
+
# and filters via Turbo Streams when the datatable requests new data.
|
|
194
|
+
#
|
|
195
|
+
# @dt_config must be set before calling this method.
|
|
196
|
+
#
|
|
197
|
+
# @return [void]
|
|
198
|
+
def render_dt_turbo_streams
|
|
199
|
+
dt_config_id = @dt_config[:id]
|
|
200
|
+
render turbo_stream: [
|
|
201
|
+
turbo_stream.replace(
|
|
202
|
+
"datatable-body-#{dt_config_id}",
|
|
203
|
+
partial: 'smriti/admin/ui/datatable_tbody',
|
|
204
|
+
locals: { row_meta: @row_meta }
|
|
205
|
+
),
|
|
206
|
+
turbo_stream.replace(
|
|
207
|
+
"datatable-tfoot-#{dt_config_id}",
|
|
208
|
+
partial: 'smriti/admin/ui/datatable_tfoot',
|
|
209
|
+
locals: { dt_config: @dt_config, data: @data }
|
|
210
|
+
),
|
|
211
|
+
if index_dt_config[:filter_enabled]
|
|
212
|
+
turbo_stream.replace(
|
|
213
|
+
"datatable-filters-#{dt_config_id}",
|
|
214
|
+
partial: 'smriti/admin/ui/datatable_filters',
|
|
215
|
+
locals: { dt_config: @dt_config, dtfilter: params[:dtfilter] || '' }
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
].compact
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Accessor methods for datatable filter parameter
|
|
222
|
+
#
|
|
223
|
+
# @api private
|
|
224
|
+
#
|
|
225
|
+
# @return [String, nil] the dtfilter parameter from params
|
|
226
|
+
def param_dtfilter
|
|
227
|
+
params[:dtfilter]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Accessor methods for datatable search parameter
|
|
231
|
+
#
|
|
232
|
+
# @api private
|
|
233
|
+
#
|
|
234
|
+
# @return [String, nil] the dtsearch parameter from params
|
|
235
|
+
def param_dtsearch
|
|
236
|
+
params[:dtsearch]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Accessor methods for datatable sort parameter
|
|
240
|
+
#
|
|
241
|
+
# @api private
|
|
242
|
+
#
|
|
243
|
+
# @return [String, nil] the dtsort parameter from params
|
|
244
|
+
def param_dtsort
|
|
245
|
+
params[:dtsort]
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
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::LocalizedDigitHelper
|
|
11
|
+
# -----------------------------------
|
|
12
|
+
# Helper methods for localizing digits in strings and dates.
|
|
13
|
+
# This is used in the admin UI to display numbers according to the current locale.
|
|
14
|
+
#
|
|
15
|
+
# Responsibilities:
|
|
16
|
+
# - Map ASCII digits (0-9) to localized representations based on I18n.t('numbers').
|
|
17
|
+
# - Replace digits in strings or numeric inputs with their localized equivalents.
|
|
18
|
+
# - Localize dates/times with localized digits.
|
|
19
|
+
#
|
|
20
|
+
# Methods:
|
|
21
|
+
# - localized_numbers: returns a hash mapping '0'-'9' to localized strings.
|
|
22
|
+
# - localized_digits: replaces digits in a string or number with localized versions.
|
|
23
|
+
# - l_with_digits: localizes an object (e.g. date/time) and replaces digits.
|
|
24
|
+
module LocalizedDigitHelper
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Replaces ASCII digits in the input with their localized equivalents.
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
#
|
|
31
|
+
# @param str_or_num [String, Numeric] the input string or number
|
|
32
|
+
# @return [String] the input with digits replaced by localized versions
|
|
33
|
+
def localized_digits(str_or_num)
|
|
34
|
+
str_or_num.to_s.gsub(/[0-9]/, localized_numbers)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns a hash mapping ASCII digits ('0'-'9') to their localized equivalents.
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash{String => String}] mapping of '0'-'9' to localized strings
|
|
42
|
+
def localized_numbers
|
|
43
|
+
map = I18n.t('numbers', default: nil)
|
|
44
|
+
{
|
|
45
|
+
'0' => map[:zero],
|
|
46
|
+
'1' => map[:one],
|
|
47
|
+
'2' => map[:two],
|
|
48
|
+
'3' => map[:three],
|
|
49
|
+
'4' => map[:four],
|
|
50
|
+
'5' => map[:five],
|
|
51
|
+
'6' => map[:six],
|
|
52
|
+
'7' => map[:seven],
|
|
53
|
+
'8' => map[:eight],
|
|
54
|
+
'9' => map[:nine]
|
|
55
|
+
}.compact
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Localizes an object (e.g. date/time) using I18n.l and replaces digits with localized versions.
|
|
59
|
+
#
|
|
60
|
+
# @api private
|
|
61
|
+
#
|
|
62
|
+
# @param obj [Object] the object to localize
|
|
63
|
+
# @param kwargs [Hash] additional keyword arguments passed to I18n.l
|
|
64
|
+
# @return [String] the localized string with digits replaced
|
|
65
|
+
def l_with_digits(obj, **)
|
|
66
|
+
localized_digits(I18n.l(obj, **))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|