maintenance_tasks 1.4.0 → 1.8.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 +229 -41
- data/app/controllers/maintenance_tasks/tasks_controller.rb +2 -1
- data/app/helpers/maintenance_tasks/application_helper.rb +1 -0
- data/app/helpers/maintenance_tasks/tasks_helper.rb +19 -0
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +28 -21
- data/app/models/maintenance_tasks/application_record.rb +1 -0
- data/app/models/maintenance_tasks/csv_collection_builder.rb +38 -0
- data/app/models/maintenance_tasks/no_collection_builder.rb +29 -0
- data/app/models/maintenance_tasks/null_collection_builder.rb +38 -0
- data/app/models/maintenance_tasks/progress.rb +8 -3
- data/app/models/maintenance_tasks/run.rb +157 -13
- data/app/models/maintenance_tasks/runner.rb +22 -9
- data/app/models/maintenance_tasks/runs_page.rb +1 -0
- data/app/models/maintenance_tasks/task.rb +236 -0
- data/app/models/maintenance_tasks/task_data.rb +15 -3
- data/app/validators/maintenance_tasks/run_status_validator.rb +2 -2
- data/app/views/maintenance_tasks/runs/_arguments.html.erb +22 -0
- data/app/views/maintenance_tasks/runs/_csv.html.erb +5 -0
- data/app/views/maintenance_tasks/runs/_run.html.erb +18 -1
- data/app/views/maintenance_tasks/runs/info/_custom.html.erb +0 -0
- data/app/views/maintenance_tasks/runs/info/_errored.html.erb +0 -2
- data/app/views/maintenance_tasks/runs/info/_running.html.erb +3 -5
- data/app/views/maintenance_tasks/tasks/_custom.html.erb +0 -0
- data/app/views/maintenance_tasks/tasks/_task.html.erb +19 -1
- data/app/views/maintenance_tasks/tasks/show.html.erb +32 -7
- data/config/routes.rb +1 -0
- data/db/migrate/20201211151756_create_maintenance_tasks_runs.rb +1 -0
- data/db/migrate/20210225152418_remove_index_on_task_name.rb +1 -0
- data/db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb +1 -0
- data/db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb +8 -0
- data/lib/generators/maintenance_tasks/install_generator.rb +1 -0
- data/lib/generators/maintenance_tasks/task_generator.rb +13 -0
- data/lib/generators/maintenance_tasks/templates/no_collection_task.rb.tt +13 -0
- data/lib/generators/maintenance_tasks/templates/no_collection_task_test.rb.tt +12 -0
- data/lib/generators/maintenance_tasks/templates/task.rb.tt +3 -1
- data/lib/generators/maintenance_tasks/templates/task_test.rb.tt +4 -0
- data/lib/maintenance_tasks/cli.rb +6 -5
- data/lib/maintenance_tasks/engine.rb +15 -1
- data/lib/maintenance_tasks.rb +12 -1
- metadata +15 -7
- data/app/models/maintenance_tasks/csv_collection.rb +0 -33
- data/app/tasks/maintenance_tasks/task.rb +0 -137
- data/app/views/maintenance_tasks/runs/_info.html.erb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e66a4f3f2755a31f9dac0125d41d63d5b1b8ca28a4f11642c8648d94dbc2a044
|
4
|
+
data.tar.gz: 1cf5d80050866d6e78d4cf392c9ff888953a3d15da8c19f83f511aaaf94640d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffc019d73a707936a483349862481e5a227e270be3578f859b7d385fa7d997d327082a6f090881dcb83306feaf496d36f03d75421533ac507d7472adc5cad991
|
7
|
+
data.tar.gz: ae2ca92ea5b989e11d6ffaa50bd6ce1e5fcd41f735f81e6261ae496b81de2b90e7da2f58fdc04803dc77d44f6c0a8814a86794413f2e7ce374aa557c7e0073fb
|
data/README.md
CHANGED
@@ -57,6 +57,7 @@ Example:
|
|
57
57
|
|
58
58
|
```ruby
|
59
59
|
# app/tasks/maintenance/update_posts_task.rb
|
60
|
+
|
60
61
|
module Maintenance
|
61
62
|
class UpdatePostsTask < MaintenanceTasks::Task
|
62
63
|
def collection
|
@@ -68,7 +69,7 @@ module Maintenance
|
|
68
69
|
end
|
69
70
|
|
70
71
|
def process(post)
|
71
|
-
post.update!(content:
|
72
|
+
post.update!(content: "New content!")
|
72
73
|
end
|
73
74
|
end
|
74
75
|
end
|
@@ -78,8 +79,8 @@ end
|
|
78
79
|
|
79
80
|
You can also write a Task that iterates on a CSV file. Note that writing CSV
|
80
81
|
Tasks **requires Active Storage to be configured**. Ensure that the dependency
|
81
|
-
is specified in your application's Gemfile, and that you've followed the
|
82
|
-
|
82
|
+
is specified in your application's Gemfile, and that you've followed the [setup
|
83
|
+
instuctions][setup].
|
83
84
|
|
84
85
|
[setup]: https://edgeguides.rubyonrails.org/active_storage_overview.html#setup
|
85
86
|
|
@@ -95,6 +96,7 @@ The generated task is a subclass of `MaintenanceTasks::Task` that implements:
|
|
95
96
|
|
96
97
|
```ruby
|
97
98
|
# app/tasks/maintenance/import_posts_task.rb
|
99
|
+
|
98
100
|
module Maintenance
|
99
101
|
class ImportPostsTask < MaintenanceTasks::Task
|
100
102
|
csv_collection
|
@@ -112,16 +114,21 @@ title,content
|
|
112
114
|
My Title,Hello World!
|
113
115
|
```
|
114
116
|
|
117
|
+
The files uploaded to your Active Storage service provider will be renamed
|
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.
|
120
|
+
|
115
121
|
### Processing Batch Collections
|
116
122
|
|
117
123
|
The Maintenance Tasks gem supports processing Active Records in batches. This
|
118
124
|
can reduce the number of calls your Task makes to the database. Use
|
119
|
-
`ActiveRecord::Batches#in_batches` on the relation returned by your collection
|
120
|
-
|
121
|
-
specified.
|
125
|
+
`ActiveRecord::Batches#in_batches` on the relation returned by your collection
|
126
|
+
to specify that your Task should process batches instead of records. Active
|
127
|
+
Record defaults to 1000 records by batch, but a custom size can be specified.
|
122
128
|
|
123
129
|
```ruby
|
124
130
|
# app/tasks/maintenance/update_posts_in_batches_task.rb
|
131
|
+
|
125
132
|
module Maintenance
|
126
133
|
class UpdatePostsInBatchesTask < MaintenanceTasks::Task
|
127
134
|
def collection
|
@@ -147,10 +154,41 @@ your collection, and your Task's progress will be displayed in terms of batches
|
|
147
154
|
**Important!** Batches should only be used if `#process` is performing a batch
|
148
155
|
operation such as `#update_all` or `#delete_all`. If you need to iterate over
|
149
156
|
individual records, you should define a collection that [returns an
|
150
|
-
`ActiveRecord::Relation`](#creating-a-task). This uses batching
|
151
|
-
|
152
|
-
|
153
|
-
records when calling `each` (or any `Enumerable` method)
|
157
|
+
`ActiveRecord::Relation`](#creating-a-task). This uses batching internally, but
|
158
|
+
loads the records with one SQL query. Conversely, batch collections load the
|
159
|
+
primary keys of the records of the batch first, and then perform an additional
|
160
|
+
query to load the records when calling `each` (or any `Enumerable` method)
|
161
|
+
inside `#process`.
|
162
|
+
|
163
|
+
### Tasks that don't need a Collection
|
164
|
+
|
165
|
+
Sometimes, you might want to run a Task that performs a single operation, such
|
166
|
+
as enqueuing another background job or hitting an external API. The gem supports
|
167
|
+
collection-less tasks.
|
168
|
+
|
169
|
+
Generate a collection-less Task by running:
|
170
|
+
|
171
|
+
```bash
|
172
|
+
$ bin/rails generate maintenance_tasks:task no_collection_task --no-collection
|
173
|
+
```
|
174
|
+
|
175
|
+
The generated task is a subclass of `MaintenanceTasks::Task` that implements:
|
176
|
+
|
177
|
+
* `process`: do the work of your maintenance task
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
# app/tasks/maintenance/no_collection_task.rb
|
181
|
+
|
182
|
+
module Maintenance
|
183
|
+
class NoCollectionTask < MaintenanceTasks::Task
|
184
|
+
no_collection
|
185
|
+
|
186
|
+
def process
|
187
|
+
SomeAsyncJob.perform_later
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
```
|
154
192
|
|
155
193
|
### Throttling
|
156
194
|
|
@@ -162,6 +200,7 @@ Specify the throttle condition as a block:
|
|
162
200
|
|
163
201
|
```ruby
|
164
202
|
# app/tasks/maintenance/update_posts_throttled_task.rb
|
203
|
+
|
165
204
|
module Maintenance
|
166
205
|
class UpdatePostsThrottledTask < MaintenanceTasks::Task
|
167
206
|
throttle_on(backoff: 1.minute) do
|
@@ -192,15 +231,29 @@ Tasks can define multiple throttle conditions. Throttle conditions are inherited
|
|
192
231
|
by descendants, and new conditions will be appended without impacting existing
|
193
232
|
conditions.
|
194
233
|
|
234
|
+
The backoff can also be specified as a proc:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# app/tasks/maintenance/update_posts_throttled_task.rb
|
238
|
+
|
239
|
+
module Maintenance
|
240
|
+
class UpdatePostsThrottledTask < MaintenanceTasks::Task
|
241
|
+
throttle_on(backoff: -> { RandomBackoffGenerator.generate_duration } ) do
|
242
|
+
DatabaseStatus.unhealthy?
|
243
|
+
end
|
244
|
+
...
|
245
|
+
end
|
246
|
+
end
|
247
|
+
```
|
195
248
|
### Custom Task Parameters
|
196
249
|
|
197
250
|
Tasks may need additional information, supplied via parameters, to run.
|
198
|
-
Parameters can be defined as Active Model Attributes in a Task, and then
|
199
|
-
|
200
|
-
`#process`.
|
251
|
+
Parameters can be defined as Active Model Attributes in a Task, and then become
|
252
|
+
accessible to any of Task's methods: `#collection`, `#count`, or `#process`.
|
201
253
|
|
202
254
|
```ruby
|
203
255
|
# app/tasks/maintenance/update_posts_via_params_task.rb
|
256
|
+
|
204
257
|
module Maintenance
|
205
258
|
class UpdatePostsViaParamsTask < MaintenanceTasks::Task
|
206
259
|
attribute :updated_content, :string
|
@@ -227,6 +280,73 @@ to run. Since arguments are specified in the user interface via text area
|
|
227
280
|
inputs, it's important to check that they conform to the format your Task
|
228
281
|
expects, and to sanitize any inputs if necessary.
|
229
282
|
|
283
|
+
### Using Task Callbacks
|
284
|
+
|
285
|
+
The Task provides callbacks that hook into its life cycle.
|
286
|
+
|
287
|
+
Available callbacks are:
|
288
|
+
|
289
|
+
* `after_start`
|
290
|
+
* `after_pause`
|
291
|
+
* `after_interrupt`
|
292
|
+
* `after_cancel`
|
293
|
+
* `after_complete`
|
294
|
+
* `after_error`
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
module Maintenance
|
298
|
+
class UpdatePostsTask < MaintenanceTasks::Task
|
299
|
+
after_start :notify
|
300
|
+
|
301
|
+
def notify
|
302
|
+
NotifyJob.perform_later(self.class.name)
|
303
|
+
end
|
304
|
+
|
305
|
+
# ...
|
306
|
+
end
|
307
|
+
end
|
308
|
+
```
|
309
|
+
|
310
|
+
Note: The `after_error` callback is guaranteed to complete,
|
311
|
+
so any exceptions raised in your callback code are ignored.
|
312
|
+
If your `after_error` callback code can raise an exception,
|
313
|
+
you'll need to rescue it and handle it appropriately
|
314
|
+
within the callback.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
module Maintenance
|
318
|
+
class UpdatePostsTask < MaintenanceTasks::Task
|
319
|
+
after_error :dangerous_notify
|
320
|
+
|
321
|
+
def dangerous_notify
|
322
|
+
# This error is rescued in favour of the original error causing the error flow.
|
323
|
+
raise NotDeliveredError
|
324
|
+
end
|
325
|
+
|
326
|
+
# ...
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
If any of the other callbacks cause an exception,
|
332
|
+
it will be handled by the error handler,
|
333
|
+
and will cause the task to stop running.
|
334
|
+
|
335
|
+
Callback behaviour can be shared across all tasks using an initializer.
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
# config/initializer/maintenance_tasks.rb
|
339
|
+
Rails.autoloaders.main.on_load("MaintenanceTasks::Task") do
|
340
|
+
MaintenanceTasks::Task.class_eval do
|
341
|
+
after_start(:notify)
|
342
|
+
|
343
|
+
private
|
344
|
+
|
345
|
+
def notify; end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
230
350
|
### Considerations when writing Tasks
|
231
351
|
|
232
352
|
MaintenanceTasks relies on the queue adapter configured for your application to
|
@@ -259,7 +379,7 @@ Example:
|
|
259
379
|
```ruby
|
260
380
|
# test/tasks/maintenance/update_posts_task_test.rb
|
261
381
|
|
262
|
-
require
|
382
|
+
require "test_helper"
|
263
383
|
|
264
384
|
module Maintenance
|
265
385
|
class UpdatePostsTaskTest < ActiveSupport::TestCase
|
@@ -268,7 +388,7 @@ module Maintenance
|
|
268
388
|
|
269
389
|
Maintenance::UpdatePostsTask.process(post)
|
270
390
|
|
271
|
-
assert_equal
|
391
|
+
assert_equal "New content!", post.content
|
272
392
|
end
|
273
393
|
end
|
274
394
|
end
|
@@ -281,20 +401,50 @@ takes a `CSV::Row` as an argument. You can pass a row, or a hash with string
|
|
281
401
|
keys to `#process` from your test.
|
282
402
|
|
283
403
|
```ruby
|
284
|
-
#
|
404
|
+
# test/tasks/maintenance/import_posts_task_test.rb
|
405
|
+
|
406
|
+
require "test_helper"
|
407
|
+
|
285
408
|
module Maintenance
|
286
409
|
class ImportPostsTaskTest < ActiveSupport::TestCase
|
287
410
|
test "#process performs a task iteration" do
|
288
411
|
assert_difference -> { Post.count } do
|
289
412
|
Maintenance::UpdatePostsTask.process({
|
290
|
-
|
291
|
-
|
413
|
+
"title" => "My Title",
|
414
|
+
"content" => "Hello World!",
|
292
415
|
})
|
293
416
|
end
|
294
417
|
|
295
418
|
post = Post.last
|
296
|
-
assert_equal
|
297
|
-
assert_equal
|
419
|
+
assert_equal "My Title", post.title
|
420
|
+
assert_equal "Hello World!", post.content
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
```
|
425
|
+
|
426
|
+
### Writing tests for a Task with parameters
|
427
|
+
|
428
|
+
Tests for tasks with parameters need to instatiate the task class in order to
|
429
|
+
assign attributes. Once the task instance is setup, you may test `#process`
|
430
|
+
normally.
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
# test/tasks/maintenance/update_posts_via_params_task_test.rb
|
434
|
+
|
435
|
+
require "test_helper"
|
436
|
+
|
437
|
+
module Maintenance
|
438
|
+
class UpdatePostsViaParamsTaskTest < ActiveSupport::TestCase
|
439
|
+
setup do
|
440
|
+
@task = UpdatePostsViaParamsTask.new
|
441
|
+
@task.updated_content = "Testing"
|
442
|
+
end
|
443
|
+
|
444
|
+
test "#process performs a task iteration" do
|
445
|
+
assert_difference -> { Post.first.content } do
|
446
|
+
task.process(Post.first)
|
447
|
+
end
|
298
448
|
end
|
299
449
|
end
|
300
450
|
end
|
@@ -313,20 +463,21 @@ $ bundle exec maintenance_tasks perform Maintenance::UpdatePostsTask
|
|
313
463
|
To run a Task that processes CSVs from the command line, use the --csv option:
|
314
464
|
|
315
465
|
```bash
|
316
|
-
$ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv
|
466
|
+
$ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv "path/to/my_csv.csv"
|
317
467
|
```
|
318
468
|
|
319
469
|
To run a Task that takes arguments from the command line, use the --arguments
|
320
|
-
option, passing arguments as a set of
|
470
|
+
option, passing arguments as a set of \<key>:\<value> pairs:
|
321
471
|
|
322
472
|
```bash
|
323
|
-
$ bundle exec maintenance_tasks perform Maintenance::ParamsTask
|
473
|
+
$ bundle exec maintenance_tasks perform Maintenance::ParamsTask \
|
474
|
+
--arguments post_ids:1,2,3 content:"Hello, World!"
|
324
475
|
```
|
325
476
|
|
326
477
|
You can also run a Task in Ruby by sending `run` with a Task name to Runner:
|
327
478
|
|
328
479
|
```ruby
|
329
|
-
MaintenanceTasks::Runner.run(name:
|
480
|
+
MaintenanceTasks::Runner.run(name: "Maintenance::UpdatePostsTask")
|
330
481
|
```
|
331
482
|
|
332
483
|
To run a Task that processes CSVs using the Runner, provide a Hash containing an
|
@@ -334,8 +485,8 @@ open IO object and a filename to `run`:
|
|
334
485
|
|
335
486
|
```ruby
|
336
487
|
MaintenanceTasks::Runner.run(
|
337
|
-
name:
|
338
|
-
csv_file: { io: File.open(
|
488
|
+
name: "Maintenance::ImportPostsTask",
|
489
|
+
csv_file: { io: File.open("path/to/my_csv.csv"), filename: "my_csv.csv" }
|
339
490
|
)
|
340
491
|
```
|
341
492
|
|
@@ -358,8 +509,8 @@ a Task can be in:
|
|
358
509
|
* **enqueued**: A Task that is waiting to be performed after a user has
|
359
510
|
instructed it to run.
|
360
511
|
* **running**: A Task that is currently being performed by a job worker.
|
361
|
-
* **pausing**: A Task that was paused by a user, but needs to finish work
|
362
|
-
|
512
|
+
* **pausing**: A Task that was paused by a user, but needs to finish work before
|
513
|
+
stopping.
|
363
514
|
* **paused**: A Task that was paused by a user and is not performing. It can be
|
364
515
|
resumed.
|
365
516
|
* **interrupted**: A Task that has been momentarily interrupted by the job
|
@@ -433,9 +584,10 @@ you can define an error handler:
|
|
433
584
|
|
434
585
|
```ruby
|
435
586
|
# config/initializers/maintenance_tasks.rb
|
587
|
+
|
436
588
|
MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
|
437
589
|
Bugsnag.notify(error) do |notification|
|
438
|
-
notification.
|
590
|
+
notification.add_metadata(:task, task_context)
|
439
591
|
end
|
440
592
|
end
|
441
593
|
```
|
@@ -448,18 +600,18 @@ The error handler should be a lambda that accepts three arguments:
|
|
448
600
|
* `task_name`: The name of the Task that errored
|
449
601
|
* `started_at`: The time the Task started
|
450
602
|
* `ended_at`: The time the Task errored
|
603
|
+
|
451
604
|
Note that `task_context` may be empty if the Task produced an error before any
|
452
605
|
context could be gathered (for example, if deserializing the job to process
|
453
606
|
your Task failed).
|
454
|
-
* `errored_element`: The element, if any, that was being processed when the
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
report.
|
607
|
+
* `errored_element`: The element, if any, that was being processed when the Task
|
608
|
+
raised an exception. If you would like to pass this object to your exception
|
609
|
+
monitoring service, make sure you **sanitize the object** to avoid leaking
|
610
|
+
sensitive data and **convert it to a format** that is compatible with your bug
|
611
|
+
tracker. For example, Bugsnag only sends the id and class name of Active
|
612
|
+
Record objects in order to protect sensitive data. CSV rows, on the other
|
613
|
+
hand, are converted to strings and passed raw to Bugsnag, so make sure to
|
614
|
+
filter any personal data from these objects before adding them to a report.
|
463
615
|
|
464
616
|
#### Customizing the maintenance tasks module
|
465
617
|
|
@@ -468,7 +620,8 @@ tasks will be placed.
|
|
468
620
|
|
469
621
|
```ruby
|
470
622
|
# config/initializers/maintenance_tasks.rb
|
471
|
-
|
623
|
+
|
624
|
+
MaintenanceTasks.tasks_module = "TaskModule"
|
472
625
|
```
|
473
626
|
|
474
627
|
If no value is specified, it will default to `Maintenance`.
|
@@ -481,9 +634,11 @@ maintenance tasks in your application.
|
|
481
634
|
|
482
635
|
```ruby
|
483
636
|
# config/initializers/maintenance_tasks.rb
|
637
|
+
|
484
638
|
MaintenanceTasks.job = 'CustomTaskJob'
|
485
639
|
|
486
640
|
# app/jobs/custom_task_job.rb
|
641
|
+
|
487
642
|
class CustomTaskJob < MaintenanceTasks::TaskJob
|
488
643
|
queue_as :low_priority
|
489
644
|
end
|
@@ -491,8 +646,8 @@ end
|
|
491
646
|
|
492
647
|
The Job class **must inherit** from `MaintenanceTasks::TaskJob`.
|
493
648
|
|
494
|
-
Note that `retry_on` is not supported for custom Job
|
495
|
-
|
649
|
+
Note that `retry_on` is not supported for custom Job classes, so failed jobs
|
650
|
+
cannot be retried.
|
496
651
|
|
497
652
|
#### Customizing the rate at which task progress gets updated
|
498
653
|
|
@@ -502,6 +657,7 @@ task progress gets persisted to the database. It can be a `Numeric` value or an
|
|
502
657
|
|
503
658
|
```ruby
|
504
659
|
# config/initializers/maintenance_tasks.rb
|
660
|
+
|
505
661
|
MaintenanceTasks.ticker_delay = 2.seconds
|
506
662
|
```
|
507
663
|
|
@@ -516,6 +672,7 @@ key, as specified in your application's `config/storage.yml`:
|
|
516
672
|
|
517
673
|
```yaml
|
518
674
|
# config/storage.yml
|
675
|
+
|
519
676
|
user_data:
|
520
677
|
service: GCS
|
521
678
|
credentials: <%= Rails.root.join("path/to/user/data/keyfile.json") %>
|
@@ -531,12 +688,31 @@ internal:
|
|
531
688
|
|
532
689
|
```ruby
|
533
690
|
# config/initializers/maintenance_tasks.rb
|
691
|
+
|
534
692
|
MaintenanceTasks.active_storage_service = :internal
|
535
693
|
```
|
536
694
|
|
537
695
|
There is no need to configure this option if your application uses only one
|
538
696
|
storage service per environment.
|
539
697
|
|
698
|
+
#### Customizing the backtrace cleaner
|
699
|
+
|
700
|
+
`MaintenanceTasks.backtrace_cleaner` can be configured to specify a backtrace
|
701
|
+
cleaner to use when a Task errors and the backtrace is cleaned and persisted.
|
702
|
+
An `ActiveSupport::BacktraceCleaner` should be used.
|
703
|
+
|
704
|
+
```ruby
|
705
|
+
# config/initializers/maintenance_tasks.rb
|
706
|
+
|
707
|
+
cleaner = ActiveSupport::BacktraceCleaner.new
|
708
|
+
cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
|
709
|
+
|
710
|
+
MaintenanceTasks.backtrace_cleaner = cleaner
|
711
|
+
```
|
712
|
+
|
713
|
+
If none is specified, the default `Rails.backtrace_cleaner` will be used to
|
714
|
+
clean backtraces.
|
715
|
+
|
540
716
|
## Upgrading
|
541
717
|
|
542
718
|
Use bundler to check for and upgrade to newer versions. After installing a new
|
@@ -548,6 +724,17 @@ $ bin/rails generate maintenance_tasks:install
|
|
548
724
|
|
549
725
|
This ensures that new migrations are installed and run as well.
|
550
726
|
|
727
|
+
**What if I've deleted my previous Maintenance Task migrations?**
|
728
|
+
|
729
|
+
The install command will attempt to reinstall these old migrations and migrating
|
730
|
+
the database will cause problems. Use `bin/rails
|
731
|
+
maintenance_tasks:install:migrations` to copy the gem's migrations to your
|
732
|
+
`db/migrate` folder. Check the release notes to see if any new migrations were
|
733
|
+
added since your last gem upgrade. Ensure that these are kept, but remove any
|
734
|
+
migrations that already ran.
|
735
|
+
|
736
|
+
Run the migrations using `bin/rails db:migrate`.
|
737
|
+
|
551
738
|
## Contributing
|
552
739
|
|
553
740
|
Would you like to report an issue or contribute with code? We accept issues and
|
@@ -564,6 +751,7 @@ are merged.
|
|
564
751
|
Once a release is ready, follow these steps:
|
565
752
|
|
566
753
|
* Update `spec.version` in `maintenance_tasks.gemspec`.
|
754
|
+
* Run `bundle install` to bump the `Gemfile.lock` version of the gem.
|
567
755
|
* Open a PR and merge on approval.
|
568
756
|
* Deploy via [Shipit][shipit] and see the new version on
|
569
757
|
<https://rubygems.org/gems/maintenance_tasks>.
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module MaintenanceTasks
|
3
4
|
# Concern that holds the behaviour of the job that runs the tasks. It is
|
4
5
|
# included in {TaskJob} and if MaintenanceTasks.job is overridden, it must be
|
@@ -11,8 +12,8 @@ module MaintenanceTasks
|
|
11
12
|
before_perform(:before_perform)
|
12
13
|
|
13
14
|
on_start(:on_start)
|
14
|
-
on_complete(:on_complete)
|
15
15
|
on_shutdown(:on_shutdown)
|
16
|
+
on_complete(:on_complete)
|
16
17
|
|
17
18
|
after_perform(:after_perform)
|
18
19
|
|
@@ -35,6 +36,8 @@ module MaintenanceTasks
|
|
35
36
|
@enumerator = nil
|
36
37
|
|
37
38
|
collection_enum = case collection
|
39
|
+
when :no_collection
|
40
|
+
enumerator_builder.build_once_enumerator(cursor: nil)
|
38
41
|
when ActiveRecord::Relation
|
39
42
|
enumerator_builder.active_record_on_records(collection, cursor: cursor)
|
40
43
|
when ActiveRecord::Batches::BatchEnumerator
|
@@ -62,9 +65,16 @@ module MaintenanceTasks
|
|
62
65
|
Array, or CSV.
|
63
66
|
MSG
|
64
67
|
end
|
68
|
+
throttle_enumerator(collection_enum)
|
69
|
+
end
|
65
70
|
|
71
|
+
def throttle_enumerator(collection_enum)
|
66
72
|
@task.throttle_conditions.reduce(collection_enum) do |enum, condition|
|
67
|
-
enumerator_builder.build_throttle_enumerator(
|
73
|
+
enumerator_builder.build_throttle_enumerator(
|
74
|
+
enum,
|
75
|
+
throttle_on: condition[:throttle_on],
|
76
|
+
backoff: condition[:backoff].call
|
77
|
+
)
|
68
78
|
end
|
69
79
|
end
|
70
80
|
|
@@ -81,7 +91,11 @@ module MaintenanceTasks
|
|
81
91
|
end
|
82
92
|
|
83
93
|
def task_iteration(input)
|
84
|
-
@task.
|
94
|
+
if @task.no_collection?
|
95
|
+
@task.process
|
96
|
+
else
|
97
|
+
@task.process(input)
|
98
|
+
end
|
85
99
|
rescue => error
|
86
100
|
@errored_element = input
|
87
101
|
raise error
|
@@ -90,12 +104,11 @@ module MaintenanceTasks
|
|
90
104
|
def before_perform
|
91
105
|
@run = arguments.first
|
92
106
|
@task = @run.task
|
93
|
-
if @task.
|
107
|
+
if @task.has_csv_content?
|
94
108
|
@task.csv_content = @run.csv_file.download
|
95
109
|
end
|
96
|
-
@run.job_id = job_id
|
97
110
|
|
98
|
-
@run.running
|
111
|
+
@run.running
|
99
112
|
|
100
113
|
@ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
|
101
114
|
@run.persist_progress(ticks, duration)
|
@@ -105,26 +118,19 @@ module MaintenanceTasks
|
|
105
118
|
def on_start
|
106
119
|
count = @task.count
|
107
120
|
count = @enumerator&.size if count == :no_count
|
108
|
-
@run.
|
109
|
-
end
|
110
|
-
|
111
|
-
def on_complete
|
112
|
-
@run.status = :succeeded
|
113
|
-
@run.ended_at = Time.now
|
121
|
+
@run.start(count)
|
114
122
|
end
|
115
123
|
|
116
124
|
def on_shutdown
|
117
|
-
|
118
|
-
|
119
|
-
@run.ended_at = Time.now
|
120
|
-
else
|
121
|
-
@run.status = @run.pausing? ? :paused : :interrupted
|
122
|
-
@run.cursor = cursor_position
|
123
|
-
end
|
124
|
-
|
125
|
+
@run.job_shutdown
|
126
|
+
@run.cursor = cursor_position
|
125
127
|
@ticker.persist
|
126
128
|
end
|
127
129
|
|
130
|
+
def on_complete
|
131
|
+
@run.complete
|
132
|
+
end
|
133
|
+
|
128
134
|
# We are reopening a private part of Job Iteration's API here, so we should
|
129
135
|
# ensure the method is still defined upstream. This way, in the case where
|
130
136
|
# the method changes upstream, we catch it at load time instead of at
|
@@ -143,7 +149,7 @@ module MaintenanceTasks
|
|
143
149
|
end
|
144
150
|
|
145
151
|
def after_perform
|
146
|
-
@run.
|
152
|
+
@run.persist_transition
|
147
153
|
if defined?(@reenqueue_iteration_job) && @reenqueue_iteration_job
|
148
154
|
reenqueue_iteration_job(should_ignore: false)
|
149
155
|
end
|
@@ -164,6 +170,7 @@ module MaintenanceTasks
|
|
164
170
|
task_context = {}
|
165
171
|
end
|
166
172
|
errored_element = @errored_element if defined?(@errored_element)
|
173
|
+
ensure
|
167
174
|
MaintenanceTasks.error_handler.call(error, task_context, errored_element)
|
168
175
|
end
|
169
176
|
end
|