good_job 3.12.5 → 3.12.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/app/assets/good_job/{modules/application.js → application.js} +4 -0
- data/app/assets/good_job/vendor/chartjs/chart.min.js +15 -9
- data/app/assets/good_job/vendor/rails_ujs.js +7 -747
- data/app/assets/good_job/vendor/stimulus.js +2408 -0
- data/app/controllers/good_job/assets_controller.rb +25 -27
- data/app/views/layouts/good_job/application.html.erb +8 -8
- data/config/routes.rb +2 -12
- data/lib/good_job/adapter.rb +9 -22
- data/lib/good_job/capsule.rb +83 -0
- data/lib/good_job/cli.rb +4 -10
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +11 -14
- metadata +5 -3
@@ -1,47 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GoodJob
|
3
3
|
class AssetsController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
4
|
-
|
4
|
+
skip_after_action :verify_same_origin_request
|
5
|
+
|
6
|
+
STATIC_ASSETS = {
|
7
|
+
css: {
|
8
|
+
bootstrap: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.min.css"),
|
9
|
+
style: GoodJob::Engine.root.join("app", "assets", "good_job", "style.css"),
|
10
|
+
},
|
11
|
+
js: {
|
12
|
+
bootstrap: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.bundle.min.js"),
|
13
|
+
chartjs: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "chartjs", "chart.min.js"),
|
14
|
+
es_module_shims: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "es_module_shims.js"),
|
15
|
+
rails_ujs: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "rails_ujs.js"),
|
16
|
+
},
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
MODULE_OVERRIDES = {
|
20
|
+
application: GoodJob::Engine.root.join("app", "assets", "good_job", "application.js"),
|
21
|
+
stimulus: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "stimulus.js"),
|
22
|
+
}.freeze
|
5
23
|
|
6
24
|
def self.js_modules
|
7
25
|
@_js_modules ||= GoodJob::Engine.root.join("app", "assets", "good_job", "modules").children.select(&:file?).each_with_object({}) do |file, modules|
|
8
26
|
key = File.basename(file.basename.to_s, ".js").to_sym
|
9
27
|
modules[key] = file
|
10
|
-
end
|
28
|
+
end.merge(MODULE_OVERRIDES)
|
11
29
|
end
|
12
30
|
|
13
31
|
before_action do
|
14
32
|
expires_in 1.year, public: true
|
15
33
|
end
|
16
34
|
|
17
|
-
def
|
18
|
-
render file:
|
35
|
+
def static
|
36
|
+
render file: STATIC_ASSETS.dig(params[:format].to_sym, params[:name].to_sym) || raise(ActionController::RoutingError, 'Not Found')
|
19
37
|
end
|
20
38
|
|
21
|
-
def
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def bootstrap_js
|
26
|
-
render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.bundle.min.js")
|
27
|
-
end
|
28
|
-
|
29
|
-
def chartjs_js
|
30
|
-
render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "chartjs", "chart.min.js")
|
31
|
-
end
|
32
|
-
|
33
|
-
def rails_ujs_js
|
34
|
-
render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "rails_ujs.js")
|
35
|
-
end
|
36
|
-
|
37
|
-
def style_css
|
38
|
-
render file: GoodJob::Engine.root.join("app", "assets", "good_job", "style.css")
|
39
|
-
end
|
39
|
+
def module
|
40
|
+
raise(ActionController::RoutingError, 'Not Found') if params[:format] != "js"
|
40
41
|
|
41
|
-
|
42
|
-
module_name = params[:module].to_sym
|
43
|
-
module_file = self.class.js_modules.fetch(module_name) { raise ActionController::RoutingError, 'Not Found' }
|
44
|
-
render file: module_file
|
42
|
+
render file: self.class.js_modules[params[:name].to_sym] || raise(ActionController::RoutingError, 'Not Found')
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -8,16 +8,16 @@
|
|
8
8
|
<%= csp_meta_tag %>
|
9
9
|
|
10
10
|
<%# Do not use asset tag helpers to avoid paths being overriden by config.asset_host %>
|
11
|
-
<%= tag.link rel: "stylesheet",
|
12
|
-
<%= tag.link rel: "stylesheet",
|
11
|
+
<%= tag.link rel: "stylesheet", href: static_asset_path(:bootstrap, format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
12
|
+
<%= tag.link rel: "stylesheet", href: static_asset_path(:style, format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
13
13
|
|
14
|
-
<%= tag.script "", src:
|
15
|
-
<%= tag.script "", src:
|
16
|
-
<%= tag.script "", src:
|
14
|
+
<%= tag.script "", src: static_asset_path(:bootstrap, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
15
|
+
<%= tag.script "", src: static_asset_path(:chartjs, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
16
|
+
<%= tag.script "", src: static_asset_path(:rails_ujs, format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
|
17
17
|
|
18
|
-
<%= tag.script "", src:
|
19
|
-
<% importmaps =
|
20
|
-
<%= tag.script importmaps.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce %>
|
18
|
+
<%= tag.script "", src: static_asset_path(:es_module_shims, format: :js, v: GoodJob::VERSION, locale: nil), async: true, nonce: content_security_policy_nonce %>
|
19
|
+
<% importmaps = GoodJob::AssetsController.js_modules.keys.index_with { |module_name| module_asset_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } %>
|
20
|
+
<%= tag.script({ imports: importmaps }.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce) %>
|
21
21
|
<%= tag.script "", type: "module", nonce: content_security_policy_nonce do %> import "application"; <% end %>
|
22
22
|
</head>
|
23
23
|
<body>
|
data/config/routes.rb
CHANGED
@@ -28,17 +28,7 @@ GoodJob::Engine.routes.draw do
|
|
28
28
|
resources :processes, only: %i[index]
|
29
29
|
|
30
30
|
scope :assets, controller: :assets do
|
31
|
-
constraints
|
32
|
-
|
33
|
-
get :style, action: :style_css
|
34
|
-
end
|
35
|
-
|
36
|
-
constraints(format: :js) do
|
37
|
-
get :bootstrap, action: :bootstrap_js
|
38
|
-
get :chartjs, action: :chartjs_js
|
39
|
-
get :rails_ujs, action: :rails_ujs_js
|
40
|
-
get :es_module_shims, action: :es_module_shims_js
|
41
|
-
get "modules/:module", action: :modules_js, as: :modules
|
42
|
-
end
|
31
|
+
get "modules/:name", action: :module, as: :module_asset, constraints: { format: 'js' }
|
32
|
+
get "static/:name", action: :static, as: :static_asset, constraints: { format: %w[css js] }
|
43
33
|
end
|
44
34
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -24,9 +24,10 @@ module GoodJob
|
|
24
24
|
# -+test+: +:inline+
|
25
25
|
# - +production+ and all other environments: +:external+
|
26
26
|
#
|
27
|
-
def initialize(execution_mode: nil)
|
27
|
+
def initialize(execution_mode: nil, _capsule: GoodJob.capsule) # rubocop:disable Lint/UnderscorePrefixedVariableName
|
28
28
|
@_execution_mode_override = execution_mode
|
29
29
|
GoodJob::Configuration.validate_execution_mode(@_execution_mode_override) if @_execution_mode_override
|
30
|
+
@capsule = _capsule
|
30
31
|
|
31
32
|
self.class.instances << self
|
32
33
|
start_async if GoodJob.async_ready?
|
@@ -98,7 +99,7 @@ module GoodJob
|
|
98
99
|
state = { queue_name: queue_name, count: executions_by_queue_and_scheduled_at.size }
|
99
100
|
state[:scheduled_at] = scheduled_at if scheduled_at
|
100
101
|
|
101
|
-
executed_locally = execute_async? && @
|
102
|
+
executed_locally = execute_async? && @capsule&.create_thread(state)
|
102
103
|
unless executed_locally
|
103
104
|
state[:count] = job_id_to_active_jobs.values_at(*executions_by_queue_and_scheduled_at.map(&:active_job_id)).count { |active_job| send_notify?(active_job) }
|
104
105
|
Notifier.notify(state) unless state[:count].zero?
|
@@ -141,7 +142,7 @@ module GoodJob
|
|
141
142
|
job_state = { queue_name: execution.queue_name }
|
142
143
|
job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
|
143
144
|
|
144
|
-
executed_locally = execute_async? && @
|
145
|
+
executed_locally = execute_async? && @capsule&.create_thread(job_state)
|
145
146
|
Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
|
146
147
|
end
|
147
148
|
|
@@ -150,20 +151,13 @@ module GoodJob
|
|
150
151
|
|
151
152
|
# Shut down the thread pool executors.
|
152
153
|
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
|
153
|
-
# * +nil
|
154
|
-
# * +-1
|
155
|
-
# * +0
|
154
|
+
# * +nil+ trigger a shutdown but not wait for it to complete.
|
155
|
+
# * +-1+ wait until the shutdown is complete.
|
156
|
+
# * +0+ immediately shutdown and stop any threads.
|
156
157
|
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
157
158
|
# @return [void]
|
158
159
|
def shutdown(timeout: :default)
|
159
|
-
timeout
|
160
|
-
GoodJob.configuration.shutdown_timeout
|
161
|
-
else
|
162
|
-
timeout
|
163
|
-
end
|
164
|
-
|
165
|
-
executables = [@notifier, @poller, @scheduler].compact
|
166
|
-
GoodJob._shutdown_all(executables, timeout: timeout)
|
160
|
+
@capsule&.shutdown(timeout: timeout)
|
167
161
|
@_async_started = false
|
168
162
|
end
|
169
163
|
|
@@ -199,14 +193,7 @@ module GoodJob
|
|
199
193
|
def start_async
|
200
194
|
return unless execute_async?
|
201
195
|
|
202
|
-
@
|
203
|
-
@poller = GoodJob::Poller.new(poll_interval: GoodJob.configuration.poll_interval)
|
204
|
-
@scheduler = GoodJob::Scheduler.from_configuration(GoodJob.configuration, warm_cache_on_initialize: true)
|
205
|
-
@notifier.recipients << [@scheduler, :create_thread]
|
206
|
-
@poller.recipients << [@scheduler, :create_thread]
|
207
|
-
|
208
|
-
@cron_manager = GoodJob::CronManager.new(GoodJob.configuration.cron_entries, start_on_initialize: true) if GoodJob.configuration.enable_cron?
|
209
|
-
|
196
|
+
@capsule.start
|
210
197
|
@_async_started = true
|
211
198
|
end
|
212
199
|
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GoodJob
|
3
|
+
# A GoodJob::Capsule contains the resources necessary to execute jobs, including
|
4
|
+
# a {GoodJob::Scheduler}, {GoodJob::Poller}, {GoodJob::Notifier}, and {GoodJob::CronManager}.
|
5
|
+
# GoodJob creates a default capsule on initialization.
|
6
|
+
class Capsule
|
7
|
+
# @!attribute [r] instances
|
8
|
+
# @!scope class
|
9
|
+
# List of all instantiated Capsules in the current process.
|
10
|
+
# @return [Array<GoodJob::Capsule>, nil]
|
11
|
+
cattr_reader :instances, default: [], instance_reader: false
|
12
|
+
|
13
|
+
# @param configuration [GoodJob::Configuration] Configuration to use for this capsule.
|
14
|
+
def initialize(configuration: GoodJob.configuration)
|
15
|
+
self.class.instances << self
|
16
|
+
@configuration = configuration
|
17
|
+
|
18
|
+
@autostart = true
|
19
|
+
@running = false
|
20
|
+
@mutex = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start executing jobs (if not already running).
|
24
|
+
def start
|
25
|
+
return if @running
|
26
|
+
|
27
|
+
@mutex.synchronize do
|
28
|
+
return if @running
|
29
|
+
|
30
|
+
@notifier = GoodJob::Notifier.new(enable_listening: @configuration.enable_listen_notify)
|
31
|
+
@poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
|
32
|
+
@scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
|
33
|
+
@notifier.recipients << [@scheduler, :create_thread]
|
34
|
+
@poller.recipients << [@scheduler, :create_thread]
|
35
|
+
|
36
|
+
@cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true) if @configuration.enable_cron?
|
37
|
+
|
38
|
+
@autostart = false
|
39
|
+
@running = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Shut down the thread pool executors.
|
44
|
+
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
|
45
|
+
# * +-1+ will wait for all active threads to complete.
|
46
|
+
# * +0+ will interrupt active threads.
|
47
|
+
# * +N+ will wait at most N seconds and then interrupt active threads.
|
48
|
+
# * +nil+ will trigger a shutdown but not wait for it to complete.
|
49
|
+
# @return [void]
|
50
|
+
def shutdown(timeout: :default)
|
51
|
+
timeout = timeout == :default ? @configuration.shutdown_timeout : timeout
|
52
|
+
GoodJob._shutdown_all([@notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
|
53
|
+
@autostart = false
|
54
|
+
@running = false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Shutdown and then start the capsule again.
|
58
|
+
# @param timeout [nil, Numeric, Symbol] Seconds to wait for active threads.
|
59
|
+
# @return [void]
|
60
|
+
def restart(timeout: :default)
|
61
|
+
shutdown(timeout: timeout)
|
62
|
+
start
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Boolean] Whether the capsule is currently running.
|
66
|
+
def running?
|
67
|
+
@running
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Boolean] Whether the capsule has been shutdown.
|
71
|
+
def shutdown?
|
72
|
+
[@notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates an execution thread(s) with the given attributes.
|
76
|
+
# @param job_state [Hash, nil] See {GoodJob::Scheduler#create_thread}.
|
77
|
+
# @return [Boolean, nil] Whether work was started.
|
78
|
+
def create_thread(job_state = nil)
|
79
|
+
start if !running? && @autostart
|
80
|
+
@scheduler&.create_thread(job_state)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/good_job/cli.rb
CHANGED
@@ -95,13 +95,8 @@ module GoodJob
|
|
95
95
|
|
96
96
|
Daemon.new(pidfile: configuration.pidfile).daemonize if configuration.daemonize?
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
|
101
|
-
notifier.recipients << [scheduler, :create_thread]
|
102
|
-
poller.recipients << [scheduler, :create_thread]
|
103
|
-
|
104
|
-
cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
|
98
|
+
capsule = GoodJob::Capsule.new
|
99
|
+
capsule.start
|
105
100
|
|
106
101
|
if configuration.probe_port
|
107
102
|
probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
|
@@ -115,11 +110,10 @@ module GoodJob
|
|
115
110
|
|
116
111
|
Kernel.loop do
|
117
112
|
sleep 0.1
|
118
|
-
break if @stop_good_job_executable ||
|
113
|
+
break if @stop_good_job_executable || capsule.shutdown?
|
119
114
|
end
|
120
115
|
|
121
|
-
|
122
|
-
GoodJob._shutdown_all(executors, timeout: configuration.shutdown_timeout)
|
116
|
+
capsule.shutdown(timeout: configuration.shutdown_timeout)
|
123
117
|
probe_server&.stop
|
124
118
|
end
|
125
119
|
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -15,6 +15,7 @@ require "good_job/active_job_extensions/notify_options"
|
|
15
15
|
|
16
16
|
require "good_job/assignable_connection"
|
17
17
|
require "good_job/bulk"
|
18
|
+
require "good_job/capsule"
|
18
19
|
require "good_job/cleanup_tracker"
|
19
20
|
require "good_job/cli"
|
20
21
|
require "good_job/configuration"
|
@@ -91,6 +92,12 @@ module GoodJob
|
|
91
92
|
# @return [GoodJob::Configuration, nil]
|
92
93
|
mattr_accessor :configuration, default: GoodJob::Configuration.new({})
|
93
94
|
|
95
|
+
# @!attribute [rw] capsule
|
96
|
+
# @!scope class
|
97
|
+
# Global/default execution capsule for GoodJob.
|
98
|
+
# @return [GoodJob::Capsule, nil]
|
99
|
+
mattr_accessor :capsule, default: GoodJob::Capsule.new(configuration: configuration)
|
100
|
+
|
94
101
|
# Called with exception when a GoodJob thread raises an exception
|
95
102
|
# @param exception [Exception] Exception that was raised
|
96
103
|
# @return [void]
|
@@ -108,16 +115,15 @@ module GoodJob
|
|
108
115
|
# * +-1+, the scheduler will wait until the shutdown is complete.
|
109
116
|
# * +0+, the scheduler will immediately shutdown and stop any active tasks.
|
110
117
|
# * +1..+, the scheduler will wait that many seconds before stopping any remaining active tasks.
|
111
|
-
# @param wait [Boolean] whether to wait for shutdown
|
112
118
|
# @return [void]
|
113
119
|
def self.shutdown(timeout: -1)
|
114
|
-
_shutdown_all(
|
120
|
+
_shutdown_all(Capsule.instances, timeout: timeout)
|
115
121
|
end
|
116
122
|
|
117
123
|
# Tests whether jobs have stopped executing.
|
118
124
|
# @return [Boolean] whether background threads are shut down
|
119
125
|
def self.shutdown?
|
120
|
-
|
126
|
+
Capsule.instances.all?(&:shutdown?)
|
121
127
|
end
|
122
128
|
|
123
129
|
# Stops and restarts executing jobs.
|
@@ -128,7 +134,7 @@ module GoodJob
|
|
128
134
|
# @param timeout [Numeric, nil] Seconds to wait for active threads to finish.
|
129
135
|
# @return [void]
|
130
136
|
def self.restart(timeout: -1)
|
131
|
-
_shutdown_all(
|
137
|
+
_shutdown_all(Capsule.instances, :restart, timeout: timeout)
|
132
138
|
end
|
133
139
|
|
134
140
|
# Sends +#shutdown+ or +#restart+ to executable objects ({GoodJob::Notifier}, {GoodJob::Poller}, {GoodJob::Scheduler}, {GoodJob::MultiScheduler}, {GoodJob::CronManager})
|
@@ -154,7 +160,7 @@ module GoodJob
|
|
154
160
|
# analyze or inspect job performance.
|
155
161
|
# If you are preserving job records this way, use this method regularly to
|
156
162
|
# destroy old records and preserve space in your database.
|
157
|
-
# @
|
163
|
+
# @param older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
|
158
164
|
# @return [Integer] Number of job execution records and batches that were destroyed.
|
159
165
|
def self.cleanup_preserved_jobs(older_than: nil)
|
160
166
|
older_than ||= GoodJob.configuration.cleanup_preserved_jobs_before_seconds_ago
|
@@ -194,14 +200,5 @@ module GoodJob
|
|
194
200
|
end
|
195
201
|
end
|
196
202
|
|
197
|
-
def self._executables
|
198
|
-
[].concat(
|
199
|
-
CronManager.instances,
|
200
|
-
Notifier.instances,
|
201
|
-
Poller.instances,
|
202
|
-
Scheduler.instances
|
203
|
-
)
|
204
|
-
end
|
205
|
-
|
206
203
|
ActiveSupport.run_load_hooks(:good_job, self)
|
207
204
|
end
|
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.12.
|
4
|
+
version: 3.12.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -318,7 +318,7 @@ files:
|
|
318
318
|
- CHANGELOG.md
|
319
319
|
- LICENSE.txt
|
320
320
|
- README.md
|
321
|
-
- app/assets/good_job/
|
321
|
+
- app/assets/good_job/application.js
|
322
322
|
- app/assets/good_job/modules/charts.js
|
323
323
|
- app/assets/good_job/modules/checkbox_toggle.js
|
324
324
|
- app/assets/good_job/modules/document_ready.js
|
@@ -331,6 +331,7 @@ files:
|
|
331
331
|
- app/assets/good_job/vendor/chartjs/chart.min.js
|
332
332
|
- app/assets/good_job/vendor/es_module_shims.js
|
333
333
|
- app/assets/good_job/vendor/rails_ujs.js
|
334
|
+
- app/assets/good_job/vendor/stimulus.js
|
334
335
|
- app/charts/good_job/scheduled_by_queue_chart.rb
|
335
336
|
- app/controllers/good_job/application_controller.rb
|
336
337
|
- app/controllers/good_job/assets_controller.rb
|
@@ -408,6 +409,7 @@ files:
|
|
408
409
|
- lib/good_job/adapter.rb
|
409
410
|
- lib/good_job/assignable_connection.rb
|
410
411
|
- lib/good_job/bulk.rb
|
412
|
+
- lib/good_job/capsule.rb
|
411
413
|
- lib/good_job/cleanup_tracker.rb
|
412
414
|
- lib/good_job/cli.rb
|
413
415
|
- lib/good_job/configuration.rb
|