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,7 @@
1
+ <ul>
2
+ <% @checks.each do |check| %>
3
+ <%# check queries shouldn't have variables, but in any case, don't pass them to url helpers %>
4
+ <li><%= link_to check.query.name, query_url(check.query_id) %> <%= check.state %></li>
5
+ <% end %>
6
+ </ul>
7
+ <p><%= link_to "Manage checks", checks_url %></p>
@@ -0,0 +1,48 @@
1
+ <html>
2
+ <head>
3
+ </head>
4
+ <body style="font-family: 'Helvetica Neue', Arial, Helvetica; font-size: 14px; color: #333;">
5
+ <%# check queries shouldn't have variables, but in any case, don't pass them to url helpers %>
6
+ <p><%= link_to "View", query_url(@check.query_id) %></p>
7
+ <% if @error %>
8
+ <p><%= @error %></p>
9
+ <% elsif @rows_count > 0 && @check_type == "bad_data" %>
10
+ <p>
11
+ <% if @rows_count <= 10 %>
12
+ <%= pluralize(@rows_count, "row") %>
13
+ <% else %>
14
+ Showing 10 of <%= @rows_count %> rows
15
+ <% end %>
16
+ </p>
17
+ <table style="width: 100%; border-spacing: 0; border-collapse: collapse;">
18
+ <thead>
19
+ <tr>
20
+ <% @columns.first(5).each do |column| %>
21
+ <th style="padding: 8px; line-height: 1.4; text-align: left; vertical-align: bottom; border-bottom: 2px solid #ddd; width: <%= (100 / @columns.size).round(2) %>%;">
22
+ <%= column %>
23
+ </th>
24
+ <% end %>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <% @rows.first(10).each do |row| %>
29
+ <tr>
30
+ <% @columns.first(5).each_with_index do |column, i| %>
31
+ <td style="padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;">
32
+ <% value = row[i] %>
33
+ <% if @column_types[i] == "time" && value.to_s.length > 10 %>
34
+ <% value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value %>
35
+ <% end %>
36
+ <%= value %>
37
+ </td>
38
+ <% end %>
39
+ </tr>
40
+ <% end %>
41
+ </tbody>
42
+ </table>
43
+ <% if @columns.size > 5 %>
44
+ <p style="color: #999;">Only first 5 columns shown</p>
45
+ <% end %>
46
+ <% end %>
47
+ </body>
48
+ </html>
@@ -0,0 +1,79 @@
1
+ <%= form_for @check, html: {class: "small-form"} do |f| %>
2
+ <% unless @check.respond_to?(:check_type) || @check.respond_to?(:invert) %>
3
+ <p class="text-muted">Checks are designed to identify bad data. A check fails if there are any results.</p>
4
+ <% end %>
5
+
6
+ <% if @check.errors.any? %>
7
+ <div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
8
+ <% end %>
9
+
10
+ <div class="form-group">
11
+ <%= f.label :query_id, "Query" %>
12
+ <div class="hide">
13
+ <%= f.select :query_id, [], {include_blank: true} %>
14
+ </div>
15
+ <script>
16
+ <%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
17
+ <%= blazer_js_var "items", [@check.query_id].compact %>
18
+
19
+ $("#check_query_id").selectize({options: queries, items: items, highlight: false, maxOptions: 100}).parents(".hide").removeClass("hide");
20
+ </script>
21
+ </div>
22
+
23
+ <% if @check.respond_to?(:check_type) %>
24
+ <div class="form-group">
25
+ <%= f.label :check_type, "Alert if" %>
26
+ <div class="hide">
27
+ <% check_options = [["Any results (bad data)", "bad_data"], ["No results (missing data)", "missing_data"]] %>
28
+ <% check_options << ["Anomaly (most recent data point)", "anomaly"] if Blazer.anomaly_checks %>
29
+ <%= f.select :check_type, check_options %>
30
+ </div>
31
+ <script>
32
+ $("#check_check_type").selectize({}).parent().removeClass("hide");
33
+ </script>
34
+ </div>
35
+ <% elsif @check.respond_to?(:invert) %>
36
+ <div class="form-group">
37
+ <%= f.label :invert, "Fails if" %>
38
+ <div class="hide">
39
+ <%= f.select :invert, [["Any results (bad data)", false], ["No results (missing data)", true]] %>
40
+ </div>
41
+ <script>
42
+ $("#check_invert").selectize({}).parent().removeClass("hide");
43
+ </script>
44
+ </div>
45
+ <% end %>
46
+
47
+ <% if @check.respond_to?(:schedule) && Blazer.check_schedules %>
48
+ <div class="form-group">
49
+ <%= f.label :schedule, "Run every" %>
50
+ <div class="hide">
51
+ <%= f.select :schedule, Blazer.check_schedules.map { |v| [v, v] } %>
52
+ </div>
53
+ <script>
54
+ $("#check_schedule").selectize({}).parent().removeClass("hide");
55
+ </script>
56
+ </div>
57
+ <% end %>
58
+
59
+ <div class="form-group">
60
+ <%= f.label :emails %>
61
+ <%= f.text_field :emails, placeholder: "Optional, comma separated", class: "form-control" %>
62
+ </div>
63
+
64
+ <% if Blazer.slack? %>
65
+ <div class="form-group">
66
+ <%= f.label :slack_channels %>
67
+ <%= f.text_field :slack_channels, placeholder: "Optional, comma separated", class: "form-control" %>
68
+ </div>
69
+ <% end %>
70
+
71
+ <p class="text-muted">Emails <%= Blazer.slack? ? "and Slack notifications " : nil %>are sent when a check starts failing, and when it starts passing again.
72
+ <p>
73
+ <% if @check.persisted? %>
74
+ <%= link_to "Delete", check_path(@check), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
75
+ <% end %>
76
+ <%= f.submit "Save", class: "btn btn-success" %>
77
+ <%= link_to "Back", :back, class: "btn btn-link" %>
78
+ </p>
79
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "Edit Check" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,69 @@
1
+ <% blazer_title "Checks" %>
2
+
3
+ <div id="header">
4
+ <div class="pull-right" style="line-height: 34px;">
5
+ <div class="btn-group">
6
+ <%= link_to "New Check", new_check_path, class: "btn btn-info" %>
7
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
8
+ <span class="caret"></span>
9
+ <span class="sr-only">Toggle Dropdown</span>
10
+ </button>
11
+ <ul class="dropdown-menu">
12
+ <li><%= link_to "Home", root_path %></li>
13
+ <li role="separator" class="divider"></li>
14
+ <li><%= link_to "New Query", new_query_path %></li>
15
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
16
+ </ul>
17
+ </div>
18
+ </div>
19
+
20
+ <input id="search" type="text" placeholder="Start typing a query or state" style="width: 300px; display: inline-block;" class="search form-control" />
21
+ </div>
22
+
23
+ <table id="checks" class="table">
24
+ <thead>
25
+ <tr>
26
+ <th>Query</th>
27
+ <th style="width: 10%;">State</th>
28
+ <th style="width: 10%;">Run</th>
29
+ <th style="width: 20%;">Notify</th>
30
+ <th style="width: 15%;"></th>
31
+ </tr>
32
+ </thead>
33
+ <tbody>
34
+ <% @checks.each do |check| %>
35
+ <tr>
36
+ <td><%= link_to check.query.name, check.query %> <span class="text-muted"><%= check.try(:check_type).to_s.gsub("_", " ") %></span></td>
37
+ <td>
38
+ <% if check.state %>
39
+ <small class="check-state <%= check.state.parameterize.gsub("-", "_") %>"><%= check.state.upcase %></small>
40
+ <% end %>
41
+ </td>
42
+ <td><%= check.schedule if check.respond_to?(:schedule) %></td>
43
+ <td>
44
+ <ul class="list-unstyled" style="margin-bottom: 0; word-break: break-all;">
45
+ <% check.split_emails.each do |email| %>
46
+ <li><%= email %></li>
47
+ <% end %>
48
+ <% check.split_slack_channels.each do |channel| %>
49
+ <li><%= channel %></li>
50
+ <% end %>
51
+ </ul>
52
+ </td>
53
+ <td style="text-align: right; padding: 1px;">
54
+ <%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
55
+ <%= link_to "Run Now", query_path(check.query), class: "btn btn-primary" %>
56
+ </td>
57
+ </tr>
58
+ <% end %>
59
+ </tbody>
60
+ </table>
61
+
62
+ <script>
63
+ $("#search").on("keyup", function() {
64
+ var value = $(this).val().toLowerCase()
65
+ $("#checks tbody tr").filter( function() {
66
+ $(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
67
+ })
68
+ }).focus()
69
+ </script>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "New Check" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,76 @@
1
+ <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params(@dashboard)) : dashboards_path(variable_params(@dashboard))), html: {id: "app", class: "small-form"} do |f| %>
2
+ <% if @dashboard.errors.any? %>
3
+ <div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
4
+ <% end %>
5
+
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,3 @@
1
+ <% blazer_title "Edit Dashboard" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "New Dashboard" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,51 @@
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, 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, 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, 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
+ <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, data_source: query.data_source, only_chart: true} %>
43
+
44
+ runQuery(data, function (data) {
45
+ $("#chart-<%= i %>").html(data)
46
+ $("#chart-<%= i %> table").stupidtable(stupidtableCustomSettings)
47
+ }, function (message) {
48
+ $("#chart-<%= i %>").addClass("query-error").html(message)
49
+ });
50
+ </script>
51
+ <% end %>
@@ -0,0 +1,250 @@
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(@query)) : queries_path(variable_params(@query))), 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" style="margin-bottom: 8px;">
16
+ <div class="pull-left" style="margin-top: 8px;">
17
+ <%= link_to "Back", :back %>
18
+ <a :href="docsPath" target="_blank" style="margin-left: 40px;">Docs</a>
19
+ <a :href="schemaPath" target="_blank" style="margin-left: 40px;">Schema</a>
20
+ </div>
21
+
22
+ <%= 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;" %>
23
+ <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;">
24
+ <select id="table_names" style="width: 240px;" placeholder="Preview table"></select>
25
+ </div>
26
+ <a v-on:click="run" v-if="!running" class="btn btn-info" style="vertical-align: top; width: 70px;">Run</a>
27
+ <a v-on:click="cancel" v-if="running" class="btn btn-danger" style="vertical-align: top; width: 70px;">Cancel</a>
28
+ </div>
29
+ </div>
30
+ <div class="col-xs-4">
31
+ <div class="form-group">
32
+ <%= f.label :name %>
33
+ <%= f.text_field :name, class: "form-control" %>
34
+ </div>
35
+ <div class="form-group">
36
+ <%= f.label :description %>
37
+ <%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
38
+ </div>
39
+ <div class="form-group text-right">
40
+ <%= f.submit "For Enter Press", class: "hide" %>
41
+ <% if @query.persisted? %>
42
+ <%= link_to "Delete", query_path(@query), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
43
+ <%= f.submit "Fork", class: "btn btn-info" %>
44
+ <% end %>
45
+ <%= f.submit @query.persisted? ? "Update" : "Create", class: "btn btn-success" %>
46
+ </div>
47
+ <% if @query.persisted? %>
48
+ <% dashboards_count = @query.dashboards.count %>
49
+ <% checks_count = @query.checks.count %>
50
+ <% words = [] %>
51
+ <% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
52
+ <% words << pluralize(checks_count, "check") if checks_count > 0 %>
53
+ <% if words.any? %>
54
+ <div class="alert alert-info">
55
+ Part of <%= words.to_sentence %>. Be careful when editing.
56
+ </div>
57
+ <% end %>
58
+ <% end %>
59
+ </div>
60
+ </div>
61
+ <% end %>
62
+
63
+ <div id="results">
64
+ <p class="text-muted" v-if="running">Loading...</p>
65
+ <div id="results-html" v-if="!running" :class="{ 'query-error': error }"></div>
66
+ </div>
67
+ </div>
68
+
69
+ <script>
70
+ <%= blazer_js_var "params", variable_params(@query) %>
71
+ <%= blazer_js_var "previewStatement", Hash[Blazer.data_sources.map { |k, v| [k, (v.preview_statement rescue "")] }] %>
72
+
73
+ var app = new Vue({
74
+ el: "#app",
75
+ data: {
76
+ running: false,
77
+ results: "",
78
+ error: false,
79
+ dataSource: "",
80
+ selectize: null,
81
+ editorHeight: "180px"
82
+ },
83
+ computed: {
84
+ schemaPath: function() {
85
+ return Routes.schema_queries_path({data_source: this.dataSource})
86
+ },
87
+ docsPath: function() {
88
+ return Routes.docs_queries_path({data_source: this.dataSource})
89
+ }
90
+ },
91
+ methods: {
92
+ run: function(e) {
93
+ this.running = true
94
+ this.results = ""
95
+ this.error = false
96
+ cancelAllQueries()
97
+
98
+ var data = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
99
+
100
+ var _this = this
101
+
102
+ runQuery(data, function (data) {
103
+ _this.running = false
104
+ _this.showResults(data)
105
+
106
+ errorLine = _this.getErrorLine()
107
+ if (errorLine) {
108
+ editor.getSession().addGutterDecoration(errorLine - 1, "error")
109
+ editor.scrollToLine(errorLine, true, true, function () {})
110
+ editor.gotoLine(errorLine, 0, true)
111
+ editor.focus()
112
+ }
113
+ }, function (data) {
114
+ _this.running = false
115
+ _this.error = true
116
+ _this.showResults(data)
117
+ })
118
+ },
119
+ cancel: function(e) {
120
+ this.running = false
121
+ cancelAllQueries()
122
+ },
123
+ updateDataSource: function(dataSource) {
124
+ this.dataSource = dataSource
125
+ var selectize = this.selectize
126
+ selectize.clearOptions()
127
+
128
+ if (this.tablesXhr) {
129
+ this.tablesXhr.abort()
130
+ }
131
+
132
+ this.tablesXhr = $.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
133
+ var newOptions = []
134
+ for (var i = 0; i < data.length; i++) {
135
+ var table = data[i]
136
+ if (typeof table === "object") {
137
+ newOptions.push({text: table.table, value: table.value})
138
+ } else {
139
+ newOptions.push({text: table, value: table})
140
+ }
141
+ }
142
+ selectize.clearOptions()
143
+ selectize.addOption(newOptions)
144
+ selectize.refreshOptions(false)
145
+ })
146
+ },
147
+ showEditor: function() {
148
+ var _this = this
149
+
150
+ editor = ace.edit("editor")
151
+ editor.setTheme("ace/theme/twilight")
152
+ editor.getSession().setMode("ace/mode/sql")
153
+ editor.setOptions({
154
+ enableBasicAutocompletion: false,
155
+ enableSnippets: false,
156
+ enableLiveAutocompletion: false,
157
+ highlightActiveLine: false,
158
+ fontSize: 12,
159
+ minLines: 10
160
+ })
161
+ editor.renderer.setShowGutter(true)
162
+ editor.renderer.setPrintMarginColumn(false)
163
+ editor.renderer.setPadding(10)
164
+ editor.getSession().setUseWrapMode(true)
165
+ editor.commands.addCommand({
166
+ name: "run",
167
+ bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
168
+ exec: function(editor) {
169
+ _this.run()
170
+ },
171
+ readOnly: false // false if this command should not apply in readOnly mode
172
+ })
173
+ // fix command+L
174
+ editor.commands.removeCommands(["gotoline", "find"])
175
+
176
+ this.editor = editor
177
+
178
+ editor.getSession().on("change", function () {
179
+ $("#query_statement").val(editor.getValue())
180
+ _this.adjustHeight()
181
+ })
182
+ this.adjustHeight()
183
+ editor.focus()
184
+ },
185
+ adjustHeight: function() {
186
+ // https://stackoverflow.com/questions/11584061/
187
+ var editor = this.editor
188
+ var lines = editor.getSession().getScreenLength()
189
+ if (lines < 9) {
190
+ lines = 9
191
+ }
192
+
193
+ this.editorHeight = ((lines + 1) * 16).toString() + "px"
194
+
195
+ Vue.nextTick(function () {
196
+ editor.resize()
197
+ })
198
+ },
199
+ getSQL: function() {
200
+ var selectedText = editor.getSelectedText()
201
+ var text = selectedText.length < 10 ? editor.getValue() : selectedText
202
+ return text.replace(/\n/g, "\r\n")
203
+ },
204
+ getErrorLine: function() {
205
+ var editor = this.editor
206
+ var errorLine = this.results.substring(0, 100).includes("alert-danger") && /LINE (\d+)/g.exec(this.results)
207
+
208
+ if (errorLine) {
209
+ errorLine = parseInt(errorLine[1], 10)
210
+ if (editor.getSelectedText().length >= 10) {
211
+ errorLine += editor.getSelectionRange().start.row
212
+ }
213
+ return errorLine
214
+ }
215
+ },
216
+ showResults(data) {
217
+ // can't do it the Vue way due to script tags in results
218
+ // this.results = data
219
+
220
+ Vue.nextTick(function () {
221
+ $("#results-html").html(data)
222
+ })
223
+ }
224
+ },
225
+ mounted: function() {
226
+ var _this = this
227
+
228
+ var $select = $("#table_names").selectize({})
229
+ var selectize = $select[0].selectize
230
+ selectize.on("change", function(val) {
231
+ editor.setValue(previewStatement[_this.dataSource].replace("{table}", val), 1)
232
+ _this.run()
233
+ selectize.clear(true)
234
+ selectize.blur()
235
+ })
236
+ this.selectize = selectize
237
+
238
+ this.updateDataSource($("#query_data_source").val())
239
+
240
+ var $dsSelect = $("#query_data_source").selectize({})
241
+ var dsSelectize = $dsSelect[0].selectize
242
+ dsSelectize.on("change", function(val) {
243
+ _this.updateDataSource(val)
244
+ dsSelectize.blur()
245
+ })
246
+
247
+ this.showEditor()
248
+ }
249
+ })
250
+ </script>