motor-admin 0.1.60 → 0.1.61
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -9
- 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/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-fd0f75f789196ce24ffd.css.gz +0 -0
- data/ui/dist/main-fd0f75f789196ce24ffd.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: 32c8b159e2bc232debb27c4998d0a06b78d11cb4dee7d9080bb3557f183ca788
|
4
|
+
data.tar.gz: ec29e2a202609d5690deccb9ca2bc1d5b46438c80add5e5e6553cd9fb37bd02d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeb234e7240f4ea926c9890f45d0bf24da8e7d811919d9584523fc9bd384403bac20c893316285cce76f217596131c78766a4092ff03f6bea5977334e70f30db
|
7
|
+
data.tar.gz: 8c11a65a3e2c33bf05918e2544624498ae396fadf83e4a6ffb549f2dcf293d7217e9a740723bb4fb342c62acb6a8e8a13c11cb086e404e8341a72ae2f5a30616
|
data/README.md
CHANGED
@@ -43,9 +43,9 @@ $ rails motor:install && rake db:migrate
|
|
43
43
|
|
44
44
|
![Settings UI](https://user-images.githubusercontent.com/5418788/119263883-90708780-bbe9-11eb-9f9f-f76fed0b7f27.png)
|
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
|
|
@@ -63,7 +63,7 @@ Values from the form fields can be used in API path via `{field_name}` syntax: `
|
|
63
63
|
|
64
64
|
![SQL query](https://user-images.githubusercontent.com/5418788/119264127-84d19080-bbea-11eb-9903-ef465d1d2c97.png)
|
65
65
|
|
66
|
-
Queries can include
|
66
|
+
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
67
|
|
68
68
|
### Data Visualization
|
69
69
|
|
@@ -143,12 +143,6 @@ Start example application in development mode:
|
|
143
143
|
MOTOR_DEVELOPMENT=true rails s
|
144
144
|
```
|
145
145
|
|
146
|
-
## Comming Soon
|
147
|
-
|
148
|
-
* Multiple databases
|
149
|
-
* NoSQL data sources
|
150
|
-
* Pro Bussines intelligence features
|
151
|
-
|
152
146
|
## License
|
153
147
|
|
154
148
|
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])
|
@@ -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-fd0f75f789196ce24ffd.css.gz": "main-fd0f75f789196ce24ffd.css.gz",
|
2072
|
+
"main-fd0f75f789196ce24ffd.js.LICENSE.txt": "main-fd0f75f789196ce24ffd.js.LICENSE.txt",
|
2073
|
+
"main-fd0f75f789196ce24ffd.js.gz": "main-fd0f75f789196ce24ffd.js.gz",
|
2074
|
+
"main.css": "main-fd0f75f789196ce24ffd.css",
|
2075
|
+
"main.js": "main-fd0f75f789196ce24ffd.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.61
|
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-21 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-fd0f75f789196ce24ffd.css.gz
|
1495
|
+
- ui/dist/main-fd0f75f789196ce24ffd.js.gz
|
1495
1496
|
- ui/dist/manifest.json
|
1496
1497
|
homepage:
|
1497
1498
|
licenses:
|
Binary file
|
Binary file
|