sql-jarvis 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -1
  3. data/LICENSE.txt +1 -1
  4. data/README.md +153 -52
  5. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/blazer/favicon.png +0 -0
  11. data/app/assets/javascripts/blazer/Chart.js +4195 -3884
  12. data/app/assets/javascripts/blazer/Sortable.js +1493 -1097
  13. data/app/assets/javascripts/blazer/ace/ace.js +21294 -4
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1991 -3
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -1
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -1
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -1
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -1
  19. data/app/assets/javascripts/blazer/application.js +4 -3
  20. data/app/assets/javascripts/blazer/bootstrap.js +623 -612
  21. data/app/assets/javascripts/blazer/chartkick.js +1769 -1248
  22. data/app/assets/javascripts/blazer/daterangepicker.js +263 -115
  23. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  24. data/app/assets/javascripts/blazer/{jquery_ujs.js → jquery-ujs.js} +161 -75
  25. data/app/assets/javascripts/blazer/jquery.js +9506 -9450
  26. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +321 -259
  27. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
  28. data/app/assets/javascripts/blazer/queries.js +1 -1
  29. data/app/assets/javascripts/blazer/routes.js +3 -0
  30. data/app/assets/javascripts/blazer/selectize.js +3828 -3604
  31. data/app/assets/javascripts/blazer/stupidtable.js +255 -88
  32. data/app/assets/javascripts/blazer/vue.js +8015 -4583
  33. data/app/assets/stylesheets/blazer/application.css +41 -5
  34. data/app/assets/stylesheets/blazer/bootstrap.css.erb +879 -325
  35. data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
  36. data/app/assets/stylesheets/blazer/selectize.default.css +26 -10
  37. data/app/controllers/blazer/base_controller.rb +7 -0
  38. data/app/controllers/blazer/checks_controller.rb +1 -1
  39. data/app/controllers/blazer/dashboards_controller.rb +0 -4
  40. data/app/controllers/blazer/queries_controller.rb +20 -12
  41. data/app/helpers/blazer/base_helper.rb +1 -1
  42. data/app/mailers/blazer/slack_notifier.rb +76 -0
  43. data/app/models/blazer/check.rb +9 -0
  44. data/app/views/blazer/_nav.html.erb +0 -1
  45. data/app/views/blazer/_variables.html.erb +41 -19
  46. data/app/views/blazer/checks/_form.html.erb +16 -8
  47. data/app/views/blazer/checks/edit.html.erb +2 -0
  48. data/app/views/blazer/checks/index.html.erb +33 -4
  49. data/app/views/blazer/checks/new.html.erb +2 -0
  50. data/app/views/blazer/dashboards/_form.html.erb +4 -4
  51. data/app/views/blazer/dashboards/edit.html.erb +2 -0
  52. data/app/views/blazer/dashboards/new.html.erb +2 -0
  53. data/app/views/blazer/dashboards/show.html.erb +7 -3
  54. data/app/views/blazer/queries/_form.html.erb +11 -6
  55. data/app/views/blazer/queries/docs.html.erb +131 -0
  56. data/app/views/blazer/queries/home.html.erb +12 -3
  57. data/app/views/blazer/queries/run.html.erb +36 -6
  58. data/app/views/blazer/queries/schema.html.erb +46 -8
  59. data/app/views/blazer/queries/show.html.erb +4 -4
  60. data/app/views/layouts/blazer/application.html.erb +3 -3
  61. data/config/routes.rb +5 -1
  62. data/lib/blazer.rb +32 -13
  63. data/lib/blazer/adapters/athena_adapter.rb +1 -1
  64. data/lib/blazer/adapters/elasticsearch_adapter.rb +14 -17
  65. data/lib/blazer/adapters/mongodb_adapter.rb +1 -1
  66. data/lib/blazer/adapters/sql_adapter.rb +7 -1
  67. data/lib/blazer/engine.rb +4 -0
  68. data/lib/blazer/result.rb +62 -29
  69. data/lib/blazer/run_statement_job.rb +6 -9
  70. data/lib/blazer/version.rb +1 -1
  71. data/lib/generators/blazer/templates/config.yml.tt +13 -2
  72. data/lib/generators/blazer/templates/install.rb.tt +1 -0
  73. data/lib/tasks/blazer.rake +1 -0
  74. metadata +33 -37
  75. data/.gitattributes +0 -1
  76. data/.github/ISSUE_TEMPLATE.md +0 -7
  77. data/.gitignore +0 -14
  78. data/Gemfile +0 -7
  79. data/Rakefile +0 -1
  80. data/app/assets/javascripts/blazer/highlight.pack.js +0 -1
  81. data/app/assets/javascripts/blazer/moment-timezone.js +0 -1007
  82. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +0 -375
  83. data/blazer.gemspec +0 -30
@@ -12,7 +12,7 @@ module Blazer
12
12
  BLAZER_IMAGE_EXT = %w[png jpg jpeg gif]
13
13
 
14
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")
15
+ if value.is_a?(Numeric) && !key.to_s.end_with?("id") && !key.to_s.start_with?("id")
16
16
  number_with_delimiter(value)
17
17
  elsif value =~ BLAZER_URL_REGEX
18
18
  # see if image or link
@@ -0,0 +1,76 @@
1
+ require "net/http"
2
+
3
+ module Blazer
4
+ class SlackNotifier
5
+ def self.state_change(check, state, state_was, rows_count, error, check_type)
6
+ check.split_slack_channels.each do |channel|
7
+ text =
8
+ if error
9
+ error
10
+ elsif rows_count > 0 && check_type == "bad_data"
11
+ pluralize(rows_count, "row")
12
+ end
13
+
14
+ payload = {
15
+ channel: channel,
16
+ attachments: [
17
+ {
18
+ title: escape("Check #{state.titleize}: #{check.query.name}"),
19
+ title_link: query_url(check.query_id),
20
+ text: escape(text),
21
+ color: state == "passing" ? "good" : "danger"
22
+ }
23
+ ]
24
+ }
25
+
26
+ post(Blazer.slack_webhook_url, payload)
27
+ end
28
+ end
29
+
30
+ def self.failing_checks(channel, checks)
31
+ text =
32
+ checks.map do |check|
33
+ "<#{query_url(check.query_id)}|#{escape(check.query.name)}> #{escape(check.state)}"
34
+ end
35
+
36
+ payload = {
37
+ channel: channel,
38
+ attachments: [
39
+ {
40
+ title: escape("#{pluralize(checks.size, "Check")} Failing"),
41
+ text: text.join("\n"),
42
+ color: "warning"
43
+ }
44
+ ]
45
+ }
46
+
47
+ post(Blazer.slack_webhook_url, payload)
48
+ end
49
+
50
+ # https://api.slack.com/docs/message-formatting#how_to_escape_characters
51
+ # - Replace the ampersand, &, with &amp;
52
+ # - Replace the less-than sign, < with &lt;
53
+ # - Replace the greater-than sign, > with &gt;
54
+ # That's it. Don't HTML entity-encode the entire message.
55
+ def self.escape(str)
56
+ str.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;") if str
57
+ end
58
+
59
+ def self.pluralize(*args)
60
+ ActionController::Base.helpers.pluralize(*args)
61
+ end
62
+
63
+ def self.query_url(id)
64
+ Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
65
+ end
66
+
67
+ def self.post(url, payload)
68
+ uri = URI.parse(url)
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ http.use_ssl = true
71
+ http.open_timeout = 3
72
+ http.read_timeout = 5
73
+ http.post(uri.request_uri, payload.to_json)
74
+ end
75
+ end
76
+ end
@@ -14,6 +14,14 @@ module Blazer
14
14
  emails.to_s.downcase.split(",").map(&:strip)
15
15
  end
16
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
+
17
25
  def update_state(result)
18
26
  check_type =
19
27
  if respond_to?(:check_type)
@@ -61,6 +69,7 @@ module Blazer
61
69
  # do not notify on creation, except when not passing
62
70
  if (state_was != "new" || state != "passing") && state != state_was && emails.present?
63
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
72
+ Blazer::SlackNotifier.state_change(self, state, state_was, result.rows.size, message, check_type)
64
73
  end
65
74
  save! if changed?
66
75
  end
@@ -5,7 +5,6 @@
5
5
  <span class="sr-only">Toggle Dropdown</span>
6
6
  </button>
7
7
  <ul class="dropdown-menu">
8
- <li><%= link_to "Dashboards", dashboards_path %></li>
9
8
  <li><%= link_to "Checks", checks_path %></li>
10
9
  <li role="separator" class="divider"></li>
11
10
  <li><%= link_to "New Query", new_query_path %></li>
@@ -1,5 +1,14 @@
1
1
  <% if @bind_vars.any? %>
2
- <form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 10px;">
2
+ <script>
3
+ <%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
4
+ var now = moment.tz(timeZone)
5
+ var format = "YYYY-MM-DD"
6
+
7
+ function toDate(time) {
8
+ return moment.tz(time.format(format), timeZone)
9
+ }
10
+ </script>
11
+ <form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 15px;">
3
12
  <% date_vars = ["start_time", "end_time"] %>
4
13
  <% if (date_vars - @bind_vars).empty? %>
5
14
  <% @bind_vars = @bind_vars - date_vars %>
@@ -16,18 +25,39 @@
16
25
  create: true
17
26
  });
18
27
  </script>
28
+ <% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
29
+ <%= hidden_field_tag var, params[var] %>
30
+
31
+ <div class="selectize-control single" style="width: 200px;">
32
+ <div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
33
+ <span>Select a date</span>
34
+ </div>
35
+ </div>
36
+
37
+ <script>
38
+ (function() {
39
+ var input = $("#<%= var %>")
40
+ var datePicker = $("#<%= var %>-select")
41
+ datePicker.daterangepicker({
42
+ singleDatePicker: true,
43
+ locale: {format: format},
44
+ autoUpdateInput: false,
45
+ startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
46
+ })
47
+ // hack to start with empty date
48
+ datePicker.on("apply.daterangepicker", function(ev, picker) {
49
+ datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
50
+ input.val(toDate(picker.startDate).utc().format())
51
+ submitIfCompleted($("#<%= var %>").closest("form"))
52
+ })
53
+ if (input.val().length > 0) {
54
+ var picker = datePicker.data("daterangepicker")
55
+ datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
56
+ }
57
+ })()
58
+ </script>
19
59
  <% else %>
20
60
  <%= 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
61
  <% end %>
32
62
  <% end %>
33
63
 
@@ -44,18 +74,10 @@
44
74
  </div>
45
75
 
46
76
  <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
77
  function dateStr(daysAgo) {
52
78
  return now.clone().subtract(daysAgo || 0, "days").format(format)
53
79
  }
54
80
 
55
- function toDate(time) {
56
- return moment.tz(time.format(format), timeZone)
57
- }
58
-
59
81
  function setTimeInputs(start, end) {
60
82
  $("#start_time").val(toDate(start).utc().format())
61
83
  $("#end_time").val(toDate(end).endOf("day").utc().format())
@@ -1,12 +1,12 @@
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 %>
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 %>
4
5
 
5
- <% if @check.errors.any? %>
6
- <div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
7
- <% end %>
6
+ <% if @check.errors.any? %>
7
+ <div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
8
+ <% end %>
8
9
 
9
- <%= form_for @check do |f| %>
10
10
  <div class="form-group">
11
11
  <%= f.label :query_id, "Query" %>
12
12
  <div class="hide">
@@ -60,7 +60,15 @@
60
60
  <%= f.label :emails %>
61
61
  <%= f.text_field :emails, placeholder: "Optional, comma separated", class: "form-control" %>
62
62
  </div>
63
- <p class="text-muted">Emails are sent when a check starts failing, and when it starts passing again.
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.
64
72
  <p>
65
73
  <% if @check.persisted? %>
66
74
  <%= link_to "Delete", check_path(@check), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
@@ -1 +1,3 @@
1
+ <% blazer_title "Edit Check" %>
2
+
1
3
  <%= render partial: "form" %>
@@ -1,15 +1,32 @@
1
1
  <% blazer_title "Checks" %>
2
2
 
3
- <p style="float: right;"><%= link_to "New Check", new_check_path, class: "btn btn-info" %></p>
4
- <%= render partial: "blazer/nav" %>
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
+ <li role="separator" class="divider"></li>
14
+ <li><%= link_to "New Query", new_query_path %></li>
15
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
16
+ </ul>
17
+ </div>
18
+ </div>
5
19
 
6
- <table class="table">
20
+ <input id="search" type="text" placeholder="Start typing a query or state" style="width: 300px; display: inline-block;" class="search form-control" />
21
+ </div>
22
+
23
+ <table id="checks" class="table">
7
24
  <thead>
8
25
  <tr>
9
26
  <th>Query</th>
10
27
  <th style="width: 10%;">State</th>
11
28
  <th style="width: 10%;">Run</th>
12
- <th style="width: 20%;">Emails</th>
29
+ <th style="width: 20%;">Notify</th>
13
30
  <th style="width: 15%;"></th>
14
31
  </tr>
15
32
  </thead>
@@ -28,6 +45,9 @@
28
45
  <% check.split_emails.each do |email| %>
29
46
  <li><%= email %></li>
30
47
  <% end %>
48
+ <% check.split_slack_channels.each do |channel| %>
49
+ <li><%= channel %></li>
50
+ <% end %>
31
51
  </ul>
32
52
  </td>
33
53
  <td style="text-align: right; padding: 1px;">
@@ -38,3 +58,12 @@
38
58
  <% end %>
39
59
  </tbody>
40
60
  </table>
61
+
62
+ <script>
63
+ $("#search").on("keyup", function() {
64
+ var value = $(this).val().toLowerCase()
65
+ $("#checks tbody tr").filter( function() {
66
+ $(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
67
+ })
68
+ }).focus()
69
+ </script>
@@ -1 +1,3 @@
1
+ <% blazer_title "New Check" %>
2
+
1
3
  <%= render partial: "form" %>
@@ -1,8 +1,8 @@
1
- <% if @dashboard.errors.any? %>
2
- <div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
3
- <% end %>
1
+ <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params) : dashboards_path(variable_params)), 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 %>
4
5
 
5
- <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params) : dashboards_path(variable_params)), html: {id: "app"} do |f| %>
6
6
  <div class="form-group">
7
7
  <%= f.label :name %>
8
8
  <%= f.text_field :name, class: "form-control" %>
@@ -1 +1,3 @@
1
+ <% blazer_title "Edit Dashboard" %>
2
+
1
3
  <%= render partial: "form" %>
@@ -1 +1,3 @@
1
+ <% blazer_title "New Dashboard" %>
2
+
1
3
  <%= render partial: "form" %>
@@ -5,7 +5,7 @@
5
5
  <div class="row" style="padding-top: 13px;">
6
6
  <div class="col-sm-9">
7
7
  <%= render partial: "blazer/nav" %>
8
- <h3 style="margin: 0; line-height: 34px; display: inline;">
8
+ <h3 style="line-height: 34px; display: inline; margin-left: 5px;">
9
9
  <%= @dashboard.name %>
10
10
  </h3>
11
11
  </div>
@@ -25,7 +25,11 @@
25
25
  </p>
26
26
  <% end %>
27
27
 
28
- <%= render partial: "blazer/variables", locals: {action: dashboard_path(@dashboard)} %>
28
+ <% if @bind_vars.any? %>
29
+ <%= render partial: "blazer/variables", locals: {action: dashboard_path(@dashboard)} %>
30
+ <% else %>
31
+ <div style="padding-bottom: 15px;"></div>
32
+ <% end %>
29
33
 
30
34
  <% @queries.each_with_index do |query, i| %>
31
35
  <div class="chart-container">
@@ -35,7 +39,7 @@
35
39
  </div>
36
40
  </div>
37
41
  <script>
38
- <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, only_chart: true} %>
42
+ <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, data_source: query.data_source, only_chart: true} %>
39
43
 
40
44
  runQuery(data, function (data) {
41
45
  $("#chart-<%= i %>").html(data)
@@ -12,11 +12,13 @@
12
12
  <div id="editor" :style="{ height: editorHeight }"><%= @query.statement %></div>
13
13
  </div>
14
14
  </div>
15
- <div class="form-group text-right">
16
- <div class="pull-left" style="margin-top: 9px;">
15
+ <div class="form-group text-right" style="margin-bottom: 8px;">
16
+ <div class="pull-left" style="margin-top: 8px;">
17
17
  <%= link_to "Back", :back %>
18
+ <a :href="docsPath" target="_blank" style="margin-left: 40px;">Docs</a>
19
+ <a :href="schemaPath" target="_blank" style="margin-left: 40px;">Schema</a>
18
20
  </div>
19
- <a :href="dataSourcePath" target="_blank" style="margin-right: 10px;">Schema</a>
21
+
20
22
  <%= f.select :data_source, Blazer.data_sources.values.select { |ds| q = @query.dup; q.data_source = ds.id; q.editable?(blazer_user) }.map { |ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size <= 1), style: "width: 140px;" %>
21
23
  <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
22
24
  <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
@@ -40,7 +42,7 @@
40
42
  <%= f.collection_select :assignee_ids, @assignees, :first, :last, {}, { placeholder: "Assignees", style: "height: 80px;", class: "form-control", multiple: true } %>
41
43
  </div>
42
44
  <%- end -%>
43
- <div class="text-right">
45
+ <div class="form-group text-right">
44
46
  <%= f.submit "For Enter Press", class: "hide" %>
45
47
  <% if @query.persisted? %>
46
48
  <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
@@ -55,7 +57,7 @@
55
57
  <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
56
58
  <% words << pluralize(checks_count, "check") if checks_count > 0 %>
57
59
  <% if words.any? %>
58
- <div class="alert alert-info" style="margin-top: 10px; padding: 8px 12px;">
60
+ <div class="alert alert-info" style="margin-bottom: 0;">
59
61
  Part of <%= words.to_sentence %>. Be careful when editing.
60
62
  </div>
61
63
  <% end %>
@@ -85,8 +87,11 @@
85
87
  editorHeight: "180px"
86
88
  },
87
89
  computed: {
88
- dataSourcePath: function() {
90
+ schemaPath: function() {
89
91
  return Routes.schema_queries_path({data_source: this.dataSource})
92
+ },
93
+ docsPath: function() {
94
+ return Routes.docs_queries_path({data_source: this.dataSource})
90
95
  }
91
96
  },
92
97
  methods: {
@@ -0,0 +1,131 @@
1
+ <% blazer_title "Docs: #{@data_source.name}" %>
2
+
3
+ <h1>Docs: <%= @data_source.name %></h1>
4
+
5
+ <hr />
6
+
7
+ <h2>Smart Variables</h2>
8
+
9
+ <% if @smart_variables.any? %>
10
+ <p>Use these variable names to get a dropdown of values.</p>
11
+
12
+ <table class="table" style="max-width: 500px;">
13
+ <thead>
14
+ <tr>
15
+ <th>Variable</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% @smart_variables.each do |k, _| %>
20
+ <tr>
21
+ <td><code>{<%= k %>}</code></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+
27
+ <p>Use <code>{start_time}</code> and <code>{end_time}</code> for a date range selector. End a variable name with <code>_at</code> for a date selector.</p>
28
+ <% else %>
29
+ <p>None set - add them in <code>config/blazer.yml</code>.</p>
30
+ <% end %>
31
+
32
+ <h2>Linked Columns</h2>
33
+
34
+ <% if @linked_columns.any? %>
35
+ <p>Use these column names to link results to other pages.</p>
36
+
37
+ <table class="table" style="max-width: 500px;">
38
+ <thead>
39
+ <tr>
40
+ <th style="width: 20%;">Name</th>
41
+ <th>URL</th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ <% @linked_columns.each do |k, v| %>
46
+ <tr>
47
+ <td><%= k %></td>
48
+ <td><%= v %></td>
49
+ </tr>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+
54
+ <p>Values that match the format of a URL will be linked automatically.</p>
55
+ <% else %>
56
+ <p>None set - add them in <code>config/blazer.yml</code>.</p>
57
+ <% end %>
58
+
59
+ <h2>Smart Columns</h2>
60
+
61
+ <% if @smart_columns.any? %>
62
+ <p>Use these column names to show additional data.</p>
63
+
64
+ <table class="table" style="max-width: 500px;">
65
+ <thead>
66
+ <tr>
67
+ <th>Name</th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ <% @smart_columns.each do |k, _| %>
72
+ <tr>
73
+ <td><%= k %></td>
74
+ </tr>
75
+ <% end %>
76
+ </tbody>
77
+ </table>
78
+ <% else %>
79
+ <p>None set - add them in <code>config/blazer.yml</code>.</p>
80
+ <% end %>
81
+
82
+ <h2>Charts</h2>
83
+
84
+ <p>Use specific combinations of column types to generate charts.</p>
85
+
86
+ <table class="table" style="max-width: 500px;">
87
+ <thead>
88
+ <tr>
89
+ <th style="width: 20%;">Chart</th>
90
+ <th>Column Types</th>
91
+ </tr>
92
+ </thead>
93
+ <tbody>
94
+ <tr>
95
+ <td>Line</td>
96
+ <td>2+ columns - timestamp, numeric(s)</td>
97
+ </tr>
98
+ <tr>
99
+ <td>Line</td>
100
+ <td>3 columns - timestamp, string, numeric</td>
101
+ </tr>
102
+ <tr>
103
+ <td>Column</td>
104
+ <td>2+ columns - string, numeric(s)</td>
105
+ </tr>
106
+ <tr>
107
+ <td>Column</td>
108
+ <td>3 columns - string, string, numeric</td>
109
+ </tr>
110
+ <tr>
111
+ <td>Scatter</td>
112
+ <td>2 columns - both numeric</td>
113
+ </tr>
114
+ <tr>
115
+ <td>Pie</td>
116
+ <td>2 columns - string, numeric - and last column named <code>pie</code></td>
117
+ </tr>
118
+ <tr>
119
+ <td>Map</td>
120
+ <td>
121
+ Named <code>latitude</code> and <code>longitude</code>, or <code>lat</code> and <code>lon</code>, or <code>lat</code> and <code>lng</code>
122
+ <% if !blazer_maps? %>
123
+ <br />
124
+ <strong>Needs configured</strong>
125
+ <% end %>
126
+ </td>
127
+ </tr>
128
+ </tbody>
129
+ </table>
130
+
131
+ <p>Use the column name <code>target</code> to draw a line for goals.</p>