blazer_xlsx 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +442 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +1093 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Sortable.js +3709 -0
  13. data/app/assets/javascripts/blazer/ace/ace.js +19630 -0
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1981 -0
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +215 -0
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +16 -0
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +9 -0
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +18 -0
  19. data/app/assets/javascripts/blazer/ace.js +6 -0
  20. data/app/assets/javascripts/blazer/application.js +84 -0
  21. data/app/assets/javascripts/blazer/bootstrap.js +2580 -0
  22. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  23. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +2570 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1578 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.min.js +466 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10872 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  30. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  31. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1548 -0
  32. data/app/assets/javascripts/blazer/moment.js +5685 -0
  33. data/app/assets/javascripts/blazer/queries.js +130 -0
  34. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  35. data/app/assets/javascripts/blazer/routes.js +26 -0
  36. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  37. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  38. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  39. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  40. data/app/assets/stylesheets/blazer/application.css +243 -0
  41. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  42. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  43. data/app/assets/stylesheets/blazer/bootstrap.css +6828 -0
  44. data/app/assets/stylesheets/blazer/daterangepicker.css +410 -0
  45. data/app/assets/stylesheets/blazer/github.css +125 -0
  46. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  47. data/app/controllers/blazer/base_controller.rb +135 -0
  48. data/app/controllers/blazer/checks_controller.rb +56 -0
  49. data/app/controllers/blazer/dashboards_controller.rb +99 -0
  50. data/app/controllers/blazer/queries_controller.rb +472 -0
  51. data/app/controllers/blazer/uploads_controller.rb +147 -0
  52. data/app/helpers/blazer/base_helper.rb +39 -0
  53. data/app/models/blazer/audit.rb +6 -0
  54. data/app/models/blazer/check.rb +104 -0
  55. data/app/models/blazer/connection.rb +5 -0
  56. data/app/models/blazer/dashboard.rb +17 -0
  57. data/app/models/blazer/dashboard_query.rb +9 -0
  58. data/app/models/blazer/query.rb +42 -0
  59. data/app/models/blazer/record.rb +5 -0
  60. data/app/models/blazer/upload.rb +11 -0
  61. data/app/models/blazer/uploads_connection.rb +7 -0
  62. data/app/views/blazer/_nav.html.erb +18 -0
  63. data/app/views/blazer/_variables.html.erb +127 -0
  64. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  65. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  66. data/app/views/blazer/checks/_form.html.erb +79 -0
  67. data/app/views/blazer/checks/edit.html.erb +3 -0
  68. data/app/views/blazer/checks/index.html.erb +72 -0
  69. data/app/views/blazer/checks/new.html.erb +3 -0
  70. data/app/views/blazer/dashboards/_form.html.erb +82 -0
  71. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  72. data/app/views/blazer/dashboards/new.html.erb +3 -0
  73. data/app/views/blazer/dashboards/show.html.erb +53 -0
  74. data/app/views/blazer/queries/_caching.html.erb +16 -0
  75. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  76. data/app/views/blazer/queries/_form.html.erb +255 -0
  77. data/app/views/blazer/queries/docs.html.erb +147 -0
  78. data/app/views/blazer/queries/edit.html.erb +2 -0
  79. data/app/views/blazer/queries/home.html.erb +169 -0
  80. data/app/views/blazer/queries/new.html.erb +2 -0
  81. data/app/views/blazer/queries/run.html.erb +183 -0
  82. data/app/views/blazer/queries/schema.html.erb +55 -0
  83. data/app/views/blazer/queries/show.html.erb +72 -0
  84. data/app/views/blazer/uploads/_form.html.erb +27 -0
  85. data/app/views/blazer/uploads/edit.html.erb +3 -0
  86. data/app/views/blazer/uploads/index.html.erb +55 -0
  87. data/app/views/blazer/uploads/new.html.erb +3 -0
  88. data/app/views/layouts/blazer/application.html.erb +25 -0
  89. data/config/routes.rb +25 -0
  90. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  91. data/lib/blazer/adapters/base_adapter.rb +76 -0
  92. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  93. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  94. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  95. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  96. data/lib/blazer/adapters/elasticsearch_adapter.rb +61 -0
  97. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  98. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  99. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  100. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  101. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  102. data/lib/blazer/adapters/presto_adapter.rb +54 -0
  103. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  104. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  105. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  106. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  107. data/lib/blazer/adapters/sql_adapter.rb +353 -0
  108. data/lib/blazer/adapters.rb +17 -0
  109. data/lib/blazer/anomaly_detectors.rb +22 -0
  110. data/lib/blazer/check_mailer.rb +27 -0
  111. data/lib/blazer/data_source.rb +266 -0
  112. data/lib/blazer/engine.rb +42 -0
  113. data/lib/blazer/forecasters.rb +7 -0
  114. data/lib/blazer/result.rb +178 -0
  115. data/lib/blazer/result_cache.rb +71 -0
  116. data/lib/blazer/run_statement.rb +45 -0
  117. data/lib/blazer/run_statement_job.rb +20 -0
  118. data/lib/blazer/slack_notifier.rb +94 -0
  119. data/lib/blazer/statement.rb +77 -0
  120. data/lib/blazer/version.rb +3 -0
  121. data/lib/blazer.rb +282 -0
  122. data/lib/generators/blazer/install_generator.rb +22 -0
  123. data/lib/generators/blazer/templates/config.yml.tt +79 -0
  124. data/lib/generators/blazer/templates/install.rb.tt +47 -0
  125. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  126. data/lib/generators/blazer/uploads_generator.rb +18 -0
  127. data/lib/tasks/blazer.rake +20 -0
  128. data/licenses/LICENSE-ace.txt +24 -0
  129. data/licenses/LICENSE-bootstrap.txt +21 -0
  130. data/licenses/LICENSE-chart.js.txt +9 -0
  131. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  132. data/licenses/LICENSE-chartkick.js.txt +22 -0
  133. data/licenses/LICENSE-date-fns.txt +21 -0
  134. data/licenses/LICENSE-daterangepicker.txt +21 -0
  135. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  136. data/licenses/LICENSE-highlight.js.txt +29 -0
  137. data/licenses/LICENSE-jquery.txt +20 -0
  138. data/licenses/LICENSE-kurkle-color.txt +9 -0
  139. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  140. data/licenses/LICENSE-moment-timezone.txt +20 -0
  141. data/licenses/LICENSE-moment.txt +22 -0
  142. data/licenses/LICENSE-rails-ujs.txt +20 -0
  143. data/licenses/LICENSE-selectize.txt +202 -0
  144. data/licenses/LICENSE-sortable.txt +21 -0
  145. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  146. data/licenses/LICENSE-stupidtable.txt +19 -0
  147. data/licenses/LICENSE-vue.txt +21 -0
  148. metadata +271 -0
@@ -0,0 +1,104 @@
1
+ module Blazer
2
+ class Check < Record
3
+ belongs_to :creator, optional: true, 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 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
+ 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
36
+
37
+ self.state =
38
+ if result.timed_out?
39
+ "timed out"
40
+ 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"
53
+ else
54
+ check_type == "missing_data" ? "failing" : "passing"
55
+ end
56
+
57
+ self.last_run_at = Time.now if respond_to?(:last_run_at=)
58
+ self.message = message if respond_to?(:message=)
59
+
60
+ if respond_to?(:timeouts=)
61
+ if result.timed_out?
62
+ self.timeouts += 1
63
+ self.state = "disabled" if timeouts >= 3
64
+ else
65
+ self.timeouts = 0
66
+ end
67
+ end
68
+
69
+ # do not notify on creation, except when not passing
70
+ if (state_was != "new" || state != "passing") && state != state_was
71
+ 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 if emails.present?
72
+ Blazer::SlackNotifier.state_change(self, state, state_was, result.rows.size, message, check_type)
73
+ end
74
+ save! if changed?
75
+ end
76
+
77
+ private
78
+
79
+ def set_state
80
+ self.state ||= "new"
81
+ end
82
+
83
+ def fix_emails
84
+ # some people like doing ; instead of ,
85
+ # but we know what they mean, so let's fix it
86
+ # also, some people like to use whitespace
87
+ if emails.present?
88
+ self.emails = emails.strip.gsub(/[;\s]/, ",").gsub(/,+/, ", ")
89
+ end
90
+ end
91
+
92
+ def validate_emails
93
+ unless split_emails.all? { |e| e =~ /\A\S+@\S+\.\S+\z/ }
94
+ errors.add(:base, "Invalid emails")
95
+ end
96
+ end
97
+
98
+ def validate_variables
99
+ if query.variables.any?
100
+ errors.add(:base, "Query can't have variables")
101
+ end
102
+ end
103
+ end
104
+ 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,17 @@
1
+ module Blazer
2
+ class Dashboard < Record
3
+ belongs_to :creator, optional: true, 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 variables
10
+ queries.flat_map { |q| q.variables }.uniq
11
+ end
12
+
13
+ def to_param
14
+ [id, name.gsub("'", "").parameterize].join("-")
15
+ end
16
+ end
17
+ 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,42 @@
1
+ module Blazer
2
+ class Query < Record
3
+ belongs_to :creator, optional: true, 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 :active, -> { column_names.include?("status") ? where(status: ["active", nil]) : all }
12
+ scope :named, -> { where.not(name: "") }
13
+
14
+ def to_param
15
+ [id, name].compact.join("-").gsub("'", "").parameterize
16
+ end
17
+
18
+ def friendly_name
19
+ name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip
20
+ end
21
+
22
+ def editable?(user)
23
+ !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == try(:creator)
24
+ end
25
+
26
+ def variables
27
+ # don't require data_source to be loaded
28
+ variables = Statement.new(statement).variables
29
+ variables += ["cohort_period"] if cohort_analysis?
30
+ variables
31
+ end
32
+
33
+ def cohort_analysis?
34
+ # don't require data_source to be loaded
35
+ Statement.new(statement).cohort_analysis?
36
+ end
37
+
38
+ def statement_object
39
+ Statement.new(statement, data_source)
40
+ end
41
+ end
42
+ 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,11 @@
1
+ module Blazer
2
+ class Upload < Record
3
+ belongs_to :creator, optional: true, class_name: Blazer.user_class.to_s if Blazer.user_class
4
+
5
+ validates :table, presence: true, uniqueness: true, format: {with: /\A[a-z0-9_]+\z/, message: "can only contain lowercase letters, numbers, and underscores"}, length: {maximum: 63}
6
+
7
+ def table_name
8
+ Blazer.uploads_table_name(table)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Blazer
2
+ class UploadsConnection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ establish_connection Blazer.settings["uploads"]["url"] if Blazer.uploads?
6
+ end
7
+ end
@@ -0,0 +1,18 @@
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 "Checks", checks_path %></li>
9
+ <% if Blazer.uploads? %>
10
+ <li><%= link_to "Uploads", uploads_path %></li>
11
+ <% end %>
12
+ <li role="separator" class="divider"></li>
13
+ <li><%= link_to "New Query", new_query_path %></li>
14
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
15
+ <% check_params = @query ? {query_id: @query.id} : {} %>
16
+ <li><%= link_to "New Check", new_check_path(check_params) %></li>
17
+ </ul>
18
+ </div>
@@ -0,0 +1,127 @@
1
+ <% if @bind_vars.any? %>
2
+ <% var_params = request.query_parameters %>
3
+ <script>
4
+ <%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
5
+ var now = moment.tz(timeZone)
6
+ var format = "YYYY-MM-DD"
7
+
8
+ function toDate(time) {
9
+ return moment.tz(time.format(format), timeZone)
10
+ }
11
+ </script>
12
+ <form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 15px;">
13
+ <% date_vars = ["start_time", "end_time"] %>
14
+ <% if (date_vars - @bind_vars).empty? %>
15
+ <% @bind_vars = @bind_vars - date_vars %>
16
+ <% else %>
17
+ <% date_vars = nil %>
18
+ <% end %>
19
+
20
+ <% @bind_vars.each_with_index do |var, i| %>
21
+ <%= label_tag var, var %>
22
+ <% if (data = @smart_vars[var]) %>
23
+ <%= select_tag var, options_for_select([[nil, nil]] + data, selected: var_params[var]), style: "margin-right: 20px; width: 200px; display: none;" %>
24
+ <script>
25
+ $("#<%= var %>").selectize({
26
+ create: true
27
+ });
28
+ </script>
29
+ <% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
30
+ <%= hidden_field_tag var, var_params[var] %>
31
+
32
+ <div class="selectize-control single" style="width: 200px;">
33
+ <div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
34
+ <span>Select a date</span>
35
+ </div>
36
+ </div>
37
+
38
+ <script>
39
+ (function() {
40
+ var input = $("#<%= var %>")
41
+ var datePicker = $("#<%= var %>-select")
42
+ datePicker.daterangepicker({
43
+ singleDatePicker: true,
44
+ locale: {format: format},
45
+ autoUpdateInput: false,
46
+ autoApply: true,
47
+ startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
48
+ })
49
+ // hack to start with empty date
50
+ datePicker.on("apply.daterangepicker", function(ev, picker) {
51
+ datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
52
+ input.val(toDate(picker.startDate).utc().format())
53
+ submitIfCompleted($("#<%= var %>").closest("form"))
54
+ })
55
+ if (input.val().length > 0) {
56
+ var picker = datePicker.data("daterangepicker")
57
+ datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
58
+ }
59
+ })()
60
+ </script>
61
+ <% else %>
62
+ <%= text_field_tag var, var_params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !var_params[var], class: "form-control" %>
63
+ <% end %>
64
+ <% end %>
65
+
66
+ <% if date_vars %>
67
+ <% date_vars.each do |var| %>
68
+ <%= hidden_field_tag var, var_params[var] %>
69
+ <% end %>
70
+
71
+ <%= label_tag nil, date_vars.join(" & ") %>
72
+ <div class="selectize-control single" style="width: 300px;">
73
+ <div id="reportrange" class="selectize-input" style="display: inline-block;">
74
+ <span>Select a time range</span>
75
+ </div>
76
+ </div>
77
+
78
+ <script>
79
+ function dateStr(daysAgo) {
80
+ return now.clone().subtract(daysAgo || 0, "days").format(format)
81
+ }
82
+
83
+ function setTimeInputs(start, end) {
84
+ $("#start_time").val(toDate(start).utc().format())
85
+ $("#end_time").val(toDate(end).endOf("day").utc().format())
86
+ }
87
+
88
+ $("#reportrange").daterangepicker(
89
+ {
90
+ ranges: {
91
+ "Today": [dateStr(), dateStr()],
92
+ "Last 7 Days": [dateStr(6), dateStr()],
93
+ "Last 30 Days": [dateStr(29), dateStr()]
94
+ },
95
+ locale: {
96
+ format: format
97
+ },
98
+ startDate: dateStr(29),
99
+ endDate: dateStr(),
100
+ opens: "right",
101
+ alwaysShowCalendars: true
102
+ },
103
+ function(start, end) {
104
+ setTimeInputs(start, end)
105
+ submitIfCompleted($("#start_time").closest("form"))
106
+ }
107
+ ).on("apply.daterangepicker", function(ev, picker) {
108
+ setTimeInputs(picker.startDate, picker.endDate)
109
+ $("#reportrange span").html(toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY"))
110
+ })
111
+
112
+ if ($("#start_time").val().length > 0) {
113
+ var picker = $("#reportrange").data("daterangepicker")
114
+ picker.setStartDate(moment.tz($("#start_time").val(), timeZone))
115
+ picker.setEndDate(moment.tz($("#end_time").val(), timeZone))
116
+ $("#reportrange").trigger("apply.daterangepicker", picker)
117
+ } else {
118
+ var picker = $("#reportrange").data("daterangepicker")
119
+ $("#reportrange").trigger("apply.daterangepicker", picker)
120
+ submitIfCompleted($("#start_time").closest("form"))
121
+ }
122
+ </script>
123
+ <% end %>
124
+
125
+ <input type="submit" class="btn btn-success" value="Run" style="vertical-align: top;" />
126
+ </form>
127
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <ul>
2
+ <% @checks.each do |check| %>
3
+ <%# check queries shouldn't have variables, but in any case, don't pass them to url helpers %>
4
+ <li><%= link_to check.query.name, query_url(check.query_id) %> <%= check.state %></li>
5
+ <% end %>
6
+ </ul>
7
+ <p><%= link_to "Manage checks", checks_url %></p>
@@ -0,0 +1,48 @@
1
+ <html>
2
+ <head>
3
+ </head>
4
+ <body style="font-family: 'Helvetica Neue', Arial, Helvetica; font-size: 14px; color: #333;">
5
+ <%# check queries shouldn't have variables, but in any case, don't pass them to url helpers %>
6
+ <p><%= link_to "View", query_url(@check.query_id) %></p>
7
+ <% if @error %>
8
+ <p><%= @error %></p>
9
+ <% elsif @rows_count > 0 && @check_type == "bad_data" %>
10
+ <p>
11
+ <% if @rows_count <= 10 %>
12
+ <%= pluralize(@rows_count, "row") %>
13
+ <% else %>
14
+ Showing 10 of <%= @rows_count %> rows
15
+ <% end %>
16
+ </p>
17
+ <table style="width: 100%; border-spacing: 0; border-collapse: collapse;">
18
+ <thead>
19
+ <tr>
20
+ <% @columns.first(5).each do |column| %>
21
+ <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) %>%;">
22
+ <%= column %>
23
+ </th>
24
+ <% end %>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <% @rows.first(10).each do |row| %>
29
+ <tr>
30
+ <% @columns.first(5).each_with_index do |column, i| %>
31
+ <td style="padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;">
32
+ <% value = row[i] %>
33
+ <% if @column_types[i] == "time" && value.to_s.length > 10 %>
34
+ <% value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value %>
35
+ <% end %>
36
+ <%= value %>
37
+ </td>
38
+ <% end %>
39
+ </tr>
40
+ <% end %>
41
+ </tbody>
42
+ </table>
43
+ <% if @columns.size > 5 %>
44
+ <p style="color: #999;">Only first 5 columns shown</p>
45
+ <% end %>
46
+ <% end %>
47
+ </body>
48
+ </html>
@@ -0,0 +1,79 @@
1
+ <%= form_for @check, html: {class: "small-form"} do |f| %>
2
+ <% unless @check.respond_to?(:check_type) || @check.respond_to?(:invert) %>
3
+ <p class="text-muted">Checks are designed to identify bad data. A check fails if there are any results.</p>
4
+ <% end %>
5
+
6
+ <% if @check.errors.any? %>
7
+ <div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
8
+ <% end %>
9
+
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.active.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
+
64
+ <% if Blazer.slack? %>
65
+ <div class="form-group">
66
+ <%= f.label :slack_channels %>
67
+ <%= f.text_field :slack_channels, placeholder: "Optional, comma separated", class: "form-control" %>
68
+ </div>
69
+ <% end %>
70
+
71
+ <p class="text-muted">Emails <%= Blazer.slack? ? "and Slack notifications " : nil %>are sent when a check starts failing, and when it starts passing again.
72
+ <p>
73
+ <% if @check.persisted? %>
74
+ <%= link_to "Delete", check_path(@check), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
75
+ <% end %>
76
+ <%= f.submit "Save", class: "btn btn-success" %>
77
+ <%= link_to "Back", :back, class: "btn btn-link" %>
78
+ </p>
79
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "Edit Check" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,72 @@
1
+ <% blazer_title "Checks" %>
2
+
3
+ <div id="header">
4
+ <div class="pull-right" style="line-height: 34px;">
5
+ <div class="btn-group">
6
+ <%= link_to "New Check", new_check_path, class: "btn btn-info" %>
7
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
8
+ <span class="caret"></span>
9
+ <span class="sr-only">Toggle Dropdown</span>
10
+ </button>
11
+ <ul class="dropdown-menu">
12
+ <li><%= link_to "Home", root_path %></li>
13
+ <% if Blazer.uploads? %>
14
+ <li><%= link_to "Uploads", uploads_path %></li>
15
+ <% end %>
16
+ <li role="separator" class="divider"></li>
17
+ <li><%= link_to "New Query", new_query_path %></li>
18
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+
23
+ <input id="search" type="text" placeholder="Start typing a query or state" style="width: 300px; display: inline-block;" class="search form-control" />
24
+ </div>
25
+
26
+ <table id="checks" class="table">
27
+ <thead>
28
+ <tr>
29
+ <th>Query</th>
30
+ <th style="width: 10%;">State</th>
31
+ <th style="width: 10%;">Run</th>
32
+ <th style="width: 20%;">Notify</th>
33
+ <th style="width: 15%;"></th>
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ <% @checks.each do |check| %>
38
+ <tr>
39
+ <td><%= link_to check.query.name, check.query %> <span class="text-muted"><%= check.try(:check_type).to_s.gsub("_", " ") %></span></td>
40
+ <td>
41
+ <% if check.state %>
42
+ <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= check.state.upcase %></small>
43
+ <% end %>
44
+ </td>
45
+ <td><%= check.schedule if check.respond_to?(:schedule) %></td>
46
+ <td>
47
+ <ul class="list-unstyled" style="margin-bottom: 0; word-break: break-all;">
48
+ <% check.split_emails.each do |email| %>
49
+ <li><%= email %></li>
50
+ <% end %>
51
+ <% check.split_slack_channels.each do |channel| %>
52
+ <li><%= channel %></li>
53
+ <% end %>
54
+ </ul>
55
+ </td>
56
+ <td style="text-align: right; padding: 1px;">
57
+ <%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
58
+ <%= button_to "Run Now", refresh_query_path(check.query), class: "btn btn-primary" %>
59
+ </td>
60
+ </tr>
61
+ <% end %>
62
+ </tbody>
63
+ </table>
64
+
65
+ <script>
66
+ $("#search").on("keyup", function() {
67
+ var value = $(this).val().toLowerCase()
68
+ $("#checks tbody tr").filter( function() {
69
+ $(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
70
+ })
71
+ }).focus()
72
+ </script>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "New Check" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,82 @@
1
+ <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, params: variable_params(@dashboard)) : dashboards_path(params: variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
2
+ <% if @dashboard.errors.any? %>
3
+ <div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
4
+ <% end %>
5
+
6
+ <div class="form-group">
7
+ <%= f.label :name %>
8
+ <%= f.text_field :name, class: "form-control" %>
9
+ </div>
10
+ <div class="form-group" v-show="queries.length">
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>
19
+ </div>
20
+ <div class="form-group" v-cloak>
21
+ <%= f.label :query_id, "Add Chart" %>
22
+ <%= select_tag :query_id, nil, {include_blank: true, placeholder: "Select chart"} %>
23
+ </div>
24
+ <p style="padding-bottom: 140px;" v-cloak>
25
+ <% if @dashboard.persisted? %>
26
+ <%= link_to "Delete", dashboard_path(@dashboard), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
27
+ <% end %>
28
+ <%= f.submit "Save", class: "btn btn-success" %>
29
+ <%= link_to "Back", :back, class: "btn btn-link" %>
30
+ </p>
31
+ <% end %>
32
+
33
+ <script>
34
+ <%= 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) %>
36
+
37
+ var app = Vue.createApp({
38
+ data: function() {
39
+ return {
40
+ queries: dashboardQueries
41
+ }
42
+ },
43
+ methods: {
44
+ remove: function(index) {
45
+ this.queries.splice(index, 1)
46
+ }
47
+ },
48
+ mounted: function() {
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)
57
+
58
+ // if duplicate query is added, remove the first one
59
+ for (var i = 0; i < app.queries.length; i++) {
60
+ if (app.queries[i].id == val) {
61
+ app.queries.splice(i, 1)
62
+ break
63
+ }
64
+ }
65
+
66
+ app.queries.push({id: val, name: item.text()})
67
+ this.setValue("")
68
+ }
69
+ }
70
+ })
71
+ }
72
+ })
73
+ app.config.compilerOptions.whitespace = "preserve"
74
+ app.mount("#app")
75
+
76
+ Sortable.create($("#queries").get(0), {
77
+ onEnd: function(e) {
78
+ var app = window.app._component.data()
79
+ app.queries.splice(e.newIndex, 0, app.queries.splice(e.oldIndex, 1)[0])
80
+ }
81
+ })
82
+ </script>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "Edit Dashboard" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "New Dashboard" %>
2
+
3
+ <%= render partial: "form" %>