finery 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +426 -0
  3. data/CONTRIBUTING.md +49 -0
  4. data/LICENSE.txt +25 -0
  5. data/README.md +1144 -0
  6. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  7. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  11. data/app/assets/images/blazer/favicon.png +0 -0
  12. data/app/assets/javascripts/blazer/Sortable.js +3709 -0
  13. data/app/assets/javascripts/blazer/ace/ace.js +19630 -0
  14. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +1981 -0
  15. data/app/assets/javascripts/blazer/ace/mode-sql.js +215 -0
  16. data/app/assets/javascripts/blazer/ace/snippets/sql.js +16 -0
  17. data/app/assets/javascripts/blazer/ace/snippets/text.js +9 -0
  18. data/app/assets/javascripts/blazer/ace/theme-twilight.js +18 -0
  19. data/app/assets/javascripts/blazer/ace.js +6 -0
  20. data/app/assets/javascripts/blazer/application.js +87 -0
  21. data/app/assets/javascripts/blazer/bootstrap.js +2580 -0
  22. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  23. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  24. data/app/assets/javascripts/blazer/chartjs-plugin-annotation.min.js +7 -0
  25. data/app/assets/javascripts/blazer/chartkick.js +2570 -0
  26. data/app/assets/javascripts/blazer/daterangepicker.js +1578 -0
  27. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  28. data/app/assets/javascripts/blazer/highlight.min.js +466 -0
  29. data/app/assets/javascripts/blazer/jquery.js +10872 -0
  30. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +325 -0
  31. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  32. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +1548 -0
  33. data/app/assets/javascripts/blazer/moment.js +5685 -0
  34. data/app/assets/javascripts/blazer/queries.js +130 -0
  35. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  36. data/app/assets/javascripts/blazer/routes.js +26 -0
  37. data/app/assets/javascripts/blazer/selectize.js +3891 -0
  38. data/app/assets/javascripts/blazer/stupidtable-custom-settings.js +13 -0
  39. data/app/assets/javascripts/blazer/stupidtable.js +281 -0
  40. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  41. data/app/assets/stylesheets/blazer/application.css +243 -0
  42. data/app/assets/stylesheets/blazer/bootstrap-propshaft.css +10 -0
  43. data/app/assets/stylesheets/blazer/bootstrap-sprockets.css.erb +10 -0
  44. data/app/assets/stylesheets/blazer/bootstrap.css +6828 -0
  45. data/app/assets/stylesheets/blazer/daterangepicker.css +410 -0
  46. data/app/assets/stylesheets/blazer/github.css +125 -0
  47. data/app/assets/stylesheets/blazer/selectize.css +403 -0
  48. data/app/controllers/blazer/base_controller.rb +133 -0
  49. data/app/controllers/blazer/checks_controller.rb +56 -0
  50. data/app/controllers/blazer/dashboards_controller.rb +99 -0
  51. data/app/controllers/blazer/queries_controller.rb +468 -0
  52. data/app/controllers/blazer/uploads_controller.rb +147 -0
  53. data/app/helpers/blazer/base_helper.rb +83 -0
  54. data/app/models/blazer/audit.rb +6 -0
  55. data/app/models/blazer/check.rb +104 -0
  56. data/app/models/blazer/connection.rb +5 -0
  57. data/app/models/blazer/dashboard.rb +17 -0
  58. data/app/models/blazer/dashboard_query.rb +9 -0
  59. data/app/models/blazer/query.rb +42 -0
  60. data/app/models/blazer/record.rb +5 -0
  61. data/app/models/blazer/upload.rb +11 -0
  62. data/app/models/blazer/uploads_connection.rb +7 -0
  63. data/app/views/blazer/_nav.html.erb +18 -0
  64. data/app/views/blazer/_variables.html.erb +127 -0
  65. data/app/views/blazer/check_mailer/failing_checks.html.erb +7 -0
  66. data/app/views/blazer/check_mailer/state_change.html.erb +48 -0
  67. data/app/views/blazer/checks/_form.html.erb +79 -0
  68. data/app/views/blazer/checks/edit.html.erb +3 -0
  69. data/app/views/blazer/checks/index.html.erb +72 -0
  70. data/app/views/blazer/checks/new.html.erb +3 -0
  71. data/app/views/blazer/dashboards/_form.html.erb +82 -0
  72. data/app/views/blazer/dashboards/edit.html.erb +3 -0
  73. data/app/views/blazer/dashboards/new.html.erb +3 -0
  74. data/app/views/blazer/dashboards/show.html.erb +53 -0
  75. data/app/views/blazer/queries/_caching.html.erb +16 -0
  76. data/app/views/blazer/queries/_cohorts.html.erb +48 -0
  77. data/app/views/blazer/queries/_form.html.erb +255 -0
  78. data/app/views/blazer/queries/docs.html.erb +147 -0
  79. data/app/views/blazer/queries/edit.html.erb +2 -0
  80. data/app/views/blazer/queries/home.html.erb +169 -0
  81. data/app/views/blazer/queries/new.html.erb +2 -0
  82. data/app/views/blazer/queries/run.html.erb +188 -0
  83. data/app/views/blazer/queries/schema.html.erb +55 -0
  84. data/app/views/blazer/queries/show.html.erb +72 -0
  85. data/app/views/blazer/uploads/_form.html.erb +27 -0
  86. data/app/views/blazer/uploads/edit.html.erb +3 -0
  87. data/app/views/blazer/uploads/index.html.erb +55 -0
  88. data/app/views/blazer/uploads/new.html.erb +3 -0
  89. data/app/views/layouts/blazer/application.html.erb +25 -0
  90. data/config/routes.rb +25 -0
  91. data/lib/blazer/adapters/athena_adapter.rb +182 -0
  92. data/lib/blazer/adapters/base_adapter.rb +76 -0
  93. data/lib/blazer/adapters/bigquery_adapter.rb +79 -0
  94. data/lib/blazer/adapters/cassandra_adapter.rb +70 -0
  95. data/lib/blazer/adapters/clickhouse_adapter.rb +84 -0
  96. data/lib/blazer/adapters/drill_adapter.rb +38 -0
  97. data/lib/blazer/adapters/druid_adapter.rb +102 -0
  98. data/lib/blazer/adapters/elasticsearch_adapter.rb +61 -0
  99. data/lib/blazer/adapters/hive_adapter.rb +55 -0
  100. data/lib/blazer/adapters/ignite_adapter.rb +64 -0
  101. data/lib/blazer/adapters/influxdb_adapter.rb +57 -0
  102. data/lib/blazer/adapters/neo4j_adapter.rb +62 -0
  103. data/lib/blazer/adapters/opensearch_adapter.rb +52 -0
  104. data/lib/blazer/adapters/presto_adapter.rb +54 -0
  105. data/lib/blazer/adapters/salesforce_adapter.rb +50 -0
  106. data/lib/blazer/adapters/snowflake_adapter.rb +82 -0
  107. data/lib/blazer/adapters/soda_adapter.rb +105 -0
  108. data/lib/blazer/adapters/spark_adapter.rb +14 -0
  109. data/lib/blazer/adapters/sql_adapter.rb +324 -0
  110. data/lib/blazer/adapters.rb +18 -0
  111. data/lib/blazer/annotations.rb +47 -0
  112. data/lib/blazer/anomaly_detectors.rb +22 -0
  113. data/lib/blazer/check_mailer.rb +27 -0
  114. data/lib/blazer/data_source.rb +270 -0
  115. data/lib/blazer/engine.rb +42 -0
  116. data/lib/blazer/forecasters.rb +7 -0
  117. data/lib/blazer/result.rb +178 -0
  118. data/lib/blazer/result_cache.rb +71 -0
  119. data/lib/blazer/run_statement.rb +44 -0
  120. data/lib/blazer/run_statement_job.rb +20 -0
  121. data/lib/blazer/slack_notifier.rb +94 -0
  122. data/lib/blazer/statement.rb +77 -0
  123. data/lib/blazer/version.rb +3 -0
  124. data/lib/blazer.rb +286 -0
  125. data/lib/finery.rb +3 -0
  126. data/lib/generators/blazer/install_generator.rb +22 -0
  127. data/lib/generators/blazer/templates/config.yml.tt +83 -0
  128. data/lib/generators/blazer/templates/install.rb.tt +47 -0
  129. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  130. data/lib/generators/blazer/uploads_generator.rb +18 -0
  131. data/lib/tasks/blazer.rake +20 -0
  132. data/lib/tasks/finery.rake +20 -0
  133. data/licenses/LICENSE-ace.txt +24 -0
  134. data/licenses/LICENSE-bootstrap.txt +21 -0
  135. data/licenses/LICENSE-chart.js.txt +9 -0
  136. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  137. data/licenses/LICENSE-chartkick.js.txt +22 -0
  138. data/licenses/LICENSE-date-fns.txt +21 -0
  139. data/licenses/LICENSE-daterangepicker.txt +21 -0
  140. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  141. data/licenses/LICENSE-highlight.js.txt +29 -0
  142. data/licenses/LICENSE-jquery.txt +20 -0
  143. data/licenses/LICENSE-kurkle-color.txt +9 -0
  144. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  145. data/licenses/LICENSE-moment-timezone.txt +20 -0
  146. data/licenses/LICENSE-moment.txt +22 -0
  147. data/licenses/LICENSE-rails-ujs.txt +20 -0
  148. data/licenses/LICENSE-selectize.txt +202 -0
  149. data/licenses/LICENSE-sortable.txt +21 -0
  150. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  151. data/licenses/LICENSE-stupidtable.txt +19 -0
  152. data/licenses/LICENSE-vue.txt +21 -0
  153. metadata +250 -0
@@ -0,0 +1,53 @@
1
+ <% blazer_title @dashboard.name %>
2
+
3
+ <div class="topbar">
4
+ <div class="container">
5
+ <div class="row" style="padding-top: 13px;">
6
+ <div class="col-sm-9">
7
+ <%= render partial: "blazer/nav" %>
8
+ <h3 style="line-height: 34px; display: inline; margin-left: 5px;">
9
+ <%= @dashboard.name %>
10
+ </h3>
11
+ </div>
12
+ <div class="col-sm-3 text-right">
13
+ <%= link_to "Edit", edit_dashboard_path(@dashboard, params: variable_params(@dashboard)), class: "btn btn-info" %>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <div style="margin-bottom: 60px;"></div>
20
+
21
+ <% if @data_sources.any? { |ds| ds.cache_mode != "off" } %>
22
+ <p class="text-muted" style="float: right;">
23
+ Some queries may be cached
24
+ <%= link_to "Refresh", refresh_dashboard_path(@dashboard, params: variable_params(@dashboard)), method: :post %>
25
+ </p>
26
+ <% end %>
27
+
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 %>
33
+
34
+ <% @queries.each_with_index do |query, i| %>
35
+ <div class="chart-container">
36
+ <h4><%= link_to query.friendly_name, query_path(query, params: variable_params(query)), target: "_blank" %></h4>
37
+ <div id="chart-<%= i %>" class="chart">
38
+ <p class="text-muted">Loading...</p>
39
+ </div>
40
+ </div>
41
+ <script>
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 %>
45
+
46
+ runQuery(data, function (data) {
47
+ $("#chart-<%= i %>").html(data)
48
+ $("#chart-<%= i %> table").stupidtable(stupidtableCustomSettings)
49
+ }, function (message) {
50
+ $("#chart-<%= i %>").addClass("query-error").html(message)
51
+ });
52
+ </script>
53
+ <% end %>
@@ -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 %>
@@ -0,0 +1,255 @@
1
+ <% if @query.errors.any? %>
2
+ <div class="alert alert-danger"><%= @query.errors.full_messages.first %></div>
3
+ <% end %>
4
+
5
+ <% @variable_params = @query.persisted? ? variable_params(@query) : nested_variable_params(@query) %>
6
+
7
+ <div id="app" v-cloak>
8
+ <%= form_for @query, url: (@query.persisted? ? query_path(@query, params: @variable_params) : queries_path(params: @variable_params)), html: {autocomplete: "off"} do |f| %>
9
+ <div class="row">
10
+ <div id="statement-box" class="col-xs-8">
11
+ <div class= "form-group">
12
+ <%= f.hidden_field :statement %>
13
+ <div id="editor-container">
14
+ <div id="editor" :style="{ height: editorHeight }"><%= @query.statement %></div>
15
+ </div>
16
+ </div>
17
+ <div class="form-group text-right" style="margin-bottom: 8px;">
18
+ <div class="pull-left" style="margin-top: 8px;">
19
+ <%= link_to "Back", :back %>
20
+ <a :href="docsPath" target="_blank" style="margin-left: 40px;">Docs</a>
21
+ <a :href="schemaPath" target="_blank" style="margin-left: 40px;">Schema</a>
22
+ </div>
23
+
24
+ <%= f.select :data_source, Blazer.data_sources.map { |_, ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size <= 1), style: "width: 140px;" %>
25
+ <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
26
+ <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
27
+ </div>
28
+ <a v-on:click="run" v-if="!running" class="btn btn-info" style="vertical-align: top; width: 70px;">Run</a>
29
+ <a v-on:click="cancel" v-if="running" class="btn btn-danger" style="vertical-align: top; width: 70px;">Cancel</a>
30
+ </div>
31
+ </div>
32
+ <div class="col-xs-4">
33
+ <div class="form-group">
34
+ <%= f.label :name %>
35
+ <%= f.text_field :name, class: "form-control" %>
36
+ </div>
37
+ <div class="form-group">
38
+ <%= f.label :description %>
39
+ <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
40
+ </div>
41
+ <div class="form-group text-right">
42
+ <%= f.submit "For Enter Press", class: "hide" %>
43
+ <% if @query.persisted? %>
44
+ <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
45
+ <%= f.submit "Fork", class: "btn btn-info" %>
46
+ <% end %>
47
+ <%= f.submit @query.persisted? ? "Update" : "Create", class: "btn btn-success" %>
48
+ </div>
49
+ <% if @query.persisted? %>
50
+ <% dashboards_count = @query.dashboards.count %>
51
+ <% checks_count = @query.checks.count %>
52
+ <% words = [] %>
53
+ <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
54
+ <% words << pluralize(checks_count, "check") if checks_count > 0 %>
55
+ <% if words.any? %>
56
+ <div class="alert alert-info">
57
+ Part of <%= words.to_sentence %>. Be careful when editing.
58
+ </div>
59
+ <% end %>
60
+ <% end %>
61
+ </div>
62
+ </div>
63
+ <% end %>
64
+
65
+ <div id="results">
66
+ <p class="text-muted" v-if="running">Loading...</p>
67
+ <div id="results-html" v-if="!running" :class="{ 'query-error': error }"></div>
68
+ </div>
69
+ </div>
70
+
71
+ <script>
72
+ <%= blazer_js_var "variableParams", @variable_params %>
73
+ <%= blazer_js_var "previewStatement", Blazer.data_sources.to_h { |k, v| [k, (v.preview_statement rescue "")] } %>
74
+
75
+ var app = Vue.createApp({
76
+ data: function() {
77
+ return {
78
+ running: false,
79
+ results: "",
80
+ error: false,
81
+ dataSource: "",
82
+ selectize: null,
83
+ editorHeight: "180px"
84
+ }
85
+ },
86
+ computed: {
87
+ schemaPath: function() {
88
+ return Routes.schema_queries_path({data_source: this.dataSource})
89
+ },
90
+ docsPath: function() {
91
+ return Routes.docs_queries_path({data_source: this.dataSource})
92
+ }
93
+ },
94
+ methods: {
95
+ run: function(e) {
96
+ this.running = true
97
+ this.results = ""
98
+ this.error = false
99
+ cancelAllQueries()
100
+
101
+ var data = {statement: this.getSQL(), data_source: $("#query_data_source").val(), variables: variableParams}
102
+
103
+ var _this = this
104
+
105
+ runQuery(data, function (data) {
106
+ _this.running = false
107
+ _this.showResults(data)
108
+
109
+ errorLine = _this.getErrorLine()
110
+ if (errorLine) {
111
+ editor.getSession().addGutterDecoration(errorLine - 1, "error")
112
+ editor.scrollToLine(errorLine, true, true, function () {})
113
+ editor.gotoLine(errorLine, 0, true)
114
+ editor.focus()
115
+ }
116
+ }, function (data) {
117
+ _this.running = false
118
+ _this.error = true
119
+ _this.showResults(data)
120
+ })
121
+ },
122
+ cancel: function(e) {
123
+ this.running = false
124
+ cancelAllQueries()
125
+ },
126
+ updateDataSource: function(dataSource) {
127
+ this.dataSource = dataSource
128
+ var selectize = this.selectize
129
+ selectize.clearOptions()
130
+
131
+ if (this.tablesXhr) {
132
+ this.tablesXhr.abort()
133
+ }
134
+
135
+ this.tablesXhr = $.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
136
+ var newOptions = []
137
+ for (var i = 0; i < data.length; i++) {
138
+ var table = data[i]
139
+ if (typeof table === "object") {
140
+ newOptions.push({text: table.table, value: table.value})
141
+ } else {
142
+ newOptions.push({text: table, value: table})
143
+ }
144
+ }
145
+ selectize.clearOptions()
146
+ selectize.addOption(newOptions)
147
+ selectize.refreshOptions(false)
148
+ })
149
+ },
150
+ showEditor: function() {
151
+ var _this = this
152
+
153
+ editor = ace.edit("editor")
154
+ editor.setTheme("ace/theme/twilight")
155
+ editor.getSession().setMode("ace/mode/sql")
156
+ editor.setOptions({
157
+ enableBasicAutocompletion: false,
158
+ enableSnippets: false,
159
+ enableLiveAutocompletion: false,
160
+ highlightActiveLine: false,
161
+ fontSize: 12,
162
+ minLines: 10
163
+ })
164
+ editor.renderer.setShowGutter(true)
165
+ editor.renderer.setPrintMarginColumn(false)
166
+ editor.renderer.setPadding(10)
167
+ editor.getSession().setUseWrapMode(true)
168
+ editor.commands.addCommand({
169
+ name: "run",
170
+ bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
171
+ exec: function(editor) {
172
+ _this.run()
173
+ },
174
+ readOnly: false // false if this command should not apply in readOnly mode
175
+ })
176
+ // fix command+L
177
+ editor.commands.removeCommands(["gotoline", "find"])
178
+
179
+ this.editor = editor
180
+
181
+ editor.getSession().on("change", function () {
182
+ $("#query_statement").val(editor.getValue())
183
+ _this.adjustHeight()
184
+ })
185
+ this.adjustHeight()
186
+ editor.focus()
187
+ },
188
+ adjustHeight: function() {
189
+ // https://stackoverflow.com/questions/11584061/
190
+ var editor = this.editor
191
+ var lines = editor.getSession().getScreenLength()
192
+ if (lines < 9) {
193
+ lines = 9
194
+ }
195
+
196
+ this.editorHeight = ((lines + 1) * 16).toString() + "px"
197
+
198
+ Vue.nextTick(function () {
199
+ editor.resize()
200
+ })
201
+ },
202
+ getSQL: function() {
203
+ var selectedText = editor.getSelectedText()
204
+ var text = selectedText.length < 10 ? editor.getValue() : selectedText
205
+ return text.replace(/\n/g, "\r\n")
206
+ },
207
+ getErrorLine: function() {
208
+ var editor = this.editor
209
+ var errorLine = this.results.substring(0, 100).includes("alert-danger") && /LINE (\d+)/g.exec(this.results)
210
+
211
+ if (errorLine) {
212
+ errorLine = parseInt(errorLine[1], 10)
213
+ if (editor.getSelectedText().length >= 10) {
214
+ errorLine += editor.getSelectionRange().start.row
215
+ }
216
+ return errorLine
217
+ }
218
+ },
219
+ showResults(data) {
220
+ // can't do it the Vue way due to script tags in results
221
+ // this.results = data
222
+
223
+ Vue.nextTick(function () {
224
+ $("#results-html").html(data)
225
+ })
226
+ }
227
+ },
228
+ mounted: function() {
229
+ var _this = this
230
+
231
+ var $select = $("#table_names").selectize({})
232
+ var selectize = $select[0].selectize
233
+ selectize.on("change", function(val) {
234
+ editor.setValue(previewStatement[_this.dataSource].replace("{table}", val), 1)
235
+ _this.run()
236
+ selectize.clear(true)
237
+ selectize.blur()
238
+ })
239
+ this.selectize = selectize
240
+
241
+ this.updateDataSource($("#query_data_source").val())
242
+
243
+ var $dsSelect = $("#query_data_source").selectize({})
244
+ var dsSelectize = $dsSelect[0].selectize
245
+ dsSelectize.on("change", function(val) {
246
+ _this.updateDataSource(val)
247
+ dsSelectize.blur()
248
+ })
249
+
250
+ this.showEditor()
251
+ }
252
+ })
253
+ app.config.compilerOptions.whitespace = "preserve"
254
+ app.mount("#app")
255
+ </script>
@@ -0,0 +1,147 @@
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
+ <tr>
129
+ <td>Area Map</td>
130
+ <td>
131
+ Named <code>geojson</code>
132
+ <% if !Blazer.maps? %>
133
+ <br />
134
+ <strong>Needs configured</strong>
135
+ <% end %>
136
+ </td>
137
+ </tr>
138
+ </tbody>
139
+ </table>
140
+
141
+ <p>Use the column name <code>target</code> to draw a line for goals.</p>
142
+
143
+ <% if @data_source.supports_cohort_analysis? %>
144
+ <h2>Cohort Analysis</h2>
145
+
146
+ <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>
147
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <% blazer_title "Edit - #{@query.name}" %>
2
+ <%= render partial: "form" %>
@@ -0,0 +1,169 @@
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
+ <% if Blazer.uploads? %>
23
+ <li><%= link_to "Uploads", uploads_path %></li>
24
+ <% end %>
25
+ <li role="separator" class="divider"></li>
26
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
27
+ <li><%= link_to "New Check", new_check_path %></li>
28
+ </ul>
29
+ </div>
30
+ </div>
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" />
32
+ </div>
33
+
34
+ <table class="table">
35
+ <thead>
36
+ <tr>
37
+ <th>Name</th>
38
+ <% if Blazer.user_class %>
39
+ <th style="width: 20%; text-align: right;">Mastermind</th>
40
+ <% end%>
41
+ </tr>
42
+ </thead>
43
+ <tbody class="list" v-cloak>
44
+ <tr v-for="query in visibleItems">
45
+ <td>
46
+ <a :href="itemPath(query)" :class="{ dashboard: query.dashboard }">{{ query.name }}</a>
47
+ <span class="vars">{{ query.vars }}</span>
48
+ </td>
49
+ <% if Blazer.user_class %>
50
+ <td class="creator">{{ query.creator }}</td>
51
+ <% end %>
52
+ </tr>
53
+ </tbody>
54
+ </table>
55
+
56
+ <p v-if="more" class="text-muted">Loading...</p>
57
+ </div>
58
+
59
+ <script>
60
+ <%= blazer_js_var "dashboards", @dashboards %>
61
+ <%= blazer_js_var "queries", @queries %>
62
+ <%= blazer_js_var "more", @more %>
63
+
64
+ var prepareSearch = function (list) {
65
+ var i, q, searchStr
66
+ for (i = 0; i < list.length; i++) {
67
+ q = list[i]
68
+ searchStr = q.name + q.creator
69
+ if (q.creator === "You") {
70
+ searchStr += "mine me"
71
+ }
72
+ q.searchStr = prepareQuery(searchStr)
73
+ }
74
+ }
75
+
76
+ var prepareQuery = function (str) {
77
+ return str.toLowerCase()
78
+ }
79
+
80
+ var app = Vue.createApp({
81
+ data: function() {
82
+ return {
83
+ searchTerm: "",
84
+ more: more,
85
+ updateCounter: 0
86
+ }
87
+ },
88
+ created: function() {
89
+ this.listItems = dashboards.concat(queries)
90
+
91
+ prepareSearch(this.listItems)
92
+
93
+ this.queryIds = {}
94
+ for (i = 0; i < queries.length; i++) {
95
+ this.queryIds[queries[i].id] = true
96
+ }
97
+
98
+ if (this.more) {
99
+ var _this = this
100
+
101
+ $.getJSON(Routes.queries_path(), function (data) {
102
+ var i, j, newValues, val, size = 500;
103
+
104
+ var newValues = []
105
+ for (j = 0; j < data.length; j++) {
106
+ val = data[j]
107
+ if (val && !_this.queryIds[val.id]) {
108
+ newValues.push(val)
109
+ }
110
+ }
111
+
112
+ prepareSearch(newValues)
113
+
114
+ _this.listItems = _this.listItems.concat(newValues)
115
+ _this.more = false
116
+ // hack to get to update
117
+ _this.updateCounter++
118
+ })
119
+ }
120
+ },
121
+ computed: {
122
+ visibleItems: function () {
123
+ // hack to get to update
124
+ this.updateCounter
125
+
126
+ var pageSize = 200
127
+ var q, i
128
+
129
+ if (this.searchTerm.length > 0) {
130
+ var term = prepareQuery(this.searchTerm)
131
+ var items = []
132
+ var fuzzyItems = []
133
+ for (i = 0; i < this.listItems.length; i++) {
134
+ q = this.listItems[i]
135
+ if (q.searchStr.indexOf(term) !== -1) {
136
+ items.push(q)
137
+ if (items.length == pageSize) {
138
+ break
139
+ }
140
+ } else if (fuzzysearch(term, q.searchStr)) {
141
+ fuzzyItems.push(q)
142
+ }
143
+ }
144
+ return items.concat(fuzzyItems).slice(0, pageSize)
145
+ } else {
146
+ return this.listItems.slice(0, pageSize)
147
+ }
148
+ }
149
+ },
150
+ methods: {
151
+ itemPath: function (item) {
152
+ if (item.dashboard) {
153
+ return Routes.dashboard_path(item.to_param)
154
+ } else {
155
+ return Routes.query_path(item.to_param)
156
+ }
157
+ }
158
+ },
159
+ directives: {
160
+ focus: {
161
+ inserted: function (el) {
162
+ el.focus()
163
+ }
164
+ }
165
+ }
166
+ })
167
+ app.config.compilerOptions.whitespace = "preserve"
168
+ app.mount("#queries")
169
+ </script>