motor-admin 0.1.48 → 0.1.53
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 +4 -4
- data/README.md +1 -1
- data/app/controllers/motor/api_base_controller.rb +5 -3
- data/app/controllers/motor/run_queries_controller.rb +10 -8
- data/app/controllers/motor/ui_controller.rb +10 -4
- data/app/models/motor/alert.rb +1 -0
- data/app/models/motor/dashboard.rb +1 -0
- data/app/models/motor/form.rb +1 -0
- data/app/models/motor/query.rb +2 -1
- data/app/models/motor/resource.rb +1 -0
- data/config/routes.rb +8 -9
- data/lib/generators/motor/templates/install.rb +33 -33
- data/lib/motor.rb +0 -1
- data/lib/motor/active_record_utils.rb +13 -1
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +4 -0
- data/lib/motor/admin.rb +4 -5
- data/lib/motor/alerts/persistance.rb +4 -4
- data/lib/motor/alerts/scheduler.rb +1 -1
- data/lib/motor/api_query/build_json.rb +4 -4
- data/lib/motor/api_query/paginate.rb +1 -0
- data/lib/motor/api_query/sort.rb +3 -1
- data/lib/motor/assets.rb +2 -0
- data/lib/motor/build_schema/load_from_rails.rb +5 -2
- data/lib/motor/configs/load_from_cache.rb +27 -15
- data/lib/motor/configs/sync_from_hash.rb +1 -1
- data/lib/motor/configs/write_to_file.rb +1 -0
- data/lib/motor/dashboards/persistance.rb +4 -4
- data/lib/motor/forms/persistance.rb +4 -4
- data/lib/motor/queries/persistance.rb +4 -4
- data/lib/motor/queries/run_query.rb +26 -5
- data/lib/motor/tags.rb +1 -1
- data/lib/motor/version.rb +1 -1
- data/ui/dist/main-67fbb3a8f6a6388434c8.css.gz +0 -0
- data/ui/dist/main-67fbb3a8f6a6388434c8.js.gz +0 -0
- data/ui/dist/manifest.json +5 -5
- metadata +9 -6
- data/ui/dist/main-af386d0c907b80c29f22.css.gz +0 -0
- data/ui/dist/main-af386d0c907b80c29f22.js.gz +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2484e6d98ef1891e59ab7bc0e50dc8337f0b99ae3fb35e2d5d72caf84020112c
|
4
|
+
data.tar.gz: 85a1e6334f83ccdd8e49b6f80ef52ff7ef8f169b586fc00b0745d281e1b75f9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dabb17786ed10544c54246f0a460cb3507d9cca5cb8269aa4e0cf0962d69f55cb8f63870f0c6ecfe2810da7c174f2cce7f75702f3472be0221a02ad49babb159
|
7
|
+
data.tar.gz: 2a5c5b2c319248b692f6d662c510ca489066a192c613714c9e970352db3a06994d032d01052707799627bf81bca0b6422a399f8584925baa1658d44706043c67
|
data/README.md
CHANGED
@@ -82,7 +82,7 @@ SQL queries can be organized into dashboards to create a convenient representati
|
|
82
82
|
|
83
83
|
Query data can be sent via email periodically using the alerts feature. Interval of the alert email can be specified using natural language, e.g., `every day at midnight`, `every Monday at 8 PM`, `every weekday at 6AM and 6PM`, `every minute`.
|
84
84
|
|
85
|
-
|
85
|
+
Sender address can be specified using `MOTOR_ALERTS_FROM_ADDRESS` environment variable.
|
86
86
|
|
87
87
|
### Intelligence Search
|
88
88
|
|
@@ -12,10 +12,12 @@ module Motor
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
unless Rails.env.test?
|
16
|
+
rescue_from StandardError do |e|
|
17
|
+
Rails.logger.error(e)
|
17
18
|
|
18
|
-
|
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
|
-
|
13
|
+
render_result
|
18
14
|
end
|
19
15
|
|
20
16
|
def create
|
21
|
-
|
17
|
+
render_result
|
22
18
|
end
|
23
19
|
|
24
20
|
private
|
25
21
|
|
26
|
-
def
|
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
|
-
|
11
|
-
|
12
|
-
Motor::Configs::SyncFromFile.call
|
10
|
+
render_ui
|
11
|
+
end
|
13
12
|
|
14
|
-
|
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
|
data/app/models/motor/alert.rb
CHANGED
@@ -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) }
|
data/app/models/motor/form.rb
CHANGED
@@ -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) }
|
data/app/models/motor/query.rb
CHANGED
@@ -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 }
|
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:
|
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:
|
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, :
|
6
|
-
t.column :sql_body, :
|
7
|
-
t.column :preferences, :
|
8
|
-
t.column :author_id, :
|
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 '
|
16
|
-
name: '
|
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, :
|
24
|
-
t.column :preferences, :
|
25
|
-
t.column :author_id, :
|
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 '
|
33
|
-
name: '
|
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, :
|
41
|
-
t.column :api_path, :
|
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, :
|
44
|
-
t.column :author_id, :
|
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 '
|
52
|
-
name: '
|
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, :
|
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, :
|
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, :
|
79
|
-
t.column :to_emails, :
|
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, :
|
82
|
-
t.column :author_id, :
|
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 '
|
90
|
-
name: '
|
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 '
|
110
|
-
name: '
|
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, :
|
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, :
|
125
|
+
t.column :auditable_id, :bigint
|
126
126
|
t.column :auditable_type, :string
|
127
|
-
t.column :associated_id, :
|
127
|
+
t.column :associated_id, :bigint
|
128
128
|
t.column :associated_type, :string
|
129
|
-
t.column :user_id, :
|
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, :
|
135
|
-
t.column :comment, :
|
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
|
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'
|
@@ -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
|
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
|
-
|
70
|
-
ActiveStorage::
|
71
|
-
end
|
69
|
+
config.after_initialize do
|
70
|
+
next unless defined?(ActiveStorage::Engine)
|
72
71
|
|
73
|
-
|
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?(
|
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(['
|
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?(
|
90
|
+
Alert.exists?(name: alert.name)
|
91
91
|
else
|
92
|
-
Alert.exists?(['
|
92
|
+
Alert.exists?(['name = ? AND id != ?', alert.name, alert.id])
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -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
|
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,
|
54
|
+
merge_fields_params!(json_params, key, fields, model)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def merge_fields_params!(key, fields,
|
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
|
data/lib/motor/api_query/sort.rb
CHANGED
@@ -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.
|
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
@@ -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)
|
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
|
-
|
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
|
59
|
+
def maybe_fetch_from_cache(type, cache_key)
|
60
60
|
return yield unless cache_key
|
61
61
|
|
62
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
90
|
+
ActiveRecordUtils.reset_id_sequence!(records.klass)
|
91
91
|
end
|
92
92
|
|
93
93
|
def update_taggable_items(records, config_items, update_proc)
|
@@ -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?(
|
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(['
|
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?(
|
66
|
+
Motor::Dashboard.exists?(title: dashboard.title)
|
67
67
|
else
|
68
|
-
Motor::Dashboard.exists?(['
|
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?(
|
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(['
|
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?(['
|
66
|
+
Motor::Form.exists?(['name = ?', form.name])
|
67
67
|
else
|
68
|
-
Motor::Form.exists?(['
|
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?(
|
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(['
|
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?(
|
66
|
+
Query.exists?(name: query.name)
|
67
67
|
else
|
68
|
-
Query.exists?(['
|
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
|
-
|
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.
|
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
Binary file
|
Binary file
|
data/ui/dist/manifest.json
CHANGED
@@ -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-
|
2072
|
-
"main-
|
2073
|
-
"main-
|
2074
|
-
"main.css": "main-
|
2075
|
-
"main.js": "main-
|
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.
|
4
|
+
version: 0.1.53
|
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-
|
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:
|
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: []
|
@@ -1481,8 +1484,8 @@ files:
|
|
1481
1484
|
- ui/dist/icons/zoom-money.svg.gz
|
1482
1485
|
- ui/dist/icons/zoom-out.svg.gz
|
1483
1486
|
- ui/dist/icons/zoom-question.svg.gz
|
1484
|
-
- ui/dist/main-
|
1485
|
-
- ui/dist/main-
|
1487
|
+
- ui/dist/main-67fbb3a8f6a6388434c8.css.gz
|
1488
|
+
- ui/dist/main-67fbb3a8f6a6388434c8.js.gz
|
1486
1489
|
- ui/dist/manifest.json
|
1487
1490
|
homepage:
|
1488
1491
|
licenses:
|
@@ -1506,5 +1509,5 @@ requirements: []
|
|
1506
1509
|
rubygems_version: 3.2.3
|
1507
1510
|
signing_key:
|
1508
1511
|
specification_version: 4
|
1509
|
-
summary: Admin
|
1512
|
+
summary: Low-code Admin panel and Business intelligence
|
1510
1513
|
test_files: []
|
Binary file
|
Binary file
|