maintenance_tasks 2.10.0 → 2.11.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 +43 -29
- data/app/helpers/maintenance_tasks/application_helper.rb +1 -1
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +16 -3
- data/app/models/maintenance_tasks/run.rb +1 -5
- data/app/models/maintenance_tasks/task.rb +15 -0
- data/app/views/maintenance_tasks/runs/_run.html.erb +1 -1
- data/app/views/maintenance_tasks/tasks/_task.html.erb +1 -1
- data/lib/maintenance_tasks/engine.rb +6 -0
- data/lib/maintenance_tasks.rb +26 -12
- metadata +12 -17
- data/lib/patches/active_record_batch_enumerator.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ac2fd148131a277d37ddc609173b8549d01d3e6a9f78786cf729bd5da4f65b0
|
4
|
+
data.tar.gz: a2db7dfe8a9b0a958d943f88305fbe6d8988fd171901c49cd688070d8125124e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8e4c707d55ca479cca9f1592cc65359cc460d70cfd1e4ea4b2caf7c0a92617b4f243feb83c9f3657cb81a76691ed660c91bdafba73634b819a0fa44a719a2c8
|
7
|
+
data.tar.gz: 0efab90ffbdea1131f72de57443c8ed01fc30550d88c3ebbfe77b8e48a37c27dec8ec2ed7591ebdf9ea463e82ff4df7fcb649b111076a7302e10fce5720eb437
|
data/README.md
CHANGED
@@ -74,9 +74,9 @@ The generator creates and runs a migration to add the necessary table to your
|
|
74
74
|
database. It also mounts Maintenance Tasks in your `config/routes.rb`. By
|
75
75
|
default the web UI can be accessed in the new `/maintenance_tasks` path.
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
This gem uses the [Rails Error Reporter](https://guides.rubyonrails.org/error_reporting.html) to report errors. If you are using a bug
|
78
|
+
tracking service you may want to subscribe to the reporter. See [Reporting Errors](#reporting-errors)
|
79
|
+
for more information.
|
80
80
|
|
81
81
|
### Active Job Dependency
|
82
82
|
|
@@ -988,44 +988,58 @@ If you are stuck in `pausing` and wish to preserve your tasks's position
|
|
988
988
|
There are a few configurable options for the gem. Custom configurations should
|
989
989
|
be placed in a `maintenance_tasks.rb` initializer.
|
990
990
|
|
991
|
-
####
|
991
|
+
#### Reporting errors
|
992
992
|
|
993
993
|
Exceptions raised while a Task is performing are rescued and information about
|
994
994
|
the error is persisted and visible in the UI.
|
995
995
|
|
996
|
-
|
997
|
-
|
996
|
+
Errors are also sent to the `Rails.error.reporter`, which can be configured by your
|
997
|
+
application. See the [Error Reporting in Rails Applications](https://guides.rubyonrails.org/error_reporting.html) guide for more details.
|
998
998
|
|
999
|
-
|
1000
|
-
# config/initializers/maintenance_tasks.rb
|
1001
|
-
|
1002
|
-
MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
|
1003
|
-
Bugsnag.notify(error) do |notification|
|
1004
|
-
notification.add_metadata(:task, task_context)
|
1005
|
-
end
|
1006
|
-
end
|
1007
|
-
```
|
1008
|
-
|
1009
|
-
The error handler should be a lambda that accepts three arguments:
|
999
|
+
Reports to the error reporter will contain the following data:
|
1010
1000
|
|
1011
1001
|
* `error`: The exception that was raised.
|
1012
|
-
* `
|
1002
|
+
* `context`: A hash with additional information about the Task and the
|
1013
1003
|
error:
|
1014
1004
|
* `task_name`: The name of the Task that errored
|
1015
1005
|
* `started_at`: The time the Task started
|
1016
1006
|
* `ended_at`: The time the Task errored
|
1007
|
+
* `run_id`: The id of the errored Task run
|
1008
|
+
* `tick_count`: The tick count at the time of the error
|
1009
|
+
* `errored_element`: The element, if any, that was being processed when the Task
|
1010
|
+
raised an exception. If you would like to pass this object to your exception
|
1011
|
+
monitoring service, make sure you **sanitize the object** to avoid leaking
|
1012
|
+
sensitive data and **convert it to a format** that is compatible with your bug
|
1013
|
+
tracker.
|
1014
|
+
* `source`: This will be `maintenance_tasks`
|
1015
|
+
|
1016
|
+
Note that `context` may be empty if the Task produced an error before any
|
1017
|
+
context could be gathered (for example, if deserializing the job to process
|
1018
|
+
your Task failed).
|
1019
|
+
|
1020
|
+
#### Reporting errors during iteration
|
1021
|
+
|
1022
|
+
By default, errors raised during task iteration will be raised to the application
|
1023
|
+
and iteration will stop. However, you may want to handle some errors and continue
|
1024
|
+
iteration. `MaintenanceTasks::Task.report_on` can be used to rescue certain
|
1025
|
+
exceptions and report them to the Rails error reporter.
|
1026
|
+
|
1027
|
+
```ruby
|
1028
|
+
class MyTask < MaintenanceTasks::Task
|
1029
|
+
report_on(MyException)
|
1030
|
+
end
|
1031
|
+
```
|
1032
|
+
|
1033
|
+
`MaintenanceTasks::Task` also includes `ActiveSupport::Rescuable` which you can use
|
1034
|
+
to implement custom error handling.
|
1017
1035
|
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
tracker. For example, Bugsnag only sends the id and class name of Active
|
1026
|
-
Record objects in order to protect sensitive data. CSV rows, on the other
|
1027
|
-
hand, are converted to strings and passed raw to Bugsnag, so make sure to
|
1028
|
-
filter any personal data from these objects before adding them to a report.
|
1036
|
+
```ruby
|
1037
|
+
class MyTask < MaintenanceTasks::Task
|
1038
|
+
rescue_from(MyException) do |exception|
|
1039
|
+
handle(exception)
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
```
|
1029
1043
|
|
1030
1044
|
#### Customizing the maintenance tasks module
|
1031
1045
|
|
@@ -14,7 +14,7 @@ module MaintenanceTasks
|
|
14
14
|
# @param datetime [ActiveSupport::TimeWithZone] the time to be presented.
|
15
15
|
# @return [String] the HTML to render with the relative datetime in words.
|
16
16
|
def time_ago(datetime)
|
17
|
-
time_tag(datetime, title: datetime.utc
|
17
|
+
time_tag(datetime, title: datetime.utc, class: "is-clickable") do
|
18
18
|
time_ago_in_words(datetime) + " ago"
|
19
19
|
end
|
20
20
|
end
|
@@ -33,6 +33,7 @@ module MaintenanceTasks
|
|
33
33
|
def build_enumerator(_run, cursor:)
|
34
34
|
cursor ||= @run.cursor
|
35
35
|
self.cursor_position = cursor
|
36
|
+
enumerator_builder = self.enumerator_builder
|
36
37
|
@collection_enum = @task.enumerator_builder(cursor: cursor)
|
37
38
|
|
38
39
|
@collection_enum ||= case (collection = @task.collection)
|
@@ -75,6 +76,9 @@ module MaintenanceTasks
|
|
75
76
|
MSG
|
76
77
|
end
|
77
78
|
|
79
|
+
unless @collection_enum.is_a?(JobIteration.enumerator_builder::Wrapper)
|
80
|
+
@collection_enum = enumerator_builder.wrap(enumerator_builder, @collection_enum)
|
81
|
+
end
|
78
82
|
throttle_enumerator(@collection_enum)
|
79
83
|
end
|
80
84
|
|
@@ -108,7 +112,7 @@ module MaintenanceTasks
|
|
108
112
|
end
|
109
113
|
rescue => error
|
110
114
|
@errored_element = input
|
111
|
-
raise error
|
115
|
+
raise error unless @task.rescue_with_handler(error)
|
112
116
|
end
|
113
117
|
|
114
118
|
def before_perform
|
@@ -177,11 +181,20 @@ module MaintenanceTasks
|
|
177
181
|
task_name: @run.task_name,
|
178
182
|
started_at: @run.started_at,
|
179
183
|
ended_at: @run.ended_at,
|
184
|
+
run_id: @run.id,
|
185
|
+
tick_count: @run.tick_count,
|
180
186
|
}
|
181
187
|
end
|
182
|
-
errored_element = @errored_element if defined?(@errored_element)
|
188
|
+
task_context[:errored_element] = @errored_element if defined?(@errored_element)
|
183
189
|
ensure
|
184
|
-
MaintenanceTasks.error_handler
|
190
|
+
if MaintenanceTasks.instance_variable_get(:@error_handler)
|
191
|
+
errored_element = task_context.delete(:errored_element)
|
192
|
+
MaintenanceTasks.error_handler.call(error, task_context.except(:run_id, :tick_count), errored_element)
|
193
|
+
elsif Rails.gem_version >= Gem::Version.new("7.1")
|
194
|
+
Rails.error.report(error, context: task_context, source: "maintenance-tasks")
|
195
|
+
else
|
196
|
+
Rails.error.report(error, handled: true, context: task_context)
|
197
|
+
end
|
185
198
|
end
|
186
199
|
end
|
187
200
|
end
|
@@ -33,11 +33,7 @@ module MaintenanceTasks
|
|
33
33
|
]
|
34
34
|
COMPLETED_STATUSES = [:succeeded, :errored, :cancelled]
|
35
35
|
|
36
|
-
|
37
|
-
enum :status, STATUSES.to_h { |status| [status, status.to_s] }
|
38
|
-
else
|
39
|
-
enum status: STATUSES.to_h { |status| [status, status.to_s] }
|
40
|
-
end
|
36
|
+
enum :status, STATUSES.to_h { |status| [status, status.to_s] }
|
41
37
|
|
42
38
|
after_save :instrument_status_change
|
43
39
|
|
@@ -8,6 +8,7 @@ module MaintenanceTasks
|
|
8
8
|
include ActiveModel::Attributes
|
9
9
|
include ActiveModel::AttributeAssignment
|
10
10
|
include ActiveModel::Validations
|
11
|
+
include ActiveSupport::Rescuable
|
11
12
|
|
12
13
|
class NotFoundError < NameError; end
|
13
14
|
|
@@ -200,6 +201,20 @@ module MaintenanceTasks
|
|
200
201
|
set_callback(:error, :after, *filter_list, &block)
|
201
202
|
end
|
202
203
|
|
204
|
+
# Rescue listed exceptions during an iteration and report them to the error reporter, then
|
205
|
+
# continue iteration.
|
206
|
+
#
|
207
|
+
# @param exceptions list of exceptions to rescue and report
|
208
|
+
def report_on(*exceptions)
|
209
|
+
rescue_from(*exceptions) do |exception|
|
210
|
+
if Rails.gem_version >= Gem::Version.new("7.1")
|
211
|
+
Rails.error.report(exception, source: "maintenance_tasks")
|
212
|
+
else
|
213
|
+
Rails.error.report(exception, handled: true)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
203
218
|
private
|
204
219
|
|
205
220
|
def load_constants
|
@@ -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.utc
|
3
|
+
<%= time_tag run.created_at, title: run.created_at.utc %>
|
4
4
|
<%= status_tag run.status %>
|
5
5
|
<span class="is-pulled-right" title="Run ID">#<%= run.id %></span>
|
6
6
|
</h5>
|
@@ -21,6 +21,12 @@ module MaintenanceTasks
|
|
21
21
|
MaintenanceTasks.backtrace_cleaner = Rails.backtrace_cleaner
|
22
22
|
end
|
23
23
|
|
24
|
+
if Rails.gem_version >= Gem::Version.new("7.1")
|
25
|
+
initializer "maintenance_tasks.deprecator" do
|
26
|
+
Rails.application.deprecators[:maintenance_tasks] = MaintenanceTasks.deprecator
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
24
30
|
config.to_prepare do
|
25
31
|
_ = TaskJobConcern # load this for JobIteration compatibility check
|
26
32
|
end
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -8,8 +8,6 @@ require "active_record"
|
|
8
8
|
require "job-iteration"
|
9
9
|
require "maintenance_tasks/engine"
|
10
10
|
|
11
|
-
require "patches/active_record_batch_enumerator"
|
12
|
-
|
13
11
|
# The engine's namespace module. It provides isolation between the host
|
14
12
|
# application's code and the engine-specific code. Top-level engine constants
|
15
13
|
# and variables are defined under this module.
|
@@ -63,16 +61,6 @@ module MaintenanceTasks
|
|
63
61
|
# use when cleaning a Run's backtrace.
|
64
62
|
mattr_accessor :backtrace_cleaner
|
65
63
|
|
66
|
-
# @!attribute error_handler
|
67
|
-
# @scope class
|
68
|
-
#
|
69
|
-
# The callback to perform when an error occurs in the Task. See the
|
70
|
-
# {file:README#label-Customizing+the+error+handler} for details.
|
71
|
-
#
|
72
|
-
# @return [Proc] the callback to perform when an error occurs in the Task.
|
73
|
-
mattr_accessor :error_handler, default:
|
74
|
-
->(_error, _task_context, _errored_element) {}
|
75
|
-
|
76
64
|
# @!attribute parent_controller
|
77
65
|
# @scope class
|
78
66
|
#
|
@@ -96,4 +84,30 @@ module MaintenanceTasks
|
|
96
84
|
#
|
97
85
|
# @return [ActiveSupport::Duration] the threshold in seconds after which a task is considered stuck.
|
98
86
|
mattr_accessor :stuck_task_duration, default: 5.minutes
|
87
|
+
|
88
|
+
class << self
|
89
|
+
DEPRECATION_MESSAGE = "MaintenanceTasks.error_handler is deprecated and will be removed in the 3.0 release. " \
|
90
|
+
"Instead, reports will be sent to the Rails error reporter. Do not set a handler and subscribe" \
|
91
|
+
"to the error reporter instead."
|
92
|
+
private_constant :DEPRECATION_MESSAGE
|
93
|
+
|
94
|
+
# @deprecated
|
95
|
+
def error_handler
|
96
|
+
deprecator.warn(DEPRECATION_MESSAGE)
|
97
|
+
|
98
|
+
@error_handler
|
99
|
+
end
|
100
|
+
|
101
|
+
# @deprecated
|
102
|
+
def error_handler=(proc)
|
103
|
+
deprecator.warn(DEPRECATION_MESSAGE)
|
104
|
+
|
105
|
+
@error_handler = proc
|
106
|
+
end
|
107
|
+
|
108
|
+
# @api-private
|
109
|
+
def deprecator
|
110
|
+
@deprecator ||= ActiveSupport::Deprecation.new("3.0", "MaintenanceTasks")
|
111
|
+
end
|
112
|
+
end
|
99
113
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maintenance_tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify Engineering
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-28 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: actionpack
|
@@ -16,42 +15,42 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
18
|
+
version: '7.0'
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
25
|
+
version: '7.0'
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: activejob
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
32
|
+
version: '7.0'
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
39
|
+
version: '7.0'
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: activerecord
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
44
|
- - ">="
|
46
45
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
46
|
+
version: '7.0'
|
48
47
|
type: :runtime
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - ">="
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
53
|
+
version: '7.0'
|
55
54
|
- !ruby/object:Gem::Dependency
|
56
55
|
name: csv
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +85,14 @@ dependencies:
|
|
86
85
|
requirements:
|
87
86
|
- - ">="
|
88
87
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
88
|
+
version: '7.0'
|
90
89
|
type: :runtime
|
91
90
|
prerelease: false
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
93
92
|
requirements:
|
94
93
|
- - ">="
|
95
94
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
95
|
+
version: '7.0'
|
97
96
|
- !ruby/object:Gem::Dependency
|
98
97
|
name: zeitwerk
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,7 +107,6 @@ dependencies:
|
|
108
107
|
- - ">="
|
109
108
|
- !ruby/object:Gem::Version
|
110
109
|
version: 2.6.2
|
111
|
-
description:
|
112
110
|
email: gems@shopify.com
|
113
111
|
executables:
|
114
112
|
- maintenance_tasks
|
@@ -179,15 +177,13 @@ files:
|
|
179
177
|
- lib/maintenance_tasks.rb
|
180
178
|
- lib/maintenance_tasks/cli.rb
|
181
179
|
- lib/maintenance_tasks/engine.rb
|
182
|
-
- lib/patches/active_record_batch_enumerator.rb
|
183
180
|
- lib/tasks/maintenance_tasks_tasks.rake
|
184
181
|
homepage: https://github.com/Shopify/maintenance_tasks
|
185
182
|
licenses:
|
186
183
|
- MIT
|
187
184
|
metadata:
|
188
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.
|
185
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.11.0
|
189
186
|
allowed_push_host: https://rubygems.org
|
190
|
-
post_install_message:
|
191
187
|
rdoc_options: []
|
192
188
|
require_paths:
|
193
189
|
- lib
|
@@ -202,8 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
198
|
- !ruby/object:Gem::Version
|
203
199
|
version: '0'
|
204
200
|
requirements: []
|
205
|
-
rubygems_version: 3.
|
206
|
-
signing_key:
|
201
|
+
rubygems_version: 3.6.3
|
207
202
|
specification_version: 4
|
208
203
|
summary: A Rails engine for queuing and managing maintenance tasks
|
209
204
|
test_files: []
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
if Rails.gem_version < Gem::Version.new("7.0")
|
4
|
-
# Add attribute readers.
|
5
|
-
module ActiveRecordBatchEnumerator
|
6
|
-
# The primary key value from which the BatchEnumerator starts,
|
7
|
-
# inclusive of the value.
|
8
|
-
attr_reader :start
|
9
|
-
|
10
|
-
# The primary key value at which the BatchEnumerator ends,
|
11
|
-
# inclusive of the value.
|
12
|
-
attr_reader :finish
|
13
|
-
|
14
|
-
# The relation from which the BatchEnumerator yields batches.
|
15
|
-
attr_reader :relation
|
16
|
-
|
17
|
-
# The size of the batches yielded by the BatchEnumerator.
|
18
|
-
def batch_size
|
19
|
-
@of
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
ActiveRecord::Batches::BatchEnumerator.include(ActiveRecordBatchEnumerator)
|
24
|
-
end
|