rails_error_dashboard 0.1.29 → 0.1.30

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -6
  3. data/app/controllers/rails_error_dashboard/errors_controller.rb +22 -0
  4. data/app/helpers/rails_error_dashboard/application_helper.rb +79 -7
  5. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +149 -0
  6. data/app/models/rails_error_dashboard/application.rb +1 -1
  7. data/app/models/rails_error_dashboard/error_log.rb +44 -16
  8. data/app/views/layouts/rails_error_dashboard.html.erb +66 -1237
  9. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +10 -2
  10. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +76 -0
  11. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +18 -82
  12. data/app/views/rails_error_dashboard/errors/index.html.erb +64 -31
  13. data/app/views/rails_error_dashboard/errors/overview.html.erb +181 -3
  14. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +2 -1
  15. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +286 -0
  16. data/app/views/rails_error_dashboard/errors/settings.html.erb +146 -480
  17. data/app/views/rails_error_dashboard/errors/show.html.erb +44 -20
  18. data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +188 -0
  19. data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +5 -0
  20. data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +3 -0
  21. data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +3 -0
  22. data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +4 -0
  23. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +3 -0
  24. data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +3 -0
  25. data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +3 -0
  26. data/db/migrate/20251225100236_create_error_occurrences.rb +3 -0
  27. data/db/migrate/20251225101920_create_cascade_patterns.rb +3 -0
  28. data/db/migrate/20251225102500_create_error_baselines.rb +3 -0
  29. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +3 -0
  30. data/db/migrate/20251226020100_create_error_comments.rb +3 -0
  31. data/db/migrate/20251229111223_add_additional_performance_indexes.rb +4 -0
  32. data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +3 -0
  33. data/db/migrate/20260106094233_add_application_to_error_logs.rb +3 -0
  34. data/db/migrate/20260106094318_finalize_application_foreign_key.rb +5 -0
  35. data/lib/generators/rails_error_dashboard/install/install_generator.rb +32 -0
  36. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +37 -4
  37. data/lib/rails_error_dashboard/configuration.rb +160 -3
  38. data/lib/rails_error_dashboard/configuration_error.rb +24 -0
  39. data/lib/rails_error_dashboard/engine.rb +17 -0
  40. data/lib/rails_error_dashboard/helpers/user_model_detector.rb +138 -0
  41. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +19 -4
  42. data/lib/rails_error_dashboard/queries/errors_list.rb +20 -8
  43. data/lib/rails_error_dashboard/services/error_normalizer.rb +143 -0
  44. data/lib/rails_error_dashboard/services/git_blame_reader.rb +195 -0
  45. data/lib/rails_error_dashboard/services/github_link_generator.rb +159 -0
  46. data/lib/rails_error_dashboard/services/source_code_reader.rb +214 -0
  47. data/lib/rails_error_dashboard/version.rb +1 -1
  48. data/lib/rails_error_dashboard.rb +6 -0
  49. metadata +13 -10
  50. data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +0 -107
  51. data/app/assets/stylesheets/rails_error_dashboard/_components.scss +0 -625
  52. data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +0 -257
  53. data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +0 -203
  54. data/app/assets/stylesheets/rails_error_dashboard/application.css +0 -15
  55. data/app/assets/stylesheets/rails_error_dashboard/application.css.map +0 -7
  56. data/app/assets/stylesheets/rails_error_dashboard/application.scss +0 -61
  57. data/app/views/layouts/rails_error_dashboard/application.html.erb +0 -55
@@ -160,9 +160,35 @@
160
160
  </div>
161
161
  </div>
162
162
  <div class="card-body p-0">
163
- <div class="code-block p-3" style="max-height: 300px; overflow-y: auto; overflow-x: auto;">
164
- <pre class="mb-0"><code><% app_frames.each do |frame| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="text-info">`<%= frame[:method_name] %>'</span></span>
165
- <% end %></code></pre>
163
+ <div class="code-block p-3" style="max-height: none; overflow-y: visible; overflow-x: auto;">
164
+ <% app_frames.each_with_index do |frame, index| %>
165
+ <div class="backtrace-frame-wrapper mb-2">
166
+ <!-- Frame header (always visible) -->
167
+ <div class="<%= frame_bg_class(frame[:category]) %> d-flex justify-content-between align-items-center px-2 py-1">
168
+ <div>
169
+ <span class="<%= frame_color_class(frame[:category]) %>">
170
+ <%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %>
171
+ </span> in <span class="backtrace-method-name">`<%= frame[:method_name] %>'</span>
172
+ </div>
173
+ <% if RailsErrorDashboard.configuration.enable_source_code_integration && frame[:category] == :app %>
174
+ <button class="btn btn-sm btn-outline-secondary" type="button"
175
+ data-bs-toggle="collapse"
176
+ data-bs-target="#source-code-<%= index %>"
177
+ aria-expanded="false"
178
+ aria-controls="source-code-<%= index %>">
179
+ <i class="bi bi-code-slash"></i> <span class="d-none d-md-inline">View Source</span>
180
+ </button>
181
+ <% end %>
182
+ </div>
183
+
184
+ <!-- Collapsible source code (only for app frames) -->
185
+ <% if RailsErrorDashboard.configuration.enable_source_code_integration && frame[:category] == :app %>
186
+ <div class="collapse" id="source-code-<%= index %>">
187
+ <%= render "source_code", frame: frame, error: @error, index: index %>
188
+ </div>
189
+ <% end %>
190
+ </div>
191
+ <% end %>
166
192
  </div>
167
193
  </div>
168
194
  </div>
@@ -183,7 +209,7 @@
183
209
  <div id="frameworkBacktrace" class="accordion-collapse collapse" data-bs-parent="#frameworkBacktraceAccordion">
184
210
  <div class="accordion-body p-0">
185
211
  <div class="code-block p-3" style="max-height: 400px; overflow-y: auto; overflow-x: auto;">
186
- <pre class="mb-0"><code><% framework_frames.each do |frame| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="text-info">`<%= frame[:method_name] %>'</span></span>
212
+ <pre class="mb-0"><code><% framework_frames.each do |frame| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="backtrace-method-name">`<%= frame[:method_name] %>'</span></span>
187
213
  <% end %></code></pre>
188
214
  </div>
189
215
  </div>
@@ -424,8 +450,8 @@
424
450
  <!-- Existing Comments -->
425
451
  <% if @error.comments.recent_first.any? %>
426
452
  <div class="mb-4">
427
- <% @error.comments.recent_first.each do |comment| %>
428
- <div class="border-bottom pb-3 mb-3">
453
+ <% @error.comments.recent_first.each_with_index do |comment, index| %>
454
+ <div class="<%= index < @error.comments.count - 1 ? 'border-bottom' : '' %> pb-3 mb-3">
429
455
  <div class="d-flex justify-content-between align-items-start mb-2">
430
456
  <div>
431
457
  <strong class="text-primary">
@@ -441,7 +467,7 @@
441
467
  </small>
442
468
  </div>
443
469
  <div class="text-break">
444
- <%= simple_format(comment.body, class: "mb-0") %>
470
+ <%= auto_link_urls(comment.body, error: @error).html_safe %>
445
471
  </div>
446
472
  </div>
447
473
  <% end %>
@@ -453,7 +479,7 @@
453
479
  <% end %>
454
480
 
455
481
  <!-- Add Comment Form -->
456
- <div class="border-top pt-3">
482
+ <div class="<%= @error.comments.any? ? '' : 'border-top' %> pt-3">
457
483
  <h6 class="mb-3">
458
484
  <i class="bi bi-plus-circle"></i> Add Comment
459
485
  </h6>
@@ -779,7 +805,7 @@
779
805
  </small>
780
806
  <% end %>
781
807
  </div>
782
- <%= button_to unassign_error_path(@error), method: :patch, class: "btn btn-sm btn-outline-secondary",
808
+ <%= button_to unassign_error_path(@error), method: :post, class: "btn btn-sm btn-outline-secondary",
783
809
  data: { turbo_confirm: "Remove assignment?" } do %>
784
810
  <i class="bi bi-x"></i>
785
811
  <% end %>
@@ -813,7 +839,7 @@
813
839
  <strong>Snoozed</strong><br>
814
840
  <small>Until <%= local_time(@error.snoozed_until, format: :datetime) %></small>
815
841
  </div>
816
- <%= button_to unsnooze_error_path(@error), method: :patch, class: "btn btn-sm btn-outline-warning" do %>
842
+ <%= button_to unsnooze_error_path(@error), method: :post, class: "btn btn-sm btn-outline-warning" do %>
817
843
  <i class="bi bi-alarm-fill"></i> Unsnooze
818
844
  <% end %>
819
845
  <% else %>
@@ -842,7 +868,7 @@
842
868
  <div class="mb-3">
843
869
  <small class="text-muted d-block mb-1">Resolution Notes</small>
844
870
  <div class="alert alert-success mb-0">
845
- <%= simple_format(@error.resolution_comment) %>
871
+ <%= auto_link_urls(@error.resolution_comment, error: @error).html_safe %>
846
872
  </div>
847
873
  </div>
848
874
  <% end %>
@@ -1068,7 +1094,7 @@
1068
1094
  <div class="modal fade" id="assignModal" tabindex="-1" aria-labelledby="assignModalLabel" aria-hidden="true">
1069
1095
  <div class="modal-dialog">
1070
1096
  <div class="modal-content">
1071
- <%= form_with url: assign_error_path(@error), method: :patch do |f| %>
1097
+ <%= form_with url: assign_error_path(@error), method: :post do |f| %>
1072
1098
  <div class="modal-header">
1073
1099
  <h5 class="modal-title" id="assignModalLabel">
1074
1100
  <i class="bi bi-person-plus"></i> Assign Error
@@ -1095,7 +1121,7 @@
1095
1121
  <div class="modal fade" id="priorityModal" tabindex="-1" aria-labelledby="priorityModalLabel" aria-hidden="true">
1096
1122
  <div class="modal-dialog">
1097
1123
  <div class="modal-content">
1098
- <%= form_with url: update_priority_error_path(@error), method: :patch do |f| %>
1124
+ <%= form_with url: update_priority_error_path(@error), method: :post do |f| %>
1099
1125
  <div class="modal-header">
1100
1126
  <h5 class="modal-title" id="priorityModalLabel">
1101
1127
  <i class="bi bi-flag"></i> Update Priority
@@ -1106,12 +1132,10 @@
1106
1132
  <div class="mb-3">
1107
1133
  <label for="priority_level" class="form-label">Priority Level <span class="text-danger">*</span></label>
1108
1134
  <%= select_tag :priority_level,
1109
- options_for_select([
1110
- ['Critical (P3)', 3],
1111
- ['High (P2)', 2],
1112
- ['Medium (P1)', 1],
1113
- ['Low (P0)', 0]
1114
- ], @error.priority_level),
1135
+ options_for_select(
1136
+ RailsErrorDashboard::ErrorLog.priority_options,
1137
+ @error.priority_level
1138
+ ),
1115
1139
  class: "form-select", required: true %>
1116
1140
  <small class="text-muted">Current: <%= @error.priority_label %></small>
1117
1141
  </div>
@@ -1129,7 +1153,7 @@
1129
1153
  <div class="modal fade" id="snoozeModal" tabindex="-1" aria-labelledby="snoozeModalLabel" aria-hidden="true">
1130
1154
  <div class="modal-dialog">
1131
1155
  <div class="modal-content">
1132
- <%= form_with url: snooze_error_path(@error), method: :patch do |f| %>
1156
+ <%= form_with url: snooze_error_path(@error), method: :post do |f| %>
1133
1157
  <div class="modal-header">
1134
1158
  <h5 class="modal-title" id="snoozeModalLabel">
1135
1159
  <i class="bi bi-alarm"></i> Snooze Error
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Squashed migration for new installations
4
+ # This migration creates the complete Rails Error Dashboard schema in one step.
5
+ #
6
+ # For existing installations, the incremental migrations (20251224-20260106) will run instead.
7
+ # Detection: If error_logs table already exists, this migration is skipped.
8
+ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
9
+ def up
10
+ # Skip if this is an existing installation (error_logs table already exists)
11
+ # Existing installations will use incremental migrations instead
12
+ return if table_exists?(:rails_error_dashboard_error_logs)
13
+
14
+ # Create applications table
15
+ create_table :rails_error_dashboard_applications do |t|
16
+ t.string :name, limit: 255, null: false
17
+ t.text :description
18
+ t.datetime :created_at
19
+ t.datetime :updated_at
20
+ end
21
+ add_index :rails_error_dashboard_applications, :name, unique: true
22
+
23
+ # Create error_logs table with ALL columns from incremental migrations
24
+ create_table :rails_error_dashboard_error_logs do |t|
25
+ # Core error fields (from 20251224000001)
26
+ t.string :error_type, null: false
27
+ t.text :message, null: false
28
+ t.text :backtrace
29
+ t.integer :user_id
30
+ t.text :request_url
31
+ t.text :request_params
32
+ t.text :user_agent
33
+ t.string :ip_address
34
+ t.string :platform
35
+ t.boolean :resolved, null: false, default: false
36
+ t.text :resolution_comment
37
+ t.string :resolution_reference
38
+ t.string :resolved_by_name
39
+ t.datetime :resolved_at
40
+ t.datetime :occurred_at, null: false
41
+
42
+ # Enhanced tracking fields (from 20251224081522)
43
+ t.string :error_hash
44
+ t.datetime :first_seen_at
45
+ t.datetime :last_seen_at
46
+ t.integer :occurrence_count, default: 1, null: false
47
+
48
+ # Controller/action context (from 20251224101217)
49
+ t.string :controller_name
50
+ t.string :action_name
51
+
52
+ # Enhanced metrics (from 20251225085859)
53
+ t.string :app_version
54
+ t.string :git_sha
55
+ t.integer :priority_score
56
+
57
+ # Similarity tracking (from 20251225093603)
58
+ t.float :similarity_score
59
+ t.string :backtrace_signature
60
+
61
+ # Workflow fields (from 20251226020000)
62
+ t.string :status, default: "new"
63
+ t.string :assigned_to
64
+ t.datetime :snoozed_until
65
+ t.integer :priority_level, default: 0
66
+
67
+ # Application association (from 20260106094233)
68
+ t.integer :application_id, null: false
69
+
70
+ t.timestamps
71
+ end
72
+
73
+ # Add ALL indexes from incremental migrations
74
+ # Basic indexes (from 20251224000001)
75
+ add_index :rails_error_dashboard_error_logs, :error_type
76
+ add_index :rails_error_dashboard_error_logs, :resolved
77
+ add_index :rails_error_dashboard_error_logs, :user_id
78
+ add_index :rails_error_dashboard_error_logs, :occurred_at
79
+ add_index :rails_error_dashboard_error_logs, :platform
80
+
81
+ # Tracking indexes (from 20251224081522)
82
+ add_index :rails_error_dashboard_error_logs, :error_hash
83
+ add_index :rails_error_dashboard_error_logs, :first_seen_at
84
+ add_index :rails_error_dashboard_error_logs, :last_seen_at
85
+ add_index :rails_error_dashboard_error_logs, :occurrence_count
86
+
87
+ # Composite indexes for performance (from 20251225071314)
88
+ add_index :rails_error_dashboard_error_logs, [ :error_type, :occurred_at ], name: "index_error_logs_on_error_type_and_occurred_at"
89
+ add_index :rails_error_dashboard_error_logs, [ :resolved, :occurred_at ], name: "index_error_logs_on_resolved_and_occurred_at"
90
+ add_index :rails_error_dashboard_error_logs, [ :platform, :occurred_at ], name: "index_error_logs_on_platform_and_occurred_at"
91
+ add_index :rails_error_dashboard_error_logs, [ :error_hash, :resolved, :occurred_at ], name: "index_error_logs_on_hash_resolved_occurred"
92
+ add_index :rails_error_dashboard_error_logs, [ :controller_name, :action_name, :error_hash ], name: "index_error_logs_on_controller_action_hash"
93
+
94
+ # Enhanced metrics indexes (from 20251225085859)
95
+ add_index :rails_error_dashboard_error_logs, :app_version
96
+ add_index :rails_error_dashboard_error_logs, :git_sha
97
+ add_index :rails_error_dashboard_error_logs, :priority_score
98
+
99
+ # Similarity tracking indexes (from 20251225093603)
100
+ add_index :rails_error_dashboard_error_logs, :similarity_score
101
+ add_index :rails_error_dashboard_error_logs, :backtrace_signature
102
+
103
+ # Application indexes (from 20260106094233, 20251229111223)
104
+ add_index :rails_error_dashboard_error_logs, :application_id
105
+ add_index :rails_error_dashboard_error_logs, [ :application_id, :occurred_at ], name: "index_error_logs_on_app_occurred"
106
+ add_index :rails_error_dashboard_error_logs, [ :application_id, :resolved ], name: "index_error_logs_on_app_resolved"
107
+
108
+ # Workflow indexes (from 20251229111223)
109
+ add_index :rails_error_dashboard_error_logs, [ :assigned_to, :status, :occurred_at ], name: "index_error_logs_on_assignment_workflow"
110
+ add_index :rails_error_dashboard_error_logs, [ :priority_level, :resolved, :occurred_at ], name: "index_error_logs_on_priority_resolution"
111
+ add_index :rails_error_dashboard_error_logs, [ :platform, :status, :occurred_at ], name: "index_error_logs_on_platform_status_time"
112
+ add_index :rails_error_dashboard_error_logs, [ :app_version, :resolved, :occurred_at ], name: "index_error_logs_on_version_resolution_time"
113
+
114
+ # Create error_occurrences table (from 20251225100236)
115
+ create_table :rails_error_dashboard_error_occurrences do |t|
116
+ t.integer :error_log_id, null: false
117
+ t.datetime :occurred_at, null: false
118
+ t.integer :user_id
119
+ t.string :request_id
120
+ t.string :session_id
121
+ t.timestamps
122
+ end
123
+ add_index :rails_error_dashboard_error_occurrences, :error_log_id, name: "index_error_occurrences_on_error_log"
124
+ add_index :rails_error_dashboard_error_occurrences, [ :occurred_at, :error_log_id ], name: "index_error_occurrences_on_time_and_error"
125
+ add_index :rails_error_dashboard_error_occurrences, :user_id, name: "index_error_occurrences_on_user"
126
+ add_index :rails_error_dashboard_error_occurrences, :request_id, name: "index_error_occurrences_on_request"
127
+
128
+ # Create cascade_patterns table (from 20251225101920)
129
+ create_table :rails_error_dashboard_cascade_patterns do |t|
130
+ t.integer :parent_error_id, null: false
131
+ t.integer :child_error_id, null: false
132
+ t.integer :frequency, default: 1, null: false
133
+ t.float :avg_delay_seconds
134
+ t.float :cascade_probability
135
+ t.datetime :last_detected_at
136
+ t.timestamps
137
+ end
138
+ add_index :rails_error_dashboard_cascade_patterns, :parent_error_id, name: "index_cascade_patterns_on_parent"
139
+ add_index :rails_error_dashboard_cascade_patterns, :child_error_id, name: "index_cascade_patterns_on_child"
140
+ add_index :rails_error_dashboard_cascade_patterns, [ :parent_error_id, :child_error_id ], unique: true, name: "index_cascade_patterns_on_parent_and_child"
141
+ add_index :rails_error_dashboard_cascade_patterns, :cascade_probability, name: "index_cascade_patterns_on_probability"
142
+
143
+ # Create error_baselines table (from 20251225102500)
144
+ create_table :rails_error_dashboard_error_baselines do |t|
145
+ t.string :error_type, null: false
146
+ t.string :platform, null: false
147
+ t.string :baseline_type, null: false
148
+ t.datetime :period_start, null: false
149
+ t.datetime :period_end, null: false
150
+ t.integer :count, default: 0, null: false
151
+ t.float :mean
152
+ t.float :std_dev
153
+ t.float :percentile_95
154
+ t.float :percentile_99
155
+ t.integer :sample_size, default: 0, null: false
156
+ t.timestamps
157
+ end
158
+ add_index :rails_error_dashboard_error_baselines, [ :error_type, :platform ], name: "index_error_baselines_on_error_type_and_platform"
159
+ add_index :rails_error_dashboard_error_baselines, :period_end, name: "index_error_baselines_on_period_end"
160
+ add_index :rails_error_dashboard_error_baselines, [ :error_type, :platform, :baseline_type, :period_start ], name: "index_error_baselines_on_type_platform_baseline_period"
161
+
162
+ # Create error_comments table (from 20251226020100)
163
+ create_table :rails_error_dashboard_error_comments do |t|
164
+ t.integer :error_log_id, null: false
165
+ t.string :author_name, null: false
166
+ t.text :body, null: false
167
+ t.timestamps
168
+ end
169
+ add_index :rails_error_dashboard_error_comments, :error_log_id
170
+ add_index :rails_error_dashboard_error_comments, [ :error_log_id, :created_at ], name: "index_error_comments_on_error_and_time"
171
+
172
+ # Add foreign keys
173
+ add_foreign_key :rails_error_dashboard_error_logs, :rails_error_dashboard_applications, column: :application_id
174
+ add_foreign_key :rails_error_dashboard_error_occurrences, :rails_error_dashboard_error_logs, column: :error_log_id
175
+ add_foreign_key :rails_error_dashboard_cascade_patterns, :rails_error_dashboard_error_logs, column: :parent_error_id
176
+ add_foreign_key :rails_error_dashboard_cascade_patterns, :rails_error_dashboard_error_logs, column: :child_error_id
177
+ add_foreign_key :rails_error_dashboard_error_comments, :rails_error_dashboard_error_logs, column: :error_log_id
178
+ end
179
+
180
+ def down
181
+ drop_table :rails_error_dashboard_error_comments, if_exists: true
182
+ drop_table :rails_error_dashboard_cascade_patterns, if_exists: true
183
+ drop_table :rails_error_dashboard_error_baselines, if_exists: true
184
+ drop_table :rails_error_dashboard_error_occurrences, if_exists: true
185
+ drop_table :rails_error_dashboard_error_logs, if_exists: true
186
+ drop_table :rails_error_dashboard_applications, if_exists: true
187
+ end
188
+ end
@@ -2,6 +2,11 @@
2
2
 
3
3
  class CreateRailsErrorDashboardErrorLogs < ActiveRecord::Migration[7.0]
4
4
  def change
5
+ # Skip if squashed migration already ran (checks for column added in later migration)
6
+ # If application_id column exists, the squashed migration created the table
7
+ return if table_exists?(:rails_error_dashboard_error_logs) &&
8
+ column_exists?(:rails_error_dashboard_error_logs, :application_id)
9
+
5
10
  create_table :rails_error_dashboard_error_logs do |t|
6
11
  # Error details
7
12
  t.string :error_type, null: false
@@ -1,5 +1,8 @@
1
1
  class AddBetterTrackingToErrorLogs < ActiveRecord::Migration[8.1]
2
2
  def change
3
+ # Skip if squashed migration already added these columns
4
+ return if column_exists?(:rails_error_dashboard_error_logs, :error_hash)
5
+
3
6
  add_column :rails_error_dashboard_error_logs, :error_hash, :string
4
7
  add_column :rails_error_dashboard_error_logs, :first_seen_at, :datetime
5
8
  add_column :rails_error_dashboard_error_logs, :last_seen_at, :datetime
@@ -1,5 +1,8 @@
1
1
  class AddControllerActionToErrorLogs < ActiveRecord::Migration[8.1]
2
2
  def change
3
+ # Skip if squashed migration already added these columns
4
+ return if column_exists?(:rails_error_dashboard_error_logs, :controller_name)
5
+
3
6
  add_column :rails_error_dashboard_error_logs, :controller_name, :string
4
7
  add_column :rails_error_dashboard_error_logs, :action_name, :string
5
8
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  class AddOptimizedIndexesToErrorLogs < ActiveRecord::Migration[8.1]
4
4
  def change
5
+ # Skip if squashed migration already added these indexes
6
+ return if index_exists?(:rails_error_dashboard_error_logs, [ :resolved, :occurred_at ],
7
+ name: 'index_error_logs_on_resolved_and_occurred_at')
8
+
5
9
  # Composite indexes for common query patterns
6
10
  # These improve performance when filtering and sorting together
7
11
 
@@ -1,5 +1,8 @@
1
1
  class RemoveEnvironmentFromErrorLogs < ActiveRecord::Migration[8.1]
2
2
  def up
3
+ # Skip if squashed migration ran (column never existed) or already removed
4
+ return unless column_exists?(:rails_error_dashboard_error_logs, :environment)
5
+
3
6
  # Remove composite index first
4
7
  remove_index :rails_error_dashboard_error_logs,
5
8
  name: 'index_error_logs_on_environment_and_occurred_at',
@@ -1,5 +1,8 @@
1
1
  class AddEnhancedMetricsToErrorLogs < ActiveRecord::Migration[8.0]
2
2
  def change
3
+ # Skip if squashed migration already added these columns
4
+ return if column_exists?(:rails_error_dashboard_error_logs, :app_version)
5
+
3
6
  add_column :rails_error_dashboard_error_logs, :app_version, :string
4
7
  add_column :rails_error_dashboard_error_logs, :git_sha, :string
5
8
  add_column :rails_error_dashboard_error_logs, :priority_score, :integer
@@ -1,5 +1,8 @@
1
1
  class AddSimilarityTrackingToErrorLogs < ActiveRecord::Migration[8.0]
2
2
  def change
3
+ # Skip if squashed migration already added these columns
4
+ return if column_exists?(:rails_error_dashboard_error_logs, :similarity_score)
5
+
3
6
  add_column :rails_error_dashboard_error_logs, :similarity_score, :float
4
7
  add_column :rails_error_dashboard_error_logs, :backtrace_signature, :string
5
8
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  class CreateErrorOccurrences < ActiveRecord::Migration[8.0]
4
4
  def change
5
+ # Skip if squashed migration already created this table
6
+ return if table_exists?(:rails_error_dashboard_error_occurrences)
7
+
5
8
  create_table :rails_error_dashboard_error_occurrences do |t|
6
9
  t.references :error_log, null: false, foreign_key: { to_table: :rails_error_dashboard_error_logs }
7
10
  t.datetime :occurred_at, null: false
@@ -2,6 +2,9 @@
2
2
 
3
3
  class CreateCascadePatterns < ActiveRecord::Migration[8.0]
4
4
  def change
5
+ # Skip if squashed migration already created this table
6
+ return if table_exists?(:rails_error_dashboard_cascade_patterns)
7
+
5
8
  create_table :rails_error_dashboard_cascade_patterns do |t|
6
9
  t.references :parent_error, null: false, foreign_key: { to_table: :rails_error_dashboard_error_logs }
7
10
  t.references :child_error, null: false, foreign_key: { to_table: :rails_error_dashboard_error_logs }
@@ -2,6 +2,9 @@
2
2
 
3
3
  class CreateErrorBaselines < ActiveRecord::Migration[8.0]
4
4
  def change
5
+ # Skip if squashed migration already created this table
6
+ return if table_exists?(:rails_error_dashboard_error_baselines)
7
+
5
8
  create_table :rails_error_dashboard_error_baselines do |t|
6
9
  t.string :error_type, null: false
7
10
  t.string :platform, null: false
@@ -2,6 +2,9 @@
2
2
 
3
3
  class AddWorkflowFieldsToErrorLogs < ActiveRecord::Migration[8.0]
4
4
  def change
5
+ # Skip if squashed migration already added these columns
6
+ return if column_exists?(:rails_error_dashboard_error_logs, :status)
7
+
5
8
  add_column :rails_error_dashboard_error_logs, :status, :string, default: 'new', null: false
6
9
  add_column :rails_error_dashboard_error_logs, :assigned_to, :string
7
10
  add_column :rails_error_dashboard_error_logs, :assigned_at, :datetime
@@ -2,6 +2,9 @@
2
2
 
3
3
  class CreateErrorComments < ActiveRecord::Migration[8.0]
4
4
  def change
5
+ # Skip if squashed migration already created this table
6
+ return if table_exists?(:rails_error_dashboard_error_comments)
7
+
5
8
  create_table :rails_error_dashboard_error_comments do |t|
6
9
  t.references :error_log,
7
10
  null: false,
@@ -2,6 +2,10 @@
2
2
 
3
3
  class AddAdditionalPerformanceIndexes < ActiveRecord::Migration[7.0]
4
4
  def change
5
+ # Skip if squashed migration already added these indexes
6
+ return if index_exists?(:rails_error_dashboard_error_logs, [ :assigned_to, :status, :occurred_at ],
7
+ name: 'index_error_logs_on_assignment_workflow')
8
+
5
9
  # Composite index for workflow filtering (assigned errors with status)
6
10
  # Common query: WHERE assigned_to = ? AND status = ? ORDER BY occurred_at DESC
7
11
  # Used in: "Show me all errors assigned to John that are investigating"
@@ -1,5 +1,8 @@
1
1
  class CreateRailsErrorDashboardApplications < ActiveRecord::Migration[7.0]
2
2
  def change
3
+ # Skip if squashed migration already ran (applications table already exists)
4
+ return if table_exists?(:rails_error_dashboard_applications)
5
+
3
6
  create_table :rails_error_dashboard_applications do |t|
4
7
  t.string :name, null: false, limit: 255
5
8
  t.text :description
@@ -1,5 +1,8 @@
1
1
  class AddApplicationToErrorLogs < ActiveRecord::Migration[7.0]
2
2
  def up
3
+ # Skip if squashed migration already added this column
4
+ return if column_exists?(:rails_error_dashboard_error_logs, :application_id)
5
+
3
6
  # Add nullable column first (for existing records)
4
7
  add_column :rails_error_dashboard_error_logs, :application_id, :bigint
5
8
 
@@ -1,5 +1,10 @@
1
1
  class FinalizeApplicationForeignKey < ActiveRecord::Migration[7.0]
2
2
  def up
3
+ # Skip if squashed migration already added the foreign key
4
+ return if foreign_key_exists?(:rails_error_dashboard_error_logs,
5
+ :rails_error_dashboard_applications,
6
+ column: :application_id)
7
+
3
8
  # Make NOT NULL
4
9
  change_column_null :rails_error_dashboard_error_logs, :application_id, false
5
10
 
@@ -27,6 +27,9 @@ module RailsErrorDashboard
27
27
  class_option :error_correlation, type: :boolean, default: false, desc: "Enable error correlation analysis"
28
28
  class_option :platform_comparison, type: :boolean, default: false, desc: "Enable platform comparison analytics"
29
29
  class_option :occurrence_patterns, type: :boolean, default: false, desc: "Enable occurrence pattern detection"
30
+ # Developer tools options
31
+ class_option :source_code_integration, type: :boolean, default: false, desc: "Enable source code viewer (NEW!)"
32
+ class_option :git_blame, type: :boolean, default: false, desc: "Enable git blame integration (NEW!)"
30
33
 
31
34
  def welcome_message
32
35
  say "\n"
@@ -148,6 +151,20 @@ module RailsErrorDashboard
148
151
  name: "Occurrence Pattern Detection",
149
152
  description: "Detect cyclical patterns and bursts",
150
153
  category: "Advanced Analytics"
154
+ },
155
+
156
+ # === DEVELOPER TOOLS ===
157
+ {
158
+ key: :source_code_integration,
159
+ name: "Source Code Integration (NEW!)",
160
+ description: "View source code directly in error details",
161
+ category: "Developer Tools"
162
+ },
163
+ {
164
+ key: :git_blame,
165
+ name: "Git Blame Integration (NEW!)",
166
+ description: "Show git blame info (author, commit, timestamp)",
167
+ category: "Developer Tools"
151
168
  }
152
169
  ]
153
170
 
@@ -197,6 +214,10 @@ module RailsErrorDashboard
197
214
  @enable_platform_comparison = @selected_features&.dig(:platform_comparison) || options[:platform_comparison]
198
215
  @enable_occurrence_patterns = @selected_features&.dig(:occurrence_patterns) || options[:occurrence_patterns]
199
216
 
217
+ # Developer Tools
218
+ @enable_source_code_integration = @selected_features&.dig(:source_code_integration) || options[:source_code_integration]
219
+ @enable_git_blame = @selected_features&.dig(:git_blame) || options[:git_blame]
220
+
200
221
  template "initializer.rb", "config/initializers/rails_error_dashboard.rb"
201
222
  end
202
223
 
@@ -268,6 +289,17 @@ module RailsErrorDashboard
268
289
  enabled_count += analytics_features.size
269
290
  end
270
291
 
292
+ # Developer Tools
293
+ developer_tools_features = []
294
+ developer_tools_features << "Source Code Integration" if @enable_source_code_integration
295
+ developer_tools_features << "Git Blame" if @enable_git_blame
296
+
297
+ if developer_tools_features.any?
298
+ say "\nDeveloper Tools:", :cyan
299
+ say " ✓ #{developer_tools_features.join(", ")}", :green
300
+ enabled_count += developer_tools_features.size
301
+ end
302
+
271
303
  say "\n"
272
304
  say "Configuration Required:", :yellow if enabled_count > 0
273
305
  say " → Edit config/initializers/rails_error_dashboard.rb", :yellow if @enable_error_sampling
@@ -22,8 +22,9 @@ RailsErrorDashboard.configure do |config|
22
22
  # User model for error associations
23
23
  config.user_model = "User"
24
24
 
25
- # Error retention policy (days to keep errors before auto-deletion)
26
- config.retention_days = 90
25
+ # Error retention policy - nil means keep forever (no automatic deletion)
26
+ # To manually cleanup old errors: rails error_dashboard:cleanup_resolved DAYS=90
27
+ config.retention_days = nil
27
28
 
28
29
  # ============================================================================
29
30
  # NOTIFICATION SETTINGS
@@ -120,8 +121,8 @@ RailsErrorDashboard.configure do |config|
120
121
  # config.async_adapter = :sidekiq # Options: :sidekiq, :solid_queue, :async
121
122
 
122
123
  <% end -%>
123
- # Backtrace size limiting (reduces storage by ~80%)
124
- config.max_backtrace_lines = 50
124
+ # Backtrace size limiting (100 lines is industry standard: Rollbar, Airbrake, Bugsnag)
125
+ config.max_backtrace_lines = 100
125
126
 
126
127
  <% if @enable_error_sampling -%>
127
128
  # Error Sampling - ENABLED
@@ -262,6 +263,38 @@ RailsErrorDashboard.configure do |config|
262
263
  config.enable_occurrence_patterns = false
263
264
 
264
265
  <% end -%>
266
+ # ============================================================================
267
+ # DEVELOPER TOOLS (NEW!)
268
+ # ============================================================================
269
+
270
+ <% if @enable_source_code_integration -%>
271
+ # Source Code Integration - ENABLED (NEW!)
272
+ # View source code directly in error details with inline viewer
273
+ config.enable_source_code_integration = true
274
+ # To disable: Set config.enable_source_code_integration = false
275
+
276
+ <% else -%>
277
+ # Source Code Integration - DISABLED (NEW!)
278
+ # To enable: Set config.enable_source_code_integration = true
279
+ config.enable_source_code_integration = false
280
+
281
+ <% end -%>
282
+ <% if @enable_git_blame -%>
283
+ # Git Blame Integration - ENABLED (NEW!)
284
+ # Show git blame info (author, commit, timestamp) for each source line
285
+ config.enable_git_blame = true
286
+ # To disable: Set config.enable_git_blame = false
287
+
288
+ <% else -%>
289
+ # Git Blame Integration - DISABLED (NEW!)
290
+ # To enable: Set config.enable_git_blame = true (requires Git installed)
291
+ config.enable_git_blame = false
292
+
293
+ <% end -%>
294
+ # Repository settings (auto-detected from git remote, optional override)
295
+ # config.repository_url = ENV["REPOSITORY_URL"] # e.g., "https://github.com/user/repo"
296
+ # config.repository_branch = ENV.fetch("REPOSITORY_BRANCH", "main") # Default branch
297
+
265
298
  # ============================================================================
266
299
  # INTERNAL LOGGING (Silent by Default)
267
300
  # ============================================================================