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
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ module RailsErrorDashboard
7
+ module Services
8
+ # Reads git blame information for specific file lines
9
+ # Executes git blame command and parses porcelain output
10
+ #
11
+ # @example
12
+ # reader = GitBlameReader.new("/path/to/file.rb", 42)
13
+ # blame = reader.read_blame
14
+ # # => { author: "John Doe", email: "john@example.com", date: Time, sha: "abc123", line: "code" }
15
+ class GitBlameReader
16
+ COMMAND_TIMEOUT = 5 # seconds
17
+ PORCELAIN_FIELDS = %w[
18
+ author
19
+ author-mail
20
+ author-time
21
+ author-tz
22
+ committer
23
+ committer-mail
24
+ committer-time
25
+ committer-tz
26
+ summary
27
+ filename
28
+ ].freeze
29
+
30
+ attr_reader :file_path, :line_number, :error
31
+
32
+ # Initialize a new git blame reader
33
+ #
34
+ # @param file_path [String] Path to the source file
35
+ # @param line_number [Integer] Target line number
36
+ def initialize(file_path, line_number)
37
+ @file_path = file_path
38
+ @line_number = line_number.to_i
39
+ @error = nil
40
+ end
41
+
42
+ # Read git blame information for the target line
43
+ #
44
+ # @return [Hash, nil] Blame data hash or nil if unavailable
45
+ def read_blame
46
+ unless git_available?
47
+ @error = "Git not available"
48
+ return nil
49
+ end
50
+
51
+ unless File.exist?(file_path)
52
+ @error = "File not found"
53
+ return nil
54
+ end
55
+
56
+ # Execute git blame command
57
+ output = execute_git_blame
58
+ return nil unless output
59
+
60
+ # Parse porcelain format output
61
+ parse_blame_output(output)
62
+ rescue StandardError => e
63
+ @error = "Error reading git blame: #{e.message}"
64
+ RailsErrorDashboard::Logger.error("GitBlameReader error for #{file_path}:#{line_number} - #{e.message}")
65
+ nil
66
+ end
67
+
68
+ # Check if git is available on the system
69
+ #
70
+ # @return [Boolean]
71
+ def git_available?
72
+ @git_available ||= begin
73
+ _stdout, _stderr, status = Open3.capture3("git", "--version")
74
+ status.success?
75
+ rescue StandardError
76
+ false
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Execute git blame command for the specific line
83
+ #
84
+ # @return [String, nil] Command output or nil if failed
85
+ def execute_git_blame
86
+ # Build command array (prevents command injection)
87
+ cmd = [
88
+ "git",
89
+ "blame",
90
+ "-L", "#{line_number},#{line_number}",
91
+ "--porcelain",
92
+ "--", # Separator
93
+ file_path
94
+ ]
95
+
96
+ # Execute with timeout
97
+ stdout, stderr, status = Timeout.timeout(COMMAND_TIMEOUT) do
98
+ Open3.capture3(*cmd, chdir: Rails.root)
99
+ end
100
+
101
+ unless status.success?
102
+ @error = "Git blame failed: #{stderr}"
103
+ RailsErrorDashboard::Logger.debug("Git blame failed for #{file_path}:#{line_number} - #{stderr}")
104
+ return nil
105
+ end
106
+
107
+ stdout
108
+ rescue Timeout::Error
109
+ @error = "Git blame timeout"
110
+ RailsErrorDashboard::Logger.warn("Git blame timeout for #{file_path}:#{line_number}")
111
+ nil
112
+ rescue StandardError => e
113
+ @error = "Git blame execution error: #{e.message}"
114
+ RailsErrorDashboard::Logger.error("Git blame execution error for #{file_path}:#{line_number} - #{e.message}")
115
+ nil
116
+ end
117
+
118
+ # Parse git blame porcelain format output
119
+ #
120
+ # Git blame --porcelain format:
121
+ # <sha> <line_number> <final_line_number> <num_lines>
122
+ # author <author_name>
123
+ # author-mail <<author_email>>
124
+ # author-time <unix_timestamp>
125
+ # author-tz <timezone>
126
+ # committer <committer_name>
127
+ # committer-mail <<committer_email>>
128
+ # committer-time <unix_timestamp>
129
+ # committer-tz <timezone>
130
+ # summary <commit_message_summary>
131
+ # filename <filename>
132
+ # <TAB><line_content>
133
+ #
134
+ # @param output [String] Git blame porcelain output
135
+ # @return [Hash, nil] Parsed blame data
136
+ def parse_blame_output(output)
137
+ return nil if output.blank?
138
+
139
+ lines = output.split("\n")
140
+ return nil if lines.empty?
141
+
142
+ # First line contains commit SHA and line info
143
+ first_line = lines[0]
144
+ match = first_line.match(/^([0-9a-f]+)\s+(\d+)\s+(\d+)/)
145
+ unless match
146
+ @error = "Incomplete git blame data"
147
+ return nil
148
+ end
149
+
150
+ sha = match[1]
151
+ data = { sha: sha }
152
+
153
+ # Parse subsequent lines
154
+ lines[1..].each do |line|
155
+ # Check for field: value lines
156
+ PORCELAIN_FIELDS.each do |field|
157
+ if line.start_with?("#{field} ")
158
+ value = line.sub("#{field} ", "")
159
+
160
+ case field
161
+ when "author"
162
+ data[:author] = value
163
+ when "author-mail"
164
+ # Remove < and > brackets
165
+ data[:email] = value.gsub(/[<>]/, "")
166
+ when "author-time"
167
+ # Convert Unix timestamp to Time object
168
+ data[:date] = Time.at(value.to_i)
169
+ when "summary"
170
+ data[:commit_message] = value
171
+ end
172
+ end
173
+ end
174
+
175
+ # Line content starts with tab
176
+ if line.start_with?("\t")
177
+ data[:line] = line.sub("\t", "")
178
+ end
179
+ end
180
+
181
+ # Validate required fields
182
+ if data[:author].present? && data[:sha].present?
183
+ data
184
+ else
185
+ @error = "Incomplete git blame data"
186
+ nil
187
+ end
188
+ rescue StandardError => e
189
+ @error = "Error parsing git blame output: #{e.message}"
190
+ RailsErrorDashboard::Logger.error("Git blame parsing error: #{e.message}")
191
+ nil
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Generates links to source code on GitHub, GitLab, or Bitbucket
6
+ # Supports commit SHA, branch, and tag references
7
+ #
8
+ # @example
9
+ # generator = GithubLinkGenerator.new(
10
+ # repository_url: "https://github.com/user/repo",
11
+ # file_path: "app/models/user.rb",
12
+ # line_number: 42,
13
+ # commit_sha: "abc123def456"
14
+ # )
15
+ # generator.generate_link
16
+ # # => "https://github.com/user/repo/blob/abc123def456/app/models/user.rb#L42"
17
+ class GithubLinkGenerator
18
+ attr_reader :repository_url, :file_path, :line_number, :commit_sha, :branch, :error
19
+
20
+ # Initialize a new link generator
21
+ #
22
+ # @param repository_url [String] Base repository URL (GitHub, GitLab, Bitbucket)
23
+ # @param file_path [String] Relative path to file from repository root
24
+ # @param line_number [Integer] Line number to link to
25
+ # @param commit_sha [String, nil] Git commit SHA (recommended for accuracy)
26
+ # @param branch [String, nil] Branch name (fallback if no commit SHA)
27
+ def initialize(repository_url:, file_path:, line_number:, commit_sha: nil, branch: nil)
28
+ @repository_url = repository_url
29
+ @file_path = file_path
30
+ @line_number = line_number.to_i
31
+ @commit_sha = commit_sha
32
+ @branch = branch || "main"
33
+ @error = nil
34
+ end
35
+
36
+ # Generate a link to the source file on the repository host
37
+ #
38
+ # @return [String, nil] URL to the source file or nil if invalid
39
+ def generate_link
40
+ return nil if repository_url.blank? || file_path.blank?
41
+
42
+ # Normalize repository URL
43
+ normalized_repo = normalize_repository_url
44
+
45
+ # Determine reference (commit SHA or branch)
46
+ reference = determine_reference
47
+
48
+ # Generate link based on repository type
49
+ case detect_repository_type
50
+ when :github
51
+ generate_github_link(normalized_repo, reference)
52
+ when :gitlab
53
+ generate_gitlab_link(normalized_repo, reference)
54
+ when :bitbucket
55
+ generate_bitbucket_link(normalized_repo, reference)
56
+ else
57
+ @error = "Unsupported repository type"
58
+ nil
59
+ end
60
+ rescue StandardError => e
61
+ @error = "Error generating link: #{e.message}"
62
+ RailsErrorDashboard::Logger.error("GithubLinkGenerator error for #{repository_url} - #{e.message}")
63
+ nil
64
+ end
65
+
66
+ private
67
+
68
+ # Normalize repository URL (remove .git suffix, trailing slashes, etc.)
69
+ #
70
+ # @return [String]
71
+ def normalize_repository_url
72
+ url = repository_url.strip
73
+ url = url.chomp(".git")
74
+ url = url.chomp("/")
75
+ url
76
+ end
77
+
78
+ # Detect repository type from URL
79
+ #
80
+ # @return [Symbol] :github, :gitlab, :bitbucket, or :unknown
81
+ def detect_repository_type
82
+ normalized = normalize_repository_url.downcase
83
+
84
+ return :github if normalized.include?("github.com")
85
+ return :gitlab if normalized.include?("gitlab.com") || normalized.include?("gitlab.")
86
+ return :bitbucket if normalized.include?("bitbucket.org") || normalized.include?("bitbucket.")
87
+
88
+ :unknown
89
+ end
90
+
91
+ # Determine which reference to use (commit SHA or branch)
92
+ #
93
+ # @return [String]
94
+ def determine_reference
95
+ if commit_sha.present?
96
+ commit_sha
97
+ else
98
+ branch
99
+ end
100
+ end
101
+
102
+ # Normalize file path (remove leading slashes, Rails.root prefix, etc.)
103
+ #
104
+ # @return [String]
105
+ def normalize_file_path
106
+ path = file_path.strip
107
+
108
+ # Remove leading slash
109
+ path = path.sub(%r{^/}, "")
110
+
111
+ # Remove Rails.root or app root prefix if present
112
+ # Handles paths like "/Users/foo/myapp/app/models/user.rb" -> "app/models/user.rb"
113
+ # Match pattern: look for one of the standard Rails directories
114
+ match = path.match(%r{.*/?((?:app|lib|config|db|spec|test)/.*)$})
115
+ if match
116
+ path = match[1]
117
+ end
118
+
119
+ path
120
+ end
121
+
122
+ # Generate GitHub link
123
+ #
124
+ # Format: https://github.com/user/repo/blob/{ref}/path/to/file.rb#L42
125
+ #
126
+ # @param repo_url [String] Normalized repository URL
127
+ # @param ref [String] Commit SHA or branch name
128
+ # @return [String]
129
+ def generate_github_link(repo_url, ref)
130
+ normalized_path = normalize_file_path
131
+ "#{repo_url}/blob/#{ref}/#{normalized_path}#L#{line_number}"
132
+ end
133
+
134
+ # Generate GitLab link
135
+ #
136
+ # Format: https://gitlab.com/user/repo/-/blob/{ref}/path/to/file.rb#L42
137
+ #
138
+ # @param repo_url [String] Normalized repository URL
139
+ # @param ref [String] Commit SHA or branch name
140
+ # @return [String]
141
+ def generate_gitlab_link(repo_url, ref)
142
+ normalized_path = normalize_file_path
143
+ "#{repo_url}/-/blob/#{ref}/#{normalized_path}#L#{line_number}"
144
+ end
145
+
146
+ # Generate Bitbucket link
147
+ #
148
+ # Format: https://bitbucket.org/user/repo/src/{ref}/path/to/file.rb#lines-42
149
+ #
150
+ # @param repo_url [String] Normalized repository URL
151
+ # @param ref [String] Commit SHA or branch name
152
+ # @return [String]
153
+ def generate_bitbucket_link(repo_url, ref)
154
+ normalized_path = normalize_file_path
155
+ "#{repo_url}/src/#{ref}/#{normalized_path}#lines-#{line_number}"
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Reads source code files from disk with security validation
6
+ # and context lines around a target line number
7
+ #
8
+ # @example
9
+ # reader = SourceCodeReader.new("/path/to/app/models/user.rb", 42)
10
+ # lines = reader.read_lines(context: 5)
11
+ # # Returns array of hashes with line numbers and content
12
+ class SourceCodeReader
13
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
14
+ MAX_CONTEXT_LINES = 50
15
+
16
+ attr_reader :file_path, :line_number, :error
17
+
18
+ # Initialize a new source code reader
19
+ #
20
+ # @param file_path [String] Path to the source file
21
+ # @param line_number [Integer] Target line number
22
+ def initialize(file_path, line_number)
23
+ @file_path = file_path
24
+ @line_number = line_number.to_i
25
+ @error = nil
26
+ end
27
+
28
+ # Read source code lines with context around the target line
29
+ #
30
+ # @param context [Integer] Number of lines before and after target line
31
+ # @return [Array<Hash>, nil] Array of line data hashes or nil if unavailable
32
+ def read_lines(context: 5)
33
+ context = [ [ context, MAX_CONTEXT_LINES ].min, 1 ].max
34
+
35
+ # Validate and resolve path
36
+ absolute_path = resolve_absolute_path
37
+ return nil unless absolute_path
38
+
39
+ # Validate path is safe
40
+ unless validate_path!(absolute_path)
41
+ @error = "Invalid or unsafe file path"
42
+ return nil
43
+ end
44
+
45
+ # Check file exists and is readable
46
+ unless File.exist?(absolute_path) && File.readable?(absolute_path)
47
+ @error = "File not found or not readable"
48
+ return nil
49
+ end
50
+
51
+ # Check file size
52
+ file_size = File.size(absolute_path)
53
+ if file_size > MAX_FILE_SIZE
54
+ @error = "File too large (#{file_size} bytes, max #{MAX_FILE_SIZE})"
55
+ return nil
56
+ end
57
+
58
+ # Check if file is binary
59
+ if binary_file?(absolute_path)
60
+ @error = "Binary file cannot be displayed"
61
+ return nil
62
+ end
63
+
64
+ # Read the specific lines
65
+ read_specific_lines(absolute_path, line_number - context, line_number + context)
66
+ rescue StandardError => e
67
+ @error = "Error reading file: #{e.message}"
68
+ RailsErrorDashboard::Logger.error("SourceCodeReader error for #{file_path}:#{line_number} - #{e.message}")
69
+ nil
70
+ end
71
+
72
+ # Check if file exists on disk
73
+ #
74
+ # @return [Boolean]
75
+ def file_exists?
76
+ absolute_path = resolve_absolute_path
77
+ return false unless absolute_path
78
+
79
+ File.exist?(absolute_path) && File.readable?(absolute_path)
80
+ rescue StandardError
81
+ false
82
+ end
83
+
84
+ private
85
+
86
+ # Resolve file path to absolute path within Rails.root
87
+ #
88
+ # @return [String, nil] Absolute path or nil if invalid
89
+ def resolve_absolute_path
90
+ return nil if file_path.blank?
91
+
92
+ # Handle relative paths from backtrace
93
+ if file_path.start_with?(Rails.root.to_s)
94
+ # Already absolute
95
+ file_path
96
+ elsif file_path.start_with?("/")
97
+ # Absolute but might be from different root (deployed location)
98
+ # Try to find relative to Rails.root
99
+ relative = file_path.sub(%r{^.*/}, "")
100
+ File.join(Rails.root, relative)
101
+ else
102
+ # Relative path
103
+ File.join(Rails.root, file_path)
104
+ end
105
+ end
106
+
107
+ # Validate path is safe to read (security check)
108
+ #
109
+ # @param path [String] Absolute path to validate
110
+ # @return [Boolean]
111
+ # @raise [SecurityError] if path is unsafe
112
+ def validate_path!(path)
113
+ # Normalize path
114
+ normalized = File.expand_path(path)
115
+ rails_root = File.expand_path(Rails.root)
116
+
117
+ # Must be within Rails.root
118
+ unless normalized.start_with?(rails_root)
119
+ RailsErrorDashboard::Logger.warn("Path outside Rails.root: #{normalized}")
120
+ return false
121
+ end
122
+
123
+ # Prevent directory traversal
124
+ if path.include?("..") || normalized.include?("..")
125
+ RailsErrorDashboard::Logger.warn("Directory traversal attempt: #{path}")
126
+ return false
127
+ end
128
+
129
+ # Check against sensitive file patterns
130
+ sensitive_patterns = [
131
+ /\.env/i,
132
+ /secrets\.yml$/i,
133
+ /credentials\.yml/i,
134
+ /database\.yml$/i,
135
+ /master\.key$/i,
136
+ /private_key/i,
137
+ /\.pem$/i,
138
+ /\.key$/i
139
+ ]
140
+
141
+ if sensitive_patterns.any? { |pattern| normalized.match?(pattern) }
142
+ RailsErrorDashboard::Logger.warn("Attempt to read sensitive file: #{normalized}")
143
+ return false
144
+ end
145
+
146
+ # Only show app code if configured
147
+ if RailsErrorDashboard.configuration.only_show_app_code_source
148
+ # Block gem/vendor code
149
+ blocked_patterns = [
150
+ %r{/gems/},
151
+ %r{/vendor/bundle/},
152
+ %r{/vendor/ruby/},
153
+ %r{/.bundle/}
154
+ ]
155
+
156
+ if blocked_patterns.any? { |pattern| normalized.match?(pattern) }
157
+ RailsErrorDashboard::Logger.debug("Skipping gem/vendor code: #{normalized}")
158
+ return false
159
+ end
160
+ end
161
+
162
+ true
163
+ end
164
+
165
+ # Read specific lines from file
166
+ #
167
+ # @param file_path [String] Path to file
168
+ # @param start_line [Integer] First line to read (1-indexed)
169
+ # @param end_line [Integer] Last line to read (1-indexed)
170
+ # @return [Array<Hash>] Array of line data
171
+ def read_specific_lines(file_path, start_line, end_line)
172
+ start_line = [ start_line, 1 ].max
173
+ lines = []
174
+
175
+ File.open(file_path, "r") do |file|
176
+ file.each_line.with_index(1) do |line, line_num|
177
+ break if line_num > end_line
178
+
179
+ next if line_num < start_line
180
+
181
+ lines << {
182
+ number: line_num,
183
+ content: line.chomp,
184
+ highlight: line_num == @line_number
185
+ }
186
+ end
187
+ end
188
+
189
+ lines
190
+ rescue StandardError => e
191
+ RailsErrorDashboard::Logger.error("Error reading specific lines from #{file_path}: #{e.message}")
192
+ []
193
+ end
194
+
195
+ # Check if file is binary
196
+ #
197
+ # @param file_path [String] Path to file
198
+ # @return [Boolean]
199
+ def binary_file?(file_path)
200
+ # Read first 8KB to check for null bytes
201
+ sample_size = 8192
202
+ File.open(file_path, "rb") do |file|
203
+ sample = file.read(sample_size)
204
+ return false if sample.nil? || sample.empty?
205
+
206
+ # Binary files typically contain null bytes
207
+ sample.include?("\x00")
208
+ end
209
+ rescue StandardError
210
+ false # If we can't determine, assume text
211
+ end
212
+ end
213
+ end
214
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.1.29"
2
+ VERSION = "0.1.30"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require "rails_error_dashboard/version"
2
2
  require "rails_error_dashboard/engine"
3
+ require "rails_error_dashboard/configuration_error"
3
4
  require "rails_error_dashboard/configuration"
4
5
  require "rails_error_dashboard/logger"
5
6
  require "rails_error_dashboard/manual_error_reporter"
@@ -16,6 +17,7 @@ require "turbo-rails"
16
17
 
17
18
  # Core library files
18
19
  require "rails_error_dashboard/value_objects/error_context"
20
+ require "rails_error_dashboard/helpers/user_model_detector"
19
21
  require "rails_error_dashboard/services/platform_detector"
20
22
  require "rails_error_dashboard/services/backtrace_parser"
21
23
  require "rails_error_dashboard/services/similarity_calculator"
@@ -23,6 +25,10 @@ require "rails_error_dashboard/services/cascade_detector"
23
25
  require "rails_error_dashboard/services/baseline_calculator"
24
26
  require "rails_error_dashboard/services/baseline_alert_throttler"
25
27
  require "rails_error_dashboard/services/pattern_detector"
28
+ require "rails_error_dashboard/services/error_normalizer"
29
+ require "rails_error_dashboard/services/source_code_reader"
30
+ require "rails_error_dashboard/services/git_blame_reader"
31
+ require "rails_error_dashboard/services/github_link_generator"
26
32
  require "rails_error_dashboard/queries/co_occurring_errors"
27
33
  require "rails_error_dashboard/queries/error_cascades"
28
34
  require "rails_error_dashboard/queries/baseline_stats"
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.29
4
+ version: 0.1.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -267,13 +267,6 @@ files:
267
267
  - MIT-LICENSE
268
268
  - README.md
269
269
  - Rakefile
270
- - app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss
271
- - app/assets/stylesheets/rails_error_dashboard/_components.scss
272
- - app/assets/stylesheets/rails_error_dashboard/_layout.scss
273
- - app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss
274
- - app/assets/stylesheets/rails_error_dashboard/application.css
275
- - app/assets/stylesheets/rails_error_dashboard/application.css.map
276
- - app/assets/stylesheets/rails_error_dashboard/application.scss
277
270
  - app/controllers/rails_error_dashboard/application_controller.rb
278
271
  - app/controllers/rails_error_dashboard/errors_controller.rb
279
272
  - app/helpers/rails_error_dashboard/application_helper.rb
@@ -298,11 +291,11 @@ files:
298
291
  - app/models/rails_error_dashboard/error_logs_record.rb
299
292
  - app/models/rails_error_dashboard/error_occurrence.rb
300
293
  - app/views/layouts/rails_error_dashboard.html.erb
301
- - app/views/layouts/rails_error_dashboard/application.html.erb
302
294
  - app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb
303
295
  - app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb
304
296
  - app/views/rails_error_dashboard/errors/_error_row.html.erb
305
297
  - app/views/rails_error_dashboard/errors/_pattern_insights.html.erb
298
+ - app/views/rails_error_dashboard/errors/_source_code.html.erb
306
299
  - app/views/rails_error_dashboard/errors/_stats.html.erb
307
300
  - app/views/rails_error_dashboard/errors/_timeline.html.erb
308
301
  - app/views/rails_error_dashboard/errors/_user_errors_table.html.erb
@@ -312,9 +305,11 @@ files:
312
305
  - app/views/rails_error_dashboard/errors/overview.html.erb
313
306
  - app/views/rails_error_dashboard/errors/platform_comparison.html.erb
314
307
  - app/views/rails_error_dashboard/errors/settings.html.erb
308
+ - app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb
315
309
  - app/views/rails_error_dashboard/errors/show.html.erb
316
310
  - config/routes.rb
317
311
  - db/development.sqlite3
312
+ - db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb
318
313
  - db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb
319
314
  - db/migrate/20251224081522_add_better_tracking_to_error_logs.rb
320
315
  - db/migrate/20251224101217_add_controller_action_to_error_logs.rb
@@ -345,8 +340,10 @@ files:
345
340
  - lib/rails_error_dashboard/commands/log_error.rb
346
341
  - lib/rails_error_dashboard/commands/resolve_error.rb
347
342
  - lib/rails_error_dashboard/configuration.rb
343
+ - lib/rails_error_dashboard/configuration_error.rb
348
344
  - lib/rails_error_dashboard/engine.rb
349
345
  - lib/rails_error_dashboard/error_reporter.rb
346
+ - lib/rails_error_dashboard/helpers/user_model_detector.rb
350
347
  - lib/rails_error_dashboard/logger.rb
351
348
  - lib/rails_error_dashboard/manual_error_reporter.rb
352
349
  - lib/rails_error_dashboard/middleware/error_catcher.rb
@@ -372,9 +369,13 @@ files:
372
369
  - lib/rails_error_dashboard/services/baseline_alert_throttler.rb
373
370
  - lib/rails_error_dashboard/services/baseline_calculator.rb
374
371
  - lib/rails_error_dashboard/services/cascade_detector.rb
372
+ - lib/rails_error_dashboard/services/error_normalizer.rb
373
+ - lib/rails_error_dashboard/services/git_blame_reader.rb
374
+ - lib/rails_error_dashboard/services/github_link_generator.rb
375
375
  - lib/rails_error_dashboard/services/pattern_detector.rb
376
376
  - lib/rails_error_dashboard/services/platform_detector.rb
377
377
  - lib/rails_error_dashboard/services/similarity_calculator.rb
378
+ - lib/rails_error_dashboard/services/source_code_reader.rb
378
379
  - lib/rails_error_dashboard/value_objects/error_context.rb
379
380
  - lib/rails_error_dashboard/version.rb
380
381
  - lib/tasks/error_dashboard.rake
@@ -386,8 +387,10 @@ metadata:
386
387
  homepage_uri: https://github.com/AnjanJ/rails_error_dashboard
387
388
  source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
388
389
  changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
390
+ documentation_uri: https://rails-error-dashboard.anjan.dev
391
+ demo_uri: https://rails-error-dashboard.anjan.dev
389
392
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
390
- \ Rails Error Dashboard v0.1.29\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
393
+ \ Rails Error Dashboard v0.1.30\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
391
394
  First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
392
395
  db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
393
396
  => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n