sidekiq-undertaker 1.1.1 → 1.4.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/ruby-build.yml +31 -3
- data/.rubocop.yml +4 -0
- data/.rubocop_codeclimate.yml +9 -0
- data/CHANGELOG.md +19 -0
- data/README.md +8 -6
- data/assets/Undertaker_demo_1_job_1_error.png +0 -0
- data/assets/Undertaker_demo_1_job_all_errors.png +0 -0
- data/assets/Undertaker_demo_all_errors.png +0 -0
- data/assets/Undertaker_demo_all_jobs.png +0 -0
- data/assets/Undertaker_demo_all_jobs_1_error.png +0 -0
- data/lib/sidekiq/undertaker/dead_job.rb +10 -2
- data/lib/sidekiq/undertaker/job_distributor.rb +4 -0
- data/lib/sidekiq/undertaker/job_filter.rb +7 -2
- data/lib/sidekiq/undertaker/version.rb +1 -1
- data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +104 -26
- data/lib/sidekiq/undertaker/web_extension.rb +17 -8
- data/sidekiq-undertaker.gemspec +6 -5
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +32 -18
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +37 -39
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +37 -39
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +231 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classerror_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +231 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_all_failures_and_errors/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +26 -21
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +284 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_job_class_error_and_specific_error_message/with_pagination/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +1310 -0
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +24 -19
- data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/with_pagination/behaves_like_a_page/the_displayed_page_is_correct_for_sidekiqv6.approved.txt +72 -67
- data/spec/sidekiq/undertaker/dead_jobs_spec.rb +7 -4
- data/spec/sidekiq/undertaker/job_distributor_spec.rb +41 -19
- data/spec/sidekiq/undertaker/job_filter_spec.rb +26 -9
- data/spec/sidekiq/undertaker/web_extension_spec.rb +172 -14
- data/spec/spec_helper.rb +4 -0
- data/web/locales/en.yml +2 -0
- data/web/views/filter.erb +17 -1
- data/web/views/filter_job_class.erb +8 -8
- data/web/views/filter_job_class_error_class.erb +36 -0
- data/web/views/morgue.erb +9 -2
- metadata +38 -30
- data/.travis.yml +0 -51
- data/Demo_Filter.png +0 -0
- data/Demo_Job_Filter.png +0 -0
- data/Demo_Morgue_1_Job.png +0 -0
- data/Demo_Morgue_all.png +0 -0
@@ -3,40 +3,45 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
module Sidekiq
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
6
7
|
module Undertaker
|
7
8
|
describe JobDistributor do
|
8
9
|
let(:job1) do
|
9
10
|
instance_double(Sidekiq::JobRecord, item: {
|
10
|
-
"class"
|
11
|
-
"failed_at"
|
12
|
-
"error_class"
|
11
|
+
"class" => "A",
|
12
|
+
"failed_at" => 1,
|
13
|
+
"error_class" => "E1",
|
14
|
+
"error_message" => "M1"
|
13
15
|
})
|
14
16
|
end
|
15
17
|
let(:job2) do
|
16
18
|
instance_double(Sidekiq::JobRecord, item: {
|
17
|
-
"class"
|
18
|
-
"failed_at"
|
19
|
-
"error_class"
|
19
|
+
"class" => "A",
|
20
|
+
"failed_at" => 1,
|
21
|
+
"error_class" => "E1",
|
22
|
+
"error_message" => "M1"
|
20
23
|
})
|
21
24
|
end
|
22
25
|
let(:job3) do
|
23
26
|
instance_double(Sidekiq::JobRecord, item: {
|
24
|
-
"class"
|
25
|
-
"failed_at"
|
26
|
-
"error_class"
|
27
|
+
"class" => "B",
|
28
|
+
"failed_at" => 1,
|
29
|
+
"error_class" => "E1",
|
30
|
+
"error_message" => "M2"
|
27
31
|
})
|
28
32
|
end
|
29
33
|
let(:job4) do
|
30
34
|
instance_double(Sidekiq::JobRecord, item: {
|
31
|
-
"class"
|
32
|
-
"failed_at"
|
33
|
-
"error_class"
|
35
|
+
"class" => "B",
|
36
|
+
"failed_at" => 1,
|
37
|
+
"error_class" => "E2",
|
38
|
+
"error_message" => "M1"
|
34
39
|
})
|
35
40
|
end
|
36
41
|
|
37
42
|
let(:dead_job1) do
|
38
43
|
DeadJob.new(
|
39
|
-
job: job1, # 'A', 'E1'
|
44
|
+
job: job1, # 'A', 'E1', 'M1'
|
40
45
|
time_elapsed_since_failure: 10,
|
41
46
|
bucket_name: "1_hour"
|
42
47
|
)
|
@@ -44,22 +49,22 @@ module Sidekiq
|
|
44
49
|
|
45
50
|
let(:dead_job2) do
|
46
51
|
DeadJob.new(
|
47
|
-
job: job2, # 'A', 'E1'
|
48
|
-
time_elapsed_since_failure: 10 + 60 * 60,
|
52
|
+
job: job2, # 'A', 'E1', 'M1'
|
53
|
+
time_elapsed_since_failure: 10 + (60 * 60),
|
49
54
|
bucket_name: "3_hours"
|
50
55
|
)
|
51
56
|
end
|
52
57
|
let(:dead_job3) do
|
53
58
|
DeadJob.new(
|
54
|
-
job: job3, # 'B', 'E1'
|
55
|
-
time_elapsed_since_failure: 10 + 60 * 60,
|
59
|
+
job: job3, # 'B', 'E1', 'M2'
|
60
|
+
time_elapsed_since_failure: 10 + (60 * 60),
|
56
61
|
bucket_name: "3_hours"
|
57
62
|
)
|
58
63
|
end
|
59
64
|
let(:dead_job4) do
|
60
65
|
DeadJob.new(
|
61
|
-
job: job4, # 'B', 'E2'
|
62
|
-
time_elapsed_since_failure: 10 + 60 * 60 * 24,
|
66
|
+
job: job4, # 'B', 'E2', 'M1'
|
67
|
+
time_elapsed_since_failure: 10 + (60 * 60 * 24),
|
63
68
|
bucket_name: "1_day"
|
64
69
|
)
|
65
70
|
end
|
@@ -98,7 +103,24 @@ module Sidekiq
|
|
98
103
|
expect(distribution).to eq expected_distribution
|
99
104
|
end
|
100
105
|
end
|
106
|
+
|
107
|
+
describe "#group_by_error_msg" do
|
108
|
+
subject(:distribution) { described_class.new(dead_jobs).group_by_error_msg }
|
109
|
+
|
110
|
+
let(:expected_distribution) do
|
111
|
+
[
|
112
|
+
["all", { "1_hour" => 1, "3_hours" => 2, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 4 }],
|
113
|
+
["M1", { "1_hour" => 1, "3_hours" => 1, "1_day" => 1, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 3 }],
|
114
|
+
["M2", { "1_hour" => 0, "3_hours" => 1, "1_day" => 0, "3_days" => 0, "1_week" => 0, "older" => 0, "total_dead" => 1 }]
|
115
|
+
]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "distributes the dead jobs into buckets and groups them by error_msg" do
|
119
|
+
expect(distribution).to eq expected_distribution
|
120
|
+
end
|
121
|
+
end
|
101
122
|
# rubocop:enable Layout/LineLength
|
102
123
|
end
|
103
124
|
end
|
125
|
+
# rubocop:enable Metrics/ModuleLength
|
104
126
|
end
|
@@ -8,25 +8,28 @@ module Sidekiq
|
|
8
8
|
describe ".filter_dead_jobs" do
|
9
9
|
let(:job1) do
|
10
10
|
instance_double(Sidekiq::JobRecord, item: {
|
11
|
-
"class"
|
12
|
-
"failed_at"
|
13
|
-
"error_class"
|
11
|
+
"class" => "HardWorkTask",
|
12
|
+
"failed_at" => Time.now.to_i - (5 * 60),
|
13
|
+
"error_class" => "NoMethodError",
|
14
|
+
"error_message" => "undefined method `pause` for H..."
|
14
15
|
})
|
15
16
|
end
|
16
17
|
|
17
18
|
let(:job2) do
|
18
19
|
instance_double(Sidekiq::JobRecord, item: {
|
19
|
-
"class"
|
20
|
-
"failed_at"
|
21
|
-
"error_class"
|
20
|
+
"class" => "HardWorkTask",
|
21
|
+
"failed_at" => Time.now.to_i - (2 * 60 * 60),
|
22
|
+
"error_class" => "RandomError",
|
23
|
+
"error_message" => "random error message"
|
22
24
|
})
|
23
25
|
end
|
24
26
|
|
25
27
|
let(:job3) do
|
26
28
|
instance_double(Sidekiq::JobRecord, item: {
|
27
|
-
"class"
|
28
|
-
"failed_at"
|
29
|
-
"error_class"
|
29
|
+
"class" => "LazyWorkTask",
|
30
|
+
"failed_at" => Time.now.to_i - (2 * 60 * 60),
|
31
|
+
"error_class" => "NoMethodError",
|
32
|
+
"error_message" => "undefined method `work_hard` for LazyWork:Class"
|
30
33
|
})
|
31
34
|
end
|
32
35
|
|
@@ -85,6 +88,20 @@ module Sidekiq
|
|
85
88
|
it { expect(dead_jobs.size).to eq 2 }
|
86
89
|
end
|
87
90
|
|
91
|
+
context "when the error_msg filter is given" do
|
92
|
+
subject(:dead_jobs) do
|
93
|
+
described_class.filter_dead_jobs("error_msg" => "undefined method `pause` for H...")
|
94
|
+
end
|
95
|
+
|
96
|
+
it "filters jobs based on error_message" do
|
97
|
+
dead_jobs.each do |dead_job|
|
98
|
+
expect(dead_job.error_msg).to eq "undefined method `pause` for H..."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it { expect(dead_jobs.size).to eq 1 }
|
103
|
+
end
|
104
|
+
|
88
105
|
context "when no filters are applied" do
|
89
106
|
subject(:dead_jobs) { described_class.filter_dead_jobs }
|
90
107
|
|
@@ -6,6 +6,7 @@ require "sidekiq/api"
|
|
6
6
|
require "sidekiq/web"
|
7
7
|
require "sinatra"
|
8
8
|
require "rack/test"
|
9
|
+
require "stringio"
|
9
10
|
|
10
11
|
module Sidekiq
|
11
12
|
# rubocop:disable Metrics/ModuleLength
|
@@ -32,13 +33,15 @@ module Sidekiq
|
|
32
33
|
"class" => "HardWorker",
|
33
34
|
"args" => ["asdf", 1234],
|
34
35
|
"queue" => "foo",
|
35
|
-
"error_message" => "
|
36
|
+
"error_message" => "Option 'data/file_name' is required! This is an extra long error message.",
|
36
37
|
"error_class" => "RuntimeError",
|
37
38
|
"retry_count" => 0,
|
38
39
|
"failed_at" => Time.now.utc
|
39
40
|
}
|
40
41
|
end
|
41
42
|
|
43
|
+
let(:encoded_error_msg) { "T3B0aW9uICdkYXRhL2ZpbGVfbmFtZScgaXMgcmVxLi4u" }
|
44
|
+
|
42
45
|
# rubocop:disable RSpec/AnyInstance
|
43
46
|
before do
|
44
47
|
Timecop.freeze(Time.gm(2018, 12, 16, 20, 57))
|
@@ -100,6 +103,20 @@ module Sidekiq
|
|
100
103
|
|
101
104
|
it_behaves_like "a page"
|
102
105
|
end
|
106
|
+
|
107
|
+
# /undertaker/filter/:job_class/:error_class/:bucket_name
|
108
|
+
context "when job-class/error-class/bucket page is called" do
|
109
|
+
subject { get "/undertaker/filter/HardWorker/RuntimeError/1_hour" }
|
110
|
+
|
111
|
+
it_behaves_like "a page"
|
112
|
+
end
|
113
|
+
|
114
|
+
# /undertaker/filter/:job_class/:error_class/:bucket_name?poll=true
|
115
|
+
context "when job-class/error-class/bucket page is polled" do
|
116
|
+
subject { get "/undertaker/filter/HardWorker/RuntimeError/1_hour?poll=true" }
|
117
|
+
|
118
|
+
it_behaves_like "a page"
|
119
|
+
end
|
103
120
|
end
|
104
121
|
|
105
122
|
describe "show morgue" do
|
@@ -107,10 +124,28 @@ module Sidekiq
|
|
107
124
|
allow_any_instance_of(Sidekiq::Web::CsrfProtection).to receive(:mask_token).and_return("stubbed-csrf-token")
|
108
125
|
end
|
109
126
|
|
110
|
-
# /undertaker/morgue/:job_class/:error_class/:bucket_name
|
127
|
+
# /undertaker/morgue/:job_class/:error_class/:error_msg/:bucket_name
|
111
128
|
context "when job-class/error/bucket is called" do
|
112
129
|
context "with specific job-class and a specific error" do
|
113
|
-
subject { get "/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
130
|
+
subject { get "/undertaker/morgue/HardWorker/RuntimeError/all/1_hour" }
|
131
|
+
|
132
|
+
it_behaves_like "a page"
|
133
|
+
|
134
|
+
context "with pagination" do
|
135
|
+
before do
|
136
|
+
50.times do |i|
|
137
|
+
job_refs.push add_dead("jid" => i.to_s)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it_behaves_like "a page"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "with job-class, error and specific error message" do
|
146
|
+
subject do
|
147
|
+
get "/undertaker/morgue/HardWorker/RuntimeError/#{encoded_error_msg}/1_hour"
|
148
|
+
end
|
114
149
|
|
115
150
|
it_behaves_like "a page"
|
116
151
|
|
@@ -126,7 +161,7 @@ module Sidekiq
|
|
126
161
|
end
|
127
162
|
|
128
163
|
context "with all failures and errors" do
|
129
|
-
subject { get "/undertaker/morgue/all/all/total_dead" }
|
164
|
+
subject { get "/undertaker/morgue/all/all/all/total_dead" }
|
130
165
|
|
131
166
|
it_behaves_like "a page"
|
132
167
|
end
|
@@ -135,12 +170,17 @@ module Sidekiq
|
|
135
170
|
# rubocop:enable RSpec/AnyInstance
|
136
171
|
|
137
172
|
describe "delete" do
|
138
|
-
context "when job-class, error and bucket are given" do
|
139
|
-
subject
|
173
|
+
context "when job-class, error, error message and bucket are given" do
|
174
|
+
subject do
|
175
|
+
post "/undertaker/morgue/HardWorker/RuntimeError/#{encoded_error_msg}/1_hour/delete"
|
176
|
+
end
|
140
177
|
|
141
|
-
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
178
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/#{encoded_error_msg}/1_hour" }
|
142
179
|
|
143
|
-
let(:params)
|
180
|
+
let(:params) do
|
181
|
+
{ "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour",
|
182
|
+
"error_msg" => "Option 'data/file_name' is req..." }
|
183
|
+
end
|
144
184
|
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
145
185
|
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
146
186
|
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
@@ -160,7 +200,7 @@ module Sidekiq
|
|
160
200
|
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(2)
|
161
201
|
end
|
162
202
|
|
163
|
-
it "redirects to
|
203
|
+
it "redirects to morgue view after the delete" do
|
164
204
|
subject
|
165
205
|
expect(last_response.status).to eq 302
|
166
206
|
|
@@ -205,13 +245,77 @@ module Sidekiq
|
|
205
245
|
end
|
206
246
|
end
|
207
247
|
|
248
|
+
describe "import" do
|
249
|
+
subject { post "/undertaker/import_jobs", "upload_file" => file }
|
250
|
+
|
251
|
+
let(:file) do
|
252
|
+
Rack::Test::UploadedFile.new(StringIO.new(file_content), file_content_type, original_filename: file_name)
|
253
|
+
end
|
254
|
+
let(:job) do
|
255
|
+
opts = default_job_opts.merge({ "class" => "SuperHardWorking" })
|
256
|
+
|
257
|
+
build_job(opts)
|
258
|
+
end
|
259
|
+
|
260
|
+
context "when the file is valid" do
|
261
|
+
let(:file_content) { [job.item].to_json }
|
262
|
+
let(:file_name) { "jobs.json" }
|
263
|
+
let(:file_content_type) { "application/json" }
|
264
|
+
|
265
|
+
it "redirects the response" do
|
266
|
+
subject
|
267
|
+
expect(last_response.status).to eq 302
|
268
|
+
end
|
269
|
+
|
270
|
+
it "adds the jobs to the deadset" do
|
271
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(5)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context "when the file type is not valid" do
|
276
|
+
let(:file_content) { "" }
|
277
|
+
let(:file_name) { "jobs.zip" }
|
278
|
+
let(:file_content_type) { "application/zip" }
|
279
|
+
|
280
|
+
it "returns status 400" do
|
281
|
+
subject
|
282
|
+
expect(last_response.status).to eq 400
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "when the file type is a json but not a Sidekiq Job" do
|
287
|
+
let(:file_content) { "{am_i_a_job: \"no\"}" }
|
288
|
+
let(:file_name) { "jobs.json" }
|
289
|
+
let(:file_content_type) { "application/json" }
|
290
|
+
|
291
|
+
it "returns status 400" do
|
292
|
+
subject
|
293
|
+
expect(last_response.status).to eq 400
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context "when the content of the file is not a json" do
|
298
|
+
let(:file_content) { "DEFINETLY NOT A JSON" }
|
299
|
+
let(:file_name) { "jobs.json" }
|
300
|
+
let(:file_content_type) { "application/json" }
|
301
|
+
|
302
|
+
it "returns status 400" do
|
303
|
+
subject
|
304
|
+
expect(last_response.status).to eq 400
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
208
309
|
describe "retry" do
|
209
310
|
context "when job class, error and bucket are given" do
|
210
|
-
subject { post "/undertaker/morgue/HardWorker/RuntimeError/1_hour/retry" }
|
311
|
+
subject { post "/undertaker/morgue/HardWorker/RuntimeError/all/1_hour/retry" }
|
211
312
|
|
212
|
-
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
313
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/all/1_hour" }
|
213
314
|
|
214
|
-
let(:params)
|
315
|
+
let(:params) do
|
316
|
+
{ "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour",
|
317
|
+
"error_msg" => "all" }
|
318
|
+
end
|
215
319
|
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
216
320
|
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
217
321
|
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
@@ -262,6 +366,51 @@ module Sidekiq
|
|
262
366
|
end
|
263
367
|
end
|
264
368
|
|
369
|
+
describe "export" do
|
370
|
+
context "when job class, error and bucket are given" do
|
371
|
+
subject { post "/undertaker/morgue/HardWorker/RuntimeError/all/1_hour/export" }
|
372
|
+
|
373
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/all/1_hour" }
|
374
|
+
let(:expected_content_disposition_header) { "attachment; filename=\"2018-12-16_20-57.json\"" }
|
375
|
+
|
376
|
+
let(:params) do
|
377
|
+
{ "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour",
|
378
|
+
"error_msg" => "all" }
|
379
|
+
end
|
380
|
+
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
381
|
+
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
382
|
+
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
383
|
+
|
384
|
+
before do
|
385
|
+
allow(Sidekiq::Undertaker::JobFilter).to receive(:filter_dead_jobs).with(params)
|
386
|
+
.and_return(dead_jobs_set)
|
387
|
+
end
|
388
|
+
|
389
|
+
it "exports the dead jobs" do
|
390
|
+
subject
|
391
|
+
expect(last_response.status).to eq 200
|
392
|
+
expect(last_response.content_type).to eq "application/json"
|
393
|
+
expect(last_response.headers["Content-Disposition"]).to eq expected_content_disposition_header
|
394
|
+
expect(last_response.body).to eq dead_jobs_set.map { |t| t.job.item }.to_json
|
395
|
+
end
|
396
|
+
|
397
|
+
context "when there are more jobs than the current CHUNK_SIZE" do
|
398
|
+
before do
|
399
|
+
stub_const("Sidekiq::Undertaker::WebExtension::APIHelpers::EXPORT_CHUNK_SIZE", 1)
|
400
|
+
end
|
401
|
+
|
402
|
+
let(:expected_content_disposition_header) { "attachment; filename=\"HardWorker_2018-12-16_20-57.zip\"" }
|
403
|
+
|
404
|
+
it "exports the dead jobs" do
|
405
|
+
subject
|
406
|
+
expect(last_response.status).to eq 200
|
407
|
+
expect(last_response.content_type).to eq "application/zip"
|
408
|
+
expect(last_response.headers["Content-Disposition"]).to eq expected_content_disposition_header
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
265
414
|
describe "specific jobs" do
|
266
415
|
let(:dead_job) { Sidekiq::DeadSet.new.find_job(jid1) }
|
267
416
|
|
@@ -282,12 +431,21 @@ module Sidekiq
|
|
282
431
|
post "/undertaker/morgue", "key[]=#{job_refs[0]}&retry=Retry+Now"
|
283
432
|
end
|
284
433
|
|
434
|
+
it "exports specific dead job now" do
|
435
|
+
post "/undertaker/morgue", "key[]=#{job_refs[0]}&export=now"
|
436
|
+
|
437
|
+
expect(last_response.status).to eq 200
|
438
|
+
expect(last_response.content_type).to eq "application/json"
|
439
|
+
expect(last_response.headers["Content-Disposition"]).to eq "attachment; filename=\"2018-12-16_20-57.json\""
|
440
|
+
expect(last_response.body).to eq [dead_job.item].to_json
|
441
|
+
end
|
442
|
+
|
285
443
|
it "redirects on specific retry post" do
|
286
444
|
post("/undertaker/morgue",
|
287
445
|
"key[]=#{job_refs[0]}&retry=Retry+Now",
|
288
|
-
"HTTP_REFERER" => "/undertaker/morgue/all/all/total_dead")
|
446
|
+
"HTTP_REFERER" => "/undertaker/morgue/all/all/all/total_dead")
|
289
447
|
expect(last_response.status).to eq 302
|
290
|
-
expect(last_response.header["Location"]).to include("/undertaker/morgue/all/all/total_dead")
|
448
|
+
expect(last_response.header["Location"]).to include("/undertaker/morgue/all/all/all/total_dead")
|
291
449
|
end
|
292
450
|
end
|
293
451
|
end
|
data/spec/spec_helper.rb
CHANGED
data/web/locales/en.yml
CHANGED
data/web/views/filter.erb
CHANGED
@@ -21,7 +21,7 @@
|
|
21
21
|
</thead>
|
22
22
|
<% @distribution.each do |group, bucket_counts| %>
|
23
23
|
<tr>
|
24
|
-
<td><a href='<%= "#{root_path}undertaker/morgue/#{group}/all/total_dead" %>'> <%= group %></a></td>
|
24
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{group}/all/all/total_dead" %>'> <%= group %></a></td>
|
25
25
|
<td><a href='<%= "#{root_path}undertaker/filter/#{group}/total_dead" %>'><%= bucket_counts['total_dead']%></a></td>
|
26
26
|
<td><a href='<%= "#{root_path}undertaker/filter/#{group}/1_hour" %>'><%= bucket_counts['1_hour']%></a></td>
|
27
27
|
<td><a href='<%= "#{root_path}undertaker/filter/#{group}/3_hours" %>'><%= bucket_counts['3_hours']%></a></td>
|
@@ -32,3 +32,19 @@
|
|
32
32
|
</tr>
|
33
33
|
<% end %>
|
34
34
|
</table>
|
35
|
+
|
36
|
+
<header class="row header">
|
37
|
+
<div class="col-sm-12">
|
38
|
+
<h3>
|
39
|
+
Import Jobs
|
40
|
+
</h3>
|
41
|
+
</div>
|
42
|
+
</header>
|
43
|
+
|
44
|
+
<form enctype="multipart/form-data" method="post" action='<%="#{root_path}undertaker/import_jobs"%>'>
|
45
|
+
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
46
|
+
<input type="file" id=upload_file" name="upload_file" >
|
47
|
+
<button class="btn btn-danger" style="margin-top: 10px" type="submit">
|
48
|
+
Import
|
49
|
+
</button>
|
50
|
+
</form>
|
@@ -22,14 +22,14 @@
|
|
22
22
|
</thead>
|
23
23
|
<% @distribution.each do |group, bucket_counts| %>
|
24
24
|
<tr>
|
25
|
-
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{group}/total_dead" %>'><%= group %></a></td>
|
26
|
-
<td><a href='<%= "#{root_path}undertaker/
|
27
|
-
<td><a href='<%= "#{root_path}undertaker/
|
28
|
-
<td><a href='<%= "#{root_path}undertaker/
|
29
|
-
<td><a href='<%= "#{root_path}undertaker/
|
30
|
-
<td><a href='<%= "#{root_path}undertaker/
|
31
|
-
<td><a href='<%= "#{root_path}undertaker/
|
32
|
-
<td><a href='<%= "#{root_path}undertaker/
|
25
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{group}/all/total_dead" %>'><%= group %></a></td>
|
26
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/total_dead" %>'><%= bucket_counts['total_dead'] %></a></td>
|
27
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/1_hour" %>'><%= bucket_counts['1_hour']%></a></td>
|
28
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/3_hours" %>'><%= bucket_counts['3_hours']%></a></td>
|
29
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/1_day" %>'><%= bucket_counts['1_day']%></a></td>
|
30
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/3_days" %>'><%= bucket_counts['3_days']%></a></td>
|
31
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/1_week" %>'><%= bucket_counts['1_week']%></a></td>
|
32
|
+
<td><a href='<%= "#{root_path}undertaker/filter/#{@req_job_class}/#{group}/older" %>'><%= bucket_counts['older']%></a></td>
|
33
33
|
</tr>
|
34
34
|
<% end %>
|
35
35
|
</table>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<header class="row header">
|
2
|
+
<div class="col-sm-12">
|
3
|
+
<h3>
|
4
|
+
<%= "<b>#{@total_dead}</b> dead #{@total_dead == 1 ? 'job' : 'jobs'}" %>
|
5
|
+
<%= " of <b>#{@req_job_class}</b> class" unless @req_job_class == "all" %>
|
6
|
+
<%= " with <b>#{@req_error_class}</b> exception" unless @req_error_class == "all" %>
|
7
|
+
</h3>
|
8
|
+
</div>
|
9
|
+
</header>
|
10
|
+
|
11
|
+
<table class="table table-striped table-bordered table-white">
|
12
|
+
<thead>
|
13
|
+
<tr>
|
14
|
+
<th style="width: 20%"><%= t('Error Message') %></th>
|
15
|
+
<th style="width: 10%"><%= t('All') %></th>
|
16
|
+
<th style="width: 10%"><%= t('1 hour') %></th>
|
17
|
+
<th style="width: 10%"><%= t('3 hours') %></th>
|
18
|
+
<th style="width: 10%"><%= t('1 day') %></th>
|
19
|
+
<th style="width: 10%"><%= t('3 days') %></th>
|
20
|
+
<th style="width: 10%"><%= t('1 week') %></th>
|
21
|
+
<th style="width: 10%"><%= t('Older') %></th>
|
22
|
+
</tr>
|
23
|
+
</thead>
|
24
|
+
<% @distribution.each do |group, bucket_counts| %>
|
25
|
+
<tr>
|
26
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/total_dead" %>'><%= group %></a></td>
|
27
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/total_dead" %>'><%= bucket_counts['total_dead'] %></a></td>
|
28
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/1_hour" %>'><%= bucket_counts['1_hour']%></a></td>
|
29
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/3_hours" %>'><%= bucket_counts['3_hours']%></a></td>
|
30
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/1_day" %>'><%= bucket_counts['1_day']%></a></td>
|
31
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/3_days" %>'><%= bucket_counts['3_days']%></a></td>
|
32
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/1_week" %>'><%= bucket_counts['1_week']%></a></td>
|
33
|
+
<td><a href='<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(group)}/older" %>'><%= bucket_counts['older']%></a></td>
|
34
|
+
</tr>
|
35
|
+
<% end %>
|
36
|
+
</table>
|
data/web/views/morgue.erb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
<%= "<b>#{@total_dead}</b> dead #{@total_dead == 1 ? 'job' : 'jobs'}" %>
|
5
5
|
<%= " of <b>#{@req_job_class}</b> class" unless @req_job_class == "all" %>
|
6
6
|
<%= " with <b>#{@req_error_class}</b> exception" unless @req_error_class == "all" %>
|
7
|
+
<%= " with <b>#{@req_error_msg}</b> message" unless @req_error_msg == "all" %>
|
7
8
|
<%= " in <b>#{@req_bucket_name}</b> bucket" %>
|
8
9
|
</h3>
|
9
10
|
</div>
|
@@ -56,15 +57,21 @@
|
|
56
57
|
<% end %>
|
57
58
|
</table>
|
58
59
|
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('UndertakerRevive') %>" />
|
60
|
+
<input class="btn btn-secondary btn-xs pull-left" type="submit" name="export" value="<%= t('UndertakerExport') %>" />
|
59
61
|
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('UndertakerBury') %>" />
|
60
62
|
</form>
|
61
63
|
|
62
|
-
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{@req_bucket_name}/delete" %>" method="post">
|
64
|
+
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(@req_error_msg)}/#{@req_bucket_name}/delete" %>" method="post">
|
63
65
|
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
64
66
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('UndertakerBuryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
65
67
|
</form>
|
66
68
|
|
67
|
-
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{@req_bucket_name}/
|
69
|
+
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(@req_error_msg)}/#{@req_bucket_name}/export" %>" method="post">
|
70
|
+
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
71
|
+
<input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="<%= t('UndertakerExportAll') %>" />
|
72
|
+
</form>
|
73
|
+
|
74
|
+
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{Base64.urlsafe_encode64(@req_error_msg)}/#{@req_bucket_name}/retry" %>" method="post">
|
68
75
|
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
69
76
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('UndertakerReviveAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
70
77
|
</form>
|