blazer 2.2.6

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +310 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +1041 -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/Chart.js +14456 -0
  13. data/app/assets/javascripts/blazer/Sortable.js +1540 -0
  14. data/app/assets/javascripts/blazer/ace.js +6 -0
  15. data/app/assets/javascripts/blazer/ace/ace.js +21301 -0
  16. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1993 -0
  17. data/app/assets/javascripts/blazer/ace/mode-sql.js +110 -0
  18. data/app/assets/javascripts/blazer/ace/snippets/sql.js +40 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/text.js +14 -0
  20. data/app/assets/javascripts/blazer/ace/theme-twilight.js +116 -0
  21. data/app/assets/javascripts/blazer/application.js +81 -0
  22. data/app/assets/javascripts/blazer/bootstrap.js +2377 -0
  23. data/app/assets/javascripts/blazer/chartkick.js +2214 -0
  24. data/app/assets/javascripts/blazer/daterangepicker.js +1653 -0
  25. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  26. data/app/assets/javascripts/blazer/highlight.min.js +3 -0
  27. data/app/assets/javascripts/blazer/jquery-ujs.js +555 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10364 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  30. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1212 -0
  31. data/app/assets/javascripts/blazer/moment.js +3043 -0
  32. data/app/assets/javascripts/blazer/queries.js +110 -0
  33. data/app/assets/javascripts/blazer/routes.js +26 -0
  34. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  35. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  37. data/app/assets/javascripts/blazer/vue.js +10947 -0
  38. data/app/assets/stylesheets/blazer/application.css +234 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6756 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker.css +269 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  43. data/app/controllers/blazer/base_controller.rb +124 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +101 -0
  46. data/app/controllers/blazer/queries_controller.rb +347 -0
  47. data/app/helpers/blazer/base_helper.rb +43 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/mailers/blazer/slack_notifier.rb +79 -0
  50. data/app/models/blazer/audit.rb +6 -0
  51. data/app/models/blazer/check.rb +104 -0
  52. data/app/models/blazer/connection.rb +5 -0
  53. data/app/models/blazer/dashboard.rb +17 -0
  54. data/app/models/blazer/dashboard_query.rb +9 -0
  55. data/app/models/blazer/query.rb +40 -0
  56. data/app/models/blazer/record.rb +5 -0
  57. data/app/views/blazer/_nav.html.erb +15 -0
  58. data/app/views/blazer/_variables.html.erb +124 -0
  59. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  60. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  61. data/app/views/blazer/checks/_form.html.erb +79 -0
  62. data/app/views/blazer/checks/edit.html.erb +3 -0
  63. data/app/views/blazer/checks/index.html.erb +69 -0
  64. data/app/views/blazer/checks/new.html.erb +3 -0
  65. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  66. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  67. data/app/views/blazer/dashboards/new.html.erb +3 -0
  68. data/app/views/blazer/dashboards/show.html.erb +51 -0
  69. data/app/views/blazer/queries/_form.html.erb +250 -0
  70. data/app/views/blazer/queries/docs.html.erb +131 -0
  71. data/app/views/blazer/queries/edit.html.erb +2 -0
  72. data/app/views/blazer/queries/home.html.erb +163 -0
  73. data/app/views/blazer/queries/new.html.erb +2 -0
  74. data/app/views/blazer/queries/run.html.erb +198 -0
  75. data/app/views/blazer/queries/schema.html.erb +55 -0
  76. data/app/views/blazer/queries/show.html.erb +75 -0
  77. data/app/views/layouts/blazer/application.html.erb +24 -0
  78. data/config/routes.rb +20 -0
  79. data/lib/blazer.rb +231 -0
  80. data/lib/blazer/adapters/athena_adapter.rb +129 -0
  81. data/lib/blazer/adapters/base_adapter.rb +53 -0
  82. data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
  83. data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
  84. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  85. data/lib/blazer/adapters/druid_adapter.rb +67 -0
  86. data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
  87. data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
  88. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  89. data/lib/blazer/adapters/neo4j_adapter.rb +47 -0
  90. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  91. data/lib/blazer/adapters/salesforce_adapter.rb +45 -0
  92. data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
  93. data/lib/blazer/adapters/soda_adapter.rb +96 -0
  94. data/lib/blazer/adapters/sql_adapter.rb +221 -0
  95. data/lib/blazer/data_source.rb +195 -0
  96. data/lib/blazer/detect_anomalies.R +19 -0
  97. data/lib/blazer/engine.rb +43 -0
  98. data/lib/blazer/result.rb +218 -0
  99. data/lib/blazer/run_statement.rb +40 -0
  100. data/lib/blazer/run_statement_job.rb +18 -0
  101. data/lib/blazer/version.rb +3 -0
  102. data/lib/generators/blazer/install_generator.rb +22 -0
  103. data/lib/generators/blazer/templates/config.yml.tt +73 -0
  104. data/lib/generators/blazer/templates/install.rb.tt +46 -0
  105. data/lib/tasks/blazer.rake +11 -0
  106. metadata +231 -0
@@ -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>
@@ -0,0 +1,2 @@
1
+ <% blazer_title "Edit - #{@query.name}" %>
2
+ <%= render partial: "form" %>
@@ -0,0 +1,163 @@
1
+ <div id="queries">
2
+ <div id="header">
3
+ <div class="pull-right" style="line-height: 34px;">
4
+ <% if blazer_user %>
5
+ <%= link_to "All", root_path, class: !params[:filter] ? "active" : nil, style: "margin-right: 40px;" %>
6
+
7
+ <% if Blazer.audit %>
8
+ <%= link_to "Viewed", root_path(filter: "viewed"), class: params[:filter] == "viewed" ? "active" : nil, style: "margin-right: 40px;" %>
9
+ <% end %>
10
+
11
+ <%= link_to "Mine", root_path(filter: "mine"), class: params[:filter] == "mine" ? "active" : nil, style: "margin-right: 40px;" %>
12
+ <% end %>
13
+
14
+ <div class="btn-group">
15
+ <%= link_to "New Query", new_query_path, class: "btn btn-info" %>
16
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
17
+ <span class="caret"></span>
18
+ <span class="sr-only">Toggle Dropdown</span>
19
+ </button>
20
+ <ul class="dropdown-menu">
21
+ <li><%= link_to "Checks", checks_path %></li>
22
+ <li role="separator" class="divider"></li>
23
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
24
+ <li><%= link_to "New Check", new_check_path %></li>
25
+ </ul>
26
+ </div>
27
+ </div>
28
+ <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
+ </div>
30
+
31
+ <table class="table">
32
+ <thead>
33
+ <tr>
34
+ <th>Name</th>
35
+ <% if Blazer.user_class %>
36
+ <th style="width: 20%; text-align: right;">Mastermind</th>
37
+ <% end%>
38
+ </tr>
39
+ </thead>
40
+ <tbody class="list" v-cloak>
41
+ <tr v-for="query in visibleItems">
42
+ <td>
43
+ <a :href="itemPath(query)" :class="{ dashboard: query.dashboard }">{{ query.name }}</a>
44
+ <span class="vars">{{ query.vars }}</span>
45
+ </td>
46
+ <% if Blazer.user_class %>
47
+ <td class="creator">{{ query.creator }}</td>
48
+ <% end %>
49
+ </tr>
50
+ </tbody>
51
+ </table>
52
+
53
+ <p v-if="more" class="text-muted">Loading...</p>
54
+ </div>
55
+
56
+ <script>
57
+ <%= blazer_js_var "dashboards", @dashboards %>
58
+ <%= blazer_js_var "queries", @queries %>
59
+ <%= blazer_js_var "more", @more %>
60
+
61
+ var prepareSearch = function (list) {
62
+ var i, q, searchStr
63
+ for (i = 0; i < list.length; i++) {
64
+ q = list[i]
65
+ searchStr = q.name + q.creator
66
+ if (q.creator === "You") {
67
+ searchStr += "mine me"
68
+ }
69
+ q.searchStr = prepareQuery(searchStr)
70
+ }
71
+ }
72
+
73
+ var prepareQuery = function (str) {
74
+ return str.toLowerCase()
75
+ }
76
+
77
+ var app = new Vue({
78
+ el: "#queries",
79
+ data: {
80
+ searchTerm: "",
81
+ more: more,
82
+ updateCounter: 0
83
+ },
84
+ created: function() {
85
+ this.listItems = dashboards.concat(queries)
86
+
87
+ prepareSearch(this.listItems)
88
+
89
+ this.queryIds = {}
90
+ for (i = 0; i < queries.length; i++) {
91
+ this.queryIds[queries[i].id] = true
92
+ }
93
+
94
+ if (this.more) {
95
+ var _this = this
96
+
97
+ $.getJSON(Routes.queries_path(), function (data) {
98
+ var i, j, newValues, val, size = 500;
99
+
100
+ var newValues = []
101
+ for (j = 0; j < data.length; j++) {
102
+ val = data[j]
103
+ if (val && !_this.queryIds[val.id]) {
104
+ newValues.push(val)
105
+ }
106
+ }
107
+
108
+ prepareSearch(newValues)
109
+
110
+ _this.listItems = _this.listItems.concat(newValues)
111
+ _this.more = false
112
+ // hack to get to update
113
+ _this.updateCounter++
114
+ })
115
+ }
116
+ },
117
+ computed: {
118
+ visibleItems: function () {
119
+ // hack to get to update
120
+ this.updateCounter
121
+
122
+ var pageSize = 200
123
+ var q, i
124
+
125
+ if (this.searchTerm.length > 0) {
126
+ var term = prepareQuery(this.searchTerm)
127
+ var items = []
128
+ var fuzzyItems = []
129
+ for (i = 0; i < this.listItems.length; i++) {
130
+ q = this.listItems[i]
131
+ if (q.searchStr.indexOf(term) !== -1) {
132
+ items.push(q)
133
+ if (items.length == pageSize) {
134
+ break
135
+ }
136
+ } else if (fuzzysearch(term, q.searchStr)) {
137
+ fuzzyItems.push(q)
138
+ }
139
+ }
140
+ return items.concat(fuzzyItems).slice(0, pageSize)
141
+ } else {
142
+ return this.listItems.slice(0, pageSize)
143
+ }
144
+ }
145
+ },
146
+ methods: {
147
+ itemPath: function (item) {
148
+ if (item.dashboard) {
149
+ return Routes.dashboard_path(item.to_param)
150
+ } else {
151
+ return Routes.query_path(item.to_param)
152
+ }
153
+ }
154
+ },
155
+ directives: {
156
+ focus: {
157
+ inserted: function (el) {
158
+ el.focus()
159
+ }
160
+ }
161
+ }
162
+ })
163
+ </script>
@@ -0,0 +1,2 @@
1
+ <% blazer_title "New Query" %>
2
+ <%= render partial: "form" %>
@@ -0,0 +1,198 @@
1
+ <% if @error %>
2
+ <div class="alert alert-danger"><%= @error.first(200) %></div>
3
+ <% elsif !@success %>
4
+ <% if @only_chart %>
5
+ <p class="text-muted">Select variables</p>
6
+ <% else %>
7
+ <div class="alert alert-info">Can’t preview queries with variables...yet!</div>
8
+ <% end %>
9
+ <% else %>
10
+ <% 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[:query_id] %>
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[:query_id] %>
23
+ <%= link_to "Refresh", refresh_query_path(@query, variable_params(@query)), method: :post %>
24
+ <% end %>
25
+ </p>
26
+ <% end %>
27
+ <p class="text-muted" style="margin-bottom: 10px;">
28
+ <%= pluralize(@rows.size, "row") %>
29
+
30
+ <% @checks.select(&:state).each do |check| %>
31
+ &middot; <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= link_to check.state.upcase, edit_check_path(check) %></small>
32
+ <% if check.try(:message) %>
33
+ &middot; <%= check.message %>
34
+ <% end %>
35
+ <% end %>
36
+
37
+ <% if @query && @result.forecastable? && !params[:forecast] %>
38
+ &middot;
39
+ <%= link_to "Forecast", query_path(@query, {forecast: "t"}.merge(variable_params(@query))) %>
40
+ <% end %>
41
+ </p>
42
+ <% end %>
43
+ <% if @forecast_error %>
44
+ <div class="alert alert-danger"><%= @forecast_error %></div>
45
+ <% end %>
46
+ <% if @rows.any? %>
47
+ <% values = @rows.first %>
48
+ <% chart_id = SecureRandom.hex %>
49
+ <% column_types = @result.column_types %>
50
+ <% chart_type = @result.chart_type %>
51
+ <% chart_options = {id: chart_id} %>
52
+ <% if ["line", "line2"].include?(chart_type) %>
53
+ <% chart_options.merge!(min: nil) %>
54
+ <% end %>
55
+ <% if chart_type == "scatter" %>
56
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
57
+ <% elsif ["bar", "bar2"].include?(chart_type) %>
58
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
59
+ <% elsif chart_type != "pie" %>
60
+ <% if column_types.size == 2 || @forecast %>
61
+ <% chart_options.merge!(library: {tooltips: {intersect: false, axis: 'x'}}) %>
62
+ <% else %>
63
+ <%# chartjs axis: 'x' has poor behavior with multiple series %>
64
+ <% chart_options.merge!(library: {tooltips: {intersect: false}}) %>
65
+ <% end %>
66
+ <% end %>
67
+ <% series_library = {} %>
68
+ <% target_index = @columns.index { |k| k.downcase == "target" } %>
69
+ <% if target_index %>
70
+ <% color = "#109618" %>
71
+ <% series_library[target_index - 1] = {pointStyle: "line", hitRadius: 5, borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
72
+ <% end %>
73
+ <% if @forecast %>
74
+ <% color = "#54a3ee" %>
75
+ <% series_library[1] = {borderDash: [8], borderColor: color, pointBackgroundColor: color, backgroundColor: color, pointHoverBackgroundColor: color} %>
76
+ <% end %>
77
+ <% if blazer_maps? && @markers.any? %>
78
+ <div id="map" style="height: <%= @only_chart ? 300 : 500 %>px;"></div>
79
+ <script>
80
+ <%= blazer_js_var "mapboxAccessToken", Blazer.mapbox_access_token %>
81
+ <%= blazer_js_var "markers", @markers %>
82
+ L.mapbox.accessToken = mapboxAccessToken;
83
+ var map = L.mapbox.map('map')
84
+ .addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
85
+ var featureLayer = L.mapbox.featureLayer().addTo(map);
86
+ var geojson = [];
87
+ for (var i = 0; i < markers.length; i++) {
88
+ var marker = markers[i];
89
+ geojson.push({
90
+ type: 'Feature',
91
+ geometry: {
92
+ type: 'Point',
93
+ coordinates: [
94
+ marker.longitude,
95
+ marker.latitude
96
+ ]
97
+ },
98
+ properties: {
99
+ description: marker.title,
100
+ 'marker-color': '#f86767',
101
+ 'marker-size': 'medium'
102
+ }
103
+ });
104
+ }
105
+ featureLayer.setGeoJSON(geojson);
106
+ map.fitBounds(featureLayer.getBounds());
107
+ </script>
108
+ <% elsif chart_type == "line" %>
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 %>
111
+ <% elsif chart_type == "line2" %>
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 %>
115
+ <% elsif chart_type == "bar" %>
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 %>
117
+ <% elsif chart_type == "bar2" %>
118
+ <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
119
+ <% labels = first_20.map { |r| r[0] }.uniq %>
120
+ <% series = first_20.map { |r| r[1] }.uniq %>
121
+ <% labels.each do |l| %>
122
+ <% series.each do |s| %>
123
+ <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
124
+ <% end %>
125
+ <% end %>
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 %>
127
+ <% elsif chart_type == "scatter" %>
128
+ <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
129
+ <% elsif @only_chart %>
130
+ <% if @rows.size == 1 && @rows.first.size == 1 %>
131
+ <% v = @rows.first.first %>
132
+ <% if v.is_a?(String) && v == "" %>
133
+ <div class="text-muted">empty string</div>
134
+ <% else %>
135
+ <p style="font-size: 160px;"><%= blazer_format_value(@columns.first, v) %></p>
136
+ <% end %>
137
+ <% else %>
138
+ <% @no_chart = true %>
139
+ <% end %>
140
+ <% end %>
141
+
142
+ <% unless @only_chart && !@no_chart %>
143
+ <% header_width = 100 / @columns.size.to_f %>
144
+ <div class="results-container">
145
+ <% if @columns == ["QUERY PLAN"] %>
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>
149
+ <% else %>
150
+ <table class="table results-table" style="margin-bottom: 0;">
151
+ <thead>
152
+ <tr>
153
+ <% @columns.each_with_index do |key, i| %>
154
+ <% type = @column_types[i] %>
155
+ <th style="width: <%= header_width %>%;" data-sort="<%= type %>">
156
+ <div style="min-width: <%= @min_width_types.include?(i) ? 180 : 60 %>px;">
157
+ <%= key %>
158
+ </div>
159
+ </th>
160
+ <% end %>
161
+ </tr>
162
+ </thead>
163
+ <tbody>
164
+ <% @rows.each do |row| %>
165
+ <tr>
166
+ <% row.each_with_index do |v, i| %>
167
+ <% k = @columns[i] %>
168
+ <td>
169
+ <% if v.is_a?(Time) %>
170
+ <% v = blazer_time_value(@data_source, k, v) %>
171
+ <% end %>
172
+
173
+ <% unless v.nil? %>
174
+ <% if v.is_a?(String) && v == "" %>
175
+ <div class="text-muted">empty string</div>
176
+ <% elsif @linked_columns[k] %>
177
+ <%= link_to blazer_format_value(k, v), @linked_columns[k].gsub("{value}", u(v.to_s)), target: "_blank" %>
178
+ <% else %>
179
+ <%= blazer_format_value(k, v) %>
180
+ <% end %>
181
+ <% end %>
182
+
183
+ <% if v2 = (@boom[k] || {})[v.nil? ? v : v.to_s] %>
184
+ <div class="text-muted"><%= v2 %></div>
185
+ <% end %>
186
+ </td>
187
+ <% end %>
188
+ </tr>
189
+ <% end %>
190
+ </tbody>
191
+ </table>
192
+ <% end %>
193
+ </div>
194
+ <% end %>
195
+ <% elsif @only_chart %>
196
+ <p class="text-muted">No rows</p>
197
+ <% end %>
198
+ <% end %>