motor-admin 0.1.13 → 0.1.14
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/app/controllers/motor/data_controller.rb +18 -1
- data/lib/motor/admin.rb +1 -2
- data/lib/motor/alerts/scheduled_alerts_cache.rb +0 -4
- data/lib/motor/api_query/build_json.rb +3 -2
- data/lib/motor/api_query/sort.rb +28 -8
- data/lib/motor/build_schema/active_storage_attachment_schema.rb +1 -1
- data/lib/motor/build_schema/load_from_rails.rb +67 -26
- data/lib/motor/build_schema/persist_resource_configs.rb +1 -0
- data/lib/motor/build_schema/reorder_schema.rb +21 -1
- data/lib/motor/queries/run_query.rb +10 -5
- data/lib/motor/version.rb +1 -1
- data/ui/dist/{main-8f36a2746422efd1e8a8.css.gz → main-f7c7f445c53544d2d7b8.css.gz} +0 -0
- data/ui/dist/main-f7c7f445c53544d2d7b8.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +4 -4
- data/ui/dist/main-8f36a2746422efd1e8a8.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: 690872e084d43d0b2759f48a2d33b7399470b6dec88678248982daf0b0ca2e8a
|
4
|
+
data.tar.gz: 3949cd11e7773c19dd03c77dd1b52477ed282e3e40e9e688bf9018e7c93c2b13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 244c24b44ab251e6a71fe9e4200062b8d6c3679effb03c902759f3ef389d87cf87f8cf7fcf5c48c15a0c4358fc0ee750964b03c17eccd2f3af7cba19a0282ba8
|
7
|
+
data.tar.gz: 9225e1067d918fccd2dbef1c661e5128299579bd05fe4b52738bcb1ca2cc90c7495cbd07a556661000654ff0e4f6fd3e76d8049d44e3748f759e39566f89ffeb
|
@@ -8,6 +8,7 @@ module Motor
|
|
8
8
|
|
9
9
|
before_action :load_and_authorize_resource
|
10
10
|
before_action :load_and_authorize_association
|
11
|
+
before_action :wrap_io_params
|
11
12
|
|
12
13
|
def index
|
13
14
|
@resources = Motor::ApiQuery.call(@resources, params)
|
@@ -98,7 +99,23 @@ module Motor
|
|
98
99
|
end
|
99
100
|
|
100
101
|
def resource_params
|
101
|
-
params
|
102
|
+
if params[:data].present?
|
103
|
+
params.require(:data).except(resource_class.primary_key).permit!
|
104
|
+
else
|
105
|
+
{}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def wrap_io_params(hash = params)
|
110
|
+
hash.each do |key, value|
|
111
|
+
if key == 'io'
|
112
|
+
hash[key] = StringIO.new(value.encode('ISO-8859-1'))
|
113
|
+
elsif value.is_a?(ActionController::Parameters)
|
114
|
+
wrap_io_params(value)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
hash
|
102
119
|
end
|
103
120
|
end
|
104
121
|
end
|
data/lib/motor/admin.rb
CHANGED
@@ -8,10 +8,9 @@ module Motor
|
|
8
8
|
|
9
9
|
initializer 'motor.alerts.scheduler' do
|
10
10
|
config.after_initialize do |_app|
|
11
|
-
next
|
11
|
+
next unless defined?(Rails::Server)
|
12
12
|
|
13
13
|
Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
|
14
|
-
Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
@@ -3,10 +3,6 @@
|
|
3
3
|
module Motor
|
4
4
|
module Alerts
|
5
5
|
module ScheduledAlertsCache
|
6
|
-
UPDATE_ALERTS_TASK = Concurrent::TimerTask.new(
|
7
|
-
execution_interval: 2.minutes
|
8
|
-
) { Motor::Alerts::ScheduledAlertsCache.load_alerts }
|
9
|
-
|
10
6
|
CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
|
11
7
|
|
12
8
|
module_function
|
@@ -49,7 +49,8 @@ module Motor
|
|
49
49
|
|
50
50
|
params[:fields].each do |key, fields|
|
51
51
|
fields = fields.split(',') if fields.is_a?(String)
|
52
|
-
|
52
|
+
reflection_class = model.reflections[key]&.klass
|
53
|
+
fields_hash = build_fields_hash(reflection_class || model, fields)
|
53
54
|
|
54
55
|
if key == model_name || model_name.split('/').last == key
|
55
56
|
json_params.merge!(fields_hash)
|
@@ -68,7 +69,7 @@ module Motor
|
|
68
69
|
fields.each_with_object(fields_hash) do |field, acc|
|
69
70
|
if field.in?(columns)
|
70
71
|
acc['only'] << field
|
71
|
-
|
72
|
+
elsif model.instance_methods.include?(field.to_sym)
|
72
73
|
acc['methods'] << field
|
73
74
|
end
|
74
75
|
end
|
data/lib/motor/api_query/sort.rb
CHANGED
@@ -7,19 +7,39 @@ module Motor
|
|
7
7
|
|
8
8
|
module_function
|
9
9
|
|
10
|
-
def call(rel,
|
11
|
-
return rel if
|
10
|
+
def call(rel, param)
|
11
|
+
return rel if param.blank?
|
12
12
|
|
13
|
-
|
13
|
+
arel_order = build_arel_order(rel.klass, param)
|
14
|
+
join_params = build_join_params(rel.klass, param)
|
14
15
|
|
15
|
-
rel.order(
|
16
|
+
rel.order(arel_order).left_joins(join_params)
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
param.split(',').each_with_object({}) do |field,
|
20
|
-
|
19
|
+
def build_join_params(_model, param)
|
20
|
+
param.split(',').each_with_object({}) do |field, result|
|
21
|
+
key = field[FIELD_PARSE_REGEXP, 2]
|
22
|
+
*path, _ = key.split('.')
|
21
23
|
|
22
|
-
|
24
|
+
path.reduce(result) do |acc, fragment|
|
25
|
+
acc[fragment] = {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_arel_order(model, param)
|
31
|
+
param.split(',').map do |field|
|
32
|
+
direction, key = field.match(FIELD_PARSE_REGEXP).captures
|
33
|
+
*path, field = key.split('.')
|
34
|
+
|
35
|
+
reflection_model =
|
36
|
+
path.reduce(model) do |acc, fragment|
|
37
|
+
acc.reflections[fragment].klass
|
38
|
+
end
|
39
|
+
|
40
|
+
arel_column = reflection_model.arel_table[field]
|
41
|
+
|
42
|
+
direction.present? ? arel_column.desc : arel_column.asc
|
23
43
|
end
|
24
44
|
end
|
25
45
|
end
|
@@ -76,22 +76,80 @@ module Motor
|
|
76
76
|
def fetch_columns(model)
|
77
77
|
default_attrs = model.new.attributes
|
78
78
|
|
79
|
-
|
79
|
+
reference_columns = fetch_reference_columns(model)
|
80
|
+
|
81
|
+
table_columns =
|
82
|
+
model.columns.map do |column|
|
83
|
+
next if reference_columns.find { |c| c[:name] == column.name }
|
84
|
+
|
85
|
+
{
|
86
|
+
name: column.name,
|
87
|
+
display_name: column.name.humanize,
|
88
|
+
column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
|
89
|
+
access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
|
90
|
+
default_value: default_attrs[column.name],
|
91
|
+
validators: fetch_validators(model, column.name),
|
92
|
+
reference: nil,
|
93
|
+
virtual: false
|
94
|
+
}
|
95
|
+
end.compact
|
96
|
+
|
97
|
+
reference_columns + table_columns
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_column(model, column)
|
101
|
+
{
|
102
|
+
name: column.name,
|
103
|
+
display_name: column.name.humanize,
|
104
|
+
column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
|
105
|
+
access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
|
106
|
+
default_value: default_attrs[column.name],
|
107
|
+
validators: fetch_validators(model, column.name),
|
108
|
+
reference: fetch_reference(model, column.name),
|
109
|
+
virtual: false
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def fetch_reference_columns(model)
|
114
|
+
default_attrs = model.new.attributes
|
115
|
+
|
116
|
+
model.reflections.map do |name, ref|
|
117
|
+
next if !ref.has_one? && !ref.belongs_to?
|
118
|
+
|
119
|
+
begin
|
120
|
+
ref.klass
|
121
|
+
rescue StandardError
|
122
|
+
next
|
123
|
+
end
|
124
|
+
|
125
|
+
column_name = ref.belongs_to? ? ref.foreign_key : name
|
126
|
+
|
127
|
+
next if ref.klass.name == 'ActiveStorage::Blob'
|
128
|
+
|
129
|
+
is_attachment = ref.klass.name == 'ActiveStorage::Attachment'
|
130
|
+
|
80
131
|
{
|
81
|
-
name:
|
82
|
-
display_name:
|
83
|
-
column_type:
|
84
|
-
access_type:
|
85
|
-
default_value: default_attrs[
|
86
|
-
validators: fetch_validators(model,
|
132
|
+
name: column_name,
|
133
|
+
display_name: column_name.humanize,
|
134
|
+
column_type: is_attachment ? 'file' : 'integer',
|
135
|
+
access_type: ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY,
|
136
|
+
default_value: default_attrs[column_name],
|
137
|
+
validators: fetch_validators(model, column_name),
|
138
|
+
reference: {
|
139
|
+
name: name,
|
140
|
+
model_name: ref.klass.name.underscore,
|
141
|
+
reference_type: ref.belongs_to? ? 'belongs_to' : 'has_one',
|
142
|
+
foreign_key: ref.foreign_key,
|
143
|
+
polymorphic: ref.polymorphic? || is_attachment
|
144
|
+
},
|
87
145
|
virtual: false
|
88
146
|
}
|
89
|
-
end
|
147
|
+
end.compact
|
90
148
|
end
|
91
149
|
|
92
150
|
def fetch_associations(model)
|
93
151
|
model.reflections.map do |name, ref|
|
94
|
-
next if ref.
|
152
|
+
next if ref.has_one? || ref.belongs_to?
|
95
153
|
|
96
154
|
begin
|
97
155
|
ref.klass
|
@@ -108,8 +166,6 @@ module Motor
|
|
108
166
|
display_name: name.humanize,
|
109
167
|
slug: name.underscore,
|
110
168
|
model_name: model_class.name.underscore,
|
111
|
-
model_slug: Utils.slugify(model_class),
|
112
|
-
association_type: fetch_association_type(ref),
|
113
169
|
foreign_key: ref.foreign_key,
|
114
170
|
polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
|
115
171
|
visible: true
|
@@ -117,21 +173,6 @@ module Motor
|
|
117
173
|
end.compact
|
118
174
|
end
|
119
175
|
|
120
|
-
def fetch_association_type(association)
|
121
|
-
case association.association_class.to_s
|
122
|
-
when 'ActiveRecord::Associations::HasManyAssociation',
|
123
|
-
'ActiveRecord::Associations::HasManyThroughAssociation'
|
124
|
-
'has_many'
|
125
|
-
when 'ActiveRecord::Associations::HasOneAssociation',
|
126
|
-
'ActiveRecord::Associations::HasOneThroughAssociation'
|
127
|
-
'has_one'
|
128
|
-
when 'ActiveRecord::Associations::BelongsToAssociation'
|
129
|
-
'belongs_to'
|
130
|
-
else
|
131
|
-
raise ArgumentError, 'Unknown association type'
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
176
|
def fetch_validators(model, column_name)
|
136
177
|
model.validators_on(column_name).map do |validator|
|
137
178
|
case validator
|
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
module Motor
|
4
4
|
module ReorderSchema
|
5
|
+
COLUMNS_DEFAULT_ORDER_WEIGHTS = {
|
6
|
+
id: 0,
|
7
|
+
updated_at: 2,
|
8
|
+
edited_at: 2,
|
9
|
+
created_at: 3,
|
10
|
+
inserted_at: 3,
|
11
|
+
deleted_at: 4,
|
12
|
+
archived_at: 4
|
13
|
+
}.with_indifferent_access
|
14
|
+
|
15
|
+
COLUMNS_DEFAULT_ORDER_WEIGHT = 1
|
16
|
+
|
5
17
|
module_function
|
6
18
|
|
7
19
|
# @param schema [Array<HashWithIndifferentAccess>]
|
@@ -19,7 +31,7 @@ module Motor
|
|
19
31
|
scopes_order = configs["resources.#{model[:name]}.scopes.order"]
|
20
32
|
|
21
33
|
model.merge(
|
22
|
-
columns: sort_by_name(model[:columns], columns_order, sort_alphabetically: false),
|
34
|
+
columns: sort_by_name(sort_columns(model[:columns]), columns_order, sort_alphabetically: false),
|
23
35
|
associations: sort_by_name(model[:associations], associations_order),
|
24
36
|
actions: sort_by_name(model[:actions], actions_order, sort_alphabetically: false),
|
25
37
|
tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false),
|
@@ -44,6 +56,14 @@ module Motor
|
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
59
|
+
def sort_columns(columns)
|
60
|
+
columns.each_with_object([]) do |column, acc|
|
61
|
+
weight = COLUMNS_DEFAULT_ORDER_WEIGHTS.fetch(column[:name], COLUMNS_DEFAULT_ORDER_WEIGHT)
|
62
|
+
|
63
|
+
(acc[weight] ||= []) << column
|
64
|
+
end.flatten.compact
|
65
|
+
end
|
66
|
+
|
47
67
|
# @return [Hash<String, HashWithIndifferentAccess>]
|
48
68
|
def load_configs
|
49
69
|
Motor::Config.all.each_with_object({}) do |config, acc|
|
@@ -57,11 +57,16 @@ module Motor
|
|
57
57
|
"$#{index}"
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
attributes =
|
61
|
+
variables.map do |variable_name, default_value|
|
62
|
+
ActiveRecord::Relation::QueryAttribute.new(
|
63
|
+
variable_name,
|
64
|
+
variables_hash[variable_name] || default_value,
|
65
|
+
ActiveRecord::Type::Value.new
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
[format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit), 'SQL', attributes]
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/motor/version.rb
CHANGED
Binary file
|
Binary file
|
data/ui/dist/manifest.json
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
"fonts/ionicons.ttf?v=3.0.0-alpha.3": "fonts/ionicons.ttf",
|
6
6
|
"fonts/ionicons.woff2?v=3.0.0-alpha.3": "fonts/ionicons.woff2",
|
7
7
|
"fonts/ionicons.woff?v=3.0.0-alpha.3": "fonts/ionicons.woff",
|
8
|
-
"main-
|
9
|
-
"main-
|
10
|
-
"main-
|
11
|
-
"main.css": "main-
|
12
|
-
"main.js": "main-
|
8
|
+
"main-f7c7f445c53544d2d7b8.css.gz": "main-f7c7f445c53544d2d7b8.css.gz",
|
9
|
+
"main-f7c7f445c53544d2d7b8.js.LICENSE.txt": "main-f7c7f445c53544d2d7b8.js.LICENSE.txt",
|
10
|
+
"main-f7c7f445c53544d2d7b8.js.gz": "main-f7c7f445c53544d2d7b8.js.gz",
|
11
|
+
"main.css": "main-f7c7f445c53544d2d7b8.css",
|
12
|
+
"main.js": "main-f7c7f445c53544d2d7b8.js"
|
13
13
|
}
|
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.14
|
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-04-
|
11
|
+
date: 2021-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord-filter
|
@@ -213,8 +213,8 @@ files:
|
|
213
213
|
- lib/motor/ui_configs.rb
|
214
214
|
- lib/motor/version.rb
|
215
215
|
- ui/dist/fonts/ionicons.woff2
|
216
|
-
- ui/dist/main-
|
217
|
-
- ui/dist/main-
|
216
|
+
- ui/dist/main-f7c7f445c53544d2d7b8.css.gz
|
217
|
+
- ui/dist/main-f7c7f445c53544d2d7b8.js.gz
|
218
218
|
- ui/dist/manifest.json
|
219
219
|
homepage:
|
220
220
|
licenses:
|
Binary file
|