maintenance_tasks 1.1.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +18 -0
- data/README.md +90 -32
- data/app/controllers/maintenance_tasks/application_controller.rb +2 -4
- data/app/controllers/maintenance_tasks/tasks_controller.rb +1 -1
- data/app/helpers/maintenance_tasks/application_helper.rb +2 -13
- data/app/helpers/maintenance_tasks/tasks_helper.rb +17 -16
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +150 -0
- data/app/jobs/maintenance_tasks/task_job.rb +1 -129
- data/app/models/maintenance_tasks/csv_collection.rb +1 -1
- data/app/models/maintenance_tasks/progress.rb +13 -11
- data/app/models/maintenance_tasks/run.rb +26 -4
- data/app/models/maintenance_tasks/runner.rb +2 -2
- data/app/models/maintenance_tasks/runs_page.rb +55 -0
- data/app/models/maintenance_tasks/task_data.rb +3 -3
- data/app/tasks/maintenance_tasks/task.rb +24 -1
- data/app/validators/maintenance_tasks/run_status_validator.rb +11 -11
- data/app/views/maintenance_tasks/runs/_info.html.erb +1 -1
- data/app/views/maintenance_tasks/runs/info/_running.html.erb +0 -2
- data/app/views/maintenance_tasks/tasks/show.html.erb +3 -3
- data/config/routes.rb +4 -4
- data/db/migrate/20210225152418_remove_index_on_task_name.rb +1 -1
- data/exe/maintenance_tasks +2 -2
- data/lib/generators/maintenance_tasks/install_generator.rb +4 -4
- data/lib/generators/maintenance_tasks/task_generator.rb +10 -10
- data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +7 -5
- data/lib/maintenance_tasks.rb +51 -29
- data/lib/maintenance_tasks/cli.rb +3 -3
- data/lib/maintenance_tasks/engine.rb +4 -3
- metadata +10 -21
- data/Rakefile +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 649f66cc11a303666134c75aad8575b81eb376e358f69312180e21a14528ce50
|
4
|
+
data.tar.gz: b564b46e647c467c5d93a6198ab112729623059cfe194d1115d095c8ab8d9953
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f313350c5a80fa8840b23976eb7cde8776f825bc25a9b6a8fb7ce5d6f1abe8148d547ae7b2c1368a72caf980a2a12377e33b7c325e1447de3c0a0b0686d87a8d
|
7
|
+
data.tar.gz: '084950e7be06503e12d437294c784d02e2f0449c76db6297c5f95fcfd40845e4ce7e3354e16fbe12dd99b82554946f16fbc3415c047c17109aab1303c6b81b9b'
|
data/LICENSE.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright 2020-present, Shopify Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -2,34 +2,6 @@
|
|
2
2
|
|
3
3
|
A Rails engine for queuing and managing maintenance tasks.
|
4
4
|
|
5
|
-
## Table of Contents
|
6
|
-
|
7
|
-
* [Demo](#demo)
|
8
|
-
* [Installation](#installation)
|
9
|
-
* [Active Job Dependency](#active-job-dependency)
|
10
|
-
* [Usage](#usage)
|
11
|
-
* [Creating a Task](#creating-a-task)
|
12
|
-
* [Creating a CSV Task](#creating-a-csv-task)
|
13
|
-
* [Considerations when writing Tasks](#considerations-when-writing-tasks)
|
14
|
-
* [Writing tests for a Task](#writing-tests-for-a-task)
|
15
|
-
* [Writing tests for a CSV Task](#writing-tests-for-a-csv-task)
|
16
|
-
* [Running a Task](#running-a-task)
|
17
|
-
* [Monitoring your Task's status](#monitoring-your-tasks-status)
|
18
|
-
* [How Maintenance Tasks runs a Task](#how-maintenance-tasks-runs-a-task)
|
19
|
-
* [Help! My Task is stuck](#help-my-task-is-stuck)
|
20
|
-
* [Configuring the gem](#configuring-the-gem)
|
21
|
-
* [Customizing the error handler](#customizing-the-error-handler)
|
22
|
-
* [Customizing the maintenance tasks module](#customizing-the-maintenance-tasks-module)
|
23
|
-
* [Customizing the underlying job class](#customizing-the-underlying-job-class)
|
24
|
-
* [Customizing the rate at which task progress gets updated](#customizing-the-rate-at-which-task-progress-gets-updated)
|
25
|
-
* [Upgrading](#upgrading)
|
26
|
-
* [Contributing](#contributing)
|
27
|
-
* [Releasing new versions](#releasing-new-versions)
|
28
|
-
|
29
|
-
## Demo
|
30
|
-
|
31
|
-
Watch this demo video to see the gem in action:
|
32
|
-
|
33
5
|
[![Link to demo video](static/demo.png)](https://www.youtube.com/watch?v=BTuvTQxlFzs)
|
34
6
|
|
35
7
|
## Installation
|
@@ -140,6 +112,46 @@ title,content
|
|
140
112
|
My Title,Hello World!
|
141
113
|
```
|
142
114
|
|
115
|
+
### Throttling
|
116
|
+
|
117
|
+
Maintenance Tasks often modify a lot of data and can be taxing on your database.
|
118
|
+
The gem provides a throttling mechanism that can be used to throttle a Task when
|
119
|
+
a given condition is met. If a Task is throttled, it will be interrupted and
|
120
|
+
retried after a backoff period has passed. The default backoff is 30 seconds.
|
121
|
+
Specify the throttle condition as a block:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# app/tasks/maintenance/update_posts_throttled_task.rb
|
125
|
+
module Maintenance
|
126
|
+
class UpdatePostsThrottledTask < MaintenanceTasks::Task
|
127
|
+
throttle_on(backoff: 1.minute) do
|
128
|
+
DatabaseStatus.unhealthy?
|
129
|
+
end
|
130
|
+
|
131
|
+
def collection
|
132
|
+
Post.all
|
133
|
+
end
|
134
|
+
|
135
|
+
def count
|
136
|
+
collection.count
|
137
|
+
end
|
138
|
+
|
139
|
+
def process(post)
|
140
|
+
post.update!(content: "New content added on #{Time.now.utc}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
Note that it's up to you to define a throttling condition that makes sense for
|
147
|
+
your app. Shopify implements `DatabaseStatus.healthy?` to check various MySQL
|
148
|
+
metrics such as replication lag, DB threads, whether DB writes are available,
|
149
|
+
etc.
|
150
|
+
|
151
|
+
Tasks can define multiple throttle conditions. Throttle conditions are inherited
|
152
|
+
by descendants, and new conditions will be appended without impacting existing
|
153
|
+
conditions.
|
154
|
+
|
143
155
|
### Considerations when writing Tasks
|
144
156
|
|
145
157
|
MaintenanceTasks relies on the queue adapter configured for your application to
|
@@ -338,12 +350,15 @@ end
|
|
338
350
|
|
339
351
|
The error handler should be a lambda that accepts three arguments:
|
340
352
|
|
341
|
-
* `error`: The
|
353
|
+
* `error`: The exception that was raised.
|
342
354
|
* `task_context`: A hash with additional information about the Task and the
|
343
355
|
error:
|
344
356
|
* `task_name`: The name of the Task that errored
|
345
357
|
* `started_at`: The time the Task started
|
346
358
|
* `ended_at`: The time the Task errored
|
359
|
+
Note that `task_context` may be empty if the Task produced an error before any
|
360
|
+
context could be gathered (for example, if deserializing the job to process
|
361
|
+
your Task failed).
|
347
362
|
* `errored_element`: The element, if any, that was being processed when the
|
348
363
|
Task raised an exception. If you would like to pass this object to your
|
349
364
|
exception monitoring service, make sure you **sanitize the object** to avoid
|
@@ -400,6 +415,36 @@ MaintenanceTasks.ticker_delay = 2.seconds
|
|
400
415
|
|
401
416
|
If no value is specified, it will default to 1 second.
|
402
417
|
|
418
|
+
#### Customizing which Active Storage service to use
|
419
|
+
|
420
|
+
The Active Storage framework in Rails 6.1 and up supports multiple storage
|
421
|
+
services per environment. To specify which service to use,
|
422
|
+
`MaintenanceTasks.active_storage_service` can be configured with the service's
|
423
|
+
key, as specified in your application's `config/storage.yml`:
|
424
|
+
|
425
|
+
```yaml
|
426
|
+
# config/storage.yml
|
427
|
+
user_data:
|
428
|
+
service: GCS
|
429
|
+
credentials: <%= Rails.root.join("path/to/user/data/keyfile.json") %>
|
430
|
+
project: "my-project"
|
431
|
+
bucket: "user-data-bucket"
|
432
|
+
|
433
|
+
internal:
|
434
|
+
service: GCS
|
435
|
+
credentials: <%= Rails.root.join("path/to/internal/keyfile.json") %>
|
436
|
+
project: "my-project"
|
437
|
+
bucket: "internal-bucket"
|
438
|
+
```
|
439
|
+
|
440
|
+
```ruby
|
441
|
+
# config/initializers/maintenance_tasks.rb
|
442
|
+
MaintenanceTasks.active_storage_service = :internal
|
443
|
+
```
|
444
|
+
|
445
|
+
There is no need to configure this option if your application uses only one
|
446
|
+
storage service per environment.
|
447
|
+
|
403
448
|
## Upgrading
|
404
449
|
|
405
450
|
Use bundler to check for and upgrade to newer versions. After installing a new
|
@@ -419,15 +464,28 @@ pull requests. You can find the contribution guidelines on
|
|
419
464
|
|
420
465
|
[contributing]: https://github.com/Shopify/maintenance_tasks/blob/main/.github/CONTRIBUTING.md
|
421
466
|
|
467
|
+
### Dependabot updates
|
468
|
+
|
469
|
+
Whenever Dependabot creates a PR for a gem bump, check out the branch locally
|
470
|
+
and run `bin/update-gemfile <gem>` to ensure all the gemfiles have the gem
|
471
|
+
updated consistently.
|
472
|
+
|
422
473
|
## Releasing new versions
|
423
474
|
|
475
|
+
Updates should be added to the latest draft release on GitHub as Pull Requests
|
476
|
+
are merged.
|
477
|
+
|
478
|
+
Once a release is ready, follow these steps:
|
479
|
+
|
424
480
|
* Update `spec.version` in `maintenance_tasks.gemspec`.
|
425
|
-
* Run `
|
481
|
+
* Run `bin/gemfile-update install` to bump the version in all the lockfiles.
|
426
482
|
* Open a PR and merge on approval.
|
427
|
-
* Create a [release on GitHub][release] with a version number that matches the
|
428
|
-
version defined in the gemspec.
|
429
483
|
* Deploy via [Shipit][shipit] and see the new version on
|
430
484
|
<https://rubygems.org/gems/maintenance_tasks>.
|
485
|
+
* Ensure the release has documented all changes and publish it.
|
486
|
+
* Create a new [draft release on GitHub][release] with the title 'Upcoming
|
487
|
+
Release'. The tag version can be left blank. This will be the starting point
|
488
|
+
for documenting changes related to the next release.
|
431
489
|
|
432
490
|
[release]: https://help.github.com/articles/creating-releases/
|
433
491
|
[shipit]: https://shipit.shopify.io/shopify/maintenance_tasks/rubygems
|
@@ -5,9 +5,7 @@ module MaintenanceTasks
|
|
5
5
|
#
|
6
6
|
# Can be extended to add different authentication and authorization code.
|
7
7
|
class ApplicationController < ActionController::Base
|
8
|
-
|
9
|
-
|
10
|
-
BULMA_CDN = 'https://cdn.jsdelivr.net'
|
8
|
+
BULMA_CDN = "https://cdn.jsdelivr.net"
|
11
9
|
|
12
10
|
content_security_policy do |policy|
|
13
11
|
policy.style_src(BULMA_CDN)
|
@@ -17,7 +15,7 @@ module MaintenanceTasks
|
|
17
15
|
before_action do
|
18
16
|
request.content_security_policy_nonce_generator ||=
|
19
17
|
->(_request) { SecureRandom.base64(16) }
|
20
|
-
request.content_security_policy_nonce_directives = [
|
18
|
+
request.content_security_policy_nonce_directives = ["style-src"]
|
21
19
|
end
|
22
20
|
|
23
21
|
protect_from_forgery with: :exception
|
@@ -20,7 +20,7 @@ module MaintenanceTasks
|
|
20
20
|
def show
|
21
21
|
@task = TaskData.find(params.fetch(:id))
|
22
22
|
set_refresh if @task.last_run&.active?
|
23
|
-
@
|
23
|
+
@runs_page = RunsPage.new(@task.previous_runs, params[:cursor])
|
24
24
|
end
|
25
25
|
|
26
26
|
# Runs a given Task and redirects to the Task page.
|
@@ -4,17 +4,6 @@ module MaintenanceTasks
|
|
4
4
|
#
|
5
5
|
# @api private
|
6
6
|
module ApplicationHelper
|
7
|
-
include Pagy::Frontend
|
8
|
-
|
9
|
-
# Renders pagination for the page, if there is more than one page present.
|
10
|
-
#
|
11
|
-
# @param pagy [Pagy] the pagy instance containing pagination details,
|
12
|
-
# including the number of pages the results are spread across.
|
13
|
-
# @return [String] the HTML to render for pagination.
|
14
|
-
def pagination(pagy)
|
15
|
-
raw(pagy_bulma_nav(pagy)) if pagy.pages > 1
|
16
|
-
end
|
17
|
-
|
18
7
|
# Renders a time element with the given datetime, worded as relative to the
|
19
8
|
# current time.
|
20
9
|
#
|
@@ -24,8 +13,8 @@ module MaintenanceTasks
|
|
24
13
|
# @param datetime [ActiveSupport::TimeWithZone] the time to be presented.
|
25
14
|
# @return [String] the HTML to render with the relative datetime in words.
|
26
15
|
def time_ago(datetime)
|
27
|
-
time_tag(datetime, title: datetime.utc.iso8601, class:
|
28
|
-
time_ago_in_words(datetime) +
|
16
|
+
time_tag(datetime, title: datetime.utc.iso8601, class: "is-clickable") do
|
17
|
+
time_ago_in_words(datetime) + " ago"
|
29
18
|
end
|
30
19
|
end
|
31
20
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "ripper"
|
4
4
|
|
5
5
|
module MaintenanceTasks
|
6
6
|
# Helpers for formatting data in the maintenance_tasks views.
|
@@ -8,16 +8,16 @@ module MaintenanceTasks
|
|
8
8
|
# @api private
|
9
9
|
module TasksHelper
|
10
10
|
STATUS_COLOURS = {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
"new" => ["is-primary"],
|
12
|
+
"enqueued" => ["is-primary is-light"],
|
13
|
+
"running" => ["is-info"],
|
14
|
+
"interrupted" => ["is-info", "is-light"],
|
15
|
+
"pausing" => ["is-warning", "is-light"],
|
16
|
+
"paused" => ["is-warning"],
|
17
|
+
"succeeded" => ["is-success"],
|
18
|
+
"cancelling" => ["is-light"],
|
19
|
+
"cancelled" => ["is-dark"],
|
20
|
+
"errored" => ["is-danger"],
|
21
21
|
}
|
22
22
|
|
23
23
|
# Formats a run backtrace.
|
@@ -44,12 +44,13 @@ module MaintenanceTasks
|
|
44
44
|
|
45
45
|
progress = Progress.new(run)
|
46
46
|
|
47
|
-
tag.progress(
|
47
|
+
progress_bar = tag.progress(
|
48
48
|
value: progress.value,
|
49
49
|
max: progress.max,
|
50
|
-
|
51
|
-
class: ['progress'] + STATUS_COLOURS.fetch(run.status)
|
50
|
+
class: ["progress"] + STATUS_COLOURS.fetch(run.status)
|
52
51
|
)
|
52
|
+
progress_text = tag.p(tag.i(progress.text))
|
53
|
+
tag.div(progress_bar + progress_text, class: "block")
|
53
54
|
end
|
54
55
|
|
55
56
|
# Renders a span with a Run's status, with the corresponding tag class
|
@@ -59,7 +60,7 @@ module MaintenanceTasks
|
|
59
60
|
# @return [String] the span element containing the status, with the
|
60
61
|
# appropriate tag class attached.
|
61
62
|
def status_tag(status)
|
62
|
-
tag.span(status.capitalize, class: [
|
63
|
+
tag.span(status.capitalize, class: ["tag"] + STATUS_COLOURS.fetch(status))
|
63
64
|
end
|
64
65
|
|
65
66
|
# Returns the distance between now and the Run's expected completion time,
|
@@ -100,7 +101,7 @@ module MaintenanceTasks
|
|
100
101
|
when :on_nl, :on_sp, :on_ignored_nl
|
101
102
|
content
|
102
103
|
else
|
103
|
-
tag.span(content, class: type.to_s.sub(
|
104
|
+
tag.span(content, class: type.to_s.sub("on_", "ruby-").dasherize)
|
104
105
|
end
|
105
106
|
end
|
106
107
|
safe_join(tokens)
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module MaintenanceTasks
|
3
|
+
# Concern that holds the behaviour of the job that runs the tasks. It is
|
4
|
+
# included in {TaskJob} and if MaintenanceTasks.job is overridden, it must be
|
5
|
+
# included in the job.
|
6
|
+
module TaskJobConcern
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include JobIteration::Iteration
|
9
|
+
|
10
|
+
included do
|
11
|
+
before_perform(:before_perform)
|
12
|
+
|
13
|
+
on_start(:on_start)
|
14
|
+
on_complete(:on_complete)
|
15
|
+
on_shutdown(:on_shutdown)
|
16
|
+
|
17
|
+
after_perform(:after_perform)
|
18
|
+
|
19
|
+
rescue_from StandardError, with: :on_error
|
20
|
+
end
|
21
|
+
|
22
|
+
class_methods do
|
23
|
+
# Overrides ActiveJob::Exceptions.retry_on to declare it unsupported.
|
24
|
+
# The use of rescue_from prevents retry_on from being usable.
|
25
|
+
def retry_on(*, **)
|
26
|
+
raise NotImplementedError, "retry_on is not supported"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_enumerator(_run, cursor:)
|
33
|
+
cursor ||= @run.cursor
|
34
|
+
collection = @task.collection
|
35
|
+
|
36
|
+
collection_enum = case collection
|
37
|
+
when ActiveRecord::Relation
|
38
|
+
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
39
|
+
when Array
|
40
|
+
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
41
|
+
when CSV
|
42
|
+
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
43
|
+
else
|
44
|
+
raise ArgumentError, "#{@task.class.name}#collection must be either "\
|
45
|
+
"an Active Record Relation, Array, or CSV."
|
46
|
+
end
|
47
|
+
|
48
|
+
@task.throttle_conditions.reduce(collection_enum) do |enum, condition|
|
49
|
+
enumerator_builder.build_throttle_enumerator(enum, **condition)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Performs task iteration logic for the current input returned by the
|
54
|
+
# enumerator.
|
55
|
+
#
|
56
|
+
# @param input [Object] the current element from the enumerator.
|
57
|
+
# @param _run [Run] the current Run, passed as an argument by Job Iteration.
|
58
|
+
def each_iteration(input, _run)
|
59
|
+
throw(:abort, :skip_complete_callbacks) if @run.stopping?
|
60
|
+
task_iteration(input)
|
61
|
+
@ticker.tick
|
62
|
+
@run.reload_status
|
63
|
+
end
|
64
|
+
|
65
|
+
def task_iteration(input)
|
66
|
+
@task.process(input)
|
67
|
+
rescue => error
|
68
|
+
@errored_element = input
|
69
|
+
raise error
|
70
|
+
end
|
71
|
+
|
72
|
+
def before_perform
|
73
|
+
@run = arguments.first
|
74
|
+
@task = Task.named(@run.task_name).new
|
75
|
+
if @task.respond_to?(:csv_content=)
|
76
|
+
@task.csv_content = @run.csv_file.download
|
77
|
+
end
|
78
|
+
@run.job_id = job_id
|
79
|
+
|
80
|
+
@run.running! unless @run.stopping?
|
81
|
+
|
82
|
+
@ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
|
83
|
+
@run.persist_progress(ticks, duration)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_start
|
88
|
+
@run.update!(started_at: Time.now, tick_total: @task.count)
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_complete
|
92
|
+
@run.status = :succeeded
|
93
|
+
@run.ended_at = Time.now
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_shutdown
|
97
|
+
if @run.cancelling?
|
98
|
+
@run.status = :cancelled
|
99
|
+
@run.ended_at = Time.now
|
100
|
+
else
|
101
|
+
@run.status = @run.pausing? ? :paused : :interrupted
|
102
|
+
@run.cursor = cursor_position
|
103
|
+
end
|
104
|
+
|
105
|
+
@ticker.persist
|
106
|
+
end
|
107
|
+
|
108
|
+
# We are reopening a private part of Job Iteration's API here, so we should
|
109
|
+
# ensure the method is still defined upstream. This way, in the case where
|
110
|
+
# the method changes upstream, we catch it at load time instead of at
|
111
|
+
# runtime while calling `super`.
|
112
|
+
unless JobIteration::Iteration
|
113
|
+
.private_method_defined?(:reenqueue_iteration_job)
|
114
|
+
error_message = <<~HEREDOC
|
115
|
+
JobIteration::Iteration#reenqueue_iteration_job is expected to be
|
116
|
+
defined. Upgrading the maintenance_tasks gem should solve this problem.
|
117
|
+
HEREDOC
|
118
|
+
raise error_message
|
119
|
+
end
|
120
|
+
def reenqueue_iteration_job(should_ignore: true)
|
121
|
+
super() unless should_ignore
|
122
|
+
@reenqueue_iteration_job = true
|
123
|
+
end
|
124
|
+
|
125
|
+
def after_perform
|
126
|
+
@run.save!
|
127
|
+
if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
|
128
|
+
reenqueue_iteration_job(should_ignore: false)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_error(error)
|
133
|
+
@ticker.persist if defined?(@ticker)
|
134
|
+
|
135
|
+
if defined?(@run)
|
136
|
+
@run.persist_error(error)
|
137
|
+
|
138
|
+
task_context = {
|
139
|
+
task_name: @run.task_name,
|
140
|
+
started_at: @run.started_at,
|
141
|
+
ended_at: @run.ended_at,
|
142
|
+
}
|
143
|
+
else
|
144
|
+
task_context = {}
|
145
|
+
end
|
146
|
+
errored_element = @errored_element if defined?(@errored_element)
|
147
|
+
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|