sql-jarvis 1.8.0

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.
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