blazer 2.6.5 → 3.0.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 (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