blazer 2.2.8 → 2.3.0
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 +5 -0
- data/README.md +67 -15
- data/app/controllers/blazer/queries_controller.rb +11 -2
- data/app/controllers/blazer/uploads_controller.rb +136 -0
- 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/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/queries/home.html.erb +3 -0
- 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/config/routes.rb +5 -0
- data/lib/blazer.rb +18 -0
- data/lib/blazer/adapters/sql_adapter.rb +1 -1
- 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 +1 -0
- 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
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c22ffd3b33bca8c53e32df849e161eaf654cc480b75ee09d4c1eba5a2728b2d1
|
4
|
+
data.tar.gz: 604d3ec2f9bc97ad85aef289392306132a6e8fd165f11f0b099a6219d40bd0c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 001d81b65da57fc18c62579df60864715ce6073bc1328696dd2d9c2b3dd14c3087ea9151c8676e853eaf5c7b705896ceab93db2a77e6703cdaffd7742e9b05dd
|
7
|
+
data.tar.gz: f7ad912b5bb3114f8d74eba474f394b4962d46a51c4f01a6d360c44da2e19943ebf17fd82a61dc3d2c0e8dbbee311ba6153a4d7fae3d3e2541a44b90f580db7b
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -25,6 +25,11 @@ Blazer is also available as a [Docker image](https://github.com/ankane/blazer-do
|
|
25
25
|
- [Charts](#charts)
|
26
26
|
- [Dashboards](#dashboards)
|
27
27
|
- [Checks](#checks)
|
28
|
+
- [Anomaly Detection](#anomaly-detection)
|
29
|
+
- [Forecasting](#forecasting)
|
30
|
+
- [Uploads](#uploads)
|
31
|
+
- [Data Sources](#data-sources)
|
32
|
+
- [Query Permissions](#query-permissions)
|
28
33
|
|
29
34
|
## Installation
|
30
35
|
|
@@ -428,7 +433,26 @@ anomaly_checks: r
|
|
428
433
|
|
429
434
|
If upgrading from version 1.4 or below, also follow the [upgrade instructions](#15).
|
430
435
|
|
431
|
-
If you’re on Heroku, follow
|
436
|
+
If you’re on Heroku, follow the additional instructions below.
|
437
|
+
|
438
|
+
### R on Heroku
|
439
|
+
|
440
|
+
Add the [R buildpack](https://github.com/virtualstaticvoid/heroku-buildpack-r) to your app.
|
441
|
+
|
442
|
+
```sh
|
443
|
+
heroku buildpacks:add --index 1 https://github.com/virtualstaticvoid/heroku-buildpack-r.git
|
444
|
+
```
|
445
|
+
|
446
|
+
And create an `init.R` with:
|
447
|
+
|
448
|
+
```r
|
449
|
+
if (!"AnomalyDetection" %in% installed.packages()) {
|
450
|
+
install.packages("remotes")
|
451
|
+
remotes::install_github("twitter/AnomalyDetection")
|
452
|
+
}
|
453
|
+
```
|
454
|
+
|
455
|
+
Commit and deploy away. The first deploy may take a few minutes.
|
432
456
|
|
433
457
|
## Forecasting
|
434
458
|
|
@@ -468,6 +492,30 @@ And add to `config/blazer.yml`:
|
|
468
492
|
forecasting: trend
|
469
493
|
```
|
470
494
|
|
495
|
+
## Uploads
|
496
|
+
|
497
|
+
Blazer has experimental support for creating database tables from CSV files. Run:
|
498
|
+
|
499
|
+
```sh
|
500
|
+
rails generate blazer:uploads
|
501
|
+
rails db:migrate
|
502
|
+
```
|
503
|
+
|
504
|
+
And add to `config/blazer.yml`:
|
505
|
+
|
506
|
+
```yml
|
507
|
+
uploads:
|
508
|
+
url: postgres://...
|
509
|
+
schema: uploads
|
510
|
+
data_source: main
|
511
|
+
```
|
512
|
+
|
513
|
+
This feature requires PostgreSQL. Create a new schema just for uploads to ensure existing tables aren’t overwritten.
|
514
|
+
|
515
|
+
```sql
|
516
|
+
CREATE SCHEMA uploads;
|
517
|
+
```
|
518
|
+
|
471
519
|
## Data Sources
|
472
520
|
|
473
521
|
Blazer supports multiple data sources :tada:
|
@@ -836,25 +884,14 @@ async: true
|
|
836
884
|
config.cache_store = :mem_cache_store
|
837
885
|
```
|
838
886
|
|
839
|
-
##
|
887
|
+
## Archiving
|
840
888
|
|
841
|
-
|
889
|
+
Archive queries that haven’t been viewed in over 90 days.
|
842
890
|
|
843
891
|
```sh
|
844
|
-
|
845
|
-
```
|
846
|
-
|
847
|
-
And create an `init.R` with:
|
848
|
-
|
849
|
-
```r
|
850
|
-
if (!"AnomalyDetection" %in% installed.packages()) {
|
851
|
-
install.packages("remotes")
|
852
|
-
remotes::install_github("twitter/AnomalyDetection")
|
853
|
-
}
|
892
|
+
rake blazer:archive_queries
|
854
893
|
```
|
855
894
|
|
856
|
-
Commit and deploy away. The first deploy may take a few minutes.
|
857
|
-
|
858
895
|
## Content Security Policy
|
859
896
|
|
860
897
|
If views are stuck with a `Loading...` message, there might be a problem with strict CSP settings in your app. This can be checked with Firefox or Chrome dev tools. You can allow Blazer to override these settings for its controllers with:
|
@@ -865,6 +902,21 @@ override_csp: true
|
|
865
902
|
|
866
903
|
## Upgrading
|
867
904
|
|
905
|
+
### 2.3
|
906
|
+
|
907
|
+
To archive queries, create a migration
|
908
|
+
|
909
|
+
```sh
|
910
|
+
rails g migration add_status_to_blazer_queries
|
911
|
+
```
|
912
|
+
|
913
|
+
with:
|
914
|
+
|
915
|
+
```ruby
|
916
|
+
add_column :blazer_queries, :status, :string
|
917
|
+
Blazer::Query.update_all(status: "active")
|
918
|
+
```
|
919
|
+
|
868
920
|
### 2.0
|
869
921
|
|
870
922
|
To use Slack notifications, create a migration
|
@@ -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
|
|
@@ -279,7 +288,7 @@ module Blazer
|
|
279
288
|
@queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
|
280
289
|
else
|
281
290
|
@queries = @queries.limit(limit) if limit
|
282
|
-
@queries = @queries.order(:name)
|
291
|
+
@queries = @queries.active.order(:name)
|
283
292
|
end
|
284
293
|
@queries = @queries.to_a
|
285
294
|
|
@@ -300,7 +309,7 @@ module Blazer
|
|
300
309
|
end
|
301
310
|
|
302
311
|
def queries_by_ids(favorite_query_ids)
|
303
|
-
queries = Blazer::Query.named.where(id: favorite_query_ids)
|
312
|
+
queries = Blazer::Query.active.named.where(id: favorite_query_ids)
|
304
313
|
queries = queries.includes(:creator) if Blazer.user_class
|
305
314
|
queries = queries.index_by(&:id)
|
306
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
|
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>
|
@@ -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({
|
@@ -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>
|
@@ -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>
|
data/config/routes.rb
CHANGED
data/lib/blazer.rb
CHANGED
@@ -32,6 +32,7 @@ require "blazer/engine"
|
|
32
32
|
|
33
33
|
module Blazer
|
34
34
|
class Error < StandardError; end
|
35
|
+
class UploadError < Error; end
|
35
36
|
class TimeoutNotSupported < Error; end
|
36
37
|
|
37
38
|
class << self
|
@@ -206,6 +207,23 @@ module Blazer
|
|
206
207
|
slack_webhook_url.present?
|
207
208
|
end
|
208
209
|
|
210
|
+
def self.uploads?
|
211
|
+
settings.key?("uploads")
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.uploads_connection
|
215
|
+
raise "Empty url for uploads" unless settings.dig("uploads", "url")
|
216
|
+
Blazer::UploadsConnection.connection
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.uploads_schema
|
220
|
+
settings.dig("uploads", "schema") || "uploads"
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.uploads_table_name(name)
|
224
|
+
uploads_connection.quote_table_name("#{uploads_schema}.#{name}")
|
225
|
+
end
|
226
|
+
|
209
227
|
def self.adapters
|
210
228
|
@adapters ||= {}
|
211
229
|
end
|
@@ -27,7 +27,7 @@ module Blazer
|
|
27
27
|
result = select_all("#{statement} /*#{comment}*/")
|
28
28
|
columns = result.columns
|
29
29
|
result.rows.each do |untyped_row|
|
30
|
-
rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] ? result.column_types[c].send(:cast_value, untyped_row[i]) : untyped_row[i] })
|
30
|
+
rows << (result.column_types.empty? ? untyped_row : columns.each_with_index.map { |c, i| untyped_row[i] && result.column_types[c] ? result.column_types[c].send(:cast_value, untyped_row[i]) : untyped_row[i] })
|
31
31
|
end
|
32
32
|
end
|
33
33
|
rescue => e
|
data/lib/blazer/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rails/generators/active_record"
|
2
|
+
|
3
|
+
module Blazer
|
4
|
+
module Generators
|
5
|
+
class UploadsGenerator < Rails::Generators::Base
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
8
|
+
|
9
|
+
def copy_migration
|
10
|
+
migration_template "uploads.rb", "db/migrate/create_blazer_uploads.rb", migration_version: migration_version
|
11
|
+
end
|
12
|
+
|
13
|
+
def migration_version
|
14
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/tasks/blazer.rake
CHANGED
@@ -8,4 +8,13 @@ namespace :blazer do
|
|
8
8
|
task send_failing_checks: :environment do
|
9
9
|
Blazer.send_failing_checks
|
10
10
|
end
|
11
|
+
|
12
|
+
desc "archive queries"
|
13
|
+
task archive_queries: :environment do
|
14
|
+
abort "Audits must be enabled to archive" unless Blazer.audit
|
15
|
+
abort "Missing status column - see https://github.com/ankane/blazer#23" unless Blazer::Query.column_names.include?("status")
|
16
|
+
|
17
|
+
viewed_query_ids = Blazer::Audit.where("created_at > ?", 90.days.ago).group(:query_id).count.keys.compact
|
18
|
+
Blazer::Query.active.where.not(id: viewed_query_ids).update_all(status: "archived")
|
19
|
+
end
|
11
20
|
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: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -145,6 +145,7 @@ files:
|
|
145
145
|
- app/controllers/blazer/checks_controller.rb
|
146
146
|
- app/controllers/blazer/dashboards_controller.rb
|
147
147
|
- app/controllers/blazer/queries_controller.rb
|
148
|
+
- app/controllers/blazer/uploads_controller.rb
|
148
149
|
- app/helpers/blazer/base_helper.rb
|
149
150
|
- app/mailers/blazer/check_mailer.rb
|
150
151
|
- app/mailers/blazer/slack_notifier.rb
|
@@ -155,6 +156,8 @@ files:
|
|
155
156
|
- app/models/blazer/dashboard_query.rb
|
156
157
|
- app/models/blazer/query.rb
|
157
158
|
- app/models/blazer/record.rb
|
159
|
+
- app/models/blazer/upload.rb
|
160
|
+
- app/models/blazer/uploads_connection.rb
|
158
161
|
- app/views/blazer/_nav.html.erb
|
159
162
|
- app/views/blazer/_variables.html.erb
|
160
163
|
- app/views/blazer/check_mailer/failing_checks.html.erb
|
@@ -175,6 +178,10 @@ files:
|
|
175
178
|
- app/views/blazer/queries/run.html.erb
|
176
179
|
- app/views/blazer/queries/schema.html.erb
|
177
180
|
- app/views/blazer/queries/show.html.erb
|
181
|
+
- app/views/blazer/uploads/_form.html.erb
|
182
|
+
- app/views/blazer/uploads/edit.html.erb
|
183
|
+
- app/views/blazer/uploads/index.html.erb
|
184
|
+
- app/views/blazer/uploads/new.html.erb
|
178
185
|
- app/views/layouts/blazer/application.html.erb
|
179
186
|
- config/routes.rb
|
180
187
|
- lib/blazer.rb
|
@@ -203,6 +210,8 @@ files:
|
|
203
210
|
- lib/generators/blazer/install_generator.rb
|
204
211
|
- lib/generators/blazer/templates/config.yml.tt
|
205
212
|
- lib/generators/blazer/templates/install.rb.tt
|
213
|
+
- lib/generators/blazer/templates/uploads.rb.tt
|
214
|
+
- lib/generators/blazer/uploads_generator.rb
|
206
215
|
- lib/tasks/blazer.rake
|
207
216
|
- licenses/LICENSE-ace.txt
|
208
217
|
- licenses/LICENSE-bootstrap.txt
|