maintenance_tasks 2.10.1 → 2.13.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +160 -45
  3. data/app/controllers/maintenance_tasks/application_controller.rb +15 -5
  4. data/app/helpers/maintenance_tasks/application_helper.rb +1 -1
  5. data/app/helpers/maintenance_tasks/tasks_helper.rb +28 -11
  6. data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +33 -4
  7. data/app/models/maintenance_tasks/run.rb +103 -71
  8. data/app/models/maintenance_tasks/task.rb +42 -0
  9. data/app/models/maintenance_tasks/task_data_index.rb +2 -1
  10. data/app/views/layouts/maintenance_tasks/_navbar.html.erb +1 -1
  11. data/app/views/layouts/maintenance_tasks/application.html.erb +35 -4
  12. data/app/views/maintenance_tasks/runs/_arguments.html.erb +1 -1
  13. data/app/views/maintenance_tasks/runs/_run.html.erb +14 -8
  14. data/app/views/maintenance_tasks/runs/_serializable.html.erb +15 -18
  15. data/app/views/maintenance_tasks/tasks/_task.html.erb +5 -5
  16. data/app/views/maintenance_tasks/tasks/index.html.erb +6 -4
  17. data/app/views/maintenance_tasks/tasks/show.html.erb +16 -12
  18. data/db/migrate/20201211151756_create_maintenance_tasks_runs.rb +10 -2
  19. data/db/migrate/20210219212931_change_cursor_to_string.rb +1 -1
  20. data/db/migrate/20210225152418_remove_index_on_task_name.rb +1 -1
  21. data/db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb +1 -1
  22. data/db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb +1 -1
  23. data/db/migrate/20220706101937_change_runs_tick_columns_to_bigints.rb +1 -1
  24. data/db/migrate/20220713131925_add_index_on_task_name_and_status_to_runs.rb +1 -1
  25. data/db/migrate/20230622035229_add_metadata_to_runs.rb +1 -1
  26. data/lib/maintenance_tasks/engine.rb +4 -0
  27. data/lib/maintenance_tasks.rb +50 -12
  28. metadata +13 -15
  29. data/lib/patches/active_record_batch_enumerator.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b660fdfcef27420bf9b08ddc50c9cfac5ff0977c4435b54ecd2b468a3e4fde8
4
- data.tar.gz: 30bac0da63839406dc623d07c85904b7a727d3af5c177736cbd72ea828ca41c9
3
+ metadata.gz: 8402f1f4b8919e892a6334373ad397d49b5c6c78893f2f107b990b90653435d1
4
+ data.tar.gz: 1459d5b92233a1196aeb83c80bec9da525f04e975a50f6a415cdcd8dc647bd59
5
5
  SHA512:
6
- metadata.gz: 071dafa23a89bd949e40f10ef199ae892d5ae81ca2ad1e15e6c7de0260dafbd55f735d67c310f5de7ef499991982eb3bad69fce4024f8f8761e4357ac3fdc42b
7
- data.tar.gz: 77265626bdeafaa6486a3843846d519168be0712399ebe3cd7a4ecbb42f5888f208f566346d6d628643ee0dcf06ff1c4b914f2ab3ef9ec73f60b9444ef3f05c0
6
+ metadata.gz: 3dff16b85a650f4d73cf79df20267d1278b1181f2ba6717742581909ebfd39145dfdd9b9cf1453edaa785474fb6f7dda87af2bad534e6398bf010945b02f2da3
7
+ data.tar.gz: c7b75c1edc684fad66c5080ee3642fe469038e1f4ab2329ead0fd3c65e5f0ca2a940250dc988658dce672e2de0d056741b9a317310afa23165ba27794722ce1d
data/README.md CHANGED
@@ -74,9 +74,11 @@ The generator creates and runs a migration to add the necessary table to your
74
74
  database. It also mounts Maintenance Tasks in your `config/routes.rb`. By
75
75
  default the web UI can be accessed in the new `/maintenance_tasks` path.
76
76
 
77
- In case you use an exception reporting service (e.g. Bugsnag) you might want to
78
- define an error handler. See [Customizing the error
79
- handler](#customizing-the-error-handler) for more information.
77
+ This gem uses the [Rails Error Reporter][rails-error-reporting] to report errors.
78
+ If you are using a bug tracking service you may want to subscribe to the
79
+ reporter. See [Reporting Errors](#reporting-errors) for more information.
80
+
81
+ [rails-error-reporting]: https://guides.rubyonrails.org/error_reporting.html
80
82
 
81
83
  ### Active Job Dependency
82
84
 
@@ -114,12 +116,12 @@ The typical Maintenance Tasks workflow is as follows:
114
116
  1. [Generate a class describing the Task](#creating-a-task) and the work to be
115
117
  done.
116
118
  2. Run the Task
117
- - either by [using the included web UI](#running-a-task-from-the-web-ui),
118
- - or by [using the command line](#running-a-task-from-the-command-line),
119
- - or by [using Ruby](#running-a-task-from-ruby).
119
+ - either by [using the included web UI](#running-a-task-from-the-web-ui),
120
+ - or by [using the command line](#running-a-task-from-the-command-line),
121
+ - or by [using Ruby](#running-a-task-from-ruby).
120
122
  3. [Monitor the Task](#monitoring-your-tasks-status)
121
- - either by using the included web UI,
122
- - or by manually checking your task’s run’s status in your database.
123
+ - either by using the included web UI,
124
+ - or by manually checking your task’s run’s status in your database.
123
125
  4. Optionally, delete the Task code if you no longer need it.
124
126
 
125
127
  ### Creating a Task
@@ -168,7 +170,8 @@ end
168
170
  When processing records from an Active Record Relation, records are fetched in
169
171
  batches internally, and then each record is passed to the `#process` method.
170
172
  Maintenance Tasks will query the database to fetch records in batches of 100 by
171
- default, but the batch size can be modified using the `collection_batch_size` macro:
173
+ default, but the batch size can be modified using the `collection_batch_size`
174
+ macro:
172
175
 
173
176
  ```ruby
174
177
  # app/tasks/maintenance/update_posts_task.rb
@@ -502,13 +505,44 @@ set of values will be used to populate a dropdown in the user interface. The
502
505
  following types are supported:
503
506
 
504
507
  * Arrays
505
- * Procs and lambdas that optionally accept the Task instance, and return an Array.
506
- * Callable objects that receive one argument, the Task instance, and return an Array.
508
+ * Procs and lambdas that optionally accept the Task instance, and return an
509
+ Array.
510
+ * Callable objects that receive one argument, the Task instance, and return an
511
+ Array.
507
512
  * Methods that return an Array, called on the Task instance.
508
513
 
509
514
  For enumerables that don't match the supported types, a text field will be
510
515
  rendered instead.
511
516
 
517
+ ### Masking Task Parameters
518
+
519
+ Task attributes can be masked in the UI by adding `mask_attribute` class method
520
+ in the task class. This will replace the value in the arguments list with
521
+ `[FILTERED]` in the UI.
522
+
523
+ ```ruby
524
+ # app/tasks/maintenance/sensitive_params_task.rb
525
+
526
+ module Maintenance
527
+ class SensitiveParamsTask < MaintenanceTasks::Task
528
+ attribute :sensitive_content, :string
529
+
530
+ mask_attribute :sensitive_content
531
+ end
532
+ end
533
+ ```
534
+
535
+ If you have any filtered parameters in the global [Rails parameter
536
+ filter][rails-parameter-filter], they will be automatically taken into account
537
+ when masking the parameters, which means that you can mask parameters across all
538
+ tasks by adding them to the global rails parameters filter.
539
+
540
+ [rails-parameter-filter]:https://guides.rubyonrails.org/configuring.html#config-filter-parameters
541
+
542
+ ```ruby
543
+ Rails.application.config.filter_parameters += %i[token]
544
+ ```
545
+
512
546
  ### Custom cursor columns to improve performance
513
547
 
514
548
  The [job-iteration gem][job-iteration], on which this gem depends, adds an
@@ -899,10 +933,10 @@ a Task can be in:
899
933
 
900
934
  The Maintenance Tasks engine uses Rails sessions for flash messages and storing
901
935
  the CSRF token. For the engine to work in an API-only Rails application, you
902
- need to add a [session middleware][] and the `ActionDispatch::Flash`
903
- middleware. The engine also defines a strict [Content Security Policy][], make
904
- sure to include `ActionDispatch::ContentSecurityPolicy::Middleware` in your
905
- app's middleware stack to ensure the CSP is delivered to the user's browser.
936
+ need to add a [session middleware][] and the `ActionDispatch::Flash` middleware.
937
+ The engine also defines a strict [Content Security Policy][], make sure to
938
+ include `ActionDispatch::ContentSecurityPolicy::Middleware` in your app's
939
+ middleware stack to ensure the CSP is delivered to the user's browser.
906
940
 
907
941
  [session middleware]: https://guides.rubyonrails.org/api_app.html#using-session-middlewares
908
942
  [Content Security Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
@@ -926,8 +960,8 @@ module YourApplication
926
960
  end
927
961
  ```
928
962
 
929
- You can read more in the [Using Rails for API-only Applications][rails api] Rails
930
- guide.
963
+ You can read more in the [Using Rails for API-only Applications][rails api]
964
+ Rails guide.
931
965
 
932
966
  [rails api]: https://guides.rubyonrails.org/api_app.html
933
967
 
@@ -944,9 +978,9 @@ infrastructure or code changes.
944
978
  This means a Task can safely be interrupted, re-enqueued and resumed without any
945
979
  intervention at the end of an iteration, after the `process` method returns.
946
980
 
947
- By default, a running Task will be interrupted after running for more 5 minutes.
948
- This is [configured in the `job-iteration` gem][max-job-runtime] and can be
949
- tweaked in an initializer if necessary.
981
+ By default, a running Task will be interrupted after running for more than 5
982
+ minutes. This is [configured in the `job-iteration` gem][max-job-runtime] and
983
+ can be tweaked in an initializer if necessary.
950
984
 
951
985
  [max-job-runtime]: https://github.com/Shopify/job-iteration/blob/-/guides/best-practices.md#max-job-runtime
952
986
 
@@ -988,44 +1022,89 @@ If you are stuck in `pausing` and wish to preserve your tasks's position
988
1022
  There are a few configurable options for the gem. Custom configurations should
989
1023
  be placed in a `maintenance_tasks.rb` initializer.
990
1024
 
991
- #### Customizing the error handler
1025
+ #### Reporting errors
992
1026
 
993
1027
  Exceptions raised while a Task is performing are rescued and information about
994
1028
  the error is persisted and visible in the UI.
995
1029
 
996
- If you want to integrate with an exception monitoring service (e.g. Bugsnag),
997
- you can define an error handler:
1030
+ Errors are also sent to `Rails.error.report`, which can be configured by
1031
+ your application. See the [Error Reporting in Rails
1032
+ Applications][rails-error-reporting] guide for more details.
1033
+
1034
+ Reports to the error reporter will contain the following data:
1035
+
1036
+ * `error`: The exception that was raised.
1037
+ * `context`: A hash with additional information about the Task and the error:
1038
+ * `task_name`: The name of the Task that errored
1039
+ * `started_at`: The time the Task started
1040
+ * `ended_at`: The time the Task errored
1041
+ * `run_id`: The id of the errored Task run
1042
+ * `tick_count`: The tick count at the time of the error
1043
+ * `errored_element`: The element, if any, that was being processed when the
1044
+ * `source`: This will be `maintenance-tasks`
1045
+ * `handled`: the value of `MaintenanceTasks.report_errors_as_handled` (default `true`, see below)
1046
+
1047
+ Note that `context` may be empty if the Task produced an error before any
1048
+ context could be gathered (for example, if deserializing the job to process your
1049
+ Task failed).
1050
+
1051
+ Here's an example custom subscriber to the Rails error reporter for integrating
1052
+ with an exception monitoring service (Bugsnag):
998
1053
 
999
1054
  ```ruby
1000
1055
  # config/initializers/maintenance_tasks.rb
1056
+ MaintenanceTasks.report_errors_as_handled = false
1057
+
1058
+ class MaintenanceTasksErrorSubscriber
1059
+ def report(error, handled:, severity:, context:, source: nil)
1060
+ return unless source == "maintenance-tasks"
1001
1061
 
1002
- MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
1003
- Bugsnag.notify(error) do |notification|
1004
- notification.add_metadata(:task, task_context)
1062
+ unless handled
1063
+ Bugsnag.notify(error) do |notification|
1064
+ notification.add_metadata(:task, context)
1065
+ end
1066
+ else
1067
+ Rails.logger.info(error)
1068
+ end
1005
1069
  end
1006
1070
  end
1071
+
1072
+ Rails.error.subscribe(MaintenanceTasksErrorSubscriber.new)
1007
1073
  ```
1008
1074
 
1009
- The error handler should be a lambda that accepts three arguments:
1075
+ `MaintenanceTasks.report_errors_as_handled` determines the value for `handled` in this example.
1076
+ By default (for backwards compatibility) this is `true`.
1077
+ Setting this to `false` provides more accurate error reporting as it allows to distinguish between
1078
+ expected (e.g., via `report_on`) and unexpected errors in error subscribers.
1079
+ `false` will be the default in v3.0.
1010
1080
 
1011
- * `error`: The exception that was raised.
1012
- * `task_context`: A hash with additional information about the Task and the
1013
- error:
1014
- * `task_name`: The name of the Task that errored
1015
- * `started_at`: The time the Task started
1016
- * `ended_at`: The time the Task errored
1017
-
1018
- Note that `task_context` may be empty if the Task produced an error before any
1019
- context could be gathered (for example, if deserializing the job to process
1020
- your Task failed).
1021
- * `errored_element`: The element, if any, that was being processed when the Task
1022
- raised an exception. If you would like to pass this object to your exception
1023
- monitoring service, make sure you **sanitize the object** to avoid leaking
1024
- sensitive data and **convert it to a format** that is compatible with your bug
1025
- tracker. For example, Bugsnag only sends the id and class name of Active
1026
- Record objects in order to protect sensitive data. CSV rows, on the other
1027
- hand, are converted to strings and passed raw to Bugsnag, so make sure to
1028
- filter any personal data from these objects before adding them to a report.
1081
+ #### Reporting errors during iteration
1082
+
1083
+ By default, errors raised during task iteration will be raised to the
1084
+ application and iteration will stop. However, you may want to handle some errors
1085
+ and continue iteration. `MaintenanceTasks::Task.report_on` can be used to rescue
1086
+ certain exceptions and report them to the Rails error reporter. Any keyword
1087
+ arguments are passed to
1088
+ [ActiveSupport::ErrorReporter#report][as-error-reporter-report]:
1089
+
1090
+ [as-error-reporter-report]: https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html#method-i-report
1091
+
1092
+ ```ruby
1093
+ class MyTask < MaintenanceTasks::Task
1094
+ report_on(MyException, OtherException, severity: :info, context: {task_name: "my_task"})
1095
+ end
1096
+ ```
1097
+
1098
+ `MaintenanceTasks::Task` also includes `ActiveSupport::Rescuable` which you can
1099
+ use to implement custom error handling.
1100
+
1101
+ ```ruby
1102
+ class MyTask < MaintenanceTasks::Task
1103
+ rescue_from(MyException) do |exception|
1104
+ handle(exception)
1105
+ end
1106
+ end
1107
+ ```
1029
1108
 
1030
1109
  #### Customizing the maintenance tasks module
1031
1110
 
@@ -1188,6 +1267,42 @@ The value for `MaintenanceTasks.stuck_task_duration` must be an
1188
1267
  `ActiveSupport::Duration`. If no value is specified, it will default to 5
1189
1268
  minutes.
1190
1269
 
1270
+ #### Configure status reload frequency
1271
+
1272
+ `MaintenanceTasks.status_reload_frequency` can be configured to specify how often
1273
+ the run status should be reloaded during iteration. By default, the status is
1274
+ reloaded every second, but this can be increased to improve performance. Note that increasing the reload interval impacts how quickly
1275
+ your task will stop if it is paused or interrupted.
1276
+
1277
+ ```ruby
1278
+ # config/initializers/maintenance_tasks.rb
1279
+ MaintenanceTasks.status_reload_frequency = 10.seconds # Reload status every 10 seconds
1280
+ ```
1281
+
1282
+ Individual tasks can also override this setting using the `reload_status_every` method:
1283
+
1284
+ ```ruby
1285
+ # app/tasks/maintenance/update_posts_task.rb
1286
+
1287
+ module Maintenance
1288
+ class UpdatePostsTask < MaintenanceTasks::Task
1289
+ # Reload status every 5 seconds instead of the global default
1290
+ reload_status_every(5.seconds)
1291
+
1292
+ def collection
1293
+ Post.all
1294
+ end
1295
+
1296
+ def process(post)
1297
+ post.update!(content: "New content!")
1298
+ end
1299
+ end
1300
+ end
1301
+ ```
1302
+
1303
+ This optimization can significantly reduce database queries, especially for short iterations.
1304
+ This is especially useful if the task doesn't need to check for cancellation/pausing very often.
1305
+
1191
1306
  #### Metadata
1192
1307
 
1193
1308
  `MaintenanceTasks.metadata` can be configured to specify a proc from which to
@@ -8,16 +8,26 @@ module MaintenanceTasks
8
8
  BULMA_CDN = "https://cdn.jsdelivr.net"
9
9
 
10
10
  content_security_policy do |policy|
11
- policy.style_src(
11
+ policy.style_src_elem(
12
12
  BULMA_CDN,
13
- # ruby syntax highlighting
14
- "'sha256-y9V0na/WU44EUNI/HDP7kZ7mfEci4PAOIjYOOan6JMA='",
13
+ # <style> tag in app/views/layouts/maintenance_tasks/application.html.erb
14
+ "'sha256-WHHDQLdkleXnAN5zs0GDXC5ls41CHUaVsJtVpaNx+EM='",
15
15
  )
16
- policy.script_src(
17
- # page refresh script
16
+ capybara_lockstep_scripts = [
17
+ "'sha256-1AoN3ZtJC5OvqkMgrYvhZjp4kI8QjJjO7TAyKYiDw+U='",
18
+ "'sha256-QVSzZi6ZsX/cu4h+hIs1iVivG1BxUmJggiEsGDIXBG0='", # with debug on
19
+ ] if defined?(Capybara::Lockstep)
20
+ policy.script_src_elem(
21
+ # <script> tag in app/views/layouts/maintenance_tasks/application.html.erb
18
22
  "'sha256-NiHKryHWudRC2IteTqmY9v1VkaDUA/5jhgXkMTkgo2w='",
23
+ # <script> tag for capybara-lockstep
24
+ *capybara_lockstep_scripts,
19
25
  )
26
+
27
+ policy.require_trusted_types_for # disable because we use new DOMParser().parseFromString
20
28
  policy.frame_ancestors(:self)
29
+ policy.connect_src(:self)
30
+ policy.form_action(:self)
21
31
  end
22
32
 
23
33
  protect_from_forgery with: :exception
@@ -14,7 +14,7 @@ module MaintenanceTasks
14
14
  # @param datetime [ActiveSupport::TimeWithZone] the time to be presented.
15
15
  # @return [String] the HTML to render with the relative datetime in words.
16
16
  def time_ago(datetime)
17
- time_tag(datetime, title: datetime.utc.iso8601, class: "is-clickable") do
17
+ time_tag(datetime, title: datetime.utc, class: "is-clickable") do
18
18
  time_ago_in_words(datetime) + " ago"
19
19
  end
20
20
  end
@@ -47,7 +47,7 @@ module MaintenanceTasks
47
47
  progress_bar = tag.progress(
48
48
  value: progress.value,
49
49
  max: progress.max,
50
- class: ["progress"] + STATUS_COLOURS.fetch(run.status),
50
+ class: ["progress", "mt-4"] + STATUS_COLOURS.fetch(run.status),
51
51
  )
52
52
  progress_text = tag.p(tag.i(progress.text))
53
53
  tag.div(progress_bar + progress_text, class: "block")
@@ -60,7 +60,10 @@ module MaintenanceTasks
60
60
  # @return [String] the span element containing the status, with the
61
61
  # appropriate tag class attached.
62
62
  def status_tag(status)
63
- tag.span(status.capitalize, class: ["tag"] + STATUS_COLOURS.fetch(status))
63
+ tag.span(
64
+ status.capitalize,
65
+ class: ["tag", "has-text-weight-medium", "pr-2", "mr-4"] + STATUS_COLOURS.fetch(status),
66
+ )
64
67
  end
65
68
 
66
69
  # Reports the approximate elapsed time a Run has been processed so far based
@@ -101,7 +104,7 @@ module MaintenanceTasks
101
104
  )
102
105
  end
103
106
 
104
- # Resolves values covered by the inclusion validator for a task attribute.
107
+ # Resolves values covered by the inclusion validator for a Task attribute.
105
108
  # Supported option types:
106
109
  # - Arrays
107
110
  # - Procs and lambdas that optionally accept the Task instance, and return an Array.
@@ -112,7 +115,7 @@ module MaintenanceTasks
112
115
  #
113
116
  # Returned values are used to populate a dropdown list of options.
114
117
  #
115
- # @param task_class [Class<Task>] The task class for which the value needs to be resolved.
118
+ # @param task [Task] The Task for which the value needs to be resolved.
116
119
  # @param parameter_name [String] The parameter name.
117
120
  #
118
121
  # @return [Array] value of the resolved inclusion option.
@@ -149,24 +152,27 @@ module MaintenanceTasks
149
152
  # If the parameter has a `validates_inclusion_of` validator, return a dropdown list of options instead.
150
153
  def parameter_field(form_builder, parameter_name)
151
154
  inclusion_values = resolve_inclusion_value(form_builder.object, parameter_name)
152
- return form_builder.select(parameter_name, inclusion_values, prompt: "Select a value") if inclusion_values
155
+ if inclusion_values
156
+ return tag.div(form_builder.select(parameter_name, inclusion_values, prompt: "Select a value"), class: "select")
157
+ end
153
158
 
154
159
  case form_builder.object.class.attribute_types[parameter_name]
155
160
  when ActiveModel::Type::Integer
156
- form_builder.number_field(parameter_name)
161
+ form_builder.number_field(parameter_name, class: "input")
157
162
  when ActiveModel::Type::Decimal, ActiveModel::Type::Float
158
- form_builder.number_field(parameter_name, { step: "any" })
163
+ form_builder.number_field(parameter_name, { step: "any", class: "input" })
159
164
  when ActiveModel::Type::DateTime
160
- form_builder.datetime_field(parameter_name) + datetime_field_help_text
165
+ form_builder.datetime_field(parameter_name, class: "input") + datetime_field_help_text
161
166
  when ActiveModel::Type::Date
162
- form_builder.date_field(parameter_name)
167
+ form_builder.date_field(parameter_name, class: "input")
163
168
  when ActiveModel::Type::Time
164
- form_builder.time_field(parameter_name)
169
+ form_builder.time_field(parameter_name, class: "input")
165
170
  when ActiveModel::Type::Boolean
166
- form_builder.check_box(parameter_name)
171
+ form_builder.check_box(parameter_name, class: "checkbox")
167
172
  else
168
173
  form_builder.text_area(parameter_name, class: "textarea")
169
174
  end
175
+ .then { |input| tag.div(input, class: "control") }
170
176
  end
171
177
 
172
178
  # Return helper text for the datetime-local form field.
@@ -182,5 +188,16 @@ module MaintenanceTasks
182
188
  class: "content is-small",
183
189
  )
184
190
  end
191
+
192
+ # Checks if an attribute is required for a given Task.
193
+ #
194
+ # @param task [MaintenanceTasks::TaskDataShow] The TaskDataShow instance.
195
+ # @param parameter_name [Symbol] The name of the attribute to check.
196
+ # @return [Boolean] Whether the attribute is required.
197
+ def attribute_required?(task, parameter_name)
198
+ task.class.validators_on(parameter_name).any? do |validator|
199
+ validator.kind == :presence
200
+ end
201
+ end
185
202
  end
186
203
  end
@@ -101,7 +101,8 @@ module MaintenanceTasks
101
101
  throw(:abort, :skip_complete_callbacks) if @run.stopping?
102
102
  task_iteration(input)
103
103
  @ticker.tick
104
- @run.reload_status
104
+
105
+ reload_run_status
105
106
  end
106
107
 
107
108
  def task_iteration(input)
@@ -112,7 +113,7 @@ module MaintenanceTasks
112
113
  end
113
114
  rescue => error
114
115
  @errored_element = input
115
- raise error
116
+ raise error unless @task.rescue_with_handler(error)
116
117
  end
117
118
 
118
119
  def before_perform
@@ -127,6 +128,8 @@ module MaintenanceTasks
127
128
  @ticker = Ticker.new(MaintenanceTasks.ticker_delay) do |ticks, duration|
128
129
  @run.persist_progress(ticks, duration)
129
130
  end
131
+
132
+ @last_status_reload = nil
130
133
  end
131
134
 
132
135
  def on_start
@@ -181,11 +184,37 @@ module MaintenanceTasks
181
184
  task_name: @run.task_name,
182
185
  started_at: @run.started_at,
183
186
  ended_at: @run.ended_at,
187
+ run_id: @run.id,
188
+ tick_count: @run.tick_count,
184
189
  }
185
190
  end
186
- errored_element = @errored_element if defined?(@errored_element)
191
+ task_context[:errored_element] = @errored_element if defined?(@errored_element)
187
192
  ensure
188
- MaintenanceTasks.error_handler.call(error, task_context, errored_element)
193
+ if MaintenanceTasks.instance_variable_get(:@error_handler)
194
+ errored_element = task_context.delete(:errored_element)
195
+ MaintenanceTasks.error_handler.call(error, task_context.except(:run_id, :tick_count), errored_element)
196
+ else
197
+ Rails.error.report(
198
+ error,
199
+ handled: MaintenanceTasks.report_errors_as_handled,
200
+ context: task_context,
201
+ source: "maintenance-tasks",
202
+ )
203
+ end
204
+ end
205
+
206
+ def reload_run_status
207
+ return unless should_reload_status?
208
+
209
+ @run.reload_status
210
+ @last_status_reload = Time.now
211
+ end
212
+
213
+ def should_reload_status?
214
+ return true if @last_status_reload.nil?
215
+
216
+ time_since_last_reload = Time.now - @last_status_reload
217
+ time_since_last_reload >= @task.status_reload_frequency
189
218
  end
190
219
  end
191
220
  end