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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +257 -700
  3. data/app/controllers/rails_error_dashboard/application_controller.rb +18 -0
  4. data/app/controllers/rails_error_dashboard/errors_controller.rb +47 -4
  5. data/app/helpers/rails_error_dashboard/application_helper.rb +17 -0
  6. data/app/jobs/rails_error_dashboard/application_job.rb +19 -0
  7. data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +48 -0
  8. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +263 -0
  9. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -8
  10. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +2 -1
  11. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +5 -5
  12. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +10 -6
  13. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +5 -6
  14. data/app/mailers/rails_error_dashboard/application_mailer.rb +1 -1
  15. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +1 -1
  16. data/app/models/rails_error_dashboard/cascade_pattern.rb +74 -0
  17. data/app/models/rails_error_dashboard/error_baseline.rb +100 -0
  18. data/app/models/rails_error_dashboard/error_log.rb +326 -3
  19. data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
  20. data/app/views/layouts/rails_error_dashboard.html.erb +150 -9
  21. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +3 -10
  22. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -2
  23. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +76 -0
  24. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +209 -0
  25. data/app/views/rails_error_dashboard/errors/_stats.html.erb +34 -0
  26. data/app/views/rails_error_dashboard/errors/analytics.html.erb +19 -39
  27. data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
  28. data/app/views/rails_error_dashboard/errors/index.html.erb +215 -138
  29. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +388 -0
  30. data/app/views/rails_error_dashboard/errors/show.html.erb +428 -11
  31. data/config/routes.rb +2 -0
  32. data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +66 -0
  33. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +26 -0
  34. data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +12 -0
  35. data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +9 -0
  36. data/db/migrate/20251225100236_create_error_occurrences.rb +31 -0
  37. data/db/migrate/20251225101920_create_cascade_patterns.rb +33 -0
  38. data/db/migrate/20251225102500_create_error_baselines.rb +38 -0
  39. data/lib/generators/rails_error_dashboard/install/install_generator.rb +270 -1
  40. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +251 -37
  41. data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +36 -0
  42. data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +55 -0
  43. data/lib/rails_error_dashboard/commands/log_error.rb +234 -7
  44. data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
  45. data/lib/rails_error_dashboard/configuration.rb +82 -5
  46. data/lib/rails_error_dashboard/error_reporter.rb +15 -7
  47. data/lib/rails_error_dashboard/middleware/error_catcher.rb +17 -10
  48. data/lib/rails_error_dashboard/plugin.rb +6 -3
  49. data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
  50. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -3
  51. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +0 -2
  52. data/lib/rails_error_dashboard/queries/analytics_stats.rb +44 -6
  53. data/lib/rails_error_dashboard/queries/baseline_stats.rb +107 -0
  54. data/lib/rails_error_dashboard/queries/co_occurring_errors.rb +86 -0
  55. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +134 -2
  56. data/lib/rails_error_dashboard/queries/error_cascades.rb +74 -0
  57. data/lib/rails_error_dashboard/queries/error_correlation.rb +375 -0
  58. data/lib/rails_error_dashboard/queries/errors_list.rb +52 -11
  59. data/lib/rails_error_dashboard/queries/filter_options.rb +0 -1
  60. data/lib/rails_error_dashboard/queries/platform_comparison.rb +254 -0
  61. data/lib/rails_error_dashboard/queries/similar_errors.rb +93 -0
  62. data/lib/rails_error_dashboard/services/baseline_alert_throttler.rb +88 -0
  63. data/lib/rails_error_dashboard/services/baseline_calculator.rb +269 -0
  64. data/lib/rails_error_dashboard/services/cascade_detector.rb +95 -0
  65. data/lib/rails_error_dashboard/services/pattern_detector.rb +268 -0
  66. data/lib/rails_error_dashboard/services/similarity_calculator.rb +144 -0
  67. data/lib/rails_error_dashboard/value_objects/error_context.rb +27 -1
  68. data/lib/rails_error_dashboard/version.rb +1 -1
  69. data/lib/rails_error_dashboard.rb +55 -7
  70. metadata +52 -9
  71. data/app/models/rails_error_dashboard/application_record.rb +0 -5
  72. data/lib/rails_error_dashboard/queries/developer_insights.rb +0 -277
  73. data/lib/rails_error_dashboard/queries/errors_list_v2.rb +0 -149
@@ -0,0 +1,388 @@
1
+ <div class="container-fluid py-4">
2
+ <div class="d-flex justify-content-between align-items-center mb-4">
3
+ <h1 class="h3 mb-0">
4
+ <i class="bi bi-phone me-2"></i>
5
+ Platform Health Comparison
6
+ </h1>
7
+
8
+ <div class="btn-group" role="group">
9
+ <%= link_to platform_comparison_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
10
+ 7 Days
11
+ <% end %>
12
+ <%= link_to platform_comparison_errors_path(days: 14), class: "btn btn-sm #{@days == 14 ? 'btn-primary' : 'btn-outline-primary'}" do %>
13
+ 14 Days
14
+ <% end %>
15
+ <%= link_to platform_comparison_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
16
+ 30 Days
17
+ <% end %>
18
+ </div>
19
+ </div>
20
+
21
+ <% if @platform_health.empty? %>
22
+ <div class="alert alert-info">
23
+ <i class="bi bi-info-circle me-2"></i>
24
+ No platform data available for the selected time period.
25
+ </div>
26
+ <% else %>
27
+ <!-- Platform Health Summary Cards -->
28
+ <div class="row mb-4">
29
+ <% @platform_health.each do |platform, health| %>
30
+ <div class="col-md-6 col-lg-3 mb-3">
31
+ <div class="card h-100 <%= health[:health_status] == :healthy ? 'border-success' : health[:health_status] == :warning ? 'border-warning' : 'border-danger' %>">
32
+ <div class="card-body">
33
+ <h5 class="card-title text-capitalize">
34
+ <%= platform || 'Unknown' %>
35
+ <% if health[:health_status] == :healthy %>
36
+ <span class="badge bg-success float-end">Healthy</span>
37
+ <% elsif health[:health_status] == :warning %>
38
+ <span class="badge bg-warning float-end">Warning</span>
39
+ <% else %>
40
+ <span class="badge bg-danger float-end">Critical</span>
41
+ <% end %>
42
+ </h5>
43
+
44
+ <div class="mb-3">
45
+ <div class="d-flex justify-content-between align-items-center mb-1">
46
+ <small class="text-muted">Stability Score</small>
47
+ <strong><%= health[:stability_score] %>/100</strong>
48
+ </div>
49
+ <div class="progress" style="height: 8px;">
50
+ <div class="progress-bar <%= health[:stability_score] >= 80 ? 'bg-success' : health[:stability_score] >= 60 ? 'bg-warning' : 'bg-danger' %>"
51
+ role="progressbar"
52
+ style="width: <%= health[:stability_score] %>%">
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="small">
58
+ <div class="d-flex justify-content-between mb-1">
59
+ <span class="text-muted">Total Errors:</span>
60
+ <strong><%= health[:total_errors] %></strong>
61
+ </div>
62
+ <div class="d-flex justify-content-between mb-1">
63
+ <span class="text-muted">Critical:</span>
64
+ <strong class="text-danger"><%= health[:critical_errors] %></strong>
65
+ </div>
66
+ <div class="d-flex justify-content-between mb-1">
67
+ <span class="text-muted">Unresolved:</span>
68
+ <strong><%= health[:unresolved_errors] %></strong>
69
+ </div>
70
+ <div class="d-flex justify-content-between mb-1">
71
+ <span class="text-muted">Resolution Rate:</span>
72
+ <strong><%= health[:resolution_rate] %>%</strong>
73
+ </div>
74
+ <div class="d-flex justify-content-between">
75
+ <span class="text-muted">Error Velocity:</span>
76
+ <strong class="<%= health[:error_velocity] > 0 ? 'text-danger' : 'text-success' %>">
77
+ <%= health[:error_velocity] > 0 ? '+' : '' %><%= health[:error_velocity] %>%
78
+ </strong>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ <% end %>
85
+ </div>
86
+
87
+ <!-- Error Rate Comparison Chart -->
88
+ <div class="card mb-4">
89
+ <div class="card-header bg-white">
90
+ <h5 class="mb-0">
91
+ <i class="bi bi-bar-chart me-2"></i>
92
+ Error Rate by Platform
93
+ </h5>
94
+ </div>
95
+ <div class="card-body">
96
+ <canvas id="errorRateChart" height="80"></canvas>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Daily Trend Charts -->
101
+ <div class="card mb-4">
102
+ <div class="card-header bg-white">
103
+ <h5 class="mb-0">
104
+ <i class="bi bi-graph-up me-2"></i>
105
+ Daily Error Trends
106
+ </h5>
107
+ </div>
108
+ <div class="card-body">
109
+ <canvas id="dailyTrendChart" height="80"></canvas>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="row mb-4">
114
+ <!-- Severity Distribution -->
115
+ <div class="col-lg-6 mb-4">
116
+ <div class="card h-100">
117
+ <div class="card-header bg-white">
118
+ <h5 class="mb-0">
119
+ <i class="bi bi-exclamation-triangle me-2"></i>
120
+ Severity Distribution
121
+ </h5>
122
+ </div>
123
+ <div class="card-body">
124
+ <% @severity_distribution.each do |platform, severities| %>
125
+ <div class="mb-3">
126
+ <h6 class="text-capitalize"><%= platform || 'Unknown' %></h6>
127
+ <% total = severities.values.sum %>
128
+ <% if total > 0 %>
129
+ <div class="progress" style="height: 25px;">
130
+ <% if severities[:critical].to_i > 0 %>
131
+ <div class="progress-bar bg-danger"
132
+ role="progressbar"
133
+ style="width: <%= (severities[:critical].to_f / total * 100).round(1) %>%"
134
+ title="Critical: <%= severities[:critical] %>">
135
+ <%= severities[:critical] if (severities[:critical].to_f / total) > 0.1 %>
136
+ </div>
137
+ <% end %>
138
+ <% if severities[:high].to_i > 0 %>
139
+ <div class="progress-bar bg-warning"
140
+ role="progressbar"
141
+ style="width: <%= (severities[:high].to_f / total * 100).round(1) %>%"
142
+ title="High: <%= severities[:high] %>">
143
+ <%= severities[:high] if (severities[:high].to_f / total) > 0.1 %>
144
+ </div>
145
+ <% end %>
146
+ <% if severities[:medium].to_i > 0 %>
147
+ <div class="progress-bar bg-info"
148
+ role="progressbar"
149
+ style="width: <%= (severities[:medium].to_f / total * 100).round(1) %>%"
150
+ title="Medium: <%= severities[:medium] %>">
151
+ <%= severities[:medium] if (severities[:medium].to_f / total) > 0.1 %>
152
+ </div>
153
+ <% end %>
154
+ <% if severities[:low].to_i > 0 %>
155
+ <div class="progress-bar bg-secondary"
156
+ role="progressbar"
157
+ style="width: <%= (severities[:low].to_f / total * 100).round(1) %>%"
158
+ title="Low: <%= severities[:low] %>">
159
+ <%= severities[:low] if (severities[:low].to_f / total) > 0.1 %>
160
+ </div>
161
+ <% end %>
162
+ </div>
163
+ <div class="small text-muted mt-1">
164
+ Critical: <%= severities[:critical] || 0 %> |
165
+ High: <%= severities[:high] || 0 %> |
166
+ Medium: <%= severities[:medium] || 0 %> |
167
+ Low: <%= severities[:low] || 0 %>
168
+ </div>
169
+ <% else %>
170
+ <p class="text-muted small mb-0">No errors</p>
171
+ <% end %>
172
+ </div>
173
+ <% end %>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <!-- Resolution Time Comparison -->
179
+ <div class="col-lg-6 mb-4">
180
+ <div class="card h-100">
181
+ <div class="card-header bg-white">
182
+ <h5 class="mb-0">
183
+ <i class="bi bi-clock-history me-2"></i>
184
+ Average Resolution Time
185
+ </h5>
186
+ </div>
187
+ <div class="card-body">
188
+ <canvas id="resolutionTimeChart" height="200"></canvas>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Cross-Platform Errors -->
195
+ <% if @cross_platform_errors.any? %>
196
+ <div class="card mb-4">
197
+ <div class="card-header bg-white">
198
+ <h5 class="mb-0">
199
+ <i class="bi bi-layers me-2"></i>
200
+ Cross-Platform Errors
201
+ <span class="badge bg-secondary ms-2"><%= @cross_platform_errors.count %></span>
202
+ </h5>
203
+ </div>
204
+ <div class="card-body">
205
+ <div class="table-responsive">
206
+ <table class="table table-hover">
207
+ <thead>
208
+ <tr>
209
+ <th>Error Type</th>
210
+ <th>Platforms</th>
211
+ <th>Total Occurrences</th>
212
+ <th>Breakdown</th>
213
+ </tr>
214
+ </thead>
215
+ <tbody>
216
+ <% @cross_platform_errors.first(10).each do |error| %>
217
+ <tr>
218
+ <td>
219
+ <code class="small"><%= error[:error_type] %></code>
220
+ </td>
221
+ <td>
222
+ <% error[:platforms].each do |platform| %>
223
+ <span class="badge bg-secondary text-capitalize me-1"><%= platform %></span>
224
+ <% end %>
225
+ </td>
226
+ <td>
227
+ <strong><%= error[:total_occurrences] %></strong>
228
+ </td>
229
+ <td class="small text-muted">
230
+ <% error[:platform_breakdown].each do |platform, count| %>
231
+ <%= platform %>: <%= count %> &nbsp;
232
+ <% end %>
233
+ </td>
234
+ </tr>
235
+ <% end %>
236
+ </tbody>
237
+ </table>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ <% end %>
242
+
243
+ <!-- Top Errors by Platform -->
244
+ <div class="row">
245
+ <% @top_errors_by_platform.each do |platform, errors| %>
246
+ <div class="col-lg-6 mb-4">
247
+ <div class="card h-100">
248
+ <div class="card-header bg-white">
249
+ <h5 class="mb-0 text-capitalize">
250
+ <i class="bi bi-bug me-2"></i>
251
+ Top Errors - <%= platform || 'Unknown' %>
252
+ </h5>
253
+ </div>
254
+ <div class="card-body">
255
+ <% if errors.any? %>
256
+ <div class="list-group list-group-flush">
257
+ <% errors.first(5).each do |error| %>
258
+ <%= link_to error_path(error[:id]), class: "list-group-item list-group-item-action" do %>
259
+ <div class="d-flex justify-content-between align-items-start">
260
+ <div class="flex-grow-1">
261
+ <code class="small"><%= error[:error_type] %></code>
262
+ <p class="mb-1 small text-muted"><%= error[:message] %></p>
263
+ </div>
264
+ <span class="badge bg-primary rounded-pill ms-2">
265
+ <%= error[:occurrence_count] %>
266
+ </span>
267
+ </div>
268
+ <% end %>
269
+ <% end %>
270
+ </div>
271
+ <% else %>
272
+ <p class="text-muted mb-0">No errors found</p>
273
+ <% end %>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ <% end %>
278
+ </div>
279
+ <% end %>
280
+ </div>
281
+
282
+ <script>
283
+ // Error Rate Chart
284
+ const errorRateCtx = document.getElementById('errorRateChart');
285
+ if (errorRateCtx) {
286
+ new Chart(errorRateCtx, {
287
+ type: 'bar',
288
+ data: {
289
+ labels: <%= raw @error_rate_by_platform.keys.map { |k| k.to_s.capitalize }.to_json %>,
290
+ datasets: [{
291
+ label: 'Total Errors',
292
+ data: <%= raw @error_rate_by_platform.values.to_json %>,
293
+ backgroundColor: 'rgba(54, 162, 235, 0.5)',
294
+ borderColor: 'rgba(54, 162, 235, 1)',
295
+ borderWidth: 1
296
+ }]
297
+ },
298
+ options: {
299
+ responsive: true,
300
+ maintainAspectRatio: true,
301
+ plugins: {
302
+ legend: { display: false }
303
+ },
304
+ scales: {
305
+ y: { beginAtZero: true }
306
+ }
307
+ }
308
+ });
309
+ }
310
+
311
+ // Daily Trend Chart
312
+ const dailyTrendCtx = document.getElementById('dailyTrendChart');
313
+ if (dailyTrendCtx) {
314
+ const colors = {
315
+ ios: 'rgba(255, 99, 132, 1)',
316
+ android: 'rgba(75, 192, 192, 1)',
317
+ api: 'rgba(255, 206, 86, 1)',
318
+ web: 'rgba(153, 102, 255, 1)',
319
+ unknown: 'rgba(201, 203, 207, 1)'
320
+ };
321
+
322
+ const datasets = <%= raw @daily_trends.map { |platform, data|
323
+ {
324
+ label: platform.to_s.capitalize,
325
+ data: data.values,
326
+ borderColor: "colors['#{platform}'] || 'rgba(75, 192, 192, 1)'",
327
+ backgroundColor: "colors['#{platform}'] ? colors['#{platform}'].replace('1)', '0.1)') : 'rgba(75, 192, 192, 0.1)'",
328
+ tension: 0.1
329
+ }
330
+ }.to_json %>;
331
+
332
+ // Replace string placeholders with actual colors
333
+ datasets.forEach(ds => {
334
+ const platform = ds.label.toLowerCase();
335
+ ds.borderColor = colors[platform] || 'rgba(75, 192, 192, 1)';
336
+ ds.backgroundColor = colors[platform] ? colors[platform].replace('1)', '0.1)') : 'rgba(75, 192, 192, 0.1)';
337
+ });
338
+
339
+ new Chart(dailyTrendCtx, {
340
+ type: 'line',
341
+ data: {
342
+ labels: <%= raw @daily_trends.values.first&.keys&.map { |d| d.strftime('%b %d') }&.to_json || [].to_json %>,
343
+ datasets: datasets
344
+ },
345
+ options: {
346
+ responsive: true,
347
+ maintainAspectRatio: true,
348
+ plugins: {
349
+ legend: { position: 'top' }
350
+ },
351
+ scales: {
352
+ y: { beginAtZero: true }
353
+ }
354
+ }
355
+ });
356
+ }
357
+
358
+ // Resolution Time Chart
359
+ const resolutionTimeCtx = document.getElementById('resolutionTimeChart');
360
+ if (resolutionTimeCtx) {
361
+ const platforms = <%= raw @resolution_times.keys.map { |k| k.to_s.capitalize }.to_json %>;
362
+ const times = <%= raw @resolution_times.values.map { |v| v || 0 }.to_json %>;
363
+
364
+ new Chart(resolutionTimeCtx, {
365
+ type: 'horizontalBar',
366
+ data: {
367
+ labels: platforms,
368
+ datasets: [{
369
+ label: 'Hours to Resolve',
370
+ data: times,
371
+ backgroundColor: 'rgba(255, 159, 64, 0.5)',
372
+ borderColor: 'rgba(255, 159, 64, 1)',
373
+ borderWidth: 1
374
+ }]
375
+ },
376
+ options: {
377
+ responsive: true,
378
+ maintainAspectRatio: false,
379
+ plugins: {
380
+ legend: { display: false }
381
+ },
382
+ scales: {
383
+ x: { beginAtZero: true, title: { display: true, text: 'Hours' } }
384
+ }
385
+ }
386
+ });
387
+ }
388
+ </script>