rails_error_dashboard 0.1.28 → 0.1.30

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -6
  3. data/app/controllers/rails_error_dashboard/errors_controller.rb +22 -0
  4. data/app/helpers/rails_error_dashboard/application_helper.rb +79 -7
  5. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +149 -0
  6. data/app/models/rails_error_dashboard/application.rb +1 -1
  7. data/app/models/rails_error_dashboard/error_log.rb +44 -16
  8. data/app/views/layouts/rails_error_dashboard.html.erb +71 -1237
  9. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +10 -2
  10. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +76 -0
  11. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +18 -82
  12. data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +70 -0
  13. data/app/views/rails_error_dashboard/errors/analytics.html.erb +9 -37
  14. data/app/views/rails_error_dashboard/errors/correlation.html.erb +11 -37
  15. data/app/views/rails_error_dashboard/errors/index.html.erb +64 -31
  16. data/app/views/rails_error_dashboard/errors/overview.html.erb +181 -3
  17. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +2 -1
  18. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +286 -0
  19. data/app/views/rails_error_dashboard/errors/settings.html.erb +146 -480
  20. data/app/views/rails_error_dashboard/errors/show.html.erb +102 -76
  21. data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +188 -0
  22. data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +5 -0
  23. data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +3 -0
  24. data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +3 -0
  25. data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +4 -0
  26. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +3 -0
  27. data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +3 -0
  28. data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +3 -0
  29. data/db/migrate/20251225100236_create_error_occurrences.rb +3 -0
  30. data/db/migrate/20251225101920_create_cascade_patterns.rb +3 -0
  31. data/db/migrate/20251225102500_create_error_baselines.rb +3 -0
  32. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +3 -0
  33. data/db/migrate/20251226020100_create_error_comments.rb +3 -0
  34. data/db/migrate/20251229111223_add_additional_performance_indexes.rb +4 -0
  35. data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +3 -0
  36. data/db/migrate/20260106094233_add_application_to_error_logs.rb +3 -0
  37. data/db/migrate/20260106094318_finalize_application_foreign_key.rb +5 -0
  38. data/lib/generators/rails_error_dashboard/install/install_generator.rb +32 -0
  39. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +37 -4
  40. data/lib/rails_error_dashboard/configuration.rb +160 -3
  41. data/lib/rails_error_dashboard/configuration_error.rb +24 -0
  42. data/lib/rails_error_dashboard/engine.rb +17 -0
  43. data/lib/rails_error_dashboard/helpers/user_model_detector.rb +138 -0
  44. data/lib/rails_error_dashboard/queries/analytics_stats.rb +1 -2
  45. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +19 -4
  46. data/lib/rails_error_dashboard/queries/errors_list.rb +27 -8
  47. data/lib/rails_error_dashboard/services/error_normalizer.rb +143 -0
  48. data/lib/rails_error_dashboard/services/git_blame_reader.rb +195 -0
  49. data/lib/rails_error_dashboard/services/github_link_generator.rb +159 -0
  50. data/lib/rails_error_dashboard/services/source_code_reader.rb +214 -0
  51. data/lib/rails_error_dashboard/version.rb +1 -1
  52. data/lib/rails_error_dashboard.rb +6 -0
  53. metadata +14 -10
  54. data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +0 -107
  55. data/app/assets/stylesheets/rails_error_dashboard/_components.scss +0 -625
  56. data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +0 -257
  57. data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +0 -203
  58. data/app/assets/stylesheets/rails_error_dashboard/application.css +0 -15
  59. data/app/assets/stylesheets/rails_error_dashboard/application.css.map +0 -7
  60. data/app/assets/stylesheets/rails_error_dashboard/application.scss +0 -61
  61. data/app/views/layouts/rails_error_dashboard/application.html.erb +0 -55
@@ -45,8 +45,23 @@
45
45
  </div>
46
46
  </div>
47
47
 
48
+ <!-- Unresolved Errors Card -->
49
+ <div class="col-12 col-md-6 col-lg-4">
50
+ <div class="card stat-card h-100">
51
+ <div class="card-body text-center">
52
+ <div class="stat-label mb-2">UNRESOLVED ERRORS</div>
53
+ <div class="stat-value text-danger">
54
+ <%= @stats[:unresolved] %>
55
+ </div>
56
+ <small class="text-muted">
57
+ Pending resolution
58
+ </small>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
48
63
  <!-- Trend Card -->
49
- <div class="col-12 col-lg-4">
64
+ <div class="col-12 col-md-6 col-lg-4">
50
65
  <div class="card stat-card h-100">
51
66
  <div class="card-body text-center">
52
67
  <div class="stat-label mb-2">ERROR TREND</div>
@@ -60,15 +75,58 @@
60
75
  </div>
61
76
  </div>
62
77
  </div>
78
+
79
+ <!-- Resolution Rate Card -->
80
+ <div class="col-12 col-md-6 col-lg-4">
81
+ <div class="card stat-card h-100">
82
+ <div class="card-body text-center">
83
+ <div class="stat-label mb-2">RESOLUTION RATE</div>
84
+ <%
85
+ total_errors = @stats[:resolved] + @stats[:unresolved]
86
+ resolution_rate = total_errors > 0 ? ((@stats[:resolved].to_f / total_errors) * 100).round(1) : 0.0
87
+ %>
88
+ <div class="stat-value <%= resolution_rate >= 80 ? 'text-success' : resolution_rate >= 50 ? 'text-warning' : 'text-danger' %>">
89
+ <%= resolution_rate %>%
90
+ </div>
91
+ <small class="text-muted">
92
+ <%= @stats[:resolved] %> of <%= total_errors %> resolved
93
+ </small>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Average Resolution Time Card -->
99
+ <div class="col-12 col-md-6 col-lg-4">
100
+ <div class="card stat-card h-100">
101
+ <div class="card-body text-center">
102
+ <div class="stat-label mb-2">AVG RESOLUTION TIME</div>
103
+ <% if @stats[:average_resolution_time].present? %>
104
+ <div class="stat-value text-warning">
105
+ <%= @stats[:average_resolution_time] %>h
106
+ </div>
107
+ <small class="text-muted">
108
+ Mean time to resolution
109
+ </small>
110
+ <% else %>
111
+ <div class="stat-value text-muted">
112
+ --
113
+ </div>
114
+ <small class="text-muted">
115
+ No resolved errors yet
116
+ </small>
117
+ <% end %>
118
+ </div>
119
+ </div>
120
+ </div>
63
121
  </div>
64
122
 
65
- <!-- Top 5 Errors by Impact (Mobile: stack, Desktop: 2 columns) -->
123
+ <!-- Top 6 Errors by Impact (Mobile: stack, Desktop: 2 columns) -->
66
124
  <% if @stats[:top_errors_by_impact].any? %>
67
125
  <div class="card mb-4">
68
126
  <div class="card-header bg-white d-flex justify-content-between align-items-center">
69
127
  <h5 class="mb-0">
70
128
  <i class="bi bi-exclamation-triangle me-2"></i>
71
- Top 5 Errors by Impact
129
+ Top 6 Errors by Impact
72
130
  </h5>
73
131
  <%= link_to "View All Errors →", errors_path, class: "btn btn-sm btn-outline-primary" %>
74
132
  </div>
@@ -174,6 +232,126 @@
174
232
  </div>
175
233
  <% end %>
176
234
 
235
+ <!-- Correlation Summary (Last 7 days) -->
236
+ <% if RailsErrorDashboard.configuration.enable_error_correlation && (@problematic_releases.any? || @time_correlated_errors.any? || @multi_error_users.any?) %>
237
+ <%
238
+ # Calculate column width based on number of cards present
239
+ cards_present = [@problematic_releases.any?, @time_correlated_errors.any?, @multi_error_users.any?].count(true)
240
+ col_class = case cards_present
241
+ when 1 then "col-12"
242
+ when 2 then "col-12 col-lg-6"
243
+ else "col-12 col-lg-4"
244
+ end
245
+ %>
246
+ <div class="card mb-4">
247
+ <div class="card-header bg-white d-flex justify-content-between align-items-center">
248
+ <h5 class="mb-0">
249
+ <i class="bi bi-diagram-3 me-2"></i>
250
+ Error Correlation Insights
251
+ </h5>
252
+ <%= link_to "Full Analysis →", correlation_errors_path, class: "btn btn-sm btn-outline-primary" %>
253
+ </div>
254
+ <div class="card-body">
255
+ <div class="row g-3">
256
+ <!-- Problematic Releases -->
257
+ <% if @problematic_releases.any? %>
258
+ <div class="<%= col_class %>">
259
+ <div class="card h-100 border-warning">
260
+ <div class="card-body">
261
+ <h6 class="mb-3">
262
+ <i class="bi bi-tag me-2"></i>
263
+ Problematic Releases
264
+ </h6>
265
+ <div class="list-group list-group-flush">
266
+ <% @problematic_releases.each do |release| %>
267
+ <div class="list-group-item px-0 py-2">
268
+ <div class="d-flex justify-content-between align-items-start">
269
+ <div class="flex-grow-1">
270
+ <code class="small"><%= release[:version] || release[:git_sha]&.truncate(8, omission: '') %></code>
271
+ <div class="small text-muted">
272
+ <%= release[:error_count] %> error<%= release[:error_count] != 1 ? 's' : '' %> •
273
+ <%= release[:unique_types] %> type<%= release[:unique_types] != 1 ? 's' : '' %>
274
+ </div>
275
+ </div>
276
+ <span class="badge bg-danger"><%= release[:severity_score] %></span>
277
+ </div>
278
+ </div>
279
+ <% end %>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ <% end %>
285
+
286
+ <!-- Time-Correlated Errors -->
287
+ <% if @time_correlated_errors.any? %>
288
+ <div class="<%= col_class %>">
289
+ <div class="card h-100 border-info">
290
+ <div class="card-body">
291
+ <h6 class="mb-3">
292
+ <i class="bi bi-clock-history me-2"></i>
293
+ Time-Correlated Errors
294
+ </h6>
295
+ <div class="list-group list-group-flush">
296
+ <% @time_correlated_errors.each do |group| %>
297
+ <div class="list-group-item px-0 py-2">
298
+ <div class="small">
299
+ <strong><%= group[:error_types].size %> error types</strong>
300
+ <div class="text-muted">
301
+ occurred together <%= group[:occurrences] %> time<%= group[:occurrences] != 1 ? 's' : '' %>
302
+ </div>
303
+ <div class="mt-1">
304
+ <% group[:error_types].first(2).each do |type| %>
305
+ <code class="small d-block text-truncate"><%= type %></code>
306
+ <% end %>
307
+ <% if group[:error_types].size > 2 %>
308
+ <small class="text-muted">+<%= group[:error_types].size - 2 %> more</small>
309
+ <% end %>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ <% end %>
314
+ </div>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ <% end %>
319
+
320
+ <!-- Multi-Error Users -->
321
+ <% if @multi_error_users.any? %>
322
+ <div class="<%= col_class %>">
323
+ <div class="card h-100 border-danger">
324
+ <div class="card-body">
325
+ <h6 class="mb-3">
326
+ <i class="bi bi-people-fill me-2"></i>
327
+ Users with Multiple Errors
328
+ </h6>
329
+ <div class="list-group list-group-flush">
330
+ <% @multi_error_users.each do |user| %>
331
+ <div class="list-group-item px-0 py-2">
332
+ <div class="d-flex justify-content-between align-items-start">
333
+ <div class="flex-grow-1">
334
+ <div class="small">
335
+ User ID: <strong><%= user[:user_id] %></strong>
336
+ </div>
337
+ <div class="small text-muted">
338
+ <%= user[:error_types].size %> different error type<%= user[:error_types].size != 1 ? 's' : '' %>
339
+ </div>
340
+ </div>
341
+ <span class="badge bg-danger"><%= user[:total_errors] %></span>
342
+ </div>
343
+ </div>
344
+ <% end %>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ <% end %>
350
+ </div>
351
+ </div>
352
+ </div>
353
+ <% end %>
354
+
177
355
  <!-- Critical Alerts (Last hour) -->
178
356
  <% if @critical_alerts.any? %>
179
357
  <div class="alert alert-danger border-danger mb-4" role="alert">
@@ -378,7 +378,7 @@
378
378
  const times = <%= raw @resolution_times.values.map { |v| v || 0 }.to_json %>;
379
379
 
380
380
  new Chart(resolutionTimeCtx, {
381
- type: 'horizontalBar',
381
+ type: 'bar',
382
382
  data: {
383
383
  labels: platforms,
384
384
  datasets: [{
@@ -390,6 +390,7 @@
390
390
  }]
391
391
  },
392
392
  options: {
393
+ indexAxis: 'y', // Horizontal bar chart
393
394
  responsive: true,
394
395
  maintainAspectRatio: false,
395
396
  plugins: {
@@ -0,0 +1,286 @@
1
+ <%
2
+ # Render value badge based on type
3
+ case type
4
+ when :boolean
5
+ if value
6
+ %>
7
+ <span class="badge bg-success fs-6">
8
+ <i class="bi bi-check-circle"></i> Enabled
9
+ </span>
10
+ <%
11
+ else
12
+ %>
13
+ <span class="badge bg-secondary fs-6">
14
+ <i class="bi bi-x-circle"></i> Disabled
15
+ </span>
16
+ <%
17
+ end
18
+
19
+ when :integer
20
+ if value.present?
21
+ %>
22
+ <span class="badge bg-info fs-6"><%= value %><%= " #{unit}" if unit %></span>
23
+ <%
24
+ else
25
+ %>
26
+ <span class="badge bg-secondary fs-6">Not set</span>
27
+ <%
28
+ end
29
+
30
+ when :float
31
+ if value.present?
32
+ %>
33
+ <span class="badge bg-info fs-6"><%= value %><%= unit %></span>
34
+ <%
35
+ else
36
+ %>
37
+ <span class="badge bg-secondary fs-6">Not set</span>
38
+ <%
39
+ end
40
+
41
+ when :percentage
42
+ if value.present?
43
+ %>
44
+ <span class="badge bg-info fs-6"><%= (value * 100).to_i %>%</span>
45
+ <%
46
+ else
47
+ %>
48
+ <span class="badge bg-secondary fs-6">Not set</span>
49
+ <%
50
+ end
51
+
52
+ when :string
53
+ if value.present?
54
+ %>
55
+ <code class="fs-6"><%= value %></code>
56
+ <%
57
+ else
58
+ %>
59
+ <span class="badge bg-secondary fs-6">Not set</span>
60
+ <%
61
+ end
62
+
63
+ when :symbol
64
+ if value.present?
65
+ %>
66
+ <code class="fs-6">:<%= value %></code>
67
+ <%
68
+ else
69
+ %>
70
+ <span class="badge bg-secondary fs-6">Not set</span>
71
+ <%
72
+ end
73
+
74
+ when :url
75
+ if value.present?
76
+ %>
77
+ <code class="fs-6 text-truncate" style="max-width: 200px; display: inline-block;"><%= value %></code>
78
+ <%
79
+ else
80
+ %>
81
+ <span class="badge bg-secondary fs-6">Not set</span>
82
+ <%
83
+ end
84
+
85
+ when :array
86
+ if value.present? && value.any?
87
+ %>
88
+ <span class="badge bg-info fs-6"><%= value.count %> item<%= value.count != 1 ? 's' : '' %></span>
89
+ <%
90
+ else
91
+ %>
92
+ <span class="badge bg-secondary fs-6">Empty</span>
93
+ <%
94
+ end
95
+
96
+ when :hash
97
+ if value.present? && value.any?
98
+ %>
99
+ <span class="badge bg-info fs-6"><%= value.count %> rule<%= value.count != 1 ? 's' : '' %></span>
100
+ <%
101
+ else
102
+ %>
103
+ <span class="badge bg-secondary fs-6">Empty</span>
104
+ <%
105
+ end
106
+
107
+ when :git_sha
108
+ if value.present?
109
+ %>
110
+ <span class="fs-6"><%= git_commit_link(value) %></span>
111
+ <%
112
+ else
113
+ %>
114
+ <span class="badge bg-secondary fs-6">Not set</span>
115
+ <%
116
+ end
117
+
118
+ when :application_name
119
+ # Show both configured and auto-detected application name
120
+ configured_value = value
121
+ auto_detected = defined?(Rails) ? Rails.application.class.module_parent_name : nil
122
+ effective_value = configured_value.presence || auto_detected
123
+ is_auto_detected = configured_value.blank? && auto_detected.present?
124
+
125
+ if effective_value.present?
126
+ %>
127
+ <div>
128
+ <code class="fs-6"><%= effective_value %></code>
129
+ <% if is_auto_detected %>
130
+ <small class="text-success d-block">
131
+ <i class="bi bi-magic"></i> Auto-detected
132
+ </small>
133
+ <% else %>
134
+ <small class="text-muted d-block">(Configured)</small>
135
+ <% end %>
136
+ </div>
137
+ <%
138
+ else
139
+ %>
140
+ <div>
141
+ <span class="badge bg-secondary fs-6">Not set</span>
142
+ <small class="text-muted d-block">Rails app name unavailable</small>
143
+ </div>
144
+ <%
145
+ end
146
+
147
+ when :database_name
148
+ # Show the active database connection name
149
+ begin
150
+ if RailsErrorDashboard.configuration.use_separate_database
151
+ # Using separate database
152
+ db_config_name = RailsErrorDashboard.configuration.database || 'error_dashboard'
153
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: db_config_name.to_s)
154
+ if db_config
155
+ db_name = db_config.database
156
+ status_badge = 'bg-success'
157
+ status_text = "Separate DB: #{db_config_name}"
158
+ else
159
+ db_name = db_config_name
160
+ status_badge = 'bg-warning'
161
+ status_text = "Configured but not found"
162
+ end
163
+ else
164
+ # Using primary database
165
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'primary')
166
+ if db_config
167
+ db_name = db_config.database
168
+ status_badge = 'bg-info'
169
+ status_text = "Shared DB (primary)"
170
+ else
171
+ # Fallback to current connection
172
+ db_name = RailsErrorDashboard::ErrorLog.connection.current_database rescue 'Unknown'
173
+ status_badge = 'bg-info'
174
+ status_text = "Shared DB"
175
+ end
176
+ end
177
+ %>
178
+ <div>
179
+ <span class="badge <%= status_badge %> fs-6"><%= File.basename(db_name.to_s) %></span>
180
+ <small class="text-muted d-block"><%= status_text %></small>
181
+ </div>
182
+ <%
183
+ rescue => e
184
+ %>
185
+ <div>
186
+ <span class="badge bg-secondary fs-6">Unknown</span>
187
+ <small class="text-muted d-block">Unable to detect</small>
188
+ </div>
189
+ <%
190
+ end
191
+
192
+ when :user_model
193
+ # Show both configured and auto-detected values
194
+ configured_value = value
195
+ effective_value = RailsErrorDashboard.configuration.effective_user_model
196
+ is_auto_detected = configured_value.blank? && effective_value.present?
197
+
198
+ if effective_value.present?
199
+ %>
200
+ <div>
201
+ <code class="fs-6"><%= effective_value %></code>
202
+ <% if is_auto_detected %>
203
+ <small class="text-success d-block">
204
+ <i class="bi bi-magic"></i> Auto-detected
205
+ </small>
206
+ <% else %>
207
+ <small class="text-muted d-block">(Configured)</small>
208
+ <% end %>
209
+ </div>
210
+ <%
211
+ else
212
+ %>
213
+ <div>
214
+ <span class="badge bg-secondary fs-6">Not found</span>
215
+ <small class="text-muted d-block">No User model detected</small>
216
+ </div>
217
+ <%
218
+ end
219
+
220
+ when :total_users
221
+ # Show both configured and auto-detected values
222
+ configured_value = value
223
+ effective_value = RailsErrorDashboard.configuration.effective_total_users
224
+ is_auto_detected = configured_value.blank? && effective_value.present?
225
+
226
+ if effective_value.present?
227
+ %>
228
+ <div>
229
+ <span class="badge <%= is_auto_detected ? 'bg-success' : 'bg-info' %> fs-6"><%= number_with_delimiter(effective_value) %> users</span>
230
+ <% if is_auto_detected %>
231
+ <small class="text-success d-block">
232
+ <i class="bi bi-magic"></i> Auto-detected
233
+ </small>
234
+ <% else %>
235
+ <small class="text-muted d-block">(Configured)</small>
236
+ <% end %>
237
+ </div>
238
+ <%
239
+ else
240
+ %>
241
+ <div>
242
+ <span class="badge bg-secondary fs-6">Not available</span>
243
+ <small class="text-muted d-block">Unable to query count</small>
244
+ </div>
245
+ <%
246
+ end
247
+
248
+ when :retention_days
249
+ # Show retention configuration with manual cleanup instructions
250
+ if value.present?
251
+ %>
252
+ <div>
253
+ <span class="badge bg-warning text-dark fs-6"><%= value %> days</span>
254
+ <small class="text-danger d-block">
255
+ <i class="bi bi-exclamation-triangle"></i> Manual cleanup required
256
+ </small>
257
+ <small class="text-muted d-block mt-1">
258
+ Run: <code class="small">rails error_dashboard:cleanup_resolved DAYS=<%= value %></code>
259
+ </small>
260
+ </div>
261
+ <%
262
+ else
263
+ %>
264
+ <div>
265
+ <span class="badge bg-success fs-6"><i class="bi bi-infinity"></i> Keep Forever</span>
266
+ <small class="text-muted d-block">No automatic deletion</small>
267
+ <small class="text-muted d-block mt-1">
268
+ To cleanup: <code class="small">rails error_dashboard:cleanup_resolved DAYS=90</code>
269
+ </small>
270
+ </div>
271
+ <%
272
+ end
273
+
274
+ else
275
+ # Default rendering
276
+ if value.present?
277
+ %>
278
+ <code class="fs-6"><%= value %></code>
279
+ <%
280
+ else
281
+ %>
282
+ <span class="badge bg-secondary fs-6">Not set</span>
283
+ <%
284
+ end
285
+ end
286
+ %>