rails_error_dashboard 0.1.3 → 0.1.5
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 +26 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css +15 -926
- data/app/controllers/rails_error_dashboard/errors_controller.rb +46 -1
- data/app/helpers/rails_error_dashboard/application_helper.rb +38 -0
- data/app/models/rails_error_dashboard/error_log.rb +14 -0
- data/app/views/layouts/rails_error_dashboard/application.html.erb +39 -1
- data/app/views/layouts/rails_error_dashboard.html.erb +20 -2
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +317 -0
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +8 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +44 -7
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +3 -0
- data/app/views/rails_error_dashboard/errors/settings.html.erb +575 -0
- data/config/routes.rb +7 -1
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +317 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +77 -5
- data/lib/rails_error_dashboard/queries/mttr_stats.rb +111 -0
- data/lib/rails_error_dashboard/queries/recurring_issues.rb +97 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +3 -0
- data/lib/tasks/rails_error_dashboard_tasks.rake +85 -0
- metadata +21 -2
|
@@ -146,6 +146,22 @@ module RailsErrorDashboard
|
|
|
146
146
|
@resolution_rate = analytics[:resolution_rate]
|
|
147
147
|
@mobile_errors = analytics[:mobile_errors]
|
|
148
148
|
@api_errors = analytics[:api_errors]
|
|
149
|
+
|
|
150
|
+
# Get recurring issues data
|
|
151
|
+
recurring = Queries::RecurringIssues.call(days)
|
|
152
|
+
@recurring_data = recurring
|
|
153
|
+
|
|
154
|
+
# Get release correlation data
|
|
155
|
+
correlation = Queries::ErrorCorrelation.new(days: days)
|
|
156
|
+
@errors_by_version = correlation.errors_by_version
|
|
157
|
+
@problematic_releases = correlation.problematic_releases
|
|
158
|
+
@release_comparison = calculate_release_comparison
|
|
159
|
+
|
|
160
|
+
# Get MTTR data
|
|
161
|
+
mttr_data = Queries::MttrStats.call(days)
|
|
162
|
+
@mttr_stats = mttr_data
|
|
163
|
+
@overall_mttr = mttr_data[:overall_mttr]
|
|
164
|
+
@mttr_by_platform = mttr_data[:mttr_by_platform]
|
|
149
165
|
end
|
|
150
166
|
|
|
151
167
|
def platform_comparison
|
|
@@ -219,8 +235,32 @@ module RailsErrorDashboard
|
|
|
219
235
|
@platform_specific_errors = correlation.platform_specific_errors
|
|
220
236
|
end
|
|
221
237
|
|
|
238
|
+
def settings
|
|
239
|
+
@config = RailsErrorDashboard.configuration
|
|
240
|
+
end
|
|
241
|
+
|
|
222
242
|
private
|
|
223
243
|
|
|
244
|
+
def calculate_release_comparison
|
|
245
|
+
return {} if @errors_by_version.empty? || @errors_by_version.count < 2
|
|
246
|
+
|
|
247
|
+
versions_sorted = @errors_by_version.sort_by { |_, data| data[:last_seen] || Time.at(0) }.reverse
|
|
248
|
+
latest = versions_sorted.first
|
|
249
|
+
previous = versions_sorted.second
|
|
250
|
+
|
|
251
|
+
return {} if latest.nil? || previous.nil?
|
|
252
|
+
|
|
253
|
+
{
|
|
254
|
+
latest_version: latest[0],
|
|
255
|
+
latest_count: latest[1][:count],
|
|
256
|
+
latest_critical: latest[1][:critical_count],
|
|
257
|
+
previous_version: previous[0],
|
|
258
|
+
previous_count: previous[1][:count],
|
|
259
|
+
previous_critical: previous[1][:critical_count],
|
|
260
|
+
change_percentage: previous[1][:count] > 0 ? ((latest[1][:count] - previous[1][:count]).to_f / previous[1][:count] * 100).round(1) : 0.0
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
|
|
224
264
|
def filter_params
|
|
225
265
|
{
|
|
226
266
|
error_type: params[:error_type],
|
|
@@ -228,11 +268,16 @@ module RailsErrorDashboard
|
|
|
228
268
|
platform: params[:platform],
|
|
229
269
|
search: params[:search],
|
|
230
270
|
severity: params[:severity],
|
|
271
|
+
timeframe: params[:timeframe],
|
|
272
|
+
frequency: params[:frequency],
|
|
231
273
|
# Phase 3: Workflow filter params
|
|
232
274
|
status: params[:status],
|
|
233
275
|
assigned_to: params[:assigned_to],
|
|
234
276
|
priority_level: params[:priority_level],
|
|
235
|
-
hide_snoozed: params[:hide_snoozed]
|
|
277
|
+
hide_snoozed: params[:hide_snoozed],
|
|
278
|
+
# Sorting params
|
|
279
|
+
sort_by: params[:sort_by],
|
|
280
|
+
sort_direction: params[:sort_direction]
|
|
236
281
|
}
|
|
237
282
|
end
|
|
238
283
|
|
|
@@ -55,5 +55,43 @@ module RailsErrorDashboard
|
|
|
55
55
|
"var(--text-color)"
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# Returns the current user name for filtering "My Errors"
|
|
60
|
+
# Uses configured dashboard username or system username
|
|
61
|
+
# @return [String] Current user identifier
|
|
62
|
+
def current_user_name
|
|
63
|
+
RailsErrorDashboard.configuration.dashboard_username || ENV["USER"] || "unknown"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Generates a sortable column header link
|
|
67
|
+
# @param label [String] The column label to display
|
|
68
|
+
# @param column [String] The column name to sort by
|
|
69
|
+
# @return [String] HTML safe link with sort indicator
|
|
70
|
+
def sortable_header(label, column)
|
|
71
|
+
current_sort = params[:sort_by]
|
|
72
|
+
current_direction = params[:sort_direction] || "desc"
|
|
73
|
+
|
|
74
|
+
# Determine new direction: if clicking same column, toggle; otherwise default to desc
|
|
75
|
+
new_direction = if current_sort == column
|
|
76
|
+
current_direction == "asc" ? "desc" : "asc"
|
|
77
|
+
else
|
|
78
|
+
"desc"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Choose icon based on current state
|
|
82
|
+
icon = if current_sort == column
|
|
83
|
+
current_direction == "asc" ? "▲" : "▼"
|
|
84
|
+
else
|
|
85
|
+
"⇅" # Unsorted indicator
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Preserve existing filter params while adding sort params
|
|
89
|
+
link_params = params.permit!.to_h.merge(sort_by: column, sort_direction: new_direction)
|
|
90
|
+
|
|
91
|
+
link_to errors_path(link_params), class: "text-decoration-none" do
|
|
92
|
+
content_tag(:span, "#{label} ", class: current_sort == column ? "fw-bold" : "") +
|
|
93
|
+
content_tag(:span, icon, class: "text-muted small")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
58
96
|
end
|
|
59
97
|
end
|
|
@@ -133,6 +133,11 @@ module RailsErrorDashboard
|
|
|
133
133
|
SystemStackError
|
|
134
134
|
SignalException
|
|
135
135
|
ActiveRecord::StatementInvalid
|
|
136
|
+
LoadError
|
|
137
|
+
SyntaxError
|
|
138
|
+
ActiveRecord::ConnectionNotEstablished
|
|
139
|
+
Redis::ConnectionError
|
|
140
|
+
OpenSSL::SSL::SSLError
|
|
136
141
|
].freeze
|
|
137
142
|
|
|
138
143
|
HIGH_SEVERITY_ERROR_TYPES = %w[
|
|
@@ -141,6 +146,11 @@ module RailsErrorDashboard
|
|
|
141
146
|
TypeError
|
|
142
147
|
NoMethodError
|
|
143
148
|
NameError
|
|
149
|
+
ZeroDivisionError
|
|
150
|
+
FloatDomainError
|
|
151
|
+
IndexError
|
|
152
|
+
KeyError
|
|
153
|
+
RangeError
|
|
144
154
|
].freeze
|
|
145
155
|
|
|
146
156
|
MEDIUM_SEVERITY_ERROR_TYPES = %w[
|
|
@@ -148,6 +158,10 @@ module RailsErrorDashboard
|
|
|
148
158
|
Timeout::Error
|
|
149
159
|
Net::ReadTimeout
|
|
150
160
|
Net::OpenTimeout
|
|
161
|
+
ActiveRecord::RecordNotUnique
|
|
162
|
+
JSON::ParserError
|
|
163
|
+
CSV::MalformedCSVError
|
|
164
|
+
Errno::ECONNREFUSED
|
|
151
165
|
].freeze
|
|
152
166
|
|
|
153
167
|
# Find existing error by hash or create new one
|
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>Rails
|
|
4
|
+
<title>Rails Error Dashboard</title>
|
|
5
5
|
<%= csrf_meta_tags %>
|
|
6
6
|
<%= csp_meta_tag %>
|
|
7
7
|
|
|
8
8
|
<%= yield :head %>
|
|
9
9
|
|
|
10
10
|
<%= stylesheet_link_tag "rails_error_dashboard/application", media: "all" %>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
|
|
11
13
|
</head>
|
|
12
14
|
<body>
|
|
13
15
|
|
|
16
|
+
<!-- Navigation -->
|
|
17
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
18
|
+
<div class="container-fluid">
|
|
19
|
+
<%= link_to "Error Dashboard", root_path, class: "navbar-brand" %>
|
|
20
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
21
|
+
<span class="navbar-toggler-icon"></span>
|
|
22
|
+
</button>
|
|
23
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
24
|
+
<ul class="navbar-nav me-auto">
|
|
25
|
+
<li class="nav-item">
|
|
26
|
+
<%= link_to overview_path, class: "nav-link #{controller_name == 'errors' && action_name == 'overview' ? 'active' : ''}" do %>
|
|
27
|
+
<i class="bi bi-speedometer2"></i> Dashboard
|
|
28
|
+
<% end %>
|
|
29
|
+
</li>
|
|
30
|
+
<li class="nav-item">
|
|
31
|
+
<%= link_to errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'index' ? 'active' : ''}" do %>
|
|
32
|
+
<i class="bi bi-list-ul"></i> Errors
|
|
33
|
+
<% end %>
|
|
34
|
+
</li>
|
|
35
|
+
<li class="nav-item">
|
|
36
|
+
<%= link_to analytics_errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'analytics' ? 'active' : ''}" do %>
|
|
37
|
+
<i class="bi bi-graph-up"></i> Analytics
|
|
38
|
+
<% end %>
|
|
39
|
+
</li>
|
|
40
|
+
</ul>
|
|
41
|
+
<ul class="navbar-nav">
|
|
42
|
+
<li class="nav-item">
|
|
43
|
+
<a class="nav-link" href="#" id="theme-toggle">
|
|
44
|
+
<i class="bi bi-moon-stars"></i>
|
|
45
|
+
</a>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</nav>
|
|
51
|
+
|
|
14
52
|
<%= yield %>
|
|
15
53
|
|
|
16
54
|
</body>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title
|
|
4
|
+
<title><%= Rails.application.class.module_parent_name %> - Error Dashboard</title>
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<%= csrf_meta_tags %>
|
|
7
7
|
<%= csp_meta_tag %>
|
|
@@ -736,7 +736,10 @@
|
|
|
736
736
|
<i class="bi bi-list fs-4"></i>
|
|
737
737
|
</button>
|
|
738
738
|
<a class="navbar-brand" href="<%= root_path %>">
|
|
739
|
-
<i class="bi bi-bug-fill"></i>
|
|
739
|
+
<i class="bi bi-bug-fill"></i>
|
|
740
|
+
<span class="d-none d-sm-inline"><%= Rails.application.class.module_parent_name %></span>
|
|
741
|
+
<span class="d-none d-md-inline text-white-50 mx-2">|</span>
|
|
742
|
+
<span class="d-none d-md-inline">Error Dashboard</span>
|
|
740
743
|
</a>
|
|
741
744
|
</div>
|
|
742
745
|
<div class="d-flex align-items-center gap-3">
|
|
@@ -771,6 +774,11 @@
|
|
|
771
774
|
<i class="bi bi-graph-up"></i> Analytics
|
|
772
775
|
<% end %>
|
|
773
776
|
</li>
|
|
777
|
+
<li class="nav-item">
|
|
778
|
+
<%= link_to settings_path, class: "nav-link #{request.path == settings_path ? 'active' : ''}" do %>
|
|
779
|
+
<i class="bi bi-gear"></i> Settings
|
|
780
|
+
<% end %>
|
|
781
|
+
</li>
|
|
774
782
|
</ul>
|
|
775
783
|
|
|
776
784
|
<h6 class="mt-4">QUICK FILTERS</h6>
|
|
@@ -780,6 +788,16 @@
|
|
|
780
788
|
<i class="bi bi-exclamation-circle"></i> Unresolved
|
|
781
789
|
<% end %>
|
|
782
790
|
</li>
|
|
791
|
+
<li class="nav-item">
|
|
792
|
+
<%= link_to errors_path(severity: 'critical'), class: "nav-link" do %>
|
|
793
|
+
<i class="bi bi-exclamation-triangle-fill text-danger"></i> Critical
|
|
794
|
+
<% end %>
|
|
795
|
+
</li>
|
|
796
|
+
<li class="nav-item">
|
|
797
|
+
<%= link_to errors_path(priority_level: 'high'), class: "nav-link" do %>
|
|
798
|
+
<i class="bi bi-flag-fill text-warning"></i> High Priority
|
|
799
|
+
<% end %>
|
|
800
|
+
</li>
|
|
783
801
|
<li class="nav-item">
|
|
784
802
|
<%= link_to errors_path(platform: 'iOS'), class: "nav-link" do %>
|
|
785
803
|
<i class="bi bi-phone"></i> iOS Errors
|
|
@@ -143,6 +143,14 @@
|
|
|
143
143
|
});
|
|
144
144
|
</script>
|
|
145
145
|
</div>
|
|
146
|
+
<div class="card-footer bg-white border-top-0">
|
|
147
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
148
|
+
<small class="text-muted me-2">Quick Links:</small>
|
|
149
|
+
<% @errors_by_platform.keys.each do |platform| %>
|
|
150
|
+
<%= link_to platform, errors_path(platform: platform), class: "btn btn-sm btn-outline-secondary" %>
|
|
151
|
+
<% end %>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
146
154
|
</div>
|
|
147
155
|
</div>
|
|
148
156
|
</div>
|
|
@@ -249,6 +257,7 @@
|
|
|
249
257
|
<th>User</th>
|
|
250
258
|
<th>Error Count</th>
|
|
251
259
|
<th>Percentage</th>
|
|
260
|
+
<th>Actions</th>
|
|
252
261
|
</tr>
|
|
253
262
|
</thead>
|
|
254
263
|
<tbody>
|
|
@@ -269,6 +278,9 @@
|
|
|
269
278
|
</div>
|
|
270
279
|
</div>
|
|
271
280
|
</td>
|
|
281
|
+
<td>
|
|
282
|
+
<%= link_to "View Errors", errors_path(search: email), class: "btn btn-sm btn-outline-primary" %>
|
|
283
|
+
</td>
|
|
272
284
|
</tr>
|
|
273
285
|
<% end %>
|
|
274
286
|
</tbody>
|
|
@@ -330,4 +342,309 @@
|
|
|
330
342
|
</div>
|
|
331
343
|
</div>
|
|
332
344
|
</div>
|
|
345
|
+
|
|
346
|
+
<!-- Recurring Issues Analysis -->
|
|
347
|
+
<div class="card mb-4">
|
|
348
|
+
<div class="card-header bg-white">
|
|
349
|
+
<h5 class="mb-0"><i class="bi bi-arrow-repeat"></i> Recurring Issues</h5>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="card-body">
|
|
352
|
+
<% if @recurring_data[:high_frequency_errors].any? %>
|
|
353
|
+
<h6 class="text-muted mb-3">High Frequency Errors</h6>
|
|
354
|
+
<div class="table-responsive mb-4">
|
|
355
|
+
<table class="table table-sm">
|
|
356
|
+
<thead>
|
|
357
|
+
<tr>
|
|
358
|
+
<th>Error Type</th>
|
|
359
|
+
<th>Total Occurrences</th>
|
|
360
|
+
<th>Duration</th>
|
|
361
|
+
<th>First Seen</th>
|
|
362
|
+
<th>Last Seen</th>
|
|
363
|
+
<th>Status</th>
|
|
364
|
+
</tr>
|
|
365
|
+
</thead>
|
|
366
|
+
<tbody>
|
|
367
|
+
<% @recurring_data[:high_frequency_errors].first(10).each do |error| %>
|
|
368
|
+
<tr>
|
|
369
|
+
<td><code class="small"><%= error[:error_type] %></code></td>
|
|
370
|
+
<td><span class="badge bg-danger"><%= error[:total_occurrences] %></span></td>
|
|
371
|
+
<td><%= error[:duration_days] %> days</td>
|
|
372
|
+
<td><small class="text-muted"><%= error[:first_seen].strftime("%b %d, %Y") %></small></td>
|
|
373
|
+
<td><small class="text-muted"><%= error[:last_seen].strftime("%b %d, %Y %H:%M") %></small></td>
|
|
374
|
+
<td>
|
|
375
|
+
<% if error[:still_active] %>
|
|
376
|
+
<span class="badge bg-warning">Active</span>
|
|
377
|
+
<% else %>
|
|
378
|
+
<span class="badge bg-secondary">Inactive</span>
|
|
379
|
+
<% end %>
|
|
380
|
+
</td>
|
|
381
|
+
</tr>
|
|
382
|
+
<% end %>
|
|
383
|
+
</tbody>
|
|
384
|
+
</table>
|
|
385
|
+
</div>
|
|
386
|
+
<% else %>
|
|
387
|
+
<p class="text-muted">No high-frequency errors found in the last <%= @days %> days.</p>
|
|
388
|
+
<% end %>
|
|
389
|
+
|
|
390
|
+
<% if @recurring_data[:persistent_errors].any? %>
|
|
391
|
+
<h6 class="text-muted mb-3 mt-4">Persistent Unresolved Errors</h6>
|
|
392
|
+
<div class="alert alert-info">
|
|
393
|
+
<i class="bi bi-info-circle"></i> These errors have been unresolved for more than 7 days.
|
|
394
|
+
</div>
|
|
395
|
+
<div class="table-responsive">
|
|
396
|
+
<table class="table table-sm">
|
|
397
|
+
<thead>
|
|
398
|
+
<tr>
|
|
399
|
+
<th>Error Type</th>
|
|
400
|
+
<th>Message</th>
|
|
401
|
+
<th>Platform</th>
|
|
402
|
+
<th>Count</th>
|
|
403
|
+
<th>Age</th>
|
|
404
|
+
<th>Actions</th>
|
|
405
|
+
</tr>
|
|
406
|
+
</thead>
|
|
407
|
+
<tbody>
|
|
408
|
+
<% @recurring_data[:persistent_errors].first(10).each do |error| %>
|
|
409
|
+
<tr>
|
|
410
|
+
<td><code class="small"><%= error[:error_type] %></code></td>
|
|
411
|
+
<td class="text-muted small"><%= error[:message] %></td>
|
|
412
|
+
<td><%= error[:platform] %></td>
|
|
413
|
+
<td><span class="badge bg-secondary"><%= error[:occurrence_count] %>x</span></td>
|
|
414
|
+
<td>
|
|
415
|
+
<span class="badge bg-warning"><%= error[:age_days] %> days</span>
|
|
416
|
+
</td>
|
|
417
|
+
<td>
|
|
418
|
+
<%= link_to "View", error_path(error[:id]), class: "btn btn-sm btn-outline-primary" %>
|
|
419
|
+
</td>
|
|
420
|
+
</tr>
|
|
421
|
+
<% end %>
|
|
422
|
+
</tbody>
|
|
423
|
+
</table>
|
|
424
|
+
</div>
|
|
425
|
+
<% end %>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<!-- Release Comparison Charts -->
|
|
430
|
+
<% if @errors_by_version.present? && @errors_by_version.count >= 2 %>
|
|
431
|
+
<div class="row g-4 mb-4">
|
|
432
|
+
<!-- Errors by Version Chart -->
|
|
433
|
+
<div class="col-md-8">
|
|
434
|
+
<div class="card">
|
|
435
|
+
<div class="card-header bg-white">
|
|
436
|
+
<h5 class="mb-0"><i class="bi bi-git"></i> Errors by Release Version</h5>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="card-body">
|
|
439
|
+
<div id="errors-by-version-chart"></div>
|
|
440
|
+
<script>
|
|
441
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
442
|
+
const versionData = <%= raw @errors_by_version.transform_values { |v| v[:count] }.to_json %>;
|
|
443
|
+
const colors = window.getChartColors();
|
|
444
|
+
|
|
445
|
+
new Chartkick.ColumnChart("errors-by-version-chart", versionData, {
|
|
446
|
+
colors: ["#8B5CF6", "#EF4444", "#F59E0B", "#10B981"],
|
|
447
|
+
height: "300px",
|
|
448
|
+
xtitle: "Version",
|
|
449
|
+
ytitle: "Error Count",
|
|
450
|
+
library: window.getChartLibraryOptions()
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
</script>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<!-- Release Comparison Card -->
|
|
459
|
+
<div class="col-md-4">
|
|
460
|
+
<div class="card">
|
|
461
|
+
<div class="card-header bg-white">
|
|
462
|
+
<h5 class="mb-0">Latest vs Previous</h5>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="card-body">
|
|
465
|
+
<% if @release_comparison.present? %>
|
|
466
|
+
<div class="mb-3">
|
|
467
|
+
<small class="text-muted">Latest Version</small>
|
|
468
|
+
<h4><%= @release_comparison[:latest_version] %></h4>
|
|
469
|
+
<p class="mb-0">
|
|
470
|
+
<span class="badge bg-danger"><%= @release_comparison[:latest_count] %> errors</span>
|
|
471
|
+
<% if @release_comparison[:latest_critical] > 0 %>
|
|
472
|
+
<span class="badge bg-dark ms-1"><%= @release_comparison[:latest_critical] %> critical</span>
|
|
473
|
+
<% end %>
|
|
474
|
+
</p>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="mb-3">
|
|
477
|
+
<small class="text-muted">Previous Version</small>
|
|
478
|
+
<h4><%= @release_comparison[:previous_version] %></h4>
|
|
479
|
+
<p class="mb-0">
|
|
480
|
+
<span class="badge bg-secondary"><%= @release_comparison[:previous_count] %> errors</span>
|
|
481
|
+
<% if @release_comparison[:previous_critical] > 0 %>
|
|
482
|
+
<span class="badge bg-secondary ms-1"><%= @release_comparison[:previous_critical] %> critical</span>
|
|
483
|
+
<% end %>
|
|
484
|
+
</p>
|
|
485
|
+
</div>
|
|
486
|
+
<hr>
|
|
487
|
+
<div>
|
|
488
|
+
<small class="text-muted">Change</small>
|
|
489
|
+
<h4 class="<%= @release_comparison[:change_percentage] > 0 ? 'text-danger' : 'text-success' %>">
|
|
490
|
+
<%= @release_comparison[:change_percentage] > 0 ? '↑' : '↓' %>
|
|
491
|
+
<%= @release_comparison[:change_percentage].abs %>%
|
|
492
|
+
</h4>
|
|
493
|
+
</div>
|
|
494
|
+
<% else %>
|
|
495
|
+
<p class="text-muted">Not enough release data for comparison.</p>
|
|
496
|
+
<% end %>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<!-- Problematic Releases Alert -->
|
|
503
|
+
<% if @problematic_releases.any? %>
|
|
504
|
+
<div class="alert alert-warning mb-4">
|
|
505
|
+
<h5 class="alert-heading"><i class="bi bi-exclamation-triangle"></i> Problematic Releases Detected</h5>
|
|
506
|
+
<p>The following releases have significantly higher error rates (>2x average):</p>
|
|
507
|
+
<ul class="mb-0">
|
|
508
|
+
<% @problematic_releases.each do |release| %>
|
|
509
|
+
<li>
|
|
510
|
+
<strong><%= release[:version] %></strong>:
|
|
511
|
+
<%= release[:error_count] %> errors
|
|
512
|
+
(<%= release[:critical_count] %> critical,
|
|
513
|
+
+<%= release[:deviation_from_avg] %>% from average)
|
|
514
|
+
</li>
|
|
515
|
+
<% end %>
|
|
516
|
+
</ul>
|
|
517
|
+
</div>
|
|
518
|
+
<% end %>
|
|
519
|
+
<% end %>
|
|
520
|
+
|
|
521
|
+
<!-- MTTR (Mean Time to Resolution) -->
|
|
522
|
+
<% if @mttr_stats[:total_resolved] > 0 %>
|
|
523
|
+
<div class="card mb-4">
|
|
524
|
+
<div class="card-header bg-white">
|
|
525
|
+
<h5 class="mb-0"><i class="bi bi-clock-history"></i> Resolution Performance (MTTR)</h5>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="card-body">
|
|
528
|
+
<div class="row g-3 mb-4">
|
|
529
|
+
<!-- Overall MTTR -->
|
|
530
|
+
<div class="col-md-3">
|
|
531
|
+
<div class="card border-info">
|
|
532
|
+
<div class="card-body text-center">
|
|
533
|
+
<small class="text-muted d-block">Overall MTTR</small>
|
|
534
|
+
<h3 class="text-info mb-0"><%= @mttr_stats[:overall_mttr] %>h</h3>
|
|
535
|
+
<small class="text-muted">Average resolution time</small>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<!-- Fastest -->
|
|
541
|
+
<div class="col-md-3">
|
|
542
|
+
<div class="card border-success">
|
|
543
|
+
<div class="card-body text-center">
|
|
544
|
+
<small class="text-muted d-block">Fastest</small>
|
|
545
|
+
<h3 class="text-success mb-0"><%= @mttr_stats[:fastest_resolution] || 0 %>m</h3>
|
|
546
|
+
<small class="text-muted">Best resolution time</small>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<!-- Slowest -->
|
|
552
|
+
<div class="col-md-3">
|
|
553
|
+
<div class="card border-danger">
|
|
554
|
+
<div class="card-body text-center">
|
|
555
|
+
<small class="text-muted d-block">Slowest</small>
|
|
556
|
+
<h3 class="text-danger mb-0"><%= @mttr_stats[:slowest_resolution] || 0 %>h</h3>
|
|
557
|
+
<small class="text-muted">Longest resolution time</small>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<!-- Total Resolved -->
|
|
563
|
+
<div class="col-md-3">
|
|
564
|
+
<div class="card border-secondary">
|
|
565
|
+
<div class="card-body text-center">
|
|
566
|
+
<small class="text-muted d-block">Total Resolved</small>
|
|
567
|
+
<h3 class="text-secondary mb-0"><%= @mttr_stats[:total_resolved] %></h3>
|
|
568
|
+
<small class="text-muted">In last <%= @days %> days</small>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<!-- MTTR by Platform -->
|
|
575
|
+
<% if @mttr_by_platform.present? && @mttr_by_platform.any? %>
|
|
576
|
+
<div class="mt-4">
|
|
577
|
+
<h6 class="text-muted mb-3">MTTR by Platform</h6>
|
|
578
|
+
<div id="mttr-by-platform-chart"></div>
|
|
579
|
+
<script>
|
|
580
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
581
|
+
const colors = window.getChartColors();
|
|
582
|
+
new Chartkick.BarChart("mttr-by-platform-chart",
|
|
583
|
+
<%= raw @mttr_by_platform.to_json %>, {
|
|
584
|
+
suffix: " hours",
|
|
585
|
+
height: "200px",
|
|
586
|
+
colors: ["#8B5CF6"],
|
|
587
|
+
library: window.getChartLibraryOptions()
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
</script>
|
|
591
|
+
</div>
|
|
592
|
+
<% end %>
|
|
593
|
+
|
|
594
|
+
<!-- MTTR by Severity -->
|
|
595
|
+
<% if @mttr_stats[:mttr_by_severity].present? && @mttr_stats[:mttr_by_severity].any? %>
|
|
596
|
+
<div class="mt-4">
|
|
597
|
+
<h6 class="text-muted mb-3">MTTR by Severity</h6>
|
|
598
|
+
<div class="table-responsive">
|
|
599
|
+
<table class="table table-sm">
|
|
600
|
+
<thead>
|
|
601
|
+
<tr>
|
|
602
|
+
<th>Severity</th>
|
|
603
|
+
<th>Average Time to Resolution</th>
|
|
604
|
+
<th>Actions</th>
|
|
605
|
+
</tr>
|
|
606
|
+
</thead>
|
|
607
|
+
<tbody>
|
|
608
|
+
<% @mttr_stats[:mttr_by_severity].each do |severity, hours| %>
|
|
609
|
+
<tr>
|
|
610
|
+
<td>
|
|
611
|
+
<span class="badge bg-<%= severity_color(severity) %>">
|
|
612
|
+
<%= severity.to_s.capitalize %>
|
|
613
|
+
</span>
|
|
614
|
+
</td>
|
|
615
|
+
<td><strong><%= hours %> hours</strong></td>
|
|
616
|
+
<td>
|
|
617
|
+
<%= link_to "View", errors_path(severity: severity), class: "btn btn-sm btn-outline-primary" %>
|
|
618
|
+
</td>
|
|
619
|
+
</tr>
|
|
620
|
+
<% end %>
|
|
621
|
+
</tbody>
|
|
622
|
+
</table>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
<% end %>
|
|
626
|
+
|
|
627
|
+
<!-- MTTR Trend -->
|
|
628
|
+
<% if @mttr_stats[:mttr_trend].present? && @mttr_stats[:mttr_trend].any? %>
|
|
629
|
+
<div class="mt-4">
|
|
630
|
+
<h6 class="text-muted mb-3">MTTR Trend (Weekly)</h6>
|
|
631
|
+
<div id="mttr-trend-chart"></div>
|
|
632
|
+
<script>
|
|
633
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
634
|
+
const colors = window.getChartColors();
|
|
635
|
+
new Chartkick.LineChart("mttr-trend-chart",
|
|
636
|
+
<%= raw @mttr_stats[:mttr_trend].to_json %>, {
|
|
637
|
+
suffix: " hours",
|
|
638
|
+
height: "250px",
|
|
639
|
+
colors: ["#10B981"],
|
|
640
|
+
curve: false,
|
|
641
|
+
library: window.getChartLibraryOptions()
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
</script>
|
|
645
|
+
</div>
|
|
646
|
+
<% end %>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
<% end %>
|
|
333
650
|
</div>
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
<th>Critical Errors</th>
|
|
83
83
|
<th>Unique Error Types</th>
|
|
84
84
|
<th>Platforms</th>
|
|
85
|
+
<th>Actions</th>
|
|
85
86
|
</tr>
|
|
86
87
|
</thead>
|
|
87
88
|
<tbody>
|
|
@@ -101,6 +102,9 @@
|
|
|
101
102
|
<span class="badge bg-secondary text-capitalize me-1"><%= platform %></span>
|
|
102
103
|
<% end %>
|
|
103
104
|
</td>
|
|
105
|
+
<td>
|
|
106
|
+
<%= link_to "View", errors_path(search: release[:version]), class: "btn btn-sm btn-outline-primary" %>
|
|
107
|
+
</td>
|
|
104
108
|
</tr>
|
|
105
109
|
<% end %>
|
|
106
110
|
</tbody>
|
|
@@ -218,6 +222,7 @@
|
|
|
218
222
|
<th>Different Error Types</th>
|
|
219
223
|
<th>Total Errors</th>
|
|
220
224
|
<th>Error Types</th>
|
|
225
|
+
<th>Actions</th>
|
|
221
226
|
</tr>
|
|
222
227
|
</thead>
|
|
223
228
|
<tbody>
|
|
@@ -238,6 +243,9 @@
|
|
|
238
243
|
<span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
|
|
239
244
|
<% end %>
|
|
240
245
|
</td>
|
|
246
|
+
<td>
|
|
247
|
+
<%= link_to "View", errors_path(search: user_data[:user_email]), class: "btn btn-sm btn-outline-primary" %>
|
|
248
|
+
</td>
|
|
241
249
|
</tr>
|
|
242
250
|
<% end %>
|
|
243
251
|
</tbody>
|