sidekiq-failures 1.0.4 → 1.1.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -3
- data/.gitignore +1 -1
- data/CHANGELOG.md +9 -4
- data/README.md +9 -4
- data/lib/sidekiq/failures/sorted_entry.rb +8 -1
- data/lib/sidekiq/failures/version.rb +1 -1
- data/lib/sidekiq/failures/views/failure.erb +37 -29
- data/lib/sidekiq/failures/views/failure_legacy.erb +31 -0
- data/lib/sidekiq/failures/views/failures.erb +106 -98
- data/lib/sidekiq/failures/views/failures_legacy.erb +105 -0
- data/lib/sidekiq/failures/web_extension.rb +118 -31
- data/lib/sidekiq/failures.rb +20 -3
- data/sidekiq-failures.gemspec +0 -1
- data/test/failures_test.rb +1 -1
- data/test/middleware_test.rb +32 -21
- data/test/test_helper.rb +3 -1
- data/test/web_extension_test.rb +57 -32
- metadata +5 -20
@@ -1,49 +1,130 @@
|
|
1
1
|
module Sidekiq
|
2
2
|
module Failures
|
3
3
|
module WebExtension
|
4
|
+
LEGACY_SIDEKIQ_VERSION = Gem::Version.new("7.3.9")
|
5
|
+
|
6
|
+
# Helper method to check Sidekiq version
|
7
|
+
def self.legacy_sidekiq?
|
8
|
+
Gem::Version.new(Sidekiq::VERSION) <= LEGACY_SIDEKIQ_VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
# Helper method to get parameters based on path and parameter name
|
12
|
+
def self.fetch_param_value(path, param_name)
|
13
|
+
if legacy_sidekiq?
|
14
|
+
# For newer Sidekiq, use route_params or url_params based on path
|
15
|
+
lambda { |env| env.params[param_name] }
|
16
|
+
else
|
17
|
+
# For legacy Sidekiq, just use params
|
18
|
+
if path.include?(":#{param_name}")
|
19
|
+
lambda { |env| env.route_params(param_name.to_sym) }
|
20
|
+
else
|
21
|
+
lambda { |env| env.url_params(param_name.to_s) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Helper method to handle parse_params vs parse_key compatibility
|
27
|
+
def self.parse_key_or_params
|
28
|
+
lambda do |env, key|
|
29
|
+
if env.respond_to?(:parse_key)
|
30
|
+
env.parse_key(key)
|
31
|
+
else
|
32
|
+
env.parse_params(key)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define the helper method implementation that will be used in both versions
|
38
|
+
# Instead of a static lambda, we'll return a method that needs to be evaluated in context
|
39
|
+
def self.safe_relative_time_implementation
|
40
|
+
lambda do |time, context|
|
41
|
+
return unless time
|
42
|
+
|
43
|
+
time = if time.is_a?(Numeric)
|
44
|
+
Time.at(time)
|
45
|
+
else
|
46
|
+
Time.parse(time)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Use the context to call relative_time
|
50
|
+
context.relative_time(time)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define the helpers module for Sidekiq 8.0+
|
55
|
+
module FailuresHelpers
|
56
|
+
def safe_relative_time(time)
|
57
|
+
# Pass self (the context with relative_time) to the implementation
|
58
|
+
WebExtension.safe_relative_time_implementation.call(time, self)
|
59
|
+
end
|
60
|
+
end
|
4
61
|
|
5
62
|
def self.registered(app)
|
6
63
|
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
64
|
+
if legacy_sidekiq?
|
65
|
+
failures_view_path = File.join(view_path, "failures_legacy.erb")
|
66
|
+
failure_view_path = File.join(view_path, "failure_legacy.erb")
|
67
|
+
else
|
68
|
+
failures_view_path = File.join(view_path, "failures.erb")
|
69
|
+
failure_view_path = File.join(view_path, "failure.erb")
|
70
|
+
end
|
7
71
|
|
8
|
-
|
9
|
-
|
10
|
-
time = if time.is_a?(Numeric)
|
11
|
-
Time.at(time)
|
12
|
-
else
|
13
|
-
Time.parse(time)
|
14
|
-
end
|
72
|
+
# Create a parse helper for use in routes
|
73
|
+
parse_helper = parse_key_or_params
|
15
74
|
|
16
|
-
|
75
|
+
# Use appropriate helpers implementation based on Sidekiq version
|
76
|
+
if legacy_sidekiq?
|
77
|
+
# Original implementation for older Sidekiq versions
|
78
|
+
app.helpers do
|
79
|
+
define_method(:safe_relative_time) do |time|
|
80
|
+
# Pass self (the context with relative_time) to the implementation
|
81
|
+
WebExtension.safe_relative_time_implementation.call(time, self)
|
82
|
+
end
|
17
83
|
end
|
84
|
+
else
|
85
|
+
# New implementation for Sidekiq 8.0+
|
86
|
+
app.helpers(FailuresHelpers)
|
18
87
|
end
|
19
88
|
|
20
89
|
app.get "/failures" do
|
21
|
-
|
22
|
-
|
90
|
+
page_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures", "page").call(self)
|
91
|
+
count_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures", "count").call(self)
|
92
|
+
@count = (count_param || 25).to_i
|
93
|
+
|
94
|
+
(@current_page, @total_size, @failures) = page(LIST_KEY, page_param, @count, :reverse => true)
|
23
95
|
@failures = @failures.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
24
96
|
|
25
|
-
render(:erb, File.read(
|
97
|
+
render(:erb, File.read(failures_view_path))
|
26
98
|
end
|
27
99
|
|
28
100
|
app.get "/failures/:key" do
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
101
|
+
key_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures/:key", "key").call(self)
|
102
|
+
halt 404 unless key_param
|
103
|
+
|
104
|
+
@failure = FailureSet.new.fetch(*parse_helper.call(self, key_param)).first
|
105
|
+
if @failure.nil?
|
106
|
+
redirect "#{root_path}failures"
|
107
|
+
else
|
108
|
+
render(:erb, File.read(failure_view_path))
|
109
|
+
end
|
34
110
|
end
|
35
111
|
|
36
112
|
app.post "/failures" do
|
37
|
-
|
113
|
+
key_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures", "key").call(self)
|
114
|
+
halt 404 unless key_param
|
38
115
|
|
39
|
-
|
40
|
-
job = FailureSet.new.fetch(*
|
116
|
+
key_param.each do |key|
|
117
|
+
job = FailureSet.new.fetch(*parse_helper.call(self, key)).first
|
41
118
|
next unless job
|
42
119
|
|
43
|
-
|
120
|
+
retry_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures", "retry").call(self)
|
121
|
+
if retry_param
|
44
122
|
job.retry_failure
|
45
|
-
|
46
|
-
|
123
|
+
else
|
124
|
+
delete_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures", "delete").call(self)
|
125
|
+
if delete_param
|
126
|
+
job.delete
|
127
|
+
end
|
47
128
|
end
|
48
129
|
end
|
49
130
|
|
@@ -51,21 +132,26 @@ module Sidekiq
|
|
51
132
|
end
|
52
133
|
|
53
134
|
app.post "/failures/:key" do
|
54
|
-
|
135
|
+
key_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures/:key", "key").call(self)
|
136
|
+
halt 404 unless key_param
|
55
137
|
|
56
|
-
job = FailureSet.new.fetch(*
|
138
|
+
job = FailureSet.new.fetch(*parse_helper.call(self, key_param)).first
|
57
139
|
if job
|
58
|
-
|
140
|
+
retry_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures/:key", "retry").call(self)
|
141
|
+
if retry_param
|
59
142
|
job.retry_failure
|
60
|
-
|
61
|
-
|
143
|
+
else
|
144
|
+
delete_param = Sidekiq::Failures::WebExtension.fetch_param_value("/failures/:key", "delete").call(self)
|
145
|
+
if delete_param
|
146
|
+
job.delete
|
147
|
+
end
|
62
148
|
end
|
63
149
|
end
|
64
150
|
redirect_with_query("#{root_path}failures")
|
65
151
|
end
|
66
152
|
|
67
153
|
app.post "/failures/all/reset" do
|
68
|
-
Sidekiq::Failures.
|
154
|
+
Sidekiq::Failures.reset_failure_count
|
69
155
|
redirect "#{root_path}failures"
|
70
156
|
end
|
71
157
|
|
@@ -84,10 +170,11 @@ module Sidekiq
|
|
84
170
|
end
|
85
171
|
|
86
172
|
app.post '/filter/failures' do
|
87
|
-
|
173
|
+
substr_param = Sidekiq::Failures::WebExtension.fetch_param_value("/filter/failures", "substr").call(self)
|
174
|
+
@failures = Sidekiq::Failures::FailureSet.new.scan("*#{substr_param}*")
|
88
175
|
@current_page = 1
|
89
|
-
@count = @total_size = @failures.count
|
90
|
-
render(:erb, File.read(
|
176
|
+
@count = @total_size = @failures.count
|
177
|
+
render(:erb, File.read(failures_view_path))
|
91
178
|
end
|
92
179
|
end
|
93
180
|
end
|
data/lib/sidekiq/failures.rb
CHANGED
@@ -59,9 +59,19 @@ module Sidekiq
|
|
59
59
|
LIST_KEY = :failed
|
60
60
|
|
61
61
|
def self.reset_failures
|
62
|
+
warn "NOTE: Sidekiq::Failures.reset_failures is deprecated; use Sidekiq::Failures.reset_failure_count instead."
|
63
|
+
|
64
|
+
reset_failure_count
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.reset_failure_count
|
62
68
|
Sidekiq.redis { |c| c.set("stat:failed", 0) }
|
63
69
|
end
|
64
70
|
|
71
|
+
def self.clear_failures
|
72
|
+
FailureSet.new.clear
|
73
|
+
end
|
74
|
+
|
65
75
|
def self.count
|
66
76
|
Sidekiq.redis {|r| r.zcard(LIST_KEY) }
|
67
77
|
end
|
@@ -87,7 +97,14 @@ Sidekiq.configure_server do |config|
|
|
87
97
|
end
|
88
98
|
|
89
99
|
if defined?(Sidekiq::Web)
|
90
|
-
|
91
|
-
|
92
|
-
|
100
|
+
if Sidekiq::Failures::WebExtension.legacy_sidekiq?
|
101
|
+
Sidekiq::Web.register Sidekiq::Failures::WebExtension
|
102
|
+
Sidekiq::Web.tabs["Failures"] = "failures"
|
103
|
+
Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "failures/locales")
|
104
|
+
else
|
105
|
+
Sidekiq::Web.configure do |config|
|
106
|
+
config.locales << File.join(File.dirname(__FILE__), "failures/locales")
|
107
|
+
config.register(Sidekiq::Failures::WebExtension, name: "failures", tab: ["Failures"], index: ["failures"])
|
108
|
+
end
|
109
|
+
end
|
93
110
|
end
|
data/sidekiq-failures.gemspec
CHANGED
data/test/failures_test.rb
CHANGED
@@ -3,7 +3,7 @@ require "test_helper"
|
|
3
3
|
module Sidekiq
|
4
4
|
describe Failures do
|
5
5
|
describe '.retry_middleware_class' do
|
6
|
-
it 'returns based on Sidekiq::
|
6
|
+
it 'returns based on Sidekiq::VERSION' do
|
7
7
|
case Sidekiq::VERSION[0]
|
8
8
|
when '5'
|
9
9
|
assert_equal Failures.retry_middleware_class, Sidekiq::JobRetry
|
data/test/middleware_test.rb
CHANGED
@@ -29,20 +29,29 @@ end
|
|
29
29
|
|
30
30
|
class SidekiqPost63
|
31
31
|
def new_processor(boss)
|
32
|
-
config = Sidekiq
|
32
|
+
config = ::Sidekiq
|
33
33
|
config[:queues] = ['default']
|
34
|
-
config[:fetch] = Sidekiq::BasicFetch.new(config)
|
34
|
+
config[:fetch] = ::Sidekiq::BasicFetch.new(config)
|
35
35
|
config[:error_handlers] << Sidekiq.method(:default_error_handler)
|
36
36
|
::Sidekiq::Processor.new(config) { |processor, reason = nil| }
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
class SidekiqPost7
|
41
|
+
def new_processor(boss)
|
42
|
+
config = ::Sidekiq.default_configuration
|
43
|
+
::Sidekiq::Processor.new(config.default_capsule) { |*args| }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
40
47
|
module Sidekiq
|
41
48
|
module Failures
|
42
49
|
describe "Middleware" do
|
43
50
|
def new_provider
|
44
51
|
version = Gem::Version.new(Sidekiq::VERSION)
|
45
|
-
if version >= Gem::Version.new('
|
52
|
+
if version >= Gem::Version.new('7')
|
53
|
+
SidekiqPost7
|
54
|
+
elsif version >= Gem::Version.new('6.4.0')
|
46
55
|
SidekiqPost63
|
47
56
|
elsif version >= Gem::Version.new('6.0')
|
48
57
|
SidekiqPre63
|
@@ -53,13 +62,16 @@ module Sidekiq
|
|
53
62
|
|
54
63
|
before do
|
55
64
|
$invokes = 0
|
56
|
-
@boss =
|
65
|
+
@boss = Minitest::Mock.new
|
57
66
|
@provider = new_provider
|
58
67
|
@processor = @provider.new_processor(@boss)
|
59
68
|
|
60
|
-
Sidekiq.
|
61
|
-
|
62
|
-
|
69
|
+
Sidekiq.configure_server do |config|
|
70
|
+
config.server_middleware do |chain|
|
71
|
+
chain.add Sidekiq::Failures::Middleware
|
72
|
+
end
|
73
|
+
end
|
74
|
+
Sidekiq.redis(&:flushdb)
|
63
75
|
Sidekiq.instance_eval { @failures_default_mode = nil }
|
64
76
|
end
|
65
77
|
|
@@ -96,7 +108,7 @@ module Sidekiq
|
|
96
108
|
assert_equal 0, failures_count
|
97
109
|
|
98
110
|
assert_raises TestException do
|
99
|
-
@processor.process
|
111
|
+
@processor.send(:process, msg)
|
100
112
|
end
|
101
113
|
|
102
114
|
assert_equal 1, failures_count
|
@@ -109,7 +121,7 @@ module Sidekiq
|
|
109
121
|
assert_equal 0, failures_count
|
110
122
|
|
111
123
|
assert_raises TestException do
|
112
|
-
@processor.process
|
124
|
+
@processor.send(:process, msg)
|
113
125
|
end
|
114
126
|
|
115
127
|
assert_equal 1, failures_count
|
@@ -121,7 +133,7 @@ module Sidekiq
|
|
121
133
|
|
122
134
|
assert_equal 0, failures_count
|
123
135
|
|
124
|
-
@processor.process
|
136
|
+
@processor.send(:process, msg)
|
125
137
|
|
126
138
|
assert_equal 0, failures_count
|
127
139
|
assert_equal 1, $invokes
|
@@ -133,7 +145,7 @@ module Sidekiq
|
|
133
145
|
assert_equal 0, failures_count
|
134
146
|
|
135
147
|
assert_raises TestException do
|
136
|
-
@processor.process
|
148
|
+
@processor.send(:process, msg)
|
137
149
|
end
|
138
150
|
|
139
151
|
assert_equal 0, failures_count
|
@@ -148,21 +160,20 @@ module Sidekiq
|
|
148
160
|
assert_equal 0, failures_count
|
149
161
|
|
150
162
|
assert_raises TestException do
|
151
|
-
@processor.process
|
163
|
+
@processor.send(:process, msg)
|
152
164
|
end
|
153
165
|
|
154
166
|
assert_equal 0, failures_count
|
155
167
|
assert_equal 1, $invokes
|
156
168
|
end
|
157
169
|
|
158
|
-
|
159
170
|
it "doesn't record failure if going to be retired again and configured to track exhaustion" do
|
160
171
|
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'failures' => 'exhausted')
|
161
172
|
|
162
173
|
assert_equal 0, failures_count
|
163
174
|
|
164
175
|
assert_raises TestException do
|
165
|
-
@processor.process
|
176
|
+
@processor.send(:process, msg)
|
166
177
|
end
|
167
178
|
|
168
179
|
assert_equal 0, failures_count
|
@@ -175,7 +186,7 @@ module Sidekiq
|
|
175
186
|
assert_equal 0, failures_count
|
176
187
|
|
177
188
|
assert_raises TestException do
|
178
|
-
@processor.process
|
189
|
+
@processor.send(:process, msg)
|
179
190
|
end
|
180
191
|
|
181
192
|
assert_equal 1, failures_count
|
@@ -188,7 +199,7 @@ module Sidekiq
|
|
188
199
|
assert_equal 0, failures_count
|
189
200
|
|
190
201
|
assert_raises TestException do
|
191
|
-
@processor.process
|
202
|
+
@processor.send(:process, msg)
|
192
203
|
end
|
193
204
|
|
194
205
|
assert_equal 1, failures_count
|
@@ -203,7 +214,7 @@ module Sidekiq
|
|
203
214
|
assert_equal 0, failures_count
|
204
215
|
|
205
216
|
assert_raises TestException do
|
206
|
-
@processor.process
|
217
|
+
@processor.send(:process, msg)
|
207
218
|
end
|
208
219
|
|
209
220
|
assert_equal 1, failures_count
|
@@ -218,7 +229,7 @@ module Sidekiq
|
|
218
229
|
assert_equal 0, failures_count
|
219
230
|
|
220
231
|
assert_raises TestException do
|
221
|
-
@processor.process
|
232
|
+
@processor.send(:process, msg)
|
222
233
|
end
|
223
234
|
|
224
235
|
assert_equal 1, failures_count
|
@@ -234,11 +245,11 @@ module Sidekiq
|
|
234
245
|
assert_equal 0, failures_count
|
235
246
|
|
236
247
|
3.times do
|
237
|
-
boss =
|
248
|
+
boss = Minitest::Mock.new
|
238
249
|
processor = @provider.new_processor(boss)
|
239
250
|
|
240
251
|
assert_raises TestException do
|
241
|
-
processor.process
|
252
|
+
processor.send(:process, msg)
|
242
253
|
end
|
243
254
|
|
244
255
|
boss.verify
|
@@ -259,7 +270,7 @@ module Sidekiq
|
|
259
270
|
assert_equal 0, Sidekiq::Failures.count
|
260
271
|
|
261
272
|
assert_raises TestException do
|
262
|
-
@processor.process
|
273
|
+
@processor.send(:process, msg)
|
263
274
|
end
|
264
275
|
|
265
276
|
assert_equal 1, Sidekiq::Failures.count
|
data/test/test_helper.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
$TESTING = true
|
2
2
|
|
3
|
+
ENV["MT_CPU"] = "1" # Disable parallel testing to avoid flaky tests, force a single CPU for minitest
|
4
|
+
|
3
5
|
require "minitest/autorun"
|
4
6
|
require "minitest/spec"
|
5
7
|
require "minitest/mock"
|
@@ -15,4 +17,4 @@ require "sidekiq/cli"
|
|
15
17
|
|
16
18
|
Sidekiq.logger.level = Logger::ERROR
|
17
19
|
|
18
|
-
REDIS = Sidekiq::RedisConnection.create(url: "redis://
|
20
|
+
REDIS = Sidekiq::RedisConnection.create(url: "redis://127.0.0.1:6379/15") # Use DB 15 for testing
|