blazer 1.7.2 → 1.7.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/app/assets/javascripts/blazer/application.js +1 -134
- data/app/assets/javascripts/blazer/fuzzysearch.js +24 -0
- data/app/assets/javascripts/blazer/queries.js +3 -3
- data/app/assets/javascripts/blazer/routes.js.erb +26 -1
- data/app/assets/stylesheets/blazer/application.css +14 -2
- data/app/controllers/blazer/base_controller.rb +7 -0
- data/app/controllers/blazer/dashboards_controller.rb +1 -1
- data/app/controllers/blazer/queries_controller.rb +7 -4
- data/app/mailers/blazer/check_mailer.rb +6 -1
- data/app/models/blazer/check.rb +1 -1
- data/app/views/blazer/check_mailer/state_change.html.erb +37 -6
- data/app/views/blazer/dashboards/show.html.erb +2 -2
- data/app/views/blazer/queries/_form.html.erb +215 -52
- data/app/views/blazer/queries/home.html.erb +14 -7
- data/lib/blazer/adapters/sql_adapter.rb +1 -1
- data/lib/blazer/version.rb +1 -1
- data/lib/generators/blazer/templates/install.rb +4 -4
- metadata +3 -4
- data/app/views/blazer/dashboards/index.html.erb +0 -19
- data/app/views/blazer/queries/_tables.html.erb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bdae12ea224ba2311ce33fdbc05dffbbc938cee
|
4
|
+
data.tar.gz: 635a76501e3608e8a188cdfdc3186b497e6337fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e81386804c7030a74a75f959f936e3ff0053c9e8e2a17f78cff84897850942f124a989f7258d429e836b0aeacf8a138f59e47ed5a35ac8686f37ecdfbd77222
|
7
|
+
data.tar.gz: f1c1da1a30477aae06877313f2130b6a6140962a31c29761938b04fad93baad8cb036fcd79a24f91c52212690f0cecd3dde7d563ecde799370fa61aac2a70353
|
data/CHANGELOG.md
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
//= require ./vue
|
16
16
|
//= require ./routes
|
17
17
|
//= require ./queries
|
18
|
+
//= require ./fuzzysearch
|
18
19
|
|
19
20
|
Vue.config.devtools = false
|
20
21
|
|
@@ -74,139 +75,5 @@ function preventBackspaceNav() {
|
|
74
75
|
})
|
75
76
|
}
|
76
77
|
|
77
|
-
var editor
|
78
|
-
|
79
|
-
// http://stackoverflow.com/questions/11584061/
|
80
|
-
function adjustHeight() {
|
81
|
-
var lines = editor.getSession().getScreenLength()
|
82
|
-
if (lines < 9) {
|
83
|
-
lines = 9
|
84
|
-
}
|
85
|
-
|
86
|
-
var newHeight = (lines + 1) * 16
|
87
|
-
$("#editor").height(newHeight.toString() + "px")
|
88
|
-
editor.resize()
|
89
|
-
}
|
90
|
-
|
91
|
-
function getSQL() {
|
92
|
-
var selectedText = editor.getSelectedText()
|
93
|
-
var text = selectedText.length < 10 ? editor.getValue() : selectedText
|
94
|
-
return text.replace(/\n/g, "\r\n")
|
95
|
-
}
|
96
|
-
|
97
|
-
function getErrorLine() {
|
98
|
-
var error_line = /LINE (\d+)/g.exec($("#results").find('.alert-danger').text())
|
99
|
-
|
100
|
-
if (error_line) {
|
101
|
-
error_line = parseInt(error_line[1], 10)
|
102
|
-
if (editor.getSelectedText().length >= 10) {
|
103
|
-
error_line += editor.getSelectionRange().start.row
|
104
|
-
}
|
105
|
-
return error_line
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
var error_line = null
|
110
|
-
|
111
|
-
$(document).on("click", "#cancel", function (e) {
|
112
|
-
e.preventDefault()
|
113
|
-
|
114
|
-
cancelAllQueries()
|
115
|
-
|
116
|
-
queryDone()
|
117
|
-
|
118
|
-
$("#results").html("")
|
119
|
-
})
|
120
|
-
|
121
|
-
function queryDone() {
|
122
|
-
$("#run").removeClass("hide")
|
123
|
-
$("#cancel").addClass("hide")
|
124
|
-
}
|
125
|
-
|
126
|
-
$(document).on("click", "#run", function (e) {
|
127
|
-
e.preventDefault()
|
128
|
-
|
129
|
-
$(this).addClass("hide")
|
130
|
-
$("#cancel").removeClass("hide")
|
131
|
-
|
132
|
-
if (error_line) {
|
133
|
-
editor.getSession().removeGutterDecoration(error_line - 1, "error")
|
134
|
-
error_line = null
|
135
|
-
}
|
136
|
-
|
137
|
-
$("#results").html('<p class="text-muted">Loading...</p>')
|
138
|
-
|
139
|
-
var data = $.extend({}, params, {statement: getSQL(), data_source: $("#query_data_source").val()})
|
140
|
-
|
141
|
-
cancelAllQueries()
|
142
|
-
|
143
|
-
runQuery(data, function (data) {
|
144
|
-
queryDone()
|
145
|
-
|
146
|
-
$("#results").html(data)
|
147
|
-
|
148
|
-
error_line = getErrorLine()
|
149
|
-
if (error_line) {
|
150
|
-
editor.getSession().addGutterDecoration(error_line - 1, "error")
|
151
|
-
editor.scrollToLine(error_line, true, true, function () {})
|
152
|
-
editor.gotoLine(error_line, 0, true)
|
153
|
-
editor.focus()
|
154
|
-
}
|
155
|
-
}, function (data) {
|
156
|
-
// TODO show error
|
157
|
-
queryDone()
|
158
|
-
})
|
159
|
-
})
|
160
|
-
|
161
|
-
$(document).on("change", "#table_names", function () {
|
162
|
-
var val = $(this).val()
|
163
|
-
if (val.length > 0) {
|
164
|
-
var dataSource = $("#query_data_source").val()
|
165
|
-
editor.setValue(previewStatement[dataSource].replace("{table}", val), 1)
|
166
|
-
$("#run").click()
|
167
|
-
}
|
168
|
-
})
|
169
|
-
|
170
|
-
function showEditor() {
|
171
|
-
editor = ace.edit("editor")
|
172
|
-
editor.setTheme("ace/theme/twilight")
|
173
|
-
editor.getSession().setMode("ace/mode/sql")
|
174
|
-
editor.setOptions({
|
175
|
-
enableBasicAutocompletion: false,
|
176
|
-
enableSnippets: false,
|
177
|
-
enableLiveAutocompletion: false,
|
178
|
-
highlightActiveLine: false,
|
179
|
-
fontSize: 12,
|
180
|
-
minLines: 10
|
181
|
-
})
|
182
|
-
editor.renderer.setShowGutter(true)
|
183
|
-
editor.renderer.setPrintMarginColumn(false)
|
184
|
-
editor.renderer.setPadding(10)
|
185
|
-
editor.getSession().setUseWrapMode(true)
|
186
|
-
editor.commands.addCommand({
|
187
|
-
name: 'run',
|
188
|
-
bindKey: {win: 'Ctrl-Enter', mac: 'Command-Enter'},
|
189
|
-
exec: function(editor) {
|
190
|
-
$("#run").click()
|
191
|
-
},
|
192
|
-
readOnly: false // false if this command should not apply in readOnly mode
|
193
|
-
})
|
194
|
-
// fix command+L
|
195
|
-
editor.commands.removeCommands(["gotoline", "find"])
|
196
|
-
|
197
|
-
editor.getSession().on("change", function () {
|
198
|
-
$("#query_statement").val(editor.getValue())
|
199
|
-
adjustHeight()
|
200
|
-
})
|
201
|
-
adjustHeight()
|
202
|
-
$("#editor").show()
|
203
|
-
editor.focus()
|
204
|
-
}
|
205
|
-
|
206
78
|
preventBackspaceNav()
|
207
79
|
|
208
|
-
function updatePreviewSelect() {
|
209
|
-
var dataSource = $("#query_data_source").val()
|
210
|
-
$("#tables").load(Routes.blazer_tables_queries_path({data_source: dataSource}))
|
211
|
-
$("#view-schema").attr("href", Routes.blazer_schema_queries_path({data_source: dataSource}))
|
212
|
-
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// https://github.com/bevacqua/fuzzysearch
|
2
|
+
// Copyright 2015 Nicolas Bevacqua
|
3
|
+
// MIT License
|
4
|
+
|
5
|
+
function fuzzysearch (needle, haystack) {
|
6
|
+
var hlen = haystack.length;
|
7
|
+
var nlen = needle.length;
|
8
|
+
if (nlen > hlen) {
|
9
|
+
return false;
|
10
|
+
}
|
11
|
+
if (nlen === hlen) {
|
12
|
+
return needle === haystack;
|
13
|
+
}
|
14
|
+
outer: for (var i = 0, j = 0; i < nlen; i++) {
|
15
|
+
var nch = needle.charCodeAt(i);
|
16
|
+
while (j < hlen) {
|
17
|
+
if (haystack.charCodeAt(j++) === nch) {
|
18
|
+
continue outer;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
return false;
|
22
|
+
}
|
23
|
+
return true;
|
24
|
+
}
|
@@ -30,7 +30,7 @@ function runNext() {
|
|
30
30
|
|
31
31
|
function runQueryHelper(query) {
|
32
32
|
var xhr = $.ajax({
|
33
|
-
url: Routes.
|
33
|
+
url: Routes.run_queries_path(),
|
34
34
|
method: "POST",
|
35
35
|
data: query.data,
|
36
36
|
dataType: "html"
|
@@ -89,7 +89,7 @@ function cancelQuery(query) {
|
|
89
89
|
}
|
90
90
|
|
91
91
|
// tell server
|
92
|
-
var path = Routes.
|
92
|
+
var path = Routes.cancel_queries_path()
|
93
93
|
var data = {run_id: query.run_id, data_source: query.data_source}
|
94
94
|
if (navigator.sendBeacon) {
|
95
95
|
navigator.sendBeacon(path, csrfProtect(data))
|
@@ -103,5 +103,5 @@ function csrfProtect(payload) {
|
|
103
103
|
var param = $("meta[name=csrf-param]").attr("content")
|
104
104
|
var token = $("meta[name=csrf-token]").attr("content")
|
105
105
|
if (param && token) payload[param] = token
|
106
|
-
return new Blob([JSON.stringify(payload)], {type : "application/json charset=utf-8"})
|
106
|
+
return new Blob([JSON.stringify(payload)], {type : "application/json; charset=utf-8"})
|
107
107
|
}
|
@@ -1 +1,26 @@
|
|
1
|
-
|
1
|
+
<%#= JsRoutes.generate(engine: Blazer::Engine, prefix: Blazer::Engine.app.url_helpers.root_path) %>
|
2
|
+
|
3
|
+
// temp fix
|
4
|
+
var Routes = {
|
5
|
+
run_queries_path: function() {
|
6
|
+
return gon.root_path + "queries/run"
|
7
|
+
},
|
8
|
+
cancel_queries_path: function() {
|
9
|
+
return gon.root_path + "queries/cancel"
|
10
|
+
},
|
11
|
+
schema_queries_path: function(params) {
|
12
|
+
return gon.root_path + "queries/schema?data_source=" + params.data_source
|
13
|
+
},
|
14
|
+
tables_queries_path: function(params) {
|
15
|
+
return gon.root_path + "queries/tables?data_source=" + params.data_source
|
16
|
+
},
|
17
|
+
queries_path: function() {
|
18
|
+
return gon.root_path + "queries"
|
19
|
+
},
|
20
|
+
query_path: function(id) {
|
21
|
+
return gon.root_path + "queries/" + id
|
22
|
+
},
|
23
|
+
dashboard_path: function(id) {
|
24
|
+
return gon.root_path + "dashboards/" + id
|
25
|
+
}
|
26
|
+
}
|
@@ -66,7 +66,6 @@ input.search:focus {
|
|
66
66
|
}
|
67
67
|
|
68
68
|
#editor {
|
69
|
-
display: none;
|
70
69
|
height: 160px;
|
71
70
|
}
|
72
71
|
|
@@ -93,7 +92,7 @@ input.search:focus {
|
|
93
92
|
text-align: left;
|
94
93
|
}
|
95
94
|
|
96
|
-
.dashboard
|
95
|
+
.dashboard {
|
97
96
|
font-weight: bold;
|
98
97
|
}
|
99
98
|
|
@@ -178,3 +177,16 @@ input.search:focus {
|
|
178
177
|
[v-cloak] {
|
179
178
|
display: none;
|
180
179
|
}
|
180
|
+
|
181
|
+
.chart-container {
|
182
|
+
padding-top: 10px;
|
183
|
+
clear: both;
|
184
|
+
}
|
185
|
+
|
186
|
+
.chart-container h4 {
|
187
|
+
text-align: center;
|
188
|
+
}
|
189
|
+
|
190
|
+
.chart-container h4 a {
|
191
|
+
color: inherit;
|
192
|
+
}
|
@@ -19,6 +19,7 @@ module Blazer
|
|
19
19
|
if Blazer.before_action
|
20
20
|
before_action Blazer.before_action.to_sym
|
21
21
|
end
|
22
|
+
before_action :set_js_routes
|
22
23
|
|
23
24
|
layout "blazer/application"
|
24
25
|
|
@@ -101,5 +102,11 @@ module Blazer
|
|
101
102
|
action = resource.persisted? ? :edit : :new
|
102
103
|
render action, status: :unprocessable_entity
|
103
104
|
end
|
105
|
+
|
106
|
+
def set_js_routes
|
107
|
+
gon.push(
|
108
|
+
root_path: root_path
|
109
|
+
)
|
110
|
+
end
|
104
111
|
end
|
105
112
|
end
|
@@ -3,9 +3,13 @@ module Blazer
|
|
3
3
|
before_action :set_query, only: [:show, :edit, :update, :destroy, :refresh]
|
4
4
|
|
5
5
|
def home
|
6
|
-
|
6
|
+
if params[:filter] == "dashboards"
|
7
|
+
@queries = []
|
8
|
+
else
|
9
|
+
set_queries(1000)
|
10
|
+
end
|
7
11
|
|
8
|
-
if params[:filter]
|
12
|
+
if params[:filter] && params[:filter] != "dashboards"
|
9
13
|
@dashboards = [] # TODO show my dashboards
|
10
14
|
else
|
11
15
|
@dashboards = Blazer::Dashboard.order(:name)
|
@@ -176,8 +180,7 @@ module Blazer
|
|
176
180
|
end
|
177
181
|
|
178
182
|
def tables
|
179
|
-
|
180
|
-
render partial: "tables", layout: false
|
183
|
+
render json: Blazer.data_sources[params[:data_source]].tables
|
181
184
|
end
|
182
185
|
|
183
186
|
def schema
|
@@ -3,13 +3,18 @@ module Blazer
|
|
3
3
|
include ActionView::Helpers::TextHelper
|
4
4
|
|
5
5
|
default from: Blazer.from_email if Blazer.from_email
|
6
|
+
layout false
|
6
7
|
|
7
|
-
def state_change(check, state, state_was, rows_count, error)
|
8
|
+
def state_change(check, state, state_was, rows_count, error, columns, rows, column_types, check_type)
|
8
9
|
@check = check
|
9
10
|
@state = state
|
10
11
|
@state_was = state_was
|
11
12
|
@rows_count = rows_count
|
12
13
|
@error = error
|
14
|
+
@columns = columns
|
15
|
+
@rows = rows
|
16
|
+
@column_types = column_types
|
17
|
+
@check_type = check_type
|
13
18
|
mail to: check.emails, reply_to: check.emails, subject: "Check #{state.titleize}: #{check.query.name}"
|
14
19
|
end
|
15
20
|
|
data/app/models/blazer/check.rb
CHANGED
@@ -58,7 +58,7 @@ module Blazer
|
|
58
58
|
|
59
59
|
# do not notify on creation, except when not passing
|
60
60
|
if (state_was != "new" || state != "passing") && state != state_was && emails.present?
|
61
|
-
Blazer::CheckMailer.state_change(self, state, state_was, result.rows.size, message).deliver_later
|
61
|
+
Blazer::CheckMailer.state_change(self, state, state_was, result.rows.size, message, result.columns, result.rows.first(10).as_json, result.column_types, check_type).deliver_later
|
62
62
|
end
|
63
63
|
save! if changed?
|
64
64
|
end
|
@@ -1,6 +1,37 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
<%
|
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><%= pluralize(@rows_count, "row") %></p>
|
10
|
+
<p><strong>Sample</strong></p>
|
11
|
+
<table style="width: 100%; border-spacing: 0; border-collapse: collapse;">
|
12
|
+
<thead>
|
13
|
+
<% @columns.first(5).each do |column| %>
|
14
|
+
<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) %>%;">
|
15
|
+
<%= column %>
|
16
|
+
</th>
|
17
|
+
<% end %>
|
18
|
+
</thead>
|
19
|
+
<tbody>
|
20
|
+
<% @rows.first(10).each do |row| %>
|
21
|
+
<tr>
|
22
|
+
<% @columns.first(5).each_with_index do |column, i| %>
|
23
|
+
<td style="padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;">
|
24
|
+
<% value = row[i] %>
|
25
|
+
<% if @column_types[i] == "time" && value.to_s.length > 10 %>
|
26
|
+
<% value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value %>
|
27
|
+
<% end %>
|
28
|
+
<%= value %>
|
29
|
+
</td>
|
30
|
+
<% end %>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</tbody>
|
34
|
+
</table>
|
35
|
+
<% end %>
|
36
|
+
</body>
|
37
|
+
</html>
|
@@ -28,8 +28,8 @@
|
|
28
28
|
<%= render partial: "blazer/variables", locals: {action: dashboard_path(@dashboard)} %>
|
29
29
|
|
30
30
|
<% @queries.each_with_index do |query, i| %>
|
31
|
-
<div
|
32
|
-
<h4
|
31
|
+
<div class="chart-container">
|
32
|
+
<h4><%= link_to query.friendly_name, query_path(query, variable_params), target: "_blank" %></h4>
|
33
33
|
<div id="chart-<%= i %>" class="chart">
|
34
34
|
<p class="text-muted">Loading...</p>
|
35
35
|
</div>
|
@@ -2,69 +2,232 @@
|
|
2
2
|
<div class="alert alert-danger"><%= @query.errors.full_messages.first %></div>
|
3
3
|
<% end %>
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
<div
|
8
|
-
<div class=
|
9
|
-
|
10
|
-
|
11
|
-
<div id="editor"
|
5
|
+
<div id="app" v-cloak>
|
6
|
+
<%= form_for @query, url: (@query.persisted? ? query_path(@query, variable_params) : queries_path(variable_params)), html: {class: "the_form", 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>
|
12
26
|
</div>
|
13
27
|
</div>
|
14
|
-
<div class="
|
15
|
-
<div class="
|
16
|
-
<%=
|
28
|
+
<div class="col-xs-4">
|
29
|
+
<div class="form-group">
|
30
|
+
<%= f.label :name %>
|
31
|
+
<%= f.text_field :name, class: "form-control" %>
|
17
32
|
</div>
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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" %>
|
22
44
|
</div>
|
23
|
-
<script>
|
24
|
-
updatePreviewSelect();
|
25
|
-
$("#query_data_source").selectize().change(updatePreviewSelect);
|
26
|
-
</script>
|
27
|
-
<%= link_to "Run", "#", class: "btn btn-info", id: "run", style: "vertical-align: top; width: 70px;" %>
|
28
|
-
<%= link_to "Cancel", "#", class: "btn btn-danger hide", id: "cancel", style: "vertical-align: top; width: 70px;" %>
|
29
|
-
</div>
|
30
|
-
</div>
|
31
|
-
<div class="col-xs-4">
|
32
|
-
<div class="form-group">
|
33
|
-
<%= f.label :name %>
|
34
|
-
<%= f.text_field :name, class: "form-control" %>
|
35
|
-
</div>
|
36
|
-
<div class="form-group">
|
37
|
-
<%= f.label :description %>
|
38
|
-
<%= f.text_area :description, placeholder: "Optional", style: "height: 80px;", class: "form-control" %>
|
39
|
-
</div>
|
40
|
-
<div class="text-right">
|
41
|
-
<%= f.submit "For Enter Press", class: "hide" %>
|
42
45
|
<% if @query.persisted? %>
|
43
|
-
|
44
|
-
|
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 %>
|
45
56
|
<% end %>
|
46
|
-
<%= f.submit @query.persisted? ? "Update" : "Create", class: "btn btn-success" %>
|
47
57
|
</div>
|
48
|
-
<% if @query.persisted? %>
|
49
|
-
<% dashboards_count = @query.dashboards.count %>
|
50
|
-
<% checks_count = @query.checks.count %>
|
51
|
-
<% words = [] %>
|
52
|
-
<% words << pluralize(dashboards_count, "dashboard") if dashboards_count > 0 %>
|
53
|
-
<% words << pluralize(checks_count, "check") if checks_count > 0 %>
|
54
|
-
<% if words.any? %>
|
55
|
-
<div class="alert alert-info" style="margin-top: 10px; padding: 8px 12px;">
|
56
|
-
Part of <%= words.to_sentence %>. Be careful when editing.
|
57
|
-
</div>
|
58
|
-
<% end %>
|
59
|
-
<% end %>
|
60
58
|
</div>
|
61
|
-
|
62
|
-
<% end %>
|
59
|
+
<% end %>
|
63
60
|
|
64
|
-
<div id="results"
|
61
|
+
<div id="results">
|
62
|
+
<p class="text-muted" v-if="running">Loading...</p>
|
63
|
+
<div id="results-html" v-if="!running"></div>
|
64
|
+
</div>
|
65
|
+
</div>
|
65
66
|
|
66
67
|
<script>
|
67
68
|
var params = <%= raw blazer_json_escape(variable_params.to_json) %>;
|
68
69
|
var previewStatement = <%= raw blazer_json_escape(Hash[Blazer.data_sources.map { |k, v| [k, v.preview_statement] }].to_json) %>;
|
69
|
-
|
70
|
+
|
71
|
+
var app = new Vue({
|
72
|
+
el: "#app",
|
73
|
+
data: {
|
74
|
+
running: false,
|
75
|
+
results: "",
|
76
|
+
dataSource: "",
|
77
|
+
selectize: null,
|
78
|
+
editorHeight: "180px"
|
79
|
+
},
|
80
|
+
computed: {
|
81
|
+
dataSourcePath: function() {
|
82
|
+
return Routes.schema_queries_path({data_source: this.dataSource})
|
83
|
+
}
|
84
|
+
},
|
85
|
+
methods: {
|
86
|
+
run: function(e) {
|
87
|
+
this.running = true
|
88
|
+
this.results = ""
|
89
|
+
cancelAllQueries()
|
90
|
+
|
91
|
+
var data = $.extend({}, params, {statement: this.getSQL(), data_source: $("#query_data_source").val()})
|
92
|
+
|
93
|
+
var _this = this
|
94
|
+
|
95
|
+
runQuery(data, function (data) {
|
96
|
+
_this.running = false
|
97
|
+
_this.showResults(data)
|
98
|
+
|
99
|
+
errorLine = _this.getErrorLine()
|
100
|
+
if (errorLine) {
|
101
|
+
editor.getSession().addGutterDecoration(errorLine - 1, "error")
|
102
|
+
editor.scrollToLine(errorLine, true, true, function () {})
|
103
|
+
editor.gotoLine(errorLine, 0, true)
|
104
|
+
editor.focus()
|
105
|
+
}
|
106
|
+
}, function (data) {
|
107
|
+
_this.running = false
|
108
|
+
_this.showResults(data)
|
109
|
+
})
|
110
|
+
},
|
111
|
+
cancel: function(e) {
|
112
|
+
this.running = false
|
113
|
+
cancelAllQueries()
|
114
|
+
},
|
115
|
+
updateDataSource: function(dataSource) {
|
116
|
+
this.dataSource = dataSource
|
117
|
+
var _this = this
|
118
|
+
|
119
|
+
$.getJSON(Routes.tables_queries_path({data_source: this.dataSource}), function(data) {
|
120
|
+
var newOptions = []
|
121
|
+
for (var i = 0; i < data.length; i++) {
|
122
|
+
newOptions.push({text: data[i], value: data[i]})
|
123
|
+
}
|
124
|
+
var selectize = _this.selectize
|
125
|
+
selectize.clearOptions()
|
126
|
+
selectize.addOption(newOptions)
|
127
|
+
selectize.refreshOptions(false)
|
128
|
+
})
|
129
|
+
},
|
130
|
+
showEditor: function() {
|
131
|
+
var _this = this
|
132
|
+
|
133
|
+
editor = ace.edit("editor")
|
134
|
+
editor.setTheme("ace/theme/twilight")
|
135
|
+
editor.getSession().setMode("ace/mode/sql")
|
136
|
+
editor.setOptions({
|
137
|
+
enableBasicAutocompletion: false,
|
138
|
+
enableSnippets: false,
|
139
|
+
enableLiveAutocompletion: false,
|
140
|
+
highlightActiveLine: false,
|
141
|
+
fontSize: 12,
|
142
|
+
minLines: 10
|
143
|
+
})
|
144
|
+
editor.renderer.setShowGutter(true)
|
145
|
+
editor.renderer.setPrintMarginColumn(false)
|
146
|
+
editor.renderer.setPadding(10)
|
147
|
+
editor.getSession().setUseWrapMode(true)
|
148
|
+
editor.commands.addCommand({
|
149
|
+
name: "run",
|
150
|
+
bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
|
151
|
+
exec: function(editor) {
|
152
|
+
_this.run()
|
153
|
+
},
|
154
|
+
readOnly: false // false if this command should not apply in readOnly mode
|
155
|
+
})
|
156
|
+
// fix command+L
|
157
|
+
editor.commands.removeCommands(["gotoline", "find"])
|
158
|
+
|
159
|
+
this.editor = editor
|
160
|
+
|
161
|
+
editor.getSession().on("change", function () {
|
162
|
+
$("#query_statement").val(editor.getValue())
|
163
|
+
_this.adjustHeight()
|
164
|
+
})
|
165
|
+
this.adjustHeight()
|
166
|
+
editor.focus()
|
167
|
+
},
|
168
|
+
adjustHeight: function() {
|
169
|
+
// http://stackoverflow.com/questions/11584061/
|
170
|
+
var editor = this.editor
|
171
|
+
var lines = editor.getSession().getScreenLength()
|
172
|
+
if (lines < 9) {
|
173
|
+
lines = 9
|
174
|
+
}
|
175
|
+
|
176
|
+
this.editorHeight = ((lines + 1) * 16).toString() + "px"
|
177
|
+
|
178
|
+
Vue.nextTick(function () {
|
179
|
+
editor.resize()
|
180
|
+
})
|
181
|
+
},
|
182
|
+
getSQL: function() {
|
183
|
+
var selectedText = editor.getSelectedText()
|
184
|
+
var text = selectedText.length < 10 ? editor.getValue() : selectedText
|
185
|
+
return text.replace(/\n/g, "\r\n")
|
186
|
+
},
|
187
|
+
getErrorLine: function() {
|
188
|
+
var editor = this.editor
|
189
|
+
var errorLine = this.results.substring(0, 100).includes("alert-danger") && /LINE (\d+)/g.exec(this.results)
|
190
|
+
|
191
|
+
if (errorLine) {
|
192
|
+
errorLine = parseInt(errorLine[1], 10)
|
193
|
+
if (editor.getSelectedText().length >= 10) {
|
194
|
+
errorLine += editor.getSelectionRange().start.row
|
195
|
+
}
|
196
|
+
return errorLine
|
197
|
+
}
|
198
|
+
},
|
199
|
+
showResults(data) {
|
200
|
+
// can't do it the Vue way due to script tags in results
|
201
|
+
// this.results = data
|
202
|
+
|
203
|
+
Vue.nextTick(function () {
|
204
|
+
$("#results-html").html(data)
|
205
|
+
})
|
206
|
+
}
|
207
|
+
},
|
208
|
+
mounted: function() {
|
209
|
+
var _this = this
|
210
|
+
|
211
|
+
var $select = $("#table_names").selectize({})
|
212
|
+
var selectize = $select[0].selectize
|
213
|
+
selectize.on("change", function(val) {
|
214
|
+
editor.setValue(previewStatement[_this.dataSource].replace("{table}", val), 1)
|
215
|
+
_this.run()
|
216
|
+
selectize.clear(true)
|
217
|
+
selectize.blur()
|
218
|
+
})
|
219
|
+
this.selectize = selectize
|
220
|
+
|
221
|
+
this.updateDataSource($("#query_data_source").val())
|
222
|
+
|
223
|
+
var $dsSelect = $("#query_data_source").selectize({})
|
224
|
+
var dsSelectize = $dsSelect[0].selectize
|
225
|
+
dsSelectize.on("change", function(val) {
|
226
|
+
_this.updateDataSource(val)
|
227
|
+
dsSelectize.blur()
|
228
|
+
})
|
229
|
+
|
230
|
+
this.showEditor()
|
231
|
+
}
|
232
|
+
})
|
70
233
|
</script>
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<tbody class="list" v-cloak>
|
39
39
|
<tr v-for="query in visibleItems">
|
40
40
|
<td>
|
41
|
-
<
|
41
|
+
<a :href="itemPath(query)" :class="{dashboard: query.dashboard}">{{ query.name }}</a>
|
42
42
|
<span class="vars">{{ query.vars }}</span>
|
43
43
|
</td>
|
44
44
|
<td class="creator">{{ query.creator }}</td>
|
@@ -51,10 +51,14 @@
|
|
51
51
|
|
52
52
|
<script>
|
53
53
|
var prepareSearch = function (list) {
|
54
|
-
var i, q
|
54
|
+
var i, q, searchStr
|
55
55
|
for (i = 0; i < list.length; i++) {
|
56
56
|
q = list[i]
|
57
|
-
|
57
|
+
searchStr = q.name + q.creator
|
58
|
+
if (q.creator === "You") {
|
59
|
+
searchStr += "mine me"
|
60
|
+
}
|
61
|
+
q.searchStr = prepareQuery(searchStr)
|
58
62
|
}
|
59
63
|
}
|
60
64
|
|
@@ -82,7 +86,7 @@
|
|
82
86
|
if (this.more) {
|
83
87
|
var _this = this
|
84
88
|
|
85
|
-
$.getJSON(Routes.
|
89
|
+
$.getJSON(Routes.queries_path(), function (data) {
|
86
90
|
var i, j, newValues, val, size = 500;
|
87
91
|
|
88
92
|
var newValues = []
|
@@ -113,6 +117,7 @@
|
|
113
117
|
if (this.searchTerm.length > 0) {
|
114
118
|
var term = prepareQuery(this.searchTerm)
|
115
119
|
var items = []
|
120
|
+
var fuzzyItems = []
|
116
121
|
for (i = 0; i < this.listItems.length; i++) {
|
117
122
|
q = this.listItems[i]
|
118
123
|
if (q.searchStr.indexOf(term) !== -1) {
|
@@ -120,9 +125,11 @@
|
|
120
125
|
if (items.length == pageSize) {
|
121
126
|
break
|
122
127
|
}
|
128
|
+
} else if (fuzzysearch(term, q.searchStr)) {
|
129
|
+
fuzzyItems.push(q)
|
123
130
|
}
|
124
131
|
}
|
125
|
-
return items
|
132
|
+
return items.concat(fuzzyItems).slice(0, pageSize)
|
126
133
|
} else {
|
127
134
|
return this.listItems.slice(0, pageSize)
|
128
135
|
}
|
@@ -131,9 +138,9 @@
|
|
131
138
|
methods: {
|
132
139
|
itemPath: function (item) {
|
133
140
|
if (item.dashboard) {
|
134
|
-
return Routes.
|
141
|
+
return Routes.dashboard_path(item.to_param)
|
135
142
|
} else {
|
136
|
-
return Routes.
|
143
|
+
return Routes.query_path(item.to_param)
|
137
144
|
}
|
138
145
|
}
|
139
146
|
}
|
@@ -116,7 +116,7 @@ module Blazer
|
|
116
116
|
|
117
117
|
def set_timeout(timeout)
|
118
118
|
if postgresql? || redshift?
|
119
|
-
select_all("SET statement_timeout = #{timeout.to_i * 1000}")
|
119
|
+
select_all("SET #{use_transaction? ? "LOCAL " : ""}statement_timeout = #{timeout.to_i * 1000}")
|
120
120
|
elsif mysql?
|
121
121
|
select_all("SET max_execution_time = #{timeout.to_i * 1000}")
|
122
122
|
else
|
data/lib/blazer/version.rb
CHANGED
@@ -6,7 +6,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
6
6
|
t.text :description
|
7
7
|
t.text :statement
|
8
8
|
t.string :data_source
|
9
|
-
t.timestamps
|
9
|
+
t.timestamps null: false
|
10
10
|
end
|
11
11
|
|
12
12
|
create_table :blazer_audits do |t|
|
@@ -20,14 +20,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
20
20
|
create_table :blazer_dashboards do |t|
|
21
21
|
t.references :creator
|
22
22
|
t.text :name
|
23
|
-
t.timestamps
|
23
|
+
t.timestamps null: false
|
24
24
|
end
|
25
25
|
|
26
26
|
create_table :blazer_dashboard_queries do |t|
|
27
27
|
t.references :dashboard
|
28
28
|
t.references :query
|
29
29
|
t.integer :position
|
30
|
-
t.timestamps
|
30
|
+
t.timestamps null: false
|
31
31
|
end
|
32
32
|
|
33
33
|
create_table :blazer_checks do |t|
|
@@ -39,7 +39,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
39
39
|
t.string :check_type
|
40
40
|
t.text :message
|
41
41
|
t.timestamp :last_run_at
|
42
|
-
t.timestamps
|
42
|
+
t.timestamps null: false
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- app/assets/javascripts/blazer/bootstrap.js
|
140
140
|
- app/assets/javascripts/blazer/chartkick.js
|
141
141
|
- app/assets/javascripts/blazer/daterangepicker.js
|
142
|
+
- app/assets/javascripts/blazer/fuzzysearch.js
|
142
143
|
- app/assets/javascripts/blazer/highlight.pack.js
|
143
144
|
- app/assets/javascripts/blazer/jquery.js
|
144
145
|
- app/assets/javascripts/blazer/jquery.stickytableheaders.js
|
@@ -177,11 +178,9 @@ files:
|
|
177
178
|
- app/views/blazer/checks/new.html.erb
|
178
179
|
- app/views/blazer/dashboards/_form.html.erb
|
179
180
|
- app/views/blazer/dashboards/edit.html.erb
|
180
|
-
- app/views/blazer/dashboards/index.html.erb
|
181
181
|
- app/views/blazer/dashboards/new.html.erb
|
182
182
|
- app/views/blazer/dashboards/show.html.erb
|
183
183
|
- app/views/blazer/queries/_form.html.erb
|
184
|
-
- app/views/blazer/queries/_tables.html.erb
|
185
184
|
- app/views/blazer/queries/edit.html.erb
|
186
185
|
- app/views/blazer/queries/home.html.erb
|
187
186
|
- app/views/blazer/queries/new.html.erb
|
@@ -1,19 +0,0 @@
|
|
1
|
-
<% blazer_title "Dashboards" %>
|
2
|
-
|
3
|
-
<p style="float: right;"><%= link_to "New Dashboard", new_dashboard_path, class: "btn btn-info" %></p>
|
4
|
-
<%= render partial: "blazer/nav" %>
|
5
|
-
|
6
|
-
<table class="table">
|
7
|
-
<thead>
|
8
|
-
<tr>
|
9
|
-
<th>Dashboard</th>
|
10
|
-
</tr>
|
11
|
-
</thead>
|
12
|
-
<tbody>
|
13
|
-
<% @dashboards.each do |dashboard| %>
|
14
|
-
<tr>
|
15
|
-
<td><%= link_to dashboard.name, dashboard %></td>
|
16
|
-
</tr>
|
17
|
-
<% end %>
|
18
|
-
</tbody>
|
19
|
-
</table>
|