motor-admin 0.1.36 → 0.1.42

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/motor/alerts_controller.rb +12 -6
  3. data/app/controllers/motor/configs_controller.rb +1 -0
  4. data/app/controllers/motor/dashboards_controller.rb +4 -0
  5. data/app/controllers/motor/forms_controller.rb +4 -0
  6. data/app/controllers/motor/queries_controller.rb +4 -0
  7. data/app/controllers/motor/resources_controller.rb +1 -0
  8. data/app/controllers/motor/ui_controller.rb +4 -0
  9. data/app/models/motor/alert.rb +3 -3
  10. data/app/models/motor/application_record.rb +10 -0
  11. data/app/models/motor/config.rb +1 -1
  12. data/app/models/motor/dashboard.rb +2 -2
  13. data/app/models/motor/form.rb +2 -2
  14. data/app/models/motor/query.rb +3 -2
  15. data/app/models/motor/resource.rb +1 -1
  16. data/app/views/motor/ui/show.html.erb +1 -1
  17. data/lib/generators/motor/templates/install.rb +13 -13
  18. data/lib/motor.rb +11 -4
  19. data/lib/motor/active_record_utils/defined_scopes_extension.rb +1 -1
  20. data/lib/motor/admin.rb +8 -0
  21. data/lib/motor/alerts/persistance.rb +17 -3
  22. data/lib/motor/alerts/scheduler.rb +1 -1
  23. data/lib/motor/api_query/apply_scope.rb +12 -5
  24. data/lib/motor/api_query/sort.rb +1 -1
  25. data/lib/motor/build_schema.rb +3 -3
  26. data/lib/motor/build_schema/find_display_column.rb +31 -29
  27. data/lib/motor/build_schema/load_from_rails.rb +18 -9
  28. data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
  29. data/lib/motor/build_schema/reorder_schema.rb +10 -4
  30. data/lib/motor/configs.rb +17 -0
  31. data/lib/motor/configs/build_configs_hash.rb +83 -0
  32. data/lib/motor/configs/build_ui_app_tag.rb +71 -0
  33. data/lib/motor/configs/load_from_cache.rb +81 -0
  34. data/lib/motor/configs/sync_from_file.rb +36 -0
  35. data/lib/motor/configs/sync_from_hash.rb +126 -0
  36. data/lib/motor/configs/sync_middleware.rb +72 -0
  37. data/lib/motor/configs/sync_with_remote.rb +47 -0
  38. data/lib/motor/configs/write_to_file.rb +36 -0
  39. data/lib/motor/dashboards/persistance.rb +15 -5
  40. data/lib/motor/forms/persistance.rb +15 -5
  41. data/lib/motor/net_http_utils.rb +38 -0
  42. data/lib/motor/queries/persistance.rb +13 -3
  43. data/lib/motor/railtie.rb +11 -0
  44. data/lib/motor/tasks/motor.rake +37 -0
  45. data/lib/motor/version.rb +1 -1
  46. data/ui/dist/{main-358ea31cd7020f915067.css.gz → main-05401628fabd32884fa6.css.gz} +0 -0
  47. data/ui/dist/main-05401628fabd32884fa6.js.gz +0 -0
  48. data/ui/dist/manifest.json +5 -5
  49. metadata +16 -6
  50. data/lib/motor/audited_utils.rb +0 -15
  51. data/lib/motor/ui_configs.rb +0 -82
  52. data/ui/dist/main-358ea31cd7020f915067.js.gz +0 -0
@@ -22,7 +22,7 @@ module Motor
22
22
  Motor::AlertSendingJob.perform_later(alert).job_id
23
23
  end
24
24
  rescue StandardError => e
25
- Rials.logger.error(e)
25
+ Rails.logger.error(e)
26
26
  end
27
27
  end
28
28
  end
@@ -13,13 +13,20 @@ module Motor
13
13
  if rel.klass.defined_scopes.include?(scope_symbol)
14
14
  rel.public_send(scope_symbol)
15
15
  else
16
- configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
17
- scope_configs = configs.preferences[:scopes].find { |s| s[:name] == scope }
16
+ apply_filter_scope(rel, scope)
17
+ end
18
+ end
18
19
 
19
- return rel unless scope_configs
20
+ def apply_filter_scope(rel, scope)
21
+ configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
20
22
 
21
- ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
22
- end
23
+ return rel unless configs
24
+
25
+ scope_configs = configs.preferences[:scopes].find { |s| s[:name] == scope }
26
+
27
+ return rel unless scope_configs
28
+
29
+ ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
23
30
  end
24
31
  end
25
32
  end
@@ -13,7 +13,7 @@ module Motor
13
13
  arel_order = build_arel_order(rel.klass, param)
14
14
  join_params = build_join_params(rel.klass, param)
15
15
 
16
- rel.order(arel_order).left_joins(join_params)
16
+ rel.reorder(arel_order).left_joins(join_params)
17
17
  end
18
18
 
19
19
  def build_join_params(_model, param)
@@ -58,11 +58,11 @@ module Motor
58
58
 
59
59
  module_function
60
60
 
61
- def call
61
+ def call(cache_keys = {})
62
62
  schema = LoadFromRails.call
63
- schema = MergeSchemaConfigs.call(schema)
63
+ schema = MergeSchemaConfigs.call(schema, cache_keys)
64
64
 
65
- ReorderSchema.call(schema)
65
+ ReorderSchema.call(schema, cache_keys)
66
66
  end
67
67
  end
68
68
  end
@@ -3,35 +3,37 @@
3
3
  module Motor
4
4
  module BuildSchema
5
5
  module FindDisplayColumn
6
- DISPLAY_NAMES = %w[
7
- name
8
- full_name
9
- fullname
10
- last_name
11
- lastname
12
- first_name
13
- firstname
14
- fname
15
- lname
16
- sname
17
- phone
18
- phone_number
19
- email
20
- domain
21
- phone
22
- company
23
- filename
24
- file_name
25
- title
26
- url
27
- make
28
- brand
29
- manufacturer
30
- model
31
- address
32
- ].freeze
33
-
34
- DISPLAY_NAME_REGEXP = Regexp.new(Regexp.union(DISPLAY_NAMES).source, Regexp::IGNORECASE)
6
+ DISPLAY_NAMES = Set.new(
7
+ %w[
8
+ name
9
+ full_name
10
+ fullname
11
+ last_name
12
+ lastname
13
+ first_name
14
+ firstname
15
+ fname
16
+ lname
17
+ sname
18
+ company
19
+ domain
20
+ title
21
+ phone
22
+ phone_number
23
+ email
24
+ phone
25
+ filename
26
+ file_name
27
+ url
28
+ make
29
+ brand
30
+ manufacturer
31
+ model
32
+ address
33
+ ]
34
+ ).freeze
35
+
36
+ DISPLAY_NAME_REGEXP = Regexp.new(Regexp.union(DISPLAY_NAMES.to_a).source, Regexp::IGNORECASE)
35
37
 
36
38
  module_function
37
39
 
@@ -4,6 +4,7 @@ module Motor
4
4
  module BuildSchema
5
5
  module LoadFromRails
6
6
  MUTEX = Mutex.new
7
+ UNIFIED_TYPES = ActiveRecordUtils::Types::UNIFIED_TYPES
7
8
 
8
9
  module_function
9
10
 
@@ -20,8 +21,7 @@ module Motor
20
21
  def models
21
22
  eager_load_models!
22
23
 
23
- models = load_descendants(ActiveRecord::Base).uniq
24
- models = models.reject(&:abstract_class)
24
+ models = ActiveRecord::Base.descendants.reject(&:abstract_class)
25
25
 
26
26
  models -= Motor::ApplicationRecord.descendants
27
27
  models -= [Motor::Audit]
@@ -32,12 +32,6 @@ module Motor
32
32
  models
33
33
  end
34
34
 
35
- def load_descendants(model)
36
- model.descendants + model.descendants.flat_map do |klass|
37
- load_descendants(klass)
38
- end
39
- end
40
-
41
35
  def build_model_schema(model)
42
36
  model_name = model.name
43
37
 
@@ -92,10 +86,13 @@ module Motor
92
86
  end
93
87
 
94
88
  def build_table_column(column, model, default_attrs)
89
+ is_enum = model.defined_enums[column.name]
90
+
95
91
  {
96
92
  name: column.name,
97
93
  display_name: column.name.humanize,
98
- column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
94
+ column_type: is_enum ? 'string' : UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
95
+ is_array: column.array?,
99
96
  access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
100
97
  default_value: default_attrs[column.name],
101
98
  validators: fetch_validators(model, column.name),
@@ -181,6 +178,10 @@ module Motor
181
178
  []
182
179
  end
183
180
 
181
+ enum = model.defined_enums[column_name]
182
+
183
+ validators += [{ includes: enum.keys }] if enum
184
+
184
185
  validators += model.validators_on(column_name).map do |validator|
185
186
  build_validator_hash(validator)
186
187
  end.compact
@@ -210,6 +211,14 @@ module Motor
210
211
  else
211
212
  Rails.application.eager_load!
212
213
  end
214
+
215
+ ActiveRecord::Base.descendants.each do |model|
216
+ model.reflections.each do |_, ref|
217
+ ref.klass
218
+ rescue StandardError
219
+ next
220
+ end
221
+ end
213
222
  end
214
223
  end
215
224
  end
@@ -12,9 +12,10 @@ module Motor
12
12
  module_function
13
13
 
14
14
  # @param schema [Array<HashWithIndifferentAccess>]
15
+ # @param cache_keys [Hash]
15
16
  # @return [Array<HashWithIndifferentAccess>]
16
- def call(schema)
17
- configs = load_configs
17
+ def call(schema, cache_keys = {})
18
+ configs = load_configs(cache_keys)
18
19
 
19
20
  schema.map do |model|
20
21
  merge_model(model, configs.fetch(model[:name], {}))
@@ -120,9 +121,12 @@ module Motor
120
121
  end.compact
121
122
  end
122
123
 
124
+ # @param cache_keys [Hash]
123
125
  # @return [HashWithIndifferentAccess<String, HashWithIndifferentAccess>]
124
- def load_configs
125
- Motor::Resource.all.each_with_object(HashWithIndifferentAccess.new) do |resource, acc|
126
+ def load_configs(cache_keys)
127
+ resources = Motor::Configs::LoadFromCache.load_resources(cache_key: cache_keys[:resources])
128
+
129
+ resources.each_with_object(HashWithIndifferentAccess.new) do |resource, acc|
126
130
  acc[resource.name] = resource.preferences
127
131
  end
128
132
  end
@@ -16,10 +16,11 @@ module Motor
16
16
 
17
17
  module_function
18
18
 
19
+ # @param cache_keys [Hash]
19
20
  # @param schema [Array<HashWithIndifferentAccess>]
20
21
  # @return [Array<HashWithIndifferentAccess>]
21
- def call(schema)
22
- configs = load_configs
22
+ def call(schema, cache_keys = {})
23
+ configs = load_configs(cache_keys)
23
24
 
24
25
  schema = sort_by_name(schema, configs['resources.order'])
25
26
 
@@ -64,6 +65,8 @@ module Motor
64
65
  end
65
66
  end
66
67
 
68
+ # @param columns [Array<HashWithIndifferentAccess>]
69
+ # @return [Array<HashWithIndifferentAccess>]
67
70
  def sort_columns(columns)
68
71
  columns.each_with_object([]) do |column, acc|
69
72
  weight = COLUMNS_DEFAULT_ORDER_WEIGHTS.fetch(column[:name], COLUMNS_DEFAULT_ORDER_WEIGHT)
@@ -72,9 +75,12 @@ module Motor
72
75
  end.flatten.compact
73
76
  end
74
77
 
78
+ # @param cache_keys [Hash]
75
79
  # @return [Hash<String, HashWithIndifferentAccess>]
76
- def load_configs
77
- Motor::Config.all.each_with_object({}) do |config, acc|
80
+ def load_configs(cache_keys = {})
81
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
82
+
83
+ configs.each_with_object({}) do |config, acc|
78
84
  acc[config.key] = config.value
79
85
  end
80
86
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ FILE_PATH = 'config/motor.yml'
6
+ SYNC_API_PATH = '/motor_configs_sync'
7
+ SYNC_ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
8
+ end
9
+ end
10
+
11
+ require_relative './configs/load_from_cache'
12
+ require_relative './configs/build_ui_app_tag'
13
+ require_relative './configs/build_configs_hash'
14
+ require_relative './configs/write_to_file'
15
+ require_relative './configs/sync_from_hash'
16
+ require_relative './configs/sync_from_file'
17
+ require_relative './configs/sync_with_remote'
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module BuildConfigsHash
6
+ module_function
7
+
8
+ def call
9
+ cache_keys = LoadFromCache.load_cache_keys
10
+
11
+ normalize_hash(
12
+ engine_version: Motor::VERSION,
13
+ file_version: cache_keys.values.compact.max.to_time,
14
+ resources: build_resources_hash(cache_keys[:resources]),
15
+ configs: build_configs_hash(cache_keys[:configs]),
16
+ queries: build_queries_hash(cache_keys[:queries]),
17
+ dashboards: build_dashboards_hash(cache_keys[:dashboards]),
18
+ forms: build_forms_hash(cache_keys[:forms]),
19
+ alerts: build_alerts_hash(cache_keys[:alerts])
20
+ )
21
+ end
22
+
23
+ def build_queries_hash(cache_key = nil)
24
+ Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key).sort_by(&:id).map do |query|
25
+ query.slice(%i[id name sql_body description preferences])
26
+ .merge(tags: query.tags.map(&:name), updated_at: query.updated_at.to_time)
27
+ end
28
+ end
29
+
30
+ def build_dashboards_hash(cache_key = nil)
31
+ Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key).sort_by(&:id).map do |dashboard|
32
+ dashboard.slice(%i[id title description preferences])
33
+ .merge(tags: dashboard.tags.map(&:name), updated_at: dashboard.updated_at.to_time)
34
+ end
35
+ end
36
+
37
+ def build_alerts_hash(cache_key = nil)
38
+ Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key).sort_by(&:id).map do |alert|
39
+ alert.slice(%i[id name query_id to_emails is_enabled description preferences])
40
+ .merge(tags: alert.tags.map(&:name), updated_at: alert.updated_at.to_time)
41
+ end
42
+ end
43
+
44
+ def build_forms_hash(cache_key = nil)
45
+ Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key).sort_by(&:id).map do |form|
46
+ form.slice(%i[id name http_method api_path description preferences])
47
+ .merge(tags: form.tags.map(&:name), updated_at: form.updated_at.to_time)
48
+ end
49
+ end
50
+
51
+ def build_configs_hash(cache_key = nil)
52
+ Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key).sort_by(&:key).map do |config|
53
+ {
54
+ key: config.key,
55
+ value: config.value,
56
+ updated_at: config.updated_at.to_time
57
+ }
58
+ end
59
+ end
60
+
61
+ def build_resources_hash(cache_key = nil)
62
+ Motor::Configs::LoadFromCache.load_resources(cache_key: cache_key).sort_by(&:name).map do |resource|
63
+ {
64
+ name: resource.name,
65
+ preferences: resource.preferences,
66
+ updated_at: resource.updated_at.to_time
67
+ }
68
+ end
69
+ end
70
+
71
+ def normalize_hash(value)
72
+ case value
73
+ when Hash, HashWithIndifferentAccess
74
+ value.to_h.stringify_keys.transform_values { |v| normalize_hash(v) }
75
+ when Array
76
+ value.map { |e| normalize_hash(e) }
77
+ else
78
+ value
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module BuildUiAppTag
6
+ CACHE_STORE =
7
+ if Motor.development?
8
+ ActiveSupport::Cache::NullStore.new
9
+ else
10
+ ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
11
+ end
12
+
13
+ module_function
14
+
15
+ def call
16
+ cache_keys = LoadFromCache.load_cache_keys
17
+
18
+ CACHE_STORE.fetch(cache_keys.hash) do
19
+ CACHE_STORE.clear
20
+
21
+ Motor::ApplicationController.helpers.content_tag(
22
+ :div, '', id: 'app', data: build_data(cache_keys)
23
+ )
24
+ end
25
+ end
26
+
27
+ # @return [Hash]
28
+ def build_data(cache_keys = {})
29
+ {
30
+ base_path: Motor::Admin.routes.url_helpers.motor_path,
31
+ schema: Motor::BuildSchema.call(cache_keys),
32
+ header_links: header_links_data_hash(cache_keys[:configs]),
33
+ queries: queries_data_hash(cache_keys[:queries]),
34
+ dashboards: dashboards_data_hash(cache_keys[:dashboards]),
35
+ alerts: alerts_data_hash(cache_keys[:alerts]),
36
+ forms: forms_data_hash(cache_keys[:forms])
37
+ }
38
+ end
39
+
40
+ def header_links_data_hash(cache_key = nil)
41
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_key)
42
+
43
+ configs.find { |c| c.key == 'header.links' }&.value || []
44
+ end
45
+
46
+ def queries_data_hash(cache_key = nil)
47
+ Motor::Configs::LoadFromCache.load_queries(cache_key: cache_key)
48
+ .as_json(only: %i[id name updated_at],
49
+ include: { tags: { only: %i[id name] } })
50
+ end
51
+
52
+ def dashboards_data_hash(cache_key = nil)
53
+ Motor::Configs::LoadFromCache.load_dashboards(cache_key: cache_key)
54
+ .as_json(only: %i[id title updated_at],
55
+ include: { tags: { only: %i[id name] } })
56
+ end
57
+
58
+ def alerts_data_hash(cache_key = nil)
59
+ Motor::Configs::LoadFromCache.load_alerts(cache_key: cache_key)
60
+ .as_json(only: %i[id name is_enabled updated_at],
61
+ include: { tags: { only: %i[id name] } })
62
+ end
63
+
64
+ def forms_data_hash(cache_key = nil)
65
+ Motor::Configs::LoadFromCache.load_forms(cache_key: cache_key)
66
+ .as_json(only: %i[id name updated_at],
67
+ include: { tags: { only: %i[id name] } })
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module Configs
5
+ module LoadFromCache
6
+ CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
7
+
8
+ module_function
9
+
10
+ def call
11
+ cache_keys = load_cache_keys
12
+
13
+ {
14
+ configs: load_configs(cache_key: cache_keys[:configs]),
15
+ resources: load_resources(cache_key: cache_keys[:resources]),
16
+ queries: load_queries(cache_key: cache_keys[:queries]),
17
+ dashboards: load_dashboards(cache_key: cache_keys[:dashboards]),
18
+ alerts: load_alerts(cache_key: cache_keys[:alerts]),
19
+ forms: load_forms(cache_key: cache_keys[:forms])
20
+ }
21
+ end
22
+
23
+ def load_configs(cache_key: nil)
24
+ maybe_fetch_from_cache('configs', cache_key) do
25
+ Motor::Config.all.load
26
+ end
27
+ end
28
+
29
+ def load_resources(cache_key: nil)
30
+ maybe_fetch_from_cache('resources', cache_key) do
31
+ Motor::Resource.all.load
32
+ end
33
+ end
34
+
35
+ def load_queries(cache_key: nil)
36
+ maybe_fetch_from_cache('queries', cache_key) do
37
+ Motor::Query.all.active.preload(:tags).load
38
+ end
39
+ end
40
+
41
+ def load_dashboards(cache_key: nil)
42
+ maybe_fetch_from_cache('dashboards', cache_key) do
43
+ Motor::Dashboard.all.active.preload(:tags).load
44
+ end
45
+ end
46
+
47
+ def load_alerts(cache_key: nil)
48
+ maybe_fetch_from_cache('alerts', cache_key) do
49
+ Motor::Alert.all.active.preload(:tags).load
50
+ end
51
+ end
52
+
53
+ def load_forms(cache_key: nil)
54
+ maybe_fetch_from_cache('forms', cache_key) do
55
+ Motor::Form.all.active.preload(:tags).load
56
+ end
57
+ end
58
+
59
+ def maybe_fetch_from_cache(type, cache_key, &block)
60
+ return block.call unless cache_key
61
+
62
+ CACHE_STORE.fetch(type + cache_key.to_s, &block)
63
+ end
64
+
65
+ def load_cache_keys
66
+ ActiveRecord::Base.connection.execute(
67
+ "(#{
68
+ [
69
+ Motor::Config.select("'configs', MAX(updated_at)").to_sql,
70
+ Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
71
+ Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
72
+ Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
73
+ Motor::Query.select("'queries', MAX(updated_at)").to_sql,
74
+ Motor::Form.select("'forms', MAX(updated_at)").to_sql
75
+ ].join(') UNION (')
76
+ })"
77
+ ).to_a.map(&:values).to_h.with_indifferent_access
78
+ end
79
+ end
80
+ end
81
+ end