rails_error_dashboard 0.1.0 → 0.1.1
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 +257 -700
- data/app/controllers/rails_error_dashboard/application_controller.rb +18 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +47 -4
- data/app/helpers/rails_error_dashboard/application_helper.rb +17 -0
- data/app/jobs/rails_error_dashboard/application_job.rb +19 -0
- data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +48 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +263 -0
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -8
- data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +2 -1
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +5 -5
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +10 -6
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +5 -6
- data/app/mailers/rails_error_dashboard/application_mailer.rb +1 -1
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +1 -1
- data/app/models/rails_error_dashboard/cascade_pattern.rb +74 -0
- data/app/models/rails_error_dashboard/error_baseline.rb +100 -0
- data/app/models/rails_error_dashboard/error_log.rb +326 -3
- data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +150 -9
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +3 -10
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -2
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +76 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +209 -0
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +34 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +19 -39
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +215 -138
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +388 -0
- data/app/views/rails_error_dashboard/errors/show.html.erb +428 -11
- data/config/routes.rb +2 -0
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +66 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +26 -0
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +12 -0
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +9 -0
- data/db/migrate/20251225100236_create_error_occurrences.rb +31 -0
- data/db/migrate/20251225101920_create_cascade_patterns.rb +33 -0
- data/db/migrate/20251225102500_create_error_baselines.rb +38 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +270 -1
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +251 -37
- data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +36 -0
- data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +55 -0
- data/lib/rails_error_dashboard/commands/log_error.rb +234 -7
- data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
- data/lib/rails_error_dashboard/configuration.rb +82 -5
- data/lib/rails_error_dashboard/error_reporter.rb +15 -7
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +17 -10
- data/lib/rails_error_dashboard/plugin.rb +6 -3
- data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -3
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +0 -2
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +44 -6
- data/lib/rails_error_dashboard/queries/baseline_stats.rb +107 -0
- data/lib/rails_error_dashboard/queries/co_occurring_errors.rb +86 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +134 -2
- data/lib/rails_error_dashboard/queries/error_cascades.rb +74 -0
- data/lib/rails_error_dashboard/queries/error_correlation.rb +375 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +52 -11
- data/lib/rails_error_dashboard/queries/filter_options.rb +0 -1
- data/lib/rails_error_dashboard/queries/platform_comparison.rb +254 -0
- data/lib/rails_error_dashboard/queries/similar_errors.rb +93 -0
- data/lib/rails_error_dashboard/services/baseline_alert_throttler.rb +88 -0
- data/lib/rails_error_dashboard/services/baseline_calculator.rb +269 -0
- data/lib/rails_error_dashboard/services/cascade_detector.rb +95 -0
- data/lib/rails_error_dashboard/services/pattern_detector.rb +268 -0
- data/lib/rails_error_dashboard/services/similarity_calculator.rb +144 -0
- data/lib/rails_error_dashboard/value_objects/error_context.rb +27 -1
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +55 -7
- metadata +52 -9
- data/app/models/rails_error_dashboard/application_record.rb +0 -5
- data/lib/rails_error_dashboard/queries/developer_insights.rb +0 -277
- data/lib/rails_error_dashboard/queries/errors_list_v2.rb +0 -149
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
<%= csrf_meta_tags %>
|
|
7
7
|
<%= csp_meta_tag %>
|
|
8
8
|
|
|
9
|
+
<!-- Apply theme immediately to prevent flash of wrong theme -->
|
|
10
|
+
<script>
|
|
11
|
+
// This runs BEFORE body renders to prevent flash
|
|
12
|
+
(function() {
|
|
13
|
+
const savedTheme = localStorage.getItem('theme');
|
|
14
|
+
if (savedTheme === 'dark') {
|
|
15
|
+
// Add to html element so we can style body
|
|
16
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
9
21
|
<!-- Bootstrap CSS -->
|
|
10
22
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
11
23
|
<!-- Bootstrap Icons -->
|
|
@@ -15,6 +27,11 @@
|
|
|
15
27
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
16
28
|
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
|
|
17
29
|
|
|
30
|
+
<!-- Turbo for real-time updates -->
|
|
31
|
+
<script type="module">
|
|
32
|
+
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/+esm'
|
|
33
|
+
</script>
|
|
34
|
+
|
|
18
35
|
<style>
|
|
19
36
|
:root {
|
|
20
37
|
--primary-color: #8B5CF6;
|
|
@@ -40,8 +57,9 @@
|
|
|
40
57
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
/* Dark mode */
|
|
44
|
-
body.dark-mode
|
|
60
|
+
/* Dark mode - support both class and data attribute for immediate loading */
|
|
61
|
+
body.dark-mode,
|
|
62
|
+
html[data-theme="dark"] body {
|
|
45
63
|
--bg-color: #1A1B26;
|
|
46
64
|
--text-color: #E4E5E9;
|
|
47
65
|
--card-bg: #24283B;
|
|
@@ -196,12 +214,13 @@
|
|
|
196
214
|
filter: brightness(0) invert(1);
|
|
197
215
|
}
|
|
198
216
|
|
|
199
|
-
body.dark-mode .btn-close
|
|
217
|
+
body.dark-mode .btn-close,
|
|
218
|
+
html[data-theme="dark"] body .btn-close {
|
|
200
219
|
filter: brightness(0) invert(1);
|
|
201
220
|
}
|
|
202
221
|
|
|
203
222
|
.text-muted {
|
|
204
|
-
color: #
|
|
223
|
+
color: #D1D5DB !important; /* Lighter gray for better contrast in dark mode */
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
.theme-toggle {
|
|
@@ -231,10 +250,89 @@
|
|
|
231
250
|
height: 300px;
|
|
232
251
|
margin: 1rem 0;
|
|
233
252
|
}
|
|
253
|
+
|
|
254
|
+
/* Real-time updates - new error animation */
|
|
255
|
+
@keyframes slideInFade {
|
|
256
|
+
0% {
|
|
257
|
+
transform: translateY(-20px);
|
|
258
|
+
opacity: 0;
|
|
259
|
+
background-color: #FEF3C7;
|
|
260
|
+
}
|
|
261
|
+
10% {
|
|
262
|
+
transform: translateY(0);
|
|
263
|
+
opacity: 1;
|
|
264
|
+
background-color: #FEF3C7;
|
|
265
|
+
}
|
|
266
|
+
100% {
|
|
267
|
+
background-color: transparent;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Highlight new errors */
|
|
272
|
+
#error_list tr:first-child.new-error {
|
|
273
|
+
animation: slideInFade 3s ease-out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Live indicator pulse */
|
|
277
|
+
@keyframes pulse {
|
|
278
|
+
0%, 100% {
|
|
279
|
+
opacity: 1;
|
|
280
|
+
}
|
|
281
|
+
50% {
|
|
282
|
+
opacity: 0.5;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#live-indicator {
|
|
287
|
+
animation: pulse 2s ease-in-out infinite;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Updated stat card pulse */
|
|
291
|
+
@keyframes statPulse {
|
|
292
|
+
0%, 100% {
|
|
293
|
+
transform: scale(1);
|
|
294
|
+
}
|
|
295
|
+
50% {
|
|
296
|
+
transform: scale(1.05);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.stat-card.updated {
|
|
301
|
+
animation: statPulse 0.5s ease-out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Loading indicator */
|
|
305
|
+
#loading-indicator {
|
|
306
|
+
position: fixed;
|
|
307
|
+
top: 0;
|
|
308
|
+
left: 0;
|
|
309
|
+
right: 0;
|
|
310
|
+
height: 3px;
|
|
311
|
+
background: linear-gradient(90deg, #8B5CF6 0%, #6D28D9 100%);
|
|
312
|
+
transform: scaleX(0);
|
|
313
|
+
transform-origin: left;
|
|
314
|
+
transition: transform 0.3s ease;
|
|
315
|
+
z-index: 9999;
|
|
316
|
+
display: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#loading-indicator.active {
|
|
320
|
+
display: block;
|
|
321
|
+
animation: loadingProgress 2s ease-in-out forwards;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@keyframes loadingProgress {
|
|
325
|
+
0% { transform: scaleX(0); }
|
|
326
|
+
50% { transform: scaleX(0.7); }
|
|
327
|
+
100% { transform: scaleX(1); opacity: 0; }
|
|
328
|
+
}
|
|
234
329
|
</style>
|
|
235
330
|
</head>
|
|
236
331
|
|
|
237
332
|
<body>
|
|
333
|
+
<!-- Loading Indicator -->
|
|
334
|
+
<div id="loading-indicator"></div>
|
|
335
|
+
|
|
238
336
|
<!-- Top Navbar -->
|
|
239
337
|
<nav class="navbar navbar-dark">
|
|
240
338
|
<div class="container-fluid">
|
|
@@ -274,6 +372,20 @@
|
|
|
274
372
|
<i class="bi bi-graph-up"></i> Analytics
|
|
275
373
|
<% end %>
|
|
276
374
|
</li>
|
|
375
|
+
<% if RailsErrorDashboard.configuration.enable_platform_comparison %>
|
|
376
|
+
<li class="nav-item">
|
|
377
|
+
<%= link_to platform_comparison_errors_path, class: "nav-link #{request.path == platform_comparison_errors_path ? 'active' : ''}" do %>
|
|
378
|
+
<i class="bi bi-phone"></i> Platform Health
|
|
379
|
+
<% end %>
|
|
380
|
+
</li>
|
|
381
|
+
<% end %>
|
|
382
|
+
<% if RailsErrorDashboard.configuration.enable_error_correlation %>
|
|
383
|
+
<li class="nav-item">
|
|
384
|
+
<%= link_to correlation_errors_path, class: "nav-link #{request.path == correlation_errors_path ? 'active' : ''}" do %>
|
|
385
|
+
<i class="bi bi-diagram-3"></i> Correlation
|
|
386
|
+
<% end %>
|
|
387
|
+
</li>
|
|
388
|
+
<% end %>
|
|
277
389
|
</ul>
|
|
278
390
|
|
|
279
391
|
<hr class="my-3">
|
|
@@ -297,11 +409,6 @@
|
|
|
297
409
|
<i class="bi bi-phone"></i> Android Errors
|
|
298
410
|
<% end %>
|
|
299
411
|
</li>
|
|
300
|
-
<li class="nav-item">
|
|
301
|
-
<%= link_to errors_path(environment: 'production'), class: "nav-link" do %>
|
|
302
|
-
<i class="bi bi-server"></i> Production
|
|
303
|
-
<% end %>
|
|
304
|
-
</li>
|
|
305
412
|
</ul>
|
|
306
413
|
</div>
|
|
307
414
|
</nav>
|
|
@@ -323,7 +430,13 @@
|
|
|
323
430
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
324
431
|
if (savedTheme === 'dark') {
|
|
325
432
|
document.body.classList.add('dark-mode');
|
|
433
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
326
434
|
updateThemeIcon(true);
|
|
435
|
+
} else {
|
|
436
|
+
// Ensure light mode is clean
|
|
437
|
+
document.body.classList.remove('dark-mode');
|
|
438
|
+
document.documentElement.removeAttribute('data-theme');
|
|
439
|
+
updateThemeIcon(false);
|
|
327
440
|
}
|
|
328
441
|
});
|
|
329
442
|
|
|
@@ -331,6 +444,13 @@
|
|
|
331
444
|
const body = document.body;
|
|
332
445
|
const isDark = body.classList.toggle('dark-mode');
|
|
333
446
|
|
|
447
|
+
// Sync with html data attribute
|
|
448
|
+
if (isDark) {
|
|
449
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
450
|
+
} else {
|
|
451
|
+
document.documentElement.removeAttribute('data-theme');
|
|
452
|
+
}
|
|
453
|
+
|
|
334
454
|
// Save preference
|
|
335
455
|
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
336
456
|
|
|
@@ -346,6 +466,27 @@
|
|
|
346
466
|
icon.className = 'bi bi-moon-fill';
|
|
347
467
|
}
|
|
348
468
|
}
|
|
469
|
+
|
|
470
|
+
// Loading indicator for form submissions and link clicks
|
|
471
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
472
|
+
|
|
473
|
+
// Show loading on form submit
|
|
474
|
+
document.addEventListener('submit', function() {
|
|
475
|
+
loadingIndicator.classList.add('active');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Show loading on link clicks (except anchors)
|
|
479
|
+
document.addEventListener('click', function(e) {
|
|
480
|
+
const link = e.target.closest('a');
|
|
481
|
+
if (link && link.href && !link.href.startsWith('#') && !link.target) {
|
|
482
|
+
loadingIndicator.classList.add('active');
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Hide loading when page loads
|
|
487
|
+
window.addEventListener('load', function() {
|
|
488
|
+
loadingIndicator.classList.remove('active');
|
|
489
|
+
});
|
|
349
490
|
</script>
|
|
350
491
|
</body>
|
|
351
492
|
</html>
|
|
@@ -136,13 +136,6 @@
|
|
|
136
136
|
<div class="label">Error Type:</div>
|
|
137
137
|
<div class="value"><code><%= @error_log.error_type %></code></div>
|
|
138
138
|
|
|
139
|
-
<div class="label">Environment:</div>
|
|
140
|
-
<div class="value">
|
|
141
|
-
<span class="badge badge-<%= @error_log.environment %>">
|
|
142
|
-
<%= @error_log.environment.upcase %>
|
|
143
|
-
</span>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
139
|
<% if @error_log.platform.present? %>
|
|
147
140
|
<div class="label">Platform:</div>
|
|
148
141
|
<div class="value">
|
|
@@ -155,9 +148,9 @@
|
|
|
155
148
|
<div class="label">Occurred At:</div>
|
|
156
149
|
<div class="value"><%= @error_log.occurred_at.strftime('%B %d, %Y at %I:%M %p %Z') %></div>
|
|
157
150
|
|
|
158
|
-
<% if @error_log.
|
|
159
|
-
<div class="label">User:</div>
|
|
160
|
-
<div class="value"><%= @error_log.
|
|
151
|
+
<% if @error_log.user_id.present? %>
|
|
152
|
+
<div class="label">User ID:</div>
|
|
153
|
+
<div class="value"><%= @error_log.user_id %></div>
|
|
161
154
|
<% end %>
|
|
162
155
|
|
|
163
156
|
<% if @error_log.ip_address.present? %>
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
==========================================
|
|
4
4
|
|
|
5
5
|
Error Type: <%= @error_log.error_type %>
|
|
6
|
-
Environment: <%= @error_log.environment.upcase %>
|
|
7
6
|
<% if @error_log.platform.present? %>Platform: <%= @error_log.platform %><% end %>
|
|
8
7
|
Occurred At: <%= @error_log.occurred_at.strftime('%B %d, %Y at %I:%M %p %Z') %>
|
|
9
|
-
<% if @error_log.
|
|
8
|
+
<% if @error_log.user_id.present? %>User ID: <%= @error_log.user_id %><% end %>
|
|
10
9
|
<% if @error_log.ip_address.present? %>IP Address: <%= @error_log.ip_address %><% end %>
|
|
11
10
|
<% if @error_log.request_url.present? %>Request URL: <%= @error_log.request_url %><% end %>
|
|
12
11
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<tr id="error_<%= error.id %>">
|
|
2
|
+
<td onclick="event.stopPropagation();">
|
|
3
|
+
<input type="checkbox" class="error-checkbox form-check-input" value="<%= error.id %>" data-error-id="<%= error.id %>">
|
|
4
|
+
</td>
|
|
5
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
6
|
+
<% severity = error.severity %>
|
|
7
|
+
<% if severity == :critical %>
|
|
8
|
+
<span class="badge bg-danger" data-bs-toggle="tooltip" title="Critical severity - requires immediate attention">CRITICAL</span>
|
|
9
|
+
<% elsif severity == :high %>
|
|
10
|
+
<span class="badge bg-warning text-dark" data-bs-toggle="tooltip" title="High severity - should be fixed soon">HIGH</span>
|
|
11
|
+
<% elsif severity == :medium %>
|
|
12
|
+
<span class="badge bg-info text-dark" data-bs-toggle="tooltip" title="Medium severity - fix when possible">MEDIUM</span>
|
|
13
|
+
<% else %>
|
|
14
|
+
<span class="badge bg-secondary" data-bs-toggle="tooltip" title="Low severity - minor issue">LOW</span>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if error.respond_to?(:priority_score) && error.priority_score %>
|
|
17
|
+
<br><small class="text-muted" data-bs-toggle="tooltip" title="Priority score (0-100) based on severity, frequency, recency, and user impact">P<%= error.priority_score %></small>
|
|
18
|
+
<% end %>
|
|
19
|
+
</td>
|
|
20
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
21
|
+
<code class="text-danger" data-bs-toggle="tooltip" title="<%= error.error_type %>"><%= error.error_type.split('::').last %></code>
|
|
22
|
+
<% if error.respond_to?(:app_version) && error.app_version.present? %>
|
|
23
|
+
<br><small class="badge bg-light text-dark" data-bs-toggle="tooltip" title="App version when error occurred">v<%= error.app_version %></small>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
27
|
+
<div class="text-truncate" style="max-width: 300px;" title="<%= error.message %>">
|
|
28
|
+
<%= error.message %>
|
|
29
|
+
</div>
|
|
30
|
+
</td>
|
|
31
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
32
|
+
<span class="badge bg-primary"><%= error.occurrence_count %>x</span>
|
|
33
|
+
</td>
|
|
34
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
35
|
+
<small>
|
|
36
|
+
<strong>First:</strong> <%= error.first_seen_at&.strftime("%m/%d %I:%M%p") || 'N/A' %><br>
|
|
37
|
+
<strong>Last:</strong> <%= error.last_seen_at&.strftime("%m/%d %I:%M%p") || 'N/A' %>
|
|
38
|
+
</small>
|
|
39
|
+
</td>
|
|
40
|
+
<% if local_assigns[:show_platform] %>
|
|
41
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
42
|
+
<% if error.platform == 'iOS' %>
|
|
43
|
+
<span class="badge badge-ios">iOS</span>
|
|
44
|
+
<% elsif error.platform == 'Android' %>
|
|
45
|
+
<span class="badge badge-android">Android</span>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span class="badge badge-api"><%= error.platform || 'API' %></span>
|
|
48
|
+
<% end %>
|
|
49
|
+
</td>
|
|
50
|
+
<% end %>
|
|
51
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
52
|
+
<% if error.user_id %>
|
|
53
|
+
<small data-bs-toggle="tooltip" title="User affected by this error">User #<%= error.user_id %></small>
|
|
54
|
+
<% if error.respond_to?(:user_impact_percentage) %>
|
|
55
|
+
<% impact = error.user_impact_percentage %>
|
|
56
|
+
<% if impact > 0 %>
|
|
57
|
+
<br><small class="badge bg-warning text-dark" data-bs-toggle="tooltip" title="Percentage of users affected by this error type"><%= impact %>% impact</small>
|
|
58
|
+
<% end %>
|
|
59
|
+
<% end %>
|
|
60
|
+
<% else %>
|
|
61
|
+
<small class="text-muted" data-bs-toggle="tooltip" title="No specific user affected">-</small>
|
|
62
|
+
<% end %>
|
|
63
|
+
</td>
|
|
64
|
+
<td onclick="window.location='<%= error_path(error) %>';">
|
|
65
|
+
<% if error.resolved? %>
|
|
66
|
+
<i class="bi bi-check-circle-fill text-success" title="Resolved"></i>
|
|
67
|
+
<% else %>
|
|
68
|
+
<i class="bi bi-exclamation-circle-fill text-danger" title="Unresolved"></i>
|
|
69
|
+
<% end %>
|
|
70
|
+
</td>
|
|
71
|
+
<td onclick="event.stopPropagation();">
|
|
72
|
+
<%= link_to error_path(error), class: "btn btn-sm btn-outline-primary" do %>
|
|
73
|
+
<i class="bi bi-eye"></i>
|
|
74
|
+
<% end %>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<% if RailsErrorDashboard.configuration.enable_occurrence_patterns %>
|
|
2
|
+
<%
|
|
3
|
+
# Display occurrence patterns and bursts for an error
|
|
4
|
+
pattern_data = @error.occurrence_pattern(days: 30)
|
|
5
|
+
bursts = @error.error_bursts(days: 7)
|
|
6
|
+
%>
|
|
7
|
+
|
|
8
|
+
<% if pattern_data.present? && pattern_data[:total_errors] > 0 %>
|
|
9
|
+
<div class="card mb-4">
|
|
10
|
+
<div class="card-header bg-white">
|
|
11
|
+
<h5 class="mb-0">
|
|
12
|
+
<i class="bi bi-graph-up"></i> Occurrence Patterns
|
|
13
|
+
<small class="text-muted">(Last 30 days)</small>
|
|
14
|
+
</h5>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="card-body">
|
|
17
|
+
<!-- Pattern Type Summary -->
|
|
18
|
+
<div class="row mb-4">
|
|
19
|
+
<div class="col-md-6">
|
|
20
|
+
<h6 class="text-muted mb-2">Pattern Type</h6>
|
|
21
|
+
<% case pattern_data[:pattern_type] %>
|
|
22
|
+
<% when :business_hours %>
|
|
23
|
+
<span class="badge bg-info fs-6">
|
|
24
|
+
<i class="bi bi-clock"></i> Business Hours Pattern
|
|
25
|
+
</span>
|
|
26
|
+
<p class="small text-muted mt-2">
|
|
27
|
+
Errors peak during business hours (9am-5pm), suggesting user activity correlation
|
|
28
|
+
</p>
|
|
29
|
+
<% when :night %>
|
|
30
|
+
<span class="badge bg-dark fs-6">
|
|
31
|
+
<i class="bi bi-moon-fill"></i> Night Pattern
|
|
32
|
+
</span>
|
|
33
|
+
<p class="small text-muted mt-2">
|
|
34
|
+
Errors peak during night hours (midnight-6am), possibly from background jobs or automated tasks
|
|
35
|
+
</p>
|
|
36
|
+
<% when :weekend %>
|
|
37
|
+
<span class="badge bg-primary fs-6">
|
|
38
|
+
<i class="bi bi-calendar-week"></i> Weekend Pattern
|
|
39
|
+
</span>
|
|
40
|
+
<p class="small text-muted mt-2">
|
|
41
|
+
Most errors occur on weekends, suggesting weekend-specific usage patterns
|
|
42
|
+
</p>
|
|
43
|
+
<% when :uniform %>
|
|
44
|
+
<span class="badge bg-secondary fs-6">
|
|
45
|
+
<i class="bi bi-distribute-horizontal"></i> Uniform Pattern
|
|
46
|
+
</span>
|
|
47
|
+
<p class="small text-muted mt-2">
|
|
48
|
+
Errors distributed evenly throughout the day with no clear pattern
|
|
49
|
+
</p>
|
|
50
|
+
<% else %>
|
|
51
|
+
<span class="badge bg-light text-dark fs-6">
|
|
52
|
+
<i class="bi bi-question-circle"></i> No Pattern Detected
|
|
53
|
+
</span>
|
|
54
|
+
<p class="small text-muted mt-2">
|
|
55
|
+
Not enough data to identify a clear occurrence pattern
|
|
56
|
+
</p>
|
|
57
|
+
<% end %>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="col-md-6">
|
|
61
|
+
<h6 class="text-muted mb-2">Pattern Strength</h6>
|
|
62
|
+
<div class="d-flex align-items-center">
|
|
63
|
+
<div class="progress flex-grow-1" style="height: 25px;">
|
|
64
|
+
<div class="progress-bar <%= pattern_data[:pattern_strength] >= 0.7 ? 'bg-success' : pattern_data[:pattern_strength] >= 0.4 ? 'bg-warning' : 'bg-secondary' %>"
|
|
65
|
+
role="progressbar"
|
|
66
|
+
style="width: <%= (pattern_data[:pattern_strength] * 100).round %>%">
|
|
67
|
+
<%= (pattern_data[:pattern_strength] * 100).round %>%
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<span class="ms-2 fw-bold"><%= (pattern_data[:pattern_strength] * 100).round %>%</span>
|
|
71
|
+
</div>
|
|
72
|
+
<p class="small text-muted mt-2">
|
|
73
|
+
<% if pattern_data[:pattern_strength] >= 0.7 %>
|
|
74
|
+
<strong>Strong pattern</strong> - Errors highly concentrated in specific hours
|
|
75
|
+
<% elsif pattern_data[:pattern_strength] >= 0.4 %>
|
|
76
|
+
<strong>Moderate pattern</strong> - Some concentration in specific hours
|
|
77
|
+
<% else %>
|
|
78
|
+
<strong>Weak pattern</strong> - Errors distributed fairly evenly
|
|
79
|
+
<% end %>
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<!-- Hourly Distribution Heatmap -->
|
|
85
|
+
<% if pattern_data[:hourly_distribution].present? %>
|
|
86
|
+
<h6 class="text-muted mb-3">Hourly Distribution Heatmap</h6>
|
|
87
|
+
<div class="mb-4">
|
|
88
|
+
<div class="d-flex flex-wrap">
|
|
89
|
+
<% (0..23).each do |hour| %>
|
|
90
|
+
<% count = pattern_data[:hourly_distribution][hour] || 0 %>
|
|
91
|
+
<% max_count = pattern_data[:hourly_distribution].values.max || 1 %>
|
|
92
|
+
<% intensity = max_count > 0 ? (count.to_f / max_count * 100).round : 0 %>
|
|
93
|
+
<% is_peak = pattern_data[:peak_hours].include?(hour) %>
|
|
94
|
+
<div class="text-center m-1" style="width: 45px;">
|
|
95
|
+
<div class="rounded p-2 <%= is_peak ? 'border border-danger border-2' : '' %>"
|
|
96
|
+
style="background-color: rgba(220, 53, 69, <%= intensity / 100.0 %>); min-height: 50px; display: flex; align-items: center; justify-content: center;"
|
|
97
|
+
title="<%= hour %>:00 - <%= count %> errors">
|
|
98
|
+
<small class="fw-bold <%= intensity > 50 ? 'text-white' : 'text-dark' %>">
|
|
99
|
+
<%= count %>
|
|
100
|
+
</small>
|
|
101
|
+
</div>
|
|
102
|
+
<small class="text-muted"><%= hour %></small>
|
|
103
|
+
</div>
|
|
104
|
+
<% end %>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="mt-2 small text-muted">
|
|
107
|
+
<i class="bi bi-info-circle"></i>
|
|
108
|
+
Darker cells = more errors. Red borders indicate peak hours (>2x average).
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<% end %>
|
|
112
|
+
|
|
113
|
+
<!-- Peak Hours -->
|
|
114
|
+
<% if pattern_data[:peak_hours].present? %>
|
|
115
|
+
<div class="alert alert-info">
|
|
116
|
+
<strong><i class="bi bi-clock-fill"></i> Peak Hours:</strong>
|
|
117
|
+
<%= pattern_data[:peak_hours].map { |h| "#{h}:00" }.join(", ") %>
|
|
118
|
+
<br>
|
|
119
|
+
<small>
|
|
120
|
+
These hours have more than 2x the average error rate. Consider monitoring deployments or load during these times.
|
|
121
|
+
</small>
|
|
122
|
+
</div>
|
|
123
|
+
<% end %>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<% end %>
|
|
127
|
+
|
|
128
|
+
<!-- Error Bursts -->
|
|
129
|
+
<% if bursts.present? && bursts.any? %>
|
|
130
|
+
<div class="card mb-4">
|
|
131
|
+
<div class="card-header bg-white">
|
|
132
|
+
<h5 class="mb-0">
|
|
133
|
+
<i class="bi bi-lightning-charge-fill text-warning"></i> Error Bursts
|
|
134
|
+
<span class="badge bg-warning text-dark"><%= bursts.count %></span>
|
|
135
|
+
<small class="text-muted">(Last 7 days)</small>
|
|
136
|
+
</h5>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="card-body">
|
|
139
|
+
<p class="text-muted">
|
|
140
|
+
Error bursts are rapid sequences where multiple errors occur within minutes. This may indicate cascading failures or load spikes.
|
|
141
|
+
</p>
|
|
142
|
+
|
|
143
|
+
<div class="table-responsive">
|
|
144
|
+
<table class="table table-hover">
|
|
145
|
+
<thead>
|
|
146
|
+
<tr>
|
|
147
|
+
<th>Start Time</th>
|
|
148
|
+
<th>Duration</th>
|
|
149
|
+
<th>Error Count</th>
|
|
150
|
+
<th>Intensity</th>
|
|
151
|
+
</tr>
|
|
152
|
+
</thead>
|
|
153
|
+
<tbody>
|
|
154
|
+
<% bursts.first(10).each do |burst| %>
|
|
155
|
+
<tr>
|
|
156
|
+
<td>
|
|
157
|
+
<%= burst[:start_time].strftime("%b %d, %Y %I:%M %p") %>
|
|
158
|
+
</td>
|
|
159
|
+
<td>
|
|
160
|
+
<% duration_minutes = (burst[:duration_seconds] / 60.0).round(1) %>
|
|
161
|
+
<%= duration_minutes %> min
|
|
162
|
+
<small class="text-muted">(<%= burst[:duration_seconds].round %>s)</small>
|
|
163
|
+
</td>
|
|
164
|
+
<td>
|
|
165
|
+
<strong><%= burst[:error_count] %></strong> errors
|
|
166
|
+
</td>
|
|
167
|
+
<td>
|
|
168
|
+
<% case burst[:burst_intensity] %>
|
|
169
|
+
<% when :high %>
|
|
170
|
+
<span class="badge bg-danger">High (20+ errors)</span>
|
|
171
|
+
<% when :medium %>
|
|
172
|
+
<span class="badge bg-warning text-dark">Medium (10-19 errors)</span>
|
|
173
|
+
<% when :low %>
|
|
174
|
+
<span class="badge bg-info">Low (5-9 errors)</span>
|
|
175
|
+
<% end %>
|
|
176
|
+
</td>
|
|
177
|
+
</tr>
|
|
178
|
+
<% end %>
|
|
179
|
+
</tbody>
|
|
180
|
+
</table>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<% if bursts.count > 10 %>
|
|
184
|
+
<p class="text-muted small mb-0">
|
|
185
|
+
Showing 10 of <%= bursts.count %> detected bursts
|
|
186
|
+
</p>
|
|
187
|
+
<% end %>
|
|
188
|
+
|
|
189
|
+
<div class="alert alert-warning mt-3">
|
|
190
|
+
<strong><i class="bi bi-exclamation-triangle"></i> Recommendation:</strong>
|
|
191
|
+
Multiple error bursts detected. Investigate:
|
|
192
|
+
<ul class="mb-0 mt-2">
|
|
193
|
+
<li>Recent deployments or configuration changes around burst times</li>
|
|
194
|
+
<li>Load spikes or traffic patterns</li>
|
|
195
|
+
<li>Cascading failures from related errors</li>
|
|
196
|
+
<li>Background job failures or retry storms</li>
|
|
197
|
+
</ul>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
<% end %>
|
|
202
|
+
|
|
203
|
+
<% if (pattern_data.blank? || pattern_data[:total_errors] == 0) && bursts.blank? %>
|
|
204
|
+
<div class="alert alert-info">
|
|
205
|
+
<i class="bi bi-info-circle"></i>
|
|
206
|
+
No occurrence patterns or bursts detected. More data may be needed for pattern analysis.
|
|
207
|
+
</div>
|
|
208
|
+
<% end %>
|
|
209
|
+
<% end %>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div class="row g-4">
|
|
2
|
+
<div class="col-md-3">
|
|
3
|
+
<div class="card stat-card">
|
|
4
|
+
<div class="card-body">
|
|
5
|
+
<div class="stat-label mb-2">Today</div>
|
|
6
|
+
<div class="stat-value text-info"><%= stats[:total_today] %></div>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="col-md-3">
|
|
11
|
+
<div class="card stat-card">
|
|
12
|
+
<div class="card-body">
|
|
13
|
+
<div class="stat-label mb-2">This Week</div>
|
|
14
|
+
<div class="stat-value text-primary"><%= stats[:total_week] %></div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="col-md-3">
|
|
19
|
+
<div class="card stat-card">
|
|
20
|
+
<div class="card-body">
|
|
21
|
+
<div class="stat-label mb-2">Unresolved</div>
|
|
22
|
+
<div class="stat-value text-danger"><%= stats[:unresolved] %></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="col-md-3">
|
|
27
|
+
<div class="card stat-card">
|
|
28
|
+
<div class="card-body">
|
|
29
|
+
<div class="stat-label mb-2">Resolved</div>
|
|
30
|
+
<div class="stat-value text-success"><%= stats[:resolved] %></div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|