blazer 2.2.4 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +110 -26
- data/app/assets/javascripts/blazer/Chart.js +13794 -12099
- data/app/assets/javascripts/blazer/Sortable.js +3695 -1526
- data/app/assets/javascripts/blazer/chartkick.js +296 -46
- data/app/assets/javascripts/blazer/daterangepicker.js +194 -269
- data/app/assets/javascripts/blazer/jquery.js +1150 -642
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +621 -287
- data/app/assets/javascripts/blazer/moment.js +5085 -2460
- data/app/assets/stylesheets/blazer/daterangepicker.css +394 -253
- data/app/controllers/blazer/base_controller.rb +4 -4
- data/app/controllers/blazer/dashboards_controller.rb +5 -2
- data/app/controllers/blazer/queries_controller.rb +14 -3
- data/app/controllers/blazer/uploads_controller.rb +136 -0
- data/app/mailers/blazer/slack_notifier.rb +1 -1
- data/app/models/blazer/query.rb +1 -0
- data/app/models/blazer/upload.rb +11 -0
- data/app/models/blazer/uploads_connection.rb +7 -0
- data/app/views/blazer/_nav.html.erb +3 -0
- data/app/views/blazer/_variables.html.erb +3 -1
- data/app/views/blazer/checks/_form.html.erb +1 -1
- data/app/views/blazer/checks/index.html.erb +3 -0
- data/app/views/blazer/dashboards/_form.html.erb +1 -1
- data/app/views/blazer/dashboards/show.html.erb +1 -1
- data/app/views/blazer/queries/home.html.erb +3 -0
- data/app/views/blazer/queries/run.html.erb +7 -6
- data/app/views/blazer/queries/show.html.erb +3 -1
- data/app/views/blazer/uploads/_form.html.erb +27 -0
- data/app/views/blazer/uploads/edit.html.erb +3 -0
- data/app/views/blazer/uploads/index.html.erb +55 -0
- data/app/views/blazer/uploads/new.html.erb +3 -0
- data/app/views/layouts/blazer/application.html.erb +2 -2
- data/config/routes.rb +5 -0
- data/lib/blazer.rb +20 -0
- data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
- data/lib/blazer/adapters/sql_adapter.rb +1 -1
- data/lib/blazer/result.rb +2 -21
- data/lib/blazer/version.rb +1 -1
- data/lib/generators/blazer/templates/config.yml.tt +6 -0
- data/lib/generators/blazer/templates/install.rb.tt +4 -3
- data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
- data/lib/generators/blazer/uploads_generator.rb +18 -0
- data/lib/tasks/blazer.rake +9 -0
- data/licenses/LICENSE-ace.txt +24 -0
- data/licenses/LICENSE-bootstrap.txt +21 -0
- data/licenses/LICENSE-chart.js.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +22 -0
- data/licenses/LICENSE-daterangepicker.txt +21 -0
- data/licenses/LICENSE-fuzzysearch.txt +20 -0
- data/licenses/LICENSE-highlight.js.txt +29 -0
- data/licenses/LICENSE-jquery-ujs.txt +20 -0
- data/licenses/LICENSE-jquery.txt +20 -0
- data/licenses/LICENSE-moment-timezone.txt +20 -0
- data/licenses/LICENSE-moment.txt +22 -0
- data/licenses/LICENSE-selectize.txt +202 -0
- data/licenses/LICENSE-sortable.txt +21 -0
- data/licenses/LICENSE-stickytableheaders.txt +20 -0
- data/licenses/LICENSE-stupidtable.txt +19 -0
- data/licenses/LICENSE-vue.txt +21 -0
- metadata +33 -7
|
@@ -96,12 +96,12 @@ module Blazer
|
|
|
96
96
|
# when permitted parameters are passed in Rails 6,
|
|
97
97
|
# they appear to be added as GET parameters
|
|
98
98
|
# root_url(params.permit(:host))
|
|
99
|
-
|
|
99
|
+
UNPERMITTED_KEYS = [:controller, :action, :id, :host, :query, :dashboard, :query_id, :query_ids, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source, :name, :fork_query_id, :blazer, :run_id, :script_name, :original_script_name]
|
|
100
100
|
|
|
101
|
-
# remove
|
|
101
|
+
# remove unpermitted keys from both params and permitted keys for better sleep
|
|
102
102
|
def variable_params(resource)
|
|
103
|
-
permitted_keys = resource.variables -
|
|
104
|
-
params.except(*
|
|
103
|
+
permitted_keys = resource.variables - UNPERMITTED_KEYS.map(&:to_s)
|
|
104
|
+
params.except(*UNPERMITTED_KEYS).slice(*permitted_keys).permit!
|
|
105
105
|
end
|
|
106
106
|
helper_method :variable_params
|
|
107
107
|
|
|
@@ -21,8 +21,11 @@ module Blazer
|
|
|
21
21
|
|
|
22
22
|
def show
|
|
23
23
|
@queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
|
|
24
|
+
@statements = []
|
|
24
25
|
@queries.each do |query|
|
|
25
|
-
|
|
26
|
+
statement = query.statement.dup
|
|
27
|
+
process_vars(statement, query.data_source)
|
|
28
|
+
@statements << statement
|
|
26
29
|
end
|
|
27
30
|
@bind_vars ||= []
|
|
28
31
|
|
|
@@ -51,7 +54,7 @@ module Blazer
|
|
|
51
54
|
|
|
52
55
|
def destroy
|
|
53
56
|
@dashboard.destroy
|
|
54
|
-
redirect_to
|
|
57
|
+
redirect_to root_path
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def refresh
|
|
@@ -38,11 +38,18 @@ module Blazer
|
|
|
38
38
|
if params[:fork_query_id]
|
|
39
39
|
@query.statement ||= Blazer::Query.find(params[:fork_query_id]).try(:statement)
|
|
40
40
|
end
|
|
41
|
+
if params[:upload_id]
|
|
42
|
+
upload = Blazer::Upload.find(params[:upload_id])
|
|
43
|
+
upload_settings = Blazer.settings["uploads"]
|
|
44
|
+
@query.data_source ||= upload_settings["data_source"]
|
|
45
|
+
@query.statement ||= "SELECT * FROM #{upload.table_name} LIMIT 10"
|
|
46
|
+
end
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
def create
|
|
44
50
|
@query = Blazer::Query.new(query_params)
|
|
45
51
|
@query.creator = blazer_user if @query.respond_to?(:creator)
|
|
52
|
+
@query.status = "active" if @query.respond_to?(:status)
|
|
46
53
|
|
|
47
54
|
if @query.save
|
|
48
55
|
redirect_to query_path(@query, variable_params(@query))
|
|
@@ -64,6 +71,8 @@ module Blazer
|
|
|
64
71
|
@sql_errors << error if error
|
|
65
72
|
end
|
|
66
73
|
|
|
74
|
+
@query.update!(status: "active") if @query.try(:status) == "archived"
|
|
75
|
+
|
|
67
76
|
Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
|
|
68
77
|
end
|
|
69
78
|
|
|
@@ -249,7 +258,9 @@ module Blazer
|
|
|
249
258
|
r[lat_index] && r[lon_index]
|
|
250
259
|
end.map do |r|
|
|
251
260
|
{
|
|
252
|
-
|
|
261
|
+
# Mapbox.js does sanitization with https://github.com/mapbox/sanitize-caja
|
|
262
|
+
# but we should do it here as well
|
|
263
|
+
title: r.each_with_index.map { |v, i| i == lat_index || i == lon_index ? nil : "<strong>#{ERB::Util.html_escape(@columns[i])}:</strong> #{ERB::Util.html_escape(v)}" }.compact.join("<br />").truncate(140),
|
|
253
264
|
latitude: r[lat_index],
|
|
254
265
|
longitude: r[lon_index]
|
|
255
266
|
}
|
|
@@ -277,7 +288,7 @@ module Blazer
|
|
|
277
288
|
@queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
|
|
278
289
|
else
|
|
279
290
|
@queries = @queries.limit(limit) if limit
|
|
280
|
-
@queries = @queries.order(:name)
|
|
291
|
+
@queries = @queries.active.order(:name)
|
|
281
292
|
end
|
|
282
293
|
@queries = @queries.to_a
|
|
283
294
|
|
|
@@ -298,7 +309,7 @@ module Blazer
|
|
|
298
309
|
end
|
|
299
310
|
|
|
300
311
|
def queries_by_ids(favorite_query_ids)
|
|
301
|
-
queries = Blazer::Query.named.where(id: favorite_query_ids)
|
|
312
|
+
queries = Blazer::Query.active.named.where(id: favorite_query_ids)
|
|
302
313
|
queries = queries.includes(:creator) if Blazer.user_class
|
|
303
314
|
queries = queries.index_by(&:id)
|
|
304
315
|
favorite_query_ids.map { |query_id| queries[query_id] }.compact
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class UploadsController < BaseController
|
|
3
|
+
before_action :ensure_uploads
|
|
4
|
+
before_action :set_upload, only: [:show, :edit, :update, :destroy]
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@uploads = Blazer::Upload.order(:table)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def new
|
|
11
|
+
@upload = Blazer::Upload.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
@upload = Blazer::Upload.new(upload_params)
|
|
16
|
+
# use creator_id instead of creator
|
|
17
|
+
# since we setup association without checking if column exists
|
|
18
|
+
@upload.creator = blazer_user if @upload.respond_to?(:creator_id=) && blazer_user
|
|
19
|
+
|
|
20
|
+
success = params.require(:upload).key?(:file)
|
|
21
|
+
if success
|
|
22
|
+
Blazer::Upload.transaction do
|
|
23
|
+
success = @upload.save
|
|
24
|
+
if success
|
|
25
|
+
begin
|
|
26
|
+
update_file(@upload)
|
|
27
|
+
rescue CSV::MalformedCSVError, Blazer::UploadError => e
|
|
28
|
+
@upload.errors.add(:base, e.message)
|
|
29
|
+
success = false
|
|
30
|
+
raise ActiveRecord::Rollback
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
@upload.errors.add(:base, "File can't be blank")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if success
|
|
39
|
+
redirect_to upload_path(@upload)
|
|
40
|
+
else
|
|
41
|
+
render_errors @upload
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def show
|
|
46
|
+
redirect_to new_query_path(upload_id: @upload.id)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def edit
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def update
|
|
53
|
+
original_table = @upload.table
|
|
54
|
+
@upload.assign_attributes(upload_params)
|
|
55
|
+
|
|
56
|
+
success = nil
|
|
57
|
+
Blazer::Upload.transaction do
|
|
58
|
+
success = @upload.save
|
|
59
|
+
if success
|
|
60
|
+
if params.require(:upload).key?(:file)
|
|
61
|
+
begin
|
|
62
|
+
update_file(@upload, drop: original_table)
|
|
63
|
+
rescue CSV::MalformedCSVError, Blazer::UploadError => e
|
|
64
|
+
@upload.errors.add(:base, e.message)
|
|
65
|
+
success = false
|
|
66
|
+
raise ActiveRecord::Rollback
|
|
67
|
+
end
|
|
68
|
+
elsif @upload.table != original_table
|
|
69
|
+
Blazer.uploads_connection.execute("ALTER TABLE #{Blazer.uploads_table_name(original_table)} RENAME TO #{Blazer.uploads_connection.quote_table_name(@upload.table)}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if success
|
|
75
|
+
redirect_to upload_path(@upload)
|
|
76
|
+
else
|
|
77
|
+
render_errors @upload
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def destroy
|
|
82
|
+
Blazer.uploads_connection.execute("DROP TABLE IF EXISTS #{@upload.table_name}")
|
|
83
|
+
@upload.destroy
|
|
84
|
+
redirect_to uploads_path
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def update_file(upload, drop: nil)
|
|
90
|
+
file = params.require(:upload).fetch(:file)
|
|
91
|
+
raise Blazer::UploadError, "File is not a CSV" if file.content_type != "text/csv"
|
|
92
|
+
raise Blazer::UploadError, "File is too large (maximum is 100MB)" if file.size > 100.megabytes
|
|
93
|
+
|
|
94
|
+
contents = file.read
|
|
95
|
+
rows = CSV.parse(contents, converters: %i[numeric date date_time])
|
|
96
|
+
columns = rows.shift.map(&:to_s)
|
|
97
|
+
column_types =
|
|
98
|
+
columns.size.times.map do |i|
|
|
99
|
+
values = rows.map { |r| r[i] }.uniq.compact
|
|
100
|
+
if values.all? { |v| v.is_a?(Integer) && v >= -9223372036854775808 && v <= 9223372036854775807 }
|
|
101
|
+
"bigint"
|
|
102
|
+
elsif values.all? { |v| v.is_a?(Numeric) }
|
|
103
|
+
"decimal"
|
|
104
|
+
elsif values.all? { |v| v.is_a?(DateTime) }
|
|
105
|
+
"timestamptz"
|
|
106
|
+
elsif values.all? { |v| v.is_a?(Date) }
|
|
107
|
+
"date"
|
|
108
|
+
else
|
|
109
|
+
"text"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# maybe SET LOCAL statement_timeout = '30s'
|
|
114
|
+
Blazer.uploads_connection.transaction do
|
|
115
|
+
Blazer.uploads_connection.execute("DROP TABLE IF EXISTS #{Blazer.uploads_table_name(drop)}") if drop
|
|
116
|
+
Blazer.uploads_connection.execute("CREATE TABLE #{upload.table_name} (#{columns.map.with_index { |c, i| "#{Blazer.uploads_connection.quote_column_name(c)} #{column_types[i]}" }.join(", ")})")
|
|
117
|
+
Blazer.uploads_connection.raw_connection.copy_data("COPY #{upload.table_name} FROM STDIN CSV HEADER") do
|
|
118
|
+
Blazer.uploads_connection.raw_connection.put_copy_data(contents)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def upload_params
|
|
124
|
+
params.require(:upload).except(:file).permit(:table, :description)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def set_upload
|
|
128
|
+
@upload = Blazer::Upload.find(params[:id])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# routes aren't added, but also check here
|
|
132
|
+
def ensure_uploads
|
|
133
|
+
render plain: "Uploads not enabled" unless Blazer.uploads?
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -62,7 +62,7 @@ module Blazer
|
|
|
62
62
|
|
|
63
63
|
# checks shouldn't have variables, but in any case,
|
|
64
64
|
# avoid passing variable params to url helpers
|
|
65
|
-
# (known unsafe parameters are removed, but
|
|
65
|
+
# (known unsafe parameters are removed, but still not ideal)
|
|
66
66
|
def self.query_url(id)
|
|
67
67
|
Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
|
|
68
68
|
end
|
data/app/models/blazer/query.rb
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Blazer
|
|
2
|
+
class Upload < Record
|
|
3
|
+
belongs_to :creator, optional: true, class_name: Blazer.user_class.to_s if Blazer.user_class
|
|
4
|
+
|
|
5
|
+
validates :table, presence: true, uniqueness: true, format: {with: /\A[a-z0-9_]+\z/, message: "can only contain lowercase letters, numbers, and underscores"}, length: {maximum: 63}
|
|
6
|
+
|
|
7
|
+
def table_name
|
|
8
|
+
Blazer.uploads_table_name(table)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
</button>
|
|
7
7
|
<ul class="dropdown-menu">
|
|
8
8
|
<li><%= link_to "Checks", checks_path %></li>
|
|
9
|
+
<% if Blazer.uploads? %>
|
|
10
|
+
<li><%= link_to "Uploads", uploads_path %></li>
|
|
11
|
+
<% end %>
|
|
9
12
|
<li role="separator" class="divider"></li>
|
|
10
13
|
<li><%= link_to "New Query", new_query_path %></li>
|
|
11
14
|
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
singleDatePicker: true,
|
|
43
43
|
locale: {format: format},
|
|
44
44
|
autoUpdateInput: false,
|
|
45
|
+
autoApply: true,
|
|
45
46
|
startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
|
|
46
47
|
})
|
|
47
48
|
// hack to start with empty date
|
|
@@ -95,7 +96,8 @@
|
|
|
95
96
|
},
|
|
96
97
|
startDate: dateStr(29),
|
|
97
98
|
endDate: dateStr(),
|
|
98
|
-
opens: "right"
|
|
99
|
+
opens: "right",
|
|
100
|
+
alwaysShowCalendars: true
|
|
99
101
|
},
|
|
100
102
|
function(start, end) {
|
|
101
103
|
setTimeInputs(start, end)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<%= f.select :query_id, [], {include_blank: true} %>
|
|
14
14
|
</div>
|
|
15
15
|
<script>
|
|
16
|
-
<%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
16
|
+
<%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
17
17
|
<%= blazer_js_var "items", [@check.query_id].compact %>
|
|
18
18
|
|
|
19
19
|
$("#check_query_id").selectize({options: queries, items: items, highlight: false, maxOptions: 100}).parents(".hide").removeClass("hide");
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
</button>
|
|
11
11
|
<ul class="dropdown-menu">
|
|
12
12
|
<li><%= link_to "Home", root_path %></li>
|
|
13
|
+
<% if Blazer.uploads? %>
|
|
14
|
+
<li><%= link_to "Uploads", uploads_path %></li>
|
|
15
|
+
<% end %>
|
|
13
16
|
<li role="separator" class="divider"></li>
|
|
14
17
|
<li><%= link_to "New Query", new_query_path %></li>
|
|
15
18
|
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<% end %>
|
|
32
32
|
|
|
33
33
|
<script>
|
|
34
|
-
<%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
34
|
+
<%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
|
|
35
35
|
<%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
|
|
36
36
|
|
|
37
37
|
var app = new Vue({
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
<script>
|
|
42
|
-
<%= blazer_js_var "data", {statement:
|
|
42
|
+
<%= blazer_js_var "data", {statement: @statements[i], query_id: query.id, data_source: query.data_source, only_chart: true} %>
|
|
43
43
|
|
|
44
44
|
runQuery(data, function (data) {
|
|
45
45
|
$("#chart-<%= i %>").html(data)
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
</button>
|
|
20
20
|
<ul class="dropdown-menu">
|
|
21
21
|
<li><%= link_to "Checks", checks_path %></li>
|
|
22
|
+
<% if Blazer.uploads? %>
|
|
23
|
+
<li><%= link_to "Uploads", uploads_path %></li>
|
|
24
|
+
<% end %>
|
|
22
25
|
<li role="separator" class="divider"></li>
|
|
23
26
|
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
24
27
|
<li><%= link_to "New Check", new_check_path %></li>
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
<%= blazer_js_var "mapboxAccessToken", Blazer.mapbox_access_token %>
|
|
81
81
|
<%= blazer_js_var "markers", @markers %>
|
|
82
82
|
L.mapbox.accessToken = mapboxAccessToken;
|
|
83
|
-
var map = L.mapbox.map('map'
|
|
83
|
+
var map = L.mapbox.map('map')
|
|
84
|
+
.addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
|
|
84
85
|
var featureLayer = L.mapbox.featureLayer().addTo(map);
|
|
85
86
|
var geojson = [];
|
|
86
87
|
for (var i = 0; i < markers.length; i++) {
|
|
@@ -106,13 +107,13 @@
|
|
|
106
107
|
</script>
|
|
107
108
|
<% elsif chart_type == "line" %>
|
|
108
109
|
<% chart_data = @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} } %>
|
|
109
|
-
<%= line_chart chart_data, chart_options %>
|
|
110
|
+
<%= line_chart chart_data, **chart_options %>
|
|
110
111
|
<% elsif chart_type == "line2" %>
|
|
111
|
-
<%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, chart_options %>
|
|
112
|
+
<%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, **chart_options %>
|
|
112
113
|
<% elsif chart_type == "pie" %>
|
|
113
|
-
<%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, chart_options %>
|
|
114
|
+
<%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, **chart_options %>
|
|
114
115
|
<% elsif chart_type == "bar" %>
|
|
115
|
-
<%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, chart_options %>
|
|
116
|
+
<%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, **chart_options %>
|
|
116
117
|
<% elsif chart_type == "bar2" %>
|
|
117
118
|
<% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
|
|
118
119
|
<% labels = first_20.map { |r| r[0] }.uniq %>
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
<% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
|
|
123
124
|
<% end %>
|
|
124
125
|
<% end %>
|
|
125
|
-
<%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, chart_options %>
|
|
126
|
+
<%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, **chart_options %>
|
|
126
127
|
<% elsif chart_type == "scatter" %>
|
|
127
128
|
<%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
|
|
128
129
|
<% elsif @only_chart %>
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
$("#results").addClass("query-error").html(message)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
<% data = variable_params(@query).merge(statement: @statement, query_id: @query.id, data_source: @query.data_source) %>
|
|
60
|
+
<% data.merge!(forecast: "t") if params[:forecast] %>
|
|
61
|
+
<%= blazer_js_var "data", data %>
|
|
60
62
|
|
|
61
63
|
runQuery(data, showRun, showError)
|
|
62
64
|
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%= form_for @upload, html: {class: "small-form"} do |f| %>
|
|
2
|
+
<% if @upload.errors.any? %>
|
|
3
|
+
<div class="alert alert-danger"><%= @upload.errors.full_messages.first %></div>
|
|
4
|
+
<% elsif !@upload.persisted? %>
|
|
5
|
+
<p>Create a database table from a CSV file. The table will be created in the <code><%= Blazer.settings["uploads"]["schema"] %></code> schema.</p>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<div class="form-group">
|
|
9
|
+
<%= f.label :table %>
|
|
10
|
+
<%= f.text_field :table, class: "form-control" %>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="form-group">
|
|
13
|
+
<%= f.label :description %>
|
|
14
|
+
<%= f.text_area :description, placeholder: "Optional", style: "height: 60px;", class: "form-control" %>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="form-group">
|
|
17
|
+
<%= f.label :file %>
|
|
18
|
+
<%= f.file_field :file, accept: "text/csv", style: "margin-top: 6px; margin-bottom: 21px;" %>
|
|
19
|
+
</div>
|
|
20
|
+
<p>
|
|
21
|
+
<% if @upload.persisted? %>
|
|
22
|
+
<%= link_to "Delete", upload_path(@upload), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<%= f.submit "Save", class: "btn btn-success" %>
|
|
25
|
+
<%= link_to "Back", :back, class: "btn btn-link" %>
|
|
26
|
+
</p>
|
|
27
|
+
<% end %>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<% blazer_title "Uploads" %>
|
|
2
|
+
|
|
3
|
+
<div id="header">
|
|
4
|
+
<div class="pull-right" style="line-height: 34px;">
|
|
5
|
+
<div class="btn-group">
|
|
6
|
+
<%= link_to "New Upload", new_upload_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><%= link_to "Checks", checks_path %></li>
|
|
14
|
+
<li role="separator" class="divider"></li>
|
|
15
|
+
<li><%= link_to "New Query", new_query_path %></li>
|
|
16
|
+
<li><%= link_to "New Dashboard", new_dashboard_path %></li>
|
|
17
|
+
<li><%= link_to "New Check", new_check_path %></li>
|
|
18
|
+
</ul>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<input id="search" type="text" placeholder="Start typing a table or person" style="width: 300px; display: inline-block;" class="search form-control" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<table id="uploads" class="table">
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
<th>Table</th>
|
|
29
|
+
<th style="width: 60%;"></th>
|
|
30
|
+
<% if Blazer.user_class %>
|
|
31
|
+
<th style="width: 20%; text-align: right;">Mastermind</th>
|
|
32
|
+
<% end%>
|
|
33
|
+
</tr>
|
|
34
|
+
</thead>
|
|
35
|
+
<tbody>
|
|
36
|
+
<% @uploads.each do |upload| %>
|
|
37
|
+
<tr>
|
|
38
|
+
<td><%= link_to upload.table, edit_upload_path(upload) %></td>
|
|
39
|
+
<td><%= truncate(upload.description, length: 100, separator: " ") %></td>
|
|
40
|
+
<% if Blazer.user_class %>
|
|
41
|
+
<td class="creator"><%= blazer_user && upload.creator == blazer_user ? "You" : upload.creator.try(Blazer.user_name) %></td>
|
|
42
|
+
<% end %>
|
|
43
|
+
</tr>
|
|
44
|
+
<% end %>
|
|
45
|
+
</tbody>
|
|
46
|
+
</table>
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
$("#search").on("keyup", function() {
|
|
50
|
+
var value = $(this).val().toLowerCase()
|
|
51
|
+
$("#uploads tbody tr").filter( function() {
|
|
52
|
+
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
|
|
53
|
+
})
|
|
54
|
+
}).focus()
|
|
55
|
+
</script>
|