Module: RailsNexus::LoggedExceptionsHelper

Includes:
SourceCodeHelper
Defined in:
app/helpers/rails_nexus/logged_exceptions_helper.rb

Instance Method Summary collapse

Methods included from SourceCodeHelper

#resolve_source_path, #source_with_blame

Instance Method Details

#active_filter_pillsObject

Active filter pills for the filter bar



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 103

def active_filter_pills
  pills = []

  if params[:q].present?
    params[:q].each do |key, value|
      next if value.blank? || key.to_s == "s"

      labels = {
        "exception_class_cont" => "Exception",
        "message_cont" => "Message",
        "message_or_exception_class_or_controller_name_or_action_name_cont" => "Search",
        "platform_eq" => "Platform",
        "priority_eq" => "Priority",
        "assigned_to_cont" => "Assigned to",
        "fingerprint_cont" => "Fingerprint",
        "occurrence_count_gteq" => "Minimum count"
      }
      label = labels[key.to_s] || key.to_s.sub(/_(cont|eq|gteq|lteq)$/, "").humanize
      pills << { label: label, value: value, group: :q, key: key.to_s }
    end
  end

  if params[:date_ranges_filter].present?
    value = { "1" => "Today", "3" => "Last 3 days", "7" => "Last 7 days", "30" => "Last 30 days" }[params[:date_ranges_filter].to_s] || "Custom"
    pills << { label: "Time", value: value, key: "date_ranges_filter" }
  end

  {
    "controller_actions_filter" => "Source",
    "status_filter" => "Status",
    "query" => "Search"
  }.each do |key, label|
    pills << { label: label, value: params[key], key: key } if params[key].present?
  end

  pills
end

#exception_occurrence_count(exception) ⇒ Object



154
155
156
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 154

def exception_occurrence_count(exception)
  exception.respond_to?(:occurrence_count) ? (exception.occurrence_count || 1) : 1
end

#exception_searchable?(attribute) ⇒ Boolean

Returns:

  • (Boolean)


173
174
175
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 173

def exception_searchable?(attribute)
  LoggedException.ransackable_attributes.include?(attribute.to_s)
end

#exception_status(exception) ⇒ Object



158
159
160
161
162
163
164
165
166
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 158

def exception_status(exception)
  if exception.has_attribute?(:muted) && exception.muted?
    ["Muted", "muted"]
  elsif exception.has_attribute?(:snoozed_until) && exception.snoozed?
    ["Snoozed", "warning"]
  else
    ["Active", "success"]
  end
end

#exception_trend_data(days: 7) ⇒ Object

Simple sparkline data for stats



178
179
180
181
182
183
184
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 178

def exception_trend_data(days: 7)
  data = (0...days).reverse_each.map do |i|
    date = i.days.ago.to_date
    LoggedException.where(created_at: date.beginning_of_day..date.end_of_day).count
  end
  data
end

#filter_params_without(pill) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 141

def filter_params_without(pill)
  filters = params_filters.deep_stringify_keys

  if pill[:group] == :q
    filters["q"]&.delete(pill[:key])
    filters.delete("q") if filters["q"].blank?
  else
    filters.delete(pill[:key])
  end

  filters
end

#filtered?Boolean

Returns:

  • (Boolean)


32
33
34
35
36
37
38
39
40
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 32

def filtered?
  legacy_filters = %i[
    query date_ranges_filter exception_names_filter platform_filter
    controller_actions_filter status_filter
  ]
  ransack_filters = params[:q].respond_to?(:to_unsafe_h) ? params[:q].to_unsafe_h.except("s") : {}

  legacy_filters.any? { |name| params[name].present? } || ransack_filters.values.any?(&:present?)
end

#format_backtrace(backtrace, limit: nil) ⇒ Object

Format backtrace for display with optional collapse



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 73

def format_backtrace(backtrace, limit: nil)
  limit ||= RailsNexus.configuration.backtrace_limit
  lines = backtrace.to_s.split("\n")

  if lines.length > limit
    collapsed = lines[0...limit]
    remaining = lines[limit..]
    {
      visible: collapsed.join("\n"),
      hidden: remaining.join("\n"),
      hidden_count: remaining.length
    }
  else
    { visible: lines.join("\n"), hidden: nil, hidden_count: 0 }
  end
end

#git_blame_for_line(file_path, line_number, **_options) ⇒ Object

Get git blame for a file:line range



194
195
196
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 194

def git_blame_for_line(file_path, line_number, **_options)
  super(file_path, line_number)
end

#listify(text) ⇒ Object



42
43
44
45
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 42

def listify(text)
  list_items = text.scan(/^\s*\* (.+)/).map { |match| (:li, match.first) }
  (:ul, list_items)
end

#page_title(text) ⇒ Object



47
48
49
50
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 47

def page_title(text)
  title = [RailsNexus.application_name.presence, text].compact.join(" :: ")
  content_for(:title, title)
end

#parse_backtrace_line(line) ⇒ Object

Parse backtrace line into file path and line number



199
200
201
202
203
204
205
206
207
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 199

def parse_backtrace_line(line)
  return nil unless line.present?
  # Match formats: "file:line" or "file:line:in `method'"
  if line =~ /^(.+?):(\d+)(?::in `(.+?)')?$/
    { file: $1, line: $2.to_i, method: $3 }
  else
    nil
  end
end

#pretty_exception_date(exception) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 6

def pretty_exception_date(exception)
  if RailsNexus.configuration.date_format
    return exception.created_at.strftime(RailsNexus.configuration.date_format)
  end

  time = exception.created_at
  diff = Time.current - time

  case
  when diff < 60
    "#{(diff).to_i}s ago"
  when diff < 3600
    "#{(diff / 60).to_i}m ago"
  when diff < 86400
    "#{(diff / 3600).to_i}h ago"
  when diff < 604800
    "#{(diff / 86400).to_i}d ago"
  when Date.today == time.to_date
    "Today, #{time.strftime('%I:%M %p')}"
  when Date.yesterday == time.to_date
    "Yesterday, #{time.strftime('%I:%M %p')}"
  else
    time.strftime('%b %d, %Y %I:%M %p')
  end
end

#pretty_format(text) ⇒ Object

Rescue textilize call if RedCloth is not available.



53
54
55
56
57
58
59
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 53

def pretty_format(text)
  begin
    textilize(text).html_safe
  rescue
    simple_format(text).html_safe
  end
end

#read_source_snippet(file_path, line_number, context: 5) ⇒ Object

Read source code snippet around a file:line



189
190
191
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 189

def read_source_snippet(file_path, line_number, context: 5)
  super(file_path, line_number, context_lines: context)
end

#severity_for(exception) ⇒ Object

Severity badge class based on exception type



91
92
93
94
95
96
97
98
99
100
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 91

def severity_for(exception)
  case exception.exception_class
  when /Timeout|Slow|Performance/i
    "warning"
  when /404|NotFound|Missing/i
    "info"
  else
    "danger"
  end
end

#sort_header_class(attribute) ⇒ Object



168
169
170
171
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 168

def sort_header_class(attribute)
  sort = @q&.sorts&.find { |item| item.name == attribute.to_s }
  ["sortable", ("sorted-#{sort.dir}" if sort)].compact.join(" ")
end

Sort link helper for Ransack-compatible table headers.



62
63
64
65
66
67
68
69
70
# File 'app/helpers/rails_nexus/logged_exceptions_helper.rb', line 62

def sort_link(search, attribute, name = nil, **options, &block)
  name ||= attribute.to_s.humanize

  if defined?(Ransack) && search.respond_to?(:result)
    Ransack::Helpers::FormHelper.instance_method(:sort_link).bind(self).call(search, attribute, name, **options, &block)
  else
    link_to name, "#", **options, &block
  end
end