sidekiq-undertaker 1.2.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-build.yml +31 -3
  3. data/.rubocop.yml +4 -0
  4. data/.rubocop_codeclimate.yml +9 -0
  5. data/CHANGELOG.md +19 -0
  6. data/README.md +8 -6
  7. data/assets/Undertaker_demo_1_job_1_error.png +0 -0
  8. data/assets/Undertaker_demo_1_job_all_errors.png +0 -0
  9. data/assets/Undertaker_demo_all_errors.png +0 -0
  10. data/assets/Undertaker_demo_all_jobs.png +0 -0
  11. data/assets/Undertaker_demo_all_jobs_1_error.png +0 -0
  12. data/lib/sidekiq/undertaker/dead_job.rb +10 -2
  13. data/lib/sidekiq/undertaker/job_distributor.rb +4 -0
  14. data/lib/sidekiq/undertaker/job_filter.rb +7 -2
  15. data/lib/sidekiq/undertaker/version.rb +1 -1
  16. data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +104 -26
  17. data/lib/sidekiq/undertaker/web_extension.rb +17 -8
  18. data/sidekiq-undertaker.gemspec +4 -4
  19. 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 +19 -3
  20. 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 +24 -24
  21. 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 +24 -24
  22. 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
  23. 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
  24. 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 +13 -6
  25. 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
  26. 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
  27. 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 +11 -4
  28. 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 +59 -52
  29. data/spec/sidekiq/undertaker/dead_jobs_spec.rb +7 -4
  30. data/spec/sidekiq/undertaker/job_distributor_spec.rb +41 -19
  31. data/spec/sidekiq/undertaker/job_filter_spec.rb +26 -9
  32. data/spec/sidekiq/undertaker/web_extension_spec.rb +172 -14
  33. data/spec/spec_helper.rb +4 -0
  34. data/web/locales/en.yml +2 -0
  35. data/web/views/filter.erb +17 -1
  36. data/web/views/filter_job_class.erb +8 -8
  37. data/web/views/filter_job_class_error_class.erb +36 -0
  38. data/web/views/morgue.erb +9 -2
  39. metadata +73 -66
  40. data/.travis.yml +0 -51
  41. data/Demo_Filter.png +0 -0
  42. data/Demo_Job_Filter.png +0 -0
  43. data/Demo_Morgue_1_Job.png +0 -0
  44. data/Demo_Morgue_all.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbe7e0e84ffed4be546988e142fadfef4b8ec6643f16f8c48507a11227a6a2f5
4
- data.tar.gz: 798f2e9631d028423fcf5b10d403f4c7074d20014310293413af767866404610
3
+ metadata.gz: 8bb9cb0de79a210eecb875abea9d75cf30f228c294ac9af748a0e90bd2634433
4
+ data.tar.gz: efeafbf06536b5ce50ea2d0f13286c92829d11c9168dbd608bf60ade9ad80cde
5
5
  SHA512:
6
- metadata.gz: ccb8eaf232d8f751ea3f75f80c4eaf91eff50bda583a392c7206ba0c69bec4d32d0fe95e3feac947430d713caf65583a18f31ce37e80d0b327058bb7b4fca401
7
- data.tar.gz: 21380b3857b132a5e7fa7016c05a698d40808d86f02f260604b61ffe718f5810c921508088a696970e77149f8877848175f008dfd3b4034c7c50081dd641b7d9
6
+ metadata.gz: f4c2faf722ee34829c0de7d9c403f42cc148784a735a036de962651e5f7eb02b385a6044cf72da3d8b9290c247b8fcca751ef161ab89f1ef658ce9312e7151bf
7
+ data.tar.gz: e599c728949f5363b1d19dcbacc590e606adcd6c15ce4ef2866bc73924f906c5a2b51b6f6533df5d147a2f97fea790fec68cb8f63200aefe00bd0cde792c3f02
@@ -34,12 +34,15 @@ jobs:
34
34
  - '2.7'
35
35
  - 'jruby'
36
36
  - 'jruby-9.3.2.0'
37
- - 'jruby-9.2.20.0'
37
+ - 'jruby-9.2.20.1'
38
38
  - 'truffleruby'
39
39
  - 'truffleruby+graalvm'
40
40
 
41
+ env:
42
+ JRUBY_OPTS: '--debug'
43
+
41
44
  steps:
42
- - uses: actions/checkout@v2
45
+ - uses: actions/checkout@v3
43
46
  - name: Set up Ruby
44
47
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
45
48
  # change this to (see https://github.com/ruby/setup-ruby#versioning):
@@ -48,4 +51,29 @@ jobs:
48
51
  ruby-version: ${{ matrix.ruby-version }}
49
52
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
50
53
  - name: Run tests
51
- run: bundle exec rspec spec
54
+ run: COVERAGE=true bundle exec rspec
55
+ - name: 'Upload Coverage Report'
56
+ uses: actions/upload-artifact@v3
57
+ if: ${{ matrix.ruby-version == 'ruby' }}
58
+ with:
59
+ name: coverage-report
60
+ path: ./coverage
61
+ retention-days: 1
62
+
63
+ coverage:
64
+ needs: [ test ]
65
+ name: coverage
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v3
69
+ - name: Download Coverage Report
70
+ uses: actions/download-artifact@v3
71
+ with:
72
+ name: coverage-report
73
+ path: ./coverage
74
+ - uses: paambaati/codeclimate-action@v3.0.0
75
+ env:
76
+ # Set CC_TEST_REPORTER_ID as secret of your repo
77
+ CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
78
+ with:
79
+ debug: true
data/.rubocop.yml CHANGED
@@ -7,3 +7,7 @@ inherit_from: .rubocop_todo.yml
7
7
 
8
8
  AllCops:
9
9
  TargetRubyVersion: 2.5
10
+
11
+ Layout/LineLength:
12
+ Exclude:
13
+ - 'sidekiq-undertaker.gemspec'
@@ -1,5 +1,14 @@
1
1
  require:
2
+ - rubocop-rake
2
3
  - rubocop-rspec
4
+
3
5
  inherit_from:
4
6
  - .rt_rubocop_defaults.yml
5
7
  - .rubocop_todo.yml
8
+
9
+ AllCops:
10
+ TargetRubyVersion: 2.5
11
+
12
+ Layout/LineLength:
13
+ Exclude:
14
+ - 'sidekiq-undertaker.gemspec'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [1.4.1] - 2022-06-03
6
+ ### Fixed
7
+ - Fix downgrade rubocop to '1.28' to keep support for JRuby '9.2.20.1'
8
+
9
+ ## [1.4.0] - 2022-06-03
10
+ ### Added
11
+ - Added a filter based on error messages
12
+
13
+ ### Changed
14
+ - Updated rubocop to version 1.30
15
+ - Updated rubyzip to version 2.3
16
+
17
+ ### Removed
18
+ - Removed Travis CI integration
19
+
20
+ ## [1.3.0] - 2022-04-27
21
+ ### Added
22
+ - Added option to export and re-import dead jobs
23
+
5
24
  ## [1.2.0] - 2022-02-03
6
25
  ### Changed
7
26
  - Updated sidekiq dependency to 6.4.0
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Sidekiq::Undertaker
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/sidekiq-undertaker.svg)](https://badge.fury.io/rb/sidekiq-undertaker)
4
- [![Build Status](https://travis-ci.org/ThomasKoppensteiner/sidekiq-undertaker.svg?branch=master)](https://travis-ci.org/ThomasKoppensteiner/sidekiq-undertaker)
4
+ [![Ruby-Build](https://github.com/ThomasKoppensteiner/sidekiq-undertaker/actions/workflows/ruby-build.yml/badge.svg)](https://github.com/ThomasKoppensteiner/sidekiq-undertaker/actions/workflows/ruby-build.yml)
5
5
  [![Code Climate](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker.svg)](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker)
6
6
  [![Test Coverage](https://api.codeclimate.com/v1/badges/d442eb0a323d8911661f/test_coverage)](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker/test_coverage)
7
7
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
@@ -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
- ![Sidekiq Undertaker](Demo_Filter.png)
48
+ ![Sidekiq Undertaker](assets/Undertaker_demo_all_jobs.png)
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
- ![Sidekiq Undertaker](Demo_Job_Filter.png)
55
+ ![Sidekiq Undertaker](assets/Undertaker_demo_1_job_all_errors.png)
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
- ![Sidekiq Undertaker](Demo_Morgue_1_Job.png)
61
+ ![Sidekiq Undertaker](assets/Undertaker_demo_1_job_1_error.png)
62
62
 
63
63
  The morgue view can, for example, also show an error distribution over all job classes.
64
64
 
65
- ![Sidekiq Undertaker](Demo_Morgue_all.png)
65
+ ![Sidekiq Undertaker](assets/Undertaker_demo_all_jobs_1_error.png)
66
66
 
67
67
  ## Contributing
68
68
 
@@ -81,7 +81,9 @@ 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
- Thank you [Zlatko Rednjak](https://github.com/Rednjak) for adding the initial version of the `CHANGELOG.md`.
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.
86
+ [thoesl](https://github.com/thoesl) added the error message based filter.
85
87
 
86
88
  For the complete list of network members have a look at the [fork overview](https://github.com/ThomasKoppensteiner/sidekiq-undertaker/network/members).
87
89
 
Binary file
@@ -27,7 +27,7 @@ module Sidekiq
27
27
  end
28
28
  end
29
29
 
30
- attr_reader :job_class, :time_elapsed_since_failure, :error_class, :bucket_name, :job
30
+ attr_reader :job_class, :time_elapsed_since_failure, :error_class, :error_msg, :bucket_name, :job
31
31
 
32
32
  def initialize(args)
33
33
  @job = args.fetch(:job)
@@ -35,12 +35,14 @@ module Sidekiq
35
35
  @bucket_name = args.fetch(:bucket_name)
36
36
  @job_class = args.fetch(:job_class, job.item["class"])
37
37
  @error_class = args.fetch(:error_class, job.item["error_class"])
38
+ @error_msg = shorten_error_msg(args.fetch(:error_message, job.item["error_message"]).to_s)
38
39
  end
39
40
 
40
41
  def ==(other)
41
42
  job_class == other.job_class &&
42
43
  time_elapsed_since_failure == other.time_elapsed_since_failure &&
43
44
  error_class == other.error_class &&
45
+ error_msg == other.error_msg &&
44
46
  bucket_name == other.bucket_name &&
45
47
  job_eql?(other.job)
46
48
  end
@@ -48,7 +50,13 @@ module Sidekiq
48
50
 
49
51
  private
50
52
 
51
- attr_writer :job_class, :time_elapsed_since_failure, :error_class, :bucket_name, :job
53
+ attr_writer :job_class, :time_elapsed_since_failure, :error_class, :error_message, :bucket_name, :job
54
+
55
+ def shorten_error_msg(msg)
56
+ max_error_msg_length = 30
57
+
58
+ msg.length > max_error_msg_length ? "#{msg[0, max_error_msg_length]}..." : msg
59
+ end
52
60
 
53
61
  def job_eql?(other_job) # rubocop:disable Metrics/AbcSize
54
62
  job.jid == other_job.jid &&
@@ -19,6 +19,10 @@ module Sidekiq
19
19
  group_by(:error_class)
20
20
  end
21
21
 
22
+ def group_by_error_msg
23
+ group_by(:error_msg)
24
+ end
25
+
22
26
  private
23
27
 
24
28
  def distribute
@@ -7,7 +7,7 @@ module Sidekiq
7
7
  class JobFilter
8
8
  class << self
9
9
  def filter_dead_jobs(filters = {})
10
- # filters can have keys in (job_class, error_class, bucket_name)
10
+ # filters can have keys in (job_class, error_class, error_msg, bucket_name)
11
11
  dead_jobs = []
12
12
  Sidekiq::Undertaker::DeadJob.for_each do |dead_job|
13
13
  filter_passed = true
@@ -26,7 +26,8 @@ module Sidekiq
26
26
  value.nil? ||
27
27
  total_dead_bucket?(filter, value) ||
28
28
  all_jobs?(filter, value) ||
29
- all_errors?(filter, value)
29
+ all_errors?(filter, value) ||
30
+ all_error_msgs?(filter, value)
30
31
  end
31
32
 
32
33
  def total_dead_bucket?(filter, value)
@@ -40,6 +41,10 @@ module Sidekiq
40
41
  def all_errors?(filter, value)
41
42
  filter == "error_class" && value == "all"
42
43
  end
44
+
45
+ def all_error_msgs?(filter, value)
46
+ filter == "error_msg" && value == "all"
47
+ end
43
48
  end
44
49
  end
45
50
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Undertaker
5
- VERSION = "1.2.0"
5
+ VERSION = "1.4.1"
6
6
  end
7
7
  end
@@ -2,15 +2,21 @@
2
2
 
3
3
  require "sidekiq/undertaker/job_distributor"
4
4
  require "sidekiq/undertaker/job_filter"
5
+ require "base64"
6
+ require "json"
7
+ require "zip"
5
8
 
6
9
  module Sidekiq
7
10
  module Undertaker
8
11
  module WebExtension
12
+ # rubocop:disable Metrics/ModuleLength
9
13
  module APIHelpers
14
+ EXPORT_CHUNK_SIZE = 2000
15
+
10
16
  def show_filter
11
17
  store_request_params
12
18
 
13
- @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
19
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
14
20
  @distribution = Sidekiq::Undertaker::JobDistributor.new(@dead_jobs).group_by_job_class
15
21
  @total_dead = @dead_jobs.size
16
22
 
@@ -20,17 +26,27 @@ module Sidekiq
20
26
  def show_filter_by_job_class_bucket_name
21
27
  store_request_params
22
28
 
23
- @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
29
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
24
30
  @distribution = Sidekiq::Undertaker::JobDistributor.new(@dead_jobs).group_by_error_class
25
31
  @total_dead = @dead_jobs.size
26
32
 
27
33
  render_result("filter_job_class.erb")
28
34
  end
29
35
 
30
- def show_undertaker_by_job_class_error_class_bucket_name
36
+ def show_filter_by_job_class_error_class_bucket_name
37
+ store_request_params
38
+
39
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
40
+ @distribution = Sidekiq::Undertaker::JobDistributor.new(@dead_jobs).group_by_error_msg
41
+ @total_dead = @dead_jobs.size
42
+
43
+ render_result("filter_job_class_error_class.erb")
44
+ end
45
+
46
+ def show_undertaker_by_job_class_error_class_error_msg_bucket_name
31
47
  store_request_params
32
48
 
33
- @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
49
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
34
50
 
35
51
  # Display dead jobs as list
36
52
  @dead_jobs = @dead_jobs.map(&:job)
@@ -39,7 +55,7 @@ module Sidekiq
39
55
 
40
56
  # Pagination
41
57
  @total_dead = @dead_jobs.size
42
- @current_page = (params[:page] || 1).to_i
58
+ @current_page = (parsed_params[:page] || 1).to_i
43
59
  @count = 50 # per page
44
60
  @dead_jobs = @dead_jobs[((@current_page - 1) * @count), @count]
45
61
 
@@ -52,32 +68,36 @@ module Sidekiq
52
68
  params.delete("job_class")
53
69
  params.delete("bucket_name")
54
70
  params.delete("error_class")
71
+ params.delete("error_msg")
55
72
 
56
73
  render_result("morgue.erb")
57
74
  end
58
75
 
59
76
  def post_undertaker
60
- raise ::ArgumentError.new("Key missing") unless params["key"]
61
-
62
- params["key"].each do |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
77
+ raise ::ArgumentError.new("Key missing") unless parsed_params["key"]
72
78
 
73
- redirect redirect_path(request)
79
+ jobs = parsed_params["key"].map { |k| Sidekiq::DeadSet.new.fetch(*parse_params(k)).first }.compact
80
+
81
+ handle_selected_jobs jobs
74
82
  rescue ::ArgumentError
75
83
  bad_request
76
84
  end
77
85
 
78
- def post_undertaker_job_class_error_class_buckent_name_delete
86
+ def handle_selected_jobs(jobs)
87
+ return send_data(*prepare_data(jobs.map(&:item), EXPORT_CHUNK_SIZE)) if parsed_params["export"]
88
+
89
+ if parsed_params["retry"]
90
+ jobs.each(&:retry)
91
+ elsif parsed_params["delete"]
92
+ jobs.each(&:delete)
93
+ end
94
+
95
+ redirect redirect_path(request)
96
+ end
97
+
98
+ def post_undertaker_job_class_error_class_error_msg_bucket_name_delete
79
99
  store_request_params
80
- @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
100
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
81
101
  @dead_jobs.each do |dead_job|
82
102
  dead_job.job.delete
83
103
  end
@@ -85,10 +105,10 @@ module Sidekiq
85
105
  redirect redirect_path(request)
86
106
  end
87
107
 
88
- def post_undertaker_job_class_error_class_buckent_name_retry
108
+ def post_undertaker_job_class_error_class_error_msg_bucket_name_retry
89
109
  store_request_params
90
110
 
91
- @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(params)
111
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
92
112
  @dead_jobs.each do |dead_job|
93
113
  dead_job.job.retry
94
114
  end
@@ -96,14 +116,45 @@ module Sidekiq
96
116
  redirect redirect_path(request)
97
117
  end
98
118
 
119
+ def post_undertaker_job_class_error_class_error_msg_bucket_name_export
120
+ store_request_params
121
+
122
+ @dead_jobs = Sidekiq::Undertaker::JobFilter.filter_dead_jobs(parsed_params)
123
+ send_data(*prepare_data(@dead_jobs.map { |j| j.job.item }, EXPORT_CHUNK_SIZE))
124
+ end
125
+
126
+ def post_import_jobs
127
+ file = parsed_params["upload_file"]
128
+ raise ::ArgumentError.new("The file is not a json") if file.nil? || file[:type] != "application/json"
129
+
130
+ data = parsed_params["upload_file"][:tempfile].read
131
+ dead_set = Sidekiq::DeadSet.new
132
+
133
+ JSON.parse(data).each do |job|
134
+ dead_set.kill(Sidekiq.dump_json(job))
135
+ end
136
+ redirect redirect_path(request)
137
+ rescue StandardError
138
+ bad_request
139
+ end
140
+
99
141
  def render_result(template)
100
142
  render(:erb, File.read(File.join(view_path, template)))
101
143
  end
102
144
 
103
145
  def store_request_params
104
- @req_job_class = params["job_class"]
105
- @req_bucket_name = params["bucket_name"]
106
- @req_error_class = params["error_class"]
146
+ @req_job_class = parsed_params["job_class"]
147
+ @req_bucket_name = parsed_params["bucket_name"]
148
+ @req_error_class = parsed_params["error_class"]
149
+ @req_error_msg = parsed_params["error_msg"]
150
+ end
151
+
152
+ def parsed_params
153
+ @parsed_params ||= if params["error_msg"] && params["error_msg"] != "all"
154
+ params.merge("error_msg" => Base64.urlsafe_decode64(params["error_msg"]))
155
+ else
156
+ params
157
+ end
107
158
  end
108
159
 
109
160
  def view_path
@@ -112,13 +163,40 @@ module Sidekiq
112
163
 
113
164
  def redirect_path(request)
114
165
  path = request.referer ? URI.parse(request.referer).path : request.path
115
- path.gsub("/delete", "").gsub("/retry", "")
166
+ path.gsub("/delete", "").gsub("/retry", "").gsub("/export", "")
167
+ end
168
+
169
+ def prepare_data(data, chunk_size)
170
+ filename = Time.now.strftime("%Y-%m-%d_%H-%M").to_s
171
+ return [data.to_json, "application/json", "#{filename}.json"] if data.length <= chunk_size
172
+
173
+ filename = "#{@req_job_class}_#{filename}"
174
+ zip = Zip::OutputStream.write_buffer do |file|
175
+ data.each_slice(chunk_size).each_with_index do |chunk, index|
176
+ file.put_next_entry("#{filename}_part-#{index + 1}.json")
177
+ file.write chunk.to_json
178
+ end
179
+ end
180
+
181
+ [zip.string, "application/zip", "#{filename}.zip"]
182
+ end
183
+
184
+ def send_data(data, content_type = "text/plain", file_name = "attachment.txt")
185
+ [
186
+ 200,
187
+ {
188
+ "Content-Type" => content_type,
189
+ "Content-Disposition" => "attachment; filename=\"#{file_name}\""
190
+ },
191
+ [data]
192
+ ]
116
193
  end
117
194
 
118
195
  def bad_request
119
196
  [400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
120
197
  end
121
198
  end
199
+ # rubocop:enable Metrics/ModuleLength
122
200
  end
123
201
  end
124
202
  end
@@ -12,25 +12,34 @@ 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
19
-
20
- app.get "/undertaker/morgue/:job_class/:error_class/:bucket_name" do
21
- show_undertaker_by_job_class_error_class_bucket_name
18
+ app.get "/undertaker/filter/:job_class/:error_class/:bucket_name" do
19
+ show_filter_by_job_class_error_class_bucket_name
20
+ end
21
+ app.get "/undertaker/morgue/:job_class/:error_class/:error_msg/:bucket_name" do
22
+ show_undertaker_by_job_class_error_class_error_msg_bucket_name
22
23
  end
23
24
 
24
25
  app.post "/undertaker/morgue" do
25
26
  post_undertaker
26
27
  end
27
28
 
28
- app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/delete" do
29
- post_undertaker_job_class_error_class_buckent_name_delete
29
+ app.post "/undertaker/morgue/:job_class/:error_class/:error_msg/:bucket_name/delete" do
30
+ post_undertaker_job_class_error_class_error_msg_bucket_name_delete
31
+ end
32
+
33
+ app.post "/undertaker/morgue/:job_class/:error_class/:error_msg/:bucket_name/retry" do
34
+ post_undertaker_job_class_error_class_error_msg_bucket_name_retry
35
+ end
36
+
37
+ app.post "/undertaker/morgue/:job_class/:error_class/:error_msg/:bucket_name/export" do
38
+ post_undertaker_job_class_error_class_error_msg_bucket_name_export
30
39
  end
31
40
 
32
- app.post "/undertaker/morgue/:job_class/:error_class/:bucket_name/retry" do
33
- post_undertaker_job_class_error_class_buckent_name_retry
41
+ app.post "/undertaker/import_jobs" do
42
+ post_import_jobs
34
43
  end
35
44
  end
36
45
  # rubocop:enable Metrics/MethodLength
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  "source_code_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker",
24
24
  "changelog_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker/blob/master/CHANGELOG.md",
25
25
  "bug_tracker_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker/issues",
26
- "build_status_uri" => "https://travis-ci.org/ThomasKoppensteiner/sidekiq-undertaker",
26
+ "build_status_uri" => "https://github.com/ThomasKoppensteiner/sidekiq-undertaker/actions/workflows/ruby-build.yml",
27
27
  "rubygems_mfa_required" => "true"
28
28
  }
29
29
 
@@ -31,7 +31,6 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.files = `git ls-files -z`.split("\x0")
33
33
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
34
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
35
34
  spec.require_paths = ["lib", "lib/sidekiq/undertaker"]
36
35
 
37
36
  spec.add_development_dependency "bundler", ">= 1.17", "<3"
@@ -47,13 +46,14 @@ Gem::Specification.new do |spec|
47
46
  spec.add_development_dependency "rspec-mocks", "~> 3.8"
48
47
  spec.add_development_dependency "rspec-sidekiq", "~> 3.0"
49
48
  spec.add_development_dependency "rt_rubocop_defaults", "~> 2.3"
50
- spec.add_development_dependency "rubocop", "~> 1.8"
49
+ spec.add_development_dependency "rubocop", "~> 1.28"
51
50
  spec.add_development_dependency "rubocop-rake", "~> 0.5"
52
51
  spec.add_development_dependency "rubocop-rspec", "~> 2.0"
53
52
  spec.add_development_dependency "rubocop_runner", "~> 2.1"
54
- spec.add_development_dependency "simplecov", "~> 0.14"
53
+ spec.add_development_dependency "simplecov", "~> 0.21"
55
54
  spec.add_development_dependency "sinatra", "~> 2.0"
56
55
  spec.add_development_dependency "timecop", "~> 0.9"
57
56
 
57
+ spec.add_runtime_dependency "rubyzip", "~> 2.3"
58
58
  spec.add_runtime_dependency "sidekiq", ">= 6.4", "< 7"
59
59
  end
@@ -177,7 +177,7 @@
177
177
  </thead>
178
178
 
179
179
  <tr>
180
- <td><a href='/sidekiq/undertaker/morgue/all/all/total_dead'> all</a></td>
180
+ <td><a href='/sidekiq/undertaker/morgue/all/all/all/total_dead'> all</a></td>
181
181
  <td><a href='/sidekiq/undertaker/filter/all/total_dead'>4</a></td>
182
182
  <td><a href='/sidekiq/undertaker/filter/all/1_hour'>4</a></td>
183
183
  <td><a href='/sidekiq/undertaker/filter/all/3_hours'>0</a></td>
@@ -188,7 +188,7 @@
188
188
  </tr>
189
189
 
190
190
  <tr>
191
- <td><a href='/sidekiq/undertaker/morgue/HardWorker/all/total_dead'> HardWorker</a></td>
191
+ <td><a href='/sidekiq/undertaker/morgue/HardWorker/all/all/total_dead'> HardWorker</a></td>
192
192
  <td><a href='/sidekiq/undertaker/filter/HardWorker/total_dead'>3</a></td>
193
193
  <td><a href='/sidekiq/undertaker/filter/HardWorker/1_hour'>3</a></td>
194
194
  <td><a href='/sidekiq/undertaker/filter/HardWorker/3_hours'>0</a></td>
@@ -199,7 +199,7 @@
199
199
  </tr>
200
200
 
201
201
  <tr>
202
- <td><a href='/sidekiq/undertaker/morgue/HardWorker1/all/total_dead'> HardWorker1</a></td>
202
+ <td><a href='/sidekiq/undertaker/morgue/HardWorker1/all/all/total_dead'> HardWorker1</a></td>
203
203
  <td><a href='/sidekiq/undertaker/filter/HardWorker1/total_dead'>1</a></td>
204
204
  <td><a href='/sidekiq/undertaker/filter/HardWorker1/1_hour'>1</a></td>
205
205
  <td><a href='/sidekiq/undertaker/filter/HardWorker1/3_hours'>0</a></td>
@@ -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>