blazer 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of blazer might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +261 -45
- data/app/assets/javascripts/blazer/Sortable.js +1144 -0
- data/app/assets/javascripts/blazer/application.js +2 -1
- data/app/assets/javascripts/blazer/chartkick.js +935 -0
- data/app/assets/javascripts/blazer/selectize.js +391 -201
- data/app/assets/stylesheets/blazer/application.css +17 -2
- data/app/assets/stylesheets/blazer/selectize.default.css +3 -2
- data/app/controllers/blazer/base_controller.rb +48 -0
- data/app/controllers/blazer/checks_controller.rb +51 -0
- data/app/controllers/blazer/dashboards_controller.rb +94 -0
- data/app/controllers/blazer/queries_controller.rb +29 -101
- data/app/helpers/blazer/{queries_helper.rb → base_helper.rb} +1 -1
- data/app/mailers/blazer/check_mailer.rb +21 -0
- data/app/models/blazer/check.rb +28 -0
- data/app/models/blazer/connection.rb +0 -1
- data/app/models/blazer/dashboard.rb +12 -0
- data/app/models/blazer/dashboard_query.rb +9 -0
- data/app/models/blazer/query.rb +5 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +6 -0
- data/app/views/blazer/checks/_form.html.erb +28 -0
- data/app/views/blazer/checks/edit.html.erb +1 -0
- data/app/views/blazer/checks/index.html.erb +41 -0
- data/app/views/blazer/checks/new.html.erb +1 -0
- data/app/views/blazer/checks/run.html.erb +9 -0
- data/app/views/blazer/dashboards/_form.html.erb +86 -0
- data/app/views/blazer/dashboards/edit.html.erb +1 -0
- data/app/views/blazer/dashboards/index.html.erb +21 -0
- data/app/views/blazer/dashboards/new.html.erb +1 -0
- data/app/views/blazer/dashboards/show.html.erb +148 -0
- data/app/views/blazer/queries/_form.html.erb +16 -5
- data/app/views/blazer/queries/_tables.html +5 -0
- data/app/views/blazer/queries/index.html.erb +6 -0
- data/app/views/blazer/queries/run.html.erb +59 -44
- data/app/views/blazer/queries/show.html.erb +20 -16
- data/config/routes.rb +5 -0
- data/lib/blazer.rb +46 -2
- data/lib/blazer/data_source.rb +70 -0
- data/lib/blazer/engine.rb +6 -2
- data/lib/blazer/tasks.rb +12 -0
- data/lib/blazer/version.rb +1 -1
- data/lib/generators/blazer/templates/config.yml +26 -6
- data/lib/generators/blazer/templates/install.rb +21 -0
- metadata +27 -3
@@ -16,11 +16,11 @@ body {
|
|
16
16
|
padding-bottom: 20px;
|
17
17
|
}
|
18
18
|
|
19
|
-
|
19
|
+
.results-table th {
|
20
20
|
cursor: pointer;
|
21
21
|
}
|
22
22
|
|
23
|
-
|
23
|
+
.results-table thead {
|
24
24
|
background-color: #fff;
|
25
25
|
}
|
26
26
|
|
@@ -68,3 +68,18 @@ input.search:focus {
|
|
68
68
|
.ace_gutter-cell.error {
|
69
69
|
background-color: red;
|
70
70
|
}
|
71
|
+
|
72
|
+
.chart {
|
73
|
+
height: 300px;
|
74
|
+
text-align: center;
|
75
|
+
display: flex;
|
76
|
+
justify-content: center;
|
77
|
+
flex-direction: column;
|
78
|
+
}
|
79
|
+
|
80
|
+
.chart > .results-container {
|
81
|
+
height: 300px;
|
82
|
+
border: solid 1px #ddd;
|
83
|
+
overflow: scroll;
|
84
|
+
text-align: left;
|
85
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* selectize.default.css (v0.
|
3
|
-
* Copyright (c) 2013 Brian Reavis & contributors
|
2
|
+
* selectize.default.css (v0.12.1) - Default Theme
|
3
|
+
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
4
4
|
*
|
5
5
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
6
6
|
* file except in compliance with the License. You may obtain a copy of the License at:
|
@@ -189,6 +189,7 @@
|
|
189
189
|
border: 1px solid #aaaaaa;
|
190
190
|
}
|
191
191
|
.selectize-input > input {
|
192
|
+
display: inline-block !important;
|
192
193
|
padding: 0 !important;
|
193
194
|
min-height: 0 !important;
|
194
195
|
max-height: none !important;
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Blazer
|
2
|
+
class BaseController < ApplicationController
|
3
|
+
# skip all filters
|
4
|
+
skip_filter *_process_action_callbacks.map(&:filter)
|
5
|
+
|
6
|
+
protect_from_forgery with: :exception
|
7
|
+
|
8
|
+
if ENV["BLAZER_PASSWORD"]
|
9
|
+
http_basic_authenticate_with name: ENV["BLAZER_USERNAME"], password: ENV["BLAZER_PASSWORD"]
|
10
|
+
end
|
11
|
+
|
12
|
+
layout "blazer/application"
|
13
|
+
|
14
|
+
before_action :ensure_database_url
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def ensure_database_url
|
19
|
+
render text: "BLAZER_DATABASE_URL required" if !ENV["BLAZER_DATABASE_URL"] && !Rails.env.development?
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_vars(statement)
|
23
|
+
(@bind_vars ||= []).concat(extract_vars(statement)).uniq!
|
24
|
+
@success = @bind_vars.all? { |v| params[v] }
|
25
|
+
|
26
|
+
if @success
|
27
|
+
@bind_vars.each do |var|
|
28
|
+
value = params[var].presence
|
29
|
+
value = value.to_i if value.to_i.to_s == value
|
30
|
+
if var.end_with?("_at")
|
31
|
+
value = Blazer.time_zone.parse(value) rescue nil
|
32
|
+
end
|
33
|
+
statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_vars(statement)
|
39
|
+
statement.scan(/\{.*?\}/).map { |v| v[1...-1] }.uniq
|
40
|
+
end
|
41
|
+
helper_method :extract_vars
|
42
|
+
|
43
|
+
def variable_params
|
44
|
+
params.except(:controller, :action, :id, :host, :query, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source)
|
45
|
+
end
|
46
|
+
helper_method :variable_params
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Blazer
|
2
|
+
class ChecksController < BaseController
|
3
|
+
before_action :set_check, only: [:edit, :update, :destroy, :run]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@checks = Blazer::Check.joins(:query).includes(:query).order("state, blazer_queries.name, blazer_checks.id").to_a
|
7
|
+
@checks.select! { |c| "#{c.query.name} #{c.emails}".downcase.include?(params[:q]) } if params[:q]
|
8
|
+
end
|
9
|
+
|
10
|
+
def new
|
11
|
+
@check = Blazer::Check.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
@check = Blazer::Check.new(check_params)
|
16
|
+
|
17
|
+
if @check.save
|
18
|
+
redirect_to run_check_path(@check)
|
19
|
+
else
|
20
|
+
render :new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def update
|
25
|
+
if @check.update(check_params)
|
26
|
+
redirect_to run_check_path(@check)
|
27
|
+
else
|
28
|
+
render :edit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy
|
33
|
+
@check.destroy
|
34
|
+
redirect_to checks_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
@query = @check.query
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def check_params
|
44
|
+
params.require(:check).permit(:query_id, :emails)
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_check
|
48
|
+
@check = Blazer::Check.find(params[:id])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Blazer
|
2
|
+
class DashboardsController < BaseController
|
3
|
+
before_action :set_dashboard, only: [:show, :edit, :update, :destroy]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@dashboards = Blazer::Dashboard.order(:name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def new
|
10
|
+
@dashboard = Blazer::Dashboard.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@dashboard = Blazer::Dashboard.new
|
15
|
+
|
16
|
+
if update_dashboard(@dashboard)
|
17
|
+
redirect_to dashboard_path(@dashboard)
|
18
|
+
else
|
19
|
+
render :new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def show
|
24
|
+
@queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
|
25
|
+
@queries.each do |query|
|
26
|
+
process_vars(query.statement)
|
27
|
+
end
|
28
|
+
@bind_vars ||= []
|
29
|
+
|
30
|
+
@smart_vars = {}
|
31
|
+
@sql_errors = []
|
32
|
+
data_sources = @queries.map { |q| Blazer.data_sources[q.data_source] }.uniq
|
33
|
+
@bind_vars.each do |var|
|
34
|
+
data_sources.each do |data_source|
|
35
|
+
query = data_source.smart_variables[var]
|
36
|
+
if query
|
37
|
+
rows, error = data_source.run_statement(query)
|
38
|
+
(@smart_vars[var] ||= []).concat rows.map { |v| v.values.reverse }
|
39
|
+
@sql_errors << error if error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def edit
|
46
|
+
end
|
47
|
+
|
48
|
+
def update
|
49
|
+
if update_dashboard(@dashboard)
|
50
|
+
redirect_to dashboard_path(@dashboard)
|
51
|
+
else
|
52
|
+
render :edit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy
|
57
|
+
@dashboard.destroy
|
58
|
+
redirect_to dashboards_path
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def dashboard_params
|
64
|
+
params.require(:dashboard).permit(:name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_dashboard
|
68
|
+
@dashboard = Blazer::Dashboard.find(params[:id])
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_dashboard(dashboard)
|
72
|
+
dashboard.assign_attributes(dashboard_params)
|
73
|
+
Blazer::Dashboard.transaction do
|
74
|
+
if params[:query_ids].is_a?(Array)
|
75
|
+
query_ids = params[:query_ids].map(&:to_i)
|
76
|
+
@queries = Blazer::Query.find(query_ids).sort_by { |q| query_ids.index(q.id) }
|
77
|
+
end
|
78
|
+
if dashboard.save
|
79
|
+
if @queries
|
80
|
+
@queries.each_with_index do |query, i|
|
81
|
+
dashboard_query = dashboard.dashboard_queries.where(query_id: query.id).first_or_initialize
|
82
|
+
dashboard_query.position = i
|
83
|
+
dashboard_query.save!
|
84
|
+
end
|
85
|
+
if dashboard.persisted?
|
86
|
+
dashboard.dashboard_queries.where.not(query_id: query_ids).destroy_all
|
87
|
+
end
|
88
|
+
end
|
89
|
+
true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,27 +1,16 @@
|
|
1
1
|
module Blazer
|
2
|
-
class QueriesController <
|
3
|
-
# skip all filters
|
4
|
-
skip_filter *_process_action_callbacks.map(&:filter)
|
5
|
-
|
6
|
-
protect_from_forgery with: :exception
|
7
|
-
|
8
|
-
if ENV["BLAZER_PASSWORD"]
|
9
|
-
http_basic_authenticate_with name: ENV["BLAZER_USERNAME"], password: ENV["BLAZER_PASSWORD"]
|
10
|
-
end
|
11
|
-
|
12
|
-
layout "blazer/application"
|
13
|
-
|
14
|
-
before_action :ensure_database_url
|
2
|
+
class QueriesController < BaseController
|
15
3
|
before_action :set_query, only: [:show, :edit, :update, :destroy]
|
16
4
|
|
17
5
|
def index
|
18
6
|
@queries = Blazer::Query.order(:name)
|
19
7
|
@queries = @queries.includes(:creator) if Blazer.user_class
|
20
8
|
@trending_queries = Blazer::Audit.group(:query_id).where("created_at > ?", 2.days.ago).having("COUNT(DISTINCT user_id) >= 3").uniq.count(:user_id)
|
9
|
+
@checks = Blazer::Check.group(:query_id).count
|
21
10
|
end
|
22
11
|
|
23
12
|
def new
|
24
|
-
@query = Blazer::Query.new(statement: params[:statement])
|
13
|
+
@query = Blazer::Query.new(statement: params[:statement], data_source: params[:data_source])
|
25
14
|
end
|
26
15
|
|
27
16
|
def create
|
@@ -41,10 +30,11 @@ module Blazer
|
|
41
30
|
|
42
31
|
@smart_vars = {}
|
43
32
|
@sql_errors = []
|
33
|
+
data_source = Blazer.data_sources[@query.data_source]
|
44
34
|
@bind_vars.each do |var|
|
45
|
-
query = smart_variables[var]
|
35
|
+
query = data_source.smart_variables[var]
|
46
36
|
if query
|
47
|
-
rows, error = run_statement(query)
|
37
|
+
rows, error = data_source.run_statement(query)
|
48
38
|
@smart_vars[var] = rows.map { |v| v.values.reverse }
|
49
39
|
@sql_errors << error if error
|
50
40
|
end
|
@@ -57,19 +47,31 @@ module Blazer
|
|
57
47
|
def run
|
58
48
|
@statement = params[:statement]
|
59
49
|
process_vars(@statement)
|
50
|
+
@only_chart = params[:only_chart]
|
60
51
|
|
61
52
|
if @success
|
62
53
|
@query = Query.find_by(id: params[:query_id]) if params[:query_id]
|
63
54
|
|
55
|
+
data_source = params[:data_source]
|
56
|
+
data_source = @query.data_source if @query && @query.data_source
|
57
|
+
|
64
58
|
# audit
|
65
59
|
if Blazer.audit
|
66
60
|
audit = Blazer::Audit.new(statement: @statement)
|
67
61
|
audit.query = @query
|
62
|
+
audit.data_source = data_source
|
68
63
|
audit.user = current_user if respond_to?(:current_user) && Blazer.user_class
|
69
64
|
audit.save!
|
70
65
|
end
|
71
66
|
|
72
|
-
@
|
67
|
+
@data_source = Blazer.data_sources[data_source]
|
68
|
+
@rows, @error = @data_source.run_statement(@statement)
|
69
|
+
|
70
|
+
if @query && !@error.to_s.include?("canceling statement due to statement timeout")
|
71
|
+
@query.checks.each do |check|
|
72
|
+
check.update_state(@rows, @error)
|
73
|
+
end
|
74
|
+
end
|
73
75
|
|
74
76
|
@columns = {}
|
75
77
|
if @rows.any?
|
@@ -88,19 +90,19 @@ module Blazer
|
|
88
90
|
|
89
91
|
@filename = @query.name.parameterize if @query
|
90
92
|
|
91
|
-
@min_width_types = (@rows.first || {}).select { |k, v| v.is_a?(Time) || v.is_a?(String) || smart_columns[k] }.keys
|
93
|
+
@min_width_types = (@rows.first || {}).select { |k, v| v.is_a?(Time) || v.is_a?(String) || @data_source.smart_columns[k] }.keys
|
92
94
|
|
93
95
|
@boom = {}
|
94
96
|
@columns.keys.each do |key|
|
95
|
-
query = smart_columns[key]
|
97
|
+
query = @data_source.smart_columns[key]
|
96
98
|
if query
|
97
99
|
values = @rows.map { |r| r[key] }.compact.uniq
|
98
|
-
rows, error = run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
|
100
|
+
rows, error = @data_source.run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
|
99
101
|
@boom[key] = Hash[rows.map(&:values)]
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
103
|
-
@linked_columns = linked_columns
|
105
|
+
@linked_columns = @data_source.linked_columns
|
104
106
|
end
|
105
107
|
|
106
108
|
respond_to do |format|
|
@@ -126,18 +128,19 @@ module Blazer
|
|
126
128
|
redirect_to root_url
|
127
129
|
end
|
128
130
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
render text: "BLAZER_DATABASE_URL required" if !ENV["BLAZER_DATABASE_URL"] && !Rails.env.development?
|
131
|
+
def tables
|
132
|
+
@tables = Blazer.data_sources[params[:data_source]].tables.keys
|
133
|
+
render partial: "tables", layout: false
|
133
134
|
end
|
134
135
|
|
136
|
+
private
|
137
|
+
|
135
138
|
def set_query
|
136
139
|
@query = Blazer::Query.find(params[:id].to_s.split("-").first)
|
137
140
|
end
|
138
141
|
|
139
142
|
def query_params
|
140
|
-
params.require(:query).permit(:name, :description, :statement)
|
143
|
+
params.require(:query).permit(:name, :description, :statement, :data_source)
|
141
144
|
end
|
142
145
|
|
143
146
|
def csv_data(rows)
|
@@ -150,80 +153,5 @@ module Blazer
|
|
150
153
|
end
|
151
154
|
end
|
152
155
|
end
|
153
|
-
|
154
|
-
def run_statement(statement)
|
155
|
-
rows = []
|
156
|
-
error = nil
|
157
|
-
begin
|
158
|
-
Blazer::Connection.transaction do
|
159
|
-
Blazer::Connection.connection.execute("SET statement_timeout = #{Blazer.timeout * 1000}") if Blazer.timeout && postgresql?
|
160
|
-
result = Blazer::Connection.connection.select_all(statement)
|
161
|
-
result.each do |untyped_row|
|
162
|
-
row = {}
|
163
|
-
untyped_row.each do |k, v|
|
164
|
-
row[k] = result.column_types.empty? ? v : result.column_types[k].send(:type_cast, v)
|
165
|
-
end
|
166
|
-
rows << row
|
167
|
-
end
|
168
|
-
raise ActiveRecord::Rollback
|
169
|
-
end
|
170
|
-
rescue ActiveRecord::StatementInvalid => e
|
171
|
-
error = e.message.sub(/.+ERROR: /, "")
|
172
|
-
end
|
173
|
-
[rows, error]
|
174
|
-
end
|
175
|
-
|
176
|
-
def extract_vars(statement)
|
177
|
-
statement.scan(/\{.*?\}/).map { |v| v[1...-1] }.uniq
|
178
|
-
end
|
179
|
-
|
180
|
-
def process_vars(statement)
|
181
|
-
@bind_vars = extract_vars(statement)
|
182
|
-
@success = @bind_vars.all? { |v| params[v] }
|
183
|
-
|
184
|
-
if @success
|
185
|
-
@bind_vars.each do |var|
|
186
|
-
value = params[var].presence
|
187
|
-
value = value.to_i if value.to_i.to_s == value
|
188
|
-
if var.end_with?("_at")
|
189
|
-
value = Blazer.time_zone.parse(value) rescue nil
|
190
|
-
end
|
191
|
-
statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
def variable_params
|
197
|
-
params.except(:controller, :action, :id, :host, :query, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement)
|
198
|
-
end
|
199
|
-
helper_method :variable_params
|
200
|
-
|
201
|
-
def settings
|
202
|
-
YAML.load(File.read(Rails.root.join("config", "blazer.yml")))
|
203
|
-
end
|
204
|
-
|
205
|
-
def linked_columns
|
206
|
-
settings["linked_columns"] || {}
|
207
|
-
end
|
208
|
-
|
209
|
-
def smart_columns
|
210
|
-
settings["smart_columns"] || {}
|
211
|
-
end
|
212
|
-
|
213
|
-
def smart_variables
|
214
|
-
settings["smart_variables"] || {}
|
215
|
-
end
|
216
|
-
|
217
|
-
def tables
|
218
|
-
default_schema = postgresql? ? "public" : Blazer::Connection.connection_config[:database]
|
219
|
-
schema = Blazer::Connection.connection_config[:schema] || default_schema
|
220
|
-
rows, error = run_statement(Blazer::Connection.send(:sanitize_sql_array, ["SELECT table_name, column_name, ordinal_position, data_type FROM information_schema.columns WHERE table_schema = ?", schema]))
|
221
|
-
Hash[rows.group_by { |r| r["table_name"] }.map { |t, f| [t, f.sort_by { |f| f["ordinal_position"] }.map { |f| f.slice("column_name", "data_type") }] }.sort_by { |t, _f| t }]
|
222
|
-
end
|
223
|
-
helper_method :tables
|
224
|
-
|
225
|
-
def postgresql?
|
226
|
-
Blazer::Connection.connection.adapter_name == "PostgreSQL"
|
227
|
-
end
|
228
156
|
end
|
229
157
|
end
|