Class: RailsErrorDashboard::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/rails_error_dashboard/install/install_generator.rb

Instance Method Summary collapse

Instance Method Details

#add_routeObject



338
339
340
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 338

def add_route
  route "mount RailsErrorDashboard::Engine => '/error_dashboard'"
end

#copy_migrationsObject



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 310

def copy_migrations
  source_dir = File.expand_path("../../../../db/migrate", __dir__)
  migrate_subdir = @enable_separate_database ? "db/error_dashboard_migrate" : "db/migrate"
  target_dir = File.join(destination_root, migrate_subdir)

  FileUtils.mkdir_p(target_dir)

  # Check which migrations are already installed (by descriptive name, ignoring timestamp)
  existing = Dir.glob(File.join(target_dir, "*rails_error_dashboard*.rb")).map { |f|
    File.basename(f).sub(/^\d+_/, "")
  }.to_set

  timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i

  Dir.glob(File.join(source_dir, "*.rb")).sort.each do |source_file|
    basename = File.basename(source_file)
    name_without_ts = basename.sub(/^\d+_/, "")
    suffixed = name_without_ts.sub(/\.rb$/, ".rails_error_dashboard.rb")

    next if existing.include?(suffixed)

    FileUtils.cp(source_file, File.join(target_dir, "#{timestamp}_#{suffixed}"))
    timestamp += 1
  end

  say_status "copied", "migrations to #{migrate_subdir}", :green
end

#create_initializer_fileObject



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 274

def create_initializer_file
  # Notifications
  @enable_slack = @selected_features&.dig(:slack) || options[:slack]
  @enable_email = @selected_features&.dig(:email) || options[:email]
  @enable_discord = @selected_features&.dig(:discord) || options[:discord]
  @enable_pagerduty = @selected_features&.dig(:pagerduty) || options[:pagerduty]
  @enable_webhooks = @selected_features&.dig(:webhooks) || options[:webhooks]

  # Performance
  @enable_async_logging = @selected_features&.dig(:async_logging) || options[:async_logging]
  @enable_error_sampling = @selected_features&.dig(:error_sampling) || options[:error_sampling]

  # Database mode (set by select_database_mode or CLI flags)
  @database_mode ||= :same
  @enable_separate_database = @database_mode == :separate || @database_mode == :multi_app
  @enable_multi_app = @database_mode == :multi_app
  # @database_name and @application_name are set by select_database_mode

  # Advanced Analytics
  @enable_baseline_alerts = @selected_features&.dig(:baseline_alerts) || options[:baseline_alerts]
  @enable_similar_errors = @selected_features&.dig(:similar_errors) || options[:similar_errors]
  @enable_co_occurring_errors = @selected_features&.dig(:co_occurring_errors) || options[:co_occurring_errors]
  @enable_error_cascades = @selected_features&.dig(:error_cascades) || options[:error_cascades]
  @enable_error_correlation = @selected_features&.dig(:error_correlation) || options[:error_correlation]
  @enable_platform_comparison = @selected_features&.dig(:platform_comparison) || options[:platform_comparison]
  @enable_occurrence_patterns = @selected_features&.dig(:occurrence_patterns) || options[:occurrence_patterns]

  # Developer Tools
  @enable_source_code_integration = @selected_features&.dig(:source_code_integration) || options[:source_code_integration]
  @enable_git_blame = @selected_features&.dig(:git_blame) || options[:git_blame]
  @enable_breadcrumbs = @selected_features&.dig(:breadcrumbs) || options[:breadcrumbs]
  @enable_system_health = @selected_features&.dig(:system_health) || options[:system_health]

  template "initializer.rb", "config/initializers/rails_error_dashboard.rb"
end

#select_database_modeObject



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 203

def select_database_mode
  # Skip if not interactive or if --separate_database was passed via CLI
  if options[:separate_database]
    @database_mode = :separate
    @database_name = options[:database] || "error_dashboard"
    @application_name = detect_application_name
    return
  end

  return unless options[:interactive] && behavior == :invoke
  return unless $stdin.tty?

  say "-" * 70
  say "  Database Setup", :cyan
  say "-" * 70
  say "\n"
  say "  How do you want to store error data?\n", :white
  say "  1) Same database (default) - store errors in your app's primary database", :white
  say "  2) Separate database       - dedicated database for error data (recommended for production)", :white
  say "  3) Shared database          - connect to an existing error database shared by multiple apps", :white
  say "\n"

  response = ask("  Choose (1/2/3):", :yellow, limited_to: [ "1", "2", "3", "" ])

  case response
  when "2"
    @database_mode = :separate
    @database_name = "error_dashboard"
    @application_name = detect_application_name

    say "\n  Database key: error_dashboard", :green
    say "  Application name: #{@application_name}", :green
    say "\n"
  when "3"
    @database_mode = :multi_app
    @database_name = "error_dashboard"
    @application_name = detect_application_name

    say "\n  Is this the first app using the shared error database,", :white
    say "  or are you connecting to one that already exists?\n", :white
    say "  a) First app  - create a new shared error database", :white
    say "  b) Existing   - connect to a database already used by another app", :white
    say "\n"

    shared_response = ask("  Choose (a/b):", :yellow, limited_to: [ "a", "A", "b", "B", "" ])

    if shared_response.downcase == "b"
      say "\n  Enter the base database name from your other app's database.yml.", :white
      say "  We'll append _development/_production automatically.", :white
      say "  (e.g., if your DB is 'my_errors_development', enter 'my_errors'):\n", :white
      @shared_db_name = ask("  Database name:", :yellow)
      @shared_db_name = @shared_db_name.strip
      # Strip environment suffixes in case the user pasted the full name
      @shared_db_name = @shared_db_name.sub(/_(development|production|test)$/, "")
      @shared_db_name = "shared_errors" if @shared_db_name.empty?
    else
      @shared_db_name = "shared_errors"
    end

    say "\n  Database key: error_dashboard", :green
    say "  Shared database: #{@shared_db_name}", :green
    say "  Application name: #{@application_name}", :green
    say "  This app will share the error database with your other apps.", :white
    say "\n"
  else
    @database_mode = :same
    @database_name = nil
    @application_name = detect_application_name
  end
end

#select_optional_featuresObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 51

def select_optional_features
  return unless options[:interactive] && behavior == :invoke
  return unless $stdin.tty?  # Skip interactive mode if not running in a terminal

  say "Let's configure optional features...\n", :cyan
  say "(You can always enable/disable these later in the initializer)\n\n", :yellow

  @selected_features = {}

  # Feature definitions with descriptions - organized by category
  # Note: separate_database is handled separately via select_database_mode
  features = [
    # === NOTIFICATIONS ===
    {
      key: :slack,
      name: "Slack Notifications",
      description: "Send error alerts to Slack channels",
      category: "Notifications"
    },
    {
      key: :email,
      name: "Email Notifications",
      description: "Send error alerts via email",
      category: "Notifications"
    },
    {
      key: :discord,
      name: "Discord Notifications",
      description: "Send error alerts to Discord",
      category: "Notifications"
    },
    {
      key: :pagerduty,
      name: "PagerDuty Integration",
      description: "Critical errors to PagerDuty",
      category: "Notifications"
    },
    {
      key: :webhooks,
      name: "Generic Webhooks",
      description: "Send data to custom endpoints",
      category: "Notifications"
    },

    # === PERFORMANCE & SCALABILITY ===
    {
      key: :async_logging,
      name: "Async Error Logging",
      description: "Process errors in background jobs (faster responses)",
      category: "Performance"
    },
    {
      key: :error_sampling,
      name: "Error Sampling",
      description: "Log only % of non-critical errors (reduce volume)",
      category: "Performance"
    },

    # === ADVANCED ANALYTICS ===
    {
      key: :baseline_alerts,
      name: "Baseline Anomaly Alerts",
      description: "Auto-detect unusual error rate spikes",
      category: "Advanced Analytics"
    },
    {
      key: :similar_errors,
      name: "Fuzzy Error Matching",
      description: "Find similar errors across different hashes",
      category: "Advanced Analytics"
    },
    {
      key: :co_occurring_errors,
      name: "Co-occurring Errors",
      description: "Detect errors that happen together",
      category: "Advanced Analytics"
    },
    {
      key: :error_cascades,
      name: "Error Cascade Detection",
      description: "Identify error chains (A causes B causes C)",
      category: "Advanced Analytics"
    },
    {
      key: :error_correlation,
      name: "Error Correlation Analysis",
      description: "Correlate with versions, users, time",
      category: "Advanced Analytics"
    },
    {
      key: :platform_comparison,
      name: "Platform Comparison",
      description: "Compare iOS vs Android vs Web health",
      category: "Advanced Analytics"
    },
    {
      key: :occurrence_patterns,
      name: "Occurrence Pattern Detection",
      description: "Detect cyclical patterns and bursts",
      category: "Advanced Analytics"
    },

    # === DEVELOPER TOOLS ===
    {
      key: :source_code_integration,
      name: "Source Code Integration (NEW!)",
      description: "View source code directly in error details",
      category: "Developer Tools"
    },
    {
      key: :git_blame,
      name: "Git Blame Integration (NEW!)",
      description: "Show git blame info (author, commit, timestamp)",
      category: "Developer Tools"
    },
    {
      key: :breadcrumbs,
      name: "Breadcrumbs (NEW!)",
      description: "Capture request activity trail (SQL, controller, cache events)",
      category: "Developer Tools"
    },
    {
      key: :system_health,
      name: "System Health Snapshot (NEW!)",
      description: "Capture GC, memory, threads, connection pool at error time",
      category: "Developer Tools"
    }
  ]

  features.each_with_index do |feature, index|
    say "\n[#{index + 1}/#{features.length}] #{feature[:name]}", :cyan
    say "    #{feature[:description]}", :white

    # Check if feature was passed via command line option
    if options[feature[:key]]
      @selected_features[feature[:key]] = true
      say "    ✓ Enabled (via --#{feature[:key]} flag)", :green
    else
      response = ask("    Enable? (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
      @selected_features[feature[:key]] = response.downcase == "y"

      if @selected_features[feature[:key]]
        say "    ✓ Enabled", :green
      else
        say "    ✗ Disabled", :white
      end
    end
  end

  say "\n"
end

#show_feature_summaryObject



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 342

def show_feature_summary
  return unless behavior == :invoke

  say "\n"
  say "=" * 70
  say "  Installation Complete!", :green
  say "=" * 70
  say "\n"

  say "Core Features (Always ON):", :cyan
  say "  ✓ Error Capture", :green
  say "  ✓ Dashboard UI", :green
  say "  ✓ Real-time Updates", :green
  say "  ✓ Analytics", :green

  # Count optional features enabled
  enabled_count = 0

  # Notifications
  notification_features = []
  notification_features << "Slack" if @enable_slack
  notification_features << "Email" if @enable_email
  notification_features << "Discord" if @enable_discord
  notification_features << "PagerDuty" if @enable_pagerduty
  notification_features << "Webhooks" if @enable_webhooks

  if notification_features.any?
    say "\nNotifications:", :cyan
    say "#{notification_features.join(", ")}", :green
    enabled_count += notification_features.size
  end

  # Performance
  performance_features = []
  performance_features << "Async Logging" if @enable_async_logging
  performance_features << "Error Sampling" if @enable_error_sampling
  performance_features << "Separate Database" if @enable_separate_database

  if performance_features.any?
    say "\nPerformance:", :cyan
    say "#{performance_features.join(", ")}", :green
    enabled_count += performance_features.size
  end

  # Advanced Analytics
  analytics_features = []
  analytics_features << "Baseline Alerts" if @enable_baseline_alerts
  analytics_features << "Fuzzy Matching" if @enable_similar_errors
  analytics_features << "Co-occurring Errors" if @enable_co_occurring_errors
  analytics_features << "Error Cascades" if @enable_error_cascades
  analytics_features << "Error Correlation" if @enable_error_correlation
  analytics_features << "Platform Comparison" if @enable_platform_comparison
  analytics_features << "Pattern Detection" if @enable_occurrence_patterns

  if analytics_features.any?
    say "\nAdvanced Analytics:", :cyan
    say "#{analytics_features.join(", ")}", :green
    enabled_count += analytics_features.size
  end

  # Developer Tools
  developer_tools_features = []
  developer_tools_features << "Source Code Integration" if @enable_source_code_integration
  developer_tools_features << "Git Blame" if @enable_git_blame
  developer_tools_features << "Breadcrumbs" if @enable_breadcrumbs
  developer_tools_features << "System Health" if @enable_system_health

  if developer_tools_features.any?
    say "\nDeveloper Tools:", :cyan
    say "#{developer_tools_features.join(", ")}", :green
    enabled_count += developer_tools_features.size
  end

  say "\nData Retention:", :cyan
  say "  Default: 90 days (change via config.retention_days)", :white
  say "  Manual cleanup: rails error_dashboard:retention_cleanup", :white
  say "  Schedule daily: RailsErrorDashboard::RetentionCleanupJob.perform_later", :white

  say "\n"
  say "Configuration Required:", :yellow if enabled_count > 0
  say "  → Edit config/initializers/rails_error_dashboard.rb", :yellow if @enable_error_sampling
  say "  → Set SLACK_WEBHOOK_URL in .env", :yellow if @enable_slack
  say "  → Set ERROR_NOTIFICATION_EMAILS in .env", :yellow if @enable_email
  say "  → Set DISCORD_WEBHOOK_URL in .env", :yellow if @enable_discord
  say "  → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
  say "  → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
  say "  → Ensure Sidekiq/Solid Queue running", :yellow if @enable_async_logging

  # Database-specific instructions
  if @enable_separate_database
    show_database_setup_instructions
  end

  say "\n"
  say "Next Steps:", :cyan
  if @enable_separate_database
    say "  1. Add the database.yml entry shown above"
    say "  2. Run: rails db:create:error_dashboard"
    if @enable_multi_app
      say "  3. Run migrations (only needed on the FIRST app):"
      say "     rails db:migrate:error_dashboard"
    else
      say "  3. Run: rails db:migrate:error_dashboard"
    end
    say "  4. Update credentials in config/initializers/rails_error_dashboard.rb"
    say "  5. Restart your Rails server"
    say "  6. Visit http://localhost:3000/error_dashboard"
    say "  7. Verify: rails error_dashboard:verify"
  else
    say "  1. Run: rails db:migrate"
    say "  2. Update credentials in config/initializers/rails_error_dashboard.rb"
    say "  3. Restart your Rails server"
    say "  4. Visit http://localhost:3000/error_dashboard"
  end
  say "Authentication:", :cyan
  say "  Default: HTTP Basic Auth (gandalf/youshallnotpass)", :white
  say "  Devise/Warden: config.authenticate_with = -> { warden.authenticated? }", :white
  say "  Session-based: config.authenticate_with = -> { session[:admin] == true }", :white
  say "  See: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md#custom-authentication", :white
  say "\n"
  say "Documentation:", :white
  say "   Quick Start: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/QUICKSTART.md", :white
  say "   Database Setup: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/DATABASE_OPTIONS.md", :white
  say "   Feature Guide: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/FEATURES.md", :white
  say "\n"
  say "To enable/disable features later:", :white
  say "   Edit config/initializers/rails_error_dashboard.rb", :white
  say "\n"
end

#show_readmeObject



472
473
474
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 472

def show_readme
  # Skip the old README display since we have the new summary
end

#welcome_messageObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/generators/rails_error_dashboard/install/install_generator.rb', line 36

def welcome_message
  say "\n"
  say "=" * 70
  say "  📊 Rails Error Dashboard Installation", :cyan
  say "=" * 70
  say "\n"
  say "Core features will be enabled automatically:", :green
  say "  ✓ Error capture (controllers, jobs, middleware)"
  say "  ✓ Dashboard UI at /error_dashboard"
  say "  ✓ Real-time updates"
  say "  ✓ Analytics & spike detection"
  say "  ✓ 90-day error retention"
  say "\n"
end