sidekiq-undertaker 1.2.0 → 1.3.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 +1 -1
- data/CHANGELOG.md +4 -0
- data/README.md +6 -5
- 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/version.rb +1 -1
- data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +69 -12
- data/lib/sidekiq/undertaker/web_extension.rb +8 -1
- data/sidekiq-undertaker.gemspec +1 -0
- 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 +16 -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 +6 -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 +6 -0
- 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 +6 -0
- data/spec/sidekiq/undertaker/job_distributor_spec.rb +3 -3
- data/spec/sidekiq/undertaker/job_filter_spec.rb +3 -3
- data/spec/sidekiq/undertaker/web_extension_spec.rb +115 -0
- data/web/locales/en.yml +2 -0
- data/web/views/filter.erb +16 -0
- data/web/views/morgue.erb +6 -0
- metadata +21 -6
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb255bb741814575247793b7ddfdd7dfa6fe5c0d86a21df3b33f315209beef53
|
|
4
|
+
data.tar.gz: fc6b4970d6e7d2c8410525c76fd97fc1c98943a0f9bb381807946829e38180ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0162683d05be1edf9d3dc362a7b7c543fc4d0d6891945540637cc03dcb919a1780003ed55503f3cbc3af8ab0ef3a03ee28bfe6d6575dac4610ee623dd373b3b1'
|
|
7
|
+
data.tar.gz: 610f3d98e380193606e37c74953dd53b00c79ae42fd6f091f940f27e9b61d55305df546a207676b8139a8d41719ba0fd3336cf87b5f35ec0b16b43283796c64a
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
- 'truffleruby+graalvm'
|
|
40
40
|
|
|
41
41
|
steps:
|
|
42
|
-
- uses: actions/checkout@
|
|
42
|
+
- uses: actions/checkout@v3
|
|
43
43
|
- name: Set up Ruby
|
|
44
44
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
45
45
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -45,24 +45,24 @@ Or install it yourself as:
|
|
|
45
45
|
|
|
46
46
|
The filter page shows a table with time-buckets as columns and rows for each job class.
|
|
47
47
|
|
|
48
|
-

|
|
49
49
|
|
|
50
50
|
#### Job Filter View
|
|
51
51
|
|
|
52
52
|
For each job class, you can drill down to view error distribution based on
|
|
53
53
|
error class.
|
|
54
54
|
|
|
55
|
-

|
|
56
56
|
|
|
57
57
|
#### Morgue View
|
|
58
58
|
Finally, click on the individual error counts to display details of the
|
|
59
59
|
errors in a list form.
|
|
60
60
|
|
|
61
|
-

|
|
62
62
|
|
|
63
63
|
The morgue view can, for example, also show an error distribution over all job classes.
|
|
64
64
|
|
|
65
|
-

|
|
66
66
|
|
|
67
67
|
## Contributing
|
|
68
68
|
|
|
@@ -81,7 +81,8 @@ this fork was renamed to `sidekiq-undertaker`.
|
|
|
81
81
|
|
|
82
82
|
The [Sidekiq-Cleaner](https://github.com/HackingHabits/sidekiq-cleaner) gem was originally created by [Madan Thangavelu](https://github.com/HackingHabits).
|
|
83
83
|
[Tout](https://github.com/Tout/sidekiq-cleaner) and [TheWudu](https://github.com/TheWudu/sidekiq-cleaner) also contributed to it.
|
|
84
|
-
|
|
84
|
+
[Zlatko Rednjak](https://github.com/Rednjak) added the initial version of the `CHANGELOG.md`.
|
|
85
|
+
[Lucas Dell'Isola](https://github.com/ldellisola) added the export/import feature for dead jobs.
|
|
85
86
|
|
|
86
87
|
For the complete list of network members have a look at the [fork overview](https://github.com/ThomasKoppensteiner/sidekiq-undertaker/network/members).
|
|
87
88
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require "sidekiq/undertaker/job_distributor"
|
|
4
4
|
require "sidekiq/undertaker/job_filter"
|
|
5
|
+
require "json"
|
|
6
|
+
require "zip"
|
|
5
7
|
|
|
6
8
|
module Sidekiq
|
|
7
9
|
module Undertaker
|
|
8
10
|
module WebExtension
|
|
11
|
+
# rubocop:disable Metrics/ModuleLength
|
|
9
12
|
module APIHelpers
|
|
13
|
+
EXPORT_CHUNK_SIZE = 2000
|
|
14
|
+
|
|
10
15
|
def show_filter
|
|
11
16
|
store_request_params
|
|
12
17
|
|
|
@@ -59,22 +64,25 @@ module Sidekiq
|
|
|
59
64
|
def post_undertaker
|
|
60
65
|
raise ::ArgumentError.new("Key missing") unless params["key"]
|
|
61
66
|
|
|
62
|
-
params["key"].
|
|
63
|
-
job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
|
|
64
|
-
if job
|
|
65
|
-
if params["retry"]
|
|
66
|
-
job.retry
|
|
67
|
-
elsif params["delete"]
|
|
68
|
-
job.delete
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
67
|
+
jobs = params["key"].map { |k| Sidekiq::DeadSet.new.fetch(*parse_params(k)).first }.compact
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
handle_selected_jobs jobs
|
|
74
70
|
rescue ::ArgumentError
|
|
75
71
|
bad_request
|
|
76
72
|
end
|
|
77
73
|
|
|
74
|
+
def handle_selected_jobs(jobs)
|
|
75
|
+
return send_data(*prepare_data(jobs.map(&:item), EXPORT_CHUNK_SIZE)) if params["export"]
|
|
76
|
+
|
|
77
|
+
if params["retry"]
|
|
78
|
+
jobs.each(&:retry)
|
|
79
|
+
elsif params["delete"]
|
|
80
|
+
jobs.each(&:delete)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
redirect redirect_path(request)
|
|
84
|
+
end
|
|
85
|
+
|
|
78
86
|
def post_undertaker_job_class_error_class_buckent_name_delete
|
|
79
87
|
store_request_params
|
|
80
88
|
@dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
|
|
@@ -96,6 +104,28 @@ module Sidekiq
|
|
|
96
104
|
redirect redirect_path(request)
|
|
97
105
|
end
|
|
98
106
|
|
|
107
|
+
def post_undertaker_job_class_error_class_buckent_name_export
|
|
108
|
+
store_request_params
|
|
109
|
+
|
|
110
|
+
@dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
|
|
111
|
+
send_data(*prepare_data(@dead_jobs.map { |j| j.job.item }, EXPORT_CHUNK_SIZE))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def post_import_jobs
|
|
115
|
+
file = params["upload_file"]
|
|
116
|
+
raise ::ArgumentError.new("The file is not a json") if file.nil? || file[:type] != "application/json"
|
|
117
|
+
|
|
118
|
+
data = params["upload_file"][:tempfile].read
|
|
119
|
+
dead_set = Sidekiq::DeadSet.new
|
|
120
|
+
|
|
121
|
+
JSON.parse(data).each do |job|
|
|
122
|
+
dead_set.kill(Sidekiq.dump_json(job))
|
|
123
|
+
end
|
|
124
|
+
redirect redirect_path(request)
|
|
125
|
+
rescue StandardError
|
|
126
|
+
bad_request
|
|
127
|
+
end
|
|
128
|
+
|
|
99
129
|
def render_result(template)
|
|
100
130
|
render(:erb, File.read(File.join(view_path, template)))
|
|
101
131
|
end
|
|
@@ -112,13 +142,40 @@ module Sidekiq
|
|
|
112
142
|
|
|
113
143
|
def redirect_path(request)
|
|
114
144
|
path = request.referer ? URI.parse(request.referer).path : request.path
|
|
115
|
-
path.gsub("/delete", "").gsub("/retry", "")
|
|
145
|
+
path.gsub("/delete", "").gsub("/retry", "").gsub("/export", "")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def prepare_data(data, chunk_size)
|
|
149
|
+
filename = Time.now.strftime("%Y-%m-%d_%H-%M").to_s
|
|
150
|
+
return [data.to_json, "application/json", "#{filename}.json"] if data.length <= chunk_size
|
|
151
|
+
|
|
152
|
+
filename = "#{@req_job_class}_#{filename}"
|
|
153
|
+
zip = Zip::OutputStream.write_buffer do |file|
|
|
154
|
+
data.each_slice(chunk_size).each_with_index do |chunk, index|
|
|
155
|
+
file.put_next_entry("#{filename}_part-#{index + 1}.json")
|
|
156
|
+
file.write chunk.to_json
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
[zip.string, "application/zip", "#{filename}.zip"]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def send_data(data, content_type = "text/plain", file_name = "attachment.txt")
|
|
164
|
+
[
|
|
165
|
+
200,
|
|
166
|
+
{
|
|
167
|
+
"Content-Type" => content_type,
|
|
168
|
+
"Content-Disposition" => "attachment; filename=\"#{file_name}\""
|
|
169
|
+
},
|
|
170
|
+
[data]
|
|
171
|
+
]
|
|
116
172
|
end
|
|
117
173
|
|
|
118
174
|
def bad_request
|
|
119
175
|
[400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
|
|
120
176
|
end
|
|
121
177
|
end
|
|
178
|
+
# rubocop:enable Metrics/ModuleLength
|
|
122
179
|
end
|
|
123
180
|
end
|
|
124
181
|
end
|
|
@@ -12,7 +12,6 @@ module Sidekiq
|
|
|
12
12
|
app.get "/undertaker/filter" do
|
|
13
13
|
show_filter
|
|
14
14
|
end
|
|
15
|
-
|
|
16
15
|
app.get "/undertaker/filter/:job_class/:bucket_name" do
|
|
17
16
|
show_filter_by_job_class_bucket_name
|
|
18
17
|
end
|
|
@@ -32,6 +31,14 @@ module Sidekiq
|
|
|
32
31
|
app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/retry" do
|
|
33
32
|
post_undertaker_job_class_error_class_buckent_name_retry
|
|
34
33
|
end
|
|
34
|
+
|
|
35
|
+
app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/export" do
|
|
36
|
+
post_undertaker_job_class_error_class_buckent_name_export
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
app.post "/undertaker/import_jobs" do
|
|
40
|
+
post_import_jobs
|
|
41
|
+
end
|
|
35
42
|
end
|
|
36
43
|
# rubocop:enable Metrics/MethodLength
|
|
37
44
|
end
|
data/sidekiq-undertaker.gemspec
CHANGED
|
@@ -211,6 +211,22 @@
|
|
|
211
211
|
|
|
212
212
|
</table>
|
|
213
213
|
|
|
214
|
+
<header class="row header">
|
|
215
|
+
<div class="col-sm-12">
|
|
216
|
+
<h3>
|
|
217
|
+
Import Jobs
|
|
218
|
+
</h3>
|
|
219
|
+
</div>
|
|
220
|
+
</header>
|
|
221
|
+
|
|
222
|
+
<form enctype="multipart/form-data" method="post" action='/sidekiq/undertaker/import_jobs'>
|
|
223
|
+
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
224
|
+
<input type="file" id=upload_file" name="upload_file" >
|
|
225
|
+
<button class="btn btn-danger" style="margin-top: 10px" type="submit">
|
|
226
|
+
Import
|
|
227
|
+
</button>
|
|
228
|
+
</form>
|
|
229
|
+
|
|
214
230
|
</div>
|
|
215
231
|
</div>
|
|
216
232
|
</div>
|
|
@@ -276,6 +276,7 @@
|
|
|
276
276
|
|
|
277
277
|
</table>
|
|
278
278
|
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="Revive" />
|
|
279
|
+
<input class="btn btn-secondary btn-xs pull-left" type="submit" name="export" value="Export" />
|
|
279
280
|
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="Bury" />
|
|
280
281
|
</form>
|
|
281
282
|
|
|
@@ -284,6 +285,11 @@
|
|
|
284
285
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="Bury All" data-confirm="Are you sure?" />
|
|
285
286
|
</form>
|
|
286
287
|
|
|
288
|
+
<form action="/sidekiq/undertaker/morgue/all/all/total_dead/export" method="post">
|
|
289
|
+
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
290
|
+
<input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="Export All" />
|
|
291
|
+
</form>
|
|
292
|
+
|
|
287
293
|
<form action="/sidekiq/undertaker/morgue/all/all/total_dead/retry" method="post">
|
|
288
294
|
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
289
295
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="Revive All" data-confirm="Are you sure?" />
|
|
@@ -234,6 +234,7 @@
|
|
|
234
234
|
|
|
235
235
|
</table>
|
|
236
236
|
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="Revive" />
|
|
237
|
+
<input class="btn btn-secondary btn-xs pull-left" type="submit" name="export" value="Export" />
|
|
237
238
|
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="Bury" />
|
|
238
239
|
</form>
|
|
239
240
|
|
|
@@ -242,6 +243,11 @@
|
|
|
242
243
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="Bury All" data-confirm="Are you sure?" />
|
|
243
244
|
</form>
|
|
244
245
|
|
|
246
|
+
<form action="/sidekiq/undertaker/morgue/HardWorker/RuntimeError/1_hour/export" method="post">
|
|
247
|
+
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
248
|
+
<input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="Export All" />
|
|
249
|
+
</form>
|
|
250
|
+
|
|
245
251
|
<form action="/sidekiq/undertaker/morgue/HardWorker/RuntimeError/1_hour/retry" method="post">
|
|
246
252
|
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
247
253
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="Revive All" data-confirm="Are you sure?" />
|
|
@@ -1260,6 +1260,7 @@
|
|
|
1260
1260
|
|
|
1261
1261
|
</table>
|
|
1262
1262
|
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="Revive" />
|
|
1263
|
+
<input class="btn btn-secondary btn-xs pull-left" type="submit" name="export" value="Export" />
|
|
1263
1264
|
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="Bury" />
|
|
1264
1265
|
</form>
|
|
1265
1266
|
|
|
@@ -1268,6 +1269,11 @@
|
|
|
1268
1269
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="Bury All" data-confirm="Are you sure?" />
|
|
1269
1270
|
</form>
|
|
1270
1271
|
|
|
1272
|
+
<form action="/sidekiq/undertaker/morgue/HardWorker/RuntimeError/1_hour/export" method="post">
|
|
1273
|
+
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
1274
|
+
<input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="Export All" />
|
|
1275
|
+
</form>
|
|
1276
|
+
|
|
1271
1277
|
<form action="/sidekiq/undertaker/morgue/HardWorker/RuntimeError/1_hour/retry" method="post">
|
|
1272
1278
|
<input type='hidden' name='authenticity_token' value='stubbed-csrf-token'/>
|
|
1273
1279
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="Revive All" data-confirm="Are you sure?" />
|
|
@@ -45,21 +45,21 @@ module Sidekiq
|
|
|
45
45
|
let(:dead_job2) do
|
|
46
46
|
DeadJob.new(
|
|
47
47
|
job: job2, # 'A', 'E1'
|
|
48
|
-
time_elapsed_since_failure: 10 + 60 * 60,
|
|
48
|
+
time_elapsed_since_failure: 10 + (60 * 60),
|
|
49
49
|
bucket_name: "3_hours"
|
|
50
50
|
)
|
|
51
51
|
end
|
|
52
52
|
let(:dead_job3) do
|
|
53
53
|
DeadJob.new(
|
|
54
54
|
job: job3, # 'B', 'E1'
|
|
55
|
-
time_elapsed_since_failure: 10 + 60 * 60,
|
|
55
|
+
time_elapsed_since_failure: 10 + (60 * 60),
|
|
56
56
|
bucket_name: "3_hours"
|
|
57
57
|
)
|
|
58
58
|
end
|
|
59
59
|
let(:dead_job4) do
|
|
60
60
|
DeadJob.new(
|
|
61
61
|
job: job4, # 'B', 'E2'
|
|
62
|
-
time_elapsed_since_failure: 10 + 60 * 60 * 24,
|
|
62
|
+
time_elapsed_since_failure: 10 + (60 * 60 * 24),
|
|
63
63
|
bucket_name: "1_day"
|
|
64
64
|
)
|
|
65
65
|
end
|
|
@@ -9,7 +9,7 @@ module Sidekiq
|
|
|
9
9
|
let(:job1) do
|
|
10
10
|
instance_double(Sidekiq::JobRecord, item: {
|
|
11
11
|
"class" => "HardWorkTask",
|
|
12
|
-
"failed_at" => Time.now.to_i - 5 * 60,
|
|
12
|
+
"failed_at" => Time.now.to_i - (5 * 60),
|
|
13
13
|
"error_class" => "NoMethodError"
|
|
14
14
|
})
|
|
15
15
|
end
|
|
@@ -17,7 +17,7 @@ module Sidekiq
|
|
|
17
17
|
let(:job2) do
|
|
18
18
|
instance_double(Sidekiq::JobRecord, item: {
|
|
19
19
|
"class" => "HardWorkTask",
|
|
20
|
-
"failed_at" => Time.now.to_i - 2 * 60 * 60,
|
|
20
|
+
"failed_at" => Time.now.to_i - (2 * 60 * 60),
|
|
21
21
|
"error_class" => "RandomError"
|
|
22
22
|
})
|
|
23
23
|
end
|
|
@@ -25,7 +25,7 @@ module Sidekiq
|
|
|
25
25
|
let(:job3) do
|
|
26
26
|
instance_double(Sidekiq::JobRecord, item: {
|
|
27
27
|
"class" => "LazyWorkTask",
|
|
28
|
-
"failed_at" => Time.now.to_i - 2 * 60 * 60,
|
|
28
|
+
"failed_at" => Time.now.to_i - (2 * 60 * 60),
|
|
29
29
|
"error_class" => "NoMethodError"
|
|
30
30
|
})
|
|
31
31
|
end
|
|
@@ -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
|
|
@@ -205,6 +206,67 @@ module Sidekiq
|
|
|
205
206
|
end
|
|
206
207
|
end
|
|
207
208
|
|
|
209
|
+
describe "import" do
|
|
210
|
+
subject { post "/undertaker/import_jobs", "upload_file" => file }
|
|
211
|
+
|
|
212
|
+
let(:file) do
|
|
213
|
+
Rack::Test::UploadedFile.new(StringIO.new(file_content), file_content_type, original_filename: file_name)
|
|
214
|
+
end
|
|
215
|
+
let(:job) do
|
|
216
|
+
opts = default_job_opts.merge({ "class" => "SuperHardWorking" })
|
|
217
|
+
|
|
218
|
+
build_job(opts)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context "when the file is valid" do
|
|
222
|
+
let(:file_content) { [job.item].to_json }
|
|
223
|
+
let(:file_name) { "jobs.json" }
|
|
224
|
+
let(:file_content_type) { "application/json" }
|
|
225
|
+
|
|
226
|
+
it "redirects the response" do
|
|
227
|
+
subject
|
|
228
|
+
expect(last_response.status).to eq 302
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "adds the jobs to the deadset" do
|
|
232
|
+
expect { subject }.to change { Sidekiq::DeadSet.new.size }.from(4).to(5)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context "when the file type is not valid" do
|
|
237
|
+
let(:file_content) { "" }
|
|
238
|
+
let(:file_name) { "jobs.zip" }
|
|
239
|
+
let(:file_content_type) { "application/zip" }
|
|
240
|
+
|
|
241
|
+
it "returns status 400" do
|
|
242
|
+
subject
|
|
243
|
+
expect(last_response.status).to eq 400
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
context "when the file type is a json but not a Sidekiq Job" do
|
|
248
|
+
let(:file_content) { "{am_i_a_job: \"no\"}" }
|
|
249
|
+
let(:file_name) { "jobs.json" }
|
|
250
|
+
let(:file_content_type) { "application/json" }
|
|
251
|
+
|
|
252
|
+
it "returns status 400" do
|
|
253
|
+
subject
|
|
254
|
+
expect(last_response.status).to eq 400
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
context "when the content of the file is not a json" do
|
|
259
|
+
let(:file_content) { "DEFINETLY NOT A JSON" }
|
|
260
|
+
let(:file_name) { "jobs.json" }
|
|
261
|
+
let(:file_content_type) { "application/json" }
|
|
262
|
+
|
|
263
|
+
it "returns status 400" do
|
|
264
|
+
subject
|
|
265
|
+
expect(last_response.status).to eq 400
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
208
270
|
describe "retry" do
|
|
209
271
|
context "when job class, error and bucket are given" do
|
|
210
272
|
subject { post "/undertaker/morgue/HardWorker/RuntimeError/1_hour/retry" }
|
|
@@ -262,6 +324,50 @@ module Sidekiq
|
|
|
262
324
|
end
|
|
263
325
|
end
|
|
264
326
|
|
|
327
|
+
describe "export" do
|
|
328
|
+
context "when job class, error and bucket are given" do
|
|
329
|
+
subject { post "/undertaker/morgue/HardWorker/RuntimeError/1_hour/export" }
|
|
330
|
+
|
|
331
|
+
let(:expected_redirect_url) { "http://example.org/undertaker/morgue/HardWorker/RuntimeError/1_hour" }
|
|
332
|
+
let(:expected_content_disposition_header) { "attachment; filename=\"2018-12-16_20-57.json\"" }
|
|
333
|
+
|
|
334
|
+
let(:params) do
|
|
335
|
+
{ "job_class" => "HardWorker", "error_class" => "RuntimeError", "bucket_name" => "1_hour" }
|
|
336
|
+
end
|
|
337
|
+
let(:dead_jobs_set) { [dead_job1, dead_job2] }
|
|
338
|
+
let(:dead_job1) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid1)) }
|
|
339
|
+
let(:dead_job2) { Sidekiq::Undertaker::DeadJob.to_dead_job(Sidekiq::DeadSet.new.find_job(jid2)) }
|
|
340
|
+
|
|
341
|
+
before do
|
|
342
|
+
allow(Sidekiq::Undertaker::JobFilter).to receive(:filter_dead_jobs).with(params)
|
|
343
|
+
.and_return(dead_jobs_set)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "exports the dead jobs" do
|
|
347
|
+
subject
|
|
348
|
+
expect(last_response.status).to eq 200
|
|
349
|
+
expect(last_response.content_type).to eq "application/json"
|
|
350
|
+
expect(last_response.headers["Content-Disposition"]).to eq expected_content_disposition_header
|
|
351
|
+
expect(last_response.body).to eq dead_jobs_set.map { |t| t.job.item }.to_json
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
context "when there are more jobs than the current CHUNK_SIZE" do
|
|
355
|
+
before do
|
|
356
|
+
stub_const("Sidekiq::Undertaker::WebExtension::APIHelpers::EXPORT_CHUNK_SIZE", 1)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
let(:expected_content_disposition_header) { "attachment; filename=\"HardWorker_2018-12-16_20-57.zip\"" }
|
|
360
|
+
|
|
361
|
+
it "exports the dead jobs" do
|
|
362
|
+
subject
|
|
363
|
+
expect(last_response.status).to eq 200
|
|
364
|
+
expect(last_response.content_type).to eq "application/zip"
|
|
365
|
+
expect(last_response.headers["Content-Disposition"]).to eq expected_content_disposition_header
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
265
371
|
describe "specific jobs" do
|
|
266
372
|
let(:dead_job) { Sidekiq::DeadSet.new.find_job(jid1) }
|
|
267
373
|
|
|
@@ -282,6 +388,15 @@ module Sidekiq
|
|
|
282
388
|
post "/undertaker/morgue", "key[]=#{job_refs[0]}&retry=Retry+Now"
|
|
283
389
|
end
|
|
284
390
|
|
|
391
|
+
it "exports specific dead job now" do
|
|
392
|
+
post "/undertaker/morgue", "key[]=#{job_refs[0]}&export=now"
|
|
393
|
+
|
|
394
|
+
expect(last_response.status).to eq 200
|
|
395
|
+
expect(last_response.content_type).to eq "application/json"
|
|
396
|
+
expect(last_response.headers["Content-Disposition"]).to eq "attachment; filename=\"2018-12-16_20-57.json\""
|
|
397
|
+
expect(last_response.body).to eq [dead_job.item].to_json
|
|
398
|
+
end
|
|
399
|
+
|
|
285
400
|
it "redirects on specific retry post" do
|
|
286
401
|
post("/undertaker/morgue",
|
|
287
402
|
"key[]=#{job_refs[0]}&retry=Retry+Now",
|
data/web/locales/en.yml
CHANGED
data/web/views/filter.erb
CHANGED
|
@@ -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>
|
data/web/views/morgue.erb
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
<% end %>
|
|
57
57
|
</table>
|
|
58
58
|
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('UndertakerRevive') %>" />
|
|
59
|
+
<input class="btn btn-secondary btn-xs pull-left" type="submit" name="export" value="<%= t('UndertakerExport') %>" />
|
|
59
60
|
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('UndertakerBury') %>" />
|
|
60
61
|
</form>
|
|
61
62
|
|
|
@@ -64,6 +65,11 @@
|
|
|
64
65
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('UndertakerBuryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
|
65
66
|
</form>
|
|
66
67
|
|
|
68
|
+
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{@req_bucket_name}/export" %>" method="post">
|
|
69
|
+
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
|
70
|
+
<input class="btn btn-secondary btn-xs pull-right" type="submit" name="export" value="<%= t('UndertakerExportAll') %>" />
|
|
71
|
+
</form>
|
|
72
|
+
|
|
67
73
|
<form action="<%= "#{root_path}undertaker/morgue/#{@req_job_class}/#{@req_error_class}/#{@req_bucket_name}/retry" %>" method="post">
|
|
68
74
|
<%= respond_to?(:csrf_tag) && csrf_tag %>
|
|
69
75
|
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('UndertakerReviveAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq-undertaker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas Koppensteiner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-04-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -282,6 +282,20 @@ dependencies:
|
|
|
282
282
|
- - "~>"
|
|
283
283
|
- !ruby/object:Gem::Version
|
|
284
284
|
version: '0.9'
|
|
285
|
+
- !ruby/object:Gem::Dependency
|
|
286
|
+
name: rubyzip
|
|
287
|
+
requirement: !ruby/object:Gem::Requirement
|
|
288
|
+
requirements:
|
|
289
|
+
- - ">="
|
|
290
|
+
- !ruby/object:Gem::Version
|
|
291
|
+
version: '0'
|
|
292
|
+
type: :runtime
|
|
293
|
+
prerelease: false
|
|
294
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
295
|
+
requirements:
|
|
296
|
+
- - ">="
|
|
297
|
+
- !ruby/object:Gem::Version
|
|
298
|
+
version: '0'
|
|
285
299
|
- !ruby/object:Gem::Dependency
|
|
286
300
|
name: sidekiq
|
|
287
301
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -322,14 +336,15 @@ files:
|
|
|
322
336
|
- ".rubocop_todo.yml"
|
|
323
337
|
- ".travis.yml"
|
|
324
338
|
- CHANGELOG.md
|
|
325
|
-
- Demo_Filter.png
|
|
326
|
-
- Demo_Job_Filter.png
|
|
327
|
-
- Demo_Morgue_1_Job.png
|
|
328
|
-
- Demo_Morgue_all.png
|
|
329
339
|
- Gemfile
|
|
330
340
|
- LICENSE.txt
|
|
331
341
|
- README.md
|
|
332
342
|
- Rakefile
|
|
343
|
+
- assets/Undertaker_demo_1_job_1_error.png
|
|
344
|
+
- assets/Undertaker_demo_1_job_all_errors.png
|
|
345
|
+
- assets/Undertaker_demo_all_errors.png
|
|
346
|
+
- assets/Undertaker_demo_all_jobs.png
|
|
347
|
+
- assets/Undertaker_demo_all_jobs_1_error.png
|
|
333
348
|
- lib/sidekiq/undertaker.rb
|
|
334
349
|
- lib/sidekiq/undertaker/bucket.rb
|
|
335
350
|
- lib/sidekiq/undertaker/dead_job.rb
|
data/Demo_Filter.png
DELETED
|
Binary file
|
data/Demo_Job_Filter.png
DELETED
|
Binary file
|
data/Demo_Morgue_1_Job.png
DELETED
|
Binary file
|
data/Demo_Morgue_all.png
DELETED
|
Binary file
|