blazer 2.2.6
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 +7 -0
- data/CHANGELOG.md +310 -0
- data/CONTRIBUTING.md +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +1041 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/images/blazer/favicon.png +0 -0
- data/app/assets/javascripts/blazer/Chart.js +14456 -0
- data/app/assets/javascripts/blazer/Sortable.js +1540 -0
- data/app/assets/javascripts/blazer/ace.js +6 -0
- data/app/assets/javascripts/blazer/ace/ace.js +21301 -0
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1993 -0
- data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -0
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -0
- data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -0
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -0
- data/app/assets/javascripts/blazer/application.js +81 -0
- data/app/assets/javascripts/blazer/bootstrap.js +2377 -0
- data/app/assets/javascripts/blazer/chartkick.js +2214 -0
- data/app/assets/javascripts/blazer/daterangepicker.js +1653 -0
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/highlight.min.js +3 -0
- data/app/assets/javascripts/blazer/jquery-ujs.js +555 -0
- data/app/assets/javascripts/blazer/jquery.js +10364 -0
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
- data/app/assets/javascripts/blazer/moment.js +3043 -0
- data/app/assets/javascripts/blazer/queries.js +110 -0
- data/app/assets/javascripts/blazer/routes.js +26 -0
- data/app/assets/javascripts/blazer/selectize.js +3891 -0
- data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
- data/app/assets/javascripts/blazer/stupidtable.js +281 -0
- data/app/assets/javascripts/blazer/vue.js +10947 -0
- data/app/assets/stylesheets/blazer/application.css +234 -0
- data/app/assets/stylesheets/blazer/bootstrap.css.erb +6756 -0
- data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
- data/app/assets/stylesheets/blazer/github.css +125 -0
- data/app/assets/stylesheets/blazer/selectize.css +403 -0
- data/app/controllers/blazer/base_controller.rb +124 -0
- data/app/controllers/blazer/checks_controller.rb +56 -0
- data/app/controllers/blazer/dashboards_controller.rb +101 -0
- data/app/controllers/blazer/queries_controller.rb +347 -0
- data/app/helpers/blazer/base_helper.rb +43 -0
- data/app/mailers/blazer/check_mailer.rb +27 -0
- data/app/mailers/blazer/slack_notifier.rb +79 -0
- data/app/models/blazer/audit.rb +6 -0
- data/app/models/blazer/check.rb +104 -0
- data/app/models/blazer/connection.rb +5 -0
- data/app/models/blazer/dashboard.rb +17 -0
- data/app/models/blazer/dashboard_query.rb +9 -0
- data/app/models/blazer/query.rb +40 -0
- data/app/models/blazer/record.rb +5 -0
- data/app/views/blazer/_nav.html.erb +15 -0
- data/app/views/blazer/_variables.html.erb +124 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
- data/app/views/blazer/checks/_form.html.erb +79 -0
- data/app/views/blazer/checks/edit.html.erb +3 -0
- data/app/views/blazer/checks/index.html.erb +69 -0
- data/app/views/blazer/checks/new.html.erb +3 -0
- data/app/views/blazer/dashboards/_form.html.erb +76 -0
- data/app/views/blazer/dashboards/edit.html.erb +3 -0
- data/app/views/blazer/dashboards/new.html.erb +3 -0
- data/app/views/blazer/dashboards/show.html.erb +51 -0
- data/app/views/blazer/queries/_form.html.erb +250 -0
- data/app/views/blazer/queries/docs.html.erb +131 -0
- data/app/views/blazer/queries/edit.html.erb +2 -0
- data/app/views/blazer/queries/home.html.erb +163 -0
- data/app/views/blazer/queries/new.html.erb +2 -0
- data/app/views/blazer/queries/run.html.erb +198 -0
- data/app/views/blazer/queries/schema.html.erb +55 -0
- data/app/views/blazer/queries/show.html.erb +75 -0
- data/app/views/layouts/blazer/application.html.erb +24 -0
- data/config/routes.rb +20 -0
- data/lib/blazer.rb +231 -0
- data/lib/blazer/adapters/athena_adapter.rb +129 -0
- data/lib/blazer/adapters/base_adapter.rb +53 -0
- data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
- data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
- data/lib/blazer/adapters/drill_adapter.rb +28 -0
- data/lib/blazer/adapters/druid_adapter.rb +67 -0
- data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
- data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/neo4j_adapter.rb +47 -0
- data/lib/blazer/adapters/presto_adapter.rb +45 -0
- data/lib/blazer/adapters/salesforce_adapter.rb +45 -0
- data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
- data/lib/blazer/adapters/soda_adapter.rb +96 -0
- data/lib/blazer/adapters/sql_adapter.rb +221 -0
- data/lib/blazer/data_source.rb +195 -0
- data/lib/blazer/detect_anomalies.R +19 -0
- data/lib/blazer/engine.rb +43 -0
- data/lib/blazer/result.rb +218 -0
- data/lib/blazer/run_statement.rb +40 -0
- data/lib/blazer/run_statement_job.rb +18 -0
- data/lib/blazer/version.rb +3 -0
- data/lib/generators/blazer/install_generator.rb +22 -0
- data/lib/generators/blazer/templates/config.yml.tt +73 -0
- data/lib/generators/blazer/templates/install.rb.tt +46 -0
- data/lib/tasks/blazer.rake +11 -0
- metadata +231 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
module Blazer
|
2
|
+
class BaseController < ApplicationController
|
3
|
+
# skip filters
|
4
|
+
filters = _process_action_callbacks.map(&:filter) - [:activate_authlogic]
|
5
|
+
skip_before_action(*filters, raise: false)
|
6
|
+
skip_after_action(*filters, raise: false)
|
7
|
+
skip_around_action(*filters, raise: false)
|
8
|
+
|
9
|
+
protect_from_forgery with: :exception
|
10
|
+
|
11
|
+
if ENV["BLAZER_PASSWORD"]
|
12
|
+
http_basic_authenticate_with name: ENV["BLAZER_USERNAME"], password: ENV["BLAZER_PASSWORD"]
|
13
|
+
end
|
14
|
+
|
15
|
+
if Blazer.settings["before_action"]
|
16
|
+
raise Blazer::Error, "The docs for protecting Blazer with a custom before_action had an incorrect example from August 2017 to June 2018. The example method had a boolean return value. However, you must render or redirect if a user is unauthorized rather than return a falsy value. Double check that your before_action works correctly for unauthorized users (if it worked when added, there should be no issue). Then, change before_action to before_action_method in config/blazer.yml."
|
17
|
+
end
|
18
|
+
|
19
|
+
if Blazer.before_action
|
20
|
+
before_action Blazer.before_action.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
if Blazer.override_csp
|
24
|
+
after_action do
|
25
|
+
response.headers['Content-Security-Policy'] = "default-src 'self' https: 'unsafe-inline' 'unsafe-eval' data:"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
layout "blazer/application"
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def process_vars(statement, data_source)
|
34
|
+
(@bind_vars ||= []).concat(Blazer.extract_vars(statement)).uniq!
|
35
|
+
@bind_vars.each do |var|
|
36
|
+
params[var] ||= Blazer.data_sources[data_source].variable_defaults[var]
|
37
|
+
end
|
38
|
+
@success = @bind_vars.all? { |v| params[v] }
|
39
|
+
|
40
|
+
if @success
|
41
|
+
@bind_vars.each do |var|
|
42
|
+
value = params[var].presence
|
43
|
+
if value
|
44
|
+
if ["start_time", "end_time"].include?(var)
|
45
|
+
value = value.to_s.gsub(" ", "+") # fix for Quip bug
|
46
|
+
end
|
47
|
+
|
48
|
+
if var.end_with?("_at")
|
49
|
+
begin
|
50
|
+
value = Blazer.time_zone.parse(value)
|
51
|
+
rescue
|
52
|
+
# do nothing
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if value =~ /\A\d+\z/
|
57
|
+
value = value.to_i
|
58
|
+
elsif value =~ /\A\d+\.\d+\z/
|
59
|
+
value = value.to_f
|
60
|
+
end
|
61
|
+
end
|
62
|
+
value = Blazer.transform_variable.call(var, value) if Blazer.transform_variable
|
63
|
+
statement.gsub!("{#{var}}", ActiveRecord::Base.connection.quote(value))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_smart_variables(var, data_source)
|
69
|
+
smart_var_data_source =
|
70
|
+
([data_source] + Array(data_source.settings["inherit_smart_settings"]).map { |ds| Blazer.data_sources[ds] }).find { |ds| ds.smart_variables[var] }
|
71
|
+
|
72
|
+
if smart_var_data_source
|
73
|
+
query = smart_var_data_source.smart_variables[var]
|
74
|
+
|
75
|
+
if query.is_a? Hash
|
76
|
+
smart_var = query.map { |k,v| [v, k] }
|
77
|
+
elsif query.is_a? Array
|
78
|
+
smart_var = query.map { |v| [v, v] }
|
79
|
+
elsif query
|
80
|
+
result = smart_var_data_source.run_statement(query)
|
81
|
+
smart_var = result.rows.map { |v| v.reverse }
|
82
|
+
error = result.error if result.error
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
[smart_var, error]
|
87
|
+
end
|
88
|
+
|
89
|
+
# don't pass to url helpers
|
90
|
+
#
|
91
|
+
# some are dangerous when passed as symbols
|
92
|
+
# root_url({host: "evilsite.com"})
|
93
|
+
#
|
94
|
+
# certain ones (like host) only affect *_url and not *_path
|
95
|
+
#
|
96
|
+
# when permitted parameters are passed in Rails 6,
|
97
|
+
# they appear to be added as GET parameters
|
98
|
+
# root_url(params.permit(:host))
|
99
|
+
BLACKLISTED_KEYS = [:controller, :action, :id, :host, :query, :dashboard, :query_id, :query_ids, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source, :name, :fork_query_id, :blazer, :run_id, :script_name, :original_script_name]
|
100
|
+
|
101
|
+
# remove blacklisted keys from both params and permitted keys for better sleep
|
102
|
+
def variable_params(resource)
|
103
|
+
permitted_keys = resource.variables - BLACKLISTED_KEYS.map(&:to_s)
|
104
|
+
params.except(*BLACKLISTED_KEYS).permit(*permitted_keys)
|
105
|
+
end
|
106
|
+
helper_method :variable_params
|
107
|
+
|
108
|
+
def blazer_user
|
109
|
+
send(Blazer.user_method) if Blazer.user_method && respond_to?(Blazer.user_method, true)
|
110
|
+
end
|
111
|
+
helper_method :blazer_user
|
112
|
+
|
113
|
+
def render_errors(resource)
|
114
|
+
@errors = resource.errors
|
115
|
+
action = resource.persisted? ? :edit : :new
|
116
|
+
render action, status: :unprocessable_entity
|
117
|
+
end
|
118
|
+
|
119
|
+
# do not inherit from ApplicationController - #120
|
120
|
+
def default_url_options
|
121
|
+
{}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Blazer
|
2
|
+
class ChecksController < BaseController
|
3
|
+
before_action :set_check, only: [:edit, :update, :destroy, :run]
|
4
|
+
|
5
|
+
def index
|
6
|
+
state_order = [nil, "disabled", "error", "timed out", "failing", "passing"]
|
7
|
+
@checks = Blazer::Check.joins(:query).includes(:query).order("blazer_queries.name, blazer_checks.id").to_a.sort_by { |q| state_order.index(q.state) || 99 }
|
8
|
+
@checks.select! { |c| "#{c.query.name} #{c.emails}".downcase.include?(params[:q]) } if params[:q]
|
9
|
+
end
|
10
|
+
|
11
|
+
def new
|
12
|
+
@check = Blazer::Check.new(query_id: params[:query_id])
|
13
|
+
end
|
14
|
+
|
15
|
+
def create
|
16
|
+
@check = Blazer::Check.new(check_params)
|
17
|
+
# use creator_id instead of creator
|
18
|
+
# since we setup association without checking if column exists
|
19
|
+
@check.creator = blazer_user if @check.respond_to?(:creator_id=) && blazer_user
|
20
|
+
|
21
|
+
if @check.save
|
22
|
+
redirect_to query_path(@check.query)
|
23
|
+
else
|
24
|
+
render_errors @check
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
if @check.update(check_params)
|
30
|
+
redirect_to query_path(@check.query)
|
31
|
+
else
|
32
|
+
render_errors @check
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
@check.destroy
|
38
|
+
redirect_to checks_path
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
@query = @check.query
|
43
|
+
redirect_to query_path(@query)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def check_params
|
49
|
+
params.require(:check).permit(:query_id, :emails, :slack_channels, :invert, :check_type, :schedule)
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_check
|
53
|
+
@check = Blazer::Check.find(params[:id])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Blazer
|
2
|
+
class DashboardsController < BaseController
|
3
|
+
before_action :set_dashboard, only: [:show, :edit, :update, :destroy, :refresh]
|
4
|
+
|
5
|
+
def new
|
6
|
+
@dashboard = Blazer::Dashboard.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def create
|
10
|
+
@dashboard = Blazer::Dashboard.new
|
11
|
+
# use creator_id instead of creator
|
12
|
+
# since we setup association without checking if column exists
|
13
|
+
@dashboard.creator = blazer_user if @dashboard.respond_to?(:creator_id=) && blazer_user
|
14
|
+
|
15
|
+
if update_dashboard(@dashboard)
|
16
|
+
redirect_to dashboard_path(@dashboard)
|
17
|
+
else
|
18
|
+
render_errors @dashboard
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def show
|
23
|
+
@queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
|
24
|
+
@queries.each do |query|
|
25
|
+
process_vars(query.statement, query.data_source)
|
26
|
+
end
|
27
|
+
@bind_vars ||= []
|
28
|
+
|
29
|
+
@smart_vars = {}
|
30
|
+
@sql_errors = []
|
31
|
+
@data_sources = @queries.map { |q| Blazer.data_sources[q.data_source] }.uniq
|
32
|
+
@bind_vars.each do |var|
|
33
|
+
@data_sources.each do |data_source|
|
34
|
+
smart_var, error = parse_smart_variables(var, data_source)
|
35
|
+
((@smart_vars[var] ||= []).concat(smart_var)).uniq! if smart_var
|
36
|
+
@sql_errors << error if error
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def edit
|
42
|
+
end
|
43
|
+
|
44
|
+
def update
|
45
|
+
if update_dashboard(@dashboard)
|
46
|
+
redirect_to dashboard_path(@dashboard, variable_params(@dashboard))
|
47
|
+
else
|
48
|
+
render_errors @dashboard
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy
|
53
|
+
@dashboard.destroy
|
54
|
+
redirect_to dashboards_path
|
55
|
+
end
|
56
|
+
|
57
|
+
def refresh
|
58
|
+
@dashboard.queries.each do |query|
|
59
|
+
data_source = Blazer.data_sources[query.data_source]
|
60
|
+
statement = query.statement.dup
|
61
|
+
process_vars(statement, query.data_source)
|
62
|
+
Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
|
63
|
+
data_source.clear_cache(statement)
|
64
|
+
end
|
65
|
+
redirect_to dashboard_path(@dashboard, variable_params(@dashboard))
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def dashboard_params
|
71
|
+
params.require(:dashboard).permit(:name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_dashboard
|
75
|
+
@dashboard = Blazer::Dashboard.find(params[:id])
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_dashboard(dashboard)
|
79
|
+
dashboard.assign_attributes(dashboard_params)
|
80
|
+
Blazer::Dashboard.transaction do
|
81
|
+
if params[:query_ids].is_a?(Array)
|
82
|
+
query_ids = params[:query_ids].map(&:to_i)
|
83
|
+
@queries = Blazer::Query.find(query_ids).sort_by { |q| query_ids.index(q.id) }
|
84
|
+
end
|
85
|
+
if dashboard.save
|
86
|
+
if @queries
|
87
|
+
@queries.each_with_index do |query, i|
|
88
|
+
dashboard_query = dashboard.dashboard_queries.where(query_id: query.id).first_or_initialize
|
89
|
+
dashboard_query.position = i
|
90
|
+
dashboard_query.save!
|
91
|
+
end
|
92
|
+
if dashboard.persisted?
|
93
|
+
dashboard.dashboard_queries.where.not(query_id: query_ids).destroy_all
|
94
|
+
end
|
95
|
+
end
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
module Blazer
|
2
|
+
class QueriesController < BaseController
|
3
|
+
before_action :set_query, only: [:show, :edit, :update, :destroy, :refresh]
|
4
|
+
before_action :set_data_source, only: [:tables, :docs, :schema, :cancel]
|
5
|
+
|
6
|
+
def home
|
7
|
+
set_queries(1000)
|
8
|
+
|
9
|
+
if params[:filter]
|
10
|
+
@dashboards = [] # TODO show my dashboards
|
11
|
+
else
|
12
|
+
@dashboards = Blazer::Dashboard.order(:name)
|
13
|
+
@dashboards = @dashboards.includes(:creator) if Blazer.user_class
|
14
|
+
end
|
15
|
+
|
16
|
+
@dashboards =
|
17
|
+
@dashboards.map do |d|
|
18
|
+
{
|
19
|
+
id: d.id,
|
20
|
+
name: d.name,
|
21
|
+
creator: blazer_user && d.try(:creator) == blazer_user ? "You" : d.try(:creator).try(Blazer.user_name),
|
22
|
+
to_param: d.to_param,
|
23
|
+
dashboard: true
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def index
|
29
|
+
set_queries
|
30
|
+
render json: @queries
|
31
|
+
end
|
32
|
+
|
33
|
+
def new
|
34
|
+
@query = Blazer::Query.new(
|
35
|
+
data_source: params[:data_source],
|
36
|
+
name: params[:name]
|
37
|
+
)
|
38
|
+
if params[:fork_query_id]
|
39
|
+
@query.statement ||= Blazer::Query.find(params[:fork_query_id]).try(:statement)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def create
|
44
|
+
@query = Blazer::Query.new(query_params)
|
45
|
+
@query.creator = blazer_user if @query.respond_to?(:creator)
|
46
|
+
|
47
|
+
if @query.save
|
48
|
+
redirect_to query_path(@query, variable_params(@query))
|
49
|
+
else
|
50
|
+
render_errors @query
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def show
|
55
|
+
@statement = @query.statement.dup
|
56
|
+
process_vars(@statement, @query.data_source)
|
57
|
+
|
58
|
+
@smart_vars = {}
|
59
|
+
@sql_errors = []
|
60
|
+
data_source = Blazer.data_sources[@query.data_source]
|
61
|
+
@bind_vars.each do |var|
|
62
|
+
smart_var, error = parse_smart_variables(var, data_source)
|
63
|
+
@smart_vars[var] = smart_var if smart_var
|
64
|
+
@sql_errors << error if error
|
65
|
+
end
|
66
|
+
|
67
|
+
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
68
|
+
end
|
69
|
+
|
70
|
+
def edit
|
71
|
+
end
|
72
|
+
|
73
|
+
def run
|
74
|
+
@statement = params[:statement]
|
75
|
+
data_source = params[:data_source]
|
76
|
+
process_vars(@statement, data_source)
|
77
|
+
@only_chart = params[:only_chart]
|
78
|
+
@run_id = blazer_params[:run_id]
|
79
|
+
@query = Query.find_by(id: params[:query_id]) if params[:query_id]
|
80
|
+
data_source = @query.data_source if @query && @query.data_source
|
81
|
+
@data_source = Blazer.data_sources[data_source]
|
82
|
+
|
83
|
+
# ensure viewable
|
84
|
+
if !(@query || Query.new(data_source: @data_source.id)).viewable?(blazer_user)
|
85
|
+
render_forbidden
|
86
|
+
elsif @run_id
|
87
|
+
@timestamp = blazer_params[:timestamp].to_i
|
88
|
+
|
89
|
+
@result = @data_source.run_results(@run_id)
|
90
|
+
@success = !@result.nil?
|
91
|
+
|
92
|
+
if @success
|
93
|
+
@data_source.delete_results(@run_id)
|
94
|
+
@columns = @result.columns
|
95
|
+
@rows = @result.rows
|
96
|
+
@error = @result.error
|
97
|
+
@just_cached = !@result.error && @result.cached_at.present?
|
98
|
+
@cached_at = nil
|
99
|
+
params[:data_source] = nil
|
100
|
+
render_run
|
101
|
+
elsif Time.now > Time.at(@timestamp + (@data_source.timeout || 600).to_i + 5)
|
102
|
+
# query lost
|
103
|
+
Rails.logger.info "[blazer lost query] #{@run_id}"
|
104
|
+
@error = "We lost your query :("
|
105
|
+
@rows = []
|
106
|
+
@columns = []
|
107
|
+
render_run
|
108
|
+
else
|
109
|
+
continue_run
|
110
|
+
end
|
111
|
+
elsif @success
|
112
|
+
@run_id = blazer_run_id
|
113
|
+
|
114
|
+
options = {user: blazer_user, query: @query, refresh_cache: params[:check], run_id: @run_id, async: Blazer.async}
|
115
|
+
if Blazer.async && request.format.symbol != :csv
|
116
|
+
Blazer::RunStatementJob.perform_later(@data_source.id, @statement, options)
|
117
|
+
wait_start = Time.now
|
118
|
+
loop do
|
119
|
+
sleep(0.1)
|
120
|
+
@result = @data_source.run_results(@run_id)
|
121
|
+
break if @result || Time.now - wait_start > 3
|
122
|
+
end
|
123
|
+
else
|
124
|
+
@result = Blazer::RunStatement.new.perform(@data_source, @statement, options)
|
125
|
+
end
|
126
|
+
|
127
|
+
if @result
|
128
|
+
@data_source.delete_results(@run_id) if @run_id
|
129
|
+
|
130
|
+
@columns = @result.columns
|
131
|
+
@rows = @result.rows
|
132
|
+
@error = @result.error
|
133
|
+
@cached_at = @result.cached_at
|
134
|
+
@just_cached = @result.just_cached
|
135
|
+
|
136
|
+
@forecast = @query && @result.forecastable? && params[:forecast]
|
137
|
+
if @forecast
|
138
|
+
@result.forecast
|
139
|
+
@forecast_error = @result.forecast_error
|
140
|
+
@forecast = @forecast_error.nil?
|
141
|
+
end
|
142
|
+
|
143
|
+
render_run
|
144
|
+
else
|
145
|
+
@timestamp = Time.now.to_i
|
146
|
+
continue_run
|
147
|
+
end
|
148
|
+
else
|
149
|
+
render layout: false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def refresh
|
154
|
+
data_source = Blazer.data_sources[@query.data_source]
|
155
|
+
@statement = @query.statement.dup
|
156
|
+
process_vars(@statement, @query.data_source)
|
157
|
+
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
158
|
+
data_source.clear_cache(@statement)
|
159
|
+
redirect_to query_path(@query, variable_params(@query))
|
160
|
+
end
|
161
|
+
|
162
|
+
def update
|
163
|
+
if params[:commit] == "Fork"
|
164
|
+
@query = Blazer::Query.new
|
165
|
+
@query.creator = blazer_user if @query.respond_to?(:creator)
|
166
|
+
end
|
167
|
+
unless @query.editable?(blazer_user)
|
168
|
+
@query.errors.add(:base, "Sorry, permission denied")
|
169
|
+
end
|
170
|
+
if @query.errors.empty? && @query.update(query_params)
|
171
|
+
redirect_to query_path(@query, variable_params(@query))
|
172
|
+
else
|
173
|
+
render_errors @query
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def destroy
|
178
|
+
@query.destroy if @query.editable?(blazer_user)
|
179
|
+
redirect_to root_path
|
180
|
+
end
|
181
|
+
|
182
|
+
def tables
|
183
|
+
render json: @data_source.tables
|
184
|
+
end
|
185
|
+
|
186
|
+
def docs
|
187
|
+
@smart_variables = @data_source.smart_variables
|
188
|
+
@linked_columns = @data_source.linked_columns
|
189
|
+
@smart_columns = @data_source.smart_columns
|
190
|
+
end
|
191
|
+
|
192
|
+
def schema
|
193
|
+
@schema = @data_source.schema
|
194
|
+
end
|
195
|
+
|
196
|
+
def cancel
|
197
|
+
@data_source.cancel(blazer_run_id)
|
198
|
+
head :ok
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def set_data_source
|
204
|
+
@data_source = Blazer.data_sources[params[:data_source]]
|
205
|
+
|
206
|
+
unless Query.new(data_source: @data_source.id).editable?(blazer_user)
|
207
|
+
render_forbidden
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def continue_run
|
212
|
+
render json: {run_id: @run_id, timestamp: @timestamp}, status: :accepted
|
213
|
+
end
|
214
|
+
|
215
|
+
def render_run
|
216
|
+
@checks = @query ? @query.checks.order(:id) : []
|
217
|
+
|
218
|
+
@first_row = @rows.first || []
|
219
|
+
@column_types = []
|
220
|
+
if @rows.any?
|
221
|
+
@columns.each_with_index do |_, i|
|
222
|
+
@column_types << (
|
223
|
+
case @first_row[i]
|
224
|
+
when Integer
|
225
|
+
"int"
|
226
|
+
when Float, BigDecimal
|
227
|
+
"float"
|
228
|
+
else
|
229
|
+
"string-ins"
|
230
|
+
end
|
231
|
+
)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
@filename = @query.name.parameterize if @query
|
236
|
+
@min_width_types = @columns.each_with_index.select { |c, i| @first_row[i].is_a?(Time) || @first_row[i].is_a?(String) || @data_source.smart_columns[c] }.map(&:last)
|
237
|
+
|
238
|
+
@boom = @result.boom if @result
|
239
|
+
|
240
|
+
@linked_columns = @data_source.linked_columns
|
241
|
+
|
242
|
+
@markers = []
|
243
|
+
[["latitude", "longitude"], ["lat", "lon"], ["lat", "lng"]].each do |keys|
|
244
|
+
lat_index = @columns.index(keys.first)
|
245
|
+
lon_index = @columns.index(keys.last)
|
246
|
+
if lat_index && lon_index
|
247
|
+
@markers =
|
248
|
+
@rows.select do |r|
|
249
|
+
r[lat_index] && r[lon_index]
|
250
|
+
end.map do |r|
|
251
|
+
{
|
252
|
+
# Mapbox.js does sanitization with https://github.com/mapbox/sanitize-caja
|
253
|
+
# but we should do it here as well
|
254
|
+
title: r.each_with_index.map { |v, i| i == lat_index || i == lon_index ? nil : "<strong>#{ERB::Util.html_escape(@columns[i])}:</strong> #{ERB::Util.html_escape(v)}" }.compact.join("<br />").truncate(140),
|
255
|
+
latitude: r[lat_index],
|
256
|
+
longitude: r[lon_index]
|
257
|
+
}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
respond_to do |format|
|
263
|
+
format.html do
|
264
|
+
render layout: false
|
265
|
+
end
|
266
|
+
format.csv do
|
267
|
+
send_data csv_data(@columns, @rows, @data_source), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def set_queries(limit = nil)
|
273
|
+
@queries = Blazer::Query.named.select(:id, :name, :creator_id, :statement)
|
274
|
+
@queries = @queries.includes(:creator) if Blazer.user_class
|
275
|
+
|
276
|
+
if blazer_user && params[:filter] == "mine"
|
277
|
+
@queries = @queries.where(creator_id: blazer_user.id).reorder(updated_at: :desc)
|
278
|
+
elsif blazer_user && params[:filter] == "viewed" && Blazer.audit
|
279
|
+
@queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
|
280
|
+
else
|
281
|
+
@queries = @queries.limit(limit) if limit
|
282
|
+
@queries = @queries.order(:name)
|
283
|
+
end
|
284
|
+
@queries = @queries.to_a
|
285
|
+
|
286
|
+
@more = limit && @queries.size >= limit
|
287
|
+
|
288
|
+
@queries = @queries.select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
|
289
|
+
|
290
|
+
@queries =
|
291
|
+
@queries.map do |q|
|
292
|
+
{
|
293
|
+
id: q.id,
|
294
|
+
name: q.name,
|
295
|
+
creator: blazer_user && q.try(:creator) == blazer_user ? "You" : q.try(:creator).try(Blazer.user_name),
|
296
|
+
vars: q.variables.join(", "),
|
297
|
+
to_param: q.to_param
|
298
|
+
}
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def queries_by_ids(favorite_query_ids)
|
303
|
+
queries = Blazer::Query.named.where(id: favorite_query_ids)
|
304
|
+
queries = queries.includes(:creator) if Blazer.user_class
|
305
|
+
queries = queries.index_by(&:id)
|
306
|
+
favorite_query_ids.map { |query_id| queries[query_id] }.compact
|
307
|
+
end
|
308
|
+
|
309
|
+
def set_query
|
310
|
+
@query = Blazer::Query.find(params[:id].to_s.split("-").first)
|
311
|
+
|
312
|
+
unless @query.viewable?(blazer_user)
|
313
|
+
render_forbidden
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def render_forbidden
|
318
|
+
render plain: "Access denied", status: :forbidden
|
319
|
+
end
|
320
|
+
|
321
|
+
def query_params
|
322
|
+
params.require(:query).permit(:name, :description, :statement, :data_source)
|
323
|
+
end
|
324
|
+
|
325
|
+
def blazer_params
|
326
|
+
params[:blazer] || {}
|
327
|
+
end
|
328
|
+
|
329
|
+
def csv_data(columns, rows, data_source)
|
330
|
+
CSV.generate do |csv|
|
331
|
+
csv << columns
|
332
|
+
rows.each do |row|
|
333
|
+
csv << row.each_with_index.map { |v, i| v.is_a?(Time) ? blazer_time_value(data_source, columns[i], v) : v }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def blazer_time_value(data_source, k, v)
|
339
|
+
data_source.local_time_suffix.any? { |s| k.ends_with?(s) } ? v.to_s.sub(" UTC", "") : v.in_time_zone(Blazer.time_zone)
|
340
|
+
end
|
341
|
+
helper_method :blazer_time_value
|
342
|
+
|
343
|
+
def blazer_run_id
|
344
|
+
params[:run_id].to_s.gsub(/[^a-z0-9\-]/i, "")
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|