sql-jarvis 2.0.1 → 2.0.2

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -1
  3. data/LICENSE.txt +1 -1
  4. data/README.md +153 -52
  5. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/blazer/favicon.png +0 -0
  11. data/app/assets/javascripts/blazer/Chart.js +4195 -3884
  12. data/app/assets/javascripts/blazer/Sortable.js +1493 -1097
  13. data/app/assets/javascripts/blazer/ace/ace.js +21294 -4
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1991 -3
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -1
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -1
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -1
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -1
  19. data/app/assets/javascripts/blazer/application.js +4 -3
  20. data/app/assets/javascripts/blazer/bootstrap.js +623 -612
  21. data/app/assets/javascripts/blazer/chartkick.js +1769 -1248
  22. data/app/assets/javascripts/blazer/daterangepicker.js +263 -115
  23. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  24. data/app/assets/javascripts/blazer/{jquery_ujs.js → jquery-ujs.js} +161 -75
  25. data/app/assets/javascripts/blazer/jquery.js +9506 -9450
  26. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +321 -259
  27. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
  28. data/app/assets/javascripts/blazer/queries.js +1 -1
  29. data/app/assets/javascripts/blazer/routes.js +3 -0
  30. data/app/assets/javascripts/blazer/selectize.js +3828 -3604
  31. data/app/assets/javascripts/blazer/stupidtable.js +255 -88
  32. data/app/assets/javascripts/blazer/vue.js +8015 -4583
  33. data/app/assets/stylesheets/blazer/application.css +41 -5
  34. data/app/assets/stylesheets/blazer/bootstrap.css.erb +879 -325
  35. data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
  36. data/app/assets/stylesheets/blazer/selectize.default.css +26 -10
  37. data/app/controllers/blazer/base_controller.rb +7 -0
  38. data/app/controllers/blazer/checks_controller.rb +1 -1
  39. data/app/controllers/blazer/dashboards_controller.rb +0 -4
  40. data/app/controllers/blazer/queries_controller.rb +20 -12
  41. data/app/helpers/blazer/base_helper.rb +1 -1
  42. data/app/mailers/blazer/slack_notifier.rb +76 -0
  43. data/app/models/blazer/check.rb +9 -0
  44. data/app/views/blazer/_nav.html.erb +0 -1
  45. data/app/views/blazer/_variables.html.erb +41 -19
  46. data/app/views/blazer/checks/_form.html.erb +16 -8
  47. data/app/views/blazer/checks/edit.html.erb +2 -0
  48. data/app/views/blazer/checks/index.html.erb +33 -4
  49. data/app/views/blazer/checks/new.html.erb +2 -0
  50. data/app/views/blazer/dashboards/_form.html.erb +4 -4
  51. data/app/views/blazer/dashboards/edit.html.erb +2 -0
  52. data/app/views/blazer/dashboards/new.html.erb +2 -0
  53. data/app/views/blazer/dashboards/show.html.erb +7 -3
  54. data/app/views/blazer/queries/_form.html.erb +11 -6
  55. data/app/views/blazer/queries/docs.html.erb +131 -0
  56. data/app/views/blazer/queries/home.html.erb +12 -3
  57. data/app/views/blazer/queries/run.html.erb +36 -6
  58. data/app/views/blazer/queries/schema.html.erb +46 -8
  59. data/app/views/blazer/queries/show.html.erb +4 -4
  60. data/app/views/layouts/blazer/application.html.erb +3 -3
  61. data/config/routes.rb +5 -1
  62. data/lib/blazer.rb +32 -13
  63. data/lib/blazer/adapters/athena_adapter.rb +1 -1
  64. data/lib/blazer/adapters/elasticsearch_adapter.rb +14 -17
  65. data/lib/blazer/adapters/mongodb_adapter.rb +1 -1
  66. data/lib/blazer/adapters/sql_adapter.rb +7 -1
  67. data/lib/blazer/engine.rb +4 -0
  68. data/lib/blazer/result.rb +62 -29
  69. data/lib/blazer/run_statement_job.rb +6 -9
  70. data/lib/blazer/version.rb +1 -1
  71. data/lib/generators/blazer/templates/config.yml.tt +13 -2
  72. data/lib/generators/blazer/templates/install.rb.tt +1 -0
  73. data/lib/tasks/blazer.rake +1 -0
  74. metadata +33 -37
  75. data/.gitattributes +0 -1
  76. data/.github/ISSUE_TEMPLATE.md +0 -7
  77. data/.gitignore +0 -14
  78. data/Gemfile +0 -7
  79. data/Rakefile +0 -1
  80. data/app/assets/javascripts/blazer/highlight.pack.js +0 -1
  81. data/app/assets/javascripts/blazer/moment-timezone.js +0 -1007
  82. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +0 -375
  83. data/blazer.gemspec +0 -30
@@ -1,6 +1,6 @@
1
1
  <div id="queries">
2
- <div id="header" style="margin-bottom: 20px;">
3
- <div class="pull-right">
2
+ <div id="header">
3
+ <div class="pull-right" style="line-height: 34px;">
4
4
  <% if blazer_user %>
5
5
  <div class='btn-group' style='margin-right: 20px;'>
6
6
  <%= link_to "All", root_path, class: !params[:filter] ? 'active btn btn-default btn-xs' : 'btn btn-default btn-xs' %>
@@ -21,12 +21,14 @@
21
21
  <span class="sr-only">Toggle Dropdown</span>
22
22
  </button>
23
23
  <ul class="dropdown-menu">
24
+ <li><%= link_to "Checks", checks_path %></li>
25
+ <li role="separator" class="divider"></li>
24
26
  <li><%= link_to "New Dashboard", new_dashboard_path %></li>
25
27
  <li><%= link_to "New Check", new_check_path %></li>
26
28
  </ul>
27
29
  </div>
28
30
  </div>
29
- <input type="text" v-model="searchTerm" placeholder="Start typing a query or person" style="width: 300px; display: inline-block;" autofocus=true class="search form-control" />
31
+ <input type="text" v-model="searchTerm" placeholder="Start typing a query, dashboard, or person" style="width: 300px; display: inline-block;" v-focus class="search form-control" />
30
32
  </div>
31
33
 
32
34
  <table class="table">
@@ -148,6 +150,13 @@
148
150
  return Routes.query_path(item.to_param)
149
151
  }
150
152
  }
153
+ },
154
+ directives: {
155
+ focus: {
156
+ inserted: function (el) {
157
+ el.focus()
158
+ }
159
+ }
151
160
  }
152
161
  })
153
162
  </script>
@@ -24,7 +24,7 @@
24
24
  <% end %>
25
25
  </p>
26
26
  <% end %>
27
- <p class="text-muted">
27
+ <p class="text-muted" style="margin-bottom: 10px;">
28
28
  <%= pluralize(@rows.size, "row") %>
29
29
 
30
30
  <% @checks.select(&:state).each do |check| %>
@@ -33,19 +33,46 @@
33
33
  &middot; <%= check.message %>
34
34
  <% end %>
35
35
  <% end %>
36
+
37
+ <% if @query && @result.forecastable? && !params[:forecast] %>
38
+ &middot;
39
+ <%= link_to "Forecast", query_path(@query, {forecast: "t"}.merge(variable_params)) %>
40
+ <% end %>
36
41
  </p>
37
42
  <% end %>
43
+ <% if @forecast_error %>
44
+ <div class="alert alert-danger"><%= @forecast_error %></div>
45
+ <% end %>
38
46
  <% if @rows.any? %>
39
47
  <% values = @rows.first %>
40
48
  <% chart_id = SecureRandom.hex %>
41
49
  <% column_types = @result.column_types %>
42
50
  <% chart_type = @result.chart_type %>
43
- <% chart_options = {id: chart_id, min: nil} %>
51
+ <% chart_options = {id: chart_id} %>
52
+ <% if ["line", "line2"].include?(chart_type) %>
53
+ <% chart_options.merge!(min: nil) %>
54
+ <% end %>
55
+ <% if chart_type == "scatter" %>
56
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
57
+ <% elsif ["bar", "bar2"].include?(chart_type) %>
58
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
59
+ <% elsif chart_type != "pie" %>
60
+ <% if column_types.size == 2 || @forecast %>
61
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
62
+ <% else %>
63
+ <%# chartjs axis: 'x' has poor behavior with multiple series %>
64
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
65
+ <% end %>
66
+ <% end %>
44
67
  <% series_library = {} %>
45
68
  <% target_index = @columns.index { |k| k.downcase == "target" } %>
46
69
  <% if target_index %>
47
70
  <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: "#109618", pointBackgroundColor: "#109618", backgroundColor: "#109618"} %>
48
71
  <% end %>
72
+ <% if @forecast %>
73
+ <% color = "#54a3ee" %>
74
+ <% series_library[1] = {borderDash: [8], borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
75
+ <% end %>
49
76
  <% if blazer_maps? && @markers.any? %>
50
77
  <div id="map" style="height: <%= @only_chart ? 300 : 500 %>px;"></div>
51
78
  <script>
@@ -76,11 +103,14 @@
76
103
  map.fitBounds(featureLayer.getBounds());
77
104
  </script>
78
105
  <% elsif chart_type == "line" %>
79
- <%= line_chart @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} }, chart_options %>
106
+ <% chart_data = @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} } %>
107
+ <%= line_chart chart_data, chart_options %>
80
108
  <% elsif chart_type == "line2" %>
81
109
  <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, chart_options %>
110
+ <% elsif chart_type == "pie" %>
111
+ <%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, chart_options %>
82
112
  <% elsif chart_type == "bar" %>
83
- <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, id: chart_id %>
113
+ <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, chart_options %>
84
114
  <% elsif chart_type == "bar2" %>
85
115
  <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
86
116
  <% labels = first_20.map { |r| r[0] }.uniq %>
@@ -90,9 +120,9 @@
90
120
  <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
91
121
  <% end %>
92
122
  <% end %>
93
- <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, id: chart_id %>
123
+ <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, chart_options %>
94
124
  <% elsif chart_type == "scatter" %>
95
- <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], id: chart_id %>
125
+ <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
96
126
  <% elsif @only_chart %>
97
127
  <% if @rows.size == 1 && @rows.first.size == 1 %>
98
128
  <% v = @rows.first.first %>
@@ -1,13 +1,25 @@
1
- <% blazer_title "Database Schema" %>
1
+ <% blazer_title "Schema: #{@data_source.name}" %>
2
+
3
+ <h1>Schema: <%= @data_source.name %></h1>
4
+
5
+ <hr />
6
+
7
+ <div id="header">
8
+ <input id="search" type="text" placeholder="Start typing a table or column" style="width: 300px; display: inline-block;" class="search form-control" />
9
+ </div>
2
10
 
3
11
  <% @schema.each do |table| %>
4
- <h4>
5
- <%= table[:table] %>
6
- <% if table[:schema] != "public" %>
7
- <small><%= table[:schema] %></small>
8
- <% end %>
9
- </h4>
10
- <table class="table" style="max-width: 500px;">
12
+ <table class="table schema-table">
13
+ <thead>
14
+ <tr>
15
+ <th colspan="2">
16
+ <%= table[:table] %>
17
+ <% if table[:schema] != "public" %>
18
+ <span class="text-muted" style="font-weight: normal;"><%= table[:schema] %></span>
19
+ <% end %>
20
+ </th>
21
+ </tr>
22
+ </thead>
11
23
  <tbody>
12
24
  <% table[:columns].each do |column| %>
13
25
  <tr>
@@ -18,3 +30,29 @@
18
30
  </tbody>
19
31
  </table>
20
32
  <% end %>
33
+
34
+ <script>
35
+ $("#search").on("keyup", function() {
36
+ var value = $(this).val().toLowerCase()
37
+ $(".schema-table").filter(function() {
38
+ // if found in table name, show entire table
39
+ // if just found in rows, show row
40
+
41
+ var found = $(this).find("thead").text().toLowerCase().indexOf(value) > -1
42
+
43
+ if (found) {
44
+ $(this).find("tbody tr").toggle(true)
45
+ } else {
46
+ $(this).find("tbody tr").filter(function() {
47
+ var found2 = $(this).text().toLowerCase().indexOf(value) > -1
48
+ $(this).toggle(found2)
49
+ if (found2) {
50
+ found = true
51
+ }
52
+ })
53
+ }
54
+
55
+ $(this).toggle(found)
56
+ })
57
+ }).focus()
58
+ </script>
@@ -5,7 +5,7 @@
5
5
  <div class="row" style="padding-top: 13px;">
6
6
  <div class="col-sm-8">
7
7
  <%= render partial: "blazer/nav" %>
8
- <h3 style="margin: 0; line-height: 34px; display: inline;">
8
+ <h3 style="line-height: 34px; display: inline; margin-left: 5px;">
9
9
  <%= @query.name %>
10
10
  </h3>
11
11
  </div>
@@ -14,7 +14,7 @@
14
14
  <%= link_to "Fork", new_query_path(variable_params.merge(fork_query_id: @query.id, data_source: @query.data_source, name: @query.name)), class: "btn btn-info" %>
15
15
 
16
16
  <% if !@error && @success %>
17
- <%= button_to "⤓ .csv", run_queries_path(query_id: @query.id, format: "csv"), params: {statement: @statement}, class: "btn btn-primary" %>
17
+ <%= button_to "⤓ .csv", run_queries_path(query_id: @query.id, format: "csv", forecast: params[:forecast]), params: {statement: @statement}, class: "btn btn-primary" %>
18
18
  <%= button_to "⤓ .xlsx", run_queries_path(query_id: @query.id, format: "xlsx"), params: {statement: @statement}, class: "btn btn-primary" %>
19
19
  <% end %>
20
20
  </div>
@@ -57,13 +57,13 @@
57
57
  $("#results").addClass("query-error").html(message)
58
58
  }
59
59
 
60
- <%= blazer_js_var "data", variable_params.merge(statement: @statement, query_id: @query.id) %>
60
+ <%= blazer_js_var "data", variable_params.merge(statement: @statement, query_id: @query.id, data_source: @query.data_source) %>
61
61
 
62
62
  runQuery(data, showRun, showError)
63
63
  </script>
64
64
  <% end %>
65
65
 
66
- <% unless %w(mongodb elasticsearch).include?(Blazer.data_sources[@query.data_source].adapter) %>
66
+ <% unless %w(mongodb).include?(Blazer.data_sources[@query.data_source].adapter) %>
67
67
  <script>
68
68
  // do not highlight really long queries
69
69
  // this can lead to performance issues
@@ -4,15 +4,15 @@
4
4
  <title><%= blazer_title ? blazer_title : "Blazer" %></title>
5
5
 
6
6
  <meta charset="utf-8" />
7
-
7
+ <%= favicon_link_tag "blazer/favicon.png" %>
8
8
  <%= stylesheet_link_tag "blazer/application" %>
9
9
  <%= javascript_include_tag "blazer/application" %>
10
10
  <script>
11
11
  <%= blazer_js_var "rootPath", root_path %>
12
12
  </script>
13
13
  <% if blazer_maps? %>
14
- <%= stylesheet_link_tag "https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.css" %>
15
- <%= javascript_include_tag "https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.js" %>
14
+ <%= stylesheet_link_tag "https://api.mapbox.com/mapbox.js/v3.1.1/mapbox.css", integrity: "sha384-o8tecBIfqi9yU5yYK2Ne/9A9hlOGFV9MBvCpmemvJH1XxqOe6h8Bl4mLxMi6PgjG", crossorigin: "anonymous" %>
15
+ <%= javascript_include_tag "https://api.mapbox.com/mapbox.js/v3.1.1/mapbox.js", integrity: "sha384-j81LqvtvYigFzGSUgAoFijpvoq4yGoCJSOXI9DFaUEpenR029MBE3E/X5Gr+WdO0", crossorigin: "anonymous" %>
16
16
  <% end %>
17
17
  <%= csrf_meta_tags %>
18
18
  </head>
data/config/routes.rb CHANGED
@@ -6,12 +6,16 @@ Blazer::Engine.routes.draw do
6
6
  get :tables, on: :collection
7
7
  get :columns, on: :collection
8
8
  get :schema, on: :collection
9
+ get :docs, on: :collection
9
10
  end
11
+
10
12
  resources :checks, except: [:show] do
11
13
  get :run, on: :member
12
14
  end
13
- resources :dashboards do
15
+
16
+ resources :dashboards, except: [:index] do
14
17
  post :refresh, on: :member
15
18
  end
19
+
16
20
  root to: "queries#home"
17
21
  end
data/lib/blazer.rb CHANGED
@@ -1,14 +1,19 @@
1
+ # dependencies
1
2
  require "csv"
2
3
  require "yaml"
3
4
  require "chartkick"
4
5
  require "zip/zip"
5
6
  require "axlsx"
6
7
  require "safely/core"
8
+
9
+ # modules
7
10
  require "blazer/version"
8
11
  require "blazer/data_source"
9
12
  require "blazer/result"
10
13
  require "blazer/excel_parser"
11
14
  require "blazer/run_statement"
15
+
16
+ # adapters
12
17
  require "blazer/adapters/base_adapter"
13
18
  require "blazer/adapters/athena_adapter"
14
19
  require "blazer/adapters/bigquery_adapter"
@@ -20,6 +25,8 @@ require "blazer/adapters/mongodb_adapter"
20
25
  require "blazer/adapters/presto_adapter"
21
26
  require "blazer/adapters/sql_adapter"
22
27
  require "blazer/adapters/snowflake_adapter"
28
+
29
+ # engine
23
30
  require "blazer/engine"
24
31
 
25
32
  module Blazer
@@ -37,14 +44,18 @@ module Blazer
37
44
  attr_accessor :subdomain
38
45
  attr_accessor :cache
39
46
  attr_accessor :transform_statement
47
+ attr_accessor :transform_variable
40
48
  attr_accessor :check_schedules
41
49
  attr_accessor :mapbox_access_token
42
50
  attr_accessor :assignees
43
51
  attr_accessor :anomaly_checks
52
+ attr_accessor :forecasting
44
53
  attr_accessor :async
45
54
  attr_accessor :images
46
55
  attr_accessor :query_viewable
47
56
  attr_accessor :query_editable
57
+ attr_accessor :override_csp
58
+ attr_accessor :slack_webhook_url
48
59
  attr_accessor :query_creatable
49
60
  end
50
61
  self.audit = true
@@ -54,8 +65,10 @@ module Blazer
54
65
  self.assignees = []
55
66
  self.subdomain = nil
56
67
  self.anomaly_checks = false
68
+ self.forecasting = false
57
69
  self.async = false
58
70
  self.images = false
71
+ self.override_csp = false
59
72
 
60
73
  TIMEOUT_MESSAGE = "Query timed out :("
61
74
  TIMEOUT_ERRORS = [
@@ -103,20 +116,11 @@ module Blazer
103
116
 
104
117
  def self.data_sources
105
118
  @data_sources ||= begin
106
- ds = Hash[
107
- settings["data_sources"].map do |id, s|
108
- [id, Blazer::DataSource.new(id, s)]
109
- end
110
- ]
111
- ds.default = ds.values.first
119
+ ds = Hash.new { |hash, key| raise Blazer::Error, "Unknown data source: #{key}" }
120
+ settings["data_sources"].each do |id, s|
121
+ ds[id] = Blazer::DataSource.new(id, s)
122
+ end
112
123
  ds
113
-
114
- # TODO Blazer 2.0
115
- # ds2 = Hash.new { |hash, key| raise Blazer::Error, "Unknown data source: #{key}" }
116
- # ds.each do |k, v|
117
- # ds2[k] = v
118
- # end
119
- # ds2
120
124
  end
121
125
  end
122
126
 
@@ -186,10 +190,15 @@ module Blazer
186
190
 
187
191
  def self.send_failing_checks
188
192
  emails = {}
193
+ slack_channels = {}
194
+
189
195
  Blazer::Check.includes(:query).where(state: ["failing", "error", "timed out", "disabled"]).find_each do |check|
190
196
  check.split_emails.each do |email|
191
197
  (emails[email] ||= []) << check
192
198
  end
199
+ check.split_slack_channels.each do |channel|
200
+ (slack_channels[channel] ||= []) << check
201
+ end
193
202
  end
194
203
 
195
204
  emails.each do |email, checks|
@@ -197,6 +206,16 @@ module Blazer
197
206
  Blazer::CheckMailer.failing_checks(email, checks).deliver_now
198
207
  end
199
208
  end
209
+
210
+ slack_channels.each do |channel, checks|
211
+ Safely.safely do
212
+ Blazer::SlackNotifier.failing_checks(channel, checks)
213
+ end
214
+ end
215
+ end
216
+
217
+ def self.slack?
218
+ slack_webhook_url.present?
200
219
  end
201
220
 
202
221
  def self.adapters
@@ -13,7 +13,7 @@ module Blazer
13
13
  client.start_query_execution(
14
14
  query_string: statement,
15
15
  # use token so we fetch cached results after query is run
16
- client_request_token: Digest::MD5.hexdigest(statement),
16
+ client_request_token: Digest::MD5.hexdigest([statement,data_source.id].join("/")),
17
17
  query_execution_context: {
18
18
  database: database,
19
19
  },
@@ -7,22 +7,17 @@ module Blazer
7
7
  error = nil
8
8
 
9
9
  begin
10
- header, body = statement.gsub(/\/\/.+/, "").strip.split("\n", 2)
11
- body = JSON.parse(body)
12
- body["timeout"] ||= data_source.timeout if data_source.timeout
13
- response = client.msearch(body: [JSON.parse(header), body])["responses"].first
14
- if response["error"]
15
- error = response["error"]
16
- else
17
- hits = response["hits"]["hits"]
18
- source_keys = hits.flat_map { |r| r["_source"].keys }.uniq
19
- hit_keys = (hits.first.try(:keys) || []) - ["_source"]
20
- columns = source_keys + hit_keys
21
- rows =
22
- hits.map do |r|
23
- source = r["_source"]
24
- source_keys.map { |k| source[k] } + hit_keys.map { |k| r[k] }
10
+ response = client.xpack.sql.query(body: {query: "#{statement} /*#{comment}*/"})
11
+ columns = response["columns"].map { |v| v["name"] }
12
+ # Elasticsearch does not differentiate between dates and times
13
+ date_indexes = response["columns"].each_index.select { |i| response["columns"][i]["type"] == "date" }
14
+ if columns.any?
15
+ rows = response["rows"]
16
+ date_indexes.each do |i|
17
+ rows.each do |row|
18
+ row[i] = Time.parse(row[i])
25
19
  end
20
+ end
26
21
  end
27
22
  rescue => e
28
23
  error = e.message
@@ -32,11 +27,13 @@ module Blazer
32
27
  end
33
28
 
34
29
  def tables
35
- client.indices.get_aliases(name: "*").map { |k, v| [k, v["aliases"].keys] }.flatten.uniq.sort
30
+ indices = client.cat.indices(format: "json").map { |v| v["index"] }
31
+ aliases = client.cat.aliases(format: "json").map { |v| v["alias"] }
32
+ (indices + aliases).uniq.sort
36
33
  end
37
34
 
38
35
  def preview_statement
39
- %!// header\n{"index": "{table}"}\n\n// body\n{"query": {"match_all": {}}, "size": 10}!
36
+ "SELECT * FROM \"{table}\" LIMIT 10"
40
37
  end
41
38
 
42
39
  protected
@@ -7,7 +7,7 @@ module Blazer
7
7
  error = nil
8
8
 
9
9
  begin
10
- documents = db.command({:$eval => "#{statement.strip}.toArray()"}).documents.first["retval"]
10
+ documents = db.command({:$eval => "#{statement.strip}.toArray()", nolock: true}).documents.first["retval"]
11
11
  columns = documents.flat_map { |r| r.keys }.uniq
12
12
  rows = documents.map { |r| columns.map { |c| r[c] } }
13
13
  rescue => e
@@ -155,7 +155,13 @@ module Blazer
155
155
  if postgresql? || redshift?
156
156
  execute("SET #{use_transaction? ? "LOCAL " : ""}statement_timeout = #{timeout.to_i * 1000}")
157
157
  elsif mysql?
158
- execute("SET max_execution_time = #{timeout.to_i * 1000}")
158
+ # use send as this method is private in Rails 4.2
159
+ mariadb = connection_model.connection.send(:mariadb?) rescue false
160
+ if mariadb
161
+ execute("SET max_statement_time = #{timeout.to_i * 1000}")
162
+ else
163
+ execute("SET max_execution_time = #{timeout.to_i * 1000}")
164
+ end
159
165
  else
160
166
  raise Blazer::TimeoutNotSupported, "Timeout not supported for #{adapter_name} adapter"
161
167
  end