maintenance_tasks 2.4.0 → 2.5.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/README.md +38 -0
- data/app/helpers/maintenance_tasks/tasks_helper.rb +10 -1
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +2 -39
- data/app/models/maintenance_tasks/run.rb +1 -2
- data/app/models/maintenance_tasks/task.rb +50 -0
- data/app/validators/maintenance_tasks/run_status_validator.rb +3 -0
- data/app/views/maintenance_tasks/runs/_arguments.html.erb +3 -21
- data/app/views/maintenance_tasks/runs/_metadata.html.erb +4 -0
- data/app/views/maintenance_tasks/runs/_run.html.erb +6 -1
- data/app/views/maintenance_tasks/runs/_serializable.html.erb +26 -0
- data/app/views/maintenance_tasks/tasks/_task.html.erb +3 -1
- data/lib/maintenance_tasks.rb +7 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f26c6afe0c4b45b6d191162ab2c37e3b058e8b6ae473b17cfc3724e29e638fce
|
4
|
+
data.tar.gz: 4308f3356279fa5879429077cb482d91033336bfa62c1c4afb51fc9d957b1785
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 383bc985f1a465e00889b20997d5718ff2039d5cdc6ca6b14a427a183fef4df3ff445eda2893d10b41d0bf243a6150ad5699afdfcbe3beaf280e4d35f130c1f5
|
7
|
+
data.tar.gz: d2b54c567a3d89e78f3538ca22b35d83dcb150e7520fc3014aa2040836f2e2741eb2bda700211c6411b8355b54ee19814655cf79827358d2d2f922fa855934af
|
data/README.md
CHANGED
@@ -299,6 +299,35 @@ module Maintenance
|
|
299
299
|
end
|
300
300
|
```
|
301
301
|
|
302
|
+
### Tasks with Custom Enumerators
|
303
|
+
|
304
|
+
If you have a special use case requiring iteration over an unsupported
|
305
|
+
collection type, such as external resources fetched from some API, you can
|
306
|
+
implement the `enumerator_builder(cursor:)` method in your task.
|
307
|
+
|
308
|
+
This method should return an `Enumerator`, yielding pairs of
|
309
|
+
`[item, cursor]`. Maintenance Tasks takes care of persisting the current
|
310
|
+
cursor position and will provide it as the `cursor` argument if your task is
|
311
|
+
interrupted or resumed. The `cursor` is stored as a `String`, so your custom
|
312
|
+
enumerator should handle serializing/deserializing the value if required.
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
# app/tasks/maintenance/custom_enumerator_task.rb
|
316
|
+
|
317
|
+
module Maintenance
|
318
|
+
class CustomEnumeratorTask < MaintenanceTasks::Task
|
319
|
+
def enumerator_builder(cursor:)
|
320
|
+
after_id = cursor&.to_i
|
321
|
+
PostAPI.index(after_id: after_id).map { |post| [post, post.id] }.to_enum
|
322
|
+
end
|
323
|
+
|
324
|
+
def process(post)
|
325
|
+
Post.create!(post)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
302
331
|
### Throttling
|
303
332
|
|
304
333
|
Maintenance tasks often modify a lot of data and can be taxing on your database.
|
@@ -897,6 +926,15 @@ controller class which **must inherit** from `ActionController::Base`.
|
|
897
926
|
|
898
927
|
If no value is specified, it will default to `"ActionController::Base"`.
|
899
928
|
|
929
|
+
#### Configure time after which the task will be considered stuck
|
930
|
+
|
931
|
+
To specify a time duration after which a task is considered stuck if it has not been updated,
|
932
|
+
you can configure `MaintenanceTasks.stuck_task_duration`. This duration should account for
|
933
|
+
job infrastructure events that may prevent the maintenance tasks job from being executed and cancelling the task.
|
934
|
+
|
935
|
+
The value for `MaintenanceTasks.stuck_task_duration` must be an `ActiveSupport::Duration`.
|
936
|
+
If no value is specified, it will default to 5 minutes.
|
937
|
+
|
900
938
|
### Metadata
|
901
939
|
|
902
940
|
`MaintenanceTasks.metadata` can be configured to specify a proc from which to
|
@@ -101,8 +101,17 @@ module MaintenanceTasks
|
|
101
101
|
)
|
102
102
|
end
|
103
103
|
|
104
|
-
# Return the appropriate field tag for the parameter
|
104
|
+
# Return the appropriate field tag for the parameter, based on its type.
|
105
|
+
# If the parameter has a `validates_inclusion_of` validator, return a dropdown list of options instead.
|
105
106
|
def parameter_field(form_builder, parameter_name)
|
107
|
+
inclusion_validator = form_builder.object.class.validators_on(parameter_name).find do |validator|
|
108
|
+
validator.kind == :inclusion
|
109
|
+
end
|
110
|
+
|
111
|
+
return form_builder.select(
|
112
|
+
parameter_name, inclusion_validator.options[:in], prompt: "Select a value"
|
113
|
+
) if inclusion_validator
|
114
|
+
|
106
115
|
case form_builder.object.class.attribute_types[parameter_name]
|
107
116
|
when ActiveModel::Type::Integer
|
108
117
|
form_builder.number_field(parameter_name)
|
@@ -32,45 +32,7 @@ module MaintenanceTasks
|
|
32
32
|
|
33
33
|
def build_enumerator(_run, cursor:)
|
34
34
|
cursor ||= @run.cursor
|
35
|
-
|
36
|
-
@enumerator = nil
|
37
|
-
|
38
|
-
@collection_enum = case collection
|
39
|
-
when :no_collection
|
40
|
-
enumerator_builder.build_once_enumerator(cursor: nil)
|
41
|
-
when ActiveRecord::Relation
|
42
|
-
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
43
|
-
when ActiveRecord::Batches::BatchEnumerator
|
44
|
-
if collection.start || collection.finish
|
45
|
-
raise ArgumentError, <<~MSG.squish
|
46
|
-
#{@task.class.name}#collection cannot support
|
47
|
-
a batch enumerator with the "start" or "finish" options.
|
48
|
-
MSG
|
49
|
-
end
|
50
|
-
|
51
|
-
# For now, only support automatic count based on the enumerator for
|
52
|
-
# batches
|
53
|
-
enumerator_builder.active_record_on_batch_relations(
|
54
|
-
collection.relation,
|
55
|
-
cursor: cursor,
|
56
|
-
batch_size: collection.batch_size,
|
57
|
-
)
|
58
|
-
when Array
|
59
|
-
enumerator_builder.build_array_enumerator(collection, cursor: cursor&.to_i)
|
60
|
-
when BatchCsvCollectionBuilder::BatchCsv
|
61
|
-
JobIteration::CsvEnumerator.new(collection.csv).batches(
|
62
|
-
batch_size: collection.batch_size,
|
63
|
-
cursor: cursor&.to_i,
|
64
|
-
)
|
65
|
-
when CSV
|
66
|
-
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor&.to_i)
|
67
|
-
else
|
68
|
-
raise ArgumentError, <<~MSG.squish
|
69
|
-
#{@task.class.name}#collection must be either an
|
70
|
-
Active Record Relation, ActiveRecord::Batches::BatchEnumerator,
|
71
|
-
Array, or CSV.
|
72
|
-
MSG
|
73
|
-
end
|
35
|
+
@collection_enum = @task.enumerator_builder(cursor: cursor)
|
74
36
|
throttle_enumerator(@collection_enum)
|
75
37
|
end
|
76
38
|
|
@@ -165,6 +127,7 @@ module MaintenanceTasks
|
|
165
127
|
@ticker.persist if defined?(@ticker)
|
166
128
|
|
167
129
|
if defined?(@run)
|
130
|
+
@run.cursor = cursor_position
|
168
131
|
@run.persist_error(error)
|
169
132
|
|
170
133
|
task_context = {
|
@@ -32,7 +32,6 @@ module MaintenanceTasks
|
|
32
32
|
:cancelled,
|
33
33
|
]
|
34
34
|
COMPLETED_STATUSES = [:succeeded, :errored, :cancelled]
|
35
|
-
STUCK_TASK_TIMEOUT = 5.minutes
|
36
35
|
|
37
36
|
enum status: STATUSES.to_h { |status| [status, status.to_s] }
|
38
37
|
|
@@ -342,7 +341,7 @@ module MaintenanceTasks
|
|
342
341
|
#
|
343
342
|
# @return [Boolean] whether the Run is stuck.
|
344
343
|
def stuck?
|
345
|
-
(cancelling? || pausing?) && updated_at <=
|
344
|
+
(cancelling? || pausing?) && updated_at <= MaintenanceTasks.stuck_task_duration.ago
|
346
345
|
end
|
347
346
|
|
348
347
|
# Performs validation on the task_name attribute.
|
@@ -247,5 +247,55 @@ module MaintenanceTasks
|
|
247
247
|
def count
|
248
248
|
self.class.collection_builder_strategy.count(self)
|
249
249
|
end
|
250
|
+
|
251
|
+
# Default enumeration builder. You may override this method to return any
|
252
|
+
# Enumerator yielding pairs of `[item, item_cursor]`.
|
253
|
+
#
|
254
|
+
# @param cursor [String, nil] cursor position to resume from, or nil on
|
255
|
+
# initial call.
|
256
|
+
#
|
257
|
+
# @return [Enumerator]
|
258
|
+
def enumerator_builder(cursor:)
|
259
|
+
collection = self.collection
|
260
|
+
|
261
|
+
job_iteration_builder = JobIteration::EnumeratorBuilder.new(nil)
|
262
|
+
|
263
|
+
case collection
|
264
|
+
when :no_collection
|
265
|
+
job_iteration_builder.build_once_enumerator(cursor: nil)
|
266
|
+
when ActiveRecord::Relation
|
267
|
+
job_iteration_builder.active_record_on_records(collection, cursor: cursor)
|
268
|
+
when ActiveRecord::Batches::BatchEnumerator
|
269
|
+
if collection.start || collection.finish
|
270
|
+
raise ArgumentError, <<~MSG.squish
|
271
|
+
#{self.class.name}#collection cannot support
|
272
|
+
a batch enumerator with the "start" or "finish" options.
|
273
|
+
MSG
|
274
|
+
end
|
275
|
+
|
276
|
+
# For now, only support automatic count based on the enumerator for
|
277
|
+
# batches
|
278
|
+
job_iteration_builder.active_record_on_batch_relations(
|
279
|
+
collection.relation,
|
280
|
+
cursor: cursor,
|
281
|
+
batch_size: collection.batch_size,
|
282
|
+
)
|
283
|
+
when Array
|
284
|
+
job_iteration_builder.build_array_enumerator(collection, cursor: cursor&.to_i)
|
285
|
+
when BatchCsvCollectionBuilder::BatchCsv
|
286
|
+
JobIteration::CsvEnumerator.new(collection.csv).batches(
|
287
|
+
batch_size: collection.batch_size,
|
288
|
+
cursor: cursor&.to_i,
|
289
|
+
)
|
290
|
+
when CSV
|
291
|
+
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor&.to_i)
|
292
|
+
else
|
293
|
+
raise ArgumentError, <<~MSG.squish
|
294
|
+
#{self.class.name}#collection must be either an
|
295
|
+
Active Record Relation, ActiveRecord::Batches::BatchEnumerator,
|
296
|
+
Array, or CSV.
|
297
|
+
MSG
|
298
|
+
end
|
299
|
+
end
|
250
300
|
end
|
251
301
|
end
|
@@ -60,6 +60,9 @@ module MaintenanceTasks
|
|
60
60
|
# interrupted -> errored occurs when the task is deleted while it is
|
61
61
|
# interrupted.
|
62
62
|
"interrupted" => ["running", "pausing", "cancelling", "errored"],
|
63
|
+
# errored -> enqueued occurs when the task is retried after encounting an
|
64
|
+
# error.
|
65
|
+
"errored" => ["enqueued"],
|
63
66
|
}
|
64
67
|
|
65
68
|
# Validate whether a transition from one Run status
|
@@ -1,22 +1,4 @@
|
|
1
|
-
<% if
|
2
|
-
<
|
3
|
-
|
4
|
-
<table class="table">
|
5
|
-
<tbody>
|
6
|
-
<% run.arguments.transform_values(&:to_s).each do |key, value| %>
|
7
|
-
<tr>
|
8
|
-
<td class="is-family-monospace"><%= key %></td>
|
9
|
-
<td>
|
10
|
-
<% next if value.empty? %>
|
11
|
-
<% if value.include?("\n") %>
|
12
|
-
<pre><%= value %></pre>
|
13
|
-
<% else %>
|
14
|
-
<code><%= value %></code>
|
15
|
-
<% end %>
|
16
|
-
</td>
|
17
|
-
</tr>
|
18
|
-
<% end %>
|
19
|
-
</tbody>
|
20
|
-
</table>
|
21
|
-
</div>
|
1
|
+
<% if arguments.present? %>
|
2
|
+
<h6 class="title is-6">Arguments:</h6>
|
3
|
+
<%= render "maintenance_tasks/runs/serializable", serializable: arguments %>
|
22
4
|
<% end %>
|
@@ -2,6 +2,7 @@
|
|
2
2
|
<h5 class="title is-5">
|
3
3
|
<%= time_tag run.created_at, title: run.created_at.utc.iso8601 %>
|
4
4
|
<%= status_tag run.status %>
|
5
|
+
<span class="is-pulled-right" title="Run ID">#<%= run.id %></span>
|
5
6
|
</h5>
|
6
7
|
|
7
8
|
<%= progress run %>
|
@@ -16,12 +17,16 @@
|
|
16
17
|
|
17
18
|
<%= render "maintenance_tasks/runs/csv", run: run %>
|
18
19
|
<%= tag.hr if run.csv_file.present? && run.arguments.present? %>
|
19
|
-
<%= render "maintenance_tasks/runs/arguments",
|
20
|
+
<%= render "maintenance_tasks/runs/arguments", arguments: run.arguments %>
|
21
|
+
<%= tag.hr if run.csv_file.present? || run.arguments.present? && run.metadata.present? %>
|
22
|
+
<%= render "maintenance_tasks/runs/metadata", metadata: run.metadata %>
|
20
23
|
|
21
24
|
<div class="buttons">
|
22
25
|
<% if run.paused? %>
|
23
26
|
<%= button_to 'Resume', resume_task_run_path(@task, run), method: :put, class: 'button is-primary', disabled: @task.deleted? %>
|
24
27
|
<%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger' %>
|
28
|
+
<% elsif run.errored? %>
|
29
|
+
<%= button_to 'Resume', resume_task_run_path(@task, run), method: :put, class: 'button is-primary', disabled: @task.deleted? %>
|
25
30
|
<% elsif run.cancelling? %>
|
26
31
|
<% if run.stuck? %>
|
27
32
|
<%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger', disabled: @task.deleted? %>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<% if serializable.present? %>
|
2
|
+
<% case serializable %>
|
3
|
+
<% when Hash %>
|
4
|
+
<div class="table-container">
|
5
|
+
<table class="table">
|
6
|
+
<tbody>
|
7
|
+
<% serializable.transform_values(&:to_s).each do |key, value| %>
|
8
|
+
<tr>
|
9
|
+
<td class="is-family-monospace"><%= key %></td>
|
10
|
+
<td>
|
11
|
+
<% next if value.empty? %>
|
12
|
+
<% if value.include?("\n") %>
|
13
|
+
<pre><%= value %></pre>
|
14
|
+
<% else %>
|
15
|
+
<code><%= value %></code>
|
16
|
+
<% end %>
|
17
|
+
</td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</tbody>
|
21
|
+
</table>
|
22
|
+
</div>
|
23
|
+
<% else %>
|
24
|
+
<code><%= serializable.inspect %></code>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
@@ -21,6 +21,8 @@
|
|
21
21
|
|
22
22
|
<%= render "maintenance_tasks/runs/csv", run: run %>
|
23
23
|
<%= tag.hr if run.csv_file.present? && run.arguments.present? %>
|
24
|
-
<%= render "maintenance_tasks/runs/arguments",
|
24
|
+
<%= render "maintenance_tasks/runs/arguments", arguments: run.arguments %>
|
25
|
+
<%= tag.hr if run.csv_file.present? || run.arguments.present? && run.metadata.present? %>
|
26
|
+
<%= render "maintenance_tasks/runs/metadata", metadata: run.metadata %>
|
25
27
|
<% end %>
|
26
28
|
</div>
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -89,4 +89,11 @@ module MaintenanceTasks
|
|
89
89
|
#
|
90
90
|
# @return [Proc] generates a hash containing the metadata to be stored on the Run
|
91
91
|
mattr_accessor :metadata, default: nil
|
92
|
+
|
93
|
+
# @!attribute stuck_task_duration
|
94
|
+
# @scope class
|
95
|
+
# The duration after which a task is considered stuck and can be force cancelled.
|
96
|
+
#
|
97
|
+
# @return [ActiveSupport::Duration] the threshold in seconds after which a task is considered stuck.
|
98
|
+
mattr_accessor :stuck_task_duration, default: 5.minutes
|
92
99
|
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.
|
4
|
+
version: 2.5.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:
|
11
|
+
date: 2024-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -128,7 +128,9 @@ files:
|
|
128
128
|
- app/views/layouts/maintenance_tasks/application.html.erb
|
129
129
|
- app/views/maintenance_tasks/runs/_arguments.html.erb
|
130
130
|
- app/views/maintenance_tasks/runs/_csv.html.erb
|
131
|
+
- app/views/maintenance_tasks/runs/_metadata.html.erb
|
131
132
|
- app/views/maintenance_tasks/runs/_run.html.erb
|
133
|
+
- app/views/maintenance_tasks/runs/_serializable.html.erb
|
132
134
|
- app/views/maintenance_tasks/runs/info/_cancelled.html.erb
|
133
135
|
- app/views/maintenance_tasks/runs/info/_cancelling.html.erb
|
134
136
|
- app/views/maintenance_tasks/runs/info/_custom.html.erb
|
@@ -169,7 +171,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
169
171
|
licenses:
|
170
172
|
- MIT
|
171
173
|
metadata:
|
172
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.
|
174
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.5.0
|
173
175
|
allowed_push_host: https://rubygems.org
|
174
176
|
post_install_message:
|
175
177
|
rdoc_options: []
|
@@ -186,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
188
|
- !ruby/object:Gem::Version
|
187
189
|
version: '0'
|
188
190
|
requirements: []
|
189
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.5.5
|
190
192
|
signing_key:
|
191
193
|
specification_version: 4
|
192
194
|
summary: A Rails engine for queuing and managing maintenance tasks
|