motor-admin 0.1.13 → 0.1.18

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9dca132c3a2481191219a518fe66e581988245cbe3212fb105c220285d1a5fa
4
- data.tar.gz: e2cb07a305545d2fb01e0c94101f6bdabd705628b8043b8a8a40b320427bc7e9
3
+ metadata.gz: ba0120981ee7e496773ab8ac05d28e9edab1a2155cacec19636c99c088329065
4
+ data.tar.gz: 0766e8cef48de36d468baa8c189757e64bc1ea77361741f45aacb80fc19e74a4
5
5
  SHA512:
6
- metadata.gz: dcce9c0ad1fff7e751a91ab366174174421f038ee3ed848e32618d663d1955221295e0cc79904a0702b939630ab4e6b6873b2b141e01b6ac4bc8361233ef265f
7
- data.tar.gz: b20916892df27d1a6c5eef23452824034d0559e5954fe44726034269496409358a3755f3b53dcd489d61953743b511e66f4f93c4bea2ea894f6dae856575492b
6
+ metadata.gz: b657cd07a7c14867bf112d07ebd2caa7d0076fa650f5803bb710188aab4ed29df37cce6f63905f5bedde77ca6558800d3f57ceaefe14625b74de11c05b9b683e
7
+ data.tar.gz: be4bde0b32f4ff911569e9acc95f55757ef1a316d0940fd33c270e3b54ddc94043d0826cd21d54def98f3a75f7aa443f497ad07c3a3875e91a3a78b540c0f80d
@@ -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.require(:data).except(resource_class.primary_key).permit!
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 if defined?(Sidekiq) && Sidekiq.server?
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,26 +49,27 @@ module Motor
49
49
 
50
50
  params[:fields].each do |key, fields|
51
51
  fields = fields.split(',') if fields.is_a?(String)
52
- fields_hash = build_fields_hash(model, fields)
53
52
 
54
53
  if key == model_name || model_name.split('/').last == key
55
- json_params.merge!(fields_hash)
54
+ json_params.merge!(build_fields_hash(model, fields))
56
55
  else
57
56
  hash = find_key_in_params(json_params, key)
58
57
 
58
+ fields_hash = build_fields_hash(model.reflections[key]&.klass, fields)
59
+
59
60
  hash.merge!(fields_hash)
60
61
  end
61
62
  end
62
63
  end
63
64
 
64
65
  def build_fields_hash(model, fields)
65
- columns = model.columns.map(&:name)
66
+ columns = model ? model.columns.map(&:name) : []
66
67
  fields_hash = { 'only' => [], 'methods' => [] }
67
68
 
68
69
  fields.each_with_object(fields_hash) do |field, acc|
69
70
  if field.in?(columns)
70
71
  acc['only'] << field
71
- else
72
+ elsif model.nil? || model.instance_methods.include?(field.to_sym)
72
73
  acc['methods'] << field
73
74
  end
74
75
  end
@@ -7,19 +7,39 @@ module Motor
7
7
 
8
8
  module_function
9
9
 
10
- def call(rel, params)
11
- return rel if params.blank?
10
+ def call(rel, param)
11
+ return rel if param.blank?
12
12
 
13
- normalized_params = build_params(params)
13
+ arel_order = build_arel_order(rel.klass, param)
14
+ join_params = build_join_params(rel.klass, param)
14
15
 
15
- rel.order(normalized_params)
16
+ rel.order(arel_order).left_joins(join_params)
16
17
  end
17
18
 
18
- def build_params(param)
19
- param.split(',').each_with_object({}) do |field, hash|
20
- direction, name = field.match(FIELD_PARSE_REGEXP).captures
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
- hash[name] = direction.present? ? :desc : :asc
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
@@ -26,7 +26,7 @@ module Motor
26
26
  access_type: 'read_only',
27
27
  default_value: nil,
28
28
  validators: [],
29
- virtual: false
29
+ virtual: true
30
30
  },
31
31
  {
32
32
  name: 'name',
@@ -76,22 +76,67 @@ module Motor
76
76
  def fetch_columns(model)
77
77
  default_attrs = model.new.attributes
78
78
 
79
- model.columns.map do |column|
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 fetch_reference_columns(model)
101
+ default_attrs = model.new.attributes
102
+
103
+ model.reflections.map do |name, ref|
104
+ next if !ref.has_one? && !ref.belongs_to?
105
+
106
+ begin
107
+ ref.klass
108
+ rescue StandardError
109
+ next
110
+ end
111
+
112
+ column_name = ref.belongs_to? ? ref.foreign_key.to_s : name
113
+
114
+ next if ref.klass.name == 'ActiveStorage::Blob'
115
+
116
+ is_attachment = ref.klass.name == 'ActiveStorage::Attachment'
117
+
80
118
  {
81
- name: column.name,
82
- display_name: column.name.humanize,
83
- column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
84
- access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
85
- default_value: default_attrs[column.name],
86
- validators: fetch_validators(model, column.name),
119
+ name: column_name,
120
+ display_name: column_name.humanize,
121
+ column_type: is_attachment ? 'file' : 'integer',
122
+ access_type: ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY,
123
+ default_value: default_attrs[column_name],
124
+ validators: fetch_validators(model, column_name),
125
+ reference: {
126
+ name: name,
127
+ model_name: ref.klass.name.underscore,
128
+ reference_type: ref.belongs_to? ? 'belongs_to' : 'has_one',
129
+ foreign_key: ref.foreign_key,
130
+ polymorphic: ref.polymorphic? || is_attachment
131
+ },
87
132
  virtual: false
88
133
  }
89
- end
134
+ end.compact
90
135
  end
91
136
 
92
137
  def fetch_associations(model)
93
138
  model.reflections.map do |name, ref|
94
- next if ref.polymorphic? && !ref.belongs_to?
139
+ next if ref.has_one? || ref.belongs_to?
95
140
 
96
141
  begin
97
142
  ref.klass
@@ -108,8 +153,6 @@ module Motor
108
153
  display_name: name.humanize,
109
154
  slug: name.underscore,
110
155
  model_name: model_class.name.underscore,
111
- model_slug: Utils.slugify(model_class),
112
- association_type: fetch_association_type(ref),
113
156
  foreign_key: ref.foreign_key,
114
157
  polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
115
158
  visible: true
@@ -117,21 +160,6 @@ module Motor
117
160
  end.compact
118
161
  end
119
162
 
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
163
  def fetch_validators(model, column_name)
136
164
  model.validators_on(column_name).map do |validator|
137
165
  case validator
@@ -13,6 +13,7 @@ module Motor
13
13
  COLUMN_DEFAULTS = {
14
14
  access_type: 'read_write',
15
15
  default_value: nil,
16
+ reference: nil,
16
17
  validators: []
17
18
  }.with_indifferent_access
18
19
 
@@ -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),
@@ -39,11 +51,19 @@ module Motor
39
51
  if order.present?
40
52
  order.index(item[:name]) || Float::MAX
41
53
  else
42
- item[:display_name]
54
+ item[:display_name].to_s
43
55
  end
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
- format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit),
62
- 'SQL',
63
- variables.map { |variable_name, default_value| variables_hash[variable_name] || default_value }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motor
4
- VERSION = '0.1.13'
4
+ VERSION = '0.1.18'
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-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"
8
+ "main-ca8fcc97a95fcfa80394.css.gz": "main-ca8fcc97a95fcfa80394.css.gz",
9
+ "main-ca8fcc97a95fcfa80394.js.LICENSE.txt": "main-ca8fcc97a95fcfa80394.js.LICENSE.txt",
10
+ "main-ca8fcc97a95fcfa80394.js.gz": "main-ca8fcc97a95fcfa80394.js.gz",
11
+ "main.css": "main-ca8fcc97a95fcfa80394.css",
12
+ "main.js": "main-ca8fcc97a95fcfa80394.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.13
4
+ version: 0.1.18
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-29 00:00:00.000000000 Z
11
+ date: 2021-05-02 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-8f36a2746422efd1e8a8.css.gz
217
- - ui/dist/main-8f36a2746422efd1e8a8.js.gz
216
+ - ui/dist/main-ca8fcc97a95fcfa80394.css.gz
217
+ - ui/dist/main-ca8fcc97a95fcfa80394.js.gz
218
218
  - ui/dist/manifest.json
219
219
  homepage:
220
220
  licenses: