good_job 3.15.14 → 3.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +20 -3
  4. data/app/controllers/good_job/application_controller.rb +16 -9
  5. data/app/controllers/good_job/batches_controller.rb +1 -0
  6. data/app/controllers/good_job/cron_entries_controller.rb +2 -1
  7. data/app/controllers/good_job/frontends_controller.rb +1 -0
  8. data/app/controllers/good_job/jobs_controller.rb +1 -0
  9. data/app/controllers/good_job/processes_controller.rb +1 -0
  10. data/app/filters/good_job/base_filter.rb +1 -0
  11. data/app/filters/good_job/batches_filter.rb +1 -0
  12. data/app/filters/good_job/jobs_filter.rb +1 -0
  13. data/app/helpers/good_job/application_helper.rb +1 -1
  14. data/app/models/concerns/good_job/error_events.rb +28 -0
  15. data/app/models/concerns/good_job/filterable.rb +1 -0
  16. data/app/models/{good_job → concerns/good_job}/lockable.rb +1 -0
  17. data/app/models/concerns/good_job/reportable.rb +1 -0
  18. data/app/models/good_job/active_record_parent_class.rb +9 -0
  19. data/app/models/good_job/base_execution.rb +9 -0
  20. data/app/models/good_job/base_record.rb +4 -1
  21. data/app/models/good_job/cron_entry.rb +1 -0
  22. data/app/models/good_job/discrete_execution.rb +9 -0
  23. data/app/models/good_job/execution.rb +41 -8
  24. data/app/models/good_job/execution_result.rb +17 -2
  25. data/app/models/good_job/i18n_config.rb +25 -0
  26. data/app/models/good_job/job.rb +4 -1
  27. data/app/models/good_job/process.rb +13 -2
  28. data/app/views/good_job/jobs/_executions.erb +1 -1
  29. data/app/views/good_job/jobs/_table.erb +17 -4
  30. data/app/views/good_job/processes/index.html.erb +2 -2
  31. data/app/views/good_job/shared/_navbar.erb +1 -1
  32. data/config/locales/de.yml +8 -1
  33. data/config/locales/en.yml +8 -1
  34. data/config/locales/es.yml +8 -1
  35. data/config/locales/fr.yml +8 -1
  36. data/config/locales/ja.yml +8 -1
  37. data/config/locales/nl.yml +8 -1
  38. data/config/locales/ru.yml +8 -1
  39. data/config/locales/tr.yml +8 -1
  40. data/config/locales/{ua.yml → uk.yml} +33 -2
  41. data/config/routes.rb +1 -0
  42. data/exe/good_job +1 -0
  43. data/lib/active_job/queue_adapters/good_job_adapter.rb +1 -0
  44. data/lib/generators/good_job/install_generator.rb +1 -0
  45. data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +5 -1
  46. data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +3 -1
  47. data/lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb +1 -0
  48. data/lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb +1 -0
  49. data/lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb +1 -0
  50. data/lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb +1 -0
  51. data/lib/generators/good_job/templates/update/migrations/06_create_good_jobs_error_event.rb.erb +16 -0
  52. data/lib/generators/good_job/update_generator.rb +1 -0
  53. data/lib/good_job/active_job_extensions/batches.rb +1 -0
  54. data/lib/good_job/active_job_extensions/concurrency.rb +1 -0
  55. data/lib/good_job/active_job_extensions/interrupt_errors.rb +1 -0
  56. data/lib/good_job/active_job_extensions/notify_options.rb +1 -0
  57. data/lib/good_job/adapter.rb +3 -2
  58. data/lib/good_job/assignable_connection.rb +1 -0
  59. data/lib/good_job/bulk.rb +1 -0
  60. data/lib/good_job/capsule.rb +4 -3
  61. data/lib/good_job/cleanup_tracker.rb +2 -1
  62. data/lib/good_job/cli.rb +1 -0
  63. data/lib/good_job/configuration.rb +7 -0
  64. data/lib/good_job/cron_manager.rb +3 -3
  65. data/lib/good_job/current_thread.rb +8 -0
  66. data/lib/good_job/daemon.rb +1 -0
  67. data/lib/good_job/engine.rb +13 -0
  68. data/lib/good_job/interrupt_error.rb +1 -0
  69. data/lib/good_job/job_performer.rb +1 -0
  70. data/lib/good_job/log_subscriber.rb +1 -0
  71. data/lib/good_job/metrics.rb +57 -0
  72. data/lib/good_job/multi_scheduler.rb +1 -0
  73. data/lib/good_job/notifier.rb +3 -3
  74. data/lib/good_job/poller.rb +3 -3
  75. data/lib/good_job/scheduler.rb +30 -11
  76. data/lib/good_job/version.rb +2 -1
  77. data/lib/good_job.rb +15 -1
  78. metadata +9 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2afabac45c47dd0326312ac0f111aaff7c977cc33baded1c2c0b97b824e1510
4
- data.tar.gz: 5d36fd716c45bb4874c5431945b5423e0385d1e49bb32b98f36902679f62511b
3
+ metadata.gz: 20bb64485159969378a5abf5d960de13a3f27df63846f85cda6e4ef410c9af23
4
+ data.tar.gz: de0d39b68577c973ca4a4c9d9bccd8bda29bad7e6ca5a936112db7c8df6b038b
5
5
  SHA512:
6
- metadata.gz: 4459b866f3d1f266b5e587e21f5fae9bb30a3deea54716f35658581adbacd09cefe470ef9f0d04f0b8cda53f330fb3ba20f69d706670353bd2615cb44ad6f38c
7
- data.tar.gz: d37e446d66085c33665bc0e93b5caff2cd249baa0b3394b337e6023f1d9803faea4622a7ffac96d4817196a10ba0a762db57648846f88bc25d1455928fad8865
6
+ metadata.gz: 9fa9474d0ee2ff9e8e47aa5e3cc31d6cd1924c7d00276a9da2bc4611b970ea80d68a0bc79c9da7f4266c85e0bedfc491860dabff4d4e1088d485a11a21a895f5
7
+ data.tar.gz: eb6a2ea5cdacd25fd930aab7eba6b498ffb5998fa82c78e552f7acaebf59f6f6cd950fb6488e4c317b461ff8931df5250820cdeb46d40a4047a1e8c24495756b
data/CHANGELOG.md CHANGED
@@ -1,12 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.16.0](https://github.com/bensheldon/good_job/tree/v3.16.0) (2023-07-10)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.15.14...v3.16.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add `GoodJob.configure_active_record` as alternative to `GoodJob.active_record_parent_class` [\#1004](https://github.com/bensheldon/good_job/pull/1004) ([bensheldon](https://github.com/bensheldon))
10
+ - Configure `dashboard_default_locale` using custom subclass of `I18n::Config` to isolate I18n configuration from parent application [\#1001](https://github.com/bensheldon/good_job/pull/1001) ([bensheldon](https://github.com/bensheldon))
11
+ - Create `error_event` column to track the context of an error \(discarded, retried, retry\_stopped, etc\) [\#995](https://github.com/bensheldon/good_job/pull/995) ([bensheldon](https://github.com/bensheldon))
12
+ - Added metrics to Scheduler and track in Process state [\#984](https://github.com/bensheldon/good_job/pull/984) ([AndersGM](https://github.com/AndersGM))
13
+
14
+ **Fixed bugs:**
15
+
16
+ - Use Concurrent::Array for class `instances` to avoid JRuby synchronization errors [\#1002](https://github.com/bensheldon/good_job/pull/1002) ([bensheldon](https://github.com/bensheldon))
17
+ - Add test to assert enqueuing behavior within transactions [\#998](https://github.com/bensheldon/good_job/pull/998) ([bensheldon](https://github.com/bensheldon))
18
+ - Fix Ukrainian language code [\#996](https://github.com/bensheldon/good_job/pull/996) ([bensheldon](https://github.com/bensheldon))
19
+
20
+ **Closed issues:**
21
+
22
+ - Is `pgcrypto` necessary? [\#805](https://github.com/bensheldon/good_job/issues/805)
23
+ - Integrate Sorbet type checking [\#404](https://github.com/bensheldon/good_job/issues/404)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Unify `frozen_string_literal` comment style [\#1003](https://github.com/bensheldon/good_job/pull/1003) ([dixpac](https://github.com/dixpac))
28
+ - Add more execution mode details and caveats to Readme Set Up section [\#997](https://github.com/bensheldon/good_job/pull/997) ([bensheldon](https://github.com/bensheldon))
29
+ - Add note in migrations that `pgcrypto` extension isn't necessary in PG 13+ [\#837](https://github.com/bensheldon/good_job/pull/837) ([bensheldon](https://github.com/bensheldon))
30
+ - Add Sorbet to linter [\#760](https://github.com/bensheldon/good_job/pull/760) ([sam1el](https://github.com/sam1el))
31
+
3
32
  ## [v3.15.14](https://github.com/bensheldon/good_job/tree/v3.15.14) (2023-07-03)
4
33
 
5
34
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.15.13...v3.15.14)
6
35
 
36
+ **Implemented enhancements:**
37
+
38
+ - Add Process heartbeat that is updated inside of Notifier [\#977](https://github.com/bensheldon/good_job/pull/977) ([bensheldon](https://github.com/bensheldon))
39
+
7
40
  **Fixed bugs:**
8
41
 
9
42
  - Dashboard error when trying to display 0 running/queued/retried jobs due to missing 'zero' translation [\#990](https://github.com/bensheldon/good_job/issues/990)
43
+ - Add explicit namespace back to `GoodJob::DiscreteExecution` [\#983](https://github.com/bensheldon/good_job/pull/983) ([bensheldon](https://github.com/bensheldon))
10
44
 
11
45
  **Closed issues:**
12
46
 
@@ -20,8 +54,6 @@
20
54
  - Bump rails from 7.0.5 to 7.0.6 [\#993](https://github.com/bensheldon/good_job/pull/993) ([dependabot[bot]](https://github.com/apps/dependabot))
21
55
  - Fix CI: Lock traces version for Ruby 2.6 compatible version [\#987](https://github.com/bensheldon/good_job/pull/987) ([bensheldon](https://github.com/bensheldon))
22
56
  - Turkish Language support [\#986](https://github.com/bensheldon/good_job/pull/986) ([SemihCag](https://github.com/SemihCag))
23
- - Add explicit namespace back to `GoodJob::DiscreteExecution` [\#983](https://github.com/bensheldon/good_job/pull/983) ([bensheldon](https://github.com/bensheldon))
24
- - Add Process heartbeat that is updated inside of Notifier [\#977](https://github.com/bensheldon/good_job/pull/977) ([bensheldon](https://github.com/bensheldon))
25
57
  - Use generic error reporter in Readme examples [\#964](https://github.com/bensheldon/good_job/pull/964) ([shouichi](https://github.com/shouichi))
26
58
 
27
59
  ## [v3.15.13](https://github.com/bensheldon/good_job/tree/v3.15.13) (2023-06-14)
data/README.md CHANGED
@@ -121,8 +121,13 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
121
121
  YourJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later
122
122
  ```
123
123
 
124
- 1. In development, GoodJob executes jobs immediately in a separate thread ("async" mode). In production, GoodJob provides different options:
125
-
124
+ 1. **In Rails' development environment**, by default, GoodJob's Adapter executes jobs `async` in a background thread pool in `rails server`.
125
+ - Because of Rails deferred autoloading, jobs enqueued via the `rails console` may not begin executing on a separate server process until the Rails application is fully initialized by loading a web page once.
126
+ - Remember, only Active Job's `perform_later` sends jobs to the queue adapter; Active Job's `perform_now` executes the job immediately and does not invoke the queue adapter. GoodJob is not involved in `perform_now` jobs.
127
+ 1. **In Rails' test environment**, by default, GoodJob's Adapter executes jobs `inline` immediately in the current thread.
128
+ - Future-scheduled jobs can be executed with `GoodJob.perform_inline` using using a tool like Timecop or `ActiveSupport::Testing::TimeHelpers`.
129
+ - Note that Active Job's TestAdapter, which powers test helpers (e.g. `assert_enqueued_with`), may override GoodJob's Adapter in [some configurations](https://github.com/rails/rails/issues/37270).
130
+ 1. **In Rails' production environment**, by default, GoodJob's Adapter enqueues jobs in `external` mode to be executed by a separate execution process:
126
131
  - By default, GoodJob separates job enqueuing from job execution so that jobs can be scaled independently of the web server. Use the GoodJob command-line tool to execute jobs:
127
132
 
128
133
  ```bash
@@ -239,6 +244,7 @@ Rails.application.configure do
239
244
  config.good_job.shutdown_timeout = 25 # seconds
240
245
  config.good_job.enable_cron = true
241
246
  config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
247
+ config.good_job.dashboard_default_locale = :en
242
248
 
243
249
  # ...or all at once.
244
250
  config.good_job = {
@@ -257,6 +263,7 @@ Rails.application.configure do
257
263
  class: 'ExampleJob'
258
264
  },
259
265
  },
266
+ dashboard_default_locale: :en,
260
267
  }
261
268
  end
262
269
  ```
@@ -311,7 +318,17 @@ config.good_job.execution_mode = :external
311
318
 
312
319
  Good Job’s general behavior can also be configured via attributes directly on the `GoodJob` module:
313
320
 
314
- - **`GoodJob.active_record_parent_class`** (string) The ActiveRecord parent class inherited by GoodJob's ActiveRecord model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this when using [multiple databases with ActiveRecord](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the ActiveRecord model to connect to the Postgres database. _The value must be a String to avoid premature initialization of ActiveRecord._
321
+ - **`GoodJob.configure_active_record { ... }`** Inject Active Record configuration into GoodJob's base model, for example, when using [multiple databases with ActiveRecord](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the ActiveRecord model to connect to the Postgres database. Example:
322
+
323
+ ```ruby
324
+ # config/initializers/good_job.rb
325
+ GoodJob.configure_active_record do
326
+ connects_to database: :special_database
327
+ self.table_name_prefix = "special_application_"
328
+ end
329
+ ```
330
+
331
+ - **`GoodJob.active_record_parent_class`** (string) Alternatively, modify the ActiveRecord parent class inherited by GoodJob's Active Record model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this _The value must be a String to avoid premature initialization of ActiveRecord._
315
332
 
316
333
  You’ll generally want to configure these in `config/initializers/good_job.rb`, like so:
317
334
 
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class ApplicationController < ActionController::Base
4
5
  protect_from_forgery with: :exception
5
6
 
6
- around_action :switch_locale
7
+ around_action :use_good_job_locale
7
8
 
8
9
  content_security_policy do |policy|
9
10
  policy.default_src(:none) if policy.default_src(*policy.default_src).blank?
@@ -30,8 +31,21 @@ module GoodJob
30
31
  { locale: I18n.locale }.merge(options)
31
32
  end
32
33
 
33
- def switch_locale(&action)
34
+ def use_good_job_locale(&action)
35
+ @original_i18n_config = I18n.config
36
+ I18n.config = ::GoodJob::I18nConfig.new
34
37
  I18n.with_locale(current_locale, &action)
38
+ ensure
39
+ I18n.config = @original_i18n_config
40
+ @original_i18n_config = nil
41
+ end
42
+
43
+ def use_original_locale
44
+ prev_config = I18n.config
45
+ I18n.config = @original_i18n_config if @original_i18n_config
46
+ yield
47
+ ensure
48
+ I18n.config = prev_config
35
49
  end
36
50
 
37
51
  def current_locale
@@ -39,16 +53,9 @@ module GoodJob
39
53
  request.GET['locale']
40
54
  elsif params[:locale]
41
55
  params[:locale]
42
- elsif good_job_available_locales.exclude?(I18n.default_locale) && I18n.available_locales.include?(:en)
43
- :en
44
56
  else
45
57
  I18n.default_locale
46
58
  end
47
59
  end
48
-
49
- def good_job_available_locales
50
- @_good_job_available_locales ||= GoodJob::Engine.root.join("config/locales").glob("*.yml").map { |path| File.basename(path, ".yml").to_sym }.uniq
51
- end
52
- helper_method :good_job_available_locales
53
60
  end
54
61
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class BatchesController < GoodJob::ApplicationController
4
5
  def index
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class CronEntriesController < GoodJob::ApplicationController
4
5
  before_action :check_settings_migration!, only: [:enable, :disable]
@@ -14,7 +15,7 @@ module GoodJob
14
15
 
15
16
  def enqueue
16
17
  @cron_entry = CronEntry.find(params[:cron_key])
17
- @cron_entry.enqueue(Time.current)
18
+ use_original_locale { @cron_entry.enqueue(Time.current) }
18
19
  redirect_back(fallback_location: cron_entries_path, notice: t(".notice"))
19
20
  end
20
21
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class FrontendsController < ActionController::Base # rubocop:disable Rails/ApplicationController
4
5
  skip_after_action :verify_same_origin_request, raise: false
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class JobsController < GoodJob::ApplicationController
4
5
  DISCARD_MESSAGE = "Discarded through dashboard"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class ProcessesController < GoodJob::ApplicationController
4
5
  def index
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class BaseFilter
4
5
  DEFAULT_LIMIT = 25
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class BatchesFilter < BaseFilter
4
5
  def records
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  class JobsFilter < BaseFilter
4
5
  def states
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module ApplicationHelper
4
5
  def format_duration(sec)
@@ -63,7 +64,6 @@ module GoodJob
63
64
  end
64
65
 
65
66
  def translation_exists?(key, **options)
66
- true if good_job_available_locales.include?(I18n.locale)
67
67
  I18n.exists?(scope_key_by_partial(key), **options)
68
68
  end
69
69
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ # Shared methods for filtering Execution/Job records from the +good_jobs+ table.
5
+ module ErrorEvents
6
+ extend ActiveSupport::Concern
7
+
8
+ ERROR_EVENTS = [
9
+ ERROR_EVENT_INTERRUPTED = 'interrupted',
10
+ ERROR_EVENT_UNHANDLED = 'unhandled',
11
+ ERROR_EVENT_HANDLED = 'handled',
12
+ ERROR_EVENT_RETRIED = 'retried',
13
+ ERROR_EVENT_RETRY_STOPPED = 'retry_stopped',
14
+ ERROR_EVENT_DISCARDED = 'discarded',
15
+ ].freeze
16
+
17
+ included do
18
+ enum error_event: {
19
+ ERROR_EVENT_INTERRUPTED => 0,
20
+ ERROR_EVENT_UNHANDLED => 1,
21
+ ERROR_EVENT_HANDLED => 2,
22
+ ERROR_EVENT_RETRIED => 3,
23
+ ERROR_EVENT_RETRY_STOPPED => 4,
24
+ ERROR_EVENT_DISCARDED => 5,
25
+ }.freeze, _prefix: :error_event
26
+ end
27
+ end
28
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Shared methods for filtering Execution/Job records from the +good_jobs+ table.
4
5
  module Filterable
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # Adds Postgres advisory locking capabilities to an ActiveRecord record.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module Reportable
4
5
  # There are 3 buckets of non-overlapping statuses:
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ ActiveRecordParentClass = if GoodJob.active_record_parent_class
5
+ Object.const_get(GoodJob.active_record_parent_class)
6
+ else
7
+ ActiveRecord::Base
8
+ end
9
+ end
@@ -4,6 +4,8 @@ module GoodJob
4
4
  # ActiveRecord model to share behavior between {Job} and {Execution} models
5
5
  # which both read out of the same table.
6
6
  class BaseExecution < BaseRecord
7
+ include ErrorEvents
8
+
7
9
  self.table_name = 'good_jobs'
8
10
 
9
11
  # With a given class name
@@ -37,6 +39,13 @@ module GoodJob
37
39
  def discrete_support?
38
40
  GoodJob::DiscreteExecution.migrated?
39
41
  end
42
+
43
+ def error_event_migrated?
44
+ return true if columns_hash["error_event"].present?
45
+
46
+ migration_pending_warning!
47
+ false
48
+ end
40
49
  end
41
50
 
42
51
  # The ActiveJob job class, as a string
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Base ActiveRecord class that all GoodJob models inherit from.
4
5
  # Parent class can be configured with +GoodJob.active_record_parent_class+.
5
6
  # @!parse
6
7
  # class BaseRecord < ActiveRecord::Base; end
7
- class BaseRecord < Object.const_get(GoodJob.active_record_parent_class)
8
+ class BaseRecord < ActiveRecordParentClass
8
9
  self.abstract_class = true
9
10
 
10
11
  def self.migration_pending_warning!
@@ -26,5 +27,7 @@ module GoodJob
26
27
  migration_pending_warning!
27
28
  false
28
29
  end
30
+
31
+ ActiveSupport.run_load_hooks(:good_job_base_record, self)
29
32
  end
30
33
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent/hash"
3
4
  require "concurrent/scheduled_task"
4
5
  require "fugit"
@@ -2,6 +2,8 @@
2
2
 
3
3
  module GoodJob # :nodoc:
4
4
  class DiscreteExecution < BaseRecord
5
+ include ErrorEvents
6
+
5
7
  self.table_name = 'good_job_executions'
6
8
 
7
9
  belongs_to :execution, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
@@ -11,6 +13,13 @@ module GoodJob # :nodoc:
11
13
 
12
14
  alias_attribute :performed_at, :created_at
13
15
 
16
+ def self.error_event_migrated?
17
+ return true if columns_hash["error_event"].present?
18
+
19
+ migration_pending_warning!
20
+ false
21
+ end
22
+
14
23
  def number
15
24
  serialized_params.fetch('executions', 0) + 1
16
25
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # ActiveRecord model that represents an +ActiveJob+ job.
4
5
  class Execution < BaseExecution
@@ -262,7 +263,11 @@ module GoodJob
262
263
  unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(unlock_session: true, select_limit: queue_select_limit) do |executions|
263
264
  execution = executions.first
264
265
  break if execution.blank?
265
- break :unlocked unless execution&.executable?
266
+
267
+ unless execution.executable?
268
+ result = ExecutionResult.new(value: nil, unexecutable: true)
269
+ break
270
+ end
266
271
 
267
272
  yield(execution) if block_given?
268
273
  result = execution.perform
@@ -373,10 +378,14 @@ module GoodJob
373
378
  if discrete?
374
379
  interrupt_error_string = self.class.format_error(GoodJob::InterruptError.new("Interrupted after starting perform at '#{performed_at}'"))
375
380
  self.error = interrupt_error_string
376
- discrete_executions.where(finished_at: nil).where.not(performed_at: nil).update_all( # rubocop:disable Rails/SkipsModelValidations
381
+ self.error_event = ERROR_EVENT_INTERRUPTED if self.class.error_event_migrated?
382
+
383
+ discrete_execution_attrs = {
377
384
  error: interrupt_error_string,
378
- finished_at: Time.current
379
- )
385
+ finished_at: Time.current,
386
+ }
387
+ discrete_execution_attrs[:error_event] = GoodJob::DiscreteExecution.error_events[GoodJob::DiscreteExecution::ERROR_EVENT_INTERRUPTED] if self.class.error_event_migrated?
388
+ discrete_executions.where(finished_at: nil).where.not(performed_at: nil).update_all(discrete_execution_attrs) # rubocop:disable Rails/SkipsModelValidations
380
389
  end
381
390
  end
382
391
 
@@ -405,15 +414,34 @@ module GoodJob
405
414
  end
406
415
  handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
407
416
 
417
+ error_event = if handled_error == current_thread.error_on_discard
418
+ ERROR_EVENT_DISCARDED
419
+ elsif handled_error == current_thread.error_on_retry
420
+ ERROR_EVENT_RETRIED
421
+ elsif handled_error == current_thread.error_on_retry_stopped
422
+ ERROR_EVENT_RETRY_STOPPED
423
+ elsif handled_error
424
+ ERROR_EVENT_HANDLED
425
+ end
426
+
408
427
  instrument_payload.merge!(
409
428
  value: value,
410
429
  handled_error: handled_error,
411
- retried: current_thread.execution_retried
430
+ retried: current_thread.execution_retried,
431
+ error_event: error_event
412
432
  )
413
- ExecutionResult.new(value: value, handled_error: handled_error, retried: current_thread.execution_retried)
433
+ ExecutionResult.new(value: value, handled_error: handled_error, error_event: error_event, retried: current_thread.execution_retried)
414
434
  rescue StandardError => e
435
+ error_event = if e.is_a?(GoodJob::InterruptError)
436
+ ERROR_EVENT_INTERRUPTED
437
+ elsif e == current_thread.error_on_retry_stopped
438
+ ERROR_EVENT_RETRY_STOPPED
439
+ else
440
+ ERROR_EVENT_UNHANDLED
441
+ end
442
+
415
443
  instrument_payload[:unhandled_error] = e
416
- ExecutionResult.new(value: nil, unhandled_error: e)
444
+ ExecutionResult.new(value: nil, unhandled_error: e, error_event: error_event)
417
445
  end
418
446
  end
419
447
 
@@ -422,9 +450,14 @@ module GoodJob
422
450
  if job_error
423
451
  error_string = self.class.format_error(job_error)
424
452
  self.error = error_string
425
- discrete_execution.error = error_string if discrete_execution
453
+ self.error_event = result.error_event if self.class.error_event_migrated?
454
+ if discrete_execution
455
+ discrete_execution.error = error_string
456
+ discrete_execution.error_event = result.error_event if discrete_execution.class.error_event_migrated?
457
+ end
426
458
  else
427
459
  self.error = nil
460
+ self.error_event = nil if self.class.error_event_migrated?
428
461
  end
429
462
 
430
463
  reenqueued = result.retried? || retried_good_job_id.present?
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Stores the results of job execution
4
5
  class ExecutionResult
@@ -8,18 +9,32 @@ module GoodJob
8
9
  attr_reader :handled_error
9
10
  # @return [Exception, nil]
10
11
  attr_reader :unhandled_error
11
- # @return [Exception, nil]
12
+ # @return [String, nil]
13
+ attr_reader :error_event
14
+ # @return [Boolean, nil]
15
+ attr_reader :unexecutable
16
+ # @return [Boolean, nil]
12
17
  attr_reader :retried
13
18
  alias retried? retried
14
19
 
15
20
  # @param value [Object, nil]
16
21
  # @param handled_error [Exception, nil]
17
22
  # @param unhandled_error [Exception, nil]
18
- def initialize(value:, handled_error: nil, unhandled_error: nil, retried: false)
23
+ # @param error_event [String, nil]
24
+ # @param unexecutable [Boolean, nil]
25
+ # @param retried [Boolean, nil]
26
+ def initialize(value:, handled_error: nil, unhandled_error: nil, error_event: nil, unexecutable: nil, retried: false)
19
27
  @value = value
20
28
  @handled_error = handled_error
21
29
  @unhandled_error = unhandled_error
30
+ @error_event = error_event
31
+ @unexecutable = unexecutable
22
32
  @retried = retried
23
33
  end
34
+
35
+ # @return [Boolean]
36
+ def succeeded?
37
+ !(handled_error || unhandled_error || unexecutable || retried)
38
+ end
24
39
  end
25
40
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class I18nConfig < ::I18n::Config
5
+ BACKEND = I18n::Backend::Simple.new
6
+ AVAILABLE_LOCALES = GoodJob::Engine.root.join("config/locales").glob("*.yml").map { |path| File.basename(path, ".yml").to_sym }.uniq
7
+ AVAILABLE_LOCALES_SET = AVAILABLE_LOCALES.inject(Set.new) { |set, locale| set << locale.to_s << locale.to_sym }
8
+
9
+ def backend
10
+ BACKEND
11
+ end
12
+
13
+ def available_locales
14
+ AVAILABLE_LOCALES
15
+ end
16
+
17
+ def available_locales_set
18
+ AVAILABLE_LOCALES_SET
19
+ end
20
+
21
+ def default_locale
22
+ GoodJob.configuration.dashboard_default_locale
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # ActiveRecord model that represents an +ActiveJob+ job.
4
5
  # There is not a table in the database whose discrete rows represents "Jobs".
@@ -198,6 +199,7 @@ module GoodJob
198
199
 
199
200
  execution.class.transaction(joinable: false, requires_new: true) do
200
201
  new_active_job = active_job.retry_job(wait: 0, error: execution.error)
202
+ execution.error_event = ERROR_EVENT_RETRIED if execution.error && execution.class.error_event_migrated?
201
203
  execution.save!
202
204
  end
203
205
  end
@@ -221,7 +223,8 @@ module GoodJob
221
223
  update_execution = proc do
222
224
  execution.update(
223
225
  finished_at: Time.current,
224
- error: GoodJob::Execution.format_error(job_error)
226
+ error: GoodJob::Execution.format_error(job_error),
227
+ error_event: :discarded
225
228
  )
226
229
  end
227
230
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'socket'
3
4
 
4
5
  module GoodJob # :nodoc:
@@ -58,8 +59,10 @@ module GoodJob # :nodoc:
58
59
  proctitle: $PROGRAM_NAME,
59
60
  preserve_job_records: GoodJob.preserve_job_records,
60
61
  retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
61
- schedulers: GoodJob::Scheduler.instances.map(&:name),
62
+ schedulers: GoodJob::Scheduler.instances.map(&:stats),
62
63
  cron_enabled: GoodJob.configuration.enable_cron?,
64
+ total_succeeded_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:succeeded_executions_count) },
65
+ total_errored_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:errored_executions_count) },
63
66
  }
64
67
  end
65
68
 
@@ -98,8 +101,16 @@ module GoodJob # :nodoc:
98
101
  end
99
102
  end
100
103
 
104
+ def state
105
+ super || {}
106
+ end
107
+
101
108
  def basename
102
- File.basename(state["proctitle"])
109
+ File.basename(state.fetch("proctitle", ""))
110
+ end
111
+
112
+ def schedulers
113
+ state.fetch("schedulers", [])
103
114
  end
104
115
 
105
116
  def refresh_if_stale(cleanup: false)
@@ -35,7 +35,7 @@
35
35
  </div>
36
36
  <% if execution.error %>
37
37
  <div class="mt-3 small">
38
- <strong class="small"><%=t ".error" %>:</strong>
38
+ <strong class="small"><%=t "good_job.shared.error" %>:</strong>
39
39
  <code class="text-wrap text-break m-0 text-black"><%= execution.error %></code>
40
40
  </div>
41
41
  <% end %>