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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. 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,6 @@
1
+ module Blazer
2
+ class Audit < Record
3
+ belongs_to :user, Blazer::BELONGS_TO_OPTIONAL.merge(class_name: Blazer.user_class.to_s)
4
+ belongs_to :query, Blazer::BELONGS_TO_OPTIONAL
5
+ end
6
+ 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,5 @@
1
+ module Blazer
2
+ class Connection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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,9 @@
1
+ module Blazer
2
+ class DashboardQuery < Record
3
+ belongs_to :dashboard
4
+ belongs_to :query
5
+
6
+ validates :dashboard_id, presence: true
7
+ validates :query_id, presence: true
8
+ end
9
+ 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,5 @@
1
+ module Blazer
2
+ class Record < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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,6 @@
1
+ <ul>
2
+ <% @checks.each do |check| %>
3
+ <li><%= link_to check.query.name, query_url(check.query_id) %> <%= check.state %></li>
4
+ <% end %>
5
+ </ul>
6
+ <p><%= link_to "Manage checks", checks_url %></p>
@@ -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 %>