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.
- checksums.yaml +4 -4
- data/README.md +160 -45
- data/app/controllers/maintenance_tasks/application_controller.rb +15 -5
- data/app/helpers/maintenance_tasks/application_helper.rb +1 -1
- data/app/helpers/maintenance_tasks/tasks_helper.rb +28 -11
- data/app/jobs/concerns/maintenance_tasks/task_job_concern.rb +33 -4
- data/app/models/maintenance_tasks/run.rb +103 -71
- data/app/models/maintenance_tasks/task.rb +42 -0
- data/app/models/maintenance_tasks/task_data_index.rb +2 -1
- data/app/views/layouts/maintenance_tasks/_navbar.html.erb +1 -1
- data/app/views/layouts/maintenance_tasks/application.html.erb +35 -4
- data/app/views/maintenance_tasks/runs/_arguments.html.erb +1 -1
- data/app/views/maintenance_tasks/runs/_run.html.erb +14 -8
- data/app/views/maintenance_tasks/runs/_serializable.html.erb +15 -18
- data/app/views/maintenance_tasks/tasks/_task.html.erb +5 -5
- data/app/views/maintenance_tasks/tasks/index.html.erb +6 -4
- data/app/views/maintenance_tasks/tasks/show.html.erb +16 -12
- data/db/migrate/20201211151756_create_maintenance_tasks_runs.rb +10 -2
- data/db/migrate/20210219212931_change_cursor_to_string.rb +1 -1
- data/db/migrate/20210225152418_remove_index_on_task_name.rb +1 -1
- data/db/migrate/20210517131953_add_arguments_to_maintenance_tasks_runs.rb +1 -1
- data/db/migrate/20211210152329_add_lock_version_to_maintenance_tasks_runs.rb +1 -1
- data/db/migrate/20220706101937_change_runs_tick_columns_to_bigints.rb +1 -1
- data/db/migrate/20220713131925_add_index_on_task_name_and_status_to_runs.rb +1 -1
- data/db/migrate/20230622035229_add_metadata_to_runs.rb +1 -1
- data/lib/maintenance_tasks/engine.rb +4 -0
- data/lib/maintenance_tasks.rb +50 -12
- metadata +13 -15
- data/lib/patches/active_record_batch_enumerator.rb +0 -24
|
@@ -33,11 +33,7 @@ module MaintenanceTasks
|
|
|
33
33
|
]
|
|
34
34
|
COMPLETED_STATUSES = [:succeeded, :errored, :cancelled]
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
enum :status, STATUSES.to_h { |status| [status, status.to_s] }
|
|
38
|
-
else
|
|
39
|
-
enum status: STATUSES.to_h { |status| [status, status.to_s] }
|
|
40
|
-
end
|
|
36
|
+
enum :status, STATUSES.to_h { |status| [status, status.to_s] }
|
|
41
37
|
|
|
42
38
|
after_save :instrument_status_change
|
|
43
39
|
|
|
@@ -48,15 +44,9 @@ module MaintenanceTasks
|
|
|
48
44
|
|
|
49
45
|
attr_readonly :task_name
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
serialize :metadata, coder: JSON
|
|
55
|
-
else
|
|
56
|
-
serialize :backtrace
|
|
57
|
-
serialize :arguments, JSON
|
|
58
|
-
serialize :metadata, JSON
|
|
59
|
-
end
|
|
47
|
+
serialize :backtrace, coder: YAML
|
|
48
|
+
serialize :arguments, coder: JSON
|
|
49
|
+
serialize :metadata, coder: JSON
|
|
60
50
|
|
|
61
51
|
scope :active, -> { where(status: ACTIVE_STATUSES) }
|
|
62
52
|
scope :completed, -> { where(status: COMPLETED_STATUSES) }
|
|
@@ -83,11 +73,10 @@ module MaintenanceTasks
|
|
|
83
73
|
# Rescues and retries status transition if an ActiveRecord::StaleObjectError
|
|
84
74
|
# is encountered.
|
|
85
75
|
def enqueued!
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
retry
|
|
76
|
+
with_stale_object_retry do
|
|
77
|
+
status_will_change!
|
|
78
|
+
super
|
|
79
|
+
end
|
|
91
80
|
end
|
|
92
81
|
|
|
93
82
|
CALLBACKS_TRANSITION = {
|
|
@@ -96,23 +85,39 @@ module MaintenanceTasks
|
|
|
96
85
|
paused: :pause,
|
|
97
86
|
succeeded: :complete,
|
|
98
87
|
}.transform_keys(&:to_s)
|
|
99
|
-
|
|
88
|
+
|
|
89
|
+
DELAYS_PER_ATTEMPT = [0.1, 0.2, 0.4, 0.8, 1.6]
|
|
90
|
+
MAX_RETRIES = DELAYS_PER_ATTEMPT.size
|
|
91
|
+
|
|
92
|
+
private_constant :CALLBACKS_TRANSITION, :DELAYS_PER_ATTEMPT, :MAX_RETRIES
|
|
100
93
|
|
|
101
94
|
# Saves the run, persisting the transition of its status, and all other
|
|
102
95
|
# changes to the object.
|
|
103
96
|
def persist_transition
|
|
104
|
-
|
|
97
|
+
retry_count = 0
|
|
98
|
+
begin
|
|
99
|
+
save!
|
|
100
|
+
rescue ActiveRecord::StaleObjectError
|
|
101
|
+
if retry_count < MAX_RETRIES
|
|
102
|
+
sleep(DELAYS_PER_ATTEMPT[retry_count])
|
|
103
|
+
retry_count += 1
|
|
104
|
+
|
|
105
|
+
success = succeeded?
|
|
106
|
+
reload_status
|
|
107
|
+
if success
|
|
108
|
+
self.status = :succeeded
|
|
109
|
+
else
|
|
110
|
+
job_shutdown
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
retry
|
|
114
|
+
else
|
|
115
|
+
raise
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
105
119
|
callback = CALLBACKS_TRANSITION[status]
|
|
106
120
|
run_task_callbacks(callback) if callback
|
|
107
|
-
rescue ActiveRecord::StaleObjectError
|
|
108
|
-
success = succeeded?
|
|
109
|
-
reload_status
|
|
110
|
-
if success
|
|
111
|
-
self.status = :succeeded
|
|
112
|
-
else
|
|
113
|
-
job_shutdown
|
|
114
|
-
end
|
|
115
|
-
retry
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
# Increments +tick_count+ by +number_of_ticks+ and +time_running+ by
|
|
@@ -130,29 +135,23 @@ module MaintenanceTasks
|
|
|
130
135
|
time_running: duration,
|
|
131
136
|
touch: true,
|
|
132
137
|
)
|
|
133
|
-
if locking_enabled?
|
|
134
|
-
locking_column = self.class.locking_column
|
|
135
|
-
self[locking_column] += 1
|
|
136
|
-
clear_attribute_change(locking_column)
|
|
137
|
-
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
# Marks the run as errored and persists the error data.
|
|
141
141
|
#
|
|
142
142
|
# @param error [StandardError] the Error being persisted.
|
|
143
143
|
def persist_error(error)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
with_stale_object_retry do
|
|
145
|
+
self.started_at ||= Time.now
|
|
146
|
+
update!(
|
|
147
|
+
status: :errored,
|
|
148
|
+
error_class: truncate(:error_class, error.class.name),
|
|
149
|
+
error_message: truncate(:error_message, error.message),
|
|
150
|
+
backtrace: MaintenanceTasks.backtrace_cleaner.clean(error.backtrace),
|
|
151
|
+
ended_at: Time.now,
|
|
152
|
+
)
|
|
153
|
+
end
|
|
152
154
|
run_error_callback
|
|
153
|
-
rescue ActiveRecord::StaleObjectError
|
|
154
|
-
reload_status
|
|
155
|
-
retry
|
|
156
155
|
end
|
|
157
156
|
|
|
158
157
|
# Refreshes the status and lock version attributes on the Active Record
|
|
@@ -245,11 +244,8 @@ module MaintenanceTasks
|
|
|
245
244
|
# is encountered.
|
|
246
245
|
def running
|
|
247
246
|
if locking_enabled?
|
|
248
|
-
|
|
247
|
+
with_stale_object_retry do
|
|
249
248
|
running! unless stopping?
|
|
250
|
-
rescue ActiveRecord::StaleObjectError
|
|
251
|
-
reload_status
|
|
252
|
-
retry
|
|
253
249
|
end
|
|
254
250
|
else
|
|
255
251
|
# Preserve swap-and-replace solution for data races until users
|
|
@@ -272,11 +268,11 @@ module MaintenanceTasks
|
|
|
272
268
|
# @param count [Integer] the total iterations to be performed, as
|
|
273
269
|
# specified by the Task.
|
|
274
270
|
def start(count)
|
|
275
|
-
|
|
271
|
+
with_stale_object_retry do
|
|
272
|
+
update!(started_at: Time.now, tick_total: count)
|
|
273
|
+
end
|
|
274
|
+
|
|
276
275
|
task.run_callbacks(:start)
|
|
277
|
-
rescue ActiveRecord::StaleObjectError
|
|
278
|
-
reload_status
|
|
279
|
-
retry
|
|
280
276
|
end
|
|
281
277
|
|
|
282
278
|
# Handles transitioning the status on a Run when the job shuts down.
|
|
@@ -311,16 +307,15 @@ module MaintenanceTasks
|
|
|
311
307
|
# minutes ago, it will transition to cancelled, and the ended_at timestamp
|
|
312
308
|
# will be updated.
|
|
313
309
|
def cancel
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
310
|
+
with_stale_object_retry do
|
|
311
|
+
if paused? || stuck?
|
|
312
|
+
self.status = :cancelled
|
|
313
|
+
self.ended_at = Time.now
|
|
314
|
+
persist_transition
|
|
315
|
+
else
|
|
316
|
+
cancelling!
|
|
317
|
+
end
|
|
320
318
|
end
|
|
321
|
-
rescue ActiveRecord::StaleObjectError
|
|
322
|
-
reload_status
|
|
323
|
-
retry
|
|
324
319
|
end
|
|
325
320
|
|
|
326
321
|
# Marks a Run as pausing.
|
|
@@ -331,15 +326,14 @@ module MaintenanceTasks
|
|
|
331
326
|
# Rescues and retries status transition if an ActiveRecord::StaleObjectError
|
|
332
327
|
# is encountered.
|
|
333
328
|
def pause
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
329
|
+
with_stale_object_retry do
|
|
330
|
+
if stuck?
|
|
331
|
+
self.status = :paused
|
|
332
|
+
persist_transition
|
|
333
|
+
else
|
|
334
|
+
pausing!
|
|
335
|
+
end
|
|
339
336
|
end
|
|
340
|
-
rescue ActiveRecord::StaleObjectError
|
|
341
|
-
reload_status
|
|
342
|
-
retry
|
|
343
337
|
end
|
|
344
338
|
|
|
345
339
|
# Returns whether a Run is stuck, which is defined as having a status of
|
|
@@ -426,12 +420,23 @@ module MaintenanceTasks
|
|
|
426
420
|
if task.attribute_names.any? && arguments.present?
|
|
427
421
|
task.assign_attributes(arguments)
|
|
428
422
|
end
|
|
423
|
+
|
|
424
|
+
task.metadata = metadata
|
|
429
425
|
task
|
|
430
426
|
rescue ActiveModel::UnknownAttributeError
|
|
431
427
|
task
|
|
432
428
|
end
|
|
433
429
|
end
|
|
434
430
|
|
|
431
|
+
# Returns all the run arguments with sensitive information masked.
|
|
432
|
+
#
|
|
433
|
+
# @return [Hash] The masked arguments.
|
|
434
|
+
def masked_arguments
|
|
435
|
+
return unless arguments.present?
|
|
436
|
+
|
|
437
|
+
argument_filter.filter(arguments)
|
|
438
|
+
end
|
|
439
|
+
|
|
435
440
|
private
|
|
436
441
|
|
|
437
442
|
def instrument_status_change
|
|
@@ -486,5 +491,32 @@ module MaintenanceTasks
|
|
|
486
491
|
|
|
487
492
|
value&.first(limit)
|
|
488
493
|
end
|
|
494
|
+
|
|
495
|
+
def argument_filter
|
|
496
|
+
@argument_filter ||= ActiveSupport::ParameterFilter.new(
|
|
497
|
+
Rails.application.config.filter_parameters + task.masked_arguments,
|
|
498
|
+
)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def with_stale_object_retry(retry_count = 0)
|
|
502
|
+
yield
|
|
503
|
+
rescue ActiveRecord::StaleObjectError
|
|
504
|
+
if retry_count < MAX_RETRIES
|
|
505
|
+
sleep(stale_object_retry_delay(retry_count))
|
|
506
|
+
retry_count += 1
|
|
507
|
+
reload_status
|
|
508
|
+
|
|
509
|
+
retry
|
|
510
|
+
else
|
|
511
|
+
raise
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def stale_object_retry_delay(retry_count)
|
|
516
|
+
delay = DELAYS_PER_ATTEMPT[retry_count]
|
|
517
|
+
# Add jitter (±25% randomization) to prevent thundering herd
|
|
518
|
+
jitter = delay * 0.25
|
|
519
|
+
delay + (rand * 2 - 1) * jitter
|
|
520
|
+
end
|
|
489
521
|
end
|
|
490
522
|
end
|
|
@@ -8,6 +8,7 @@ module MaintenanceTasks
|
|
|
8
8
|
include ActiveModel::Attributes
|
|
9
9
|
include ActiveModel::AttributeAssignment
|
|
10
10
|
include ActiveModel::Validations
|
|
11
|
+
include ActiveSupport::Rescuable
|
|
11
12
|
|
|
12
13
|
class NotFoundError < NameError; end
|
|
13
14
|
|
|
@@ -27,8 +28,21 @@ module MaintenanceTasks
|
|
|
27
28
|
# @api private
|
|
28
29
|
class_attribute :collection_builder_strategy, default: NullCollectionBuilder.new
|
|
29
30
|
|
|
31
|
+
# The sensitive attributes that will be filtered when fetching a run.
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
class_attribute :masked_arguments, default: []
|
|
35
|
+
|
|
36
|
+
# The frequency at which to reload the run status during iteration.
|
|
37
|
+
# Defaults to the global MaintenanceTasks.status_reload_frequency setting.
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
class_attribute :status_reload_frequency, default: MaintenanceTasks.status_reload_frequency
|
|
41
|
+
|
|
30
42
|
define_callbacks :start, :complete, :error, :cancel, :pause, :interrupt
|
|
31
43
|
|
|
44
|
+
attr_accessor :metadata
|
|
45
|
+
|
|
32
46
|
class << self
|
|
33
47
|
# Finds a Task with the given name.
|
|
34
48
|
#
|
|
@@ -152,6 +166,22 @@ module MaintenanceTasks
|
|
|
152
166
|
self.active_record_enumerator_batch_size = size
|
|
153
167
|
end
|
|
154
168
|
|
|
169
|
+
# Adds attribute names to sensitive arguments list.
|
|
170
|
+
#
|
|
171
|
+
# @param attributes [Array<Symbol>] the attribute names to filter.
|
|
172
|
+
def mask_attribute(*attributes)
|
|
173
|
+
self.masked_arguments += attributes
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Configure how frequently the run status should be reloaded during iteration.
|
|
177
|
+
# Use this to reduce database queries when processing large collections.
|
|
178
|
+
#
|
|
179
|
+
# @param frequency [ActiveSupport::Duration, Numeric] reload status every N seconds (default: 1 second).
|
|
180
|
+
# Setting this to 10.seconds means status will be reloaded every 10 seconds.
|
|
181
|
+
def reload_status_every(frequency)
|
|
182
|
+
self.status_reload_frequency = frequency
|
|
183
|
+
end
|
|
184
|
+
|
|
155
185
|
# Initialize a callback to run after the task starts.
|
|
156
186
|
#
|
|
157
187
|
# @param filter_list apply filters to the callback
|
|
@@ -200,6 +230,18 @@ module MaintenanceTasks
|
|
|
200
230
|
set_callback(:error, :after, *filter_list, &block)
|
|
201
231
|
end
|
|
202
232
|
|
|
233
|
+
# Rescue listed exceptions during an iteration and report them to the error reporter, then
|
|
234
|
+
# continue iteration.
|
|
235
|
+
#
|
|
236
|
+
# @param exceptions list of exceptions to rescue and report
|
|
237
|
+
# @param report_options [Hash] optionally, supply additional options for `Rails.error.report`.
|
|
238
|
+
# By default: <code>{ handled: true, source: "maintenance-tasks" }</code>.
|
|
239
|
+
def report_on(*exceptions, **report_options)
|
|
240
|
+
rescue_from(*exceptions) do |exception|
|
|
241
|
+
Rails.error.report(exception, source: "maintenance-tasks", **report_options)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
203
245
|
private
|
|
204
246
|
|
|
205
247
|
def load_constants
|
|
@@ -34,7 +34,8 @@ module MaintenanceTasks
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
completed_runs = Run.completed.where(task_name: task_names)
|
|
37
|
-
last_runs = Run.with_attached_csv
|
|
37
|
+
last_runs = Run.with_attached_csv
|
|
38
|
+
.where(created_at: completed_runs.select("MAX(created_at) as created_at").group(:task_name))
|
|
38
39
|
task_names.map do |task_name|
|
|
39
40
|
last_run = last_runs.find { |run| run.task_name == task_name }
|
|
40
41
|
tasks << TaskDataIndex.new(task_name, last_run)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<nav class="navbar is-
|
|
1
|
+
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
|
|
2
2
|
<div class="navbar-brand">
|
|
3
3
|
<%= link_to 'Maintenance Tasks', root_path, class: 'navbar-item is-size-4 has-text-weight-semibold has-text-danger' %>
|
|
4
4
|
</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<%= capybara_lockstep if defined?(Capybara::Lockstep) %>
|
|
6
7
|
|
|
7
8
|
<title>
|
|
8
9
|
<% if content_for?(:page_title) %>
|
|
@@ -15,10 +16,10 @@
|
|
|
15
16
|
<%= csrf_meta_tags %>
|
|
16
17
|
|
|
17
18
|
<%=
|
|
18
|
-
stylesheet_link_tag(URI.join(controller.class::BULMA_CDN,
|
|
19
|
+
stylesheet_link_tag(URI.join(controller.class::BULMA_CDN, "npm/bulma@1.0.3/css/versions/bulma-no-dark-mode.min.css"),
|
|
19
20
|
media: :all,
|
|
20
|
-
integrity:
|
|
21
|
-
crossorigin:
|
|
21
|
+
integrity: "sha256-HCNMQcqH/4MnGR0EYg2S3/BXYMM1z9lrFV10ANRd79o",
|
|
22
|
+
crossorigin: "anonymous") unless request.xhr?
|
|
22
23
|
%>
|
|
23
24
|
|
|
24
25
|
<style>
|
|
@@ -29,6 +30,36 @@
|
|
|
29
30
|
.ruby-ivar, .ruby-cvar, .ruby-gvar, .ruby-int, .ruby-imaginary, .ruby-float, .ruby-rational { color: #005cc5; }
|
|
30
31
|
.ruby-kw { color: #d73a49; }
|
|
31
32
|
.ruby-label, .ruby-tstring-beg, .ruby-tstring-content, .ruby-tstring-end { color: #032f62; }
|
|
33
|
+
|
|
34
|
+
.select, select { width: 100%; }
|
|
35
|
+
summary { cursor: pointer; }
|
|
36
|
+
input[type="datetime-local"], input[type="date"], input[type="time"] {
|
|
37
|
+
width: fit-content;
|
|
38
|
+
}
|
|
39
|
+
details > summary {
|
|
40
|
+
list-style: none;
|
|
41
|
+
}
|
|
42
|
+
summary::-webkit-details-marker {
|
|
43
|
+
display: none
|
|
44
|
+
}
|
|
45
|
+
summary::before {
|
|
46
|
+
content: '► ';
|
|
47
|
+
position:absolute;
|
|
48
|
+
font-size: 16px
|
|
49
|
+
}
|
|
50
|
+
details[open] summary:before {
|
|
51
|
+
content: "▼ ";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.box {
|
|
55
|
+
box-shadow: 0 4px 6px -1px #0000001a,
|
|
56
|
+
0 2px 4px -2px #0000001a;
|
|
57
|
+
}
|
|
58
|
+
.label.is-required:after {
|
|
59
|
+
content: " (required)";
|
|
60
|
+
color: #ff6685;
|
|
61
|
+
font-size: 12px;
|
|
62
|
+
}
|
|
32
63
|
</style>
|
|
33
64
|
|
|
34
65
|
<script>
|
|
@@ -69,6 +100,6 @@
|
|
|
69
100
|
|
|
70
101
|
<%= yield %>
|
|
71
102
|
</div>
|
|
72
|
-
</
|
|
103
|
+
</section>
|
|
73
104
|
</body>
|
|
74
105
|
</html>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<details class="box" open id="run_<%= run.id %>">
|
|
2
|
+
<summary class="is-flex is-justify-content-space-between is-align-items-center">
|
|
3
|
+
<div class="is-flex is-align-items-center">
|
|
4
|
+
<h5 class="title is-5 has-text-weight-medium pl-5 pr-5 mb-0">
|
|
5
|
+
<%= time_tag run.created_at, title: run.created_at.utc %>
|
|
6
|
+
</h5>
|
|
7
|
+
<%= status_tag run.status %>
|
|
8
|
+
</div>
|
|
9
|
+
<div>
|
|
10
|
+
<a href="#run_<%= run.id %>" class="is-pulled-right" title="Run ID">#<%= run.id %></a>
|
|
11
|
+
</div>
|
|
12
|
+
</summary>
|
|
7
13
|
|
|
8
14
|
<%= progress run %>
|
|
9
15
|
|
|
@@ -17,7 +23,7 @@
|
|
|
17
23
|
|
|
18
24
|
<%= render "maintenance_tasks/runs/csv", run: run %>
|
|
19
25
|
<%= tag.hr if run.csv_file.present? && run.arguments.present? %>
|
|
20
|
-
<%= render "maintenance_tasks/runs/arguments", arguments: run.
|
|
26
|
+
<%= render "maintenance_tasks/runs/arguments", arguments: run.masked_arguments %>
|
|
21
27
|
<%= tag.hr if run.csv_file.present? || run.arguments.present? && run.metadata.present? %>
|
|
22
28
|
<%= render "maintenance_tasks/runs/metadata", metadata: run.metadata %>
|
|
23
29
|
|
|
@@ -42,4 +48,4 @@
|
|
|
42
48
|
<%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
|
|
43
49
|
<% end%>
|
|
44
50
|
</div>
|
|
45
|
-
</
|
|
51
|
+
</details>
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
<% if serializable.present? %>
|
|
2
2
|
<% case serializable %>
|
|
3
3
|
<% when Hash %>
|
|
4
|
-
<div class="
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<% end %>
|
|
20
|
-
</tbody>
|
|
21
|
-
</table>
|
|
4
|
+
<div class="arguments-container grid is-col-min-15">
|
|
5
|
+
<% serializable.transform_values(&:to_s).each do |key, value| %>
|
|
6
|
+
<div class="cell mb-4">
|
|
7
|
+
<div class="is-family-monospace mb-2"><%= key %></div>
|
|
8
|
+
<div class="is-flex justify-content">
|
|
9
|
+
<% if !value.empty? %>
|
|
10
|
+
<% if value.include?("\n") %>
|
|
11
|
+
<pre><%= value %></pre>
|
|
12
|
+
<% else %>
|
|
13
|
+
<code><%= value %></code>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
22
19
|
</div>
|
|
23
20
|
<% else %>
|
|
24
21
|
<code><%= serializable.inspect %></code>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
<div class="box">
|
|
2
|
-
<h3 class="title is-
|
|
1
|
+
<div class="cell box">
|
|
2
|
+
<h3 class="title is-5 has-text-weight-medium">
|
|
3
3
|
<%= link_to task, task_path(task) %>
|
|
4
4
|
<%= status_tag(task.status) %>
|
|
5
5
|
</h3>
|
|
6
6
|
|
|
7
7
|
<% if (run = task.related_run) %>
|
|
8
|
-
<h5 class="title is-5">
|
|
9
|
-
<%= time_tag run.created_at, title: run.created_at %>
|
|
8
|
+
<h5 class="title is-5 has-text-weight-medium">
|
|
9
|
+
<%= time_tag run.created_at, title: run.created_at.utc %>
|
|
10
10
|
</h5>
|
|
11
11
|
|
|
12
12
|
<%= progress run %>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
<%= render "maintenance_tasks/runs/csv", run: run %>
|
|
23
23
|
<%= tag.hr if run.csv_file.present? && run.arguments.present? %>
|
|
24
|
-
<%= render "maintenance_tasks/runs/arguments", arguments: run.
|
|
24
|
+
<%= render "maintenance_tasks/runs/arguments", arguments: run.masked_arguments %>
|
|
25
25
|
<%= tag.hr if run.csv_file.present? || run.arguments.present? && run.metadata.present? %>
|
|
26
26
|
<%= render "maintenance_tasks/runs/metadata", metadata: run.metadata %>
|
|
27
27
|
<% end %>
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<% else %>
|
|
11
11
|
<% if active_tasks = @available_tasks[:active] %>
|
|
12
|
-
<h3 class="title is-
|
|
12
|
+
<h3 class="title is-4 has-text-weight-bold">Active Tasks</h3>
|
|
13
13
|
<%= render partial: 'task', collection: active_tasks %>
|
|
14
14
|
<% end %>
|
|
15
15
|
<% if new_tasks = @available_tasks[:new] %>
|
|
16
|
-
<h3 class="title is-
|
|
17
|
-
|
|
16
|
+
<h3 class="title is-4 has-text-weight-bold">New Tasks</h3>
|
|
17
|
+
<div class="grid is-col-min-20">
|
|
18
|
+
<%= render partial: 'task', collection: new_tasks %>
|
|
19
|
+
</div>
|
|
18
20
|
<% end %>
|
|
19
21
|
<% if completed_tasks = @available_tasks[:completed] %>
|
|
20
|
-
<h3 class="title is-
|
|
22
|
+
<h3 class="title is-4 has-text-weight-bold">Completed Tasks</h3>
|
|
21
23
|
<%= render partial: 'task', collection: completed_tasks %>
|
|
22
24
|
<% end %>
|
|
23
25
|
<% end %>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
<% content_for :page_title, @task %>
|
|
2
2
|
|
|
3
|
-
<h1 class="title is-
|
|
3
|
+
<h1 class="title is-3 has-text-weight-bold">
|
|
4
4
|
<%= @task %>
|
|
5
5
|
</h1>
|
|
6
6
|
|
|
7
|
-
<div class="
|
|
7
|
+
<div class="container">
|
|
8
8
|
<%= form_with url: task_runs_path(@task), method: :post do |form| %>
|
|
9
9
|
<% if @task.csv_task? %>
|
|
10
|
-
<div class="
|
|
11
|
-
<%= form.label :csv_file %>
|
|
10
|
+
<div class="container mb-4">
|
|
11
|
+
<%= form.label :csv_file, class: "label" %>
|
|
12
12
|
<%= form.file_field :csv_file, accept: "text/csv" %>
|
|
13
13
|
</div>
|
|
14
14
|
<% end %>
|
|
15
15
|
<% parameter_names = @task.parameter_names %>
|
|
16
16
|
<% if parameter_names.any? %>
|
|
17
|
-
<div class="
|
|
17
|
+
<div class="grid is-col-min-15">
|
|
18
18
|
<%= fields_for :task, @task.new do |ff| %>
|
|
19
19
|
<% parameter_names.each do |parameter_name| %>
|
|
20
|
-
<div class="
|
|
21
|
-
<%= ff.label parameter_name, parameter_name, class: "label is-family-monospace" %>
|
|
22
|
-
|
|
23
|
-
<%= parameter_field(ff, parameter_name) %>
|
|
24
|
-
</div>
|
|
20
|
+
<div class="cell">
|
|
21
|
+
<%= ff.label parameter_name, parameter_name, class: ["label", "is-family-monospace", { "is-required": attribute_required?(ff.object, parameter_name) }] %>
|
|
22
|
+
<%= parameter_field(ff, parameter_name) %>
|
|
25
23
|
</div>
|
|
26
24
|
<% end %>
|
|
27
25
|
<% end %>
|
|
@@ -29,13 +27,19 @@
|
|
|
29
27
|
<% end %>
|
|
30
28
|
<%= render "maintenance_tasks/tasks/custom", form: form %>
|
|
31
29
|
<div class="block">
|
|
32
|
-
<%= form.submit 'Run', class: "button is-success", disabled: @task.deleted? %>
|
|
30
|
+
<%= form.submit 'Run', class: "button is-success is-rounded mb-4 has-text-white-ter", disabled: @task.deleted? %>
|
|
33
31
|
</div>
|
|
34
32
|
<% end %>
|
|
35
33
|
</div>
|
|
36
34
|
|
|
37
35
|
<% if (code = @task.code) %>
|
|
36
|
+
|
|
37
|
+
<details class="box">
|
|
38
|
+
<summary class="is-size-5 is-flex is-align-items-center">
|
|
39
|
+
<h5 class="pl-5">Source code</h5>
|
|
40
|
+
</summary>
|
|
38
41
|
<pre><code><%= highlight_code(code) %></code></pre>
|
|
42
|
+
</details>
|
|
39
43
|
<% end %>
|
|
40
44
|
|
|
41
45
|
<%= tag.div(data: { refresh: @task.refresh? || "" }) do %>
|
|
@@ -50,7 +54,7 @@
|
|
|
50
54
|
<% if @task.runs_page.records.present? %>
|
|
51
55
|
<hr/>
|
|
52
56
|
|
|
53
|
-
<h4 class="title is-
|
|
57
|
+
<h4 class="title is-5 has-text-weight-bold">Previous Runs</h4>
|
|
54
58
|
|
|
55
59
|
<%= render partial: "maintenance_tasks/runs/run", collection: @task.runs_page.records %>
|
|
56
60
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateMaintenanceTasksRuns < ActiveRecord::Migration[
|
|
3
|
+
class CreateMaintenanceTasksRuns < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
|
-
create_table(:maintenance_tasks_runs) do |t|
|
|
5
|
+
create_table(:maintenance_tasks_runs, id: primary_key_type) do |t|
|
|
6
6
|
t.string(:task_name, null: false)
|
|
7
7
|
t.datetime(:started_at)
|
|
8
8
|
t.datetime(:ended_at)
|
|
@@ -20,4 +20,12 @@ class CreateMaintenanceTasksRuns < ActiveRecord::Migration[6.0]
|
|
|
20
20
|
t.index([:task_name, :created_at], order: { created_at: :desc })
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def primary_key_type
|
|
27
|
+
config = Rails.configuration.generators
|
|
28
|
+
setting = config.options[config.orm][:primary_key_type]
|
|
29
|
+
setting || :primary_key
|
|
30
|
+
end
|
|
23
31
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class ChangeCursorToString < ActiveRecord::Migration[
|
|
3
|
+
class ChangeCursorToString < ActiveRecord::Migration[7.0]
|
|
4
4
|
# This migration will clear all existing data in the cursor column with MySQL.
|
|
5
5
|
# Ensure no Tasks are paused when this migration is deployed, or they will be resumed from the start.
|
|
6
6
|
# Running tasks are able to gracefully handle this change, even if interrupted.
|