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
|
@@ -4,7 +4,19 @@
|
|
|
4
4
|
<%= link_to errors_path, class: "btn btn-outline-secondary btn-sm mb-2" do %>
|
|
5
5
|
<i class="bi bi-arrow-left"></i> Back to Errors
|
|
6
6
|
<% end %>
|
|
7
|
-
<h2 class="mb-0">
|
|
7
|
+
<h2 class="mb-0">
|
|
8
|
+
Error Details
|
|
9
|
+
<% severity = @error.severity %>
|
|
10
|
+
<% if severity == :critical %>
|
|
11
|
+
<span class="badge bg-danger fs-5">CRITICAL</span>
|
|
12
|
+
<% elsif severity == :high %>
|
|
13
|
+
<span class="badge bg-warning text-dark fs-5">HIGH</span>
|
|
14
|
+
<% elsif severity == :medium %>
|
|
15
|
+
<span class="badge bg-info text-dark fs-5">MEDIUM</span>
|
|
16
|
+
<% else %>
|
|
17
|
+
<span class="badge bg-secondary fs-5">LOW</span>
|
|
18
|
+
<% end %>
|
|
19
|
+
</h2>
|
|
8
20
|
</div>
|
|
9
21
|
<div>
|
|
10
22
|
<% if @error.resolved? %>
|
|
@@ -32,11 +44,72 @@
|
|
|
32
44
|
<%= @error.message %>
|
|
33
45
|
</div>
|
|
34
46
|
|
|
35
|
-
<h6 class="text-muted mb-2 mt-4">
|
|
47
|
+
<h6 class="text-muted mb-2 mt-4">
|
|
48
|
+
Backtrace:
|
|
49
|
+
<% if @error.backtrace.present? %>
|
|
50
|
+
<% frames = parse_backtrace(@error.backtrace) %>
|
|
51
|
+
<% app_frames = filter_app_code(frames) %>
|
|
52
|
+
<% framework_frames = filter_framework_code(frames) %>
|
|
53
|
+
<small class="text-muted">
|
|
54
|
+
(<%= app_frames.count %> your code, <%= framework_frames.count %> framework/gems)
|
|
55
|
+
</small>
|
|
56
|
+
<% end %>
|
|
57
|
+
</h6>
|
|
36
58
|
<% if @error.backtrace.present? %>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
<% frames = parse_backtrace(@error.backtrace) %>
|
|
60
|
+
<% app_frames = filter_app_code(frames) %>
|
|
61
|
+
<% framework_frames = filter_framework_code(frames) %>
|
|
62
|
+
|
|
63
|
+
<% if app_frames.any? %>
|
|
64
|
+
<!-- Your Code Section (Expanded by default) -->
|
|
65
|
+
<div class="card mb-3">
|
|
66
|
+
<div class="card-header bg-success bg-opacity-10 border-success">
|
|
67
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
68
|
+
<strong class="text-success">
|
|
69
|
+
<i class="bi bi-code-square"></i> Your Code
|
|
70
|
+
</strong>
|
|
71
|
+
<span class="badge bg-success"><%= app_frames.count %> frames</span>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="card-body p-0">
|
|
75
|
+
<div class="code-block p-3" style="max-height: 300px; overflow-y: auto; overflow-x: auto;">
|
|
76
|
+
<pre class="mb-0"><code><% app_frames.each do |frame| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="text-info">`<%= frame[:method_name] %>'</span></span>
|
|
77
|
+
<% end %></code></pre>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<% end %>
|
|
82
|
+
|
|
83
|
+
<% if framework_frames.any? %>
|
|
84
|
+
<!-- Framework/Gem Code Section (Collapsed by default) -->
|
|
85
|
+
<div class="accordion" id="frameworkBacktraceAccordion">
|
|
86
|
+
<div class="accordion-item">
|
|
87
|
+
<h2 class="accordion-header">
|
|
88
|
+
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#frameworkBacktrace" aria-expanded="false" aria-controls="frameworkBacktrace">
|
|
89
|
+
<i class="bi bi-gear me-2 text-warning"></i>
|
|
90
|
+
<strong>Framework & Gem Code</strong>
|
|
91
|
+
<span class="badge bg-secondary ms-2"><%= framework_frames.count %> frames</span>
|
|
92
|
+
<small class="text-muted ms-2">(click to expand)</small>
|
|
93
|
+
</button>
|
|
94
|
+
</h2>
|
|
95
|
+
<div id="frameworkBacktrace" class="accordion-collapse collapse" data-bs-parent="#frameworkBacktraceAccordion">
|
|
96
|
+
<div class="accordion-body p-0">
|
|
97
|
+
<div class="code-block p-3" style="max-height: 400px; overflow-y: auto; overflow-x: auto;">
|
|
98
|
+
<pre class="mb-0"><code><% framework_frames.each do |frame| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="text-info">`<%= frame[:method_name] %>'</span></span>
|
|
99
|
+
<% end %></code></pre>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<% end %>
|
|
106
|
+
|
|
107
|
+
<% if app_frames.empty? && framework_frames.empty? %>
|
|
108
|
+
<!-- Fallback: Show raw backtrace if parsing failed -->
|
|
109
|
+
<div class="code-block p-3 rounded" style="max-height: 400px; overflow-y: auto; overflow-x: auto;">
|
|
110
|
+
<pre class="mb-0"><code><%= @error.backtrace %></code></pre>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
40
113
|
<% else %>
|
|
41
114
|
<p class="text-muted">No backtrace available</p>
|
|
42
115
|
<% end %>
|
|
@@ -66,7 +139,28 @@
|
|
|
66
139
|
</tr>
|
|
67
140
|
<tr>
|
|
68
141
|
<th>User Agent:</th>
|
|
69
|
-
<td
|
|
142
|
+
<td>
|
|
143
|
+
<% if @error.user_agent.present? %>
|
|
144
|
+
<% ua_info = parse_user_agent(@error.user_agent) %>
|
|
145
|
+
<div class="mb-2">
|
|
146
|
+
<%= browser_icon(ua_info) %>
|
|
147
|
+
<strong><%= ua_info[:browser_name] %></strong>
|
|
148
|
+
<% if ua_info[:browser_version].present? %>
|
|
149
|
+
<span class="text-muted"><%= ua_info[:browser_version] %></span>
|
|
150
|
+
<% end %>
|
|
151
|
+
<span class="ms-2"><%= os_icon(ua_info) %> <%= ua_info[:os_name] %></span>
|
|
152
|
+
<span class="ms-2"><%= device_icon(ua_info) %> <%= ua_info[:device_type] %></span>
|
|
153
|
+
</div>
|
|
154
|
+
<details>
|
|
155
|
+
<summary class="text-muted" style="cursor: pointer;">
|
|
156
|
+
<small>Raw User Agent</small>
|
|
157
|
+
</summary>
|
|
158
|
+
<small class="text-muted"><%= @error.user_agent %></small>
|
|
159
|
+
</details>
|
|
160
|
+
<% else %>
|
|
161
|
+
<span class="text-muted">N/A</span>
|
|
162
|
+
<% end %>
|
|
163
|
+
</td>
|
|
70
164
|
</tr>
|
|
71
165
|
<tr>
|
|
72
166
|
<th>IP Address:</th>
|
|
@@ -76,60 +170,355 @@
|
|
|
76
170
|
</div>
|
|
77
171
|
</div>
|
|
78
172
|
|
|
79
|
-
<!--
|
|
80
|
-
<% if @
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
173
|
+
<!-- Similar Errors (Fuzzy Matching) -->
|
|
174
|
+
<% if RailsErrorDashboard.configuration.enable_similar_errors && @error.respond_to?(:similar_errors) %>
|
|
175
|
+
<% similar = @error.similar_errors(threshold: 0.6, limit: 5) %>
|
|
176
|
+
<% if similar.any? %>
|
|
177
|
+
<div class="card mb-4">
|
|
178
|
+
<div class="card-header bg-white">
|
|
179
|
+
<h5 class="mb-0">
|
|
180
|
+
<i class="bi bi-diagram-3"></i> Similar Errors
|
|
181
|
+
<span class="badge bg-info text-dark">Fuzzy Matching</span>
|
|
182
|
+
</h5>
|
|
183
|
+
<small class="text-muted">Errors with similar backtraces and messages (60%+ similarity)</small>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="card-body p-0">
|
|
186
|
+
<div class="table-responsive">
|
|
187
|
+
<table class="table table-hover mb-0">
|
|
188
|
+
<thead class="table-light">
|
|
189
|
+
<tr>
|
|
190
|
+
<th>Similarity</th>
|
|
191
|
+
<th>Error Type</th>
|
|
192
|
+
<th>Message</th>
|
|
193
|
+
<th>Platform</th>
|
|
194
|
+
<th>Occurrences</th>
|
|
195
|
+
<th></th>
|
|
196
|
+
</tr>
|
|
197
|
+
</thead>
|
|
198
|
+
<tbody>
|
|
199
|
+
<% similar.each do |item| %>
|
|
200
|
+
<% similar_error = item[:error] %>
|
|
201
|
+
<% similarity_pct = (item[:similarity] * 100).round %>
|
|
202
|
+
<tr>
|
|
203
|
+
<td>
|
|
204
|
+
<div class="progress" style="width: 60px; height: 20px;">
|
|
205
|
+
<div class="progress-bar bg-success" role="progressbar"
|
|
206
|
+
style="width: <%= similarity_pct %>%"
|
|
207
|
+
aria-valuenow="<%= similarity_pct %>"
|
|
208
|
+
aria-valuemin="0"
|
|
209
|
+
aria-valuemax="100">
|
|
210
|
+
<%= similarity_pct %>%
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</td>
|
|
214
|
+
<td><code><%= similar_error.error_type %></code></td>
|
|
215
|
+
<td>
|
|
216
|
+
<div class="text-truncate" style="max-width: 300px;"
|
|
217
|
+
data-bs-toggle="tooltip"
|
|
218
|
+
title="<%= similar_error.message %>">
|
|
219
|
+
<%= similar_error.message %>
|
|
220
|
+
</div>
|
|
221
|
+
</td>
|
|
222
|
+
<td>
|
|
223
|
+
<% if similar_error.platform == 'iOS' %>
|
|
224
|
+
<span class="badge badge-ios">iOS</span>
|
|
225
|
+
<% elsif similar_error.platform == 'Android' %>
|
|
226
|
+
<span class="badge badge-android">Android</span>
|
|
227
|
+
<% else %>
|
|
228
|
+
<span class="badge badge-api"><%= similar_error.platform || 'API' %></span>
|
|
229
|
+
<% end %>
|
|
230
|
+
</td>
|
|
231
|
+
<td><span class="badge bg-primary"><%= similar_error.occurrence_count %>x</span></td>
|
|
232
|
+
<td>
|
|
233
|
+
<%= link_to "View", error_path(similar_error), class: "btn btn-sm btn-outline-primary" %>
|
|
234
|
+
</td>
|
|
235
|
+
</tr>
|
|
236
|
+
<% end %>
|
|
237
|
+
</tbody>
|
|
238
|
+
</table>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
84
241
|
</div>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
</
|
|
97
|
-
|
|
98
|
-
|
|
242
|
+
<% end %>
|
|
243
|
+
<% end %>
|
|
244
|
+
|
|
245
|
+
<!-- Co-occurring Errors -->
|
|
246
|
+
<% if RailsErrorDashboard.configuration.enable_co_occurring_errors && @error.respond_to?(:co_occurring_errors) %>
|
|
247
|
+
<% co_occurring = @error.co_occurring_errors(window_minutes: 5, min_frequency: 2, limit: 5) %>
|
|
248
|
+
<% if co_occurring.any? %>
|
|
249
|
+
<div class="card mb-4">
|
|
250
|
+
<div class="card-header bg-white">
|
|
251
|
+
<h5 class="mb-0">
|
|
252
|
+
<i class="bi bi-clock-history"></i> Co-occurring Errors
|
|
253
|
+
<span class="badge bg-info text-dark">Time Patterns</span>
|
|
254
|
+
</h5>
|
|
255
|
+
<small class="text-muted">Errors that occur within 5 minutes of this error</small>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="card-body p-0">
|
|
258
|
+
<div class="table-responsive">
|
|
259
|
+
<table class="table table-hover mb-0">
|
|
260
|
+
<thead class="table-light">
|
|
99
261
|
<tr>
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<td>
|
|
107
|
-
<% if related.platform == 'iOS' %>
|
|
108
|
-
<span class="badge badge-ios">iOS</span>
|
|
109
|
-
<% elsif related.platform == 'Android' %>
|
|
110
|
-
<span class="badge badge-android">Android</span>
|
|
111
|
-
<% else %>
|
|
112
|
-
<span class="badge badge-api"><%= related.platform || 'API' %></span>
|
|
113
|
-
<% end %>
|
|
114
|
-
</td>
|
|
115
|
-
<td>
|
|
116
|
-
<% if related.resolved? %>
|
|
117
|
-
<i class="bi bi-check-circle-fill text-success"></i>
|
|
118
|
-
<% else %>
|
|
119
|
-
<i class="bi bi-exclamation-circle-fill text-danger"></i>
|
|
120
|
-
<% end %>
|
|
121
|
-
</td>
|
|
122
|
-
<td>
|
|
123
|
-
<%= link_to "View", error_path(related), class: "btn btn-sm btn-outline-primary" %>
|
|
124
|
-
</td>
|
|
262
|
+
<th>Error Type</th>
|
|
263
|
+
<th>Message</th>
|
|
264
|
+
<th>Platform</th>
|
|
265
|
+
<th>Frequency</th>
|
|
266
|
+
<th>Avg Delay</th>
|
|
267
|
+
<th></th>
|
|
125
268
|
</tr>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
269
|
+
</thead>
|
|
270
|
+
<tbody>
|
|
271
|
+
<% co_occurring.each do |result| %>
|
|
272
|
+
<% error = result[:error] %>
|
|
273
|
+
<% frequency = result[:frequency] %>
|
|
274
|
+
<% avg_delay = result[:avg_delay_seconds] %>
|
|
275
|
+
<tr>
|
|
276
|
+
<td>
|
|
277
|
+
<span class="badge bg-danger"><%= error.error_type %></span>
|
|
278
|
+
</td>
|
|
279
|
+
<td>
|
|
280
|
+
<span data-bs-toggle="tooltip" title="<%= error.message %>">
|
|
281
|
+
<%= truncate(error.message, length: 60) %>
|
|
282
|
+
</span>
|
|
283
|
+
</td>
|
|
284
|
+
<td>
|
|
285
|
+
<% if error.platform.present? %>
|
|
286
|
+
<span class="badge bg-secondary"><%= error.platform %></span>
|
|
287
|
+
<% end %>
|
|
288
|
+
</td>
|
|
289
|
+
<td>
|
|
290
|
+
<span class="badge bg-info text-dark">
|
|
291
|
+
<%= frequency %> time<%= frequency > 1 ? 's' : '' %>
|
|
292
|
+
</span>
|
|
293
|
+
</td>
|
|
294
|
+
<td>
|
|
295
|
+
<% if avg_delay < 0 %>
|
|
296
|
+
<span class="text-warning"><%= number_to_human(avg_delay.abs, units: {unit: "s", thousand: "s"}, precision: 1) %> before</span>
|
|
297
|
+
<% elsif avg_delay > 0 %>
|
|
298
|
+
<span class="text-success"><%= number_to_human(avg_delay, units: {unit: "s", thousand: "s"}, precision: 1) %> after</span>
|
|
299
|
+
<% else %>
|
|
300
|
+
<span class="text-muted">simultaneous</span>
|
|
301
|
+
<% end %>
|
|
302
|
+
</td>
|
|
303
|
+
<td class="text-end">
|
|
304
|
+
<%= link_to "View", error_path(error), class: "btn btn-sm btn-outline-primary" %>
|
|
305
|
+
</td>
|
|
306
|
+
</tr>
|
|
307
|
+
<% end %>
|
|
308
|
+
</tbody>
|
|
309
|
+
</table>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
<% end %>
|
|
314
|
+
<% end %>
|
|
315
|
+
|
|
316
|
+
<!-- Timeline: Related Errors -->
|
|
317
|
+
<%= render "timeline" %>
|
|
318
|
+
|
|
319
|
+
<!-- Phase 3: Comment Threads -->
|
|
320
|
+
<% if @error.respond_to?(:comments) %>
|
|
321
|
+
<div class="card mb-4">
|
|
322
|
+
<div class="card-header bg-white">
|
|
323
|
+
<h5 class="mb-0">
|
|
324
|
+
<i class="bi bi-chat-dots"></i> Discussion
|
|
325
|
+
<span class="badge bg-secondary ms-2"><%= @error.comments.count %></span>
|
|
326
|
+
</h5>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="card-body">
|
|
329
|
+
<!-- Existing Comments -->
|
|
330
|
+
<% if @error.comments.recent_first.any? %>
|
|
331
|
+
<div class="mb-4">
|
|
332
|
+
<% @error.comments.recent_first.each do |comment| %>
|
|
333
|
+
<div class="border-bottom pb-3 mb-3">
|
|
334
|
+
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
335
|
+
<div>
|
|
336
|
+
<strong class="text-primary">
|
|
337
|
+
<i class="bi bi-person-circle"></i> <%= comment.author_name %>
|
|
338
|
+
</strong>
|
|
339
|
+
<% if comment.recent? %>
|
|
340
|
+
<span class="badge bg-success ms-2">New</span>
|
|
341
|
+
<% end %>
|
|
342
|
+
</div>
|
|
343
|
+
<small class="text-muted">
|
|
344
|
+
<%= comment.formatted_time %>
|
|
345
|
+
<span class="ms-1 text-muted">(<%= time_ago_in_words(comment.created_at) %> ago)</span>
|
|
346
|
+
</small>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="text-break">
|
|
349
|
+
<%= simple_format(comment.body, class: "mb-0") %>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
<% end %>
|
|
353
|
+
</div>
|
|
354
|
+
<% else %>
|
|
355
|
+
<p class="text-muted mb-4">
|
|
356
|
+
<i class="bi bi-info-circle"></i> No comments yet. Start the discussion below.
|
|
357
|
+
</p>
|
|
358
|
+
<% end %>
|
|
359
|
+
|
|
360
|
+
<!-- Add Comment Form -->
|
|
361
|
+
<div class="border-top pt-3">
|
|
362
|
+
<h6 class="mb-3">
|
|
363
|
+
<i class="bi bi-plus-circle"></i> Add Comment
|
|
364
|
+
</h6>
|
|
365
|
+
<%= form_with url: add_comment_error_path(@error), method: :post do |f| %>
|
|
366
|
+
<div class="mb-3">
|
|
367
|
+
<label for="author_name" class="form-label">Your Name <span class="text-danger">*</span></label>
|
|
368
|
+
<%= text_field_tag :author_name, @error.assigned_to, class: "form-control", placeholder: "e.g., John Doe", required: true %>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="mb-3">
|
|
371
|
+
<label for="body" class="form-label">Comment <span class="text-danger">*</span></label>
|
|
372
|
+
<%= text_area_tag :body, nil, class: "form-control", rows: 4, placeholder: "Share your thoughts, findings, or updates...", required: true %>
|
|
373
|
+
</div>
|
|
374
|
+
<%= submit_tag "Post Comment", class: "btn btn-primary" %>
|
|
375
|
+
<% end %>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
<% end %>
|
|
380
|
+
|
|
381
|
+
<!-- Error Cascades -->
|
|
382
|
+
<% if RailsErrorDashboard.configuration.enable_error_cascades %>
|
|
383
|
+
<% cascades = @error.error_cascades %>
|
|
384
|
+
<% if cascades[:parents].any? || cascades[:children].any? %>
|
|
385
|
+
<div class="card">
|
|
386
|
+
<div class="card-header bg-white">
|
|
387
|
+
<h5 class="mb-0">
|
|
388
|
+
<i class="bi bi-diagram-3"></i> Error Cascades
|
|
389
|
+
<small class="text-muted ms-2">Causal relationships between errors</small>
|
|
390
|
+
</h5>
|
|
391
|
+
</div>
|
|
392
|
+
<div class="card-body">
|
|
393
|
+
<!-- Parent Errors (What causes this error) -->
|
|
394
|
+
<% if cascades[:parents].any? %>
|
|
395
|
+
<div class="mb-4">
|
|
396
|
+
<h6 class="text-danger">
|
|
397
|
+
<i class="bi bi-arrow-down-circle"></i> Triggered By
|
|
398
|
+
<small class="text-muted">(errors that cause this one)</small>
|
|
399
|
+
</h6>
|
|
400
|
+
<div class="table-responsive">
|
|
401
|
+
<table class="table table-sm table-hover mb-0">
|
|
402
|
+
<thead class="table-light">
|
|
403
|
+
<tr>
|
|
404
|
+
<th>Error Type</th>
|
|
405
|
+
<th>Message</th>
|
|
406
|
+
<th>Probability</th>
|
|
407
|
+
<th>Frequency</th>
|
|
408
|
+
<th>Avg Delay</th>
|
|
409
|
+
<th></th>
|
|
410
|
+
</tr>
|
|
411
|
+
</thead>
|
|
412
|
+
<tbody>
|
|
413
|
+
<% cascades[:parents].each do |cascade| %>
|
|
414
|
+
<% parent = cascade[:error] %>
|
|
415
|
+
<tr>
|
|
416
|
+
<td>
|
|
417
|
+
<span class="badge bg-<%= severity_color(parent.severity) %>">
|
|
418
|
+
<%= parent.error_type %>
|
|
419
|
+
</span>
|
|
420
|
+
</td>
|
|
421
|
+
<td>
|
|
422
|
+
<div class="text-truncate" style="max-width: 250px;">
|
|
423
|
+
<%= parent.message %>
|
|
424
|
+
</div>
|
|
425
|
+
</td>
|
|
426
|
+
<td>
|
|
427
|
+
<% probability_pct = (cascade[:probability] * 100).round(1) %>
|
|
428
|
+
<span class="badge <%= probability_pct >= 70 ? 'bg-danger' : 'bg-warning' %>">
|
|
429
|
+
<%= probability_pct %>%
|
|
430
|
+
</span>
|
|
431
|
+
</td>
|
|
432
|
+
<td>
|
|
433
|
+
<span class="badge bg-secondary"><%= cascade[:frequency] %>x</span>
|
|
434
|
+
</td>
|
|
435
|
+
<td>
|
|
436
|
+
<small class="text-muted">
|
|
437
|
+
<%= cascade[:avg_delay_seconds].round(1) %>s
|
|
438
|
+
</small>
|
|
439
|
+
</td>
|
|
440
|
+
<td>
|
|
441
|
+
<%= link_to "View", error_path(parent), class: "btn btn-sm btn-outline-primary" %>
|
|
442
|
+
</td>
|
|
443
|
+
</tr>
|
|
444
|
+
<% end %>
|
|
445
|
+
</tbody>
|
|
446
|
+
</table>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
<% end %>
|
|
450
|
+
|
|
451
|
+
<!-- Child Errors (What this error causes) -->
|
|
452
|
+
<% if cascades[:children].any? %>
|
|
453
|
+
<div>
|
|
454
|
+
<h6 class="text-warning">
|
|
455
|
+
<i class="bi bi-arrow-up-circle"></i> Triggers
|
|
456
|
+
<small class="text-muted">(errors caused by this one)</small>
|
|
457
|
+
</h6>
|
|
458
|
+
<div class="table-responsive">
|
|
459
|
+
<table class="table table-sm table-hover mb-0">
|
|
460
|
+
<thead class="table-light">
|
|
461
|
+
<tr>
|
|
462
|
+
<th>Error Type</th>
|
|
463
|
+
<th>Message</th>
|
|
464
|
+
<th>Probability</th>
|
|
465
|
+
<th>Frequency</th>
|
|
466
|
+
<th>Avg Delay</th>
|
|
467
|
+
<th></th>
|
|
468
|
+
</tr>
|
|
469
|
+
</thead>
|
|
470
|
+
<tbody>
|
|
471
|
+
<% cascades[:children].each do |cascade| %>
|
|
472
|
+
<% child = cascade[:error] %>
|
|
473
|
+
<tr>
|
|
474
|
+
<td>
|
|
475
|
+
<span class="badge bg-<%= severity_color(child.severity) %>">
|
|
476
|
+
<%= child.error_type %>
|
|
477
|
+
</span>
|
|
478
|
+
</td>
|
|
479
|
+
<td>
|
|
480
|
+
<div class="text-truncate" style="max-width: 250px;">
|
|
481
|
+
<%= child.message %>
|
|
482
|
+
</div>
|
|
483
|
+
</td>
|
|
484
|
+
<td>
|
|
485
|
+
<% probability_pct = (cascade[:probability] * 100).round(1) %>
|
|
486
|
+
<span class="badge <%= probability_pct >= 70 ? 'bg-danger' : 'bg-warning' %>">
|
|
487
|
+
<%= probability_pct %>%
|
|
488
|
+
</span>
|
|
489
|
+
</td>
|
|
490
|
+
<td>
|
|
491
|
+
<span class="badge bg-secondary"><%= cascade[:frequency] %>x</span>
|
|
492
|
+
</td>
|
|
493
|
+
<td>
|
|
494
|
+
<small class="text-muted">
|
|
495
|
+
+<%= cascade[:avg_delay_seconds].round(1) %>s
|
|
496
|
+
</small>
|
|
497
|
+
</td>
|
|
498
|
+
<td>
|
|
499
|
+
<%= link_to "View", error_path(child), class: "btn btn-sm btn-outline-primary" %>
|
|
500
|
+
</td>
|
|
501
|
+
</tr>
|
|
502
|
+
<% end %>
|
|
503
|
+
</tbody>
|
|
504
|
+
</table>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
<% end %>
|
|
508
|
+
|
|
509
|
+
<div class="mt-3">
|
|
510
|
+
<small class="text-muted">
|
|
511
|
+
<i class="bi bi-info-circle"></i>
|
|
512
|
+
Cascade patterns show causal relationships. High probability (≥70%) and frequency (≥3) indicate strong cascades.
|
|
513
|
+
</small>
|
|
129
514
|
</div>
|
|
130
515
|
</div>
|
|
131
516
|
</div>
|
|
517
|
+
<% end %>
|
|
132
518
|
<% end %>
|
|
519
|
+
|
|
520
|
+
<!-- Occurrence Patterns and Bursts -->
|
|
521
|
+
<%= render "pattern_insights" %>
|
|
133
522
|
</div>
|
|
134
523
|
|
|
135
524
|
<!-- Sidebar -->
|
|
@@ -141,16 +530,37 @@
|
|
|
141
530
|
</div>
|
|
142
531
|
<div class="card-body">
|
|
143
532
|
<div class="mb-3">
|
|
144
|
-
<small class="text-muted d-block mb-1">
|
|
145
|
-
<
|
|
146
|
-
|
|
533
|
+
<small class="text-muted d-block mb-1">Occurrence Count</small>
|
|
534
|
+
<span class="badge bg-primary fs-5"><%= @error.occurrence_count %>x</span>
|
|
535
|
+
<% if @error.occurrence_count > 1 %>
|
|
536
|
+
<br><small class="text-muted">This error has occurred <%= @error.occurrence_count %> times</small>
|
|
537
|
+
<% end %>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<div class="mb-3">
|
|
541
|
+
<small class="text-muted d-block mb-1">First Seen</small>
|
|
542
|
+
<strong><%= @error.first_seen_at&.strftime("%B %d, %Y") || 'N/A' %></strong><br>
|
|
543
|
+
<small><%= @error.first_seen_at&.strftime("%I:%M:%S %p %Z") || 'N/A' %></small>
|
|
147
544
|
</div>
|
|
148
545
|
|
|
149
546
|
<div class="mb-3">
|
|
150
|
-
<small class="text-muted d-block mb-1">
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
547
|
+
<small class="text-muted d-block mb-1">Last Seen</small>
|
|
548
|
+
<strong><%= @error.last_seen_at&.strftime("%B %d, %Y") || 'N/A' %></strong><br>
|
|
549
|
+
<small><%= @error.last_seen_at&.strftime("%I:%M:%S %p %Z") || 'N/A' %></small>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
<div class="mb-3">
|
|
553
|
+
<small class="text-muted d-block mb-1">Severity Level</small>
|
|
554
|
+
<% severity = @error.severity %>
|
|
555
|
+
<% if severity == :critical %>
|
|
556
|
+
<span class="badge bg-danger fs-6">CRITICAL</span>
|
|
557
|
+
<% elsif severity == :high %>
|
|
558
|
+
<span class="badge bg-warning text-dark fs-6">HIGH</span>
|
|
559
|
+
<% elsif severity == :medium %>
|
|
560
|
+
<span class="badge bg-info text-dark fs-6">MEDIUM</span>
|
|
561
|
+
<% else %>
|
|
562
|
+
<span class="badge bg-secondary fs-6">LOW</span>
|
|
563
|
+
<% end %>
|
|
154
564
|
</div>
|
|
155
565
|
|
|
156
566
|
<div class="mb-3">
|
|
@@ -166,17 +576,24 @@
|
|
|
166
576
|
|
|
167
577
|
<div class="mb-3">
|
|
168
578
|
<small class="text-muted d-block mb-1">User</small>
|
|
169
|
-
<% if @error.user %>
|
|
579
|
+
<% if @error.respond_to?(:user) && @error.user %>
|
|
170
580
|
<strong><%= @error.user.email %></strong><br>
|
|
171
581
|
<small class="text-muted">ID: <%= @error.user_id %></small>
|
|
582
|
+
<% elsif @error.user_id %>
|
|
583
|
+
<span class="text-muted">User ID: <%= @error.user_id %></span>
|
|
172
584
|
<% else %>
|
|
173
585
|
<span class="text-muted">Guest / Unauthenticated</span>
|
|
174
586
|
<% end %>
|
|
175
587
|
</div>
|
|
176
588
|
|
|
589
|
+
<!-- Phase 3: Workflow Status -->
|
|
177
590
|
<div class="mb-3">
|
|
178
|
-
<small class="text-muted d-block mb-1">Status</small>
|
|
179
|
-
<% if @error.
|
|
591
|
+
<small class="text-muted d-block mb-1">Workflow Status</small>
|
|
592
|
+
<% if @error.respond_to?(:status) %>
|
|
593
|
+
<span class="badge bg-<%= @error.status_badge_color %> fs-6">
|
|
594
|
+
<%= @error.status.titleize %>
|
|
595
|
+
</span>
|
|
596
|
+
<% elsif @error.resolved? %>
|
|
180
597
|
<span class="badge bg-success">
|
|
181
598
|
<i class="bi bi-check-circle"></i> Resolved
|
|
182
599
|
</span>
|
|
@@ -193,6 +610,67 @@
|
|
|
193
610
|
<% end %>
|
|
194
611
|
</div>
|
|
195
612
|
|
|
613
|
+
<!-- Phase 3: Assignment -->
|
|
614
|
+
<% if @error.respond_to?(:assigned_to) %>
|
|
615
|
+
<div class="mb-3">
|
|
616
|
+
<small class="text-muted d-block mb-1">Assigned To</small>
|
|
617
|
+
<% if @error.assigned? %>
|
|
618
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
619
|
+
<div>
|
|
620
|
+
<i class="bi bi-person-fill text-primary"></i>
|
|
621
|
+
<strong><%= @error.assigned_to %></strong>
|
|
622
|
+
<% if @error.assigned_at.present? %>
|
|
623
|
+
<br>
|
|
624
|
+
<small class="text-muted">
|
|
625
|
+
<%= time_ago_in_words(@error.assigned_at) %> ago
|
|
626
|
+
</small>
|
|
627
|
+
<% end %>
|
|
628
|
+
</div>
|
|
629
|
+
<%= button_to unassign_error_path(@error), method: :patch, class: "btn btn-sm btn-outline-secondary",
|
|
630
|
+
data: { turbo_confirm: "Remove assignment?" } do %>
|
|
631
|
+
<i class="bi bi-x"></i>
|
|
632
|
+
<% end %>
|
|
633
|
+
</div>
|
|
634
|
+
<% else %>
|
|
635
|
+
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#assignModal">
|
|
636
|
+
<i class="bi bi-person-plus"></i> Assign
|
|
637
|
+
</button>
|
|
638
|
+
<% end %>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
<!-- Phase 3: Priority -->
|
|
642
|
+
<div class="mb-3">
|
|
643
|
+
<small class="text-muted d-block mb-1">Priority</small>
|
|
644
|
+
<% if @error.respond_to?(:priority_level) %>
|
|
645
|
+
<span class="badge bg-<%= @error.priority_color %> fs-6">
|
|
646
|
+
<%= @error.priority_label %>
|
|
647
|
+
</span>
|
|
648
|
+
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#priorityModal">
|
|
649
|
+
<i class="bi bi-pencil"></i>
|
|
650
|
+
</button>
|
|
651
|
+
<% end %>
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<!-- Phase 3: Snooze -->
|
|
655
|
+
<div class="mb-3">
|
|
656
|
+
<small class="text-muted d-block mb-1">Snooze</small>
|
|
657
|
+
<% if @error.respond_to?(:snoozed?) && @error.snoozed? %>
|
|
658
|
+
<div class="alert alert-warning py-2 mb-2">
|
|
659
|
+
<i class="bi bi-alarm"></i>
|
|
660
|
+
<strong>Snoozed</strong><br>
|
|
661
|
+
<small>Until <%= @error.snoozed_until&.strftime("%b %d at %I:%M %p") %></small>
|
|
662
|
+
</div>
|
|
663
|
+
<%= button_to unsnooze_error_path(@error), method: :patch, class: "btn btn-sm btn-outline-warning" do %>
|
|
664
|
+
<i class="bi bi-alarm-fill"></i> Unsnooze
|
|
665
|
+
<% end %>
|
|
666
|
+
<% else %>
|
|
667
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#snoozeModal">
|
|
668
|
+
<i class="bi bi-alarm"></i> Snooze
|
|
669
|
+
</button>
|
|
670
|
+
<% end %>
|
|
671
|
+
</div>
|
|
672
|
+
<% end %>
|
|
673
|
+
|
|
196
674
|
<% if @error.resolved? && @error.resolved_by_name.present? %>
|
|
197
675
|
<div class="mb-3">
|
|
198
676
|
<small class="text-muted d-block mb-1">Resolved By</small>
|
|
@@ -216,6 +694,44 @@
|
|
|
216
694
|
</div>
|
|
217
695
|
<% end %>
|
|
218
696
|
|
|
697
|
+
<!-- Environment Information -->
|
|
698
|
+
<div class="mb-3">
|
|
699
|
+
<small class="text-muted d-block mb-2">
|
|
700
|
+
<i class="bi bi-gear"></i> Environment
|
|
701
|
+
</small>
|
|
702
|
+
|
|
703
|
+
<% if @error.app_version.present? %>
|
|
704
|
+
<div class="mb-1">
|
|
705
|
+
<small class="text-muted">App Version:</small>
|
|
706
|
+
<code class="ms-1"><%= @error.app_version %></code>
|
|
707
|
+
</div>
|
|
708
|
+
<% end %>
|
|
709
|
+
|
|
710
|
+
<% if @error.git_sha.present? %>
|
|
711
|
+
<div class="mb-1">
|
|
712
|
+
<small class="text-muted">Git SHA:</small>
|
|
713
|
+
<code class="ms-1"><%= @error.git_sha[0..7] %></code>
|
|
714
|
+
</div>
|
|
715
|
+
<% end %>
|
|
716
|
+
|
|
717
|
+
<div class="mb-1">
|
|
718
|
+
<small class="text-muted">Rails:</small>
|
|
719
|
+
<code class="ms-1"><%= Rails.version %></code>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
<div class="mb-1">
|
|
723
|
+
<small class="text-muted">Ruby:</small>
|
|
724
|
+
<code class="ms-1"><%= RUBY_VERSION %></code>
|
|
725
|
+
</div>
|
|
726
|
+
|
|
727
|
+
<div class="mb-1">
|
|
728
|
+
<small class="text-muted">Environment:</small>
|
|
729
|
+
<span class="badge bg-<%= Rails.env.production? ? 'danger' : Rails.env.development? ? 'success' : 'warning' %> ms-1">
|
|
730
|
+
<%= Rails.env.titleize %>
|
|
731
|
+
</span>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
219
735
|
<div>
|
|
220
736
|
<small class="text-muted d-block mb-1">Error ID</small>
|
|
221
737
|
<code><%= @error.id %></code>
|
|
@@ -223,6 +739,103 @@
|
|
|
223
739
|
</div>
|
|
224
740
|
</div>
|
|
225
741
|
|
|
742
|
+
<!-- Baseline Statistics -->
|
|
743
|
+
<% if RailsErrorDashboard.configuration.enable_baseline_alerts %>
|
|
744
|
+
<% baselines = @error.baselines %>
|
|
745
|
+
<% if baselines[:hourly].present? || baselines[:daily].present? || baselines[:weekly].present? %>
|
|
746
|
+
<div class="card mb-4">
|
|
747
|
+
<div class="card-header bg-white">
|
|
748
|
+
<h5 class="mb-0">
|
|
749
|
+
<i class="bi bi-graph-up"></i> Baseline Statistics
|
|
750
|
+
<small class="text-muted ms-2">Historical patterns</small>
|
|
751
|
+
</h5>
|
|
752
|
+
</div>
|
|
753
|
+
<div class="card-body">
|
|
754
|
+
<% anomaly = @error.baseline_anomaly %>
|
|
755
|
+
<% if anomaly[:anomaly] %>
|
|
756
|
+
<div class="alert alert-<%= anomaly[:level] == :critical ? 'danger' : anomaly[:level] == :high ? 'warning' : 'info' %> mb-3">
|
|
757
|
+
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
758
|
+
<strong>Anomaly Detected!</strong><br>
|
|
759
|
+
<small>
|
|
760
|
+
This error is <%= anomaly[:std_devs_above]&.round(1) %>σ above baseline
|
|
761
|
+
(<%= anomaly[:level].to_s.upcase %>)
|
|
762
|
+
</small>
|
|
763
|
+
</div>
|
|
764
|
+
<% end %>
|
|
765
|
+
|
|
766
|
+
<% [:hourly, :daily, :weekly].each do |type| %>
|
|
767
|
+
<% baseline = baselines[type] %>
|
|
768
|
+
<% next unless baseline %>
|
|
769
|
+
|
|
770
|
+
<div class="mb-3">
|
|
771
|
+
<small class="text-muted d-block mb-1 text-capitalize">
|
|
772
|
+
<i class="bi bi-<%= type == :hourly ? 'clock' : type == :daily ? 'calendar-day' : 'calendar-week' %>"></i>
|
|
773
|
+
<%= type.to_s.capitalize %> Baseline
|
|
774
|
+
</small>
|
|
775
|
+
|
|
776
|
+
<div class="row g-2">
|
|
777
|
+
<div class="col-6">
|
|
778
|
+
<div class="text-center p-2 bg-light rounded">
|
|
779
|
+
<small class="text-muted d-block">Mean</small>
|
|
780
|
+
<strong><%= baseline.mean&.round(1) || 'N/A' %></strong>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
<div class="col-6">
|
|
784
|
+
<div class="text-center p-2 bg-light rounded">
|
|
785
|
+
<small class="text-muted d-block">Std Dev</small>
|
|
786
|
+
<strong><%= baseline.std_dev&.round(1) || 'N/A' %></strong>
|
|
787
|
+
</div>
|
|
788
|
+
</div>
|
|
789
|
+
</div>
|
|
790
|
+
|
|
791
|
+
<div class="row g-2 mt-2">
|
|
792
|
+
<div class="col-6">
|
|
793
|
+
<div class="text-center p-2 bg-light rounded">
|
|
794
|
+
<small class="text-muted d-block">95th %</small>
|
|
795
|
+
<strong><%= baseline.percentile_95&.round(1) || 'N/A' %></strong>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
<div class="col-6">
|
|
799
|
+
<div class="text-center p-2 bg-light rounded">
|
|
800
|
+
<small class="text-muted d-block">99th %</small>
|
|
801
|
+
<strong><%= baseline.percentile_99&.round(1) || 'N/A' %></strong>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
|
|
806
|
+
<% if baseline.mean && baseline.std_dev %>
|
|
807
|
+
<div class="mt-2">
|
|
808
|
+
<small class="text-muted">
|
|
809
|
+
Threshold (2σ): <strong><%= baseline.threshold(sensitivity: 2)&.round(1) %></strong>
|
|
810
|
+
</small>
|
|
811
|
+
</div>
|
|
812
|
+
<% end %>
|
|
813
|
+
|
|
814
|
+
<div class="mt-2">
|
|
815
|
+
<small class="text-muted">
|
|
816
|
+
Sample: <%= baseline.sample_size %> periods
|
|
817
|
+
<span class="text-muted">|</span>
|
|
818
|
+
Updated: <%= baseline.updated_at&.strftime("%m/%d %I:%M%p") %>
|
|
819
|
+
</small>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
|
|
823
|
+
<% unless baseline == baselines.values.compact.last %>
|
|
824
|
+
<hr>
|
|
825
|
+
<% end %>
|
|
826
|
+
<% end %>
|
|
827
|
+
|
|
828
|
+
<div class="mt-3">
|
|
829
|
+
<small class="text-muted">
|
|
830
|
+
<i class="bi bi-info-circle"></i>
|
|
831
|
+
Baselines establish "normal" error rates. Anomalies are detected when current rates exceed baseline + 2σ.
|
|
832
|
+
</small>
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
<% end %>
|
|
837
|
+
<% end %>
|
|
838
|
+
|
|
226
839
|
<!-- Quick Actions -->
|
|
227
840
|
<div class="card">
|
|
228
841
|
<div class="card-header bg-white">
|
|
@@ -234,7 +847,7 @@
|
|
|
234
847
|
<i class="bi bi-filter"></i> View Similar Errors
|
|
235
848
|
<% end %>
|
|
236
849
|
|
|
237
|
-
<% if @error.
|
|
850
|
+
<% if @error.user_id %>
|
|
238
851
|
<%= link_to errors_path(user_id: @error.user_id), class: "btn btn-outline-primary btn-sm" do %>
|
|
239
852
|
<i class="bi bi-person"></i> View User's Errors
|
|
240
853
|
<% end %>
|
|
@@ -258,7 +871,7 @@
|
|
|
258
871
|
<div class="modal fade" id="resolveModal" tabindex="-1" aria-labelledby="resolveModalLabel" aria-hidden="true">
|
|
259
872
|
<div class="modal-dialog">
|
|
260
873
|
<div class="modal-content">
|
|
261
|
-
<%= form_with url: resolve_error_path(@error), method: :
|
|
874
|
+
<%= form_with url: resolve_error_path(@error), method: :post, class: "modal-content" do |f| %>
|
|
262
875
|
<div class="modal-header">
|
|
263
876
|
<h5 class="modal-title" id="resolveModalLabel">
|
|
264
877
|
<i class="bi bi-check-circle"></i> Mark Error as Resolved
|
|
@@ -292,3 +905,106 @@
|
|
|
292
905
|
</div>
|
|
293
906
|
</div>
|
|
294
907
|
</div>
|
|
908
|
+
|
|
909
|
+
<!-- Phase 3: Assignment Modal -->
|
|
910
|
+
<div class="modal fade" id="assignModal" tabindex="-1" aria-labelledby="assignModalLabel" aria-hidden="true">
|
|
911
|
+
<div class="modal-dialog">
|
|
912
|
+
<div class="modal-content">
|
|
913
|
+
<%= form_with url: assign_error_path(@error), method: :patch do |f| %>
|
|
914
|
+
<div class="modal-header">
|
|
915
|
+
<h5 class="modal-title" id="assignModalLabel">
|
|
916
|
+
<i class="bi bi-person-plus"></i> Assign Error
|
|
917
|
+
</h5>
|
|
918
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
919
|
+
</div>
|
|
920
|
+
<div class="modal-body">
|
|
921
|
+
<div class="mb-3">
|
|
922
|
+
<label for="assigned_to" class="form-label">Assign To <span class="text-danger">*</span></label>
|
|
923
|
+
<%= text_field_tag :assigned_to, nil, class: "form-control", placeholder: "e.g., John Doe", required: true, autofocus: true %>
|
|
924
|
+
<small class="text-muted">Who should investigate this error?</small>
|
|
925
|
+
</div>
|
|
926
|
+
</div>
|
|
927
|
+
<div class="modal-footer">
|
|
928
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
929
|
+
<%= submit_tag "Assign", class: "btn btn-primary" %>
|
|
930
|
+
</div>
|
|
931
|
+
<% end %>
|
|
932
|
+
</div>
|
|
933
|
+
</div>
|
|
934
|
+
</div>
|
|
935
|
+
|
|
936
|
+
<!-- Phase 3: Priority Modal -->
|
|
937
|
+
<div class="modal fade" id="priorityModal" tabindex="-1" aria-labelledby="priorityModalLabel" aria-hidden="true">
|
|
938
|
+
<div class="modal-dialog">
|
|
939
|
+
<div class="modal-content">
|
|
940
|
+
<%= form_with url: update_priority_error_path(@error), method: :patch do |f| %>
|
|
941
|
+
<div class="modal-header">
|
|
942
|
+
<h5 class="modal-title" id="priorityModalLabel">
|
|
943
|
+
<i class="bi bi-flag"></i> Update Priority
|
|
944
|
+
</h5>
|
|
945
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
946
|
+
</div>
|
|
947
|
+
<div class="modal-body">
|
|
948
|
+
<div class="mb-3">
|
|
949
|
+
<label for="priority_level" class="form-label">Priority Level <span class="text-danger">*</span></label>
|
|
950
|
+
<%= select_tag :priority_level,
|
|
951
|
+
options_for_select([
|
|
952
|
+
['Critical (P3)', 3],
|
|
953
|
+
['High (P2)', 2],
|
|
954
|
+
['Medium (P1)', 1],
|
|
955
|
+
['Low (P0)', 0]
|
|
956
|
+
], @error.priority_level),
|
|
957
|
+
class: "form-select", required: true %>
|
|
958
|
+
<small class="text-muted">Current: <%= @error.priority_label %></small>
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
<div class="modal-footer">
|
|
962
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
963
|
+
<%= submit_tag "Update Priority", class: "btn btn-warning" %>
|
|
964
|
+
</div>
|
|
965
|
+
<% end %>
|
|
966
|
+
</div>
|
|
967
|
+
</div>
|
|
968
|
+
</div>
|
|
969
|
+
|
|
970
|
+
<!-- Phase 3: Snooze Modal -->
|
|
971
|
+
<div class="modal fade" id="snoozeModal" tabindex="-1" aria-labelledby="snoozeModalLabel" aria-hidden="true">
|
|
972
|
+
<div class="modal-dialog">
|
|
973
|
+
<div class="modal-content">
|
|
974
|
+
<%= form_with url: snooze_error_path(@error), method: :patch do |f| %>
|
|
975
|
+
<div class="modal-header">
|
|
976
|
+
<h5 class="modal-title" id="snoozeModalLabel">
|
|
977
|
+
<i class="bi bi-alarm"></i> Snooze Error
|
|
978
|
+
</h5>
|
|
979
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
980
|
+
</div>
|
|
981
|
+
<div class="modal-body">
|
|
982
|
+
<div class="mb-3">
|
|
983
|
+
<label for="hours" class="form-label">Snooze Duration <span class="text-danger">*</span></label>
|
|
984
|
+
<%= select_tag :hours,
|
|
985
|
+
options_for_select([
|
|
986
|
+
['1 hour', 1],
|
|
987
|
+
['4 hours', 4],
|
|
988
|
+
['8 hours', 8],
|
|
989
|
+
['24 hours (1 day)', 24],
|
|
990
|
+
['48 hours (2 days)', 48],
|
|
991
|
+
['168 hours (1 week)', 168]
|
|
992
|
+
], 24),
|
|
993
|
+
class: "form-select", required: true %>
|
|
994
|
+
<small class="text-muted">Hide this error from active views</small>
|
|
995
|
+
</div>
|
|
996
|
+
|
|
997
|
+
<div class="mb-3">
|
|
998
|
+
<label for="reason" class="form-label">Reason (Optional)</label>
|
|
999
|
+
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: "Why are you snoozing this error?" %>
|
|
1000
|
+
<small class="text-muted">Reason will be added as a comment</small>
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
<div class="modal-footer">
|
|
1004
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
1005
|
+
<%= submit_tag "Snooze", class: "btn btn-warning" %>
|
|
1006
|
+
</div>
|
|
1007
|
+
<% end %>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|