sql-jarvis 1.8.0

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 (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +228 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +775 -0
  7. data/Rakefile +1 -0
  8. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  9. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  10. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  13. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  14. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  15. data/app/assets/javascripts/blazer/ace.js +6 -0
  16. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  17. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  18. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  19. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  20. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  21. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  22. data/app/assets/javascripts/blazer/application.js +79 -0
  23. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  24. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  25. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  26. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  27. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  28. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  29. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  30. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  31. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  32. data/app/assets/javascripts/blazer/moment.js +3043 -0
  33. data/app/assets/javascripts/blazer/queries.js +110 -0
  34. data/app/assets/javascripts/blazer/routes.js +23 -0
  35. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  36. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  37. data/app/assets/javascripts/blazer/vue.js +7515 -0
  38. data/app/assets/stylesheets/blazer/application.css +198 -0
  39. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  40. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  41. data/app/assets/stylesheets/blazer/github.css +125 -0
  42. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  43. data/app/controllers/blazer/base_controller.rb +103 -0
  44. data/app/controllers/blazer/checks_controller.rb +56 -0
  45. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  46. data/app/controllers/blazer/queries_controller.rb +325 -0
  47. data/app/helpers/blazer/base_helper.rb +57 -0
  48. data/app/mailers/blazer/check_mailer.rb +27 -0
  49. data/app/models/blazer/audit.rb +6 -0
  50. data/app/models/blazer/check.rb +95 -0
  51. data/app/models/blazer/connection.rb +5 -0
  52. data/app/models/blazer/dashboard.rb +13 -0
  53. data/app/models/blazer/dashboard_query.rb +9 -0
  54. data/app/models/blazer/query.rb +31 -0
  55. data/app/models/blazer/record.rb +5 -0
  56. data/app/views/blazer/_nav.html.erb +16 -0
  57. data/app/views/blazer/_variables.html.erb +102 -0
  58. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  59. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  60. data/app/views/blazer/checks/_form.html.erb +71 -0
  61. data/app/views/blazer/checks/edit.html.erb +1 -0
  62. data/app/views/blazer/checks/index.html.erb +40 -0
  63. data/app/views/blazer/checks/new.html.erb +1 -0
  64. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  65. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  66. data/app/views/blazer/dashboards/new.html.erb +1 -0
  67. data/app/views/blazer/dashboards/show.html.erb +47 -0
  68. data/app/views/blazer/queries/_form.html.erb +240 -0
  69. data/app/views/blazer/queries/edit.html.erb +2 -0
  70. data/app/views/blazer/queries/home.html.erb +152 -0
  71. data/app/views/blazer/queries/new.html.erb +2 -0
  72. data/app/views/blazer/queries/run.html.erb +163 -0
  73. data/app/views/blazer/queries/schema.html.erb +18 -0
  74. data/app/views/blazer/queries/show.html.erb +73 -0
  75. data/app/views/layouts/blazer/application.html.erb +24 -0
  76. data/blazer.gemspec +26 -0
  77. data/config/routes.rb +16 -0
  78. data/lib/blazer.rb +185 -0
  79. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  80. data/lib/blazer/adapters/base_adapter.rb +53 -0
  81. data/lib/blazer/adapters/bigquery_adapter.rb +67 -0
  82. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  83. data/lib/blazer/adapters/elasticsearch_adapter.rb +49 -0
  84. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  85. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  86. data/lib/blazer/adapters/sql_adapter.rb +182 -0
  87. data/lib/blazer/data_source.rb +193 -0
  88. data/lib/blazer/detect_anomalies.R +19 -0
  89. data/lib/blazer/engine.rb +47 -0
  90. data/lib/blazer/result.rb +170 -0
  91. data/lib/blazer/run_statement.rb +40 -0
  92. data/lib/blazer/run_statement_job.rb +21 -0
  93. data/lib/blazer/version.rb +3 -0
  94. data/lib/generators/blazer/install_generator.rb +39 -0
  95. data/lib/generators/blazer/templates/config.yml +62 -0
  96. data/lib/generators/blazer/templates/install.rb +45 -0
  97. data/lib/tasks/blazer.rake +10 -0
  98. metadata +211 -0
@@ -0,0 +1 @@
1
+ <%= render partial: "form" %>
@@ -0,0 +1,40 @@
1
+ <% blazer_title "Checks" %>
2
+
3
+ <p style="float: right;"><%= link_to "New Check", new_check_path, class: "btn btn-info" %></p>
4
+ <%= render partial: "blazer/nav" %>
5
+
6
+ <table class="table">
7
+ <thead>
8
+ <tr>
9
+ <th>Query</th>
10
+ <th style="width: 10%;">State</th>
11
+ <th style="width: 10%;">Run</th>
12
+ <th style="width: 20%;">Emails</th>
13
+ <th style="width: 15%;"></th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% @checks.each do |check| %>
18
+ <tr>
19
+ <td><%= link_to check.query.name, check.query %> <span class="text-muted"><%= check.try(:check_type).to_s.gsub("_", " ") %></span></td>
20
+ <td>
21
+ <% if check.state %>
22
+ <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= check.state.upcase %></small>
23
+ <% end %>
24
+ </td>
25
+ <td><%= check.schedule if check.respond_to?(:schedule) %></td>
26
+ <td>
27
+ <ul class="list-unstyled" style="margin-bottom: 0; word-break: break-all;">
28
+ <% check.split_emails.each do |email| %>
29
+ <li><%= email %></li>
30
+ <% end %>
31
+ </ul>
32
+ </td>
33
+ <td style="text-align: right; padding: 1px;">
34
+ <%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
35
+ <%= link_to "Run Now", query_path(check.query), class: "btn btn-primary" %>
36
+ </td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
@@ -0,0 +1 @@
1
+ <%= render partial: "form" %>
@@ -0,0 +1,76 @@
1
+ <% if @dashboard.errors.any? %>
2
+ <div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
3
+ <% end %>
4
+
5
+ <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params) : dashboards_path(variable_params)), html: {id: "app"} do |f| %>
6
+ <div class="form-group">
7
+ <%= f.label :name %>
8
+ <%= f.text_field :name, class: "form-control" %>
9
+ </div>
10
+ <div class="form-group" v-show="queries.length">
11
+ <%= f.label :charts %>
12
+ <ul id="queries" class="list-group">
13
+ <li class="list-group-item" v-for="(query, index) in queries" :key="query.id" v-cloak>
14
+ <span class="glyphicon glyphicon-remove" aria-hidden="true" v-on:click="remove(index)"></span>
15
+ {{ query.name }}
16
+ <input type="hidden" name="query_ids[]" :value="query.id">
17
+ </li>
18
+ </ul>
19
+ </div>
20
+ <div class="form-group" v-cloak>
21
+ <%= f.label :query_id, "Add Chart" %>
22
+ <%= select_tag :query_id, nil, {include_blank: true, placeholder: "Select chart"} %>
23
+ </div>
24
+ <p style="padding-bottom: 140px;" v-cloak>
25
+ <% if @dashboard.persisted? %>
26
+ <%= link_to "Delete", dashboard_path(@dashboard), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger"%>
27
+ <% end %>
28
+ <%= f.submit "Save", class: "btn btn-success" %>
29
+ <%= link_to "Back", :back, class: "btn btn-link" %>
30
+ </p>
31
+ <% end %>
32
+
33
+ <script>
34
+ <%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
35
+ <%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
36
+
37
+ var app = new Vue({
38
+ el: "#app",
39
+ data: {
40
+ queries: dashboardQueries
41
+ },
42
+ methods: {
43
+ remove: function(index) {
44
+ this.queries.splice(index, 1)
45
+ }
46
+ },
47
+ mounted: function() {
48
+ $("#query_id").selectize({
49
+ options: queries,
50
+ highlight: false,
51
+ maxOptions: 100,
52
+ onChange: function(val) {
53
+ if (val) {
54
+ var item = this.getItem(val)
55
+
56
+ // if duplicate query is added, remove the first one
57
+ for (var i = 0; i < app.queries.length; i++) {
58
+ if (app.queries[i].id == val) {
59
+ app.queries.splice(i, 1)
60
+ break
61
+ }
62
+ }
63
+
64
+ app.queries.push({id: val, name: item.text()})
65
+ this.setValue("")
66
+ }
67
+ }
68
+ })
69
+ }
70
+ })
71
+ Sortable.create($("#queries").get(0), {
72
+ onEnd: function(e) {
73
+ app.queries.splice(e.newIndex, 0, app.queries.splice(e.oldIndex, 1)[0])
74
+ }
75
+ })
76
+ </script>
@@ -0,0 +1 @@
1
+ <%= render partial: "form" %>
@@ -0,0 +1 @@
1
+ <%= render partial: "form" %>
@@ -0,0 +1,47 @@
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="margin: 0; line-height: 34px; display: inline;">
9
+ <%= @dashboard.name %>
10
+ </h3>
11
+ </div>
12
+ <div class="col-sm-3 text-right">
13
+ <%= link_to "Edit", edit_dashboard_path(@dashboard, variable_params), 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, variable_params), method: :post %>
25
+ </p>
26
+ <% end %>
27
+
28
+ <%= render partial: "blazer/variables", locals: {action: dashboard_path(@dashboard)} %>
29
+
30
+ <% @queries.each_with_index do |query, i| %>
31
+ <div class="chart-container">
32
+ <h4><%= link_to query.friendly_name, query_path(query, variable_params), target: "_blank" %></h4>
33
+ <div id="chart-<%= i %>" class="chart">
34
+ <p class="text-muted">Loading...</p>
35
+ </div>
36
+ </div>
37
+ <script>
38
+ <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, only_chart: true} %>
39
+
40
+ runQuery(data, function (data) {
41
+ $("#chart-<%= i %>").html(data)
42
+ $("#chart-<%= i %> table").stupidtable()
43
+ }, function (message) {
44
+ $("#chart-<%= i %>").addClass("query-error").html(message)
45
+ });
46
+ </script>
47
+ <% end %>
@@ -0,0 +1,240 @@
1
+ <% if @query.errors.any? %>
2
+ <div class="alert alert-danger"><%= @query.errors.full_messages.first %></div>
3
+ <% end %>
4
+
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| %>
7
+ <div class="row">
8
+ <div id="statement-box" class="col-xs-8">
9
+ <div class= "form-group">
10
+ <%= f.hidden_field :statement %>
11
+ <div id="editor-container">
12
+ <div id="editor" :style="{ height: editorHeight }"><%= @query.statement %></div>
13
+ </div>
14
+ </div>
15
+ <div class="form-group text-right">
16
+ <div class="pull-left" style="margin-top: 9px;">
17
+ <%= link_to "Back", :back %>
18
+ </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
+ <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
22
+ <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
23
+ </div>
24
+ <a v-on:click="run" v-if="!running" class="btn btn-info" style="vertical-align: top; width: 70px;">Run</a>
25
+ <a v-on:click="cancel" v-if="running" class="btn btn-danger" style="vertical-align: top; width: 70px;">Cancel</a>
26
+ </div>
27
+ </div>
28
+ <div class="col-xs-4">
29
+ <div class="form-group">
30
+ <%= f.label :name %>
31
+ <%= f.text_field :name, class: "form-control" %>
32
+ </div>
33
+ <div class="form-group">
34
+ <%= f.label :description %>
35
+ <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
36
+ </div>
37
+ <div class="text-right">
38
+ <%= f.submit "For Enter Press", class: "hide" %>
39
+ <% if @query.persisted? %>
40
+ <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
41
+ <%= f.submit "Fork", class: "btn btn-info" %>
42
+ <% end %>
43
+ <%= f.submit @query.persisted? ? "Update" : "Create", class: "btn btn-success" %>
44
+ </div>
45
+ <% if @query.persisted? %>
46
+ <% dashboards_count = @query.dashboards.count %>
47
+ <% checks_count = @query.checks.count %>
48
+ <% words = [] %>
49
+ <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
50
+ <% words << pluralize(checks_count, "check") if checks_count > 0 %>
51
+ <% if words.any? %>
52
+ <div class="alert alert-info" style="margin-top: 10px; padding: 8px 12px;">
53
+ Part of <%= words.to_sentence %>. Be careful when editing.
54
+ </div>
55
+ <% end %>
56
+ <% end %>
57
+ </div>
58
+ </div>
59
+ <% end %>
60
+
61
+ <div id="results">
62
+ <p class="text-muted" v-if="running">Loading...</p>
63
+ <div id="results-html" v-if="!running" :class="{ 'query-error': error }"></div>
64
+ </div>
65
+ </div>
66
+
67
+ <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
+
71
+ var app = new Vue({
72
+ el: "#app",
73
+ data: {
74
+ running: false,
75
+ results: "",
76
+ error: false,
77
+ dataSource: "",
78
+ selectize: null,
79
+ editorHeight: "180px"
80
+ },
81
+ computed: {
82
+ dataSourcePath: function() {
83
+ return Routes.schema_queries_path({data_source: this.dataSource})
84
+ }
85
+ },
86
+ methods: {
87
+ run: function(e) {
88
+ this.running = true
89
+ this.results = ""
90
+ this.error = false
91
+ cancelAllQueries()
92
+
93
+ var data = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
94
+
95
+ var _this = this
96
+
97
+ runQuery(data, function (data) {
98
+ _this.running = false
99
+ _this.showResults(data)
100
+
101
+ errorLine = _this.getErrorLine()
102
+ if (errorLine) {
103
+ editor.getSession().addGutterDecoration(errorLine - 1, "error")
104
+ editor.scrollToLine(errorLine, true, true, function () {})
105
+ editor.gotoLine(errorLine, 0, true)
106
+ editor.focus()
107
+ }
108
+ }, function (data) {
109
+ _this.running = false
110
+ _this.error = true
111
+ _this.showResults(data)
112
+ })
113
+ },
114
+ cancel: function(e) {
115
+ this.running = false
116
+ cancelAllQueries()
117
+ },
118
+ updateDataSource: function(dataSource) {
119
+ this.dataSource = dataSource
120
+ var selectize = this.selectize
121
+ selectize.clearOptions()
122
+
123
+ if (this.tablesXhr) {
124
+ this.tablesXhr.abort()
125
+ }
126
+
127
+ this.tablesXhr = $.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
128
+ var newOptions = []
129
+ for (var i = 0; i < data.length; i++) {
130
+ newOptions.push({text: data[i], value: data[i]})
131
+ }
132
+ selectize.clearOptions()
133
+ selectize.addOption(newOptions)
134
+ selectize.refreshOptions(false)
135
+ })
136
+ },
137
+ showEditor: function() {
138
+ var _this = this
139
+
140
+ editor = ace.edit("editor")
141
+ editor.setTheme("ace/theme/twilight")
142
+ editor.getSession().setMode("ace/mode/sql")
143
+ editor.setOptions({
144
+ enableBasicAutocompletion: false,
145
+ enableSnippets: false,
146
+ enableLiveAutocompletion: false,
147
+ highlightActiveLine: false,
148
+ fontSize: 12,
149
+ minLines: 10
150
+ })
151
+ editor.renderer.setShowGutter(true)
152
+ editor.renderer.setPrintMarginColumn(false)
153
+ editor.renderer.setPadding(10)
154
+ editor.getSession().setUseWrapMode(true)
155
+ editor.commands.addCommand({
156
+ name: "run",
157
+ bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
158
+ exec: function(editor) {
159
+ _this.run()
160
+ },
161
+ readOnly: false // false if this command should not apply in readOnly mode
162
+ })
163
+ // fix command+L
164
+ editor.commands.removeCommands(["gotoline", "find"])
165
+
166
+ this.editor = editor
167
+
168
+ editor.getSession().on("change", function () {
169
+ $("#query_statement").val(editor.getValue())
170
+ _this.adjustHeight()
171
+ })
172
+ this.adjustHeight()
173
+ editor.focus()
174
+ },
175
+ adjustHeight: function() {
176
+ // http://stackoverflow.com/questions/11584061/
177
+ var editor = this.editor
178
+ var lines = editor.getSession().getScreenLength()
179
+ if (lines < 9) {
180
+ lines = 9
181
+ }
182
+
183
+ this.editorHeight = ((lines + 1) * 16).toString() + "px"
184
+
185
+ Vue.nextTick(function () {
186
+ editor.resize()
187
+ })
188
+ },
189
+ getSQL: function() {
190
+ var selectedText = editor.getSelectedText()
191
+ var text = selectedText.length < 10 ? editor.getValue() : selectedText
192
+ return text.replace(/\n/g, "\r\n")
193
+ },
194
+ getErrorLine: function() {
195
+ var editor = this.editor
196
+ var errorLine = this.results.substring(0, 100).includes("alert-danger") && /LINE (\d+)/g.exec(this.results)
197
+
198
+ if (errorLine) {
199
+ errorLine = parseInt(errorLine[1], 10)
200
+ if (editor.getSelectedText().length >= 10) {
201
+ errorLine += editor.getSelectionRange().start.row
202
+ }
203
+ return errorLine
204
+ }
205
+ },
206
+ showResults(data) {
207
+ // can't do it the Vue way due to script tags in results
208
+ // this.results = data
209
+
210
+ Vue.nextTick(function () {
211
+ $("#results-html").html(data)
212
+ })
213
+ }
214
+ },
215
+ mounted: function() {
216
+ var _this = this
217
+
218
+ var $select = $("#table_names").selectize({})
219
+ var selectize = $select[0].selectize
220
+ selectize.on("change", function(val) {
221
+ editor.setValue(previewStatement[_this.dataSource].replace("{table}", val), 1)
222
+ _this.run()
223
+ selectize.clear(true)
224
+ selectize.blur()
225
+ })
226
+ this.selectize = selectize
227
+
228
+ this.updateDataSource($("#query_data_source").val())
229
+
230
+ var $dsSelect = $("#query_data_source").selectize({})
231
+ var dsSelectize = $dsSelect[0].selectize
232
+ dsSelectize.on("change", function(val) {
233
+ _this.updateDataSource(val)
234
+ dsSelectize.blur()
235
+ })
236
+
237
+ this.showEditor()
238
+ }
239
+ })
240
+ </script>
@@ -0,0 +1,2 @@
1
+ <% blazer_title "Edit - #{@query.name}" %>
2
+ <%= render partial: "form" %>
@@ -0,0 +1,152 @@
1
+ <div id="queries">
2
+ <div id="header" style="margin-bottom: 20px;">
3
+ <div class="pull-right">
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
+ <div class="btn-group">
14
+ <%= link_to "New Query", new_query_path, class: "btn btn-info" %>
15
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
16
+ <span class="caret"></span>
17
+ <span class="sr-only">Toggle Dropdown</span>
18
+ </button>
19
+ <ul class="dropdown-menu">
20
+ <li><%= link_to "Dashboards", dashboards_path %></li>
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 or person" style="width: 300px; display: inline-block;" autofocus=true class="search form-control" />
29
+ </div>
30
+
31
+ <table class="table">
32
+ <thead>
33
+ <tr>
34
+ <th>Name</th>
35
+ <th style="width: 20%; text-align: right;">Mastermind</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody class="list" v-cloak>
39
+ <tr v-for="query in visibleItems">
40
+ <td>
41
+ <a :href="itemPath(query)" :class="{ dashboard: query.dashboard }">{{ query.name }}</a>
42
+ <span class="vars">{{ query.vars }}</span>
43
+ </td>
44
+ <td class="creator">{{ query.creator }}</td>
45
+ </tr>
46
+ </tbody>
47
+ </table>
48
+
49
+ <p v-if="more" class="text-muted">Loading...</p>
50
+ </div>
51
+
52
+ <script>
53
+ <%= blazer_js_var "dashboards", @dashboards %>
54
+ <%= blazer_js_var "queries", @queries %>
55
+ <%= blazer_js_var "more", @more %>
56
+
57
+ var prepareSearch = function (list) {
58
+ var i, q, searchStr
59
+ for (i = 0; i < list.length; i++) {
60
+ q = list[i]
61
+ searchStr = q.name + q.creator
62
+ if (q.creator === "You") {
63
+ searchStr += "mine me"
64
+ }
65
+ q.searchStr = prepareQuery(searchStr)
66
+ }
67
+ }
68
+
69
+ var prepareQuery = function (str) {
70
+ return str.toLowerCase().replace(/\W+/g, "")
71
+ }
72
+
73
+ var app = new Vue({
74
+ el: "#queries",
75
+ data: {
76
+ searchTerm: "",
77
+ more: more,
78
+ updateCounter: 0
79
+ },
80
+ created: function() {
81
+ this.listItems = dashboards.concat(queries)
82
+
83
+ prepareSearch(this.listItems)
84
+
85
+ this.queryIds = {}
86
+ for (i = 0; i < queries.length; i++) {
87
+ this.queryIds[queries[i].id] = true
88
+ }
89
+
90
+ if (this.more) {
91
+ var _this = this
92
+
93
+ $.getJSON(Routes.queries_path(), function (data) {
94
+ var i, j, newValues, val, size = 500;
95
+
96
+ var newValues = []
97
+ for (j = 0; j < data.length; j++) {
98
+ val = data[j]
99
+ if (val && !_this.queryIds[val.id]) {
100
+ newValues.push(val)
101
+ }
102
+ }
103
+
104
+ prepareSearch(newValues)
105
+
106
+ _this.listItems = _this.listItems.concat(newValues)
107
+ _this.more = false
108
+ // hack to get to update
109
+ _this.updateCounter++
110
+ })
111
+ }
112
+ },
113
+ computed: {
114
+ visibleItems: function () {
115
+ // hack to get to update
116
+ this.updateCounter
117
+
118
+ var pageSize = 200
119
+ var q, i
120
+
121
+ if (this.searchTerm.length > 0) {
122
+ var term = prepareQuery(this.searchTerm)
123
+ var items = []
124
+ var fuzzyItems = []
125
+ for (i = 0; i < this.listItems.length; i++) {
126
+ q = this.listItems[i]
127
+ if (q.searchStr.indexOf(term) !== -1) {
128
+ items.push(q)
129
+ if (items.length == pageSize) {
130
+ break
131
+ }
132
+ } else if (fuzzysearch(term, q.searchStr)) {
133
+ fuzzyItems.push(q)
134
+ }
135
+ }
136
+ return items.concat(fuzzyItems).slice(0, pageSize)
137
+ } else {
138
+ return this.listItems.slice(0, pageSize)
139
+ }
140
+ }
141
+ },
142
+ methods: {
143
+ itemPath: function (item) {
144
+ if (item.dashboard) {
145
+ return Routes.dashboard_path(item.to_param)
146
+ } else {
147
+ return Routes.query_path(item.to_param)
148
+ }
149
+ }
150
+ }
151
+ })
152
+ </script>