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.
- checksums.yaml +4 -4
- data/README.md +60 -7
- data/app/controllers/rails_error_dashboard/errors_controller.rb +41 -0
- data/app/controllers/rails_error_dashboard/webhooks_controller.rb +2 -1
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +12 -0
- data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +40 -0
- data/app/mailers/rails_error_dashboard/digest_mailer.rb +23 -0
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +2 -1
- data/app/views/layouts/rails_error_dashboard.html.erb +10 -0
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +172 -0
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +49 -0
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +14 -1
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -7
- data/app/views/rails_error_dashboard/errors/releases.html.erb +284 -0
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +15 -0
- data/app/views/rails_error_dashboard/errors/settings.html.erb +37 -1
- data/app/views/rails_error_dashboard/errors/show.html.erb +21 -0
- data/app/views/rails_error_dashboard/errors/user_impact.html.erb +172 -0
- data/config/routes.rb +4 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +6 -0
- data/lib/generators/rails_error_dashboard/install/templates/README +9 -18
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +32 -2
- data/lib/rails_error_dashboard/configuration.rb +55 -13
- data/lib/rails_error_dashboard/middleware/rate_limiter.rb +16 -12
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -1
- data/lib/rails_error_dashboard/queries/release_timeline.rb +181 -0
- data/lib/rails_error_dashboard/queries/user_impact_summary.rb +93 -0
- data/lib/rails_error_dashboard/services/coverage_tracker.rb +139 -0
- data/lib/rails_error_dashboard/services/digest_builder.rb +158 -0
- data/lib/rails_error_dashboard/services/notification_helpers.rb +2 -1
- data/lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb +6 -7
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +4 -0
- data/lib/tasks/error_dashboard.rake +23 -0
- 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">—</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
|
+
→ <%= 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">—</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> ≤ avg errors,
|
|
274
|
+
<span class="badge bg-warning">Yellow</span> 1-2x avg,
|
|
275
|
+
<span class="badge bg-danger">Red</span> > 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.
|
|
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
|
-
|
|
20
|
-
http://localhost:3000/
|
|
13
|
+
3. Visit the dashboard:
|
|
14
|
+
http://localhost:3000/red
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Password: password
|
|
16
|
+
4. Change the default credentials in:
|
|
17
|
+
config/initializers/rails_error_dashboard.rb
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
https://github.com/anjanjagirdar/rails_error_dashboard
|
|
22
|
+
Full docs: https://github.com/AnjanJ/rails_error_dashboard
|
|
32
23
|
|
|
33
24
|
===============================================================================
|