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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2747b49a61187194593a74c6cdba9979d26fa99a
4
- data.tar.gz: 3cda4ae1544bff66d97b7a94503a75aca57a237f
2
+ SHA256:
3
+ metadata.gz: aff61224563645891063110d5f9ae5d16e6ccb0fee92df6a5f26976d0169c01b
4
+ data.tar.gz: d8a6ac2c6e17426bc095904ac3721a9433a21cfd1be433e5f280b9af5469954a
5
5
  SHA512:
6
- metadata.gz: fc793b476d9a19bbc541dedef091e52ffabf08d88800c4a423121e9181d6042b39bd06498bec1c98dc3763b7ee4bf6b0f13e43896bf7d58d672d3570f5039776
7
- data.tar.gz: 97da3f5c4e75468877f59e713407554364befe57b8891371dab4b17387b2f18cbba633695b28cbb99e53f6f63ac090ee3ae3bd64abf2875062f49db71cf45d84
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
- Sidekiq::Scheduler.reload_schedule!
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
- Sidekiq::Scheduler.reload_schedule!
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 as in the server time zone
272
- rather than the `config.time_zone` specified in Rails.
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
- Sidekiq::Scheduler.rufus_scheduler_options = { max_work_threads: 5 }
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
- Sidekiq::Scheduler.reload_schedule!
396
+ SidekiqScheduler::Scheduler.instance.reload_schedule!
371
397
  end
372
398
  end
373
399
  else
374
- Sidekiq::Scheduler.enabled = false
375
- puts "Sidekiq::Scheduler.enabled is #{Sidekiq::Scheduler.enabled.inspect}"
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 - 2017 Moove-IT.
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__) + '/../../../web/locales')
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' }]]
@@ -66,7 +66,7 @@ module SidekiqScheduler
66
66
  def self.build_collection(schedule_hash)
67
67
  schedule_hash ||= {}
68
68
 
69
- schedule_hash.map do |name, job_spec|
69
+ schedule_hash.sort.map do |name, job_spec|
70
70
  new(name, job_spec)
71
71
  end
72
72
  end
@@ -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
- SidekiqScheduler::Scheduler.enabled = options[:enabled]
19
- SidekiqScheduler::Scheduler.dynamic = options[:dynamic]
20
- SidekiqScheduler::Scheduler.dynamic_every = options[:dynamic_every]
21
- SidekiqScheduler::Scheduler.listened_queues_only = options[:listened_queues_only]
22
- Sidekiq.schedule = options[:schedule] if SidekiqScheduler::Scheduler.enabled
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
- SidekiqScheduler::Scheduler.clear_schedule!
33
+ @scheduler_instance.clear_schedule!
27
34
  end
28
35
 
29
36
  def start
30
- SidekiqScheduler::Scheduler.load_schedule!
37
+ @scheduler_instance.load_schedule!
31
38
  end
32
39
 
33
- def reset
34
- clear_scheduled_work
35
- end
40
+ private
36
41
 
37
- end
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 { |r| r.exists(:schedules) }
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
- class << self
17
+ # Set to enable or disable the scheduler.
18
+ attr_accessor :enabled
23
19
 
24
- # Set to enable or disable the scheduler.
25
- attr_accessor :enabled
20
+ # Set to update the schedule in runtime in a given time period.
21
+ attr_accessor :dynamic
26
22
 
27
- # Set to update the schedule in runtime in a given time period.
28
- attr_accessor :dynamic
23
+ # Set to update the schedule in runtime dynamically per this period.
24
+ attr_accessor :dynamic_every
29
25
 
30
- # Set to update the schedule in runtime dynamically per this period.
31
- attr_accessor :dynamic_every
26
+ # Set to schedule jobs only when will be pushed to queues listened by sidekiq
27
+ attr_accessor :listened_queues_only
32
28
 
33
- # Set to schedule jobs only when will be pushed to queues listened by sidekiq
34
- attr_accessor :listened_queues_only
29
+ class << self
35
30
 
36
- # the Rufus::Scheduler jobs that are scheduled
37
- def scheduled_jobs
38
- @@scheduled_jobs
31
+ def instance
32
+ @instance = new unless @instance
33
+ @instance
39
34
  end
40
35
 
41
- def print_schedule
42
- if rufus_scheduler
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
- # Pulls the schedule from Sidekiq.schedule and loads it into the
52
- # rufus scheduler instance
53
- def load_schedule!
54
- if enabled
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
- @@scheduled_jobs = {}
70
- queues = sidekiq_queues
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
- Sidekiq.schedule.each do |name, config|
73
- if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
74
- load_schedule_job(name, config)
75
- else
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
- logger.info 'Schedules Loaded'
81
- else
82
- logger.info 'SidekiqScheduler is disabled'
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
- # Loads a job schedule into the Rufus::Scheduler and stores it in @@scheduled_jobs
87
- def load_schedule_job(name, config)
88
- # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
89
- # required for the jobs to be scheduled. If rails_env is missing, the
90
- # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
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
- rufus_job = new_job(name, interval_type, config, schedule, options)
104
- @@scheduled_jobs[name] = rufus_job
105
- update_job_next_time(name, rufus_job.next_time)
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
- interval_defined = true
82
+ Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
108
83
 
109
- break
110
- end
111
- end
84
+ @scheduled_jobs = {}
85
+ queues = sidekiq_queues
112
86
 
113
- unless interval_defined
114
- logger.info "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
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
- # Pushes the job into Sidekiq if not already pushed for the given time
120
- #
121
- # @param [String] job_name The job's name
122
- # @param [Time] time The time when the job got cleared for triggering
123
- # @param [Hash] config Job's config hash
124
- def idempotent_job_enqueue(job_name, time, config)
125
- registered = register_job_instance(job_name, time)
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
- if registered
128
- logger.info "queueing #{config['class']} (#{job_name})"
114
+ if !config_interval_type.nil? && config_interval_type.length > 0
129
115
 
130
- handle_errors { enqueue_job(config, time) }
116
+ schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
131
117
 
132
- remove_elder_job_instances(job_name)
133
- else
134
- logger.debug { "Ignoring #{job_name} job as it has been already enqueued" }
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
- # Pushes job's next time execution
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
- # Pushes job's last execution time
151
- #
152
- # @param [String] name The job's name
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
- def handle_errors
165
- begin
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
- # Enqueue a job based on a config hash
173
- #
174
- # @param job_config [Hash] the job configuration
175
- # @param time [Time] time the job is enqueued
176
- def enqueue_job(job_config, time=Time.now)
177
- config = prepare_arguments(job_config.dup)
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
- if config.delete('include_metadata')
180
- config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
181
- end
142
+ if registered
143
+ Sidekiq.logger.info "queueing #{config['class']} (#{job_name})"
182
144
 
183
- if active_job_enqueue?(config['class'])
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
- def rufus_scheduler_options
191
- @rufus_scheduler_options ||= {}
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
- def rufus_scheduler_options=(options)
195
- @rufus_scheduler_options = options
196
- end
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
- def rufus_scheduler
199
- @rufus_scheduler ||= new_rufus_scheduler
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
- # Stops old rufus scheduler and creates a new one. Returns the new
203
- # rufus scheduler
204
- #
205
- # @param [Symbol] stop_option The option to be passed to Rufus::Scheduler#stop
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
- def reload_schedule!
214
- if enabled
215
- logger.info 'Reloading Schedule'
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
- def update_schedule
224
- last_changed_score, @current_changed_score = @current_changed_score, Time.now.to_f
225
- schedule_changes = SidekiqScheduler::RedisManager.get_schedule_changes(last_changed_score, @current_changed_score)
175
+ def rufus_scheduler_options=(options)
176
+ @rufus_scheduler_options = options
177
+ end
226
178
 
227
- if schedule_changes.size > 0
228
- logger.info 'Updating schedule'
179
+ def rufus_scheduler
180
+ @rufus_scheduler ||= SidekiqScheduler::Utils.new_rufus_scheduler(rufus_scheduler_options)
181
+ end
229
182
 
230
- Sidekiq.reload_schedule!
231
- schedule_changes.each do |schedule_name|
232
- if Sidekiq.schedule.keys.include?(schedule_name)
233
- unschedule_job(schedule_name)
234
- load_schedule_job(schedule_name, Sidekiq.schedule[schedule_name])
235
- else
236
- unschedule_job(schedule_name)
237
- end
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
- def unschedule_job(name)
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
- def enqueue_with_active_job(config)
252
- options = {
253
- queue: config['queue']
254
- }.keep_if { |_, v| !v.nil? }
195
+ rufus_scheduler
196
+ end
255
197
 
256
- initialize_active_job(config['class'], config['args']).enqueue(options)
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
- def enqueue_with_sidekiq(config)
260
- Sidekiq::Client.push(sanitize_job_config(config))
261
- end
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
- def initialize_active_job(klass, args)
264
- if args.is_a?(Array)
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
- # Returns true if the enqueuing needs to be done for an ActiveJob
272
- # class false otherwise.
273
- #
274
- # @param [Class] klass the class to check is decendant from ActiveJob
275
- #
276
- # @return [Boolean]
277
- def active_job_enqueue?(klass)
278
- klass.is_a?(Class) && defined?(ActiveJob::Enqueuing) &&
279
- klass.included_modules.include?(ActiveJob::Enqueuing)
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
- # Convert the given arguments in the format expected to be enqueued.
283
- #
284
- # @param [Hash] config the options to be converted
285
- # @option config [String] class the job class
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
- config
300
- end
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
- def try_to_constantize(klass)
303
- klass.is_a?(String) ? klass.constantize : klass
304
- rescue NameError
305
- klass
306
- end
239
+ private
307
240
 
308
- # Returns true if a job's queue is included in the array of queues
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
- # Registers a queued job instance
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
- def remove_elder_job_instances(job_name)
331
- SidekiqScheduler::RedisManager.remove_elder_job_instances(job_name)
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
- def job_enabled?(name)
335
- job = Sidekiq.schedule[name]
336
- schedule_state(name).fetch('enabled', job.fetch('enabled', true)) if job
337
- end
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
- def toggle_job_enabled(name)
340
- state = schedule_state(name)
341
- state['enabled'] = !job_enabled?(name)
342
- set_schedule_state(name, state)
343
- end
264
+ state ? JSON.parse(state) : {}
265
+ end
344
266
 
345
- private
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
- def new_rufus_scheduler
348
- Rufus::Scheduler.new(rufus_scheduler_options).tap do |scheduler|
349
- scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
350
- SidekiqScheduler::Scheduler.update_job_last_time(job.tags[0], triggered_time)
351
- SidekiqScheduler::Scheduler.update_job_next_time(job.tags[0], job.next_time)
352
- end
353
- end
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
- def new_job(name, interval_type, config, schedule, options)
357
- options = options.merge({ :job => true, :tags => [name] })
294
+ def sidekiq_queues
295
+ Sidekiq.options[:queues].map(&:to_s)
296
+ end
358
297
 
359
- rufus_scheduler.send(interval_type, schedule, options) do |job, time|
360
- idempotent_job_enqueue(name, time, sanitize_job_config(config)) if job_enabled?(name)
361
- end
362
- end
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
- def sanitize_job_config(config)
365
- config.reject { |k, _| RUFUS_METADATA_KEYS.include?(k) }
366
- end
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
- # Retrieves a schedule state
369
- #
370
- # @param name [String] with the schedule's name
371
- # @return [Hash] with the schedule's state
372
- def schedule_state(name)
373
- state = SidekiqScheduler::RedisManager.get_job_state(name)
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
- state ? JSON.parse(state) : {}
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
- # Saves a schedule state
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
- # Adds a Hash with schedule metadata as the last argument to call the worker.
387
- # It currently returns the schedule time as a Float number representing the milisencods
388
- # since epoch.
389
- #
390
- # @example with hash argument
391
- # arguments_with_metadata({value: 1}, scheduled_at: Time.now)
392
- # #=> [{value: 1}, {scheduled_at: <miliseconds since epoch>}]
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
- def sidekiq_queues
406
- Sidekiq.options[:queues].map(&:to_s)
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
@@ -1,5 +1,5 @@
1
1
  module SidekiqScheduler
2
2
 
3
- VERSION = '2.2.2'
3
+ VERSION = '3.1.1'
4
4
 
5
5
  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.get '/recurring-jobs/:name/enqueue' do
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.get '/recurring-jobs/:name/toggle' do
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
@@ -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(scheduler_options)
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(:shutdown) do
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
+ }
@@ -0,0 +1,14 @@
1
+ ja:
2
+ recurring_jobs: 定期ジョブ
3
+ name: 名前
4
+ description: 説明
5
+ interval: インターバル
6
+ class: クラス
7
+ queue: キュー
8
+ arguments: 引数
9
+ enqueue_now: すぐにエンキュー
10
+ last_time: 前回
11
+ next_time: 次回
12
+ no_next_time: このジョブの次の実行はありません
13
+ disable: 無効
14
+ enable: 有効
@@ -1,48 +1,50 @@
1
- <h3><%= t('recurring_jobs') %></h3>
1
+ <link href="<%= root_path %>stylesheets/recurring_jobs.css" media="screen" rel="stylesheet" type="text/css" />
2
2
 
3
- <div class="table_container">
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
- <tbody>
20
- <% @presented_jobs.each do |job| %>
21
- <tr>
22
- <td><%= job.name %></td>
23
- <td><%= job['description'] %></td>
24
- <td><%= job.interval %></td>
25
- <td><%= job['class'] %></td>
26
- <td>
27
- <a href="<%= root_path %>queues/<%= job.queue %>"><%= job.queue %></a>
28
- </td>
29
- <td><%= job['args'] %></td>
30
- <td><%= job.last_time %></td>
31
- <td>
32
- <span style="<%= 'text-decoration:line-through' unless job.enabled? %>">
33
- <%= job.next_time || t('no_next_time') %>
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
- </td>
36
- <td class="text-center">
37
- <a class="btn btn-warn btn-xs" href="<%= root_path %>recurring-jobs/<%= URI.escape(job.name) %>/enqueue">
38
- <%= t('enqueue_now') %>
39
- </a>
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: 2.2.2
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: 2018-05-15 00:00:00.000000000 Z
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: '0'
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: '0'
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
- rubyforge_project:
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