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,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Pure algorithm: Build a digest summary of error activity for a time period.
6
+ # Aggregates stats from existing queries into a single hash suitable for email templates.
7
+ #
8
+ # @example
9
+ # DigestBuilder.call(period: :daily)
10
+ # # => { period: :daily, stats: { new_errors: 12, ... }, top_errors: [...], ... }
11
+ class DigestBuilder
12
+ PERIODS = {
13
+ daily: { days: 1, label: "Last 24 hours" },
14
+ weekly: { days: 7, label: "Last 7 days" }
15
+ }.freeze
16
+
17
+ def self.call(period: :daily, application_id: nil)
18
+ new(period: period, application_id: application_id).call
19
+ end
20
+
21
+ def initialize(period: :daily, application_id: nil)
22
+ @period = PERIODS.key?(period) ? period : :daily
23
+ @days = PERIODS[@period][:days]
24
+ @application_id = application_id
25
+ @start_date = @days.days.ago
26
+ end
27
+
28
+ def call
29
+ {
30
+ period: @period,
31
+ period_label: PERIODS[@period][:label],
32
+ generated_at: Time.current,
33
+ stats: build_stats,
34
+ top_errors: top_errors,
35
+ critical_unresolved: critical_unresolved,
36
+ comparison: build_comparison
37
+ }
38
+ rescue => e
39
+ Rails.logger.error("[RailsErrorDashboard] DigestBuilder failed: #{e.class}: #{e.message}")
40
+ empty_result
41
+ end
42
+
43
+ private
44
+
45
+ def base_scope
46
+ scope = ErrorLog.where("occurred_at >= ?", @start_date)
47
+ scope = scope.where(application_id: @application_id) if @application_id.present?
48
+ scope
49
+ end
50
+
51
+ def build_stats
52
+ scope = base_scope
53
+
54
+ new_errors = scope.where("occurrence_count <= 1").count
55
+ total_occurrences = scope.sum(:occurrence_count)
56
+ resolved = scope.where(resolved: true).count
57
+ unresolved = scope.where(resolved: false).count
58
+ # Severity is computed from error_type via SeverityClassifier (not a DB column).
59
+ # Count critical+high by matching known error type patterns via SQL WHERE IN.
60
+ critical_types = Services::SeverityClassifier::CRITICAL_ERROR_TYPES +
61
+ Services::SeverityClassifier::HIGH_SEVERITY_ERROR_TYPES
62
+ critical_high = scope.where(error_type: critical_types).count
63
+
64
+ total = resolved + unresolved
65
+ resolution_rate = total > 0 ? (resolved.to_f / total * 100).round(1) : 0
66
+
67
+ {
68
+ new_errors: new_errors,
69
+ total_occurrences: total_occurrences,
70
+ resolved: resolved,
71
+ unresolved: unresolved,
72
+ critical_high: critical_high,
73
+ resolution_rate: resolution_rate
74
+ }
75
+ rescue => e
76
+ Rails.logger.error("[RailsErrorDashboard] DigestBuilder.build_stats failed: #{e.class}: #{e.message}")
77
+ { new_errors: 0, total_occurrences: 0, resolved: 0, unresolved: 0, critical_high: 0, resolution_rate: 0 }
78
+ end
79
+
80
+ def top_errors
81
+ base_scope
82
+ .where(resolved: false)
83
+ .group(:error_type)
84
+ .order("count_all DESC")
85
+ .limit(5)
86
+ .count
87
+ .map do |error_type, count|
88
+ sample = base_scope.where(error_type: error_type).order(occurred_at: :desc).first
89
+ {
90
+ error_type: error_type,
91
+ message: sample&.message.to_s.truncate(100),
92
+ count: count,
93
+ id: sample&.id
94
+ }
95
+ end
96
+ rescue => e
97
+ Rails.logger.error("[RailsErrorDashboard] DigestBuilder.top_errors failed: #{e.class}: #{e.message}")
98
+ []
99
+ end
100
+
101
+ def critical_unresolved
102
+ critical_types = Services::SeverityClassifier::CRITICAL_ERROR_TYPES +
103
+ Services::SeverityClassifier::HIGH_SEVERITY_ERROR_TYPES
104
+ base_scope
105
+ .where(resolved: false)
106
+ .where(error_type: critical_types)
107
+ .order(occurred_at: :desc)
108
+ .limit(5)
109
+ .map do |error|
110
+ {
111
+ error_type: error.error_type,
112
+ message: error.message.to_s.truncate(100),
113
+ severity: error.severity,
114
+ id: error.id
115
+ }
116
+ end
117
+ rescue => e
118
+ Rails.logger.error("[RailsErrorDashboard] DigestBuilder.critical_unresolved failed: #{e.class}: #{e.message}")
119
+ []
120
+ end
121
+
122
+ def build_comparison
123
+ previous_start = (@days * 2).days.ago
124
+ previous_end = @start_date
125
+
126
+ current_count = base_scope.count
127
+ previous_scope = ErrorLog.where("occurred_at >= ? AND occurred_at < ?", previous_start, previous_end)
128
+ previous_scope = previous_scope.where(application_id: @application_id) if @application_id.present?
129
+ previous_count = previous_scope.count
130
+
131
+ delta = current_count - previous_count
132
+ percentage = previous_count > 0 ? ((delta.to_f / previous_count) * 100).round(1) : nil
133
+
134
+ {
135
+ current_count: current_count,
136
+ previous_count: previous_count,
137
+ error_delta: delta,
138
+ error_delta_percentage: percentage
139
+ }
140
+ rescue => e
141
+ Rails.logger.error("[RailsErrorDashboard] DigestBuilder.build_comparison failed: #{e.class}: #{e.message}")
142
+ { current_count: 0, previous_count: 0, error_delta: 0, error_delta_percentage: nil }
143
+ end
144
+
145
+ def empty_result
146
+ {
147
+ period: @period,
148
+ period_label: PERIODS.dig(@period, :label) || "Unknown",
149
+ generated_at: Time.current,
150
+ stats: { new_errors: 0, total_occurrences: 0, resolved: 0, unresolved: 0, critical_high: 0, resolution_rate: 0 },
151
+ top_errors: [],
152
+ critical_unresolved: [],
153
+ comparison: { current_count: 0, previous_count: 0, error_delta: 0, error_delta_percentage: nil }
154
+ }
155
+ end
156
+ end
157
+ end
158
+ end
@@ -14,7 +14,8 @@ module RailsErrorDashboard
14
14
  # @return [String] Full URL to the error detail page
15
15
  def dashboard_url(error_log)
16
16
  base_url = RailsErrorDashboard.configuration.dashboard_base_url || "http://localhost:3000"
17
- "#{base_url}/error_dashboard/errors/#{error_log.id}"
17
+ mount_path = RailsErrorDashboard.configuration.engine_mount_path
18
+ "#{base_url}#{mount_path}/errors/#{error_log.id}"
18
19
  end
19
20
 
20
21
  # Truncate a message to a maximum length
@@ -13,7 +13,8 @@ module RailsErrorDashboard
13
13
  # Called when a new error is first logged
14
14
  def on_error_logged(error_log)
15
15
  return unless should_auto_create?(error_log)
16
- CreateIssueJob.perform_later(error_log.id)
16
+ dashboard_url = Services::NotificationHelpers.dashboard_url(error_log)
17
+ CreateIssueJob.perform_later(error_log.id, dashboard_url: dashboard_url)
17
18
  rescue => e
18
19
  nil
19
20
  end
@@ -49,17 +50,15 @@ module RailsErrorDashboard
49
50
 
50
51
  def should_auto_create?(error_log)
51
52
  config = RailsErrorDashboard.configuration
52
- return false unless config.enable_issue_tracking && config.auto_create_issues
53
+ return false unless config.enable_issue_tracking
53
54
  return false if error_log.external_issue_url.present?
54
55
 
55
- # First occurrence check
56
- if config.auto_create_issues_on_first_occurrence && error_log.occurrence_count == 1
57
- return true
58
- end
56
+ # First occurrence — always auto-create
57
+ return true if error_log.occurrence_count == 1
59
58
 
60
59
  # Severity threshold check
61
60
  severity = error_log.severity&.to_sym
62
- if config.auto_create_issues_for_severities&.include?(severity)
61
+ if config.issue_tracker_auto_create_severities&.include?(severity)
63
62
  return true
64
63
  end
65
64
 
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.9"
2
+ VERSION = "0.5.11"
3
3
  end
@@ -67,6 +67,9 @@ require "rails_error_dashboard/services/local_variable_capturer"
67
67
  require "rails_error_dashboard/services/swallowed_exception_tracker"
68
68
  require "rails_error_dashboard/services/crash_capture"
69
69
  require "rails_error_dashboard/services/diagnostic_dump_generator"
70
+ require "rails_error_dashboard/services/coverage_tracker"
71
+ require "rails_error_dashboard/services/digest_builder"
72
+ require "rails_error_dashboard/queries/user_impact_summary"
70
73
  require "rails_error_dashboard/subscribers/breadcrumb_subscriber"
71
74
  require "rails_error_dashboard/subscribers/rack_attack_subscriber"
72
75
  require "rails_error_dashboard/subscribers/action_cable_subscriber"
@@ -119,6 +122,7 @@ require "rails_error_dashboard/queries/job_health_summary"
119
122
  require "rails_error_dashboard/queries/database_health_summary"
120
123
  require "rails_error_dashboard/queries/swallowed_exception_summary"
121
124
  require "rails_error_dashboard/queries/rack_attack_summary"
125
+ require "rails_error_dashboard/queries/release_timeline"
122
126
  require "rails_error_dashboard/error_reporter"
123
127
  require "rails_error_dashboard/middleware/error_catcher"
124
128
  require "rails_error_dashboard/middleware/rate_limiter"
@@ -525,4 +525,27 @@ namespace :error_dashboard do
525
525
 
526
526
  puts "\n" + "=" * 80 + "\n"
527
527
  end
528
+
529
+ desc "Send error digest email (PERIOD=daily|weekly, APP_ID=optional)"
530
+ task send_digest: :environment do
531
+ period = ENV.fetch("PERIOD", "daily")
532
+ app_id = ENV["APP_ID"]
533
+
534
+ unless RailsErrorDashboard.configuration.enable_scheduled_digests
535
+ puts "Scheduled digests are not enabled. Set config.enable_scheduled_digests = true"
536
+ next
537
+ end
538
+
539
+ recipients = RailsErrorDashboard.configuration.digest_recipients ||
540
+ RailsErrorDashboard.configuration.notification_email_recipients
541
+
542
+ if recipients.blank?
543
+ puts "No digest recipients configured. Set config.digest_recipients or config.notification_email_recipients"
544
+ next
545
+ end
546
+
547
+ puts "Sending #{period} error digest to #{recipients.join(', ')}..."
548
+ RailsErrorDashboard::ScheduledDigestJob.perform_later(period: period, application_id: app_id)
549
+ puts "Digest job enqueued."
550
+ end
528
551
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_error_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.9
4
+ version: 0.5.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -260,10 +260,12 @@ files:
260
260
  - app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb
261
261
  - app/jobs/rails_error_dashboard/reopen_linked_issue_job.rb
262
262
  - app/jobs/rails_error_dashboard/retention_cleanup_job.rb
263
+ - app/jobs/rails_error_dashboard/scheduled_digest_job.rb
263
264
  - app/jobs/rails_error_dashboard/slack_error_notification_job.rb
264
265
  - app/jobs/rails_error_dashboard/swallowed_exception_flush_job.rb
265
266
  - app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
266
267
  - app/mailers/rails_error_dashboard/application_mailer.rb
268
+ - app/mailers/rails_error_dashboard/digest_mailer.rb
267
269
  - app/mailers/rails_error_dashboard/error_notification_mailer.rb
268
270
  - app/models/rails_error_dashboard/application.rb
269
271
  - app/models/rails_error_dashboard/cascade_pattern.rb
@@ -275,6 +277,8 @@ files:
275
277
  - app/models/rails_error_dashboard/error_occurrence.rb
276
278
  - app/models/rails_error_dashboard/swallowed_exception.rb
277
279
  - app/views/layouts/rails_error_dashboard.html.erb
280
+ - app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb
281
+ - app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb
278
282
  - app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb
279
283
  - app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb
280
284
  - app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb
@@ -310,10 +314,12 @@ files:
310
314
  - app/views/rails_error_dashboard/errors/overview.html.erb
311
315
  - app/views/rails_error_dashboard/errors/platform_comparison.html.erb
312
316
  - app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb
317
+ - app/views/rails_error_dashboard/errors/releases.html.erb
313
318
  - app/views/rails_error_dashboard/errors/settings.html.erb
314
319
  - app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb
315
320
  - app/views/rails_error_dashboard/errors/show.html.erb
316
321
  - app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb
322
+ - app/views/rails_error_dashboard/errors/user_impact.html.erb
317
323
  - config/routes.rb
318
324
  - db/development.sqlite3
319
325
  - db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb
@@ -414,8 +420,10 @@ files:
414
420
  - lib/rails_error_dashboard/queries/platform_comparison.rb
415
421
  - lib/rails_error_dashboard/queries/rack_attack_summary.rb
416
422
  - lib/rails_error_dashboard/queries/recurring_issues.rb
423
+ - lib/rails_error_dashboard/queries/release_timeline.rb
417
424
  - lib/rails_error_dashboard/queries/similar_errors.rb
418
425
  - lib/rails_error_dashboard/queries/swallowed_exception_summary.rb
426
+ - lib/rails_error_dashboard/queries/user_impact_summary.rb
419
427
  - lib/rails_error_dashboard/services/analytics_cache_manager.rb
420
428
  - lib/rails_error_dashboard/services/backtrace_parser.rb
421
429
  - lib/rails_error_dashboard/services/backtrace_processor.rb
@@ -427,10 +435,12 @@ files:
427
435
  - lib/rails_error_dashboard/services/cascade_detector.rb
428
436
  - lib/rails_error_dashboard/services/cause_chain_extractor.rb
429
437
  - lib/rails_error_dashboard/services/codeberg_issue_client.rb
438
+ - lib/rails_error_dashboard/services/coverage_tracker.rb
430
439
  - lib/rails_error_dashboard/services/crash_capture.rb
431
440
  - lib/rails_error_dashboard/services/curl_generator.rb
432
441
  - lib/rails_error_dashboard/services/database_health_inspector.rb
433
442
  - lib/rails_error_dashboard/services/diagnostic_dump_generator.rb
443
+ - lib/rails_error_dashboard/services/digest_builder.rb
434
444
  - lib/rails_error_dashboard/services/discord_payload_builder.rb
435
445
  - lib/rails_error_dashboard/services/environment_snapshot.rb
436
446
  - lib/rails_error_dashboard/services/error_broadcaster.rb
@@ -484,14 +494,30 @@ metadata:
484
494
  documentation_uri: https://AnjanJ.github.io/rails_error_dashboard
485
495
  bug_tracker_uri: https://github.com/AnjanJ/rails_error_dashboard/issues
486
496
  funding_uri: https://github.com/sponsors/AnjanJ
487
- post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
488
- \ Rails Error Dashboard v0.5.9\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
489
- First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
490
- db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
491
- => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
492
- \ \U0001F4DD Changelog: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md\n\n\U0001F3AE
493
- Live demo: https://rails-error-dashboard.anjan.dev\n (gandalf / youshallnotpass)\n\n\U0001F4D6
494
- Full docs: https://github.com/AnjanJ/rails_error_dashboard\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
497
+ post_install_message: |
498
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
499
+ RED (Rails Error Dashboard) v0.5.11
500
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
501
+
502
+ First install:
503
+ rails generate rails_error_dashboard:install
504
+ rails db:migrate
505
+ # Route and config are set up automatically by the generator.
506
+
507
+ Upgrading from a previous version:
508
+ rails generate rails_error_dashboard:install
509
+ rails db:migrate
510
+ # The generator detects your existing config and only adds new migrations.
511
+
512
+ Separate database users:
513
+ rails generate rails_error_dashboard:install
514
+ rails db:migrate:error_dashboard
515
+ # See docs for full separate-DB setup.
516
+
517
+ Live demo: https://rails-error-dashboard.anjan.dev
518
+ Full docs: https://github.com/AnjanJ/rails_error_dashboard
519
+ Changelog: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
520
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
495
521
  rdoc_options: []
496
522
  require_paths:
497
523
  - lib