sidekiq-debouncer 2.0.2 → 3.0.0

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: bab3413954e727ee13bc4d216f13621d1337df24f1ed970ecf146fae30501ef6
4
- data.tar.gz: 883a815a2bb23e08abb175bf04f0231e028684d174c3d60bef6cc1450294764d
3
+ metadata.gz: c98e0c985f240350757ce46298f39cb5586d7e01966f78bd3a886c8b4a78b7e0
4
+ data.tar.gz: e1f7ceda05cd4cc580fc5d318da59ccaf9a8a3f737020524c76a147add5fa5e3
5
5
  SHA512:
6
- metadata.gz: 254b6d10c2cf611d91e6971f9e21cc76964600ab951f718c88aa0d7eacb38fe3fccc4bd4f0e0f971d33e829e5a6ce0997d5c1a7a9886bc95e503a554b57e0241
7
- data.tar.gz: 8d5877eec61fb2dbac4305db1815afc320306e0c1af78a872468a5a2218dd48f4b9f8a954e323e59aa3d088b783ae61cc14140a987c3f0785c80a3621c0dcbf7
6
+ metadata.gz: 3175ce8da01db8a593290273a4a7c392133792de93c04a9091662ca96ab24caeea8a60e17e0ab7a7fd16e8b8273d184b62a421deb58374430e26f720f0708927
7
+ data.tar.gz: 2e42dedd11a4b6c05e9c4af5446803a1fab05daa7fe762b83ef3846b0f5c8cb253bf6f03e1bbbdc8e21311245087526c89098955c088d040c0c09606bba526da
data/CHANGELOG.md CHANGED
@@ -1,4 +1,8 @@
1
- ## Unreleased
1
+ ## [3.0.0] - 2024-10-22
2
+ - Complete rewrite of the library ([#25](https://github.com/paladinsoftware/sidekiq-debouncer/pull/25))
3
+ - Read only Web UI ([#26](https://github.com/paladinsoftware/sidekiq-debouncer/pull/26))
4
+ - Drop support for sidekiq 6.x and ruby 2.7 ([#28](https://github.com/paladinsoftware/sidekiq-debouncer/pull/28))
5
+ - Respect sidekiq_options overridden by .set ([#27](https://github.com/paladinsoftware/sidekiq-debouncer/pull/27))
2
6
 
3
7
  ## [2.0.2] - 2023-03-13
4
8
  - support Sidekiq::Testing
data/README.md CHANGED
@@ -99,6 +99,9 @@ Sidekiq.configure_client do |config|
99
99
  end
100
100
  ```
101
101
 
102
+ ## Web UI
103
+ Add `require 'sidekiq/debouncer/web'` after `require 'sidekiq/web'`.
104
+
102
105
  ## Testing
103
106
 
104
107
  In order to test the behavior of `sidekiq-debouncer` it is necessary to disable testing mode. It is the limitation of internal implementation.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/scheduled"
4
+
5
+ module Sidekiq
6
+ module Debouncer
7
+ class Enq < ::Sidekiq::Scheduled::Enq
8
+ extend LuaCommands
9
+
10
+ LUA_ZPOPBYSCORE_WITHSCORE = File.read(File.expand_path("../lua/zpopbyscore_withscore.lua", __FILE__))
11
+ LUA_ZPOPBYSCORE_MULTI = File.read(File.expand_path("../lua/zpopbyscore_multi.lua", __FILE__))
12
+
13
+ define_lua_command(:zpopbyscore_withscore, LUA_ZPOPBYSCORE_WITHSCORE)
14
+ define_lua_command(:zpopbyscore_multi, LUA_ZPOPBYSCORE_MULTI)
15
+
16
+ def enqueue_jobs
17
+ redis do |conn|
18
+ while !@done && (job, score = zpopbyscore_withscore(conn, [Sidekiq::Debouncer::SET], [Time.now.to_f.to_s]))
19
+ job_args = zpopbyscore_multi(conn, [job], [score])
20
+
21
+ final_args = job_args.map { |elem| Sidekiq.load_json(elem.split("-", 2)[1]) }
22
+ job_class = job.split("/")[2]
23
+ klass = Object.const_get(job_class)
24
+
25
+ @client.push({"args" => final_args, "class" => klass, "debounce_key" => job})
26
+
27
+ logger.debug { "enqueued #{Sidekiq::Debouncer::SET}: #{job}" }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Debouncer
5
+ class Job
6
+ include Sidekiq::JobUtil
7
+
8
+ attr_reader :key, :score
9
+
10
+ def initialize(key, score)
11
+ @key = key
12
+ @score = score
13
+ end
14
+
15
+ def at
16
+ Time.at(score).utc
17
+ end
18
+
19
+ def args
20
+ @_args ||= Sidekiq.redis { |conn| conn.call("ZRANGE", key, "-inf", "+inf", "BYSCORE") }
21
+ .map { |elem| Sidekiq.load_json(elem.split("-", 2)[1]) }
22
+ end
23
+
24
+ def queue
25
+ normalized["queue"]
26
+ end
27
+
28
+ def klass
29
+ key.split("/")[2]
30
+ end
31
+
32
+ alias_method :display_class, :klass
33
+
34
+ private
35
+
36
+ def normalized
37
+ @_normalized ||= normalize_item({"args" => args, "class" => Object.const_get(klass), "debounce_key" => key})
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Debouncer
5
+ module Launcher
6
+ def initialize(config, **kwargs)
7
+ @debounce_poller = Sidekiq::Debouncer::Poller.new(config)
8
+ super
9
+ end
10
+
11
+ def run
12
+ super
13
+ @debounce_poller.start
14
+ end
15
+
16
+ def quiet
17
+ super
18
+ @debounce_poller.terminate
19
+ end
20
+
21
+ def stop
22
+ @debounce_poller.terminate
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ Sidekiq.configure_server do
30
+ require "sidekiq/launcher"
31
+
32
+ ::Sidekiq::Launcher.prepend(Sidekiq::Debouncer::Launcher)
33
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ Debounces: Debounces
3
+ DebounceKey: Debounce Key
4
+ NoDebouncesFound: No debounces found
@@ -1,22 +1,10 @@
1
1
  local set = KEYS[1]
2
2
  local debounce_key = KEYS[2]
3
3
 
4
- local job = ARGV[1]
4
+ local args = ARGV[1]
5
5
  local time = ARGV[2]
6
6
  local ttl = ARGV[3]
7
7
 
8
- local existing_debounce = redis.call("GET", debounce_key)
9
-
10
- if existing_debounce then
11
- redis.call("DEL", debounce_key)
12
- -- skip if job wasn't found in schedule set
13
- if redis.call("ZREM", set, existing_debounce) > 0 then
14
- local new_args = cjson.decode(job)['args'][1]
15
- local new_job = cjson.decode(existing_debounce)
16
- table.insert(new_job['args'], new_args)
17
- job = cjson.encode(new_job)
18
- end
19
- end
20
-
21
- redis.call("SET", debounce_key, job, "EX", ttl)
22
- redis.call("ZADD", set, time, job)
8
+ redis.call("ZADD", set, time, debounce_key)
9
+ redis.call("ZADD", debounce_key, time, args)
10
+ redis.call("EXPIRE", debounce_key, ttl)
@@ -0,0 +1,5 @@
1
+ local key, now = KEYS[1], ARGV[1]
2
+ local jobs = redis.call("ZRANGE", key, "-inf", now, "BYSCORE")
3
+ redis.call("ZREMRANGEBYSCORE", key, "-inf", now)
4
+
5
+ return jobs
@@ -0,0 +1,6 @@
1
+ local key, now = KEYS[1], ARGV[1]
2
+ local jobs = redis.call("ZRANGE", key, "-inf", now, "BYSCORE", "LIMIT", 0, 1, "WITHSCORES")
3
+ if jobs[1] then
4
+ redis.call("ZREM", key, jobs[1])
5
+ return {jobs[1], jobs[2]}
6
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module Sidekiq
6
+ module Debouncer
7
+ module LuaCommands
8
+ def define_lua_command(command, script)
9
+ sha = Digest::SHA1.hexdigest(script)
10
+ define_method(command) do |conn, keys, argv|
11
+ retryable = true
12
+ begin
13
+ conn.call("EVALSHA", sha, keys.size, *keys, *argv)
14
+ rescue RedisClient::CommandError => e
15
+ raise if !e.message.start_with?("NOSCRIPT") || !retryable
16
+
17
+ # upload script to redis cache and retry
18
+ conn.call("SCRIPT", "LOAD", script)
19
+ retryable = false
20
+ retry
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest/sha1"
3
+ require "securerandom"
4
4
 
5
5
  module Sidekiq
6
6
  module Debouncer
@@ -11,10 +11,11 @@ module Sidekiq
11
11
  # job from schedule set when another debounce occurs.
12
12
  class Client
13
13
  include Sidekiq::ClientMiddleware
14
+ extend Sidekiq::Debouncer::LuaCommands
14
15
 
15
16
  LUA_DEBOUNCE = File.read(File.expand_path("../../lua/debounce.lua", __FILE__))
16
- LUA_DEBOUNCE_SHA = Digest::SHA1.hexdigest(LUA_DEBOUNCE)
17
- REDIS_ERROR_CLASS = defined?(RedisClient::CommandError) ? RedisClient::CommandError : Redis::CommandError
17
+
18
+ define_lua_command(:redis_debounce, LUA_DEBOUNCE)
18
19
 
19
20
  def initialize(options = {})
20
21
  @debounce_key_ttl = options.fetch(:ttl, 60 * 60 * 24) # 24 hours by default
@@ -35,18 +36,16 @@ module Sidekiq
35
36
  def debounce(klass, job)
36
37
  raise NotSupportedError, "perform_at is not supported with debounce" if job.key?("at")
37
38
 
38
- options = debounce_options(klass)
39
+ options = debounce_options(klass, job)
39
40
  key = debounce_key(klass, job, options)
40
41
  time = (options[:time].to_f + Time.now.to_f).to_s
41
42
 
42
- job["debounce_key"] = key
43
- job["args"] = [job["args"]]
44
- job.delete("debounce")
43
+ return job.merge("args" => [job["args"]], "debounce_key" => key) if testing?
45
44
 
46
- return job if testing?
45
+ args_stringified = "#{SecureRandom.hex(12)}-#{Sidekiq.dump_json(job["args"])}"
47
46
 
48
47
  redis do |connection|
49
- redis_debounce(connection, keys: ["schedule", key], argv: [Sidekiq.dump_json(job), time, @debounce_key_ttl])
48
+ redis_debounce(connection, [Sidekiq::Debouncer::SET, key], [args_stringified, time, @debounce_key_ttl])
50
49
  end
51
50
 
52
51
  # prevent normal sidekiq flow
@@ -56,11 +55,12 @@ module Sidekiq
56
55
  def debounce_key(klass, job, options)
57
56
  method = options[:by]
58
57
  result = method.is_a?(Symbol) ? klass.send(method, job["args"]) : method.call(job["args"])
59
- "debounce/#{klass.name}/#{result}"
58
+ "debounce/v3/#{klass.name}/#{result}"
60
59
  end
61
60
 
62
- def debounce_options(klass)
63
- options = klass.get_sidekiq_options["debounce"].transform_keys(&:to_sym)
61
+ def debounce_options(klass, job)
62
+ options = job.fetch("debounce") { klass.get_sidekiq_options["debounce"] }
63
+ options = options.transform_keys(&:to_sym)
64
64
 
65
65
  raise MissingArgumentError, "'by' attribute not provided" unless options[:by]
66
66
  raise MissingArgumentError, "'time' attribute not provided" unless options[:time]
@@ -68,20 +68,6 @@ module Sidekiq
68
68
  options
69
69
  end
70
70
 
71
- def redis_debounce(connection, keys:, argv:)
72
- retryable = true
73
- begin
74
- connection.call("EVALSHA", LUA_DEBOUNCE_SHA, keys.size, *keys, *argv)
75
- rescue REDIS_ERROR_CLASS => e
76
- raise if !e.message.start_with?("NOSCRIPT") || !retryable
77
-
78
- # upload script to redis cache and retry
79
- connection.call("SCRIPT", "LOAD", LUA_DEBOUNCE)
80
- retryable = false
81
- retry
82
- end
83
- end
84
-
85
71
  def testing?
86
72
  defined?(Sidekiq::Testing) && Sidekiq::Testing.enabled?
87
73
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/scheduled"
4
+
5
+ module Sidekiq
6
+ module Debouncer
7
+ class Poller < ::Sidekiq::Scheduled::Poller
8
+ def initialize(config)
9
+ super
10
+ @enq = Sidekiq::Debouncer::Enq.new(config)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Debouncer
5
+ class Set < Sidekiq::JobSet
6
+ def initialize
7
+ super Sidekiq::Debouncer::SET
8
+ end
9
+
10
+ def fetch_by_key(key)
11
+ score = Sidekiq.redis { |conn| conn.zscore(Sidekiq::Debouncer::SET, key) }
12
+ Job.new(key, score)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Debouncer
5
- VERSION = "2.0.2"
5
+ VERSION = "3.0.0"
6
6
  end
7
7
  end
@@ -0,0 +1,48 @@
1
+ <header class='row'>
2
+ <div class='col-sm-5 pull-left'>
3
+ <h3><%= t('Debounces') %></h3>
4
+ </div>
5
+ <div class='col-sm-7 pull-right'>
6
+ <%= erb :_paging, locals: { url: "#{root_path}debounces" } %>
7
+ </div>
8
+ </header>
9
+
10
+ <% if @debounces.size > 0 %>
11
+ <form action="<%= root_path %>debounces" method="post">
12
+ <%= csrf_tag %>
13
+ <div class="table_container">
14
+ <table class="table table-striped table-bordered table-hover">
15
+ <thead>
16
+ <tr>
17
+ <th class="table-checkbox checkbox-column">
18
+ <label>
19
+ <input type="checkbox" class="check_all" />
20
+ </label>
21
+ </th>
22
+ <th><%= t('When') %></th>
23
+ <th><%= t('DebounceKey') %></th>
24
+ </tr>
25
+ </thead>
26
+ <% @debounces.each do |entry| %>
27
+ <tr>
28
+ <td class="table-checkbox">
29
+ <label>
30
+ <input type='checkbox' name='key[]' value='<%= entry.key %>' class='shift_clickable' />
31
+ </label>
32
+ </td>
33
+ <td>
34
+ <a href="<%= root_path %>debounces/<%= ::Base64.urlsafe_encode64(entry.key) %>"><%= relative_time(entry.at) %></a>
35
+ </td>
36
+ <td>
37
+ <a href="<%= root_path %>debounces/<%= ::Base64.urlsafe_encode64(entry.key) %>"><%= entry.key %></a>
38
+ </td>
39
+ </tr>
40
+ <% end %>
41
+ </table>
42
+ </div>
43
+ <!-- <input class="btn btn-danger pull-right flip" type="submit" name="delete" value="<%#= t('Delete') %>" />-->
44
+ <!-- <input class="btn btn-danger pull-right flip" type="submit" name="add_to_queue" value="<%#= t('AddToQueue') %>" />-->
45
+ </form>
46
+ <% else %>
47
+ <div class="alert alert-success"><%= t('NoDebouncesFound') %></div>
48
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <div class="header-container">
2
+ <h3><%= t('Job') %></h3>
3
+ </div>
4
+
5
+ <div class="table_container">
6
+ <table class="table table-bordered table-striped table-hover">
7
+ <tbody>
8
+ <tr>
9
+ <th><%= t('Queue') %></th>
10
+ <td><%= @job.queue %></td>
11
+ </tr>
12
+ <tr>
13
+ <th><%= t('When') %></th>
14
+ <td><%= relative_time(@job.at) %></td>
15
+ </tr>
16
+ <tr>
17
+ <th><%= t('DebounceKey') %></th>
18
+ <td>
19
+ <%= @job.key %>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <th><%= t('Job') %></th>
24
+ <td>
25
+ <%= @job.display_class %>
26
+ </td>
27
+ </tr>
28
+ <tr>
29
+ <th><%= t('Arguments') %></th>
30
+ <td>
31
+ <code class="code-wrap">
32
+ <div class="args-extended"><%= display_args(@job.args, nil) %></div>
33
+ </code>
34
+ </td>
35
+ </tr>
36
+ </tbody>
37
+ </table>
38
+ </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Sidekiq::Web)
4
+ require "sidekiq/debouncer/web_extension"
5
+
6
+ Sidekiq::Web.register Sidekiq::Debouncer::WebExtension
7
+ Sidekiq::Web.tabs["Debounces"] = "debounces"
8
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Sidekiq
6
+ module Debouncer
7
+ module WebExtension
8
+ def self.registered(app)
9
+ app.settings.locales << File.join(File.expand_path("..", __FILE__), "locales")
10
+
11
+ app.get "/debounces" do
12
+ view_path = File.join(File.expand_path("..", __FILE__), "views")
13
+
14
+ @count = (params["count"] || 25).to_i
15
+ (@current_page, @total_size, @debounces) = page("debouncer", params["page"], @count)
16
+ @debounces = @debounces.map { |key, score| Sidekiq::Debouncer::Job.new(key, score) }
17
+
18
+ render(:erb, File.read(File.join(view_path, "index.html.erb")))
19
+ end
20
+
21
+ app.get "/debounces/:key" do
22
+ view_path = File.join(File.expand_path("..", __FILE__), "views")
23
+
24
+ @job = Sidekiq::Debouncer::Set.new.fetch_by_key(Base64.urlsafe_decode64(route_params[:key]))
25
+
26
+ render(:erb, File.read(File.join(view_path, "show.html.erb")))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,11 +2,16 @@
2
2
 
3
3
  require "sidekiq/debouncer/version"
4
4
  require "sidekiq/debouncer/errors"
5
+ require "sidekiq/debouncer/lua_commands"
5
6
  require "sidekiq/debouncer/middleware/client"
6
7
  require "sidekiq/debouncer/middleware/server"
8
+ require "sidekiq/debouncer/job"
9
+ require "sidekiq/debouncer/set"
7
10
 
8
11
  module Sidekiq
9
12
  module Debouncer
13
+ SET = "debouncer"
14
+
10
15
  def self.included(base)
11
16
  base.extend ClassMethods
12
17
  end
@@ -19,3 +24,9 @@ module Sidekiq
19
24
  end
20
25
  end
21
26
  end
27
+
28
+ Sidekiq.configure_server do
29
+ require "sidekiq/debouncer/enq"
30
+ require "sidekiq/debouncer/poller"
31
+ require "sidekiq/debouncer/launcher"
32
+ end
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  DESCRIPTION
19
19
  gem.homepage = "https://github.com/paladinsoftware/sidekiq-debouncer"
20
20
  gem.license = "MIT"
21
- gem.required_ruby_version = ">= 2.7.0"
21
+ gem.required_ruby_version = ">= 3.0.0"
22
22
 
23
23
  gem.files = Dir.glob("lib/**/*") + [
24
24
  "CHANGELOG.md",
@@ -27,11 +27,12 @@ Gem::Specification.new do |gem|
27
27
  "sidekiq-debouncer.gemspec"
28
28
  ]
29
29
 
30
- gem.add_dependency "sidekiq", ">= 6.5", "< 8.0"
30
+ gem.add_dependency "sidekiq", ">= 7.0", "< 8.0"
31
31
 
32
32
  gem.add_development_dependency "rspec", "~> 3.12.0"
33
33
  gem.add_development_dependency "timecop", "~> 0.9.6"
34
34
  gem.add_development_dependency "simplecov", "~> 0.22.0"
35
35
  gem.add_development_dependency "parallel", "~> 1.22.1"
36
36
  gem.add_development_dependency "standard", "~> 1.24.3"
37
+ gem.add_development_dependency "rack-test", "~> 2.1.0"
37
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-debouncer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Zuchmański
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-13 00:00:00.000000000 Z
12
+ date: 2024-10-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -17,7 +17,7 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '6.5'
20
+ version: '7.0'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
23
  version: '8.0'
@@ -27,7 +27,7 @@ dependencies:
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- version: '6.5'
30
+ version: '7.0'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '8.0'
@@ -101,6 +101,20 @@ dependencies:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: 1.24.3
104
+ - !ruby/object:Gem::Dependency
105
+ name: rack-test
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.1.0
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.1.0
104
118
  description: |
105
119
  Sidekiq extension that adds the ability to debounce job execution.
106
120
  Worker will postpone its execution after `wait time` have elapsed since the last time it was invoked.
@@ -117,11 +131,24 @@ files:
117
131
  - README.md
118
132
  - lib/sidekiq-debouncer.rb
119
133
  - lib/sidekiq/debouncer.rb
134
+ - lib/sidekiq/debouncer/enq.rb
120
135
  - lib/sidekiq/debouncer/errors.rb
136
+ - lib/sidekiq/debouncer/job.rb
137
+ - lib/sidekiq/debouncer/launcher.rb
138
+ - lib/sidekiq/debouncer/locales/en.yml
121
139
  - lib/sidekiq/debouncer/lua/debounce.lua
140
+ - lib/sidekiq/debouncer/lua/zpopbyscore_multi.lua
141
+ - lib/sidekiq/debouncer/lua/zpopbyscore_withscore.lua
142
+ - lib/sidekiq/debouncer/lua_commands.rb
122
143
  - lib/sidekiq/debouncer/middleware/client.rb
123
144
  - lib/sidekiq/debouncer/middleware/server.rb
145
+ - lib/sidekiq/debouncer/poller.rb
146
+ - lib/sidekiq/debouncer/set.rb
124
147
  - lib/sidekiq/debouncer/version.rb
148
+ - lib/sidekiq/debouncer/views/index.html.erb
149
+ - lib/sidekiq/debouncer/views/show.html.erb
150
+ - lib/sidekiq/debouncer/web.rb
151
+ - lib/sidekiq/debouncer/web_extension.rb
125
152
  - sidekiq-debouncer.gemspec
126
153
  homepage: https://github.com/paladinsoftware/sidekiq-debouncer
127
154
  licenses:
@@ -135,7 +162,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
162
  requirements:
136
163
  - - ">="
137
164
  - !ruby/object:Gem::Version
138
- version: 2.7.0
165
+ version: 3.0.0
139
166
  required_rubygems_version: !ruby/object:Gem::Requirement
140
167
  requirements:
141
168
  - - ">="