good_job 3.24.0 → 3.26.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b464ea5a307aa615b5800649e4ff4083276900fe42bfb32edfc9ed66c470914
4
- data.tar.gz: 7ff522ebb69f55a222a089ddc7c2b1fc2e0835603bcf515a630b2b27a2e90b7a
3
+ metadata.gz: a51ff44544ae7fb381fce50f13d3196d6a561bcdc6a5af7f37ad1e8fb0d6b258
4
+ data.tar.gz: 3703fe631d3f549bb152999203e5d67ac6d305c4ac77bba5b47621f3419cef21
5
5
  SHA512:
6
- metadata.gz: ae2eb90d2b35711d3d4ade8ed678bba89e36fa32eb36b6bb291a4b99fbe0d60b4ea2a143521f9b20b6d386b745d690f8e6b99467b0ec1b05d9e44709f71a6b7f
7
- data.tar.gz: a695f55188543d9ea6d53f1f25c4e3cfb311d4a4d7dddbed342b8034e5d09cf911f212e57fe4fba0ab64c0cc8ce0f9dcb2529756dc9c701403e5e6ee1b8113e7
6
+ metadata.gz: ba6647af970ee04455977ccdfb835ac03648bc544ae3606e69e2007d04bf13634f0767c1263738d04101a6839949affd3227a19b1b573d27a6cb8bb4ea56a5b6
7
+ data.tar.gz: 3c8e79ae13f69d050f35aea95ebaf4c035a97138a89d24c57430776909d44c9fbbd9a5ab4460ca30efd65c2c16ec231780957c555f43a77dc100388dbfffb341
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.26.0](https://github.com/bensheldon/good_job/tree/v3.26.0) (2024-03-01)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.25.0...v3.26.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add `GoodJob.current_thread_running?` and `GoodJob.current_thread_shutting_down?` for graceful shutdowns [\#1253](https://github.com/bensheldon/good_job/pull/1253) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Ensure "shutdown?" behavior is consistent between J Ruby and C Ruby [\#1267](https://github.com/bensheldon/good_job/pull/1267) ([bensheldon](https://github.com/bensheldon))
14
+
15
+ **Closed issues:**
16
+
17
+ - PG Good Job rows add up [\#1262](https://github.com/bensheldon/good_job/issues/1262)
18
+ - Bulk operations do not work for Batches [\#1255](https://github.com/bensheldon/good_job/issues/1255)
19
+ - What's the difference between 'reschedule' and 'retry' in the dashboard? [\#1241](https://github.com/bensheldon/good_job/issues/1241)
20
+
21
+ **Merged pull requests:**
22
+
23
+ - feat: add italian locale [\#1268](https://github.com/bensheldon/good_job/pull/1268) ([metalelf0](https://github.com/metalelf0))
24
+
25
+ ## [v3.25.0](https://github.com/bensheldon/good_job/tree/v3.25.0) (2024-02-22)
26
+
27
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.24.0...v3.25.0)
28
+
29
+ **Implemented enhancements:**
30
+
31
+ - Allow disabling of Dashboard Live Polling configuration [\#1235](https://github.com/bensheldon/good_job/pull/1235) ([erick-tmr](https://github.com/erick-tmr))
32
+ - Add customizable extension partials to good\_job/jobs\#show view [\#1200](https://github.com/bensheldon/good_job/pull/1200) ([grncdr](https://github.com/grncdr))
33
+
34
+ **Fixed bugs:**
35
+
36
+ - Fix default engine cron value [\#1258](https://github.com/bensheldon/good_job/pull/1258) ([hss-mateus](https://github.com/hss-mateus))
37
+ - Print an error when daemon pidfile dir doesn't exist [\#1252](https://github.com/bensheldon/good_job/pull/1252) ([thepry](https://github.com/thepry))
38
+
39
+ **Closed issues:**
40
+
41
+ - Production deployment question [\#1257](https://github.com/bensheldon/good_job/issues/1257)
42
+ - Daemon and App not connecting to secondary database [\#1254](https://github.com/bensheldon/good_job/issues/1254)
43
+ - Logging with logger.warn in classes is suppressed by good job? \(semantic\_logger\) [\#1250](https://github.com/bensheldon/good_job/issues/1250)
44
+
45
+ **Merged pull requests:**
46
+
47
+ - Fix Active Record connection changes on Rails head [\#1259](https://github.com/bensheldon/good_job/pull/1259) ([bensheldon](https://github.com/bensheldon))
48
+ - \[Docs\] Bulk.enqueue takes an array of jobs [\#1256](https://github.com/bensheldon/good_job/pull/1256) ([jpcamara](https://github.com/jpcamara))
49
+ - Clean up icon helpers for less noisy view rendering [\#1248](https://github.com/bensheldon/good_job/pull/1248) ([bensheldon](https://github.com/bensheldon))
50
+ - Use dotenv-rails instead of dotenv [\#1247](https://github.com/bensheldon/good_job/pull/1247) ([bensheldon](https://github.com/bensheldon))
51
+ - Perform inline retries iteratively instead of recursively [\#1246](https://github.com/bensheldon/good_job/pull/1246) ([bensheldon](https://github.com/bensheldon))
52
+
3
53
  ## [v3.24.0](https://github.com/bensheldon/good_job/tree/v3.24.0) (2024-02-12)
4
54
 
5
55
  [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)
@@ -56,7 +57,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
56
57
  - [Exceptions](#exceptions)
57
58
  - [Retries](#retries)
58
59
  - [Action Mailer retries](#action-mailer-retries)
59
- - [Interrupts](#interrupts)
60
+ - [Interrupts, graceful shutdown, and SIGKILL](#Interrupts-graceful-shutdown-and-SIGKILL)
60
61
  - [Timeouts](#timeouts)
61
62
  - [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes)
62
63
  - [Database connections](#database-connections)
@@ -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
@@ -937,9 +974,24 @@ end
937
974
  Note, that `ActionMailer::MailDeliveryJob` is a default since Rails 6.0. Be sure that your app is using that class, as it
938
975
  might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`.
939
976
 
940
- ### Interrupts
977
+ ### Interrupts, graceful shutdown, and SIGKILL
978
+
979
+ When GoodJob receives an interrupt (SIGINT, SIGTERM) or explicitly with `GoodJob.shutdown`, GoodJob will attempt to gracefully shut down, waiting for all jobs to finish before exiting based on the `shutdown_timeout` configuration.
980
+
981
+ To detect the start of a graceful shutdown from within a performing job, for example while looping/iterating over multiple items, you can call `GoodJob.current_thread_shutting_down?` or `GoodJob.current_thread_running?` from within the job. For example:
982
+
983
+ ```ruby
984
+ def perform(lots_of_records)
985
+ lots_of_records.each do |record|
986
+ break if GoodJob.current_thread_shutting_down? # or `unless GoodJob.current_thread.running?`
987
+ # process record ...
988
+ end
989
+ end
990
+ ````
991
+
992
+ Note that when running jobs in `:inline` execution mode, `GoodJob.current_thread_running?` will always be truthy and `GoodJob.current_thread_shutting_down?` will always be falsey.
941
993
 
942
- Jobs will be automatically retried if the process is interrupted while performing a job, for example as the result of a `SIGKILL` or power failure.
994
+ Jobs will be automatically retried if the process is interrupted while performing a job and the job is unable to finish before the timeout or as the result of a `SIGKILL` or power failure.
943
995
 
944
996
  If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use Active Job's `retry_on` and `discard_on` to control the behavior of the job.
945
997
 
@@ -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 = true
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
- if performed_at
376
- current_thread.execution_interrupted = performed_at
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 '#{performed_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: Time.current,
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: now
403
+ created_at: job_performed_at
401
404
  )
402
- update!(performed_at: now, executions_count: ((executions_count || 0) + 1))
405
+ update!(performed_at: job_performed_at, executions_count: ((executions_count || 0) + 1))
403
406
  end
404
407
  else
405
- update!(performed_at: Time.current)
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
- job_error = result.handled_error || result.unhandled_error
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
- self.error = error_string
453
- self.error_event = result.error_event if self.class.error_event_migrated?
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 if discrete_execution.class.error_event_migrated?
461
+ discrete_execution.error_event = result.error_event
457
462
  end
458
463
  else
459
- self.error = nil
460
- self.error_event = nil if self.class.error_event_migrated?
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
- reenqueued = result.retried? || retried_good_job_id.present?
464
- if result.unhandled_error && GoodJob.retry_on_unhandled_error
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
- transaction do
467
- discrete_execution.update!(finished_at: Time.current)
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
- save!
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
- elsif GoodJob.preserve_job_records == true || reenqueued || (result.unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error) || cron_key.present?
474
- now = Time.current
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
- save!
490
+ update!(job_attributes)
485
491
  end
486
492
  else
487
- self.finished_at = now
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 [Boolean, nil]
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: false)
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
@@ -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: execution.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 AssignableConnection
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
- <%= render "good_job/shared/icons/dots" %>
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
- <%= render "good_job/shared/icons/skip_forward" %>
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
- <%= render "good_job/shared/icons/stop" %>
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
- <%= render "good_job/shared/icons/arrow_clockwise" %>
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
- <%= render "good_job/shared/icons/dots" %>
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
- <%= render "good_job/shared/icons/skip_forward" %>
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
- <%= render "good_job/shared/icons/stop" %>
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
- <%= render "good_job/shared/icons/eject" %>
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
- <%= render "good_job/shared/icons/arrow_clockwise" %>
139
+ <%= render_icon "arrow_clockwise" %>
140
140
  <%=t "good_job.actions.retry" %>
141
141
  <% end %>
142
142
  </li>
@@ -67,6 +67,8 @@
67
67
  </div>
68
68
  </div>
69
69
 
70
+ <%= render 'good_job/custom_job_details', job: @job %>
71
+
70
72
  <div class="my-4">
71
73
  <h5>
72
74
  <%= t "good_job.models.job.arguments" %>
@@ -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
- <%= render "good_job/shared/icons/check", class: "flex-shrink-0 text-success" %>
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
- <%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 text-danger" %>
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?, role: "switch", class: "form-check-input" %>
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>