good_job 2.4.0 → 2.6.0
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 +89 -0
- data/README.md +51 -20
- data/engine/app/assets/vendor/rails_ujs.js +747 -0
- data/engine/app/controllers/good_job/assets_controller.rb +4 -0
- data/engine/app/controllers/good_job/base_controller.rb +8 -0
- data/engine/app/controllers/good_job/cron_entries_controller.rb +19 -0
- data/engine/app/controllers/good_job/jobs_controller.rb +36 -0
- data/engine/app/filters/good_job/base_filter.rb +12 -7
- data/engine/app/filters/good_job/executions_filter.rb +1 -1
- data/engine/app/filters/good_job/jobs_filter.rb +4 -2
- data/engine/app/views/good_job/cron_entries/index.html.erb +51 -0
- data/engine/app/views/good_job/cron_entries/show.html.erb +4 -0
- data/engine/app/views/good_job/{shared/_executions_table.erb → executions/_table.erb} +1 -1
- data/engine/app/views/good_job/executions/index.html.erb +1 -1
- data/engine/app/views/good_job/{shared/_jobs_table.erb → jobs/_table.erb} +17 -5
- data/engine/app/views/good_job/jobs/index.html.erb +14 -1
- data/engine/app/views/good_job/jobs/show.html.erb +2 -2
- data/engine/app/views/good_job/shared/_filter.erb +9 -10
- data/engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb +5 -0
- data/engine/app/views/good_job/shared/icons/_play.html.erb +4 -0
- data/engine/app/views/good_job/shared/icons/_skip_forward.html.erb +4 -0
- data/engine/app/views/good_job/shared/icons/_stop.html.erb +4 -0
- data/engine/app/views/layouts/good_job/base.html.erb +3 -1
- data/engine/config/routes.rb +15 -2
- data/lib/generators/good_job/install_generator.rb +6 -0
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +3 -1
- data/lib/generators/good_job/templates/update/migrations/{01_create_good_jobs.rb → 01_create_good_jobs.rb.erb} +1 -1
- data/lib/generators/good_job/templates/update/migrations/02_add_cron_at_to_good_jobs.rb.erb +14 -0
- data/lib/generators/good_job/templates/update/migrations/03_add_cron_key_cron_at_index_to_good_jobs.rb.erb +20 -0
- data/lib/generators/good_job/update_generator.rb +6 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +3 -4
- data/lib/good_job/active_job_job.rb +245 -0
- data/lib/good_job/adapter.rb +4 -2
- data/lib/good_job/cli.rb +3 -1
- data/lib/good_job/configuration.rb +5 -1
- data/lib/good_job/cron_entry.rb +138 -0
- data/lib/good_job/cron_manager.rb +17 -31
- data/lib/good_job/current_thread.rb +38 -5
- data/lib/good_job/execution.rb +50 -25
- data/lib/good_job/lockable.rb +1 -1
- data/lib/good_job/log_subscriber.rb +3 -3
- data/lib/good_job/scheduler.rb +1 -0
- data/lib/good_job/version.rb +1 -1
- metadata +21 -12
- data/engine/app/controllers/good_job/cron_schedules_controller.rb +0 -9
- data/engine/app/models/good_job/active_job_job.rb +0 -127
- data/engine/app/views/good_job/cron_schedules/index.html.erb +0 -50
data/lib/good_job/execution.rb
CHANGED
@@ -10,6 +10,9 @@ module GoodJob
|
|
10
10
|
# Raised if something attempts to execute a previously completed Execution again.
|
11
11
|
PreviouslyPerformedError = Class.new(StandardError)
|
12
12
|
|
13
|
+
# String separating Error Class from Error Message
|
14
|
+
ERROR_MESSAGE_SEPARATOR = ": "
|
15
|
+
|
13
16
|
# ActiveJob jobs without a +queue_name+ attribute are placed on this queue.
|
14
17
|
DEFAULT_QUEUE_NAME = 'default'
|
15
18
|
# ActiveJob jobs without a +priority+ attribute are given this priority.
|
@@ -50,6 +53,16 @@ module GoodJob
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
56
|
+
def self._migration_pending_warning
|
57
|
+
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
58
|
+
GoodJob has pending database migrations. To create the migration files, run:
|
59
|
+
rails generate good_job:update
|
60
|
+
To apply the migration files, run:
|
61
|
+
rails db:migrate
|
62
|
+
DEPRECATION
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
53
66
|
# Get Jobs with given ActiveJob ID
|
54
67
|
# @!method active_job_id
|
55
68
|
# @!scope class
|
@@ -174,13 +187,7 @@ module GoodJob
|
|
174
187
|
break if execution.blank?
|
175
188
|
break :unlocked unless execution&.executable?
|
176
189
|
|
177
|
-
|
178
|
-
execution.with_advisory_lock(key: "good_jobs-#{execution.active_job_id}") do
|
179
|
-
execution.perform
|
180
|
-
end
|
181
|
-
rescue RecordAlreadyAdvisoryLockedError => e
|
182
|
-
ExecutionResult.new(value: nil, handled_error: e)
|
183
|
-
end
|
190
|
+
execution.perform
|
184
191
|
end
|
185
192
|
end
|
186
193
|
|
@@ -228,7 +235,15 @@ module GoodJob
|
|
228
235
|
|
229
236
|
if CurrentThread.cron_key
|
230
237
|
execution_args[:cron_key] = CurrentThread.cron_key
|
231
|
-
|
238
|
+
|
239
|
+
@cron_at_index = column_names.include?('cron_at') && connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at) unless instance_variable_defined?(:@cron_at_index)
|
240
|
+
|
241
|
+
if @cron_at_index
|
242
|
+
execution_args[:cron_at] = CurrentThread.cron_at
|
243
|
+
else
|
244
|
+
_migration_pending_warning
|
245
|
+
end
|
246
|
+
elsif CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
|
232
247
|
execution_args[:cron_key] = CurrentThread.execution.cron_key
|
233
248
|
end
|
234
249
|
|
@@ -239,7 +254,7 @@ module GoodJob
|
|
239
254
|
execution.save!
|
240
255
|
active_job.provider_job_id = execution.id
|
241
256
|
|
242
|
-
CurrentThread.execution.retried_good_job_id = execution.id if CurrentThread.
|
257
|
+
CurrentThread.execution.retried_good_job_id = execution.id if CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
|
243
258
|
|
244
259
|
execution
|
245
260
|
end
|
@@ -259,7 +274,7 @@ module GoodJob
|
|
259
274
|
result = execute
|
260
275
|
|
261
276
|
job_error = result.handled_error || result.unhandled_error
|
262
|
-
self.error =
|
277
|
+
self.error = [job_error.class, ERROR_MESSAGE_SEPARATOR, job_error.message].join if job_error
|
263
278
|
|
264
279
|
if result.unhandled_error && GoodJob.retry_on_unhandled_error
|
265
280
|
save!
|
@@ -279,29 +294,39 @@ module GoodJob
|
|
279
294
|
self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
|
280
295
|
end
|
281
296
|
|
297
|
+
def active_job
|
298
|
+
ActiveJob::Base.deserialize(active_job_data)
|
299
|
+
end
|
300
|
+
|
282
301
|
private
|
283
302
|
|
303
|
+
def active_job_data
|
304
|
+
serialized_params.deep_dup
|
305
|
+
.tap do |job_data|
|
306
|
+
job_data["provider_job_id"] = id
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
284
310
|
# @return [ExecutionResult]
|
285
311
|
def execute
|
286
|
-
GoodJob::CurrentThread.
|
287
|
-
|
312
|
+
GoodJob::CurrentThread.within do |current_thread|
|
313
|
+
current_thread.reset
|
314
|
+
current_thread.execution = self
|
288
315
|
|
289
|
-
|
290
|
-
|
316
|
+
# DEPRECATION: Remove deprecated `good_job:` parameter in GoodJob v3
|
317
|
+
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do
|
318
|
+
value = ActiveJob::Base.execute(active_job_data)
|
291
319
|
|
292
|
-
|
293
|
-
|
294
|
-
|
320
|
+
if value.is_a?(Exception)
|
321
|
+
handled_error = value
|
322
|
+
value = nil
|
323
|
+
end
|
324
|
+
handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
|
295
325
|
|
296
|
-
|
297
|
-
|
298
|
-
value
|
326
|
+
ExecutionResult.new(value: value, handled_error: handled_error)
|
327
|
+
rescue StandardError => e
|
328
|
+
ExecutionResult.new(value: nil, unhandled_error: e)
|
299
329
|
end
|
300
|
-
handled_error ||= GoodJob::CurrentThread.error_on_retry || GoodJob::CurrentThread.error_on_discard
|
301
|
-
|
302
|
-
ExecutionResult.new(value: value, handled_error: handled_error)
|
303
|
-
rescue StandardError => e
|
304
|
-
ExecutionResult.new(value: nil, unhandled_error: e)
|
305
330
|
end
|
306
331
|
end
|
307
332
|
end
|
data/lib/good_job/lockable.rb
CHANGED
@@ -59,11 +59,11 @@ module GoodJob
|
|
59
59
|
|
60
60
|
# @!macro notification_responder
|
61
61
|
def cron_manager_start(event)
|
62
|
-
|
63
|
-
cron_jobs_count =
|
62
|
+
cron_entries = event.payload[:cron_entries]
|
63
|
+
cron_jobs_count = cron_entries.size
|
64
64
|
|
65
65
|
info do
|
66
|
-
"GoodJob started cron with #{cron_jobs_count} #{'
|
66
|
+
"GoodJob started cron with #{cron_jobs_count} #{'job'.pluralize(cron_jobs_count)}."
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -230,6 +230,7 @@ module GoodJob # :nodoc:
|
|
230
230
|
# @return [void]
|
231
231
|
def create_task(delay = 0)
|
232
232
|
future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
|
233
|
+
Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
|
233
234
|
Rails.application.reloader.wrap do
|
234
235
|
thr_performer.next
|
235
236
|
end
|
data/lib/good_job/version.rb
CHANGED
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: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -126,16 +126,16 @@ dependencies:
|
|
126
126
|
name: capybara
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 3.35.0
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - "
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 3.35.0
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: database_cleaner
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -351,26 +351,31 @@ files:
|
|
351
351
|
- engine/app/assets/vendor/bootstrap/bootstrap.min.css
|
352
352
|
- engine/app/assets/vendor/chartist/chartist.css
|
353
353
|
- engine/app/assets/vendor/chartist/chartist.js
|
354
|
+
- engine/app/assets/vendor/rails_ujs.js
|
354
355
|
- engine/app/controllers/good_job/assets_controller.rb
|
355
356
|
- engine/app/controllers/good_job/base_controller.rb
|
356
|
-
- engine/app/controllers/good_job/
|
357
|
+
- engine/app/controllers/good_job/cron_entries_controller.rb
|
357
358
|
- engine/app/controllers/good_job/executions_controller.rb
|
358
359
|
- engine/app/controllers/good_job/jobs_controller.rb
|
359
360
|
- engine/app/filters/good_job/base_filter.rb
|
360
361
|
- engine/app/filters/good_job/executions_filter.rb
|
361
362
|
- engine/app/filters/good_job/jobs_filter.rb
|
362
363
|
- engine/app/helpers/good_job/application_helper.rb
|
363
|
-
- engine/app/
|
364
|
-
- engine/app/views/good_job/
|
364
|
+
- engine/app/views/good_job/cron_entries/index.html.erb
|
365
|
+
- engine/app/views/good_job/cron_entries/show.html.erb
|
366
|
+
- engine/app/views/good_job/executions/_table.erb
|
365
367
|
- engine/app/views/good_job/executions/index.html.erb
|
368
|
+
- engine/app/views/good_job/jobs/_table.erb
|
366
369
|
- engine/app/views/good_job/jobs/index.html.erb
|
367
370
|
- engine/app/views/good_job/jobs/show.html.erb
|
368
371
|
- engine/app/views/good_job/shared/_chart.erb
|
369
|
-
- engine/app/views/good_job/shared/_executions_table.erb
|
370
372
|
- engine/app/views/good_job/shared/_filter.erb
|
371
|
-
- engine/app/views/good_job/shared/
|
373
|
+
- engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb
|
372
374
|
- engine/app/views/good_job/shared/icons/_check.html.erb
|
373
375
|
- engine/app/views/good_job/shared/icons/_exclamation.html.erb
|
376
|
+
- engine/app/views/good_job/shared/icons/_play.html.erb
|
377
|
+
- engine/app/views/good_job/shared/icons/_skip_forward.html.erb
|
378
|
+
- engine/app/views/good_job/shared/icons/_stop.html.erb
|
374
379
|
- engine/app/views/good_job/shared/icons/_trash.html.erb
|
375
380
|
- engine/app/views/layouts/good_job/base.html.erb
|
376
381
|
- engine/config/routes.rb
|
@@ -379,14 +384,18 @@ files:
|
|
379
384
|
- lib/active_job/queue_adapters/good_job_adapter.rb
|
380
385
|
- lib/generators/good_job/install_generator.rb
|
381
386
|
- lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
|
382
|
-
- lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb
|
387
|
+
- lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
|
388
|
+
- lib/generators/good_job/templates/update/migrations/02_add_cron_at_to_good_jobs.rb.erb
|
389
|
+
- lib/generators/good_job/templates/update/migrations/03_add_cron_key_cron_at_index_to_good_jobs.rb.erb
|
383
390
|
- lib/generators/good_job/update_generator.rb
|
384
391
|
- lib/good_job.rb
|
385
392
|
- lib/good_job/active_job_extensions.rb
|
386
393
|
- lib/good_job/active_job_extensions/concurrency.rb
|
394
|
+
- lib/good_job/active_job_job.rb
|
387
395
|
- lib/good_job/adapter.rb
|
388
396
|
- lib/good_job/cli.rb
|
389
397
|
- lib/good_job/configuration.rb
|
398
|
+
- lib/good_job/cron_entry.rb
|
390
399
|
- lib/good_job/cron_manager.rb
|
391
400
|
- lib/good_job/current_thread.rb
|
392
401
|
- lib/good_job/daemon.rb
|
@@ -1,127 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module GoodJob
|
3
|
-
# ActiveRecord model that represents an +ActiveJob+ job.
|
4
|
-
# Is the same record data as a {GoodJob::Execution} but only the most recent execution.
|
5
|
-
# Parent class can be configured with +GoodJob.active_record_parent_class+.
|
6
|
-
# @!parse
|
7
|
-
# class ActiveJob < ActiveRecord::Base; end
|
8
|
-
class ActiveJobJob < Object.const_get(GoodJob.active_record_parent_class)
|
9
|
-
include GoodJob::Lockable
|
10
|
-
|
11
|
-
self.table_name = 'good_jobs'
|
12
|
-
self.primary_key = 'active_job_id'
|
13
|
-
self.advisory_lockable_column = 'active_job_id'
|
14
|
-
|
15
|
-
has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id'
|
16
|
-
|
17
|
-
# Only the most-recent unretried execution represents a "Job"
|
18
|
-
default_scope { where(retried_good_job_id: nil) }
|
19
|
-
|
20
|
-
# Get Jobs with given class name
|
21
|
-
# @!method job_class
|
22
|
-
# @!scope class
|
23
|
-
# @param string [String]
|
24
|
-
# Execution class name
|
25
|
-
# @return [ActiveRecord::Relation]
|
26
|
-
scope :job_class, ->(job_class) { where("serialized_params->>'job_class' = ?", job_class) }
|
27
|
-
|
28
|
-
# First execution will run in the future
|
29
|
-
scope :scheduled, -> { where(finished_at: nil).where('COALESCE(scheduled_at, created_at) > ?', DateTime.current).where("(serialized_params->>'executions')::integer < 2") }
|
30
|
-
# Execution errored, will run in the future
|
31
|
-
scope :retried, -> { where(finished_at: nil).where('COALESCE(scheduled_at, created_at) > ?', DateTime.current).where("(serialized_params->>'executions')::integer > 1") }
|
32
|
-
# Immediate/Scheduled time to run has passed, waiting for an available thread run
|
33
|
-
scope :queued, -> { where(finished_at: nil).where('COALESCE(scheduled_at, created_at) <= ?', DateTime.current).joins_advisory_locks.where(pg_locks: { locktype: nil }) }
|
34
|
-
# Advisory locked and executing
|
35
|
-
scope :running, -> { where(finished_at: nil).joins_advisory_locks.where.not(pg_locks: { locktype: nil }) }
|
36
|
-
# Completed executing successfully
|
37
|
-
scope :finished, -> { where.not(finished_at: nil).where(error: nil) }
|
38
|
-
# Errored but will not be retried
|
39
|
-
scope :discarded, -> { where.not(finished_at: nil).where.not(error: nil) }
|
40
|
-
|
41
|
-
# Get Jobs in display order with optional keyset pagination.
|
42
|
-
# @!method display_all(after_scheduled_at: nil, after_id: nil)
|
43
|
-
# @!scope class
|
44
|
-
# @param after_scheduled_at [DateTime, String, nil]
|
45
|
-
# Display records scheduled after this time for keyset pagination
|
46
|
-
# @param after_id [Numeric, String, nil]
|
47
|
-
# Display records after this ID for keyset pagination
|
48
|
-
# @return [ActiveRecord::Relation]
|
49
|
-
scope :display_all, (lambda do |after_scheduled_at: nil, after_id: nil|
|
50
|
-
query = order(Arel.sql('COALESCE(scheduled_at, created_at) DESC, id DESC'))
|
51
|
-
if after_scheduled_at.present? && after_id.present?
|
52
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at), id) < (:after_scheduled_at, :after_id)'), after_scheduled_at: after_scheduled_at, after_id: after_id)
|
53
|
-
elsif after_scheduled_at.present?
|
54
|
-
query = query.where(Arel.sql('(COALESCE(scheduled_at, created_at)) < (:after_scheduled_at)'), after_scheduled_at: after_scheduled_at)
|
55
|
-
end
|
56
|
-
query
|
57
|
-
end)
|
58
|
-
|
59
|
-
def id
|
60
|
-
active_job_id
|
61
|
-
end
|
62
|
-
|
63
|
-
def _execution_id
|
64
|
-
attributes['id']
|
65
|
-
end
|
66
|
-
|
67
|
-
def job_class
|
68
|
-
serialized_params['job_class']
|
69
|
-
end
|
70
|
-
|
71
|
-
def status
|
72
|
-
if finished_at.present?
|
73
|
-
if error.present?
|
74
|
-
:discarded
|
75
|
-
else
|
76
|
-
:finished
|
77
|
-
end
|
78
|
-
elsif (scheduled_at || created_at) > DateTime.current
|
79
|
-
if serialized_params.fetch('executions', 0) > 1
|
80
|
-
:retried
|
81
|
-
else
|
82
|
-
:scheduled
|
83
|
-
end
|
84
|
-
elsif running?
|
85
|
-
:running
|
86
|
-
else
|
87
|
-
:queued
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def head_execution
|
92
|
-
executions.last
|
93
|
-
end
|
94
|
-
|
95
|
-
def tail_execution
|
96
|
-
executions.first
|
97
|
-
end
|
98
|
-
|
99
|
-
def executions_count
|
100
|
-
aj_count = serialized_params.fetch('executions', 0)
|
101
|
-
# The execution count within serialized_params is not updated
|
102
|
-
# once the underlying execution has been executed.
|
103
|
-
if status.in? [:discarded, :finished, :running]
|
104
|
-
aj_count + 1
|
105
|
-
else
|
106
|
-
aj_count
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def preserved_executions_count
|
111
|
-
executions.size
|
112
|
-
end
|
113
|
-
|
114
|
-
def recent_error
|
115
|
-
error.presence || executions[-2]&.error
|
116
|
-
end
|
117
|
-
|
118
|
-
def running?
|
119
|
-
# Avoid N+1 Query: `.joins_advisory_locks.select('good_jobs.*', 'pg_locks.locktype AS locktype')`
|
120
|
-
if has_attribute?(:locktype)
|
121
|
-
self['locktype'].present?
|
122
|
-
else
|
123
|
-
advisory_locked?
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
<% if @cron_schedules.present? %>
|
2
|
-
<div class="card my-3">
|
3
|
-
<div class="table-responsive">
|
4
|
-
<table class="table card-table table-bordered table-hover table-sm mb-0">
|
5
|
-
<thead>
|
6
|
-
<th>Cron Job Name</th>
|
7
|
-
<th>Configuration</th>
|
8
|
-
<th>
|
9
|
-
Set
|
10
|
-
<%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
11
|
-
data: { bs_toggle: "collapse", bs_target: ".job-properties" },
|
12
|
-
aria: { expanded: false, controls: @cron_schedules.map { |job_key, _| "##{job_key.to_param}" }.join(" ") }
|
13
|
-
%>
|
14
|
-
</th>
|
15
|
-
<th>Class</th>
|
16
|
-
<th>Description</th>
|
17
|
-
<th>Next scheduled</th>
|
18
|
-
</thead>
|
19
|
-
<tbody>
|
20
|
-
<% @cron_schedules.each do |job_key, job| %>
|
21
|
-
<tr>
|
22
|
-
<td class="font-monospace"><%= job_key %></td>
|
23
|
-
<td class="font-monospace"><%= job[:cron] %></td>
|
24
|
-
<td>
|
25
|
-
<%=
|
26
|
-
case job[:set]
|
27
|
-
when NilClass
|
28
|
-
"None"
|
29
|
-
when Proc
|
30
|
-
"Lambda/Callable"
|
31
|
-
when Hash
|
32
|
-
tag.button("Preview", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
|
33
|
-
data: { bs_toggle: "collapse", bs_target: "##{job_key.to_param}" },
|
34
|
-
aria: { expanded: false, controls: job_key.to_param }) +
|
35
|
-
tag.pre(JSON.pretty_generate(job[:set]), id: job_key.to_param, class: "collapse job-properties")
|
36
|
-
end
|
37
|
-
%>
|
38
|
-
</td>
|
39
|
-
<td class="font-monospace"><%= job[:class] %></td>
|
40
|
-
<td><%= job[:description] %></td>
|
41
|
-
<td><%= Fugit.parse_cron(job[:cron]).next_time.to_local_time %></td>
|
42
|
-
</tr>
|
43
|
-
<% end %>
|
44
|
-
</tbody>
|
45
|
-
</table>
|
46
|
-
</div>
|
47
|
-
</div>
|
48
|
-
<% else %>
|
49
|
-
<em>No cron jobs present.</em>
|
50
|
-
<% end %>
|