rails_error_dashboard 0.1.1 → 0.1.4
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 +92 -21
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +135 -1
- data/app/helpers/rails_error_dashboard/application_helper.rb +80 -4
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
- data/app/models/rails_error_dashboard/error_comment.rb +27 -0
- data/app/models/rails_error_dashboard/error_log.rb +159 -0
- data/app/views/layouts/rails_error_dashboard/application.html.erb +39 -1
- data/app/views/layouts/rails_error_dashboard.html.erb +796 -299
- data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +2 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +4 -4
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +439 -22
- data/app/views/rails_error_dashboard/errors/index.html.erb +127 -11
- data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +29 -18
- data/app/views/rails_error_dashboard/errors/show.html.erb +353 -54
- data/config/routes.rb +11 -1
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
- data/db/migrate/20251226020100_create_error_comments.rb +18 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +8 -2
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +21 -0
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +317 -0
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +47 -9
- data/lib/rails_error_dashboard/commands/resolve_error.rb +1 -1
- data/lib/rails_error_dashboard/configuration.rb +8 -0
- data/lib/rails_error_dashboard/error_reporter.rb +4 -4
- data/lib/rails_error_dashboard/logger.rb +105 -0
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +2 -2
- data/lib/rails_error_dashboard/plugin.rb +3 -3
- data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +1 -1
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -1
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +109 -1
- data/lib/rails_error_dashboard/queries/errors_list.rb +134 -7
- data/lib/rails_error_dashboard/queries/mttr_stats.rb +111 -0
- data/lib/rails_error_dashboard/queries/recurring_issues.rb +97 -0
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +5 -0
- data/lib/tasks/rails_error_dashboard_tasks.rake +85 -4
- metadata +36 -2
|
@@ -15,6 +15,9 @@ module RailsErrorDashboard
|
|
|
15
15
|
# Association for tracking individual error occurrences
|
|
16
16
|
has_many :error_occurrences, class_name: "RailsErrorDashboard::ErrorOccurrence", dependent: :destroy
|
|
17
17
|
|
|
18
|
+
# Association for comment threads (Phase 3: Workflow Integration)
|
|
19
|
+
has_many :comments, class_name: "RailsErrorDashboard::ErrorComment", foreign_key: :error_log_id, dependent: :destroy
|
|
20
|
+
|
|
18
21
|
# Cascade pattern associations
|
|
19
22
|
# parent_cascade_patterns: patterns where this error is the CHILD (errors that cause this error)
|
|
20
23
|
has_many :parent_cascade_patterns, class_name: "RailsErrorDashboard::CascadePattern",
|
|
@@ -38,6 +41,15 @@ module RailsErrorDashboard
|
|
|
38
41
|
scope :last_24_hours, -> { where("occurred_at >= ?", 24.hours.ago) }
|
|
39
42
|
scope :last_week, -> { where("occurred_at >= ?", 1.week.ago) }
|
|
40
43
|
|
|
44
|
+
# Phase 3: Workflow Integration scopes
|
|
45
|
+
scope :active, -> { where("snoozed_until IS NULL OR snoozed_until < ?", Time.current) }
|
|
46
|
+
scope :snoozed, -> { where("snoozed_until IS NOT NULL AND snoozed_until >= ?", Time.current) }
|
|
47
|
+
scope :by_status, ->(status) { where(status: status) }
|
|
48
|
+
scope :assigned, -> { where.not(assigned_to: nil) }
|
|
49
|
+
scope :unassigned, -> { where(assigned_to: nil) }
|
|
50
|
+
scope :by_assignee, ->(name) { where(assigned_to: name) }
|
|
51
|
+
scope :by_priority, ->(level) { where(priority_level: level) }
|
|
52
|
+
|
|
41
53
|
# Set defaults and tracking
|
|
42
54
|
before_validation :set_defaults, on: :create
|
|
43
55
|
before_create :set_tracking_fields
|
|
@@ -121,6 +133,11 @@ module RailsErrorDashboard
|
|
|
121
133
|
SystemStackError
|
|
122
134
|
SignalException
|
|
123
135
|
ActiveRecord::StatementInvalid
|
|
136
|
+
LoadError
|
|
137
|
+
SyntaxError
|
|
138
|
+
ActiveRecord::ConnectionNotEstablished
|
|
139
|
+
Redis::ConnectionError
|
|
140
|
+
OpenSSL::SSL::SSLError
|
|
124
141
|
].freeze
|
|
125
142
|
|
|
126
143
|
HIGH_SEVERITY_ERROR_TYPES = %w[
|
|
@@ -129,6 +146,11 @@ module RailsErrorDashboard
|
|
|
129
146
|
TypeError
|
|
130
147
|
NoMethodError
|
|
131
148
|
NameError
|
|
149
|
+
ZeroDivisionError
|
|
150
|
+
FloatDomainError
|
|
151
|
+
IndexError
|
|
152
|
+
KeyError
|
|
153
|
+
RangeError
|
|
132
154
|
].freeze
|
|
133
155
|
|
|
134
156
|
MEDIUM_SEVERITY_ERROR_TYPES = %w[
|
|
@@ -136,6 +158,10 @@ module RailsErrorDashboard
|
|
|
136
158
|
Timeout::Error
|
|
137
159
|
Net::ReadTimeout
|
|
138
160
|
Net::OpenTimeout
|
|
161
|
+
ActiveRecord::RecordNotUnique
|
|
162
|
+
JSON::ParserError
|
|
163
|
+
CSV::MalformedCSVError
|
|
164
|
+
Errno::ECONNREFUSED
|
|
139
165
|
].freeze
|
|
140
166
|
|
|
141
167
|
# Find existing error by hash or create new one
|
|
@@ -179,6 +205,139 @@ module RailsErrorDashboard
|
|
|
179
205
|
Commands::ResolveError.call(id, resolution_data)
|
|
180
206
|
end
|
|
181
207
|
|
|
208
|
+
# Phase 3: Workflow Integration methods
|
|
209
|
+
|
|
210
|
+
# Assignment methods
|
|
211
|
+
def assign_to!(assignee_name)
|
|
212
|
+
update!(
|
|
213
|
+
assigned_to: assignee_name,
|
|
214
|
+
assigned_at: Time.current,
|
|
215
|
+
status: "in_progress" # Auto-transition to in_progress when assigned
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def unassign!
|
|
220
|
+
update!(
|
|
221
|
+
assigned_to: nil,
|
|
222
|
+
assigned_at: nil
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def assigned?
|
|
227
|
+
assigned_to.present?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Snooze methods
|
|
231
|
+
def snooze!(hours, reason: nil)
|
|
232
|
+
snooze_until = hours.hours.from_now
|
|
233
|
+
# Store snooze reason in comments if provided
|
|
234
|
+
if reason.present?
|
|
235
|
+
comments.create!(
|
|
236
|
+
author_name: assigned_to || "System",
|
|
237
|
+
body: "Snoozed for #{hours} hours: #{reason}"
|
|
238
|
+
)
|
|
239
|
+
end
|
|
240
|
+
update!(snoozed_until: snooze_until)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def unsnooze!
|
|
244
|
+
update!(snoozed_until: nil)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def snoozed?
|
|
248
|
+
snoozed_until.present? && snoozed_until >= Time.current
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Priority methods
|
|
252
|
+
def priority_label
|
|
253
|
+
case priority_level
|
|
254
|
+
when 3 then "Critical"
|
|
255
|
+
when 2 then "High"
|
|
256
|
+
when 1 then "Medium"
|
|
257
|
+
when 0 then "Low"
|
|
258
|
+
else "Unset"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def priority_color
|
|
263
|
+
case priority_level
|
|
264
|
+
when 3 then "danger" # Critical = red
|
|
265
|
+
when 2 then "warning" # High = orange
|
|
266
|
+
when 1 then "info" # Medium = blue
|
|
267
|
+
when 0 then "secondary" # Low = gray
|
|
268
|
+
else "light"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def calculate_priority
|
|
273
|
+
# Automatic priority calculation based on severity and frequency
|
|
274
|
+
severity_weight = case severity
|
|
275
|
+
when :critical then 3
|
|
276
|
+
when :high then 2
|
|
277
|
+
when :medium then 1
|
|
278
|
+
else 0
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
frequency_weight = if occurrence_count >= 100
|
|
282
|
+
3
|
|
283
|
+
elsif occurrence_count >= 10
|
|
284
|
+
2
|
|
285
|
+
elsif occurrence_count >= 5
|
|
286
|
+
1
|
|
287
|
+
else
|
|
288
|
+
0
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Take the higher of severity or frequency
|
|
292
|
+
[ severity_weight, frequency_weight ].max
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Status transition methods
|
|
296
|
+
def status_badge_color
|
|
297
|
+
case status
|
|
298
|
+
when "new" then "primary"
|
|
299
|
+
when "in_progress" then "info"
|
|
300
|
+
when "investigating" then "warning"
|
|
301
|
+
when "resolved" then "success"
|
|
302
|
+
when "wont_fix" then "secondary"
|
|
303
|
+
else "light"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def can_transition_to?(new_status)
|
|
308
|
+
# Define valid status transitions
|
|
309
|
+
valid_transitions = {
|
|
310
|
+
"new" => [ "in_progress", "investigating", "wont_fix" ],
|
|
311
|
+
"in_progress" => [ "investigating", "resolved", "new" ],
|
|
312
|
+
"investigating" => [ "resolved", "in_progress", "wont_fix" ],
|
|
313
|
+
"resolved" => [ "new" ], # Can reopen if error recurs
|
|
314
|
+
"wont_fix" => [ "new" ] # Can reopen
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
valid_transitions[status]&.include?(new_status) || false
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def update_status!(new_status, comment: nil)
|
|
321
|
+
return false unless can_transition_to?(new_status)
|
|
322
|
+
|
|
323
|
+
transaction do
|
|
324
|
+
update!(status: new_status)
|
|
325
|
+
|
|
326
|
+
# Auto-resolve if status is "resolved"
|
|
327
|
+
update!(resolved: true) if new_status == "resolved"
|
|
328
|
+
|
|
329
|
+
# Add comment about status change
|
|
330
|
+
if comment.present?
|
|
331
|
+
comments.create!(
|
|
332
|
+
author_name: assigned_to || "System",
|
|
333
|
+
body: "Status changed to #{new_status}: #{comment}"
|
|
334
|
+
)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
true
|
|
339
|
+
end
|
|
340
|
+
|
|
182
341
|
# Get error statistics
|
|
183
342
|
def self.statistics(days = 7)
|
|
184
343
|
start_date = days.days.ago
|
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<title>Rails
|
|
4
|
+
<title>Rails Error Dashboard</title>
|
|
5
5
|
<%= csrf_meta_tags %>
|
|
6
6
|
<%= csp_meta_tag %>
|
|
7
7
|
|
|
8
8
|
<%= yield :head %>
|
|
9
9
|
|
|
10
10
|
<%= stylesheet_link_tag "rails_error_dashboard/application", media: "all" %>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
|
|
11
13
|
</head>
|
|
12
14
|
<body>
|
|
13
15
|
|
|
16
|
+
<!-- Navigation -->
|
|
17
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
18
|
+
<div class="container-fluid">
|
|
19
|
+
<%= link_to "Error Dashboard", root_path, class: "navbar-brand" %>
|
|
20
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
21
|
+
<span class="navbar-toggler-icon"></span>
|
|
22
|
+
</button>
|
|
23
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
24
|
+
<ul class="navbar-nav me-auto">
|
|
25
|
+
<li class="nav-item">
|
|
26
|
+
<%= link_to overview_path, class: "nav-link #{controller_name == 'errors' && action_name == 'overview' ? 'active' : ''}" do %>
|
|
27
|
+
<i class="bi bi-speedometer2"></i> Dashboard
|
|
28
|
+
<% end %>
|
|
29
|
+
</li>
|
|
30
|
+
<li class="nav-item">
|
|
31
|
+
<%= link_to errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'index' ? 'active' : ''}" do %>
|
|
32
|
+
<i class="bi bi-list-ul"></i> Errors
|
|
33
|
+
<% end %>
|
|
34
|
+
</li>
|
|
35
|
+
<li class="nav-item">
|
|
36
|
+
<%= link_to analytics_errors_path, class: "nav-link #{controller_name == 'errors' && action_name == 'analytics' ? 'active' : ''}" do %>
|
|
37
|
+
<i class="bi bi-graph-up"></i> Analytics
|
|
38
|
+
<% end %>
|
|
39
|
+
</li>
|
|
40
|
+
</ul>
|
|
41
|
+
<ul class="navbar-nav">
|
|
42
|
+
<li class="nav-item">
|
|
43
|
+
<a class="nav-link" href="#" id="theme-toggle">
|
|
44
|
+
<i class="bi bi-moon-stars"></i>
|
|
45
|
+
</a>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</nav>
|
|
51
|
+
|
|
14
52
|
<%= yield %>
|
|
15
53
|
|
|
16
54
|
</body>
|