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.
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :error_dashboard do
4
+ desc "List all registered applications with error counts"
5
+ task list_applications: :environment do
6
+ puts "\n" + "=" * 80
7
+ puts "RAILS ERROR DASHBOARD - REGISTERED APPLICATIONS"
8
+ puts "=" * 80
9
+
10
+ # Use single SQL query with aggregates to avoid N+1 queries
11
+ # This fetches all app data + error counts in one query instead of 6N queries
12
+ apps = RailsErrorDashboard::Application
13
+ .select('rails_error_dashboard_applications.*')
14
+ .select('COUNT(rails_error_dashboard_error_logs.id) as total_errors')
15
+ .select('COALESCE(SUM(CASE WHEN NOT rails_error_dashboard_error_logs.resolved THEN 1 ELSE 0 END), 0) as unresolved_errors')
16
+ .joins('LEFT JOIN rails_error_dashboard_error_logs ON rails_error_dashboard_error_logs.application_id = rails_error_dashboard_applications.id')
17
+ .group('rails_error_dashboard_applications.id')
18
+ .order(:name)
19
+
20
+ if apps.empty?
21
+ puts "\nNo applications registered yet."
22
+ puts "Applications are auto-registered when they log their first error."
23
+ puts "\n" + "=" * 80 + "\n"
24
+ next
25
+ end
26
+
27
+ puts "\n#{apps.count} application(s) registered:\n\n"
28
+
29
+ # Calculate column widths using aggregated data (no additional queries)
30
+ name_width = [apps.map(&:name).map(&:length).max, 20].max
31
+ total_width = [apps.map(&:total_errors).map(&:to_s).map(&:length).max, 5].max
32
+ unresolved_width = [apps.map(&:unresolved_errors).map(&:to_s).map(&:length).max, 10].max
33
+
34
+ # Print header
35
+ printf "%-#{name_width}s %#{total_width}s %#{unresolved_width}s %s\n",
36
+ "APPLICATION", "TOTAL", "UNRESOLVED", "CREATED"
37
+ puts "-" * 80
38
+
39
+ # Print each application (total_errors and unresolved_errors are already loaded as attributes)
40
+ apps.each do |app|
41
+ printf "%-#{name_width}s %#{total_width}d %#{unresolved_width}d %s\n",
42
+ app.name,
43
+ app.total_errors.to_i,
44
+ app.unresolved_errors.to_i,
45
+ app.created_at.strftime("%Y-%m-%d %H:%M")
46
+ end
47
+
48
+ puts "\n" + "=" * 80
49
+
50
+ # Summary stats using already-loaded aggregates (no additional queries)
51
+ total_errors = apps.sum(&:total_errors)
52
+ total_unresolved = apps.sum(&:unresolved_errors)
53
+
54
+ puts "\nSUMMARY:"
55
+ puts " Total Applications: #{apps.count}"
56
+ puts " Total Errors: #{total_errors}"
57
+ puts " Total Unresolved: #{total_unresolved}"
58
+ puts " Resolution Rate: #{total_errors.zero? ? 'N/A' : "#{((total_errors - total_unresolved).to_f / total_errors * 100).round(1)}%"}"
59
+
60
+ puts "\n" + "=" * 80 + "\n"
61
+ end
62
+
63
+ desc "Backfill application_id for existing errors"
64
+ task backfill_application: :environment do
65
+ app_name = ENV['APP_NAME'] || ENV['APPLICATION_NAME'] ||
66
+ (defined?(Rails) && Rails.application.class.module_parent_name) ||
67
+ 'Legacy Application'
68
+
69
+ puts "\n" + "=" * 80
70
+ puts "RAILS ERROR DASHBOARD - BACKFILL APPLICATION"
71
+ puts "=" * 80
72
+ puts "\nApplication Name: #{app_name}"
73
+
74
+ # Check if there are any errors without application
75
+ orphaned_count = RailsErrorDashboard::ErrorLog.where(application_id: nil).count
76
+
77
+ if orphaned_count.zero?
78
+ puts "\n✓ No errors found without application_id"
79
+ puts " All errors are already associated with an application."
80
+ puts "\n" + "=" * 80 + "\n"
81
+ next
82
+ end
83
+
84
+ puts "Errors to backfill: #{orphaned_count}"
85
+
86
+ # Find or create application
87
+ app = RailsErrorDashboard::Application.find_or_create_by!(name: app_name) do |a|
88
+ a.description = "Backfilled via rake task on #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}"
89
+ end
90
+
91
+ puts "Using application: #{app.name} (ID: #{app.id})"
92
+
93
+ # Confirm before proceeding
94
+ print "\nProceed with backfill? (y/N): "
95
+ confirmation = $stdin.gets.chomp.downcase
96
+
97
+ unless confirmation == 'y' || confirmation == 'yes'
98
+ puts "\n✗ Backfill cancelled"
99
+ puts "\n" + "=" * 80 + "\n"
100
+ next
101
+ end
102
+
103
+ puts "\nBackfilling errors..."
104
+ count = 0
105
+ start_time = Time.current
106
+
107
+ # Process in batches with progress indicator
108
+ RailsErrorDashboard::ErrorLog.where(application_id: nil).find_in_batches(batch_size: 1000) do |batch|
109
+ batch.each do |error|
110
+ error.update_column(:application_id, app.id)
111
+ count += 1
112
+ print "." if count % 100 == 0
113
+ end
114
+ end
115
+
116
+ elapsed = (Time.current - start_time).round(2)
117
+
118
+ puts "\n\n✓ Backfill complete!"
119
+ puts " Processed: #{count} errors"
120
+ puts " Time elapsed: #{elapsed} seconds"
121
+ puts " Rate: #{(count / elapsed).round(0)} errors/sec"
122
+
123
+ puts "\n" + "=" * 80 + "\n"
124
+ end
125
+
126
+ desc "Show application statistics and health metrics"
127
+ task app_stats: :environment do
128
+ app_id = ENV['APP_ID']
129
+ app_name = ENV['APP_NAME']
130
+
131
+ puts "\n" + "=" * 80
132
+ puts "RAILS ERROR DASHBOARD - APPLICATION STATISTICS"
133
+ puts "=" * 80
134
+
135
+ # Find application
136
+ app = if app_id
137
+ RailsErrorDashboard::Application.find_by(id: app_id)
138
+ elsif app_name
139
+ RailsErrorDashboard::Application.find_by(name: app_name)
140
+ else
141
+ puts "\n✗ Please specify APP_ID or APP_NAME"
142
+ puts "\nExamples:"
143
+ puts " rails error_dashboard:app_stats APP_NAME='My App'"
144
+ puts " rails error_dashboard:app_stats APP_ID=1"
145
+ puts "\n" + "=" * 80 + "\n"
146
+ next
147
+ end
148
+
149
+ unless app
150
+ puts "\n✗ Application not found"
151
+ puts "\n" + "=" * 80 + "\n"
152
+ next
153
+ end
154
+
155
+ # Display application info
156
+ puts "\nApplication: #{app.name}"
157
+ puts "Created: #{app.created_at.strftime('%Y-%m-%d %H:%M')}"
158
+ puts "Description: #{app.description || 'N/A'}"
159
+ puts "\n" + "-" * 80
160
+
161
+ # Error counts
162
+ errors = app.error_logs
163
+ puts "\nERROR COUNTS:"
164
+ puts " Total: #{errors.count}"
165
+ puts " Unresolved: #{errors.unresolved.count}"
166
+ puts " Resolved: #{errors.resolved.count}"
167
+ puts " Resolution Rate: #{errors.count.zero? ? 'N/A' : "#{(errors.resolved.count.to_f / errors.count * 100).round(1)}%"}"
168
+
169
+ # Time-based stats
170
+ puts "\nTIME-BASED STATS:"
171
+ puts " Last 24 hours: #{errors.where('occurred_at >= ?', 24.hours.ago).count}"
172
+ puts " Last 7 days: #{errors.where('occurred_at >= ?', 7.days.ago).count}"
173
+ puts " Last 30 days: #{errors.where('occurred_at >= ?', 30.days.ago).count}"
174
+
175
+ # Top error types
176
+ puts "\nTOP 5 ERROR TYPES:"
177
+ top_errors = errors.group(:error_type).count.sort_by { |_, count| -count }.first(5)
178
+ if top_errors.any?
179
+ top_errors.each_with_index do |(error_type, count), index|
180
+ puts " #{index + 1}. #{error_type}: #{count}"
181
+ end
182
+ else
183
+ puts " No errors logged yet"
184
+ end
185
+
186
+ # Platform breakdown
187
+ platforms = errors.group(:platform).count
188
+ if platforms.any?
189
+ puts "\nPLATFORM BREAKDOWN:"
190
+ platforms.sort_by { |_, count| -count }.each do |platform, count|
191
+ platform_name = platform || "Unknown"
192
+ percentage = (count.to_f / errors.count * 100).round(1)
193
+ puts " #{platform_name}: #{count} (#{percentage}%)"
194
+ end
195
+ end
196
+
197
+ # Recent activity
198
+ recent = errors.order(occurred_at: :desc).limit(5)
199
+ if recent.any?
200
+ puts "\nRECENT ERRORS:"
201
+ recent.each do |error|
202
+ time_ago = Time.current - error.occurred_at
203
+ time_str = if time_ago < 3600
204
+ "#{(time_ago / 60).to_i}m ago"
205
+ elsif time_ago < 86400
206
+ "#{(time_ago / 3600).to_i}h ago"
207
+ else
208
+ "#{(time_ago / 86400).to_i}d ago"
209
+ end
210
+ puts " • #{error.error_type} (#{time_str}) - #{error.message.truncate(50)}"
211
+ end
212
+ end
213
+
214
+ puts "\n" + "=" * 80 + "\n"
215
+ end
216
+
217
+ desc "Clean up old resolved errors"
218
+ task cleanup_resolved: :environment do
219
+ days = ENV['DAYS']&.to_i || 90
220
+ app_name = ENV['APP_NAME']
221
+
222
+ puts "\n" + "=" * 80
223
+ puts "RAILS ERROR DASHBOARD - CLEANUP RESOLVED ERRORS"
224
+ puts "=" * 80
225
+ puts "\nCleaning up resolved errors older than #{days} days"
226
+
227
+ scope = RailsErrorDashboard::ErrorLog.resolved
228
+ .where('resolved_at < ?', days.days.ago)
229
+
230
+ if app_name
231
+ app = RailsErrorDashboard::Application.find_by(name: app_name)
232
+ unless app
233
+ puts "\n✗ Application '#{app_name}' not found"
234
+ puts "\n" + "=" * 80 + "\n"
235
+ next
236
+ end
237
+ scope = scope.where(application: app)
238
+ puts "Filtering to application: #{app_name}"
239
+ end
240
+
241
+ count = scope.count
242
+
243
+ if count.zero?
244
+ puts "\n✓ No resolved errors found older than #{days} days"
245
+ puts "\n" + "=" * 80 + "\n"
246
+ next
247
+ end
248
+
249
+ puts "Found #{count} resolved errors to delete"
250
+
251
+ # Confirm before proceeding
252
+ print "\nProceed with deletion? (y/N): "
253
+ confirmation = $stdin.gets.chomp.downcase
254
+
255
+ unless confirmation == 'y' || confirmation == 'yes'
256
+ puts "\n✗ Cleanup cancelled"
257
+ puts "\n" + "=" * 80 + "\n"
258
+ next
259
+ end
260
+
261
+ puts "\nDeleting errors..."
262
+ start_time = Time.current
263
+ deleted = scope.delete_all
264
+ elapsed = (Time.current - start_time).round(2)
265
+
266
+ puts "\n✓ Cleanup complete!"
267
+ puts " Deleted: #{deleted} errors"
268
+ puts " Time elapsed: #{elapsed} seconds"
269
+
270
+ puts "\n" + "=" * 80 + "\n"
271
+ end
272
+ 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.1.21
4
+ version: 0.1.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -290,6 +290,7 @@ files:
290
290
  - app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
291
291
  - app/mailers/rails_error_dashboard/application_mailer.rb
292
292
  - app/mailers/rails_error_dashboard/error_notification_mailer.rb
293
+ - app/models/rails_error_dashboard/application.rb
293
294
  - app/models/rails_error_dashboard/cascade_pattern.rb
294
295
  - app/models/rails_error_dashboard/error_baseline.rb
295
296
  - app/models/rails_error_dashboard/error_comment.rb
@@ -327,6 +328,10 @@ files:
327
328
  - db/migrate/20251226020100_create_error_comments.rb
328
329
  - db/migrate/20251229111223_add_additional_performance_indexes.rb
329
330
  - db/migrate/20251230075315_cleanup_orphaned_migrations.rb
331
+ - db/migrate/20260106094220_create_rails_error_dashboard_applications.rb
332
+ - db/migrate/20260106094233_add_application_to_error_logs.rb
333
+ - db/migrate/20260106094256_backfill_application_for_existing_errors.rb
334
+ - db/migrate/20260106094318_finalize_application_foreign_key.rb
330
335
  - lib/generators/rails_error_dashboard/install/install_generator.rb
331
336
  - lib/generators/rails_error_dashboard/install/templates/README
332
337
  - lib/generators/rails_error_dashboard/install/templates/initializer.rb
@@ -371,6 +376,7 @@ files:
371
376
  - lib/rails_error_dashboard/services/similarity_calculator.rb
372
377
  - lib/rails_error_dashboard/value_objects/error_context.rb
373
378
  - lib/rails_error_dashboard/version.rb
379
+ - lib/tasks/error_dashboard.rake
374
380
  - lib/tasks/rails_error_dashboard_tasks.rake
375
381
  homepage: https://github.com/AnjanJ/rails_error_dashboard
376
382
  licenses:
@@ -380,7 +386,7 @@ metadata:
380
386
  source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
381
387
  changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
382
388
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
383
- \ Rails Error Dashboard v0.1.21\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
389
+ \ Rails Error Dashboard v0.1.22\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
384
390
  First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
385
391
  db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
386
392
  => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n