sidekiq-scheduler 2.2.2 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +35 -9
- data/lib/sidekiq-scheduler/extensions/web.rb +7 -1
- data/lib/sidekiq-scheduler/job_presenter.rb +1 -1
- data/lib/sidekiq-scheduler/manager.rb +29 -11
- data/lib/sidekiq-scheduler/redis_manager.rb +8 -1
- data/lib/sidekiq-scheduler/scheduler.rb +261 -314
- data/lib/sidekiq-scheduler/utils.rb +86 -0
- data/lib/sidekiq-scheduler/version.rb +1 -1
- data/lib/sidekiq-scheduler/web.rb +4 -4
- data/lib/sidekiq-scheduler.rb +2 -27
- data/web/assets/stylesheets/recurring_jobs.css +23 -0
- data/web/locales/ja.yml +14 -0
- data/web/views/recurring_jobs.erb +46 -44
- metadata +35 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aff61224563645891063110d5f9ae5d16e6ccb0fee92df6a5f26976d0169c01b
|
4
|
+
data.tar.gz: d8a6ac2c6e17426bc095904ac3721a9433a21cfd1be433e5f280b9af5469954a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b01ea69bb3bafd959681e3f7cb3a5699385bdf73bf8abeb8e75a6e2765ec1476862f1d499046ae00233539ec266d313c55c03558961fab8f02b048e4164778bd
|
7
|
+
data.tar.gz: 7662c439f79b05f2954cfc0e1da2d0a84236334f75101431ed187ae8c69df47baa52cbee901e90a84e81b7ed5b7f81198938ca09cf3710f5885866b334cfcb43
|
data/README.md
CHANGED
@@ -30,6 +30,8 @@
|
|
30
30
|
`sidekiq-scheduler` is an extension to [Sidekiq](http://github.com/mperham/sidekiq) that
|
31
31
|
pushes jobs in a scheduled way, mimicking cron utility.
|
32
32
|
|
33
|
+
__Note:__ If you are looking for version 2.2.*, go to [2.2-stable branch](https://github.com/moove-it/sidekiq-scheduler/tree/2.2-stable).
|
34
|
+
|
33
35
|
## Installation
|
34
36
|
|
35
37
|
``` shell
|
@@ -177,9 +179,19 @@ Cron, every, and interval types push jobs into sidekiq in a recurrent manner.
|
|
177
179
|
every: '45m' # Runs every 45 minutes
|
178
180
|
```
|
179
181
|
|
182
|
+
The value is parsed by [`Fugit::Duration.parse`](https://github.com/floraison/fugit#fugitduration). It understands quite a number of formats, including human-readable ones:
|
183
|
+
|
184
|
+
``` yaml
|
185
|
+
every: 45 minutes
|
186
|
+
every: 2 hours and 30 minutes
|
187
|
+
every: 1.5 hours
|
188
|
+
```
|
189
|
+
|
180
190
|
`interval` is similar to `every`, the difference between them is that `interval` type schedules the
|
181
191
|
next execution after the interval has elapsed counting from its last job enqueue.
|
182
192
|
|
193
|
+
Note that `every` and `interval` count from when the Sidekiq process (re)starts. So `every: '48h'` will never run if the Sidekiq process is restarted daily, for example. You can do `every: ['48h', first_in: '0s']` to make the job run immediately after a restart, and then have the worker check when it was last run.
|
194
|
+
|
183
195
|
At, and in types push jobs only once. `at` schedules in a point in time:
|
184
196
|
``` yaml
|
185
197
|
at: '3001/01/01'
|
@@ -228,7 +240,7 @@ require 'sidekiq-scheduler'
|
|
228
240
|
Sidekiq.configure_server do |config|
|
229
241
|
config.on(:startup) do
|
230
242
|
Sidekiq.schedule = YAML.load_file(File.expand_path('../../sidekiq_scheduler.yml', __FILE__))
|
231
|
-
|
243
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
232
244
|
end
|
233
245
|
end
|
234
246
|
```
|
@@ -256,7 +268,7 @@ If `:dynamic` flag is set to `false`, you'll have to reload the schedule manuall
|
|
256
268
|
side:
|
257
269
|
|
258
270
|
``` ruby
|
259
|
-
|
271
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
260
272
|
```
|
261
273
|
|
262
274
|
Invoke `Sidekiq.get_schedule` to obtain the current schedule:
|
@@ -268,8 +280,9 @@ Sidekiq.get_schedule
|
|
268
280
|
|
269
281
|
## Time zones
|
270
282
|
|
271
|
-
Note that if you use the cron syntax, this will be interpreted
|
272
|
-
|
283
|
+
Note that if you use the cron syntax and are not running a Rails app, this will be interpreted in the server time zone.
|
284
|
+
|
285
|
+
In a Rails app, [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) (>= 3.3.3) will use the `config.time_zone` specified in Rails.
|
273
286
|
|
274
287
|
You can explicitly specify the time zone that rufus-scheduler will use:
|
275
288
|
|
@@ -294,7 +307,7 @@ That's a minimum of `concurrency` + 5 (per the [Sidekiq wiki](https://github.com
|
|
294
307
|
You can also override the thread pool size in Rufus Scheduler by setting e.g.:
|
295
308
|
|
296
309
|
```
|
297
|
-
|
310
|
+
SidekiqScheduler::Scheduler.instance.rufus_scheduler_options = { max_work_threads: 5 }
|
298
311
|
```
|
299
312
|
|
300
313
|
## Notes about running on Multiple Hosts
|
@@ -308,6 +321,19 @@ Non-normal conditions that could push a specific job multiple times are:
|
|
308
321
|
|
309
322
|
`every`, `interval` and `in` jobs will be pushed once per host.
|
310
323
|
|
324
|
+
## Notes on when sidekiq worker is down
|
325
|
+
|
326
|
+
For a `cron`/`at` (and all other) job to be successfully enqueued, you need at least one sidekiq worker with scheduler to be up at that moment. Handling this is up to you and depends on your application.
|
327
|
+
|
328
|
+
Possible solutions include:
|
329
|
+
- Simply ignoring this fact, if you only run frequent periodic jobs, that can tolerate some increased interval
|
330
|
+
- Abstaining from deploys/restarts during time when critical jobs are usually scheduled
|
331
|
+
- Making your infrequent jobs idempotent (so that they can be enqueued multiple times but still produce result as if was run once) and scheduling them multiple times to reduce likelihood of not being run
|
332
|
+
- Zero downtime deploy for sidekiq workers: keep at least one worker up during whole deploy and only restart/shut it down after when new one has started
|
333
|
+
- Running scheduler inside your unicorn/rails processes (if you already have zero downtime deploy set up for these)
|
334
|
+
|
335
|
+
Each option has it's own pros and cons.
|
336
|
+
|
311
337
|
## Sidekiq Web Integration
|
312
338
|
|
313
339
|
sidekiq-scheduler provides an extension to the Sidekiq web interface that adds a `Recurring Jobs` page.
|
@@ -367,12 +393,12 @@ if Rails.env == 'production' && (defined?(Rails::Server) || defined?(Unicorn))
|
|
367
393
|
|
368
394
|
config.on(:startup) do
|
369
395
|
Sidekiq.schedule = YAML.load_file(File.expand_path('../../scheduler.yml', __FILE__))
|
370
|
-
|
396
|
+
SidekiqScheduler::Scheduler.instance.reload_schedule!
|
371
397
|
end
|
372
398
|
end
|
373
399
|
else
|
374
|
-
|
375
|
-
puts "
|
400
|
+
SidekiqScheduler::Scheduler.instance.enabled = false
|
401
|
+
puts "SidekiqScheduler::Scheduler.instance.enabled is #{SidekiqScheduler::Scheduler.instance.enabled.inspect}"
|
376
402
|
end
|
377
403
|
```
|
378
404
|
|
@@ -382,6 +408,6 @@ MIT License
|
|
382
408
|
|
383
409
|
## Copyright
|
384
410
|
|
385
|
-
Copyright 2013 -
|
411
|
+
Copyright 2013 - 2018 Moove-IT.
|
386
412
|
Copyright 2012 Morton Jonuschat.
|
387
413
|
Some parts copyright 2010 Ben VandenBos.
|
@@ -1,5 +1,11 @@
|
|
1
1
|
require 'sidekiq/web' unless defined?(Sidekiq::Web)
|
2
2
|
|
3
|
+
ASSETS_PATH = File.expand_path('../../../web/assets', __dir__)
|
4
|
+
|
3
5
|
Sidekiq::Web.register(SidekiqScheduler::Web)
|
4
6
|
Sidekiq::Web.tabs['recurring_jobs'] = 'recurring-jobs'
|
5
|
-
Sidekiq::Web.locales << File.expand_path(File.dirname(__FILE__)
|
7
|
+
Sidekiq::Web.locales << File.expand_path("#{File.dirname(__FILE__)}/../../../web/locales")
|
8
|
+
Sidekiq::Web.use Rack::Static, urls: ['/stylesheets'],
|
9
|
+
root: ASSETS_PATH,
|
10
|
+
cascade: true,
|
11
|
+
header_rules: [[:all, { 'Cache-Control' => 'public, max-age=86400' }]]
|
@@ -14,26 +14,44 @@ module SidekiqScheduler
|
|
14
14
|
class Manager
|
15
15
|
include Sidekiq::Util
|
16
16
|
|
17
|
+
DEFAULT_SCHEDULER_OPTIONS = {
|
18
|
+
enabled: true,
|
19
|
+
dynamic: false,
|
20
|
+
dynamic_every: '5s',
|
21
|
+
schedule: {}
|
22
|
+
}
|
23
|
+
|
17
24
|
def initialize(options)
|
18
|
-
|
19
|
-
|
20
|
-
SidekiqScheduler::Scheduler.
|
21
|
-
SidekiqScheduler::Scheduler.
|
22
|
-
Sidekiq.schedule =
|
25
|
+
scheduler_options = load_scheduler_options(options)
|
26
|
+
|
27
|
+
@scheduler_instance = SidekiqScheduler::Scheduler.new(scheduler_options)
|
28
|
+
SidekiqScheduler::Scheduler.instance = @scheduler_instance
|
29
|
+
Sidekiq.schedule = scheduler_options[:schedule] if @scheduler_instance.enabled
|
23
30
|
end
|
24
31
|
|
25
32
|
def stop
|
26
|
-
|
33
|
+
@scheduler_instance.clear_schedule!
|
27
34
|
end
|
28
35
|
|
29
36
|
def start
|
30
|
-
|
37
|
+
@scheduler_instance.load_schedule!
|
31
38
|
end
|
32
39
|
|
33
|
-
|
34
|
-
clear_scheduled_work
|
35
|
-
end
|
40
|
+
private
|
36
41
|
|
37
|
-
|
42
|
+
def load_scheduler_options(options)
|
43
|
+
options[:listened_queues_only] = options.fetch(:scheduler, {})[:listened_queues_only]
|
44
|
+
scheduler_options = DEFAULT_SCHEDULER_OPTIONS.merge(options)
|
38
45
|
|
46
|
+
current_options = {
|
47
|
+
enabled: SidekiqScheduler::Scheduler.enabled,
|
48
|
+
dynamic: SidekiqScheduler::Scheduler.dynamic,
|
49
|
+
dynamic_every: SidekiqScheduler::Scheduler.dynamic_every,
|
50
|
+
schedule: Sidekiq.schedule,
|
51
|
+
listened_queues_only: SidekiqScheduler::Scheduler.listened_queues_only
|
52
|
+
}.delete_if { |_, value| value.nil? }
|
53
|
+
|
54
|
+
scheduler_options.merge(current_options)
|
55
|
+
end
|
56
|
+
end
|
39
57
|
end
|
@@ -96,7 +96,14 @@ module SidekiqScheduler
|
|
96
96
|
#
|
97
97
|
# @return [Boolean] true if the schedules key is set, false otherwise
|
98
98
|
def self.schedule_exist?
|
99
|
-
Sidekiq.redis
|
99
|
+
Sidekiq.redis do |r|
|
100
|
+
case r.exists(:schedules)
|
101
|
+
when true, 1
|
102
|
+
true
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
100
107
|
end
|
101
108
|
|
102
109
|
# Returns all the schedule changes for a given time range.
|
@@ -2,7 +2,6 @@ require 'rufus/scheduler'
|
|
2
2
|
require 'thwait'
|
3
3
|
require 'sidekiq/util'
|
4
4
|
require 'json'
|
5
|
-
require 'sidekiq-scheduler/manager'
|
6
5
|
require 'sidekiq-scheduler/rufus_utils'
|
7
6
|
require 'sidekiq-scheduler/redis_manager'
|
8
7
|
|
@@ -10,400 +9,348 @@ module SidekiqScheduler
|
|
10
9
|
class Scheduler
|
11
10
|
extend Sidekiq::Util
|
12
11
|
|
13
|
-
RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
|
14
|
-
|
15
12
|
# We expect rufus jobs to have #params
|
16
13
|
Rufus::Scheduler::Job.module_eval do
|
17
|
-
|
18
14
|
alias_method :params, :opts
|
19
|
-
|
20
15
|
end
|
21
16
|
|
22
|
-
|
17
|
+
# Set to enable or disable the scheduler.
|
18
|
+
attr_accessor :enabled
|
23
19
|
|
24
|
-
|
25
|
-
|
20
|
+
# Set to update the schedule in runtime in a given time period.
|
21
|
+
attr_accessor :dynamic
|
26
22
|
|
27
|
-
|
28
|
-
|
23
|
+
# Set to update the schedule in runtime dynamically per this period.
|
24
|
+
attr_accessor :dynamic_every
|
29
25
|
|
30
|
-
|
31
|
-
|
26
|
+
# Set to schedule jobs only when will be pushed to queues listened by sidekiq
|
27
|
+
attr_accessor :listened_queues_only
|
32
28
|
|
33
|
-
|
34
|
-
attr_accessor :listened_queues_only
|
29
|
+
class << self
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
def instance
|
32
|
+
@instance = new unless @instance
|
33
|
+
@instance
|
39
34
|
end
|
40
35
|
|
41
|
-
def
|
42
|
-
|
43
|
-
logger.info "Scheduling Info\tLast Run"
|
44
|
-
scheduler_jobs = rufus_scheduler.all_jobs
|
45
|
-
scheduler_jobs.each_value do |v|
|
46
|
-
logger.info "#{v.t}\t#{v.last}\t"
|
47
|
-
end
|
48
|
-
end
|
36
|
+
def instance=(value)
|
37
|
+
@instance = value
|
49
38
|
end
|
50
39
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
logger.info 'Loading Schedule'
|
56
|
-
|
57
|
-
# Load schedule from redis for the first time if dynamic
|
58
|
-
if dynamic
|
59
|
-
Sidekiq.reload_schedule!
|
60
|
-
@current_changed_score = Time.now.to_f
|
61
|
-
rufus_scheduler.every(dynamic_every) do
|
62
|
-
update_schedule
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
67
|
-
|
40
|
+
def method_missing(method, *arguments, &block)
|
41
|
+
instance_methods.include?(method) ? instance.public_send(method, *arguments) : super
|
42
|
+
end
|
43
|
+
end
|
68
44
|
|
69
|
-
|
70
|
-
|
45
|
+
def initialize(options = {})
|
46
|
+
self.enabled = options[:enabled]
|
47
|
+
self.dynamic = options[:dynamic]
|
48
|
+
self.dynamic_every = options[:dynamic_every]
|
49
|
+
self.listened_queues_only = options[:listened_queues_only]
|
50
|
+
end
|
71
51
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
logger.info { "Ignoring #{name}, job's queue is not enabled." }
|
77
|
-
end
|
78
|
-
end
|
52
|
+
# the Rufus::Scheduler jobs that are scheduled
|
53
|
+
def scheduled_jobs
|
54
|
+
@scheduled_jobs
|
55
|
+
end
|
79
56
|
|
80
|
-
|
81
|
-
|
82
|
-
|
57
|
+
def print_schedule
|
58
|
+
if rufus_scheduler
|
59
|
+
Sidekiq.logger.info "Scheduling Info\tLast Run"
|
60
|
+
scheduler_jobs = rufus_scheduler.all_jobs
|
61
|
+
scheduler_jobs.each_value do |v|
|
62
|
+
Sidekiq.logger.info "#{v.t}\t#{v.last}\t"
|
83
63
|
end
|
84
64
|
end
|
65
|
+
end
|
85
66
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# to.
|
92
|
-
if config['rails_env'].nil? || rails_env_matches?(config)
|
93
|
-
logger.info "Scheduling #{name} #{config}"
|
94
|
-
interval_defined = false
|
95
|
-
interval_types = %w{cron every at in interval}
|
96
|
-
interval_types.each do |interval_type|
|
97
|
-
config_interval_type = config[interval_type]
|
98
|
-
|
99
|
-
if !config_interval_type.nil? && config_interval_type.length > 0
|
100
|
-
|
101
|
-
schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
|
67
|
+
# Pulls the schedule from Sidekiq.schedule and loads it into the
|
68
|
+
# rufus scheduler instance
|
69
|
+
def load_schedule!
|
70
|
+
if enabled
|
71
|
+
Sidekiq.logger.info 'Loading Schedule'
|
102
72
|
|
103
|
-
|
104
|
-
|
105
|
-
|
73
|
+
# Load schedule from redis for the first time if dynamic
|
74
|
+
if dynamic
|
75
|
+
Sidekiq.reload_schedule!
|
76
|
+
@current_changed_score = Time.now.to_f
|
77
|
+
rufus_scheduler.every(dynamic_every) do
|
78
|
+
update_schedule
|
79
|
+
end
|
80
|
+
end
|
106
81
|
|
107
|
-
|
82
|
+
Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
108
83
|
|
109
|
-
|
110
|
-
|
111
|
-
end
|
84
|
+
@scheduled_jobs = {}
|
85
|
+
queues = sidekiq_queues
|
112
86
|
|
113
|
-
|
114
|
-
|
87
|
+
Sidekiq.schedule.each do |name, config|
|
88
|
+
if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
|
89
|
+
load_schedule_job(name, config)
|
90
|
+
else
|
91
|
+
Sidekiq.logger.info { "Ignoring #{name}, job's queue is not enabled." }
|
115
92
|
end
|
116
93
|
end
|
94
|
+
|
95
|
+
Sidekiq.logger.info 'Schedules Loaded'
|
96
|
+
else
|
97
|
+
Sidekiq.logger.info 'SidekiqScheduler is disabled'
|
117
98
|
end
|
99
|
+
end
|
118
100
|
|
119
|
-
|
120
|
-
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
|
125
|
-
|
101
|
+
# Loads a job schedule into the Rufus::Scheduler and stores it in @scheduled_jobs
|
102
|
+
def load_schedule_job(name, config)
|
103
|
+
# If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
|
104
|
+
# required for the jobs to be scheduled. If rails_env is missing, the
|
105
|
+
# job should be scheduled regardless of what ENV['RAILS_ENV'] is set
|
106
|
+
# to.
|
107
|
+
if config['rails_env'].nil? || rails_env_matches?(config)
|
108
|
+
Sidekiq.logger.info "Scheduling #{name} #{config}"
|
109
|
+
interval_defined = false
|
110
|
+
interval_types = %w(cron every at in interval)
|
111
|
+
interval_types.each do |interval_type|
|
112
|
+
config_interval_type = config[interval_type]
|
126
113
|
|
127
|
-
|
128
|
-
logger.info "queueing #{config['class']} (#{job_name})"
|
114
|
+
if !config_interval_type.nil? && config_interval_type.length > 0
|
129
115
|
|
130
|
-
|
116
|
+
schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
|
131
117
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
136
|
-
end
|
118
|
+
rufus_job = new_job(name, interval_type, config, schedule, options)
|
119
|
+
@scheduled_jobs[name] = rufus_job
|
120
|
+
SidekiqScheduler::Utils.update_job_next_time(name, rufus_job.next_time)
|
137
121
|
|
138
|
-
|
139
|
-
#
|
140
|
-
# @param [String] name The job's name
|
141
|
-
# @param [Time] next_time The job's next time execution
|
142
|
-
def update_job_next_time(name, next_time)
|
143
|
-
if next_time
|
144
|
-
SidekiqScheduler::RedisManager.set_job_next_time(name, next_time)
|
145
|
-
else
|
146
|
-
SidekiqScheduler::RedisManager.remove_job_next_time(name)
|
147
|
-
end
|
148
|
-
end
|
122
|
+
interval_defined = true
|
149
123
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
# @param [Time] last_time The job's last execution time
|
154
|
-
def update_job_last_time(name, last_time)
|
155
|
-
SidekiqScheduler::RedisManager.set_job_last_time(name, last_time) if last_time
|
156
|
-
end
|
157
|
-
|
158
|
-
# Returns true if the given schedule config hash matches the current
|
159
|
-
# ENV['RAILS_ENV']
|
160
|
-
def rails_env_matches?(config)
|
161
|
-
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
|
162
|
-
end
|
124
|
+
break
|
125
|
+
end
|
126
|
+
end
|
163
127
|
|
164
|
-
|
165
|
-
|
166
|
-
yield
|
167
|
-
rescue StandardError => e
|
168
|
-
logger.info "#{e.class.name}: #{e.message}"
|
128
|
+
unless interval_defined
|
129
|
+
Sidekiq.logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
|
169
130
|
end
|
170
131
|
end
|
132
|
+
end
|
171
133
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
134
|
+
# Pushes the job into Sidekiq if not already pushed for the given time
|
135
|
+
#
|
136
|
+
# @param [String] job_name The job's name
|
137
|
+
# @param [Time] time The time when the job got cleared for triggering
|
138
|
+
# @param [Hash] config Job's config hash
|
139
|
+
def idempotent_job_enqueue(job_name, time, config)
|
140
|
+
registered = SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
|
178
141
|
|
179
|
-
|
180
|
-
|
181
|
-
end
|
142
|
+
if registered
|
143
|
+
Sidekiq.logger.info "queueing #{config['class']} (#{job_name})"
|
182
144
|
|
183
|
-
|
184
|
-
enqueue_with_active_job(config)
|
185
|
-
else
|
186
|
-
enqueue_with_sidekiq(config)
|
187
|
-
end
|
188
|
-
end
|
145
|
+
handle_errors { enqueue_job(config, time) }
|
189
146
|
|
190
|
-
|
191
|
-
|
147
|
+
SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
|
148
|
+
else
|
149
|
+
Sidekiq.logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
|
192
150
|
end
|
151
|
+
end
|
193
152
|
|
194
|
-
|
195
|
-
|
196
|
-
|
153
|
+
# Enqueue a job based on a config hash
|
154
|
+
#
|
155
|
+
# @param job_config [Hash] the job configuration
|
156
|
+
# @param time [Time] time the job is enqueued
|
157
|
+
def enqueue_job(job_config, time = Time.now)
|
158
|
+
config = prepare_arguments(job_config.dup)
|
197
159
|
|
198
|
-
|
199
|
-
|
160
|
+
if config.delete('include_metadata')
|
161
|
+
config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
|
200
162
|
end
|
201
163
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
def clear_schedule!(stop_option = :wait)
|
207
|
-
rufus_scheduler.stop(stop_option)
|
208
|
-
@rufus_scheduler = nil
|
209
|
-
@@scheduled_jobs = {}
|
210
|
-
rufus_scheduler
|
164
|
+
if active_job_enqueue?(config['class'])
|
165
|
+
SidekiqScheduler::Utils.enqueue_with_active_job(config)
|
166
|
+
else
|
167
|
+
SidekiqScheduler::Utils.enqueue_with_sidekiq(config)
|
211
168
|
end
|
169
|
+
end
|
212
170
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
clear_schedule!
|
217
|
-
load_schedule!
|
218
|
-
else
|
219
|
-
logger.info 'SidekiqScheduler is disabled'
|
220
|
-
end
|
221
|
-
end
|
171
|
+
def rufus_scheduler_options
|
172
|
+
@rufus_scheduler_options ||= {}
|
173
|
+
end
|
222
174
|
|
223
|
-
|
224
|
-
|
225
|
-
|
175
|
+
def rufus_scheduler_options=(options)
|
176
|
+
@rufus_scheduler_options = options
|
177
|
+
end
|
226
178
|
|
227
|
-
|
228
|
-
|
179
|
+
def rufus_scheduler
|
180
|
+
@rufus_scheduler ||= SidekiqScheduler::Utils.new_rufus_scheduler(rufus_scheduler_options)
|
181
|
+
end
|
229
182
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
end
|
239
|
-
logger.info 'Schedule updated'
|
240
|
-
end
|
183
|
+
# Stops old rufus scheduler and creates a new one. Returns the new
|
184
|
+
# rufus scheduler
|
185
|
+
#
|
186
|
+
# @param [Symbol] stop_option The option to be passed to Rufus::Scheduler#stop
|
187
|
+
def clear_schedule!(stop_option = :wait)
|
188
|
+
if @rufus_scheduler
|
189
|
+
@rufus_scheduler.stop(stop_option)
|
190
|
+
@rufus_scheduler = nil
|
241
191
|
end
|
242
192
|
|
243
|
-
|
244
|
-
if scheduled_jobs[name]
|
245
|
-
logger.debug "Removing schedule #{name}"
|
246
|
-
scheduled_jobs[name].unschedule
|
247
|
-
scheduled_jobs.delete(name)
|
248
|
-
end
|
249
|
-
end
|
193
|
+
@@scheduled_jobs = {}
|
250
194
|
|
251
|
-
|
252
|
-
|
253
|
-
queue: config['queue']
|
254
|
-
}.keep_if { |_, v| !v.nil? }
|
195
|
+
rufus_scheduler
|
196
|
+
end
|
255
197
|
|
256
|
-
|
198
|
+
def reload_schedule!
|
199
|
+
if enabled
|
200
|
+
Sidekiq.logger.info 'Reloading Schedule'
|
201
|
+
clear_schedule!
|
202
|
+
load_schedule!
|
203
|
+
else
|
204
|
+
Sidekiq.logger.info 'SidekiqScheduler is disabled'
|
257
205
|
end
|
206
|
+
end
|
258
207
|
|
259
|
-
|
260
|
-
|
261
|
-
|
208
|
+
def update_schedule
|
209
|
+
last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
|
210
|
+
schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
|
262
211
|
|
263
|
-
|
264
|
-
|
265
|
-
klass.new(*args)
|
266
|
-
else
|
267
|
-
klass.new(args)
|
268
|
-
end
|
269
|
-
end
|
212
|
+
if schedule_changes.size > 0
|
213
|
+
Sidekiq.logger.info 'Updating schedule'
|
270
214
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
215
|
+
Sidekiq.reload_schedule!
|
216
|
+
schedule_changes.each do |schedule_name|
|
217
|
+
if Sidekiq.schedule.keys.include?(schedule_name)
|
218
|
+
unschedule_job(schedule_name)
|
219
|
+
load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
|
220
|
+
else
|
221
|
+
unschedule_job(schedule_name)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
Sidekiq.logger.info 'Schedule updated'
|
280
225
|
end
|
226
|
+
end
|
281
227
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
# @option config [Hash/Array] args the arguments to be passed to the job
|
287
|
-
# class
|
288
|
-
#
|
289
|
-
# @return [Hash]
|
290
|
-
def prepare_arguments(config)
|
291
|
-
config['class'] = try_to_constantize(config['class'])
|
292
|
-
|
293
|
-
if config['args'].is_a?(Hash)
|
294
|
-
config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
|
295
|
-
else
|
296
|
-
config['args'] = Array(config['args'])
|
297
|
-
end
|
228
|
+
def job_enabled?(name)
|
229
|
+
job = Sidekiq.schedule[name]
|
230
|
+
schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
|
231
|
+
end
|
298
232
|
|
299
|
-
|
300
|
-
|
233
|
+
def toggle_job_enabled(name)
|
234
|
+
state = schedule_state(name)
|
235
|
+
state['enabled'] = !job_enabled?(name)
|
236
|
+
set_schedule_state(name, state)
|
237
|
+
end
|
301
238
|
|
302
|
-
|
303
|
-
klass.is_a?(String) ? klass.constantize : klass
|
304
|
-
rescue NameError
|
305
|
-
klass
|
306
|
-
end
|
239
|
+
private
|
307
240
|
|
308
|
-
|
309
|
-
|
310
|
-
# If queues are empty, returns true.
|
311
|
-
#
|
312
|
-
# @param [String] job_queue Job's queue name
|
313
|
-
# @param [Array<String>] queues
|
314
|
-
#
|
315
|
-
# @return [Boolean]
|
316
|
-
def enabled_queue?(job_queue, queues)
|
317
|
-
queues.empty? || queues.include?(job_queue)
|
318
|
-
end
|
241
|
+
def new_job(name, interval_type, config, schedule, options)
|
242
|
+
options = options.merge({ :job => true, :tags => [name] })
|
319
243
|
|
320
|
-
|
321
|
-
|
322
|
-
# @param [String] job_name The job's name
|
323
|
-
# @param [Time] time Time at which the job was cleared by the scheduler
|
324
|
-
#
|
325
|
-
# @return [Boolean] true if the job was registered, false when otherwise
|
326
|
-
def register_job_instance(job_name, time)
|
327
|
-
SidekiqScheduler::RedisManager.register_job_instance(job_name, time)
|
244
|
+
rufus_scheduler.send(interval_type, schedule, options) do |job, time|
|
245
|
+
idempotent_job_enqueue(name, time, SidekiqScheduler::Utils.sanitize_job_config(config)) if job_enabled?(name)
|
328
246
|
end
|
247
|
+
end
|
329
248
|
|
330
|
-
|
331
|
-
|
249
|
+
def unschedule_job(name)
|
250
|
+
if scheduled_jobs[name]
|
251
|
+
Sidekiq.logger.debug "Removing schedule #{name}"
|
252
|
+
scheduled_jobs[name].unschedule
|
253
|
+
scheduled_jobs.delete(name)
|
332
254
|
end
|
255
|
+
end
|
333
256
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
257
|
+
# Retrieves a schedule state
|
258
|
+
#
|
259
|
+
# @param name [String] with the schedule's name
|
260
|
+
# @return [Hash] with the schedule's state
|
261
|
+
def schedule_state(name)
|
262
|
+
state = SidekiqScheduler::RedisManager.get_job_state(name)
|
338
263
|
|
339
|
-
|
340
|
-
|
341
|
-
state['enabled'] = !job_enabled?(name)
|
342
|
-
set_schedule_state(name, state)
|
343
|
-
end
|
264
|
+
state ? JSON.parse(state) : {}
|
265
|
+
end
|
344
266
|
|
345
|
-
|
267
|
+
# Saves a schedule state
|
268
|
+
#
|
269
|
+
# @param name [String] with the schedule's name
|
270
|
+
# @param name [Hash] with the schedule's state
|
271
|
+
def set_schedule_state(name, state)
|
272
|
+
SidekiqScheduler::RedisManager.set_job_state(name, state)
|
273
|
+
end
|
346
274
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
275
|
+
# Adds a Hash with schedule metadata as the last argument to call the worker.
|
276
|
+
# It currently returns the schedule time as a Float number representing the milisencods
|
277
|
+
# since epoch.
|
278
|
+
#
|
279
|
+
# @example with hash argument
|
280
|
+
# arguments_with_metadata({value: 1}, scheduled_at: Time.now)
|
281
|
+
# #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
|
282
|
+
#
|
283
|
+
# @param args [Array|Hash]
|
284
|
+
# @param metadata [Hash]
|
285
|
+
# @return [Array] arguments with added metadata
|
286
|
+
def arguments_with_metadata(args, metadata)
|
287
|
+
if args.is_a? Array
|
288
|
+
[*args, metadata]
|
289
|
+
else
|
290
|
+
[args, metadata]
|
354
291
|
end
|
292
|
+
end
|
355
293
|
|
356
|
-
|
357
|
-
|
294
|
+
def sidekiq_queues
|
295
|
+
Sidekiq.options[:queues].map(&:to_s)
|
296
|
+
end
|
358
297
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
298
|
+
# Returns true if a job's queue is included in the array of queues
|
299
|
+
#
|
300
|
+
# If queues are empty, returns true.
|
301
|
+
#
|
302
|
+
# @param [String] job_queue Job's queue name
|
303
|
+
# @param [Array<String>] queues
|
304
|
+
#
|
305
|
+
# @return [Boolean]
|
306
|
+
def enabled_queue?(job_queue, queues)
|
307
|
+
queues.empty? || queues.include?(job_queue)
|
308
|
+
end
|
363
309
|
|
364
|
-
|
365
|
-
|
366
|
-
|
310
|
+
# Returns true if the enqueuing needs to be done for an ActiveJob
|
311
|
+
# class false otherwise.
|
312
|
+
#
|
313
|
+
# @param [Class] klass the class to check is decendant from ActiveJob
|
314
|
+
#
|
315
|
+
# @return [Boolean]
|
316
|
+
def active_job_enqueue?(klass)
|
317
|
+
klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
|
318
|
+
klass.included_modules.include?(ActiveJob::Enqueuing)
|
319
|
+
end
|
367
320
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
321
|
+
# Convert the given arguments in the format expected to be enqueued.
|
322
|
+
#
|
323
|
+
# @param [Hash] config the options to be converted
|
324
|
+
# @option config [String] class the job class
|
325
|
+
# @option config [Hash/Array] args the arguments to be passed to the job
|
326
|
+
# class
|
327
|
+
#
|
328
|
+
# @return [Hash]
|
329
|
+
def prepare_arguments(config)
|
330
|
+
config['class'] = SidekiqScheduler::Utils.try_to_constantize(config['class'])
|
374
331
|
|
375
|
-
|
332
|
+
if config['args'].is_a?(Hash)
|
333
|
+
config['args'].symbolize_keys! if config['args'].respond_to?(:symbolize_keys!)
|
334
|
+
else
|
335
|
+
config['args'] = Array(config['args'])
|
376
336
|
end
|
377
337
|
|
378
|
-
|
379
|
-
|
380
|
-
# @param name [String] with the schedule's name
|
381
|
-
# @param name [Hash] with the schedule's state
|
382
|
-
def set_schedule_state(name, state)
|
383
|
-
SidekiqScheduler::RedisManager.set_job_state(name, state)
|
384
|
-
end
|
338
|
+
config
|
339
|
+
end
|
385
340
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
#
|
394
|
-
# @param args [Array|Hash]
|
395
|
-
# @param metadata [Hash]
|
396
|
-
# @return [Array] arguments with added metadata
|
397
|
-
def arguments_with_metadata(args, metadata)
|
398
|
-
if args.is_a? Array
|
399
|
-
[*args, metadata]
|
400
|
-
else
|
401
|
-
[args, metadata]
|
402
|
-
end
|
403
|
-
end
|
341
|
+
# Returns true if the given schedule config hash matches the current ENV['RAILS_ENV']
|
342
|
+
# @param [Hash] config The schedule job configuration
|
343
|
+
#
|
344
|
+
# @return [Boolean] true if the schedule config matches the current ENV['RAILS_ENV']
|
345
|
+
def rails_env_matches?(config)
|
346
|
+
config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
|
347
|
+
end
|
404
348
|
|
405
|
-
|
406
|
-
|
349
|
+
def handle_errors
|
350
|
+
begin
|
351
|
+
yield
|
352
|
+
rescue StandardError => e
|
353
|
+
Sidekiq.logger.info "#{e.class.name}: #{e.message}"
|
407
354
|
end
|
408
355
|
end
|
409
356
|
end
|
@@ -3,6 +3,8 @@ require 'set'
|
|
3
3
|
module SidekiqScheduler
|
4
4
|
module Utils
|
5
5
|
|
6
|
+
RUFUS_METADATA_KEYS = %w(description at cron every in interval enabled)
|
7
|
+
|
6
8
|
# Stringify keys belonging to a hash.
|
7
9
|
#
|
8
10
|
# Also stringifies nested keys and keys of hashes inside arrays, and sets
|
@@ -40,5 +42,89 @@ module SidekiqScheduler
|
|
40
42
|
object
|
41
43
|
end
|
42
44
|
end
|
45
|
+
|
46
|
+
# Constantize a given string.
|
47
|
+
#
|
48
|
+
# @param [String] klass The string to constantize
|
49
|
+
#
|
50
|
+
# @return [Class] the class corresponding to the klass param
|
51
|
+
def self.try_to_constantize(klass)
|
52
|
+
klass.is_a?(String) ? klass.constantize : klass
|
53
|
+
rescue NameError
|
54
|
+
klass
|
55
|
+
end
|
56
|
+
|
57
|
+
# Initializes active_job using the passed parameters.
|
58
|
+
#
|
59
|
+
# @param [Class] klass The class to initialize
|
60
|
+
# @param [Array/Hash] the parameters passed to the klass initializer
|
61
|
+
#
|
62
|
+
# @return [Object] instance of the class klass
|
63
|
+
def self.initialize_active_job(klass, args)
|
64
|
+
if args.is_a?(Array)
|
65
|
+
klass.new(*args)
|
66
|
+
else
|
67
|
+
klass.new(args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Enqueues the job using the Sidekiq client.
|
72
|
+
#
|
73
|
+
# @param [Hash] config The job configuration
|
74
|
+
def self.enqueue_with_sidekiq(config)
|
75
|
+
Sidekiq::Client.push(sanitize_job_config(config))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Enqueues the job using the ActiveJob.
|
79
|
+
#
|
80
|
+
# @param [Hash] config The job configuration
|
81
|
+
def self.enqueue_with_active_job(config)
|
82
|
+
options = {
|
83
|
+
queue: config['queue']
|
84
|
+
}.keep_if { |_, v| !v.nil? }
|
85
|
+
|
86
|
+
initialize_active_job(config['class'], config['args']).enqueue(options)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Removes the hash values associated to the rufus metadata keys.
|
90
|
+
#
|
91
|
+
# @param [Hash] config The job configuration
|
92
|
+
#
|
93
|
+
# @return [Hash] the sanitized job config
|
94
|
+
def self.sanitize_job_config(config)
|
95
|
+
config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a new instance of rufus scheduler.
|
99
|
+
#
|
100
|
+
# @return [Rufus::Scheduler] the scheduler instance
|
101
|
+
def self.new_rufus_scheduler(options = {})
|
102
|
+
Rufus::Scheduler.new(options).tap do |scheduler|
|
103
|
+
scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
|
104
|
+
SidekiqScheduler::Utils.update_job_last_time(job.tags[0], triggered_time)
|
105
|
+
SidekiqScheduler::Utils.update_job_next_time(job.tags[0], job.next_time)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Pushes job's next time execution
|
111
|
+
#
|
112
|
+
# @param [String] name The job's name
|
113
|
+
# @param [Time] next_time The job's next time execution
|
114
|
+
def self.update_job_next_time(name, next_time)
|
115
|
+
if next_time
|
116
|
+
SidekiqScheduler::RedisManager.set_job_next_time(name, next_time)
|
117
|
+
else
|
118
|
+
SidekiqScheduler::RedisManager.remove_job_next_time(name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Pushes job's last execution time
|
123
|
+
#
|
124
|
+
# @param [String] name The job's name
|
125
|
+
# @param [Time] last_time The job's last execution time
|
126
|
+
def self.update_job_last_time(name, last_time)
|
127
|
+
SidekiqScheduler::RedisManager.set_job_last_time(name, last_time) if last_time
|
128
|
+
end
|
43
129
|
end
|
44
130
|
end
|
@@ -15,16 +15,16 @@ module SidekiqScheduler
|
|
15
15
|
erb File.read(File.join(VIEW_PATH, 'recurring_jobs.erb'))
|
16
16
|
end
|
17
17
|
|
18
|
-
app.
|
18
|
+
app.post '/recurring-jobs/:name/enqueue' do
|
19
19
|
schedule = Sidekiq.get_schedule(params[:name])
|
20
|
-
SidekiqScheduler::Scheduler.enqueue_job(schedule)
|
20
|
+
SidekiqScheduler::Scheduler.instance.enqueue_job(schedule)
|
21
21
|
redirect "#{root_path}recurring-jobs"
|
22
22
|
end
|
23
23
|
|
24
|
-
app.
|
24
|
+
app.post '/recurring-jobs/:name/toggle' do
|
25
25
|
Sidekiq.reload_schedule!
|
26
26
|
|
27
|
-
SidekiqScheduler::Scheduler.toggle_job_enabled(params[:name])
|
27
|
+
SidekiqScheduler::Scheduler.instance.toggle_job_enabled(params[:name])
|
28
28
|
redirect "#{root_path}recurring-jobs"
|
29
29
|
end
|
30
30
|
end
|
data/lib/sidekiq-scheduler.rb
CHANGED
@@ -10,40 +10,15 @@ require_relative 'sidekiq-scheduler/extensions/schedule'
|
|
10
10
|
Sidekiq.configure_server do |config|
|
11
11
|
|
12
12
|
config.on(:startup) do
|
13
|
-
dynamic = Sidekiq::Scheduler.dynamic
|
14
|
-
dynamic = dynamic.nil? ? config.options.fetch(:dynamic, false) : dynamic
|
15
|
-
|
16
|
-
dynamic_every = Sidekiq::Scheduler.dynamic_every
|
17
|
-
dynamic_every = dynamic_every.nil? ? config.options.fetch(:dynamic_every, '5s') : dynamic_every
|
18
|
-
|
19
|
-
enabled = Sidekiq::Scheduler.enabled
|
20
|
-
enabled = enabled.nil? ? config.options.fetch(:enabled, true) : enabled
|
21
|
-
|
22
|
-
scheduler = config.options.fetch(:scheduler, {})
|
23
|
-
|
24
|
-
listened_queues_only = Sidekiq::Scheduler.listened_queues_only
|
25
|
-
listened_queues_only = listened_queues_only.nil? ? scheduler[:listened_queues_only] : listened_queues_only
|
26
|
-
|
27
|
-
schedule = Sidekiq.schedule
|
28
|
-
schedule ||= config.options[:schedule] || {}
|
29
|
-
|
30
|
-
scheduler_options = {
|
31
|
-
dynamic: dynamic,
|
32
|
-
dynamic_every: dynamic_every,
|
33
|
-
enabled: enabled,
|
34
|
-
schedule: schedule,
|
35
|
-
listened_queues_only: listened_queues_only
|
36
|
-
}
|
37
|
-
|
38
13
|
# schedules_changed's type was changed from SET to ZSET, so we remove old versions at startup
|
39
14
|
SidekiqScheduler::RedisManager.clean_schedules_changed
|
40
15
|
|
41
|
-
schedule_manager = SidekiqScheduler::Manager.new(
|
16
|
+
schedule_manager = SidekiqScheduler::Manager.new(config.options)
|
42
17
|
config.options[:schedule_manager] = schedule_manager
|
43
18
|
config.options[:schedule_manager].start
|
44
19
|
end
|
45
20
|
|
46
|
-
config.on(:
|
21
|
+
config.on(:quiet) do
|
47
22
|
config.options[:schedule_manager].stop
|
48
23
|
end
|
49
24
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
.recurring-jobs { border-top-left-radius: 4px; border-top-right-radius: 4px; }
|
2
|
+
.recurring-jobs .title { margin-bottom: 5px; }
|
3
|
+
.recurring-jobs .title .name { font-weight: bold;}
|
4
|
+
.recurring-jobs .info,
|
5
|
+
.recurring-jobs .description { margin-bottom: 5px; }
|
6
|
+
.recurring-jobs .actions { margin-bottom: 5px; }
|
7
|
+
.recurring-jobs .status,
|
8
|
+
.recurring-jobs .description { font-size: 12px; }
|
9
|
+
.recurring-jobs .enqueue { margin-bottom: 0.5rem }
|
10
|
+
|
11
|
+
.list-group-item {
|
12
|
+
background-color: #f3f3f3;
|
13
|
+
color: #585454;
|
14
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
15
|
+
}
|
16
|
+
|
17
|
+
@media (prefers-color-scheme: dark) {
|
18
|
+
.list-group-item {
|
19
|
+
background-color: #222;
|
20
|
+
color: white;
|
21
|
+
border: 1px solid #555;
|
22
|
+
}
|
23
|
+
}
|
data/web/locales/ja.yml
ADDED
@@ -1,48 +1,50 @@
|
|
1
|
-
<
|
1
|
+
<link href="<%= root_path %>stylesheets/recurring_jobs.css" media="screen" rel="stylesheet" type="text/css" />
|
2
2
|
|
3
|
-
<
|
4
|
-
<table class="table table-hover table-bordered table-striped table-white">
|
5
|
-
<thead>
|
6
|
-
<tr>
|
7
|
-
<th><%= t('name') %></th>
|
8
|
-
<th><%= t('description') %></th>
|
9
|
-
<th><%= t('interval') %></th>
|
10
|
-
<th><%= t('class') %></th>
|
11
|
-
<th><%= t('queue') %></th>
|
12
|
-
<th><%= t('arguments') %></th>
|
13
|
-
<th><%= t('last_time') %></th>
|
14
|
-
<th><%= t('next_time') %></th>
|
15
|
-
<th></th>
|
16
|
-
</tr>
|
17
|
-
</thead>
|
3
|
+
<h3><%= t('recurring_jobs') %></h3>
|
18
4
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
<
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
5
|
+
<div class="recurring-jobs">
|
6
|
+
<ul class="list-group">
|
7
|
+
<% @presented_jobs.each do |job| %>
|
8
|
+
<li class="list-group-item">
|
9
|
+
<div class="title">
|
10
|
+
<div class="row">
|
11
|
+
<div class="col-xs-6">
|
12
|
+
<span class="name"><%= job.name %></span>
|
13
|
+
</div>
|
14
|
+
<div class="col-xs-6 text-right">
|
15
|
+
<a href="<%= root_path %>queues/<%= job.queue %>"><%= job.queue %></a>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<div class="description"><%= job['description'] %></div>
|
20
|
+
<div class="info">
|
21
|
+
<div class="row">
|
22
|
+
<div class="col-md-4 class"><%= job['class'] %></div>
|
23
|
+
<div class="col-md-4 interval text-left"><%= t('interval') %>: <%= job.interval %></div>
|
24
|
+
<div class="col-md-4 args"><%= t('arguments') %>: <%= job['args'] %></div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<div class="status row">
|
28
|
+
<div class="col-md-4 actions">
|
29
|
+
<form action="<%= root_path %>recurring-jobs/<%= ERB::Util.url_encode(job.name) %>/enqueue" method="post" class="enqueue">
|
30
|
+
<%= csrf_tag %>
|
31
|
+
<input type="submit" class="btn btn-warn btn-xs" value="<%= t('enqueue_now') %>" />
|
32
|
+
</form>
|
33
|
+
<form action="<%= root_path %>recurring-jobs/<%= ERB::Util.url_encode(job.name) %>/toggle" method="post">
|
34
|
+
<%= csrf_tag %>
|
35
|
+
<input type="submit" class="btn <%= job.enabled? ? "btn-primary" : "btn-warn"%> btn-xs" value="<%= job.enabled? ? t('disable') : t('enable') %>" />
|
36
|
+
</form>
|
37
|
+
</div>
|
38
|
+
<div class="col-md-4">
|
39
|
+
<span class="last_time"><%= t('last_time') %>: <%= job.last_time %></span>
|
40
|
+
</div>
|
41
|
+
<div class="col-md-4">
|
42
|
+
<span class="next_time text-right" style="<%= 'text-decoration:line-through' unless job.enabled? %>">
|
43
|
+
<%= t('next_time') %>: <%= job.next_time || t('no_next_time') %>
|
34
44
|
</span>
|
35
|
-
</
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
<a class="btn <%= job.enabled? ? "btn-primary" : "btn-warn"%> btn-xs" href="<%= root_path %>recurring-jobs/<%= URI.escape(job.name) %>/toggle">
|
41
|
-
<%= job.enabled? ? t('disable') : t('enable') %>
|
42
|
-
</a>
|
43
|
-
</td>
|
44
|
-
</tr>
|
45
|
-
<% end %>
|
46
|
-
</tbody>
|
47
|
-
</table>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</li>
|
48
|
+
<% end %>
|
49
|
+
</ul>
|
48
50
|
</div>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Morton Jonuschat
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|
@@ -73,6 +73,34 @@ dependencies:
|
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: 1.4.0
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: thwait
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: e2mmap
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
type: :runtime
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
76
104
|
- !ruby/object:Gem::Dependency
|
77
105
|
name: rake
|
78
106
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,14 +163,14 @@ dependencies:
|
|
135
163
|
requirements:
|
136
164
|
- - "~>"
|
137
165
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
166
|
+
version: 0.19.0
|
139
167
|
type: :development
|
140
168
|
prerelease: false
|
141
169
|
version_requirements: !ruby/object:Gem::Requirement
|
142
170
|
requirements:
|
143
171
|
- - "~>"
|
144
172
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
173
|
+
version: 0.19.0
|
146
174
|
- !ruby/object:Gem::Dependency
|
147
175
|
name: simplecov
|
148
176
|
requirement: !ruby/object:Gem::Requirement
|
@@ -251,12 +279,14 @@ files:
|
|
251
279
|
- lib/sidekiq-scheduler/version.rb
|
252
280
|
- lib/sidekiq-scheduler/web.rb
|
253
281
|
- lib/sidekiq/scheduler.rb
|
282
|
+
- web/assets/stylesheets/recurring_jobs.css
|
254
283
|
- web/locales/cs.yml
|
255
284
|
- web/locales/de.yml
|
256
285
|
- web/locales/en.yml
|
257
286
|
- web/locales/es.yml
|
258
287
|
- web/locales/fr.yml
|
259
288
|
- web/locales/it.yml
|
289
|
+
- web/locales/ja.yml
|
260
290
|
- web/locales/nl.yml
|
261
291
|
- web/locales/pl.yml
|
262
292
|
- web/locales/pt-BR.yml
|
@@ -283,8 +313,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
283
313
|
- !ruby/object:Gem::Version
|
284
314
|
version: '0'
|
285
315
|
requirements: []
|
286
|
-
|
287
|
-
rubygems_version: 2.6.11
|
316
|
+
rubygems_version: 3.2.19
|
288
317
|
signing_key:
|
289
318
|
specification_version: 4
|
290
319
|
summary: Light weight job scheduling extension for Sidekiq
|