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.

@@ -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
- * v1.5.1
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 adapters = [];
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
- if (matches = input.match(ISO8601_PATTERN)) {
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 (config.smarterDates && (matches = n.match(DATE_PATTERN))) {
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
- var HighchartsAdapter = new function () {
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
- var chartType = chartType || "column";
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
- var GoogleChartsAdapter = new function () {
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
- var ChartjsAdapter = new function () {
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 (var j = 0; j < series.length; j++) {
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 (var j = 0; j < series.length; j++) {
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 (var i = 0; i < series.length; i++) {
949
- var s = series[i];
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 && day) {
1004
- options.scales.xAxes[0].time.tooltipFormat = "ll";
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 = jsOptions(chart.data, chart.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.push(ChartjsAdapter);
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
- if (adapters.length == 0) {
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 d.getMilliseconds() + d.getSeconds() + d.getMinutes() + d.getHours() === 0;
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 (config.smarterDiscrete && (opts.discrete === null || opts.discrete === undefined)) {
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
- window.Chartkick = Chartkick;
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
- @checks = Blazer::Check.joins(:query).includes(:query).order("state, blazer_queries.name, blazer_checks.id").to_a
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.values.reverse })).uniq!
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.values.reverse }
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
- @filename = @query.name.parameterize if @query
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
- @min_width_types = (@rows.first || {}).select { |k, v| v.is_a?(Time) || v.is_a?(String) || @data_source.smart_columns[k] }.keys
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.keys
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
- if rows.any?
230
- csv << rows.first.keys
231
- end
220
+ csv << columns
232
221
  rows.each do |row|
233
- csv << row.map { |k, v| v.is_a?(Time) ? blazer_time_value(k, v) : v }
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
- # yuck, instance var
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[k] } || {})[k]
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)
@@ -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 = self.respond_to?(:invert) && self.invert
12
+ invert = respond_to?(:invert) && self.invert
13
13
  self.state =
14
14
  if error
15
- "error"
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 self.respond_to?(:last_run_at=)
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>