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.
- checksums.yaml +4 -4
- data/README.md +34 -6
- data/app/controllers/rails_error_dashboard/errors_controller.rb +22 -0
- data/app/helpers/rails_error_dashboard/application_helper.rb +79 -7
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +149 -0
- data/app/models/rails_error_dashboard/application.rb +1 -1
- data/app/models/rails_error_dashboard/error_log.rb +44 -16
- data/app/views/layouts/rails_error_dashboard.html.erb +66 -1237
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +10 -2
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +76 -0
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +18 -82
- data/app/views/rails_error_dashboard/errors/index.html.erb +64 -31
- data/app/views/rails_error_dashboard/errors/overview.html.erb +181 -3
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +2 -1
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +286 -0
- data/app/views/rails_error_dashboard/errors/settings.html.erb +146 -480
- data/app/views/rails_error_dashboard/errors/show.html.erb +44 -20
- data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +188 -0
- data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +5 -0
- data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +3 -0
- data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +3 -0
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +4 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +3 -0
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +3 -0
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +3 -0
- data/db/migrate/20251225100236_create_error_occurrences.rb +3 -0
- data/db/migrate/20251225101920_create_cascade_patterns.rb +3 -0
- data/db/migrate/20251225102500_create_error_baselines.rb +3 -0
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +3 -0
- data/db/migrate/20251226020100_create_error_comments.rb +3 -0
- data/db/migrate/20251229111223_add_additional_performance_indexes.rb +4 -0
- data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +3 -0
- data/db/migrate/20260106094233_add_application_to_error_logs.rb +3 -0
- data/db/migrate/20260106094318_finalize_application_foreign_key.rb +5 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +32 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +37 -4
- data/lib/rails_error_dashboard/configuration.rb +160 -3
- data/lib/rails_error_dashboard/configuration_error.rb +24 -0
- data/lib/rails_error_dashboard/engine.rb +17 -0
- data/lib/rails_error_dashboard/helpers/user_model_detector.rb +138 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +19 -4
- data/lib/rails_error_dashboard/queries/errors_list.rb +20 -8
- data/lib/rails_error_dashboard/services/error_normalizer.rb +143 -0
- data/lib/rails_error_dashboard/services/git_blame_reader.rb +195 -0
- data/lib/rails_error_dashboard/services/github_link_generator.rb +159 -0
- data/lib/rails_error_dashboard/services/source_code_reader.rb +214 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +6 -0
- metadata +13 -10
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +0 -107
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +0 -625
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +0 -257
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +0 -203
- data/app/assets/stylesheets/rails_error_dashboard/application.css +0 -15
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +0 -7
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +0 -61
- data/app/views/layouts/rails_error_dashboard/application.html.erb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9a0ec7ce2177fab0bbbd7103440111b9d737add4281bd85e651e219b032242a
|
|
4
|
+
data.tar.gz: c7edac468fff5d5318d78f384b460b1b9cf1ddbc50373ad71764c5675e33c67e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 166ed8da84a2ef05fc69b1e4352e4f108300b34ec78b75b7685d41622a07e1dfe45113556f27f55756002d0af93d2ae6fd165683fcef9393562ab117fe02aa0d
|
|
7
|
+
data.tar.gz: 79f5193644e2754c9f4f49e6d8f03edd463d91a7e021d23f9b02a02f8e412d748666a9b2cc3e8ff843b821f926f781a4e60887cce5d01b6f9b96c597cea6d317
|
data/README.md
CHANGED
|
@@ -143,6 +143,32 @@ Detect cyclical patterns (business hours, nighttime, weekend rhythms) and error
|
|
|
143
143
|
**Plus: Developer Insights Dashboard** 💡
|
|
144
144
|
Built-in analytics dashboard with severity detection, platform stability scoring, actionable recommendations, and recent error activity summaries (always available, no configuration needed).
|
|
145
145
|
|
|
146
|
+
#### 🔍 Source Code Integration (NEW!)
|
|
147
|
+
|
|
148
|
+
**View actual source code directly in error backtraces** - no need to switch to your editor or GitHub.
|
|
149
|
+
|
|
150
|
+
- **Inline Source Code Viewer** - Click "View Source" on any error frame to see the actual code with ±7 lines of context
|
|
151
|
+
- **Git Blame Integration** - See who last modified the code, when, and the commit message
|
|
152
|
+
- **Repository Links** - Jump directly to GitHub/GitLab/Bitbucket at the exact error line
|
|
153
|
+
- **Smart Caching** - Fast performance with 1-hour cache (configurable)
|
|
154
|
+
- **Security Controls** - Only shows your app code by default (not gems/frameworks)
|
|
155
|
+
|
|
156
|
+
**Perfect for debugging:**
|
|
157
|
+
- Understand the code context without leaving the dashboard
|
|
158
|
+
- Identify code ownership with git blame
|
|
159
|
+
- Quick navigation to your repository
|
|
160
|
+
- See recent changes that might have caused the error
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# Enable in config/initializers/rails_error_dashboard.rb
|
|
164
|
+
config.enable_source_code_integration = true
|
|
165
|
+
config.source_code_context_lines = 7
|
|
166
|
+
config.enable_git_blame = true
|
|
167
|
+
config.git_repository_url = "https://github.com/user/repo"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**📖 [Complete documentation →](docs/SOURCE_CODE_INTEGRATION.md)**
|
|
171
|
+
|
|
146
172
|
#### 🔌 Plugin System
|
|
147
173
|
Extensible architecture with event hooks (`on_error_logged`, `on_error_resolved`, `on_threshold_exceeded`). Built-in examples for Jira integration, metrics tracking, audit logging. Easy to create custom plugins - just drop a file in `config/initializers/error_dashboard_plugins/`.
|
|
148
174
|
|
|
@@ -763,15 +789,17 @@ See [Customization Guide](docs/CUSTOMIZATION.md).
|
|
|
763
789
|
<details>
|
|
764
790
|
<summary><strong>How long are errors stored?</strong></summary>
|
|
765
791
|
|
|
766
|
-
Forever by default.
|
|
792
|
+
Forever by default (no automatic deletion). Manual cleanup with rake task:
|
|
767
793
|
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
794
|
+
```bash
|
|
795
|
+
# Delete resolved errors older than 90 days
|
|
796
|
+
rails error_dashboard:cleanup_resolved DAYS=90
|
|
797
|
+
|
|
798
|
+
# Filter by application name
|
|
799
|
+
rails error_dashboard:cleanup_resolved DAYS=30 APP_NAME="My App"
|
|
772
800
|
```
|
|
773
801
|
|
|
774
|
-
Or
|
|
802
|
+
Or schedule with cron/whenever. See [Database Optimization](docs/guides/DATABASE_OPTIMIZATION.md).
|
|
775
803
|
</details>
|
|
776
804
|
|
|
777
805
|
<details>
|
|
@@ -18,6 +18,7 @@ module RailsErrorDashboard
|
|
|
18
18
|
frequency
|
|
19
19
|
status
|
|
20
20
|
assigned_to
|
|
21
|
+
assignee_name
|
|
21
22
|
priority_level
|
|
22
23
|
hide_snoozed
|
|
23
24
|
sort_by
|
|
@@ -38,6 +39,18 @@ module RailsErrorDashboard
|
|
|
38
39
|
@platform_scores = {}
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
# Get correlation summary (if enabled, pass application filter)
|
|
43
|
+
if RailsErrorDashboard.configuration.enable_error_correlation
|
|
44
|
+
correlation = Queries::ErrorCorrelation.new(days: 7, application_id: @current_application_id)
|
|
45
|
+
@problematic_releases = correlation.problematic_releases.first(3)
|
|
46
|
+
@time_correlated_errors = correlation.time_correlated_errors.first(3)
|
|
47
|
+
@multi_error_users = correlation.multi_error_users(min_error_types: 2).first(5)
|
|
48
|
+
else
|
|
49
|
+
@problematic_releases = []
|
|
50
|
+
@time_correlated_errors = []
|
|
51
|
+
@multi_error_users = []
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
# Get critical alerts (critical/high severity errors from last hour)
|
|
42
55
|
# Filter by priority_level in database instead of loading all records into memory
|
|
43
56
|
@critical_alerts = ErrorLog
|
|
@@ -62,6 +75,15 @@ module RailsErrorDashboard
|
|
|
62
75
|
filter_options = Queries::FilterOptions.call(application_id: @current_application_id)
|
|
63
76
|
@error_types = filter_options[:error_types]
|
|
64
77
|
@platforms = filter_options[:platforms]
|
|
78
|
+
|
|
79
|
+
# Get all distinct assignees for the assignee filter dropdown
|
|
80
|
+
assignee_query = ErrorLog.where.not(assigned_to: nil)
|
|
81
|
+
# Filter by application if specified
|
|
82
|
+
assignee_query = assignee_query.where(application_id: @current_application_id) if @current_application_id.present?
|
|
83
|
+
@assignees = assignee_query.select(:assigned_to)
|
|
84
|
+
.distinct
|
|
85
|
+
.pluck(:assigned_to)
|
|
86
|
+
.sort
|
|
65
87
|
end
|
|
66
88
|
|
|
67
89
|
def show
|
|
@@ -74,13 +74,6 @@ module RailsErrorDashboard
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
# Returns the current user name for filtering "My Errors"
|
|
78
|
-
# Uses configured dashboard username or system username
|
|
79
|
-
# @return [String] Current user identifier
|
|
80
|
-
def current_user_name
|
|
81
|
-
RailsErrorDashboard.configuration.dashboard_username || ENV["USER"] || "unknown"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
77
|
# Returns a sanitized hash of filter params safe for query links
|
|
85
78
|
# @param extra_keys [Array<Symbol>] Additional permitted keys for specific contexts
|
|
86
79
|
# @return [Hash] Whitelisted params for building URLs
|
|
@@ -202,5 +195,84 @@ module RailsErrorDashboard
|
|
|
202
195
|
}
|
|
203
196
|
)
|
|
204
197
|
end
|
|
198
|
+
|
|
199
|
+
# Automatically converts URLs in text to clickable links that open in new window
|
|
200
|
+
# Also highlights inline code wrapped in backticks with syntax highlighting
|
|
201
|
+
# Also converts file paths to GitHub links if repository URL is configured
|
|
202
|
+
# Supports http://, https://, and common patterns like github.com/user/repo
|
|
203
|
+
# @param text [String] The text containing URLs, file paths, and inline code
|
|
204
|
+
# @param error [RailsErrorDashboard::ErrorLog, nil] The error for context (to get repo URL)
|
|
205
|
+
# @return [String] HTML safe text with clickable links and styled code
|
|
206
|
+
def auto_link_urls(text, error: nil)
|
|
207
|
+
return "" if text.blank?
|
|
208
|
+
|
|
209
|
+
# Get repository URL from error's application or global config
|
|
210
|
+
repo_url = if error&.application&.repository_url.present?
|
|
211
|
+
error.application.repository_url
|
|
212
|
+
elsif RailsErrorDashboard.configuration.git_repository_url.present?
|
|
213
|
+
RailsErrorDashboard.configuration.git_repository_url
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# First, protect inline code with backticks by replacing with placeholders
|
|
217
|
+
code_blocks = []
|
|
218
|
+
file_paths = []
|
|
219
|
+
text_with_placeholders = text.gsub(/`([^`]+)`/) do |match|
|
|
220
|
+
code_content = Regexp.last_match(1)
|
|
221
|
+
|
|
222
|
+
# Check if the code block contains a file path pattern
|
|
223
|
+
if repo_url && code_content =~ %r{^(app|lib|config|db|spec|test)/[^\s]+\.(rb|js|jsx|ts|tsx|erb|yml|yaml|json|css|scss)$}
|
|
224
|
+
# It's a file path - save it and mark for GitHub linking
|
|
225
|
+
file_paths << code_content
|
|
226
|
+
"###FILE_PATH_#{file_paths.length - 1}###"
|
|
227
|
+
else
|
|
228
|
+
# Regular code block
|
|
229
|
+
code_blocks << code_content
|
|
230
|
+
"###CODE_BLOCK_#{code_blocks.length - 1}###"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Regex to match URLs (http://, https://, www., and common domains)
|
|
235
|
+
url_regex = %r{
|
|
236
|
+
(
|
|
237
|
+
(?:https?://|www\.) # http://, https://, or www.
|
|
238
|
+
(?:[^\s<>"]+) # Domain and path (no spaces, <, >, or ")
|
|
239
|
+
|
|
|
240
|
+
(?:^|\s) # Start of string or whitespace
|
|
241
|
+
(?:github\.com|gitlab\.com|bitbucket\.org|jira\.[^\s]+)
|
|
242
|
+
/[^\s<>"]+ # Path after domain
|
|
243
|
+
)
|
|
244
|
+
}xi
|
|
245
|
+
|
|
246
|
+
# Replace URLs with clickable links
|
|
247
|
+
linked_text = text_with_placeholders.gsub(url_regex) do |url|
|
|
248
|
+
# Clean up the URL
|
|
249
|
+
clean_url = url.strip
|
|
250
|
+
|
|
251
|
+
# Add protocol if missing
|
|
252
|
+
href = clean_url.start_with?("http://", "https://") ? clean_url : "https://#{clean_url}"
|
|
253
|
+
|
|
254
|
+
# Truncate display text for very long URLs
|
|
255
|
+
display_text = clean_url.length > 60 ? "#{clean_url[0..57]}..." : clean_url
|
|
256
|
+
|
|
257
|
+
"<a href=\"#{ERB::Util.html_escape(href)}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-primary text-decoration-underline\">#{ERB::Util.html_escape(display_text)}</a>"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Restore file paths with GitHub links (elvish magic! 🧝♀️)
|
|
261
|
+
linked_text.gsub!(/###FILE_PATH_(\d+)###/) do
|
|
262
|
+
file_path = file_paths[Regexp.last_match(1).to_i]
|
|
263
|
+
github_url = "#{repo_url.chomp('/')}/blob/main/#{file_path}"
|
|
264
|
+
"<a href=\"#{ERB::Util.html_escape(github_url)}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-decoration-none\" title=\"View on GitHub\">" \
|
|
265
|
+
"<code class=\"inline-code-highlight file-path-link\">#{ERB::Util.html_escape(file_path)}</code></a>"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Restore code blocks with styling
|
|
269
|
+
linked_text.gsub!(/###CODE_BLOCK_(\d+)###/) do
|
|
270
|
+
code_content = code_blocks[Regexp.last_match(1).to_i]
|
|
271
|
+
"<code class=\"inline-code-highlight\">#{ERB::Util.html_escape(code_content)}</code>"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Preserve line breaks and return as HTML safe
|
|
275
|
+
simple_format(linked_text, {}, sanitize: false)
|
|
276
|
+
end
|
|
205
277
|
end
|
|
206
278
|
end
|
|
@@ -2,11 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
module RailsErrorDashboard
|
|
4
4
|
module BacktraceHelper
|
|
5
|
+
# Language mapping for syntax highlighting
|
|
6
|
+
LANGUAGE_MAP = {
|
|
7
|
+
".rb" => "ruby",
|
|
8
|
+
".js" => "javascript",
|
|
9
|
+
".jsx" => "javascript",
|
|
10
|
+
".ts" => "typescript",
|
|
11
|
+
".tsx" => "typescript",
|
|
12
|
+
".erb" => "erb",
|
|
13
|
+
".html" => "html",
|
|
14
|
+
".htm" => "html",
|
|
15
|
+
".css" => "css",
|
|
16
|
+
".scss" => "scss",
|
|
17
|
+
".sass" => "scss",
|
|
18
|
+
".yml" => "yaml",
|
|
19
|
+
".yaml" => "yaml",
|
|
20
|
+
".json" => "json",
|
|
21
|
+
".sql" => "sql",
|
|
22
|
+
".xml" => "xml",
|
|
23
|
+
".py" => "python",
|
|
24
|
+
".go" => "go",
|
|
25
|
+
".java" => "java",
|
|
26
|
+
".c" => "c",
|
|
27
|
+
".cpp" => "cpp",
|
|
28
|
+
".h" => "c",
|
|
29
|
+
".hpp" => "cpp",
|
|
30
|
+
".sh" => "bash",
|
|
31
|
+
".bash" => "bash",
|
|
32
|
+
".zsh" => "bash",
|
|
33
|
+
".php" => "php",
|
|
34
|
+
".pl" => "perl",
|
|
35
|
+
".r" => "r",
|
|
36
|
+
".rs" => "rust",
|
|
37
|
+
".swift" => "swift",
|
|
38
|
+
".kt" => "kotlin",
|
|
39
|
+
".scala" => "scala",
|
|
40
|
+
".clj" => "clojure",
|
|
41
|
+
".ex" => "elixir",
|
|
42
|
+
".exs" => "elixir"
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
5
45
|
# Parse backtrace string into structured frames
|
|
6
46
|
def parse_backtrace(backtrace_string)
|
|
7
47
|
Services::BacktraceParser.parse(backtrace_string)
|
|
8
48
|
end
|
|
9
49
|
|
|
50
|
+
# Detect programming language from file path
|
|
51
|
+
# Returns Highlight.js language identifier
|
|
52
|
+
def detect_language_from_path(file_path)
|
|
53
|
+
return "plaintext" unless file_path
|
|
54
|
+
|
|
55
|
+
ext = File.extname(file_path).downcase
|
|
56
|
+
LANGUAGE_MAP[ext] || "plaintext"
|
|
57
|
+
end
|
|
58
|
+
|
|
10
59
|
# Filter to show only application code
|
|
11
60
|
def filter_app_code(frames)
|
|
12
61
|
frames.select { |f| f[:category] == :app }
|
|
@@ -87,5 +136,105 @@ module RailsErrorDashboard
|
|
|
87
136
|
frames.group_by { |f| f[:category] }
|
|
88
137
|
.transform_values(&:count)
|
|
89
138
|
end
|
|
139
|
+
|
|
140
|
+
# Read source code for a backtrace frame
|
|
141
|
+
# Returns hash with { lines:, error: } or nil
|
|
142
|
+
def read_source_code(frame, context: 5)
|
|
143
|
+
return nil unless RailsErrorDashboard.configuration.enable_source_code_integration
|
|
144
|
+
return nil unless frame[:file_path] && frame[:line_number]
|
|
145
|
+
|
|
146
|
+
# Cache key includes file path, line number, and git SHA if available
|
|
147
|
+
cache_key = "source_code/#{frame[:file_path]}/#{frame[:line_number]}"
|
|
148
|
+
cache_ttl = RailsErrorDashboard.configuration.source_code_cache_ttl || 3600
|
|
149
|
+
|
|
150
|
+
Rails.cache.fetch(cache_key, expires_in: cache_ttl) do
|
|
151
|
+
context_lines = RailsErrorDashboard.configuration.source_code_context_lines || 5
|
|
152
|
+
reader = Services::SourceCodeReader.new(frame[:file_path], frame[:line_number])
|
|
153
|
+
lines = reader.read_lines(context: context_lines)
|
|
154
|
+
|
|
155
|
+
if lines
|
|
156
|
+
{
|
|
157
|
+
lines: lines,
|
|
158
|
+
language: detect_language_from_path(frame[:file_path]),
|
|
159
|
+
error: nil
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
{
|
|
163
|
+
lines: nil,
|
|
164
|
+
language: nil,
|
|
165
|
+
error: reader.error
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Read git blame for a backtrace frame
|
|
172
|
+
# Returns blame data hash or nil
|
|
173
|
+
def read_git_blame(frame)
|
|
174
|
+
return nil unless RailsErrorDashboard.configuration.enable_source_code_integration
|
|
175
|
+
return nil unless RailsErrorDashboard.configuration.enable_git_blame
|
|
176
|
+
return nil unless frame[:file_path] && frame[:line_number]
|
|
177
|
+
|
|
178
|
+
# Cache key includes file path and line number
|
|
179
|
+
cache_key = "git_blame/#{frame[:file_path]}/#{frame[:line_number]}"
|
|
180
|
+
cache_ttl = RailsErrorDashboard.configuration.source_code_cache_ttl || 3600
|
|
181
|
+
|
|
182
|
+
Rails.cache.fetch(cache_key, expires_in: cache_ttl) do
|
|
183
|
+
reader = Services::GitBlameReader.new(frame[:file_path], frame[:line_number])
|
|
184
|
+
reader.read_blame
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Generate GitHub/GitLab/Bitbucket link for a frame
|
|
189
|
+
# Returns URL string or nil
|
|
190
|
+
def generate_repository_link(frame, error_log)
|
|
191
|
+
return nil unless RailsErrorDashboard.configuration.enable_source_code_integration
|
|
192
|
+
return nil unless RailsErrorDashboard.configuration.git_repository_url.present?
|
|
193
|
+
return nil unless frame[:file_path] && frame[:line_number]
|
|
194
|
+
|
|
195
|
+
repo_url = RailsErrorDashboard.configuration.git_repository_url
|
|
196
|
+
commit_sha = determine_commit_sha(error_log)
|
|
197
|
+
|
|
198
|
+
generator = Services::GithubLinkGenerator.new(
|
|
199
|
+
repository_url: repo_url,
|
|
200
|
+
file_path: frame[:file_path],
|
|
201
|
+
line_number: frame[:line_number],
|
|
202
|
+
commit_sha: commit_sha
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
generator.generate_link
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
# Determine which commit SHA to use based on strategy
|
|
211
|
+
def determine_commit_sha(error_log)
|
|
212
|
+
strategy = RailsErrorDashboard.configuration.git_branch_strategy || :commit_sha
|
|
213
|
+
|
|
214
|
+
case strategy
|
|
215
|
+
when :commit_sha
|
|
216
|
+
# Use the SHA from when the error occurred (most accurate)
|
|
217
|
+
error_log.respond_to?(:git_sha) ? error_log.git_sha : nil
|
|
218
|
+
when :current_branch
|
|
219
|
+
# Use current branch HEAD
|
|
220
|
+
get_current_git_sha
|
|
221
|
+
when :main
|
|
222
|
+
# Use main/master branch
|
|
223
|
+
nil # Will default to "main" in GithubLinkGenerator
|
|
224
|
+
else
|
|
225
|
+
nil
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Get current git SHA from repository
|
|
230
|
+
def get_current_git_sha
|
|
231
|
+
return @current_git_sha if defined?(@current_git_sha)
|
|
232
|
+
|
|
233
|
+
@current_git_sha = begin
|
|
234
|
+
`git rev-parse HEAD 2>/dev/null`.strip.presence
|
|
235
|
+
rescue StandardError
|
|
236
|
+
nil
|
|
237
|
+
end
|
|
238
|
+
end
|
|
90
239
|
end
|
|
91
240
|
end
|
|
@@ -4,6 +4,15 @@ module RailsErrorDashboard
|
|
|
4
4
|
class ErrorLog < ErrorLogsRecord
|
|
5
5
|
self.table_name = "rails_error_dashboard_error_logs"
|
|
6
6
|
|
|
7
|
+
# Priority level constants
|
|
8
|
+
# Using industry standard: P0 = Critical (highest), P3 = Low (lowest)
|
|
9
|
+
PRIORITY_LEVELS = {
|
|
10
|
+
3 => { label: "Critical", short_label: "P0", color: "danger", emoji: "🔴" },
|
|
11
|
+
2 => { label: "High", short_label: "P1", color: "warning", emoji: "🟠" },
|
|
12
|
+
1 => { label: "Medium", short_label: "P2", color: "info", emoji: "🟡" },
|
|
13
|
+
0 => { label: "Low", short_label: "P3", color: "secondary", emoji: "⚪" }
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
7
16
|
# Application association
|
|
8
17
|
belongs_to :application, optional: false
|
|
9
18
|
|
|
@@ -94,11 +103,17 @@ module RailsErrorDashboard
|
|
|
94
103
|
# Includes controller/action/application for better context-aware grouping
|
|
95
104
|
# Per-app deduplication: same error in App A vs App B creates separate records
|
|
96
105
|
def generate_error_hash
|
|
97
|
-
#
|
|
106
|
+
# Use smart normalization to improve error grouping accuracy
|
|
107
|
+
normalized_message = Services::ErrorNormalizer.normalize(message)
|
|
108
|
+
|
|
109
|
+
# Extract significant backtrace frames (skips gem/vendor code)
|
|
110
|
+
significant_frames = Services::ErrorNormalizer.extract_significant_frames(backtrace, count: 3)
|
|
111
|
+
|
|
112
|
+
# Hash based on error class, normalized message, significant frames, controller, action, and application
|
|
98
113
|
digest_input = [
|
|
99
114
|
error_type,
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
normalized_message,
|
|
116
|
+
significant_frames,
|
|
102
117
|
controller_name, # Controller context
|
|
103
118
|
action_name, # Action context
|
|
104
119
|
application_id.to_s # Application context (for per-app deduplication)
|
|
@@ -283,22 +298,35 @@ module RailsErrorDashboard
|
|
|
283
298
|
|
|
284
299
|
# Priority methods
|
|
285
300
|
def priority_label
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
when 0 then "Low"
|
|
291
|
-
else "Unset"
|
|
292
|
-
end
|
|
301
|
+
priority_data = PRIORITY_LEVELS[priority_level]
|
|
302
|
+
return "Unset" unless priority_data
|
|
303
|
+
|
|
304
|
+
"#{priority_data[:label]} (#{priority_data[:short_label]})"
|
|
293
305
|
end
|
|
294
306
|
|
|
295
307
|
def priority_color
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
308
|
+
priority_data = PRIORITY_LEVELS[priority_level]
|
|
309
|
+
return "light" unless priority_data
|
|
310
|
+
|
|
311
|
+
priority_data[:color]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def priority_emoji
|
|
315
|
+
priority_data = PRIORITY_LEVELS[priority_level]
|
|
316
|
+
return "" unless priority_data
|
|
317
|
+
|
|
318
|
+
priority_data[:emoji]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Class method to get priority options for select dropdowns
|
|
322
|
+
def self.priority_options(include_emoji: false)
|
|
323
|
+
PRIORITY_LEVELS.sort_by { |level, _| -level }.map do |level, data|
|
|
324
|
+
label = if include_emoji
|
|
325
|
+
"#{data[:emoji]} #{data[:label]} (#{data[:short_label]})"
|
|
326
|
+
else
|
|
327
|
+
"#{data[:label]} (#{data[:short_label]})"
|
|
328
|
+
end
|
|
329
|
+
[ label, level ]
|
|
302
330
|
end
|
|
303
331
|
end
|
|
304
332
|
|