maintenance_tasks 1.4.0 → 1.8.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 +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
|