sidekiq-scheduler 4.0.2 → 5.0.0.beta1

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
2
  SHA256:
3
- metadata.gz: 46f14eafd933ccc91c36ba3b52babf250a9d17723d8a321b016bad2caaf36262
4
- data.tar.gz: d285ce8992b380f98c05860883c54aa504c83df31aeba252297b1c02d35590a6
3
+ metadata.gz: adc6f05c8d6f36cedff6c3fb051e7314581cbe1b4e460fe3227ec7c2e11cf842
4
+ data.tar.gz: d88174e3e2bacf2b4571f9b5a4d758ff5e30e456039dd967fbf1a2ff3440b155
5
5
  SHA512:
6
- metadata.gz: fe3a692c9419fe21b60333f4801c519fa9eb96b2c49f4f7ad9f6bb41001ac8c0b9884ffa01c19018fe5101e2419f09af3df93f3efcd4de11b87b108d785a5047
7
- data.tar.gz: 264780676f625a002b67b88eb03ee12981a2e222889334799e76c1aefde9ec3d35fad50d645233bd27c195113ea14e5af68ad3d82b4f853e7342f6ae2c3d08c3
6
+ metadata.gz: f41792a0657385179077cda82953df1dfc304af70f80a50355803b5a3b5b33de32e764e043b79874216e2fbbc38997e6fd9dde1ba8a9d3359019ccc86f9ca52e
7
+ data.tar.gz: 392629ec1306d5ea3d3af1b5a20c23756696d447ee5cee618b5f16b322f0def210c269331248956e43758d6b653257ef123bd73af48de66e49eb019a53b477db
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # 5.0.0.beta1
2
+
3
+ - [**BREAKING CHANGE**] Moves all sidekiq-scheduler config options under the `scheduler` key in the `sidekiq.yml` file [#412](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/412)
4
+ - If you're migrating from v4 to v5, any `sidekiq-scheduler` config you may have in your `sidekiq.yml` should be moved under the `scheduler` key.
5
+ - See [#412](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/412) to see which are the config options that you need to move under the `scheduler` key.
6
+
7
+ - [**BREAKING CHANGE**] Drop support for EOL Ruby & Sidekiq versions (Ruby: `2.5.x` & `2.6.x`; Sidekiq: `4.x` & `5.x`) [#411](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/411)
8
+
9
+ - [**FIX**] Add support for Sidekiq 7 [#410](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/410)
10
+
11
+
12
+ # 4.0.3
13
+
14
+ - [**GH Actions**] Add dependabot for GitHub Actions [#390](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/390)
15
+ - [**ENHANCEMENT**] Add «Enable all» and «Disable all» buttons [#398](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/398)
16
+ - [**ENHANCEMENT**] Allow for multiple applications to share a Redis DB [#401](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/401)
17
+ - [**FIX**] Fix metadata for Sidekiq strict_args! [#403](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/403)
18
+ - [**FIX**] Redis 5.0 compatibility [#404](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/404)
19
+ - [**FIX**] Fix the constantize method [#408](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/408)
20
+
1
21
  # 4.0.2
2
22
 
3
23
  - [**FIX**] Fix sidekiq deprecation warning when Sidekiq 6.5+ [#385](https://github.com/sidekiq-scheduler/sidekiq-scheduler/pull/385)
data/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  `sidekiq-scheduler` is an extension to [Sidekiq](http://github.com/mperham/sidekiq) that
19
19
  pushes jobs in a scheduled way, mimicking cron utility.
20
20
 
21
- __Note:__ Current branch contains work of the v4 release, if you are looking for version 2.2.* or 3.*, go to [2.2-stable branch](https://github.com/sidekiq-scheduler/sidekiq-scheduler/tree/2.2-stable) / [v3-stable](https://github.com/sidekiq-scheduler/sidekiq-scheduler/tree/v3-stable).
21
+ __Note:__ Current branch contains work of the upcoming v5 release, if you are looking for version 2.2.\*, 3.\*, or 4.\*, go to [2.2-stable branch](https://github.com/sidekiq-scheduler/sidekiq-scheduler/tree/2.2-stable) / [v3-stable](https://github.com/sidekiq-scheduler/sidekiq-scheduler/tree/v3-stable) / [v4-stable](https://github.com/sidekiq-scheduler/sidekiq-scheduler/tree/v4-stable).
22
22
 
23
23
  ## Installation
24
24
 
@@ -48,10 +48,11 @@ end
48
48
  ``` yaml
49
49
  # config/sidekiq.yml
50
50
 
51
- :schedule:
52
- hello_world:
53
- cron: '0 * * * * *' # Runs once per minute
54
- class: HelloWorld
51
+ :scheduler:
52
+ :schedule:
53
+ hello_world:
54
+ cron: '0 * * * * *' # Runs once per minute
55
+ class: HelloWorld
55
56
  ```
56
57
 
57
58
  Run sidekiq:
@@ -83,40 +84,41 @@ Configuration options are placed inside `sidekiq.yml` config file.
83
84
  Available options are:
84
85
 
85
86
  ``` yaml
86
- :dynamic: <if true the schedule can be modified in runtime [false by default]>
87
- :dynamic_every: <if dynamic is true, the schedule is reloaded every interval [5s by default]>
88
- :enabled: <enables scheduler if true [true by default]>
89
87
  :scheduler:
88
+ :dynamic: <if true the schedule can be modified in runtime [false by default]>
89
+ :dynamic_every: <if dynamic is true, the schedule is reloaded every interval [5s by default]>
90
+ :enabled: <enables scheduler if true [true by default]>
90
91
  :listened_queues_only: <push jobs whose queue is being listened by sidekiq [false by default]>
91
- :rufus_scheduler_options: <Set custom options for rufus scheduler, like max_work_threads [{} by default]>
92
+ :rufus_scheduler_options: <Set custom options for rufus scheduler, like max_work_threads [{} by default]>
92
93
  ```
93
94
 
94
95
  ## Schedule configuration
95
96
 
96
- The schedule is configured through the `:schedule` config entry in the sidekiq config file:
97
+ The schedule is configured through the `:scheduler:` -> `:schedule` config entry in the sidekiq config file:
97
98
 
98
99
  ``` yaml
99
- :schedule:
100
- CancelAbandonedOrders:
101
- cron: '0 */5 * * * *' # Runs when second = 0, every 5 minutes
100
+ :scheduler:
101
+ :schedule:
102
+ CancelAbandonedOrders:
103
+ cron: '0 */5 * * * *' # Runs when second = 0, every 5 minutes
102
104
 
103
- queue_documents_for_indexing:
104
- cron: '0 0 * * * *' # Runs every hour
105
+ queue_documents_for_indexing:
106
+ cron: '0 0 * * * *' # Runs every hour
105
107
 
106
- # By default the job name will be taken as worker class name.
107
- # If you want to have a different job name and class name, provide the 'class' option
108
- class: QueueDocuments
108
+ # By default the job name will be taken as worker class name.
109
+ # If you want to have a different job name and class name, provide the 'class' option
110
+ class: QueueDocuments
109
111
 
110
- queue: slow
111
- args: ['*.pdf']
112
- description: "This job queues pdf content for indexing in solr"
112
+ queue: slow
113
+ args: ['*.pdf']
114
+ description: "This job queues pdf content for indexing in solr"
113
115
 
114
- # Enable the `metadata` argument which will pass a Hash containing the schedule metadata
115
- # as the last argument of the `perform` method. `false` by default.
116
- include_metadata: true
116
+ # Enable the `metadata` argument which will pass a Hash containing the schedule metadata
117
+ # as the last argument of the `perform` method. `false` by default.
118
+ include_metadata: true
117
119
 
118
- # Enable / disable a job. All jobs are enabled by default.
119
- enabled: true
120
+ # Enable / disable a job. All jobs are enabled by default.
121
+ enabled: true
120
122
  ```
121
123
 
122
124
  ### Schedule metadata
@@ -157,9 +159,10 @@ Cron, every, and interval types push jobs into sidekiq in a recurrent manner.
157
159
  `cron` follows the same pattern as cron utility, with seconds resolution.
158
160
 
159
161
  ``` yaml
160
- :schedule:
161
- HelloWorld:
162
- cron: '0 * * * * *' # Runs when second = 0
162
+ :scheduler:
163
+ :schedule:
164
+ HelloWorld:
165
+ cron: '0 * * * * *' # Runs when second = 0
163
166
  ```
164
167
 
165
168
  `every` triggers following a given frequency:
@@ -250,7 +253,8 @@ When `:dynamic` flag is set to `true`, schedule changes are loaded every 5 secon
250
253
 
251
254
  ``` yaml
252
255
  # config/sidekiq.yml
253
- :dynamic: true
256
+ :scheduler:
257
+ :dynamic: true
254
258
  ```
255
259
 
256
260
  If `:dynamic` flag is set to `false`, you'll have to reload the schedule manually in sidekiq
@@ -299,8 +303,9 @@ You can also override the thread pool size in Rufus Scheduler by setting the fol
299
303
  ---
300
304
  ...
301
305
 
302
- rufus_scheduler_options:
303
- max_work_threads: 5
306
+ :scheduler:
307
+ rufus_scheduler_options:
308
+ max_work_threads: 5
304
309
 
305
310
  ...
306
311
  ```
@@ -316,6 +321,47 @@ Non-normal conditions that could push a specific job multiple times are:
316
321
 
317
322
  `every`, `interval` and `in` jobs will be pushed once per host.
318
323
 
324
+ ### Suggested setup for Multiple Hosts using Heroku and Rails
325
+
326
+ Configuration options `every`, `interval` and `in` will push once per host. This may be undesirable. One way to achieve single jobs per the schedule would be to manually designate a host as the scheduler. The goal is to have a single scheduler process running across all your hosts.
327
+
328
+ This can be achieved by using an environment variable and controlling the number of dynos. In Rails, you can read this variable during initialization and then conditionally load your config file.
329
+
330
+ Suppose we are using Rails and have the following schedule:
331
+
332
+ ```yaml
333
+ # config/scheduler.yml
334
+ MyRegularJob:
335
+ description: "We want this job to run very often, but we do not want to run more of them as we scale"
336
+ interval: ["1m"]
337
+ queue: default
338
+ ```
339
+
340
+ Then we can conditionally load it via an initializer:
341
+
342
+ ```ruby
343
+ # config/initializer/sidekiq.rb
344
+ if ENV.fetch("IS_SCHEDULER", false)
345
+ Sidekiq.configure_server do |config|
346
+ config.on(:startup) do
347
+ Sidekiq.schedule = YAML.load_file(File.expand_path("../scheduler.yml", File.dirname(__FILE__)))
348
+ Sidekiq::Scheduler.reload_schedule!
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ Then you would just need to flag the scheduler process when you start it. If you are using a Procfile, it would look like this:
355
+
356
+ ```yaml
357
+ # Procfile
358
+ web: bin/rails server
359
+ worker: bundle exec sidekiq -q default
360
+ scheduler: IS_SCHEDULER=true bundle exec sidekiq -q default
361
+ ```
362
+
363
+ When running via Heroku, you set your `scheduler` process to have 1 dyno. This will ensure you have at most 1 worker loading the schedule.
364
+
319
365
  ## Notes on when Sidekiq worker is down
320
366
 
321
367
  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.
@@ -345,12 +391,13 @@ Say you have one process with the schedule:
345
391
 
346
392
  :queues:
347
393
  - default
348
- :schedule:
349
- do_something_every_minute:
350
- class: DoSomethingJob
351
- args: matey
352
- queue: :scheduler
353
- cron: '0 * * * * * America/Los_Angeles'
394
+ :scheduler:
395
+ :schedule:
396
+ do_something_every_minute:
397
+ class: DoSomethingJob
398
+ args: matey
399
+ queue: :scheduler
400
+ cron: '0 * * * * * America/Los_Angeles'
354
401
  ```
355
402
 
356
403
  And a separate separate configured process without one:
@@ -360,7 +407,8 @@ And a separate separate configured process without one:
360
407
  - scheduler
361
408
 
362
409
  ## NOTE Disable the Scheduler
363
- :enabled: false
410
+ :scheduler:
411
+ :enabled: false
364
412
  ```
365
413
 
366
414
  ### Details
@@ -377,6 +425,22 @@ Redis.
377
425
 
378
426
  See https://github.com/sidekiq-scheduler/sidekiq-scheduler/issues/361 for a more details.
379
427
 
428
+ ## Notes when running multiple applications in the same Redis database
429
+
430
+ _NOTE_: **Although we support this option, we recommend having separate redis databases for each application. Choosing this option is at your own risk.**
431
+
432
+ If you need to run multiple applications with differing schedules, the easiest way is to use a different Redis database per application. Doing that will ensure that each application will have its own schedule, web interface and statistics.
433
+
434
+ However, you may want to have a set of related applications share the same Redis database in order to aggregate statistics and manage them all in a single web interface. To do this while maintaining a different schedule for each application, you can configure each application to use a different `key_prefix` in Redis. This prevents the applications overwriting each others' schedules and schedule data.
435
+
436
+ ```ruby
437
+ Rails.application.reloader.to_prepare do
438
+ SidekiqScheduler::RedisManager.key_prefix = "my-app"
439
+ end
440
+ ```
441
+
442
+ Note that this must be set before the schedule is loaded (or it will go into the wrong key). If you are using the web integration, make sure that the prefix is set in the web process so that you see the correct schedule.
443
+
380
444
  ## Sidekiq Web Integration
381
445
 
382
446
  sidekiq-scheduler provides an extension to the Sidekiq web interface that adds a `Recurring Jobs` page.
@@ -0,0 +1,80 @@
1
+ module SidekiqScheduler
2
+ class Config
3
+ # We have to set the default as nil because the scheduler could be instantiated without
4
+ # passing the sidekiq config, and in those scenarios we don't want to fail
5
+ def initialize(sidekiq_config: nil, without_defaults: false)
6
+ @sidekiq_config = sidekiq_config
7
+ @scheduler_config = fetch_scheduler_config(sidekiq_config, without_defaults)
8
+ end
9
+
10
+ def enabled?
11
+ scheduler_config[:enabled]
12
+ end
13
+
14
+ def enabled=(value)
15
+ scheduler_config[:enabled] = value
16
+ end
17
+
18
+ def dynamic?
19
+ scheduler_config[:dynamic]
20
+ end
21
+
22
+ def dynamic=(value)
23
+ scheduler_config[:dynamic] = value
24
+ end
25
+
26
+ def dynamic_every?
27
+ scheduler_config[:dynamic_every]
28
+ end
29
+
30
+ def dynamic_every=(value)
31
+ scheduler_config[:dynamic_every] = value
32
+ end
33
+
34
+ def schedule
35
+ scheduler_config[:schedule]
36
+ end
37
+
38
+ def schedule=(value)
39
+ scheduler_config[:schedule] = value
40
+ end
41
+
42
+ def listened_queues_only?
43
+ scheduler_config[:listened_queues_only]
44
+ end
45
+
46
+ def listened_queues_only=(value)
47
+ scheduler_config[:listened_queues_only] = value
48
+ end
49
+
50
+ def rufus_scheduler_options
51
+ scheduler_config[:rufus_scheduler_options]
52
+ end
53
+
54
+ def rufus_scheduler_options=(value)
55
+ scheduler_config[:rufus_scheduler_options] = value
56
+ end
57
+
58
+ def sidekiq_queues
59
+ SidekiqScheduler::SidekiqAdapter.sidekiq_queues(sidekiq_config)
60
+ end
61
+
62
+ private
63
+
64
+ attr_reader :scheduler_config
65
+ attr_reader :sidekiq_config
66
+
67
+ DEFAULT_OPTIONS = {
68
+ enabled: true,
69
+ dynamic: false,
70
+ dynamic_every: '5s',
71
+ schedule: {},
72
+ rufus_scheduler_options: {}
73
+ }.freeze
74
+
75
+ def fetch_scheduler_config(sidekiq_config, without_defaults)
76
+ conf = SidekiqScheduler::SidekiqAdapter.fetch_scheduler_config_from_sidekiq(sidekiq_config)
77
+ without_defaults ? conf : DEFAULT_OPTIONS.merge(conf)
78
+ end
79
+ end
80
+ end
@@ -7,7 +7,7 @@ Sidekiq::Web.tabs['recurring_jobs'] = 'recurring-jobs'
7
7
  Sidekiq::Web.locales << File.expand_path("#{File.dirname(__FILE__)}/../../../web/locales")
8
8
 
9
9
  if Sidekiq::VERSION >= '6.0.0'
10
- Sidekiq::Web.use Rack::Static, urls: ['/stylesheets'],
10
+ Sidekiq::Web.use Rack::Static, urls: ['/stylesheets-scheduler'],
11
11
  root: ASSETS_PATH,
12
12
  cascade: true,
13
13
  header_rules: [[:all, { 'Cache-Control' => 'public, max-age=86400' }]]
@@ -10,19 +10,12 @@ module SidekiqScheduler
10
10
  # from Redis onto the work queues
11
11
  #
12
12
  class Manager
13
- DEFAULT_SCHEDULER_OPTIONS = {
14
- enabled: true,
15
- dynamic: false,
16
- dynamic_every: '5s',
17
- schedule: {}
18
- }
13
+ def initialize(config)
14
+ set_current_scheduler_options(config)
19
15
 
20
- def initialize(options)
21
- scheduler_options = load_scheduler_options(options)
22
-
23
- @scheduler_instance = SidekiqScheduler::Scheduler.new(scheduler_options)
16
+ @scheduler_instance = SidekiqScheduler::Scheduler.new(config)
24
17
  SidekiqScheduler::Scheduler.instance = @scheduler_instance
25
- Sidekiq.schedule = scheduler_options[:schedule] if @scheduler_instance.enabled
18
+ Sidekiq.schedule = config.schedule if @scheduler_instance.enabled
26
19
  end
27
20
 
28
21
  def stop
@@ -35,19 +28,17 @@ module SidekiqScheduler
35
28
 
36
29
  private
37
30
 
38
- def load_scheduler_options(options)
39
- options[:listened_queues_only] = options.fetch(:scheduler, {})[:listened_queues_only]
40
- scheduler_options = DEFAULT_SCHEDULER_OPTIONS.merge(options)
41
-
42
- current_options = {
43
- enabled: SidekiqScheduler::Scheduler.enabled,
44
- dynamic: SidekiqScheduler::Scheduler.dynamic,
45
- dynamic_every: SidekiqScheduler::Scheduler.dynamic_every,
46
- schedule: Sidekiq.schedule,
47
- listened_queues_only: SidekiqScheduler::Scheduler.listened_queues_only
48
- }.delete_if { |_, value| value.nil? }
49
-
50
- scheduler_options.merge(current_options)
31
+ def set_current_scheduler_options(config)
32
+ enabled = SidekiqScheduler::Scheduler.enabled
33
+ dynamic = SidekiqScheduler::Scheduler.dynamic
34
+ dynamic_every = SidekiqScheduler::Scheduler.dynamic_every
35
+ listened_queues_only = SidekiqScheduler::Scheduler.listened_queues_only
36
+
37
+ config.enabled = enabled unless enabled.nil?
38
+ config.dynamic = dynamic unless dynamic.nil?
39
+ config.dynamic_every = dynamic_every unless dynamic_every.nil?
40
+ config.schedule = Sidekiq.schedule if Sidekiq.schedule.present?
41
+ config.listened_queues_only = listened_queues_only unless listened_queues_only.nil?
51
42
  end
52
43
  end
53
44
  end
@@ -9,7 +9,7 @@ module SidekiqScheduler
9
9
  #
10
10
  # @return [String] schedule in JSON format
11
11
  def self.get_job_schedule(name)
12
- hget('schedules', name)
12
+ hget(schedules_key, name)
13
13
  end
14
14
 
15
15
  # Returns the state of a given job
@@ -44,7 +44,7 @@ module SidekiqScheduler
44
44
  # @param [String] name The name of the job
45
45
  # @param [Hash] config The new schedule for the job
46
46
  def self.set_job_schedule(name, config)
47
- hset('schedules', name, JSON.generate(config))
47
+ hset(schedules_key, name, JSON.generate(config))
48
48
  end
49
49
 
50
50
  # Sets the state for a given job
@@ -60,7 +60,7 @@ module SidekiqScheduler
60
60
  # @param [String] name The name of the job
61
61
  # @param [String] next_time The next time the job has to be executed
62
62
  def self.set_job_next_time(name, next_time)
63
- hset(next_times_key, name, next_time)
63
+ hset(next_times_key, name, String(next_time))
64
64
  end
65
65
 
66
66
  # Sets the last execution time for a given job
@@ -68,14 +68,14 @@ module SidekiqScheduler
68
68
  # @param [String] name The name of the job
69
69
  # @param [String] last_time The last time the job was executed
70
70
  def self.set_job_last_time(name, last_time)
71
- hset(last_times_key, name, last_time)
71
+ hset(last_times_key, name, String(last_time))
72
72
  end
73
73
 
74
74
  # Removes the schedule for a given job
75
75
  #
76
76
  # @param [String] name The name of the job
77
77
  def self.remove_job_schedule(name)
78
- hdel('schedules', name)
78
+ hdel(schedules_key, name)
79
79
  end
80
80
 
81
81
  # Removes the next execution time for a given job
@@ -89,14 +89,14 @@ module SidekiqScheduler
89
89
  #
90
90
  # @return [Hash] hash with all the job schedules
91
91
  def self.get_all_schedules
92
- Sidekiq.redis { |r| r.hgetall('schedules') }
92
+ Sidekiq.redis { |r| r.hgetall(schedules_key) }
93
93
  end
94
94
 
95
95
  # Returns boolean value that indicates if the schedules value exists
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
+ SidekiqScheduler::SidekiqAdapter.redis_key_exists?(schedules_key)
100
100
  end
101
101
 
102
102
  # Returns all the schedule changes for a given time range.
@@ -106,19 +106,19 @@ module SidekiqScheduler
106
106
  #
107
107
  # @return [Array] array with all the changed job names
108
108
  def self.get_schedule_changes(from, to)
109
- Sidekiq.redis { |r| r.zrangebyscore('schedules_changed', from, "(#{to}") }
109
+ Sidekiq.redis { |r| r.zrangebyscore(schedules_changed_key, from, "(#{to}") }
110
110
  end
111
111
 
112
112
  # Register a schedule change for a given job
113
113
  #
114
114
  # @param [String] name The name of the job
115
115
  def self.add_schedule_change(name)
116
- Sidekiq.redis { |r| r.zadd('schedules_changed', Time.now.to_f, name) }
116
+ Sidekiq.redis { |r| r.zadd(schedules_changed_key, Time.now.to_f, name) }
117
117
  end
118
118
 
119
119
  # Remove all the schedule changes records
120
120
  def self.clean_schedules_changed
121
- Sidekiq.redis { |r| r.del('schedules_changed') unless r.type('schedules_changed') == 'zset' }
121
+ Sidekiq.redis { |r| r.del(schedules_changed_key) unless r.type(schedules_changed_key) == 'zset' }
122
122
  end
123
123
 
124
124
  # Removes a queued job instance
@@ -136,7 +136,7 @@ module SidekiqScheduler
136
136
  end
137
137
  end
138
138
 
139
- registered
139
+ registered.instance_of?(Integer) ? (registered > 0) : registered
140
140
  end
141
141
 
142
142
  # Removes instances of the job older than 24 hours
@@ -156,32 +156,61 @@ module SidekiqScheduler
156
156
  #
157
157
  # @return [String] the pushed job key
158
158
  def self.pushed_job_key(job_name)
159
- "sidekiq-scheduler:pushed:#{job_name}"
159
+ "#{key_prefix}sidekiq-scheduler:pushed:#{job_name}"
160
160
  end
161
161
 
162
162
  # Returns the key of the Redis hash for job's execution times hash
163
163
  #
164
164
  # @return [String] with the key
165
165
  def self.next_times_key
166
- 'sidekiq-scheduler:next_times'
166
+ "#{key_prefix}sidekiq-scheduler:next_times"
167
167
  end
168
168
 
169
169
  # Returns the key of the Redis hash for job's last execution times hash
170
170
  #
171
171
  # @return [String] with the key
172
172
  def self.last_times_key
173
- 'sidekiq-scheduler:last_times'
173
+ "#{key_prefix}sidekiq-scheduler:last_times"
174
174
  end
175
175
 
176
176
  # Returns the Redis's key for saving schedule states.
177
177
  #
178
178
  # @return [String] with the key
179
179
  def self.schedules_state_key
180
- 'sidekiq-scheduler:states'
180
+ "#{key_prefix}sidekiq-scheduler:states"
181
+ end
182
+
183
+ # Returns the Redis's key for saving schedules.
184
+ #
185
+ # @return [String] with the key
186
+ def self.schedules_key
187
+ "#{key_prefix}schedules"
188
+ end
189
+
190
+ # Returns the Redis's key for saving schedule changes.
191
+ #
192
+ # @return [String] with the key
193
+ def self.schedules_changed_key
194
+ "#{key_prefix}schedules_changed"
195
+ end
196
+
197
+ # Returns the key prefix used to generate all scheduler keys
198
+ #
199
+ # @return [String] with the key prefix
200
+ def self.key_prefix
201
+ @key_prefix
202
+ end
203
+
204
+ # Sets the key prefix used to scope all scheduler keys
205
+ #
206
+ # @param [String] value The string to use as the prefix. A ":" will be appended as a delimiter if needed.
207
+ def self.key_prefix=(value)
208
+ value = "#{value}:" if value && !%w[. :].include?(value[-1])
209
+ @key_prefix = value
181
210
  end
182
211
 
183
212
  private
184
-
213
+
185
214
  # Returns the value of a Redis stored hash field
186
215
  #
187
216
  # @param [String] hash_key The key name of the hash
@@ -148,9 +148,7 @@ module SidekiqScheduler
148
148
  end
149
149
 
150
150
  def try_to_constantize(klass)
151
- klass.is_a?(String) ? klass.constantize : klass
152
- rescue NameError
153
- klass
151
+ SidekiqScheduler::Utils.try_to_constantize(klass)
154
152
  end
155
153
  end
156
154
  end
@@ -2,6 +2,7 @@ require 'rufus/scheduler'
2
2
  require 'json'
3
3
  require 'sidekiq-scheduler/rufus_utils'
4
4
  require 'sidekiq-scheduler/redis_manager'
5
+ require 'sidekiq-scheduler/config'
5
6
 
6
7
  module SidekiqScheduler
7
8
  class Scheduler
@@ -10,6 +11,15 @@ module SidekiqScheduler
10
11
  alias_method :params, :opts
11
12
  end
12
13
 
14
+ # TODO: Can we remove those attr_accessor's? If we need to keep them, we should
15
+ # update those values on the config object instead of just here in the scheduler.
16
+ # That's why we need to do what we do in the set_current_scheduler_options (not
17
+ # saying we will have to do it somehow still)
18
+ #
19
+ # NOTE: ^ Keeping this TODO here for now, in a future version of this project
20
+ # we will remove those attr acessors and use only our config object. For now,
21
+ # let's keep as it is.
22
+
13
23
  # Set to enable or disable the scheduler.
14
24
  attr_accessor :enabled
15
25
 
@@ -41,12 +51,14 @@ module SidekiqScheduler
41
51
  end
42
52
  end
43
53
 
44
- def initialize(options = {})
45
- self.enabled = options[:enabled]
46
- self.dynamic = options[:dynamic]
47
- self.dynamic_every = options[:dynamic_every]
48
- self.listened_queues_only = options[:listened_queues_only]
49
- self.rufus_scheduler_options = options[:rufus_scheduler_options] || {}
54
+ def initialize(config = SidekiqScheduler::Config.new(without_defaults: true))
55
+ @scheduler_config = config
56
+
57
+ self.enabled = config.enabled?
58
+ self.dynamic = config.dynamic?
59
+ self.dynamic_every = config.dynamic_every?
60
+ self.listened_queues_only = config.listened_queues_only?
61
+ self.rufus_scheduler_options = config.rufus_scheduler_options
50
62
  end
51
63
 
52
64
  # the Rufus::Scheduler jobs that are scheduled
@@ -82,7 +94,7 @@ module SidekiqScheduler
82
94
  Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
83
95
 
84
96
  @scheduled_jobs = {}
85
- queues = sidekiq_queues
97
+ queues = scheduler_config.sidekiq_queues
86
98
 
87
99
  Sidekiq.schedule.each do |name, config|
88
100
  if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
@@ -158,7 +170,7 @@ module SidekiqScheduler
158
170
  config = prepare_arguments(job_config.dup)
159
171
 
160
172
  if config.delete('include_metadata')
161
- config['args'] = arguments_with_metadata(config['args'], scheduled_at: time.to_f)
173
+ config['args'] = arguments_with_metadata(config['args'], "scheduled_at" => time.to_f)
162
174
  end
163
175
 
164
176
  if SidekiqScheduler::Utils.active_job_enqueue?(config['class'])
@@ -228,8 +240,18 @@ module SidekiqScheduler
228
240
  set_schedule_state(name, state)
229
241
  end
230
242
 
243
+ def toggle_all_jobs(new_state)
244
+ Sidekiq.schedule!.keys.each do |name|
245
+ state = schedule_state(name)
246
+ state['enabled'] = new_state
247
+ set_schedule_state(name, state)
248
+ end
249
+ end
250
+
231
251
  private
232
252
 
253
+ attr_reader :scheduler_config
254
+
233
255
  def new_job(name, interval_type, config, schedule, options)
234
256
  options = options.merge({ :job => true, :tags => [name] })
235
257
 
@@ -283,14 +305,6 @@ module SidekiqScheduler
283
305
  end
284
306
  end
285
307
 
286
- def sidekiq_queues
287
- if SIDEKIQ_GTE_6_5_0
288
- Sidekiq[:queues].map(&:to_s)
289
- else
290
- Sidekiq.options[:queues].map(&:to_s)
291
- end
292
- end
293
-
294
308
  # Returns true if a job's queue is included in the array of queues
295
309
  #
296
310
  # If queues are empty, returns true.
@@ -0,0 +1,80 @@
1
+ module SidekiqScheduler
2
+ class OptionNotSupportedAnymore < StandardError; end
3
+
4
+ class SidekiqAdapter
5
+ SIDEKIQ_GTE_6_5_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('6.5.0')
6
+ SIDEKIQ_GTE_7_0_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.0.0')
7
+
8
+ def self.fetch_scheduler_config_from_sidekiq(sidekiq_config)
9
+ return {} if sidekiq_config.nil?
10
+
11
+ check_using_old_sidekiq_scheduler_config!(sidekiq_config)
12
+
13
+ if SIDEKIQ_GTE_6_5_0
14
+ sidekiq_config.fetch(:scheduler, {})
15
+ else
16
+ sidekiq_config.options.fetch(:scheduler, {})
17
+ end
18
+ end
19
+
20
+ def self.check_using_old_sidekiq_scheduler_config!(sidekiq_config)
21
+ %i[enabled dynamic dynamic_every schedule listened_queues_only rufus_scheduler_options].each do |option|
22
+ if SIDEKIQ_GTE_7_0_0
23
+ if sidekiq_config.key?(option)
24
+ raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
25
+ end
26
+ elsif SIDEKIQ_GTE_6_5_0
27
+ unless sidekiq_config[option].nil?
28
+ raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
29
+ end
30
+ else
31
+ if sidekiq_config.options.key?(option)
32
+ raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.start_schedule_manager(sidekiq_config:, schedule_manager:)
39
+ if SIDEKIQ_GTE_6_5_0
40
+ sidekiq_config[:schedule_manager] = schedule_manager
41
+ sidekiq_config[:schedule_manager].start
42
+ else
43
+ sidekiq_config.options[:schedule_manager] = schedule_manager
44
+ sidekiq_config.options[:schedule_manager].start
45
+ end
46
+ end
47
+
48
+ def self.stop_schedule_manager(sidekiq_config:)
49
+ if SIDEKIQ_GTE_6_5_0
50
+ sidekiq_config[:schedule_manager].stop
51
+ else
52
+ sidekiq_config.options[:schedule_manager].stop
53
+ end
54
+ end
55
+
56
+ def self.sidekiq_queues(sidekiq_config)
57
+ if SIDEKIQ_GTE_7_0_0
58
+ if sidekiq_config.present?
59
+ sidekiq_config.queues.map(&:to_s)
60
+ else
61
+ Sidekiq.instance_variable_get(:@config).queues.map(&:to_s)
62
+ end
63
+ elsif SIDEKIQ_GTE_6_5_0
64
+ Sidekiq[:queues].map(&:to_s)
65
+ else
66
+ Sidekiq.options[:queues].map(&:to_s)
67
+ end
68
+ end
69
+
70
+ def self.redis_key_exists?(key_name)
71
+ Sidekiq.redis do |r|
72
+ if SIDEKIQ_GTE_7_0_0
73
+ r.exists(key_name) > 0
74
+ else
75
+ r.exists?(key_name)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -49,7 +49,7 @@ module SidekiqScheduler
49
49
  #
50
50
  # @return [Class] the class corresponding to the klass param
51
51
  def self.try_to_constantize(klass)
52
- klass.is_a?(String) ? klass.constantize : klass
52
+ klass.is_a?(String) ? Object.const_get(klass) : klass
53
53
  rescue NameError
54
54
  klass
55
55
  end
@@ -112,8 +112,10 @@ module SidekiqScheduler
112
112
  def self.new_rufus_scheduler(options = {})
113
113
  Rufus::Scheduler.new(options).tap do |scheduler|
114
114
  scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
115
- SidekiqScheduler::Utils.update_job_last_time(job.tags[0], triggered_time)
116
- SidekiqScheduler::Utils.update_job_next_time(job.tags[0], job.next_time)
115
+ if (job_name = job.tags[0])
116
+ SidekiqScheduler::Utils.update_job_last_time(job_name, triggered_time)
117
+ SidekiqScheduler::Utils.update_job_next_time(job_name, job.next_time)
118
+ end
117
119
  end
118
120
  end
119
121
  end
@@ -1,3 +1,3 @@
1
1
  module SidekiqScheduler
2
- VERSION = "4.0.2"
2
+ VERSION = "5.0.0.beta1"
3
3
  end
@@ -3,7 +3,7 @@ require 'sidekiq-scheduler'
3
3
  require_relative 'job_presenter'
4
4
 
5
5
  module SidekiqScheduler
6
- # Hook into *Sidekiq::Web* Sinatra app which adds a new '/recurring-jobs' page
6
+ # Hook into *Sidekiq::Web* app which adds a new '/recurring-jobs' page
7
7
 
8
8
  module Web
9
9
  VIEW_PATH = File.expand_path('../../../web/views', __FILE__)
@@ -27,6 +27,11 @@ module SidekiqScheduler
27
27
  SidekiqScheduler::Scheduler.instance.toggle_job_enabled(params[:name])
28
28
  redirect "#{root_path}recurring-jobs"
29
29
  end
30
+
31
+ app.post '/recurring-jobs/toggle-all' do
32
+ SidekiqScheduler::Scheduler.instance.toggle_all_jobs(params[:action] == 'enable')
33
+ redirect "#{root_path}recurring-jobs"
34
+ end
30
35
  end
31
36
  end
32
37
  end
@@ -5,9 +5,9 @@ require_relative 'sidekiq/scheduler'
5
5
  require_relative 'sidekiq-scheduler/version'
6
6
  require_relative 'sidekiq-scheduler/manager'
7
7
  require_relative 'sidekiq-scheduler/redis_manager'
8
+ require_relative 'sidekiq-scheduler/config'
8
9
  require_relative 'sidekiq-scheduler/extensions/schedule'
9
-
10
- SIDEKIQ_GTE_6_5_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('6.5.0')
10
+ require_relative 'sidekiq-scheduler/sidekiq_adapter'
11
11
 
12
12
  Sidekiq.configure_server do |config|
13
13
 
@@ -15,25 +15,14 @@ Sidekiq.configure_server do |config|
15
15
  # schedules_changed's type was changed from SET to ZSET, so we remove old versions at startup
16
16
  SidekiqScheduler::RedisManager.clean_schedules_changed
17
17
 
18
- # Accessing the raw @config hash through .options is deprecated in 6.5 and to be removed in 7.0
19
- config_options = SIDEKIQ_GTE_6_5_0 ? Sidekiq.instance_variable_get(:@config) : config.options
18
+ scheduler_config = SidekiqScheduler::Config.new(sidekiq_config: config)
20
19
 
21
- schedule_manager = SidekiqScheduler::Manager.new(config_options)
22
- if SIDEKIQ_GTE_6_5_0
23
- config[:schedule_manager] = schedule_manager
24
- config[:schedule_manager].start
25
- else
26
- config.options[:schedule_manager] = schedule_manager
27
- config.options[:schedule_manager].start
28
- end
20
+ schedule_manager = SidekiqScheduler::Manager.new(scheduler_config)
21
+ SidekiqScheduler::SidekiqAdapter.start_schedule_manager(sidekiq_config: config, schedule_manager: schedule_manager)
29
22
  end
30
23
 
31
24
  config.on(:quiet) do
32
- if SIDEKIQ_GTE_6_5_0
33
- config[:schedule_manager].stop
34
- else
35
- config.options[:schedule_manager].stop
36
- end
25
+ SidekiqScheduler::SidekiqAdapter.stop_schedule_manager(sidekiq_config: config)
37
26
  end
38
27
 
39
28
  end
@@ -25,3 +25,18 @@
25
25
  border: 1px solid #555;
26
26
  }
27
27
  }
28
+
29
+ .toggle-all-buttons {
30
+ margin-top: 20px;
31
+ margin-bottom: 10px;
32
+ line-height: 45px;
33
+ text-align: right;
34
+ }
35
+
36
+ @media (max-width: 768px) {
37
+ .toggle-all-buttons {
38
+ margin-top: 0;
39
+ text-align: left;
40
+ line-height: inherit;
41
+ }
42
+ }
data/web/locales/de.yml CHANGED
@@ -12,3 +12,5 @@ de:
12
12
  no_next_time: Keine zukünftige Ausführung für diesen Job
13
13
  disable: Deaktivieren
14
14
  enable: Aktivieren
15
+ disable_all: Alle deaktivieren
16
+ enable_all: Alle aktivieren
data/web/locales/en.yml CHANGED
@@ -12,3 +12,5 @@ en:
12
12
  no_next_time: no next execution for this job
13
13
  disable: Disable
14
14
  enable: Enable
15
+ disable_all: Disable all
16
+ enable_all: Enable all
data/web/locales/ru.yml CHANGED
@@ -12,3 +12,5 @@ ru:
12
12
  no_next_time: выполнение задачи не запланировано
13
13
  disable: Выключить
14
14
  enable: Включить
15
+ disable_all: Выключить все
16
+ enable_all: Включить все
@@ -1,5 +1,5 @@
1
1
  <% if Sidekiq::VERSION >= '6.0.0' %>
2
- <link href="<%= root_path %>stylesheets/recurring_jobs.css" media="screen" rel="stylesheet" type="text/css" />
2
+ <link href="<%= root_path %>stylesheets-scheduler/recurring_jobs.css" media="screen" rel="stylesheet" type="text/css" />
3
3
  <% else %>
4
4
  <style>
5
5
  .recurring-jobs { border-top-left-radius: 4px; border-top-right-radius: 4px; }
@@ -21,10 +21,36 @@
21
21
  .list-group-item-disabled {
22
22
  background-color: #f3d3d3;
23
23
  }
24
+
25
+ .toggle-all-buttons {
26
+ margin-top: 20px;
27
+ margin-bottom: 10px;
28
+ line-height: 45px;
29
+ text-align: right;
30
+ }
31
+
32
+ @media (max-width: 768px) {
33
+ .toggle-all-buttons {
34
+ margin-top: 0;
35
+ text-align: left;
36
+ line-height: inherit;
37
+ }
38
+ }
24
39
  </style>
25
40
  <% end %>
26
41
 
27
- <h3><%= t('recurring_jobs') %></h3>
42
+ <div class="row">
43
+ <div class="col-sm-6">
44
+ <h3><%= t('recurring_jobs') %></h3>
45
+ </div>
46
+ <div class="col-sm-6 toggle-all-buttons">
47
+ <form action="<%= root_path %>recurring-jobs/toggle-all" method="post">
48
+ <%= csrf_tag %>
49
+ <button type="submit" class="btn btn-warn btn-xs" name="action" value="enable"><%= t('enable_all') %></button>
50
+ <button type="submit" class="btn btn-warn btn-xs" name="action" value="disable"><%= t('disable_all') %></button>
51
+ </form>
52
+ </div>
53
+ </div>
28
54
 
29
55
  <div class="recurring-jobs">
30
56
  <ul class="list-group">
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.0.2
4
+ version: 5.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morton Jonuschat
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-06-27 00:00:00.000000000 Z
13
+ date: 2022-12-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: sidekiq
@@ -19,6 +19,9 @@ dependencies:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '4'
22
+ - - "<"
23
+ - !ruby/object:Gem::Version
24
+ version: '8'
22
25
  type: :runtime
23
26
  prerelease: false
24
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,20 +29,9 @@ dependencies:
26
29
  - - ">="
27
30
  - !ruby/object:Gem::Version
28
31
  version: '4'
29
- - !ruby/object:Gem::Dependency
30
- name: redis
31
- requirement: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - ">="
34
- - !ruby/object:Gem::Version
35
- version: 4.2.0
36
- type: :runtime
37
- prerelease: false
38
- version_requirements: !ruby/object:Gem::Requirement
39
- requirements:
40
- - - ">="
32
+ - - "<"
41
33
  - !ruby/object:Gem::Version
42
- version: 4.2.0
34
+ version: '8'
43
35
  - !ruby/object:Gem::Dependency
44
36
  name: rufus-scheduler
45
37
  requirement: !ruby/object:Gem::Requirement
@@ -74,14 +66,14 @@ dependencies:
74
66
  requirements:
75
67
  - - "~>"
76
68
  - !ruby/object:Gem::Version
77
- version: '10.0'
69
+ version: '12.0'
78
70
  type: :development
79
71
  prerelease: false
80
72
  version_requirements: !ruby/object:Gem::Requirement
81
73
  requirements:
82
74
  - - "~>"
83
75
  - !ruby/object:Gem::Version
84
- version: '10.0'
76
+ version: '12.0'
85
77
  - !ruby/object:Gem::Dependency
86
78
  name: timecop
87
79
  requirement: !ruby/object:Gem::Requirement
@@ -111,33 +103,33 @@ dependencies:
111
103
  - !ruby/object:Gem::Version
112
104
  version: '0'
113
105
  - !ruby/object:Gem::Dependency
114
- name: rspec
106
+ name: redis
115
107
  requirement: !ruby/object:Gem::Requirement
116
108
  requirements:
117
109
  - - ">="
118
110
  - !ruby/object:Gem::Version
119
- version: '0'
111
+ version: 4.2.0
120
112
  type: :development
121
113
  prerelease: false
122
114
  version_requirements: !ruby/object:Gem::Requirement
123
115
  requirements:
124
116
  - - ">="
125
117
  - !ruby/object:Gem::Version
126
- version: '0'
118
+ version: 4.2.0
127
119
  - !ruby/object:Gem::Dependency
128
- name: mock_redis
120
+ name: rspec
129
121
  requirement: !ruby/object:Gem::Requirement
130
122
  requirements:
131
- - - "~>"
123
+ - - ">="
132
124
  - !ruby/object:Gem::Version
133
- version: 0.28.0
125
+ version: '0'
134
126
  type: :development
135
127
  prerelease: false
136
128
  version_requirements: !ruby/object:Gem::Requirement
137
129
  requirements:
138
- - - "~>"
130
+ - - ">="
139
131
  - !ruby/object:Gem::Version
140
- version: 0.28.0
132
+ version: '0'
141
133
  - !ruby/object:Gem::Dependency
142
134
  name: simplecov
143
135
  requirement: !ruby/object:Gem::Requirement
@@ -195,19 +187,19 @@ dependencies:
195
187
  - !ruby/object:Gem::Version
196
188
  version: '0'
197
189
  - !ruby/object:Gem::Dependency
198
- name: sinatra
190
+ name: rack
199
191
  requirement: !ruby/object:Gem::Requirement
200
192
  requirements:
201
- - - ">="
193
+ - - "<"
202
194
  - !ruby/object:Gem::Version
203
- version: '0'
195
+ version: '3'
204
196
  type: :development
205
197
  prerelease: false
206
198
  version_requirements: !ruby/object:Gem::Requirement
207
199
  requirements:
208
- - - ">="
200
+ - - "<"
209
201
  - !ruby/object:Gem::Version
210
- version: '0'
202
+ version: '3'
211
203
  description: Light weight job scheduling extension for Sidekiq that adds support for
212
204
  queueing jobs in a recurring way.
213
205
  email:
@@ -222,6 +214,7 @@ files:
222
214
  - README.md
223
215
  - Rakefile
224
216
  - lib/sidekiq-scheduler.rb
217
+ - lib/sidekiq-scheduler/config.rb
225
218
  - lib/sidekiq-scheduler/extensions/schedule.rb
226
219
  - lib/sidekiq-scheduler/extensions/web.rb
227
220
  - lib/sidekiq-scheduler/job_presenter.rb
@@ -230,11 +223,12 @@ files:
230
223
  - lib/sidekiq-scheduler/rufus_utils.rb
231
224
  - lib/sidekiq-scheduler/schedule.rb
232
225
  - lib/sidekiq-scheduler/scheduler.rb
226
+ - lib/sidekiq-scheduler/sidekiq_adapter.rb
233
227
  - lib/sidekiq-scheduler/utils.rb
234
228
  - lib/sidekiq-scheduler/version.rb
235
229
  - lib/sidekiq-scheduler/web.rb
236
230
  - lib/sidekiq/scheduler.rb
237
- - web/assets/stylesheets/recurring_jobs.css
231
+ - web/assets/stylesheets-scheduler/recurring_jobs.css
238
232
  - web/locales/cs.yml
239
233
  - web/locales/de.yml
240
234
  - web/locales/en.yml
@@ -261,14 +255,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
261
255
  requirements:
262
256
  - - ">="
263
257
  - !ruby/object:Gem::Version
264
- version: '2.5'
258
+ version: '2.7'
265
259
  required_rubygems_version: !ruby/object:Gem::Requirement
266
260
  requirements:
267
- - - ">="
261
+ - - ">"
268
262
  - !ruby/object:Gem::Version
269
- version: '0'
263
+ version: 1.3.1
270
264
  requirements: []
271
- rubygems_version: 3.2.19
265
+ rubygems_version: 3.2.33
272
266
  signing_key:
273
267
  specification_version: 4
274
268
  summary: Light weight job scheduling extension for Sidekiq