sidekiq-status 0.6.0 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yaml +53 -0
  3. data/.gitignore +4 -1
  4. data/.gitlab-ci.yml +17 -0
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +36 -18
  7. data/Gemfile +0 -4
  8. data/README.md +129 -50
  9. data/Rakefile +2 -0
  10. data/gemfiles/sidekiq_6.1.gemfile +7 -0
  11. data/gemfiles/sidekiq_6.x.gemfile +7 -0
  12. data/gemfiles/sidekiq_7.x.gemfile +7 -0
  13. data/lib/sidekiq-status/client_middleware.rb +46 -8
  14. data/lib/sidekiq-status/redis_adapter.rb +18 -0
  15. data/lib/sidekiq-status/redis_client_adapter.rb +14 -0
  16. data/lib/sidekiq-status/server_middleware.rb +76 -20
  17. data/lib/sidekiq-status/sidekiq_extensions.rb +7 -0
  18. data/lib/sidekiq-status/storage.rb +19 -9
  19. data/lib/sidekiq-status/testing/inline.rb +10 -0
  20. data/lib/sidekiq-status/version.rb +1 -1
  21. data/lib/sidekiq-status/web.rb +116 -25
  22. data/lib/sidekiq-status/worker.rb +12 -5
  23. data/lib/sidekiq-status.rb +40 -5
  24. data/sidekiq-status.gemspec +7 -4
  25. data/spec/environment.rb +1 -0
  26. data/spec/lib/sidekiq-status/client_middleware_spec.rb +15 -12
  27. data/spec/lib/sidekiq-status/server_middleware_spec.rb +66 -22
  28. data/spec/lib/sidekiq-status/web_spec.rb +62 -15
  29. data/spec/lib/sidekiq-status/worker_spec.rb +19 -1
  30. data/spec/lib/sidekiq-status_spec.rb +94 -21
  31. data/spec/spec_helper.rb +104 -26
  32. data/spec/support/test_jobs.rb +71 -6
  33. data/web/sidekiq-status-single-web.png +0 -0
  34. data/web/views/status.erb +118 -0
  35. data/web/views/status_not_found.erb +6 -0
  36. data/web/views/statuses.erb +108 -22
  37. metadata +74 -13
  38. data/.travis.yml +0 -16
  39. data/gemfiles/Gemfile.sidekiq-2 +0 -5
@@ -1,6 +1,13 @@
1
+ if Sidekiq.major_version >= 5
2
+ require 'sidekiq/job_retry'
3
+ end
4
+
1
5
  module Sidekiq::Status
2
- # Should be in the server middleware chain
6
+ # Should be in the server middleware chain
3
7
  class ServerMiddleware
8
+
9
+ DEFAULT_MAX_RETRY_ATTEMPTS = Sidekiq.major_version >= 5 ? Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS : 25
10
+
4
11
  include Storage
5
12
 
6
13
  # Parameterized initialization, use it when adding middleware to server chain
@@ -22,28 +29,77 @@ module Sidekiq::Status
22
29
  # @param [Array] msg job args, should have jid format
23
30
  # @param [String] queue queue name
24
31
  def call(worker, msg, queue)
25
- # a way of overriding default expiration time,
26
- # so worker wouldn't lose its data
27
- # and it allows also to overwrite global expiration time on worker basis
28
- if worker.respond_to? :expiration
29
- if !worker.expiration && worker.respond_to?(:expiration=)
30
- worker.expiration = @expiration
31
- else
32
- @expiration = worker.expiration
32
+
33
+ # Initial assignment to prevent SystemExit & co. from excepting
34
+ expiry = @expiration
35
+
36
+ # Determine the actual job class
37
+ klass = msg["args"][0]["job_class"] || msg["class"] rescue msg["class"]
38
+ job_class = klass.is_a?(Class) ? klass : Module.const_get(klass)
39
+
40
+ # Bypass unless this is a Sidekiq::Status::Worker job
41
+ unless job_class.ancestors.include?(Sidekiq::Status::Worker)
42
+ yield
43
+ return
44
+ end
45
+
46
+ begin
47
+ # Determine job expiration
48
+ expiry = job_class.new.expiration || @expiration rescue @expiration
49
+
50
+ store_status worker.jid, :working, expiry
51
+ yield
52
+ store_status worker.jid, :complete, expiry
53
+ rescue Worker::Stopped
54
+ store_status worker.jid, :stopped, expiry
55
+ rescue SystemExit, Interrupt
56
+ store_status worker.jid, :interrupted, expiry
57
+ raise
58
+ rescue Exception
59
+ status = :failed
60
+ if msg['retry']
61
+ if retry_attempt_number(msg) < retry_attempts_from(msg['retry'], DEFAULT_MAX_RETRY_ATTEMPTS)
62
+ status = :retrying
63
+ end
33
64
  end
65
+ store_status(worker.jid, status, expiry) if job_class && job_class.ancestors.include?(Sidekiq::Status::Worker)
66
+ raise
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def retry_attempt_number(msg)
73
+ if msg['retry_count']
74
+ msg['retry_count'] + sidekiq_version_dependent_retry_offset
75
+ else
76
+ 0
34
77
  end
78
+ end
79
+
80
+ def retry_attempts_from(msg_retry, default)
81
+ msg_retry.is_a?(Integer) ? msg_retry : default
82
+ end
35
83
 
36
- store_status worker.jid, :working, @expiration
37
- yield
38
- store_status worker.jid, :complete, @expiration
39
- rescue Worker::Stopped
40
- store_status worker.jid, :stopped, @expiration
41
- rescue SystemExit, Interrupt
42
- store_status worker.jid, :interrupted, @expiration
43
- raise
44
- rescue
45
- store_status worker.jid, :failed, @expiration
46
- raise
84
+ def sidekiq_version_dependent_retry_offset
85
+ Sidekiq.major_version >= 4 ? 1 : 0
47
86
  end
48
87
  end
88
+
89
+ # Helper method to easily configure sidekiq-status server middleware
90
+ # whatever the Sidekiq version is.
91
+ # @param [Sidekiq] sidekiq_config the Sidekiq config
92
+ # @param [Hash] server_middleware_options server middleware initialization options
93
+ # @option server_middleware_options [Fixnum] :expiration ttl for complete jobs
94
+ def self.configure_server_middleware(sidekiq_config, server_middleware_options = {})
95
+ sidekiq_config.server_middleware do |chain|
96
+ if Sidekiq.major_version < 5
97
+ chain.insert_after Sidekiq::Middleware::Server::Logging,
98
+ Sidekiq::Status::ServerMiddleware, server_middleware_options
99
+ else
100
+ chain.add Sidekiq::Status::ServerMiddleware, server_middleware_options
101
+ end
102
+ end
103
+
104
+ end
49
105
  end
@@ -0,0 +1,7 @@
1
+ require 'sidekiq/version'
2
+
3
+ module Sidekiq
4
+ def self.major_version
5
+ VERSION.split('.').first.to_i
6
+ end
7
+ end
@@ -12,11 +12,12 @@ module Sidekiq::Status::Storage
12
12
  # @param [ConnectionPool] redis_pool optional redis connection pool
13
13
  # @return [String] Redis operation status code
14
14
  def store_for_id(id, status_updates, expiration = nil, redis_pool=nil)
15
+ status_updates.transform_values!(&:to_s)
15
16
  redis_connection(redis_pool) do |conn|
16
- conn.multi do
17
- conn.hmset key(id), 'update_time', Time.now.to_i, *(status_updates.to_a.flatten(1))
18
- conn.expire key(id), (expiration || Sidekiq::Status::DEFAULT_EXPIRY)
19
- conn.publish "status_updates", id
17
+ conn.multi do |pipeline|
18
+ pipeline.hset key(id), 'update_time', Time.now.to_i, *(status_updates.to_a.flatten(1))
19
+ pipeline.expire key(id), (expiration || Sidekiq::Status::DEFAULT_EXPIRY)
20
+ pipeline.publish "status_updates", id
20
21
  end[0]
21
22
  end
22
23
  end
@@ -36,7 +37,7 @@ module Sidekiq::Status::Storage
36
37
  # @param [String] id job id
37
38
  # @param [Num] job_unix_time, unix timestamp for the scheduled job
38
39
  def delete_and_unschedule(job_id, job_unix_time = nil)
39
- Sidekiq.redis do |conn|
40
+ Sidekiq::Status.redis_adapter do |conn|
40
41
  scan_options = {offset: 0, conn: conn, start: (job_unix_time || '-inf'), end: (job_unix_time || '+inf')}
41
42
 
42
43
  while not (jobs = schedule_batch(scan_options)).empty?
@@ -52,12 +53,21 @@ module Sidekiq::Status::Storage
52
53
  false
53
54
  end
54
55
 
56
+ # Deletes status hash info for given job id
57
+ # @param[String] job id
58
+ # @retrun [Integer] number of keys that were removed
59
+ def delete_status(id)
60
+ redis_connection do |conn|
61
+ conn.del(key(id))
62
+ end
63
+ end
64
+
55
65
  # Gets a single valued from job status hash
56
66
  # @param [String] id job id
57
67
  # @param [String] Symbol field fetched field name
58
68
  # @return [String] Redis operation status code
59
69
  def read_field_for_id(id, field)
60
- Sidekiq.redis do |conn|
70
+ Sidekiq::Status.redis_adapter do |conn|
61
71
  conn.hget(key(id), field)
62
72
  end
63
73
  end
@@ -66,8 +76,8 @@ module Sidekiq::Status::Storage
66
76
  # @param [String] id job id
67
77
  # @return [Hash] Hash stored in redis
68
78
  def read_hash_for_id(id)
69
- Sidekiq.redis do |conn|
70
- conn.hgetall key(id)
79
+ Sidekiq::Status.redis_adapter do |conn|
80
+ conn.hgetall(key(id))
71
81
  end
72
82
  end
73
83
 
@@ -81,7 +91,7 @@ module Sidekiq::Status::Storage
81
91
  # - end: end score (i.e. +inf or a unix timestamp)
82
92
  # - offset: current progress through (all) jobs (e.g.: 100 if you want jobs from 100 to BATCH_LIMIT)
83
93
  def schedule_batch(options)
84
- options[:conn].zrangebyscore "schedule", options[:start], options[:end], {limit: [options[:offset], BATCH_LIMIT]}
94
+ Sidekiq::Status.wrap_redis_connection(options[:conn]).schedule_batch("schedule", options.merge(limit: BATCH_LIMIT))
85
95
  end
86
96
 
87
97
  # Searches the jobs Array for the job_id
@@ -5,6 +5,16 @@ module Sidekiq
5
5
  :complete
6
6
  end
7
7
  end
8
+
9
+ module Storage
10
+ def store_status(id, status, expiration = nil, redis_pool=nil)
11
+ 'ok'
12
+ end
13
+
14
+ def store_for_id(id, status_updates, expiration = nil, redis_pool=nil)
15
+ 'ok'
16
+ end
17
+ end
8
18
  end
9
19
  end
10
20
 
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Status
3
- VERSION = '0.6.0'
3
+ VERSION = '3.0.3'
4
4
  end
5
5
  end
@@ -6,9 +6,40 @@ module Sidekiq::Status
6
6
  # Location of Sidekiq::Status::Web view templates
7
7
  VIEW_PATH = File.expand_path('../../../web/views', __FILE__)
8
8
 
9
+ DEFAULT_PER_PAGE_OPTS = [25, 50, 100].freeze
10
+ DEFAULT_PER_PAGE = 25
11
+ COMMON_STATUS_HASH_KEYS = %w(update_time jid status worker args label pct_complete total at message working_at elapsed eta)
12
+
13
+ class << self
14
+ def per_page_opts= arr
15
+ @per_page_opts = arr
16
+ end
17
+ def per_page_opts
18
+ @per_page_opts || DEFAULT_PER_PAGE_OPTS
19
+ end
20
+ def default_per_page= val
21
+ @default_per_page = val
22
+ end
23
+ def default_per_page
24
+ @default_per_page || DEFAULT_PER_PAGE
25
+ end
26
+ end
27
+
9
28
  # @param [Sidekiq::Web] app
10
29
  def self.registered(app)
30
+
31
+ # Allow method overrides to support RESTful deletes
32
+ app.set :method_override, true
33
+
11
34
  app.helpers do
35
+ def csrf_tag
36
+ "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
37
+ end
38
+
39
+ def poll_path
40
+ "?#{request.query_string}" if params[:poll]
41
+ end
42
+
12
43
  def sidekiq_status_template(name)
13
44
  path = File.join(VIEW_PATH, name.to_s) + ".erb"
14
45
  File.open(path).read
@@ -16,20 +47,40 @@ module Sidekiq::Status
16
47
 
17
48
  def add_details_to_status(status)
18
49
  status['label'] = status_label(status['status'])
19
- status["pct_complete"] = pct_complete(status)
50
+ status["pct_complete"] ||= pct_complete(status)
51
+ status["elapsed"] ||= elapsed(status).to_s
52
+ status["eta"] ||= eta(status).to_s
53
+ status["custom"] = process_custom_data(status)
20
54
  return status
21
55
  end
22
56
 
57
+ def process_custom_data(hash)
58
+ hash.reject { |key, _| COMMON_STATUS_HASH_KEYS.include?(key) }
59
+ end
60
+
23
61
  def pct_complete(status)
24
62
  return 100 if status['status'] == 'complete'
25
- Sidekiq::Status::pct_complete(status['jid'])
63
+ Sidekiq::Status::pct_complete(status['jid']) || 0
64
+ end
65
+
66
+ def elapsed(status)
67
+ case status['status']
68
+ when 'complete'
69
+ Sidekiq::Status.update_time(status['jid']) - Sidekiq::Status.working_at(status['jid'])
70
+ when 'working', 'retrying'
71
+ Time.now.to_i - Sidekiq::Status.working_at(status['jid'])
72
+ end
73
+ end
74
+
75
+ def eta(status)
76
+ Sidekiq::Status.eta(status['jid']) if status['status'] == 'working'
26
77
  end
27
78
 
28
79
  def status_label(status)
29
80
  case status
30
81
  when 'complete'
31
82
  'success'
32
- when 'working'
83
+ when 'working', 'retrying'
33
84
  'warning'
34
85
  when 'queued'
35
86
  'primary'
@@ -39,62 +90,102 @@ module Sidekiq::Status
39
90
  end
40
91
 
41
92
  def has_sort_by?(value)
42
- ["worker", "status", "update_time", "pct_complete", "message"].include?(value)
93
+ ["worker", "status", "update_time", "pct_complete", "message", "args"].include?(value)
43
94
  end
44
95
  end
45
96
 
46
97
  app.get '/statuses' do
47
- namespace_jids = Sidekiq.redis{ |conn| conn.keys('sidekiq:status:*') }
48
- jids = namespace_jids.map{|id_namespace| id_namespace.split(':').last }
98
+
99
+ jids = Sidekiq::Status.redis_adapter do |conn|
100
+ conn.scan(match: 'sidekiq:status:*', count: 100).map do |key|
101
+ key.split(':').last
102
+ end.uniq
103
+ end
49
104
  @statuses = []
50
105
 
51
106
  jids.each do |jid|
52
107
  status = Sidekiq::Status::get_all jid
53
108
  next if !status || status.count < 2
54
109
  status = add_details_to_status(status)
55
- @statuses << OpenStruct.new(status)
110
+ @statuses << status
56
111
  end
57
112
 
58
113
  sort_by = has_sort_by?(params[:sort_by]) ? params[:sort_by] : "update_time"
59
114
  sort_dir = "asc"
60
115
 
61
116
  if params[:sort_dir] == "asc"
62
- @statuses = @statuses.sort { |x,y| x.send(sort_by) <=> y.send(sort_by) }
117
+ @statuses = @statuses.sort { |x,y| (x[sort_by] <=> y[sort_by]) || -1 }
63
118
  else
64
119
  sort_dir = "desc"
65
- @statuses = @statuses.sort { |y,x| x.send(sort_by) <=> y.send(sort_by) }
120
+ @statuses = @statuses.sort { |y,x| (x[sort_by] <=> y[sort_by]) || 1 }
66
121
  end
67
-
68
- working_jobs = @statuses.select{|job| job.status == "working"}
69
- if working_jobs.size >= 25
70
- @statuses = working_jobs
71
- else
72
- @statuses = (@statuses.size >= 25) ? @statuses.take(25) : @statuses
122
+
123
+ if params[:status] && params[:status] != "all"
124
+ @statuses = @statuses.select {|job_status| job_status["status"] == params[:status] }
73
125
  end
74
-
126
+
127
+ # Sidekiq pagination
128
+ @total_size = @statuses.count
129
+ @count = params[:per_page] ? params[:per_page].to_i : Sidekiq::Status::Web.default_per_page
130
+ @count = @total_size if params[:per_page] == 'all'
131
+ @current_page = params[:page].to_i < 1 ? 1 : params[:page].to_i
132
+ @statuses = @statuses.slice((@current_page - 1) * @count, @count)
133
+
75
134
  @headers = [
76
- { id: "worker", name: "Worker/jid", class: nil, url: nil},
77
- { id: "status", name: "Status", class: nil, url: nil},
78
- { id: "update_time", name: "Last Updated", class: nil, url: nil},
79
- { id: "pct_complete", name: "Progress", class: nil, url: nil},
80
- { id: "message", name: "Message", class: nil, url: nil}
135
+ {id: "worker", name: "Worker / JID", class: nil, url: nil},
136
+ {id: "args", name: "Arguments", class: nil, url: nil},
137
+ {id: "status", name: "Status", class: nil, url: nil},
138
+ {id: "update_time", name: "Last Updated", class: nil, url: nil},
139
+ {id: "pct_complete", name: "Progress", class: nil, url: nil},
140
+ {id: "elapsed", name: "Time Elapsed", class: nil, url: nil},
141
+ {id: "eta", name: "ETA", class: nil, url: nil},
81
142
  ]
82
143
 
83
144
  @headers.each do |h|
84
- params["sort_by"] = h[:id]
85
- params["sort_dir"] = (sort_by == h[:id] && sort_dir == "asc") ? "desc" : "asc"
86
- h[:url] = "statuses?" + params.map {|k,v| "#{k}=#{v}" }.join("&")
145
+ h[:url] = "statuses?" + params.merge("sort_by" => h[:id], "sort_dir" => (sort_by == h[:id] && sort_dir == "asc") ? "desc" : "asc").map{|k, v| "#{k}=#{CGI.escape v.to_s}"}.join("&")
87
146
  h[:class] = "sorted_#{sort_dir}" if sort_by == h[:id]
88
147
  end
89
148
 
90
149
  erb(sidekiq_status_template(:statuses))
91
150
  end
151
+
152
+ app.get '/statuses/:jid' do
153
+ job = Sidekiq::Status::get_all params['jid']
154
+
155
+ if job.empty?
156
+ throw :halt, [404, {"Content-Type" => "text/html"}, [erb(sidekiq_status_template(:status_not_found))]]
157
+ else
158
+ @status = add_details_to_status(job)
159
+ erb(sidekiq_status_template(:status))
160
+ end
161
+ end
162
+
163
+ # Retries a failed job from the status list
164
+ app.put '/statuses' do
165
+ job = Sidekiq::RetrySet.new.find_job(params[:jid])
166
+ job ||= Sidekiq::DeadSet.new.find_job(params[:jid])
167
+ job.retry if job
168
+ throw :halt, [302, { "Location" => request.referer }, []]
169
+ end
170
+
171
+ # Removes a completed job from the status list
172
+ app.delete '/statuses' do
173
+ Sidekiq::Status.delete(params[:jid])
174
+ throw :halt, [302, { "Location" => request.referer }, []]
175
+ end
92
176
  end
93
177
  end
94
178
  end
95
179
 
96
- require 'sidekiq/web' unless defined?(Sidekiq::Web)
180
+ unless defined?(Sidekiq::Web)
181
+ require 'delegate' # Needed for sidekiq 5.x
182
+ require 'sidekiq/web'
183
+ end
184
+
97
185
  Sidekiq::Web.register(Sidekiq::Status::Web)
186
+ ["per_page", "sort_by", "sort_dir", "status"].each do |key|
187
+ Sidekiq::WebHelpers::SAFE_QPARAMS.push(key)
188
+ end
98
189
  if Sidekiq::Web.tabs.is_a?(Array)
99
190
  # For sidekiq < 2.5
100
191
  Sidekiq::Web.tabs << "statuses"
@@ -11,14 +11,14 @@ module Sidekiq::Status::Worker
11
11
  # @param [Hash] status_updates updated values
12
12
  # @return [String] Redis operation status code
13
13
  def store(hash)
14
- store_for_id @jid, hash, @expiration
14
+ store_for_id @provider_job_id || @job_id || @jid || "", hash, @expiration
15
15
  end
16
16
 
17
17
  # Read value from job status hash
18
18
  # @param String|Symbol hask key
19
19
  # @return [String]
20
20
  def retrieve(name)
21
- read_field_for_id @jid, name
21
+ read_field_for_id @provider_job_id || @job_id || @jid || "", name
22
22
  end
23
23
 
24
24
  # Sets current task progress
@@ -27,15 +27,22 @@ module Sidekiq::Status::Worker
27
27
  # @param String optional message
28
28
  # @return [String]
29
29
  def at(num, message = nil)
30
- total(100) if retrieve(:total).nil?
31
- store(at: num, message: message)
30
+ @_status_total = 100 if @_status_total.nil?
31
+ pct_complete = ((num / @_status_total.to_f) * 100).to_i rescue 0
32
+ store(at: num, total: @_status_total, pct_complete: pct_complete, message: message, working_at: working_at)
32
33
  end
33
34
 
34
35
  # Sets total number of tasks
35
36
  # @param Fixnum total number of tasks
36
37
  # @return [String]
37
38
  def total(num)
38
- store(total: num)
39
+ @_status_total = num
40
+ store(total: num, working_at: working_at)
39
41
  end
40
42
 
43
+ private
44
+
45
+ def working_at
46
+ @working_at ||= Time.now.to_i
47
+ end
41
48
  end
@@ -1,14 +1,18 @@
1
- require "sidekiq-status/version"
1
+ require 'sidekiq-status/version'
2
+ require 'sidekiq-status/sidekiq_extensions'
2
3
  require 'sidekiq-status/storage'
3
4
  require 'sidekiq-status/worker'
5
+ require 'sidekiq-status/redis_client_adapter'
6
+ require 'sidekiq-status/redis_adapter'
4
7
  require 'sidekiq-status/client_middleware'
5
8
  require 'sidekiq-status/server_middleware'
6
9
  require 'sidekiq-status/web' if defined?(Sidekiq::Web)
10
+ require 'chronic_duration'
7
11
 
8
12
  module Sidekiq::Status
9
13
  extend Storage
10
14
  DEFAULT_EXPIRY = 60 * 30
11
- STATUS = [ :queued, :working, :complete, :stopped, :failed, :interrupted ].freeze
15
+ STATUS = [ :queued, :working, :retrying, :complete, :stopped, :failed, :interrupted ].freeze
12
16
 
13
17
  class << self
14
18
  # Job status by id
@@ -21,8 +25,8 @@ module Sidekiq::Status
21
25
  # Get all status fields for a job
22
26
  # @params [String] id job id returned by async_perform
23
27
  # @return [Hash] hash of all fields stored for the job
24
- def get_all(id)
25
- read_hash_for_id(id)
28
+ def get_all(job_id)
29
+ read_hash_for_id(job_id)
26
30
  end
27
31
 
28
32
  def status(job_id)
@@ -34,6 +38,10 @@ module Sidekiq::Status
34
38
  delete_and_unschedule(job_id, job_unix_time)
35
39
  end
36
40
 
41
+ def delete(job_id)
42
+ delete_status(job_id)
43
+ end
44
+
37
45
  alias_method :unschedule, :cancel
38
46
 
39
47
  STATUS.each do |name|
@@ -52,11 +60,38 @@ module Sidekiq::Status
52
60
  end
53
61
 
54
62
  def pct_complete(job_id)
55
- ((at(job_id) / total(job_id).to_f) * 100 ).to_i if total(job_id).to_f > 0
63
+ get(job_id, :pct_complete).to_i
64
+ end
65
+
66
+ def working_at(job_id)
67
+ (get(job_id, :working_at) || Time.now).to_i
68
+ end
69
+
70
+ def update_time(job_id)
71
+ (get(job_id, :update_time) || Time.now).to_i
72
+ end
73
+
74
+ def eta(job_id)
75
+ at = at(job_id)
76
+ return nil if at.zero?
77
+
78
+ (Time.now.to_i - working_at(job_id)).to_f / at * (total(job_id) - at)
56
79
  end
57
80
 
58
81
  def message(job_id)
59
82
  get(job_id, :message)
60
83
  end
84
+
85
+ def wrap_redis_connection(conn)
86
+ if Sidekiq.major_version >= 7
87
+ conn.is_a?(RedisClientAdapter) ? conn : RedisClientAdapter.new(conn)
88
+ else
89
+ conn.is_a?(RedisAdapter) ? conn : RedisAdapter.new(conn)
90
+ end
91
+ end
92
+
93
+ def redis_adapter
94
+ Sidekiq.redis { |conn| yield wrap_redis_connection(conn) }
95
+ end
61
96
  end
62
97
  end
@@ -2,10 +2,10 @@
2
2
  require File.expand_path('../lib/sidekiq-status/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ['Evgeniy Tsvigun']
6
- gem.email = ['utgarda@gmail.com']
5
+ gem.authors = ['Evgeniy Tsvigun', 'Kenaniah Cerny']
6
+ gem.email = ['utgarda@gmail.com', 'kenaniah@gmail.com']
7
7
  gem.summary = 'An extension to the sidekiq message processing to track your jobs'
8
- gem.homepage = 'http://github.com/utgarda/sidekiq-status'
8
+ gem.homepage = 'https://github.com/kenaniah/sidekiq-status'
9
9
  gem.license = 'MIT'
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
@@ -14,7 +14,10 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ['lib']
15
15
  gem.version = Sidekiq::Status::VERSION
16
16
 
17
- gem.add_dependency 'sidekiq', '>= 2.7'
17
+ gem.add_dependency 'sidekiq', '>= 6.0', '< 8'
18
+ gem.add_dependency 'chronic_duration'
19
+ gem.add_development_dependency 'appraisal'
20
+ gem.add_development_dependency 'colorize'
18
21
  gem.add_development_dependency 'rack-test'
19
22
  gem.add_development_dependency 'rake'
20
23
  gem.add_development_dependency 'rspec'
@@ -0,0 +1 @@
1
+ # This file has been intentionally left blank
@@ -5,21 +5,20 @@ describe Sidekiq::Status::ClientMiddleware do
5
5
  let!(:redis) { Sidekiq.redis { |conn| conn } }
6
6
  let!(:job_id) { SecureRandom.hex(12) }
7
7
 
8
- # Clean Redis before each test
9
- before { redis.flushall }
8
+ before do
9
+ allow(SecureRandom).to receive(:hex).once.and_return(job_id)
10
+ end
11
+
12
+ describe "without :expiration parameter" do
10
13
 
11
- describe "#call" do
12
- before { client_middleware }
13
14
  it "sets queued status" do
14
- allow(SecureRandom).to receive(:hex).once.and_return(job_id)
15
- expect(StubJob.perform_async(:arg1 => 'val1')).to eq(job_id)
15
+ expect(StubJob.perform_async 'arg1' => 'val1').to eq(job_id)
16
16
  expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('queued')
17
17
  expect(Sidekiq::Status::queued?(job_id)).to be_truthy
18
18
  end
19
19
 
20
20
  it "sets status hash ttl" do
21
- allow(SecureRandom).to receive(:hex).once.and_return(job_id)
22
- expect(StubJob.perform_async(:arg1 => 'val1')).to eq(job_id)
21
+ expect(StubJob.perform_async 'arg1' => 'val1').to eq(job_id)
23
22
  expect(1..Sidekiq::Status::DEFAULT_EXPIRY).to cover redis.ttl("sidekiq:status:#{job_id}")
24
23
  end
25
24
 
@@ -38,18 +37,22 @@ describe Sidekiq::Status::ClientMiddleware do
38
37
  Sidekiq::Status::ClientMiddleware.new.call(StubJob, {'jid' => SecureRandom.hex}, :queued) do end
39
38
  end
40
39
  end
40
+
41
41
  end
42
42
 
43
- describe ":expiration parameter" do
43
+ describe "with :expiration parameter" do
44
+
44
45
  let(:huge_expiration) { Sidekiq::Status::DEFAULT_EXPIRY * 100 }
46
+
47
+ # Ensure client middleware is loaded with an expiration parameter set
45
48
  before do
46
- allow(SecureRandom).to receive(:hex).once.and_return(job_id)
49
+ client_middleware expiration: huge_expiration
47
50
  end
48
51
 
49
52
  it "overwrites default expiry value" do
50
- client_middleware(expiration: huge_expiration)
51
- StubJob.perform_async(:arg1 => 'val1')
53
+ StubJob.perform_async 'arg1' => 'val1'
52
54
  expect((Sidekiq::Status::DEFAULT_EXPIRY+1)..huge_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
53
55
  end
56
+
54
57
  end
55
58
  end