maintenance_tasks 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8affacb1fe5aa5298899bb27875132a4b299b1bd89e239b8fc234df1d00e171d
4
- data.tar.gz: cd9351e3abed12d1c1bede76cea7ac1d664159983b3ece57fe2707a0de971db5
3
+ metadata.gz: 6019b1623a8cc72e877154f52e8437339598f0cab121189d78692e0989e86e61
4
+ data.tar.gz: 21563dc6dffadd2e58dd551fe2b7d71297b2da36500722f5d9fed91892b1fd6e
5
5
  SHA512:
6
- metadata.gz: c0459cf005532667f4d5bbc8a1b05adc5e4aa57bccb4b4a3e9b9338589ff73043272201e5cd0a86ed0abfa38430b18ae5021ec1991a81f064444665be8de4e3f
7
- data.tar.gz: d1f9b671f215a2d4a908a9a2353f02d6800511ea8d3337b4074712ec3aed7bdcd3a6b243960915a11e4ee31ca1b756d1992ce3af5a5cd27457a9016b42136842
6
+ metadata.gz: 93228d08b49fab144297cb44a0aa4b9be53144ff13b88c2eba384ef29fdb47b9a8caefaf94aaf1fbfbf4584f4d544396720c17d86f3bad31508fd66dfd7507b8
7
+ data.tar.gz: cab4cebb685fddf978eeebd62a5c16867215f849d424b9ba948beba0e93f42a5fe11ee1eccc15174c1494fa16a2fa100d3a7745c5eacef2527d425895805cf29
data/README.md CHANGED
@@ -114,8 +114,9 @@ title,content
114
114
  My Title,Hello World!
115
115
  ```
116
116
 
117
- The files uploaded to your Active Storage service provider will be renamed first
117
+ The files uploaded to your Active Storage service provider will be renamed
118
118
  to include an ISO8601 timestamp and the Task name in snake case format.
119
+ The CSV is expected to have a trailing newline at the end of the file.
119
120
 
120
121
  ### Processing Batch Collections
121
122
 
@@ -200,6 +201,20 @@ Tasks can define multiple throttle conditions. Throttle conditions are inherited
200
201
  by descendants, and new conditions will be appended without impacting existing
201
202
  conditions.
202
203
 
204
+ The backoff can also be specified as a proc:
205
+
206
+ ```ruby
207
+ # app/tasks/maintenance/update_posts_throttled_task.rb
208
+
209
+ module Maintenance
210
+ class UpdatePostsThrottledTask < MaintenanceTasks::Task
211
+ throttle_on(backoff: -> { RandomBackoffGenerator.generate_duration } ) do
212
+ DatabaseStatus.unhealthy?
213
+ end
214
+ ...
215
+ end
216
+ end
217
+ ```
203
218
  ### Custom Task Parameters
204
219
 
205
220
  Tasks may need additional information, supplied via parameters, to run.
@@ -241,12 +256,12 @@ The Task provides callbacks that hook into its life cycle.
241
256
 
242
257
  Available callbacks are:
243
258
 
244
- `after_start`
245
- `after_pause`
246
- `after_interrupt`
247
- `after_cancel`
248
- `after_complete`
249
- `after_error`
259
+ * `after_start`
260
+ * `after_pause`
261
+ * `after_interrupt`
262
+ * `after_cancel`
263
+ * `after_complete`
264
+ * `after_error`
250
265
 
251
266
  ```ruby
252
267
  module Maintenance
@@ -542,7 +557,7 @@ you can define an error handler:
542
557
 
543
558
  MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
544
559
  Bugsnag.notify(error) do |notification|
545
- notification.add_tab(:task, task_context)
560
+ notification.add_metadata(:task, task_context)
546
561
  end
547
562
  end
548
563
  ```
@@ -24,11 +24,12 @@ module MaintenanceTasks
24
24
  end
25
25
 
26
26
  # Runs a given Task and redirects to the Task page.
27
- def run
27
+ def run(&block)
28
28
  task = Runner.run(
29
29
  name: params.fetch(:id),
30
30
  csv_file: params[:csv_file],
31
31
  arguments: params.fetch(:task_arguments, {}).permit!.to_h,
32
+ &block
32
33
  )
33
34
  redirect_to(task_path(task))
34
35
  rescue ActiveRecord::RecordInvalid => error
@@ -100,5 +100,24 @@ module MaintenanceTasks
100
100
  only_path: true
101
101
  )
102
102
  end
103
+
104
+ # Return the appropriate field tag for the parameter
105
+ def parameter_field(form_builder, parameter_name)
106
+ case form_builder.object.class.attribute_types[parameter_name]
107
+ when ActiveModel::Type::Integer, ActiveModel::Type::Decimal,
108
+ ActiveModel::Type::Float
109
+ form_builder.number_field(parameter_name)
110
+ when ActiveModel::Type::DateTime
111
+ form_builder.datetime_field(parameter_name)
112
+ when ActiveModel::Type::Date
113
+ form_builder.date_field(parameter_name)
114
+ when ActiveModel::Type::Time
115
+ form_builder.time_field(parameter_name)
116
+ when ActiveModel::Type::Boolean
117
+ form_builder.check_box(parameter_name)
118
+ else
119
+ form_builder.text_area(parameter_name, class: "textarea")
120
+ end
121
+ end
103
122
  end
104
123
  end
@@ -12,8 +12,8 @@ module MaintenanceTasks
12
12
  before_perform(:before_perform)
13
13
 
14
14
  on_start(:on_start)
15
- on_complete(:on_complete)
16
15
  on_shutdown(:on_shutdown)
16
+ on_complete(:on_complete)
17
17
 
18
18
  after_perform(:after_perform)
19
19
 
@@ -63,9 +63,16 @@ module MaintenanceTasks
63
63
  Array, or CSV.
64
64
  MSG
65
65
  end
66
+ throttle_enumerator(collection_enum)
67
+ end
66
68
 
69
+ def throttle_enumerator(collection_enum)
67
70
  @task.throttle_conditions.reduce(collection_enum) do |enum, condition|
68
- enumerator_builder.build_throttle_enumerator(enum, **condition)
71
+ enumerator_builder.build_throttle_enumerator(
72
+ enum,
73
+ throttle_on: condition[:throttle_on],
74
+ backoff: condition[:backoff].call
75
+ )
69
76
  end
70
77
  end
71
78
 
@@ -105,33 +112,19 @@ module MaintenanceTasks
105
112
  def on_start
106
113
  count = @task.count
107
114
  count = @enumerator&.size if count == :no_count
108
- @run.update!(started_at: Time.now, tick_total: count)
109
- @task.run_callbacks(:start)
110
- end
111
-
112
- def on_complete
113
- @run.status = :succeeded
114
- @run.ended_at = Time.now
115
- @task.run_callbacks(:complete)
115
+ @run.start(count)
116
116
  end
117
117
 
118
118
  def on_shutdown
119
- if @run.cancelling?
120
- @run.status = :cancelled
121
- @task.run_callbacks(:cancel)
122
- @run.ended_at = Time.now
123
- elsif @run.pausing?
124
- @run.status = :paused
125
- @task.run_callbacks(:pause)
126
- else
127
- @run.status = :interrupted
128
- @task.run_callbacks(:interrupt)
129
- end
130
-
119
+ @run.job_shutdown
131
120
  @run.cursor = cursor_position
132
121
  @ticker.persist
133
122
  end
134
123
 
124
+ def on_complete
125
+ @run.complete
126
+ end
127
+
135
128
  # We are reopening a private part of Job Iteration's API here, so we should
136
129
  # ensure the method is still defined upstream. This way, in the case where
137
130
  # the method changes upstream, we catch it at load time instead of at
@@ -150,7 +143,7 @@ module MaintenanceTasks
150
143
  end
151
144
 
152
145
  def after_perform
153
- @run.save!
146
+ @run.persist_transition
154
147
  if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
155
148
  reenqueue_iteration_job(should_ignore: false)
156
149
  end
@@ -171,15 +164,8 @@ module MaintenanceTasks
171
164
  task_context = {}
172
165
  end
173
166
  errored_element = @errored_element if defined?(@errored_element)
174
- run_error_callback
175
167
  ensure
176
168
  MaintenanceTasks.error_handler.call(error, task_context, errored_element)
177
169
  end
178
-
179
- def run_error_callback
180
- @task.run_callbacks(:error) if defined?(@task)
181
- rescue
182
- nil
183
- end
184
170
  end
185
171
  end
@@ -15,9 +15,10 @@ module MaintenanceTasks
15
15
  CSV.new(task.csv_content, headers: true)
16
16
  end
17
17
 
18
- # The number of rows to be processed. Excludes the header row from the count
19
- # and assumed a trailing new line in the CSV file. Note that this number is
20
- # an approximation based on the number of new lines.
18
+ # The number of rows to be processed. Excludes the header row from the
19
+ # count and assumes a trailing newline is at the end of the CSV file.
20
+ # Note that this number is an approximation based on the number of
21
+ # newlines.
21
22
  #
22
23
  # @return [Integer] the approximate number of rows to process.
23
24
  def count(task)
@@ -66,9 +66,40 @@ module MaintenanceTasks
66
66
 
67
67
  # Sets the run status to enqueued, making sure the transition is validated
68
68
  # in case it's already enqueued.
69
+ #
70
+ # Rescues and retries status transition if an ActiveRecord::StaleObjectError
71
+ # is encountered.
69
72
  def enqueued!
70
73
  status_will_change!
71
74
  super
75
+ rescue ActiveRecord::StaleObjectError
76
+ reload_status
77
+ retry
78
+ end
79
+
80
+ CALLBACKS_TRANSITION = {
81
+ cancelled: :cancel,
82
+ interrupted: :interrupt,
83
+ paused: :pause,
84
+ succeeded: :complete,
85
+ }.transform_keys(&:to_s)
86
+ private_constant :CALLBACKS_TRANSITION
87
+
88
+ # Saves the run, persisting the transition of its status, and all other
89
+ # changes to the object.
90
+ def persist_transition
91
+ save!
92
+ callback = CALLBACKS_TRANSITION[status]
93
+ run_task_callbacks(callback) if callback
94
+ rescue ActiveRecord::StaleObjectError
95
+ success = succeeded?
96
+ reload_status
97
+ if success
98
+ self.status = :succeeded
99
+ else
100
+ job_shutdown
101
+ end
102
+ retry
72
103
  end
73
104
 
74
105
  # Increments +tick_count+ by +number_of_ticks+ and +time_running+ by
@@ -86,6 +117,11 @@ module MaintenanceTasks
86
117
  time_running: duration,
87
118
  touch: true
88
119
  )
120
+ if locking_enabled?
121
+ locking_column = self.class.locking_column
122
+ self[locking_column] += 1
123
+ clear_attribute_change(locking_column)
124
+ end
89
125
  end
90
126
 
91
127
  # Marks the run as errored and persists the error data.
@@ -100,20 +136,34 @@ module MaintenanceTasks
100
136
  backtrace: MaintenanceTasks.backtrace_cleaner.clean(error.backtrace),
101
137
  ended_at: Time.now,
102
138
  )
139
+ run_task_callbacks(:error)
140
+ rescue ActiveRecord::StaleObjectError
141
+ reload_status
142
+ retry
103
143
  end
104
144
 
105
- # Refreshes just the status attribute on the Active Record object, and
106
- # ensures ActiveModel::Dirty does not mark the object as changed.
145
+ # Refreshes the status and lock version attributes on the Active Record
146
+ # object, and ensures ActiveModel::Dirty doesn't mark the object as changed.
147
+ #
107
148
  # This allows us to get the Run's most up-to-date status without needing
108
149
  # to reload the entire record.
109
150
  #
110
151
  # @return [MaintenanceTasks::Run] the Run record with its updated status.
111
152
  def reload_status
112
- updated_status = self.class.uncached do
113
- self.class.where(id: id).pluck(:status).first
153
+ columns_to_reload = if locking_enabled?
154
+ [:status, self.class.locking_column]
155
+ else
156
+ [:status]
157
+ end
158
+ updated_status, updated_lock_version = self.class.uncached do
159
+ self.class.where(id: id).pluck(*columns_to_reload).first
114
160
  end
161
+
115
162
  self.status = updated_status
116
- clear_attribute_changes([:status])
163
+ if updated_lock_version
164
+ self[self.class.locking_column] = updated_lock_version
165
+ end
166
+ clear_attribute_changes(columns_to_reload)
117
167
  self
118
168
  end
119
169
 
@@ -173,21 +223,65 @@ module MaintenanceTasks
173
223
  seconds_to_finished.seconds
174
224
  end
175
225
 
176
- # Mark a Run as running.
226
+ # Marks a Run as running.
177
227
  #
178
228
  # If the run is stopping already, it will not transition to running.
229
+ # Rescues and retries status transition if an ActiveRecord::StaleObjectError
230
+ # is encountered.
179
231
  def running
180
- return if stopping?
181
- updated = self.class.where(id: id).where.not(status: STOPPING_STATUSES)
182
- .update_all(status: :running, updated_at: Time.now) > 0
183
- if updated
184
- self.status = :running
185
- clear_attribute_changes([:status])
232
+ if locking_enabled?
233
+ begin
234
+ running! unless stopping?
235
+ rescue ActiveRecord::StaleObjectError
236
+ reload_status
237
+ retry
238
+ end
239
+ else
240
+ # Preserve swap-and-replace solution for data races until users
241
+ # run migration to upgrade to optimistic locking solution
242
+ return if stopping?
243
+ updated = self.class.where(id: id).where.not(status: STOPPING_STATUSES)
244
+ .update_all(status: :running, updated_at: Time.now) > 0
245
+ if updated
246
+ self.status = :running
247
+ clear_attribute_changes([:status])
248
+ else
249
+ reload_status
250
+ end
251
+ end
252
+ end
253
+
254
+ # Starts a Run, setting its started_at timestamp and tick_total.
255
+ #
256
+ # @param count [Integer] the total iterations to be performed, as
257
+ # specified by the Task.
258
+ def start(count)
259
+ update!(started_at: Time.now, tick_total: count)
260
+ run_task_callbacks(:start)
261
+ rescue ActiveRecord::StaleObjectError
262
+ reload_status
263
+ retry
264
+ end
265
+
266
+ # Handles transitioning the status on a Run when the job shuts down.
267
+ def job_shutdown
268
+ if cancelling?
269
+ self.status = :cancelled
270
+ self.ended_at = Time.now
271
+ elsif pausing?
272
+ self.status = :paused
186
273
  else
187
- reload_status
274
+ self.status = :interrupted
188
275
  end
189
276
  end
190
277
 
278
+ # Handles the completion of a Run, setting a status of succeeded and the
279
+ # ended_at timestamp.
280
+ def complete
281
+ self.status = :succeeded
282
+ self.ended_at = Time.now
283
+ end
284
+
191
285
  # Cancels a Run.
192
286
  #
193
287
  # If the Run is paused, it will transition directly to cancelled, since the
@@ -201,10 +295,26 @@ module MaintenanceTasks
201
295
  # will be updated.
202
296
  def cancel
203
297
  if paused? || stuck?
204
- update!(status: :cancelled, ended_at: Time.now)
298
+ self.status = :cancelled
299
+ self.ended_at = Time.now
300
+ persist_transition
205
301
  else
206
302
  cancelling!
207
303
  end
304
+ rescue ActiveRecord::StaleObjectError
305
+ reload_status
306
+ retry
307
+ end
308
+
309
+ # Marks a Run as pausing.
310
+ #
311
+ # Rescues and retries status transition if an ActiveRecord::StaleObjectError
312
+ # is encountered.
313
+ def pausing!
314
+ super
315
+ rescue ActiveRecord::StaleObjectError
316
+ reload_status
317
+ retry
208
318
  end
209
319
 
210
320
  # Returns whether a Run is stuck, which is defined as having a status of
@@ -297,6 +407,12 @@ module MaintenanceTasks
297
407
 
298
408
  private
299
409
 
410
+ def run_task_callbacks(callback)
411
+ task.run_callbacks(callback)
412
+ rescue
413
+ nil
414
+ end
415
+
300
416
  def arguments_match_task_attributes
301
417
  invalid_argument_keys = arguments.keys - task.attribute_names
302
418
  if invalid_argument_keys.any?
@@ -59,7 +59,8 @@ module MaintenanceTasks
59
59
  "To resolve this issue run: bin/rails active_storage:install"
60
60
  end
61
61
 
62
- self.collection_builder_strategy = CsvCollectionBuilder.new
62
+ self.collection_builder_strategy =
63
+ MaintenanceTasks::CsvCollectionBuilder.new
63
64
  end
64
65
 
65
66
  # Returns whether the Task handles CSV.
@@ -98,14 +99,19 @@ module MaintenanceTasks
98
99
 
99
100
  # Add a condition under which this Task will be throttled.
100
101
  #
101
- # @param backoff [ActiveSupport::Duration] optionally, a custom backoff
102
- # can be specified. This is the time to wait before retrying the Task.
103
- # If no value is specified, it defaults to 30 seconds.
102
+ # @param backoff [ActiveSupport::Duration, #call] a custom backoff
103
+ # can be specified. This is the time to wait before retrying the Task,
104
+ # defaulting to 30 seconds. If provided as a Duration, the backoff is
105
+ # wrapped in a proc. Alternatively,an object responding to call can be
106
+ # used. It must return an ActiveSupport::Duration.
104
107
  # @yieldreturn [Boolean] where the throttle condition is being met,
105
108
  # indicating that the Task should throttle.
106
109
  def throttle_on(backoff: 30.seconds, &condition)
110
+ backoff_as_proc = backoff
111
+ backoff_as_proc = -> { backoff } unless backoff.respond_to?(:call)
112
+
107
113
  self.throttle_conditions += [
108
- { throttle_on: condition, backoff: backoff },
114
+ { throttle_on: condition, backoff: backoff_as_proc },
109
115
  ]
110
116
  end
111
117
 
@@ -74,7 +74,11 @@ module MaintenanceTasks
74
74
  def code
75
75
  return if deleted?
76
76
  task = Task.named(name)
77
- file = task.instance_method(:process).source_location.first
77
+ file = if Object.respond_to?(:const_source_location)
78
+ Object.const_source_location(task.name).first
79
+ else
80
+ task.instance_method(:process).source_location.first
81
+ end
78
82
  File.read(file)
79
83
  end
80
84
 
@@ -142,6 +146,13 @@ module MaintenanceTasks
142
146
  end
143
147
  end
144
148
 
149
+ # @return [MaintenanceTasks::Task, nil] an instance of the Task class.
150
+ # @return [nil] if the Task file was deleted.
151
+ def new
152
+ return if deleted?
153
+ MaintenanceTasks::Task.named(name).new
154
+ end
155
+
145
156
  private
146
157
 
147
158
  def runs
@@ -10,6 +10,10 @@
10
10
  <%= render "maintenance_tasks/runs/info/#{run.status}", run: run %>
11
11
  </div>
12
12
 
13
+ <div class="content" id="custom-content">
14
+ <%= render "maintenance_tasks/runs/info/custom", run: run %>
15
+ </div>
16
+
13
17
  <%= render "maintenance_tasks/runs/csv", run: run %>
14
18
  <%= tag.hr if run.csv_file.present? && run.arguments.present? %>
15
19
  <%= render "maintenance_tasks/runs/arguments", run: run %>
@@ -15,6 +15,10 @@
15
15
  <%= render "maintenance_tasks/runs/info/#{run.status}", run: run %>
16
16
  </div>
17
17
 
18
+ <div class="content" id="custom-content">
19
+ <%= render "maintenance_tasks/runs/info/custom", run: run %>
20
+ </div>
21
+
18
22
  <%= render "maintenance_tasks/runs/csv", run: run %>
19
23
  <%= tag.hr if run.csv_file.present? && run.arguments.present? %>
20
24
  <%= render "maintenance_tasks/runs/arguments", run: run %>
@@ -16,6 +16,10 @@
16
16
  <%= render "maintenance_tasks/runs/info/#{last_run.status}", run: last_run %>
17
17
  </div>
18
18
 
19
+ <div class="content" id="custom-content">
20
+ <%= render "maintenance_tasks/runs/info/custom", run: last_run %>
21
+ </div>
22
+
19
23
  <%= render "maintenance_tasks/runs/csv", run: last_run %>
20
24
  <%= tag.hr if last_run.csv_file.present? %>
21
25
  <%= render "maintenance_tasks/runs/arguments", run: last_run %>
@@ -31,20 +35,22 @@
31
35
  <%= form.file_field :csv_file %>
32
36
  </div>
33
37
  <% end %>
34
- <% if @task.parameter_names.any? %>
38
+ <% parameter_names = @task.parameter_names %>
39
+ <% if parameter_names.any? %>
35
40
  <div class="block">
36
- <%= form.fields_for :task_arguments do |ff| %>
37
- <% @task.parameter_names.each do |parameter| %>
41
+ <%= form.fields_for :task_arguments, @task.new do |ff| %>
42
+ <% parameter_names.each do |parameter_name| %>
38
43
  <div class="field">
39
- <%= ff.label parameter, parameter, class: "label is-family-monospace" %>
44
+ <%= ff.label parameter_name, parameter_name, class: "label is-family-monospace" %>
40
45
  <div class="control">
41
- <%= ff.text_area parameter, class: "textarea" %>
46
+ <%= parameter_field(ff, parameter_name) %>
42
47
  </div>
43
48
  </div>
44
49
  <% end %>
45
50
  <% end %>
46
51
  </div>
47
52
  <% end %>
53
+ <%= render "maintenance_tasks/tasks/custom", form: form %>
48
54
  <div class="block">
49
55
  <%= form.submit 'Run', class: "button is-success", disabled: @task.deleted? %>
50
56
  </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddLockVersionToMaintenanceTasksRuns < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column(:maintenance_tasks_runs, :lock_version, :integer,
6
+ default: 0, null: false)
7
+ end
8
+ end
@@ -8,7 +8,16 @@ module MaintenanceTasks
8
8
  class Engine < ::Rails::Engine
9
9
  isolate_namespace MaintenanceTasks
10
10
 
11
- initializer "eager_load_for_classic_autoloader" do
11
+ initializer "maintenance_tasks.warn_classic_autoloader" do
12
+ unless Rails.autoloaders.zeitwerk_enabled?
13
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
14
+ Autoloading in classic mode is deprecated and support will be removed in the next
15
+ release of Maintenance Tasks. Please use Zeitwerk to autoload your application.
16
+ MSG
17
+ end
18
+ end
19
+
20
+ initializer "maintenance_tasks.eager_load_for_classic_autoloader" do
12
21
  eager_load! unless Rails.autoloaders.zeitwerk_enabled?
13
22
  end
14
23
 
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.6.0
4
+ version: 1.7.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-05 00:00:00.000000000 Z
11
+ date: 2022-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -114,6 +114,7 @@ files:
114
114
  - app/views/maintenance_tasks/runs/_run.html.erb
115
115
  - app/views/maintenance_tasks/runs/info/_cancelled.html.erb
116
116
  - app/views/maintenance_tasks/runs/info/_cancelling.html.erb
117
+ - app/views/maintenance_tasks/runs/info/_custom.html.erb
117
118
  - app/views/maintenance_tasks/runs/info/_enqueued.html.erb
118
119
  - app/views/maintenance_tasks/runs/info/_errored.html.erb
119
120
  - app/views/maintenance_tasks/runs/info/_interrupted.html.erb
@@ -121,6 +122,7 @@ files:
121
122
  - app/views/maintenance_tasks/runs/info/_pausing.html.erb
122
123
  - app/views/maintenance_tasks/runs/info/_running.html.erb
123
124
  - app/views/maintenance_tasks/runs/info/_succeeded.html.erb
125
+ - app/views/maintenance_tasks/tasks/_custom.html.erb
124
126
  - app/views/maintenance_tasks/tasks/_task.html.erb
125
127
  - app/views/maintenance_tasks/tasks/index.html.erb
126
128
  - app/views/maintenance_tasks/tasks/show.html.erb
@@ -128,6 +130,7 @@ files:
128
130
  - db/migrate/20201211151756_create_maintenance_tasks_runs.rb
129
131
  - db/migrate/20210225152418_remove_index_on_task_name.rb
130
132
  - db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb
133
+ - db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb
131
134
  - exe/maintenance_tasks
132
135
  - lib/generators/maintenance_tasks/install_generator.rb
133
136
  - lib/generators/maintenance_tasks/task_generator.rb
@@ -144,7 +147,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
144
147
  licenses:
145
148
  - MIT
146
149
  metadata:
147
- source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.6.0
150
+ source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v1.7.0
148
151
  allowed_push_host: https://rubygems.org
149
152
  post_install_message:
150
153
  rdoc_options: []