sql-jarvis 1.8.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 +7 -0
- data/.gitignore +14 -0
- data/CHANGELOG.md +228 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +775 -0
- data/Rakefile +1 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/javascripts/blazer/Chart.js +14145 -0
- data/app/assets/javascripts/blazer/Sortable.js +1144 -0
- data/app/assets/javascripts/blazer/ace.js +6 -0
- data/app/assets/javascripts/blazer/ace/ace.js +11 -0
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
- data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
- data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
- data/app/assets/javascripts/blazer/application.js +79 -0
- data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
- data/app/assets/javascripts/blazer/chartkick.js +1693 -0
- data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
- data/app/assets/javascripts/blazer/jquery.js +10308 -0
- data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
- data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
- data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
- data/app/assets/javascripts/blazer/moment.js +3043 -0
- data/app/assets/javascripts/blazer/queries.js +110 -0
- data/app/assets/javascripts/blazer/routes.js +23 -0
- data/app/assets/javascripts/blazer/selectize.js +3667 -0
- data/app/assets/javascripts/blazer/stupidtable.js +114 -0
- data/app/assets/javascripts/blazer/vue.js +7515 -0
- data/app/assets/stylesheets/blazer/application.css +198 -0
- data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
- data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
- data/app/assets/stylesheets/blazer/github.css +125 -0
- data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
- data/app/controllers/blazer/base_controller.rb +103 -0
- data/app/controllers/blazer/checks_controller.rb +56 -0
- data/app/controllers/blazer/dashboards_controller.rb +105 -0
- data/app/controllers/blazer/queries_controller.rb +325 -0
- data/app/helpers/blazer/base_helper.rb +57 -0
- data/app/mailers/blazer/check_mailer.rb +27 -0
- data/app/models/blazer/audit.rb +6 -0
- data/app/models/blazer/check.rb +95 -0
- data/app/models/blazer/connection.rb +5 -0
- data/app/models/blazer/dashboard.rb +13 -0
- data/app/models/blazer/dashboard_query.rb +9 -0
- data/app/models/blazer/query.rb +31 -0
- data/app/models/blazer/record.rb +5 -0
- data/app/views/blazer/_nav.html.erb +16 -0
- data/app/views/blazer/_variables.html.erb +102 -0
- data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
- data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
- data/app/views/blazer/checks/_form.html.erb +71 -0
- data/app/views/blazer/checks/edit.html.erb +1 -0
- data/app/views/blazer/checks/index.html.erb +40 -0
- data/app/views/blazer/checks/new.html.erb +1 -0
- data/app/views/blazer/dashboards/_form.html.erb +76 -0
- data/app/views/blazer/dashboards/edit.html.erb +1 -0
- data/app/views/blazer/dashboards/new.html.erb +1 -0
- data/app/views/blazer/dashboards/show.html.erb +47 -0
- data/app/views/blazer/queries/_form.html.erb +240 -0
- data/app/views/blazer/queries/edit.html.erb +2 -0
- data/app/views/blazer/queries/home.html.erb +152 -0
- data/app/views/blazer/queries/new.html.erb +2 -0
- data/app/views/blazer/queries/run.html.erb +163 -0
- data/app/views/blazer/queries/schema.html.erb +18 -0
- data/app/views/blazer/queries/show.html.erb +73 -0
- data/app/views/layouts/blazer/application.html.erb +24 -0
- data/blazer.gemspec +26 -0
- data/config/routes.rb +16 -0
- data/lib/blazer.rb +185 -0
- data/lib/blazer/adapters/athena_adapter.rb +128 -0
- data/lib/blazer/adapters/base_adapter.rb +53 -0
- data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
- data/lib/blazer/adapters/drill_adapter.rb +28 -0
- data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
- data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
- data/lib/blazer/adapters/presto_adapter.rb +45 -0
- data/lib/blazer/adapters/sql_adapter.rb +182 -0
- data/lib/blazer/data_source.rb +193 -0
- data/lib/blazer/detect_anomalies.R +19 -0
- data/lib/blazer/engine.rb +47 -0
- data/lib/blazer/result.rb +170 -0
- data/lib/blazer/run_statement.rb +40 -0
- data/lib/blazer/run_statement_job.rb +21 -0
- data/lib/blazer/version.rb +3 -0
- data/lib/generators/blazer/install_generator.rb +39 -0
- data/lib/generators/blazer/templates/config.yml +62 -0
- data/lib/generators/blazer/templates/install.rb +45 -0
- data/lib/tasks/blazer.rake +10 -0
- metadata +211 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
module BaseHelper
|
|
3
|
+
def blazer_title(title = nil)
|
|
4
|
+
if title
|
|
5
|
+
content_for(:title) { title }
|
|
6
|
+
else
|
|
7
|
+
content_for?(:title) ? content_for(:title) : nil
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
BLAZER_URL_REGEX = /\Ahttps?:\/\/[\S]+\z/
|
|
12
|
+
BLAZER_IMAGE_EXT = %w[png jpg jpeg gif]
|
|
13
|
+
|
|
14
|
+
def blazer_format_value(key, value)
|
|
15
|
+
if value.is_a?(Integer) && !key.to_s.end_with?("id") && !key.to_s.start_with?("id")
|
|
16
|
+
number_with_delimiter(value)
|
|
17
|
+
elsif value =~ BLAZER_URL_REGEX
|
|
18
|
+
# see if image or link
|
|
19
|
+
if Blazer.images && (key.include?("image") || BLAZER_IMAGE_EXT.include?(value.split(".").last.split("?").first.try(:downcase)))
|
|
20
|
+
link_to value, target: "_blank" do
|
|
21
|
+
image_tag value, referrerpolicy: "no-referrer"
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
link_to value, value, target: "_blank"
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def blazer_maps?
|
|
32
|
+
Blazer.mapbox_access_token.present?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def blazer_js_var(name, value)
|
|
36
|
+
"var #{name} = #{blazer_json_escape(value.to_json(root: false))};".html_safe
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
|
|
40
|
+
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
|
41
|
+
|
|
42
|
+
# Prior to version 4.1 of rails double quotes were inadventently removed in json_escape.
|
|
43
|
+
# This adds the correct json_escape functionality to rails versions < 4.1
|
|
44
|
+
def blazer_json_escape(s)
|
|
45
|
+
if Rails::VERSION::STRING < "4.1"
|
|
46
|
+
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
|
|
47
|
+
s.html_safe? ? result.html_safe : result
|
|
48
|
+
else
|
|
49
|
+
json_escape(s)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def blazer_series_name(k)
|
|
54
|
+
k.nil? ? "null" : k.to_s
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class CheckMailer < ActionMailer::Base
|
|
3
|
+
include ActionView::Helpers::TextHelper
|
|
4
|
+
|
|
5
|
+
default from: Blazer.from_email if Blazer.from_email
|
|
6
|
+
layout false
|
|
7
|
+
|
|
8
|
+
def state_change(check, state, state_was, rows_count, error, columns, rows, column_types, check_type)
|
|
9
|
+
@check = check
|
|
10
|
+
@state = state
|
|
11
|
+
@state_was = state_was
|
|
12
|
+
@rows_count = rows_count
|
|
13
|
+
@error = error
|
|
14
|
+
@columns = columns
|
|
15
|
+
@rows = rows
|
|
16
|
+
@column_types = column_types
|
|
17
|
+
@check_type = check_type
|
|
18
|
+
mail to: check.emails, reply_to: check.emails, subject: "Check #{state.titleize}: #{check.query.name}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failing_checks(email, checks)
|
|
22
|
+
@checks = checks
|
|
23
|
+
# add reply_to for mailing lists
|
|
24
|
+
mail to: email, reply_to: email, subject: "#{pluralize(checks.size, "Check")} Failing"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Check < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
belongs_to :query
|
|
5
|
+
|
|
6
|
+
validates :query_id, presence: true
|
|
7
|
+
validate :validate_emails
|
|
8
|
+
validate :validate_variables, if: -> { query_id_changed? }
|
|
9
|
+
|
|
10
|
+
before_validation :set_state
|
|
11
|
+
before_validation :fix_emails
|
|
12
|
+
|
|
13
|
+
def split_emails
|
|
14
|
+
emails.to_s.downcase.split(",").map(&:strip)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update_state(result)
|
|
18
|
+
check_type =
|
|
19
|
+
if respond_to?(:check_type)
|
|
20
|
+
self.check_type
|
|
21
|
+
elsif respond_to?(:invert)
|
|
22
|
+
invert ? "missing_data" : "bad_data"
|
|
23
|
+
else
|
|
24
|
+
"bad_data"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
message = result.error
|
|
28
|
+
|
|
29
|
+
self.state =
|
|
30
|
+
if result.timed_out?
|
|
31
|
+
"timed out"
|
|
32
|
+
elsif result.error
|
|
33
|
+
"error"
|
|
34
|
+
elsif check_type == "anomaly"
|
|
35
|
+
anomaly, message = result.detect_anomaly
|
|
36
|
+
if anomaly.nil?
|
|
37
|
+
"error"
|
|
38
|
+
elsif anomaly
|
|
39
|
+
"failing"
|
|
40
|
+
else
|
|
41
|
+
"passing"
|
|
42
|
+
end
|
|
43
|
+
elsif result.rows.any?
|
|
44
|
+
check_type == "missing_data" ? "passing" : "failing"
|
|
45
|
+
else
|
|
46
|
+
check_type == "missing_data" ? "failing" : "passing"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
self.last_run_at = Time.now if respond_to?(:last_run_at=)
|
|
50
|
+
self.message = message if respond_to?(:message=)
|
|
51
|
+
|
|
52
|
+
if respond_to?(:timeouts=)
|
|
53
|
+
if result.timed_out?
|
|
54
|
+
self.timeouts += 1
|
|
55
|
+
self.state = "disabled" if timeouts >= 3
|
|
56
|
+
else
|
|
57
|
+
self.timeouts = 0
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# do not notify on creation, except when not passing
|
|
62
|
+
if (state_was != "new" || state != "passing") && state != state_was && emails.present?
|
|
63
|
+
Blazer::CheckMailer.state_change(self, state, state_was, result.rows.size, message, result.columns, result.rows.first(10).as_json, result.column_types, check_type).deliver_now
|
|
64
|
+
end
|
|
65
|
+
save! if changed?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def set_state
|
|
71
|
+
self.state ||= "new"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fix_emails
|
|
75
|
+
# some people like doing ; instead of ,
|
|
76
|
+
# but we know what they mean, so let's fix it
|
|
77
|
+
# also, some people like to use whitespace
|
|
78
|
+
if emails.present?
|
|
79
|
+
self.emails = emails.strip.gsub(/[;\s]/, ",").gsub(/,+/, ", ")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_emails
|
|
84
|
+
unless split_emails.all? { |e| e =~ /\A\S+@\S+\.\S+\z/ }
|
|
85
|
+
errors.add(:base, "Invalid emails")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_variables
|
|
90
|
+
if query.variables.any?
|
|
91
|
+
errors.add(:base, "Query can't have variables")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Dashboard < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
has_many :dashboard_queries, dependent: :destroy
|
|
5
|
+
has_many :queries, through: :dashboard_queries
|
|
6
|
+
|
|
7
|
+
validates :name, presence: true
|
|
8
|
+
|
|
9
|
+
def to_param
|
|
10
|
+
[id, name.gsub("'", "").parameterize].join("-")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Query < Record
|
|
3
|
+
belongs_to :creator, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s) if Blazer.user_class
|
|
4
|
+
has_many :checks, dependent: :destroy
|
|
5
|
+
has_many :dashboard_queries, dependent: :destroy
|
|
6
|
+
has_many :dashboards, through: :dashboard_queries
|
|
7
|
+
has_many :audits
|
|
8
|
+
|
|
9
|
+
validates :statement, presence: true
|
|
10
|
+
|
|
11
|
+
scope :named, -> { where("blazer_queries.name <> ''") }
|
|
12
|
+
|
|
13
|
+
def to_param
|
|
14
|
+
[id, name].compact.join("-").gsub("'", "").parameterize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def friendly_name
|
|
18
|
+
name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def editable?(user)
|
|
22
|
+
editable = !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
|
|
23
|
+
editable &&= Blazer.query_editable.call(self, user) if Blazer.query_editable
|
|
24
|
+
editable
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def variables
|
|
28
|
+
Blazer.extract_vars(statement)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="btn-group" style="vertical-align: top; margin-right: 5px;">
|
|
2
|
+
<%= link_to "Home", root_path, class: "btn btn-primary" %>
|
|
3
|
+
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
4
|
+
<span class="caret"></span>
|
|
5
|
+
<span class="sr-only">Toggle Dropdown</span>
|
|
6
|
+
</button>
|
|
7
|
+
<ul class="dropdown-menu">
|
|
8
|
+
<li><%= link_to "Dashboards", dashboards_path %></li>
|
|
9
|
+
<li><%= link_to "Checks", checks_path %></li>
|
|
10
|
+
<li role="separator" class="divider"></li>
|
|
11
|
+
<li><%= link_to "New Query", new_query_path %></li>
|
|
12
|
+
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
13
|
+
<% check_params = @query ? {query_id: @query.id} : {} %>
|
|
14
|
+
<li><%= link_to "New Check", new_check_path(check_params) %></li>
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<% if @bind_vars.any? %>
|
|
2
|
+
<form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 10px;">
|
|
3
|
+
<% date_vars = ["start_time", "end_time"] %>
|
|
4
|
+
<% if (date_vars - @bind_vars).empty? %>
|
|
5
|
+
<% @bind_vars = @bind_vars - date_vars %>
|
|
6
|
+
<% else %>
|
|
7
|
+
<% date_vars = nil %>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<% @bind_vars.each_with_index do |var, i| %>
|
|
11
|
+
<%= label_tag var, var %>
|
|
12
|
+
<% if (data = @smart_vars[var]) %>
|
|
13
|
+
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
|
|
14
|
+
<script>
|
|
15
|
+
$("#<%= var %>").selectize({
|
|
16
|
+
create: true
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
<% else %>
|
|
20
|
+
<%= text_field_tag var, params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !params[var], class: "form-control" %>
|
|
21
|
+
<% if var.end_with?("_at") %>
|
|
22
|
+
<script>
|
|
23
|
+
$("#<%= var %>").daterangepicker({singleDatePicker: true, locale: {format: "YYYY-MM-DD"}, autoUpdateInput: false});
|
|
24
|
+
// hack to start with empty date
|
|
25
|
+
$("#<%= var %>").on("apply.daterangepicker", function(ev, picker) {
|
|
26
|
+
$(this).val(picker.startDate.format("YYYY-MM-DD"));
|
|
27
|
+
$(this).change();
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<% if date_vars %>
|
|
35
|
+
<% date_vars.each do |var| %>
|
|
36
|
+
<%= hidden_field_tag var, params[var] %>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<%= label_tag nil, date_vars.join(" & ") %>
|
|
40
|
+
<div class="selectize-control single" style="width: 300px;">
|
|
41
|
+
<div id="reportrange" class="selectize-input" style="display: inline-block;">
|
|
42
|
+
<span>Select a time range</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
|
48
|
+
var format = "YYYY-MM-DD"
|
|
49
|
+
var now = moment.tz(timeZone)
|
|
50
|
+
|
|
51
|
+
function dateStr(daysAgo) {
|
|
52
|
+
return now.clone().subtract(daysAgo || 0, "days").format(format)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toDate(time) {
|
|
56
|
+
return moment.tz(time.format(format), timeZone)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setTimeInputs(start, end) {
|
|
60
|
+
$("#start_time").val(toDate(start).utc().format())
|
|
61
|
+
$("#end_time").val(toDate(end).endOf("day").utc().format())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$("#reportrange").daterangepicker(
|
|
65
|
+
{
|
|
66
|
+
ranges: {
|
|
67
|
+
"Today": [dateStr(), dateStr()],
|
|
68
|
+
"Last 7 Days": [dateStr(6), dateStr()],
|
|
69
|
+
"Last 30 Days": [dateStr(29), dateStr()]
|
|
70
|
+
},
|
|
71
|
+
locale: {
|
|
72
|
+
format: format
|
|
73
|
+
},
|
|
74
|
+
startDate: dateStr(29),
|
|
75
|
+
endDate: dateStr(),
|
|
76
|
+
opens: "right"
|
|
77
|
+
},
|
|
78
|
+
function(start, end) {
|
|
79
|
+
setTimeInputs(start, end)
|
|
80
|
+
submitIfCompleted($("#start_time").closest("form"))
|
|
81
|
+
}
|
|
82
|
+
).on("apply.daterangepicker", function(ev, picker) {
|
|
83
|
+
setTimeInputs(picker.startDate, picker.endDate)
|
|
84
|
+
$("#reportrange span").html(toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY"))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if ($("#start_time").val().length > 0) {
|
|
88
|
+
var picker = $("#reportrange").data("daterangepicker")
|
|
89
|
+
picker.setStartDate(moment.tz($("#start_time").val(), timeZone))
|
|
90
|
+
picker.setEndDate(moment.tz($("#end_time").val(), timeZone))
|
|
91
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
|
92
|
+
} else {
|
|
93
|
+
var picker = $("#reportrange").data("daterangepicker")
|
|
94
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
|
95
|
+
submitIfCompleted($("#start_time").closest("form"))
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
<% end %>
|
|
99
|
+
|
|
100
|
+
<input type="submit" class="btn btn-success" value="Run" style="vertical-align: top;" />
|
|
101
|
+
</form>
|
|
102
|
+
<% end %>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
</head>
|
|
4
|
+
<body style="font-family: 'Helvetica Neue', Arial, Helvetica; font-size: 14px; color: #333;">
|
|
5
|
+
<p><%= link_to "View", query_url(@check.query_id) %></p>
|
|
6
|
+
<% if @error %>
|
|
7
|
+
<p><%= @error %></p>
|
|
8
|
+
<% elsif @rows_count > 0 && @check_type == "bad_data" %>
|
|
9
|
+
<p>
|
|
10
|
+
<% if @rows_count <= 10 %>
|
|
11
|
+
<%= pluralize(@rows_count, "row") %>
|
|
12
|
+
<% else %>
|
|
13
|
+
Showing 10 of <%= @rows_count %> rows
|
|
14
|
+
<% end %>
|
|
15
|
+
</p>
|
|
16
|
+
<table style="width: 100%; border-spacing: 0; border-collapse: collapse;">
|
|
17
|
+
<thead>
|
|
18
|
+
<tr>
|
|
19
|
+
<% @columns.first(5).each do |column| %>
|
|
20
|
+
<th style="padding: 8px; line-height: 1.4; text-align: left; vertical-align: bottom; border-bottom: 2px solid #ddd; width: <%= (100 / @columns.size).round(2) %>%;">
|
|
21
|
+
<%= column %>
|
|
22
|
+
</th>
|
|
23
|
+
<% end %>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
<% @rows.first(10).each do |row| %>
|
|
28
|
+
<tr>
|
|
29
|
+
<% @columns.first(5).each_with_index do |column, i| %>
|
|
30
|
+
<td style="padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;">
|
|
31
|
+
<% value = row[i] %>
|
|
32
|
+
<% if @column_types[i] == "time" && value.to_s.length > 10 %>
|
|
33
|
+
<% value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value %>
|
|
34
|
+
<% end %>
|
|
35
|
+
<%= value %>
|
|
36
|
+
</td>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tr>
|
|
39
|
+
<% end %>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
<% if @columns.size > 5 %>
|
|
43
|
+
<p style="color: #999;">Only first 5 columns shown</p>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% end %>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<% unless @check.respond_to?(:invert) %>
|
|
2
|
+
<p class="text-muted">Checks are designed to identify bad data. A check fails if there are any results.</p>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<% if @check.errors.any? %>
|
|
6
|
+
<div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
<%= form_for @check do |f| %>
|
|
10
|
+
<div class="form-group">
|
|
11
|
+
<%= f.label :query_id, "Query" %>
|
|
12
|
+
<div class="hide">
|
|
13
|
+
<%= f.select :query_id, [], {include_blank: true} %>
|
|
14
|
+
</div>
|
|
15
|
+
<script>
|
|
16
|
+
<%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
17
|
+
<%= 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");
|
|
20
|
+
</script>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<% if @check.respond_to?(:check_type) %>
|
|
24
|
+
<div class="form-group">
|
|
25
|
+
<%= f.label :check_type, "Alert if" %>
|
|
26
|
+
<div class="hide">
|
|
27
|
+
<% check_options = [["Any results (bad data)", "bad_data"], ["No results (missing data)", "missing_data"]] %>
|
|
28
|
+
<% check_options << ["Anomaly (most recent data point)", "anomaly"] if Blazer.anomaly_checks %>
|
|
29
|
+
<%= f.select :check_type, check_options %>
|
|
30
|
+
</div>
|
|
31
|
+
<script>
|
|
32
|
+
$("#check_check_type").selectize({}).parent().removeClass("hide");
|
|
33
|
+
</script>
|
|
34
|
+
</div>
|
|
35
|
+
<% elsif @check.respond_to?(:invert) %>
|
|
36
|
+
<div class="form-group">
|
|
37
|
+
<%= f.label :invert, "Fails if" %>
|
|
38
|
+
<div class="hide">
|
|
39
|
+
<%= f.select :invert, [["Any results (bad data)", false], ["No results (missing data)", true]] %>
|
|
40
|
+
</div>
|
|
41
|
+
<script>
|
|
42
|
+
$("#check_invert").selectize({}).parent().removeClass("hide");
|
|
43
|
+
</script>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
|
|
47
|
+
<% if @check.respond_to?(:schedule) && Blazer.check_schedules %>
|
|
48
|
+
<div class="form-group">
|
|
49
|
+
<%= f.label :schedule, "Run every" %>
|
|
50
|
+
<div class="hide">
|
|
51
|
+
<%= f.select :schedule, Blazer.check_schedules.map { |v| [v, v] } %>
|
|
52
|
+
</div>
|
|
53
|
+
<script>
|
|
54
|
+
$("#check_schedule").selectize({}).parent().removeClass("hide");
|
|
55
|
+
</script>
|
|
56
|
+
</div>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<div class="form-group">
|
|
60
|
+
<%= f.label :emails %>
|
|
61
|
+
<%= f.text_field :emails, placeholder: "Optional, comma separated", class: "form-control" %>
|
|
62
|
+
</div>
|
|
63
|
+
<p class="text-muted">Emails are sent when a check starts failing, and when it starts passing again.
|
|
64
|
+
<p>
|
|
65
|
+
<% if @check.persisted? %>
|
|
66
|
+
<%= link_to "Delete", check_path(@check), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
|
|
67
|
+
<% end %>
|
|
68
|
+
<%= f.submit "Save", class: "btn btn-success" %>
|
|
69
|
+
<%= link_to "Back", :back, class: "btn btn-link" %>
|
|
70
|
+
</p>
|
|
71
|
+
<% end %>
|