blazer 2.6.5 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +1 -1
- data/README.md +13 -28
- data/app/assets/javascripts/blazer/ace/ace.js +7235 -8906
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +762 -774
- data/app/assets/javascripts/blazer/ace/mode-sql.js +177 -72
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +5 -29
- data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -6
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +8 -106
- data/app/assets/javascripts/blazer/application.js +9 -6
- data/app/assets/javascripts/blazer/chart.umd.js +13 -0
- data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
- data/app/assets/javascripts/blazer/chartkick.js +1020 -914
- data/app/assets/javascripts/blazer/highlight.min.js +466 -3
- data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +39 -38
- data/app/assets/javascripts/blazer/moment.js +105 -88
- data/app/assets/javascripts/blazer/queries.js +10 -1
- data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
- data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
- data/app/assets/stylesheets/blazer/bootstrap.css +1 -1
- data/app/assets/stylesheets/blazer/selectize.css +1 -1
- data/app/controllers/blazer/base_controller.rb +85 -84
- data/app/controllers/blazer/checks_controller.rb +6 -6
- data/app/controllers/blazer/dashboards_controller.rb +24 -24
- data/app/controllers/blazer/queries_controller.rb +208 -186
- data/app/controllers/blazer/uploads_controller.rb +49 -49
- data/app/helpers/blazer/base_helper.rb +0 -4
- data/app/models/blazer/query.rb +1 -12
- data/app/views/blazer/checks/index.html.erb +1 -1
- data/app/views/blazer/dashboards/_form.html.erb +11 -5
- data/app/views/blazer/queries/_form.html.erb +19 -14
- data/app/views/blazer/queries/docs.html.erb +11 -1
- data/app/views/blazer/queries/home.html.erb +9 -6
- data/app/views/blazer/queries/run.html.erb +17 -32
- data/app/views/blazer/queries/show.html.erb +12 -20
- data/app/views/layouts/blazer/application.html.erb +1 -5
- data/lib/blazer/adapters/sql_adapter.rb +1 -1
- data/lib/blazer/adapters.rb +17 -0
- data/lib/blazer/anomaly_detectors.rb +22 -0
- data/lib/blazer/data_source.rb +29 -40
- data/lib/blazer/engine.rb +11 -9
- data/lib/blazer/forecasters.rb +7 -0
- data/lib/blazer/result.rb +13 -71
- data/lib/blazer/result_cache.rb +71 -0
- data/lib/blazer/run_statement.rb +1 -1
- data/lib/blazer/run_statement_job.rb +2 -2
- data/lib/blazer/statement.rb +3 -1
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +51 -53
- data/licenses/LICENSE-chart.js.txt +1 -1
- data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +1 -1
- data/licenses/LICENSE-date-fns.txt +21 -0
- data/licenses/LICENSE-kurkle-color.txt +9 -0
- data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
- data/licenses/{LICENSE-jquery-ujs.txt → LICENSE-rails-ujs.txt} +1 -1
- data/licenses/LICENSE-vue.txt +1 -1
- metadata +26 -18
- data/app/assets/javascripts/blazer/Chart.js +0 -16172
- data/app/assets/javascripts/blazer/jquery-ujs.js +0 -555
- data/app/assets/javascripts/blazer/vue.js +0 -12014
- data/lib/blazer/adapters/mongodb_adapter.rb +0 -43
- data/lib/blazer/detect_anomalies.R +0 -19
data/app/models/blazer/query.rb
CHANGED
@@ -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
|
-
|
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
|
-
<%=
|
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 =
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
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.
|
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
|
71
|
-
<%= blazer_js_var "previewStatement",
|
72
|
-
|
73
|
-
var app =
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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 !
|
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 =
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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]; (@
|
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| [(@
|
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| [(@
|
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]; (@
|
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 = (@
|
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)
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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/
|
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>
|
@@ -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
|
data/lib/blazer/data_source.rb
CHANGED
@@ -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
|
78
|
-
|
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
|
-
|
80
|
+
result_cache.read_run(run_id)
|
86
81
|
end
|
87
82
|
|
88
83
|
def delete_results(run_id)
|
89
|
-
|
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 =
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
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 "
|
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 >=
|
7
|
-
app.config.assets.precompile
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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/ }
|