blazer 3.4.0 → 3.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +23 -27
- data/app/assets/javascripts/blazer/Sortable.js +826 -1162
- data/app/assets/javascripts/blazer/ace/ace.js +15775 -13360
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1411 -668
- data/app/assets/javascripts/blazer/ace/mode-sql.js +12 -6
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +3 -3
- data/app/assets/javascripts/blazer/application.js +94 -52
- data/app/assets/javascripts/blazer/chart.umd.js +6 -6
- data/app/assets/javascripts/blazer/daterangepicker.js +193 -199
- data/app/assets/javascripts/blazer/highlight.min.js +235 -373
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +116 -82
- data/app/assets/javascripts/blazer/moment.js +368 -365
- data/app/assets/javascripts/blazer/queries.js +68 -58
- data/app/assets/javascripts/blazer/routes.js +11 -11
- data/app/assets/javascripts/blazer/tablesort.js +42 -0
- data/app/assets/javascripts/blazer/tom-select.base.js +4175 -0
- data/app/assets/stylesheets/blazer/application.css +114 -5
- data/app/assets/stylesheets/blazer/tom-select.css +502 -0
- data/app/controllers/blazer/base_controller.rb +1 -1
- data/app/controllers/blazer/checks_controller.rb +5 -2
- data/app/controllers/blazer/queries_controller.rb +3 -1
- data/app/helpers/blazer/base_helper.rb +1 -1
- data/app/models/blazer/check.rb +35 -57
- data/app/views/blazer/_nav.html.erb +1 -1
- data/app/views/blazer/_variables.html.erb +57 -42
- data/app/views/blazer/checks/_form.html.erb +20 -16
- data/app/views/blazer/checks/index.html.erb +17 -20
- data/app/views/blazer/dashboards/_form.html.erb +63 -48
- data/app/views/blazer/dashboards/show.html.erb +23 -16
- data/app/views/blazer/queries/_caching.html.erb +1 -1
- data/app/views/blazer/queries/_chart.html.erb +8 -0
- data/app/views/blazer/queries/_form.html.erb +153 -180
- data/app/views/blazer/queries/_map.html.erb +8 -0
- data/app/views/blazer/queries/docs.html.erb +6 -6
- data/app/views/blazer/queries/edit.html.erb +1 -0
- data/app/views/blazer/queries/home.html.erb +101 -102
- data/app/views/blazer/queries/new.html.erb +1 -0
- data/app/views/blazer/queries/run.html.erb +23 -34
- data/app/views/blazer/queries/schema.html.erb +20 -20
- data/app/views/blazer/queries/show.html.erb +19 -12
- data/app/views/blazer/uploads/index.html.erb +10 -10
- data/app/views/layouts/blazer/application.html.erb +3 -3
- data/lib/blazer/adapters/athena_adapter.rb +12 -6
- data/lib/blazer/adapters/clickhouse_adapter.rb +136 -0
- data/lib/blazer/adapters/druid_adapter.rb +0 -2
- data/lib/blazer/adapters/snowflake2_adapter.rb +198 -0
- data/lib/blazer/adapters/soda_adapter.rb +0 -2
- data/lib/blazer/adapters/sql_adapter.rb +4 -0
- data/lib/blazer/adapters.rb +2 -0
- data/lib/blazer/check_types.rb +20 -0
- data/lib/blazer/email_notifier.rb +49 -0
- data/lib/blazer/result.rb +15 -0
- data/lib/blazer/slack_notifier.rb +39 -7
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +32 -26
- data/lib/generators/blazer/templates/config.yml.tt +0 -2
- data/licenses/LICENSE-chart.js.txt +1 -1
- metadata +12 -31
- data/app/assets/javascripts/blazer/bootstrap.js +0 -2580
- data/app/assets/javascripts/blazer/jquery.js +0 -10872
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +0 -325
- data/app/assets/javascripts/blazer/rails-ujs.js +0 -746
- data/app/assets/javascripts/blazer/selectize.js +0 -3891
- data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +0 -13
- data/app/assets/javascripts/blazer/stupidtable.js +0 -281
- data/app/assets/javascripts/blazer/vue.global.prod.js +0 -1
- data/app/assets/stylesheets/blazer/selectize.css +0 -403
- data/licenses/LICENSE-jquery.txt +0 -20
- data/licenses/LICENSE-rails-ujs.txt +0 -20
- data/licenses/LICENSE-stickytableheaders.txt +0 -20
- data/licenses/LICENSE-stupidtable.txt +0 -19
- data/licenses/LICENSE-vue.txt +0 -21
- /data/licenses/{LICENSE-selectize.txt → LICENSE-tom-select.txt} +0 -0
data/app/models/blazer/check.rb
CHANGED
|
@@ -10,48 +10,16 @@ module Blazer
|
|
|
10
10
|
before_validation :set_state
|
|
11
11
|
before_validation :fix_emails
|
|
12
12
|
|
|
13
|
-
def split_emails
|
|
14
|
-
emails.to_s.downcase.split(",").map(&:strip)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def split_slack_channels
|
|
18
|
-
if Blazer.slack?
|
|
19
|
-
slack_channels.to_s.downcase.split(",").map(&:strip)
|
|
20
|
-
else
|
|
21
|
-
[]
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
13
|
def update_state(result)
|
|
26
|
-
check_type =
|
|
27
|
-
if respond_to?(:check_type)
|
|
28
|
-
self.check_type
|
|
29
|
-
elsif respond_to?(:invert)
|
|
30
|
-
invert ? "missing_data" : "bad_data"
|
|
31
|
-
else
|
|
32
|
-
"bad_data"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
message = result.error
|
|
14
|
+
check_type = computed_check_type
|
|
36
15
|
|
|
37
|
-
self.state =
|
|
16
|
+
self.state, message =
|
|
38
17
|
if result.timed_out?
|
|
39
|
-
"timed out"
|
|
18
|
+
["timed out", result.error]
|
|
40
19
|
elsif result.error
|
|
41
|
-
"error"
|
|
42
|
-
elsif check_type == "anomaly"
|
|
43
|
-
anomaly, message = result.detect_anomaly
|
|
44
|
-
if anomaly.nil?
|
|
45
|
-
"error"
|
|
46
|
-
elsif anomaly
|
|
47
|
-
"failing"
|
|
48
|
-
else
|
|
49
|
-
"passing"
|
|
50
|
-
end
|
|
51
|
-
elsif result.rows.any?
|
|
52
|
-
check_type == "missing_data" ? "passing" : "failing"
|
|
20
|
+
["error", result.error]
|
|
53
21
|
else
|
|
54
|
-
check_type
|
|
22
|
+
Blazer.check_types.fetch(check_type).fetch(:block).call(result)
|
|
55
23
|
end
|
|
56
24
|
|
|
57
25
|
self.last_run_at = Time.now if respond_to?(:last_run_at=)
|
|
@@ -68,37 +36,47 @@ module Blazer
|
|
|
68
36
|
|
|
69
37
|
# do not notify on creation, except when not passing
|
|
70
38
|
if (state_was != "new" || state != "passing") && state != state_was
|
|
71
|
-
Blazer
|
|
72
|
-
|
|
39
|
+
Blazer.notifiers.each do |notifier|
|
|
40
|
+
notifier.state_change(check: self, state:, state_was:, result:, message:, check_type:)
|
|
41
|
+
end
|
|
73
42
|
end
|
|
74
43
|
save! if changed?
|
|
75
44
|
end
|
|
76
45
|
|
|
46
|
+
def computed_check_type
|
|
47
|
+
if respond_to?(:check_type)
|
|
48
|
+
check_type
|
|
49
|
+
elsif respond_to?(:invert)
|
|
50
|
+
invert ? "missing_data" : "bad_data"
|
|
51
|
+
else
|
|
52
|
+
"bad_data"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
77
56
|
private
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
58
|
+
def set_state
|
|
59
|
+
self.state ||= "new"
|
|
60
|
+
end
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if emails.present?
|
|
88
|
-
self.emails = emails.strip.gsub(/[;\s]/, ",").gsub(/,+/, ", ")
|
|
89
|
-
end
|
|
62
|
+
# TODO rename
|
|
63
|
+
def fix_emails
|
|
64
|
+
Blazer.notifiers.each do |notifier|
|
|
65
|
+
notifier.before_validation(self) if notifier.respond_to?(:before_validation)
|
|
90
66
|
end
|
|
67
|
+
end
|
|
91
68
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
69
|
+
# TODO rename
|
|
70
|
+
def validate_emails
|
|
71
|
+
Blazer.notifiers.each do |notifier|
|
|
72
|
+
notifier.validate(self) if notifier.respond_to?(:validate)
|
|
96
73
|
end
|
|
74
|
+
end
|
|
97
75
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
76
|
+
def validate_variables
|
|
77
|
+
if query && query.variables.any?
|
|
78
|
+
errors.add(:base, "Query can't have variables")
|
|
102
79
|
end
|
|
80
|
+
end
|
|
103
81
|
end
|
|
104
82
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class="btn-group
|
|
1
|
+
<div class="btn-group nav-btn-group">
|
|
2
2
|
<%= link_to "Home", root_path, class: "btn btn-primary" %>
|
|
3
3
|
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
4
4
|
<span class="caret"></span>
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
<% var_params = request.query_parameters %>
|
|
3
3
|
<%= javascript_tag nonce: true do %>
|
|
4
4
|
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const now = moment.tz(timeZone)
|
|
6
|
+
const format = "YYYY-MM-DD"
|
|
7
7
|
|
|
8
8
|
function toDate(time) {
|
|
9
9
|
return moment.tz(time.format(format), timeZone)
|
|
10
10
|
}
|
|
11
11
|
<% end %>
|
|
12
|
-
|
|
12
|
+
<%= tag.form id: "bind", method: "get", action: action, class: "form-inline", style: "margin-bottom: 15px;" do %>
|
|
13
13
|
<% date_vars = ["start_time", "end_time"] %>
|
|
14
14
|
<% if (date_vars - @bind_vars).empty? %>
|
|
15
15
|
<% @bind_vars = @bind_vars - date_vars %>
|
|
@@ -22,39 +22,47 @@
|
|
|
22
22
|
<% if (data = @smart_vars[var]) %>
|
|
23
23
|
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: var_params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
|
|
24
24
|
<%= javascript_tag nonce: true do %>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
(function () {
|
|
26
|
+
<%= blazer_js_var "varId", var %>
|
|
27
|
+
new TomSelect(document.getElementById(varId), {
|
|
28
|
+
create: true,
|
|
29
|
+
onChange: function () {
|
|
30
|
+
submitIfCompleted(this.input.closest("form"))
|
|
31
|
+
this.blur()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
})()
|
|
28
35
|
<% end %>
|
|
29
36
|
<% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
|
|
30
37
|
<%= hidden_field_tag var, var_params[var] %>
|
|
31
38
|
|
|
32
|
-
<div class="
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
39
|
+
<div class="ts-wrapper single" style="width: 200px;">
|
|
40
|
+
<%= tag.div id: "#{var}-select", class: "ts-control" do %>
|
|
41
|
+
<div class="item">Select a date</div>
|
|
42
|
+
<% end %>
|
|
36
43
|
</div>
|
|
37
44
|
|
|
38
45
|
<%= javascript_tag nonce: true do %>
|
|
39
|
-
(function() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
(function () {
|
|
47
|
+
<%= blazer_js_var "varId", var %>
|
|
48
|
+
<%= blazer_js_var "selectId", "#{var}-select" %>
|
|
49
|
+
const input = document.getElementById(varId)
|
|
50
|
+
const datePicker = document.getElementById(selectId)
|
|
51
|
+
const picker = new DateRangePicker(datePicker, {
|
|
43
52
|
singleDatePicker: true,
|
|
44
53
|
locale: {format: format},
|
|
45
54
|
autoUpdateInput: false,
|
|
46
55
|
autoApply: true,
|
|
47
|
-
startDate: input.
|
|
56
|
+
startDate: input.value.length > 0 ? moment.tz(input.value, timeZone) : now
|
|
48
57
|
})
|
|
49
58
|
// hack to start with empty date
|
|
50
|
-
datePicker.
|
|
51
|
-
datePicker.
|
|
52
|
-
input.
|
|
53
|
-
submitIfCompleted(
|
|
59
|
+
datePicker.addEventListener("apply.daterangepicker", function () {
|
|
60
|
+
datePicker.querySelector(".item").textContent = toDate(picker.startDate).format("MMMM D, YYYY")
|
|
61
|
+
input.value = toDate(picker.startDate).utc().format()
|
|
62
|
+
submitIfCompleted(input.closest("form"))
|
|
54
63
|
})
|
|
55
|
-
if (input.
|
|
56
|
-
|
|
57
|
-
datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
|
|
64
|
+
if (input.value.length > 0) {
|
|
65
|
+
datePicker.querySelector(".item").textContent = toDate(picker.startDate).format("MMMM D, YYYY")
|
|
58
66
|
}
|
|
59
67
|
})()
|
|
60
68
|
<% end %>
|
|
@@ -69,23 +77,32 @@
|
|
|
69
77
|
<% end %>
|
|
70
78
|
|
|
71
79
|
<%= label_tag nil, date_vars.join(" & ") %>
|
|
72
|
-
<div class="
|
|
73
|
-
<div
|
|
74
|
-
<
|
|
80
|
+
<div class="ts-wrapper single" style="width: 300px;">
|
|
81
|
+
<div class="ts-control" id="reportrange">
|
|
82
|
+
<div class="item">Select a time range</div>
|
|
75
83
|
</div>
|
|
76
84
|
</div>
|
|
77
85
|
|
|
78
86
|
<%= javascript_tag nonce: true do %>
|
|
87
|
+
const startTime = document.getElementById("start_time")
|
|
88
|
+
const endTime = document.getElementById("end_time")
|
|
89
|
+
const reportrange = document.getElementById("reportrange")
|
|
90
|
+
|
|
79
91
|
function dateStr(daysAgo) {
|
|
80
92
|
return now.clone().subtract(daysAgo || 0, "days").format(format)
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
function setTimeInputs(start, end) {
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
startTime.value = toDate(start).utc().format()
|
|
97
|
+
endTime.value = toDate(end).endOf("day").utc().format()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function applyDateRangePicker(picker) {
|
|
101
|
+
setTimeInputs(picker.startDate, picker.endDate)
|
|
102
|
+
reportrange.querySelector(".item").textContent = toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY")
|
|
86
103
|
}
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
const picker = new DateRangePicker(reportrange,
|
|
89
106
|
{
|
|
90
107
|
ranges: {
|
|
91
108
|
"Today": [dateStr(), dateStr()],
|
|
@@ -100,28 +117,26 @@
|
|
|
100
117
|
opens: "right",
|
|
101
118
|
alwaysShowCalendars: true
|
|
102
119
|
},
|
|
103
|
-
function(start, end) {
|
|
120
|
+
function (start, end) {
|
|
104
121
|
setTimeInputs(start, end)
|
|
105
|
-
submitIfCompleted(
|
|
122
|
+
submitIfCompleted(startTime.closest("form"))
|
|
106
123
|
}
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
)
|
|
125
|
+
reportrange.addEventListener("apply.daterangepicker", function () {
|
|
126
|
+
applyDateRangePicker(picker)
|
|
110
127
|
})
|
|
111
128
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
picker.
|
|
115
|
-
picker
|
|
116
|
-
$("#reportrange").trigger("apply.daterangepicker", picker)
|
|
129
|
+
if (startTime.value.length > 0) {
|
|
130
|
+
picker.setStartDate(moment.tz(startTime.value, timeZone))
|
|
131
|
+
picker.setEndDate(moment.tz(endTime.value, timeZone))
|
|
132
|
+
applyDateRangePicker(picker)
|
|
117
133
|
} else {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
submitIfCompleted($("#start_time").closest("form"))
|
|
134
|
+
applyDateRangePicker(picker)
|
|
135
|
+
submitIfCompleted(startTime.closest("form"))
|
|
121
136
|
}
|
|
122
137
|
<% end %>
|
|
123
138
|
<% end %>
|
|
124
139
|
|
|
125
140
|
<input type="submit" class="btn btn-success" value="Run" style="vertical-align: top;" />
|
|
126
|
-
|
|
141
|
+
<% end %>
|
|
127
142
|
<% end %>
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
<%= javascript_tag nonce: true do %>
|
|
2
|
+
function createSelect(id, options) {
|
|
3
|
+
const element = document.getElementById(id)
|
|
4
|
+
const onChange = function () {
|
|
5
|
+
this.blur()
|
|
6
|
+
}
|
|
7
|
+
new TomSelect(element, {onChange, ...options})
|
|
8
|
+
show(element.closest(".hide"))
|
|
9
|
+
}
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
1
12
|
<%= form_for @check, html: {class: "small-form"} do |f| %>
|
|
2
13
|
<% unless @check.respond_to?(:check_type) || @check.respond_to?(:invert) %>
|
|
3
14
|
<p class="text-muted">Checks are designed to identify bad data. A check fails if there are any results.</p>
|
|
@@ -15,8 +26,7 @@
|
|
|
15
26
|
<%= javascript_tag nonce: true do %>
|
|
16
27
|
<%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
17
28
|
<%= blazer_js_var "items", [@check.query_id].compact %>
|
|
18
|
-
|
|
19
|
-
$("#check_query_id").selectize({options: queries, items: items, highlight: false, maxOptions: 100}).parents(".hide").removeClass("hide");
|
|
29
|
+
createSelect("check_query_id", {options: queries, items: items, highlight: false, maxOptions: 100})
|
|
20
30
|
<% end %>
|
|
21
31
|
</div>
|
|
22
32
|
|
|
@@ -24,12 +34,11 @@
|
|
|
24
34
|
<div class="form-group">
|
|
25
35
|
<%= f.label :check_type, "Alert if" %>
|
|
26
36
|
<div class="hide">
|
|
27
|
-
<% check_options =
|
|
28
|
-
<% check_options << ["Anomaly (most recent data point)", "anomaly"] if Blazer.anomaly_checks %>
|
|
37
|
+
<% check_options = Blazer.check_types.reject { |k, _| k == "anomaly" && !Blazer.anomaly_checks }.map { |k, v| [v[:label], k] } %>
|
|
29
38
|
<%= f.select :check_type, check_options %>
|
|
30
39
|
</div>
|
|
31
40
|
<%= javascript_tag nonce: true do %>
|
|
32
|
-
|
|
41
|
+
createSelect("check_check_type", {})
|
|
33
42
|
<% end %>
|
|
34
43
|
</div>
|
|
35
44
|
<% elsif @check.respond_to?(:invert) %>
|
|
@@ -39,7 +48,7 @@
|
|
|
39
48
|
<%= f.select :invert, [["Any results (bad data)", false], ["No results (missing data)", true]] %>
|
|
40
49
|
</div>
|
|
41
50
|
<%= javascript_tag nonce: true do %>
|
|
42
|
-
|
|
51
|
+
createSelect("check_invert", {})
|
|
43
52
|
<% end %>
|
|
44
53
|
</div>
|
|
45
54
|
<% end %>
|
|
@@ -51,24 +60,19 @@
|
|
|
51
60
|
<%= f.select :schedule, Blazer.check_schedules.map { |v| [v, v] } %>
|
|
52
61
|
</div>
|
|
53
62
|
<%= javascript_tag nonce: true do %>
|
|
54
|
-
|
|
63
|
+
createSelect("check_schedule", {})
|
|
55
64
|
<% end %>
|
|
56
65
|
</div>
|
|
57
66
|
<% end %>
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
<%= f.label :emails %>
|
|
61
|
-
<%= f.text_field :emails, placeholder: "Optional, comma separated", class: "form-control" %>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<% if Blazer.slack? %>
|
|
68
|
+
<% Blazer.notifiers.flat_map(&:fields).each do |field| %>
|
|
65
69
|
<div class="form-group">
|
|
66
|
-
<%= f.label
|
|
67
|
-
<%= f.text_field
|
|
70
|
+
<%= f.label field %>
|
|
71
|
+
<%= f.text_field field, placeholder: "Optional, comma separated", class: "form-control" %>
|
|
68
72
|
</div>
|
|
69
73
|
<% end %>
|
|
70
74
|
|
|
71
|
-
<p class="text-muted">
|
|
75
|
+
<p class="text-muted">Notifications are sent when a check starts failing, and when it starts passing again.
|
|
72
76
|
<p>
|
|
73
77
|
<% if @check.persisted? %>
|
|
74
78
|
<%= link_to "Delete", check_path(@check), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<% blazer_title "Checks" %>
|
|
2
2
|
|
|
3
3
|
<div id="header">
|
|
4
|
-
<div class="pull-right
|
|
4
|
+
<div class="pull-right header-div">
|
|
5
5
|
<div class="btn-group">
|
|
6
6
|
<%= link_to "New Check", new_check_path, class: "btn btn-info" %>
|
|
7
7
|
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<input id="search" type="text" placeholder="Start typing a query or state"
|
|
23
|
+
<input id="search" type="text" placeholder="Start typing a query or state" class="search form-control" autofocus />
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
<table id="checks" class="table">
|
|
27
27
|
<thead>
|
|
28
28
|
<tr>
|
|
29
29
|
<th>Query</th>
|
|
30
|
-
<th
|
|
31
|
-
<th
|
|
32
|
-
<th
|
|
33
|
-
<th
|
|
30
|
+
<th class="state-header">State</th>
|
|
31
|
+
<th class="run-header">Run</th>
|
|
32
|
+
<th class="notify-header">Notify</th>
|
|
33
|
+
<th class="actions-header"></th>
|
|
34
34
|
</tr>
|
|
35
35
|
</thead>
|
|
36
36
|
<tbody>
|
|
@@ -39,21 +39,18 @@
|
|
|
39
39
|
<td><%= link_to check.query.name, check.query %> <span class="text-muted"><%= check.try(:check_type).to_s.gsub("_", " ") %></span></td>
|
|
40
40
|
<td>
|
|
41
41
|
<% if check.state %>
|
|
42
|
-
|
|
42
|
+
<%= tag.small check.state.upcase, class: ["check-state", check.state.parameterize.gsub("-", "_")] %>
|
|
43
43
|
<% end %>
|
|
44
44
|
</td>
|
|
45
45
|
<td><%= check.schedule if check.respond_to?(:schedule) %></td>
|
|
46
46
|
<td>
|
|
47
|
-
<ul class="list-unstyled
|
|
48
|
-
<% check.
|
|
49
|
-
<li><%=
|
|
50
|
-
<% end %>
|
|
51
|
-
<% check.split_slack_channels.each do |channel| %>
|
|
52
|
-
<li><%= channel %></li>
|
|
47
|
+
<ul class="list-unstyled notifiers">
|
|
48
|
+
<% Blazer.notifiers.flat_map { |n| n.notify_list(check) }.each do |item| %>
|
|
49
|
+
<li><%= item %></li>
|
|
53
50
|
<% end %>
|
|
54
51
|
</ul>
|
|
55
52
|
</td>
|
|
56
|
-
<td
|
|
53
|
+
<td class="check-actions">
|
|
57
54
|
<%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
|
|
58
55
|
<%= button_to "Run Now", refresh_query_path(check.query), class: "btn btn-primary" %>
|
|
59
56
|
</td>
|
|
@@ -63,10 +60,10 @@
|
|
|
63
60
|
</table>
|
|
64
61
|
|
|
65
62
|
<%= javascript_tag nonce: true do %>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
})
|
|
63
|
+
document.getElementById("search").addEventListener("input", function () {
|
|
64
|
+
const value = this.value.toLowerCase()
|
|
65
|
+
for (const check of document.querySelectorAll("#checks tbody tr")) {
|
|
66
|
+
toggle(check, check.textContent.toLowerCase().includes(value))
|
|
67
|
+
}
|
|
68
|
+
})
|
|
72
69
|
<% end %>
|
|
@@ -7,21 +7,15 @@
|
|
|
7
7
|
<%= f.label :name %>
|
|
8
8
|
<%= f.text_field :name, class: "form-control" %>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="form-group"
|
|
10
|
+
<div class="form-group hide" id="queries-group">
|
|
11
11
|
<%= f.label :charts %>
|
|
12
|
-
<ul id="queries" class="list-group">
|
|
13
|
-
<li class="list-group-item" v-for="(query, index) in queries" :key="query.id" v-cloak>
|
|
14
|
-
<span class="glyphicon glyphicon-remove" aria-hidden="true" v-on:click="remove(index)"></span>
|
|
15
|
-
{{ query.name }}
|
|
16
|
-
<input type="hidden" name="query_ids[]" :value="query.id">
|
|
17
|
-
</li>
|
|
18
|
-
</ul>
|
|
12
|
+
<ul id="queries" class="list-group"></ul>
|
|
19
13
|
</div>
|
|
20
|
-
<div class="form-group"
|
|
14
|
+
<div class="form-group hide">
|
|
21
15
|
<%= f.label :query_id, "Add Chart" %>
|
|
22
16
|
<%= select_tag :query_id, nil, {include_blank: true, placeholder: "Select chart"} %>
|
|
23
17
|
</div>
|
|
24
|
-
<p
|
|
18
|
+
<p class="hide" id="dashboard-actions">
|
|
25
19
|
<% if @dashboard.persisted? %>
|
|
26
20
|
<%= link_to "Delete", dashboard_path(@dashboard), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
|
|
27
21
|
<% end %>
|
|
@@ -32,51 +26,72 @@
|
|
|
32
26
|
|
|
33
27
|
<%= javascript_tag nonce: true do %>
|
|
34
28
|
<%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
35
|
-
<%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
|
|
29
|
+
<%= blazer_js_var "dashboardQueries", (@queries || @dashboard.dashboard_queries.order(:position).map(&:query)).as_json(only: [:id, :name]) %>
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
const queriesList = document.getElementById("queries")
|
|
32
|
+
const queriesGroup = document.getElementById("queries-group")
|
|
33
|
+
const querySelect = document.getElementById("query_id")
|
|
34
|
+
const actions = document.getElementById("dashboard-actions")
|
|
35
|
+
|
|
36
|
+
function addQuery(query) {
|
|
37
|
+
const li = document.createElement("li")
|
|
38
|
+
li.classList.add("list-group-item")
|
|
39
|
+
li.setAttribute("data-query-id", query.id)
|
|
40
|
+
|
|
41
|
+
const span = document.createElement("span")
|
|
42
|
+
span.classList.add("glyphicon", "glyphicon-remove")
|
|
43
|
+
span.setAttribute("aria-hidden", "true")
|
|
44
|
+
|
|
45
|
+
const input = document.createElement("input")
|
|
46
|
+
input.setAttribute("type", "hidden")
|
|
47
|
+
input.setAttribute("name", "query_ids[]")
|
|
48
|
+
input.setAttribute("value", query.id)
|
|
49
|
+
|
|
50
|
+
li.append(span, query.name, input)
|
|
51
|
+
queriesList.append(li)
|
|
52
|
+
show(queriesGroup)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const query of dashboardQueries) {
|
|
56
|
+
addQuery(query)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
queriesList.addEventListener("click", function (e) {
|
|
60
|
+
const target = e.target.closest(".glyphicon-remove")
|
|
61
|
+
if (target) {
|
|
62
|
+
target.closest("li").remove()
|
|
63
|
+
|
|
64
|
+
if (queriesList.querySelectorAll("li").length == 0) {
|
|
65
|
+
hide(queriesGroup)
|
|
46
66
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
var app = this
|
|
50
|
-
$("#query_id").selectize({
|
|
51
|
-
options: queries,
|
|
52
|
-
highlight: false,
|
|
53
|
-
maxOptions: 100,
|
|
54
|
-
onChange: function(val) {
|
|
55
|
-
if (val) {
|
|
56
|
-
var item = this.getItem(val)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
new TomSelect(querySelect, {
|
|
71
|
+
options: queries,
|
|
72
|
+
highlight: false,
|
|
73
|
+
maxOptions: 100,
|
|
74
|
+
onChange: function (id) {
|
|
75
|
+
if (id) {
|
|
76
|
+
const item = this.getItem(id)
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
// if duplicate query is added, remove the first one
|
|
79
|
+
for (const query of queriesList.querySelectorAll("li")) {
|
|
80
|
+
if (query.getAttribute("data-query-id") == id) {
|
|
81
|
+
query.remove()
|
|
82
|
+
break
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
app.config.compilerOptions.whitespace = "preserve"
|
|
74
|
-
app.mount("#app")
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
addQuery({id: id, name: item.textContent})
|
|
87
|
+
this.setValue("")
|
|
88
|
+
this.blur()
|
|
89
|
+
}
|
|
80
90
|
}
|
|
81
91
|
})
|
|
92
|
+
|
|
93
|
+
show(querySelect.closest(".hide"))
|
|
94
|
+
show(actions)
|
|
95
|
+
|
|
96
|
+
Sortable.create(queriesList)
|
|
82
97
|
<% end %>
|