rails_error_dashboard 0.5.9 → 0.5.11

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -7
  3. data/app/controllers/rails_error_dashboard/errors_controller.rb +41 -0
  4. data/app/controllers/rails_error_dashboard/webhooks_controller.rb +2 -1
  5. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +12 -0
  6. data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +40 -0
  7. data/app/mailers/rails_error_dashboard/digest_mailer.rb +23 -0
  8. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +2 -1
  9. data/app/views/layouts/rails_error_dashboard.html.erb +10 -0
  10. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +172 -0
  11. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +49 -0
  12. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +14 -1
  13. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -7
  14. data/app/views/rails_error_dashboard/errors/releases.html.erb +284 -0
  15. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +15 -0
  16. data/app/views/rails_error_dashboard/errors/settings.html.erb +37 -1
  17. data/app/views/rails_error_dashboard/errors/show.html.erb +21 -0
  18. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +172 -0
  19. data/config/routes.rb +4 -0
  20. data/lib/generators/rails_error_dashboard/install/install_generator.rb +6 -0
  21. data/lib/generators/rails_error_dashboard/install/templates/README +9 -18
  22. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +32 -2
  23. data/lib/rails_error_dashboard/configuration.rb +55 -13
  24. data/lib/rails_error_dashboard/middleware/rate_limiter.rb +16 -12
  25. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -1
  26. data/lib/rails_error_dashboard/queries/release_timeline.rb +181 -0
  27. data/lib/rails_error_dashboard/queries/user_impact_summary.rb +93 -0
  28. data/lib/rails_error_dashboard/services/coverage_tracker.rb +139 -0
  29. data/lib/rails_error_dashboard/services/digest_builder.rb +158 -0
  30. data/lib/rails_error_dashboard/services/notification_helpers.rb +2 -1
  31. data/lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb +6 -7
  32. data/lib/rails_error_dashboard/version.rb +1 -1
  33. data/lib/rails_error_dashboard.rb +4 -0
  34. data/lib/tasks/error_dashboard.rake +23 -0
  35. metadata +35 -9
@@ -0,0 +1,284 @@
1
+ <% content_for :page_title, "Releases" %>
2
+
3
+ <div class="container-fluid py-4">
4
+ <div class="d-flex justify-content-between align-items-center mb-4">
5
+ <h1 class="h3 mb-0">
6
+ <i class="bi bi-rocket-takeoff me-2"></i>
7
+ Releases
8
+ </h1>
9
+
10
+ <div class="btn-group" role="group">
11
+ <%= link_to releases_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
+ 7 Days
13
+ <% end %>
14
+ <%= link_to releases_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
+ 30 Days
16
+ <% end %>
17
+ <%= link_to releases_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
+ 90 Days
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+
23
+ <% if @releases.empty? %>
24
+ <div class="text-center py-5">
25
+ <i class="bi bi-rocket-takeoff display-1 text-muted mb-3"></i>
26
+ <h4 class="text-muted">No Release Data Found</h4>
27
+ <p class="text-muted">
28
+ No version data was detected in errors over the last <%= @days %> days.
29
+ </p>
30
+ <div class="card mx-auto" style="max-width: 500px;">
31
+ <div class="card-body text-start">
32
+ <h6>How to enable release tracking:</h6>
33
+ <ul class="mb-0">
34
+ <li>Set <code>config.app_version</code> in your initializer</li>
35
+ <li>Or set the <code>APP_VERSION</code> environment variable</li>
36
+ <li>Git SHA is auto-detected from <code>GIT_SHA</code>, <code>HEROKU_SLUG_COMMIT</code>, or <code>RENDER_GIT_COMMIT</code></li>
37
+ <li>Version and SHA are captured automatically when errors occur</li>
38
+ </ul>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <% else %>
43
+ <%
44
+ current_release = @releases.find { |r| r[:current] }
45
+ %>
46
+
47
+ <!-- Summary Cards -->
48
+ <div class="row mb-4">
49
+ <div class="col-md-3">
50
+ <div class="card text-center">
51
+ <div class="card-body">
52
+ <div class="display-6 text-primary"><%= @summary[:total_releases] %></div>
53
+ <small class="text-muted">Releases</small>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="col-md-3">
58
+ <div class="card text-center">
59
+ <div class="card-body">
60
+ <div class="display-6"><code><%= @summary[:current_version] || "N/A" %></code></div>
61
+ <small class="text-muted">Current Release</small>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <div class="col-md-3">
66
+ <div class="card text-center">
67
+ <div class="card-body">
68
+ <div class="display-6 text-warning"><%= @summary[:avg_errors_per_release] %></div>
69
+ <small class="text-muted">Avg Errors / Release</small>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <div class="col-md-3">
74
+ <div class="card text-center">
75
+ <div class="card-body">
76
+ <%
77
+ problematic_count = @releases.count { |r| r[:problematic] }
78
+ %>
79
+ <div class="display-6 <%= problematic_count > 0 ? 'text-danger' : 'text-success' %>"><%= problematic_count %></div>
80
+ <small class="text-muted">Problematic Releases</small>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <% if current_release %>
87
+ <!-- Current Release Card -->
88
+ <div class="card mb-4 border-primary">
89
+ <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
90
+ <h5 class="mb-0">
91
+ <i class="bi bi-broadcast"></i> Current Release: <strong><%= current_release[:version] %></strong>
92
+ </h5>
93
+ <span class="badge bg-light text-primary">Live</span>
94
+ </div>
95
+ <div class="card-body">
96
+ <div class="row text-center">
97
+ <div class="col-md-2">
98
+ <div class="h4 mb-0"><%= current_release[:total_errors] %></div>
99
+ <small class="text-muted">Errors</small>
100
+ </div>
101
+ <div class="col-md-2">
102
+ <div class="h4 mb-0"><%= current_release[:unique_error_types] %></div>
103
+ <small class="text-muted">Error Types</small>
104
+ </div>
105
+ <div class="col-md-2">
106
+ <div class="h4 mb-0">
107
+ <% if current_release[:new_error_count] > 0 %>
108
+ <span class="text-danger"><%= current_release[:new_error_count] %></span>
109
+ <% else %>
110
+ <span class="text-success">0</span>
111
+ <% end %>
112
+ </div>
113
+ <small class="text-muted">New Errors</small>
114
+ </div>
115
+ <div class="col-md-2">
116
+ <%
117
+ stability_color = case current_release[:stability]
118
+ when :green then "success"
119
+ when :yellow then "warning"
120
+ when :red then "danger"
121
+ else "secondary"
122
+ end
123
+ %>
124
+ <div class="h4 mb-0">
125
+ <span class="badge bg-<%= stability_color %>">
126
+ <%= current_release[:stability].to_s.capitalize %>
127
+ </span>
128
+ </div>
129
+ <small class="text-muted">Stability</small>
130
+ </div>
131
+ <div class="col-md-2">
132
+ <% if current_release[:git_shas].any? %>
133
+ <div class="h4 mb-0"><%= git_commit_link(current_release[:git_shas].first) %></div>
134
+ <% else %>
135
+ <div class="h4 mb-0 text-muted">N/A</div>
136
+ <% end %>
137
+ <small class="text-muted">Git SHA</small>
138
+ </div>
139
+ <div class="col-md-2">
140
+ <% if current_release[:delta_from_previous] %>
141
+ <%
142
+ delta = current_release[:delta_from_previous]
143
+ delta_class = delta > 0 ? "text-danger" : (delta < 0 ? "text-success" : "text-muted")
144
+ delta_icon = delta > 0 ? "bi-arrow-up" : (delta < 0 ? "bi-arrow-down" : "bi-dash")
145
+ %>
146
+ <div class="h4 mb-0 <%= delta_class %>">
147
+ <i class="bi <%= delta_icon %>"></i> <%= delta.abs %>
148
+ </div>
149
+ <% else %>
150
+ <div class="h4 mb-0 text-muted">&mdash;</div>
151
+ <% end %>
152
+ <small class="text-muted">vs Previous</small>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ <% end %>
158
+
159
+ <!-- Release Timeline Table -->
160
+ <div class="card mb-4">
161
+ <div class="card-header bg-white d-flex justify-content-between align-items-center">
162
+ <h5 class="mb-0">
163
+ <i class="bi bi-clock-history text-primary me-2"></i>
164
+ Release Timeline
165
+ <span class="badge bg-primary"><%= @summary[:total_releases] %></span>
166
+ </h5>
167
+ <small class="text-muted"><%== @pagy.info_tag %></small>
168
+ </div>
169
+ <div class="card-body p-0">
170
+ <div class="table-responsive">
171
+ <table class="table table-hover mb-0">
172
+ <thead class="table-light">
173
+ <tr>
174
+ <th>Version</th>
175
+ <th>Git SHA</th>
176
+ <th>Time Range</th>
177
+ <th width="100">Errors</th>
178
+ <th width="100">Types</th>
179
+ <th width="120">New Errors</th>
180
+ <th width="100">Stability</th>
181
+ <th width="120">vs Previous</th>
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ <% @releases.each do |release| %>
186
+ <tr class="<%= 'table-active' if release[:current] %>">
187
+ <td>
188
+ <strong><%= release[:version] %></strong>
189
+ <% if release[:current] %>
190
+ <span class="badge bg-primary ms-1">Current</span>
191
+ <% end %>
192
+ <% if release[:problematic] %>
193
+ <span class="badge bg-danger ms-1">Problematic</span>
194
+ <% end %>
195
+ </td>
196
+ <td>
197
+ <% if release[:git_shas].any? %>
198
+ <%= git_commit_link(release[:git_shas].first) %>
199
+ <% if release[:git_shas].size > 1 %>
200
+ <small class="text-muted">+<%= release[:git_shas].size - 1 %></small>
201
+ <% end %>
202
+ <% else %>
203
+ <span class="text-muted">N/A</span>
204
+ <% end %>
205
+ </td>
206
+ <td>
207
+ <small>
208
+ <%= local_time_ago(release[:first_seen]) %>
209
+ <% unless release[:first_seen] == release[:last_seen] %>
210
+ &rarr; <%= local_time_ago(release[:last_seen]) %>
211
+ <% end %>
212
+ </small>
213
+ </td>
214
+ <td>
215
+ <span class="badge bg-secondary"><%= release[:total_errors] %></span>
216
+ </td>
217
+ <td>
218
+ <span class="badge bg-info"><%= release[:unique_error_types] %></span>
219
+ </td>
220
+ <td>
221
+ <% if release[:new_error_count] > 0 %>
222
+ <span class="badge bg-danger">
223
+ <i class="bi bi-exclamation-circle"></i> <%= release[:new_error_count] %> new
224
+ </span>
225
+ <% else %>
226
+ <span class="badge bg-success">
227
+ <i class="bi bi-check-circle"></i> 0 new
228
+ </span>
229
+ <% end %>
230
+ </td>
231
+ <td>
232
+ <%
233
+ stability_color = case release[:stability]
234
+ when :green then "success"
235
+ when :yellow then "warning"
236
+ when :red then "danger"
237
+ else "secondary"
238
+ end
239
+ %>
240
+ <span class="badge bg-<%= stability_color %>">
241
+ <i class="bi bi-circle-fill" style="font-size: 0.5em; vertical-align: middle;"></i>
242
+ <%= release[:stability].to_s.capitalize %>
243
+ </span>
244
+ </td>
245
+ <td>
246
+ <% if release[:delta_from_previous] %>
247
+ <%
248
+ delta = release[:delta_from_previous]
249
+ pct = release[:delta_percentage]
250
+ delta_class = delta > 0 ? "text-danger" : (delta < 0 ? "text-success" : "text-muted")
251
+ delta_icon = delta > 0 ? "bi-arrow-up" : (delta < 0 ? "bi-arrow-down" : "bi-dash")
252
+ %>
253
+ <span class="<%= delta_class %>">
254
+ <i class="bi <%= delta_icon %>"></i>
255
+ <%= delta > 0 ? "+#{delta}" : delta %>
256
+ <% if pct %>
257
+ <small>(<%= pct > 0 ? "+#{pct}" : pct %>%)</small>
258
+ <% end %>
259
+ </span>
260
+ <% else %>
261
+ <span class="text-muted">&mdash;</span>
262
+ <% end %>
263
+ </td>
264
+ </tr>
265
+ <% end %>
266
+ </tbody>
267
+ </table>
268
+ </div>
269
+ </div>
270
+ <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
271
+ <small class="text-muted">
272
+ <i class="bi bi-info-circle"></i>
273
+ Stability: <span class="badge bg-success">Green</span> &le; avg errors,
274
+ <span class="badge bg-warning">Yellow</span> 1-2x avg,
275
+ <span class="badge bg-danger">Red</span> &gt; 2x avg.
276
+ "New" errors are error types first seen in that release.
277
+ </small>
278
+ <div>
279
+ <%== @pagy.series_nav(:bootstrap) if @pagy.pages > 1 %>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ <% end %>
284
+ </div>
@@ -245,6 +245,21 @@
245
245
  <%
246
246
  end
247
247
 
248
+ when :secret
249
+ if value.present?
250
+ %>
251
+ <span class="badge bg-success fs-6">
252
+ <i class="bi bi-check-circle"></i> Set
253
+ </span>
254
+ <%
255
+ else
256
+ %>
257
+ <span class="badge bg-warning text-dark fs-6">
258
+ <i class="bi bi-exclamation-triangle"></i> Not set
259
+ </span>
260
+ <%
261
+ end
262
+
248
263
  when :retention_days
249
264
  # Show retention configuration with manual cleanup instructions
250
265
  if value.present?
@@ -55,7 +55,10 @@
55
55
  enable_pagerduty_notifications: { label: "PagerDuty", description: "Critical error escalation", type: :boolean },
56
56
  pagerduty_integration_key: { label: "PagerDuty Integration Key", description: "PagerDuty API key", type: :string, show_if: :enable_pagerduty_notifications },
57
57
  enable_webhook_notifications: { label: "Custom Webhooks", description: "Generic webhook endpoints", type: :boolean },
58
- webhook_urls: { label: "Webhook URLs", description: "Custom webhook endpoints", type: :array, show_if: :enable_webhook_notifications }
58
+ webhook_urls: { label: "Webhook URLs", description: "Custom webhook endpoints", type: :array, show_if: :enable_webhook_notifications },
59
+ enable_scheduled_digests: { label: "Scheduled Digests", description: "Daily/weekly summary emails. Schedule via SolidQueue, Sidekiq, or cron", type: :boolean },
60
+ digest_frequency: { label: "Digest Frequency", description: "How often to send digest emails", type: :symbol, show_if: :enable_scheduled_digests },
61
+ digest_recipients: { label: "Digest Recipients", description: "Email addresses (falls back to notification recipients)", type: :array, show_if: :enable_scheduled_digests }
59
62
  }
60
63
  },
61
64
  "Advanced Analytics Features" => {
@@ -113,6 +116,39 @@
113
116
  dashboard_base_url: { label: "Dashboard Base URL", description: "Base URL for dashboard links", type: :url }
114
117
  }
115
118
  },
119
+ "Issue Tracking" => {
120
+ icon: "bi-link-45deg",
121
+ color: "primary",
122
+ settings: {
123
+ enable_issue_tracking: { label: "Issue Tracking", description: "GitHub/GitLab/Codeberg integration — one switch enables all: issue creation, auto-create, lifecycle sync, platform state mirroring, comment display. Replaces workflow controls with platform state.", type: :boolean },
124
+ issue_tracker_token: { label: "API Token", description: "RED bot token (set RED_BOT_TOKEN env var)", type: :secret, show_if: :enable_issue_tracking },
125
+ issue_tracker_provider: { label: "Provider", description: "Auto-detected from git_repository_url", type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_provider },
126
+ issue_tracker_repo: { label: "Repository", description: "Auto-detected from git_repository_url", type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_repo },
127
+ issue_tracker_labels: { label: "Labels", description: "Labels added to new issues", type: :array, show_if: :enable_issue_tracking },
128
+ issue_webhook_secret: { label: "Webhook Secret", description: "Set to enable two-way sync (HMAC verification)", type: :secret, show_if: :enable_issue_tracking }
129
+ }
130
+ },
131
+ "Deep Debugging" => {
132
+ icon: "bi-search",
133
+ color: "danger",
134
+ settings: {
135
+ enable_local_variables: { label: "Local Variable Capture", description: "TracePoint(:raise) — capture locals at exception time", type: :boolean },
136
+ enable_instance_variables: { label: "Instance Variable Capture", description: "Capture self's ivars at exception time", type: :boolean },
137
+ detect_swallowed_exceptions: { label: "Swallowed Exception Detection", description: "TracePoint(:raise) + (:rescue) — detect silently rescued exceptions (Ruby 3.3+)", type: :boolean },
138
+ enable_diagnostic_dump: { label: "Diagnostic Dump", description: "On-demand system state snapshots", type: :boolean },
139
+ enable_crash_capture: { label: "Process Crash Capture", description: "at_exit hook for unhandled crashes", type: :boolean },
140
+ enable_coverage_tracking: { label: "Code Path Coverage", description: "Diagnostic mode — enable via dashboard to see executed lines in source view (Ruby 3.2+)", type: :boolean }
141
+ }
142
+ },
143
+ "Event Tracking" => {
144
+ icon: "bi-broadcast",
145
+ color: "info",
146
+ settings: {
147
+ enable_rack_attack_tracking: { label: "Rack Attack Tracking", description: "Track throttle/blocklist events as breadcrumbs", type: :boolean },
148
+ enable_actioncable_tracking: { label: "ActionCable Tracking", description: "Track WebSocket channel events as breadcrumbs", type: :boolean },
149
+ enable_activestorage_tracking: { label: "ActiveStorage Tracking", description: "Track uploads/downloads/deletes as breadcrumbs", type: :boolean }
150
+ }
151
+ },
116
152
  "Advanced Configuration" => {
117
153
  icon: "bi-sliders",
118
154
  color: "secondary",
@@ -67,6 +67,27 @@
67
67
  <div class="row g-4">
68
68
  <!-- Error Information -->
69
69
  <div class="col-md-8">
70
+ <% if RailsErrorDashboard.configuration.enable_coverage_tracking && RailsErrorDashboard.configuration.enable_source_code_integration %>
71
+ <div class="alert mb-3 py-2 d-flex justify-content-between align-items-center <%= RailsErrorDashboard::Services::CoverageTracker.active? ? 'alert-info' : 'alert-light border' %>">
72
+ <div>
73
+ <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
74
+ <i class="bi bi-broadcast text-info"></i>
75
+ <strong>Coverage Active</strong> — expand source code to see executed lines
76
+ <% else %>
77
+ <i class="bi bi-code-square"></i>
78
+ <strong>Code Path Coverage</strong> — enable to see which lines were executed in production
79
+ <% end %>
80
+ </div>
81
+ <div>
82
+ <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
83
+ <%= button_to "Disable Coverage", disable_coverage_errors_path, method: :post, class: "btn btn-sm btn-outline-secondary" %>
84
+ <% else %>
85
+ <%= button_to "Enable Coverage", enable_coverage_errors_path, method: :post, class: "btn btn-sm btn-info" %>
86
+ <% end %>
87
+ </div>
88
+ </div>
89
+ <% end %>
90
+
70
91
  <%= render "error_info", error: @error %>
71
92
 
72
93
  <%= render "local_variables", error: @error %>
@@ -0,0 +1,172 @@
1
+ <% content_for :page_title, "User Impact" %>
2
+
3
+ <div class="container-fluid py-4">
4
+ <div class="d-flex justify-content-between align-items-center mb-4">
5
+ <h1 class="h3 mb-0">
6
+ <i class="bi bi-people me-2"></i>
7
+ User Impact
8
+ </h1>
9
+
10
+ <div class="btn-group" role="group">
11
+ <%= link_to user_impact_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
+ 7 Days
13
+ <% end %>
14
+ <%= link_to user_impact_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
+ 30 Days
16
+ <% end %>
17
+ <%= link_to user_impact_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
+ 90 Days
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+
23
+ <% if @entries.empty? %>
24
+ <div class="text-center py-5">
25
+ <i class="bi bi-people display-1 text-muted mb-3"></i>
26
+ <h4 class="text-muted">No User Impact Data</h4>
27
+ <p class="text-muted">
28
+ No errors with user attribution were found in the last <%= @days %> days.
29
+ </p>
30
+ <div class="card mx-auto" style="max-width: 500px;">
31
+ <div class="card-body text-start">
32
+ <h6>How user impact is tracked:</h6>
33
+ <ul class="mb-0">
34
+ <li>Errors must have a <code>user_id</code> associated</li>
35
+ <li>Auto-detected via <code>CurrentAttributes</code> or <code>current_user</code></li>
36
+ <li>Or set <code>config.user_model = "User"</code> in your initializer</li>
37
+ </ul>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <% else %>
42
+ <!-- Summary Cards -->
43
+ <div class="row mb-4">
44
+ <div class="col-md-3">
45
+ <div class="card text-center">
46
+ <div class="card-body">
47
+ <div class="display-6 text-primary"><%= @summary[:total_error_types_with_users] %></div>
48
+ <small class="text-muted">Error Types with Users</small>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <div class="col-md-3">
53
+ <div class="card text-center">
54
+ <div class="card-body">
55
+ <div class="display-6 text-danger"><%= @summary[:total_unique_users_affected] %></div>
56
+ <small class="text-muted">Total Users Affected</small>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div class="col-md-3">
61
+ <div class="card text-center">
62
+ <div class="card-body">
63
+ <div class="display-6"><code><%= @summary[:most_impactful] || "N/A" %></code></div>
64
+ <small class="text-muted">Most Impactful Error</small>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <div class="col-md-3">
69
+ <div class="card text-center">
70
+ <div class="card-body">
71
+ <div class="display-6 text-info"><%= @summary[:total_users] ? number_with_delimiter(@summary[:total_users]) : "N/A" %></div>
72
+ <small class="text-muted">Total Users (for %)</small>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- User Impact Table -->
79
+ <div class="card mb-4">
80
+ <div class="card-header bg-white d-flex justify-content-between align-items-center">
81
+ <h5 class="mb-0">
82
+ <i class="bi bi-bar-chart text-primary me-2"></i>
83
+ Errors Ranked by User Impact
84
+ <span class="badge bg-primary"><%= @summary[:total_error_types_with_users] %></span>
85
+ </h5>
86
+ <small class="text-muted"><%== @pagy.info_tag %></small>
87
+ </div>
88
+ <div class="card-body p-0">
89
+ <div class="table-responsive">
90
+ <table class="table table-hover mb-0">
91
+ <thead class="table-light">
92
+ <tr>
93
+ <th width="40">#</th>
94
+ <th>Error Type</th>
95
+ <th width="120">Unique Users</th>
96
+ <th width="120">Occurrences</th>
97
+ <% if @summary[:total_users] %>
98
+ <th width="150">User Impact %</th>
99
+ <% end %>
100
+ <th width="100">Severity</th>
101
+ <th width="140">Last Seen</th>
102
+ <th width="80"></th>
103
+ </tr>
104
+ </thead>
105
+ <tbody>
106
+ <% @entries.each_with_index do |entry, index| %>
107
+ <tr>
108
+ <td><strong><%= (@pagy.from || 1) + index %></strong></td>
109
+ <td>
110
+ <strong><%= entry[:error_type] %></strong>
111
+ <br><small class="text-muted"><%= entry[:message] %></small>
112
+ </td>
113
+ <td>
114
+ <span class="badge bg-danger"><%= entry[:unique_users] %> users</span>
115
+ </td>
116
+ <td>
117
+ <span class="badge bg-secondary"><%= entry[:total_occurrences] %>x</span>
118
+ </td>
119
+ <% if @summary[:total_users] && entry[:impact_percentage] %>
120
+ <td>
121
+ <div class="d-flex align-items-center gap-2">
122
+ <div class="progress flex-grow-1" style="height: 18px;">
123
+ <%
124
+ pct = entry[:impact_percentage]
125
+ bar_class = pct > 10 ? "bg-danger" : (pct > 5 ? "bg-warning" : "bg-info")
126
+ %>
127
+ <div class="progress-bar <%= bar_class %>" style="width: <%= [ pct, 100 ].min %>%;">
128
+ <%= pct %>%
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </td>
133
+ <% end %>
134
+ <td>
135
+ <%
136
+ sev = entry[:severity].to_s
137
+ sev_class = case sev
138
+ when "critical" then "bg-danger"
139
+ when "high" then "bg-warning text-dark"
140
+ when "medium" then "bg-info"
141
+ else "bg-secondary"
142
+ end
143
+ %>
144
+ <span class="badge <%= sev_class %>"><%= sev.capitalize %></span>
145
+ </td>
146
+ <td><small><%= local_time_ago(entry[:last_seen]) %></small></td>
147
+ <td>
148
+ <% if entry[:id] %>
149
+ <%= link_to error_path(entry[:id]), class: "btn btn-sm btn-outline-primary" do %>
150
+ <i class="bi bi-eye"></i>
151
+ <% end %>
152
+ <% end %>
153
+ </td>
154
+ </tr>
155
+ <% end %>
156
+ </tbody>
157
+ </table>
158
+ </div>
159
+ </div>
160
+ <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
161
+ <small class="text-muted">
162
+ <i class="bi bi-info-circle"></i>
163
+ Ranked by unique users affected, not occurrence count.
164
+ An error hitting 1000 users once is worse than hitting 1 user 1000 times.
165
+ </small>
166
+ <div>
167
+ <%== @pagy.series_nav(:bootstrap) if @pagy.pages > 1 %>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <% end %>
172
+ </div>
data/config/routes.rb CHANGED
@@ -37,8 +37,12 @@ RailsErrorDashboard::Engine.routes.draw do
37
37
  get :rack_attack_summary
38
38
  get :actioncable_health_summary
39
39
  get :activestorage_health_summary
40
+ get :releases
41
+ get :user_impact
40
42
  get :diagnostic_dumps
41
43
  post :create_diagnostic_dump
44
+ post :enable_coverage
45
+ post :disable_coverage
42
46
  post :batch_action
43
47
  end
44
48
  end
@@ -398,6 +398,12 @@ module RailsErrorDashboard
398
398
  end
399
399
 
400
400
  def add_route
401
+ routes_path = File.join(destination_root, "config", "routes.rb")
402
+ if File.exist?(routes_path) && File.read(routes_path).include?("RailsErrorDashboard::Engine")
403
+ say_status "skip", "route already exists (RailsErrorDashboard::Engine is already mounted)", :yellow
404
+ return
405
+ end
406
+
401
407
  route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard) — also works at /error_dashboard"
402
408
  end
403
409
 
@@ -1,33 +1,24 @@
1
1
  ===============================================================================
2
2
 
3
- Rails Error Dashboard has been installed!
3
+ RED (Rails Error Dashboard) has been installed!
4
4
 
5
5
  Next steps:
6
6
 
7
7
  1. Run migrations:
8
8
  rails db:migrate
9
9
 
10
- 2. (Optional) If you want to use a separate database for error logs:
11
- - Set USE_SEPARATE_ERROR_DB=true in your .env file
12
- - Configure error_logs database in config/database.yml
13
- - Run: rails db:create:error_logs
14
- - Run: rails db:migrate:error_logs
15
-
16
- 3. Start your Rails server:
10
+ 2. Start your Rails server:
17
11
  rails server
18
12
 
19
- 4. Visit the error dashboard:
20
- http://localhost:3000/error_dashboard
13
+ 3. Visit the dashboard:
14
+ http://localhost:3000/red
21
15
 
22
- 5. Default credentials (change in config/initializers/rails_error_dashboard.rb):
23
- Username: admin
24
- Password: password
16
+ 4. Change the default credentials in:
17
+ config/initializers/rails_error_dashboard.rb
25
18
 
26
- 6. (Optional) Set up Slack notifications:
27
- - Set SLACK_WEBHOOK_URL in your .env file
28
- - Errors will be sent to your Slack channel automatically
19
+ For separate database setup, see:
20
+ https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md
29
21
 
30
- For more information, visit:
31
- https://github.com/anjanjagirdar/rails_error_dashboard
22
+ Full docs: https://github.com/AnjanJ/rails_error_dashboard
32
23
 
33
24
  ===============================================================================