maintenance_tasks 2.1.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c395c28308277378c5ac627845abc512bba455e11e085558aa4ef106f73b1130
4
- data.tar.gz: 7e44c8b5c484aa44008e2ebd3dd35458f84dec2be2741c20cc1ae20871c10f48
3
+ metadata.gz: a14c442d683123d8aabb30db8c73c15a31ddd24852356aa64b0255b1cb2312e0
4
+ data.tar.gz: d5df5cbfa0cf9feab143ae2ea15cb5a0013978b552a068d9a588fed4e04178bd
5
5
  SHA512:
6
- metadata.gz: 825418a040b4b15f0a98c5e28b06c553e240ec7b0ba10b2adec7b28180203d3e7094cb4e4b2933076d7564050b42f33504ede5b830e1819274393cf9d574f058
7
- data.tar.gz: d2d7aa0c97a37851d8540edea5a4e890b1eeb9fbfc740c157070558fbc51e60ef9d3ff882b5ab21cd9b80481f7b2692d9a3f8fbaa03bbdcd4256f1b51aa669ce
6
+ metadata.gz: 528669b01c611fe0e502683665182bddb2a98f2a74911c5d957f3868fca2ff57323a07eb2093acdfedc39e44057f8346702f70fc67cf027f59a32b985a6f7cea
7
+ data.tar.gz: bb00d7ef688b49ad9ae295650eea0b57718ea95aea6b341780011d882ae2b0f9ac830241cd9663d3bd7c9b9e08b3ee9bf6daa91a4ef2e4c53b69a15350c2da06
data/README.md CHANGED
@@ -804,6 +804,47 @@ MaintenanceTasks.backtrace_cleaner = cleaner
804
804
  If none is specified, the default `Rails.backtrace_cleaner` will be used to
805
805
  clean backtraces.
806
806
 
807
+ #### Customizing the parent controller for the web UI
808
+
809
+ `MaintenanceTasks.parent_controller` can be configured to specify a controller class for all of the web UI engine's
810
+ controllers to inherit from.
811
+
812
+ This allows applications with common logic in their `ApplicationController` (or
813
+ any other controller) to optionally configure the web UI to inherit that logic
814
+ with a simple assignment in the initializer.
815
+
816
+ ```ruby
817
+ # config/initializers/maintenance_tasks.rb
818
+
819
+ MaintenanceTasks.parent_controller = "Services::CustomController"
820
+
821
+ # app/controllers/services/custom_controller.rb
822
+
823
+ class Services::CustomController < ActionController::Base
824
+ include CustomSecurityThings
825
+ include CustomLoggingThings
826
+ ...
827
+ end
828
+ ```
829
+
830
+ The parent controller value **must** be a string corresponding to an existing
831
+ controller class which **must inherit** from `ActionController::Base`.
832
+
833
+ If no value is specified, it will default to `"ActionController::Base"`.
834
+
835
+ ### Metadata
836
+
837
+ `MaintenanceTasks.metadata` can be configured to specify a proc from which to get extra information about the run.
838
+ Since this proc will be ran in the context of the `MaintenanceTasks.parent_controller`, it can be used to keep the id
839
+ or email of the user who performed the maintenance task.
840
+
841
+ ```ruby
842
+ # config/initializers/maintenance_tasks.rb
843
+ MaintenanceTasks.metadata = -> do
844
+ { user_email: current_user.email }
845
+ end
846
+ ```
847
+
807
848
  ## Upgrading
808
849
 
809
850
  Use bundler to check for and upgrade to newer versions. After installing a new
@@ -4,7 +4,7 @@ module MaintenanceTasks
4
4
  # Base class for all controllers used by this engine.
5
5
  #
6
6
  # Can be extended to add different authentication and authorization code.
7
- class ApplicationController < ActionController::Base
7
+ class ApplicationController < MaintenanceTasks.parent_controller.constantize
8
8
  BULMA_CDN = "https://cdn.jsdelivr.net"
9
9
 
10
10
  content_security_policy do |policy|
@@ -20,11 +20,6 @@ module MaintenanceTasks
20
20
  policy.frame_ancestors(:self)
21
21
  end
22
22
 
23
- before_action do
24
- request.content_security_policy_nonce_generator ||= ->(_request) { SecureRandom.base64(16) }
25
- request.content_security_policy_nonce_directives = ["style-src"]
26
- end
27
-
28
23
  protect_from_forgery with: :exception
29
24
  end
30
25
  end
@@ -14,13 +14,14 @@ module MaintenanceTasks
14
14
  name: params.fetch(:task_id),
15
15
  csv_file: params[:csv_file],
16
16
  arguments: params.fetch(:task_arguments, {}).permit!.to_h,
17
+ metadata: MaintenanceTasks.metadata&.call,
17
18
  &block
18
19
  )
19
20
  redirect_to(task_path(task))
20
21
  rescue ActiveRecord::RecordInvalid => error
21
22
  redirect_to(task_path(error.record.task_name), alert: error.message)
22
23
  rescue ActiveRecord::ValueTooLong => error
23
- task_name = params.fetch(:id)
24
+ task_name = params.fetch(:task_id)
24
25
  redirect_to(task_path(task_name), alert: error.message)
25
26
  rescue Runner::EnqueuingError => error
26
27
  redirect_to(task_path(error.run.task_name), alert: error.message)
@@ -109,7 +109,7 @@ module MaintenanceTasks
109
109
  when ActiveModel::Type::Decimal, ActiveModel::Type::Float
110
110
  form_builder.number_field(parameter_name, { step: "any" })
111
111
  when ActiveModel::Type::DateTime
112
- form_builder.datetime_field(parameter_name)
112
+ form_builder.datetime_field(parameter_name) + datetime_field_help_text
113
113
  when ActiveModel::Type::Date
114
114
  form_builder.date_field(parameter_name)
115
115
  when ActiveModel::Type::Time
@@ -120,5 +120,19 @@ module MaintenanceTasks
120
120
  form_builder.text_area(parameter_name, class: "textarea")
121
121
  end
122
122
  end
123
+
124
+ # Return helper text for the datetime-local form field.
125
+ def datetime_field_help_text
126
+ text =
127
+ if Time.zone_default.nil? || Time.zone_default.name == "UTC"
128
+ "Timezone: UTC."
129
+ else
130
+ "Timezone: #{Time.now.zone}."
131
+ end
132
+ tag.div(
133
+ tag.p(text),
134
+ class: "content is-small",
135
+ )
136
+ end
123
137
  end
124
138
  end
@@ -56,14 +56,14 @@ module MaintenanceTasks
56
56
  batch_size: collection.batch_size,
57
57
  )
58
58
  when Array
59
- enumerator_builder.build_array_enumerator(collection, cursor: cursor)
59
+ enumerator_builder.build_array_enumerator(collection, cursor: cursor&.to_i)
60
60
  when BatchCsvCollectionBuilder::BatchCsv
61
61
  JobIteration::CsvEnumerator.new(collection.csv).batches(
62
62
  batch_size: collection.batch_size,
63
- cursor: cursor,
63
+ cursor: cursor&.to_i,
64
64
  )
65
65
  when CSV
66
- JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
66
+ JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor&.to_i)
67
67
  else
68
68
  raise ArgumentError, <<~MSG.squish
69
69
  #{@task.class.name}#collection must be either an
@@ -32,14 +32,11 @@ module MaintenanceTasks
32
32
  :cancelled,
33
33
  ]
34
34
  COMPLETED_STATUSES = [:succeeded, :errored, :cancelled]
35
- COMPLETED_RUNS_LIMIT = 10
36
35
  STUCK_TASK_TIMEOUT = 5.minutes
37
36
 
38
37
  enum status: STATUSES.to_h { |status| [status, status.to_s] }
39
38
 
40
- validates :task_name, on: :create, inclusion: {
41
- in: ->(_) { Task.available_tasks.map(&:to_s) },
42
- }
39
+ validate :task_name_belongs_to_a_valid_task, on: :create
43
40
  validate :csv_attachment_presence, on: :create
44
41
  validate :csv_content_type, on: :create
45
42
  validate :validate_task_arguments, on: :create
@@ -49,9 +46,11 @@ module MaintenanceTasks
49
46
  if Rails.gem_version >= Gem::Version.new("7.1.alpha")
50
47
  serialize :backtrace, coder: YAML
51
48
  serialize :arguments, coder: JSON
49
+ serialize :metadata, coder: JSON
52
50
  else
53
51
  serialize :backtrace
54
52
  serialize :arguments, JSON
53
+ serialize :metadata, JSON
55
54
  end
56
55
 
57
56
  scope :active, -> { where(status: ACTIVE_STATUSES) }
@@ -338,6 +337,15 @@ module MaintenanceTasks
338
337
  cancelling? && updated_at <= STUCK_TASK_TIMEOUT.ago
339
338
  end
340
339
 
340
+ # Performs validation on the task_name attribute.
341
+ # A Run must be associated with a valid Task to be valid.
342
+ # In order to confirm that, the Task is looked up by name.
343
+ def task_name_belongs_to_a_valid_task
344
+ Task.named(task_name)
345
+ rescue Task::NotFoundError
346
+ errors.add(:task_name, "must be the name of an existing Task.")
347
+ end
348
+
341
349
  # Performs validation on the presence of a :csv_file attachment.
342
350
  # A Run for a Task that uses CsvCollection must have an attached :csv_file
343
351
  # to be valid. Conversely, a Run for a Task that doesn't use CsvCollection
@@ -39,8 +39,8 @@ module MaintenanceTasks
39
39
  # creating the Run.
40
40
  # @raise [ActiveRecord::ValueTooLong] if the creation of the Run fails due
41
41
  # to a value being too long for the column type.
42
- def run(name:, csv_file: nil, arguments: {}, run_model: Run)
43
- run = run_model.new(task_name: name, arguments: arguments)
42
+ def run(name:, csv_file: nil, arguments: {}, run_model: Run, metadata: nil)
43
+ run = run_model.new(task_name: name, arguments: arguments, metadata: metadata)
44
44
  if csv_file
45
45
  run.csv_file.attach(csv_file)
46
46
  run.csv_file.filename = filename(name)
@@ -9,9 +9,9 @@
9
9
  <td>
10
10
  <% next if value.empty? %>
11
11
  <% if value.include?("\n") %>
12
- <pre><%= value %><pre>
12
+ <pre><%= value %></pre>
13
13
  <% else %>
14
- <code><%= value %><code>
14
+ <code><%= value %></code>
15
15
  <% end %>
16
16
  </td>
17
17
  </tr>
@@ -1,6 +1,6 @@
1
1
  <div class="box">
2
2
  <h5 class="title is-5">
3
- <%= time_tag run.created_at, title: run.created_at %>
3
+ <%= time_tag run.created_at, title: run.created_at.utc.iso8601 %>
4
4
  <%= status_tag run.status %>
5
5
  </h5>
6
6
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeCursorToString < ActiveRecord::Migration[6.0]
4
+ # This migration will clear all existing data in the cursor column with MySQL.
5
+ # Ensure no Tasks are paused when this migration is deployed, or they will be resumed from the start.
6
+ # Running tasks are able to gracefully handle this change, even if interrupted.
7
+ def up
8
+ change_table(:maintenance_tasks_runs) do |t|
9
+ t.change(:cursor, :string)
10
+ end
11
+ end
12
+
13
+ def down
14
+ change_table(:maintenance_tasks_runs) do |t|
15
+ t.change(:cursor, :bigint)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMetadataToRuns < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column(:maintenance_tasks_runs, :metadata, :text)
6
+ end
7
+ end
@@ -72,4 +72,21 @@ module MaintenanceTasks
72
72
  # @return [Proc] the callback to perform when an error occurs in the Task.
73
73
  mattr_accessor :error_handler, default:
74
74
  ->(_error, _task_context, _errored_element) {}
75
+
76
+ # @!attribute parent_controller
77
+ # @scope class
78
+ #
79
+ # The parent controller all web UI controllers will inherit from.
80
+ # Must be a class that inherits from `ActionController::Base`.
81
+ # Defaults to `"ActionController::Base"`
82
+ #
83
+ # @return [String] the name of the parent controller for web UI.
84
+ mattr_accessor :parent_controller, default: "ActionController::Base"
85
+
86
+ # @!attribute metadata
87
+ # @scope class
88
+ # The Proc to call from the controller to generate metadata that will be persisted on the Run.
89
+ #
90
+ # @return [Proc] generates a hash containing the metadata to be stored on the Run
91
+ mattr_accessor :metadata, default: nil
75
92
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maintenance_tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-20 00:00:00.000000000 Z
11
+ date: 2023-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -131,11 +131,13 @@ files:
131
131
  - app/views/maintenance_tasks/tasks/show.html.erb
132
132
  - config/routes.rb
133
133
  - db/migrate/20201211151756_create_maintenance_tasks_runs.rb
134
+ - db/migrate/20210219212931_change_cursor_to_string.rb
134
135
  - db/migrate/20210225152418_remove_index_on_task_name.rb
135
136
  - db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb
136
137
  - db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb
137
138
  - db/migrate/20220706101937_change_runs_tick_columns_to_bigints.rb
138
139
  - db/migrate/20220713131925_add_index_on_task_name_and_status_to_runs.rb
140
+ - db/migrate/20230622035229_add_metadata_to_runs.rb
139
141
  - exe/maintenance_tasks
140
142
  - lib/generators/maintenance_tasks/install_generator.rb
141
143
  - lib/generators/maintenance_tasks/task_generator.rb
@@ -154,7 +156,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
154
156
  licenses:
155
157
  - MIT
156
158
  metadata:
157
- source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.1.1
159
+ source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.3.0
158
160
  allowed_push_host: https://rubygems.org
159
161
  post_install_message:
160
162
  rdoc_options: []
@@ -164,14 +166,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
166
  requirements:
165
167
  - - ">="
166
168
  - !ruby/object:Gem::Version
167
- version: '0'
169
+ version: '3.0'
168
170
  required_rubygems_version: !ruby/object:Gem::Requirement
169
171
  requirements:
170
172
  - - ">="
171
173
  - !ruby/object:Gem::Version
172
174
  version: '0'
173
175
  requirements: []
174
- rubygems_version: 3.4.10
176
+ rubygems_version: 3.4.18
175
177
  signing_key:
176
178
  specification_version: 4
177
179
  summary: A Rails engine for queuing and managing maintenance tasks