sql-jarvis 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. metadata +211 -0
@@ -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, :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,105 @@
1
+ module Blazer
2
+ class DashboardsController < BaseController
3
+ before_action :set_dashboard, only: [:show, :edit, :update, :destroy, :refresh]
4
+
5
+ def index
6
+ redirect_to root_path(filter: "dashboards")
7
+ end
8
+
9
+ def new
10
+ @dashboard = Blazer::Dashboard.new
11
+ end
12
+
13
+ def create
14
+ @dashboard = Blazer::Dashboard.new
15
+ # use creator_id instead of creator
16
+ # since we setup association without checking if column exists
17
+ @dashboard.creator = blazer_user if @dashboard.respond_to?(:creator_id=) && blazer_user
18
+
19
+ if update_dashboard(@dashboard)
20
+ redirect_to dashboard_path(@dashboard)
21
+ else
22
+ render_errors @dashboard
23
+ end
24
+ end
25
+
26
+ def show
27
+ @queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
28
+ @queries.each do |query|
29
+ process_vars(query.statement, query.data_source)
30
+ end
31
+ @bind_vars ||= []
32
+
33
+ @smart_vars = {}
34
+ @sql_errors = []
35
+ @data_sources = @queries.map { |q| Blazer.data_sources[q.data_source] }.uniq
36
+ @bind_vars.each do |var|
37
+ @data_sources.each do |data_source|
38
+ smart_var, error = parse_smart_variables(var, data_source)
39
+ ((@smart_vars[var] ||= []).concat(smart_var)).uniq! if smart_var
40
+ @sql_errors << error if error
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, variable_params)
51
+ else
52
+ render_errors @dashboard
53
+ end
54
+ end
55
+
56
+ def destroy
57
+ @dashboard.destroy
58
+ redirect_to dashboards_path
59
+ end
60
+
61
+ def refresh
62
+ @dashboard.queries.each do |query|
63
+ data_source = Blazer.data_sources[query.data_source]
64
+ statement = query.statement.dup
65
+ process_vars(statement, query.data_source)
66
+ Blazer.transform_statement.call(data_source, statement) if Blazer.transform_statement
67
+ data_source.clear_cache(statement)
68
+ end
69
+ redirect_to dashboard_path(@dashboard, variable_params)
70
+ end
71
+
72
+ private
73
+
74
+ def dashboard_params
75
+ params.require(:dashboard).permit(:name)
76
+ end
77
+
78
+ def set_dashboard
79
+ @dashboard = Blazer::Dashboard.find(params[:id])
80
+ end
81
+
82
+ def update_dashboard(dashboard)
83
+ dashboard.assign_attributes(dashboard_params)
84
+ Blazer::Dashboard.transaction do
85
+ if params[:query_ids].is_a?(Array)
86
+ query_ids = params[:query_ids].map(&:to_i)
87
+ @queries = Blazer::Query.find(query_ids).sort_by { |q| query_ids.index(q.id) }
88
+ end
89
+ if dashboard.save
90
+ if @queries
91
+ @queries.each_with_index do |query, i|
92
+ dashboard_query = dashboard.dashboard_queries.where(query_id: query.id).first_or_initialize
93
+ dashboard_query.position = i
94
+ dashboard_query.save!
95
+ end
96
+ if dashboard.persisted?
97
+ dashboard.dashboard_queries.where.not(query_id: query_ids).destroy_all
98
+ end
99
+ end
100
+ true
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,325 @@
1
+ module Blazer
2
+ class QueriesController < BaseController
3
+ before_action :set_query, only: [:show, :edit, :update, :destroy, :refresh]
4
+
5
+ def home
6
+ if params[:filter] == "dashboards"
7
+ @queries = []
8
+ else
9
+ set_queries(1000)
10
+ end
11
+
12
+ if params[:filter] && params[:filter] != "dashboards"
13
+ @dashboards = [] # TODO show my dashboards
14
+ else
15
+ @dashboards = Blazer::Dashboard.order(:name)
16
+ @dashboards = @dashboards.includes(:creator) if Blazer.user_class
17
+ end
18
+
19
+ @dashboards =
20
+ @dashboards.map do |d|
21
+ {
22
+ id: d.id,
23
+ name: d.name,
24
+ creator: blazer_user && d.try(:creator) == blazer_user ? "You" : d.try(:creator).try(Blazer.user_name),
25
+ to_param: d.to_param,
26
+ dashboard: true
27
+ }
28
+ end
29
+ end
30
+
31
+ def index
32
+ set_queries
33
+ render json: @queries
34
+ end
35
+
36
+ def new
37
+ @query = Blazer::Query.new(
38
+ data_source: params[:data_source],
39
+ name: params[:name]
40
+ )
41
+ if params[:fork_query_id]
42
+ @query.statement ||= Blazer::Query.find(params[:fork_query_id]).try(:statement)
43
+ end
44
+ end
45
+
46
+ def create
47
+ @query = Blazer::Query.new(query_params)
48
+ @query.creator = blazer_user if @query.respond_to?(:creator)
49
+
50
+ if @query.save
51
+ redirect_to query_path(@query, variable_params)
52
+ else
53
+ render_errors @query
54
+ end
55
+ end
56
+
57
+ def show
58
+ @statement = @query.statement.dup
59
+ process_vars(@statement, @query.data_source)
60
+
61
+ @smart_vars = {}
62
+ @sql_errors = []
63
+ data_source = Blazer.data_sources[@query.data_source]
64
+ @bind_vars.each do |var|
65
+ smart_var, error = parse_smart_variables(var, data_source)
66
+ @smart_vars[var] = smart_var if smart_var
67
+ @sql_errors << error if error
68
+ end
69
+
70
+ Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
71
+ end
72
+
73
+ def edit
74
+ end
75
+
76
+ def run
77
+ @statement = params[:statement]
78
+ data_source = params[:data_source]
79
+ process_vars(@statement, data_source)
80
+ @only_chart = params[:only_chart]
81
+ @run_id = blazer_params[:run_id]
82
+ @query = Query.find_by(id: params[:query_id]) if params[:query_id]
83
+ data_source = @query.data_source if @query && @query.data_source
84
+ @data_source = Blazer.data_sources[data_source]
85
+
86
+ if @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
+ result = []
117
+ Blazer::RunStatementJob.perform_async(result, @data_source, @statement, options)
118
+ wait_start = Time.now
119
+ loop do
120
+ sleep(0.02)
121
+ break if result.any? || Time.now - wait_start > 3
122
+ end
123
+ @result = result.first
124
+ else
125
+ @result = Blazer::RunStatement.new.perform(@data_source, @statement, options)
126
+ end
127
+
128
+ if @result
129
+ @data_source.delete_results(@run_id) if @run_id
130
+
131
+ @columns = @result.columns
132
+ @rows = @result.rows
133
+ @error = @result.error
134
+ @cached_at = @result.cached_at
135
+ @just_cached = @result.just_cached
136
+
137
+ render_run
138
+ else
139
+ @timestamp = Time.now.to_i
140
+ continue_run
141
+ end
142
+ else
143
+ render layout: false
144
+ end
145
+ end
146
+
147
+ def refresh
148
+ data_source = Blazer.data_sources[@query.data_source]
149
+ @statement = @query.statement.dup
150
+ process_vars(@statement, @query.data_source)
151
+ Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
152
+ data_source.clear_cache(@statement)
153
+ redirect_to query_path(@query, variable_params)
154
+ end
155
+
156
+ def update
157
+ if params[:commit] == "Fork"
158
+ @query = Blazer::Query.new
159
+ @query.creator = blazer_user if @query.respond_to?(:creator)
160
+ end
161
+ unless @query.editable?(blazer_user)
162
+ @query.errors.add(:base, "Sorry, permission denied")
163
+ end
164
+ if @query.errors.empty? && @query.update(query_params)
165
+ redirect_to query_path(@query, variable_params)
166
+ else
167
+ render_errors @query
168
+ end
169
+ end
170
+
171
+ def destroy
172
+ @query.destroy if @query.editable?(blazer_user)
173
+ redirect_to root_url
174
+ end
175
+
176
+ def tables
177
+ render json: Blazer.data_sources[params[:data_source]].tables
178
+ end
179
+
180
+ def schema
181
+ @schema = Blazer.data_sources[params[:data_source]].schema
182
+ end
183
+
184
+ def cancel
185
+ Blazer.data_sources[params[:data_source]].cancel(blazer_run_id)
186
+ head :ok
187
+ end
188
+
189
+ private
190
+
191
+ def continue_run
192
+ render json: {run_id: @run_id, timestamp: @timestamp}, status: :accepted
193
+ end
194
+
195
+ def render_run
196
+ @checks = @query ? @query.checks.order(:id) : []
197
+
198
+ @first_row = @rows.first || []
199
+ @column_types = []
200
+ if @rows.any?
201
+ @columns.each_with_index do |column, i|
202
+ @column_types << (
203
+ case @first_row[i]
204
+ when Integer
205
+ "int"
206
+ when Float, BigDecimal
207
+ "float"
208
+ else
209
+ "string-ins"
210
+ end
211
+ )
212
+ end
213
+ end
214
+
215
+ @filename = @query.name.parameterize if @query
216
+ @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)
217
+
218
+ @boom = @result.boom if @result
219
+
220
+ @linked_columns = @data_source.linked_columns
221
+
222
+ @markers = []
223
+ [["latitude", "longitude"], ["lat", "lon"], ["lat", "lng"]].each do |keys|
224
+ lat_index = @columns.index(keys.first)
225
+ lon_index = @columns.index(keys.last)
226
+ if lat_index && lon_index
227
+ @markers =
228
+ @rows.select do |r|
229
+ r[lat_index] && r[lon_index]
230
+ end.map do |r|
231
+ {
232
+ title: r.each_with_index.map{ |v, i| i == lat_index || i == lon_index ? nil : "<strong>#{@columns[i]}:</strong> #{v}" }.compact.join("<br />").truncate(140),
233
+ latitude: r[lat_index],
234
+ longitude: r[lon_index]
235
+ }
236
+ end
237
+ end
238
+ end
239
+
240
+ respond_to do |format|
241
+ format.html do
242
+ render layout: false
243
+ end
244
+ format.csv do
245
+ 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\""
246
+ end
247
+ end
248
+ end
249
+
250
+ def set_queries(limit = nil)
251
+ @my_queries =
252
+ if limit && blazer_user && !params[:filter] && Blazer.audit
253
+ queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).where("created_at > ?", 30.days.ago).where("query_id IS NOT NULL").group(:query_id).order("count_all desc").count.keys)
254
+ else
255
+ []
256
+ end
257
+
258
+ @queries = Blazer::Query.named.select(:id, :name, :creator_id, :statement)
259
+ @queries = @queries.includes(:creator) if Blazer.user_class
260
+
261
+ if blazer_user && params[:filter] == "mine"
262
+ @queries = @queries.where(creator_id: blazer_user.id).reorder(updated_at: :desc)
263
+ elsif blazer_user && params[:filter] == "viewed" && Blazer.audit
264
+ @queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
265
+ else
266
+ @queries = @queries.where("id NOT IN (?)", @my_queries.map(&:id)) if @my_queries.any?
267
+ @queries = @queries.limit(limit) if limit
268
+ @queries = @queries.order(:name)
269
+ end
270
+ @queries = @queries.to_a
271
+
272
+ @more = limit && @queries.size >= limit
273
+
274
+ @queries = (@my_queries + @queries).select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
275
+
276
+ @queries =
277
+ @queries.map do |q|
278
+ {
279
+ id: q.id,
280
+ name: q.name,
281
+ creator: blazer_user && q.try(:creator) == blazer_user ? "You" : q.try(:creator).try(Blazer.user_name),
282
+ vars: q.variables.join(", "),
283
+ to_param: q.to_param
284
+ }
285
+ end
286
+ end
287
+
288
+ def queries_by_ids(favorite_query_ids)
289
+ queries = Blazer::Query.named.where(id: favorite_query_ids)
290
+ queries = queries.includes(:creator) if Blazer.user_class
291
+ queries = queries.index_by(&:id)
292
+ favorite_query_ids.map { |query_id| queries[query_id] }.compact
293
+ end
294
+
295
+ def set_query
296
+ @query = Blazer::Query.find(params[:id].to_s.split("-").first)
297
+ end
298
+
299
+ def query_params
300
+ params.require(:query).permit(:name, :description, :statement, :data_source)
301
+ end
302
+
303
+ def blazer_params
304
+ params[:blazer] || {}
305
+ end
306
+
307
+ def csv_data(columns, rows, data_source)
308
+ CSV.generate do |csv|
309
+ csv << columns
310
+ rows.each do |row|
311
+ csv << row.each_with_index.map { |v, i| v.is_a?(Time) ? blazer_time_value(data_source, columns[i], v) : v }
312
+ end
313
+ end
314
+ end
315
+
316
+ def blazer_time_value(data_source, k, v)
317
+ data_source.local_time_suffix.any? { |s| k.ends_with?(s) } ? v.to_s.sub(" UTC", "") : v.in_time_zone(Blazer.time_zone)
318
+ end
319
+ helper_method :blazer_time_value
320
+
321
+ def blazer_run_id
322
+ params[:run_id].to_s.gsub(/[^a-z0-9\-]/i, "")
323
+ end
324
+ end
325
+ end