rails_error_dashboard 0.1.28 → 0.1.30
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/README.md +50 -6
- data/app/controllers/rails_error_dashboard/errors_controller.rb +22 -0
- data/app/helpers/rails_error_dashboard/application_helper.rb +79 -7
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +149 -0
- data/app/models/rails_error_dashboard/application.rb +1 -1
- data/app/models/rails_error_dashboard/error_log.rb +44 -16
- data/app/views/layouts/rails_error_dashboard.html.erb +71 -1237
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +10 -2
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +76 -0
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +18 -82
- data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +70 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +9 -37
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +11 -37
- data/app/views/rails_error_dashboard/errors/index.html.erb +64 -31
- data/app/views/rails_error_dashboard/errors/overview.html.erb +181 -3
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +2 -1
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +286 -0
- data/app/views/rails_error_dashboard/errors/settings.html.erb +146 -480
- data/app/views/rails_error_dashboard/errors/show.html.erb +102 -76
- data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +188 -0
- data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +5 -0
- data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +3 -0
- data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +3 -0
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +4 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +3 -0
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +3 -0
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +3 -0
- data/db/migrate/20251225100236_create_error_occurrences.rb +3 -0
- data/db/migrate/20251225101920_create_cascade_patterns.rb +3 -0
- data/db/migrate/20251225102500_create_error_baselines.rb +3 -0
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +3 -0
- data/db/migrate/20251226020100_create_error_comments.rb +3 -0
- data/db/migrate/20251229111223_add_additional_performance_indexes.rb +4 -0
- data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +3 -0
- data/db/migrate/20260106094233_add_application_to_error_logs.rb +3 -0
- data/db/migrate/20260106094318_finalize_application_foreign_key.rb +5 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +32 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +37 -4
- data/lib/rails_error_dashboard/configuration.rb +160 -3
- data/lib/rails_error_dashboard/configuration_error.rb +24 -0
- data/lib/rails_error_dashboard/engine.rb +17 -0
- data/lib/rails_error_dashboard/helpers/user_model_detector.rb +138 -0
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +1 -2
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +19 -4
- data/lib/rails_error_dashboard/queries/errors_list.rb +27 -8
- data/lib/rails_error_dashboard/services/error_normalizer.rb +143 -0
- data/lib/rails_error_dashboard/services/git_blame_reader.rb +195 -0
- data/lib/rails_error_dashboard/services/github_link_generator.rb +159 -0
- data/lib/rails_error_dashboard/services/source_code_reader.rb +214 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +6 -0
- metadata +14 -10
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +0 -107
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +0 -625
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +0 -257
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +0 -203
- data/app/assets/stylesheets/rails_error_dashboard/application.css +0 -15
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +0 -7
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +0 -61
- data/app/views/layouts/rails_error_dashboard/application.html.erb +0 -55
|
@@ -75,9 +75,17 @@
|
|
|
75
75
|
</td>
|
|
76
76
|
<td onclick="window.location='<%= error_path(error) %>';">
|
|
77
77
|
<% if error.resolved? %>
|
|
78
|
-
<i class="bi bi-check-circle-fill text-success"
|
|
78
|
+
<i class="bi bi-check-circle-fill text-success"
|
|
79
|
+
data-bs-toggle="tooltip"
|
|
80
|
+
data-bs-placement="left"
|
|
81
|
+
data-bs-html="true"
|
|
82
|
+
title="<%= if error.resolution_comment.present?
|
|
83
|
+
"<strong>Resolved</strong><br><em>#{error.resolution_comment.truncate(100).gsub('"', '"').gsub("'", ''')}</em>"
|
|
84
|
+
else
|
|
85
|
+
'Resolved'
|
|
86
|
+
end %>"></i>
|
|
79
87
|
<% else %>
|
|
80
|
-
<i class="bi bi-exclamation-circle-fill text-danger" title="Unresolved"></i>
|
|
88
|
+
<i class="bi bi-exclamation-circle-fill text-danger" data-bs-toggle="tooltip" title="Unresolved"></i>
|
|
81
89
|
<% end %>
|
|
82
90
|
</td>
|
|
83
91
|
<td onclick="event.stopPropagation();">
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<%# Displays source code for a backtrace frame with git blame and repository links %>
|
|
2
|
+
<%# Usage: render "source_code", frame: frame, error: @error, index: 0 %>
|
|
3
|
+
|
|
4
|
+
<% if RailsErrorDashboard.configuration.enable_source_code_integration %>
|
|
5
|
+
<% source_data = read_source_code(frame) %>
|
|
6
|
+
<% blame_data = read_git_blame(frame) if RailsErrorDashboard.configuration.enable_git_blame %>
|
|
7
|
+
<% repo_link = generate_repository_link(frame, error) %>
|
|
8
|
+
|
|
9
|
+
<% if source_data && source_data[:lines].present? %>
|
|
10
|
+
<div class="source-code-viewer mt-2 mb-3 border rounded shadow-sm">
|
|
11
|
+
<!-- Header with file info and actions -->
|
|
12
|
+
<div class="source-code-header bg-light border-bottom">
|
|
13
|
+
<!-- Top row: File path and View Source button -->
|
|
14
|
+
<div class="d-flex justify-content-between align-items-center px-3 py-2 border-bottom">
|
|
15
|
+
<div class="d-flex align-items-center gap-2">
|
|
16
|
+
<i class="bi bi-file-code text-primary"></i>
|
|
17
|
+
<span class="font-monospace source-file-path fw-medium" style="font-size: 0.9rem;">
|
|
18
|
+
<%= frame[:short_path] %>:<%= frame[:line_number] %>
|
|
19
|
+
</span>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<% if repo_link %>
|
|
23
|
+
<%= link_to repo_link, target: "_blank", rel: "noopener",
|
|
24
|
+
class: "btn btn-sm btn-primary",
|
|
25
|
+
title: "View on #{repo_link.include?('github') ? 'GitHub' : repo_link.include?('gitlab') ? 'GitLab' : 'Bitbucket'}" do %>
|
|
26
|
+
<i class="bi bi-box-arrow-up-right"></i> View Source
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Bottom row: Git blame info (if available) -->
|
|
32
|
+
<% if blame_data %>
|
|
33
|
+
<div class="git-blame-info px-3 py-2 bg-white">
|
|
34
|
+
<div class="d-flex align-items-center gap-3 text-muted" style="font-size: 0.85rem;">
|
|
35
|
+
<div class="d-flex align-items-center gap-1">
|
|
36
|
+
<i class="bi bi-person-circle"></i>
|
|
37
|
+
<span><%= blame_data[:author] %></span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="d-flex align-items-center gap-1">
|
|
40
|
+
<i class="bi bi-clock"></i>
|
|
41
|
+
<span><%= time_ago_in_words(blame_data[:date]) %> ago</span>
|
|
42
|
+
</div>
|
|
43
|
+
<% if blame_data[:commit_message] %>
|
|
44
|
+
<div class="d-flex align-items-center gap-1 flex-grow-1">
|
|
45
|
+
<i class="bi bi-chat-left-text"></i>
|
|
46
|
+
<span class="text-truncate" style="max-width: 400px;" title="<%= blame_data[:commit_message] %>">
|
|
47
|
+
<%= blame_data[:commit_message] %>
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Source code lines with syntax highlighting -->
|
|
57
|
+
<div class="source-code-content bg-white">
|
|
58
|
+
<div class="code-block" style="max-height: 500px; overflow-y: auto; overflow-x: auto;">
|
|
59
|
+
<%
|
|
60
|
+
# Find the error line number for highlighting
|
|
61
|
+
error_line_number = source_data[:lines].find { |l| l[:highlight] }&.dig(:number)
|
|
62
|
+
# Get start line number (first line in context)
|
|
63
|
+
start_line = source_data[:lines].first[:number]
|
|
64
|
+
%>
|
|
65
|
+
<pre class="mb-0"><code class="language-<%= source_data[:language] || 'plaintext' %>" data-error-line="<%= error_line_number %>" data-start-line="<%= start_line %>"><%= source_data[:lines].map { |l| l[:content] }.join("\n") %></code></pre>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<% elsif source_data && source_data[:error] %>
|
|
70
|
+
<!-- Error message if source couldn't be read -->
|
|
71
|
+
<div class="alert alert-warning mt-2 mb-2 py-2">
|
|
72
|
+
<i class="bi bi-exclamation-triangle"></i>
|
|
73
|
+
<small>Could not read source: <%= source_data[:error] %></small>
|
|
74
|
+
</div>
|
|
75
|
+
<% end %>
|
|
76
|
+
<% end %>
|
|
@@ -74,96 +74,32 @@
|
|
|
74
74
|
<% end %>
|
|
75
75
|
|
|
76
76
|
<% if error.resolved? %>
|
|
77
|
-
<span class="badge bg-success"
|
|
77
|
+
<span class="badge bg-success"
|
|
78
|
+
<% if error.resolution_comment.present? %>
|
|
79
|
+
data-bs-toggle="tooltip"
|
|
80
|
+
data-bs-placement="top"
|
|
81
|
+
data-bs-html="true"
|
|
82
|
+
title="<strong>Resolution Notes:</strong><br><%= error.resolution_comment.truncate(150).gsub('"', '"') %>"
|
|
83
|
+
<% end %>>
|
|
78
84
|
<i class="bi bi-check-circle"></i> Resolved
|
|
79
85
|
</span>
|
|
80
86
|
<% end %>
|
|
81
87
|
</div>
|
|
88
|
+
|
|
89
|
+
<% if error.resolved? && error.resolution_comment.present? %>
|
|
90
|
+
<div class="mt-2 p-2 bg-success bg-opacity-10 border border-success rounded">
|
|
91
|
+
<small class="text-success fw-bold">
|
|
92
|
+
<i class="bi bi-journal-text"></i> Resolution Notes:
|
|
93
|
+
</small>
|
|
94
|
+
<div class="mb-0 small text-muted mt-1">
|
|
95
|
+
<%= auto_link_urls(truncate(error.resolution_comment, length: 200), error: error).html_safe %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<% end %>
|
|
82
99
|
</div>
|
|
83
100
|
</div>
|
|
84
101
|
<% end %>
|
|
85
102
|
</div>
|
|
86
103
|
</div>
|
|
87
104
|
</div>
|
|
88
|
-
|
|
89
|
-
<style>
|
|
90
|
-
.timeline {
|
|
91
|
-
position: relative;
|
|
92
|
-
padding-left: 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.timeline-item {
|
|
96
|
-
position: relative;
|
|
97
|
-
padding-left: 40px;
|
|
98
|
-
padding-bottom: 30px;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.timeline-item-last {
|
|
102
|
-
padding-bottom: 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.timeline-item::before {
|
|
106
|
-
content: '';
|
|
107
|
-
position: absolute;
|
|
108
|
-
left: 11px;
|
|
109
|
-
top: 30px;
|
|
110
|
-
bottom: 0;
|
|
111
|
-
width: 2px;
|
|
112
|
-
background: var(--bs-border-color);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.timeline-item-last::before {
|
|
116
|
-
display: none;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.timeline-marker {
|
|
120
|
-
position: absolute;
|
|
121
|
-
left: 0;
|
|
122
|
-
top: 0;
|
|
123
|
-
width: 24px;
|
|
124
|
-
height: 24px;
|
|
125
|
-
display: flex;
|
|
126
|
-
align-items: center;
|
|
127
|
-
justify-content: center;
|
|
128
|
-
background: white;
|
|
129
|
-
border: 2px solid var(--bs-border-color);
|
|
130
|
-
border-radius: 50%;
|
|
131
|
-
z-index: 1;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.timeline-marker-current {
|
|
135
|
-
border-color: var(--bs-danger);
|
|
136
|
-
background: var(--bs-danger-bg-subtle);
|
|
137
|
-
box-shadow: 0 0 0 4px var(--bs-danger-bg-subtle);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.timeline-marker i {
|
|
141
|
-
font-size: 12px;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.timeline-content {
|
|
145
|
-
background: var(--bs-body-bg);
|
|
146
|
-
padding: 12px;
|
|
147
|
-
border-radius: 8px;
|
|
148
|
-
border: 1px solid var(--bs-border-color);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.timeline-item:hover .timeline-content {
|
|
152
|
-
border-color: var(--bs-primary);
|
|
153
|
-
background: var(--bs-primary-bg-subtle);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* Dark mode support */
|
|
157
|
-
body.dark-mode .timeline-marker,
|
|
158
|
-
html[data-theme="dark"] body .timeline-marker {
|
|
159
|
-
background: var(--card-bg);
|
|
160
|
-
border-color: var(--border-color);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
body.dark-mode .timeline-marker-current,
|
|
164
|
-
html[data-theme="dark"] body .timeline-marker-current {
|
|
165
|
-
border-color: var(--bs-danger);
|
|
166
|
-
background: rgba(220, 53, 69, 0.2);
|
|
167
|
-
}
|
|
168
|
-
</style>
|
|
169
105
|
<% end %>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div class="table-responsive">
|
|
2
|
+
<table class="table table-hover mb-0">
|
|
3
|
+
<thead class="table-light">
|
|
4
|
+
<tr>
|
|
5
|
+
<% if show_rank %>
|
|
6
|
+
<th>Rank</th>
|
|
7
|
+
<% end %>
|
|
8
|
+
<th>User</th>
|
|
9
|
+
<% if show_error_type_count %>
|
|
10
|
+
<th>Different Error Types</th>
|
|
11
|
+
<% end %>
|
|
12
|
+
<th>Error Count</th>
|
|
13
|
+
<% if show_percentage %>
|
|
14
|
+
<th>Percentage</th>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if show_error_types %>
|
|
17
|
+
<th>Error Types</th>
|
|
18
|
+
<% end %>
|
|
19
|
+
<th>Actions</th>
|
|
20
|
+
</tr>
|
|
21
|
+
</thead>
|
|
22
|
+
<tbody>
|
|
23
|
+
<% users.each_with_index do |user_data, index| %>
|
|
24
|
+
<tr>
|
|
25
|
+
<% if show_rank %>
|
|
26
|
+
<td><strong>#<%= index + 1 %></strong></td>
|
|
27
|
+
<% end %>
|
|
28
|
+
<td><%= user_data[:email] || user_data[:user_email] %></td>
|
|
29
|
+
<% if show_error_type_count %>
|
|
30
|
+
<td>
|
|
31
|
+
<span class="badge bg-warning text-dark">
|
|
32
|
+
<%= user_data[:error_type_count] %> types
|
|
33
|
+
</span>
|
|
34
|
+
</td>
|
|
35
|
+
<% end %>
|
|
36
|
+
<td>
|
|
37
|
+
<span class="badge bg-danger"><%= user_data[:count] || user_data[:total_errors] %></span>
|
|
38
|
+
</td>
|
|
39
|
+
<% if show_percentage && total_errors.present? %>
|
|
40
|
+
<td>
|
|
41
|
+
<div class="progress" style="height: 20px;">
|
|
42
|
+
<div class="progress-bar bg-danger"
|
|
43
|
+
role="progressbar"
|
|
44
|
+
style="width: <%= ((user_data[:count] || user_data[:total_errors]).to_f / total_errors * 100).round(1) %>%"
|
|
45
|
+
aria-valuenow="<%= user_data[:count] || user_data[:total_errors] %>"
|
|
46
|
+
aria-valuemin="0"
|
|
47
|
+
aria-valuemax="100">
|
|
48
|
+
<%= ((user_data[:count] || user_data[:total_errors]).to_f / total_errors * 100).round(1) %>%
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</td>
|
|
52
|
+
<% end %>
|
|
53
|
+
<% if show_error_types && user_data[:error_types].present? %>
|
|
54
|
+
<td>
|
|
55
|
+
<% user_data[:error_types].first(3).each do |error_type| %>
|
|
56
|
+
<code class="small me-1"><%= error_type %></code>
|
|
57
|
+
<% end %>
|
|
58
|
+
<% if user_data[:error_types].count > 3 %>
|
|
59
|
+
<span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
|
|
60
|
+
<% end %>
|
|
61
|
+
</td>
|
|
62
|
+
<% end %>
|
|
63
|
+
<td>
|
|
64
|
+
<%= link_to "View Errors", errors_path(user_id: user_data[:user_id]), class: "btn btn-sm btn-outline-primary" %>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
@@ -266,43 +266,15 @@
|
|
|
266
266
|
<h5 class="mb-0"><i class="bi bi-people"></i> Top 10 Affected Users</h5>
|
|
267
267
|
</div>
|
|
268
268
|
<div class="card-body p-0">
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
</tr>
|
|
279
|
-
</thead>
|
|
280
|
-
<tbody>
|
|
281
|
-
<% @top_users.each_with_index do |(email, count), index| %>
|
|
282
|
-
<tr>
|
|
283
|
-
<td><strong>#<%= index + 1 %></strong></td>
|
|
284
|
-
<td><%= email %></td>
|
|
285
|
-
<td><span class="badge bg-danger"><%= count %></span></td>
|
|
286
|
-
<td>
|
|
287
|
-
<div class="progress" style="height: 20px;">
|
|
288
|
-
<div class="progress-bar bg-danger"
|
|
289
|
-
role="progressbar"
|
|
290
|
-
style="width: <%= (count.to_f / @error_stats[:total] * 100).round(1) %>%"
|
|
291
|
-
aria-valuenow="<%= count %>"
|
|
292
|
-
aria-valuemin="0"
|
|
293
|
-
aria-valuemax="<%= @error_stats[:total] %>">
|
|
294
|
-
<%= (count.to_f / @error_stats[:total] * 100).round(1) %>%
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
</td>
|
|
298
|
-
<td>
|
|
299
|
-
<%= link_to "View Errors", errors_path(search: email), class: "btn btn-sm btn-outline-primary" %>
|
|
300
|
-
</td>
|
|
301
|
-
</tr>
|
|
302
|
-
<% end %>
|
|
303
|
-
</tbody>
|
|
304
|
-
</table>
|
|
305
|
-
</div>
|
|
269
|
+
<%= render partial: 'user_errors_table',
|
|
270
|
+
locals: {
|
|
271
|
+
users: @top_users,
|
|
272
|
+
show_rank: true,
|
|
273
|
+
show_error_type_count: false,
|
|
274
|
+
show_percentage: true,
|
|
275
|
+
show_error_types: false,
|
|
276
|
+
total_errors: @error_stats[:total]
|
|
277
|
+
} %>
|
|
306
278
|
</div>
|
|
307
279
|
</div>
|
|
308
280
|
</div>
|
|
@@ -216,43 +216,17 @@
|
|
|
216
216
|
<small class="text-muted">Users experiencing 2+ different error types</small>
|
|
217
217
|
</div>
|
|
218
218
|
<div class="card-body">
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<tbody>
|
|
231
|
-
<% @multi_error_users.first(20).each do |user_data| %>
|
|
232
|
-
<tr>
|
|
233
|
-
<td><%= user_data[:user_email] %></td>
|
|
234
|
-
<td>
|
|
235
|
-
<span class="badge bg-warning text-dark">
|
|
236
|
-
<%= user_data[:error_type_count] %> types
|
|
237
|
-
</span>
|
|
238
|
-
</td>
|
|
239
|
-
<td><%= user_data[:total_errors] %></td>
|
|
240
|
-
<td>
|
|
241
|
-
<% user_data[:error_types].first(3).each do |error_type| %>
|
|
242
|
-
<code class="small me-1"><%= error_type %></code>
|
|
243
|
-
<% end %>
|
|
244
|
-
<% if user_data[:error_types].count > 3 %>
|
|
245
|
-
<span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
|
|
246
|
-
<% end %>
|
|
247
|
-
</td>
|
|
248
|
-
<td>
|
|
249
|
-
<%= link_to "View", errors_path(search: user_data[:user_email]), class: "btn btn-sm btn-outline-primary" %>
|
|
250
|
-
</td>
|
|
251
|
-
</tr>
|
|
252
|
-
<% end %>
|
|
253
|
-
</tbody>
|
|
254
|
-
</table>
|
|
255
|
-
</div>
|
|
219
|
+
<%= render partial: 'user_errors_table',
|
|
220
|
+
locals: {
|
|
221
|
+
users: @multi_error_users.first(20),
|
|
222
|
+
show_rank: false,
|
|
223
|
+
show_error_type_count: true,
|
|
224
|
+
show_percentage: false,
|
|
225
|
+
show_error_types: true,
|
|
226
|
+
total_errors: nil
|
|
227
|
+
} %>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="card-body pt-0 border-top">
|
|
256
230
|
<% if @multi_error_users.count > 20 %>
|
|
257
231
|
<p class="text-muted small mb-0">
|
|
258
232
|
Showing 20 of <%= @multi_error_users.count %> users
|
|
@@ -148,6 +148,10 @@
|
|
|
148
148
|
active_filters << { label: "Unassigned", param: :assigned_to }
|
|
149
149
|
elsif params[:assigned_to] == '__assigned__'
|
|
150
150
|
active_filters << { label: "Assigned", param: :assigned_to }
|
|
151
|
+
# Show assignee name filter if present
|
|
152
|
+
if params[:assignee_name].present?
|
|
153
|
+
active_filters << { label: "Assignee: #{params[:assignee_name]}", param: :assignee_name }
|
|
154
|
+
end
|
|
151
155
|
end
|
|
152
156
|
%>
|
|
153
157
|
|
|
@@ -185,8 +189,6 @@
|
|
|
185
189
|
class: "btn btn-sm #{params[:assigned_to].blank? ? 'btn-primary' : 'btn-outline-primary'}" %>
|
|
186
190
|
<%= link_to "Unassigned", errors_path(assigned_to: '__unassigned__'),
|
|
187
191
|
class: "btn btn-sm #{params[:assigned_to] == '__unassigned__' ? 'btn-primary' : 'btn-outline-primary'}" %>
|
|
188
|
-
<%= link_to "My Errors", errors_path(assigned_to: current_user_name),
|
|
189
|
-
class: "btn btn-sm #{params[:assigned_to] == current_user_name ? 'btn-primary' : 'btn-outline-primary'}" %>
|
|
190
192
|
</div>
|
|
191
193
|
|
|
192
194
|
<%= form_with url: errors_path, method: :get, class: "row g-3", data: { turbo: false } do %>
|
|
@@ -268,44 +270,54 @@
|
|
|
268
270
|
['All Assignments', ''],
|
|
269
271
|
['Unassigned', '__unassigned__'],
|
|
270
272
|
['Assigned', '__assigned__']
|
|
271
|
-
], params[:assigned_to]), class: "form-select" %>
|
|
273
|
+
], params[:assigned_to]), class: "form-select", id: "assigned_to_filter" %>
|
|
272
274
|
</div>
|
|
273
275
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
['
|
|
278
|
-
[
|
|
279
|
-
|
|
280
|
-
['⚪ Low (P0)', 0]
|
|
281
|
-
], params[:priority_level]), class: "form-select" %>
|
|
276
|
+
<!-- Assignee Name Filter - Only shown when "Assigned" is selected -->
|
|
277
|
+
<div class="col-md-2" id="assignee_name_filter" style="<%= params[:assigned_to] == '__assigned__' ? '' : 'display: none;' %>">
|
|
278
|
+
<%= select_tag :assignee_name, options_for_select(
|
|
279
|
+
[['All Assignees', '']] + @assignees.map { |name| [name, name] },
|
|
280
|
+
params[:assignee_name]
|
|
281
|
+
), class: "form-select", placeholder: "Filter by assignee..." %>
|
|
282
282
|
</div>
|
|
283
283
|
|
|
284
|
-
<div class="col-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
# If unresolved param is explicitly "false" or "0", uncheck it
|
|
290
|
-
is_checked = if params[:unresolved].nil?
|
|
291
|
-
true # Default to checked when first loading
|
|
292
|
-
else
|
|
293
|
-
params[:unresolved] != "false" && params[:unresolved] != "0"
|
|
294
|
-
end
|
|
295
|
-
%>
|
|
296
|
-
<%= check_box_tag :unresolved, "1", is_checked, class: "form-check-input", id: "unresolved_checkbox" %>
|
|
297
|
-
<%= label_tag :unresolved, "Unresolved only", class: "form-check-label" %>
|
|
298
|
-
</div>
|
|
284
|
+
<div class="col-md-2">
|
|
285
|
+
<%= select_tag :priority_level, options_for_select(
|
|
286
|
+
[['All Priorities', '']] + RailsErrorDashboard::ErrorLog.priority_options(include_emoji: true),
|
|
287
|
+
params[:priority_level]
|
|
288
|
+
), class: "form-select" %>
|
|
299
289
|
</div>
|
|
300
290
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
291
|
+
<!-- Checkboxes on their own row -->
|
|
292
|
+
<div class="col-12 mt-2">
|
|
293
|
+
<div class="row g-3">
|
|
294
|
+
<div class="col-auto">
|
|
295
|
+
<div class="form-check">
|
|
296
|
+
<%
|
|
297
|
+
# Determine checkbox state based on params
|
|
298
|
+
# If unresolved param is missing or nil, default to checked (true)
|
|
299
|
+
# If unresolved param is explicitly "false" or "0", uncheck it
|
|
300
|
+
is_checked = if params[:unresolved].nil?
|
|
301
|
+
true # Default to checked when first loading
|
|
302
|
+
else
|
|
303
|
+
params[:unresolved] != "false" && params[:unresolved] != "0"
|
|
304
|
+
end
|
|
305
|
+
%>
|
|
306
|
+
<%= check_box_tag :unresolved, "1", is_checked, class: "form-check-input", id: "unresolved_checkbox" %>
|
|
307
|
+
<%= label_tag :unresolved, "Unresolved only", class: "form-check-label" %>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div class="col-auto">
|
|
312
|
+
<div class="form-check">
|
|
313
|
+
<%= check_box_tag :hide_snoozed, "1", params[:hide_snoozed] == "1", class: "form-check-input" %>
|
|
314
|
+
<%= label_tag :hide_snoozed, "Hide snoozed", class: "form-check-label" %>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
305
317
|
</div>
|
|
306
318
|
</div>
|
|
307
319
|
|
|
308
|
-
<div class="col-12">
|
|
320
|
+
<div class="col-12 mt-3">
|
|
309
321
|
<%= submit_tag "Apply Filters", class: "btn btn-primary" %>
|
|
310
322
|
<%= link_to "Clear", errors_path, class: "btn btn-outline-secondary" %>
|
|
311
323
|
</div>
|
|
@@ -600,4 +612,25 @@
|
|
|
600
612
|
document.title = `(${unresolvedCount}) ${document.title}`;
|
|
601
613
|
}
|
|
602
614
|
});
|
|
615
|
+
|
|
616
|
+
// Toggle assignee name filter visibility based on assigned_to selection
|
|
617
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
618
|
+
const assignedToFilter = document.getElementById('assigned_to_filter');
|
|
619
|
+
const assigneeNameFilter = document.getElementById('assignee_name_filter');
|
|
620
|
+
|
|
621
|
+
if (assignedToFilter && assigneeNameFilter) {
|
|
622
|
+
assignedToFilter.addEventListener('change', function() {
|
|
623
|
+
if (this.value === '__assigned__') {
|
|
624
|
+
assigneeNameFilter.style.display = 'block';
|
|
625
|
+
} else {
|
|
626
|
+
assigneeNameFilter.style.display = 'none';
|
|
627
|
+
// Reset assignee name filter when hiding
|
|
628
|
+
const assigneeNameSelect = assigneeNameFilter.querySelector('select');
|
|
629
|
+
if (assigneeNameSelect) {
|
|
630
|
+
assigneeNameSelect.value = '';
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
603
636
|
</script>
|