sidekiq-undertaker 1.0.0.rc01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +6 -0
  3. data/.gitignore +22 -0
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +23 -0
  6. data/.travis.yml +53 -0
  7. data/Demo_Filter.png +0 -0
  8. data/Demo_Job_Filter.png +0 -0
  9. data/Demo_Morgue_1_Job.png +0 -0
  10. data/Demo_Morgue_all.png +0 -0
  11. data/Gemfile +11 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +95 -0
  14. data/Rakefile +34 -0
  15. data/lib/sidekiq/undertaker.rb +16 -0
  16. data/lib/sidekiq/undertaker/bucket.rb +29 -0
  17. data/lib/sidekiq/undertaker/dead_job.rb +64 -0
  18. data/lib/sidekiq/undertaker/job_distributor.rb +64 -0
  19. data/lib/sidekiq/undertaker/job_filter.rb +46 -0
  20. data/lib/sidekiq/undertaker/version.rb +7 -0
  21. data/lib/sidekiq/undertaker/web_extension.rb +37 -0
  22. data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +120 -0
  23. data/sidekiq-undertaker.gemspec +55 -0
  24. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +240 -0
  25. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
  26. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
  27. 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.approved.txt +316 -0
  28. 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.approved.txt +274 -0
  29. data/spec/sidekiq/undertaker/bucket_spec.rb +26 -0
  30. data/spec/sidekiq/undertaker/dead_jobs_spec.rb +90 -0
  31. data/spec/sidekiq/undertaker/job_distributor_spec.rb +104 -0
  32. data/spec/sidekiq/undertaker/job_filter_spec.rb +105 -0
  33. data/spec/sidekiq/undertaker/web_extension_spec.rb +270 -0
  34. data/spec/spec_helper.rb +76 -0
  35. data/web/locales/en.yml +5 -0
  36. data/web/views/filter.erb +34 -0
  37. data/web/views/filter_job_class.erb +35 -0
  38. data/web/views/morgue.erb +75 -0
  39. metadata +378 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/dead_job"
4
+
5
+ module Sidekiq
6
+ module Undertaker
7
+ class JobFilter
8
+ class << self
9
+ def filter_dead_jobs(filters = {})
10
+ # filters can have keys in (job_class, error_class, bucket_name)
11
+ dead_jobs = []
12
+ Sidekiq::Undertaker::DeadJob.for_each do |dead_job|
13
+ filter_passed = true
14
+ filters.each do |filter, value|
15
+ next if skip?(filter, value)
16
+
17
+ filter_passed = false if dead_job.respond_to?(filter) && dead_job.public_send(filter) != value
18
+ end
19
+ dead_jobs << dead_job if filter_passed
20
+ end
21
+
22
+ dead_jobs
23
+ end
24
+
25
+ def skip?(filter, value)
26
+ value.nil? ||
27
+ total_dead_bucket?(filter, value) ||
28
+ all_jobs?(filter, value) ||
29
+ all_errors?(filter, value)
30
+ end
31
+
32
+ def total_dead_bucket?(filter, value)
33
+ filter == "bucket_name" && value == "total_dead"
34
+ end
35
+
36
+ def all_jobs?(filter, value)
37
+ filter == "job_class" && value == "all"
38
+ end
39
+
40
+ def all_errors?(filter, value)
41
+ filter == "error_class" && value == "all"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Undertaker
5
+ VERSION = "1.0.0.rc01"
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/web_extension/api_helpers"
4
+
5
+ module Sidekiq
6
+ module Undertaker
7
+ module WebExtension
8
+ def self.registered(app)
9
+ app.helpers APIHelpers
10
+
11
+ app.get "/undertaker/filter" do
12
+ show_filter
13
+ end
14
+
15
+ app.get "/undertaker/filter/:job_class/:bucket_name" do
16
+ show_filter_by_job_class_bucket_name
17
+ end
18
+
19
+ app.get "/undertaker/morgue/:job_class/:error_class/:bucket_name" do
20
+ show_undertaker_by_job_class_error_class_bucket_name
21
+ end
22
+
23
+ app.post "/undertaker/morgue" do
24
+ post_undertaker
25
+ end
26
+
27
+ app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/delete" do
28
+ post_undertaker_job_class_error_class_buckent_name_delete
29
+ end
30
+
31
+ app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/retry" do
32
+ post_undertaker_job_class_error_class_buckent_name_retry
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/job_distributor"
4
+ require "sidekiq/undertaker/job_filter"
5
+
6
+ module Sidekiq
7
+ module Undertaker
8
+ module WebExtension
9
+ module APIHelpers
10
+ def show_filter
11
+ store_request_params
12
+
13
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
14
+ @distribution = Sidekiq::Undertaker::JobDistributor.new(@dead_jobs).group_by_job_class
15
+ @total_dead = @dead_jobs.size
16
+
17
+ render_result("filter.erb")
18
+ end
19
+
20
+ def show_filter_by_job_class_bucket_name
21
+ store_request_params
22
+
23
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
24
+ @distribution = Sidekiq::Undertaker::JobDistributor.new(@dead_jobs).group_by_error_class
25
+ @total_dead = @dead_jobs.size
26
+
27
+ render_result("filter_job_class.erb")
28
+ end
29
+
30
+ def show_undertaker_by_job_class_error_class_bucket_name
31
+ store_request_params
32
+
33
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
34
+
35
+ # Display dead jobs as list
36
+ @dead_jobs = @dead_jobs.map(&:job)
37
+
38
+ @undertaker_path = "undertaker/#{@req_job_class}/#{@req_error_class}/#{@req_bucket_name}"
39
+
40
+ # Pagination
41
+ @total_dead = @dead_jobs.size
42
+ @current_page = (params[:page] || 1).to_i
43
+ @count = 50 # per page
44
+ @dead_jobs = @dead_jobs[((@current_page - 1) * @count), @count]
45
+
46
+ # Remove unrelated arguments to allow _paginate url to be clean
47
+ # Hack to continue to reuse sidekiq's _pagination template
48
+ params.delete("job_class")
49
+ params.delete("bucket_name")
50
+ params.delete("error_class")
51
+
52
+ render_result("morgue.erb")
53
+ end
54
+
55
+ def post_undertaker
56
+ raise ::ArgumentError.new("Key missing") unless params["key"]
57
+
58
+ params["key"].each do |key|
59
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
60
+ if job
61
+ if params["retry"]
62
+ job.retry
63
+ elsif params["delete"]
64
+ job.delete
65
+ end
66
+ end
67
+ end
68
+
69
+ redirect redirect_path(request)
70
+ rescue ::ArgumentError
71
+ bad_request
72
+ end
73
+
74
+ def post_undertaker_job_class_error_class_buckent_name_delete
75
+ store_request_params
76
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
77
+ @dead_jobs.each do |dead_job|
78
+ dead_job.job.delete
79
+ end
80
+
81
+ redirect redirect_path(request)
82
+ end
83
+
84
+ def post_undertaker_job_class_error_class_buckent_name_retry
85
+ store_request_params
86
+
87
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
88
+ @dead_jobs.each do |dead_job|
89
+ dead_job.job.retry
90
+ end
91
+
92
+ redirect redirect_path(request)
93
+ end
94
+
95
+ def render_result(template)
96
+ render(:erb, File.read(File.join(view_path, template)))
97
+ end
98
+
99
+ def store_request_params
100
+ @req_job_class = params["job_class"]
101
+ @req_bucket_name = params["bucket_name"]
102
+ @req_error_class = params["error_class"]
103
+ end
104
+
105
+ def view_path
106
+ File.join(File.expand_path(__dir__), "../../../../web/views")
107
+ end
108
+
109
+ def redirect_path(request)
110
+ path = request.referer ? URI.parse(request.referer).path : request.path
111
+ path.gsub("/delete", "").gsub("/retry", "")
112
+ end
113
+
114
+ def bad_request
115
+ [400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "sidekiq/undertaker/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "sidekiq-undertaker"
9
+ spec.version = Sidekiq::Undertaker::VERSION
10
+ spec.authors = ["Thomas Koppensteiner"]
11
+ spec.email = ["thomas.koppensteiner@gmx.net "]
12
+ spec.summary = "Sidekiq::Undertaker makes exploring the Sidekiq dead job queue easier"
13
+ spec.description = <<-DES
14
+ Sidekiq::Undertaker is a plugin for Sidekiq.
15
+ It makes exploring the dead job queue easier
16
+ by breaking down the dead jobs into time windows (buckets) of hours and days.
17
+ The gems allows retrying or deleting a certain subset of failures.
18
+ DES
19
+ spec.license = "MIT"
20
+
21
+ spec.homepage = "https://github.com/ThomasKoppensteiner/sidekiq-undertaker"
22
+ spec.metadata = {
23
+ "homepage_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker",
24
+ "source_code_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker",
25
+ "bug_tracker_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker/issues",
26
+ "build_status_uri" => "https://travis-ci.org/ThomasKoppensteiner/sidekiq-undertaker"
27
+ }
28
+
29
+ spec.files = `git ls-files -z`.split("\x0")
30
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
32
+ spec.require_paths = ["lib", "lib/sidekiq/undertaker"]
33
+
34
+ spec.add_development_dependency "bundler", ">= 1.17", "<3"
35
+ spec.add_development_dependency "rake", "~> 12.3"
36
+
37
+ spec.add_development_dependency "approvals", "~> 0.0", ">= 0.0.24"
38
+ spec.add_development_dependency "mock_redis", "~> 0.19"
39
+ spec.add_development_dependency "pry", "~> 0.12"
40
+ spec.add_development_dependency "rack-test", "~> 1.1"
41
+ spec.add_development_dependency "rb-readline", "~> 0.5"
42
+ spec.add_development_dependency "rspec", "~> 3.8"
43
+ spec.add_development_dependency "rspec-core", "~> 3.8"
44
+ spec.add_development_dependency "rspec-mocks", "~> 3.8"
45
+ spec.add_development_dependency "rspec-sidekiq", "~> 3.0"
46
+ spec.add_development_dependency "rt_rubocop_defaults", "~> 1.2"
47
+ spec.add_development_dependency "rubocop", "~> 0.77"
48
+ spec.add_development_dependency "rubocop-rspec", "~> 1.30"
49
+ spec.add_development_dependency "rubocop_runner", "~> 2.1"
50
+ spec.add_development_dependency "simplecov", "~> 0.14"
51
+ spec.add_development_dependency "sinatra", "~> 2.0"
52
+ spec.add_development_dependency "timecop", "~> 0.9"
53
+
54
+ spec.add_runtime_dependency "sidekiq", "~> 5.2"
55
+ end
@@ -0,0 +1,240 @@
1
+ <!doctype html>
2
+ <html dir="ltr">
3
+ <head>
4
+ <title>[TEST] Sidekiq</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
+
8
+ <link href="/sidekiq/stylesheets/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />
9
+
10
+
11
+ <link href="/sidekiq/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
12
+
13
+
14
+ <link rel="shortcut icon" type="image/ico" href="/sidekiq/images/favicon.ico" />
15
+ <script type="text/javascript" src="/sidekiq/javascripts/application.js"></script>
16
+ <meta name="google" content="notranslate" />
17
+
18
+ </head>
19
+ <body class="admin" data-poll-path="" data-locale="en">
20
+ <div class="navbar navbar-default navbar-fixed-top">
21
+ <div class="container-fluid">
22
+ <div class="navbar-header" data-navbar="static">
23
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-menu">
24
+ <span class="icon-bar"></span>
25
+ <span class="icon-bar"></span>
26
+ <span class="icon-bar"></span>
27
+ </button>
28
+ <div class="navbar-toggle collapsed navbar-livereload">
29
+
30
+
31
+ <a id="live-poll" class="btn btn-primary" href="/sidekiq/undertaker/filter?poll=true">Live Poll</a>
32
+
33
+
34
+
35
+
36
+ </div>
37
+ <a class="navbar-brand" href="/sidekiq/">
38
+ Sidekiq
39
+ <span class="status">
40
+ <i class="status-sprite status-idle"></i>
41
+ idle
42
+ </span>
43
+
44
+ </a>
45
+ </div>
46
+
47
+ <div class="collapse navbar-collapse" id="navbar-menu">
48
+ <ul class="nav navbar-nav" data-navbar="static">
49
+
50
+
51
+ <li class="">
52
+ <a href="/sidekiq/">Dashboard</a>
53
+ </li>
54
+
55
+
56
+
57
+ <li class="">
58
+ <a href="/sidekiq/busy">Busy</a>
59
+ </li>
60
+
61
+
62
+
63
+ <li class="">
64
+ <a href="/sidekiq/queues">Queues</a>
65
+ </li>
66
+
67
+
68
+
69
+ <li class="">
70
+ <a href="/sidekiq/retries">Retries</a>
71
+ </li>
72
+
73
+
74
+
75
+ <li class="">
76
+ <a href="/sidekiq/scheduled">Scheduled</a>
77
+ </li>
78
+
79
+
80
+
81
+ <li class="">
82
+ <a href="/sidekiq/morgue">Dead</a>
83
+ </li>
84
+
85
+
86
+
87
+
88
+ <li class="active" data-navbar="custom-tab">
89
+ <a href="/sidekiq/undertaker/filter">Undertaker</a>
90
+ </li>
91
+
92
+
93
+ <li class="navbar-livereload">
94
+ <div class="poll-wrapper">
95
+
96
+
97
+ <a id="live-poll" class="btn btn-primary" href="/sidekiq/undertaker/filter?poll=true">Live Poll</a>
98
+
99
+
100
+
101
+
102
+ </div>
103
+ </li>
104
+ </ul>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <div id="page">
110
+ <div class="container">
111
+ <div class="row">
112
+ <div class="col-sm-12 summary_bar">
113
+ <ul class="list-unstyled summary row">
114
+ <li class="processed col-sm-1">
115
+ <span class="count">0</span>
116
+ <span class="desc">Processed</span>
117
+ </li>
118
+ <li class="failed col-sm-1">
119
+ <span class="count">0</span>
120
+ <span class="desc">Failed</span>
121
+ </li>
122
+ <li class="busy col-sm-1">
123
+ <a href="/sidekiq/busy">
124
+ <span class="count">0</span>
125
+ <span class="desc">Busy</span>
126
+ </a>
127
+ </li>
128
+ <li class="enqueued col-sm-1">
129
+ <a href="/sidekiq/queues">
130
+ <span class="count">0</span>
131
+ <span class="desc">Enqueued</span>
132
+ </a>
133
+ </li>
134
+ <li class="retries col-sm-1">
135
+ <a href="/sidekiq/retries">
136
+ <span class="count">0</span>
137
+ <span class="desc">Retries</span>
138
+ </a>
139
+ </li>
140
+ <li class="scheduled col-sm-1">
141
+ <a href="/sidekiq/scheduled">
142
+ <span class="count">0</span>
143
+ <span class="desc">Scheduled</span>
144
+ </a>
145
+ </li>
146
+ <li class="dead col-sm-1">
147
+ <a href="/sidekiq/morgue">
148
+ <span class="count">4</span>
149
+ <span class="desc">Dead</span>
150
+ </a>
151
+ </li>
152
+ </ul>
153
+
154
+ </div>
155
+
156
+ <div class="col-sm-12">
157
+ <header class="row header">
158
+ <div class="col-sm-12">
159
+ <h3>
160
+ <b>4</b> dead jobs
161
+ </h3>
162
+ </div>
163
+ </header>
164
+
165
+ <table class="table table-striped table-bordered table-white">
166
+ <thead>
167
+ <tr>
168
+ <th style="width: 20%">Jobs</th>
169
+ <th style="width: 10%">All</th>
170
+ <th style="width: 10%">1 hour</th>
171
+ <th style="width: 10%">3 hours</th>
172
+ <th style="width: 10%">1 day</th>
173
+ <th style="width: 10%">3 days</th>
174
+ <th style="width: 10%">1 week</th>
175
+ <th style="width: 10%">Older</th>
176
+ </tr>
177
+ </thead>
178
+
179
+ <tr>
180
+ <td><a href='/sidekiq/undertaker/morgue/all/all/total_dead'> all</a></td>
181
+ <td><a href='/all/total_dead'>4</a></td>
182
+ <td><a href='/all/1_hour'>4</a></td>
183
+ <td><a href='/all/3_hours'>0</a></td>
184
+ <td><a href='/all/1_day'>0</a></td>
185
+ <td><a href='/all/3_days'>0</a></td>
186
+ <td><a href='/all/1_week'>0</a></td>
187
+ <td><a href='/all/older'>0</a></td>
188
+ </tr>
189
+
190
+ <tr>
191
+ <td><a href='/sidekiq/undertaker/morgue/HardWorker/all/total_dead'> HardWorker</a></td>
192
+ <td><a href='/HardWorker/total_dead'>3</a></td>
193
+ <td><a href='/HardWorker/1_hour'>3</a></td>
194
+ <td><a href='/HardWorker/3_hours'>0</a></td>
195
+ <td><a href='/HardWorker/1_day'>0</a></td>
196
+ <td><a href='/HardWorker/3_days'>0</a></td>
197
+ <td><a href='/HardWorker/1_week'>0</a></td>
198
+ <td><a href='/HardWorker/older'>0</a></td>
199
+ </tr>
200
+
201
+ <tr>
202
+ <td><a href='/sidekiq/undertaker/morgue/HardWorker1/all/total_dead'> HardWorker1</a></td>
203
+ <td><a href='/HardWorker1/total_dead'>1</a></td>
204
+ <td><a href='/HardWorker1/1_hour'>1</a></td>
205
+ <td><a href='/HardWorker1/3_hours'>0</a></td>
206
+ <td><a href='/HardWorker1/1_day'>0</a></td>
207
+ <td><a href='/HardWorker1/3_days'>0</a></td>
208
+ <td><a href='/HardWorker1/1_week'>0</a></td>
209
+ <td><a href='/HardWorker1/older'>0</a></td>
210
+ </tr>
211
+
212
+ </table>
213
+
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ <div class="navbar navbar-fixed-bottom navbar-inverse ltr">
219
+ <div class="navbar-inner">
220
+ <div class="container text-center">
221
+ <ul class="nav">
222
+ <li>
223
+ <p class="navbar-text product-version">Sidekiq v*EXCLUDED*</p>
224
+ </li>
225
+ <li>
226
+ <p class="navbar-text redis-url" title="redis://127.0.0.1:6379/0">redis://127.0.0.1:6379/0</p>
227
+ </li>
228
+ <li>
229
+ <p class="navbar-text server-utc-time">20:57:00 UTC</p>
230
+ </li>
231
+ <li>
232
+ <p class="navbar-text"><a style="color: gray;" href="https://github.com/mperham/sidekiq/wiki">docs</a></p>
233
+ </li>
234
+ </ul>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ </body>
240
+ </html>