good_job 4.13.3 → 4.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/app/controllers/good_job/cron_entries_controller.rb +4 -0
- data/app/models/good_job/batch.rb +223 -0
- data/app/models/good_job/cron_entry.rb +26 -2
- data/app/models/good_job/setting.rb +10 -0
- data/app/views/good_job/cron_entries/index.html.erb +6 -4
- data/lib/good_job/adapter.rb +4 -0
- data/lib/good_job/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af515dffcbbdb5c6f3e02ec7a1426b7d59aea355b33791c81cc20d043bcab7f4
|
|
4
|
+
data.tar.gz: e44f93e479b483af4c3848c7957df934198fb780260c3008c427ff864c3d67a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 812281e391af58a897dd119e875be51c7569eaefa021f7fc05d42d69b185819c4077c04e8f40d23c3cf498862e04f8f9285fe9a29931a1d9bba4f1a61650ca37
|
|
7
|
+
data.tar.gz: 1941b6294d3339c590e2c37188a2f1568a3c32bc5e1f9cb2a76f0c58b94cce6781797d3d86853111a842f435adb8262611eca426dc16001bb6bc1b0a8437bde4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v4.14.1](https://github.com/bensheldon/good_job/tree/v4.14.1) (2026-04-03)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.14.0...v4.14.1)
|
|
6
|
+
|
|
7
|
+
**Fixed bugs:**
|
|
8
|
+
|
|
9
|
+
- Fix N+1 queries on cron entries dashboard index page [\#1727](https://github.com/bensheldon/good_job/pull/1727) ([clinejj](https://github.com/clinejj))
|
|
10
|
+
|
|
11
|
+
## [v4.14.0](https://github.com/bensheldon/good_job/tree/v4.14.0) (2026-03-31)
|
|
12
|
+
|
|
13
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.13.3...v4.14.0)
|
|
14
|
+
|
|
15
|
+
**Implemented enhancements:**
|
|
16
|
+
|
|
17
|
+
- Consider using pg\_cron for Cron-style repeating/recurring jobs [\#328](https://github.com/bensheldon/good_job/issues/328)
|
|
18
|
+
- Add Batch.enqueue\_all for bulk-enqueuing multiple batches [\#1726](https://github.com/bensheldon/good_job/pull/1726) ([AliOsm](https://github.com/AliOsm))
|
|
19
|
+
- Allow perform\_all\_later to enqueue to Batches [\#1720](https://github.com/bensheldon/good_job/pull/1720) ([bensheldon](https://github.com/bensheldon))
|
|
20
|
+
|
|
21
|
+
**Closed issues:**
|
|
22
|
+
|
|
23
|
+
- perform\_all\_later not intercepted by Batch [\#1719](https://github.com/bensheldon/good_job/issues/1719)
|
|
24
|
+
- Deprecate and drop :on\_unhandled\_error option [\#1706](https://github.com/bensheldon/good_job/issues/1706)
|
|
25
|
+
|
|
26
|
+
**Merged pull requests:**
|
|
27
|
+
|
|
28
|
+
- Bump actions/upload-artifact from 6 to 7 [\#1718](https://github.com/bensheldon/good_job/pull/1718) ([dependabot[bot]](https://github.com/apps/dependabot))
|
|
29
|
+
|
|
3
30
|
## [v4.13.3](https://github.com/bensheldon/good_job/tree/v4.13.3) (2026-02-18)
|
|
4
31
|
|
|
5
32
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.13.2...v4.13.3)
|
|
@@ -4,6 +4,10 @@ module GoodJob
|
|
|
4
4
|
class CronEntriesController < GoodJob::ApplicationController
|
|
5
5
|
def index
|
|
6
6
|
@cron_entries = CronEntry.all
|
|
7
|
+
@last_jobs = CronEntry.last_jobs_by_key(@cron_entries)
|
|
8
|
+
@enabled_states = GoodJob::Setting.cron_keys_enabled(
|
|
9
|
+
@cron_entries.map { |entry| [entry.key, entry.enabled_by_default?] }
|
|
10
|
+
)
|
|
7
11
|
end
|
|
8
12
|
|
|
9
13
|
def show
|
|
@@ -61,6 +61,104 @@ module GoodJob
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Bulk-enqueue multiple batches with their jobs in minimal DB round-trips.
|
|
65
|
+
#
|
|
66
|
+
# Instead of creating batches one-at-a-time (~7 queries per batch), this method
|
|
67
|
+
# inserts all batch records and jobs in a fixed number of queries:
|
|
68
|
+
# 1. INSERT all BatchRecords (insert_all!)
|
|
69
|
+
# 2. INSERT all Jobs (insert_all)
|
|
70
|
+
# 3. NOTIFY per distinct queue/scheduled_at
|
|
71
|
+
#
|
|
72
|
+
# @param batch_job_pairs [Array<Array(GoodJob::Batch, Array<ActiveJob::Base>)>]
|
|
73
|
+
# Array of [batch, jobs] pairs. Each batch must be new (not yet persisted).
|
|
74
|
+
# @return [Array<GoodJob::Batch>] The enqueued batches
|
|
75
|
+
# @raise [ArgumentError] if any batch is already persisted
|
|
76
|
+
def self.enqueue_all(batch_job_pairs)
|
|
77
|
+
batch_job_pairs = Array(batch_job_pairs)
|
|
78
|
+
return [] if batch_job_pairs.empty?
|
|
79
|
+
|
|
80
|
+
batch_job_pairs.each do |(batch, _)|
|
|
81
|
+
raise ArgumentError, "All batches must be new (not persisted)" if batch.persisted?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Rails.application.executor.wrap do
|
|
85
|
+
current_time = Time.current
|
|
86
|
+
adapter = ActiveJob::Base.queue_adapter
|
|
87
|
+
execute_inline = adapter.respond_to?(:execute_inline?) && adapter.execute_inline?
|
|
88
|
+
|
|
89
|
+
# Phase 1: Insert all batch records
|
|
90
|
+
batch_rows = _build_batch_rows(batch_job_pairs, current_time)
|
|
91
|
+
BatchRecord.insert_all!(batch_rows) # rubocop:disable Rails/SkipsModelValidations
|
|
92
|
+
_mark_batches_persisted(batch_job_pairs, batch_rows, current_time)
|
|
93
|
+
|
|
94
|
+
# Phase 2: Build and partition jobs by concurrency limits
|
|
95
|
+
build_result = _build_and_partition_jobs(batch_job_pairs, current_time)
|
|
96
|
+
|
|
97
|
+
# Phase 3: Insert bulkable jobs
|
|
98
|
+
persisted_jobs = []
|
|
99
|
+
inline_jobs = []
|
|
100
|
+
|
|
101
|
+
if build_result[:bulkable].any?
|
|
102
|
+
Job.transaction(requires_new: true, joinable: false) do
|
|
103
|
+
persisted_jobs = _insert_jobs(build_result[:bulkable], build_result[:active_jobs_by_job_id])
|
|
104
|
+
|
|
105
|
+
if execute_inline
|
|
106
|
+
inline_jobs = persisted_jobs.select { |job| job.scheduled_at.nil? || job.scheduled_at <= Time.current }
|
|
107
|
+
inline_jobs.each(&:advisory_lock!)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Phase 4: Handle empty batches — they need _continue_discard_or_finish
|
|
113
|
+
# to trigger on_success/on_finish callbacks (batch_record.rb:77).
|
|
114
|
+
batches_with_jobs = Set.new
|
|
115
|
+
build_result[:bulkable].each { |entry| batches_with_jobs.add(entry[:batch]) }
|
|
116
|
+
build_result[:unbulkable].each { |entry| batches_with_jobs.add(entry[:batch]) }
|
|
117
|
+
|
|
118
|
+
empty_batches = batch_job_pairs.map(&:first).reject { |batch| batches_with_jobs.include?(batch) }
|
|
119
|
+
if empty_batches.any?
|
|
120
|
+
buffer = GoodJob::Adapter::InlineBuffer.capture do
|
|
121
|
+
empty_batches.each do |batch|
|
|
122
|
+
batch._record.reload
|
|
123
|
+
batch._record._continue_discard_or_finish(lock: true)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
buffer.call
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Phase 5: Enqueue concurrency-limited jobs individually
|
|
130
|
+
build_result[:unbulkable].each do |entry|
|
|
131
|
+
within_thread(batch_id: entry[:batch].id) do
|
|
132
|
+
entry[:active_job].enqueue
|
|
133
|
+
end
|
|
134
|
+
rescue GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError
|
|
135
|
+
# ignore — matches Bulk::Buffer behavior (bulk.rb:107-109)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Phase 6: Execute inline jobs
|
|
139
|
+
if inline_jobs.any?
|
|
140
|
+
GoodJob::Adapter::InlineBuffer.perform_now_or_defer do
|
|
141
|
+
GoodJob.capsule.tracker.register do
|
|
142
|
+
until inline_jobs.empty?
|
|
143
|
+
inline_job = inline_jobs.shift
|
|
144
|
+
active_job = build_result[:active_jobs_by_job_id][inline_job.active_job_id]
|
|
145
|
+
adapter.send(:perform_inline, inline_job, notify: adapter.send(:send_notify?, active_job))
|
|
146
|
+
end
|
|
147
|
+
ensure
|
|
148
|
+
inline_jobs.each(&:advisory_unlock)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Phase 7: Send NOTIFY for non-inline jobs
|
|
154
|
+
non_inline_jobs = persisted_jobs - inline_jobs
|
|
155
|
+
non_inline_jobs = non_inline_jobs.reject(&:finished_at) if inline_jobs.any?
|
|
156
|
+
_send_notifications(non_inline_jobs, build_result[:active_jobs_by_job_id], adapter) if non_inline_jobs.any?
|
|
157
|
+
|
|
158
|
+
batch_job_pairs.map(&:first)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
64
162
|
def self.primary_key
|
|
65
163
|
:id
|
|
66
164
|
end
|
|
@@ -183,6 +281,131 @@ module GoodJob
|
|
|
183
281
|
record
|
|
184
282
|
end
|
|
185
283
|
|
|
284
|
+
# @!visibility private
|
|
285
|
+
def self._build_batch_rows(batch_job_pairs, current_time)
|
|
286
|
+
batch_job_pairs.map do |batch, _jobs|
|
|
287
|
+
record = batch._record
|
|
288
|
+
{
|
|
289
|
+
id: SecureRandom.uuid,
|
|
290
|
+
created_at: current_time,
|
|
291
|
+
updated_at: current_time,
|
|
292
|
+
enqueued_at: current_time,
|
|
293
|
+
on_finish: record.on_finish,
|
|
294
|
+
on_success: record.on_success,
|
|
295
|
+
on_discard: record.on_discard,
|
|
296
|
+
callback_queue_name: record.callback_queue_name,
|
|
297
|
+
callback_priority: record.callback_priority,
|
|
298
|
+
description: record.description,
|
|
299
|
+
# record.serialized_properties returns the internally-stored form
|
|
300
|
+
# which already includes _aj_symbol_keys metadata from
|
|
301
|
+
# PropertySerializer.dump. Pass it directly — insert_all! handles
|
|
302
|
+
# jsonb encoding, and PropertySerializer.load will deserialize
|
|
303
|
+
# correctly on read.
|
|
304
|
+
serialized_properties: record.serialized_properties || {},
|
|
305
|
+
}
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# @!visibility private
|
|
310
|
+
def self._mark_batches_persisted(batch_job_pairs, batch_rows, current_time)
|
|
311
|
+
batch_job_pairs.each_with_index do |(batch, _jobs), index|
|
|
312
|
+
record = batch._record
|
|
313
|
+
record.id = batch_rows[index][:id]
|
|
314
|
+
record.created_at = current_time
|
|
315
|
+
record.updated_at = current_time
|
|
316
|
+
record.enqueued_at = current_time
|
|
317
|
+
record.instance_variable_set(:@new_record, false)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# @!visibility private
|
|
322
|
+
def self._build_and_partition_jobs(batch_job_pairs, current_time)
|
|
323
|
+
bulkable = []
|
|
324
|
+
unbulkable = []
|
|
325
|
+
active_jobs_by_job_id = {}
|
|
326
|
+
|
|
327
|
+
batch_job_pairs.each do |batch, jobs|
|
|
328
|
+
next if jobs.blank?
|
|
329
|
+
|
|
330
|
+
jobs.each do |active_job|
|
|
331
|
+
active_jobs_by_job_id[active_job.job_id] = active_job
|
|
332
|
+
|
|
333
|
+
# Jobs with concurrency limits must be enqueued individually so the
|
|
334
|
+
# before_enqueue concurrency check runs. Mirrors Bulk::Buffer#enqueue
|
|
335
|
+
# partitioning (bulk.rb:95-98).
|
|
336
|
+
if active_job.respond_to?(:good_job_concurrency_key) &&
|
|
337
|
+
active_job.good_job_concurrency_key.present? &&
|
|
338
|
+
(active_job.class.good_job_concurrency_config[:enqueue_limit] ||
|
|
339
|
+
active_job.class.good_job_concurrency_config[:total_limit])
|
|
340
|
+
unbulkable << { batch: batch, active_job: active_job }
|
|
341
|
+
else
|
|
342
|
+
good_job = Job.build_for_enqueue(active_job)
|
|
343
|
+
|
|
344
|
+
# Normalize timestamps (mirrors Adapter#enqueue_all, adapter.rb:62-65)
|
|
345
|
+
good_job.scheduled_at = current_time if good_job.scheduled_at == good_job.created_at
|
|
346
|
+
good_job.created_at = current_time
|
|
347
|
+
good_job.updated_at = current_time
|
|
348
|
+
|
|
349
|
+
# Set batch_id directly — can't use thread-local for multi-batch bulk
|
|
350
|
+
good_job.batch_id = batch.id
|
|
351
|
+
good_job.batch_callback_id = nil
|
|
352
|
+
|
|
353
|
+
bulkable << { batch: batch, active_job: active_job, good_job: good_job }
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
{ bulkable: bulkable, unbulkable: unbulkable, active_jobs_by_job_id: active_jobs_by_job_id }
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# @!visibility private
|
|
362
|
+
def self._insert_jobs(bulkable_entries, active_jobs_by_job_id)
|
|
363
|
+
job_attributes = bulkable_entries.map { |entry| entry[:good_job].attributes }
|
|
364
|
+
results = Job.insert_all(job_attributes, returning: %w[id active_job_id]) # rubocop:disable Rails/SkipsModelValidations
|
|
365
|
+
|
|
366
|
+
job_id_map = results.each_with_object({}) { |row, hash| hash[row['active_job_id']] = row['id'] }
|
|
367
|
+
|
|
368
|
+
# Set provider_job_id on ActiveJob instances (mirrors adapter.rb:74-76)
|
|
369
|
+
active_jobs_by_job_id.each_value do |active_job|
|
|
370
|
+
active_job.provider_job_id = job_id_map[active_job.job_id]
|
|
371
|
+
active_job.successfully_enqueued = active_job.provider_job_id.present? if active_job.respond_to?(:successfully_enqueued=)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Mark Job AR objects as persisted (mirrors adapter.rb:78-80)
|
|
375
|
+
bulkable_entries.each do |entry|
|
|
376
|
+
entry[:good_job].instance_variable_set(:@new_record, false) if job_id_map[entry[:good_job].active_job_id]
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
bulkable_entries.pluck(:good_job).select(&:persisted?)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# @!visibility private
|
|
383
|
+
def self._send_notifications(jobs, active_jobs_by_job_id, adapter)
|
|
384
|
+
return unless GoodJob.configuration.enable_listen_notify
|
|
385
|
+
|
|
386
|
+
jobs.group_by(&:queue_name).each do |queue_name, jobs_by_queue|
|
|
387
|
+
jobs_by_queue.group_by(&:scheduled_at).each do |scheduled_at, grouped_jobs|
|
|
388
|
+
state = { queue_name: queue_name, count: grouped_jobs.size }
|
|
389
|
+
state[:scheduled_at] = scheduled_at if scheduled_at
|
|
390
|
+
|
|
391
|
+
executed_locally = adapter.respond_to?(:execute_async?) && adapter.execute_async? && GoodJob.capsule&.create_thread(state)
|
|
392
|
+
unless executed_locally
|
|
393
|
+
state[:count] = grouped_jobs.count { |job| _send_notify?(active_jobs_by_job_id[job.active_job_id]) }
|
|
394
|
+
Notifier.notify(state) unless state[:count].zero?
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Mirrors Adapter#send_notify? (adapter.rb:242-247)
|
|
401
|
+
# @!visibility private
|
|
402
|
+
def self._send_notify?(active_job)
|
|
403
|
+
return true unless active_job.respond_to?(:good_job_notify)
|
|
404
|
+
|
|
405
|
+
!(active_job.good_job_notify == false ||
|
|
406
|
+
(active_job.class.good_job_notify == false && active_job.good_job_notify.nil?))
|
|
407
|
+
end
|
|
408
|
+
|
|
186
409
|
private
|
|
187
410
|
|
|
188
411
|
attr_accessor :record
|
|
@@ -18,6 +18,30 @@ module GoodJob # :nodoc:
|
|
|
18
18
|
configuration.cron_entries
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def self.last_jobs_by_key(cron_entries)
|
|
22
|
+
cron_keys = cron_entries.map { |entry| entry.key.to_s }
|
|
23
|
+
return {} if cron_keys.empty?
|
|
24
|
+
|
|
25
|
+
from_clause = GoodJob::Job.sanitize_sql_array(
|
|
26
|
+
["unnest(ARRAY[?]::text[]) AS cron_keys(cron_key)", cron_keys]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
join_clause = <<~SQL.squish
|
|
30
|
+
CROSS JOIN LATERAL (
|
|
31
|
+
SELECT * FROM good_jobs
|
|
32
|
+
WHERE good_jobs.cron_key = cron_keys.cron_key
|
|
33
|
+
ORDER BY cron_at DESC NULLS LAST
|
|
34
|
+
LIMIT 1
|
|
35
|
+
) AS lateral_jobs
|
|
36
|
+
SQL
|
|
37
|
+
|
|
38
|
+
GoodJob::Job
|
|
39
|
+
.select("lateral_jobs.*")
|
|
40
|
+
.from(from_clause)
|
|
41
|
+
.joins(join_clause)
|
|
42
|
+
.index_by(&:cron_key)
|
|
43
|
+
end
|
|
44
|
+
|
|
21
45
|
def self.find(key, configuration: nil)
|
|
22
46
|
all(configuration: configuration).find { |entry| entry.key == key.to_sym }.tap do |cron_entry|
|
|
23
47
|
raise ActiveRecord::RecordNotFound unless cron_entry
|
|
@@ -144,13 +168,13 @@ module GoodJob # :nodoc:
|
|
|
144
168
|
(last_job.cron_at || last_job.created_at).localtime
|
|
145
169
|
end
|
|
146
170
|
|
|
147
|
-
private
|
|
148
|
-
|
|
149
171
|
def enabled_by_default?
|
|
150
172
|
value = params.fetch(:enabled_by_default, true)
|
|
151
173
|
value.respond_to?(:call) ? value.call : value
|
|
152
174
|
end
|
|
153
175
|
|
|
176
|
+
private
|
|
177
|
+
|
|
154
178
|
def cron
|
|
155
179
|
params.fetch(:cron)
|
|
156
180
|
end
|
|
@@ -19,6 +19,16 @@ module GoodJob
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def self.cron_keys_enabled(keys_with_defaults)
|
|
23
|
+
cron_disabled = find_by(key: CRON_KEYS_DISABLED)&.value || []
|
|
24
|
+
cron_enabled = find_by(key: CRON_KEYS_ENABLED)&.value || []
|
|
25
|
+
|
|
26
|
+
keys_with_defaults.to_h do |key, default|
|
|
27
|
+
key_s = key.to_s
|
|
28
|
+
[key_s, default ? cron_disabled.exclude?(key_s) : cron_enabled.include?(key_s)]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
def self.cron_key_enable(key)
|
|
23
33
|
key_string = key.to_s
|
|
24
34
|
enabled_setting = find_or_initialize_by(key: CRON_KEYS_ENABLED) do |record|
|
|
@@ -39,14 +39,16 @@
|
|
|
39
39
|
<%= relative_time cron_entry.next_at %>
|
|
40
40
|
</div>
|
|
41
41
|
<div class="col-6 col-lg-1 text-wrap small">
|
|
42
|
-
<%
|
|
42
|
+
<% last_job = @last_jobs[cron_entry.key.to_s] %>
|
|
43
|
+
<% if last_job.present? %>
|
|
44
|
+
<% last_job_at = (last_job.cron_at || last_job.created_at).localtime %>
|
|
43
45
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.cron.last_run" %></div>
|
|
44
|
-
<%= link_to relative_time(
|
|
46
|
+
<%= link_to relative_time(last_job_at), cron_entry_path(cron_entry), title: "Job #{last_job.id}" %>
|
|
45
47
|
<% end %>
|
|
46
48
|
</div>
|
|
47
49
|
<div class="col-6 col-lg-1 text-wrap small">
|
|
48
50
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.cron.status" %></div>
|
|
49
|
-
<% if cron_entry.
|
|
51
|
+
<% if @enabled_states[cron_entry.key.to_s] %>
|
|
50
52
|
<span class="text-success"><%= t "good_job.models.cron.states.active" %></span>
|
|
51
53
|
<% else %>
|
|
52
54
|
<span class="text-muted"><%= t "good_job.models.cron.states.paused" %></span>
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
<%= render_icon "skip_forward" %>
|
|
58
60
|
<% end %>
|
|
59
61
|
|
|
60
|
-
<% if cron_entry.
|
|
62
|
+
<% if @enabled_states[cron_entry.key.to_s] %>
|
|
61
63
|
<%= button_to disable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: t("good_job.cron_entries.actions.disable") }, title: t("good_job.cron_entries.actions.disable"), data: { turbo_confirm: t("good_job.cron_entries.actions.confirm_disable") } do %>
|
|
62
64
|
<%= render_icon "pause" %>
|
|
63
65
|
<% end %>
|
data/lib/good_job/adapter.rb
CHANGED
|
@@ -55,6 +55,10 @@ module GoodJob
|
|
|
55
55
|
active_jobs = Array(active_jobs)
|
|
56
56
|
return 0 if active_jobs.empty?
|
|
57
57
|
|
|
58
|
+
# If there is a currently open Bulk in the current thread, direct the
|
|
59
|
+
# jobs there to (eventually) be enqueued using enqueue_all
|
|
60
|
+
return if GoodJob::Bulk.capture(active_jobs, queue_adapter: self)
|
|
61
|
+
|
|
58
62
|
Rails.application.executor.wrap do
|
|
59
63
|
current_time = Time.current
|
|
60
64
|
jobs = active_jobs.map do |active_job|
|
data/lib/good_job/version.rb
CHANGED