sidekiq-merger 0.0.9 → 0.1.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
- SHA1:
3
- metadata.gz: 7acc8bcc7c43fe634d4be54f9a4ce5c09135da27
4
- data.tar.gz: ae19824415ac8fe3a38cd876977334f7e41ffaa2
2
+ SHA256:
3
+ metadata.gz: 53e070ac5acbed069e9ac6b49e9f43651bdf0e061a8a29d461e5b40111c8c5f7
4
+ data.tar.gz: 9d08abf9726549e6dacb30a6382591bcb7d86b3bf219c30b4c151b471346872e
5
5
  SHA512:
6
- metadata.gz: 8fe75f849916a6c70177f68bc9f4326b873d7715e43efdc42db5f26206f7561dd2db4a986c229e32fbe43e40b915ac769db94c6d03b10aaab0a78d27a777e9e5
7
- data.tar.gz: 6a0ef14b3ebac1b36ff959da8d35a28454757b5cbe4ea89efb0799fec10d2c34f4d7e324e67b482c432f24812758f79d640401ad1bec877419abcb2e0dc1cf78
6
+ metadata.gz: 8baeb68048097d0e32c31ebb099f658fdb7201bccd8fed439956a60d82fd309d67dec63d4bfd95c4276c72d115c669d8fc49bf0405de789d28f3852725f5950c
7
+ data.tar.gz: 874696413e3d3d8b37cfa196b7adb72bb01526080017a9a34142cbd051f153568b03372d23ab36ca8944e05883452d440e88d65d20339ca3c5a35031b0402085
data/.codeclimate.yml ADDED
@@ -0,0 +1,21 @@
1
+ engines:
2
+ brakeman:
3
+ enabled: true
4
+ duplication:
5
+ enabled: true
6
+ config:
7
+ languages:
8
+ - ruby
9
+ fixme:
10
+ enabled: true
11
+ rubocop:
12
+ enabled: true
13
+ ratings:
14
+ paths:
15
+ - Gemfile.lock
16
+ - "**.erb"
17
+ - "**.rb"
18
+ exclude_paths:
19
+ - "app/"
20
+ - "spec/"
21
+ - "misc/"
data/.gitignore CHANGED
@@ -9,6 +9,8 @@ tmp/
9
9
  rdoc/
10
10
 
11
11
  /Gemfile.lock
12
+ #Appraisal
13
+ *.gemfile.lock
12
14
 
13
15
  *.gem
14
16
  *.rbc
data/.rubocop.yml CHANGED
@@ -1,5 +1,7 @@
1
1
  AllCops:
2
2
  DisabledByDefault: true
3
+ Exclude:
4
+ - 'gemfiles/**/*'
3
5
 
4
6
  Style/StringLiterals:
5
7
  Enabled: true
@@ -9,39 +11,42 @@ Style/StringLiteralsInInterpolation:
9
11
  Enabled: true
10
12
  EnforcedStyle: double_quotes
11
13
 
12
- Style/SpaceBeforeBlockBraces:
14
+ Layout/SpaceBeforeBlockBraces:
13
15
  Enabled: true
14
16
  EnforcedStyle: 'space'
15
17
 
16
- Style/SpaceInsideBrackets:
18
+ Layout/SpaceInsideArrayLiteralBrackets:
19
+ Enabled: true
20
+
21
+ Layout/SpaceInsideReferenceBrackets:
17
22
  Enabled: true
18
23
 
19
- Style/SpaceInsideHashLiteralBraces:
24
+ Layout/SpaceInsideHashLiteralBraces:
20
25
  Enabled: true
21
26
 
22
- Style/SpaceInsideBlockBraces:
27
+ Layout/SpaceInsideBlockBraces:
23
28
  Enabled: true
24
29
 
25
- Style/SpaceAroundEqualsInParameterDefault:
30
+ Layout/SpaceAroundEqualsInParameterDefault:
26
31
  Enabled: true
27
32
 
28
- Style/SpaceBeforeComma:
33
+ Layout/SpaceBeforeComma:
29
34
  Enabled: false
30
35
 
31
- Style/SpaceAroundOperators:
36
+ Layout/SpaceAroundOperators:
32
37
  Enabled: true
33
38
 
34
- Style/SpaceAfterComma:
39
+ Layout/SpaceAfterComma:
35
40
  Enabled: true
36
41
 
37
- Style/ExtraSpacing:
42
+ Layout/ExtraSpacing:
38
43
  Enabled: true
39
44
  AllowForAlignment: true
40
45
 
41
46
  Lint/DuplicateMethods:
42
47
  Enabled: true
43
48
 
44
- Metrics/LineLength:
49
+ Layout/LineLength:
45
50
  Enabled: true
46
51
  Max: 200
47
52
  AllowHeredoc: true
@@ -65,16 +70,13 @@ Style/ClassAndModuleChildren:
65
70
  Exclude:
66
71
  - lib/sidekiq/merger/version.rb
67
72
 
68
- Rails/PluralizationGrammar:
69
- Enabled: true
70
-
71
- Style/AlignArray:
73
+ Layout/ArrayAlignment:
72
74
  Enabled: true
73
75
 
74
- Style/AlignHash:
76
+ Layout/HashAlignment:
75
77
  Enabled: true
76
78
 
77
- Style/BlockEndNewline:
79
+ Layout/BlockEndNewline:
78
80
  Enabled: false
79
81
 
80
82
  Style/DoubleNegation:
data/.travis.yml CHANGED
@@ -1,14 +1,20 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.6
5
- - 2.3.3
6
- - 2.4.0
4
+ - 2.5.9
5
+ - 2.6.7
6
+ - 2.7.3
7
+ gemfile:
8
+ - gemfiles/sidekiq_4_0.gemfile
9
+ - gemfiles/sidekiq_4_1.gemfile
10
+ - gemfiles/sidekiq_4_2.gemfile
11
+ - gemfiles/sidekiq_5_0.gemfile
12
+ - gemfiles/sidekiq_5_1.gemfile
7
13
  services:
8
14
  - redis-server
9
- before_install:
10
- - gem update --system
11
- - gem --version
15
+ cache: bundler
12
16
  script:
13
17
  - "bundle exec rake spec"
14
18
  - "bundle exec rubocop -D"
19
+ notifications:
20
+ email: false
data/Appraisals ADDED
@@ -0,0 +1,19 @@
1
+ appraise "sidekiq-4-0" do
2
+ gem "sidekiq", "~> 4.0.0"
3
+ end
4
+
5
+ appraise "sidekiq-4-1" do
6
+ gem "sidekiq", "~> 4.1.0"
7
+ end
8
+
9
+ appraise "sidekiq-4-2" do
10
+ gem "sidekiq", "~> 4.2.0"
11
+ end
12
+
13
+ appraise "sidekiq-5-0" do
14
+ gem "sidekiq", "~> 5.0.0"
15
+ end
16
+
17
+ appraise "sidekiq-5-1" do
18
+ gem "sidekiq", "~> 5.1.0"
19
+ end
data/Dockerfile CHANGED
@@ -1,10 +1,13 @@
1
1
  FROM ruby:2.3.3
2
2
  MAINTAINER dtaniwaki
3
3
 
4
- ENV PORT ${PORT:-3000}
4
+ ENV PORT 3000
5
+ ENV REDIS_HOST 127.0.0.1
6
+ ENV REDIS_PORT 6379
7
+
5
8
  RUN gem install bundler
6
9
  ADD . /gem
7
10
  WORKDIR /gem/app
8
11
  RUN bundle install -j4
9
12
 
10
- EXPOSE 3000
13
+ EXPOSE $PORT
data/Gemfile CHANGED
@@ -4,3 +4,5 @@ gemspec
4
4
 
5
5
  gem "gem-release"
6
6
  gem "pry"
7
+
8
+ gem "sidekiq", "~> #{ENV["SIDEKIQ_VERSION"]}" unless ENV["SIDEKIQ_VERSION"].nil?
data/README.md CHANGED
@@ -6,8 +6,22 @@
6
6
  [![Coverage Status][cov-image]][cov-link]
7
7
  [![Code Climate][gpa-image]][gpa-link]
8
8
 
9
+ [![Docker][docker-hub-image]][docker-hub-link]
10
+
9
11
  Merge [sidekiq](http://sidekiq.org/) jobs occurring before the execution times. Inspired by [sidekiq-grouping](https://github.com/gzigzigzeo/sidekiq-grouping).
10
12
 
13
+ [Demo](http://sidekiq-merger.dtaniwaki.com/)
14
+
15
+ ## Use Case
16
+
17
+ ### Cancel Task
18
+
19
+ ![Cancel Task](misc/cancel_task_flow.png)
20
+
21
+ ### Bulk Notification
22
+
23
+ ![Bulk Notification](misc/bulk_notification_flow.png)
24
+
11
25
  ## Installation
12
26
 
13
27
  Add this line to your application's Gemfile:
@@ -91,6 +105,14 @@ Format: `Boolean`
91
105
 
92
106
  e.g. `true`
93
107
 
108
+ ### `batch_size` (optional, default: `nil`)
109
+
110
+ Allow to specify how many jobs max to provide as arguments per aggregation
111
+
112
+ Format: `Int`
113
+
114
+ e.g. `50`
115
+
94
116
  ## Web UI
95
117
 
96
118
  ![Web UI](misc/web_ui.png)
@@ -103,13 +125,13 @@ require "sidekiq/merger/web"
103
125
 
104
126
  ## Test
105
127
 
106
- $ bundle exec rspec
128
+ $ bundle exec appraisal rspec
107
129
 
108
130
  The test coverage is available at `./coverage/index.html`.
109
131
 
110
132
  ## Lint
111
133
 
112
- $ bundle exec rubocop
134
+ $ bundle exec appraisal rubocop
113
135
 
114
136
  ## Contributing
115
137
 
@@ -133,4 +155,5 @@ Copyright (c) 2017 dtaniwaki. See [LICENSE](LICENSE) for details.
133
155
  [cov-link]: https://coveralls.io/r/dtaniwaki/sidekiq-merger
134
156
  [gpa-image]: https://codeclimate.com/github/dtaniwaki/sidekiq-merger.svg
135
157
  [gpa-link]: https://codeclimate.com/github/dtaniwaki/sidekiq-merger
136
-
158
+ [docker-hub-image]: http://dockeri.co/image/dtaniwaki/sidekiq-merger
159
+ [docker-hub-link]: https://hub.docker.com/r/dtaniwaki/sidekiq-merger/
data/app/Gemfile CHANGED
@@ -2,6 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem "rack-flash3"
4
4
  gem "sinatra"
5
+ gem "sinatra-contrib"
5
6
  gem "sidekiq"
6
7
  gem "sidekiq-status"
7
8
 
data/app/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- sidekiq-merger (0.0.6)
4
+ sidekiq-merger (0.0.10)
5
5
  activesupport (>= 3.2, < 6)
6
6
  concurrent-ruby (~> 1.0)
7
7
  sidekiq (>= 3.4, < 5)
@@ -14,15 +14,19 @@ GEM
14
14
  i18n (~> 0.7)
15
15
  minitest (~> 5.1)
16
16
  tzinfo (~> 1.1)
17
+ backports (3.6.8)
17
18
  concurrent-ruby (1.0.4)
18
19
  connection_pool (2.2.1)
19
- i18n (0.7.0)
20
+ i18n (0.8.0)
20
21
  minitest (5.10.1)
22
+ multi_json (1.12.1)
21
23
  rack (1.6.5)
22
24
  rack-flash3 (1.0.5)
23
25
  rack
24
26
  rack-protection (1.5.3)
25
27
  rack
28
+ rack-test (0.6.3)
29
+ rack (>= 1.0)
26
30
  redis (3.3.3)
27
31
  sidekiq (4.2.9)
28
32
  concurrent-ruby (~> 1.0)
@@ -35,6 +39,13 @@ GEM
35
39
  rack (~> 1.5)
36
40
  rack-protection (~> 1.4)
37
41
  tilt (>= 1.3, < 3)
42
+ sinatra-contrib (1.4.7)
43
+ backports (>= 2.0)
44
+ multi_json
45
+ rack-protection
46
+ rack-test
47
+ sinatra (~> 1.4.0)
48
+ tilt (>= 1.3, < 3)
38
49
  thread_safe (0.3.5)
39
50
  tilt (2.0.6)
40
51
  tzinfo (1.2.2)
@@ -49,6 +60,7 @@ DEPENDENCIES
49
60
  sidekiq-merger!
50
61
  sidekiq-status
51
62
  sinatra
63
+ sinatra-contrib
52
64
 
53
65
  BUNDLED WITH
54
66
  1.13.6
data/app/app.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require_relative "./sidekiq"
2
2
  require "sinatra/base"
3
+ require "sinatra/cookies"
4
+ require "securerandom"
3
5
  require "rack/flash"
4
6
  require "sidekiq/web"
5
7
  require "sidekiq-status/web"
@@ -9,35 +11,57 @@ class App < Sinatra::Application
9
11
  enable :sessions
10
12
  use Rack::Flash
11
13
 
14
+ before do
15
+ @queue = cookies[:queue] ||= SecureRandom.urlsafe_base64(8)
16
+ end
17
+
12
18
  get "/" do
13
19
  erb :index
14
20
  end
15
21
 
16
22
  post "/some_worker/perform_in" do
17
23
  n = rand(10)
18
- SomeWorker.perform_in((params[:in] || 60).to_i, n)
19
- flash[:notice] = "Added #{n} to SomeWorker"
24
+ Sidekiq::Client.push(
25
+ "queue" => @queue,
26
+ "class" => SomeWorker,
27
+ "args" => [n],
28
+ "at" => Time.now + (params[:in] || 60)
29
+ )
30
+ flash[:notice] = "Added #{n} to SomeWorker to Queue #{@queue}"
20
31
  redirect "/"
21
32
  end
22
33
 
23
34
  post "/some_worker/perform_async" do
24
35
  n = rand(10)
25
- SomeWorker.perform_async(n)
26
- flash[:notice] = "Added #{n} to SomeWorker"
36
+ Sidekiq::Client.push(
37
+ "queue" => @queue,
38
+ "class" => SomeWorker,
39
+ "args" => [n]
40
+ )
41
+ flash[:notice] = "Added #{n} to SomeWorker to Queue #{@queue}"
27
42
  redirect "/"
28
43
  end
29
44
 
30
45
  post "/unique_worker/perform_in" do
31
46
  n = rand(10)
32
- UniqueWorker.perform_in((params[:in] || 60).to_i, n)
33
- flash[:notice] = "Added #{n} to UniqueWorker"
47
+ Sidekiq::Client.push(
48
+ "queue" => @queue,
49
+ "class" => UniqueWorker,
50
+ "args" => [n],
51
+ "at" => Time.now + (params[:in] || 60)
52
+ )
53
+ flash[:notice] = "Added #{n} to UniqueWorker to Queue #{@queue}"
34
54
  redirect "/"
35
55
  end
36
56
 
37
57
  post "/unique_worker/perform_async" do
38
58
  n = rand(10)
39
- UniqueWorker.perform_async(n)
40
- flash[:notice] = "Added #{n} to UniqueWorker"
59
+ Sidekiq::Client.push(
60
+ "queue" => @queue,
61
+ "class" => UniqueWorker,
62
+ "args" => [n]
63
+ )
64
+ flash[:notice] = "Added #{n} to UniqueWorker to Queue #{@queue}"
41
65
  redirect "/"
42
66
  end
43
67
  end
data/app/views/index.erb CHANGED
@@ -1,35 +1,64 @@
1
- <html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
2
3
  <head>
3
- <meta charset="UTF-8">
4
+ <meta charset="UTF-8">
5
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
6
+ <title>Sidekiq Merger</title>
7
+ <meta name="description" content="Merge sidekiq jobs occurring before the execution times. Inspired by sidekiq-grouping.">
4
8
  </head>
5
9
  <body>
6
- <h1>Sidekiq Merger</h1>
7
- <% if flash[:notice] %>
8
- <p style="color: green; font-weight: bold;"><%= flash[:notice] %></p>
9
- <% end %>
10
- <p>
11
- <a href="/sidekiq" target="_blank">Open sidekiq console</a>
12
- </p>
13
- <h2>Workers</h2>
14
- <div style="margin-left: 20px;">
15
- <h3>SomeWorker</h3>
16
- <div>
17
- <form action="/some_worker/perform_in" method="post" style="display: inline-block;">
18
- <input type="submit" name="perform_in" value="perform_in">
19
- </form>
20
- <form action="/some_worker/perform_in" method="post" style="display: inline-block;">
21
- <input type="submit" name="perform_async" value="perform_async">
22
- </form>
10
+ <header class="navbar">
11
+ <div class="container">
12
+ <h1>Sidekiq Merger</h1>
13
+ <a href="https://github.com/dtaniwaki/sidekiq-merger">View Source on GitHub →</a>
23
14
  </div>
24
- <h3>UniqueWorker</h3>
15
+ </header>
16
+ <div class="container">
17
+ <% if flash[:notice] %>
18
+ <div class="alert alert-info">
19
+ <a href="#" class="close" data-dismiss="alert">&times;</a>
20
+ <p><%= flash[:notice] %></p>
21
+ </div>
22
+ <% end %>
23
+ <p class="lead">
24
+ Click the `perform_in` buttons to create or merge tasks until the execution time (in 60s).<br>
25
+ Click the `perform_async` buttons to execute a single task.<br><br>
26
+ Open <a href="<%= "/sidekiq/merges?queue=#{@queue}" %>" target="_blank">sidekiq console</a> to check what happens.
27
+ </p>
25
28
  <div>
26
- <form action="/unique_worker/perform_in" method="post" style="display: inline-block;">
27
- <input type="submit" name="perform_in" value="perform_in">
28
- </form>
29
- <form action="/unique_worker/perform_in" method="post" style="display: inline-block;">
30
- <input type="submit" name="perform_async" value="perform_async">
31
- </form>
29
+ <h2>SomeWorker</h2>
30
+ <p>
31
+ <code>sidekiq_options merger: { unique: false }</code>
32
+ </p>
33
+ <p class="lead">
34
+ <small>Tasks will be merged regardless of uniqueness.</small>
35
+ </p>
36
+ <div>
37
+ <form action="/some_worker/perform_in" method="post" style="display: inline-block;">
38
+ <input type="submit" name="perform_in" value="perform_in" class="btn btn-primary">
39
+ </form>
40
+ <form action="/some_worker/perform_in" method="post" style="display: inline-block;">
41
+ <input type="submit" name="perform_async" value="perform_async" class="btn btn-default">
42
+ </form>
43
+ </div>
44
+ <h2>UniqueWorker</h2>
45
+ <p>
46
+ <code>sidekiq_options merger: { unique: true }</code>
47
+ </p>
48
+ <p class="lead">
49
+ <small>Tasks will be merged if they haven't added already.</small>
50
+ </p>
51
+ <div>
52
+ <form action="/unique_worker/perform_in" method="post" style="display: inline-block;">
53
+ <input type="submit" name="perform_in" value="perform_in" class="btn btn-primary">
54
+ </form>
55
+ <form action="/unique_worker/perform_in" method="post" style="display: inline-block;">
56
+ <input type="submit" name="perform_async" value="perform_async" class="btn btn-default">
57
+ </form>
58
+ </div>
32
59
  </div>
33
60
  </div>
61
+ <script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
62
+ <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
34
63
  </body>
35
64
  </html>
data/docker-compose.yml CHANGED
@@ -20,7 +20,8 @@ services:
20
20
  links:
21
21
  - redis
22
22
  environment:
23
+ - PORT=3000
23
24
  - REDIS_HOST=redis
24
25
  - REDIS_PORT=6379
25
26
  redis:
26
- image: redis:latest
27
+ image: redis:3.2.7
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "gem-release"
6
+ gem "pry"
7
+ gem "sidekiq", "~> 4.0.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "gem-release"
6
+ gem "pry"
7
+ gem "sidekiq", "~> 4.1.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "gem-release"
6
+ gem "pry"
7
+ gem "sidekiq", "~> 4.2.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "gem-release"
6
+ gem "pry"
7
+ gem "sidekiq", "~> 5.0.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "gem-release"
6
+ gem "pry"
7
+ gem "sidekiq", "~> 5.1.0"
8
+
9
+ gemspec path: "../"
@@ -1,6 +1,6 @@
1
1
  require "active_support/configurable"
2
2
 
3
- module Sidekiq::Merger::Config
3
+ class Sidekiq::Merger::Config
4
4
  include ActiveSupport::Configurable
5
5
 
6
6
  def self.options
@@ -73,12 +73,16 @@ class Sidekiq::Merger::Merge
73
73
  end
74
74
 
75
75
  unless msgs.empty?
76
- Sidekiq::Client.push(
77
- "class" => worker_class,
78
- "queue" => queue,
79
- "args" => msgs,
80
- "merged" => true
81
- )
76
+ batches = options[:batch_size].nil? ? [msgs] : msgs.each_slice(options[:batch_size].to_i).to_a
77
+ batches.each do |batch_msgs|
78
+ # preserve FIFO when enqueuing batches
79
+ Sidekiq::Client.push(
80
+ "class" => worker_class,
81
+ "queue" => queue,
82
+ "args" => batch_msgs,
83
+ "merged" => true
84
+ )
85
+ end
82
86
  end
83
87
  end
84
88
 
@@ -1,7 +1,7 @@
1
1
  require_relative "merge"
2
2
 
3
3
  class Sidekiq::Merger::Middleware
4
- def call(worker_class, msg, queue, redis_pool = nil)
4
+ def call(worker_class, msg, queue, _ = nil)
5
5
  return yield if defined?(Sidekiq::Testing) && Sidekiq::Testing.inline?
6
6
 
7
7
  worker_class = worker_class.camelize.constantize if worker_class.is_a?(String)
@@ -9,14 +9,16 @@ class Sidekiq::Merger::Middleware
9
9
 
10
10
  merger_enabled = options.key?("merger")
11
11
 
12
- if merger_enabled && !msg["at"].nil? && msg["at"].to_f > Time.now.to_f
12
+ return yield unless merger_enabled
13
+
14
+ if !msg["at"].nil? && msg["at"].to_f > Time.now.to_f
13
15
  Sidekiq::Merger::Merge
14
16
  .initialize_with_args(worker_class, queue, msg["args"])
15
17
  .add(msg["args"], msg["at"])
16
18
  false
17
19
  else
18
- msg["args"] = [msg["args"]] unless msg.delete("merged")
19
- yield(worker_class, msg, queue, redis_pool)
20
+ msg["args"] = [msg["args"].flatten] unless msg.delete("merged")
21
+ yield
20
22
  end
21
23
  end
22
24
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Merger
3
- VERSION = "0.0.9".freeze
3
+ VERSION = "0.1.0".freeze
4
4
  end
5
5
  end
@@ -25,7 +25,7 @@
25
25
  <td><%= merge.all_args %></td>
26
26
  <td><%= merge.execution_time || "&ndash;"%></td>
27
27
  <td>
28
- <form action="<%= "#{root_path}merger/#{URI.encode_www_form_component merge.full_merge_key}/delete" %>" method="post">
28
+ <form action="<%= "#{root_path}merges/#{URI.encode_www_form_component merge.full_merge_key}/delete" %>" method="post">
29
29
  <%= csrf_tag %>
30
30
  <input class="btn btn-danger btn-xs" type="submit" name="delete" value="Delete" data-confirm="Are you sure you want to delete this merge?" />
31
31
  </form>
@@ -6,6 +6,7 @@ module Sidekiq::Merger::Web
6
6
  def self.registered(app)
7
7
  app.get "/merges" do
8
8
  @merges = Sidekiq::Merger::Merge.all
9
+ @merges.select! { |m| m.queue == params[:queue] } unless params[:queue].nil?
9
10
  erb File.read(File.join(VIEWS, "index.erb")), locals: { view_path: VIEWS }
10
11
  end
11
12
 
@@ -22,6 +22,14 @@ module Sidekiq::Merger
22
22
  task.add_observer(observer)
23
23
  task
24
24
  end
25
+
26
+ def configure(&block)
27
+ yield config
28
+ end
29
+
30
+ def config
31
+ @config ||= Config.new
32
+ end
25
33
  end
26
34
 
27
35
  self.logger = Sidekiq.logger
Binary file
Binary file
@@ -22,16 +22,17 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = [">= 2.2.2", "< 2.5"]
25
+ spec.required_ruby_version = [">= 2.5.0"]
26
26
 
27
27
  spec.add_development_dependency "rake", ">= 10.0", "< 13"
28
28
  spec.add_development_dependency "rspec", ">= 3.0", "< 4"
29
29
  spec.add_development_dependency "simplecov", "~> 0.12"
30
30
  spec.add_development_dependency "timecop", "~> 0.8"
31
- spec.add_development_dependency "rubocop", "~> 0.47"
31
+ spec.add_development_dependency "rubocop", "~> 0.93.1"
32
32
  spec.add_development_dependency "coveralls", "~> 0.8"
33
+ spec.add_development_dependency "appraisal"
33
34
 
34
- spec.add_runtime_dependency "sidekiq", ">= 3.4", "< 5"
35
+ spec.add_runtime_dependency "sidekiq", ">= 4.0", "< 6"
35
36
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
36
37
  spec.add_runtime_dependency "activesupport", ">= 3.2", "< 6"
37
38
  end
@@ -1,28 +1,13 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Sidekiq::Merger::Merge do
3
+ describe Sidekiq::Merger::Merge, worker_class: true do
4
4
  subject { described_class.new(worker_class, queue, args, redis: redis) }
5
5
  let(:args) { "foo" }
6
6
  let(:redis) { Sidekiq::Merger::Redis.new }
7
7
  let(:queue) { "queue" }
8
8
  let(:now) { Time.now }
9
9
  let(:execution_time) { now + 10.seconds }
10
- let(:options) { { key: -> (args) { args.to_json } } }
11
- let(:worker_class) do
12
- local_options = options
13
- Class.new do
14
- include Sidekiq::Worker
15
-
16
- sidekiq_options merger: local_options
17
-
18
- def self.name
19
- "name"
20
- end
21
-
22
- def perform(args)
23
- end
24
- end
25
- end
10
+ let(:worker_options) { { key: -> (args) { args.to_json } } }
26
11
  before { Timecop.freeze(now) }
27
12
 
28
13
  describe ".all" do
@@ -64,30 +49,30 @@ describe Sidekiq::Merger::Merge do
64
49
 
65
50
  describe ".merge_key" do
66
51
  let(:args) { "foo" }
67
- let(:options) { {} }
52
+ let(:worker_options) { {} }
68
53
  it "returns an empty string" do
69
54
  expect(described_class.merge_key(worker_class, args)).to eq ""
70
55
  end
71
56
  context "string key" do
72
- let(:options) { { key: "bar" } }
57
+ let(:worker_options) { { key: "bar" } }
73
58
  it "returns the string" do
74
59
  expect(described_class.merge_key(worker_class, args)).to eq "bar"
75
60
  end
76
61
  end
77
62
  context "other type key" do
78
- let(:options) { { key: [1, 2, 3] } }
63
+ let(:worker_options) { { key: [1, 2, 3] } }
79
64
  it "returns nil" do
80
65
  expect(described_class.merge_key(worker_class, args)).to eq "[1,2,3]"
81
66
  end
82
67
  end
83
68
  context "proc key" do
84
69
  let(:args) { [1, 2, 3] }
85
- let(:options) { { key: -> (args) { args[0].to_s } } }
70
+ let(:worker_options) { { key: -> (args) { args[0].to_s } } }
86
71
  it "returns the result of the proc" do
87
72
  expect(described_class.merge_key(worker_class, args)).to eq "1"
88
73
  end
89
74
  context "non-string result" do
90
- let(:options) { { key: -> (args) { args[0] } } }
75
+ let(:worker_options) { { key: -> (args) { args[0] } } }
91
76
  it "returns nil" do
92
77
  expect(described_class.merge_key(worker_class, args)).to eq "1"
93
78
  end
@@ -97,13 +82,13 @@ describe Sidekiq::Merger::Merge do
97
82
 
98
83
  describe "#add" do
99
84
  it "adds the args in lazy merge" do
100
- expect(redis).to receive(:push_message).with("name:queue:foo", [1, 2, 3], execution_time)
85
+ expect(redis).to receive(:push_message).with("some_worker:queue:foo", [1, 2, 3], execution_time)
101
86
  subject.add([1, 2, 3], execution_time)
102
87
  end
103
88
  context "with unique option" do
104
- let(:options) { { key: -> (args) { args.to_json }, unique: true } }
89
+ let(:worker_options) { { key: -> (args) { args.to_json }, unique: true } }
105
90
  it "adds the args in lazy merge" do
106
- expect(redis).to receive(:push_message).with("name:queue:foo", [1, 2, 3], execution_time)
91
+ expect(redis).to receive(:push_message).with("some_worker:queue:foo", [1, 2, 3], execution_time)
107
92
  subject.add([1, 2, 3], execution_time)
108
93
  end
109
94
  context "the args has alredy been added" do
@@ -118,7 +103,7 @@ describe Sidekiq::Merger::Merge do
118
103
 
119
104
  describe "#delete" do
120
105
  it "adds the args in lazy merge" do
121
- expect(redis).to receive(:delete_message).with("name:queue:foo", [1, 2, 3])
106
+ expect(redis).to receive(:delete_message).with("some_worker:queue:foo", [1, 2, 3])
122
107
  subject.delete([1, 2, 3])
123
108
  end
124
109
  end
@@ -156,19 +141,48 @@ describe Sidekiq::Merger::Merge do
156
141
  end
157
142
 
158
143
  describe "#flush" do
159
- before do
160
- subject.add([1, 2, 3], execution_time)
161
- subject.add([2, 3, 4], execution_time)
144
+ context "when no batch_size is configured" do
145
+ before do
146
+ subject.add([1, 2, 3], execution_time)
147
+ subject.add([2, 3, 4], execution_time)
148
+ end
149
+ it "flushes all the args" do
150
+ expect(Sidekiq::Client).to receive(:push).with(
151
+ "class" => worker_class,
152
+ "queue" => queue,
153
+ "args" => a_collection_containing_exactly([1, 2, 3], [2, 3, 4]),
154
+ "merged" => true
155
+ )
156
+
157
+ subject.flush
158
+ end
162
159
  end
163
- it "flushes all the args" do
164
- expect(Sidekiq::Client).to receive(:push).with(
165
- "class" => worker_class,
166
- "queue" => queue,
167
- "args" => a_collection_containing_exactly([1, 2, 3], [2, 3, 4]),
168
- "merged" => true
169
- )
170
160
 
171
- subject.flush
161
+ context "when batch_size is configured to 2" do
162
+ let(:worker_options) { { key: -> (args) { args.to_json }, batch_size: 2 } }
163
+ before do
164
+ subject.add([1, 2, 3], execution_time)
165
+ subject.add([2, 3, 4], execution_time)
166
+ subject.add([3, 4, 5], execution_time)
167
+ subject.add([4, 5, 6], execution_time)
168
+ end
169
+ it "flushes all the args" do
170
+ expect(Sidekiq::Client).to receive(:push).with(
171
+ "class" => worker_class,
172
+ "queue" => queue,
173
+ "args" => a_collection_containing_exactly([1, 2, 3], [2, 3, 4]),
174
+ "merged" => true
175
+ )
176
+
177
+ expect(Sidekiq::Client).to receive(:push).with(
178
+ "class" => worker_class,
179
+ "queue" => queue,
180
+ "args" => a_collection_containing_exactly([3, 4, 5], [4, 5, 6]),
181
+ "merged" => true
182
+ )
183
+
184
+ subject.flush
185
+ end
172
186
  end
173
187
  end
174
188
 
@@ -195,7 +209,7 @@ describe Sidekiq::Merger::Merge do
195
209
 
196
210
  describe "#full_merge_key" do
197
211
  it "returns full merge key" do
198
- expect(subject.full_merge_key).to eq "name:queue:foo"
212
+ expect(subject.full_merge_key).to eq "some_worker:queue:foo"
199
213
  end
200
214
  end
201
215
  end
@@ -1,32 +1,24 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Sidekiq::Merger::Middleware do
3
+ describe Sidekiq::Merger::Middleware, worker_class: true do
4
4
  subject { described_class.new }
5
5
  let(:flusher) { Sidekiq::Merger::Flusher.new(Sidekiq.logger) }
6
6
  let(:queue) { "queue" }
7
7
  let(:now) { Time.now }
8
- let(:options) { { key: -> (args) { "key" } } }
9
- let(:worker_class) do
10
- local_options = options
11
- Class.new do
12
- include Sidekiq::Worker
13
-
14
- sidekiq_options merger: local_options
15
-
16
- def self.name
17
- "name"
18
- end
19
-
20
- def perform(*args)
21
- end
22
- end
23
- end
24
8
  before :example do
25
- allow(Object).to receive(:const_get).with("Name").and_return worker_class
26
9
  Timecop.freeze(now)
27
10
  end
28
11
 
29
12
  describe "#call" do
13
+ context "non-merger worker" do
14
+ it "leaves args alone" do
15
+ msg = { "args" => [1, 2, 3] }
16
+ expect { |b| subject.call(non_merge_worker_class, msg, queue, &b) }.to yield_with_no_args
17
+ expect(msg).to eq({ "args" => [1, 2, 3] }) #unmodified
18
+ flusher.flush
19
+ expect(worker_class.jobs.size).to eq 0
20
+ end
21
+ end
30
22
  it "adds the args to the merge" do
31
23
  subject.call(worker_class, { "args" => [1, 2, 3], "at" => (now + 10.seconds).to_f }, queue) {}
32
24
  subject.call(worker_class, { "args" => [2, 3, 4], "at" => (now + 15.seconds).to_f }, queue) {}
@@ -41,13 +33,17 @@ describe Sidekiq::Merger::Middleware do
41
33
  end
42
34
  context "without at msg" do
43
35
  it "peforms now with brackets" do
44
- expect { |b| subject.call(worker_class, { "args" => [1, 2, 3] }, queue, &b) }.to yield_with_args(worker_class, { "args" => [[1, 2, 3]] }, queue, anything)
36
+ msg = { "args" => [1, 2, 3] }
37
+ expect { |b| subject.call(worker_class, msg, queue, &b) }.to yield_with_no_args
38
+ expect(msg).to eq({ "args" => [[1, 2, 3]] })
45
39
  flusher.flush
46
40
  expect(worker_class.jobs.size).to eq 0
47
41
  end
48
42
  context "merged msgs" do
49
43
  it "performs now" do
50
- expect { |b| subject.call(worker_class, { "args" => [[1, 2, 3]], "merged" => true }, queue, &b) }.to yield_with_args(worker_class, { "args" => [[1, 2, 3]] }, queue, anything)
44
+ msg = { "args" => [[1, 2, 3]], "merged" => true }
45
+ expect { |b| subject.call(worker_class, msg, queue, &b) }.to yield_with_no_args
46
+ expect(msg).to eq({ "args" => [[1, 2, 3]] })
51
47
  flusher.flush
52
48
  expect(worker_class.jobs.size).to eq 0
53
49
  end
@@ -55,7 +51,9 @@ describe Sidekiq::Merger::Middleware do
55
51
  end
56
52
  context "at is before current time" do
57
53
  it "peforms now" do
58
- expect { |b| subject.call(worker_class, { "args" => [1, 2, 3], "at" => now.to_f }, queue, &b) }.to yield_with_args(worker_class, { "args" => [[1, 2, 3]], "at" => now.to_f }, queue, anything)
54
+ msg = { "args" => [1, 2, 3], "at" => now.to_f }
55
+ expect { |b| subject.call(worker_class, msg, queue, &b) }.to yield_with_no_args
56
+ expect(msg).to eq({ "args" => [[1, 2, 3]], "at" => now.to_f })
59
57
  flusher.flush
60
58
  expect(worker_class.jobs.size).to eq 0
61
59
  end
@@ -4,11 +4,26 @@ describe Sidekiq::Merger do
4
4
  it "has a version number" do
5
5
  expect(described_class::VERSION).not_to be nil
6
6
  end
7
- describe "#create_task" do
7
+ describe ".create_task" do
8
8
  it "starts a monitoring task" do
9
9
  task = described_class.create_task
10
10
  expect(task).to be_a Concurrent::TimerTask
11
11
  task.shutdown
12
12
  end
13
13
  end
14
+ describe ".configure" do
15
+ it "yields to the config" do
16
+ expect { |b| described_class.configure(&b) }.to yield_with_args(described_class.config)
17
+ end
18
+ end
19
+ describe ".config" do
20
+ it "returns a config" do
21
+ expect(described_class.config).to be_a Sidekiq::Merger::Config
22
+ end
23
+ context "called twice" do
24
+ it "returns the same config instance" do
25
+ expect(described_class.config).to be described_class.config
26
+ end
27
+ end
28
+ end
14
29
  end
data/spec/spec_helper.rb CHANGED
@@ -21,31 +21,15 @@ require "sidekiq/merger"
21
21
  Dir[File.join(__dir__, "support", "**", "*.rb")].each { |f| require f }
22
22
 
23
23
  RSpec.configure do |config|
24
- # rspec-expectations config goes here. You can use an alternate
25
- # assertion/expectation library such as wrong or the stdlib/minitest
26
- # assertions if you prefer.
27
24
  config.expect_with :rspec do |expectations|
28
- # This option will default to `true` in RSpec 4. It makes the `description`
29
- # and `failure_message` of custom matchers include text for helper methods
30
- # defined using `chain`, e.g.:
31
- # be_bigger_than(2).and_smaller_than(4).description
32
- # # => "be bigger than 2 and smaller than 4"
33
- # ...rather than:
34
- # # => "be bigger than 2"
35
25
  expectations.include_chain_clauses_in_custom_matcher_descriptions = true
36
26
  end
37
27
 
38
28
  config.mock_with :rspec do |mocks|
39
- # Prevents you from mocking or stubbing a method that does not exist on
40
- # a real object. This is generally recommended, and will default to
41
- # `true` in RSpec 4.
42
29
  mocks.verify_partial_doubles = true
43
30
  end
44
31
 
45
32
  if config.files_to_run.one?
46
- # Use the documentation formatter for detailed output,
47
- # unless a formatter has already been configured
48
- # (e.g. via a command-line flag).
49
33
  config.default_formatter = "doc"
50
34
  end
51
35
 
@@ -57,11 +41,17 @@ RSpec.configure do |config|
57
41
  Sidekiq::Testing.fake!
58
42
  Sidekiq::Merger.logger = nil
59
43
  Sidekiq.logger = nil
44
+ if Redis.respond_to?(:exists_returns_integer)
45
+ Redis.exists_returns_integer = false
46
+ end
60
47
  end
61
48
 
62
- config.before :example do
63
- Sidekiq::Merger::Redis.redis do |conn|
64
- conn.flushall
49
+ config.around :example do |example|
50
+ Sidekiq::Merger::Redis.redis { |conn| conn.flushall }
51
+ begin
52
+ example.run
53
+ ensure
54
+ Sidekiq::Merger::Redis.redis { |conn| conn.flushall }
65
55
  end
66
56
  end
67
57
 
@@ -0,0 +1,49 @@
1
+ RSpec.shared_context "worker class", worker_class: true do
2
+ let(:worker_options) { { key: -> (args) { "key" } } }
3
+ let(:worker_class) do
4
+ local_options = worker_options
5
+ Class.new do
6
+ include Sidekiq::Worker
7
+
8
+ sidekiq_options merger: local_options
9
+
10
+ def self.name
11
+ "SomeWorker"
12
+ end
13
+
14
+ def self.to_s
15
+ "SomeWorker"
16
+ end
17
+
18
+ def perform(*args)
19
+ end
20
+ end
21
+ end
22
+ let(:non_merge_worker_class) do
23
+ Class.new do
24
+ include Sidekiq::Worker
25
+
26
+ def self.to_s
27
+ "NonMergeWorker"
28
+ end
29
+
30
+ def perform(*args)
31
+ end
32
+ end
33
+ end
34
+ before :example do
35
+ allow(Object).to receive(:const_get).with(anything).and_call_original
36
+ allow(Object).to receive(:const_get).with("SomeWorker").and_return worker_class
37
+ allow(Object).to receive(:const_get).with("NonMergeWorker").and_return non_merge_worker_class
38
+ end
39
+ around :example do |example|
40
+ worker_class.jobs.clear
41
+ non_merge_worker_class.jobs.clear
42
+ begin
43
+ example.run
44
+ ensure
45
+ worker_class.jobs.clear
46
+ non_merge_worker_class.jobs.clear
47
+ end
48
+ end
49
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-merger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dtaniwaki
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-01 00:00:00.000000000 Z
11
+ date: 2021-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -84,14 +84,14 @@ dependencies:
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '0.47'
87
+ version: 0.93.1
88
88
  type: :development
89
89
  prerelease: false
90
90
  version_requirements: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '0.47'
94
+ version: 0.93.1
95
95
  - !ruby/object:Gem::Dependency
96
96
  name: coveralls
97
97
  requirement: !ruby/object:Gem::Requirement
@@ -106,26 +106,40 @@ dependencies:
106
106
  - - "~>"
107
107
  - !ruby/object:Gem::Version
108
108
  version: '0.8'
109
+ - !ruby/object:Gem::Dependency
110
+ name: appraisal
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
109
123
  - !ruby/object:Gem::Dependency
110
124
  name: sidekiq
111
125
  requirement: !ruby/object:Gem::Requirement
112
126
  requirements:
113
127
  - - ">="
114
128
  - !ruby/object:Gem::Version
115
- version: '3.4'
129
+ version: '4.0'
116
130
  - - "<"
117
131
  - !ruby/object:Gem::Version
118
- version: '5'
132
+ version: '6'
119
133
  type: :runtime
120
134
  prerelease: false
121
135
  version_requirements: !ruby/object:Gem::Requirement
122
136
  requirements:
123
137
  - - ">="
124
138
  - !ruby/object:Gem::Version
125
- version: '3.4'
139
+ version: '4.0'
126
140
  - - "<"
127
141
  - !ruby/object:Gem::Version
128
- version: '5'
142
+ version: '6'
129
143
  - !ruby/object:Gem::Dependency
130
144
  name: concurrent-ruby
131
145
  requirement: !ruby/object:Gem::Requirement
@@ -167,12 +181,14 @@ executables: []
167
181
  extensions: []
168
182
  extra_rdoc_files: []
169
183
  files:
184
+ - ".codeclimate.yml"
170
185
  - ".dockerignore"
171
186
  - ".gemrelease"
172
187
  - ".gitignore"
173
188
  - ".rspec"
174
189
  - ".rubocop.yml"
175
190
  - ".travis.yml"
191
+ - Appraisals
176
192
  - Dockerfile
177
193
  - Gemfile
178
194
  - LICENSE
@@ -188,6 +204,11 @@ files:
188
204
  - app/workers/unique_worker.rb
189
205
  - docker-compose-common.yml
190
206
  - docker-compose.yml
207
+ - gemfiles/sidekiq_4_0.gemfile
208
+ - gemfiles/sidekiq_4_1.gemfile
209
+ - gemfiles/sidekiq_4_2.gemfile
210
+ - gemfiles/sidekiq_5_0.gemfile
211
+ - gemfiles/sidekiq_5_1.gemfile
191
212
  - lib/sidekiq-merger.rb
192
213
  - lib/sidekiq/merger.rb
193
214
  - lib/sidekiq/merger/config.rb
@@ -199,6 +220,8 @@ files:
199
220
  - lib/sidekiq/merger/version.rb
200
221
  - lib/sidekiq/merger/views/index.erb
201
222
  - lib/sidekiq/merger/web.rb
223
+ - misc/bulk_notification_flow.png
224
+ - misc/cancel_task_flow.png
202
225
  - misc/web_ui.png
203
226
  - sidekiq-merger.gemspec
204
227
  - spec/sidekiq/merger/flusher_spec.rb
@@ -209,11 +232,12 @@ files:
209
232
  - spec/sidekiq/merger_spec.rb
210
233
  - spec/spec_helper.rb
211
234
  - spec/support/matchers.rb
235
+ - spec/support/worker_class.rb
212
236
  homepage: https://github.com/dtaniwaki/sidekiq-merger
213
237
  licenses:
214
238
  - MIT
215
239
  metadata: {}
216
- post_install_message:
240
+ post_install_message:
217
241
  rdoc_options: []
218
242
  require_paths:
219
243
  - lib
@@ -221,19 +245,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
245
  requirements:
222
246
  - - ">="
223
247
  - !ruby/object:Gem::Version
224
- version: 2.2.2
225
- - - "<"
226
- - !ruby/object:Gem::Version
227
- version: '2.5'
248
+ version: 2.5.0
228
249
  required_rubygems_version: !ruby/object:Gem::Requirement
229
250
  requirements:
230
251
  - - ">="
231
252
  - !ruby/object:Gem::Version
232
253
  version: '0'
233
254
  requirements: []
234
- rubyforge_project:
235
- rubygems_version: 2.5.1
236
- signing_key:
255
+ rubygems_version: 3.1.2
256
+ signing_key:
237
257
  specification_version: 4
238
258
  summary: Sidekiq merger plugin
239
259
  test_files:
@@ -245,3 +265,4 @@ test_files:
245
265
  - spec/sidekiq/merger_spec.rb
246
266
  - spec/spec_helper.rb
247
267
  - spec/support/matchers.rb
268
+ - spec/support/worker_class.rb