motor-admin 0.1.28 → 0.1.34

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: 5799ce999fa943638fa7c4d655400c1418ee80977bf47f64a71e6d5c496d02de
4
- data.tar.gz: da99ec7b1c48708d983b663a7f9cb79b6d0e270b2fc9f5538a8121cfabdbc209
3
+ metadata.gz: 6d39e56e7ea8a0b52d05594f2b1fb6f12860d9fd5771fbcf9bec758ce5752f08
4
+ data.tar.gz: 5cd61073f5a310dd5562225476134358cec33ab1df16983b0faca249a434c91e
5
5
  SHA512:
6
- metadata.gz: c5fc31a79b01ad0e716700a7c3daed980b293d13081294d1013918cce6f4d24cf4c104e0c44144cde225fd3b1ce07253f9b3ddaf44f1eca2252a01ff9124c52e
7
- data.tar.gz: efca3a6d85ce6e33c83a63f3cd5addbd75b2baf099bd96c5efbac40445520a7725d68a5c3d0564792f06bdf12fed8958b13a6189047303d3c77f7f0af0ff0bd4
6
+ metadata.gz: bcb86c3d249b14ae24d8a37fc5418ab41e789602ede38c5ab31dbda19d82100ac1f1321ff868fef6c7e892fdde2559905afbcfe22e85c65b63ce0c826092bfc1
7
+ data.tar.gz: 051cfe7a8bdcee2ca9f173954de10108d5682c5f59c2a4291443f32552f31020baeaec78a2f7806d78be13df73f5a9a73742b77c9c4943eee1a60db81ec18100
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module WrapIoParams
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :wrap_io_params, only: %i[update create]
9
+ end
10
+
11
+ private
12
+
13
+ def wrap_io_params(hash = params)
14
+ hash.each do |key, value|
15
+ if key == 'io'
16
+ hash[key] = StringIO.new(value.encode('ISO-8859-1'))
17
+ elsif value.is_a?(ActionController::Parameters)
18
+ wrap_io_params(value)
19
+ end
20
+ end
21
+
22
+ hash
23
+ end
24
+ end
25
+ end
@@ -2,16 +2,15 @@
2
2
 
3
3
  module Motor
4
4
  class ActiveStorageAttachmentsController < ApiBaseController
5
+ include Motor::WrapIoParams
6
+
5
7
  wrap_parameters :data, except: %i[include fields]
6
8
 
7
9
  load_and_authorize_resource :attachment, class: 'ActiveStorage::Attachment', parent: false
8
10
 
9
11
  def create
10
12
  if attachable?(@attachment.record)
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
- )
13
+ @attachment.record.public_send(@attachment.name).attach(file_params)
15
14
 
16
15
  head :ok
17
16
  else
@@ -26,8 +25,16 @@ module Motor
26
25
  record.respond_to?("#{@attachment.name}_attachments=")
27
26
  end
28
27
 
28
+ def file_params
29
+ params.require(:data).require(:file).permit(:io, :filename).to_h
30
+ end
31
+
29
32
  def attachment_params
30
- params.require(:data).except(:file).permit!
33
+ if params[:data].present?
34
+ params.require(:data).except(:file).permit!
35
+ else
36
+ {}
37
+ end
31
38
  end
32
39
  end
33
40
  end
@@ -2,13 +2,14 @@
2
2
 
3
3
  module Motor
4
4
  class DataController < ApiBaseController
5
+ include Motor::WrapIoParams
6
+
5
7
  INSTANCE_VARIABLE_NAME = 'resource'
6
8
 
7
9
  wrap_parameters :data, except: %i[include fields]
8
10
 
9
11
  before_action :load_and_authorize_resource
10
12
  before_action :load_and_authorize_association
11
- before_action :wrap_io_params
12
13
 
13
14
  def index
14
15
  @resources = Motor::ApiQuery.call(@resources, params)
@@ -27,12 +28,20 @@ module Motor
27
28
  @resource.save!
28
29
 
29
30
  render json: { data: Motor::ApiQuery::BuildJson.call(@resource, params) }
31
+ rescue ActiveRecord::RecordInvalid
32
+ render json: { errors: @resource.errors }, status: :unprocessable_entity
33
+ rescue StandardError => e
34
+ render json: { errors: [e.message] }, status: :unprocessable_entity
30
35
  end
31
36
 
32
37
  def update
33
38
  @resource.update!(resource_params)
34
39
 
35
40
  render json: { data: Motor::ApiQuery::BuildJson.call(@resource, params) }
41
+ rescue ActiveRecord::RecordInvalid
42
+ render json: { errors: @resource.errors }, status: :unprocessable_entity
43
+ rescue StandardError => e
44
+ render json: { errors: [e.message] }, status: :unprocessable_entity
36
45
  end
37
46
 
38
47
  def destroy
@@ -77,6 +86,10 @@ module Motor
77
86
  self,
78
87
  options
79
88
  ).load_and_authorize_resource
89
+ rescue ActiveRecord::RecordNotFound
90
+ head :not_found
91
+ rescue StandardError => e
92
+ render json: { errors: [e.message] }, status: :unprocessable_entity
80
93
  end
81
94
 
82
95
  def load_and_authorize_association
@@ -96,6 +109,10 @@ module Motor
96
109
  else
97
110
  render json: { message: 'Unknown association' }, status: :not_found
98
111
  end
112
+ rescue ActiveRecord::RecordNotFound
113
+ head :not_found
114
+ rescue StandardError => e
115
+ render json: { errors: [e.message] }, status: :unprocessable_entity
99
116
  end
100
117
 
101
118
  def resource_params
@@ -105,17 +122,5 @@ module Motor
105
122
  {}
106
123
  end
107
124
  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
119
- end
120
125
  end
121
126
  end
@@ -10,5 +10,13 @@ module Motor
10
10
  serialize :preferences, HashSerializer
11
11
 
12
12
  scope :active, -> { where(deleted_at: nil) }
13
+
14
+ def result(variables_hash = {})
15
+ result = Motor::Queries::RunQuery.call(self, variables_hash: variables_hash)
16
+ column_names = result.columns.pluck(:name)
17
+
18
+ result.data.map { |row| column_names.zip(row).to_h }
19
+ end
20
+ alias run result
13
21
  end
14
22
  end
@@ -77,7 +77,7 @@
77
77
  <% if @query_result.data.length > 1 %>
78
78
  <p style="margin: 7px 0; float: left">Showing <%= @query_result.data.first(40).size %> of <%= @query_result.data.size %> rows</p>
79
79
  <% end %>
80
- <a href="<%= Motor::Admin.routes.url_helpers.motor_ui_query_url(@alert.query, { host: 'localhost:3000'}.merge(Rails.application.config.action_mailer.default_url_options || {})) %>" target="blank" style="margin: 7px 0; float: right">Open query </a>
80
+ <a href="<%= Motor::Admin.routes.url_helpers.motor_ui_query_url(@alert.query, { host: 'localhost:3000' }.merge(Rails.application.config.action_mailer.default_url_options || {})) %>" target="blank" style="margin: 7px 0; float: right">Open query </a>
81
81
  <table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 3px;">
82
82
  <tr>
83
83
  <td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px; max-width: 0px; overflow: scroll">
data/config/routes.rb CHANGED
@@ -47,15 +47,4 @@ Motor::Admin.routes.draw do
47
47
  end
48
48
  end
49
49
 
50
- Motor::Api.routes.draw do
51
- namespace :motor, path: '' do
52
- resources :resources, path: '/:resource',
53
- only: %i[index show update create destroy],
54
- controller: 'data' do
55
- put '/:method', to: 'data#execute'
56
- resources :association, path: '/:association',
57
- only: %i[index create],
58
- controller: 'data'
59
- end
60
- end
61
- end
50
+ ActiveSupport::Notifications.instrument('motor.routes.loaded')
@@ -16,6 +16,8 @@ module Motor
16
16
 
17
17
  def copy_migration
18
18
  migration_template 'install.rb', 'db/migrate/install_motor_admin.rb'
19
+
20
+ route "mount Motor::Admin => '/motor_admin'"
19
21
  end
20
22
  end
21
23
  end
data/lib/motor.rb CHANGED
@@ -45,7 +45,6 @@ end
45
45
 
46
46
  require 'motor/version'
47
47
  require 'motor/admin'
48
- require 'motor/api'
49
48
  require 'motor/assets'
50
49
  require 'motor/build_schema'
51
50
  require 'motor/api_query'
data/lib/motor/admin.rb CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  module Motor
4
4
  class Admin < ::Rails::Engine
5
+ initializer 'motor.startup_message' do
6
+ ActiveSupport::Notifications.subscribe('motor.routes.loaded') do
7
+ next unless Motor.server?
8
+
9
+ if Rails.application.routes.url_helpers.respond_to?(:motor_admin_path)
10
+ url =
11
+ begin
12
+ Rails.application.routes.url_helpers.motor_admin_url
13
+ rescue ArgumentError
14
+ Rails.application.routes.url_helpers.motor_admin_path
15
+ end
16
+
17
+ puts
18
+ puts "⚡ Motor::Admin is starting under #{url}"
19
+ else
20
+ puts
21
+ puts '⚠️ Motor::Admin is not mounted.'
22
+ puts 'Add the following line to your config/routes.rb:'
23
+ puts
24
+ puts " mount Motor::Admin => '/admin'"
25
+ end
26
+
27
+ puts
28
+ end
29
+ end
30
+
5
31
  initializer 'motor.filter_params' do
6
32
  Rails.application.config.filter_parameters += %i[io]
7
33
  end
@@ -34,20 +34,29 @@ module Motor
34
34
 
35
35
  def normalize_filter_hash(hash)
36
36
  hash.each_with_object({}) do |(action, value), acc|
37
- acc[action] =
37
+ new_action, new_value =
38
38
  if value.is_a?(Hash)
39
- normalize_filter_hash(value)
39
+ [action, normalize_filter_hash(value)]
40
40
  else
41
- normalize_action_value(action, value)
41
+ normalize_action(action, value)
42
42
  end
43
+
44
+ acc[new_action] = new_value
45
+
46
+ acc
43
47
  end
44
48
  end
45
49
 
46
- def normalize_action_value(action, value)
47
- if %w[like ilike].include?(action)
48
- value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1%')
50
+ def normalize_action(action, value)
51
+ case action
52
+ when 'contains'
53
+ ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1%')]
54
+ when 'starts_with'
55
+ ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '\1%')]
56
+ when 'ends_with'
57
+ ['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1')]
49
58
  else
50
- value
59
+ [action, value]
51
60
  end
52
61
  end
53
62
  end
@@ -48,8 +48,8 @@ module Motor
48
48
 
49
49
  DEFAULT_TABS = [
50
50
  {
51
- name: 'summary',
52
- display_name: 'Summary',
51
+ name: 'details',
52
+ display_name: 'Details',
53
53
  tab_type: 'default',
54
54
  preferences: {},
55
55
  visible: true
@@ -133,7 +133,7 @@ module Motor
133
133
  column_type: is_attachment ? 'file' : 'integer',
134
134
  access_type: access_type,
135
135
  default_value: default_attrs[column_name],
136
- validators: fetch_validators(model, column_name),
136
+ validators: fetch_validators(model, column_name, ref),
137
137
  format: {},
138
138
  reference: {
139
139
  name: name,
@@ -172,23 +172,34 @@ module Motor
172
172
  end.compact
173
173
  end
174
174
 
175
- def fetch_validators(model, column_name)
176
- model.validators_on(column_name).map do |validator|
177
- case validator
178
- when ActiveModel::Validations::InclusionValidator
179
- { includes: validator.send(:delimiter) }
180
- when ActiveRecord::Validations::PresenceValidator
181
- { required: true }
182
- when ActiveModel::Validations::FormatValidator
183
- { format: JsRegex.new(validator.options[:with]).to_h.slice(:source, :options) }
184
- when ActiveRecord::Validations::LengthValidator
185
- { length: validator.options }
186
- when ActiveModel::Validations::NumericalityValidator
187
- { numeric: validator.options }
175
+ def fetch_validators(model, column_name, reflection = nil)
176
+ validators =
177
+ if reflection&.belongs_to? && !reflection.options[:optional]
178
+ [{ required: true }]
188
179
  else
189
- next
180
+ []
190
181
  end
182
+
183
+ validators += model.validators_on(column_name).map do |validator|
184
+ build_validator_hash(validator)
191
185
  end.compact
186
+
187
+ validators.uniq
188
+ end
189
+
190
+ def build_validator_hash(validator)
191
+ case validator
192
+ when ActiveModel::Validations::InclusionValidator
193
+ { includes: validator.send(:delimiter) }
194
+ when ActiveRecord::Validations::PresenceValidator
195
+ { required: true }
196
+ when ActiveModel::Validations::FormatValidator
197
+ { format: JsRegex.new(validator.options[:with]).to_h.slice(:source, :options) }
198
+ when ActiveRecord::Validations::LengthValidator
199
+ { length: validator.options }
200
+ when ActiveModel::Validations::NumericalityValidator
201
+ { numeric: validator.options }
202
+ end
192
203
  end
193
204
 
194
205
  def eager_load_models!
@@ -90,9 +90,9 @@ module Motor
90
90
  return preferences if new_prefs[configs_name].blank?
91
91
 
92
92
  normalized_configs = public_send("normalize_#{configs_name}",
93
- default_prefs[:actions],
94
- existing_prefs.fetch(:actions, []),
95
- new_prefs.fetch(:actions, []))
93
+ default_prefs[configs_name],
94
+ existing_prefs.fetch(configs_name, []),
95
+ new_prefs.fetch(configs_name, []))
96
96
 
97
97
  preferences[configs_name] = normalized_configs
98
98
 
data/lib/motor/tags.rb CHANGED
@@ -21,7 +21,8 @@ module Motor
21
21
  end
22
22
 
23
23
  def remove_missing_tags(taggable, tags)
24
- tags_to_remove = taggable.tags.reject { |tt| tt.name.downcase.in?(tags) }
24
+ downcase_tags = tags.map(&:downcase)
25
+ tags_to_remove = taggable.tags.reject { |tt| tt.name.downcase.in?(downcase_tags) }
25
26
 
26
27
  taggable.tags -= tags_to_remove
27
28
 
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.28'
4
+ VERSION = '0.1.34'
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-4da1a5102d7bc66aefd0.css.gz": "main-4da1a5102d7bc66aefd0.css.gz",
9
- "main-4da1a5102d7bc66aefd0.js.LICENSE.txt": "main-4da1a5102d7bc66aefd0.js.LICENSE.txt",
10
- "main-4da1a5102d7bc66aefd0.js.gz": "main-4da1a5102d7bc66aefd0.js.gz",
11
- "main.css": "main-4da1a5102d7bc66aefd0.css",
12
- "main.js": "main-4da1a5102d7bc66aefd0.js"
8
+ "main-052729fa924c6434623f.css.gz": "main-052729fa924c6434623f.css.gz",
9
+ "main-052729fa924c6434623f.js.LICENSE.txt": "main-052729fa924c6434623f.js.LICENSE.txt",
10
+ "main-052729fa924c6434623f.js.gz": "main-052729fa924c6434623f.js.gz",
11
+ "main.css": "main-052729fa924c6434623f.css",
12
+ "main.js": "main-052729fa924c6434623f.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.28
4
+ version: 0.1.34
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-05-06 00:00:00.000000000 Z
11
+ date: 2021-05-11 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/concerns/motor/wrap_io_params.rb
135
136
  - app/controllers/motor/active_storage_attachments_controller.rb
136
137
  - app/controllers/motor/alerts_controller.rb
137
138
  - app/controllers/motor/api_base_controller.rb
@@ -182,7 +183,6 @@ files:
182
183
  - lib/motor/alerts/persistance.rb
183
184
  - lib/motor/alerts/scheduled_alerts_cache.rb
184
185
  - lib/motor/alerts/scheduler.rb
185
- - lib/motor/api.rb
186
186
  - lib/motor/api_query.rb
187
187
  - lib/motor/api_query/apply_scope.rb
188
188
  - lib/motor/api_query/build_json.rb
@@ -214,8 +214,8 @@ files:
214
214
  - lib/motor/ui_configs.rb
215
215
  - lib/motor/version.rb
216
216
  - ui/dist/fonts/ionicons.woff2
217
- - ui/dist/main-4da1a5102d7bc66aefd0.css.gz
218
- - ui/dist/main-4da1a5102d7bc66aefd0.js.gz
217
+ - ui/dist/main-052729fa924c6434623f.css.gz
218
+ - ui/dist/main-052729fa924c6434623f.js.gz
219
219
  - ui/dist/manifest.json
220
220
  homepage:
221
221
  licenses:
data/lib/motor/api.rb DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Motor
4
- class Api < ::Rails::Engine
5
- end
6
- end