sidekiq-status 0.6.0 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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