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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4289e0500fd1a7576f07fb77fc69e8c35e07b622283e757731048e2c6aacf975
4
- data.tar.gz: 743d90a4d483cffd37e89a5b5d6299385b558481db8756a5c48b310850dfc58a
3
+ metadata.gz: c22ffd3b33bca8c53e32df849e161eaf654cc480b75ee09d4c1eba5a2728b2d1
4
+ data.tar.gz: 604d3ec2f9bc97ad85aef289392306132a6e8fd165f11f0b099a6219d40bd0c9
5
5
  SHA512:
6
- metadata.gz: 0de93b666eaf3212e9e6069ed85359068afb4d46952f9bd4cc363f065571d82b2c4cd9c3e8cf8be45cb530585971b1f01d3617b188faa7b9db6f1db322cf14a7
7
- data.tar.gz: 617672bf28e0e9692d934a0e5446607a4fcd925f8ce41e47e384c2e8d7b57baacf5f58dc4271101dbedd849b4fad1032bdc097ee02a69bf7612721ce2ef29428
6
+ metadata.gz: 001d81b65da57fc18c62579df60864715ce6073bc1328696dd2d9c2b3dd14c3087ea9151c8676e853eaf5c7b705896ceab93db2a77e6703cdaffd7742e9b05dd
7
+ data.tar.gz: f7ad912b5bb3114f8d74eba474f394b4962d46a51c4f01a6d360c44da2e19943ebf17fd82a61dc3d2c0e8dbbee311ba6153a4d7fae3d3e2541a44b90f580db7b
@@ -1,3 +1,8 @@
1
+ ## 2.3.0 (2020-11-16)
2
+
3
+ - Added support for archiving queries
4
+ - Added experimental support for uploads
5
+
1
6
  ## 2.2.8 (2020-11-01)
2
7
 
3
8
  - Fixed error when deleting dashboard
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 [these additional instructions](#anomaly-detection-on-heroku).
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
- ## Anomaly Detection on Heroku
887
+ ## Archiving
840
888
 
841
- Add the [R buildpack](https://github.com/virtualstaticvoid/heroku-buildpack-r) to your app.
889
+ Archive queries that haven’t been viewed in over 90 days.
842
890
 
843
891
  ```sh
844
- heroku buildpacks:add --index 1 https://github.com/virtualstaticvoid/heroku-buildpack-r.git
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
@@ -8,6 +8,7 @@ module Blazer
8
8
 
9
9
  validates :statement, presence: true
10
10
 
11
+ scope :active, -> { column_names.include?("status") ? where(status: "active") : all }
11
12
  scope :named, -> { where("blazer_queries.name <> ''") }
12
13
 
13
14
  def to_param
@@ -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
@@ -0,0 +1,7 @@
1
+ module Blazer
2
+ class UploadsConnection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ establish_connection Blazer.settings["uploads"]["url"] if Blazer.uploads?
6
+ end
7
+ 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,3 @@
1
+ <% blazer_title "Edit Upload" %>
2
+
3
+ <%= render partial: "form" %>
@@ -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>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "New Upload" %>
2
+
3
+ <%= render partial: "form" %>
@@ -16,5 +16,10 @@ Blazer::Engine.routes.draw do
16
16
  post :refresh, on: :member
17
17
  end
18
18
 
19
+ if Blazer.uploads?
20
+ resources :uploads do
21
+ end
22
+ end
23
+
19
24
  root to: "queries#home"
20
25
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Blazer
2
- VERSION = "2.2.8"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -71,3 +71,9 @@ check_schedules:
71
71
 
72
72
  # enable map
73
73
  # mapbox_access_token: <%%= ENV["MAPBOX_ACCESS_TOKEN"] %>
74
+
75
+ # enable uploads
76
+ # uploads:
77
+ # url: <%%= ENV["BLAZER_UPLOADS_URL"] %>
78
+ # schema: uploads
79
+ # data_source: main
@@ -6,6 +6,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
6
6
  t.text :description
7
7
  t.text :statement
8
8
  t.string :data_source
9
+ t.string :status
9
10
  t.timestamps null: false
10
11
  end
11
12
 
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :blazer_uploads do |t|
4
+ t.references :creator
5
+ t.string :table
6
+ t.text :description
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -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
@@ -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.2.8
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-01 00:00:00.000000000 Z
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