motor-admin 0.1.49 → 0.1.54

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/motor/api_base_controller.rb +5 -3
  3. data/app/controllers/motor/run_queries_controller.rb +10 -8
  4. data/app/controllers/motor/ui_controller.rb +10 -4
  5. data/app/models/motor/alert.rb +1 -0
  6. data/app/models/motor/dashboard.rb +1 -0
  7. data/app/models/motor/form.rb +1 -0
  8. data/app/models/motor/query.rb +2 -1
  9. data/app/models/motor/resource.rb +1 -0
  10. data/config/routes.rb +8 -9
  11. data/lib/generators/motor/templates/install.rb +33 -33
  12. data/lib/motor.rb +0 -1
  13. data/lib/motor/active_record_utils.rb +14 -1
  14. data/lib/motor/active_record_utils/active_record_filter.rb +7 -0
  15. data/lib/motor/active_record_utils/active_storage_blob_patch.rb +4 -0
  16. data/lib/motor/admin.rb +4 -5
  17. data/lib/motor/alerts/persistance.rb +4 -4
  18. data/lib/motor/alerts/scheduler.rb +1 -1
  19. data/lib/motor/api_query/build_json.rb +4 -4
  20. data/lib/motor/api_query/paginate.rb +1 -0
  21. data/lib/motor/api_query/sort.rb +3 -1
  22. data/lib/motor/assets.rb +2 -0
  23. data/lib/motor/build_schema/load_from_rails.rb +5 -2
  24. data/lib/motor/configs/load_from_cache.rb +27 -15
  25. data/lib/motor/configs/sync_from_hash.rb +1 -1
  26. data/lib/motor/configs/write_to_file.rb +1 -0
  27. data/lib/motor/dashboards/persistance.rb +4 -4
  28. data/lib/motor/forms/persistance.rb +4 -4
  29. data/lib/motor/queries/persistance.rb +4 -4
  30. data/lib/motor/queries/run_query.rb +26 -5
  31. data/lib/motor/tags.rb +1 -1
  32. data/lib/motor/version.rb +1 -1
  33. data/ui/dist/main-67fbb3a8f6a6388434c8.css.gz +0 -0
  34. data/ui/dist/main-67fbb3a8f6a6388434c8.js.gz +0 -0
  35. data/ui/dist/manifest.json +5 -5
  36. metadata +10 -6
  37. data/ui/dist/main-918b506ecd64910a5845.css.gz +0 -0
  38. data/ui/dist/main-918b506ecd64910a5845.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11b39999a22bc50fe6e3dbcd11765207acaf301b61c0e914afe3a7e553216446
4
- data.tar.gz: 4d39e9ae7ce4df7661008d1db9346f99daaeb3143ae22076ba26032a42bece36
3
+ metadata.gz: 7f9950ae7594d0e67084469151a267b131627fbfe5a2e7954628dd4b1afd5b12
4
+ data.tar.gz: cb6f0a797d08007c4e7eeef3b2f501a3b0768807e9ba4e9b36ad9912fe764f93
5
5
  SHA512:
6
- metadata.gz: 784ae2079cafceacf3ec1a8cf8b8796e515afe5411dd065f6b976ed25d7f0041de3603aa3ca1ad768e7a4c5d1259fc25e2bdd459db5003edb46db5911f2d07dc
7
- data.tar.gz: cf5177b94de2da402d9122c339d379a95a9b71d52bcdbb4c807710243dc80c0d5b4d05673d4ed5c5385f3c42e5f6e6c39553df12c1de35e27876c7b69d872542
6
+ metadata.gz: 772e65f663cc7abb9212d31ccfd1dd3fa85a0d890e83e24a6c18a07120cc004a177a5f9efd95f41340e11e211e8ba698d46f0f3dd973579804a9b082eaf0f976
7
+ data.tar.gz: 8ec2b114b8689746d0a7f4e7656186c693653e3fcc359771dc82294ffe6a2f0b8e01e624bf9417c9f887a4fa65e8ab6609242a70a2d9bdd345b72f7a05d02347
@@ -12,10 +12,12 @@ module Motor
12
12
  end
13
13
  end
14
14
 
15
- rescue_from StandardError do |e|
16
- Rails.logger.error(e)
15
+ unless Rails.env.test?
16
+ rescue_from StandardError do |e|
17
+ Rails.logger.error(e)
17
18
 
18
- render json: { errors: [e.message] }, status: :internal_server_error
19
+ render json: { errors: [e.message] }, status: :internal_server_error
20
+ end
19
21
  end
20
22
 
21
23
  def current_ability
@@ -9,22 +9,24 @@ module Motor
9
9
  before_action :build_query, only: :create
10
10
  authorize_resource :query, only: :create
11
11
 
12
- rescue_from 'ActiveRecord::StatementInvalid' do |e|
13
- render json: { errors: [{ detail: e.message }] }, status: :unprocessable_entity
14
- end
15
-
16
12
  def show
17
- render json: query_result_hash(query_result)
13
+ render_result
18
14
  end
19
15
 
20
16
  def create
21
- render json: query_result_hash(query_result)
17
+ render_result
22
18
  end
23
19
 
24
20
  private
25
21
 
26
- def query_result
27
- Queries::RunQuery.call(@query, variables_hash: params[:variables])
22
+ def render_result
23
+ query_result = Queries::RunQuery.call(@query, variables_hash: params[:variables])
24
+
25
+ if query_result.error
26
+ render json: { errors: [{ detail: query_result.error }] }, status: :unprocessable_entity
27
+ else
28
+ render json: query_result_hash(query_result)
29
+ end
28
30
  end
29
31
 
30
32
  def query_result_hash(query_result)
@@ -7,14 +7,20 @@ module Motor
7
7
  helper_method :current_user
8
8
 
9
9
  def index
10
- Motor.reload! if Motor.development?
11
-
12
- Motor::Configs::SyncFromFile.call
10
+ render_ui
11
+ end
13
12
 
14
- render :show
13
+ def new
14
+ render_ui
15
15
  end
16
16
 
17
17
  def show
18
+ render_ui
19
+ end
20
+
21
+ private
22
+
23
+ def render_ui
18
24
  Motor.reload! if Motor.development?
19
25
 
20
26
  Motor::Configs::SyncFromFile.call
@@ -11,6 +11,7 @@ module Motor
11
11
  has_many :taggable_tags, as: :taggable, dependent: :destroy
12
12
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
13
13
 
14
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
14
15
  serialize :preferences, HashSerializer
15
16
 
16
17
  scope :active, -> { where(deleted_at: nil) }
@@ -9,6 +9,7 @@ module Motor
9
9
  has_many :taggable_tags, as: :taggable, dependent: :destroy
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
 
12
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
12
13
  serialize :preferences, HashSerializer
13
14
 
14
15
  scope :active, -> { where(deleted_at: nil) }
@@ -9,6 +9,7 @@ module Motor
9
9
  has_many :taggable_tags, as: :taggable, dependent: :destroy
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
 
12
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
12
13
  serialize :preferences, HashSerializer
13
14
 
14
15
  scope :active, -> { where(deleted_at: nil) }
@@ -10,12 +10,13 @@ module Motor
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
  has_many :alerts, dependent: :destroy
12
12
 
13
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
13
14
  serialize :preferences, HashSerializer
14
15
 
15
16
  scope :active, -> { where(deleted_at: nil) }
16
17
 
17
18
  def result(variables_hash = {})
18
- result = Motor::Queries::RunQuery.call(self, variables_hash: variables_hash)
19
+ result = Motor::Queries::RunQuery.call!(self, variables_hash: variables_hash)
19
20
  column_names = result.columns.pluck(:name)
20
21
 
21
22
  result.data.map { |row| column_names.zip(row).to_h }
@@ -4,6 +4,7 @@ module Motor
4
4
  class Resource < ::Motor::ApplicationRecord
5
5
  audited
6
6
 
7
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
7
8
  serialize :preferences, HashSerializer
8
9
  end
9
10
  end
data/config/routes.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Motor::Admin.routes.draw do
4
4
  namespace :motor, path: '' do
5
- scope 'api', as: 'api' do
5
+ scope 'api', as: :api do
6
6
  resources :run_queries, only: %i[show create]
7
7
  resources :send_alerts, only: %i[create]
8
8
  resources :queries, only: %i[index show create update destroy]
@@ -34,16 +34,15 @@ Motor::Admin.routes.draw do
34
34
 
35
35
  get '/', to: 'ui#show'
36
36
 
37
- scope as: 'ui' do
37
+ scope as: :ui do
38
+ get '/data(/*path)', to: 'ui#index', as: :data
39
+
38
40
  with_options controller: 'ui' do
39
- resources :data, only: %i[index show],
40
- param: 'path',
41
- constraints: { path: /.+/ }
42
41
  resources :reports, only: %i[index show]
43
- resources :queries, only: %i[index show]
44
- resources :dashboards, only: %i[index show]
45
- resources :alerts, only: %i[index show]
46
- resources :forms, only: %i[index show]
42
+ resources :queries, only: %i[index show new]
43
+ resources :dashboards, only: %i[index show new]
44
+ resources :alerts, only: %i[index show new]
45
+ resources :forms, only: %i[index show new]
47
46
  end
48
47
  end
49
48
  end
@@ -2,61 +2,61 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
2
2
  def self.up
3
3
  create_table :motor_queries do |t|
4
4
  t.column :name, :string, null: false
5
- t.column :description, :string
6
- t.column :sql_body, :string, null: false
7
- t.column :preferences, :string, null: false, default: '{}'
8
- t.column :author_id, :integer
5
+ t.column :description, :text
6
+ t.column :sql_body, :text, null: false
7
+ t.column :preferences, :text, null: false
8
+ t.column :author_id, :bigint
9
9
  t.column :author_type, :string
10
10
  t.column :deleted_at, :datetime
11
11
 
12
12
  t.timestamps
13
13
 
14
14
  t.index :updated_at
15
- t.index 'lower(name)',
16
- name: 'motor_queries_lower_name_unique_index',
15
+ t.index 'name',
16
+ name: 'motor_queries_name_unique_index',
17
17
  unique: true,
18
18
  where: 'deleted_at IS NULL'
19
19
  end
20
20
 
21
21
  create_table :motor_dashboards do |t|
22
22
  t.column :title, :string, null: false
23
- t.column :description, :string
24
- t.column :preferences, :string, null: false, default: '{}'
25
- t.column :author_id, :integer
23
+ t.column :description, :text
24
+ t.column :preferences, :text, null: false
25
+ t.column :author_id, :bigint
26
26
  t.column :author_type, :string
27
27
  t.column :deleted_at, :datetime
28
28
 
29
29
  t.timestamps
30
30
 
31
31
  t.index :updated_at
32
- t.index 'lower(title)',
33
- name: 'motor_dashboards_lower_title_unique_index',
32
+ t.index 'title',
33
+ name: 'motor_dashboards_title_unique_index',
34
34
  unique: true,
35
35
  where: 'deleted_at IS NULL'
36
36
  end
37
37
 
38
38
  create_table :motor_forms do |t|
39
39
  t.column :name, :string, null: false
40
- t.column :description, :string
41
- t.column :api_path, :string, null: false
40
+ t.column :description, :text
41
+ t.column :api_path, :text, null: false
42
42
  t.column :http_method, :string, null: false
43
- t.column :preferences, :string, null: false, default: '{}'
44
- t.column :author_id, :integer
43
+ t.column :preferences, :text, null: false
44
+ t.column :author_id, :bigint
45
45
  t.column :author_type, :string
46
46
  t.column :deleted_at, :datetime
47
47
 
48
48
  t.timestamps
49
49
 
50
50
  t.index :updated_at
51
- t.index 'lower(name)',
52
- name: 'motor_forms_lower_name_unique_index',
51
+ t.index 'name',
52
+ name: 'motor_forms_name_unique_index',
53
53
  unique: true,
54
54
  where: 'deleted_at IS NULL'
55
55
  end
56
56
 
57
57
  create_table :motor_resources do |t|
58
58
  t.column :name, :string, null: false, index: { unique: true }
59
- t.column :preferences, :string, null: false, default: '{}'
59
+ t.column :preferences, :text, null: false
60
60
 
61
61
  t.timestamps
62
62
 
@@ -65,7 +65,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
65
65
 
66
66
  create_table :motor_configs do |t|
67
67
  t.column :key, :string, null: false, index: { unique: true }
68
- t.column :value, :string, null: false, default: '{}'
68
+ t.column :value, :text, null: false
69
69
 
70
70
  t.timestamps
71
71
 
@@ -75,19 +75,19 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
75
75
  create_table :motor_alerts do |t|
76
76
  t.references :query, null: false, foreign_key: { to_table: :motor_queries }, index: true
77
77
  t.column :name, :string, null: false
78
- t.column :description, :string
79
- t.column :to_emails, :string, null: false
78
+ t.column :description, :text
79
+ t.column :to_emails, :text, null: false
80
80
  t.column :is_enabled, :boolean, null: false, default: true
81
- t.column :preferences, :string, null: false, default: '{}'
82
- t.column :author_id, :integer
81
+ t.column :preferences, :text, null: false
82
+ t.column :author_id, :bigint
83
83
  t.column :author_type, :string
84
84
  t.column :deleted_at, :datetime
85
85
 
86
86
  t.timestamps
87
87
 
88
88
  t.index :updated_at
89
- t.index 'lower(name)',
90
- name: 'motor_alerts_lower_name_unique_index',
89
+ t.index 'name',
90
+ name: 'motor_alerts_name_unique_index',
91
91
  unique: true,
92
92
  where: 'deleted_at IS NULL'
93
93
  end
@@ -106,14 +106,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
106
106
 
107
107
  t.timestamps
108
108
 
109
- t.index 'lower(name)',
110
- name: 'motor_tags_lower_name_unique_index',
109
+ t.index 'name',
110
+ name: 'motor_tags_name_unique_index',
111
111
  unique: true
112
112
  end
113
113
 
114
114
  create_table :motor_taggable_tags do |t|
115
115
  t.references :tag, null: false, foreign_key: { to_table: :motor_tags }, index: true
116
- t.column :taggable_id, :integer, null: false
116
+ t.column :taggable_id, :bigint, null: false
117
117
  t.column :taggable_type, :string, null: false
118
118
 
119
119
  t.index %i[taggable_id taggable_type tag_id],
@@ -122,17 +122,17 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
122
122
  end
123
123
 
124
124
  create_table :motor_audits do |t|
125
- t.column :auditable_id, :integer
125
+ t.column :auditable_id, :bigint
126
126
  t.column :auditable_type, :string
127
- t.column :associated_id, :integer
127
+ t.column :associated_id, :bigint
128
128
  t.column :associated_type, :string
129
- t.column :user_id, :integer
129
+ t.column :user_id, :bigint
130
130
  t.column :user_type, :string
131
131
  t.column :username, :string
132
132
  t.column :action, :string
133
133
  t.column :audited_changes, :text
134
- t.column :version, :integer, default: 0
135
- t.column :comment, :string
134
+ t.column :version, :bigint, default: 0
135
+ t.column :comment, :text
136
136
  t.column :remote_address, :string
137
137
  t.column :request_uuid, :string
138
138
  t.column :created_at, :datetime
data/lib/motor.rb CHANGED
@@ -40,7 +40,6 @@ module Motor
40
40
  (defined?(::Puma) && File.basename($PROGRAM_NAME) == 'puma') ||
41
41
  defined?(::Unicorn::HttpServer) ||
42
42
  defined?(::Mongrel::HttpServer) ||
43
- defined?(::WEBrick::VERSION) ||
44
43
  defined?(JRuby::Rack::VERSION) ||
45
44
  defined?(::Trinidad::Server)
46
45
  end
@@ -1,6 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecordUtils
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module_function
6
+
7
+ def reset_id_sequence!(model)
8
+ case ActiveRecord::Base.connection.class.name
9
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
10
+ ActiveRecord::Base.connection.reset_pk_sequence!(model.table_name)
11
+ else
12
+ ActiveRecord::Base.connection.reset_sequence!(model.table_name, 'id')
13
+ end
14
+ end
15
+ end
4
16
  end
5
17
 
6
18
  require_relative './active_record_utils/types'
@@ -8,3 +20,4 @@ require_relative './active_record_utils/fetch_methods'
8
20
  require_relative './active_record_utils/defined_scopes_extension'
9
21
  require_relative './active_record_utils/active_storage_links_extension'
10
22
  require_relative './active_record_utils/active_storage_blob_patch'
23
+ require_relative './active_record_utils/active_record_filter'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Filter.module_eval do
4
+ def filters
5
+ @filters ||= HashWithIndifferentAccess.new
6
+ end
7
+ end
@@ -13,6 +13,10 @@ module Motor
13
13
  super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
14
14
  end
15
15
 
16
+ def create_after_upload!(hash)
17
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
18
+ end
19
+
16
20
  def create_after_unfurling!(hash)
17
21
  super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
18
22
  end
data/lib/motor/admin.rb CHANGED
@@ -43,7 +43,7 @@ module Motor
43
43
  end
44
44
 
45
45
  initializer 'motor.alerts.scheduler' do
46
- config.after_initialize do |_app|
46
+ config.after_initialize do
47
47
  next unless Motor.server?
48
48
 
49
49
  Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
@@ -66,11 +66,10 @@ module Motor
66
66
  end
67
67
 
68
68
  initializer 'motor.active_storage.extensions' do
69
- ActiveSupport.on_load(:active_storage_attachment) do
70
- ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
71
- end
69
+ config.after_initialize do
70
+ next unless defined?(ActiveStorage::Engine)
72
71
 
73
- ActiveSupport.on_load(:active_storage_blob) do
72
+ ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
74
73
  ActiveStorage::Blob.singleton_class.prepend(Motor::ActiveRecordUtils::ActiveStorageBlobPatch)
75
74
  end
76
75
  end
@@ -28,7 +28,7 @@ module Motor
28
28
  end
29
29
 
30
30
  def create_from_params!(params, current_user = nil)
31
- raise NameAlreadyExists if Alert.exists?(['lower(name) = ?', params[:name].to_s.downcase])
31
+ raise NameAlreadyExists if Alert.exists?(name: params[:name])
32
32
 
33
33
  alert = build_from_params(params, current_user)
34
34
 
@@ -77,7 +77,7 @@ module Motor
77
77
  end
78
78
 
79
79
  def archive_with_existing_name(alert)
80
- Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
80
+ Motor::Alert.where(['name = ? AND id != ?', alert.name, alert.id])
81
81
  .update_all(deleted_at: Time.current)
82
82
  end
83
83
 
@@ -87,9 +87,9 @@ module Motor
87
87
 
88
88
  def name_already_exists?(alert)
89
89
  if alert.new_record?
90
- Alert.exists?(['lower(name) = ?', alert.name.to_s.downcase])
90
+ Alert.exists?(name: alert.name)
91
91
  else
92
- Alert.exists?(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
92
+ Alert.exists?(['name = ? AND id != ?', alert.name, alert.id])
93
93
  end
94
94
  end
95
95
  end
@@ -3,7 +3,7 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module Scheduler
6
- SCHEDULER_INTERVAL = 10.seconds
6
+ SCHEDULER_INTERVAL = 1.minute
7
7
  CHECK_BEHIND_DURATION = 15.minutes
8
8
 
9
9
  SCHEDULER_TASK = Concurrent::TimerTask.new(
@@ -10,12 +10,12 @@ module Motor
10
10
 
11
11
  rel = rel.preload_associations_lazily if rel.is_a?(ActiveRecord::Relation)
12
12
 
13
- json_params = {}
13
+ json_params = {}.with_indifferent_access
14
14
 
15
15
  assign_include_params(json_params, rel, params)
16
16
  assign_fields_params(json_params, rel, params)
17
17
 
18
- rel.as_json(json_params.with_indifferent_access)
18
+ rel.as_json(json_params)
19
19
  end
20
20
 
21
21
  def assign_include_params(json_params, _rel, api_params)
@@ -51,11 +51,11 @@ module Motor
51
51
  params[:fields].each do |key, fields|
52
52
  fields = fields.split(',') if fields.is_a?(String)
53
53
 
54
- merge_fields_params!(key, fields, json_params, model)
54
+ merge_fields_params!(json_params, key, fields, model)
55
55
  end
56
56
  end
57
57
 
58
- def merge_fields_params!(key, fields, json_params, model)
58
+ def merge_fields_params!(json_params, key, fields, model)
59
59
  model_name = model.name.underscore
60
60
 
61
61
  if key == model_name || model_name.split('/').last == key
@@ -11,6 +11,7 @@ module Motor
11
11
  params ||= {}
12
12
 
13
13
  rel = rel.limit([MAX_PER_PAGE, (params[:limit] || MAX_PER_PAGE).to_i].min)
14
+
14
15
  rel.offset(params[:offset].to_i)
15
16
  end
16
17
  end
@@ -13,7 +13,9 @@ 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.reorder(arel_order).left_joins(join_params)
16
+ rel = rel.left_joins(join_params) if join_params.present?
17
+
18
+ rel.reorder(arel_order)
17
19
  end
18
20
 
19
21
  def build_join_params(_model, param)
data/lib/motor/assets.rb CHANGED
@@ -35,6 +35,8 @@ module Motor
35
35
  def load_from_disk(filename, gzip:)
36
36
  filename += '.gz' if gzip
37
37
 
38
+ raise InvalidPathError if filename.include?('..')
39
+
38
40
  path = ASSETS_PATH.join(filename)
39
41
 
40
42
  raise InvalidPathError unless path.to_s.starts_with?(ASSETS_PATH.to_s)
@@ -12,6 +12,8 @@ module Motor
12
12
  models.map do |model|
13
13
  Object.const_get(model.name)
14
14
 
15
+ next unless ActiveRecord::Base.connection.table_exists?(model.table_name)
16
+
15
17
  schema = build_model_schema(model)
16
18
 
17
19
  if model.respond_to?(:devise_modules)
@@ -20,7 +22,7 @@ module Motor
20
22
 
21
23
  schema
22
24
  rescue StandardError, NotImplementedError => e
23
- Rails.logger.error(e) if model.name != 'Audited::Audit'
25
+ Rails.logger.error(e)
24
26
 
25
27
  next
26
28
  end.compact
@@ -34,6 +36,7 @@ module Motor
34
36
  models -= Motor::ApplicationRecord.descendants
35
37
  models -= [Motor::Audit]
36
38
  models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
39
+ models -= [ActiveRecord::InternalMetadata] if defined?(ActiveRecord::InternalMetadata)
37
40
  models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
38
41
  models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
39
42
 
@@ -101,7 +104,7 @@ module Motor
101
104
  name: column.name,
102
105
  display_name: Utils.humanize_column_name(column.name),
103
106
  column_type: is_enum ? 'string' : UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
104
- is_array: column.array?,
107
+ is_array: column.respond_to?(:array?) && column.array?,
105
108
  access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
106
109
  default_value: default_attrs[column.name],
107
110
  validators: fetch_validators(model, column.name),
@@ -3,7 +3,7 @@
3
3
  module Motor
4
4
  module Configs
5
5
  module LoadFromCache
6
- CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
6
+ CACHE_HASH = HashWithIndifferentAccess.new
7
7
 
8
8
  module_function
9
9
 
@@ -56,25 +56,37 @@ module Motor
56
56
  end
57
57
  end
58
58
 
59
- def maybe_fetch_from_cache(type, cache_key, &block)
59
+ def maybe_fetch_from_cache(type, cache_key)
60
60
  return yield unless cache_key
61
61
 
62
- CACHE_STORE.fetch(type + cache_key.to_s, &block)
62
+ if CACHE_HASH[type] && CACHE_HASH[type][:key] == cache_key
63
+ CACHE_HASH[type][:value]
64
+ else
65
+ result = yield
66
+
67
+ CACHE_HASH[type] = { key: cache_key, value: result }
68
+
69
+ result
70
+ end
63
71
  end
64
72
 
65
73
  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
74
+ result = ActiveRecord::Base.connection.execute(cache_keys_sql).to_a
75
+
76
+ result = result.map(&:values) if result.first.is_a?(Hash)
77
+
78
+ result.to_h.with_indifferent_access
79
+ end
80
+
81
+ def cache_keys_sql
82
+ [
83
+ Motor::Config.select("'configs', MAX(updated_at)").to_sql,
84
+ Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
85
+ Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
86
+ Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
87
+ Motor::Query.select("'queries', MAX(updated_at)").to_sql,
88
+ Motor::Form.select("'forms', MAX(updated_at)").to_sql
89
+ ].join(' UNION ')
78
90
  end
79
91
  end
80
92
  end
@@ -87,7 +87,7 @@ module Motor
87
87
 
88
88
  archive_taggable_items(records - processed_records, configs_timestamp)
89
89
 
90
- ActiveRecord::Base.connection.reset_pk_sequence!(records.klass.table_name)
90
+ ActiveRecordUtils.reset_id_sequence!(records.klass)
91
91
  end
92
92
 
93
93
  def update_taggable_items(records, config_items, update_proc)
@@ -9,6 +9,7 @@ module Motor
9
9
  module_function
10
10
 
11
11
  def call
12
+ return if Rails.env.test?
12
13
  return if THREAD_POOL.queue_length.positive?
13
14
 
14
15
  THREAD_POOL.post do
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise TitleAlreadyExists if Dashboard.exists?(['lower(title) = ?', params[:title].to_s.downcase])
19
+ raise TitleAlreadyExists if Dashboard.exists?(title: params[:title])
20
20
 
21
21
  dashboard = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(dashboard)
60
- Motor::Dashboard.where(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
60
+ Motor::Dashboard.where(['title = ? AND id != ?', dashboard.title, dashboard.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def title_already_exists?(dashboard)
65
65
  if dashboard.new_record?
66
- Motor::Dashboard.exists?(['lower(title) = ?', dashboard.title.to_s.downcase])
66
+ Motor::Dashboard.exists?(title: dashboard.title)
67
67
  else
68
- Motor::Dashboard.exists?(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
68
+ Motor::Dashboard.exists?(['title = ? AND id != ?', dashboard.title, dashboard.id])
69
69
  end
70
70
  end
71
71
  end
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise NameAlreadyExists if Form.exists?(['lower(name) = ?', params[:name].to_s.downcase])
19
+ raise NameAlreadyExists if Form.exists?(name: params[:name])
20
20
 
21
21
  form = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(form)
60
- Motor::Form.where(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
60
+ Motor::Form.where(['name = ? AND id != ?', form.name, form.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def name_already_exists?(form)
65
65
  if form.new_record?
66
- Motor::Form.exists?(['lower(name) = ?', form.name.to_s.downcase])
66
+ Motor::Form.exists?(['name = ?', form.name])
67
67
  else
68
- Motor::Form.exists?(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
68
+ Motor::Form.exists?(['name = ? AND id != ?', form.name, form.id])
69
69
  end
70
70
  end
71
71
  end
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise NameAlreadyExists if Query.exists?(['lower(name) = ?', params[:name].to_s.downcase])
19
+ raise NameAlreadyExists if Query.exists?(name: params[:name])
20
20
 
21
21
  query = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(query)
60
- Motor::Query.where(['lower(name) = ? AND id != ?', query.name.to_s.downcase, query.id])
60
+ Motor::Query.where(['name = ? AND id != ?', query.name, query.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def name_already_exists?(query)
65
65
  if query.new_record?
66
- Query.exists?(['lower(name) = ?', query.name.to_s.downcase])
66
+ Query.exists?(name: query.name)
67
67
  else
68
- Query.exists?(['lower(name) = ? AND id != ?', query.name.to_s.downcase, query.id])
68
+ Query.exists?(['name = ? AND id != ?', query.name, query.id])
69
69
  end
70
70
  end
71
71
  end
@@ -5,19 +5,24 @@ module Motor
5
5
  module RunQuery
6
6
  DEFAULT_LIMIT = 100_000
7
7
 
8
- QueryResult = Struct.new(:data, :columns, keyword_init: true)
8
+ QueryResult = Struct.new(:data, :columns, :error, keyword_init: true)
9
+
10
+ WITH_STATEMENT_START = 'WITH __query__ AS ('
9
11
 
10
12
  WITH_STATEMENT_TEMPLATE = <<~SQL
11
- WITH __query__ AS (%<sql_body>s) SELECT * FROM __query__ LIMIT %<limit>s;
13
+ #{WITH_STATEMENT_START}%<sql_body>s
14
+ ) SELECT * FROM __query__ LIMIT %<limit>s;
12
15
  SQL
13
16
 
17
+ PG_ERROR_REGEXP = /\APG.+ERROR:/.freeze
18
+
14
19
  module_function
15
20
 
16
21
  # @param query [Motor::Query]
17
22
  # @param variables_hash [Hash]
18
23
  # @param limit [Integer]
19
24
  # @return [Motor::Queries::RunQuery::QueryResult]
20
- def call(query, variables_hash: nil, limit: DEFAULT_LIMIT)
25
+ def call!(query, variables_hash: nil, limit: DEFAULT_LIMIT)
21
26
  variables_hash ||= {}
22
27
 
23
28
  result = execute_query(query, limit, variables_hash)
@@ -25,6 +30,22 @@ module Motor
25
30
  QueryResult.new(data: result.rows, columns: build_columns_hash(result))
26
31
  end
27
32
 
33
+ # @param query [Motor::Query]
34
+ # @param variables_hash [Hash]
35
+ # @param limit [Integer]
36
+ # @return [Motor::Queries::RunQuery::QueryResult]
37
+ def call(query, variables_hash: nil, limit: DEFAULT_LIMIT)
38
+ call!(query, variables_hash: variables_hash, limit: limit)
39
+ rescue ActiveRecord::StatementInvalid => e
40
+ QueryResult.new(error: build_error_message(e))
41
+ end
42
+
43
+ # @param exception [ActiveRecord::StatementInvalid]
44
+ # @return [String]
45
+ def build_error_message(exception)
46
+ exception.message.sub(WITH_STATEMENT_START, '').sub(PG_ERROR_REGEXP, '').strip.upcase_first
47
+ end
48
+
28
49
  # @param query [Motor::Query]
29
50
  # @param limit [Integer]
30
51
  # @param variables_hash [Hash]
@@ -35,8 +56,8 @@ module Motor
35
56
 
36
57
  ActiveRecord::Base.transaction do
37
58
  result =
38
- case ActiveRecord::Base.connection
39
- when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
59
+ case ActiveRecord::Base.connection.class.name
60
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
40
61
  PostgresqlExecQuery.call(ActiveRecord::Base.connection, statement)
41
62
  else
42
63
  ActiveRecord::Base.connection.exec_query(*statement)
data/lib/motor/tags.rb CHANGED
@@ -10,7 +10,7 @@ module Motor
10
10
  tags.each do |tag_name|
11
11
  next if taggable.taggable_tags.find { |tt| tt.tag.name.casecmp(tag_name).zero? }
12
12
 
13
- tag = Tag.where('lower(name) = ?', tag_name.downcase).take || Tag.new(name: tag_name)
13
+ tag = Tag.find_or_initialize_by(name: tag_name)
14
14
 
15
15
  taggable.taggable_tags.new(tag: tag)
16
16
  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.49'
4
+ VERSION = '0.1.54'
5
5
  end
@@ -2068,11 +2068,11 @@
2068
2068
  "mail-opened.svg": "icons/mail-opened.svg",
2069
2069
  "mail.svg": "icons/mail.svg",
2070
2070
  "mailbox.svg": "icons/mailbox.svg",
2071
- "main-918b506ecd64910a5845.css.gz": "main-918b506ecd64910a5845.css.gz",
2072
- "main-918b506ecd64910a5845.js.LICENSE.txt": "main-918b506ecd64910a5845.js.LICENSE.txt",
2073
- "main-918b506ecd64910a5845.js.gz": "main-918b506ecd64910a5845.js.gz",
2074
- "main.css": "main-918b506ecd64910a5845.css",
2075
- "main.js": "main-918b506ecd64910a5845.js",
2071
+ "main-67fbb3a8f6a6388434c8.css.gz": "main-67fbb3a8f6a6388434c8.css.gz",
2072
+ "main-67fbb3a8f6a6388434c8.js.LICENSE.txt": "main-67fbb3a8f6a6388434c8.js.LICENSE.txt",
2073
+ "main-67fbb3a8f6a6388434c8.js.gz": "main-67fbb3a8f6a6388434c8.js.gz",
2074
+ "main.css": "main-67fbb3a8f6a6388434c8.css",
2075
+ "main.js": "main-67fbb3a8f6a6388434c8.js",
2076
2076
  "man.svg": "icons/man.svg",
2077
2077
  "manual-gearbox.svg": "icons/manual-gearbox.svg",
2078
2078
  "map-2.svg": "icons/map-2.svg",
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.49
4
+ version: 0.1.54
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-25 00:00:00.000000000 Z
11
+ date: 2021-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -108,7 +108,10 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '5.2'
111
- description: Admin UI and Business Analytics.
111
+ description: |
112
+ Motor Admin allows to create a flexible admin panel with writing less code.
113
+ All customizations to the admin panel can be made directly in the UI without
114
+ the need of writing any configurations code.
112
115
  email:
113
116
  - pete.matsy@gmail.com
114
117
  executables: []
@@ -165,6 +168,7 @@ files:
165
168
  - lib/motor-admin.rb
166
169
  - lib/motor.rb
167
170
  - lib/motor/active_record_utils.rb
171
+ - lib/motor/active_record_utils/active_record_filter.rb
168
172
  - lib/motor/active_record_utils/active_storage_blob_patch.rb
169
173
  - lib/motor/active_record_utils/active_storage_links_extension.rb
170
174
  - lib/motor/active_record_utils/defined_scopes_extension.rb
@@ -1481,8 +1485,8 @@ files:
1481
1485
  - ui/dist/icons/zoom-money.svg.gz
1482
1486
  - ui/dist/icons/zoom-out.svg.gz
1483
1487
  - ui/dist/icons/zoom-question.svg.gz
1484
- - ui/dist/main-918b506ecd64910a5845.css.gz
1485
- - ui/dist/main-918b506ecd64910a5845.js.gz
1488
+ - ui/dist/main-67fbb3a8f6a6388434c8.css.gz
1489
+ - ui/dist/main-67fbb3a8f6a6388434c8.js.gz
1486
1490
  - ui/dist/manifest.json
1487
1491
  homepage:
1488
1492
  licenses:
@@ -1506,5 +1510,5 @@ requirements: []
1506
1510
  rubygems_version: 3.2.3
1507
1511
  signing_key:
1508
1512
  specification_version: 4
1509
- summary: Admin UI and Business Analytics
1513
+ summary: Low-code Admin panel and Business intelligence
1510
1514
  test_files: []