rails_error_dashboard 0.1.15 → 0.1.17

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: da18321511a53167fa248ae72974c6c15807a725396aabd27af1059296ecbc46
4
- data.tar.gz: 84f348d386b6b07375797275dedf97e5a9504522270447bebd6b56307f5e4a7d
3
+ metadata.gz: 66993309911427e7fdbddadebfea4a8603fa851ce47c7d5ac6189510c741965b
4
+ data.tar.gz: 86d88a89e817adcb790a9b83791decfaa13ffeac4d1b744b846d072e6610ebb5
5
5
  SHA512:
6
- metadata.gz: b5b3d045f7a0b7bcae61823aa57e373111b6dab59e5a651ae5a33df9a3f043598e6469195b9cb508e79ad6bda62182877572888dba3cea37a4039715eee79349
7
- data.tar.gz: 460491a0614445e91f098dbb792d86d9d7c8616f0456ad88cd6b14fdf27aba4d87990b080bd6c289eafe03befccceec94b6b23dcce29d850fd05b3bda946d39d
6
+ metadata.gz: 6581ea39ea6340b0df0dcb9835c597367f5c37459462f3ebe6f79315f99771ade90770fd04605477edef545b75aef32ae6d04acffe4caf896390b219ae11380e
7
+ data.tar.gz: 1dce7070b16addd615deb960d93e3226a448cb776526a17d1f353a1fdd8d25e90c8a2916bba21482b23637bcebf5a34c153b9227113ea6a23eeba79891f6acd7
@@ -2,6 +2,12 @@ module RailsErrorDashboard
2
2
  class ApplicationController < ActionController::Base
3
3
  include Pagy::Backend
4
4
 
5
+ # Enable features that are disabled in API-only mode
6
+ # These are ONLY enabled for Error Dashboard routes, not the entire app
7
+ include ActionController::Cookies
8
+ include ActionController::Flash
9
+ include ActionController::RequestForgeryProtection
10
+
5
11
  layout "rails_error_dashboard"
6
12
 
7
13
  protect_from_forgery with: :exception
@@ -518,7 +518,9 @@ module RailsErrorDashboard
518
518
 
519
519
  # Turbo Stream broadcasting methods
520
520
  def broadcast_new_error
521
+ # Skip broadcasting in API-only mode or if Turbo is not available
521
522
  return unless defined?(Turbo)
523
+ return unless broadcast_available?
522
524
 
523
525
  platforms = ErrorLog.distinct.pluck(:platform).compact
524
526
  show_platform = platforms.size > 1
@@ -531,11 +533,14 @@ module RailsErrorDashboard
531
533
  )
532
534
  broadcast_replace_stats
533
535
  rescue => e
534
- Rails.logger.error("Failed to broadcast new error: #{e.message}")
536
+ Rails.logger.error("[RailsErrorDashboard] Failed to broadcast new error: #{e.class} - #{e.message}")
537
+ Rails.logger.debug("[RailsErrorDashboard] Backtrace: #{e.backtrace&.first(3)&.join("\n")}")
535
538
  end
536
539
 
537
540
  def broadcast_error_update
541
+ # Skip broadcasting in API-only mode or if Turbo is not available
538
542
  return unless defined?(Turbo)
543
+ return unless broadcast_available?
539
544
 
540
545
  platforms = ErrorLog.distinct.pluck(:platform).compact
541
546
  show_platform = platforms.size > 1
@@ -548,13 +553,19 @@ module RailsErrorDashboard
548
553
  )
549
554
  broadcast_replace_stats
550
555
  rescue => e
551
- Rails.logger.error("Failed to broadcast error update: #{e.message}")
556
+ Rails.logger.error("[RailsErrorDashboard] Failed to broadcast error update: #{e.class} - #{e.message}")
557
+ Rails.logger.debug("[RailsErrorDashboard] Backtrace: #{e.backtrace&.first(3)&.join("\n")}")
552
558
  end
553
559
 
554
560
  def broadcast_replace_stats
561
+ # Skip broadcasting in API-only mode or if Turbo is not available
555
562
  return unless defined?(Turbo)
563
+ return unless broadcast_available?
556
564
 
557
565
  stats = Queries::DashboardStats.call
566
+ # Safety check: ensure stats is not nil before broadcasting
567
+ return unless stats.is_a?(Hash) && stats.present?
568
+
558
569
  Turbo::StreamsChannel.broadcast_replace_to(
559
570
  "error_list",
560
571
  target: "dashboard_stats",
@@ -562,7 +573,26 @@ module RailsErrorDashboard
562
573
  locals: { stats: stats }
563
574
  )
564
575
  rescue => e
565
- Rails.logger.error("Failed to broadcast stats update: #{e.message}")
576
+ Rails.logger.error("[RailsErrorDashboard] Failed to broadcast stats update: #{e.class} - #{e.message}")
577
+ Rails.logger.debug("[RailsErrorDashboard] Backtrace: #{e.backtrace&.first(3)&.join("\n")}")
578
+ end
579
+
580
+ # Check if broadcast functionality is available and properly configured
581
+ # In API-only apps, ActionCable might not be configured or Rails.cache might not be available
582
+ def broadcast_available?
583
+ # Check if ActionCable is available (required for Turbo Streams)
584
+ return false unless defined?(ActionCable)
585
+
586
+ # Check if Rails.cache is configured and working
587
+ # This prevents errors when cache is not available in API-only mode
588
+ begin
589
+ Rails.cache.write("rails_error_dashboard_broadcast_test", true, expires_in: 1.second)
590
+ Rails.cache.delete("rails_error_dashboard_broadcast_test")
591
+ true
592
+ rescue => e
593
+ Rails.logger.debug("[RailsErrorDashboard] Broadcast not available: #{e.message}")
594
+ false
595
+ end
566
596
  end
567
597
 
568
598
  # Enhanced Metrics: Release/Version Tracking
@@ -3,8 +3,12 @@
3
3
  <head>
4
4
  <title><%= content_for?(:page_title) ? "#{content_for(:page_title)} | " : "" %><%= Rails.application.class.module_parent_name %> - Error Dashboard</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <%= csrf_meta_tags %>
7
- <%= csp_meta_tag %>
6
+ <% if respond_to?(:csrf_meta_tags) %>
7
+ <%= csrf_meta_tags %>
8
+ <% end %>
9
+ <% if respond_to?(:csp_meta_tag) %>
10
+ <%= csp_meta_tag %>
11
+ <% end %>
8
12
 
9
13
  <!-- Bootstrap CSS -->
10
14
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
@@ -1170,17 +1174,19 @@
1170
1174
  };
1171
1175
 
1172
1176
  // Show flash messages as toasts
1173
- <% if flash[:notice] %>
1174
- showToast('<%= j flash[:notice] %>', 'success');
1175
- <% end %>
1176
- <% if flash[:alert] %>
1177
- showToast('<%= j flash[:alert] %>', 'danger');
1178
- <% end %>
1179
- <% if flash[:success] %>
1180
- showToast('<%= j flash[:success] %>', 'success');
1181
- <% end %>
1182
- <% if flash[:error] %>
1183
- showToast('<%= j flash[:error] %>', 'danger');
1177
+ <% if defined?(flash) && flash.present? %>
1178
+ <% if flash[:notice] %>
1179
+ showToast('<%= j flash[:notice] %>', 'success');
1180
+ <% end %>
1181
+ <% if flash[:alert] %>
1182
+ showToast('<%= j flash[:alert] %>', 'danger');
1183
+ <% end %>
1184
+ <% if flash[:success] %>
1185
+ showToast('<%= j flash[:success] %>', 'success');
1186
+ <% end %>
1187
+ <% if flash[:error] %>
1188
+ showToast('<%= j flash[:error] %>', 'danger');
1189
+ <% end %>
1184
1190
  <% end %>
1185
1191
  });
1186
1192
  </script>
@@ -131,14 +131,29 @@
131
131
  <script>
132
132
  document.addEventListener('DOMContentLoaded', function() {
133
133
  const colors = window.getChartColors();
134
+ // Distinct, accessible colors for 5 platforms with good contrast
135
+ const platformColors = [
136
+ "#2563EB", // Blue - API
137
+ "#10B981", // Green - Android
138
+ "#F59E0B", // Amber - Background Jobs
139
+ "#8B5CF6", // Purple - Web
140
+ "#EF4444" // Red - iOS
141
+ ];
142
+
134
143
  new Chartkick.PieChart("errors-by-platform-chart", <%= raw @errors_by_platform.to_json %>, {
135
- colors: ["#000000", "#3DDC84", "#3B82F6"],
144
+ colors: platformColors,
136
145
  height: "300px",
137
146
  legend: "bottom",
138
147
  donut: true,
139
148
  library: {
140
149
  plugins: {
141
- legend: { labels: { color: colors.textColor } }
150
+ legend: {
151
+ labels: {
152
+ color: colors.textColor,
153
+ font: { size: 12, weight: 'bold' }
154
+ }
155
+ }
156
+ // Tooltip uses global theme-aware defaults from layout
142
157
  }
143
158
  }
144
159
  });
@@ -291,6 +291,19 @@
291
291
  </div>
292
292
 
293
293
  <script>
294
+ // Platform color mapping - consistent with analytics page
295
+ const platformColorMap = {
296
+ 'api': '#2563EB', // Blue
297
+ 'android': '#10B981', // Green
298
+ 'background_jobs': '#F59E0B', // Amber
299
+ 'web': '#8B5CF6', // Purple
300
+ 'ios': '#EF4444' // Red
301
+ };
302
+
303
+ function getPlatformColor(platform) {
304
+ return platformColorMap[platform.toLowerCase()] || '#6B7280'; // Gray fallback
305
+ }
306
+
294
307
  // Error Rate Chart
295
308
  const errorRateCtx = document.getElementById('errorRateChart');
296
309
  if (errorRateCtx) {
@@ -301,16 +314,8 @@
301
314
  datasets: [{
302
315
  label: 'Total Errors',
303
316
  data: <%= raw @error_rate_by_platform.values.to_json %>,
304
- backgroundColor: (function() {
305
- const isDark = document.body.classList.contains('dark-mode');
306
- const config = getCatppuccinChartConfig(isDark);
307
- return config.colors.blue.replace('rgb', 'rgba').replace(')', ', 0.5)');
308
- })(),
309
- borderColor: (function() {
310
- const isDark = document.body.classList.contains('dark-mode');
311
- const config = getCatppuccinChartConfig(isDark);
312
- return config.colors.blue;
313
- })(),
317
+ backgroundColor: 'rgba(37, 99, 235, 0.5)', // Blue with transparency
318
+ borderColor: '#2563EB', // Blue
314
319
  borderWidth: 1
315
320
  }]
316
321
  },
@@ -330,9 +335,6 @@
330
335
  // Daily Trend Chart
331
336
  const dailyTrendCtx = document.getElementById('dailyTrendChart');
332
337
  if (dailyTrendCtx) {
333
- // Use Catppuccin colors from global theme config
334
- const isDark = document.body.classList.contains('dark-mode');
335
-
336
338
  const datasets = <%= raw @daily_trends.map { |platform, data|
337
339
  {
338
340
  label: platform.to_s.capitalize,
@@ -342,12 +344,12 @@
342
344
  }
343
345
  }.to_json %>;
344
346
 
345
- // Apply Catppuccin platform colors
347
+ // Apply platform-specific colors
346
348
  datasets.forEach(ds => {
347
- const color = getPlatformColor(ds.platform, isDark);
349
+ const color = getPlatformColor(ds.platform);
348
350
  ds.borderColor = color;
349
351
  // Add transparency for background
350
- ds.backgroundColor = color.replace('rgb', 'rgba').replace(')', ', 0.1)');
352
+ ds.backgroundColor = color + '1A'; // Add 10% alpha in hex
351
353
  });
352
354
 
353
355
  new Chart(dailyTrendCtx, {
@@ -382,16 +384,8 @@
382
384
  datasets: [{
383
385
  label: 'Hours to Resolve',
384
386
  data: times,
385
- backgroundColor: (function() {
386
- const isDark = document.body.classList.contains('dark-mode');
387
- const config = getCatppuccinChartConfig(isDark);
388
- return config.colors.orange.replace('rgb', 'rgba').replace(')', ', 0.5)');
389
- })(),
390
- borderColor: (function() {
391
- const isDark = document.body.classList.contains('dark-mode');
392
- const config = getCatppuccinChartConfig(isDark);
393
- return config.colors.orange;
394
- })(),
387
+ backgroundColor: 'rgba(245, 158, 11, 0.5)', // Amber with transparency
388
+ borderColor: '#F59E0B', // Amber
395
389
  borderWidth: 1
396
390
  }]
397
391
  },
@@ -30,9 +30,6 @@
30
30
  <button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON()" title="Download error details as JSON">
31
31
  <i class="bi bi-download"></i> Export JSON
32
32
  </button>
33
- <button type="button" class="btn btn-outline-primary" onclick="copyErrorURL()" title="Copy shareable link to this error">
34
- <i class="bi bi-share"></i> Share
35
- </button>
36
33
  <% if @error.resolved? %>
37
34
  <span class="badge bg-success fs-6">
38
35
  <i class="bi bi-check-circle"></i> Resolved
@@ -46,26 +43,6 @@
46
43
  </div>
47
44
 
48
45
  <script>
49
- function copyErrorURL() {
50
- const url = window.location.href;
51
- navigator.clipboard.writeText(url).then(() => {
52
- // Change button text temporarily
53
- const button = event.currentTarget;
54
- const originalHTML = button.innerHTML;
55
- button.innerHTML = '<i class="bi bi-check"></i> Copied!';
56
- button.classList.remove('btn-outline-primary');
57
- button.classList.add('btn-success');
58
-
59
- setTimeout(() => {
60
- button.innerHTML = originalHTML;
61
- button.classList.remove('btn-success');
62
- button.classList.add('btn-outline-primary');
63
- }, 2000);
64
- }).catch(err => {
65
- alert('Failed to copy URL: ' + err);
66
- });
67
- }
68
-
69
46
  function downloadErrorJSON() {
70
47
  const errorData = {
71
48
  id: <%= @error.id %>,
@@ -4,6 +4,15 @@ module RailsErrorDashboard
4
4
 
5
5
  # Initialize the engine
6
6
  initializer "rails_error_dashboard.middleware" do |app|
7
+ # Enable Flash middleware for Error Dashboard routes in API-only apps
8
+ # This ensures flash messages work even when config.api_only = true
9
+ if app.config.api_only
10
+ # Insert Flash middleware ONLY for Error Dashboard routes
11
+ app.middleware.use ActionDispatch::Flash
12
+ app.middleware.use ActionDispatch::Cookies
13
+ app.middleware.use ActionDispatch::Session::CookieStore
14
+ end
15
+
7
16
  # Add error catching middleware if enabled
8
17
  if RailsErrorDashboard.configuration.enable_middleware
9
18
  app.config.middleware.insert_before 0, RailsErrorDashboard::Middleware::ErrorCatcher
@@ -12,28 +12,57 @@ module RailsErrorDashboard
12
12
  def call
13
13
  # Cache dashboard stats for 1 minute to reduce database load
14
14
  # Dashboard is viewed frequently, so short cache prevents stale data
15
- Rails.cache.fetch(cache_key, expires_in: 1.minute) do
15
+ begin
16
+ Rails.cache.fetch(cache_key, expires_in: 1.minute) do
17
+ {
18
+ total_today: ErrorLog.where("occurred_at >= ?", Time.current.beginning_of_day).count,
19
+ total_week: ErrorLog.where("occurred_at >= ?", 7.days.ago).count,
20
+ total_month: ErrorLog.where("occurred_at >= ?", 30.days.ago).count,
21
+ unresolved: ErrorLog.unresolved.count,
22
+ resolved: ErrorLog.resolved.count,
23
+ by_platform: ErrorLog.group(:platform).count,
24
+ top_errors: top_errors,
25
+ # Trend visualizations
26
+ errors_trend_7d: errors_trend_7d,
27
+ errors_by_severity_7d: errors_by_severity_7d,
28
+ spike_detected: spike_detected?,
29
+ spike_info: spike_info,
30
+ # New metrics for Overview dashboard
31
+ error_rate: error_rate,
32
+ affected_users_today: affected_users_today,
33
+ affected_users_yesterday: affected_users_yesterday,
34
+ affected_users_change: affected_users_change,
35
+ trend_percentage: trend_percentage,
36
+ trend_direction: trend_direction,
37
+ top_errors_by_impact: top_errors_by_impact
38
+ }
39
+ end
40
+ rescue => e
41
+ # If Rails.cache or any stats query fails, return empty stats hash
42
+ # This prevents broadcast failures in API-only mode or when cache is unavailable
43
+ Rails.logger.error("[RailsErrorDashboard] DashboardStats failed: #{e.class} - #{e.message}")
44
+ Rails.logger.debug("[RailsErrorDashboard] Backtrace: #{e.backtrace&.first(3)&.join("\n")}")
45
+
46
+ # Return minimal stats hash to prevent nil errors in views
16
47
  {
17
- total_today: ErrorLog.where("occurred_at >= ?", Time.current.beginning_of_day).count,
18
- total_week: ErrorLog.where("occurred_at >= ?", 7.days.ago).count,
19
- total_month: ErrorLog.where("occurred_at >= ?", 30.days.ago).count,
20
- unresolved: ErrorLog.unresolved.count,
21
- resolved: ErrorLog.resolved.count,
22
- by_platform: ErrorLog.group(:platform).count,
23
- top_errors: top_errors,
24
- # Trend visualizations
25
- errors_trend_7d: errors_trend_7d,
26
- errors_by_severity_7d: errors_by_severity_7d,
27
- spike_detected: spike_detected?,
28
- spike_info: spike_info,
29
- # New metrics for Overview dashboard
30
- error_rate: error_rate,
31
- affected_users_today: affected_users_today,
32
- affected_users_yesterday: affected_users_yesterday,
33
- affected_users_change: affected_users_change,
34
- trend_percentage: trend_percentage,
35
- trend_direction: trend_direction,
36
- top_errors_by_impact: top_errors_by_impact
48
+ total_today: 0,
49
+ total_week: 0,
50
+ total_month: 0,
51
+ unresolved: 0,
52
+ resolved: 0,
53
+ by_platform: {},
54
+ top_errors: {},
55
+ errors_trend_7d: {},
56
+ errors_by_severity_7d: { critical: 0, high: 0, medium: 0, low: 0 },
57
+ spike_detected: false,
58
+ spike_info: nil,
59
+ error_rate: 0.0,
60
+ affected_users_today: 0,
61
+ affected_users_yesterday: 0,
62
+ affected_users_change: 0,
63
+ trend_percentage: 0.0,
64
+ trend_direction: :stable,
65
+ top_errors_by_impact: []
37
66
  }
38
67
  end
39
68
  end
@@ -46,7 +46,16 @@ module RailsErrorDashboard
46
46
  end
47
47
 
48
48
  def build_request_url
49
- return @context[:request]&.fullpath if @context[:request]
49
+ # Handle both full Rails requests and API-only requests
50
+ if @context[:request]
51
+ begin
52
+ return @context[:request].fullpath
53
+ rescue NoMethodError
54
+ # Fallback for minimal request objects
55
+ return @context[:request].path rescue nil
56
+ end
57
+ end
58
+
50
59
  return @context[:request_url] if @context[:request_url]
51
60
  return "Background Job: #{@context[:job]&.class}" if @context[:job]
52
61
  return "Sidekiq: #{@context[:job_class]}" if @context[:job_class]
@@ -114,10 +123,17 @@ module RailsErrorDashboard
114
123
  def detect_platform
115
124
  # Check if it's from a mobile request
116
125
  user_agent = extract_user_agent
117
- return Services::PlatformDetector.detect(user_agent) if @context[:request]
118
126
 
119
- # Everything else is API/backend
120
- "API"
127
+ return "API" unless user_agent.present? && @context[:request]
128
+
129
+ # Only detect platform if we have a valid user agent
130
+ begin
131
+ Services::PlatformDetector.detect(user_agent)
132
+ rescue => e
133
+ # Fallback to API if platform detection fails
134
+ Rails.logger.debug("[RailsErrorDashboard] Platform detection failed: #{e.message}")
135
+ "API"
136
+ end
121
137
  end
122
138
 
123
139
  def extract_controller_name
@@ -161,8 +177,8 @@ module RailsErrorDashboard
161
177
  end
162
178
 
163
179
  def extract_session_id
164
- # From Rails session
165
- return @context[:request]&.session&.id if @context[:request]&.session
180
+ # Session is only available in full Rails mode, not API-only
181
+ return @context[:request]&.session&.id if @context[:request]&.respond_to?(:session) && @context[:request]&.session
166
182
 
167
183
  # From explicit context
168
184
  return @context[:session_id] if @context[:session_id]
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.1.15"
2
+ VERSION = "0.1.17"
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.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -379,7 +379,7 @@ metadata:
379
379
  source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
380
380
  changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
381
381
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
382
- \ Rails Error Dashboard v0.1.15\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
382
+ \ Rails Error Dashboard v0.1.17\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
383
383
  First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
384
384
  db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
385
385
  => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n