motor-admin 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/motor/active_storage_attachments_controller.rb +28 -0
  3. data/app/controllers/motor/alerts_controller.rb +2 -0
  4. data/app/controllers/motor/configs_controller.rb +2 -0
  5. data/app/controllers/motor/dashboards_controller.rb +2 -0
  6. data/app/controllers/motor/data_controller.rb +3 -1
  7. data/app/controllers/motor/forms_controller.rb +2 -0
  8. data/app/controllers/motor/queries_controller.rb +2 -0
  9. data/app/controllers/motor/resources_controller.rb +2 -0
  10. data/app/controllers/motor/run_queries_controller.rb +2 -0
  11. data/app/controllers/motor/send_alerts_controller.rb +2 -0
  12. data/config/routes.rb +1 -0
  13. data/lib/motor.rb +1 -0
  14. data/lib/motor/active_record_utils.rb +2 -0
  15. data/lib/motor/active_record_utils/active_storage_links_extension.rb +15 -0
  16. data/lib/motor/active_record_utils/defined_scopes_extension.rb +19 -0
  17. data/lib/motor/admin.rb +15 -3
  18. data/lib/motor/api_query.rb +2 -0
  19. data/lib/motor/api_query/apply_scope.rb +26 -0
  20. data/lib/motor/build_schema.rb +60 -7
  21. data/lib/motor/build_schema/active_storage_attachment_schema.rb +84 -0
  22. data/lib/motor/build_schema/load_from_rails.rb +30 -56
  23. data/lib/motor/build_schema/merge_schema_configs.rb +13 -6
  24. data/lib/motor/build_schema/persist_resource_configs.rb +37 -1
  25. data/lib/motor/build_schema/reorder_schema.rb +3 -1
  26. data/lib/motor/version.rb +1 -1
  27. data/ui/dist/main-8f36a2746422efd1e8a8.css.gz +0 -0
  28. data/ui/dist/main-8f36a2746422efd1e8a8.js.gz +0 -0
  29. data/ui/dist/manifest.json +5 -5
  30. metadata +9 -4
  31. data/ui/dist/main-4bdedd7fcff1351efaf6.css.gz +0 -0
  32. data/ui/dist/main-4bdedd7fcff1351efaf6.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7905a9e6d289a069f10e5980bd861b2aec8da549e397bcd3b666c8fa8c75479
4
- data.tar.gz: 233b3747ee84e99c6c27ce72c9783c549d70a1ed12b568896bf8bef26d702ff5
3
+ metadata.gz: a9dca132c3a2481191219a518fe66e581988245cbe3212fb105c220285d1a5fa
4
+ data.tar.gz: e2cb07a305545d2fb01e0c94101f6bdabd705628b8043b8a8a40b320427bc7e9
5
5
  SHA512:
6
- metadata.gz: f62a5bfc817a1e339774277027a18e0c28f147fbd2c7078bfd013ec72a60b5aafebc28e065e064d0bf60cc63e18f48e7d7424ce3ba0c484d9489a107528cc7bf
7
- data.tar.gz: '01830208623fa1a67ea1c2e681a89b574c7c0ec62ff30ec2e3973918779e5c52913a81494ce0c9998dfd6d4a1cb3825cecab5c3ea1915d46b6a4eb922365ab9a'
6
+ metadata.gz: dcce9c0ad1fff7e751a91ab366174174421f038ee3ed848e32618d663d1955221295e0cc79904a0702b939630ab4e6b6873b2b141e01b6ac4bc8361233ef265f
7
+ data.tar.gz: b20916892df27d1a6c5eef23452824034d0559e5954fe44726034269496409358a3755f3b53dcd489d61953743b511e66f4f93c4bea2ea894f6dae856575492b
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ActiveStorageAttachmentsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :attachment, class: 'ActiveStorage::Attachment', parent: false
8
+
9
+ def create
10
+ if @attachment.record.respond_to?("#{@attachment.name}_attachment=") || @attachment.record.respond_to?("#{@attachment.name}_attachments=")
11
+ @attachment.record.public_send(@attachment.name).attach(
12
+ io: StringIO.new(params.dig(:data, :file, :io).to_s.encode('ISO-8859-1')),
13
+ filename: params.dig(:data, :file, :filename)
14
+ )
15
+
16
+ head :ok
17
+ else
18
+ head :unprocessable_entity
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def attachment_params
25
+ params.require(:data).except(:file).permit!
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class AlertsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :alert, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_alert, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class ConfigsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource
6
8
 
7
9
  def index
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class DashboardsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :dashboard, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_dashboard, only: :create
@@ -4,6 +4,8 @@ module Motor
4
4
  class DataController < ApiBaseController
5
5
  INSTANCE_VARIABLE_NAME = 'resource'
6
6
 
7
+ wrap_parameters :data, except: %i[include fields]
8
+
7
9
  before_action :load_and_authorize_resource
8
10
  before_action :load_and_authorize_association
9
11
 
@@ -96,7 +98,7 @@ module Motor
96
98
  end
97
99
 
98
100
  def resource_params
99
- params.fetch(:data, {}).except(resource_class.primary_key).permit!
101
+ params.require(:data).except(resource_class.primary_key).permit!
100
102
  end
101
103
  end
102
104
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class FormsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :form, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_form, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class QueriesController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :query, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_query, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class ResourcesController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource
6
8
 
7
9
  def index
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class RunQueriesController < ApiBaseController
5
+ wrap_parameters :data
6
+
5
7
  load_and_authorize_resource :query, only: :show, parent: false
6
8
 
7
9
  before_action :build_query, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class SendAlertsController < ApiBaseController
5
+ wrap_parameters :data
6
+
5
7
  before_action :build_alert, only: :create
6
8
  authorize_resource :alert, only: :create
7
9
 
data/config/routes.rb CHANGED
@@ -13,6 +13,7 @@ Motor::Admin.routes.draw do
13
13
  resources :dashboards, only: %i[index show create update destroy]
14
14
  resources :forms, only: %i[index show create update destroy]
15
15
  resources :alerts, only: %i[index show create update destroy]
16
+ resources :active_storage_attachments, only: %i[create], path: 'data/active_storage__attachments'
16
17
  resource :schema, only: %i[show update]
17
18
  resources :resources, path: '/data/:resource',
18
19
  only: %i[index show update create destroy],
data/lib/motor.rb CHANGED
@@ -6,6 +6,7 @@ require 'js_regex'
6
6
  require 'fugit'
7
7
  require 'csv'
8
8
  require 'active_record/filter'
9
+ require 'base64'
9
10
 
10
11
  module Motor
11
12
  PATH = Pathname.new(__dir__)
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative './active_record_utils/types'
4
4
  require_relative './active_record_utils/fetch_methods'
5
+ require_relative './active_record_utils/defined_scopes_extension'
6
+ require_relative './active_record_utils/active_storage_links_extension'
5
7
 
6
8
  module ActiveRecordUtils
7
9
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module ActiveStorageLinksExtension
6
+ def path
7
+ Rails.application.routes.url_helpers.rails_blob_path(self)
8
+ end
9
+
10
+ def url
11
+ Rails.application.routes.url_helpers.url_for(self)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module DefinedScopesExtension
6
+ def scope(name, _body)
7
+ (@__scopes__ ||= []) << name
8
+
9
+ super
10
+ end
11
+
12
+ def defined_scopes
13
+ @__scopes__ || []
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Base.extend(Motor::ActiveRecordUtils::DefinedScopesExtension)
data/lib/motor/admin.rb CHANGED
@@ -2,11 +2,23 @@
2
2
 
3
3
  module Motor
4
4
  class Admin < ::Rails::Engine
5
+ initializer 'motor.filter_params' do
6
+ Rails.application.config.filter_parameters += %i[io]
7
+ end
8
+
5
9
  initializer 'motor.alerts.scheduler' do
6
- next if defined?(Sidekiq) && Sidekiq.server?
10
+ config.after_initialize do |_app|
11
+ next if defined?(Sidekiq) && Sidekiq.server?
12
+
13
+ Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
14
+ Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
15
+ end
16
+ end
7
17
 
8
- Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
9
- Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
18
+ initializer 'motor.active_storage.extensions' do
19
+ ActiveSupport.on_load(:active_storage_attachment) do
20
+ ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
21
+ end
10
22
  end
11
23
  end
12
24
  end
@@ -4,6 +4,7 @@ require_relative './api_query/sort'
4
4
  require_relative './api_query/paginate'
5
5
  require_relative './api_query/filter'
6
6
  require_relative './api_query/search'
7
+ require_relative './api_query/apply_scope'
7
8
  require_relative './api_query/build_meta'
8
9
  require_relative './api_query/build_json'
9
10
 
@@ -15,6 +16,7 @@ module Motor
15
16
  rel = ApiQuery::Sort.call(rel, params[:sort])
16
17
  rel = ApiQuery::Paginate.call(rel, params[:page])
17
18
  rel = ApiQuery::Filter.call(rel, params[:filter])
19
+ rel = ApiQuery::ApplyScope.call(rel, params[:scope])
18
20
 
19
21
  ApiQuery::Search.call(rel, params[:q] || params[:search] || params[:query])
20
22
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ApiQuery
5
+ module ApplyScope
6
+ module_function
7
+
8
+ def call(rel, scope)
9
+ return rel if scope.blank?
10
+
11
+ scope_symbol = scope.to_sym
12
+
13
+ if rel.klass.defined_scopes.include?(scope_symbol)
14
+ rel.public_send(scope_symbol)
15
+ else
16
+ configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
17
+ scope_configs = configs.preferences[:scopes].find { |s| s[:name] == scope }
18
+
19
+ return rel unless scope_configs
20
+
21
+ ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,16 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './build_schema/load_from_rails'
4
- require_relative './build_schema/find_display_column'
5
- require_relative './build_schema/persist_resource_configs'
6
- require_relative './build_schema/reorder_schema'
7
- require_relative './build_schema/merge_schema_configs'
8
- require_relative './build_schema/utils'
9
-
10
3
  module Motor
11
4
  module BuildSchema
5
+ module ColumnAccessTypes
6
+ ALL = [
7
+ READ_ONLY = 'read_only',
8
+ WRITE_ONLY = 'write_only',
9
+ READ_WRITE = 'read_write',
10
+ HIDDEN = 'hidden'
11
+ ].freeze
12
+ end
13
+
12
14
  SEARCHABLE_COLUMN_TYPES = %i[citext text string bitstring].freeze
13
15
 
16
+ COLUMN_NAME_ACCESS_TYPES = {
17
+ id: ColumnAccessTypes::READ_ONLY,
18
+ created_at: ColumnAccessTypes::READ_ONLY,
19
+ updated_at: ColumnAccessTypes::READ_ONLY,
20
+ deleted_at: ColumnAccessTypes::READ_ONLY
21
+ }.with_indifferent_access.freeze
22
+
23
+ DEFAULT_SCOPE_TYPE = 'default'
24
+
25
+ DEFAULT_ACTIONS = [
26
+ {
27
+ name: 'create',
28
+ display_name: 'Create',
29
+ action_type: 'default',
30
+ preferences: {},
31
+ visible: true
32
+ },
33
+ {
34
+ name: 'edit',
35
+ display_name: 'Edit',
36
+ action_type: 'default',
37
+ preferences: {},
38
+ visible: true
39
+ },
40
+ {
41
+ name: 'remove',
42
+ display_name: 'Remove',
43
+ action_type: 'default',
44
+ preferences: {},
45
+ visible: true
46
+ }
47
+ ].freeze
48
+
49
+ DEFAULT_TABS = [
50
+ {
51
+ name: 'summary',
52
+ display_name: 'Summary',
53
+ tab_type: 'default',
54
+ preferences: {},
55
+ visible: true
56
+ }
57
+ ].freeze
58
+
14
59
  module_function
15
60
 
16
61
  def call
@@ -21,3 +66,11 @@ module Motor
21
66
  end
22
67
  end
23
68
  end
69
+
70
+ require_relative './build_schema/active_storage_attachment_schema'
71
+ require_relative './build_schema/load_from_rails'
72
+ require_relative './build_schema/find_display_column'
73
+ require_relative './build_schema/persist_resource_configs'
74
+ require_relative './build_schema/reorder_schema'
75
+ require_relative './build_schema/merge_schema_configs'
76
+ require_relative './build_schema/utils'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module BuildSchema
5
+ ACTIVE_STORAGE_ATTACHMENT_SCHEMA = {
6
+ name: 'active_storage/attachment',
7
+ slug: 'active_storage__attachments',
8
+ table_name: 'active_storage_attachments',
9
+ primary_key: 'id',
10
+ display_name: 'Attachments',
11
+ display_column: 'filename',
12
+ columns: [
13
+ {
14
+ name: 'id',
15
+ display_name: 'Id',
16
+ column_type: 'integer',
17
+ access_type: 'read_only',
18
+ default_value: nil,
19
+ validators: [],
20
+ virtual: false
21
+ },
22
+ {
23
+ name: 'path',
24
+ display_name: 'Path',
25
+ column_type: 'string',
26
+ access_type: 'read_only',
27
+ default_value: nil,
28
+ validators: [],
29
+ virtual: false
30
+ },
31
+ {
32
+ name: 'name',
33
+ display_name: 'Name',
34
+ column_type: 'string',
35
+ access_type: 'read_write',
36
+ default_value: nil,
37
+ validators: [],
38
+ virtual: false
39
+ },
40
+ {
41
+ name: 'record_type',
42
+ display_name: 'Record type',
43
+ column_type: 'string',
44
+ access_type: 'read_write',
45
+ default_value: nil,
46
+ validators: [],
47
+ virtual: false
48
+ },
49
+ {
50
+ name: 'record_id',
51
+ display_name: 'Record',
52
+ column_type: 'integer',
53
+ access_type: 'read_write',
54
+ default_value: nil,
55
+ validators: [],
56
+ virtual: false
57
+ },
58
+ {
59
+ name: 'file',
60
+ display_name: 'File',
61
+ column_type: 'file',
62
+ access_type: 'write_only',
63
+ default_value: nil,
64
+ validators: [],
65
+ virtual: false
66
+ },
67
+ {
68
+ name: 'created_at',
69
+ display_name: 'Created at',
70
+ column_type: 'datetime',
71
+ access_type: 'read_only',
72
+ default_value: nil,
73
+ validators: [],
74
+ virtual: false
75
+ }
76
+ ],
77
+ associations: [],
78
+ scopes: [],
79
+ actions: Motor::BuildSchema::DEFAULT_ACTIONS.reject { |e| e[:name] == 'edit' },
80
+ tabs: Motor::BuildSchema::DEFAULT_TABS,
81
+ visible: true
82
+ }.with_indifferent_access
83
+ end
84
+ end
@@ -3,56 +3,6 @@
3
3
  module Motor
4
4
  module BuildSchema
5
5
  module LoadFromRails
6
- module ColumnAccessTypes
7
- ALL = [
8
- READ_ONLY = 'read_only',
9
- WRITE_ONLY = 'write_only',
10
- READ_WRITE = 'read_write',
11
- HIDDEN = 'hidden'
12
- ].freeze
13
- end
14
-
15
- COLUMN_NAME_ACCESS_TYPES = {
16
- id: ColumnAccessTypes::READ_ONLY,
17
- created_at: ColumnAccessTypes::READ_ONLY,
18
- updated_at: ColumnAccessTypes::READ_ONLY,
19
- deleted_at: ColumnAccessTypes::READ_ONLY
20
- }.with_indifferent_access.freeze
21
-
22
- DEFAULT_ACTIONS = [
23
- {
24
- name: 'create',
25
- display_name: 'Create',
26
- action_type: 'default',
27
- preferences: {},
28
- visible: true
29
- },
30
- {
31
- name: 'edit',
32
- display_name: 'Edit',
33
- action_type: 'default',
34
- preferences: {},
35
- visible: true
36
- },
37
- {
38
- name: 'remove',
39
- display_name: 'Remove',
40
- action_type: 'default',
41
- preferences: {},
42
- visible: true
43
- }
44
- ].freeze
45
-
46
- DEFAULT_TABS = [
47
- {
48
- name: 'summary',
49
- display_name: 'Summary',
50
- tab_type: 'default',
51
- preferences: {},
52
- visible: true
53
- }
54
- ].freeze
55
-
56
6
  module_function
57
7
 
58
8
  def call
@@ -86,21 +36,43 @@ module Motor
86
36
  end
87
37
 
88
38
  def build_model_schema(model)
39
+ model_name = model.name
40
+
41
+ return Motor::BuildSchema::ACTIVE_STORAGE_ATTACHMENT_SCHEMA if model_name == 'ActiveStorage::Attachment'
42
+
89
43
  {
90
- name: model.name.underscore,
44
+ name: model_name.underscore,
91
45
  slug: Utils.slugify(model),
92
46
  table_name: model.table_name,
47
+ class_name: model.name,
93
48
  primary_key: model.primary_key,
94
- display_name: model.name.titleize.pluralize,
49
+ display_name: model_name.titleize.pluralize,
95
50
  display_column: FindDisplayColumn.call(model),
96
51
  columns: fetch_columns(model),
97
52
  associations: fetch_associations(model),
53
+ scopes: fetch_scopes(model),
98
54
  actions: DEFAULT_ACTIONS,
99
55
  tabs: DEFAULT_TABS,
100
56
  visible: true
101
57
  }.with_indifferent_access
102
58
  end
103
59
 
60
+ def fetch_scopes(model)
61
+ model.defined_scopes.map do |scope_name|
62
+ scope_name = scope_name.to_s
63
+
64
+ next if scope_name.starts_with?('with_attached')
65
+
66
+ {
67
+ name: scope_name,
68
+ display_name: scope_name.humanize,
69
+ scope_type: DEFAULT_SCOPE_TYPE,
70
+ visible: true,
71
+ preferences: {}
72
+ }
73
+ end.compact
74
+ end
75
+
104
76
  def fetch_columns(model)
105
77
  default_attrs = model.new.attributes
106
78
 
@@ -127,17 +99,19 @@ module Motor
127
99
  next
128
100
  end
129
101
 
130
- next if defined?(ActiveStorage::Blob) && ref.klass == ActiveStorage::Blob
102
+ model_class = ref.klass
103
+
104
+ next if model_class.name == 'ActiveStorage::Blob'
131
105
 
132
106
  {
133
107
  name: name,
134
108
  display_name: name.humanize,
135
109
  slug: name.underscore,
136
- model_name: ref.klass.name.underscore,
137
- model_slug: Utils.slugify(ref.klass),
110
+ model_name: model_class.name.underscore,
111
+ model_slug: Utils.slugify(model_class),
138
112
  association_type: fetch_association_type(ref),
139
113
  foreign_key: ref.foreign_key,
140
- polymorphic: ref.polymorphic?,
114
+ polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
141
115
  visible: true
142
116
  }
143
117
  end.compact
@@ -7,6 +7,7 @@ module Motor
7
7
  COLUMN_DEFAULTS = PersistResourceConfigs::COLUMN_DEFAULTS
8
8
  ACTION_DEFAULTS = PersistResourceConfigs::ACTION_DEFAULTS
9
9
  TAB_DEFAULTS = PersistResourceConfigs::TAB_DEFAULTS
10
+ SCOPE_DEFAULTS = PersistResourceConfigs::SCOPE_DEFAULTS
10
11
 
11
12
  module_function
12
13
 
@@ -26,17 +27,17 @@ module Motor
26
27
  def merge_model(model, configs)
27
28
  updated_model = model.merge(configs.slice(*RESOURCE_ATTRS))
28
29
 
30
+ updated_model[:associations] = merge_by_name(
31
+ model[:associations],
32
+ configs[:associations]
33
+ )
34
+
29
35
  updated_model[:columns] = merge_by_name(
30
36
  model[:columns],
31
37
  configs[:columns],
32
38
  COLUMN_DEFAULTS
33
39
  )
34
40
 
35
- updated_model[:associations] = merge_by_name(
36
- model[:associations],
37
- configs[:associations]
38
- )
39
-
40
41
  updated_model[:actions] = merge_by_name(
41
42
  model[:actions],
42
43
  configs[:actions],
@@ -46,7 +47,13 @@ module Motor
46
47
  updated_model[:tabs] = merge_by_name(
47
48
  model[:tabs],
48
49
  configs[:tabs],
49
- ACTION_DEFAULTS
50
+ TAB_DEFAULTS
51
+ )
52
+
53
+ updated_model[:scopes] = merge_by_name(
54
+ model[:scopes],
55
+ configs[:scopes],
56
+ SCOPE_DEFAULTS
50
57
  )
51
58
 
52
59
  updated_model
@@ -6,6 +6,7 @@ module Motor
6
6
  RESOURCE_ATTRS = %w[display_name visible].freeze
7
7
  COLUMN_ATTRS = %w[name display_name column_type access_type default_value virtual].freeze
8
8
  ASSOCIATION_ATTRS = %w[name display_name visible].freeze
9
+ SCOPE_ATTRS = %w[name display_name scope_type preferences visible].freeze
9
10
  ACTION_ATTRS = %w[name display_name action_type preferences visible].freeze
10
11
  TAB_ATTRS = %w[name display_name tab_type preferences visible].freeze
11
12
 
@@ -25,6 +26,12 @@ module Motor
25
26
  preferences: {}
26
27
  }.with_indifferent_access
27
28
 
29
+ SCOPE_DEFAULTS = {
30
+ visible: true,
31
+ scope_type: 'default',
32
+ preferences: {}
33
+ }.with_indifferent_access
34
+
28
35
  module_function
29
36
 
30
37
  # @param resource [Motor::Resource]
@@ -99,6 +106,14 @@ module Motor
99
106
  )
100
107
  end
101
108
 
109
+ if new_prefs[:scopes].present?
110
+ normalized_preferences[:scopes] = normalize_scopes(
111
+ default_prefs[:scopes],
112
+ existing_prefs.fetch(:scopes, []),
113
+ new_prefs.fetch(:scopes, [])
114
+ )
115
+ end
116
+
102
117
  normalized_preferences.compact
103
118
  end
104
119
 
@@ -138,7 +153,7 @@ module Motor
138
153
  action_attrs = new_action.slice(*ACTION_ATTRS)
139
154
 
140
155
  normalized_action = existing_action.merge(action_attrs)
141
- normalized_action = reject_default(default_action.presence || TAB_DEFAULTS, normalized_action)
156
+ normalized_action = reject_default(default_action.presence || ACTION_DEFAULTS, normalized_action)
142
157
 
143
158
  normalized_action.merge(name: name) if normalized_action.present?
144
159
  end.compact.presence
@@ -165,6 +180,27 @@ module Motor
165
180
  end.compact.presence
166
181
  end
167
182
 
183
+ # @param default_scopes [Array<HashWithIndifferentAccess>]
184
+ # @param existing_scopes [Array<HashWithIndifferentAccess>]
185
+ # @param new_scopes [Array<HashWithIndifferentAccess>]
186
+ # @return [Array<HashWithIndifferentAccess>]
187
+ def normalize_scopes(default_scopes, existing_scopes, new_scopes)
188
+ (existing_scopes.pluck(:name) + new_scopes.pluck(:name)).uniq.map do |name|
189
+ new_scope = safe_fetch_by_name(new_scopes, name)
190
+
191
+ next if new_scope[:_remove]
192
+
193
+ existing_scope = safe_fetch_by_name(existing_scopes, name)
194
+ default_scope = safe_fetch_by_name(default_scopes, name)
195
+ scope_attrs = new_scope.slice(*SCOPE_ATTRS)
196
+
197
+ normalized_scope = existing_scope.merge(scope_attrs)
198
+ normalized_scope = reject_default(default_scope.presence || SCOPE_DEFAULTS, normalized_scope)
199
+
200
+ normalized_scope.merge(name: name) if normalized_scope.present?
201
+ end.compact.presence
202
+ end
203
+
168
204
  # @param default_assocs [Array<HashWithIndifferentAccess>]
169
205
  # @param existing_assocs [Array<HashWithIndifferentAccess>]
170
206
  # @param new_assocs [Array<HashWithIndifferentAccess>]
@@ -16,12 +16,14 @@ module Motor
16
16
  associations_order = configs["resources.#{model[:name]}.associations.order"]
17
17
  actions_order = configs["resources.#{model[:name]}.actions.order"]
18
18
  tabs_order = configs["resources.#{model[:name]}.tabs.order"]
19
+ scopes_order = configs["resources.#{model[:name]}.scopes.order"]
19
20
 
20
21
  model.merge(
21
22
  columns: sort_by_name(model[:columns], columns_order, sort_alphabetically: false),
22
23
  associations: sort_by_name(model[:associations], associations_order),
23
24
  actions: sort_by_name(model[:actions], actions_order, sort_alphabetically: false),
24
- tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false)
25
+ tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false),
26
+ scopes: sort_by_name(model[:scopes], scopes_order)
25
27
  )
26
28
  end
27
29
  end
data/lib/motor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motor
4
- VERSION = '0.1.12'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -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-4bdedd7fcff1351efaf6.css.gz": "main-4bdedd7fcff1351efaf6.css.gz",
9
- "main-4bdedd7fcff1351efaf6.js.LICENSE.txt": "main-4bdedd7fcff1351efaf6.js.LICENSE.txt",
10
- "main-4bdedd7fcff1351efaf6.js.gz": "main-4bdedd7fcff1351efaf6.js.gz",
11
- "main.css": "main-4bdedd7fcff1351efaf6.css",
12
- "main.js": "main-4bdedd7fcff1351efaf6.js"
8
+ "main-8f36a2746422efd1e8a8.css.gz": "main-8f36a2746422efd1e8a8.css.gz",
9
+ "main-8f36a2746422efd1e8a8.js.LICENSE.txt": "main-8f36a2746422efd1e8a8.js.LICENSE.txt",
10
+ "main-8f36a2746422efd1e8a8.js.gz": "main-8f36a2746422efd1e8a8.js.gz",
11
+ "main.css": "main-8f36a2746422efd1e8a8.css",
12
+ "main.js": "main-8f36a2746422efd1e8a8.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.12
4
+ version: 0.1.13
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-27 00:00:00.000000000 Z
11
+ date: 2021-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -132,6 +132,7 @@ files:
132
132
  - LICENSE
133
133
  - README.md
134
134
  - Rakefile
135
+ - app/controllers/motor/active_storage_attachments_controller.rb
135
136
  - app/controllers/motor/alerts_controller.rb
136
137
  - app/controllers/motor/api_base_controller.rb
137
138
  - app/controllers/motor/application_controller.rb
@@ -172,6 +173,8 @@ files:
172
173
  - lib/motor-admin.rb
173
174
  - lib/motor.rb
174
175
  - lib/motor/active_record_utils.rb
176
+ - lib/motor/active_record_utils/active_storage_links_extension.rb
177
+ - lib/motor/active_record_utils/defined_scopes_extension.rb
175
178
  - lib/motor/active_record_utils/fetch_methods.rb
176
179
  - lib/motor/active_record_utils/types.rb
177
180
  - lib/motor/admin.rb
@@ -181,6 +184,7 @@ files:
181
184
  - lib/motor/alerts/scheduler.rb
182
185
  - lib/motor/api.rb
183
186
  - lib/motor/api_query.rb
187
+ - lib/motor/api_query/apply_scope.rb
184
188
  - lib/motor/api_query/build_json.rb
185
189
  - lib/motor/api_query/build_meta.rb
186
190
  - lib/motor/api_query/filter.rb
@@ -189,6 +193,7 @@ files:
189
193
  - lib/motor/api_query/sort.rb
190
194
  - lib/motor/assets.rb
191
195
  - lib/motor/build_schema.rb
196
+ - lib/motor/build_schema/active_storage_attachment_schema.rb
192
197
  - lib/motor/build_schema/find_display_column.rb
193
198
  - lib/motor/build_schema/load_from_rails.rb
194
199
  - lib/motor/build_schema/merge_schema_configs.rb
@@ -208,8 +213,8 @@ files:
208
213
  - lib/motor/ui_configs.rb
209
214
  - lib/motor/version.rb
210
215
  - ui/dist/fonts/ionicons.woff2
211
- - ui/dist/main-4bdedd7fcff1351efaf6.css.gz
212
- - ui/dist/main-4bdedd7fcff1351efaf6.js.gz
216
+ - ui/dist/main-8f36a2746422efd1e8a8.css.gz
217
+ - ui/dist/main-8f36a2746422efd1e8a8.js.gz
213
218
  - ui/dist/manifest.json
214
219
  homepage:
215
220
  licenses: