maintenance_tasks 2.1.1 → 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 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