rails_error_dashboard 0.1.23 → 0.1.25

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: e8ebc7a75413a15752df2dc4a425f0c2dc046480e92d394d68212646360787e9
4
- data.tar.gz: c4174fc14f0e8c9ca2da761bf7895f0ff5d3cf85d99470b31705dc748d9083f2
3
+ metadata.gz: eb29048c8c0c29401f6295d9e2b1d797cd82908b81197f906a9df5a6f221a182
4
+ data.tar.gz: 63f26da41e171cc6956edb1bb5a0027a8c67a37bc94ff323665d4c34a2673d1e
5
5
  SHA512:
6
- metadata.gz: 33ab7875fcef261cc93f8af9ac7a7203e5b1c49a708baf247e63c7c2c9e40932d1fa21eb25c34f8b1f0d4e4f63dff3f1fdbb43f0f2b7b252575de6aaccd9cd08
7
- data.tar.gz: 9213caf3f27b27b6a04e0dc198da8e45c7561734f6c5f14f8ccbaf150f17015b3e6693c05a14b4c73e47674ca6d12ae90f75097167e65ec80d47f03e7de9e507
6
+ metadata.gz: a12dfcad4a1f99763ed15bb861d3937ed577eaf2eaa89b66d99dda8fc50e63cf093d76c01dbfb226f18a4181a57352183f2ddfd8678009b02e247f0ae0d651d0
7
+ data.tar.gz: b213451cd2c971ab5c583f2301917a87f2c79d4b5c26526abaa72dfa59bbcc8db949cc4c7452a854966229648a7ae43f861484306e55701e780070ef72054b9b
data/README.md CHANGED
@@ -16,6 +16,8 @@ gem 'rails_error_dashboard'
16
16
 
17
17
  **5-minute setup** · **Works out-of-the-box** · **100% Rails + Postgres** · **No vendor lock-in**
18
18
 
19
+ 📖 **[Full Documentation](https://anjanj.github.io/rails_error_dashboard/)** · 🎮 **[Live Demo](https://rails-error-dashboard.anjan.dev)** · 💎 **[RubyGems](https://rubygems.org/gems/rails_error_dashboard)**
20
+
19
21
  ### 🎮 Try the Live Demo
20
22
 
21
23
  **See it in action:** [https://rails-error-dashboard.anjan.dev](https://rails-error-dashboard.anjan.dev)
@@ -586,6 +588,7 @@ config.webhook_urls = ['https://yourapp.com/hooks/errors']
586
588
  - **[Error Trend Visualizations](docs/guides/ERROR_TREND_VISUALIZATIONS.md)** - Charts & analytics
587
589
 
588
590
  ### Advanced
591
+ - **[Multi-App Support](docs/MULTI_APP_PERFORMANCE.md)** - Track multiple applications
589
592
  - **[Plugin System](docs/PLUGIN_SYSTEM.md)** - Build custom integrations
590
593
  - **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation
591
594
  - **[Customization Guide](docs/CUSTOMIZATION.md)** - Customize everything
@@ -5,14 +5,32 @@ module RailsErrorDashboard
5
5
  include Pagy::Backend
6
6
 
7
7
  before_action :authenticate_dashboard_user!
8
+ before_action :set_application_context
9
+
10
+ FILTERABLE_PARAMS = %i[
11
+ error_type
12
+ unresolved
13
+ platform
14
+ application_id
15
+ search
16
+ severity
17
+ timeframe
18
+ frequency
19
+ status
20
+ assigned_to
21
+ priority_level
22
+ hide_snoozed
23
+ sort_by
24
+ sort_direction
25
+ ].freeze
8
26
 
9
27
  def overview
10
- # Get dashboard stats using Query
11
- @stats = Queries::DashboardStats.call
28
+ # Get dashboard stats using Query (pass application filter)
29
+ @stats = Queries::DashboardStats.call(application_id: @current_application_id)
12
30
 
13
- # Get platform health summary (if enabled)
31
+ # Get platform health summary (if enabled, pass application filter)
14
32
  if RailsErrorDashboard.configuration.enable_platform_comparison
15
- comparison = Queries::PlatformComparison.new(days: 7)
33
+ comparison = Queries::PlatformComparison.new(days: 7, application_id: @current_application_id)
16
34
  @platform_health = comparison.platform_health_summary
17
35
  @platform_scores = comparison.platform_stability_scores
18
36
  else
@@ -26,8 +44,8 @@ module RailsErrorDashboard
26
44
  .where("occurred_at >= ?", 1.hour.ago)
27
45
  .where(resolved_at: nil)
28
46
  .where(priority_level: [ 3, 4 ]) # 3 = high, 4 = critical (based on severity enum)
29
- .order(occurred_at: :desc)
30
- .limit(10)
47
+ @critical_alerts = @critical_alerts.where(application_id: @current_application_id) if @current_application_id.present?
48
+ @critical_alerts = @critical_alerts.order(occurred_at: :desc).limit(10)
31
49
  end
32
50
 
33
51
  def index
@@ -38,10 +56,10 @@ module RailsErrorDashboard
38
56
  @pagy, @errors = pagy(errors_query, items: params[:per_page] || 25)
39
57
 
40
58
  # Get dashboard stats using Query (pass application filter)
41
- @stats = Queries::DashboardStats.call(application_id: params[:application_id])
59
+ @stats = Queries::DashboardStats.call(application_id: @current_application_id)
42
60
 
43
- # Get filter options using Query
44
- filter_options = Queries::FilterOptions.call
61
+ # Get filter options using Query (pass application filter)
62
+ filter_options = Queries::FilterOptions.call(application_id: @current_application_id)
45
63
  @error_types = filter_options[:error_types]
46
64
  @platforms = filter_options[:platforms]
47
65
  @applications = filter_options[:applications]
@@ -52,7 +70,7 @@ module RailsErrorDashboard
52
70
  # - comments: Used in the comments section (@error.comments.count, @error.comments.recent_first)
53
71
  # - parent_cascade_patterns/child_cascade_patterns: Used if cascade detection is enabled
54
72
  @error = ErrorLog.includes(:comments, :parent_cascade_patterns, :child_cascade_patterns).find(params[:id])
55
- @related_errors = @error.related_errors(limit: 5)
73
+ @related_errors = @error.related_errors(limit: 5, application_id: @current_application_id)
56
74
 
57
75
  # Dispatch plugin event for error viewed
58
76
  RailsErrorDashboard::PluginRegistry.dispatch(:on_error_viewed, @error)
@@ -139,7 +157,7 @@ module RailsErrorDashboard
139
157
  @days = days
140
158
 
141
159
  # Use Query to get analytics data (pass application filter)
142
- analytics = Queries::AnalyticsStats.call(days, application_id: params[:application_id])
160
+ analytics = Queries::AnalyticsStats.call(days, application_id: @current_application_id)
143
161
 
144
162
  @error_stats = analytics[:error_stats]
145
163
  @errors_over_time = analytics[:errors_over_time]
@@ -151,18 +169,18 @@ module RailsErrorDashboard
151
169
  @mobile_errors = analytics[:mobile_errors]
152
170
  @api_errors = analytics[:api_errors]
153
171
 
154
- # Get recurring issues data
155
- recurring = Queries::RecurringIssues.call(days)
172
+ # Get recurring issues data (pass application filter)
173
+ recurring = Queries::RecurringIssues.call(days, application_id: @current_application_id)
156
174
  @recurring_data = recurring
157
175
 
158
- # Get release correlation data
159
- correlation = Queries::ErrorCorrelation.new(days: days)
176
+ # Get release correlation data (pass application filter)
177
+ correlation = Queries::ErrorCorrelation.new(days: days, application_id: @current_application_id)
160
178
  @errors_by_version = correlation.errors_by_version
161
179
  @problematic_releases = correlation.problematic_releases
162
180
  @release_comparison = calculate_release_comparison
163
181
 
164
- # Get MTTR data
165
- mttr_data = Queries::MttrStats.call(days)
182
+ # Get MTTR data (pass application filter)
183
+ mttr_data = Queries::MttrStats.call(days, application_id: @current_application_id)
166
184
  @mttr_stats = mttr_data
167
185
  @overall_mttr = mttr_data[:overall_mttr]
168
186
  @mttr_by_platform = mttr_data[:mttr_by_platform]
@@ -179,8 +197,8 @@ module RailsErrorDashboard
179
197
  days = (params[:days] || 7).to_i
180
198
  @days = days
181
199
 
182
- # Use Query to get platform comparison data
183
- comparison = Queries::PlatformComparison.new(days: days)
200
+ # Use Query to get platform comparison data (pass application filter)
201
+ comparison = Queries::PlatformComparison.new(days: days, application_id: @current_application_id)
184
202
 
185
203
  @error_rate_by_platform = comparison.error_rate_by_platform
186
204
  @severity_distribution = comparison.severity_distribution_by_platform
@@ -228,7 +246,7 @@ module RailsErrorDashboard
228
246
 
229
247
  days = (params[:days] || 30).to_i
230
248
  @days = days
231
- correlation = Queries::ErrorCorrelation.new(days: days)
249
+ correlation = Queries::ErrorCorrelation.new(days: days, application_id: @current_application_id)
232
250
 
233
251
  @errors_by_version = correlation.errors_by_version
234
252
  @errors_by_git_sha = correlation.errors_by_git_sha
@@ -266,24 +284,11 @@ module RailsErrorDashboard
266
284
  end
267
285
 
268
286
  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
- }
287
+ params.permit(*FILTERABLE_PARAMS).to_h.symbolize_keys
288
+ end
289
+
290
+ def set_application_context
291
+ @current_application_id = params[:application_id].presence
287
292
  end
288
293
 
289
294
  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" : "") +
@@ -12,22 +12,32 @@ module RailsErrorDashboard
12
12
  scope :ordered_by_name, -> { order(:name) }
13
13
 
14
14
  # Class method for finding or creating with caching
15
- # Only caches successful finds, not creates (to avoid caching nil on creation failures)
15
+ # Caches application IDs (not objects) to avoid stale ActiveRecord references
16
+ # This is safer with transactional fixtures and database rollbacks
16
17
  def self.find_or_create_by_name(name)
17
- # Try to find in cache or database first
18
- cached = Rails.cache.read("error_dashboard/application/#{name}")
19
- return cached if cached
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}")
27
+ end
20
28
 
21
- # Try to find existing
29
+ # Try to find existing in database
22
30
  found = find_by(name: name)
23
31
  if found
24
- Rails.cache.write("error_dashboard/application/#{name}", found, expires_in: 1.hour)
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)
25
34
  return found
26
35
  end
27
36
 
28
- # Create if not found (don't cache until successful)
37
+ # Create if not found
29
38
  created = create!(name: name)
30
- Rails.cache.write("error_dashboard/application/#{name}", created, expires_in: 1.hour)
39
+ # Cache the ID (not the object)
40
+ Rails.cache.write("error_dashboard/application_id/#{name}", created.id, expires_in: 1.hour)
31
41
  created
32
42
  end
33
43
 
@@ -390,11 +390,11 @@ module RailsErrorDashboard
390
390
  end
391
391
 
392
392
  # Find related errors of the same type
393
- def related_errors(limit: 5)
394
- self.class.where(error_type: error_type)
395
- .where.not(id: id)
396
- .order(occurred_at: :desc)
397
- .limit(limit)
393
+ def related_errors(limit: 5, application_id: nil)
394
+ scope = self.class.where(error_type: error_type)
395
+ .where.not(id: id)
396
+ scope = scope.where(application_id: application_id) if application_id.present?
397
+ scope.order(occurred_at: :desc).limit(limit)
398
398
  end
399
399
 
400
400
  # Extract backtrace frames for similarity comparison
@@ -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 %>
@@ -996,6 +1000,19 @@
996
1000
  </div>
997
1001
  </div>
998
1002
 
1003
+ <!-- Footer -->
1004
+ <footer class="mt-5 py-4 text-center border-top">
1005
+ <div class="container">
1006
+ <p class="text-muted mb-1">
1007
+ <i class="bi bi-bug-fill text-primary"></i>
1008
+ Built with <a href="https://github.com/AnjanJ/rails_error_dashboard" target="_blank" class="text-decoration-none">Rails Error Dashboard</a>
1009
+ </p>
1010
+ <p class="text-muted small mb-0">
1011
+ Created by <a href="https://www.anjan.dev/" target="_blank" class="text-decoration-none">Anjan Jagirdar</a>
1012
+ </p>
1013
+ </div>
1014
+ </footer>
1015
+
999
1016
  <!-- Bootstrap JS -->
1000
1017
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1001
1018
 
@@ -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
@@ -14,11 +14,13 @@ module RailsErrorDashboard
14
14
  # correlation.errors_by_version
15
15
  # # => { "1.0.0" => { count: 100, error_types: 15, critical_count: 5 } }
16
16
  class ErrorCorrelation
17
- attr_reader :days
17
+ attr_reader :days, :application_id
18
18
 
19
19
  # @param days [Integer] Number of days to analyze (default: 30)
20
- def initialize(days: 30)
20
+ # @param application_id [Integer, nil] Optional application ID to filter by
21
+ def initialize(days: 30, application_id: nil)
21
22
  @days = days
23
+ @application_id = application_id
22
24
  @start_date = days.days.ago
23
25
  end
24
26
 
@@ -285,7 +287,9 @@ module RailsErrorDashboard
285
287
  private
286
288
 
287
289
  def base_query
288
- ErrorLog.where("occurred_at >= ?", @start_date)
290
+ scope = ErrorLog.where("occurred_at >= ?", @start_date)
291
+ scope = scope.where(application_id: @application_id) if @application_id.present?
292
+ scope
289
293
  end
290
294
 
291
295
  # Check if app_version column exists
@@ -5,17 +5,29 @@ module RailsErrorDashboard
5
5
  # Query: Fetch available filter options
6
6
  # This is a read operation that returns distinct values for filters
7
7
  class FilterOptions
8
- def self.call
9
- new.call
8
+ def self.call(application_id: nil)
9
+ new(application_id: application_id).call
10
+ end
11
+
12
+ def initialize(application_id: nil)
13
+ @application_id = application_id
10
14
  end
11
15
 
12
16
  def call
13
17
  {
14
- error_types: ErrorLog.distinct.pluck(:error_type).compact.sort,
15
- platforms: ErrorLog.distinct.pluck(:platform).compact,
18
+ error_types: base_scope.distinct.pluck(:error_type).compact.sort,
19
+ platforms: base_scope.distinct.pluck(:platform).compact,
16
20
  applications: Application.ordered_by_name.pluck(:name, :id)
17
21
  }
18
22
  end
23
+
24
+ private
25
+
26
+ def base_scope
27
+ scope = ErrorLog.all
28
+ scope = scope.where(application_id: @application_id) if @application_id.present?
29
+ scope
30
+ end
19
31
  end
20
32
  end
21
33
  end
@@ -5,12 +5,13 @@ module RailsErrorDashboard
5
5
  # Query: Calculate Mean Time to Resolution (MTTR) statistics
6
6
  # Provides metrics on how quickly errors are resolved
7
7
  class MttrStats
8
- def self.call(days = 30)
9
- new(days).call
8
+ def self.call(days = 30, application_id: nil)
9
+ new(days, application_id: application_id).call
10
10
  end
11
11
 
12
- def initialize(days = 30)
12
+ def initialize(days = 30, application_id: nil)
13
13
  @days = days
14
+ @application_id = application_id
14
15
  @start_date = days.days.ago
15
16
  end
16
17
 
@@ -29,9 +30,13 @@ module RailsErrorDashboard
29
30
  private
30
31
 
31
32
  def resolved_errors
32
- @resolved_errors ||= ErrorLog
33
- .where.not(resolved_at: nil)
34
- .where("occurred_at >= ?", @start_date)
33
+ @resolved_errors ||= begin
34
+ scope = ErrorLog
35
+ .where.not(resolved_at: nil)
36
+ .where("occurred_at >= ?", @start_date)
37
+ scope = scope.where(application_id: @application_id) if @application_id.present?
38
+ scope
39
+ end
35
40
  end
36
41
 
37
42
  def calculate_overall_mttr
@@ -44,7 +49,9 @@ module RailsErrorDashboard
44
49
  end
45
50
 
46
51
  def mttr_by_platform
47
- platforms = ErrorLog.distinct.pluck(:platform).compact
52
+ platforms_scope = ErrorLog.all
53
+ platforms_scope = platforms_scope.where(application_id: @application_id) if @application_id.present?
54
+ platforms = platforms_scope.distinct.pluck(:platform).compact
48
55
 
49
56
  platforms.each_with_object({}) do |platform, result|
50
57
  platform_resolved = resolved_errors.where(platform: platform)
@@ -81,6 +88,7 @@ module RailsErrorDashboard
81
88
  week_resolved = ErrorLog
82
89
  .where.not(resolved_at: nil)
83
90
  .where("occurred_at >= ? AND occurred_at < ?", current_date, week_end)
91
+ week_resolved = week_resolved.where(application_id: @application_id) if @application_id.present?
84
92
 
85
93
  if week_resolved.any?
86
94
  total_hours = week_resolved.sum { |e| ((e.resolved_at - e.occurred_at) / 3600.0) }
@@ -17,18 +17,32 @@ module RailsErrorDashboard
17
17
  # comparison.error_rate_by_platform
18
18
  # # => { "ios" => 150, "android" => 200, "api" => 50, "web" => 100 }
19
19
  class PlatformComparison
20
- attr_reader :days
20
+ attr_reader :days, :application_id
21
21
 
22
22
  # @param days [Integer] Number of days to analyze (default: 7)
23
- def initialize(days: 7)
23
+ # @param application_id [Integer, nil] Optional application ID to filter by
24
+ def initialize(days: 7, application_id: nil)
24
25
  @days = days
26
+ @application_id = application_id
25
27
  @start_date = days.days.ago
26
28
  end
27
29
 
30
+ private
31
+
32
+ # Base scope that filters by application when present
33
+ # @return [ActiveRecord::Relation]
34
+ def base_scope
35
+ scope = ErrorLog.all
36
+ scope = scope.where(application_id: @application_id) if @application_id.present?
37
+ scope
38
+ end
39
+
40
+ public
41
+
28
42
  # Get error count by platform for the time period
29
43
  # @return [Hash] Platform name => error count
30
44
  def error_rate_by_platform
31
- ErrorLog
45
+ base_scope
32
46
  .where("occurred_at >= ?", @start_date)
33
47
  .group(:platform)
34
48
  .count
@@ -37,10 +51,10 @@ module RailsErrorDashboard
37
51
  # Get severity distribution by platform
38
52
  # @return [Hash] Platform => { severity => count }
39
53
  def severity_distribution_by_platform
40
- platforms = ErrorLog.distinct.pluck(:platform).compact
54
+ platforms = base_scope.distinct.pluck(:platform).compact
41
55
 
42
56
  platforms.each_with_object({}) do |platform, result|
43
- errors = ErrorLog
57
+ errors = base_scope
44
58
  .where(platform: platform)
45
59
  .where("occurred_at >= ?", @start_date)
46
60
 
@@ -57,10 +71,10 @@ module RailsErrorDashboard
57
71
  # Get average resolution time by platform
58
72
  # @return [Hash] Platform => average hours to resolve
59
73
  def resolution_time_by_platform
60
- platforms = ErrorLog.distinct.pluck(:platform).compact
74
+ platforms = base_scope.distinct.pluck(:platform).compact
61
75
 
62
76
  platforms.each_with_object({}) do |platform, result|
63
- resolved_errors = ErrorLog
77
+ resolved_errors = base_scope
64
78
  .where(platform: platform)
65
79
  .where.not(resolved_at: nil)
66
80
  .where("occurred_at >= ?", @start_date)
@@ -79,10 +93,10 @@ module RailsErrorDashboard
79
93
  # Get top 10 errors for each platform
80
94
  # @return [Hash] Platform => Array of error hashes
81
95
  def top_errors_by_platform
82
- platforms = ErrorLog.distinct.pluck(:platform).compact
96
+ platforms = base_scope.distinct.pluck(:platform).compact
83
97
 
84
98
  platforms.each_with_object({}) do |platform, result|
85
- result[platform] = ErrorLog
99
+ result[platform] = base_scope
86
100
  .where(platform: platform)
87
101
  .where("occurred_at >= ?", @start_date)
88
102
  .select(:id, :error_type, :message, :occurrence_count, :occurred_at)
@@ -105,7 +119,7 @@ module RailsErrorDashboard
105
119
  # Higher score = more stable (fewer errors, faster resolution)
106
120
  # @return [Hash] Platform => stability score
107
121
  def platform_stability_scores
108
- platforms = ErrorLog.distinct.pluck(:platform).compact
122
+ platforms = base_scope.distinct.pluck(:platform).compact
109
123
  error_rates = error_rate_by_platform
110
124
  resolution_times = resolution_time_by_platform
111
125
 
@@ -131,7 +145,7 @@ module RailsErrorDashboard
131
145
  # @return [Array<Hash>] Errors with their platforms
132
146
  def cross_platform_errors
133
147
  # Get error types that appear on 2+ platforms
134
- error_types_with_platforms = ErrorLog
148
+ error_types_with_platforms = base_scope
135
149
  .where("occurred_at >= ?", @start_date)
136
150
  .group(:error_type, :platform)
137
151
  .select(:error_type, :platform)
@@ -145,7 +159,7 @@ module RailsErrorDashboard
145
159
  .select { |_, platforms| platforms.map(&:last).uniq.count > 1 }
146
160
  .map do |error_type, platform_pairs|
147
161
  platforms = platform_pairs.map(&:last).uniq
148
- total_count = ErrorLog
162
+ total_count = base_scope
149
163
  .where(error_type: error_type, platform: platforms)
150
164
  .where("occurred_at >= ?", @start_date)
151
165
  .sum(:occurrence_count)
@@ -155,7 +169,7 @@ module RailsErrorDashboard
155
169
  platforms: platforms.sort,
156
170
  total_occurrences: total_count,
157
171
  platform_breakdown: platforms.each_with_object({}) do |platform, breakdown|
158
- breakdown[platform] = ErrorLog
172
+ breakdown[platform] = base_scope
159
173
  .where(error_type: error_type, platform: platform)
160
174
  .where("occurred_at >= ?", @start_date)
161
175
  .sum(:occurrence_count)
@@ -168,10 +182,10 @@ module RailsErrorDashboard
168
182
  # Get daily error trend by platform
169
183
  # @return [Hash] Platform => { date => count }
170
184
  def daily_trend_by_platform
171
- platforms = ErrorLog.distinct.pluck(:platform).compact
185
+ platforms = base_scope.distinct.pluck(:platform).compact
172
186
 
173
187
  platforms.each_with_object({}) do |platform, result|
174
- result[platform] = ErrorLog
188
+ result[platform] = base_scope
175
189
  .where(platform: platform)
176
190
  .where("occurred_at >= ?", @start_date)
177
191
  .group_by_day(:occurred_at, range: @start_date..Time.current)
@@ -182,7 +196,7 @@ module RailsErrorDashboard
182
196
  # Get platform health summary
183
197
  # @return [Hash] Platform => health metrics
184
198
  def platform_health_summary
185
- platforms = ErrorLog.distinct.pluck(:platform).compact
199
+ platforms = base_scope.distinct.pluck(:platform).compact
186
200
  error_rates = error_rate_by_platform
187
201
  stability_scores = platform_stability_scores
188
202
 
@@ -190,18 +204,18 @@ module RailsErrorDashboard
190
204
  total_errors = error_rates[platform] || 0
191
205
 
192
206
  # Count critical errors by checking severity method
193
- critical_errors = ErrorLog
207
+ critical_errors = base_scope
194
208
  .where(platform: platform)
195
209
  .where("occurred_at >= ?", @start_date)
196
210
  .select { |error| error.severity == :critical }
197
211
  .count
198
212
 
199
- unresolved_errors = ErrorLog
213
+ unresolved_errors = base_scope
200
214
  .where(platform: platform, resolved_at: nil)
201
215
  .where("occurred_at >= ?", @start_date)
202
216
  .count
203
217
 
204
- resolved_errors = ErrorLog
218
+ resolved_errors = base_scope
205
219
  .where(platform: platform)
206
220
  .where.not(resolved_at: nil)
207
221
  .where("occurred_at >= ?", @start_date)
@@ -210,12 +224,12 @@ module RailsErrorDashboard
210
224
  resolution_rate = total_errors.positive? ? ((resolved_errors.to_f / total_errors) * 100).round(1) : 0.0
211
225
 
212
226
  # Calculate error velocity (increasing or decreasing)
213
- first_half = ErrorLog
227
+ first_half = base_scope
214
228
  .where(platform: platform)
215
229
  .where("occurred_at >= ? AND occurred_at < ?", @start_date, @start_date + (@days / 2.0).days)
216
230
  .count
217
231
 
218
- second_half = ErrorLog
232
+ second_half = base_scope
219
233
  .where(platform: platform)
220
234
  .where("occurred_at >= ?", @start_date + (@days / 2.0).days)
221
235
  .count
@@ -5,12 +5,13 @@ module RailsErrorDashboard
5
5
  # Query: Analyze recurring and persistent errors
6
6
  # Returns data about high-frequency errors, persistent issues, and cyclical patterns
7
7
  class RecurringIssues
8
- def self.call(days = 30)
9
- new(days).call
8
+ def self.call(days = 30, application_id: nil)
9
+ new(days, application_id: application_id).call
10
10
  end
11
11
 
12
- def initialize(days = 30)
12
+ def initialize(days = 30, application_id: nil)
13
13
  @days = days
14
+ @application_id = application_id
14
15
  @start_date = days.days.ago
15
16
  end
16
17
 
@@ -25,7 +26,9 @@ module RailsErrorDashboard
25
26
  private
26
27
 
27
28
  def base_query
28
- ErrorLog.where("occurred_at >= ?", @start_date)
29
+ scope = ErrorLog.where("occurred_at >= ?", @start_date)
30
+ scope = scope.where(application_id: @application_id) if @application_id.present?
31
+ scope
29
32
  end
30
33
 
31
34
  def high_frequency_errors
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.1.23"
2
+ VERSION = "0.1.25"
3
3
  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.23
4
+ version: 0.1.25
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.23\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
389
+ \ Rails Error Dashboard v0.1.25\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