rails_error_dashboard 0.1.0 → 0.1.3
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 +305 -703
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css +926 -15
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
- data/app/controllers/rails_error_dashboard/application_controller.rb +18 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +140 -4
- data/app/helpers/rails_error_dashboard/application_helper.rb +55 -0
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -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_comment.rb +27 -0
- data/app/models/rails_error_dashboard/error_log.rb +471 -3
- data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +816 -178
- data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
- 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 +78 -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/_timeline.html.erb +167 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +152 -56
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +294 -138
- data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +399 -0
- data/app/views/rails_error_dashboard/errors/show.html.erb +781 -65
- data/config/routes.rb +9 -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/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
- data/db/migrate/20251226020100_create_error_comments.rb +18 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +276 -1
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +272 -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/batch_delete_errors.rb +1 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +272 -7
- data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
- data/lib/rails_error_dashboard/configuration.rb +90 -5
- data/lib/rails_error_dashboard/error_reporter.rb +15 -7
- data/lib/rails_error_dashboard/logger.rb +105 -0
- 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/plugin_registry.rb +2 -2
- data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +3 -4
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -3
- 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 +242 -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 +106 -10
- 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/backtrace_parser.rb +113 -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 +57 -7
- metadata +69 -10
- 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
- data/lib/tasks/rails_error_dashboard_tasks.rake +0 -4
|
@@ -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 heatmap-cell-wrapper" style="width: 45px;">
|
|
95
|
+
<div class="rounded p-2 heatmap-cell <%= 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 heatmap-count <%= intensity > 50 ? 'text-white' : 'text-dark' %>">
|
|
99
|
+
<%= count %>
|
|
100
|
+
</small>
|
|
101
|
+
</div>
|
|
102
|
+
<small class="heatmap-hour"><%= 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>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<%# Timeline view for related errors leading up to this error %>
|
|
2
|
+
<% if @related_errors.any? %>
|
|
3
|
+
<div class="card">
|
|
4
|
+
<div class="card-header bg-white">
|
|
5
|
+
<h5 class="mb-0">
|
|
6
|
+
<i class="bi bi-clock-history"></i> Timeline
|
|
7
|
+
<small class="text-muted ms-2">Recent related errors</small>
|
|
8
|
+
</h5>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<div class="timeline">
|
|
12
|
+
<% @related_errors.each_with_index do |error, index| %>
|
|
13
|
+
<div class="timeline-item <%= index == @related_errors.length - 1 ? 'timeline-item-last' : '' %>">
|
|
14
|
+
<div class="timeline-marker <%= error.id == @error.id ? 'timeline-marker-current' : '' %>">
|
|
15
|
+
<% if error.id == @error.id %>
|
|
16
|
+
<i class="bi bi-exclamation-circle-fill text-danger"></i>
|
|
17
|
+
<% elsif error.resolved? %>
|
|
18
|
+
<i class="bi bi-check-circle-fill text-success"></i>
|
|
19
|
+
<% else %>
|
|
20
|
+
<i class="bi bi-circle-fill text-secondary"></i>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="timeline-content">
|
|
25
|
+
<div class="d-flex justify-content-between align-items-start mb-1">
|
|
26
|
+
<div class="flex-grow-1">
|
|
27
|
+
<% if error.id == @error.id %>
|
|
28
|
+
<strong class="text-danger">
|
|
29
|
+
<i class="bi bi-arrow-right-circle"></i> Current Error
|
|
30
|
+
</strong>
|
|
31
|
+
<% else %>
|
|
32
|
+
<%= link_to error_path(error), class: "text-decoration-none" do %>
|
|
33
|
+
<strong><%= error.error_type %></strong>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if error.platform.present? %>
|
|
38
|
+
<% if error.platform == 'iOS' %>
|
|
39
|
+
<span class="badge badge-ios ms-2">iOS</span>
|
|
40
|
+
<% elsif error.platform == 'Android' %>
|
|
41
|
+
<span class="badge badge-android ms-2">Android</span>
|
|
42
|
+
<% else %>
|
|
43
|
+
<span class="badge badge-api ms-2"><%= error.platform %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<small class="text-muted">
|
|
49
|
+
<%= time_ago_in_words(error.occurred_at) %> ago
|
|
50
|
+
</small>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<% if error.message.present? %>
|
|
54
|
+
<p class="mb-1 text-muted small">
|
|
55
|
+
<%= truncate(error.message, length: 100) %>
|
|
56
|
+
</p>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<div class="d-flex align-items-center gap-2 small">
|
|
60
|
+
<% if error.user_id %>
|
|
61
|
+
<span class="text-muted">
|
|
62
|
+
<i class="bi bi-person"></i>
|
|
63
|
+
User: <%= error.user_id %>
|
|
64
|
+
</span>
|
|
65
|
+
<% end %>
|
|
66
|
+
|
|
67
|
+
<% if error.occurrence_count > 1 %>
|
|
68
|
+
<span class="text-muted">
|
|
69
|
+
<i class="bi bi-arrow-repeat"></i>
|
|
70
|
+
<%= error.occurrence_count %>x
|
|
71
|
+
</span>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<% if error.resolved? %>
|
|
75
|
+
<span class="badge bg-success">
|
|
76
|
+
<i class="bi bi-check-circle"></i> Resolved
|
|
77
|
+
</span>
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
.timeline {
|
|
89
|
+
position: relative;
|
|
90
|
+
padding-left: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.timeline-item {
|
|
94
|
+
position: relative;
|
|
95
|
+
padding-left: 40px;
|
|
96
|
+
padding-bottom: 30px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.timeline-item-last {
|
|
100
|
+
padding-bottom: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.timeline-item::before {
|
|
104
|
+
content: '';
|
|
105
|
+
position: absolute;
|
|
106
|
+
left: 11px;
|
|
107
|
+
top: 30px;
|
|
108
|
+
bottom: 0;
|
|
109
|
+
width: 2px;
|
|
110
|
+
background: var(--bs-border-color);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.timeline-item-last::before {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.timeline-marker {
|
|
118
|
+
position: absolute;
|
|
119
|
+
left: 0;
|
|
120
|
+
top: 0;
|
|
121
|
+
width: 24px;
|
|
122
|
+
height: 24px;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
background: white;
|
|
127
|
+
border: 2px solid var(--bs-border-color);
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.timeline-marker-current {
|
|
133
|
+
border-color: var(--bs-danger);
|
|
134
|
+
background: var(--bs-danger-bg-subtle);
|
|
135
|
+
box-shadow: 0 0 0 4px var(--bs-danger-bg-subtle);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.timeline-marker i {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.timeline-content {
|
|
143
|
+
background: var(--bs-body-bg);
|
|
144
|
+
padding: 12px;
|
|
145
|
+
border-radius: 8px;
|
|
146
|
+
border: 1px solid var(--bs-border-color);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.timeline-item:hover .timeline-content {
|
|
150
|
+
border-color: var(--bs-primary);
|
|
151
|
+
background: var(--bs-primary-bg-subtle);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Dark mode support */
|
|
155
|
+
body.dark-mode .timeline-marker,
|
|
156
|
+
html[data-theme="dark"] body .timeline-marker {
|
|
157
|
+
background: var(--card-bg);
|
|
158
|
+
border-color: var(--border-color);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
body.dark-mode .timeline-marker-current,
|
|
162
|
+
html[data-theme="dark"] body .timeline-marker-current {
|
|
163
|
+
border-color: var(--bs-danger);
|
|
164
|
+
background: rgba(220, 53, 69, 0.2);
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
167
|
+
<% end %>
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// Dynamic chart colors based on theme
|
|
3
|
+
window.getChartColors = function() {
|
|
4
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
5
|
+
return {
|
|
6
|
+
textColor: isDark ? '#cdd6f4' : '#1f2937',
|
|
7
|
+
gridColor: isDark ? 'rgba(88, 91, 112, 0.2)' : 'rgba(0, 0, 0, 0.1)'
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
window.getChartLibraryOptions = function() {
|
|
12
|
+
const colors = window.getChartColors();
|
|
13
|
+
return {
|
|
14
|
+
scales: {
|
|
15
|
+
x: {
|
|
16
|
+
ticks: { color: colors.textColor },
|
|
17
|
+
title: { color: colors.textColor },
|
|
18
|
+
grid: { color: colors.gridColor }
|
|
19
|
+
},
|
|
20
|
+
y: {
|
|
21
|
+
ticks: { color: colors.textColor },
|
|
22
|
+
title: { color: colors.textColor },
|
|
23
|
+
grid: { color: colors.gridColor }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
plugins: {
|
|
27
|
+
legend: { labels: { color: colors.textColor } }
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
</script>
|
|
32
|
+
|
|
1
33
|
<div class="py-4">
|
|
2
34
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
3
35
|
<h2 class="mb-0"><i class="bi bi-graph-up text-primary"></i> Error Analytics</h2>
|
|
@@ -16,7 +48,7 @@
|
|
|
16
48
|
|
|
17
49
|
<!-- Summary Stats -->
|
|
18
50
|
<div class="row g-4 mb-4">
|
|
19
|
-
<div class="col-md-
|
|
51
|
+
<div class="col-md-4">
|
|
20
52
|
<div class="card stat-card border-primary">
|
|
21
53
|
<div class="card-body">
|
|
22
54
|
<div class="stat-label mb-2 text-primary">Total Errors</div>
|
|
@@ -24,7 +56,7 @@
|
|
|
24
56
|
</div>
|
|
25
57
|
</div>
|
|
26
58
|
</div>
|
|
27
|
-
<div class="col-md-
|
|
59
|
+
<div class="col-md-4">
|
|
28
60
|
<div class="card stat-card border-danger">
|
|
29
61
|
<div class="card-body">
|
|
30
62
|
<div class="stat-label mb-2 text-danger">Unresolved</div>
|
|
@@ -32,7 +64,7 @@
|
|
|
32
64
|
</div>
|
|
33
65
|
</div>
|
|
34
66
|
</div>
|
|
35
|
-
<div class="col-md-
|
|
67
|
+
<div class="col-md-4">
|
|
36
68
|
<div class="card stat-card border-success">
|
|
37
69
|
<div class="card-body">
|
|
38
70
|
<div class="stat-label mb-2 text-success">Resolution Rate</div>
|
|
@@ -40,15 +72,6 @@
|
|
|
40
72
|
</div>
|
|
41
73
|
</div>
|
|
42
74
|
</div>
|
|
43
|
-
<div class="col-md-3">
|
|
44
|
-
<div class="card stat-card border-info">
|
|
45
|
-
<div class="card-body">
|
|
46
|
-
<div class="stat-label mb-2 text-info">Mobile Errors</div>
|
|
47
|
-
<div class="stat-value text-info"><%= @mobile_errors %></div>
|
|
48
|
-
<small class="text-muted">vs <%= @api_errors %> API</small>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
75
|
</div>
|
|
53
76
|
|
|
54
77
|
<!-- Error Trend Chart -->
|
|
@@ -57,48 +80,73 @@
|
|
|
57
80
|
<h5 class="mb-0"><i class="bi bi-graph-up"></i> Error Trend (Last <%= @days %> Days)</h5>
|
|
58
81
|
</div>
|
|
59
82
|
<div class="card-body">
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
<div id="errors-over-time-chart"></div>
|
|
84
|
+
<script>
|
|
85
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
86
|
+
const colors = window.getChartColors();
|
|
87
|
+
new Chartkick.LineChart("errors-over-time-chart", <%= raw @errors_over_time.to_json %>, {
|
|
88
|
+
color: "#8B5CF6",
|
|
89
|
+
curve: false,
|
|
90
|
+
points: true,
|
|
91
|
+
height: "300px",
|
|
92
|
+
xtitle: "Date",
|
|
93
|
+
ytitle: "Number of Errors",
|
|
94
|
+
library: {
|
|
95
|
+
scales: {
|
|
96
|
+
x: {
|
|
97
|
+
ticks: { color: colors.textColor },
|
|
98
|
+
title: { color: colors.textColor, display: true, text: 'Date' },
|
|
99
|
+
grid: { color: colors.gridColor }
|
|
100
|
+
},
|
|
101
|
+
y: {
|
|
102
|
+
ticks: { color: colors.textColor },
|
|
103
|
+
title: { color: colors.textColor, display: true, text: 'Number of Errors' },
|
|
104
|
+
grid: { color: colors.gridColor }
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
plugins: {
|
|
108
|
+
legend: { labels: { color: colors.textColor } }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
67
114
|
</div>
|
|
68
115
|
</div>
|
|
69
116
|
|
|
70
117
|
<!-- Charts Row 1 -->
|
|
71
|
-
|
|
72
|
-
<div class="col-md-6">
|
|
73
|
-
<div class="card">
|
|
74
|
-
<div class="card-header bg-white">
|
|
75
|
-
<h5 class="mb-0"><i class="bi bi-phone"></i> Errors by Platform</h5>
|
|
76
|
-
</div>
|
|
77
|
-
<div class="card-body">
|
|
78
|
-
<%= pie_chart @errors_by_platform,
|
|
79
|
-
colors: ["#000000", "#3DDC84", "#3B82F6"],
|
|
80
|
-
height: "300px",
|
|
81
|
-
legend: "bottom",
|
|
82
|
-
donut: true %>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
118
|
+
<% show_platform_chart = @errors_by_platform.size > 1 %>
|
|
86
119
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
<% if show_platform_chart %>
|
|
121
|
+
<div class="row g-4 mb-4">
|
|
122
|
+
<div class="col-md-12">
|
|
123
|
+
<div class="card">
|
|
124
|
+
<div class="card-header bg-white">
|
|
125
|
+
<h5 class="mb-0"><i class="bi bi-phone"></i> Errors by Platform</h5>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="card-body">
|
|
128
|
+
<div id="errors-by-platform-chart"></div>
|
|
129
|
+
<script>
|
|
130
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
131
|
+
const colors = window.getChartColors();
|
|
132
|
+
new Chartkick.PieChart("errors-by-platform-chart", <%= raw @errors_by_platform.to_json %>, {
|
|
133
|
+
colors: ["#000000", "#3DDC84", "#3B82F6"],
|
|
134
|
+
height: "300px",
|
|
135
|
+
legend: "bottom",
|
|
136
|
+
donut: true,
|
|
137
|
+
library: {
|
|
138
|
+
plugins: {
|
|
139
|
+
legend: { labels: { color: colors.textColor } }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
</script>
|
|
145
|
+
</div>
|
|
98
146
|
</div>
|
|
99
147
|
</div>
|
|
100
148
|
</div>
|
|
101
|
-
|
|
149
|
+
<% end %>
|
|
102
150
|
|
|
103
151
|
<!-- Charts Row 2 -->
|
|
104
152
|
<div class="row g-4 mb-4">
|
|
@@ -108,11 +156,35 @@
|
|
|
108
156
|
<h5 class="mb-0"><i class="bi bi-bug"></i> Top 10 Error Types</h5>
|
|
109
157
|
</div>
|
|
110
158
|
<div class="card-body">
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
159
|
+
<div id="errors-by-type-chart"></div>
|
|
160
|
+
<script>
|
|
161
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
162
|
+
const colors = window.getChartColors();
|
|
163
|
+
new Chartkick.BarChart("errors-by-type-chart", <%= raw @errors_by_type.to_json %>, {
|
|
164
|
+
color: "#EF4444",
|
|
165
|
+
height: "400px",
|
|
166
|
+
xtitle: "Error Type",
|
|
167
|
+
ytitle: "Count",
|
|
168
|
+
library: {
|
|
169
|
+
scales: {
|
|
170
|
+
x: {
|
|
171
|
+
ticks: { color: colors.textColor },
|
|
172
|
+
title: { color: colors.textColor, display: true, text: 'Error Type' },
|
|
173
|
+
grid: { color: colors.gridColor }
|
|
174
|
+
},
|
|
175
|
+
y: {
|
|
176
|
+
ticks: { color: colors.textColor },
|
|
177
|
+
title: { color: colors.textColor, display: true, text: 'Count' },
|
|
178
|
+
grid: { color: colors.gridColor }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
plugins: {
|
|
182
|
+
legend: { labels: { color: colors.textColor } }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
</script>
|
|
116
188
|
</div>
|
|
117
189
|
</div>
|
|
118
190
|
</div>
|
|
@@ -126,11 +198,35 @@
|
|
|
126
198
|
<h5 class="mb-0"><i class="bi bi-clock"></i> Errors by Hour of Day</h5>
|
|
127
199
|
</div>
|
|
128
200
|
<div class="card-body">
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
<div id="errors-by-hour-chart"></div>
|
|
202
|
+
<script>
|
|
203
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
204
|
+
const colors = window.getChartColors();
|
|
205
|
+
new Chartkick.ColumnChart("errors-by-hour-chart", <%= raw @errors_by_hour.to_json %>, {
|
|
206
|
+
color: "#8B5CF6",
|
|
207
|
+
height: "300px",
|
|
208
|
+
xtitle: "Hour",
|
|
209
|
+
ytitle: "Number of Errors",
|
|
210
|
+
library: {
|
|
211
|
+
scales: {
|
|
212
|
+
x: {
|
|
213
|
+
ticks: { color: colors.textColor },
|
|
214
|
+
title: { color: colors.textColor, display: true, text: 'Hour' },
|
|
215
|
+
grid: { color: colors.gridColor }
|
|
216
|
+
},
|
|
217
|
+
y: {
|
|
218
|
+
ticks: { color: colors.textColor },
|
|
219
|
+
title: { color: colors.textColor, display: true, text: 'Number of Errors' },
|
|
220
|
+
grid: { color: colors.gridColor }
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
plugins: {
|
|
224
|
+
legend: { labels: { color: colors.textColor } }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
</script>
|
|
134
230
|
</div>
|
|
135
231
|
</div>
|
|
136
232
|
</div>
|