railsblazer 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.github/ISSUE_TEMPLATE.md +0 -0
  4. data/.gitignore +14 -0
  5. data/CHANGELOG.md +247 -0
  6. data/CONTRIBUTING.md +42 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +855 -0
  10. data/Rakefile +1 -0
  11. data/app/assets/fonts/blazer/glyphicons-halflings-regular.eot +0 -0
  12. data/app/assets/fonts/blazer/glyphicons-halflings-regular.svg +288 -0
  13. data/app/assets/fonts/blazer/glyphicons-halflings-regular.ttf +0 -0
  14. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff +0 -0
  15. data/app/assets/fonts/blazer/glyphicons-halflings-regular.woff2 +0 -0
  16. data/app/assets/javascripts/blazer/Chart.js +14145 -0
  17. data/app/assets/javascripts/blazer/Sortable.js +1144 -0
  18. data/app/assets/javascripts/blazer/ace.js +6 -0
  19. data/app/assets/javascripts/blazer/ace/ace.js +11 -0
  20. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +5 -0
  21. data/app/assets/javascripts/blazer/ace/mode-sql.js +1 -0
  22. data/app/assets/javascripts/blazer/ace/snippets/sql.js +1 -0
  23. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -0
  24. data/app/assets/javascripts/blazer/ace/theme-twilight.js +1 -0
  25. data/app/assets/javascripts/blazer/application.js +79 -0
  26. data/app/assets/javascripts/blazer/bootstrap.js +2366 -0
  27. data/app/assets/javascripts/blazer/chartkick.js +1693 -0
  28. data/app/assets/javascripts/blazer/daterangepicker.js +1505 -0
  29. data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
  30. data/app/assets/javascripts/blazer/highlight.pack.js +1 -0
  31. data/app/assets/javascripts/blazer/jquery.js +10308 -0
  32. data/app/assets/javascripts/blazer/jquery.stickytableheaders.js +263 -0
  33. data/app/assets/javascripts/blazer/jquery_ujs.js +469 -0
  34. data/app/assets/javascripts/blazer/moment-timezone.js +1007 -0
  35. data/app/assets/javascripts/blazer/moment.js +3043 -0
  36. data/app/assets/javascripts/blazer/queries.js +110 -0
  37. data/app/assets/javascripts/blazer/routes.js +23 -0
  38. data/app/assets/javascripts/blazer/selectize.js +3667 -0
  39. data/app/assets/javascripts/blazer/stupidtable.js +114 -0
  40. data/app/assets/javascripts/blazer/vue.js +7515 -0
  41. data/app/assets/stylesheets/blazer/application.css +198 -0
  42. data/app/assets/stylesheets/blazer/bootstrap.css.erb +6202 -0
  43. data/app/assets/stylesheets/blazer/daterangepicker-bs3.css +375 -0
  44. data/app/assets/stylesheets/blazer/github.css +125 -0
  45. data/app/assets/stylesheets/blazer/selectize.default.css +387 -0
  46. data/app/controllers/blazer/base_controller.rb +113 -0
  47. data/app/controllers/blazer/checks_controller.rb +56 -0
  48. data/app/controllers/blazer/dashboards_controller.rb +105 -0
  49. data/app/controllers/blazer/queries_controller.rb +337 -0
  50. data/app/helpers/blazer/base_helper.rb +57 -0
  51. data/app/mailers/blazer/check_mailer.rb +27 -0
  52. data/app/mailers/blazer/slack_notifier.rb +76 -0
  53. data/app/models/blazer/audit.rb +6 -0
  54. data/app/models/blazer/check.rb +104 -0
  55. data/app/models/blazer/connection.rb +5 -0
  56. data/app/models/blazer/dashboard.rb +13 -0
  57. data/app/models/blazer/dashboard_query.rb +9 -0
  58. data/app/models/blazer/query.rb +40 -0
  59. data/app/models/blazer/record.rb +5 -0
  60. data/app/views/blazer/_nav.html.erb +16 -0
  61. data/app/views/blazer/_variables.html.erb +102 -0
  62. data/app/views/blazer/check_mailer/failing_checks.html.erb +6 -0
  63. data/app/views/blazer/check_mailer/state_change.html.erb +47 -0
  64. data/app/views/blazer/checks/_form.html.erb +79 -0
  65. data/app/views/blazer/checks/edit.html.erb +1 -0
  66. data/app/views/blazer/checks/index.html.erb +43 -0
  67. data/app/views/blazer/checks/new.html.erb +1 -0
  68. data/app/views/blazer/dashboards/_form.html.erb +76 -0
  69. data/app/views/blazer/dashboards/edit.html.erb +1 -0
  70. data/app/views/blazer/dashboards/new.html.erb +1 -0
  71. data/app/views/blazer/dashboards/show.html.erb +47 -0
  72. data/app/views/blazer/queries/_form.html.erb +240 -0
  73. data/app/views/blazer/queries/edit.html.erb +2 -0
  74. data/app/views/blazer/queries/home.html.erb +152 -0
  75. data/app/views/blazer/queries/new.html.erb +2 -0
  76. data/app/views/blazer/queries/run.html.erb +165 -0
  77. data/app/views/blazer/queries/schema.html.erb +20 -0
  78. data/app/views/blazer/queries/show.html.erb +73 -0
  79. data/app/views/layouts/blazer/application.html.erb +24 -0
  80. data/blazer-0.0.1.gem +0 -0
  81. data/blazer.gemspec +27 -0
  82. data/config/routes.rb +16 -0
  83. data/lib/blazer.rb +223 -0
  84. data/lib/blazer/adapters/athena_adapter.rb +128 -0
  85. data/lib/blazer/adapters/base_adapter.rb +53 -0
  86. data/lib/blazer/adapters/bigquery_adapter.rb +68 -0
  87. data/lib/blazer/adapters/cassandra_adapter.rb +59 -0
  88. data/lib/blazer/adapters/drill_adapter.rb +28 -0
  89. data/lib/blazer/adapters/druid_adapter.rb +67 -0
  90. data/lib/blazer/adapters/elasticsearch_adapter.rb +46 -0
  91. data/lib/blazer/adapters/mongodb_adapter.rb +39 -0
  92. data/lib/blazer/adapters/presto_adapter.rb +45 -0
  93. data/lib/blazer/adapters/snowflake_adapter.rb +73 -0
  94. data/lib/blazer/adapters/sql_adapter.rb +182 -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 +30 -0
  98. data/lib/blazer/result.rb +170 -0
  99. data/lib/blazer/run_statement.rb +40 -0
  100. data/lib/blazer/run_statement_job.rb +21 -0
  101. data/lib/blazer/version.rb +3 -0
  102. data/lib/generators/blazer/install_generator.rb +39 -0
  103. data/lib/generators/blazer/templates/config.yml.tt +62 -0
  104. data/lib/generators/blazer/templates/install.rb.tt +46 -0
  105. data/lib/tasks/blazer.rake +11 -0
  106. data/railsblazer-0.0.1.gem +0 -0
  107. metadata +234 -0
@@ -0,0 +1,6 @@
1
+ <ul>
2
+ <% @checks.each do |check| %>
3
+ <li><%= link_to check.query.name, query_url(check.query_id) %> <%= check.state %></li>
4
+ <% end %>
5
+ </ul>
6
+ <p><%= link_to "Manage checks", checks_url %></p>
@@ -0,0 +1,47 @@
1
+ <html>
2
+ <head>
3
+ </head>
4
+ <body style="font-family: 'Helvetica Neue', Arial, Helvetica; font-size: 14px; color: #333;">
5
+ <p><%= link_to "View", query_url(@check.query_id) %></p>
6
+ <% if @error %>
7
+ <p><%= @error %></p>
8
+ <% elsif @rows_count > 0 && @check_type == "bad_data" %>
9
+ <p>
10
+ <% if @rows_count <= 10 %>
11
+ <%= pluralize(@rows_count, "row") %>
12
+ <% else %>
13
+ Showing 10 of <%= @rows_count %> rows
14
+ <% end %>
15
+ </p>
16
+ <table style="width: 100%; border-spacing: 0; border-collapse: collapse;">
17
+ <thead>
18
+ <tr>
19
+ <% @columns.first(5).each do |column| %>
20
+ <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) %>%;">
21
+ <%= column %>
22
+ </th>
23
+ <% end %>
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ <% @rows.first(10).each do |row| %>
28
+ <tr>
29
+ <% @columns.first(5).each_with_index do |column, i| %>
30
+ <td style="padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;">
31
+ <% value = row[i] %>
32
+ <% if @column_types[i] == "time" && value.to_s.length > 10 %>
33
+ <% value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value %>
34
+ <% end %>
35
+ <%= value %>
36
+ </td>
37
+ <% end %>
38
+ </tr>
39
+ <% end %>
40
+ </tbody>
41
+ </table>
42
+ <% if @columns.size > 5 %>
43
+ <p style="color: #999;">Only first 5 columns shown</p>
44
+ <% end %>
45
+ <% end %>
46
+ </body>
47
+ </html>
@@ -0,0 +1,79 @@
1
+ <% unless @check.respond_to?(:invert) %>
2
+ <p class="text-muted">Checks are designed to identify bad data. A check fails if there are any results.</p>
3
+ <% end %>
4
+
5
+ <% if @check.errors.any? %>
6
+ <div class="alert alert-danger"><%= @check.errors.full_messages.first %></div>
7
+ <% end %>
8
+
9
+ <%= form_for @check do |f| %>
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 @@
1
+ <%= render partial: "form" %>
@@ -0,0 +1,43 @@
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%;">Notify</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
+ <% check.split_slack_channels.each do |channel| %>
32
+ <li><%= channel %></li>
33
+ <% end %>
34
+ </ul>
35
+ </td>
36
+ <td style="text-align: right; padding: 1px;">
37
+ <%= link_to "Edit", edit_check_path(check), class: "btn btn-info" %>
38
+ <%= link_to "Run Now", query_path(check.query), class: "btn btn-primary" %>
39
+ </td>
40
+ </tr>
41
+ <% end %>
42
+ </tbody>
43
+ </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 rescue "")] }] %>
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
+ // https://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>