blazer 1.3.5 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of blazer might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +22 -10
- data/app/assets/javascripts/blazer/Chart.js +8820 -8686
- data/app/assets/javascripts/blazer/application.js +12 -0
- data/app/assets/javascripts/blazer/chartkick.js +114 -33
- data/app/controllers/blazer/base_controller.rb +0 -6
- data/app/controllers/blazer/checks_controller.rb +3 -2
- data/app/controllers/blazer/dashboards_controller.rb +3 -3
- data/app/controllers/blazer/queries_controller.rb +63 -75
- data/app/helpers/blazer/base_helper.rb +2 -2
- data/app/models/blazer/check.rb +17 -4
- data/app/views/blazer/checks/index.html.erb +2 -2
- data/app/views/blazer/checks/run.html.erb +3 -1
- data/app/views/blazer/dashboards/show.html.erb +6 -10
- data/app/views/blazer/queries/_form.html.erb +4 -1
- data/app/views/blazer/queries/run.html.erb +25 -17
- data/app/views/blazer/queries/show.html.erb +14 -10
- data/app/views/layouts/blazer/application.html.erb +2 -2
- data/blazer.gemspec +1 -0
- data/lib/blazer/data_source.rb +139 -37
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +43 -30
- data/lib/generators/blazer/templates/config.yml +6 -3
- metadata +16 -2
@@ -25,6 +25,18 @@ $( function () {
|
|
25
25
|
});
|
26
26
|
});
|
27
27
|
|
28
|
+
function runQuery(data, success, error) {
|
29
|
+
return $.ajax({
|
30
|
+
url: window.runQueriesPath,
|
31
|
+
method: "POST",
|
32
|
+
data: data,
|
33
|
+
dataType: "html"
|
34
|
+
}).done(success).fail( function(jqXHR, textStatus, errorThrown) {
|
35
|
+
var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
|
36
|
+
error(message);
|
37
|
+
});
|
38
|
+
}
|
39
|
+
|
28
40
|
// Prevent backspace from navigating backwards.
|
29
41
|
// Adapted from Biff MaGriff: http://stackoverflow.com/a/7895814/1196499
|
30
42
|
function preventBackspaceNav() {
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* Chartkick.js
|
3
3
|
* Create beautiful JavaScript charts with minimal code
|
4
4
|
* https://github.com/ankane/chartkick.js
|
5
|
-
*
|
5
|
+
* v2.0.0
|
6
6
|
* MIT License
|
7
7
|
*/
|
8
8
|
|
@@ -14,7 +14,7 @@
|
|
14
14
|
var config = window.Chartkick || {};
|
15
15
|
var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, adapters = [];
|
16
16
|
var DATE_PATTERN = /^(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)$/i;
|
17
|
-
var
|
17
|
+
var GoogleChartsAdapter, HighchartsAdapter, ChartjsAdapter;
|
18
18
|
|
19
19
|
// helpers
|
20
20
|
|
@@ -68,7 +68,8 @@
|
|
68
68
|
if (type !== '[object String]') {
|
69
69
|
return;
|
70
70
|
}
|
71
|
-
|
71
|
+
matches = input.match(ISO8601_PATTERN);
|
72
|
+
if (matches) {
|
72
73
|
year = parseInt(matches[1], 10);
|
73
74
|
month = parseInt(matches[3], 10) - 1;
|
74
75
|
day = parseInt(matches[5], 10);
|
@@ -211,7 +212,7 @@
|
|
211
212
|
if (typeof n !== "object") {
|
212
213
|
if (typeof n === "number") {
|
213
214
|
n = new Date(n * 1000); // ms
|
214
|
-
} else if (
|
215
|
+
} else if ((matches = n.match(DATE_PATTERN))) {
|
215
216
|
year = parseInt(matches[1], 10);
|
216
217
|
month = parseInt(matches[3], 10) - 1;
|
217
218
|
day = parseInt(matches[5], 10);
|
@@ -249,7 +250,7 @@
|
|
249
250
|
|
250
251
|
function loadAdapters() {
|
251
252
|
if (!HighchartsAdapter && "Highcharts" in window) {
|
252
|
-
|
253
|
+
HighchartsAdapter = new function () {
|
253
254
|
var Highcharts = window.Highcharts;
|
254
255
|
|
255
256
|
this.name = "highcharts";
|
@@ -385,7 +386,7 @@
|
|
385
386
|
};
|
386
387
|
|
387
388
|
this.renderColumnChart = function (chart, chartType) {
|
388
|
-
|
389
|
+
chartType = chartType || "column";
|
389
390
|
var series = chart.data;
|
390
391
|
var options = jsOptions(series, chart.options), i, j, s, d, rows = [];
|
391
392
|
options.chart.type = chartType;
|
@@ -441,7 +442,7 @@
|
|
441
442
|
adapters.push(HighchartsAdapter);
|
442
443
|
}
|
443
444
|
if (!GoogleChartsAdapter && window.google && window.google.setOnLoadCallback) {
|
444
|
-
|
445
|
+
GoogleChartsAdapter = new function () {
|
445
446
|
var google = window.google;
|
446
447
|
|
447
448
|
this.name = "google";
|
@@ -453,7 +454,7 @@
|
|
453
454
|
var cb, call;
|
454
455
|
for (var i = 0; i < callbacks.length; i++) {
|
455
456
|
cb = callbacks[i];
|
456
|
-
call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline))
|
457
|
+
call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline));
|
457
458
|
if (call) {
|
458
459
|
cb.callback();
|
459
460
|
callbacks.splice(i, 1);
|
@@ -557,7 +558,7 @@
|
|
557
558
|
var setXtitle = function (options, title) {
|
558
559
|
options.hAxis.title = title;
|
559
560
|
options.hAxis.titleTextStyle.italic = false;
|
560
|
-
}
|
561
|
+
};
|
561
562
|
|
562
563
|
var setYtitle = function (options, title) {
|
563
564
|
options.vAxis.title = title;
|
@@ -769,7 +770,7 @@
|
|
769
770
|
adapters.push(GoogleChartsAdapter);
|
770
771
|
}
|
771
772
|
if (!ChartjsAdapter && "Chart" in window) {
|
772
|
-
|
773
|
+
ChartjsAdapter = new function () {
|
773
774
|
var Chart = window.Chart;
|
774
775
|
|
775
776
|
this.name = "chartjs";
|
@@ -832,6 +833,16 @@
|
|
832
833
|
options.scales.yAxes[0].ticks.max = max;
|
833
834
|
};
|
834
835
|
|
836
|
+
var setBarMin = function (options, min) {
|
837
|
+
if (min !== null) {
|
838
|
+
options.scales.xAxes[0].ticks.min = min;
|
839
|
+
}
|
840
|
+
};
|
841
|
+
|
842
|
+
var setBarMax = function (options, max) {
|
843
|
+
options.scales.xAxes[0].ticks.max = max;
|
844
|
+
};
|
845
|
+
|
835
846
|
var setStacked = function (options, stacked) {
|
836
847
|
options.scales.xAxes[0].stacked = !!stacked;
|
837
848
|
options.scales.yAxes[0].stacked = !!stacked;
|
@@ -866,6 +877,9 @@
|
|
866
877
|
|
867
878
|
var setLabelSize = function (chart, data, options) {
|
868
879
|
var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
|
880
|
+
if (maxLabelSize > 25) {
|
881
|
+
maxLabelSize = 25;
|
882
|
+
}
|
869
883
|
options.scales.xAxes[0].ticks.callback = function (value) {
|
870
884
|
value = toStr(value);
|
871
885
|
if (value.length > maxLabelSize) {
|
@@ -889,6 +903,8 @@
|
|
889
903
|
var dayOfWeek;
|
890
904
|
var month = true;
|
891
905
|
var year = true;
|
906
|
+
var hour = true;
|
907
|
+
var minute = true;
|
892
908
|
var detectType = (chartType === "line" || chartType === "area") && !chart.options.discrete;
|
893
909
|
|
894
910
|
var series = chart.data;
|
@@ -917,11 +933,10 @@
|
|
917
933
|
}
|
918
934
|
|
919
935
|
var rows2 = [];
|
920
|
-
for (
|
936
|
+
for (j = 0; j < series.length; j++) {
|
921
937
|
rows2.push([]);
|
922
938
|
}
|
923
939
|
|
924
|
-
var day = true;
|
925
940
|
var value;
|
926
941
|
var k;
|
927
942
|
for (k = 0; k < sortedLabels.length; k++) {
|
@@ -936,17 +951,19 @@
|
|
936
951
|
week = week && isWeek(value, dayOfWeek);
|
937
952
|
month = month && isMonth(value);
|
938
953
|
year = year && isYear(value);
|
954
|
+
hour = hour && isHour(value);
|
955
|
+
minute = minute && isMinute(value);
|
939
956
|
} else {
|
940
957
|
value = i;
|
941
958
|
}
|
942
959
|
labels.push(value);
|
943
|
-
for (
|
944
|
-
rows2[j].push(rows[i][j])
|
960
|
+
for (j = 0; j < series.length; j++) {
|
961
|
+
rows2[j].push(rows[i][j]);
|
945
962
|
}
|
946
963
|
}
|
947
964
|
|
948
|
-
for (
|
949
|
-
|
965
|
+
for (i = 0; i < series.length; i++) {
|
966
|
+
s = series[i];
|
950
967
|
|
951
968
|
var backgroundColor = chartType !== "line" ? addOpacity(colors[i], 0.5) : colors[i];
|
952
969
|
|
@@ -989,19 +1006,33 @@
|
|
989
1006
|
} else if (day || timeDiff > 10) {
|
990
1007
|
options.scales.xAxes[0].time.unit = "day";
|
991
1008
|
step = 1;
|
1009
|
+
} else if (hour) {
|
1010
|
+
options.scales.xAxes[0].time.unit = "hour";
|
1011
|
+
step = 1 / 24.0;
|
1012
|
+
} else if (minute) {
|
1013
|
+
options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
|
1014
|
+
options.scales.xAxes[0].time.unit = "minute";
|
1015
|
+
step = 1 / 24.0 / 60.0;
|
992
1016
|
}
|
993
1017
|
|
1018
|
+
|
994
1019
|
if (step && timeDiff > 0) {
|
995
1020
|
var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
|
996
|
-
if (week) {
|
1021
|
+
if (week && step === 1) {
|
997
1022
|
unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
|
998
1023
|
}
|
999
1024
|
options.scales.xAxes[0].time.unitStepSize = unitStepSize;
|
1000
1025
|
}
|
1001
1026
|
}
|
1002
1027
|
|
1003
|
-
if (!options.scales.xAxes[0].time.tooltipFormat
|
1004
|
-
|
1028
|
+
if (!options.scales.xAxes[0].time.tooltipFormat) {
|
1029
|
+
if (day) {
|
1030
|
+
options.scales.xAxes[0].time.tooltipFormat = "ll";
|
1031
|
+
} else if (hour) {
|
1032
|
+
options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
|
1033
|
+
} else if (minute) {
|
1034
|
+
options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
|
1035
|
+
}
|
1005
1036
|
}
|
1006
1037
|
}
|
1007
1038
|
|
@@ -1019,6 +1050,11 @@
|
|
1019
1050
|
// TODO fix area stacked
|
1020
1051
|
// areaOptions.stacked = true;
|
1021
1052
|
}
|
1053
|
+
// fix for https://github.com/chartjs/Chart.js/issues/2441
|
1054
|
+
if (!chart.options.max && allZeros(chart.data)) {
|
1055
|
+
chart.options.max = 1;
|
1056
|
+
}
|
1057
|
+
|
1022
1058
|
var options = jsOptions(chart.data, merge(areaOptions, chart.options));
|
1023
1059
|
|
1024
1060
|
var data = createDataTable(chart, options, chartType || "line");
|
@@ -1026,7 +1062,7 @@
|
|
1026
1062
|
options.scales.xAxes[0].type = chart.options.discrete ? "category" : "time";
|
1027
1063
|
|
1028
1064
|
drawChart(chart, "line", data, options);
|
1029
|
-
}
|
1065
|
+
};
|
1030
1066
|
|
1031
1067
|
this.renderPieChart = function (chart) {
|
1032
1068
|
var options = merge(baseOptions, chart.options.library || {});
|
@@ -1050,14 +1086,19 @@
|
|
1050
1086
|
};
|
1051
1087
|
|
1052
1088
|
drawChart(chart, "pie", data, options);
|
1053
|
-
}
|
1089
|
+
};
|
1054
1090
|
|
1055
1091
|
this.renderColumnChart = function (chart, chartType) {
|
1056
|
-
var options
|
1092
|
+
var options;
|
1093
|
+
if (chartType === "bar") {
|
1094
|
+
options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setBarMin, setBarMax, setStacked)(chart.data, chart.options);
|
1095
|
+
} else {
|
1096
|
+
options = jsOptions(chart.data, chart.options);
|
1097
|
+
}
|
1057
1098
|
var data = createDataTable(chart, options, "column");
|
1058
1099
|
setLabelSize(chart, data, options);
|
1059
1100
|
drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
|
1060
|
-
}
|
1101
|
+
};
|
1061
1102
|
|
1062
1103
|
var self = this;
|
1063
1104
|
|
@@ -1066,11 +1107,11 @@
|
|
1066
1107
|
};
|
1067
1108
|
|
1068
1109
|
this.renderBarChart = function (chart) {
|
1069
|
-
self.renderColumnChart(chart, "bar")
|
1070
|
-
}
|
1071
|
-
}
|
1110
|
+
self.renderColumnChart(chart, "bar");
|
1111
|
+
};
|
1112
|
+
};
|
1072
1113
|
|
1073
|
-
adapters.
|
1114
|
+
adapters.unshift(ChartjsAdapter);
|
1074
1115
|
}
|
1075
1116
|
}
|
1076
1117
|
|
@@ -1081,9 +1122,7 @@
|
|
1081
1122
|
fnName = "render" + chartType;
|
1082
1123
|
adapterName = chart.options.adapter;
|
1083
1124
|
|
1084
|
-
|
1085
|
-
loadAdapters();
|
1086
|
-
}
|
1125
|
+
loadAdapters();
|
1087
1126
|
|
1088
1127
|
for (i = 0; i < adapters.length; i++) {
|
1089
1128
|
adapter = adapters[i];
|
@@ -1119,8 +1158,16 @@
|
|
1119
1158
|
return r;
|
1120
1159
|
};
|
1121
1160
|
|
1161
|
+
function isMinute(d) {
|
1162
|
+
return d.getMilliseconds() === 0 && d.getSeconds() === 0;
|
1163
|
+
}
|
1164
|
+
|
1165
|
+
function isHour(d) {
|
1166
|
+
return isMinute(d) && d.getMinutes() === 0;
|
1167
|
+
}
|
1168
|
+
|
1122
1169
|
function isDay(d) {
|
1123
|
-
return
|
1170
|
+
return isHour(d) && d.getHours() === 0;
|
1124
1171
|
}
|
1125
1172
|
|
1126
1173
|
function isWeek(d, dayOfWeek) {
|
@@ -1139,6 +1186,19 @@
|
|
1139
1186
|
return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
|
1140
1187
|
}
|
1141
1188
|
|
1189
|
+
function allZeros(data) {
|
1190
|
+
var i, j, d;
|
1191
|
+
for (i = 0; i < data.length; i++) {
|
1192
|
+
d = data[i].data;
|
1193
|
+
for (j = 0; j < d.length; j++) {
|
1194
|
+
if (d[j][1] != 0) {
|
1195
|
+
return false;
|
1196
|
+
}
|
1197
|
+
}
|
1198
|
+
}
|
1199
|
+
return true;
|
1200
|
+
}
|
1201
|
+
|
1142
1202
|
function detectDiscrete(series) {
|
1143
1203
|
var i, j, data;
|
1144
1204
|
for (i = 0; i < series.length; i++) {
|
@@ -1162,7 +1222,7 @@
|
|
1162
1222
|
} else {
|
1163
1223
|
opts.hideLegend = false;
|
1164
1224
|
}
|
1165
|
-
if (
|
1225
|
+
if ((opts.discrete === null || opts.discrete === undefined)) {
|
1166
1226
|
opts.discrete = detectDiscrete(series);
|
1167
1227
|
}
|
1168
1228
|
if (opts.discrete) {
|
@@ -1236,12 +1296,29 @@
|
|
1236
1296
|
}
|
1237
1297
|
|
1238
1298
|
function setElement(chart, element, dataSource, opts, callback) {
|
1299
|
+
var elementId;
|
1239
1300
|
if (typeof element === "string") {
|
1301
|
+
elementId = element;
|
1240
1302
|
element = document.getElementById(element);
|
1303
|
+
if (!element) {
|
1304
|
+
throw new Error("No element with id " + elementId);
|
1305
|
+
}
|
1241
1306
|
}
|
1242
1307
|
chart.element = element;
|
1243
1308
|
chart.options = opts || {};
|
1244
1309
|
chart.dataSource = dataSource;
|
1310
|
+
chart.getElement = function () {
|
1311
|
+
return element;
|
1312
|
+
};
|
1313
|
+
chart.getData = function () {
|
1314
|
+
return chart.data;
|
1315
|
+
};
|
1316
|
+
chart.getOptions = function () {
|
1317
|
+
return opts || {};
|
1318
|
+
};
|
1319
|
+
chart.getChartObject = function () {
|
1320
|
+
return chart.chart;
|
1321
|
+
};
|
1245
1322
|
Chartkick.charts[element.id] = chart;
|
1246
1323
|
fetchDataSource(chart, callback);
|
1247
1324
|
}
|
@@ -1276,5 +1353,9 @@
|
|
1276
1353
|
charts: {}
|
1277
1354
|
};
|
1278
1355
|
|
1279
|
-
|
1356
|
+
if (typeof module === "object" && typeof module.exports === "object") {
|
1357
|
+
module.exports = Chartkick;
|
1358
|
+
} else {
|
1359
|
+
window.Chartkick = Chartkick;
|
1360
|
+
}
|
1280
1361
|
}(window));
|
@@ -15,14 +15,8 @@ module Blazer
|
|
15
15
|
|
16
16
|
layout "blazer/application"
|
17
17
|
|
18
|
-
before_action :ensure_database_url
|
19
|
-
|
20
18
|
private
|
21
19
|
|
22
|
-
def ensure_database_url
|
23
|
-
render text: "BLAZER_DATABASE_URL required" if !ENV["BLAZER_DATABASE_URL"] && !Rails.env.development?
|
24
|
-
end
|
25
|
-
|
26
20
|
def process_vars(statement, data_source)
|
27
21
|
(@bind_vars ||= []).concat(extract_vars(statement)).uniq!
|
28
22
|
@bind_vars.each do |var|
|
@@ -3,7 +3,8 @@ module Blazer
|
|
3
3
|
before_action :set_check, only: [:edit, :update, :destroy, :run]
|
4
4
|
|
5
5
|
def index
|
6
|
-
|
6
|
+
state_order = [nil, "disabled", "error", "timed out", "failing", "passing"]
|
7
|
+
@checks = Blazer::Check.joins(:query).includes(:query).order("blazer_queries.name, blazer_checks.id").to_a.sort_by { |q| state_order.index(q.state) || 99 }
|
7
8
|
@checks.select! { |c| "#{c.query.name} #{c.emails}".downcase.include?(params[:q]) } if params[:q]
|
8
9
|
end
|
9
10
|
|
@@ -15,7 +16,7 @@ module Blazer
|
|
15
16
|
@check = Blazer::Check.new(check_params)
|
16
17
|
# use creator_id instead of creator
|
17
18
|
# since we setup association without checking if column exists
|
18
|
-
@check.creator = blazer_user if @check.respond_to?(:creator_id=)
|
19
|
+
@check.creator = blazer_user if @check.respond_to?(:creator_id=) && blazer_user
|
19
20
|
|
20
21
|
if @check.save
|
21
22
|
redirect_to run_check_path(@check)
|
@@ -14,7 +14,7 @@ module Blazer
|
|
14
14
|
@dashboard = Blazer::Dashboard.new
|
15
15
|
# use creator_id instead of creator
|
16
16
|
# since we setup association without checking if column exists
|
17
|
-
@dashboard.creator = blazer_user if @dashboard.respond_to?(:creator_id=)
|
17
|
+
@dashboard.creator = blazer_user if @dashboard.respond_to?(:creator_id=) && blazer_user
|
18
18
|
|
19
19
|
if update_dashboard(@dashboard)
|
20
20
|
redirect_to dashboard_path(@dashboard)
|
@@ -37,8 +37,8 @@ module Blazer
|
|
37
37
|
@data_sources.each do |data_source|
|
38
38
|
query = data_source.smart_variables[var]
|
39
39
|
if query
|
40
|
-
rows, error, cached_at = data_source.run_statement(query)
|
41
|
-
((@smart_vars[var] ||= []).concat(rows.map { |v| v.
|
40
|
+
columns, rows, error, cached_at = data_source.run_statement(query)
|
41
|
+
((@smart_vars[var] ||= []).concat(rows.map { |v| v.reverse })).uniq!
|
42
42
|
@sql_errors << error if error
|
43
43
|
end
|
44
44
|
end
|
@@ -51,8 +51,8 @@ module Blazer
|
|
51
51
|
@bind_vars.each do |var|
|
52
52
|
query = data_source.smart_variables[var]
|
53
53
|
if query
|
54
|
-
rows, error, cached_at = data_source.run_statement(query)
|
55
|
-
@smart_vars[var] = rows.map { |v| v.
|
54
|
+
columns, rows, error, cached_at = data_source.run_statement(query)
|
55
|
+
@smart_vars[var] = rows.map { |v| v.reverse }
|
56
56
|
@sql_errors << error if error
|
57
57
|
end
|
58
58
|
end
|
@@ -74,71 +74,10 @@ module Blazer
|
|
74
74
|
|
75
75
|
data_source = @query.data_source if @query && @query.data_source
|
76
76
|
@data_source = Blazer.data_sources[data_source]
|
77
|
-
Blazer.transform_statement.call(@data_source, @statement) if Blazer.transform_statement
|
78
|
-
|
79
|
-
# audit
|
80
|
-
if Blazer.audit
|
81
|
-
audit = Blazer::Audit.new(statement: @statement)
|
82
|
-
audit.query = @query
|
83
|
-
audit.data_source = data_source
|
84
|
-
audit.user = blazer_user
|
85
|
-
audit.save!
|
86
|
-
end
|
87
|
-
|
88
|
-
@rows, @error, @cached_at = @data_source.run_statement(@statement, user: blazer_user, query: @query, refresh_cache: params[:check])
|
89
|
-
|
90
|
-
if @query && @error != Blazer::TIMEOUT_MESSAGE
|
91
|
-
@query.checks.each do |check|
|
92
|
-
check.update_state(@rows, @error)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
@columns = {}
|
97
|
-
if @rows.any?
|
98
|
-
@rows.first.each do |key, value|
|
99
|
-
@columns[key] =
|
100
|
-
case value
|
101
|
-
when Integer
|
102
|
-
"int"
|
103
|
-
when Float
|
104
|
-
"float"
|
105
|
-
else
|
106
|
-
"string-ins"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
77
|
|
111
|
-
@
|
78
|
+
@columns, @rows, @error, @cached_at, @just_cached = @data_source.run_main_statement(@statement, user: blazer_user, query: @query, refresh_cache: params[:check])
|
112
79
|
|
113
|
-
|
114
|
-
|
115
|
-
@boom = {}
|
116
|
-
@columns.keys.each do |key|
|
117
|
-
query = @data_source.smart_columns[key]
|
118
|
-
if query
|
119
|
-
values = @rows.map { |r| r[key] }.compact.uniq
|
120
|
-
rows, error, cached_at = @data_source.run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
|
121
|
-
@boom[key] = Hash[rows.map(&:values).map { |k, v| [k.to_s, v] }]
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
@linked_columns = @data_source.linked_columns
|
126
|
-
|
127
|
-
@markers = []
|
128
|
-
[["latitude", "longitude"], ["lat", "lon"]].each do |keys|
|
129
|
-
if (keys - (@rows.first || {}).keys).empty?
|
130
|
-
@markers =
|
131
|
-
@rows.select do |r|
|
132
|
-
r[keys.first] && r[keys.last]
|
133
|
-
end.map do |r|
|
134
|
-
{
|
135
|
-
title: r.except(*keys).map{ |k, v| "<strong>#{k}:</strong> #{v}" }.join("<br />").truncate(140),
|
136
|
-
latitude: r[keys.first],
|
137
|
-
longitude: r[keys.last]
|
138
|
-
}
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
80
|
+
render_run
|
142
81
|
end
|
143
82
|
|
144
83
|
respond_to do |format|
|
@@ -146,7 +85,7 @@ module Blazer
|
|
146
85
|
render layout: false
|
147
86
|
end
|
148
87
|
format.csv do
|
149
|
-
send_data csv_data(@rows), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
|
88
|
+
send_data csv_data(@columns, @rows, @data_source), type: "text/csv; charset=utf-8; header=present", disposition: "attachment; filename=\"#{@query.try(:name).try(:parameterize).presence || 'query'}.csv\""
|
150
89
|
end
|
151
90
|
end
|
152
91
|
end
|
@@ -181,12 +120,64 @@ module Blazer
|
|
181
120
|
end
|
182
121
|
|
183
122
|
def tables
|
184
|
-
@tables = Blazer.data_sources[params[:data_source]].tables
|
123
|
+
@tables = Blazer.data_sources[params[:data_source]].tables
|
185
124
|
render partial: "tables", layout: false
|
186
125
|
end
|
187
126
|
|
188
127
|
private
|
189
128
|
|
129
|
+
def render_run
|
130
|
+
@first_row = @rows.first || []
|
131
|
+
@column_types = []
|
132
|
+
if @rows.any?
|
133
|
+
@columns.each_with_index do |column, i|
|
134
|
+
@column_types << (
|
135
|
+
case @first_row[i]
|
136
|
+
when Integer
|
137
|
+
"int"
|
138
|
+
when Float
|
139
|
+
"float"
|
140
|
+
else
|
141
|
+
"string-ins"
|
142
|
+
end
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
@filename = @query.name.parameterize if @query
|
148
|
+
@min_width_types = @columns.each_with_index.select { |c, i| @first_row[i].is_a?(Time) || @first_row[i].is_a?(String) || @data_source.smart_columns[c] }
|
149
|
+
|
150
|
+
@boom = {}
|
151
|
+
@columns.each_with_index do |key, i|
|
152
|
+
query = @data_source.smart_columns[key]
|
153
|
+
if query
|
154
|
+
values = @rows.map { |r| r[i] }.compact.uniq
|
155
|
+
columns, rows, error, cached_at = @data_source.run_statement(ActiveRecord::Base.send(:sanitize_sql_array, [query.sub("{value}", "(?)"), values]))
|
156
|
+
@boom[key] = Hash[rows.map { |k, v| [k.to_s, v] }]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
@linked_columns = @data_source.linked_columns
|
161
|
+
|
162
|
+
@markers = []
|
163
|
+
[["latitude", "longitude"], ["lat", "lon"]].each do |keys|
|
164
|
+
lat_index = @columns.index(keys.first)
|
165
|
+
lon_index = @columns.index(keys.last)
|
166
|
+
if lat_index && lon_index
|
167
|
+
@markers =
|
168
|
+
@rows.select do |r|
|
169
|
+
r[lat_index] && r[lon_index]
|
170
|
+
end.map do |r|
|
171
|
+
{
|
172
|
+
title: r.each_with_index.map{ |v, i| i == lat_index || i == lon_index ? nil : "<strong>#{@columns[i]}:</strong> #{v}" }.compact.join("<br />").truncate(140),
|
173
|
+
latitude: r[lat_index],
|
174
|
+
longitude: r[lon_index]
|
175
|
+
}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
190
181
|
def set_queries(limit = nil)
|
191
182
|
@my_queries =
|
192
183
|
if limit && blazer_user
|
@@ -224,20 +215,17 @@ module Blazer
|
|
224
215
|
params.require(:query).permit(:name, :description, :statement, :data_source)
|
225
216
|
end
|
226
217
|
|
227
|
-
def csv_data(rows)
|
218
|
+
def csv_data(columns, rows, data_source)
|
228
219
|
CSV.generate do |csv|
|
229
|
-
|
230
|
-
csv << rows.first.keys
|
231
|
-
end
|
220
|
+
csv << columns
|
232
221
|
rows.each do |row|
|
233
|
-
csv << row.map { |
|
222
|
+
csv << row.each_with_index.map { |v, i| v.is_a?(Time) ? blazer_time_value(data_source, columns[i], v) : v }
|
234
223
|
end
|
235
224
|
end
|
236
225
|
end
|
237
226
|
|
238
|
-
def blazer_time_value(k, v)
|
239
|
-
|
240
|
-
@data_source.local_time_suffix.any? { |s| k.ends_with?(s) } ? v.to_s.sub(" UTC", "") : v.in_time_zone(Blazer.time_zone)
|
227
|
+
def blazer_time_value(data_source, k, v)
|
228
|
+
data_source.local_time_suffix.any? { |s| k.ends_with?(s) } ? v.to_s.sub(" UTC", "") : v.in_time_zone(Blazer.time_zone)
|
241
229
|
end
|
242
230
|
helper_method :blazer_time_value
|
243
231
|
end
|
@@ -17,8 +17,8 @@ module Blazer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def blazer_column_types(columns, rows, boom)
|
20
|
-
columns.map do |k,
|
21
|
-
v = (rows.find { |r| r[
|
20
|
+
columns.each_with_index.map do |k, i|
|
21
|
+
v = (rows.find { |r| r[i] } || {})[i]
|
22
22
|
if boom[k]
|
23
23
|
"string"
|
24
24
|
elsif v.is_a?(Numeric)
|
data/app/models/blazer/check.rb
CHANGED
@@ -5,21 +5,34 @@ module Blazer
|
|
5
5
|
validates :query_id, presence: true
|
6
6
|
|
7
7
|
def split_emails
|
8
|
-
emails.to_s.split(",").map(&:strip)
|
8
|
+
emails.to_s.downcase.split(",").map(&:strip)
|
9
9
|
end
|
10
10
|
|
11
11
|
def update_state(rows, error)
|
12
|
-
invert =
|
12
|
+
invert = respond_to?(:invert) && self.invert
|
13
13
|
self.state =
|
14
14
|
if error
|
15
|
-
|
15
|
+
if error == Blazer::TIMEOUT_MESSAGE
|
16
|
+
"timed out"
|
17
|
+
else
|
18
|
+
"error"
|
19
|
+
end
|
16
20
|
elsif rows.any?
|
17
21
|
invert ? "passing" : "failing"
|
18
22
|
else
|
19
23
|
invert ? "failing" : "passing"
|
20
24
|
end
|
21
25
|
|
22
|
-
self.last_run_at = Time.now if
|
26
|
+
self.last_run_at = Time.now if respond_to?(:last_run_at=)
|
27
|
+
|
28
|
+
if respond_to?(:timeouts=)
|
29
|
+
if state == "timed out"
|
30
|
+
self.timeouts += 1
|
31
|
+
self.state = "disabled" if timeouts >= 3
|
32
|
+
else
|
33
|
+
self.timeouts = 0
|
34
|
+
end
|
35
|
+
end
|
23
36
|
|
24
37
|
# do not notify on creation, except when not passing
|
25
38
|
if (state_was || state != "passing") && state != state_was && emails.present?
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<p style="float: right;"><%= link_to "New Check", new_check_path, class: "btn btn-info" %></p>
|
4
4
|
<%= render partial: "blazer/nav" %>
|
5
5
|
|
6
|
-
<% colors = {failing: "red", passing: "#5cb85c", error: "#666"} %>
|
6
|
+
<% colors = {failing: "red", passing: "#5cb85c", error: "#666", timed_out: "orange", disabled: "#000"} %>
|
7
7
|
<table class="table">
|
8
8
|
<thead>
|
9
9
|
<tr>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<td><%= link_to check.query.name, check.query %></td>
|
21
21
|
<td>
|
22
22
|
<% if check.state %>
|
23
|
-
<small style="font-weight: bold; color: <%= colors[check.state.to_sym] %>;"><%= check.state.upcase %></small>
|
23
|
+
<small style="font-weight: bold; color: <%= colors[check.state.parameterize("_").to_sym] %>;"><%= check.state.upcase %></small>
|
24
24
|
<% end %>
|
25
25
|
</td>
|
26
26
|
<td><%= check.schedule if check.respond_to?(:schedule) %></td>
|