maintenance_tasks 1.2.2 → 1.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 +4 -4
- data/README.md +40 -0
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +150 -0
- data/app/jobs/maintenance_tasks/task_job.rb +1 -134
- data/app/tasks/maintenance_tasks/task.rb +20 -1
- data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +7 -5
- data/lib/maintenance_tasks.rb +38 -20
- data/lib/maintenance_tasks/engine.rb +1 -0
- metadata +6 -5
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/README.md
CHANGED
@@ -112,6 +112,46 @@ title,content
|
|
112
112
|
My Title,Hello World!
|
113
113
|
```
|
114
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
|
+
|
115
155
|
### Considerations when writing Tasks
|
116
156
|
|
117
157
|
MaintenanceTasks relies on the queue adapter configured for your application to
|
@@ -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
|
@@ -3,139 +3,6 @@
|
|
3
3
|
module MaintenanceTasks
|
4
4
|
# Base class that is inherited by the host application's task classes.
|
5
5
|
class TaskJob < ActiveJob::Base
|
6
|
-
include
|
7
|
-
|
8
|
-
before_perform(:before_perform)
|
9
|
-
|
10
|
-
on_start(:on_start)
|
11
|
-
on_complete(:on_complete)
|
12
|
-
on_shutdown(:on_shutdown)
|
13
|
-
|
14
|
-
after_perform(:after_perform)
|
15
|
-
|
16
|
-
rescue_from StandardError, with: :on_error
|
17
|
-
|
18
|
-
class << self
|
19
|
-
# Overrides ActiveJob::Exceptions.retry_on to declare it unsupported.
|
20
|
-
# The use of rescue_from prevents retry_on from being usable.
|
21
|
-
def retry_on(*, **)
|
22
|
-
raise NotImplementedError, "retry_on is not supported"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def build_enumerator(_run, cursor:)
|
29
|
-
cursor ||= @run.cursor
|
30
|
-
collection = @task.collection
|
31
|
-
|
32
|
-
case collection
|
33
|
-
when ActiveRecord::Relation
|
34
|
-
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
35
|
-
when Array
|
36
|
-
enumerator_builder.build_array_enumerator(collection, cursor: cursor)
|
37
|
-
when CSV
|
38
|
-
JobIteration::CsvEnumerator.new(collection).rows(cursor: cursor)
|
39
|
-
else
|
40
|
-
raise ArgumentError, "#{@task.class.name}#collection must be either "\
|
41
|
-
"an Active Record Relation, Array, or CSV."
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Performs task iteration logic for the current input returned by the
|
46
|
-
# enumerator.
|
47
|
-
#
|
48
|
-
# @param input [Object] the current element from the enumerator.
|
49
|
-
# @param _run [Run] the current Run, passed as an argument by Job Iteration.
|
50
|
-
def each_iteration(input, _run)
|
51
|
-
throw(:abort, :skip_complete_callbacks) if @run.stopping?
|
52
|
-
task_iteration(input)
|
53
|
-
@ticker.tick
|
54
|
-
@run.reload_status
|
55
|
-
end
|
56
|
-
|
57
|
-
def task_iteration(input)
|
58
|
-
@task.process(input)
|
59
|
-
rescue => error
|
60
|
-
@errored_element = input
|
61
|
-
raise error
|
62
|
-
end
|
63
|
-
|
64
|
-
def before_perform
|
65
|
-
@run = arguments.first
|
66
|
-
@task = Task.named(@run.task_name).new
|
67
|
-
if @task.respond_to?(:csv_content=)
|
68
|
-
@task.csv_content = @run.csv_file.download
|
69
|
-
end
|
70
|
-
@run.job_id = job_id
|
71
|
-
|
72
|
-
@run.running! unless @run.stopping?
|
73
|
-
|
74
|
-
@ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
|
75
|
-
@run.persist_progress(ticks, duration)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def on_start
|
80
|
-
@run.update!(started_at: Time.now, tick_total: @task.count)
|
81
|
-
end
|
82
|
-
|
83
|
-
def on_complete
|
84
|
-
@run.status = :succeeded
|
85
|
-
@run.ended_at = Time.now
|
86
|
-
end
|
87
|
-
|
88
|
-
def on_shutdown
|
89
|
-
if @run.cancelling?
|
90
|
-
@run.status = :cancelled
|
91
|
-
@run.ended_at = Time.now
|
92
|
-
else
|
93
|
-
@run.status = @run.pausing? ? :paused : :interrupted
|
94
|
-
@run.cursor = cursor_position
|
95
|
-
end
|
96
|
-
|
97
|
-
@ticker.persist
|
98
|
-
end
|
99
|
-
|
100
|
-
# We are reopening a private part of Job Iteration's API here, so we should
|
101
|
-
# ensure the method is still defined upstream. This way, in the case where
|
102
|
-
# the method changes upstream, we catch it at load time instead of at
|
103
|
-
# runtime while calling `super`.
|
104
|
-
unless private_method_defined?(:reenqueue_iteration_job)
|
105
|
-
error_message = <<~HEREDOC
|
106
|
-
JobIteration::Iteration#reenqueue_iteration_job is expected to be
|
107
|
-
defined. Upgrading the maintenance_tasks gem should solve this problem.
|
108
|
-
HEREDOC
|
109
|
-
raise error_message
|
110
|
-
end
|
111
|
-
def reenqueue_iteration_job(should_ignore: true)
|
112
|
-
super() unless should_ignore
|
113
|
-
@reenqueue_iteration_job = true
|
114
|
-
end
|
115
|
-
|
116
|
-
def after_perform
|
117
|
-
@run.save!
|
118
|
-
if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
|
119
|
-
reenqueue_iteration_job(should_ignore: false)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def on_error(error)
|
124
|
-
@ticker.persist if defined?(@ticker)
|
125
|
-
|
126
|
-
if defined?(@run)
|
127
|
-
@run.persist_error(error)
|
128
|
-
|
129
|
-
task_context = {
|
130
|
-
task_name: @run.task_name,
|
131
|
-
started_at: @run.started_at,
|
132
|
-
ended_at: @run.ended_at,
|
133
|
-
}
|
134
|
-
else
|
135
|
-
task_context = {}
|
136
|
-
end
|
137
|
-
errored_element = @errored_element if defined?(@errored_element)
|
138
|
-
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
139
|
-
end
|
6
|
+
include TaskJobConcern
|
140
7
|
end
|
141
8
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
module MaintenanceTasks
|
4
3
|
# Base class that is inherited by the host application's task classes.
|
5
4
|
class Task
|
@@ -7,6 +6,13 @@ module MaintenanceTasks
|
|
7
6
|
|
8
7
|
class NotFoundError < NameError; end
|
9
8
|
|
9
|
+
# The throttle conditions for a given Task. This is provided as an array of
|
10
|
+
# hashes, with each hash specifying two keys: throttle_condition and
|
11
|
+
# backoff. Note that Tasks inherit conditions from their superclasses.
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class_attribute :throttle_conditions, default: []
|
15
|
+
|
10
16
|
class << self
|
11
17
|
# Finds a Task with the given name.
|
12
18
|
#
|
@@ -72,6 +78,19 @@ module MaintenanceTasks
|
|
72
78
|
new.count
|
73
79
|
end
|
74
80
|
|
81
|
+
# Add a condition under which this Task will be throttled.
|
82
|
+
#
|
83
|
+
# @param backoff [ActiveSupport::Duration] optionally, a custom backoff
|
84
|
+
# can be specified. This is the time to wait before retrying the Task.
|
85
|
+
# If no value is specified, it defaults to 30 seconds.
|
86
|
+
# @yieldreturn [Boolean] where the throttle condition is being met,
|
87
|
+
# indicating that the Task should throttle.
|
88
|
+
def throttle_on(backoff: 30.seconds, &condition)
|
89
|
+
self.throttle_conditions += [
|
90
|
+
{ throttle_on: condition, backoff: backoff },
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
75
94
|
private
|
76
95
|
|
77
96
|
def load_constants
|
@@ -4,11 +4,13 @@ require 'rails_helper'
|
|
4
4
|
module <%= tasks_module %>
|
5
5
|
<% module_namespacing do -%>
|
6
6
|
RSpec.describe <%= class_name %>Task do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
describe "#process" do
|
8
|
+
subject(:process) { described_class.process(element) }
|
9
|
+
let(:element) {
|
10
|
+
# Object to be processed in a single iteration of this task
|
11
|
+
}
|
12
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
13
|
+
end
|
12
14
|
end
|
13
15
|
<% end -%>
|
14
16
|
end
|
data/lib/maintenance_tasks.rb
CHANGED
@@ -7,46 +7,56 @@ require "active_record"
|
|
7
7
|
require "job-iteration"
|
8
8
|
require "maintenance_tasks/engine"
|
9
9
|
|
10
|
-
# Force the TaskJob class to load so we can verify upstream compatibility with
|
11
|
-
# the JobIteration gem
|
12
|
-
require_relative "../app/jobs/maintenance_tasks/task_job"
|
13
|
-
|
14
10
|
# The engine's namespace module. It provides isolation between the host
|
15
11
|
# application's code and the engine-specific code. Top-level engine constants
|
16
12
|
# and variables are defined under this module.
|
17
13
|
module MaintenanceTasks
|
18
|
-
#
|
19
|
-
#
|
14
|
+
# @!attribute tasks_module
|
15
|
+
# @scope class
|
16
|
+
#
|
17
|
+
# The module to namespace Tasks in, as a String. Defaults to 'Maintenance'.
|
18
|
+
# @return [String] the name of the module.
|
20
19
|
mattr_accessor :tasks_module, default: "Maintenance"
|
21
20
|
|
22
|
-
#
|
23
|
-
#
|
21
|
+
# @!attribute job
|
22
|
+
# @scope class
|
24
23
|
#
|
25
|
-
#
|
24
|
+
# The name of the job to be used to perform Tasks. Defaults to
|
25
|
+
# `"MaintenanceTasks::TaskJob"`. This job must be either a class that
|
26
|
+
# inherits from {TaskJob} or a class that includes {TaskJobConcern}.
|
27
|
+
#
|
28
|
+
# @return [String] the name of the job class.
|
26
29
|
mattr_accessor :job, default: "MaintenanceTasks::TaskJob"
|
27
30
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
31
|
+
# @!attribute ticker_delay
|
32
|
+
# @scope class
|
33
|
+
#
|
34
|
+
# The delay between updates to the tick count. After each iteration, the
|
35
|
+
# progress of the Task may be updated. This duration in seconds limits
|
36
|
+
# these updates, skipping if the duration since the last update is lower
|
37
|
+
# than this value, except if the job is interrupted, in which case the
|
38
|
+
# progress will always be recorded.
|
32
39
|
#
|
33
|
-
#
|
34
|
-
#
|
40
|
+
# @return [ActiveSupport::Duration, Numeric] duration of the delay between
|
41
|
+
# updates to the tick count during Task iterations.
|
35
42
|
mattr_accessor :ticker_delay, default: 1.second
|
36
43
|
|
37
|
-
#
|
44
|
+
# @!attribute active_storage_service
|
45
|
+
# @scope class
|
38
46
|
#
|
39
|
-
#
|
40
|
-
#
|
47
|
+
# The Active Storage service to use for uploading CSV file blobs.
|
48
|
+
#
|
49
|
+
# @return [Symbol] the key for the storage service, as specified in the
|
50
|
+
# app's config/storage.yml.
|
41
51
|
mattr_accessor :active_storage_service
|
42
52
|
|
43
|
-
#
|
53
|
+
# @private
|
44
54
|
def self.error_handler
|
45
55
|
return @error_handler if defined?(@error_handler)
|
46
56
|
@error_handler = ->(_error, _task_context, _errored_element) {}
|
47
57
|
end
|
48
58
|
|
49
|
-
#
|
59
|
+
# @private
|
50
60
|
def self.error_handler=(error_handler)
|
51
61
|
unless error_handler.arity == 3
|
52
62
|
ActiveSupport::Deprecation.warn(
|
@@ -59,4 +69,12 @@ module MaintenanceTasks
|
|
59
69
|
end
|
60
70
|
@error_handler = error_handler
|
61
71
|
end
|
72
|
+
|
73
|
+
# @!attribute error_handler
|
74
|
+
# @scope class
|
75
|
+
#
|
76
|
+
# The callback to perform when an error occurs in the Task. See the
|
77
|
+
# {file:README#label-Customizing+the+error+handler} for details.
|
78
|
+
#
|
79
|
+
# @return [Proc] the callback to perform when an error occurs in the Task.
|
62
80
|
end
|
@@ -12,6 +12,7 @@ module MaintenanceTasks
|
|
12
12
|
end
|
13
13
|
|
14
14
|
config.to_prepare do
|
15
|
+
_ = TaskJobConcern # load this for JobIteration compatibility check
|
15
16
|
unless Rails.autoloaders.zeitwerk_enabled?
|
16
17
|
tasks_module = MaintenanceTasks.tasks_module.underscore
|
17
18
|
Dir["#{Rails.root}/app/tasks/#{tasks_module}/*.rb"].each do |file|
|
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: 1.
|
4
|
+
version: 1.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: 2021-
|
11
|
+
date: 2021-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- app/controllers/maintenance_tasks/tasks_controller.rb
|
95
95
|
- app/helpers/maintenance_tasks/application_helper.rb
|
96
96
|
- app/helpers/maintenance_tasks/tasks_helper.rb
|
97
|
+
- app/jobs/concerns/maintenance_tasks/task_job_concern.rb
|
97
98
|
- app/jobs/maintenance_tasks/task_job.rb
|
98
99
|
- app/models/maintenance_tasks/application_record.rb
|
99
100
|
- app/models/maintenance_tasks/csv_collection.rb
|
@@ -139,10 +140,10 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
139
140
|
licenses:
|
140
141
|
- MIT
|
141
142
|
metadata:
|
142
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.
|
143
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.3.0
|
143
144
|
allowed_push_host: https://rubygems.org
|
144
145
|
post_install_message: |-
|
145
|
-
Thank you for installing Maintenance Tasks 1.
|
146
|
+
Thank you for installing Maintenance Tasks 1.3.0. To complete, please run:
|
146
147
|
|
147
148
|
rails generate maintenance_tasks:install
|
148
149
|
rdoc_options: []
|
@@ -159,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
160
|
- !ruby/object:Gem::Version
|
160
161
|
version: '0'
|
161
162
|
requirements: []
|
162
|
-
rubygems_version: 3.
|
163
|
+
rubygems_version: 3.2.17
|
163
164
|
signing_key:
|
164
165
|
specification_version: 4
|
165
166
|
summary: A Rails engine for queuing and managing maintenance tasks
|