sidekiq-undertaker 1.0.0.rc01

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 +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>