maintenance_tasks 1.3.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +288 -45
  3. data/app/controllers/maintenance_tasks/tasks_controller.rb +7 -2
  4. data/app/helpers/maintenance_tasks/application_helper.rb +1 -0
  5. data/app/helpers/maintenance_tasks/tasks_helper.rb +19 -14
  6. data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +44 -23
  7. data/app/models/maintenance_tasks/application_record.rb +1 -0
  8. data/app/models/maintenance_tasks/csv_collection_builder.rb +33 -0
  9. data/app/models/maintenance_tasks/null_collection_builder.rb +31 -0
  10. data/app/models/maintenance_tasks/progress.rb +8 -3
  11. data/app/models/maintenance_tasks/run.rb +224 -18
  12. data/app/models/maintenance_tasks/runner.rb +26 -7
  13. data/app/models/maintenance_tasks/runs_page.rb +1 -0
  14. data/app/models/maintenance_tasks/task.rb +225 -0
  15. data/app/models/maintenance_tasks/task_data.rb +24 -3
  16. data/app/validators/maintenance_tasks/run_status_validator.rb +2 -2
  17. data/app/views/maintenance_tasks/runs/_arguments.html.erb +22 -0
  18. data/app/views/maintenance_tasks/runs/_csv.html.erb +5 -0
  19. data/app/views/maintenance_tasks/runs/_run.html.erb +18 -1
  20. data/app/views/maintenance_tasks/runs/info/_custom.html.erb +0 -0
  21. data/app/views/maintenance_tasks/runs/info/_errored.html.erb +0 -2
  22. data/app/views/maintenance_tasks/runs/info/_paused.html.erb +2 -2
  23. data/app/views/maintenance_tasks/runs/info/_running.html.erb +3 -5
  24. data/app/views/maintenance_tasks/tasks/_custom.html.erb +0 -0
  25. data/app/views/maintenance_tasks/tasks/_task.html.erb +19 -1
  26. data/app/views/maintenance_tasks/tasks/index.html.erb +2 -2
  27. data/app/views/maintenance_tasks/tasks/show.html.erb +37 -2
  28. data/config/routes.rb +1 -0
  29. data/db/migrate/20201211151756_create_maintenance_tasks_runs.rb +1 -0
  30. data/db/migrate/20210225152418_remove_index_on_task_name.rb +1 -0
  31. data/db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb +7 -0
  32. data/db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb +8 -0
  33. data/lib/generators/maintenance_tasks/install_generator.rb +1 -0
  34. data/lib/generators/maintenance_tasks/templates/task.rb.tt +3 -1
  35. data/lib/maintenance_tasks/cli.rb +11 -4
  36. data/lib/maintenance_tasks/engine.rb +15 -1
  37. data/lib/maintenance_tasks.rb +14 -1
  38. data/lib/patches/active_record_batch_enumerator.rb +23 -0
  39. metadata +15 -11
  40. data/app/models/maintenance_tasks/csv_collection.rb +0 -33
  41. data/app/tasks/maintenance_tasks/task.rb +0 -133
  42. 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: 649f66cc11a303666134c75aad8575b81eb376e358f69312180e21a14528ce50
4
- data.tar.gz: b564b46e647c467c5d93a6198ab112729623059cfe194d1115d095c8ab8d9953
3
+ metadata.gz: 6019b1623a8cc72e877154f52e8437339598f0cab121189d78692e0989e86e61
4
+ data.tar.gz: 21563dc6dffadd2e58dd551fe2b7d71297b2da36500722f5d9fed91892b1fd6e
5
5
  SHA512:
6
- metadata.gz: f313350c5a80fa8840b23976eb7cde8776f825bc25a9b6a8fb7ce5d6f1abe8148d547ae7b2c1368a72caf980a2a12377e33b7c325e1447de3c0a0b0686d87a8d
7
- data.tar.gz: '084950e7be06503e12d437294c784d02e2f0449c76db6297c5f95fcfd40845e4ce7e3354e16fbe12dd99b82554946f16fbc3415c047c17109aab1303c6b81b9b'
6
+ metadata.gz: 93228d08b49fab144297cb44a0aa4b9be53144ff13b88c2eba384ef29fdb47b9a8caefaf94aaf1fbfbf4584f4d544396720c17d86f3bad31508fd66dfd7507b8
7
+ data.tar.gz: cab4cebb685fddf978eeebd62a5c16867215f849d424b9ba948beba0e93f42a5fe11ee1eccc15174c1494fa16a2fa100d3a7745c5eacef2527d425895805cf29
data/README.md CHANGED
@@ -10,7 +10,7 @@ To install the gem and run the install generator, execute:
10
10
 
11
11
  ```bash
12
12
  $ bundle add maintenance_tasks
13
- $ rails generate maintenance_tasks:install
13
+ $ bin/rails generate maintenance_tasks:install
14
14
  ```
15
15
 
16
16
  The generator creates and runs a migration to add the necessary table to your
@@ -23,12 +23,12 @@ handler](#customizing-the-error-handler) for more information.
23
23
 
24
24
  ### Active Job Dependency
25
25
 
26
- The Maintenance Tasks framework relies on ActiveJob behind the scenes to run
27
- Tasks. The default queuing backend for ActiveJob is
26
+ The Maintenance Tasks framework relies on Active Job behind the scenes to run
27
+ Tasks. The default queuing backend for Active Job is
28
28
  [asynchronous][async-adapter]. It is **strongly recommended** to change this to
29
29
  a persistent backend so that Task progress is not lost during code or
30
30
  infrastructure changes. For more information on configuring a queuing backend,
31
- take a look at the [ActiveJob documentation][active-job-docs].
31
+ take a look at the [Active Job documentation][active-job-docs].
32
32
 
33
33
  [async-adapter]: https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html
34
34
  [active-job-docs]: https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend
@@ -40,7 +40,7 @@ take a look at the [ActiveJob documentation][active-job-docs].
40
40
  A generator is provided to create tasks. Generate a new task by running:
41
41
 
42
42
  ```bash
43
- $ rails generate maintenance_tasks:task update_posts
43
+ $ bin/rails generate maintenance_tasks:task update_posts
44
44
  ```
45
45
 
46
46
  This creates the task file `app/tasks/maintenance/update_posts_task.rb`.
@@ -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: 'New content!')
72
+ post.update!(content: "New content!")
72
73
  end
73
74
  end
74
75
  end
@@ -77,16 +78,16 @@ end
77
78
  ### Creating a CSV Task
78
79
 
79
80
  You can also write a Task that iterates on a CSV file. Note that writing CSV
80
- Tasks **requires ActiveStorage to be configured**. Ensure that the dependency
81
- is specified in your application's Gemfile, and that you've followed the
82
- [setup instuctions][setup].
81
+ Tasks **requires Active Storage to be configured**. Ensure that the dependency
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
 
86
87
  Generate a CSV Task by running:
87
88
 
88
89
  ```bash
89
- $ rails generate maintenance_tasks:task import_posts --csv
90
+ $ bin/rails generate maintenance_tasks:task import_posts --csv
90
91
  ```
91
92
 
92
93
  The generated task is a subclass of `MaintenanceTasks::Task` that implements:
@@ -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,6 +114,52 @@ 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
+
121
+ ### Processing Batch Collections
122
+
123
+ The Maintenance Tasks gem supports processing Active Records in batches. This
124
+ can reduce the number of calls your Task makes to the database. Use
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.
128
+
129
+ ```ruby
130
+ # app/tasks/maintenance/update_posts_in_batches_task.rb
131
+
132
+ module Maintenance
133
+ class UpdatePostsInBatchesTask < MaintenanceTasks::Task
134
+ def collection
135
+ Post.in_batches
136
+ end
137
+
138
+ def process(batch_of_posts)
139
+ batch_of_posts.update_all(content: "New content added on #{Time.now.utc}")
140
+ end
141
+ end
142
+ end
143
+ ```
144
+
145
+ Ensure that you've implemented the following methods:
146
+
147
+ * `collection`: return an `ActiveRecord::Batches::BatchEnumerator`.
148
+ * `process`: do the work of your Task on a batch (`ActiveRecord::Relation`).
149
+
150
+ Note that `#count` is calculated automatically based on the number of batches in
151
+ your collection, and your Task's progress will be displayed in terms of batches
152
+ (not the number of records in the relation).
153
+
154
+ **Important!** Batches should only be used if `#process` is performing a batch
155
+ operation such as `#update_all` or `#delete_all`. If you need to iterate over
156
+ individual records, you should define a collection that [returns an
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
+
115
163
  ### Throttling
116
164
 
117
165
  Maintenance Tasks often modify a lot of data and can be taxing on your database.
@@ -122,6 +170,7 @@ Specify the throttle condition as a block:
122
170
 
123
171
  ```ruby
124
172
  # app/tasks/maintenance/update_posts_throttled_task.rb
173
+
125
174
  module Maintenance
126
175
  class UpdatePostsThrottledTask < MaintenanceTasks::Task
127
176
  throttle_on(backoff: 1.minute) do
@@ -145,13 +194,129 @@ end
145
194
 
146
195
  Note that it's up to you to define a throttling condition that makes sense for
147
196
  your app. Shopify implements `DatabaseStatus.healthy?` to check various MySQL
148
- metrics such as replication lag, DB threads, whether DB writes are available,
197
+ metrics such as replication lag, DB threads, whether DB writes are available,
149
198
  etc.
150
199
 
151
200
  Tasks can define multiple throttle conditions. Throttle conditions are inherited
152
201
  by descendants, and new conditions will be appended without impacting existing
153
202
  conditions.
154
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
+ ```
218
+ ### Custom Task Parameters
219
+
220
+ Tasks may need additional information, supplied via parameters, to run.
221
+ Parameters can be defined as Active Model Attributes in a Task, and then become
222
+ accessible to any of Task's methods: `#collection`, `#count`, or `#process`.
223
+
224
+ ```ruby
225
+ # app/tasks/maintenance/update_posts_via_params_task.rb
226
+
227
+ module Maintenance
228
+ class UpdatePostsViaParamsTask < MaintenanceTasks::Task
229
+ attribute :updated_content, :string
230
+ validates :updated_content, presence: true
231
+
232
+ def collection
233
+ Post.all
234
+ end
235
+
236
+ def count
237
+ collection.count
238
+ end
239
+
240
+ def process(post)
241
+ post.update!(content: updated_content)
242
+ end
243
+ end
244
+ end
245
+ ```
246
+
247
+ Tasks can leverage Active Model Validations when defining parameters. Arguments
248
+ supplied to a Task accepting parameters will be validated before the Task starts
249
+ to run. Since arguments are specified in the user interface via text area
250
+ inputs, it's important to check that they conform to the format your Task
251
+ expects, and to sanitize any inputs if necessary.
252
+
253
+ ### Using Task Callbacks
254
+
255
+ The Task provides callbacks that hook into its life cycle.
256
+
257
+ Available callbacks are:
258
+
259
+ * `after_start`
260
+ * `after_pause`
261
+ * `after_interrupt`
262
+ * `after_cancel`
263
+ * `after_complete`
264
+ * `after_error`
265
+
266
+ ```ruby
267
+ module Maintenance
268
+ class UpdatePostsTask < MaintenanceTasks::Task
269
+ after_start :notify
270
+
271
+ def notify
272
+ NotifyJob.perform_later(self.class.name)
273
+ end
274
+
275
+ # ...
276
+ end
277
+ end
278
+ ```
279
+
280
+ Note: The `after_error` callback is guaranteed to complete,
281
+ so any exceptions raised in your callback code are ignored.
282
+ If your `after_error` callback code can raise an exception,
283
+ you'll need to rescue it and handle it appropriately
284
+ within the callback.
285
+
286
+ ```ruby
287
+ module Maintenance
288
+ class UpdatePostsTask < MaintenanceTasks::Task
289
+ after_error :dangerous_notify
290
+
291
+ def dangerous_notify
292
+ # This error is rescued in favour of the original error causing the error flow.
293
+ raise NotDeliveredError
294
+ end
295
+
296
+ # ...
297
+ end
298
+ end
299
+ ```
300
+
301
+ If any of the other callbacks cause an exception,
302
+ it will be handled by the error handler,
303
+ and will cause the task to stop running.
304
+
305
+ Callback behaviour can be shared across all tasks using an initializer.
306
+
307
+ ```ruby
308
+ # config/initializer/maintenance_tasks.rb
309
+ Rails.autoloaders.main.on_load("MaintenanceTasks::Task") do
310
+ MaintenanceTasks::Task.class_eval do
311
+ after_start(:notify)
312
+
313
+ private
314
+
315
+ def notify; end
316
+ end
317
+ end
318
+ ```
319
+
155
320
  ### Considerations when writing Tasks
156
321
 
157
322
  MaintenanceTasks relies on the queue adapter configured for your application to
@@ -184,7 +349,7 @@ Example:
184
349
  ```ruby
185
350
  # test/tasks/maintenance/update_posts_task_test.rb
186
351
 
187
- require 'test_helper'
352
+ require "test_helper"
188
353
 
189
354
  module Maintenance
190
355
  class UpdatePostsTaskTest < ActiveSupport::TestCase
@@ -193,7 +358,7 @@ module Maintenance
193
358
 
194
359
  Maintenance::UpdatePostsTask.process(post)
195
360
 
196
- assert_equal 'New content!', post.content
361
+ assert_equal "New content!", post.content
197
362
  end
198
363
  end
199
364
  end
@@ -206,20 +371,50 @@ takes a `CSV::Row` as an argument. You can pass a row, or a hash with string
206
371
  keys to `#process` from your test.
207
372
 
208
373
  ```ruby
209
- # app/tasks/maintenance/import_posts_task_test.rb
374
+ # test/tasks/maintenance/import_posts_task_test.rb
375
+
376
+ require "test_helper"
377
+
210
378
  module Maintenance
211
379
  class ImportPostsTaskTest < ActiveSupport::TestCase
212
380
  test "#process performs a task iteration" do
213
381
  assert_difference -> { Post.count } do
214
382
  Maintenance::UpdatePostsTask.process({
215
- 'title' => 'My Title',
216
- 'content' => 'Hello World!',
383
+ "title" => "My Title",
384
+ "content" => "Hello World!",
217
385
  })
218
386
  end
219
387
 
220
388
  post = Post.last
221
- assert_equal 'My Title', post.title
222
- assert_equal 'Hello World!', post.content
389
+ assert_equal "My Title", post.title
390
+ assert_equal "Hello World!", post.content
391
+ end
392
+ end
393
+ end
394
+ ```
395
+
396
+ ### Writing tests for a Task with parameters
397
+
398
+ Tests for tasks with parameters need to instatiate the task class in order to
399
+ assign attributes. Once the task instance is setup, you may test `#process`
400
+ normally.
401
+
402
+ ```ruby
403
+ # test/tasks/maintenance/update_posts_via_params_task_test.rb
404
+
405
+ require "test_helper"
406
+
407
+ module Maintenance
408
+ class UpdatePostsViaParamsTaskTest < ActiveSupport::TestCase
409
+ setup do
410
+ @task = UpdatePostsViaParamsTask.new
411
+ @task.updated_content = "Testing"
412
+ end
413
+
414
+ test "#process performs a task iteration" do
415
+ assert_difference -> { Post.first.content } do
416
+ task.process(Post.first)
417
+ end
223
418
  end
224
419
  end
225
420
  end
@@ -238,13 +433,21 @@ $ bundle exec maintenance_tasks perform Maintenance::UpdatePostsTask
238
433
  To run a Task that processes CSVs from the command line, use the --csv option:
239
434
 
240
435
  ```bash
241
- $ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv 'path/to/my_csv.csv'
436
+ $ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv "path/to/my_csv.csv"
437
+ ```
438
+
439
+ To run a Task that takes arguments from the command line, use the --arguments
440
+ option, passing arguments as a set of \<key>:\<value> pairs:
441
+
442
+ ```bash
443
+ $ bundle exec maintenance_tasks perform Maintenance::ParamsTask \
444
+ --arguments post_ids:1,2,3 content:"Hello, World!"
242
445
  ```
243
446
 
244
447
  You can also run a Task in Ruby by sending `run` with a Task name to Runner:
245
448
 
246
449
  ```ruby
247
- MaintenanceTasks::Runner.run(name: 'Maintenance::UpdatePostsTask')
450
+ MaintenanceTasks::Runner.run(name: "Maintenance::UpdatePostsTask")
248
451
  ```
249
452
 
250
453
  To run a Task that processes CSVs using the Runner, provide a Hash containing an
@@ -252,8 +455,18 @@ open IO object and a filename to `run`:
252
455
 
253
456
  ```ruby
254
457
  MaintenanceTasks::Runner.run(
255
- name: 'Maintenance::ImportPostsTask'
256
- csv_file: { io: File.open('path/to/my_csv.csv'), filename: 'my_csv.csv' }
458
+ name: "Maintenance::ImportPostsTask",
459
+ csv_file: { io: File.open("path/to/my_csv.csv"), filename: "my_csv.csv" }
460
+ )
461
+ ```
462
+
463
+ To run a Task that takes arguments using the Runner, provide a Hash containing
464
+ the set of arguments (`{ parameter_name: argument_value }`) to `run`:
465
+
466
+ ```ruby
467
+ MaintenanceTasks::Runner.run(
468
+ name: "Maintenance::ParamsTask",
469
+ arguments: { post_ids: "1,2,3" }
257
470
  )
258
471
  ```
259
472
 
@@ -266,8 +479,8 @@ a Task can be in:
266
479
  * **enqueued**: A Task that is waiting to be performed after a user has
267
480
  instructed it to run.
268
481
  * **running**: A Task that is currently being performed by a job worker.
269
- * **pausing**: A Task that was paused by a user, but needs to finish work
270
- before stopping.
482
+ * **pausing**: A Task that was paused by a user, but needs to finish work before
483
+ stopping.
271
484
  * **paused**: A Task that was paused by a user and is not performing. It can be
272
485
  resumed.
273
486
  * **interrupted**: A Task that has been momentarily interrupted by the job
@@ -341,9 +554,10 @@ you can define an error handler:
341
554
 
342
555
  ```ruby
343
556
  # config/initializers/maintenance_tasks.rb
557
+
344
558
  MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
345
559
  Bugsnag.notify(error) do |notification|
346
- notification.add_tab(:task, task_context)
560
+ notification.add_metadata(:task, task_context)
347
561
  end
348
562
  end
349
563
  ```
@@ -356,18 +570,18 @@ The error handler should be a lambda that accepts three arguments:
356
570
  * `task_name`: The name of the Task that errored
357
571
  * `started_at`: The time the Task started
358
572
  * `ended_at`: The time the Task errored
573
+
359
574
  Note that `task_context` may be empty if the Task produced an error before any
360
575
  context could be gathered (for example, if deserializing the job to process
361
576
  your Task failed).
362
- * `errored_element`: The element, if any, that was being processed when the
363
- Task raised an exception. If you would like to pass this object to your
364
- exception monitoring service, make sure you **sanitize the object** to avoid
365
- leaking sensitive data and **convert it to a format** that is compatible with
366
- your bug tracker. For example, Bugsnag only sends the id and class name of
367
- ActiveRecord objects in order to protect sensitive data. CSV rows, on the
368
- other hand, are converted to strings and passed raw to Bugsnag, so make sure
369
- to filter any personal data from these objects before adding them to a
370
- report.
577
+ * `errored_element`: The element, if any, that was being processed when the Task
578
+ raised an exception. If you would like to pass this object to your exception
579
+ monitoring service, make sure you **sanitize the object** to avoid leaking
580
+ sensitive data and **convert it to a format** that is compatible with your bug
581
+ tracker. For example, Bugsnag only sends the id and class name of Active
582
+ Record objects in order to protect sensitive data. CSV rows, on the other
583
+ hand, are converted to strings and passed raw to Bugsnag, so make sure to
584
+ filter any personal data from these objects before adding them to a report.
371
585
 
372
586
  #### Customizing the maintenance tasks module
373
587
 
@@ -376,7 +590,8 @@ tasks will be placed.
376
590
 
377
591
  ```ruby
378
592
  # config/initializers/maintenance_tasks.rb
379
- MaintenanceTasks.tasks_module = 'TaskModule'
593
+
594
+ MaintenanceTasks.tasks_module = "TaskModule"
380
595
  ```
381
596
 
382
597
  If no value is specified, it will default to `Maintenance`.
@@ -389,9 +604,11 @@ maintenance tasks in your application.
389
604
 
390
605
  ```ruby
391
606
  # config/initializers/maintenance_tasks.rb
607
+
392
608
  MaintenanceTasks.job = 'CustomTaskJob'
393
609
 
394
610
  # app/jobs/custom_task_job.rb
611
+
395
612
  class CustomTaskJob < MaintenanceTasks::TaskJob
396
613
  queue_as :low_priority
397
614
  end
@@ -399,8 +616,8 @@ end
399
616
 
400
617
  The Job class **must inherit** from `MaintenanceTasks::TaskJob`.
401
618
 
402
- Note that `retry_on` is not supported for custom Job
403
- classes, so failed jobs cannot be retried.
619
+ Note that `retry_on` is not supported for custom Job classes, so failed jobs
620
+ cannot be retried.
404
621
 
405
622
  #### Customizing the rate at which task progress gets updated
406
623
 
@@ -410,6 +627,7 @@ task progress gets persisted to the database. It can be a `Numeric` value or an
410
627
 
411
628
  ```ruby
412
629
  # config/initializers/maintenance_tasks.rb
630
+
413
631
  MaintenanceTasks.ticker_delay = 2.seconds
414
632
  ```
415
633
 
@@ -424,6 +642,7 @@ key, as specified in your application's `config/storage.yml`:
424
642
 
425
643
  ```yaml
426
644
  # config/storage.yml
645
+
427
646
  user_data:
428
647
  service: GCS
429
648
  credentials: <%= Rails.root.join("path/to/user/data/keyfile.json") %>
@@ -439,23 +658,53 @@ internal:
439
658
 
440
659
  ```ruby
441
660
  # config/initializers/maintenance_tasks.rb
661
+
442
662
  MaintenanceTasks.active_storage_service = :internal
443
663
  ```
444
664
 
445
665
  There is no need to configure this option if your application uses only one
446
666
  storage service per environment.
447
667
 
668
+ #### Customizing the backtrace cleaner
669
+
670
+ `MaintenanceTasks.backtrace_cleaner` can be configured to specify a backtrace
671
+ cleaner to use when a Task errors and the backtrace is cleaned and persisted.
672
+ An `ActiveSupport::BacktraceCleaner` should be used.
673
+
674
+ ```ruby
675
+ # config/initializers/maintenance_tasks.rb
676
+
677
+ cleaner = ActiveSupport::BacktraceCleaner.new
678
+ cleaner.add_silencer { |line| line =~ /ignore_this_dir/ }
679
+
680
+ MaintenanceTasks.backtrace_cleaner = cleaner
681
+ ```
682
+
683
+ If none is specified, the default `Rails.backtrace_cleaner` will be used to
684
+ clean backtraces.
685
+
448
686
  ## Upgrading
449
687
 
450
688
  Use bundler to check for and upgrade to newer versions. After installing a new
451
689
  version, re-run the install command:
452
690
 
453
691
  ```bash
454
- $ rails generate maintenance_tasks:install
692
+ $ bin/rails generate maintenance_tasks:install
455
693
  ```
456
694
 
457
695
  This ensures that new migrations are installed and run as well.
458
696
 
697
+ **What if I've deleted my previous Maintenance Task migrations?**
698
+
699
+ The install command will attempt to reinstall these old migrations and migrating
700
+ the database will cause problems. Use `bin/rails
701
+ maintenance_tasks:install:migrations` to copy the gem's migrations to your
702
+ `db/migrate` folder. Check the release notes to see if any new migrations were
703
+ added since your last gem upgrade. Ensure that these are kept, but remove any
704
+ migrations that already ran.
705
+
706
+ Run the migrations using `bin/rails db:migrate`.
707
+
459
708
  ## Contributing
460
709
 
461
710
  Would you like to report an issue or contribute with code? We accept issues and
@@ -464,12 +713,6 @@ pull requests. You can find the contribution guidelines on
464
713
 
465
714
  [contributing]: https://github.com/Shopify/maintenance_tasks/blob/main/.github/CONTRIBUTING.md
466
715
 
467
- ### Dependabot updates
468
-
469
- Whenever Dependabot creates a PR for a gem bump, check out the branch locally
470
- and run `bin/update-gemfile <gem>` to ensure all the gemfiles have the gem
471
- updated consistently.
472
-
473
716
  ## Releasing new versions
474
717
 
475
718
  Updates should be added to the latest draft release on GitHub as Pull Requests
@@ -478,7 +721,7 @@ are merged.
478
721
  Once a release is ready, follow these steps:
479
722
 
480
723
  * Update `spec.version` in `maintenance_tasks.gemspec`.
481
- * Run `bin/gemfile-update install` to bump the version in all the lockfiles.
724
+ * Run `bundle install` to bump the `Gemfile.lock` version of the gem.
482
725
  * Open a PR and merge on approval.
483
726
  * Deploy via [Shipit][shipit] and see the new version on
484
727
  <https://rubygems.org/gems/maintenance_tasks>.
@@ -24,14 +24,19 @@ 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
- csv_file: params[:csv_file]
30
+ csv_file: params[:csv_file],
31
+ arguments: params.fetch(:task_arguments, {}).permit!.to_h,
32
+ &block
31
33
  )
32
34
  redirect_to(task_path(task))
33
35
  rescue ActiveRecord::RecordInvalid => error
34
36
  redirect_to(task_path(error.record.task_name), alert: error.message)
37
+ rescue ActiveRecord::ValueTooLong => error
38
+ task_name = params.fetch(:id)
39
+ redirect_to(task_path(task_name), alert: error.message)
35
40
  rescue Runner::EnqueuingError => error
36
41
  redirect_to(task_path(error.run.task_name), alert: error.message)
37
42
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MaintenanceTasks
3
4
  # Module for common view helpers.
4
5
  #
@@ -63,20 +63,6 @@ module MaintenanceTasks
63
63
  tag.span(status.capitalize, class: ["tag"] + STATUS_COLOURS.fetch(status))
64
64
  end
65
65
 
66
- # Returns the distance between now and the Run's expected completion time,
67
- # if the Run has an estimated_completion_time.
68
- #
69
- # @param run [MaintenanceTasks::Run] the Run for which the estimated time to
70
- # completion is being calculated.
71
- # return [String, nil] the distance in words, or nil if the Run has no
72
- # estimated completion time.
73
- def estimated_time_to_completion(run)
74
- estimated_completion_time = run.estimated_completion_time
75
- if estimated_completion_time.present?
76
- time_ago_in_words(estimated_completion_time)
77
- end
78
- end
79
-
80
66
  # Reports the approximate elapsed time a Run has been processed so far based
81
67
  # on the Run's time running attribute.
82
68
  #
@@ -114,5 +100,24 @@ module MaintenanceTasks
114
100
  only_path: true
115
101
  )
116
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
117
122
  end
118
123
  end