motor-admin 0.1.35 → 0.1.41

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +70 -0
  3. data/app/controllers/motor/alerts_controller.rb +12 -6
  4. data/app/controllers/motor/audits_controller.rb +16 -0
  5. data/app/controllers/motor/configs_controller.rb +1 -0
  6. data/app/controllers/motor/dashboards_controller.rb +4 -0
  7. data/app/controllers/motor/data_controller.rb +2 -57
  8. data/app/controllers/motor/forms_controller.rb +4 -0
  9. data/app/controllers/motor/queries_controller.rb +4 -0
  10. data/app/controllers/motor/resources_controller.rb +1 -0
  11. data/app/controllers/motor/ui_controller.rb +4 -0
  12. data/app/models/motor/alert.rb +4 -2
  13. data/app/models/motor/application_record.rb +10 -0
  14. data/app/models/motor/audit.rb +9 -0
  15. data/app/models/motor/config.rb +2 -0
  16. data/app/models/motor/dashboard.rb +3 -1
  17. data/app/models/motor/form.rb +3 -1
  18. data/app/models/motor/query.rb +4 -1
  19. data/app/models/motor/resource.rb +2 -0
  20. data/app/views/motor/ui/show.html.erb +1 -1
  21. data/config/routes.rb +1 -0
  22. data/lib/generators/motor/templates/install.rb +40 -16
  23. data/lib/motor.rb +12 -3
  24. data/lib/motor/active_record_utils/defined_scopes_extension.rb +1 -1
  25. data/lib/motor/admin.rb +8 -0
  26. data/lib/motor/alerts/persistance.rb +17 -3
  27. data/lib/motor/alerts/scheduler.rb +1 -1
  28. data/lib/motor/api_query/apply_scope.rb +12 -5
  29. data/lib/motor/api_query/sort.rb +1 -1
  30. data/lib/motor/build_schema.rb +3 -3
  31. data/lib/motor/build_schema/find_display_column.rb +4 -4
  32. data/lib/motor/build_schema/load_from_rails.rb +20 -10
  33. data/lib/motor/build_schema/merge_schema_configs.rb +8 -4
  34. data/lib/motor/build_schema/reorder_schema.rb +10 -4
  35. data/lib/motor/configs.rb +17 -0
  36. data/lib/motor/configs/build_configs_hash.rb +83 -0
  37. data/lib/motor/configs/build_ui_app_tag.rb +71 -0
  38. data/lib/motor/configs/load_from_cache.rb +81 -0
  39. data/lib/motor/configs/sync_from_file.rb +36 -0
  40. data/lib/motor/configs/sync_from_hash.rb +126 -0
  41. data/lib/motor/configs/sync_middleware.rb +72 -0
  42. data/lib/motor/configs/sync_with_remote.rb +47 -0
  43. data/lib/motor/configs/write_to_file.rb +36 -0
  44. data/lib/motor/dashboards/persistance.rb +15 -5
  45. data/lib/motor/forms/persistance.rb +15 -5
  46. data/lib/motor/net_http_utils.rb +38 -0
  47. data/lib/motor/queries/persistance.rb +13 -3
  48. data/lib/motor/railtie.rb +11 -0
  49. data/lib/motor/tasks/motor.rake +37 -0
  50. data/lib/motor/version.rb +1 -1
  51. data/ui/dist/{main-03c3b1d3390877206e02.css.gz → main-0ef3be65da8d3b0dbabb.css.gz} +0 -0
  52. data/ui/dist/main-0ef3be65da8d3b0dbabb.js.gz +0 -0
  53. data/ui/dist/manifest.json +5 -5
  54. metadata +33 -5
  55. data/lib/motor/ui_configs.rb +0 -82
  56. data/ui/dist/main-03c3b1d3390877206e02.js.gz +0 -0
@@ -13,9 +13,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
13
13
 
14
14
  t.index :updated_at
15
15
  t.index 'lower(name)',
16
- name: 'motor_queries_lower_name_unique_index',
17
- unique: true,
18
- where: 'deleted_at IS NULL'
16
+ name: 'motor_queries_lower_name_unique_index',
17
+ unique: true,
18
+ where: 'deleted_at IS NULL'
19
19
  end
20
20
 
21
21
  create_table :motor_dashboards, force: true do |t|
@@ -30,9 +30,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
30
30
 
31
31
  t.index :updated_at
32
32
  t.index 'lower(title)',
33
- name: 'motor_dashboards_lower_title_unique_index',
34
- unique: true,
35
- where: 'deleted_at IS NULL'
33
+ name: 'motor_dashboards_lower_title_unique_index',
34
+ unique: true,
35
+ where: 'deleted_at IS NULL'
36
36
  end
37
37
 
38
38
  create_table :motor_forms, force: true do |t|
@@ -49,9 +49,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
49
49
 
50
50
  t.index :updated_at
51
51
  t.index 'lower(name)',
52
- name: 'motor_forms_lower_name_unique_index',
53
- unique: true,
54
- where: 'deleted_at IS NULL'
52
+ name: 'motor_forms_lower_name_unique_index',
53
+ unique: true,
54
+ where: 'deleted_at IS NULL'
55
55
  end
56
56
 
57
57
  create_table :motor_resources, force: true do |t|
@@ -87,9 +87,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
87
87
 
88
88
  t.index :updated_at
89
89
  t.index 'lower(name)',
90
- name: 'motor_alerts_lower_name_unique_index',
91
- unique: true,
92
- where: 'deleted_at IS NULL'
90
+ name: 'motor_alerts_lower_name_unique_index',
91
+ unique: true,
92
+ where: 'deleted_at IS NULL'
93
93
  end
94
94
 
95
95
  create_table :motor_alert_locks, force: true do |t|
@@ -107,8 +107,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
107
107
  t.timestamps
108
108
 
109
109
  t.index 'lower(name)',
110
- name: 'motor_tags_lower_name_unique_index',
111
- unique: true
110
+ name: 'motor_tags_lower_name_unique_index',
111
+ unique: true
112
112
  end
113
113
 
114
114
  create_table :motor_taggable_tags, force: true do |t|
@@ -117,12 +117,36 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
117
117
  t.column :taggable_type, :string, null: false
118
118
 
119
119
  t.index %i[taggable_id taggable_type tag_id],
120
- name: 'motor_polymorphic_association_tag_index',
121
- unique: true
120
+ name: 'motor_polymorphic_association_tag_index',
121
+ unique: true
122
122
  end
123
+
124
+ create_table :motor_audits, force: true do |t|
125
+ t.column :auditable_id, :integer
126
+ t.column :auditable_type, :string
127
+ t.column :associated_id, :integer
128
+ t.column :associated_type, :string
129
+ t.column :user_id, :integer
130
+ t.column :user_type, :string
131
+ t.column :username, :string
132
+ t.column :action, :string
133
+ t.column :audited_changes, :text
134
+ t.column :version, :integer, default: 0
135
+ t.column :comment, :string
136
+ t.column :remote_address, :string
137
+ t.column :request_uuid, :string
138
+ t.column :created_at, :datetime
139
+ end
140
+
141
+ add_index :motor_audits, %i[auditable_type auditable_id version], name: 'motor_auditable_index'
142
+ add_index :motor_audits, %i[associated_type associated_id], name: 'motor_auditable_associated_index'
143
+ add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index'
144
+ add_index :motor_audits, :request_uuid
145
+ add_index :motor_audits, :created_at
123
146
  end
124
147
 
125
148
  def self.down
149
+ drop_table :motor_audits
126
150
  drop_table :motor_alert_locks
127
151
  drop_table :motor_alerts
128
152
  drop_table :motor_taggable_tags
data/lib/motor.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent/executor/fixed_thread_pool'
4
+ require 'concurrent/timer_task'
3
5
  require 'cancancan'
4
6
  require 'ar_lazy_preload'
5
7
  require 'js_regex'
6
8
  require 'fugit'
7
9
  require 'csv'
8
10
  require 'active_record/filter'
11
+ require 'audited'
12
+ require 'uri'
13
+ require 'net/http'
14
+ require 'net/https'
9
15
 
10
16
  module Motor
11
17
  PATH = Pathname.new(__dir__)
@@ -17,7 +23,8 @@ module Motor
17
23
  Dir[PATH.join('./motor/**/*.rb')].each do |f|
18
24
  next if f.ends_with?('alerts/scheduler.rb')
19
25
  next if f.ends_with?('alerts/scheduled_alerts_cache.rb')
20
- next if f.ends_with?('ui_configs.rb')
26
+ next if f.ends_with?('configs/load_from_cache.rb')
27
+ next if f.ends_with?('configs/sync_from_file.rb')
21
28
 
22
29
  load f
23
30
  end
@@ -46,13 +53,15 @@ end
46
53
  require 'motor/version'
47
54
  require 'motor/admin'
48
55
  require 'motor/assets'
56
+ require 'motor/active_record_utils'
49
57
  require 'motor/build_schema'
50
58
  require 'motor/api_query'
51
59
  require 'motor/tags'
52
- require 'motor/ui_configs'
60
+ require 'motor/configs'
53
61
  require 'motor/queries'
54
62
  require 'motor/dashboards'
55
63
  require 'motor/forms'
56
64
  require 'motor/alerts'
57
65
  require 'motor/hash_serializer'
58
- require 'motor/active_record_utils'
66
+ require 'motor/net_http_utils'
67
+ require 'motor/railtie'
@@ -4,7 +4,7 @@ module Motor
4
4
  module ActiveRecordUtils
5
5
  module DefinedScopesExtension
6
6
  def scope(name, _body)
7
- (@__scopes__ ||= []) << name
7
+ (@__scopes__ ||= []) << name.to_sym
8
8
 
9
9
  super
10
10
  end
data/lib/motor/admin.rb CHANGED
@@ -28,6 +28,14 @@ module Motor
28
28
  end
29
29
  end
30
30
 
31
+ initializer 'motor.configs.sync_middleware' do
32
+ next if Motor::Configs::SYNC_ACCESS_KEY.blank?
33
+
34
+ require 'motor/configs/sync_middleware'
35
+
36
+ Rails.application.config.middleware.insert_after(Rails::Rack::Logger, Motor::Configs::SyncMiddleware)
37
+ end
38
+
31
39
  initializer 'motor.filter_params' do
32
40
  Rails.application.config.filter_parameters += %i[io]
33
41
  end
@@ -43,30 +43,44 @@ module Motor
43
43
  retry
44
44
  end
45
45
 
46
- def update_from_params!(alert, params)
46
+ def update_from_params!(alert, params, force_replace: false)
47
+ tag_ids = alert.tags.ids
48
+
47
49
  alert = assign_attributes(alert, params)
48
50
 
49
- raise NameAlreadyExists if name_already_exists?(alert)
51
+ raise NameAlreadyExists if !force_replace && name_already_exists?(alert)
50
52
  raise InvalidInterval unless alert.cron
51
53
 
52
54
  ApplicationRecord.transaction do
55
+ archive_with_existing_name(alert) if force_replace
56
+
53
57
  alert.save!
54
58
  end
55
59
 
56
- alert.tags.reload
60
+ alert.touch if tags_changed?(tag_ids, alert) && params[:updated_at].blank?
57
61
 
58
62
  alert
59
63
  rescue ActiveRecord::RecordNotUnique
60
64
  retry
61
65
  end
62
66
 
67
+ def tags_changed?(previous_ids, alert)
68
+ previous_ids.sort != alert.tags.reload.ids.sort
69
+ end
70
+
63
71
  def assign_attributes(alert, params)
64
72
  alert.assign_attributes(params.slice(*ALERT_ATTRIBUTES))
65
73
  alert.preferences[:interval] = normalize_interval(alert.preferences[:interval])
74
+ alert.updated_at = [params[:updated_at], Time.current].min if params[:updated_at].present?
66
75
 
67
76
  Motor::Tags.assign_tags(alert, params[:tags])
68
77
  end
69
78
 
79
+ def archive_with_existing_name(alert)
80
+ Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
81
+ .update_all(deleted_at: Time.current)
82
+ end
83
+
70
84
  def normalize_interval(interval)
71
85
  interval.to_s.gsub(NORMALIZE_INTERVAL_REGEXP, 'every ')
72
86
  end
@@ -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
@@ -14,15 +14,15 @@ module Motor
14
14
  fname
15
15
  lname
16
16
  sname
17
+ company
18
+ domain
19
+ title
17
20
  phone
18
21
  phone_number
19
22
  email
20
- domain
21
23
  phone
22
- company
23
24
  filename
24
25
  file_name
25
- title
26
26
  url
27
27
  make
28
28
  brand
@@ -42,7 +42,7 @@ module Motor
42
42
  end
43
43
 
44
44
  def select_column_name(column_names)
45
- name = column_names.find { |column_name| column_name.in?(DISPLAY_NAMES) }
45
+ name = DISPLAY_NAMES.find { |column_name| column_name.in?(column_names) }
46
46
  name ||= column_names.find { |column_name| column_name.match?(DISPLAY_NAME_REGEXP) }
47
47
 
48
48
  name
@@ -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
 
@@ -11,7 +12,7 @@ module Motor
11
12
  models.map do |model|
12
13
  build_model_schema(model)
13
14
  rescue StandardError, NotImplementedError => e
14
- Rails.logger.error(e)
15
+ Rails.logger.error(e) if model.name != 'Audited::Audit'
15
16
 
16
17
  next
17
18
  end.compact
@@ -20,10 +21,10 @@ 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
+ models -= [Motor::Audit]
27
28
  models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
28
29
  models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
29
30
  models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
@@ -31,12 +32,6 @@ module Motor
31
32
  models
32
33
  end
33
34
 
34
- def load_descendants(model)
35
- model.descendants + model.descendants.flat_map do |klass|
36
- load_descendants(klass)
37
- end
38
- end
39
-
40
35
  def build_model_schema(model)
41
36
  model_name = model.name
42
37
 
@@ -91,10 +86,13 @@ module Motor
91
86
  end
92
87
 
93
88
  def build_table_column(column, model, default_attrs)
89
+ is_enum = model.defined_enums[column.name]
90
+
94
91
  {
95
92
  name: column.name,
96
93
  display_name: column.name.humanize,
97
- 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?,
98
96
  access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
99
97
  default_value: default_attrs[column.name],
100
98
  validators: fetch_validators(model, column.name),
@@ -180,6 +178,10 @@ module Motor
180
178
  []
181
179
  end
182
180
 
181
+ enum = model.defined_enums[column_name]
182
+
183
+ validators += [{ includes: enum.keys }] if enum
184
+
183
185
  validators += model.validators_on(column_name).map do |validator|
184
186
  build_validator_hash(validator)
185
187
  end.compact
@@ -209,6 +211,14 @@ module Motor
209
211
  else
210
212
  Rails.application.eager_load!
211
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
212
222
  end
213
223
  end
214
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