blazer 2.2.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of blazer might be problematic. Click here for more details.

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 %>