blazer 2.6.5 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +13 -28
  5. data/app/assets/javascripts/blazer/ace/ace.js +7235 -8906
  6. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +762 -774
  7. data/app/assets/javascripts/blazer/ace/mode-sql.js +177 -72
  8. data/app/assets/javascripts/blazer/ace/snippets/sql.js +5 -29
  9. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -6
  10. data/app/assets/javascripts/blazer/ace/theme-twilight.js +8 -106
  11. data/app/assets/javascripts/blazer/application.js +9 -6
  12. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  13. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  14. data/app/assets/javascripts/blazer/chartkick.js +1020 -914
  15. data/app/assets/javascripts/blazer/highlight.min.js +466 -3
  16. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  17. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +39 -38
  18. data/app/assets/javascripts/blazer/moment.js +105 -88
  19. data/app/assets/javascripts/blazer/queries.js +10 -1
  20. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  21. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  22. data/app/assets/stylesheets/blazer/bootstrap.css +1 -1
  23. data/app/assets/stylesheets/blazer/selectize.css +1 -1
  24. data/app/controllers/blazer/base_controller.rb +85 -84
  25. data/app/controllers/blazer/checks_controller.rb +6 -6
  26. data/app/controllers/blazer/dashboards_controller.rb +24 -24
  27. data/app/controllers/blazer/queries_controller.rb +208 -186
  28. data/app/controllers/blazer/uploads_controller.rb +49 -49
  29. data/app/helpers/blazer/base_helper.rb +0 -4
  30. data/app/models/blazer/query.rb +1 -12
  31. data/app/views/blazer/checks/index.html.erb +1 -1
  32. data/app/views/blazer/dashboards/_form.html.erb +11 -5
  33. data/app/views/blazer/queries/_form.html.erb +19 -14
  34. data/app/views/blazer/queries/docs.html.erb +11 -1
  35. data/app/views/blazer/queries/home.html.erb +9 -6
  36. data/app/views/blazer/queries/run.html.erb +17 -32
  37. data/app/views/blazer/queries/show.html.erb +12 -20
  38. data/app/views/layouts/blazer/application.html.erb +1 -5
  39. data/lib/blazer/adapters/sql_adapter.rb +1 -1
  40. data/lib/blazer/adapters.rb +17 -0
  41. data/lib/blazer/anomaly_detectors.rb +22 -0
  42. data/lib/blazer/data_source.rb +29 -40
  43. data/lib/blazer/engine.rb +11 -9
  44. data/lib/blazer/forecasters.rb +7 -0
  45. data/lib/blazer/result.rb +13 -71
  46. data/lib/blazer/result_cache.rb +71 -0
  47. data/lib/blazer/run_statement.rb +1 -1
  48. data/lib/blazer/run_statement_job.rb +2 -2
  49. data/lib/blazer/statement.rb +3 -1
  50. data/lib/blazer/version.rb +1 -1
  51. data/lib/blazer.rb +51 -53
  52. data/licenses/LICENSE-chart.js.txt +1 -1
  53. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  54. data/licenses/LICENSE-chartkick.js.txt +1 -1
  55. data/licenses/LICENSE-date-fns.txt +21 -0
  56. data/licenses/LICENSE-kurkle-color.txt +9 -0
  57. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  58. data/licenses/{LICENSE-jquery-ujs.txt → LICENSE-rails-ujs.txt} +1 -1
  59. data/licenses/LICENSE-vue.txt +1 -1
  60. metadata +26 -18
  61. data/app/assets/javascripts/blazer/Chart.js +0 -16172
  62. data/app/assets/javascripts/blazer/jquery-ujs.js +0 -555
  63. data/app/assets/javascripts/blazer/vue.js +0 -12014
  64. data/lib/blazer/adapters/mongodb_adapter.rb +0 -43
  65. data/lib/blazer/detect_anomalies.R +0 -19
@@ -19,19 +19,8 @@ module Blazer
19
19
  name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip
20
20
  end
21
21
 
22
- def viewable?(user)
23
- if Blazer.query_viewable
24
- Blazer.query_viewable.call(self, user)
25
- else
26
- true
27
- end
28
- end
29
-
30
22
  def editable?(user)
31
- editable = !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
32
- editable &&= viewable?(user)
33
- editable &&= Blazer.query_editable.call(self, user) if Blazer.query_editable
34
- editable
23
+ !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
35
24
  end
36
25
 
37
26
  def variables
@@ -55,7 +55,7 @@
55
55
  </td>
56
56
  <td style="text-align: right; padding: 1px;">
57
57
  <%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
58
- <%= link_to "Run Now", query_path(check.query), class: "btn btn-primary" %>
58
+ <%= button_to "Run Now", refresh_query_path(check.query), class: "btn btn-primary" %>
59
59
  </td>
60
60
  </tr>
61
61
  <% end %>
@@ -23,7 +23,7 @@
23
23
  </div>
24
24
  <p style="padding-bottom: 140px;" v-cloak>
25
25
  <% if @dashboard.persisted? %>
26
- <%= link_to "Delete", dashboard_path(@dashboard), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger"%>
26
+ <%= link_to "Delete", dashboard_path(@dashboard), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
27
27
  <% end %>
28
28
  <%= f.submit "Save", class: "btn btn-success" %>
29
29
  <%= link_to "Back", :back, class: "btn btn-link" %>
@@ -34,10 +34,11 @@
34
34
  <%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
35
35
  <%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
36
36
 
37
- var app = new Vue({
38
- el: "#app",
39
- data: {
40
- queries: dashboardQueries
37
+ var app = Vue.createApp({
38
+ data: function() {
39
+ return {
40
+ queries: dashboardQueries
41
+ }
41
42
  },
42
43
  methods: {
43
44
  remove: function(index) {
@@ -45,6 +46,7 @@
45
46
  }
46
47
  },
47
48
  mounted: function() {
49
+ var app = this
48
50
  $("#query_id").selectize({
49
51
  options: queries,
50
52
  highlight: false,
@@ -68,8 +70,12 @@
68
70
  })
69
71
  }
70
72
  })
73
+ app.config.compilerOptions.whitespace = "preserve"
74
+ app.mount("#app")
75
+
71
76
  Sortable.create($("#queries").get(0), {
72
77
  onEnd: function(e) {
78
+ var app = window.app._component.data()
73
79
  app.queries.splice(e.newIndex, 0, app.queries.splice(e.oldIndex, 1)[0])
74
80
  }
75
81
  })
@@ -2,8 +2,10 @@
2
2
  <div class="alert alert-danger"><%= @query.errors.full_messages.first %></div>
3
3
  <% end %>
4
4
 
5
+ <% @variable_params = @query.persisted? ? variable_params(@query) : nested_variable_params(@query) %>
6
+
5
7
  <div id="app" v-cloak>
6
- <%= form_for @query, url: (@query.persisted? ? query_path(@query, params: variable_params(@query)) : queries_path(params: variable_params(@query))), html: {autocomplete: "off"} do |f| %>
8
+ <%= form_for @query, url: (@query.persisted? ? query_path(@query, params: @variable_params) : queries_path(params: @variable_params)), html: {autocomplete: "off"} do |f| %>
7
9
  <div class="row">
8
10
  <div id="statement-box" class="col-xs-8">
9
11
  <div class= "form-group">
@@ -19,7 +21,7 @@
19
21
  <a :href="schemaPath" target="_blank" style="margin-left: 40px;">Schema</a>
20
22
  </div>
21
23
 
22
- <%= f.select :data_source, Blazer.data_sources.values.select { |ds| q = @query.dup; q.data_source = ds.id; q.editable?(blazer_user) }.map { |ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size <= 1), style: "width: 140px;" %>
24
+ <%= f.select :data_source, Blazer.data_sources.map { |_, ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size <= 1), style: "width: 140px;" %>
23
25
  <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
24
26
  <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
25
27
  </div>
@@ -67,18 +69,19 @@
67
69
  </div>
68
70
 
69
71
  <script>
70
- <%= blazer_js_var "variableParams", variable_params(@query) %>
71
- <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
72
-
73
- var app = new Vue({
74
- el: "#app",
75
- data: {
76
- running: false,
77
- results: "",
78
- error: false,
79
- dataSource: "",
80
- selectize: null,
81
- editorHeight: "180px"
72
+ <%= blazer_js_var "variableParams", @variable_params %>
73
+ <%= blazer_js_var "previewStatement", Blazer.data_sources.to_h { |k, v| [k, (v.preview_statement rescue "")] } %>
74
+
75
+ var app = Vue.createApp({
76
+ data: function() {
77
+ return {
78
+ running: false,
79
+ results: "",
80
+ error: false,
81
+ dataSource: "",
82
+ selectize: null,
83
+ editorHeight: "180px"
84
+ }
82
85
  },
83
86
  computed: {
84
87
  schemaPath: function() {
@@ -247,4 +250,6 @@
247
250
  this.showEditor()
248
251
  }
249
252
  })
253
+ app.config.compilerOptions.whitespace = "preserve"
254
+ app.mount("#app")
250
255
  </script>
@@ -119,7 +119,17 @@
119
119
  <td>Map</td>
120
120
  <td>
121
121
  Named <code>latitude</code> and <code>longitude</code>, or <code>lat</code> and <code>lon</code>, or <code>lat</code> and <code>lng</code>
122
- <% if !blazer_maps? %>
122
+ <% if !Blazer.maps? %>
123
+ <br />
124
+ <strong>Needs configured</strong>
125
+ <% end %>
126
+ </td>
127
+ </tr>
128
+ <tr>
129
+ <td>Area Map</td>
130
+ <td>
131
+ Named <code>geojson</code>
132
+ <% if !Blazer.maps? %>
123
133
  <br />
124
134
  <strong>Needs configured</strong>
125
135
  <% end %>
@@ -77,12 +77,13 @@
77
77
  return str.toLowerCase()
78
78
  }
79
79
 
80
- var app = new Vue({
81
- el: "#queries",
82
- data: {
83
- searchTerm: "",
84
- more: more,
85
- updateCounter: 0
80
+ var app = Vue.createApp({
81
+ data: function() {
82
+ return {
83
+ searchTerm: "",
84
+ more: more,
85
+ updateCounter: 0
86
+ }
86
87
  },
87
88
  created: function() {
88
89
  this.listItems = dashboards.concat(queries)
@@ -163,4 +164,6 @@
163
164
  }
164
165
  }
165
166
  })
167
+ app.config.compilerOptions.whitespace = "preserve"
168
+ app.mount("#queries")
166
169
  </script>
@@ -66,54 +66,39 @@
66
66
  <% target_index = @columns.index { |k| k.downcase == "target" } %>
67
67
  <% if target_index %>
68
68
  <% color = "#109618" %>
69
- <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
69
+ <% series_library[target_index - 1] = {pointStyle: "line", pointBorderWidth: 0, hitRadius: 5, borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
70
70
  <% end %>
71
71
  <% if @forecast %>
72
72
  <% color = "#54a3ee" %>
73
73
  <% series_library[1] = {borderDash: [8], borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
74
74
  <% end %>
75
- <% if blazer_maps? && @markers.any? %>
75
+ <% if @markers.any? %>
76
76
  <% map_id = SecureRandom.hex %>
77
77
  <%= content_tag :div, nil, id: map_id, style: "height: #{@only_chart ? 300 : 500}px;" %>
78
78
  <script>
79
79
  <%= blazer_js_var "mapboxAccessToken", Blazer.mapbox_access_token %>
80
80
  <%= blazer_js_var "markers", @markers %>
81
81
  <%= blazer_js_var "mapId", map_id %>
82
- L.mapbox.accessToken = mapboxAccessToken;
83
- var map = L.mapbox.map(mapId)
84
- .addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
85
- var featureLayer = L.mapbox.featureLayer().addTo(map);
86
- var geojson = [];
87
- for (var i = 0; i < markers.length; i++) {
88
- var marker = markers[i];
89
- geojson.push({
90
- type: 'Feature',
91
- geometry: {
92
- type: 'Point',
93
- coordinates: [
94
- marker.longitude,
95
- marker.latitude
96
- ]
97
- },
98
- properties: {
99
- description: marker.title,
100
- 'marker-color': '#f86767',
101
- 'marker-size': 'medium'
102
- }
103
- });
104
- }
105
- featureLayer.setGeoJSON(geojson);
106
- map.fitBounds(featureLayer.getBounds());
82
+ new Mapkick.Map(mapId, markers, {accessToken: mapboxAccessToken, tooltips: {hover: false, html: true}});
83
+ </script>
84
+ <% elsif @geojson.any? %>
85
+ <% map_id = SecureRandom.hex %>
86
+ <%= content_tag :div, nil, id: map_id, style: "height: #{@only_chart ? 300 : 500}px;" %>
87
+ <script>
88
+ <%= blazer_js_var "mapboxAccessToken", Blazer.mapbox_access_token %>
89
+ <%= blazer_js_var "geojson", @geojson %>
90
+ <%= blazer_js_var "mapId", map_id %>
91
+ new Mapkick.AreaMap(mapId, geojson, {accessToken: mapboxAccessToken, tooltips: {hover: false, html: true}});
107
92
  </script>
108
93
  <% elsif chart_type == "line" %>
109
94
  <% 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]} } %>
110
95
  <%= line_chart chart_data, **chart_options %>
111
96
  <% elsif chart_type == "line2" %>
112
- <%= 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 %>
97
+ <%= line_chart @rows.group_by { |r| v = r[1]; (@smart_values[@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 %>
113
98
  <% elsif chart_type == "pie" %>
114
- <%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, **chart_options %>
99
+ <%= pie_chart @rows.map { |r| [(@smart_values[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, **chart_options %>
115
100
  <% elsif chart_type == "bar" %>
116
- <%= 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 %>
101
+ <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@smart_values[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, **chart_options %>
117
102
  <% elsif chart_type == "bar2" %>
118
103
  <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
119
104
  <% labels = first_20.map { |r| r[0] }.uniq %>
@@ -123,7 +108,7 @@
123
108
  <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
124
109
  <% end %>
125
110
  <% end %>
126
- <%= 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 %>
111
+ <%= column_chart first_20.group_by { |r| v = r[1]; (@smart_values[@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]; [(@smart_values[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, **chart_options %>
127
112
  <% elsif chart_type == "scatter" %>
128
113
  <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
129
114
  <% elsif @only_chart %>
@@ -180,7 +165,7 @@
180
165
  <% end %>
181
166
  <% end %>
182
167
 
183
- <% if v2 = (@boom[k] || {})[v.nil? ? v : v.to_s] %>
168
+ <% if v2 = (@smart_values[k] || {})[v.nil? ? v : v.to_s] %>
184
169
  <div class="text-muted"><%= v2 %></div>
185
170
  <% end %>
186
171
  </td>
@@ -1,12 +1,5 @@
1
1
  <% blazer_title @query.name %>
2
2
 
3
- <% if @success %>
4
- <% run_data = {statement: @query.statement, query_id: @query.id, data_source: @query.data_source, variables: variable_params(@query)} %>
5
- <% run_data.merge!(forecast: "t") if params[:forecast] %>
6
- <% run_data.merge!(cohort_period: params[:cohort_period]) if params[:cohort_period] %>
7
- <% run_data.transform_keys!(&:to_s) if Rails::VERSION::MAJOR < 6 %>
8
- <% end %>
9
-
10
3
  <div class="topbar">
11
4
  <div class="container">
12
5
  <div class="row" style="padding-top: 13px;">
@@ -18,10 +11,10 @@
18
11
  </div>
19
12
  <div class="col-sm-3 text-right">
20
13
  <%= link_to "Edit", edit_query_path(@query, params: variable_params(@query)), class: "btn btn-default", disabled: !@query.editable?(blazer_user) %>
21
- <%= link_to "Fork", new_query_path(params: variable_params(@query).merge(fork_query_id: @query.id, data_source: @query.data_source, name: @query.name)), class: "btn btn-info" %>
14
+ <%= link_to "Fork", new_query_path(params: {variables: variable_params(@query), fork_query_id: @query.id, data_source: @query.data_source, name: @query.name}), class: "btn btn-info" %>
22
15
 
23
16
  <% if !@error && @success %>
24
- <%= button_to "Download", run_queries_path(format: "csv"), params: run_data, class: "btn btn-primary" %>
17
+ <%= button_to "Download", run_queries_path(format: "csv"), params: @run_data, class: "btn btn-primary" %>
25
18
  <% end %>
26
19
  </div>
27
20
  </div>
@@ -41,7 +34,7 @@
41
34
  <% end %>
42
35
 
43
36
  <% if @query.description.present? %>
44
- <p><%= @query.description %></p>
37
+ <p style="white-space: pre-line;"><%= @query.description %></p>
45
38
  <% end %>
46
39
 
47
40
  <%= render partial: "blazer/variables", locals: {action: query_path(@query)} %>
@@ -63,18 +56,17 @@
63
56
  $("#results").addClass("query-error").html(message)
64
57
  }
65
58
 
66
- <%= blazer_js_var "data", run_data %>
59
+ <%= blazer_js_var "data", @run_data %>
67
60
 
68
61
  runQuery(data, showRun, showError)
69
62
  </script>
70
63
  <% end %>
71
64
 
72
- <% unless %w(mongodb).include?(Blazer.data_sources[@query.data_source].adapter) %>
73
- <script>
74
- // do not highlight really long queries
75
- // this can lead to performance issues
76
- if ($("code").text().length < 10000) {
77
- hljs.highlightBlock(document.getElementById("code"));
78
- }
79
- </script>
80
- <% end %>
65
+ <script>
66
+ // do not highlight really long queries
67
+ // this can lead to performance issues
68
+ var code = $("#code code")
69
+ if (code.text().length < 10000) {
70
+ hljs.highlightElement(code.get(0))
71
+ }
72
+ </script>
@@ -7,7 +7,7 @@
7
7
  <%= favicon_link_tag "blazer/favicon.png" %>
8
8
  <% if defined?(Propshaft::Railtie) %>
9
9
  <%= stylesheet_link_tag "blazer/bootstrap-propshaft", "blazer/bootstrap", "blazer/selectize", "blazer/github", "blazer/daterangepicker", "blazer/application" %>
10
- <%= javascript_include_tag "blazer/jquery", "blazer/jquery-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/Chart.js", "blazer/chartkick", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/bootstrap", "blazer/vue", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", "blazer/application" %>
10
+ <%= javascript_include_tag "blazer/jquery", "blazer/rails-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/chart.umd", "blazer/chartjs-adapter-date-fns.bundle", "blazer/chartkick", "blazer/mapkick.bundle", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/bootstrap", "blazer/vue.global.prod", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", "blazer/application" %>
11
11
  <% else %>
12
12
  <%= stylesheet_link_tag "blazer/application" %>
13
13
  <%= javascript_include_tag "blazer/application" %>
@@ -15,10 +15,6 @@
15
15
  <script>
16
16
  <%= blazer_js_var "rootPath", root_path %>
17
17
  </script>
18
- <% if blazer_maps? %>
19
- <%= stylesheet_link_tag "https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.css", integrity: "sha384-vxzdEt+wZRPNQbhChjmiaFMLWg86IGuq1NGDehJHsD2mphYkxXll/eSs16WWi6Dq", crossorigin: "anonymous" %>
20
- <%= javascript_include_tag "https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.js", integrity: "sha384-CTBEiDLiZJ8gkAQ3fYGoeiRp81/ecNiBkGz11jXFALOZ6++rbnqmdo6OImkmr1MO", crossorigin: "anonymous" %>
21
- <% end %>
22
18
  <%= csrf_meta_tags %>
23
19
  </head>
24
20
  <body>
@@ -233,7 +233,7 @@ module Blazer
233
233
  end
234
234
 
235
235
  def mysql?
236
- ["MySQL", "Mysql2", "Mysql2Spatial"].include?(adapter_name)
236
+ ["MySQL", "Mysql2", "Mysql2Spatial", "Trilogy"].include?(adapter_name)
237
237
  end
238
238
 
239
239
  def sqlite?
@@ -0,0 +1,17 @@
1
+ Blazer.register_adapter "athena", Blazer::Adapters::AthenaAdapter
2
+ Blazer.register_adapter "bigquery", Blazer::Adapters::BigQueryAdapter
3
+ Blazer.register_adapter "cassandra", Blazer::Adapters::CassandraAdapter
4
+ Blazer.register_adapter "drill", Blazer::Adapters::DrillAdapter
5
+ Blazer.register_adapter "druid", Blazer::Adapters::DruidAdapter
6
+ Blazer.register_adapter "elasticsearch", Blazer::Adapters::ElasticsearchAdapter
7
+ Blazer.register_adapter "hive", Blazer::Adapters::HiveAdapter
8
+ Blazer.register_adapter "ignite", Blazer::Adapters::IgniteAdapter
9
+ Blazer.register_adapter "influxdb", Blazer::Adapters::InfluxdbAdapter
10
+ Blazer.register_adapter "neo4j", Blazer::Adapters::Neo4jAdapter
11
+ Blazer.register_adapter "opensearch", Blazer::Adapters::OpensearchAdapter
12
+ Blazer.register_adapter "presto", Blazer::Adapters::PrestoAdapter
13
+ Blazer.register_adapter "salesforce", Blazer::Adapters::SalesforceAdapter
14
+ Blazer.register_adapter "soda", Blazer::Adapters::SodaAdapter
15
+ Blazer.register_adapter "spark", Blazer::Adapters::SparkAdapter
16
+ Blazer.register_adapter "sql", Blazer::Adapters::SqlAdapter
17
+ Blazer.register_adapter "snowflake", Blazer::Adapters::SnowflakeAdapter
@@ -0,0 +1,22 @@
1
+ Blazer.register_anomaly_detector "anomaly_detection" do |series|
2
+ anomalies = AnomalyDetection.detect(series.to_h, period: :auto)
3
+ anomalies.include?(series.last[0])
4
+ end
5
+
6
+ Blazer.register_anomaly_detector "prophet" do |series|
7
+ df = Rover::DataFrame.new(series[0..-2].map { |v| {"ds" => v[0], "y" => v[1]} })
8
+ m = Prophet.new(interval_width: 0.99)
9
+ m.logger.level = ::Logger::FATAL # no logging
10
+ m.fit(df)
11
+ future = Rover::DataFrame.new(series[-1..-1].map { |v| {"ds" => v[0]} })
12
+ forecast = m.predict(future).to_a[0]
13
+ lower = forecast["yhat_lower"]
14
+ upper = forecast["yhat_upper"]
15
+ value = series.last[1]
16
+ value < lower || value > upper
17
+ end
18
+
19
+ Blazer.register_anomaly_detector "trend" do |series|
20
+ anomalies = Trend.anomalies(series.to_h)
21
+ anomalies.include?(series.last[0])
22
+ end
@@ -1,5 +1,3 @@
1
- require "digest/md5"
2
-
3
1
  module Blazer
4
2
  class DataSource
5
3
  extend Forwardable
@@ -74,19 +72,16 @@ module Blazer
74
72
  @local_time_suffix ||= Array(settings["local_time_suffix"])
75
73
  end
76
74
 
77
- def read_cache(cache_key)
78
- value = Blazer.cache.read(cache_key)
79
- if value
80
- Blazer::Result.new(self, *Marshal.load(value), nil)
81
- end
75
+ def result_cache
76
+ @result_cache ||= Blazer::ResultCache.new(self)
82
77
  end
83
78
 
84
79
  def run_results(run_id)
85
- read_cache(run_cache_key(run_id))
80
+ result_cache.read_run(run_id)
86
81
  end
87
82
 
88
83
  def delete_results(run_id)
89
- Blazer.cache.delete(run_cache_key(run_id))
84
+ result_cache.delete_run(run_id)
90
85
  end
91
86
 
92
87
  def sub_variables(statement, vars)
@@ -102,13 +97,12 @@ module Blazer
102
97
  statement = Statement.new(statement, self) if statement.is_a?(String)
103
98
  statement.bind unless statement.bind_statement
104
99
 
105
- async = options[:async]
106
100
  result = nil
107
101
  if cache_mode != "off"
108
102
  if options[:refresh_cache]
109
103
  clear_cache(statement) # for checks
110
104
  else
111
- result = read_cache(statement_cache_key(statement))
105
+ result = result_cache.read_statement(statement)
112
106
  end
113
107
  end
114
108
 
@@ -130,26 +124,24 @@ module Blazer
130
124
  if options[:run_id]
131
125
  comment << ",run_id:#{options[:run_id]}"
132
126
  end
133
- result = run_statement_helper(statement, comment, async ? options[:run_id] : nil, options)
127
+ result = run_statement_helper(statement, comment, options)
128
+ end
129
+
130
+ if options[:async] && options[:run_id]
131
+ run_id = options[:run_id]
132
+ begin
133
+ result_cache.write_run(run_id, result)
134
+ rescue
135
+ result = Blazer::Result.new(self, [], [], "Error storing the results of this query :(", nil, false)
136
+ result_cache.write_run(run_id, result)
137
+ end
134
138
  end
135
139
 
136
140
  result
137
141
  end
138
142
 
139
143
  def clear_cache(statement)
140
- Blazer.cache.delete(statement_cache_key(statement))
141
- end
142
-
143
- def cache_key(key)
144
- (["blazer", "v4"] + key).join("/")
145
- end
146
-
147
- def statement_cache_key(statement)
148
- cache_key(["statement", id, Digest::MD5.hexdigest(statement.bind_statement.to_s.gsub("\r\n", "\n") + statement.bind_values.to_json)])
149
- end
150
-
151
- def run_cache_key(run_id)
152
- cache_key(["run", run_id])
144
+ result_cache.delete_statement(statement)
153
145
  end
154
146
 
155
147
  def quote(value)
@@ -232,7 +224,7 @@ module Blazer
232
224
  @parameter_binding ||= adapter_instance.parameter_binding
233
225
  end
234
226
 
235
- def run_statement_helper(statement, comment, run_id, options)
227
+ def run_statement_helper(statement, comment, options)
236
228
  start_time = Blazer.monotonic_time
237
229
  columns, rows, error =
238
230
  if adapter_instance.parameter_binding
@@ -242,32 +234,29 @@ module Blazer
242
234
  end
243
235
  duration = Blazer.monotonic_time - start_time
244
236
 
245
- cache_data = nil
246
237
  cache = !error && (cache_mode == "all" || (cache_mode == "slow" && duration >= cache_slow_threshold))
247
- if cache || run_id
248
- cache_data = Marshal.dump([columns, rows, error, cache ? Time.now : nil]) rescue nil
249
- end
250
238
 
251
- if cache && cache_data && adapter_instance.cachable?(statement.bind_statement)
252
- Blazer.cache.write(statement_cache_key(statement), cache_data, expires_in: cache_expires_in.to_f * 60)
253
- end
239
+ result = Blazer::Result.new(self, columns, rows, error, cache ? Time.now : nil, false)
254
240
 
255
- if run_id
256
- unless cache_data
257
- error = "Error storing the results of this query :("
258
- cache_data = Marshal.dump([[], [], error, nil])
241
+ if cache && adapter_instance.cachable?(statement.bind_statement)
242
+ begin
243
+ result_cache.write_statement(statement, result, expires_in: cache_expires_in.to_f * 60)
244
+ # set just_cached after caching
245
+ result.just_cached = true
246
+ rescue
247
+ # do nothing
259
248
  end
260
- Blazer.cache.write(run_cache_key(run_id), cache_data, expires_in: 30.seconds)
261
249
  end
262
250
 
263
- Blazer::Result.new(self, columns, rows, error, nil, cache && !cache_data.nil?)
251
+ result.cached_at = nil
252
+ result
264
253
  end
265
254
 
266
255
  # TODO check for adapter with same name, default to sql
267
256
  def detect_adapter
268
257
  scheme = settings["url"].to_s.split("://").first
269
258
  case scheme
270
- when "mongodb", "presto", "cassandra", "ignite"
259
+ when "presto", "cassandra", "ignite"
271
260
  scheme
272
261
  else
273
262
  "sql"
data/lib/blazer/engine.rb CHANGED
@@ -3,15 +3,17 @@ module Blazer
3
3
  isolate_namespace Blazer
4
4
 
5
5
  initializer "blazer" do |app|
6
- if defined?(Sprockets) && Sprockets::VERSION >= "4"
7
- app.config.assets.precompile << "blazer/application.js"
8
- app.config.assets.precompile << "blazer/application.css"
9
- app.config.assets.precompile << "blazer/glyphicons-halflings-regular.eot"
10
- app.config.assets.precompile << "blazer/glyphicons-halflings-regular.svg"
11
- app.config.assets.precompile << "blazer/glyphicons-halflings-regular.ttf"
12
- app.config.assets.precompile << "blazer/glyphicons-halflings-regular.woff"
13
- app.config.assets.precompile << "blazer/glyphicons-halflings-regular.woff2"
14
- app.config.assets.precompile << "blazer/favicon.png"
6
+ if defined?(Sprockets) && Sprockets::VERSION.to_i >= 4
7
+ app.config.assets.precompile += [
8
+ "blazer/application.js",
9
+ "blazer/application.css",
10
+ "blazer/glyphicons-halflings-regular.eot",
11
+ "blazer/glyphicons-halflings-regular.svg",
12
+ "blazer/glyphicons-halflings-regular.ttf",
13
+ "blazer/glyphicons-halflings-regular.woff",
14
+ "blazer/glyphicons-halflings-regular.woff2",
15
+ "blazer/favicon.png"
16
+ ]
15
17
  else
16
18
  # use a proc instead of a string
17
19
  app.config.assets.precompile << proc { |path| path =~ /\Ablazer\/application\.(js|css)\z/ }
@@ -0,0 +1,7 @@
1
+ Blazer.register_forecaster "prophet" do |series, count:|
2
+ Prophet.forecast(series, count: count)
3
+ end
4
+
5
+ Blazer.register_forecaster "trend" do |series, count:|
6
+ Trend.forecast(series, count: count)
7
+ end