rails_error_dashboard 0.1.21 → 0.1.22
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 +180 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +7 -11
- data/app/models/rails_error_dashboard/application.rb +30 -0
- data/app/models/rails_error_dashboard/error_log.rb +36 -7
- data/app/views/layouts/rails_error_dashboard.html.erb +77 -7
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +7 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +26 -1
- data/app/views/rails_error_dashboard/errors/settings.html.erb +4 -10
- data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +13 -0
- data/db/migrate/20260106094233_add_application_to_error_logs.rb +24 -0
- data/db/migrate/20260106094256_backfill_application_for_existing_errors.rb +23 -0
- data/db/migrate/20260106094318_finalize_application_foreign_key.rb +17 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +2 -7
- data/lib/rails_error_dashboard/commands/log_error.rb +26 -10
- data/lib/rails_error_dashboard/configuration.rb +10 -6
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +16 -7
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +59 -47
- data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
- data/lib/rails_error_dashboard/queries/filter_options.rb +2 -1
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/tasks/error_dashboard.rake +272 -0
- metadata +8 -2
|
@@ -2,20 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
RailsErrorDashboard.configure do |config|
|
|
4
4
|
# ============================================================================
|
|
5
|
-
# AUTHENTICATION (Always Required)
|
|
5
|
+
# AUTHENTICATION (Always Required - Cannot Be Disabled)
|
|
6
6
|
# ============================================================================
|
|
7
7
|
|
|
8
8
|
# Dashboard authentication credentials
|
|
9
9
|
# ⚠️ CHANGE THESE BEFORE PRODUCTION! ⚠️
|
|
10
|
+
# Authentication is ALWAYS enforced in ALL environments (production, development, test)
|
|
10
11
|
config.dashboard_username = ENV.fetch("ERROR_DASHBOARD_USER", "gandalf")
|
|
11
12
|
config.dashboard_password = ENV.fetch("ERROR_DASHBOARD_PASSWORD", "youshallnotpass")
|
|
12
13
|
|
|
13
|
-
# Require authentication for dashboard access
|
|
14
|
-
config.require_authentication = true
|
|
15
|
-
|
|
16
|
-
# Require authentication even in development mode
|
|
17
|
-
config.require_authentication_in_development = false
|
|
18
|
-
|
|
19
14
|
# ============================================================================
|
|
20
15
|
# CORE FEATURES (Always Enabled)
|
|
21
16
|
# ============================================================================
|
|
@@ -46,9 +46,13 @@ module RailsErrorDashboard
|
|
|
46
46
|
|
|
47
47
|
error_context = ValueObjects::ErrorContext.new(@context, @context[:source])
|
|
48
48
|
|
|
49
|
+
# Find or create application (cached lookup)
|
|
50
|
+
application = find_or_create_application
|
|
51
|
+
|
|
49
52
|
# Build error attributes
|
|
50
53
|
truncated_backtrace = truncate_backtrace(@exception.backtrace)
|
|
51
54
|
attributes = {
|
|
55
|
+
application_id: application.id,
|
|
52
56
|
error_type: @exception.class.name,
|
|
53
57
|
message: @exception.message,
|
|
54
58
|
backtrace: truncated_backtrace,
|
|
@@ -63,8 +67,8 @@ module RailsErrorDashboard
|
|
|
63
67
|
occurred_at: Time.current
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
# Generate error hash for deduplication (including controller/action context)
|
|
67
|
-
error_hash = generate_error_hash(@exception, error_context.controller_name, error_context.action_name)
|
|
70
|
+
# Generate error hash for deduplication (including controller/action context and application)
|
|
71
|
+
error_hash = generate_error_hash(@exception, error_context.controller_name, error_context.action_name, application.id)
|
|
68
72
|
|
|
69
73
|
# Calculate backtrace signature for fuzzy matching (if column exists)
|
|
70
74
|
if ErrorLog.column_names.include?("backtrace_signature")
|
|
@@ -127,10 +131,6 @@ module RailsErrorDashboard
|
|
|
127
131
|
rescue => e
|
|
128
132
|
# Don't let error logging cause more errors - fail silently
|
|
129
133
|
# CRITICAL: Log but never propagate exception
|
|
130
|
-
# Log to Rails logger for visibility during development
|
|
131
|
-
Rails.logger.error("[RailsErrorDashboard] LogError command failed: #{e.class} - #{e.message}")
|
|
132
|
-
Rails.logger.error("Backtrace: #{e.backtrace&.first(10)&.join("\n")}")
|
|
133
|
-
|
|
134
134
|
RailsErrorDashboard::Logger.error("[RailsErrorDashboard] LogError command failed: #{e.class} - #{e.message}")
|
|
135
135
|
RailsErrorDashboard::Logger.error("Original exception: #{@exception.class} - #{@exception.message}") if @exception
|
|
136
136
|
RailsErrorDashboard::Logger.error("Context: #{@context.inspect}") if @context
|
|
@@ -140,6 +140,20 @@ module RailsErrorDashboard
|
|
|
140
140
|
|
|
141
141
|
private
|
|
142
142
|
|
|
143
|
+
# Find or create application for multi-app support
|
|
144
|
+
def find_or_create_application
|
|
145
|
+
app_name = RailsErrorDashboard.configuration.application_name ||
|
|
146
|
+
ENV['APPLICATION_NAME'] ||
|
|
147
|
+
(defined?(Rails) && Rails.application.class.module_parent_name) ||
|
|
148
|
+
'Rails Application'
|
|
149
|
+
|
|
150
|
+
Application.find_or_create_by_name(app_name)
|
|
151
|
+
rescue => e
|
|
152
|
+
RailsErrorDashboard::Logger.error("[RailsErrorDashboard] Failed to find/create application: #{e.message}")
|
|
153
|
+
# Fallback: try to find any application or create default
|
|
154
|
+
Application.first || Application.create!(name: 'Default Application')
|
|
155
|
+
end
|
|
156
|
+
|
|
143
157
|
# Trigger notification callbacks for error logging
|
|
144
158
|
def trigger_callbacks(error_log)
|
|
145
159
|
# Trigger general error_logged callbacks
|
|
@@ -237,13 +251,14 @@ module RailsErrorDashboard
|
|
|
237
251
|
# Generate consistent hash for error deduplication
|
|
238
252
|
# Same hash = same error type
|
|
239
253
|
# Note: This is also defined in ErrorLog model for backward compatibility
|
|
240
|
-
def generate_error_hash(exception, controller_name = nil, action_name = nil)
|
|
254
|
+
def generate_error_hash(exception, controller_name = nil, action_name = nil, application_id = nil)
|
|
241
255
|
# Hash components:
|
|
242
256
|
# 1. Error class (NoMethodError, ArgumentError, etc.)
|
|
243
257
|
# 2. Normalized message (replace numbers, quoted strings)
|
|
244
258
|
# 3. First stack frame file (ignore line numbers)
|
|
245
259
|
# 4. Controller name (for context-aware grouping)
|
|
246
260
|
# 5. Action name (for context-aware grouping)
|
|
261
|
+
# 6. Application ID (for per-app deduplication)
|
|
247
262
|
|
|
248
263
|
normalized_message = exception.message
|
|
249
264
|
&.gsub(/\d+/, "N") # Replace numbers: "User 123" -> "User N"
|
|
@@ -261,13 +276,14 @@ module RailsErrorDashboard
|
|
|
261
276
|
# Extract just the file path, not line number
|
|
262
277
|
file_path = first_app_frame&.split(":")&.first
|
|
263
278
|
|
|
264
|
-
# Generate hash including controller/action for better grouping
|
|
279
|
+
# Generate hash including controller/action/application for better grouping
|
|
265
280
|
digest_input = [
|
|
266
281
|
exception.class.name,
|
|
267
282
|
normalized_message,
|
|
268
283
|
file_path,
|
|
269
|
-
controller_name,
|
|
270
|
-
action_name
|
|
284
|
+
controller_name, # Context: which controller
|
|
285
|
+
action_name, # Context: which action
|
|
286
|
+
application_id.to_s # Context: which application (for per-app deduplication)
|
|
271
287
|
].compact.join("|")
|
|
272
288
|
|
|
273
289
|
Digest::SHA256.hexdigest(digest_input)[0..15]
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module RailsErrorDashboard
|
|
4
4
|
class Configuration
|
|
5
|
-
# Dashboard authentication
|
|
5
|
+
# Dashboard authentication (always required)
|
|
6
6
|
attr_accessor :dashboard_username
|
|
7
7
|
attr_accessor :dashboard_password
|
|
8
|
-
attr_accessor :require_authentication
|
|
9
|
-
attr_accessor :require_authentication_in_development
|
|
10
8
|
|
|
11
9
|
# User model (for associations)
|
|
12
10
|
attr_accessor :user_model
|
|
13
11
|
|
|
12
|
+
# Multi-app support - Application name
|
|
13
|
+
attr_accessor :application_name
|
|
14
|
+
attr_accessor :database # Database connection name for shared error dashboard DB
|
|
15
|
+
|
|
14
16
|
# Notifications
|
|
15
17
|
attr_accessor :slack_webhook_url
|
|
16
18
|
attr_accessor :notification_email_recipients
|
|
@@ -94,14 +96,16 @@ module RailsErrorDashboard
|
|
|
94
96
|
attr_accessor :log_level
|
|
95
97
|
|
|
96
98
|
def initialize
|
|
97
|
-
# Default values
|
|
99
|
+
# Default values - Authentication is ALWAYS required
|
|
98
100
|
@dashboard_username = ENV.fetch("ERROR_DASHBOARD_USER", "gandalf")
|
|
99
101
|
@dashboard_password = ENV.fetch("ERROR_DASHBOARD_PASSWORD", "youshallnotpass")
|
|
100
|
-
@require_authentication = true
|
|
101
|
-
@require_authentication_in_development = false
|
|
102
102
|
|
|
103
103
|
@user_model = "User"
|
|
104
104
|
|
|
105
|
+
# Multi-app support defaults
|
|
106
|
+
@application_name = ENV["APPLICATION_NAME"] # Auto-detected if not set
|
|
107
|
+
@database = nil # Use primary database by default
|
|
108
|
+
|
|
105
109
|
# Notification settings (disabled by default - enable during installation or in initializer)
|
|
106
110
|
@slack_webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
|
107
111
|
@notification_email_recipients = ENV.fetch("ERROR_NOTIFICATION_EMAILS", "").split(",").map(&:strip)
|
|
@@ -5,15 +5,16 @@ module RailsErrorDashboard
|
|
|
5
5
|
# Query: Fetch analytics statistics for charts and trends
|
|
6
6
|
# This is a read operation that aggregates error data over time
|
|
7
7
|
class AnalyticsStats
|
|
8
|
-
def
|
|
9
|
-
new(days).call
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def initialize(days = 30)
|
|
8
|
+
def initialize(days = 30, application_id: nil)
|
|
13
9
|
@days = days
|
|
10
|
+
@application_id = application_id
|
|
14
11
|
@start_date = days.days.ago
|
|
15
12
|
end
|
|
16
13
|
|
|
14
|
+
def self.call(days = 30, application_id: nil)
|
|
15
|
+
new(days, application_id: application_id).call
|
|
16
|
+
end
|
|
17
|
+
|
|
17
18
|
def call
|
|
18
19
|
# Cache analytics data for 5 minutes to reduce database load
|
|
19
20
|
# Cache key includes days parameter and last error update timestamp
|
|
@@ -38,20 +39,28 @@ module RailsErrorDashboard
|
|
|
38
39
|
# Cache key includes:
|
|
39
40
|
# - Query class name
|
|
40
41
|
# - Days parameter (different time ranges = different caches)
|
|
42
|
+
# - Application ID (per-app caching)
|
|
41
43
|
# - Last error update timestamp (auto-invalidates when errors change)
|
|
42
44
|
# - Start date (ensures correct time window)
|
|
43
45
|
[
|
|
44
46
|
"analytics_stats",
|
|
45
47
|
@days,
|
|
46
|
-
|
|
48
|
+
@application_id || "all",
|
|
49
|
+
base_scope.maximum(:updated_at)&.to_i || 0,
|
|
47
50
|
@start_date.to_date.to_s
|
|
48
51
|
].join("/")
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
private
|
|
52
55
|
|
|
56
|
+
def base_scope
|
|
57
|
+
scope = ErrorLog.all
|
|
58
|
+
scope = scope.where(application_id: @application_id) if @application_id.present?
|
|
59
|
+
scope
|
|
60
|
+
end
|
|
61
|
+
|
|
53
62
|
def base_query
|
|
54
|
-
|
|
63
|
+
base_scope.where("occurred_at >= ?", @start_date)
|
|
55
64
|
end
|
|
56
65
|
|
|
57
66
|
def error_statistics
|
|
@@ -5,8 +5,12 @@ module RailsErrorDashboard
|
|
|
5
5
|
# Query: Fetch dashboard statistics
|
|
6
6
|
# This is a read operation that aggregates error data for the dashboard
|
|
7
7
|
class DashboardStats
|
|
8
|
-
def
|
|
9
|
-
|
|
8
|
+
def initialize(application_id: nil)
|
|
9
|
+
@application_id = application_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.call(application_id: nil)
|
|
13
|
+
new(application_id: application_id).call
|
|
10
14
|
end
|
|
11
15
|
|
|
12
16
|
def call
|
|
@@ -15,12 +19,12 @@ module RailsErrorDashboard
|
|
|
15
19
|
begin
|
|
16
20
|
Rails.cache.fetch(cache_key, expires_in: 1.minute) do
|
|
17
21
|
{
|
|
18
|
-
total_today:
|
|
19
|
-
total_week:
|
|
20
|
-
total_month:
|
|
21
|
-
unresolved:
|
|
22
|
-
resolved:
|
|
23
|
-
by_platform:
|
|
22
|
+
total_today: base_scope.where("occurred_at >= ?", Time.current.beginning_of_day).count,
|
|
23
|
+
total_week: base_scope.where("occurred_at >= ?", 7.days.ago).count,
|
|
24
|
+
total_month: base_scope.where("occurred_at >= ?", 30.days.ago).count,
|
|
25
|
+
unresolved: base_scope.unresolved.count,
|
|
26
|
+
resolved: base_scope.resolved.count,
|
|
27
|
+
by_platform: base_scope.group(:platform).count,
|
|
24
28
|
top_errors: top_errors,
|
|
25
29
|
# Trend visualizations
|
|
26
30
|
errors_trend_7d: errors_trend_7d,
|
|
@@ -40,8 +44,8 @@ module RailsErrorDashboard
|
|
|
40
44
|
rescue => e
|
|
41
45
|
# If Rails.cache or any stats query fails, return empty stats hash
|
|
42
46
|
# This prevents broadcast failures in API-only mode or when cache is unavailable
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
RailsErrorDashboard::Logger.error("[RailsErrorDashboard] DashboardStats failed: #{e.class} - #{e.message}")
|
|
48
|
+
RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] Backtrace: #{e.backtrace&.first(3)&.join("\n")}")
|
|
45
49
|
|
|
46
50
|
# Return minimal stats hash to prevent nil errors in views
|
|
47
51
|
{
|
|
@@ -70,41 +74,49 @@ module RailsErrorDashboard
|
|
|
70
74
|
def cache_key
|
|
71
75
|
# Cache key includes last error update timestamp for auto-invalidation
|
|
72
76
|
# Also includes current hour to ensure fresh data
|
|
77
|
+
# Uses base_scope to respect application_id filter for proper cache isolation
|
|
73
78
|
[
|
|
74
79
|
"dashboard_stats",
|
|
75
|
-
|
|
80
|
+
@application_id || "all",
|
|
81
|
+
base_scope.maximum(:updated_at)&.to_i || 0,
|
|
76
82
|
Time.current.hour
|
|
77
83
|
].join("/")
|
|
78
84
|
end
|
|
79
85
|
|
|
80
86
|
private
|
|
81
87
|
|
|
88
|
+
def base_scope
|
|
89
|
+
scope = ErrorLog.all
|
|
90
|
+
scope = scope.where(application_id: @application_id) if @application_id.present?
|
|
91
|
+
scope
|
|
92
|
+
end
|
|
93
|
+
|
|
82
94
|
def top_errors
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
base_scope.where("occurred_at >= ?", 7.days.ago)
|
|
96
|
+
.group(:error_type)
|
|
97
|
+
.count
|
|
98
|
+
.sort_by { |_, count| -count }
|
|
99
|
+
.first(10)
|
|
100
|
+
.to_h
|
|
89
101
|
end
|
|
90
102
|
|
|
91
103
|
# Get 7-day error trend (daily counts)
|
|
92
104
|
def errors_trend_7d
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
base_scope.where("occurred_at >= ?", 7.days.ago)
|
|
106
|
+
.group_by_day(:occurred_at, range: 7.days.ago.to_date..Date.current, default_value: 0)
|
|
107
|
+
.count
|
|
96
108
|
end
|
|
97
109
|
|
|
98
110
|
# Get error counts by severity for last 7 days
|
|
99
111
|
# OPTIMIZED: Use database filtering instead of loading all records into Ruby
|
|
100
112
|
def errors_by_severity_7d
|
|
101
|
-
|
|
113
|
+
scoped_errors = base_scope.where("occurred_at >= ?", 7.days.ago)
|
|
102
114
|
|
|
103
115
|
{
|
|
104
|
-
critical:
|
|
105
|
-
high:
|
|
106
|
-
medium:
|
|
107
|
-
low:
|
|
116
|
+
critical: scoped_errors.where(error_type: ErrorLog::CRITICAL_ERROR_TYPES).count,
|
|
117
|
+
high: scoped_errors.where(error_type: ErrorLog::HIGH_SEVERITY_ERROR_TYPES).count,
|
|
118
|
+
medium: scoped_errors.where(error_type: ErrorLog::MEDIUM_SEVERITY_ERROR_TYPES).count,
|
|
119
|
+
low: scoped_errors.where.not(
|
|
108
120
|
error_type: ErrorLog::CRITICAL_ERROR_TYPES +
|
|
109
121
|
ErrorLog::HIGH_SEVERITY_ERROR_TYPES +
|
|
110
122
|
ErrorLog::MEDIUM_SEVERITY_ERROR_TYPES
|
|
@@ -117,7 +129,7 @@ module RailsErrorDashboard
|
|
|
117
129
|
def spike_detected?
|
|
118
130
|
return false if errors_trend_7d.empty?
|
|
119
131
|
|
|
120
|
-
today_count =
|
|
132
|
+
today_count = base_scope.where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
121
133
|
|
|
122
134
|
# Try baseline-based detection first
|
|
123
135
|
if baseline_anomaly_detected?(today_count)
|
|
@@ -136,7 +148,7 @@ module RailsErrorDashboard
|
|
|
136
148
|
def spike_info
|
|
137
149
|
return nil unless spike_detected?
|
|
138
150
|
|
|
139
|
-
today_count =
|
|
151
|
+
today_count = base_scope.where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
140
152
|
avg_count = (errors_trend_7d.values.sum / 7.0).round(1)
|
|
141
153
|
|
|
142
154
|
info = {
|
|
@@ -158,9 +170,9 @@ module RailsErrorDashboard
|
|
|
158
170
|
return false unless defined?(Queries::BaselineStats)
|
|
159
171
|
|
|
160
172
|
# Check most common error types for anomalies
|
|
161
|
-
|
|
173
|
+
base_scope.distinct.pluck(:error_type, :platform).compact.any? do |(error_type, platform)|
|
|
162
174
|
stats = Queries::BaselineStats.new(error_type, platform)
|
|
163
|
-
error_count =
|
|
175
|
+
error_count = base_scope.where(
|
|
164
176
|
error_type: error_type,
|
|
165
177
|
platform: platform
|
|
166
178
|
).where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
@@ -175,9 +187,9 @@ module RailsErrorDashboard
|
|
|
175
187
|
return nil unless defined?(Queries::BaselineStats)
|
|
176
188
|
|
|
177
189
|
# Find the most anomalous error type
|
|
178
|
-
anomalies =
|
|
190
|
+
anomalies = base_scope.distinct.pluck(:error_type, :platform).compact.map do |(error_type, platform)|
|
|
179
191
|
stats = Queries::BaselineStats.new(error_type, platform)
|
|
180
|
-
error_count =
|
|
192
|
+
error_count = base_scope.where(
|
|
181
193
|
error_type: error_type,
|
|
182
194
|
platform: platform
|
|
183
195
|
).where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
@@ -225,7 +237,7 @@ module RailsErrorDashboard
|
|
|
225
237
|
# Since we don't track total requests, we'll use error count as proxy
|
|
226
238
|
# In the future, this could be: (errors / total_requests) * 100
|
|
227
239
|
def error_rate
|
|
228
|
-
today_errors =
|
|
240
|
+
today_errors = base_scope.where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
229
241
|
return 0.0 if today_errors.zero?
|
|
230
242
|
|
|
231
243
|
# For now, use a simple heuristic: errors per hour today
|
|
@@ -243,20 +255,20 @@ module RailsErrorDashboard
|
|
|
243
255
|
|
|
244
256
|
# Count distinct users affected by errors today
|
|
245
257
|
def affected_users_today
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
258
|
+
base_scope.where("occurred_at >= ?", Time.current.beginning_of_day)
|
|
259
|
+
.where.not(user_id: nil)
|
|
260
|
+
.distinct
|
|
261
|
+
.count(:user_id)
|
|
250
262
|
end
|
|
251
263
|
|
|
252
264
|
# Count distinct users affected by errors yesterday
|
|
253
265
|
def affected_users_yesterday
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
base_scope.where("occurred_at >= ? AND occurred_at < ?",
|
|
267
|
+
1.day.ago.beginning_of_day,
|
|
268
|
+
Time.current.beginning_of_day)
|
|
269
|
+
.where.not(user_id: nil)
|
|
270
|
+
.distinct
|
|
271
|
+
.count(:user_id)
|
|
260
272
|
end
|
|
261
273
|
|
|
262
274
|
# Calculate change in affected users (today vs yesterday)
|
|
@@ -272,10 +284,10 @@ module RailsErrorDashboard
|
|
|
272
284
|
|
|
273
285
|
# Calculate percentage change in errors (today vs yesterday)
|
|
274
286
|
def trend_percentage
|
|
275
|
-
today =
|
|
276
|
-
yesterday =
|
|
277
|
-
|
|
278
|
-
|
|
287
|
+
today = base_scope.where("occurred_at >= ?", Time.current.beginning_of_day).count
|
|
288
|
+
yesterday = base_scope.where("occurred_at >= ? AND occurred_at < ?",
|
|
289
|
+
1.day.ago.beginning_of_day,
|
|
290
|
+
Time.current.beginning_of_day).count
|
|
279
291
|
|
|
280
292
|
return 0.0 if today.zero? && yesterday.zero?
|
|
281
293
|
return 100.0 if yesterday.zero? && today.positive?
|
|
@@ -299,7 +311,7 @@ module RailsErrorDashboard
|
|
|
299
311
|
# Get top 5 errors ranked by impact score
|
|
300
312
|
# Impact = affected_users_count × occurrence_count
|
|
301
313
|
def top_errors_by_impact
|
|
302
|
-
|
|
314
|
+
base_scope.where("occurred_at >= ?", 7.days.ago)
|
|
303
315
|
.group(:error_type, :id)
|
|
304
316
|
.select("error_type, id, occurrence_count,
|
|
305
317
|
COUNT(DISTINCT user_id) as affected_users,
|
|
@@ -28,6 +28,7 @@ module RailsErrorDashboard
|
|
|
28
28
|
query = filter_by_error_type(query)
|
|
29
29
|
query = filter_by_resolved(query)
|
|
30
30
|
query = filter_by_platform(query)
|
|
31
|
+
query = filter_by_application(query)
|
|
31
32
|
query = filter_by_search(query)
|
|
32
33
|
query = filter_by_severity(query)
|
|
33
34
|
query = filter_by_timeframe(query)
|
|
@@ -74,6 +75,13 @@ module RailsErrorDashboard
|
|
|
74
75
|
query.where(platform: @filters[:platform])
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
def filter_by_application(query)
|
|
79
|
+
return query unless @filters[:application_id].present?
|
|
80
|
+
|
|
81
|
+
# ActiveRecord handles both single values and arrays automatically
|
|
82
|
+
query.where(application_id: @filters[:application_id])
|
|
83
|
+
end
|
|
84
|
+
|
|
77
85
|
def filter_by_search(query)
|
|
78
86
|
return query unless @filters[:search].present?
|
|
79
87
|
|
|
@@ -12,7 +12,8 @@ module RailsErrorDashboard
|
|
|
12
12
|
def call
|
|
13
13
|
{
|
|
14
14
|
error_types: ErrorLog.distinct.pluck(:error_type).compact.sort,
|
|
15
|
-
platforms: ErrorLog.distinct.pluck(:platform).compact
|
|
15
|
+
platforms: ErrorLog.distinct.pluck(:platform).compact,
|
|
16
|
+
applications: Application.ordered_by_name.pluck(:name, :id)
|
|
16
17
|
}
|
|
17
18
|
end
|
|
18
19
|
end
|