sql-jarvis 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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>