rails_error_dashboard 0.1.22 → 0.1.24

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b04467a3dfb4cb2500b3dd7cdcc8ab157e54dccbd9630f1333a2aae81e0d714
4
- data.tar.gz: ffadf9eefd995500948a560f04c394adf1c005b892f7e4f23cd80ac5a7b9f4c8
3
+ metadata.gz: 3ca536416dd8806b35ae7b3bda7aaad734d7d12f9af382db58db91ac8cfa3d3e
4
+ data.tar.gz: c015a441f1aea077f1cd742f008d1a5cc08749247e6e00714994a421c4a32e98
5
5
  SHA512:
6
- metadata.gz: 46997287677b84461147c6ac344927a4e38d90f52da72de5069e48afe3072bc6b04d88936dc28e3c8b7c50fe47c065980286e8cbf98b3388756a8ae29a57b8f0
7
- data.tar.gz: 93f7d23fe4bdf2a6fd5d62d488108e63423167b77af171e4ab42a9203c9d220010f26a34c339edda553efdcd083d54761c4d8bb0b303400b5c4a9c615775a2e3
6
+ metadata.gz: 2f7e9246fd8327c6f6a7109e6c283cc42ae4eeaa85125b0eddf96f53128d7560acefac4303b2367d7d82fb07287ac05907f8929a58644bf41768702dc6120951
7
+ data.tar.gz: 6b952f9198afcfeb7d40b44a118d29303eb8bac87cc52ca0635c394c111b05ce94f877f56cb498b87c036519b90a0a692dc03b3aab155c1265588be8c72eca50
@@ -6,6 +6,23 @@ module RailsErrorDashboard
6
6
 
7
7
  before_action :authenticate_dashboard_user!
8
8
 
9
+ FILTERABLE_PARAMS = %i[
10
+ error_type
11
+ unresolved
12
+ platform
13
+ application_id
14
+ search
15
+ severity
16
+ timeframe
17
+ frequency
18
+ status
19
+ assigned_to
20
+ priority_level
21
+ hide_snoozed
22
+ sort_by
23
+ sort_direction
24
+ ].freeze
25
+
9
26
  def overview
10
27
  # Get dashboard stats using Query
11
28
  @stats = Queries::DashboardStats.call
@@ -266,24 +283,7 @@ module RailsErrorDashboard
266
283
  end
267
284
 
268
285
  def filter_params
269
- {
270
- error_type: params[:error_type],
271
- unresolved: params[:unresolved],
272
- platform: params[:platform],
273
- application_id: params[:application_id],
274
- search: params[:search],
275
- severity: params[:severity],
276
- timeframe: params[:timeframe],
277
- frequency: params[:frequency],
278
- # Phase 3: Workflow filter params
279
- status: params[:status],
280
- assigned_to: params[:assigned_to],
281
- priority_level: params[:priority_level],
282
- hide_snoozed: params[:hide_snoozed],
283
- # Sorting params
284
- sort_by: params[:sort_by],
285
- sort_direction: params[:sort_direction]
286
- }
286
+ params.permit(*FILTERABLE_PARAMS).to_h.symbolize_keys
287
287
  end
288
288
 
289
289
  def authenticate_dashboard_user!
@@ -81,6 +81,15 @@ module RailsErrorDashboard
81
81
  RailsErrorDashboard.configuration.dashboard_username || ENV["USER"] || "unknown"
82
82
  end
83
83
 
84
+ # Returns a sanitized hash of filter params safe for query links
85
+ # @param extra_keys [Array<Symbol>] Additional permitted keys for specific contexts
86
+ # @return [Hash] Whitelisted params for building URLs
87
+ def permitted_filter_params(extra_keys: [])
88
+ base_keys = RailsErrorDashboard::ErrorsController::FILTERABLE_PARAMS + %i[page per_page days]
89
+ allowed_keys = base_keys + Array(extra_keys)
90
+ params.permit(*allowed_keys).to_h.symbolize_keys
91
+ end
92
+
84
93
  # Generates a sortable column header link
85
94
  # @param label [String] The column label to display
86
95
  # @param column [String] The column name to sort by
@@ -103,8 +112,8 @@ module RailsErrorDashboard
103
112
  "⇅" # Unsorted indicator
104
113
  end
105
114
 
106
- # Preserve existing filter params while adding sort params
107
- link_params = params.permit!.to_h.merge(sort_by: column, sort_direction: new_direction)
115
+ # Preserve whitelisted filter params while adding sort params
116
+ link_params = permitted_filter_params.merge(sort_by: column, sort_direction: new_direction)
108
117
 
109
118
  link_to errors_path(link_params), class: "text-decoration-none" do
110
119
  content_tag(:span, "#{label} ", class: current_sort == column ? "fw-bold" : "") +
@@ -1,6 +1,6 @@
1
1
  module RailsErrorDashboard
2
2
  class Application < ActiveRecord::Base
3
- self.table_name = 'rails_error_dashboard_applications'
3
+ self.table_name = "rails_error_dashboard_applications"
4
4
 
5
5
  # Associations
6
6
  has_many :error_logs, dependent: :restrict_with_error
@@ -12,10 +12,33 @@ module RailsErrorDashboard
12
12
  scope :ordered_by_name, -> { order(:name) }
13
13
 
14
14
  # Class method for finding or creating with caching
15
+ # Caches application IDs (not objects) to avoid stale ActiveRecord references
16
+ # This is safer with transactional fixtures and database rollbacks
15
17
  def self.find_or_create_by_name(name)
16
- Rails.cache.fetch("error_dashboard/application/#{name}", expires_in: 1.hour) do
17
- find_or_create_by!(name: name)
18
+ # Try to find ID in cache
19
+ cached_id = Rails.cache.read("error_dashboard/application_id/#{name}")
20
+ if cached_id
21
+ # Verify the cached ID still exists in database
22
+ # (could have been deleted in tests with transactional rollback)
23
+ cached_record = find_by(id: cached_id)
24
+ return cached_record if cached_record
25
+ # Cache was stale, clear it
26
+ Rails.cache.delete("error_dashboard/application_id/#{name}")
18
27
  end
28
+
29
+ # Try to find existing in database
30
+ found = find_by(name: name)
31
+ if found
32
+ # Cache the ID (not the object) for future lookups
33
+ Rails.cache.write("error_dashboard/application_id/#{name}", found.id, expires_in: 1.hour)
34
+ return found
35
+ end
36
+
37
+ # Create if not found
38
+ created = create!(name: name)
39
+ # Cache the ID (not the object)
40
+ Rails.cache.write("error_dashboard/application_id/#{name}", created.id, expires_in: 1.hour)
41
+ created
19
42
  end
20
43
 
21
44
  # Instance methods
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Abstract base class for models stored in the error_logs database
3
+ # Abstract base class for models stored in the error dashboard database
4
4
  #
5
5
  # By default, this connects to the same database as the main application.
6
6
  #
7
- # To enable a separate error logs database:
7
+ # To enable a separate error dashboard database:
8
8
  # 1. Set use_separate_database: true in the gem configuration
9
- # 2. Configure error_logs_database settings in config/database.yml
10
- # 3. Run: rails db:create:error_logs
11
- # 4. Run: rails db:migrate:error_logs
9
+ # 2. Set database: :error_dashboard (or your custom name) in the gem configuration
10
+ # 3. Configure error_dashboard settings in config/database.yml
11
+ # 4. Run: rails db:create
12
+ # 5. Run: rails db:migrate
12
13
  #
13
14
  # Benefits of separate database:
14
15
  # - Performance isolation (error logging doesn't slow down user requests)
@@ -25,10 +26,8 @@ module RailsErrorDashboard
25
26
  class ErrorLogsRecord < ActiveRecord::Base
26
27
  self.abstract_class = true
27
28
 
28
- # Connect to error_logs database (or primary if not using separate DB)
29
- # Only connect to separate database if configuration is enabled
30
- if RailsErrorDashboard.configuration&.use_separate_database
31
- connects_to database: { writing: :error_logs, reading: :error_logs }
32
- end
29
+ # Database connection will be configured by the engine initializer
30
+ # after the user's configuration is loaded
31
+ # See lib/rails_error_dashboard/engine.rb
33
32
  end
34
33
  end
@@ -866,16 +866,20 @@
866
866
  <% end %>
867
867
  </button>
868
868
  <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="appSwitcher">
869
+ <%
870
+ base_route_params = request.path_parameters.slice(:controller, :action)
871
+ current_filter_params = permitted_filter_params
872
+ %>
869
873
  <li>
870
874
  <%= link_to "All Applications",
871
- url_for(params.permit!.except(:controller, :action, :application_id)),
875
+ url_for(base_route_params.merge(current_filter_params.except(:application_id))),
872
876
  class: "dropdown-item #{'active' unless params[:application_id].present?}" %>
873
877
  </li>
874
878
  <li><hr class="dropdown-divider"></li>
875
879
  <% @applications.each do |app_name, app_id| %>
876
880
  <li>
877
881
  <%= link_to app_name,
878
- url_for(params.permit!.except(:controller, :action).merge(application_id: app_id)),
882
+ url_for(base_route_params.merge(current_filter_params.merge(application_id: app_id))),
879
883
  class: "dropdown-item #{params[:application_id].to_s == app_id.to_s ? 'active' : ''}" %>
880
884
  </li>
881
885
  <% end %>
@@ -158,7 +158,7 @@
158
158
  <% active_filters.each do |filter| %>
159
159
  <%
160
160
  # Build URL without this specific filter
161
- filter_params = params.permit!.except(:controller, :action, filter[:param])
161
+ filter_params = permitted_filter_params.except(filter[:param])
162
162
  %>
163
163
  <%= link_to errors_path(filter_params), class: "badge bg-primary text-decoration-none filter-pill" do %>
164
164
  <%= filter[:label] %>
@@ -7,11 +7,11 @@ class AddApplicationToErrorLogs < ActiveRecord::Migration[7.0]
7
7
  add_index :rails_error_dashboard_error_logs, :application_id
8
8
 
9
9
  add_index :rails_error_dashboard_error_logs,
10
- [:application_id, :occurred_at],
10
+ [ :application_id, :occurred_at ],
11
11
  name: 'index_error_logs_on_app_occurred'
12
12
 
13
13
  add_index :rails_error_dashboard_error_logs,
14
- [:application_id, :resolved],
14
+ [ :application_id, :resolved ],
15
15
  name: 'index_error_logs_on_app_resolved'
16
16
  end
17
17
 
@@ -18,6 +18,7 @@ module RailsErrorDashboard
18
18
  class_option :async_logging, type: :boolean, default: false, desc: "Enable async error logging"
19
19
  class_option :error_sampling, type: :boolean, default: false, desc: "Enable error sampling (reduce volume)"
20
20
  class_option :separate_database, type: :boolean, default: false, desc: "Use separate database for errors"
21
+ class_option :database, type: :string, default: nil, desc: "Database name to use for errors (e.g., 'error_dashboard')"
21
22
  # Advanced analytics options
22
23
  class_option :baseline_alerts, type: :boolean, default: false, desc: "Enable baseline anomaly alerts"
23
24
  class_option :similar_errors, type: :boolean, default: false, desc: "Enable fuzzy error matching"
@@ -185,6 +186,7 @@ module RailsErrorDashboard
185
186
  @enable_async_logging = @selected_features&.dig(:async_logging) || options[:async_logging]
186
187
  @enable_error_sampling = @selected_features&.dig(:error_sampling) || options[:error_sampling]
187
188
  @enable_separate_database = @selected_features&.dig(:separate_database) || options[:separate_database]
189
+ @database_name = options[:database]
188
190
 
189
191
  # Advanced Analytics
190
192
  @enable_baseline_alerts = @selected_features&.dig(:baseline_alerts) || options[:baseline_alerts]
@@ -275,7 +277,14 @@ module RailsErrorDashboard
275
277
  say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
276
278
  say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
277
279
  say " → Ensure Sidekiq/Solid Queue running", :yellow if @enable_async_logging
278
- say " → Configure database.yml (docs/guides/DATABASE_OPTIONS.md)", :yellow if @enable_separate_database
280
+ if @enable_separate_database
281
+ if @database_name
282
+ say " → Configure '#{@database_name}' database in database.yml", :yellow
283
+ else
284
+ say " → Configure database in database.yml and set config.database", :yellow
285
+ end
286
+ say " See docs/guides/DATABASE_OPTIONS.md for details", :yellow
287
+ end
279
288
 
280
289
  say "\n"
281
290
  say "Next Steps:", :cyan
@@ -153,6 +153,11 @@ RailsErrorDashboard.configure do |config|
153
153
  # Errors will be stored in a dedicated database
154
154
  # See docs/guides/DATABASE_OPTIONS.md for setup instructions
155
155
  config.use_separate_database = true
156
+ <% if @database_name -%>
157
+ config.database = :<%= @database_name %>
158
+ <% else -%>
159
+ # config.database = :error_dashboard # Uncomment and set your database name
160
+ <% end -%>
156
161
  # To disable: Set config.use_separate_database = false
157
162
 
158
163
  <% else -%>
@@ -160,6 +165,7 @@ RailsErrorDashboard.configure do |config|
160
165
  # Errors are stored in your main application database
161
166
  # To enable: Set config.use_separate_database = true and configure database.yml
162
167
  config.use_separate_database = false
168
+ # config.database = :error_dashboard # Database name when using separate database
163
169
 
164
170
  <% end -%>
165
171
  # ============================================================================
@@ -143,15 +143,15 @@ module RailsErrorDashboard
143
143
  # Find or create application for multi-app support
144
144
  def find_or_create_application
145
145
  app_name = RailsErrorDashboard.configuration.application_name ||
146
- ENV['APPLICATION_NAME'] ||
146
+ ENV["APPLICATION_NAME"] ||
147
147
  (defined?(Rails) && Rails.application.class.module_parent_name) ||
148
- 'Rails Application'
148
+ "Rails Application"
149
149
 
150
150
  Application.find_or_create_by_name(app_name)
151
151
  rescue => e
152
152
  RailsErrorDashboard::Logger.error("[RailsErrorDashboard] Failed to find/create application: #{e.message}")
153
153
  # Fallback: try to find any application or create default
154
- Application.first || Application.create!(name: 'Default Application')
154
+ Application.first || Application.create!(name: "Default Application")
155
155
  end
156
156
 
157
157
  # Trigger notification callbacks for error logging
@@ -2,6 +2,20 @@ module RailsErrorDashboard
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace RailsErrorDashboard
4
4
 
5
+ # Configure database connection for error models
6
+ # This runs early, before middleware setup, but after database.yml is loaded
7
+ initializer "rails_error_dashboard.database", before: :load_config_initializers do
8
+ config.after_initialize do
9
+ if RailsErrorDashboard.configuration&.use_separate_database
10
+ database_name = RailsErrorDashboard.configuration&.database || :error_dashboard
11
+
12
+ RailsErrorDashboard::ErrorLogsRecord.connects_to(
13
+ database: { writing: database_name, reading: database_name }
14
+ )
15
+ end
16
+ end
17
+ end
18
+
5
19
  # Initialize the engine
6
20
  initializer "rails_error_dashboard.middleware" do |app|
7
21
  # Enable Flash middleware for Error Dashboard routes in API-only apps
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.1.22"
2
+ VERSION = "0.1.24"
3
3
  end
@@ -10,11 +10,11 @@ namespace :error_dashboard do
10
10
  # Use single SQL query with aggregates to avoid N+1 queries
11
11
  # This fetches all app data + error counts in one query instead of 6N queries
12
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')
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
18
  .order(:name)
19
19
 
20
20
  if apps.empty?
@@ -27,9 +27,9 @@ namespace :error_dashboard do
27
27
  puts "\n#{apps.count} application(s) registered:\n\n"
28
28
 
29
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
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
33
 
34
34
  # Print header
35
35
  printf "%-#{name_width}s %#{total_width}s %#{unresolved_width}s %s\n",
@@ -62,9 +62,9 @@ namespace :error_dashboard do
62
62
 
63
63
  desc "Backfill application_id for existing errors"
64
64
  task backfill_application: :environment do
65
- app_name = ENV['APP_NAME'] || ENV['APPLICATION_NAME'] ||
65
+ app_name = ENV["APP_NAME"] || ENV["APPLICATION_NAME"] ||
66
66
  (defined?(Rails) && Rails.application.class.module_parent_name) ||
67
- 'Legacy Application'
67
+ "Legacy Application"
68
68
 
69
69
  puts "\n" + "=" * 80
70
70
  puts "RAILS ERROR DASHBOARD - BACKFILL APPLICATION"
@@ -94,7 +94,7 @@ namespace :error_dashboard do
94
94
  print "\nProceed with backfill? (y/N): "
95
95
  confirmation = $stdin.gets.chomp.downcase
96
96
 
97
- unless confirmation == 'y' || confirmation == 'yes'
97
+ unless confirmation == "y" || confirmation == "yes"
98
98
  puts "\n✗ Backfill cancelled"
99
99
  puts "\n" + "=" * 80 + "\n"
100
100
  next
@@ -125,8 +125,8 @@ namespace :error_dashboard do
125
125
 
126
126
  desc "Show application statistics and health metrics"
127
127
  task app_stats: :environment do
128
- app_id = ENV['APP_ID']
129
- app_name = ENV['APP_NAME']
128
+ app_id = ENV["APP_ID"]
129
+ app_name = ENV["APP_NAME"]
130
130
 
131
131
  puts "\n" + "=" * 80
132
132
  puts "RAILS ERROR DASHBOARD - APPLICATION STATISTICS"
@@ -135,16 +135,16 @@ namespace :error_dashboard do
135
135
  # Find application
136
136
  app = if app_id
137
137
  RailsErrorDashboard::Application.find_by(id: app_id)
138
- elsif app_name
138
+ elsif app_name
139
139
  RailsErrorDashboard::Application.find_by(name: app_name)
140
- else
140
+ else
141
141
  puts "\n✗ Please specify APP_ID or APP_NAME"
142
142
  puts "\nExamples:"
143
143
  puts " rails error_dashboard:app_stats APP_NAME='My App'"
144
144
  puts " rails error_dashboard:app_stats APP_ID=1"
145
145
  puts "\n" + "=" * 80 + "\n"
146
146
  next
147
- end
147
+ end
148
148
 
149
149
  unless app
150
150
  puts "\n✗ Application not found"
@@ -202,11 +202,11 @@ namespace :error_dashboard do
202
202
  time_ago = Time.current - error.occurred_at
203
203
  time_str = if time_ago < 3600
204
204
  "#{(time_ago / 60).to_i}m ago"
205
- elsif time_ago < 86400
205
+ elsif time_ago < 86400
206
206
  "#{(time_ago / 3600).to_i}h ago"
207
- else
207
+ else
208
208
  "#{(time_ago / 86400).to_i}d ago"
209
- end
209
+ end
210
210
  puts " • #{error.error_type} (#{time_str}) - #{error.message.truncate(50)}"
211
211
  end
212
212
  end
@@ -216,8 +216,8 @@ namespace :error_dashboard do
216
216
 
217
217
  desc "Clean up old resolved errors"
218
218
  task cleanup_resolved: :environment do
219
- days = ENV['DAYS']&.to_i || 90
220
- app_name = ENV['APP_NAME']
219
+ days = ENV["DAYS"]&.to_i || 90
220
+ app_name = ENV["APP_NAME"]
221
221
 
222
222
  puts "\n" + "=" * 80
223
223
  puts "RAILS ERROR DASHBOARD - CLEANUP RESOLVED ERRORS"
@@ -225,7 +225,7 @@ namespace :error_dashboard do
225
225
  puts "\nCleaning up resolved errors older than #{days} days"
226
226
 
227
227
  scope = RailsErrorDashboard::ErrorLog.resolved
228
- .where('resolved_at < ?', days.days.ago)
228
+ .where("resolved_at < ?", days.days.ago)
229
229
 
230
230
  if app_name
231
231
  app = RailsErrorDashboard::Application.find_by(name: app_name)
@@ -252,7 +252,7 @@ namespace :error_dashboard do
252
252
  print "\nProceed with deletion? (y/N): "
253
253
  confirmation = $stdin.gets.chomp.downcase
254
254
 
255
- unless confirmation == 'y' || confirmation == 'yes'
255
+ unless confirmation == "y" || confirmation == "yes"
256
256
  puts "\n✗ Cleanup cancelled"
257
257
  puts "\n" + "=" * 80 + "\n"
258
258
  next
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.22
4
+ version: 0.1.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -83,16 +83,16 @@ dependencies:
83
83
  name: httparty
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '0.21'
88
+ version: 0.24.0
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: '0.21'
95
+ version: 0.24.0
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: turbo-rails
98
98
  requirement: !ruby/object:Gem::Requirement
@@ -386,7 +386,7 @@ metadata:
386
386
  source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
387
387
  changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
388
388
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
389
- \ Rails Error Dashboard v0.1.22\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
389
+ \ Rails Error Dashboard v0.1.24\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
390
390
  First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
391
391
  db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
392
392
  => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n