good_job 3.24.0 → 3.25.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +38 -1
- data/app/helpers/good_job/application_helper.rb +5 -36
- data/app/helpers/good_job/icons_helper.rb +41 -0
- data/app/models/good_job/execution.rb +45 -34
- data/app/models/good_job/execution_result.rb +8 -4
- data/app/models/good_job/job.rb +9 -1
- data/app/models/good_job/process.rb +1 -1
- data/app/views/good_job/_custom_execution_details.html.erb +11 -0
- data/app/views/good_job/_custom_job_details.html.erb +10 -0
- data/app/views/good_job/batches/_jobs.erb +4 -4
- data/app/views/good_job/jobs/_executions.erb +1 -0
- data/app/views/good_job/jobs/_table.erb +5 -5
- data/app/views/good_job/jobs/show.html.erb +2 -0
- data/app/views/good_job/shared/_alert.erb +2 -2
- data/app/views/good_job/shared/_navbar.erb +1 -1
- data/app/views/good_job/shared/icons/_check.html.erb +1 -1
- data/app/views/good_job/shared/icons/_exclamation.html.erb +1 -1
- data/lib/good_job/adapter.rb +35 -11
- data/lib/good_job/configuration.rb +10 -1
- data/lib/good_job/current_thread.rb +7 -0
- data/lib/good_job/daemon.rb +9 -0
- data/lib/good_job/notifier/process_heartbeat.rb +3 -3
- data/lib/good_job/{assignable_connection.rb → overridable_connection.rb} +7 -14
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47f288c089d005a2b97e5fe6d7bd8fb7094d48fb52565bacf0493b5e547f6516
|
4
|
+
data.tar.gz: cf8cd69a7a1f64fee5172b7fd3b184d774eed0cd48d97ba0d01971af0d721646
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c74189b7315da1fcb5747b285cb4d7837550dcf1456899f2d6f023065a893bd99c93a7e292415b6796a64232335a732666a7783fc04f2319968fff4bfaf7dcc
|
7
|
+
data.tar.gz: 67fd833bfa126355ef0853263c7e72b5bfa5d013e1061bc128480408a52b49eddf9724686e3dc47293f8713696074f8029452c035159aee50940f0b320ff36ab
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.25.0](https://github.com/bensheldon/good_job/tree/v3.25.0) (2024-02-22)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.24.0...v3.25.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Allow disabling of Dashboard Live Polling configuration [\#1235](https://github.com/bensheldon/good_job/pull/1235) ([erick-tmr](https://github.com/erick-tmr))
|
10
|
+
- Add customizable extension partials to good\_job/jobs\#show view [\#1200](https://github.com/bensheldon/good_job/pull/1200) ([grncdr](https://github.com/grncdr))
|
11
|
+
|
12
|
+
**Fixed bugs:**
|
13
|
+
|
14
|
+
- Fix default engine cron value [\#1258](https://github.com/bensheldon/good_job/pull/1258) ([hss-mateus](https://github.com/hss-mateus))
|
15
|
+
- Print an error when daemon pidfile dir doesn't exist [\#1252](https://github.com/bensheldon/good_job/pull/1252) ([thepry](https://github.com/thepry))
|
16
|
+
|
17
|
+
**Closed issues:**
|
18
|
+
|
19
|
+
- Production deployment question [\#1257](https://github.com/bensheldon/good_job/issues/1257)
|
20
|
+
- Daemon and App not connecting to secondary database [\#1254](https://github.com/bensheldon/good_job/issues/1254)
|
21
|
+
- Logging with logger.warn in classes is suppressed by good job? \(semantic\_logger\) [\#1250](https://github.com/bensheldon/good_job/issues/1250)
|
22
|
+
|
23
|
+
**Merged pull requests:**
|
24
|
+
|
25
|
+
- Fix Active Record connection changes on Rails head [\#1259](https://github.com/bensheldon/good_job/pull/1259) ([bensheldon](https://github.com/bensheldon))
|
26
|
+
- \[Docs\] Bulk.enqueue takes an array of jobs [\#1256](https://github.com/bensheldon/good_job/pull/1256) ([jpcamara](https://github.com/jpcamara))
|
27
|
+
- Clean up icon helpers for less noisy view rendering [\#1248](https://github.com/bensheldon/good_job/pull/1248) ([bensheldon](https://github.com/bensheldon))
|
28
|
+
- Use dotenv-rails instead of dotenv [\#1247](https://github.com/bensheldon/good_job/pull/1247) ([bensheldon](https://github.com/bensheldon))
|
29
|
+
- Perform inline retries iteratively instead of recursively [\#1246](https://github.com/bensheldon/good_job/pull/1246) ([bensheldon](https://github.com/bensheldon))
|
30
|
+
|
3
31
|
## [v3.24.0](https://github.com/bensheldon/good_job/tree/v3.24.0) (2024-02-12)
|
4
32
|
|
5
33
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.23.0...v3.24.0)
|
data/README.md
CHANGED
@@ -41,6 +41,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
41
41
|
- [Dashboard](#dashboard)
|
42
42
|
- [API-only Rails applications](#api-only-rails-applications)
|
43
43
|
- [Live polling](#live-polling)
|
44
|
+
- [Extending dashboard views](#extending-dashboard-views)
|
44
45
|
- [Job priority](#job-priority)
|
45
46
|
- [Concurrency controls](#concurrency-controls)
|
46
47
|
- [How concurrency controls work](#how-concurrency-controls-work)
|
@@ -451,6 +452,42 @@ end
|
|
451
452
|
|
452
453
|
The Dashboard can be set to automatically refresh by checking "Live Poll" in the Dashboard header, or by setting `?poll=10` with the interval in seconds (default 30 seconds).
|
453
454
|
|
455
|
+
#### Extending dashboard views
|
456
|
+
|
457
|
+
GoodJob exposes some views that are intended to be overriden by placing views in your application:
|
458
|
+
|
459
|
+
- [`app/views/good_job/jobs/_custom_job_details.html.erb`](app/views/good_job/_custom_job_details.html.erb): content added to this partial will be displayed above the argument list on the good_job/jobs#show page.
|
460
|
+
- [`app/views/good_job/jobs/_custom_execution_details.html.erb`](app/views/good_job/_custom_execution_details.html.erb): content added to this partial will be displayed above each execution on the good_job/jobs#show page.
|
461
|
+
|
462
|
+
**Warning:** these partials expose classes (such as `GoodJob::Job`) that are considered internal implementation details of GoodJob. You should always test your custom partials after upgrading GoodJob.
|
463
|
+
|
464
|
+
For example, if your app deals with widgets and you want to show a link to the widget a job acted on, you can add the following to `app/views/good_job/_custom_job_details.html.erb`:
|
465
|
+
|
466
|
+
```erb
|
467
|
+
<%# file: app/views/good_job/_custom_job_details.html.erb %>
|
468
|
+
<% arguments = job.active_job.arguments rescue [] %>
|
469
|
+
<% widgets = arguments.select { |arg| arg.is_a?(Widget) } %>
|
470
|
+
<% if widgets.any? %>
|
471
|
+
<div class="my-4">
|
472
|
+
<h5>Widgets</h5>
|
473
|
+
<ul>
|
474
|
+
<% widgets.each do |widget| %>
|
475
|
+
<li><%= link_to widget.name, main_app.widget_url(widget) %></li>
|
476
|
+
<% end %>
|
477
|
+
</ul>
|
478
|
+
</div>
|
479
|
+
<% end %>
|
480
|
+
```
|
481
|
+
|
482
|
+
As a second example, you may wish to show a link to a log aggregator next to each job execution. You can do this by adding the following to `app/views/good_job/_custom_execution_details.html.erb`:
|
483
|
+
|
484
|
+
```erb
|
485
|
+
<%# file: app/views/good_job/_custom_execution_details.html.erb %>
|
486
|
+
<div class="py-3">
|
487
|
+
<%= link_to "Logs", main_app.logs_url(filter: { job_id: job.id }, start_time: execution.performed_at, end_time: execution.finished_at + 1.minute) %>
|
488
|
+
</div>
|
489
|
+
```
|
490
|
+
|
454
491
|
### Job priority
|
455
492
|
|
456
493
|
Higher priority numbers run first in all versions of GoodJob v3.x and below. GoodJob v4.x will change job `priority` to give smaller numbers higher priority (default: `0`), in accordance with Active Job's definition of priority (see #524). To opt-in to this behavior now, set `config.good_job.smaller_number_is_higher_priority = true` in your GoodJob initializer or `application.rb`.
|
@@ -609,7 +646,7 @@ end
|
|
609
646
|
active_jobs.all?(&:provider_job_id)
|
610
647
|
|
611
648
|
# Bulk enqueue Active Job instances directly without using `.perform_later`:
|
612
|
-
GoodJob::Bulk.enqueue(MyJob.new, AnotherJob.new)
|
649
|
+
GoodJob::Bulk.enqueue([MyJob.new, AnotherJob.new])
|
613
650
|
```
|
614
651
|
|
615
652
|
### Batches
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module GoodJob
|
4
4
|
module ApplicationHelper
|
5
|
+
# Explicit helper inclusion because ApplicationController inherits from the host app.
|
6
|
+
#
|
7
|
+
# We can't rely on +config.action_controller.include_all_helpers = true+ in the host app.
|
8
|
+
include IconsHelper
|
9
|
+
|
5
10
|
def format_duration(sec)
|
6
11
|
return unless sec
|
7
12
|
|
@@ -24,42 +29,6 @@ module GoodJob
|
|
24
29
|
tag.time(text, datetime: timestamp, title: timestamp)
|
25
30
|
end
|
26
31
|
|
27
|
-
STATUS_ICONS = {
|
28
|
-
discarded: "exclamation",
|
29
|
-
succeeded: "check",
|
30
|
-
queued: "dash_circle",
|
31
|
-
retried: "arrow_clockwise",
|
32
|
-
running: "play",
|
33
|
-
scheduled: "clock",
|
34
|
-
}.freeze
|
35
|
-
|
36
|
-
STATUS_COLOR = {
|
37
|
-
discarded: "danger",
|
38
|
-
succeeded: "success",
|
39
|
-
queued: "secondary",
|
40
|
-
retried: "warning",
|
41
|
-
running: "primary",
|
42
|
-
scheduled: "secondary",
|
43
|
-
}.freeze
|
44
|
-
|
45
|
-
def status_badge(status)
|
46
|
-
content_tag :span, status_icon(status, class: "text-white") + t(status, scope: 'good_job.status', count: 1),
|
47
|
-
class: "badge rounded-pill bg-#{STATUS_COLOR.fetch(status)} d-inline-flex gap-2 ps-1 pe-3 align-items-center"
|
48
|
-
end
|
49
|
-
|
50
|
-
def status_icon(status, **options)
|
51
|
-
options[:class] ||= "text-#{STATUS_COLOR.fetch(status)}"
|
52
|
-
icon = render_icon STATUS_ICONS.fetch(status)
|
53
|
-
content_tag :span, icon, **options
|
54
|
-
end
|
55
|
-
|
56
|
-
def render_icon(name, **options)
|
57
|
-
# workaround to render svg icons without all of the log messages
|
58
|
-
partial = lookup_context.find_template("good_job/shared/icons/#{name}", [], true)
|
59
|
-
options[:class] = Array(options[:class]).join(" ")
|
60
|
-
partial.render(self, { class: options[:class] })
|
61
|
-
end
|
62
|
-
|
63
32
|
def translate_hash(key, **options)
|
64
33
|
translation_exists?(key, **options) ? translate(key, **options) : {}
|
65
34
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
module IconsHelper
|
5
|
+
STATUS_ICONS = {
|
6
|
+
discarded: "exclamation",
|
7
|
+
succeeded: "check",
|
8
|
+
queued: "dash_circle",
|
9
|
+
retried: "arrow_clockwise",
|
10
|
+
running: "play",
|
11
|
+
scheduled: "clock",
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
STATUS_COLOR = {
|
15
|
+
discarded: "danger",
|
16
|
+
succeeded: "success",
|
17
|
+
queued: "secondary",
|
18
|
+
retried: "warning",
|
19
|
+
running: "primary",
|
20
|
+
scheduled: "secondary",
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
def status_badge(status)
|
24
|
+
content_tag :span, status_icon(status, class: "text-white") + t(status, scope: 'good_job.status', count: 1),
|
25
|
+
class: "badge rounded-pill bg-#{STATUS_COLOR.fetch(status)} d-inline-flex gap-2 ps-1 pe-3 align-items-center"
|
26
|
+
end
|
27
|
+
|
28
|
+
def status_icon(status, **options)
|
29
|
+
options[:class] ||= "text-#{STATUS_COLOR.fetch(status)}"
|
30
|
+
icon = render_icon STATUS_ICONS.fetch(status)
|
31
|
+
content_tag :span, icon, **options
|
32
|
+
end
|
33
|
+
|
34
|
+
def render_icon(name, **options)
|
35
|
+
# workaround to render svg icons without all of the log messages
|
36
|
+
partial = lookup_context.find_template("good_job/shared/icons/#{name}", [], true)
|
37
|
+
options[:class] = Array(options[:class]).join(" ")
|
38
|
+
partial.render(self, { class: options[:class] })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -343,8 +343,10 @@ module GoodJob
|
|
343
343
|
execution.save!
|
344
344
|
|
345
345
|
if retried
|
346
|
-
CurrentThread.execution_retried =
|
346
|
+
CurrentThread.execution_retried = execution
|
347
347
|
CurrentThread.execution.retried_good_job_id = execution.id unless current_execution.discrete?
|
348
|
+
else
|
349
|
+
CurrentThread.execution_retried = nil
|
348
350
|
end
|
349
351
|
|
350
352
|
active_job.provider_job_id = execution.id
|
@@ -367,22 +369,24 @@ module GoodJob
|
|
367
369
|
run_callbacks(:perform) do
|
368
370
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
369
371
|
|
372
|
+
job_performed_at = Time.current
|
370
373
|
discrete_execution = nil
|
371
374
|
result = GoodJob::CurrentThread.within do |current_thread|
|
372
375
|
current_thread.reset
|
373
376
|
current_thread.execution = self
|
374
377
|
|
375
|
-
|
376
|
-
|
378
|
+
existing_performed_at = performed_at
|
379
|
+
if existing_performed_at
|
380
|
+
current_thread.execution_interrupted = existing_performed_at
|
377
381
|
|
378
382
|
if discrete?
|
379
|
-
interrupt_error_string = self.class.format_error(GoodJob::InterruptError.new("Interrupted after starting perform at '#{
|
383
|
+
interrupt_error_string = self.class.format_error(GoodJob::InterruptError.new("Interrupted after starting perform at '#{existing_performed_at}'"))
|
380
384
|
self.error = interrupt_error_string
|
381
385
|
self.error_event = ERROR_EVENT_INTERRUPTED if self.class.error_event_migrated?
|
382
386
|
|
383
387
|
discrete_execution_attrs = {
|
384
388
|
error: interrupt_error_string,
|
385
|
-
finished_at:
|
389
|
+
finished_at: job_performed_at,
|
386
390
|
}
|
387
391
|
discrete_execution_attrs[:error_event] = GoodJob::ErrorEvents::ERROR_EVENT_ENUMS[GoodJob::ErrorEvents::ERROR_EVENT_INTERRUPTED] if self.class.error_event_migrated?
|
388
392
|
discrete_executions.where(finished_at: nil).where.not(performed_at: nil).update_all(discrete_execution_attrs) # rubocop:disable Rails/SkipsModelValidations
|
@@ -391,18 +395,17 @@ module GoodJob
|
|
391
395
|
|
392
396
|
if discrete?
|
393
397
|
transaction do
|
394
|
-
now = Time.current
|
395
398
|
discrete_execution = discrete_executions.create!(
|
396
399
|
job_class: job_class,
|
397
400
|
queue_name: queue_name,
|
398
401
|
serialized_params: serialized_params,
|
399
402
|
scheduled_at: (scheduled_at || created_at),
|
400
|
-
created_at:
|
403
|
+
created_at: job_performed_at
|
401
404
|
)
|
402
|
-
update!(performed_at:
|
405
|
+
update!(performed_at: job_performed_at, executions_count: ((executions_count || 0) + 1))
|
403
406
|
end
|
404
407
|
else
|
405
|
-
update!(performed_at:
|
408
|
+
update!(performed_at: job_performed_at)
|
406
409
|
end
|
407
410
|
|
408
411
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do |instrument_payload|
|
@@ -427,7 +430,7 @@ module GoodJob
|
|
427
430
|
instrument_payload.merge!(
|
428
431
|
value: value,
|
429
432
|
handled_error: handled_error,
|
430
|
-
retried: current_thread.execution_retried
|
433
|
+
retried: current_thread.execution_retried.present?,
|
431
434
|
error_event: error_event
|
432
435
|
)
|
433
436
|
ExecutionResult.new(value: value, handled_error: handled_error, error_event: error_event, retried: current_thread.execution_retried)
|
@@ -445,47 +448,49 @@ module GoodJob
|
|
445
448
|
end
|
446
449
|
end
|
447
450
|
|
448
|
-
|
451
|
+
job_attributes = {}
|
449
452
|
|
453
|
+
job_error = result.handled_error || result.unhandled_error
|
450
454
|
if job_error
|
451
455
|
error_string = self.class.format_error(job_error)
|
452
|
-
|
453
|
-
|
456
|
+
|
457
|
+
job_attributes[:error] = error_string
|
458
|
+
job_attributes[:error_event] = result.error_event if self.class.error_event_migrated?
|
454
459
|
if discrete_execution
|
455
460
|
discrete_execution.error = error_string
|
456
|
-
discrete_execution.error_event = result.error_event
|
461
|
+
discrete_execution.error_event = result.error_event
|
457
462
|
end
|
458
463
|
else
|
459
|
-
|
460
|
-
|
464
|
+
job_attributes[:error] = nil
|
465
|
+
job_attributes[:error_event] = nil
|
461
466
|
end
|
467
|
+
job_attributes.delete(:error_event) unless self.class.error_event_migrated?
|
468
|
+
|
469
|
+
job_finished_at = Time.current
|
470
|
+
job_attributes[:finished_at] = job_finished_at
|
471
|
+
discrete_execution.finished_at = job_finished_at if discrete_execution
|
462
472
|
|
463
|
-
|
464
|
-
|
473
|
+
retry_unhandled_error = result.unhandled_error && GoodJob.retry_on_unhandled_error
|
474
|
+
reenqueued = result.retried? || retried_good_job_id.present? || retry_unhandled_error
|
475
|
+
if reenqueued
|
465
476
|
if discrete_execution
|
466
|
-
|
467
|
-
|
468
|
-
update!(performed_at: nil, finished_at: nil, retried_good_job_id: nil)
|
469
|
-
end
|
477
|
+
job_attributes[:performed_at] = nil
|
478
|
+
job_attributes[:finished_at] = nil
|
470
479
|
else
|
471
|
-
|
480
|
+
job_attributes[:retried_good_job_id] = retried_good_job_id
|
481
|
+
job_attributes[:finished_at] = nil if retry_unhandled_error
|
472
482
|
end
|
473
|
-
|
474
|
-
|
483
|
+
end
|
484
|
+
|
485
|
+
preserve_unhandled = (result.unhandled_error && (GoodJob.retry_on_unhandled_error || GoodJob.preserve_job_records == :on_unhandled_error))
|
486
|
+
if GoodJob.preserve_job_records == true || reenqueued || preserve_unhandled || cron_key.present?
|
475
487
|
if discrete_execution
|
476
|
-
if reenqueued
|
477
|
-
self.performed_at = nil
|
478
|
-
else
|
479
|
-
self.finished_at = now
|
480
|
-
end
|
481
|
-
discrete_execution.finished_at = now
|
482
488
|
transaction do
|
483
489
|
discrete_execution.save!
|
484
|
-
|
490
|
+
update!(job_attributes)
|
485
491
|
end
|
486
492
|
else
|
487
|
-
|
488
|
-
save!
|
493
|
+
update!(job_attributes)
|
489
494
|
end
|
490
495
|
else
|
491
496
|
destroy_job
|
@@ -556,6 +561,12 @@ module GoodJob
|
|
556
561
|
@_destroy_job = false
|
557
562
|
end
|
558
563
|
|
564
|
+
def job_state
|
565
|
+
state = { queue_name: queue_name }
|
566
|
+
state[:scheduled_at] = scheduled_at if scheduled_at
|
567
|
+
state
|
568
|
+
end
|
569
|
+
|
559
570
|
private
|
560
571
|
|
561
572
|
def reset_batch_values(&block)
|
@@ -13,9 +13,8 @@ module GoodJob
|
|
13
13
|
attr_reader :error_event
|
14
14
|
# @return [Boolean, nil]
|
15
15
|
attr_reader :unexecutable
|
16
|
-
# @return [
|
16
|
+
# @return [GoodJob::Execution, nil]
|
17
17
|
attr_reader :retried
|
18
|
-
alias retried? retried
|
19
18
|
|
20
19
|
# @param value [Object, nil]
|
21
20
|
# @param handled_error [Exception, nil]
|
@@ -23,7 +22,7 @@ module GoodJob
|
|
23
22
|
# @param error_event [String, nil]
|
24
23
|
# @param unexecutable [Boolean, nil]
|
25
24
|
# @param retried [Boolean, nil]
|
26
|
-
def initialize(value:, handled_error: nil, unhandled_error: nil, error_event: nil, unexecutable: nil, retried:
|
25
|
+
def initialize(value:, handled_error: nil, unhandled_error: nil, error_event: nil, unexecutable: nil, retried: nil)
|
27
26
|
@value = value
|
28
27
|
@handled_error = handled_error
|
29
28
|
@unhandled_error = unhandled_error
|
@@ -34,7 +33,12 @@ module GoodJob
|
|
34
33
|
|
35
34
|
# @return [Boolean]
|
36
35
|
def succeeded?
|
37
|
-
!(handled_error || unhandled_error || unexecutable || retried)
|
36
|
+
!(handled_error || unhandled_error || unexecutable || retried?)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
def retried?
|
41
|
+
retried.present?
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
data/app/models/good_job/job.rb
CHANGED
@@ -195,12 +195,20 @@ module GoodJob
|
|
195
195
|
# Do not update `exception_executions` because that comes from rescue_from's arguments
|
196
196
|
active_job.executions = (active_job.executions || 0) + 1
|
197
197
|
|
198
|
+
begin
|
199
|
+
error_class, error_message = execution.error.split(GoodJob::Execution::ERROR_MESSAGE_SEPARATOR).map(&:strip)
|
200
|
+
error = error_class.constantize.new(error_message)
|
201
|
+
rescue StandardError
|
202
|
+
error = StandardError.new(execution.error)
|
203
|
+
end
|
204
|
+
|
198
205
|
new_active_job = nil
|
199
206
|
GoodJob::CurrentThread.within do |current_thread|
|
200
207
|
current_thread.execution = execution
|
208
|
+
current_thread.retry_now = true
|
201
209
|
|
202
210
|
execution.class.transaction(joinable: false, requires_new: true) do
|
203
|
-
new_active_job = active_job.retry_job(wait: 0, error:
|
211
|
+
new_active_job = active_job.retry_job(wait: 0, error: error)
|
204
212
|
execution.error_event = ERROR_EVENT_RETRIED if execution.error && execution.class.error_event_migrated?
|
205
213
|
execution.save!
|
206
214
|
end
|
@@ -6,7 +6,7 @@ module GoodJob # :nodoc:
|
|
6
6
|
# ActiveRecord model that represents an GoodJob process (either async or CLI).
|
7
7
|
class Process < BaseRecord
|
8
8
|
include AdvisoryLockable
|
9
|
-
include
|
9
|
+
include OverridableConnection
|
10
10
|
|
11
11
|
# Interval until the process record being updated
|
12
12
|
STALE_INTERVAL = 30.seconds
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%#
|
2
|
+
Content added to this partial will be displayed above the collapsible JSON representation of each execution on the jobs#show view.
|
3
|
+
|
4
|
+
You can make use of the following variables:
|
5
|
+
|
6
|
+
- `job`: The `GoodJob::Job` instance.
|
7
|
+
- `execution`: The `GoodJob::DiscreteExecution` instance.
|
8
|
+
- `main_app`: Use this to access helpers (e.g. route helpers) from your application.
|
9
|
+
|
10
|
+
Note: the `GoodJob::Job` and `GoodJob::Execution` classes are considered an internal implementation detail of GoodJob and may change at any time. Please test your view extensions when upgrading.
|
11
|
+
%>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%#
|
2
|
+
Content added to this partial will be displayed above the argument list on the good_job/jobs#show page.
|
3
|
+
|
4
|
+
You can make use of the following variables:
|
5
|
+
|
6
|
+
- `job`: The `GoodJob::Job` instance. Calling `job.active_job` will deserialize an instance of your ActiveJob class.
|
7
|
+
- `main_app`: Use this to access helpers (e.g. route helpers) from your application.
|
8
|
+
|
9
|
+
Note: the `GoodJob::Job` class is considered an internal implementation detail and may change at any time. Please test your view extensions when upgrading.
|
10
|
+
%>
|
@@ -52,27 +52,27 @@
|
|
52
52
|
|
53
53
|
<div class="dropdown float-end">
|
54
54
|
<button class="d-flex align-items-center btn btn-sm" type="button" id="<%= dom_id(job, :actions) %>" data-bs-toggle="dropdown" aria-expanded="false">
|
55
|
-
<%=
|
55
|
+
<%= render_icon :dots %>
|
56
56
|
<span class="visually-hidden"><%=t ".actions.title" %></span>
|
57
57
|
</button>
|
58
58
|
<ul class="dropdown-menu shadow" aria-labelledby="<%= dom_id(job, :actions) %>">
|
59
59
|
<li>
|
60
60
|
<% job_reschedulable = job.status.in? [:scheduled, :retried, :queued] %>
|
61
61
|
<%= link_to reschedule_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_reschedulable}", title: t(".actions.reschedule"), data: { confirm: t(".actions.confirm_reschedule"), disable: true } do %>
|
62
|
-
<%=
|
62
|
+
<%= render_icon "skip_forward" %>
|
63
63
|
<%=t "good_job.actions.reschedule" %>
|
64
64
|
<% end %>
|
65
65
|
</li>
|
66
66
|
<li>
|
67
67
|
<% job_discardable = job.status.in? [:scheduled, :retried, :queued] %>
|
68
68
|
<%= link_to discard_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_discardable}", title: t(".actions.discard"), data: { confirm: t(".actions.confirm_discard"), disable: true } do %>
|
69
|
-
<%=
|
69
|
+
<%= render_icon "stop" %>
|
70
70
|
<%=t "good_job.actions.discard" %>
|
71
71
|
<% end %>
|
72
72
|
</li>
|
73
73
|
<li>
|
74
74
|
<%= link_to retry_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job.status == :discarded}", title: t(".actions.retry"), data: { confirm: t(".actions.confirm_retry"), disable: true } do %>
|
75
|
-
<%=
|
75
|
+
<%= render_icon "arrow_clockwise" %>
|
76
76
|
<%=t "good_job.actions.retry" %>
|
77
77
|
<% end %>
|
78
78
|
</li>
|
@@ -40,6 +40,7 @@
|
|
40
40
|
</div>
|
41
41
|
<% end %>
|
42
42
|
<% end %>
|
43
|
+
<%= render 'good_job/custom_execution_details', execution: execution, job: @job %>
|
43
44
|
<%= tag.div id: dom_id(execution, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
44
45
|
<%= tag.pre JSON.pretty_generate(execution.display_serialized_params) %>
|
45
46
|
<% end %>
|
@@ -109,34 +109,34 @@
|
|
109
109
|
|
110
110
|
<div class="dropdown float-end">
|
111
111
|
<button class="d-flex align-items-center btn btn-sm" type="button" id="<%= dom_id(job, :actions) %>" data-bs-toggle="dropdown" aria-expanded="false">
|
112
|
-
<%=
|
112
|
+
<%= render_icon "dots" %>
|
113
113
|
<span class="visually-hidden"><%=t ".actions.title" %></span>
|
114
114
|
</button>
|
115
115
|
<ul class="dropdown-menu shadow" aria-labelledby="<%= dom_id(job, :actions) %>">
|
116
116
|
<li>
|
117
117
|
<% job_reschedulable = job.status.in? [:scheduled, :retried, :queued] %>
|
118
118
|
<%= link_to reschedule_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_reschedulable}", title: t("good_job.jobs.actions.reschedule"), data: { confirm: t("good_job.jobs.actions.confirm_reschedule"), disable: true } do %>
|
119
|
-
<%=
|
119
|
+
<%= render_icon "skip_forward" %>
|
120
120
|
<%=t "good_job.actions.reschedule" %>
|
121
121
|
<% end %>
|
122
122
|
</li>
|
123
123
|
<li>
|
124
124
|
<% job_discardable = job.status.in? [:scheduled, :retried, :queued] %>
|
125
125
|
<%= link_to discard_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_discardable}", title: t("good_job.jobs.actions.discard"), data: { confirm: t("good_job.jobs.actions.confirm_discard"), disable: true } do %>
|
126
|
-
<%=
|
126
|
+
<%= render_icon "stop" %>
|
127
127
|
<%=t "good_job.actions.discard" %>
|
128
128
|
<% end %>
|
129
129
|
</li>
|
130
130
|
<li>
|
131
131
|
<% job_force_discardable = job.status.in? [:running] %>
|
132
132
|
<%= link_to force_discard_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job_force_discardable}", title: t("good_job.jobs.actions.force_discard"), data: { confirm: t("good_job.jobs.actions.confirm_force_discard"), disable: true } do %>
|
133
|
-
<%=
|
133
|
+
<%= render_icon "eject" %>
|
134
134
|
<%=t "good_job.actions.force_discard" %>
|
135
135
|
<% end %>
|
136
136
|
</li>
|
137
137
|
<li>
|
138
138
|
<%= link_to retry_job_path(job.id), method: :put, class: "dropdown-item #{'disabled' unless job.status == :discarded}", title: t("good_job.jobs.actions.retry"), data: { confirm: t("good_job.jobs.actions.confirm_retry"), disable: true } do %>
|
139
|
-
<%=
|
139
|
+
<%= render_icon "arrow_clockwise" %>
|
140
140
|
<%=t "good_job.actions.retry" %>
|
141
141
|
<% end %>
|
142
142
|
</li>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<% if notice %>
|
3
3
|
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
4
4
|
<div class="toast-body d-flex align-items-center gap-2">
|
5
|
-
<%=
|
5
|
+
<%= render_icon "check", class: "flex-shrink-0 text-success" %>
|
6
6
|
<div class="flex-fill"><%= notice %></div>
|
7
7
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
8
8
|
</div>
|
@@ -11,7 +11,7 @@
|
|
11
11
|
<% if alert %>
|
12
12
|
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
13
13
|
<div class="toast-body d-flex align-items-center gap-2">
|
14
|
-
<%=
|
14
|
+
<%= render_icon "exclamation", class: "flex-shrink-0 text-danger" %>
|
15
15
|
<div class="flex-fill"><%= alert %></div>
|
16
16
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
17
17
|
</div>
|
@@ -60,7 +60,7 @@
|
|
60
60
|
|
61
61
|
<li class="nav-item d-flex flex-column justify-content-center">
|
62
62
|
<div class="form-check form-switch m-0">
|
63
|
-
<%= check_box_tag "live_poll", params.fetch("poll", 30), params[:poll].present
|
63
|
+
<%= check_box_tag "live_poll", params.fetch("poll", 30), (GoodJob.configuration.dashboard_live_poll_enabled && params[:poll].present?), role: "switch", class: "form-check-input", disabled: !GoodJob.configuration.dashboard_live_poll_enabled %>
|
64
64
|
<label class="form-check-label navbar-text p-0" for="live_poll">
|
65
65
|
<%= t(".live_poll") %>
|
66
66
|
</label>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<!-- https://icons.getbootstrap.com/icons/check-circle/ -->
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle" viewBox="0 0 16 16">
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle <%= local_assigns[:class] %>" viewBox="0 0 16 16">
|
3
3
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
4
4
|
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
|
5
5
|
</svg>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<!-- https://icons.getbootstrap.com/icons/exclamation-circle/ -->
|
2
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-circle" viewBox="0 0 16 16">
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-circle <%= local_assigns[:class] %>" viewBox="0 0 16 16">
|
3
3
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
4
4
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
5
5
|
</svg>
|
data/lib/good_job/adapter.rb
CHANGED
@@ -96,6 +96,13 @@ module GoodJob
|
|
96
96
|
begin
|
97
97
|
inline_execution = inline_executions.shift
|
98
98
|
inline_result = inline_execution.perform
|
99
|
+
|
100
|
+
retried_execution = inline_result.retried
|
101
|
+
while retried_execution && retried_execution.scheduled_at <= Time.current
|
102
|
+
inline_execution = retried_execution
|
103
|
+
inline_result = inline_execution.perform
|
104
|
+
retried_execution = inline_result.retried
|
105
|
+
end
|
99
106
|
ensure
|
100
107
|
inline_execution.advisory_unlock
|
101
108
|
inline_execution.run_callbacks(:perform_unlocked)
|
@@ -141,26 +148,43 @@ module GoodJob
|
|
141
148
|
|
142
149
|
Rails.application.executor.wrap do
|
143
150
|
will_execute_inline = execute_inline? && (scheduled_at.nil? || scheduled_at <= Time.current)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
+
will_retry_inline = will_execute_inline && CurrentThread.execution&.active_job_id == active_job.job_id && !CurrentThread.retry_now
|
152
|
+
|
153
|
+
if will_retry_inline
|
154
|
+
execution = GoodJob::Execution.enqueue(
|
155
|
+
active_job,
|
156
|
+
scheduled_at: scheduled_at
|
157
|
+
)
|
158
|
+
elsif will_execute_inline
|
159
|
+
execution = GoodJob::Execution.enqueue(
|
160
|
+
active_job,
|
161
|
+
scheduled_at: scheduled_at,
|
162
|
+
create_with_advisory_lock: true
|
163
|
+
)
|
151
164
|
begin
|
152
165
|
result = execution.perform
|
166
|
+
|
167
|
+
retried_execution = result.retried
|
168
|
+
while retried_execution && (retried_execution.scheduled_at.nil? || retried_execution.scheduled_at <= Time.current)
|
169
|
+
execution = retried_execution
|
170
|
+
result = execution.perform
|
171
|
+
retried_execution = result.retried
|
172
|
+
end
|
173
|
+
|
174
|
+
Notifier.notify(retried_execution.job_state) if retried_execution&.scheduled_at && retried_execution.scheduled_at > Time.current && send_notify?(active_job)
|
153
175
|
ensure
|
154
176
|
execution.advisory_unlock
|
155
177
|
execution.run_callbacks(:perform_unlocked)
|
156
178
|
end
|
157
179
|
raise result.unhandled_error if result.unhandled_error
|
158
180
|
else
|
159
|
-
|
160
|
-
|
181
|
+
execution = GoodJob::Execution.enqueue(
|
182
|
+
active_job,
|
183
|
+
scheduled_at: scheduled_at
|
184
|
+
)
|
161
185
|
|
162
|
-
executed_locally = execute_async? && @capsule&.create_thread(job_state)
|
163
|
-
Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
|
186
|
+
executed_locally = execute_async? && @capsule&.create_thread(execution.job_state)
|
187
|
+
Notifier.notify(execution.job_state) if !executed_locally && send_notify?(active_job)
|
164
188
|
end
|
165
189
|
|
166
190
|
execution
|
@@ -31,6 +31,8 @@ module GoodJob
|
|
31
31
|
DEFAULT_ENABLE_LISTEN_NOTIFY = true
|
32
32
|
# Default Dashboard I18n locale
|
33
33
|
DEFAULT_DASHBOARD_DEFAULT_LOCALE = :en
|
34
|
+
# Default Dashboard Live Poll button enabled
|
35
|
+
DEFAULT_DASHBOARD_LIVE_POLL_ENABLED = true
|
34
36
|
|
35
37
|
def self.validate_execution_mode(execution_mode)
|
36
38
|
raise ArgumentError, "GoodJob execution mode must be one of #{EXECUTION_MODES.join(', ')}. It was '#{execution_mode}' which is not valid." unless execution_mode.in?(EXECUTION_MODES)
|
@@ -204,9 +206,10 @@ module GoodJob
|
|
204
206
|
|
205
207
|
def cron
|
206
208
|
env_cron = JSON.parse(ENV.fetch('GOOD_JOB_CRON'), symbolize_names: true) if ENV['GOOD_JOB_CRON'].present?
|
209
|
+
rails_config_cron = rails_config[:cron].presence
|
207
210
|
|
208
211
|
options[:cron] ||
|
209
|
-
|
212
|
+
rails_config_cron ||
|
210
213
|
env_cron ||
|
211
214
|
{}
|
212
215
|
end
|
@@ -380,6 +383,12 @@ module GoodJob
|
|
380
383
|
rails_config[:dashboard_default_locale] || DEFAULT_DASHBOARD_DEFAULT_LOCALE
|
381
384
|
end
|
382
385
|
|
386
|
+
def dashboard_live_poll_enabled
|
387
|
+
return rails_config[:dashboard_live_poll_enabled] unless rails_config[:dashboard_live_poll_enabled].nil?
|
388
|
+
|
389
|
+
DEFAULT_DASHBOARD_LIVE_POLL_ENABLED
|
390
|
+
end
|
391
|
+
|
383
392
|
# Whether running in a web server process.
|
384
393
|
# @return [Boolean, nil]
|
385
394
|
def in_webserver?
|
@@ -16,6 +16,7 @@ module GoodJob
|
|
16
16
|
execution
|
17
17
|
execution_interrupted
|
18
18
|
execution_retried
|
19
|
+
retry_now
|
19
20
|
].freeze
|
20
21
|
|
21
22
|
# @!attribute [rw] cron_at
|
@@ -66,6 +67,12 @@ module GoodJob
|
|
66
67
|
# @return [Boolean, nil]
|
67
68
|
thread_mattr_accessor :execution_retried
|
68
69
|
|
70
|
+
# @!attribute [rw] retry_now
|
71
|
+
# @!scope class
|
72
|
+
# Execution Retried
|
73
|
+
# @return [Boolean, nil]
|
74
|
+
thread_mattr_accessor :retry_now
|
75
|
+
|
69
76
|
# Resets attributes
|
70
77
|
# @param [Hash] values to assign
|
71
78
|
# @return [void]
|
data/lib/good_job/daemon.rb
CHANGED
@@ -17,6 +17,7 @@ module GoodJob
|
|
17
17
|
# Daemonizes the current process and writes out a pidfile.
|
18
18
|
# @return [void]
|
19
19
|
def daemonize
|
20
|
+
check_pid_dir
|
20
21
|
check_pid
|
21
22
|
::Process.daemon
|
22
23
|
write_pid
|
@@ -38,6 +39,14 @@ module GoodJob
|
|
38
39
|
File.delete(pidfile) if File.exist?(pidfile) # rubocop:disable Lint/NonAtomicFileOperation
|
39
40
|
end
|
40
41
|
|
42
|
+
# @return [void]
|
43
|
+
def check_pid_dir
|
44
|
+
dirname = File.dirname(pidfile)
|
45
|
+
return if Dir.exist?(dirname)
|
46
|
+
|
47
|
+
abort "Pidfile directory \"#{dirname}\" doesn't exist. Aborting..."
|
48
|
+
end
|
49
|
+
|
41
50
|
# @return [void]
|
42
51
|
def check_pid
|
43
52
|
case pid_status(pidfile)
|
@@ -14,7 +14,7 @@ module GoodJob # :nodoc:
|
|
14
14
|
|
15
15
|
# Registers the current process.
|
16
16
|
def register_process
|
17
|
-
GoodJob::Process.
|
17
|
+
GoodJob::Process.override_connection(connection) do
|
18
18
|
GoodJob::Process.cleanup
|
19
19
|
@process = GoodJob::Process.register
|
20
20
|
end
|
@@ -22,7 +22,7 @@ module GoodJob # :nodoc:
|
|
22
22
|
|
23
23
|
def refresh_process
|
24
24
|
Rails.application.executor.wrap do
|
25
|
-
GoodJob::Process.
|
25
|
+
GoodJob::Process.override_connection(connection) do
|
26
26
|
GoodJob::Process.with_logger_silenced do
|
27
27
|
@process&.refresh_if_stale(cleanup: true)
|
28
28
|
end
|
@@ -32,7 +32,7 @@ module GoodJob # :nodoc:
|
|
32
32
|
|
33
33
|
# Deregisters the current process.
|
34
34
|
def deregister_process
|
35
|
-
GoodJob::Process.
|
35
|
+
GoodJob::Process.override_connection(connection) do
|
36
36
|
@process&.deregister
|
37
37
|
end
|
38
38
|
end
|
@@ -3,36 +3,29 @@
|
|
3
3
|
module GoodJob # :nodoc:
|
4
4
|
# Extends an ActiveRecord odel to override the connection and use
|
5
5
|
# an explicit connection that has been removed from the pool.
|
6
|
-
module
|
6
|
+
module OverridableConnection
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
thread_cattr_accessor :
|
10
|
+
thread_cattr_accessor :_overridden_connection
|
11
11
|
end
|
12
12
|
|
13
13
|
class_methods do
|
14
|
-
# Assigns a connection to the model.
|
15
|
-
# @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
16
|
-
# @return [void]
|
17
|
-
def connection=(conn)
|
18
|
-
self._connection = conn
|
19
|
-
end
|
20
|
-
|
21
14
|
# Overrides the existing connection method to use the assigned connection
|
22
15
|
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
23
16
|
def connection
|
24
|
-
|
17
|
+
_overridden_connection || super
|
25
18
|
end
|
26
19
|
|
27
20
|
# Block interface to assign the connection, yield, then unassign the connection.
|
28
21
|
# @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
29
22
|
# @return [void]
|
30
|
-
def
|
31
|
-
original_conn =
|
32
|
-
self.
|
23
|
+
def override_connection(conn)
|
24
|
+
original_conn = _overridden_connection
|
25
|
+
self._overridden_connection = conn
|
33
26
|
yield
|
34
27
|
ensure
|
35
|
-
self.
|
28
|
+
self._overridden_connection = original_conn
|
36
29
|
end
|
37
30
|
end
|
38
31
|
end
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -15,7 +15,7 @@ require "good_job/active_job_extensions/interrupt_errors"
|
|
15
15
|
require "good_job/active_job_extensions/labels"
|
16
16
|
require "good_job/active_job_extensions/notify_options"
|
17
17
|
|
18
|
-
require "good_job/
|
18
|
+
require "good_job/overridable_connection"
|
19
19
|
require "good_job/bulk"
|
20
20
|
require "good_job/callable"
|
21
21
|
require "good_job/capsule"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -288,6 +288,7 @@ files:
|
|
288
288
|
- app/frontend/good_job/vendor/rails_ujs.js
|
289
289
|
- app/frontend/good_job/vendor/stimulus.js
|
290
290
|
- app/helpers/good_job/application_helper.rb
|
291
|
+
- app/helpers/good_job/icons_helper.rb
|
291
292
|
- app/models/concerns/good_job/advisory_lockable.rb
|
292
293
|
- app/models/concerns/good_job/error_events.rb
|
293
294
|
- app/models/concerns/good_job/filterable.rb
|
@@ -305,6 +306,8 @@ files:
|
|
305
306
|
- app/models/good_job/job.rb
|
306
307
|
- app/models/good_job/process.rb
|
307
308
|
- app/models/good_job/setting.rb
|
309
|
+
- app/views/good_job/_custom_execution_details.html.erb
|
310
|
+
- app/views/good_job/_custom_job_details.html.erb
|
308
311
|
- app/views/good_job/batches/_jobs.erb
|
309
312
|
- app/views/good_job/batches/_table.erb
|
310
313
|
- app/views/good_job/batches/index.html.erb
|
@@ -375,7 +378,6 @@ files:
|
|
375
378
|
- lib/good_job/active_job_extensions/labels.rb
|
376
379
|
- lib/good_job/active_job_extensions/notify_options.rb
|
377
380
|
- lib/good_job/adapter.rb
|
378
|
-
- lib/good_job/assignable_connection.rb
|
379
381
|
- lib/good_job/bulk.rb
|
380
382
|
- lib/good_job/callable.rb
|
381
383
|
- lib/good_job/capsule.rb
|
@@ -394,6 +396,7 @@ files:
|
|
394
396
|
- lib/good_job/multi_scheduler.rb
|
395
397
|
- lib/good_job/notifier.rb
|
396
398
|
- lib/good_job/notifier/process_heartbeat.rb
|
399
|
+
- lib/good_job/overridable_connection.rb
|
397
400
|
- lib/good_job/poller.rb
|
398
401
|
- lib/good_job/probe_server.rb
|
399
402
|
- lib/good_job/probe_server/healthcheck_middleware.rb
|