maintenance_tasks 2.10.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|