motor-admin 0.1.60 → 0.1.65
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 +4 -4
- data/README.md +5 -10
- data/app/controllers/motor/api_base_controller.rb +1 -0
- data/app/controllers/motor/run_queries_controller.rb +11 -2
- data/lib/generators/motor/templates/install.rb +1 -0
- data/lib/motor/active_record_utils.rb +2 -1
- data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +13 -0
- data/lib/motor/active_record_utils/{active_record_filter.rb → active_record_filter_patch.rb} +0 -0
- data/lib/motor/admin.rb +1 -0
- data/lib/motor/api_query.rb +2 -2
- data/lib/motor/api_query/filter.rb +11 -1
- data/lib/motor/queries/render_sql_template.rb +12 -2
- data/lib/motor/queries/run_query.rb +66 -16
- data/lib/motor/version.rb +1 -1
- data/ui/dist/main-f6cb10fb0e14bdd1703b.css.gz +0 -0
- data/ui/dist/main-f6cb10fb0e14bdd1703b.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +6 -5
- data/ui/dist/main-96c4a62d2fb789ab1080.css.gz +0 -0
- data/ui/dist/main-96c4a62d2fb789ab1080.js.gz +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5f4c9fccf887dc60e81cb6bb4d2aacda0fbbf4e9aeac48474ef4c5067263873
|
4
|
+
data.tar.gz: 6eb4219e8ae7796991e3973b44daca3ec59d647ef4bb9471796558565ac49541
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45bf72ec83403b6ac6fdb02a584d3edd71007f0be8edbd3d58177ca3ed1b4b2f4b527df4bac5d3389bb8f8931234a1498e82ca6ced50c12c1b3c4e6950dfe36e
|
7
|
+
data.tar.gz: 7aca3f7e224bc2dc0ed87d8554b769d238ecd8a573f256e080412c9498be33fe58b1b0bc30bb9189d5f0854bf19193ad61e71c18a514ee2b4311a25eeec4f877
|
data/README.md
CHANGED
@@ -43,9 +43,9 @@ $ rails motor:install && rake db:migrate
|
|
43
43
|
|
44
44
|

|
45
45
|
|
46
|
-
Everything in the admin panel can be configured using intuitive settings UI, which can be opened via the icon in the top right corner.
|
46
|
+
Everything in the admin panel can be configured using the intuitive settings UI, which can be opened via the icon in the top right corner.
|
47
47
|
|
48
|
-
Data displayed on the resource page can be completely
|
48
|
+
Data displayed on the resource page can be completely customized via [SQL queries](#sql-queries) and [dashboards](#dashboards) attached to the resource as a tab. Usually, queries used to display resource data should contain `{{resource_name_id}}` [variable](#sql-queries).
|
49
49
|
|
50
50
|
### Custom Actions
|
51
51
|
|
@@ -57,13 +57,14 @@ Custom resource actions can be added via Active Record method call, API endpoint
|
|
57
57
|
|
58
58
|

|
59
59
|
|
60
|
-
Values from the form fields can be used in API path via `{field_name}` syntax: `/api/some-endpoint/{resource_id}/apply
|
60
|
+
Values from the form fields can be used in API path via `{field_name}` syntax: `/api/some-endpoint/{resource_id}/apply`.<br>
|
61
|
+
[Learn more about custom forms builder](https://github.com/omohokcoj/motor-admin/blob/master/guides/building_custom_forms.md).
|
61
62
|
|
62
63
|
### SQL Queries
|
63
64
|
|
64
65
|

|
65
66
|
|
66
|
-
Queries can include
|
67
|
+
Queries can include embedded variables via `{{variable}}` syntax ([mustache](https://mustache.github.io)). `{{#variable}} ... {{/variable}}` syntax allows to decide if conditions inside the scope should be included in the query.
|
67
68
|
|
68
69
|
### Data Visualization
|
69
70
|
|
@@ -143,12 +144,6 @@ Start example application in development mode:
|
|
143
144
|
MOTOR_DEVELOPMENT=true rails s
|
144
145
|
```
|
145
146
|
|
146
|
-
## Comming Soon
|
147
|
-
|
148
|
-
* Multiple databases
|
149
|
-
* NoSQL data sources
|
150
|
-
* Pro Bussines intelligence features
|
151
|
-
|
152
147
|
## License
|
153
148
|
|
154
149
|
The gem is available as open source under the terms of the [MIT License](https://github.com/omohokcoj/motor-admin/blob/master/LICENSE).
|
@@ -20,8 +20,9 @@ module Motor
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def render_result
|
23
|
-
|
24
|
-
|
23
|
+
query_result = Queries::RunQuery.call(@query, variables_hash: variables_params,
|
24
|
+
limit: params[:limit].presence,
|
25
|
+
filters: filter_params)
|
25
26
|
|
26
27
|
if query_result.error
|
27
28
|
render json: { errors: [{ detail: query_result.error }] }, status: :unprocessable_entity
|
@@ -56,5 +57,13 @@ module Motor
|
|
56
57
|
def query_params
|
57
58
|
params.require(:data).permit(:sql_body, preferences: {})
|
58
59
|
end
|
60
|
+
|
61
|
+
def variables_params
|
62
|
+
params.fetch(:variables, {}).merge(current_user_variables)
|
63
|
+
end
|
64
|
+
|
65
|
+
def filter_params
|
66
|
+
(params[:filter] || params[:filters])&.to_unsafe_h
|
67
|
+
end
|
59
68
|
end
|
60
69
|
end
|
@@ -149,6 +149,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
|
|
149
149
|
drop_table :motor_audits
|
150
150
|
drop_table :motor_alert_locks
|
151
151
|
drop_table :motor_alerts
|
152
|
+
drop_table :motor_forms
|
152
153
|
drop_table :motor_taggable_tags
|
153
154
|
drop_table :motor_tags
|
154
155
|
drop_table :motor_resources
|
@@ -20,4 +20,5 @@ require_relative './active_record_utils/fetch_methods'
|
|
20
20
|
require_relative './active_record_utils/defined_scopes_extension'
|
21
21
|
require_relative './active_record_utils/active_storage_links_extension'
|
22
22
|
require_relative './active_record_utils/active_storage_blob_patch'
|
23
|
-
require_relative './active_record_utils/
|
23
|
+
require_relative './active_record_utils/active_record_filter_patch'
|
24
|
+
require_relative './active_record_utils/active_record_connection_column_patch'
|
data/lib/motor/active_record_utils/{active_record_filter.rb → active_record_filter_patch.rb}
RENAMED
File without changes
|
data/lib/motor/admin.rb
CHANGED
data/lib/motor/api_query.rb
CHANGED
@@ -5,9 +5,9 @@ module Motor
|
|
5
5
|
module_function
|
6
6
|
|
7
7
|
def call(rel, params)
|
8
|
-
rel = ApiQuery::Sort.call(rel, params[:sort])
|
8
|
+
rel = ApiQuery::Sort.call(rel, params[:sort] || params[:order])
|
9
9
|
rel = ApiQuery::Paginate.call(rel, params[:page])
|
10
|
-
rel = ApiQuery::Filter.call(rel, params[:filter])
|
10
|
+
rel = ApiQuery::Filter.call(rel, params[:filter] || params[:filters])
|
11
11
|
rel = ApiQuery::ApplyScope.call(rel, params[:scope])
|
12
12
|
|
13
13
|
ApiQuery::Search.call(rel, params[:q] || params[:search] || params[:query])
|
@@ -4,6 +4,7 @@ module Motor
|
|
4
4
|
module ApiQuery
|
5
5
|
module Filter
|
6
6
|
LIKE_FILTER_VALUE_REGEXP = /\A%?(.*?)%?\z/.freeze
|
7
|
+
DISTINCT_RESTRICTED_COLUMN_TYPES = %i[json point].freeze
|
7
8
|
|
8
9
|
module_function
|
9
10
|
|
@@ -12,7 +13,10 @@ module Motor
|
|
12
13
|
|
13
14
|
normalized_params = normalize_params(Array.wrap(params))
|
14
15
|
|
15
|
-
rel.filter(normalized_params)
|
16
|
+
rel = rel.filter(normalized_params)
|
17
|
+
rel = rel.distinct if can_apply_distinct?(rel)
|
18
|
+
|
19
|
+
rel
|
16
20
|
end
|
17
21
|
|
18
22
|
def normalize_params(params)
|
@@ -47,6 +51,12 @@ module Motor
|
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
54
|
+
def can_apply_distinct?(rel)
|
55
|
+
rel.columns.none? do |column|
|
56
|
+
DISTINCT_RESTRICTED_COLUMN_TYPES.include?(column.type)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
50
60
|
def normalize_action(action, value)
|
51
61
|
case action
|
52
62
|
when 'includes'
|
@@ -22,9 +22,19 @@ module Motor
|
|
22
22
|
variable_name = Regexp.last_match[1]
|
23
23
|
|
24
24
|
index = selected_variables.index { |name, _| name == variable_name }
|
25
|
-
|
25
|
+
variable_values = variables[variable_name]
|
26
26
|
|
27
|
-
|
27
|
+
if variable_values.is_a?(Array)
|
28
|
+
first_variable_index = selected_variables.size + 1
|
29
|
+
|
30
|
+
variable_values.each { |value| selected_variables << [variable_name, value] } unless index
|
31
|
+
|
32
|
+
(first_variable_index..selected_variables.size).map { |i| "$#{i}" }.join(', ')
|
33
|
+
else
|
34
|
+
selected_variables << [variable_name, variables[variable_name]] unless index
|
35
|
+
|
36
|
+
"$#{selected_variables.size}"
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
30
40
|
[rendered, selected_variables]
|
@@ -7,13 +7,16 @@ module Motor
|
|
7
7
|
|
8
8
|
QueryResult = Struct.new(:data, :columns, :error, keyword_init: true)
|
9
9
|
|
10
|
-
|
10
|
+
CTE_NAME = '__query__'
|
11
|
+
|
12
|
+
WITH_STATEMENT_START = "WITH #{CTE_NAME} AS ("
|
11
13
|
|
12
14
|
WITH_STATEMENT_TEMPLATE = <<~SQL
|
13
15
|
#{WITH_STATEMENT_START}%<sql_body>s
|
14
|
-
)
|
16
|
+
)
|
15
17
|
SQL
|
16
18
|
|
19
|
+
STATEMENT_VARIABLE_REGEXP = /\$\d+/.freeze
|
17
20
|
PG_ERROR_REGEXP = /\APG.+ERROR:/.freeze
|
18
21
|
|
19
22
|
RESERVED_VARIABLES = %w[current_user_id current_user_email].freeze
|
@@ -23,11 +26,14 @@ module Motor
|
|
23
26
|
# @param query [Motor::Query]
|
24
27
|
# @param variables_hash [Hash]
|
25
28
|
# @param limit [Integer]
|
29
|
+
# @param filters [Hash]
|
26
30
|
# @return [Motor::Queries::RunQuery::QueryResult]
|
27
|
-
def call!(query, variables_hash: nil, limit:
|
31
|
+
def call!(query, variables_hash: nil, limit: nil, filters: nil)
|
28
32
|
variables_hash ||= {}
|
33
|
+
limit ||= DEFAULT_LIMIT
|
34
|
+
filters ||= {}
|
29
35
|
|
30
|
-
result = execute_query(query, limit, variables_hash)
|
36
|
+
result = execute_query(query, limit, variables_hash, filters)
|
31
37
|
|
32
38
|
QueryResult.new(data: result.rows, columns: build_columns_hash(result))
|
33
39
|
end
|
@@ -36,8 +42,8 @@ module Motor
|
|
36
42
|
# @param variables_hash [Hash]
|
37
43
|
# @param limit [Integer]
|
38
44
|
# @return [Motor::Queries::RunQuery::QueryResult]
|
39
|
-
def call(query, variables_hash: nil, limit:
|
40
|
-
call!(query, variables_hash: variables_hash, limit: limit)
|
45
|
+
def call(query, variables_hash: nil, limit: nil, filters: nil)
|
46
|
+
call!(query, variables_hash: variables_hash, limit: limit, filters: filters)
|
41
47
|
rescue ActiveRecord::StatementInvalid => e
|
42
48
|
QueryResult.new(error: build_error_message(e))
|
43
49
|
end
|
@@ -51,10 +57,11 @@ module Motor
|
|
51
57
|
# @param query [Motor::Query]
|
52
58
|
# @param limit [Integer]
|
53
59
|
# @param variables_hash [Hash]
|
60
|
+
# @param filters [Hash]
|
54
61
|
# @return [ActiveRecord::Result]
|
55
|
-
def execute_query(query, limit, variables_hash)
|
62
|
+
def execute_query(query, limit, variables_hash, filters)
|
56
63
|
result = nil
|
57
|
-
statement = prepare_sql_statement(query, limit, variables_hash)
|
64
|
+
statement = prepare_sql_statement(query, limit, variables_hash, filters)
|
58
65
|
|
59
66
|
ActiveRecord::Base.transaction do
|
60
67
|
result =
|
@@ -62,6 +69,8 @@ module Motor
|
|
62
69
|
when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
|
63
70
|
PostgresqlExecQuery.call(ActiveRecord::Base.connection, statement)
|
64
71
|
else
|
72
|
+
statement = normalize_statement_for_sql(statement)
|
73
|
+
|
65
74
|
ActiveRecord::Base.connection.exec_query(*statement)
|
66
75
|
end
|
67
76
|
|
@@ -88,27 +97,68 @@ module Motor
|
|
88
97
|
# @param query [Motor::Query]
|
89
98
|
# @param limit [Integer]
|
90
99
|
# @param variables_hash [Hash]
|
100
|
+
# @param filters [Hash]
|
91
101
|
# @return [Array]
|
92
|
-
def prepare_sql_statement(query, limit, variables_hash)
|
102
|
+
def prepare_sql_statement(query, limit, variables_hash, filters)
|
93
103
|
variables = merge_variable_default_values(query.preferences.fetch(:variables, []), variables_hash)
|
94
104
|
|
95
105
|
sql, query_variables = RenderSqlTemplate.call(query.sql_body, variables)
|
106
|
+
cte_sql = format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.delete_suffix(';'))
|
107
|
+
cte_select_sql = build_cte_select_sql(limit, filters)
|
96
108
|
|
97
109
|
attributes = build_statement_attributes(query_variables)
|
98
110
|
|
99
|
-
[
|
111
|
+
[[cte_sql, cte_select_sql].join, 'SQL', attributes]
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param limit [Number]
|
115
|
+
# @param filters [Hash]
|
116
|
+
# @return [String]
|
117
|
+
def build_cte_select_sql(limit, filters)
|
118
|
+
table = Arel::Table.new(CTE_NAME)
|
119
|
+
|
120
|
+
arel_filters = build_filters_arel(filters)
|
121
|
+
|
122
|
+
expresion = table.project(table[Arel.star])
|
123
|
+
expresion = expresion.where(arel_filters) if arel_filters.present?
|
124
|
+
|
125
|
+
expresion.take(limit.to_i).to_sql
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param filters [Hash]
|
129
|
+
# @return [Arel::Nodes, nil]
|
130
|
+
def build_filters_arel(filters)
|
131
|
+
return nil if filters.blank?
|
132
|
+
|
133
|
+
table = Arel::Table.new(CTE_NAME)
|
134
|
+
|
135
|
+
arel_filters = filters.map { |key, value| table[key].in(value) }
|
136
|
+
|
137
|
+
arel_filters[1..].reduce(arel_filters.first) { |acc, arel| acc.and(arel) }
|
100
138
|
end
|
101
139
|
|
102
140
|
# @param variables [Array<(String, Object)>]
|
103
141
|
# @return [Array<ActiveRecord::Relation::QueryAttribute>]
|
104
142
|
def build_statement_attributes(variables)
|
105
143
|
variables.map do |variable_name, value|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
144
|
+
[value].flatten.map do |val|
|
145
|
+
ActiveRecord::Relation::QueryAttribute.new(
|
146
|
+
variable_name,
|
147
|
+
val,
|
148
|
+
ActiveRecord::Type::Value.new
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end.flatten
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param array [Array]
|
155
|
+
# @return [Array]
|
156
|
+
def normalize_statement_for_sql(statement)
|
157
|
+
sql, _, attributes = statement
|
158
|
+
|
159
|
+
sql = ActiveRecord::Base.sanitize_sql([sql.gsub(STATEMENT_VARIABLE_REGEXP, '?'), attributes.map(&:value)])
|
160
|
+
|
161
|
+
[sql, 'SQL', attributes]
|
112
162
|
end
|
113
163
|
|
114
164
|
# @param variable_configs [Array<Hash>]
|
data/lib/motor/version.rb
CHANGED
Binary file
|
Binary file
|
data/ui/dist/manifest.json
CHANGED
@@ -2068,11 +2068,11 @@
|
|
2068
2068
|
"mail-opened.svg": "icons/mail-opened.svg",
|
2069
2069
|
"mail.svg": "icons/mail.svg",
|
2070
2070
|
"mailbox.svg": "icons/mailbox.svg",
|
2071
|
-
"main-
|
2072
|
-
"main-
|
2073
|
-
"main-
|
2074
|
-
"main.css": "main-
|
2075
|
-
"main.js": "main-
|
2071
|
+
"main-f6cb10fb0e14bdd1703b.css.gz": "main-f6cb10fb0e14bdd1703b.css.gz",
|
2072
|
+
"main-f6cb10fb0e14bdd1703b.js.LICENSE.txt": "main-f6cb10fb0e14bdd1703b.js.LICENSE.txt",
|
2073
|
+
"main-f6cb10fb0e14bdd1703b.js.gz": "main-f6cb10fb0e14bdd1703b.js.gz",
|
2074
|
+
"main.css": "main-f6cb10fb0e14bdd1703b.css",
|
2075
|
+
"main.js": "main-f6cb10fb0e14bdd1703b.js",
|
2076
2076
|
"man.svg": "icons/man.svg",
|
2077
2077
|
"manual-gearbox.svg": "icons/manual-gearbox.svg",
|
2078
2078
|
"map-2.svg": "icons/map-2.svg",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motor-admin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.65
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pete Matsyburka
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord-filter
|
@@ -169,7 +169,8 @@ files:
|
|
169
169
|
- lib/motor-admin.rb
|
170
170
|
- lib/motor.rb
|
171
171
|
- lib/motor/active_record_utils.rb
|
172
|
-
- lib/motor/active_record_utils/
|
172
|
+
- lib/motor/active_record_utils/active_record_connection_column_patch.rb
|
173
|
+
- lib/motor/active_record_utils/active_record_filter_patch.rb
|
173
174
|
- lib/motor/active_record_utils/active_storage_blob_patch.rb
|
174
175
|
- lib/motor/active_record_utils/active_storage_links_extension.rb
|
175
176
|
- lib/motor/active_record_utils/defined_scopes_extension.rb
|
@@ -1490,8 +1491,8 @@ files:
|
|
1490
1491
|
- ui/dist/icons/zoom-money.svg.gz
|
1491
1492
|
- ui/dist/icons/zoom-out.svg.gz
|
1492
1493
|
- ui/dist/icons/zoom-question.svg.gz
|
1493
|
-
- ui/dist/main-
|
1494
|
-
- ui/dist/main-
|
1494
|
+
- ui/dist/main-f6cb10fb0e14bdd1703b.css.gz
|
1495
|
+
- ui/dist/main-f6cb10fb0e14bdd1703b.js.gz
|
1495
1496
|
- ui/dist/manifest.json
|
1496
1497
|
homepage:
|
1497
1498
|
licenses:
|
Binary file
|
Binary file
|