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.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +310 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +1041 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Chart.js +14456 -0
  13. data/app/assets/javascripts/blazer/Sortable.js +1540 -0
  14. data/app/assets/javascripts/blazer/ace.js +6 -0
  15. data/app/assets/javascripts/blazer/ace/ace.js +21301 -0
  16. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1993 -0
  17. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -0
  18. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -0
  20. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -0
  21. data/app/assets/javascripts/blazer/application.js +81 -0
  22. data/app/assets/javascripts/blazer/bootstrap.js +2377 -0
  23. data/app/assets/javascripts/blazer/chartkick.js +2214 -0
  24. data/app/assets/javascripts/blazer/daterangepicker.js +1653 -0
  25. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  26. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  27. data/app/assets/javascripts/blazer/jquery-ujs.js +555 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10364 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  30. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
  31. data/app/assets/javascripts/blazer/moment.js +3043 -0
  32. data/app/assets/javascripts/blazer/queries.js +110 -0
  33. data/app/assets/javascripts/blazer/routes.js +26 -0
  34. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  35. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  37. data/app/assets/javascripts/blazer/vue.js +10947 -0
  38. data/app/assets/stylesheets/blazer/application.css +234 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6756 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  43. data/app/controllers/blazer/base_controller.rb +124 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +101 -0
  46. data/app/controllers/blazer/queries_controller.rb +347 -0
  47. data/app/helpers/blazer/base_helper.rb +43 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/mailers/blazer/slack_notifier.rb +79 -0
  50. data/app/models/blazer/audit.rb +6 -0
  51. data/app/models/blazer/check.rb +104 -0
  52. data/app/models/blazer/connection.rb +5 -0
  53. data/app/models/blazer/dashboard.rb +17 -0
  54. data/app/models/blazer/dashboard_query.rb +9 -0
  55. data/app/models/blazer/query.rb +40 -0
  56. data/app/models/blazer/record.rb +5 -0
  57. data/app/views/blazer/_nav.html.erb +15 -0
  58. data/app/views/blazer/_variables.html.erb +124 -0
  59. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  60. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  61. data/app/views/blazer/checks/_form.html.erb +79 -0
  62. data/app/views/blazer/checks/edit.html.erb +3 -0
  63. data/app/views/blazer/checks/index.html.erb +69 -0
  64. data/app/views/blazer/checks/new.html.erb +3 -0
  65. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  66. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  67. data/app/views/blazer/dashboards/new.html.erb +3 -0
  68. data/app/views/blazer/dashboards/show.html.erb +51 -0
  69. data/app/views/blazer/queries/_form.html.erb +250 -0
  70. data/app/views/blazer/queries/docs.html.erb +131 -0
  71. data/app/views/blazer/queries/edit.html.erb +2 -0
  72. data/app/views/blazer/queries/home.html.erb +163 -0
  73. data/app/views/blazer/queries/new.html.erb +2 -0
  74. data/app/views/blazer/queries/run.html.erb +198 -0
  75. data/app/views/blazer/queries/schema.html.erb +55 -0
  76. data/app/views/blazer/queries/show.html.erb +75 -0
  77. data/app/views/layouts/blazer/application.html.erb +24 -0
  78. data/config/routes.rb +20 -0
  79. data/lib/blazer.rb +231 -0
  80. data/lib/blazer/adapters/athena_adapter.rb +129 -0
  81. data/lib/blazer/adapters/base_adapter.rb +53 -0
  82. data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
  83. data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
  84. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  85. data/lib/blazer/adapters/druid_adapter.rb +67 -0
  86. data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
  87. data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
  88. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  89. data/lib/blazer/adapters/neo4j_adapter.rb +47 -0
  90. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  91. data/lib/blazer/adapters/salesforce_adapter.rb +45 -0
  92. data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
  93. data/lib/blazer/adapters/soda_adapter.rb +96 -0
  94. data/lib/blazer/adapters/sql_adapter.rb +221 -0
  95. data/lib/blazer/data_source.rb +195 -0
  96. data/lib/blazer/detect_anomalies.R +19 -0
  97. data/lib/blazer/engine.rb +43 -0
  98. data/lib/blazer/result.rb +218 -0
  99. data/lib/blazer/run_statement.rb +40 -0
  100. data/lib/blazer/run_statement_job.rb +18 -0
  101. data/lib/blazer/version.rb +3 -0
  102. data/lib/generators/blazer/install_generator.rb +22 -0
  103. data/lib/generators/blazer/templates/config.yml.tt +73 -0
  104. data/lib/generators/blazer/templates/install.rb.tt +46 -0
  105. data/lib/tasks/blazer.rake +11 -0
  106. 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