sidekiq-error-groups 0.1.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 +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +238 -0
- data/exe/sidekiq-error-groups +6 -0
- data/lib/sidekiq/error_groups/actions.rb +124 -0
- data/lib/sidekiq/error_groups/aggregator.rb +40 -0
- data/lib/sidekiq/error_groups/cli.rb +155 -0
- data/lib/sidekiq/error_groups/compat.rb +36 -0
- data/lib/sidekiq/error_groups/config.rb +69 -0
- data/lib/sidekiq/error_groups/formatter.rb +90 -0
- data/lib/sidekiq/error_groups/group.rb +129 -0
- data/lib/sidekiq/error_groups/job.rb +137 -0
- data/lib/sidekiq/error_groups/job_query.rb +69 -0
- data/lib/sidekiq/error_groups/railtie.rb +13 -0
- data/lib/sidekiq/error_groups/scanner.rb +53 -0
- data/lib/sidekiq/error_groups/selector.rb +101 -0
- data/lib/sidekiq/error_groups/summary.rb +57 -0
- data/lib/sidekiq/error_groups/tasks.rake +17 -0
- data/lib/sidekiq/error_groups/version.rb +7 -0
- data/lib/sidekiq/error_groups/web.rb +28 -0
- data/lib/sidekiq/error_groups/web_extension.rb +178 -0
- data/lib/sidekiq/error_groups.rb +116 -0
- data/lib/sidekiq-error-groups.rb +3 -0
- data/sidekiq-error-groups.gemspec +37 -0
- data/web/locales/en.yml +35 -0
- data/web/locales/ru.yml +35 -0
- data/web/views/group.erb +114 -0
- data/web/views/index.erb +83 -0
- data/web/views/legacy/group.erb +114 -0
- data/web/views/legacy/index.erb +83 -0
- metadata +93 -0
data/web/views/index.erb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<header>
|
|
3
|
+
<h1><%= t('ErrorGroups') %></h1>
|
|
4
|
+
</header>
|
|
5
|
+
|
|
6
|
+
<% if (notice = eg_notice) %>
|
|
7
|
+
<p><strong><%= h notice %></strong></p>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<strong><%= t('GroupBy') %>:</strong>
|
|
12
|
+
<% eg_group_modes.each do |mode, label| %>
|
|
13
|
+
<% if mode == eg_group_by %>
|
|
14
|
+
<strong><%= h label %></strong>
|
|
15
|
+
<% else %>
|
|
16
|
+
<a href="<%= h eg_index_path(mode) %>"><%= h label %></a>
|
|
17
|
+
<% end %>
|
|
18
|
+
<%= "·" unless mode == eg_group_modes.last.first %>
|
|
19
|
+
<% end %>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p>
|
|
23
|
+
<%= t('ScannedJobs', scanned: @summary.scanned, total: @summary.total) %>
|
|
24
|
+
<% @summary.set_sizes.each do |set, size| %>
|
|
25
|
+
· <%= h set.to_s %>: <%= size %>
|
|
26
|
+
<% end %>
|
|
27
|
+
<% if @summary.truncated? %>
|
|
28
|
+
<br/><strong><%= t('ScanTruncated', max_scan: Sidekiq::ErrorGroups.config.max_scan) %></strong>
|
|
29
|
+
<% end %>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<% if @summary.empty? %>
|
|
33
|
+
<p><%= t('NoFailedJobs') %></p>
|
|
34
|
+
<% else %>
|
|
35
|
+
<div class="table_container">
|
|
36
|
+
<table>
|
|
37
|
+
<thead>
|
|
38
|
+
<tr>
|
|
39
|
+
<% if eg_group_by != :job %>
|
|
40
|
+
<th><%= t('ErrorClass') %></th>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% if eg_group_by != :error %>
|
|
43
|
+
<th><%= t('JobClass') %></th>
|
|
44
|
+
<% end %>
|
|
45
|
+
<th><%= t('Total') %></th>
|
|
46
|
+
<th><%= t('Retries') %></th>
|
|
47
|
+
<th><%= t('Dead') %></th>
|
|
48
|
+
<th><%= t('LastFailure') %></th>
|
|
49
|
+
<th><%= t('FirstFailure') %></th>
|
|
50
|
+
<% if eg_group_by != :error_job %>
|
|
51
|
+
<th><%= (eg_group_by == :error) ? t('AffectedJobs') : t('AffectedErrors') %></th>
|
|
52
|
+
<% end %>
|
|
53
|
+
</tr>
|
|
54
|
+
</thead>
|
|
55
|
+
<tbody>
|
|
56
|
+
<% @summary.groups.each do |group| %>
|
|
57
|
+
<tr>
|
|
58
|
+
<% if eg_group_by != :job %>
|
|
59
|
+
<td>
|
|
60
|
+
<a href="<%= h eg_group_path(group.selector) %>"><%= h eg_label(group.error_class) %></a>
|
|
61
|
+
<% if group.sample && !group.sample.error_message.empty? %>
|
|
62
|
+
<br/><small><%= h truncate(group.sample.error_message, 120) %></small>
|
|
63
|
+
<% end %>
|
|
64
|
+
</td>
|
|
65
|
+
<% end %>
|
|
66
|
+
<% if eg_group_by != :error %>
|
|
67
|
+
<td><a href="<%= h eg_group_path(group.selector) %>"><%= h group.job_class.to_s %></a></td>
|
|
68
|
+
<% end %>
|
|
69
|
+
<td><%= group.count %></td>
|
|
70
|
+
<td><%= group.retry_count %></td>
|
|
71
|
+
<td><%= group.dead_count %></td>
|
|
72
|
+
<td><%= eg_time(group.last_failed_at) %></td>
|
|
73
|
+
<td><%= eg_time(group.first_failed_at) %></td>
|
|
74
|
+
<% if eg_group_by != :error_job %>
|
|
75
|
+
<td><small><%= h eg_breakdown(group) %></small></td>
|
|
76
|
+
<% end %>
|
|
77
|
+
</tr>
|
|
78
|
+
<% end %>
|
|
79
|
+
</tbody>
|
|
80
|
+
</table>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
</section>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<header class="row">
|
|
2
|
+
<div class="col-sm-8 pull-left flip">
|
|
3
|
+
<h3><%= h eg_selector_label(@selector) %></h3>
|
|
4
|
+
<p><a href="<%= h eg_index_path %>">← <%= t('BackToErrorGroups') %></a></p>
|
|
5
|
+
</div>
|
|
6
|
+
</header>
|
|
7
|
+
|
|
8
|
+
<% if @group.count.zero? %>
|
|
9
|
+
<div class="alert alert-success"><%= t('GroupEmpty') %></div>
|
|
10
|
+
<% else %>
|
|
11
|
+
<div class="table_container">
|
|
12
|
+
<table class="table table-bordered table-striped table-white">
|
|
13
|
+
<tbody>
|
|
14
|
+
<% if @selector.error_class %>
|
|
15
|
+
<tr><th><%= t('ErrorClass') %></th><td><%= h eg_label(@selector.error_class) %></td></tr>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% if @selector.job_class %>
|
|
18
|
+
<tr><th><%= t('JobClass') %></th><td><%= h @selector.job_class %></td></tr>
|
|
19
|
+
<% end %>
|
|
20
|
+
<tr><th><%= t('Total') %></th><td><%= @group.count %></td></tr>
|
|
21
|
+
<tr><th><%= t('Retries') %></th><td><%= @group.retry_count %></td></tr>
|
|
22
|
+
<tr><th><%= t('Dead') %></th><td><%= @group.dead_count %></td></tr>
|
|
23
|
+
<tr><th><%= t('FirstFailure') %></th><td><%= eg_time(@group.first_failed_at) %></td></tr>
|
|
24
|
+
<tr><th><%= t('LastFailure') %></th><td><%= eg_time(@group.last_failed_at) %></td></tr>
|
|
25
|
+
<% unless @group.breakdown.empty? %>
|
|
26
|
+
<tr>
|
|
27
|
+
<th><%= (@group.mode == :error) ? t('AffectedJobs') : t('AffectedErrors') %></th>
|
|
28
|
+
<td><%= h eg_breakdown(@group) %></td>
|
|
29
|
+
</tr>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% if @group.sample && !@group.sample.error_message.empty? %>
|
|
32
|
+
<tr><th><%= t('ErrorMessage') %></th><td><code><%= h @group.sample.error_message %></code></td></tr>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% backtrace = eg_backtrace(@group.sample) %>
|
|
35
|
+
<% unless backtrace.empty? %>
|
|
36
|
+
<tr>
|
|
37
|
+
<th><%= t('Backtrace') %></th>
|
|
38
|
+
<td><code><%= backtrace.map { |line| h(line) }.join("<br/>") %></code></td>
|
|
39
|
+
</tr>
|
|
40
|
+
<% end %>
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="row">
|
|
46
|
+
<form action="<%= h eg_group_path(@selector, 'retry') %>" method="post" class="pull-left flip">
|
|
47
|
+
<%= csrf_tag %>
|
|
48
|
+
<%= eg_selector_fields(@selector) %>
|
|
49
|
+
<input class="btn btn-primary" type="submit" value="<%= t('RetryGroup') %>"
|
|
50
|
+
data-confirm="<%= t('AreYouSureRetryGroup', count: @group.count) %>" />
|
|
51
|
+
</form>
|
|
52
|
+
<% if @group.retry_count > 0 %>
|
|
53
|
+
<form action="<%= h eg_group_path(@selector, 'kill') %>" method="post" class="pull-left flip">
|
|
54
|
+
<%= csrf_tag %>
|
|
55
|
+
<%= eg_selector_fields(@selector) %>
|
|
56
|
+
<input class="btn btn-danger" type="submit" value="<%= t('KillGroup') %>"
|
|
57
|
+
data-confirm="<%= t('AreYouSureKillGroup', count: @group.retry_count) %>" />
|
|
58
|
+
</form>
|
|
59
|
+
<% end %>
|
|
60
|
+
<form action="<%= h eg_group_path(@selector, 'delete') %>" method="post" class="pull-left flip">
|
|
61
|
+
<%= csrf_tag %>
|
|
62
|
+
<%= eg_selector_fields(@selector) %>
|
|
63
|
+
<input class="btn btn-danger" type="submit" value="<%= t('DeleteGroup') %>"
|
|
64
|
+
data-confirm="<%= t('AreYouSureDeleteGroup', count: @group.count) %>" />
|
|
65
|
+
</form>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<% if @page.truncated %>
|
|
69
|
+
<p><strong><%= t('ScanTruncated', max_scan: Sidekiq::ErrorGroups.config.max_scan) %></strong></p>
|
|
70
|
+
<% end %>
|
|
71
|
+
|
|
72
|
+
<div class="table_container">
|
|
73
|
+
<table class="table table-striped table-bordered table-white">
|
|
74
|
+
<thead>
|
|
75
|
+
<tr>
|
|
76
|
+
<th><%= t('Set') %></th>
|
|
77
|
+
<th><%= t('LastFailure') %></th>
|
|
78
|
+
<th><%= t('Queue') %></th>
|
|
79
|
+
<th><%= t('Job') %></th>
|
|
80
|
+
<% unless @selector.error_class %>
|
|
81
|
+
<th><%= t('ErrorClass') %></th>
|
|
82
|
+
<% end %>
|
|
83
|
+
<th><%= t('Arguments') %></th>
|
|
84
|
+
<th><%= t('Attempts') %></th>
|
|
85
|
+
</tr>
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody>
|
|
88
|
+
<% @page.jobs.each do |job| %>
|
|
89
|
+
<tr>
|
|
90
|
+
<td><%= h job.set.to_s %></td>
|
|
91
|
+
<td><a href="<%= eg_job_path(job) %>"><%= eg_time(job.last_failure_at) %></a></td>
|
|
92
|
+
<td><a href="<%= root_path %>queues/<%= h job.queue.to_s %>"><%= h job.queue.to_s %></a></td>
|
|
93
|
+
<td><%= h job.display_class %></td>
|
|
94
|
+
<% unless @selector.error_class %>
|
|
95
|
+
<td><%= h eg_label(job.error_class) %></td>
|
|
96
|
+
<% end %>
|
|
97
|
+
<td><%= display_args(job.item["args"]) %></td>
|
|
98
|
+
<td><%= job.retry_count %></td>
|
|
99
|
+
</tr>
|
|
100
|
+
<% end %>
|
|
101
|
+
</tbody>
|
|
102
|
+
</table>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<p>
|
|
106
|
+
<% if @page.page > 1 %>
|
|
107
|
+
<a href="<%= h eg_page_path(@selector, @page.page - 1) %>">← <%= t('PreviousPage') %></a>
|
|
108
|
+
<% end %>
|
|
109
|
+
<%= t('PageOf', page: @page.page, pages: @page.pages) %>
|
|
110
|
+
<% if @page.page < @page.pages %>
|
|
111
|
+
<a href="<%= h eg_page_path(@selector, @page.page + 1) %>"><%= t('NextPage') %> →</a>
|
|
112
|
+
<% end %>
|
|
113
|
+
</p>
|
|
114
|
+
<% end %>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<header class="row">
|
|
2
|
+
<div class="col-sm-5 pull-left flip">
|
|
3
|
+
<h3><%= t('ErrorGroups') %></h3>
|
|
4
|
+
</div>
|
|
5
|
+
</header>
|
|
6
|
+
|
|
7
|
+
<% if (notice = eg_notice) %>
|
|
8
|
+
<div class="alert alert-success"><%= h notice %></div>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<p>
|
|
12
|
+
<strong><%= t('GroupBy') %>:</strong>
|
|
13
|
+
<% eg_group_modes.each do |mode, label| %>
|
|
14
|
+
<% if mode == eg_group_by %>
|
|
15
|
+
<strong><%= h label %></strong>
|
|
16
|
+
<% else %>
|
|
17
|
+
<a href="<%= h eg_index_path(mode) %>"><%= h label %></a>
|
|
18
|
+
<% end %>
|
|
19
|
+
<%= "·" unless mode == eg_group_modes.last.first %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p>
|
|
24
|
+
<%= t('ScannedJobs', scanned: @summary.scanned, total: @summary.total) %>
|
|
25
|
+
<% @summary.set_sizes.each do |set, size| %>
|
|
26
|
+
· <%= h set.to_s %>: <%= size %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% if @summary.truncated? %>
|
|
29
|
+
<br/><strong><%= t('ScanTruncated', max_scan: Sidekiq::ErrorGroups.config.max_scan) %></strong>
|
|
30
|
+
<% end %>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<% if @summary.empty? %>
|
|
34
|
+
<div class="alert alert-success"><%= t('NoFailedJobs') %></div>
|
|
35
|
+
<% else %>
|
|
36
|
+
<div class="table_container">
|
|
37
|
+
<table class="table table-striped table-bordered table-white">
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<% if eg_group_by != :job %>
|
|
41
|
+
<th><%= t('ErrorClass') %></th>
|
|
42
|
+
<% end %>
|
|
43
|
+
<% if eg_group_by != :error %>
|
|
44
|
+
<th><%= t('JobClass') %></th>
|
|
45
|
+
<% end %>
|
|
46
|
+
<th><%= t('Total') %></th>
|
|
47
|
+
<th><%= t('Retries') %></th>
|
|
48
|
+
<th><%= t('Dead') %></th>
|
|
49
|
+
<th><%= t('LastFailure') %></th>
|
|
50
|
+
<th><%= t('FirstFailure') %></th>
|
|
51
|
+
<% if eg_group_by != :error_job %>
|
|
52
|
+
<th><%= (eg_group_by == :error) ? t('AffectedJobs') : t('AffectedErrors') %></th>
|
|
53
|
+
<% end %>
|
|
54
|
+
</tr>
|
|
55
|
+
</thead>
|
|
56
|
+
<tbody>
|
|
57
|
+
<% @summary.groups.each do |group| %>
|
|
58
|
+
<tr>
|
|
59
|
+
<% if eg_group_by != :job %>
|
|
60
|
+
<td>
|
|
61
|
+
<a href="<%= h eg_group_path(group.selector) %>"><%= h eg_label(group.error_class) %></a>
|
|
62
|
+
<% if group.sample && !group.sample.error_message.empty? %>
|
|
63
|
+
<br/><small><%= h truncate(group.sample.error_message, 120) %></small>
|
|
64
|
+
<% end %>
|
|
65
|
+
</td>
|
|
66
|
+
<% end %>
|
|
67
|
+
<% if eg_group_by != :error %>
|
|
68
|
+
<td><a href="<%= h eg_group_path(group.selector) %>"><%= h group.job_class.to_s %></a></td>
|
|
69
|
+
<% end %>
|
|
70
|
+
<td><%= group.count %></td>
|
|
71
|
+
<td><%= group.retry_count %></td>
|
|
72
|
+
<td><%= group.dead_count %></td>
|
|
73
|
+
<td><%= eg_time(group.last_failed_at) %></td>
|
|
74
|
+
<td><%= eg_time(group.first_failed_at) %></td>
|
|
75
|
+
<% if eg_group_by != :error_job %>
|
|
76
|
+
<td><small><%= h eg_breakdown(group) %></small></td>
|
|
77
|
+
<% end %>
|
|
78
|
+
</tr>
|
|
79
|
+
<% end %>
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
<% end %>
|
metadata
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sidekiq-error-groups
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- r3cha
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sidekiq
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
description: Adds an "Error groups" tab to the Sidekiq Web UI that groups retrying
|
|
27
|
+
and dead jobs by their error class, with per-group retry/delete/kill actions, plus
|
|
28
|
+
a Ruby API, a rake task and a CLI. Works with Sidekiq 6, 7 and 8.
|
|
29
|
+
email:
|
|
30
|
+
- frontandstart@gmail.com
|
|
31
|
+
executables:
|
|
32
|
+
- sidekiq-error-groups
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- CHANGELOG.md
|
|
37
|
+
- LICENSE.txt
|
|
38
|
+
- README.md
|
|
39
|
+
- exe/sidekiq-error-groups
|
|
40
|
+
- lib/sidekiq-error-groups.rb
|
|
41
|
+
- lib/sidekiq/error_groups.rb
|
|
42
|
+
- lib/sidekiq/error_groups/actions.rb
|
|
43
|
+
- lib/sidekiq/error_groups/aggregator.rb
|
|
44
|
+
- lib/sidekiq/error_groups/cli.rb
|
|
45
|
+
- lib/sidekiq/error_groups/compat.rb
|
|
46
|
+
- lib/sidekiq/error_groups/config.rb
|
|
47
|
+
- lib/sidekiq/error_groups/formatter.rb
|
|
48
|
+
- lib/sidekiq/error_groups/group.rb
|
|
49
|
+
- lib/sidekiq/error_groups/job.rb
|
|
50
|
+
- lib/sidekiq/error_groups/job_query.rb
|
|
51
|
+
- lib/sidekiq/error_groups/railtie.rb
|
|
52
|
+
- lib/sidekiq/error_groups/scanner.rb
|
|
53
|
+
- lib/sidekiq/error_groups/selector.rb
|
|
54
|
+
- lib/sidekiq/error_groups/summary.rb
|
|
55
|
+
- lib/sidekiq/error_groups/tasks.rake
|
|
56
|
+
- lib/sidekiq/error_groups/version.rb
|
|
57
|
+
- lib/sidekiq/error_groups/web.rb
|
|
58
|
+
- lib/sidekiq/error_groups/web_extension.rb
|
|
59
|
+
- sidekiq-error-groups.gemspec
|
|
60
|
+
- web/locales/en.yml
|
|
61
|
+
- web/locales/ru.yml
|
|
62
|
+
- web/views/group.erb
|
|
63
|
+
- web/views/index.erb
|
|
64
|
+
- web/views/legacy/group.erb
|
|
65
|
+
- web/views/legacy/index.erb
|
|
66
|
+
homepage: https://github.com/r3cha/sidekiq-error-groups
|
|
67
|
+
licenses:
|
|
68
|
+
- MIT
|
|
69
|
+
metadata:
|
|
70
|
+
homepage_uri: https://github.com/r3cha/sidekiq-error-groups
|
|
71
|
+
source_code_uri: https://github.com/r3cha/sidekiq-error-groups
|
|
72
|
+
changelog_uri: https://github.com/r3cha/sidekiq-error-groups/blob/main/CHANGELOG.md
|
|
73
|
+
bug_tracker_uri: https://github.com/r3cha/sidekiq-error-groups/issues
|
|
74
|
+
documentation_uri: https://github.com/r3cha/sidekiq-error-groups#readme
|
|
75
|
+
rubygems_mfa_required: 'true'
|
|
76
|
+
rdoc_options: []
|
|
77
|
+
require_paths:
|
|
78
|
+
- lib
|
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: 2.7.0
|
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
requirements: []
|
|
90
|
+
rubygems_version: 3.6.9
|
|
91
|
+
specification_version: 4
|
|
92
|
+
summary: Sidekiq Web UI tab with failed jobs grouped by error class.
|
|
93
|
+
test_files: []
|