blazer 1.7.7 → 2.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +242 -33
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +621 -211
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +0 -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/Chart.js +15658 -10011
  13. data/app/assets/javascripts/blazer/Sortable.js +3413 -848
  14. data/app/assets/javascripts/blazer/ace/ace.js +21294 -4
  15. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1991 -3
  16. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -1
  17. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -1
  18. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -1
  19. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -1
  20. data/app/assets/javascripts/blazer/application.js +5 -3
  21. data/app/assets/javascripts/blazer/bootstrap.js +842 -628
  22. data/app/assets/javascripts/blazer/chartkick.js +2015 -1244
  23. data/app/assets/javascripts/blazer/daterangepicker.js +372 -299
  24. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  25. data/app/assets/javascripts/blazer/{jquery_ujs.js → jquery-ujs.js} +161 -75
  26. data/app/assets/javascripts/blazer/jquery.js +10126 -9562
  27. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +321 -259
  28. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1546 -0
  29. data/app/assets/javascripts/blazer/moment.js +5085 -2460
  30. data/app/assets/javascripts/blazer/queries.js +18 -4
  31. data/app/assets/javascripts/blazer/routes.js +3 -0
  32. data/app/assets/javascripts/blazer/selectize.js +3828 -3604
  33. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  34. data/app/assets/javascripts/blazer/stupidtable.js +254 -87
  35. data/app/assets/javascripts/blazer/vue.js +11175 -6676
  36. data/app/assets/stylesheets/blazer/application.css +51 -6
  37. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  38. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  39. data/app/assets/stylesheets/blazer/{bootstrap.css.erb → bootstrap.css} +1337 -711
  40. data/app/assets/stylesheets/blazer/{daterangepicker-bs3.css → daterangepicker.css} +207 -172
  41. data/app/assets/stylesheets/blazer/{selectize.default.css → selectize.css} +26 -10
  42. data/app/controllers/blazer/base_controller.rb +73 -46
  43. data/app/controllers/blazer/checks_controller.rb +1 -1
  44. data/app/controllers/blazer/dashboards_controller.rb +7 -13
  45. data/app/controllers/blazer/queries_controller.rb +171 -51
  46. data/app/controllers/blazer/uploads_controller.rb +147 -0
  47. data/app/helpers/blazer/base_helper.rb +6 -16
  48. data/app/models/blazer/audit.rb +3 -3
  49. data/app/models/blazer/check.rb +31 -5
  50. data/app/models/blazer/dashboard.rb +6 -2
  51. data/app/models/blazer/dashboard_query.rb +1 -1
  52. data/app/models/blazer/query.rb +30 -4
  53. data/app/models/blazer/record.rb +5 -0
  54. data/app/models/blazer/upload.rb +11 -0
  55. data/app/models/blazer/uploads_connection.rb +7 -0
  56. data/app/views/blazer/_nav.html.erb +3 -1
  57. data/app/views/blazer/_variables.html.erb +48 -23
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +1 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +1 -0
  60. data/app/views/blazer/checks/_form.html.erb +17 -9
  61. data/app/views/blazer/checks/edit.html.erb +2 -0
  62. data/app/views/blazer/checks/index.html.erb +37 -5
  63. data/app/views/blazer/checks/new.html.erb +2 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +5 -5
  65. data/app/views/blazer/dashboards/edit.html.erb +2 -0
  66. data/app/views/blazer/dashboards/new.html.erb +2 -0
  67. data/app/views/blazer/dashboards/show.html.erb +13 -7
  68. data/app/views/blazer/queries/_caching.html.erb +16 -0
  69. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  70. data/app/views/blazer/queries/_form.html.erb +23 -13
  71. data/app/views/blazer/queries/docs.html.erb +137 -0
  72. data/app/views/blazer/queries/home.html.erb +21 -7
  73. data/app/views/blazer/queries/run.html.erb +64 -29
  74. data/app/views/blazer/queries/schema.html.erb +44 -7
  75. data/app/views/blazer/queries/show.html.erb +15 -8
  76. data/app/views/blazer/uploads/_form.html.erb +27 -0
  77. data/app/views/blazer/uploads/edit.html.erb +3 -0
  78. data/app/views/blazer/uploads/index.html.erb +55 -0
  79. data/app/views/blazer/uploads/new.html.erb +3 -0
  80. data/app/views/layouts/blazer/application.html.erb +10 -5
  81. data/config/routes.rb +10 -1
  82. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  83. data/lib/blazer/adapters/base_adapter.rb +24 -1
  84. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  85. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  86. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  87. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  88. data/lib/blazer/adapters/elasticsearch_adapter.rb +30 -18
  89. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  90. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  91. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  92. data/lib/blazer/adapters/mongodb_adapter.rb +5 -1
  93. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  94. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  95. data/lib/blazer/adapters/presto_adapter.rb +9 -0
  96. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  97. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  98. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  99. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  100. data/lib/blazer/adapters/sql_adapter.rb +187 -20
  101. data/{app/mailers → lib}/blazer/check_mailer.rb +0 -0
  102. data/lib/blazer/data_source.rb +107 -30
  103. data/lib/blazer/engine.rb +21 -23
  104. data/lib/blazer/result.rb +95 -29
  105. data/lib/blazer/run_statement.rb +8 -4
  106. data/lib/blazer/run_statement_job.rb +8 -9
  107. data/lib/blazer/slack_notifier.rb +94 -0
  108. data/lib/blazer/statement.rb +75 -0
  109. data/lib/blazer/version.rb +1 -1
  110. data/lib/blazer.rb +154 -26
  111. data/lib/generators/blazer/install_generator.rb +7 -18
  112. data/lib/generators/blazer/templates/{config.yml → config.yml.tt} +26 -3
  113. data/lib/generators/blazer/templates/{install.rb → install.rb.tt} +6 -4
  114. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  115. data/lib/generators/blazer/uploads_generator.rb +18 -0
  116. data/lib/tasks/blazer.rake +11 -1
  117. data/licenses/LICENSE-ace.txt +24 -0
  118. data/licenses/LICENSE-bootstrap.txt +21 -0
  119. data/licenses/LICENSE-chart.js.txt +9 -0
  120. data/licenses/LICENSE-chartkick.js.txt +22 -0
  121. data/licenses/LICENSE-daterangepicker.txt +21 -0
  122. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  123. data/licenses/LICENSE-highlight.js.txt +29 -0
  124. data/licenses/LICENSE-jquery-ujs.txt +20 -0
  125. data/licenses/LICENSE-jquery.txt +20 -0
  126. data/licenses/LICENSE-moment-timezone.txt +20 -0
  127. data/licenses/LICENSE-moment.txt +22 -0
  128. data/licenses/LICENSE-selectize.txt +202 -0
  129. data/licenses/LICENSE-sortable.txt +21 -0
  130. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  131. data/licenses/LICENSE-stupidtable.txt +19 -0
  132. data/licenses/LICENSE-vue.txt +21 -0
  133. metadata +83 -53
  134. data/.gitignore +0 -14
  135. data/Gemfile +0 -4
  136. data/Rakefile +0 -1
  137. data/app/assets/javascripts/blazer/highlight.pack.js +0 -1
  138. data/app/assets/javascripts/blazer/moment-timezone.js +0 -1007
  139. data/blazer.gemspec +0 -26
@@ -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, 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 %>
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" %>
@@ -31,7 +31,7 @@
31
31
  <% end %>
32
32
 
33
33
  <script>
34
- <%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
34
+ <%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
35
35
  <%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
36
36
 
37
37
  var app = new Vue({
@@ -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,12 +5,12 @@
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>
12
12
  <div class="col-sm-3 text-right">
13
- <%= link_to "Edit", edit_dashboard_path(@dashboard, variable_params), class: "btn btn-info" %>
13
+ <%= link_to "Edit", edit_dashboard_path(@dashboard, params: variable_params(@dashboard)), class: "btn btn-info" %>
14
14
  </div>
15
15
  </div>
16
16
  </div>
@@ -21,25 +21,31 @@
21
21
  <% if @data_sources.any? { |ds| ds.cache_mode != "off" } %>
22
22
  <p class="text-muted" style="float: right;">
23
23
  Some queries may be cached
24
- <%= link_to "Refresh", refresh_dashboard_path(@dashboard, variable_params), method: :post %>
24
+ <%= link_to "Refresh", refresh_dashboard_path(@dashboard, params: variable_params(@dashboard)), method: :post %>
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">
32
- <h4><%= link_to query.friendly_name, query_path(query, variable_params), target: "_blank" %></h4>
36
+ <h4><%= link_to query.friendly_name, query_path(query, params: variable_params(query)), target: "_blank" %></h4>
33
37
  <div id="chart-<%= i %>" class="chart">
34
38
  <p class="text-muted">Loading...</p>
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
+ <% data = {statement: query.statement, query_id: query.id, data_source: query.data_source, variables: variable_params(query), only_chart: true} %>
43
+ <% data.merge!(cohort_period: params[:cohort_period]) if params[:cohort_period] %>
44
+ <%= blazer_js_var "data", data %>
39
45
 
40
46
  runQuery(data, function (data) {
41
47
  $("#chart-<%= i %>").html(data)
42
- $("#chart-<%= i %> table").stupidtable()
48
+ $("#chart-<%= i %> table").stupidtable(stupidtableCustomSettings)
43
49
  }, function (message) {
44
50
  $("#chart-<%= i %>").addClass("query-error").html(message)
45
51
  });
@@ -0,0 +1,16 @@
1
+ <% if @cached_at || @just_cached %>
2
+ <p class="text-muted" style="float: right;">
3
+ <% if @cached_at %>
4
+ Cached <%= time_ago_in_words(@cached_at, include_seconds: true) %> ago
5
+ <% elsif params[:query_id] %>
6
+ Cached just now
7
+ <% if @data_source.cache_mode == "slow" %>
8
+ (over <%= "%g" % @data_source.cache_slow_threshold %>s)
9
+ <% end %>
10
+ <% end %>
11
+
12
+ <% if @query && params[:query_id] %>
13
+ <%= link_to "Refresh", refresh_query_path(@query, params: variable_params(@query, @var_params)), method: :post %>
14
+ <% end %>
15
+ </p>
16
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <% unless @only_chart %>
2
+ <%= render partial: "caching" %>
3
+ <p class="text-muted" style="margin-bottom: 10px;">
4
+ <%= pluralize(@rows.size, "cohort") %>
5
+ </p>
6
+ <% end %>
7
+ <% if @rows.any? %>
8
+ <div class="results-container">
9
+ <table class="table results-table">
10
+ <thead>
11
+ <tr>
12
+ <th style="min-width: 100px;">Cohort</th>
13
+ <% 12.times do |i| %>
14
+ <th style="width: 7.5%; text-align: right;"><%= @conversion_period.titleize %> <%= i + 1 %></th>
15
+ <% end %>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% @rows.each do |row| %>
20
+ <tr>
21
+ <td>
22
+ <%= row[0] %>
23
+ <div style="font-size: 12px; color: #999;"><%= row[1] == 1 ? "1 user" : "#{number_with_delimiter(row[1])} users" %></div>
24
+ </td>
25
+ <% 12.times do |i| %>
26
+ <td style="text-align: right;">
27
+ <% num = row[i + 2] %>
28
+ <% if num %>
29
+ <% denom = row[1] %>
30
+ <% if denom > 0 %>
31
+ <%= (100.0 * num / denom).round %>%
32
+ <% else %>
33
+ -
34
+ <% end %>
35
+ <div style="font-size: 12px; color: #999;"><%= number_with_delimiter(num) %></div>
36
+ <% else %>
37
+ -
38
+ <% end %>
39
+ </td>
40
+ <% end %>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+ </div>
46
+ <% elsif @only_chart %>
47
+ <p class="text-muted">No cohorts</p>
48
+ <% end %>
@@ -3,7 +3,7 @@
3
3
  <% end %>
4
4
 
5
5
  <div id="app" v-cloak>
6
- <%= form_for @query, url: (@query.persisted? ? query_path(@query, variable_params) : queries_path(variable_params)), html: {autocomplete: "off"} do |f| %>
6
+ <%= form_for @query, url: (@query.persisted? ? query_path(@query, params: variable_params(@query)) : queries_path(params: variable_params(@query))), html: {autocomplete: "off"} do |f| %>
7
7
  <div class="row">
8
8
  <div id="statement-box" class="col-xs-8">
9
9
  <div class= "form-group">
@@ -12,12 +12,14 @@
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>
20
- <%= 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
+
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>
23
25
  </div>
@@ -34,7 +36,7 @@
34
36
  <%= f.label :description %>
35
37
  <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
36
38
  </div>
37
- <div class="text-right">
39
+ <div class="form-group text-right">
38
40
  <%= f.submit "For Enter Press", class: "hide" %>
39
41
  <% if @query.persisted? %>
40
42
  <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
@@ -49,7 +51,7 @@
49
51
  <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
50
52
  <% words << pluralize(checks_count, "check") if checks_count > 0 %>
51
53
  <% if words.any? %>
52
- <div class="alert alert-info" style="margin-top: 10px; padding: 8px 12px;">
54
+ <div class="alert alert-info">
53
55
  Part of <%= words.to_sentence %>. Be careful when editing.
54
56
  </div>
55
57
  <% end %>
@@ -65,8 +67,8 @@
65
67
  </div>
66
68
 
67
69
  <script>
68
- <%= blazer_js_var "params", variable_params %>
69
- <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, v.preview_statement] }] %>
70
+ <%= blazer_js_var "variableParams", variable_params(@query) %>
71
+ <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
70
72
 
71
73
  var app = new Vue({
72
74
  el: "#app",
@@ -79,8 +81,11 @@
79
81
  editorHeight: "180px"
80
82
  },
81
83
  computed: {
82
- dataSourcePath: function() {
84
+ schemaPath: function() {
83
85
  return Routes.schema_queries_path({data_source: this.dataSource})
86
+ },
87
+ docsPath: function() {
88
+ return Routes.docs_queries_path({data_source: this.dataSource})
84
89
  }
85
90
  },
86
91
  methods: {
@@ -90,7 +95,7 @@
90
95
  this.error = false
91
96
  cancelAllQueries()
92
97
 
93
- var data = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
98
+ var data = {statement: this.getSQL(), data_source: $("#query_data_source").val(), variables: variableParams}
94
99
 
95
100
  var _this = this
96
101
 
@@ -127,7 +132,12 @@
127
132
  this.tablesXhr = $.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
128
133
  var newOptions = []
129
134
  for (var i = 0; i < data.length; i++) {
130
- newOptions.push({text: data[i], value: data[i]})
135
+ var table = data[i]
136
+ if (typeof table === "object") {
137
+ newOptions.push({text: table.table, value: table.value})
138
+ } else {
139
+ newOptions.push({text: table, value: table})
140
+ }
131
141
  }
132
142
  selectize.clearOptions()
133
143
  selectize.addOption(newOptions)
@@ -173,7 +183,7 @@
173
183
  editor.focus()
174
184
  },
175
185
  adjustHeight: function() {
176
- // http://stackoverflow.com/questions/11584061/
186
+ // https://stackoverflow.com/questions/11584061/
177
187
  var editor = this.editor
178
188
  var lines = editor.getSession().getScreenLength()
179
189
  if (lines < 9) {
@@ -0,0 +1,137 @@
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>
132
+
133
+ <% if @data_source.supports_cohort_analysis? %>
134
+ <h2>Cohort Analysis</h2>
135
+
136
+ <p>Create a query with the comment <code>/* cohort analysis */</code>. The result should have columns named <code>user_id</code> and <code>conversion_time</code> and optionally <code>cohort_time</code>.</p>
137
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  <div id="queries">
2
- <div id="header" style="margin-bottom: 20px;">
3
- <div class="pull-right">
2
+ <div id="header">
3
+ <div class="pull-right" style="line-height: 34px;">
4
4
  <% if blazer_user %>
5
5
  <%= link_to "All", root_path, class: !params[:filter] ? "active" : nil, style: "margin-right: 40px;" %>
6
6
 
@@ -10,6 +10,7 @@
10
10
 
11
11
  <%= link_to "Mine", root_path(filter: "mine"), class: params[:filter] == "mine" ? "active" : nil, style: "margin-right: 40px;" %>
12
12
  <% end %>
13
+
13
14
  <div class="btn-group">
14
15
  <%= link_to "New Query", new_query_path, class: "btn btn-info" %>
15
16
  <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -17,22 +18,26 @@
17
18
  <span class="sr-only">Toggle Dropdown</span>
18
19
  </button>
19
20
  <ul class="dropdown-menu">
20
- <li><%= link_to "Dashboards", dashboards_path %></li>
21
21
  <li><%= link_to "Checks", checks_path %></li>
22
+ <% if Blazer.uploads? %>
23
+ <li><%= link_to "Uploads", uploads_path %></li>
24
+ <% end %>
22
25
  <li role="separator" class="divider"></li>
23
26
  <li><%= link_to "New Dashboard", new_dashboard_path %></li>
24
27
  <li><%= link_to "New Check", new_check_path %></li>
25
28
  </ul>
26
29
  </div>
27
30
  </div>
28
- <input type="text" v-model="searchTerm" placeholder="Start typing a query or person" style="width: 300px; display: inline-block;" autofocus=true class="search form-control" />
31
+ <input type="text" v-model="searchTerm" placeholder="Start typing a query, dashboard, or person" style="width: 300px; display: inline-block;" v-focus class="search form-control" />
29
32
  </div>
30
33
 
31
34
  <table class="table">
32
35
  <thead>
33
36
  <tr>
34
37
  <th>Name</th>
35
- <th style="width: 20%; text-align: right;">Mastermind</th>
38
+ <% if Blazer.user_class %>
39
+ <th style="width: 20%; text-align: right;">Mastermind</th>
40
+ <% end%>
36
41
  </tr>
37
42
  </thead>
38
43
  <tbody class="list" v-cloak>
@@ -41,7 +46,9 @@
41
46
  <a :href="itemPath(query)" :class="{ dashboard: query.dashboard }">{{ query.name }}</a>
42
47
  <span class="vars">{{ query.vars }}</span>
43
48
  </td>
44
- <td class="creator">{{ query.creator }}</td>
49
+ <% if Blazer.user_class %>
50
+ <td class="creator">{{ query.creator }}</td>
51
+ <% end %>
45
52
  </tr>
46
53
  </tbody>
47
54
  </table>
@@ -67,7 +74,7 @@
67
74
  }
68
75
 
69
76
  var prepareQuery = function (str) {
70
- return str.toLowerCase().replace(/\W+/g, "")
77
+ return str.toLowerCase()
71
78
  }
72
79
 
73
80
  var app = new Vue({
@@ -147,6 +154,13 @@
147
154
  return Routes.query_path(item.to_param)
148
155
  }
149
156
  }
157
+ },
158
+ directives: {
159
+ focus: {
160
+ inserted: function (el) {
161
+ el.focus()
162
+ }
163
+ }
150
164
  }
151
165
  })
152
166
  </script>
@@ -6,52 +6,82 @@
6
6
  <% else %>
7
7
  <div class="alert alert-info">Can’t preview queries with variables...yet!</div>
8
8
  <% end %>
9
+ <% elsif @cohort_analysis %>
10
+ <% if @cohort_error %>
11
+ <div class="alert alert-info"><%= @cohort_error %></div>
12
+ <% else %>
13
+ <%= render partial: "cohorts" %>
14
+ <% end %>
9
15
  <% else %>
10
16
  <% unless @only_chart %>
11
- <% if @cached_at || @just_cached %>
12
- <p class="text-muted" style="float: right;">
13
- <% if @cached_at %>
14
- Cached <%= time_ago_in_words(@cached_at, include_seconds: true) %> ago
15
- <% elsif !params[:data_source] %>
16
- Cached just now
17
- <% if @data_source.cache_mode == "slow" %>
18
- (over <%= "%g" % @data_source.cache_slow_threshold %>s)
19
- <% end %>
20
- <% end %>
21
-
22
- <% if @query && !params[:data_source] %>
23
- <%= link_to "Refresh", refresh_query_path(@query, variable_params), method: :post %>
24
- <% end %>
25
- </p>
26
- <% end %>
27
- <p class="text-muted">
17
+ <%= render partial: "caching" %>
18
+ <p class="text-muted" style="margin-bottom: 10px;">
19
+ <% if @row_limit && @rows.size > @row_limit %>
20
+ First
21
+ <% @rows = @rows.first(@row_limit) %>
22
+ <% end %>
28
23
  <%= pluralize(@rows.size, "row") %>
29
24
 
30
25
  <% @checks.select(&:state).each do |check| %>
31
- &middot; <small class="check-state <%= check.state.parameterize("_") %>"><%= link_to check.state.upcase, edit_check_path(check) %></small>
26
+ &middot; <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= link_to check.state.upcase, edit_check_path(check) %></small>
32
27
  <% if check.try(:message) %>
33
28
  &middot; <%= check.message %>
34
29
  <% end %>
35
30
  <% end %>
31
+
32
+ <% if @query && @result.forecastable? && !params[:forecast] %>
33
+ &middot;
34
+ <%= link_to "Forecast", query_path(@query, params: {forecast: "t"}.merge(variable_params(@query))) %>
35
+ <% end %>
36
36
  </p>
37
37
  <% end %>
38
+ <% if @forecast_error %>
39
+ <div class="alert alert-danger"><%= @forecast_error %></div>
40
+ <% end %>
41
+ <% if @cohort_error %>
42
+ <div class="alert alert-info"><%= @cohort_error %></div>
43
+ <% end %>
38
44
  <% if @rows.any? %>
39
45
  <% values = @rows.first %>
40
46
  <% chart_id = SecureRandom.hex %>
41
47
  <% column_types = @result.column_types %>
42
48
  <% chart_type = @result.chart_type %>
43
- <% chart_options = {id: chart_id, min: nil} %>
49
+ <% chart_options = {id: chart_id} %>
50
+ <% if ["line", "line2"].include?(chart_type) %>
51
+ <% chart_options.merge!(min: nil) %>
52
+ <% end %>
53
+ <% if chart_type == "scatter" %>
54
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
55
+ <% elsif ["bar", "bar2"].include?(chart_type) %>
56
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
57
+ <% elsif chart_type != "pie" %>
58
+ <% if column_types.size == 2 || @forecast %>
59
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
60
+ <% else %>
61
+ <%# chartjs axis: 'x' has poor behavior with multiple series %>
62
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
63
+ <% end %>
64
+ <% end %>
44
65
  <% series_library = {} %>
45
66
  <% target_index = @columns.index { |k| k.downcase == "target" } %>
46
67
  <% if target_index %>
47
- <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: "#109618", pointBackgroundColor: "#109618", backgroundColor: "#109618"} %>
68
+ <% color = "#109618" %>
69
+ <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
70
+ <% end %>
71
+ <% if @forecast %>
72
+ <% color = "#54a3ee" %>
73
+ <% series_library[1] = {borderDash: [8], borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
48
74
  <% end %>
49
75
  <% if blazer_maps? && @markers.any? %>
50
- <div id="map" style="height: <%= @only_chart ? 300 : 500 %>px;"></div>
76
+ <% map_id = SecureRandom.hex %>
77
+ <%= content_tag :div, nil, id: map_id, style: "height: #{@only_chart ? 300 : 500}px;" %>
51
78
  <script>
52
- L.mapbox.accessToken = '<%= ENV["MAPBOX_ACCESS_TOKEN"] %>';
53
- var map = L.mapbox.map('map', 'ankane.ioo8nki0');
79
+ <%= blazer_js_var "mapboxAccessToken", Blazer.mapbox_access_token %>
54
80
  <%= blazer_js_var "markers", @markers %>
81
+ <%= blazer_js_var "mapId", map_id %>
82
+ L.mapbox.accessToken = mapboxAccessToken;
83
+ var map = L.mapbox.map(mapId)
84
+ .addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
55
85
  var featureLayer = L.mapbox.featureLayer().addTo(map);
56
86
  var geojson = [];
57
87
  for (var i = 0; i < markers.length; i++) {
@@ -76,11 +106,14 @@
76
106
  map.fitBounds(featureLayer.getBounds());
77
107
  </script>
78
108
  <% elsif chart_type == "line" %>
79
- <%= line_chart @columns[1..-1].each_with_index.map{ |k, i| {name: k, data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} }, chart_options %>
109
+ <% chart_data = @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} } %>
110
+ <%= line_chart chart_data, **chart_options %>
80
111
  <% elsif chart_type == "line2" %>
81
- <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: name, data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, chart_options %>
112
+ <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, **chart_options %>
113
+ <% elsif chart_type == "pie" %>
114
+ <%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, **chart_options %>
82
115
  <% elsif chart_type == "bar" %>
83
- <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: name, data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, id: chart_id %>
116
+ <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, **chart_options %>
84
117
  <% elsif chart_type == "bar2" %>
85
118
  <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
86
119
  <% labels = first_20.map { |r| r[0] }.uniq %>
@@ -90,9 +123,9 @@
90
123
  <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
91
124
  <% end %>
92
125
  <% end %>
93
- <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: name, data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, id: chart_id %>
126
+ <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, **chart_options %>
94
127
  <% elsif chart_type == "scatter" %>
95
- <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1] %>
128
+ <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
96
129
  <% elsif @only_chart %>
97
130
  <% if @rows.size == 1 && @rows.first.size == 1 %>
98
131
  <% v = @rows.first.first %>
@@ -111,8 +144,10 @@
111
144
  <div class="results-container">
112
145
  <% if @columns == ["QUERY PLAN"] %>
113
146
  <pre><code><%= @rows.map { |r| r[0] }.join("\n") %></code></pre>
147
+ <% elsif @columns == ["PLAN"] && @data_source.adapter == "druid" %>
148
+ <pre><code><%= @rows[0][0] %></code></pre>
114
149
  <% else %>
115
- <table class="table results-table" style="margin-bottom: 0;">
150
+ <table class="table results-table">
116
151
  <thead>
117
152
  <tr>
118
153
  <% @columns.each_with_index do |key, i| %>