rails_error_dashboard 0.1.0

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +858 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/stylesheets/rails_error_dashboard/application.css +15 -0
  6. data/app/controllers/rails_error_dashboard/application_controller.rb +12 -0
  7. data/app/controllers/rails_error_dashboard/errors_controller.rb +123 -0
  8. data/app/helpers/rails_error_dashboard/application_helper.rb +4 -0
  9. data/app/jobs/rails_error_dashboard/application_job.rb +4 -0
  10. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +116 -0
  11. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +19 -0
  12. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +105 -0
  13. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +166 -0
  14. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +108 -0
  15. data/app/mailers/rails_error_dashboard/application_mailer.rb +8 -0
  16. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +27 -0
  17. data/app/models/rails_error_dashboard/application_record.rb +5 -0
  18. data/app/models/rails_error_dashboard/error_log.rb +185 -0
  19. data/app/models/rails_error_dashboard/error_logs_record.rb +34 -0
  20. data/app/views/layouts/rails_error_dashboard/application.html.erb +17 -0
  21. data/app/views/layouts/rails_error_dashboard.html.erb +351 -0
  22. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +200 -0
  23. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +32 -0
  24. data/app/views/rails_error_dashboard/errors/analytics.html.erb +237 -0
  25. data/app/views/rails_error_dashboard/errors/index.html.erb +334 -0
  26. data/app/views/rails_error_dashboard/errors/show.html.erb +294 -0
  27. data/config/routes.rb +13 -0
  28. data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +40 -0
  29. data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +13 -0
  30. data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +10 -0
  31. data/lib/generators/rails_error_dashboard/install/install_generator.rb +27 -0
  32. data/lib/generators/rails_error_dashboard/install/templates/README +33 -0
  33. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +64 -0
  34. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +40 -0
  35. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +60 -0
  36. data/lib/rails_error_dashboard/commands/log_error.rb +134 -0
  37. data/lib/rails_error_dashboard/commands/resolve_error.rb +35 -0
  38. data/lib/rails_error_dashboard/configuration.rb +83 -0
  39. data/lib/rails_error_dashboard/engine.rb +20 -0
  40. data/lib/rails_error_dashboard/error_reporter.rb +35 -0
  41. data/lib/rails_error_dashboard/middleware/error_catcher.rb +41 -0
  42. data/lib/rails_error_dashboard/plugin.rb +98 -0
  43. data/lib/rails_error_dashboard/plugin_registry.rb +88 -0
  44. data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +96 -0
  45. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +122 -0
  46. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +78 -0
  47. data/lib/rails_error_dashboard/queries/analytics_stats.rb +108 -0
  48. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +37 -0
  49. data/lib/rails_error_dashboard/queries/developer_insights.rb +277 -0
  50. data/lib/rails_error_dashboard/queries/errors_list.rb +66 -0
  51. data/lib/rails_error_dashboard/queries/errors_list_v2.rb +149 -0
  52. data/lib/rails_error_dashboard/queries/filter_options.rb +21 -0
  53. data/lib/rails_error_dashboard/services/platform_detector.rb +41 -0
  54. data/lib/rails_error_dashboard/value_objects/error_context.rb +148 -0
  55. data/lib/rails_error_dashboard/version.rb +3 -0
  56. data/lib/rails_error_dashboard.rb +60 -0
  57. data/lib/tasks/rails_error_dashboard_tasks.rake +4 -0
  58. metadata +318 -0
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ # Base class for creating plugins
5
+ # Plugins can hook into error lifecycle events and extend functionality
6
+ #
7
+ # Example plugin:
8
+ #
9
+ # class MyNotificationPlugin < RailsErrorDashboard::Plugin
10
+ # def name
11
+ # "My Custom Notifier"
12
+ # end
13
+ #
14
+ # def on_error_logged(error_log)
15
+ # # Send notification to custom service
16
+ # MyService.notify(error_log)
17
+ # end
18
+ # end
19
+ #
20
+ # # Register the plugin
21
+ # RailsErrorDashboard.register_plugin(MyNotificationPlugin.new)
22
+ #
23
+ class Plugin
24
+ # Plugin name (must be implemented by subclass)
25
+ def name
26
+ raise NotImplementedError, "Plugin must implement #name"
27
+ end
28
+
29
+ # Plugin description (optional)
30
+ def description
31
+ "No description provided"
32
+ end
33
+
34
+ # Plugin version (optional)
35
+ def version
36
+ "1.0.0"
37
+ end
38
+
39
+ # Called when plugin is registered
40
+ # Use this for initialization logic
41
+ def on_register
42
+ # Override in subclass if needed
43
+ end
44
+
45
+ # Called when a new error is logged (first occurrence)
46
+ # @param error_log [ErrorLog] The newly created error log
47
+ def on_error_logged(error_log)
48
+ # Override in subclass to handle event
49
+ end
50
+
51
+ # Called when an existing error recurs (subsequent occurrences)
52
+ # @param error_log [ErrorLog] The updated error log
53
+ def on_error_recurred(error_log)
54
+ # Override in subclass to handle event
55
+ end
56
+
57
+ # Called when an error is resolved
58
+ # @param error_log [ErrorLog] The resolved error log
59
+ def on_error_resolved(error_log)
60
+ # Override in subclass to handle event
61
+ end
62
+
63
+ # Called when errors are batch resolved
64
+ # @param error_logs [Array<ErrorLog>] The resolved error logs
65
+ def on_errors_batch_resolved(error_logs)
66
+ # Override in subclass to handle event
67
+ end
68
+
69
+ # Called when errors are batch deleted
70
+ # @param error_ids [Array<Integer>] The IDs of deleted errors
71
+ def on_errors_batch_deleted(error_ids)
72
+ # Override in subclass to handle event
73
+ end
74
+
75
+ # Called when an error is viewed in the dashboard
76
+ # @param error_log [ErrorLog] The viewed error log
77
+ def on_error_viewed(error_log)
78
+ # Override in subclass to handle event
79
+ end
80
+
81
+ # Helper method to check if plugin is enabled
82
+ # Override this to add conditional logic
83
+ def enabled?
84
+ true
85
+ end
86
+
87
+ # Helper method to safely execute plugin hooks
88
+ # Prevents plugin errors from breaking the main application
89
+ def safe_execute(method_name, *args)
90
+ return unless enabled?
91
+
92
+ send(method_name, *args)
93
+ rescue => e
94
+ Rails.logger.error("Plugin '#{name}' failed in #{method_name}: #{e.message}")
95
+ Rails.logger.error(e.backtrace.first(5).join("\n"))
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ # Registry for managing plugins
5
+ # Provides plugin registration and event dispatching
6
+ class PluginRegistry
7
+ class << self
8
+ # Get all registered plugins
9
+ def plugins
10
+ @plugins ||= []
11
+ end
12
+
13
+ # Register a plugin
14
+ # @param plugin [Plugin] The plugin instance to register
15
+ def register(plugin)
16
+ unless plugin.is_a?(Plugin)
17
+ raise ArgumentError, "Plugin must be an instance of RailsErrorDashboard::Plugin"
18
+ end
19
+
20
+ if plugins.any? { |p| p.name == plugin.name }
21
+ Rails.logger.warn("Plugin '#{plugin.name}' is already registered, skipping")
22
+ return false
23
+ end
24
+
25
+ plugins << plugin
26
+ plugin.on_register
27
+ Rails.logger.info("Registered plugin: #{plugin.name} (#{plugin.version})")
28
+ true
29
+ end
30
+
31
+ # Unregister a plugin by name
32
+ # @param plugin_name [String] The name of the plugin to unregister
33
+ def unregister(plugin_name)
34
+ plugins.reject! { |p| p.name == plugin_name }
35
+ end
36
+
37
+ # Clear all plugins (useful for testing)
38
+ def clear
39
+ @plugins = []
40
+ end
41
+
42
+ # Get a plugin by name
43
+ # @param plugin_name [String] The name of the plugin
44
+ # @return [Plugin, nil] The plugin instance or nil if not found
45
+ def find(plugin_name)
46
+ plugins.find { |p| p.name == plugin_name }
47
+ end
48
+
49
+ # Dispatch an event to all registered plugins
50
+ # @param event_name [Symbol] The event name (e.g., :on_error_logged)
51
+ # @param args [Array] Arguments to pass to the event handler
52
+ def dispatch(event_name, *args)
53
+ plugins.each do |plugin|
54
+ next unless plugin.enabled?
55
+
56
+ plugin.safe_execute(event_name, *args)
57
+ end
58
+ end
59
+
60
+ # Get count of registered plugins
61
+ def count
62
+ plugins.size
63
+ end
64
+
65
+ # Check if any plugins are registered
66
+ def any?
67
+ plugins.any?
68
+ end
69
+
70
+ # Get list of plugin names
71
+ def names
72
+ plugins.map(&:name)
73
+ end
74
+
75
+ # Get plugin information for debugging
76
+ def info
77
+ plugins.map do |plugin|
78
+ {
79
+ name: plugin.name,
80
+ version: plugin.version,
81
+ description: plugin.description,
82
+ enabled: plugin.enabled?
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Plugins
5
+ # Example plugin: Audit logging
6
+ # Logs all error dashboard activities to a separate audit log
7
+ #
8
+ # Usage:
9
+ # RailsErrorDashboard.register_plugin(
10
+ # RailsErrorDashboard::Plugins::AuditLogPlugin.new(logger: Rails.logger)
11
+ # )
12
+ #
13
+ class AuditLogPlugin < Plugin
14
+ def initialize(logger: Rails.logger)
15
+ @logger = logger
16
+ end
17
+
18
+ def name
19
+ "Audit Logger"
20
+ end
21
+
22
+ def description
23
+ "Logs all error dashboard activities for compliance and auditing"
24
+ end
25
+
26
+ def version
27
+ "1.0.0"
28
+ end
29
+
30
+ def on_error_logged(error_log)
31
+ log_event(
32
+ event: "error_logged",
33
+ error_id: error_log.id,
34
+ error_type: error_log.error_type,
35
+ platform: error_log.platform,
36
+ environment: error_log.environment,
37
+ timestamp: Time.current
38
+ )
39
+ end
40
+
41
+ def on_error_recurred(error_log)
42
+ log_event(
43
+ event: "error_recurred",
44
+ error_id: error_log.id,
45
+ error_type: error_log.error_type,
46
+ occurrence_count: error_log.occurrence_count,
47
+ timestamp: Time.current
48
+ )
49
+ end
50
+
51
+ def on_error_resolved(error_log)
52
+ log_event(
53
+ event: "error_resolved",
54
+ error_id: error_log.id,
55
+ error_type: error_log.error_type,
56
+ resolved_by: error_log.resolved_by_name,
57
+ resolution_comment: error_log.resolution_comment,
58
+ timestamp: Time.current
59
+ )
60
+ end
61
+
62
+ def on_errors_batch_resolved(error_logs)
63
+ log_event(
64
+ event: "errors_batch_resolved",
65
+ count: error_logs.size,
66
+ error_ids: error_logs.map(&:id),
67
+ timestamp: Time.current
68
+ )
69
+ end
70
+
71
+ def on_errors_batch_deleted(error_ids)
72
+ log_event(
73
+ event: "errors_batch_deleted",
74
+ count: error_ids.size,
75
+ error_ids: error_ids,
76
+ timestamp: Time.current
77
+ )
78
+ end
79
+
80
+ def on_error_viewed(error_log)
81
+ log_event(
82
+ event: "error_viewed",
83
+ error_id: error_log.id,
84
+ error_type: error_log.error_type,
85
+ timestamp: Time.current
86
+ )
87
+ end
88
+
89
+ private
90
+
91
+ def log_event(data)
92
+ @logger.info("[RailsErrorDashboard Audit] #{data.to_json}")
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Plugins
5
+ # Example plugin: Jira integration
6
+ # Automatically creates Jira tickets for critical errors
7
+ #
8
+ # Usage:
9
+ # RailsErrorDashboard.register_plugin(
10
+ # RailsErrorDashboard::Plugins::JiraIntegrationPlugin.new(
11
+ # jira_url: ENV['JIRA_URL'],
12
+ # jira_username: ENV['JIRA_USERNAME'],
13
+ # jira_api_token: ENV['JIRA_API_TOKEN'],
14
+ # jira_project_key: ENV['JIRA_PROJECT_KEY'],
15
+ # only_critical: true
16
+ # )
17
+ # )
18
+ #
19
+ class JiraIntegrationPlugin < Plugin
20
+ def initialize(jira_url: nil, jira_username: nil, jira_api_token: nil, jira_project_key: nil, only_critical: true)
21
+ @jira_url = jira_url
22
+ @jira_username = jira_username
23
+ @jira_api_token = jira_api_token
24
+ @jira_project_key = jira_project_key
25
+ @only_critical = only_critical
26
+ end
27
+
28
+ def name
29
+ "Jira Integration"
30
+ end
31
+
32
+ def description
33
+ "Automatically creates Jira tickets for critical errors"
34
+ end
35
+
36
+ def version
37
+ "1.0.0"
38
+ end
39
+
40
+ def enabled?
41
+ @jira_url.present? && @jira_username.present? && @jira_api_token.present? && @jira_project_key.present?
42
+ end
43
+
44
+ def on_error_logged(error_log)
45
+ return if @only_critical && !error_log.critical?
46
+
47
+ create_jira_ticket(error_log)
48
+ end
49
+
50
+ private
51
+
52
+ def create_jira_ticket(error_log)
53
+ # Example Jira ticket creation
54
+ # In production, you'd use the jira-ruby gem or make API calls directly
55
+
56
+ ticket_data = {
57
+ project: { key: @jira_project_key },
58
+ summary: "[#{error_log.environment}] #{error_log.error_type}",
59
+ description: build_description(error_log),
60
+ issuetype: { name: "Bug" },
61
+ priority: { name: jira_priority(error_log) },
62
+ labels: [ "rails-error-dashboard", error_log.platform, error_log.environment ]
63
+ }
64
+
65
+ Rails.logger.info("Would create Jira ticket: #{ticket_data.to_json}")
66
+
67
+ # Actual implementation:
68
+ # require 'httparty'
69
+ # response = HTTParty.post(
70
+ # "#{@jira_url}/rest/api/2/issue",
71
+ # basic_auth: { username: @jira_username, password: @jira_api_token },
72
+ # headers: { 'Content-Type' => 'application/json' },
73
+ # body: { fields: ticket_data }.to_json
74
+ # )
75
+ end
76
+
77
+ def build_description(error_log)
78
+ <<~DESC
79
+ h2. Error Details
80
+
81
+ *Error Type:* #{error_log.error_type}
82
+ *Message:* #{error_log.message}
83
+ *Platform:* #{error_log.platform}
84
+ *Environment:* #{error_log.environment}
85
+ *Severity:* #{error_log.severity}
86
+ *Controller:* #{error_log.controller_name}
87
+ *Action:* #{error_log.action_name}
88
+ *First Seen:* #{error_log.first_seen_at}
89
+ *Occurrences:* #{error_log.occurrence_count}
90
+
91
+ h2. Backtrace
92
+
93
+ {code}
94
+ #{error_log.backtrace&.lines&.first(10)&.join}
95
+ {code}
96
+
97
+ h2. Dashboard Link
98
+
99
+ [View in Dashboard|#{dashboard_url(error_log)}]
100
+ DESC
101
+ end
102
+
103
+ def jira_priority(error_log)
104
+ case error_log.severity.to_s
105
+ when "critical"
106
+ "Highest"
107
+ when "high"
108
+ "High"
109
+ when "medium"
110
+ "Medium"
111
+ else
112
+ "Low"
113
+ end
114
+ end
115
+
116
+ def dashboard_url(error_log)
117
+ base_url = RailsErrorDashboard.configuration.dashboard_base_url || "http://localhost:3000"
118
+ "#{base_url}/error_dashboard/errors/#{error_log.id}"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Plugins
5
+ # Example plugin: Metrics tracking
6
+ # Tracks error counts and sends to metrics service (e.g., StatsD, Datadog)
7
+ #
8
+ # Usage:
9
+ # RailsErrorDashboard.register_plugin(
10
+ # RailsErrorDashboard::Plugins::MetricsPlugin.new
11
+ # )
12
+ #
13
+ class MetricsPlugin < Plugin
14
+ def name
15
+ "Metrics Tracker"
16
+ end
17
+
18
+ def description
19
+ "Tracks error metrics and sends to monitoring service"
20
+ end
21
+
22
+ def version
23
+ "1.0.0"
24
+ end
25
+
26
+ def on_error_logged(error_log)
27
+ increment_counter("errors.new", error_log)
28
+ increment_counter("errors.by_type.#{sanitize_metric_name(error_log.error_type)}", error_log)
29
+ increment_counter("errors.by_platform.#{error_log.platform || 'unknown'}", error_log)
30
+ increment_counter("errors.by_environment.#{error_log.environment}", error_log)
31
+ end
32
+
33
+ def on_error_recurred(error_log)
34
+ increment_counter("errors.recurred", error_log)
35
+ increment_counter("errors.occurrence.#{error_log.id}", error_log)
36
+ end
37
+
38
+ def on_error_resolved(error_log)
39
+ increment_counter("errors.resolved", error_log)
40
+ end
41
+
42
+ def on_errors_batch_resolved(error_logs)
43
+ increment_counter("errors.batch_resolved", count: error_logs.size)
44
+ end
45
+
46
+ def on_errors_batch_deleted(error_ids)
47
+ increment_counter("errors.batch_deleted", count: error_ids.size)
48
+ end
49
+
50
+ private
51
+
52
+ def increment_counter(metric_name, data)
53
+ # Example: Send to StatsD
54
+ # StatsD.increment(metric_name, tags: metric_tags(data))
55
+
56
+ # Example: Send to Datadog
57
+ # Datadog::Statsd.increment(metric_name, tags: metric_tags(data))
58
+
59
+ # For demonstration, just log
60
+ Rails.logger.info("Metrics: #{metric_name} - #{data.is_a?(Hash) ? data : data.class.name}")
61
+ end
62
+
63
+ def metric_tags(data)
64
+ return [] unless data.respond_to?(:platform)
65
+
66
+ [
67
+ "platform:#{data.platform || 'unknown'}",
68
+ "environment:#{data.environment}",
69
+ "severity:#{data.severity}"
70
+ ]
71
+ end
72
+
73
+ def sanitize_metric_name(name)
74
+ name.gsub("::", ".").downcase
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Fetch analytics statistics for charts and trends
6
+ # This is a read operation that aggregates error data over time
7
+ class AnalyticsStats
8
+ def self.call(days = 30)
9
+ new(days).call
10
+ end
11
+
12
+ def initialize(days = 30)
13
+ @days = days
14
+ @start_date = days.days.ago
15
+ end
16
+
17
+ def call
18
+ {
19
+ days: @days,
20
+ error_stats: error_statistics,
21
+ errors_over_time: errors_over_time,
22
+ errors_by_type: errors_by_type,
23
+ errors_by_platform: errors_by_platform,
24
+ errors_by_environment: errors_by_environment,
25
+ errors_by_hour: errors_by_hour,
26
+ top_users: top_affected_users,
27
+ resolution_rate: resolution_rate,
28
+ mobile_errors: mobile_errors_count,
29
+ api_errors: api_errors_count
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def base_query
36
+ ErrorLog.where("occurred_at >= ?", @start_date)
37
+ end
38
+
39
+ def error_statistics
40
+ {
41
+ total: base_query.count,
42
+ unresolved: base_query.unresolved.count,
43
+ by_type: base_query.group(:error_type).count.sort_by { |_, count| -count }.to_h,
44
+ by_day: base_query.group("DATE(occurred_at)").count
45
+ }
46
+ end
47
+
48
+ def errors_over_time
49
+ base_query.group_by_day(:occurred_at).count
50
+ end
51
+
52
+ def errors_by_type
53
+ base_query.group(:error_type)
54
+ .count
55
+ .sort_by { |_, count| -count }
56
+ .first(10)
57
+ .to_h
58
+ end
59
+
60
+ def errors_by_platform
61
+ base_query.group(:platform).count
62
+ end
63
+
64
+ def errors_by_environment
65
+ base_query.group(:environment).count
66
+ end
67
+
68
+ def errors_by_hour
69
+ base_query.group_by_hour(:occurred_at).count
70
+ end
71
+
72
+ def top_affected_users
73
+ user_model = RailsErrorDashboard.configuration.user_model
74
+
75
+ base_query.where.not(user_id: nil)
76
+ .group(:user_id)
77
+ .count
78
+ .sort_by { |_, count| -count }
79
+ .first(10)
80
+ .map { |user_id, count| [ find_user_email(user_id, user_model), count ] }
81
+ .to_h
82
+ end
83
+
84
+ def find_user_email(user_id, user_model)
85
+ user = user_model.constantize.find_by(id: user_id)
86
+ user&.email || "User ##{user_id}"
87
+ rescue
88
+ "User ##{user_id}"
89
+ end
90
+
91
+ def resolution_rate
92
+ total = error_statistics[:total]
93
+ return 0 if total.zero?
94
+
95
+ resolved_count = ErrorLog.resolved.where("occurred_at >= ?", @start_date).count
96
+ ((resolved_count.to_f / total) * 100).round(1)
97
+ end
98
+
99
+ def mobile_errors_count
100
+ base_query.where(platform: [ "iOS", "Android" ]).count
101
+ end
102
+
103
+ def api_errors_count
104
+ base_query.where("platform IS NULL OR platform = ?", "API").count
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Fetch dashboard statistics
6
+ # This is a read operation that aggregates error data for the dashboard
7
+ class DashboardStats
8
+ def self.call
9
+ new.call
10
+ end
11
+
12
+ def call
13
+ {
14
+ total_today: ErrorLog.where("occurred_at >= ?", Time.current.beginning_of_day).count,
15
+ total_week: ErrorLog.where("occurred_at >= ?", 7.days.ago).count,
16
+ total_month: ErrorLog.where("occurred_at >= ?", 30.days.ago).count,
17
+ unresolved: ErrorLog.unresolved.count,
18
+ resolved: ErrorLog.resolved.count,
19
+ by_environment: ErrorLog.group(:environment).count,
20
+ by_platform: ErrorLog.group(:platform).count,
21
+ top_errors: top_errors
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ def top_errors
28
+ ErrorLog.where("occurred_at >= ?", 7.days.ago)
29
+ .group(:error_type)
30
+ .count
31
+ .sort_by { |_, count| -count }
32
+ .first(10)
33
+ .to_h
34
+ end
35
+ end
36
+ end
37
+ end