Module: RailsErrorDashboard::ApplicationHelper

Defined in:
app/helpers/rails_error_dashboard/application_helper.rb

Instance Method Summary collapse

Instance Method Details

#app_contextHash

Returns the current application context param for preserving app selection across navigation. Use this in link helpers: errors_path(app_context) or error_path(error, **app_context)

Returns:

  • (Hash)

    { application_id: X } if an app is selected, empty hash otherwise



107
108
109
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 107

def app_context
  params[:application_id].present? ? { application_id: params[:application_id] } : {}
end

Automatically converts URLs in text to clickable links that open in new window Also highlights inline code wrapped in backticks with syntax highlighting Also converts file paths to GitHub links if repository URL is configured Supports http://, https://, and common patterns like github.com/user/repo

Parameters:

  • text (String)

    The text containing URLs, file paths, and inline code

  • error (RailsErrorDashboard::ErrorLog, nil) (defaults to: nil)

    The error for context (to get repo URL)

Returns:

  • (String)

    HTML safe text with clickable links and styled code



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 265

def auto_link_urls(text, error: nil)
  return "" if text.blank?

  # Get repository URL from error's application or global config
  repo_url = if error&.application.respond_to?(:repository_url) && error.application.repository_url.present?
    error.application.repository_url
  elsif RailsErrorDashboard.configuration.git_repository_url.present?
    RailsErrorDashboard.configuration.git_repository_url
  end

  # First, protect inline code with backticks by replacing with placeholders
  code_blocks = []
  file_paths = []
  text_with_placeholders = text.gsub(/`([^`]+)`/) do |match|
    code_content = Regexp.last_match(1)

    # Check if the code block contains a file path pattern
    if repo_url && code_content =~ %r{^(app|lib|config|db|spec|test)/[^\s]+\.(rb|js|jsx|ts|tsx|erb|yml|yaml|json|css|scss)$}
      # It's a file path - save it and mark for GitHub linking
      file_paths << code_content
      "###FILE_PATH_#{file_paths.length - 1}###"
    else
      # Regular code block
      code_blocks << code_content
      "###CODE_BLOCK_#{code_blocks.length - 1}###"
    end
  end

  # Regex to match URLs (http://, https://, www., and common domains)
  url_regex = %r{
    (
      (?:https?://|www\.)           # http://, https://, or www.
      (?:[^\s<>"]+)                 # Domain and path (no spaces, <, >, or ")
      |
      (?:^|\s)                      # Start of string or whitespace
      (?:github\.com|gitlab\.com|bitbucket\.org|jira\.[^\s]+)
      /[^\s<>"]+                    # Path after domain
    )
  }xi

  # Replace URLs with clickable links
  linked_text = text_with_placeholders.gsub(url_regex) do |url|
    # Clean up the URL
    clean_url = url.strip

    # Add protocol if missing
    href = clean_url.start_with?("http://", "https://") ? clean_url : "https://#{clean_url}"

    # Truncate display text for very long URLs
    display_text = clean_url.length > 60 ? "#{clean_url[0..57]}..." : clean_url

    "<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>"
  end

  # Restore file paths with GitHub links (elvish magic! 🧝‍♀️)
  linked_text.gsub!(/###FILE_PATH_(\d+)###/) do
    file_path = file_paths[Regexp.last_match(1).to_i]
    github_url = "#{repo_url.chomp('/')}/blob/main/#{file_path}"
    "<a href=\"#{ERB::Util.html_escape(github_url)}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-decoration-none\" title=\"View on GitHub\">" \
    "<code class=\"inline-code-highlight file-path-link\">#{ERB::Util.html_escape(file_path)}</code></a>"
  end

  # Restore code blocks with styling
  linked_text.gsub!(/###CODE_BLOCK_(\d+)###/) do
    code_content = code_blocks[Regexp.last_match(1).to_i]
    "<code class=\"inline-code-highlight\">#{ERB::Util.html_escape(code_content)}</code>"
  end

  # Preserve line breaks and return as HTML safe
  simple_format(linked_text, {}, sanitize: false)
end

Returns Bootstrap badge color class for breadcrumb category

Parameters:

  • category (String)

    Breadcrumb category (sql, controller, cache, job, mailer, custom)

Returns:

  • (String)

    Bootstrap color class



236
237
238
239
240
241
242
243
244
245
246
247
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 236

def breadcrumb_badge_color(category)
  case category.to_s
  when "sql"        then "primary"
  when "controller" then "success"
  when "cache"      then "info"
  when "job"        then "warning"
  when "mailer"     then "secondary"
  when "custom"     then "dark"
  when "deprecation" then "danger"
  else "light"
  end
end

#extract_table_from_sql(sql) ⇒ String?

Extracts table name from a SQL query string

Parameters:

  • sql (String)

    SQL query (e.g., ‘SELECT “users”.* FROM “users” WHERE …’)

Returns:

  • (String, nil)

    The table name or nil if not extractable



252
253
254
255
256
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 252

def extract_table_from_sql(sql)
  return nil if sql.blank?
  match = sql.match(/FROM\s+["`]?(\w+)["`]?/i)
  match ? match[1] : nil
end

Generates a link to a git commit if repository URL is configured

Parameters:

  • git_sha (String)

    The git commit SHA

  • short (Boolean) (defaults to: true)

    Whether to show short SHA (7 chars) or full SHA

Returns:

  • (String)

    HTML safe link to commit or plain text if no repo configured



155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 155

def git_commit_link(git_sha, short: true)
  return "" if git_sha.blank?

  config = RailsErrorDashboard.configuration
  display_sha = short ? git_sha[0..6] : git_sha

  if config.git_repository_url.present?
    # Support GitHub, GitLab, Bitbucket URL formats
    commit_url = "#{config.git_repository_url.chomp("/")}/commit/#{git_sha}"
    link_to display_sha, commit_url, class: "text-decoration-none font-monospace", target: "_blank", rel: "noopener"
  else
    (:code, display_sha, class: "font-monospace")
  end
end

#local_time(time, format: :full, fallback: "N/A") ⇒ String

Renders a timestamp that will be automatically converted to user’s local timezone Server sends UTC timestamp, JavaScript converts to local timezone on page load

Parameters:

  • time (Time, DateTime, nil)

    The timestamp to display

  • format (Symbol) (defaults to: :full)

    Format preset (:full, :short, :date_only, :time_only, :datetime)

  • fallback (String) (defaults to: "N/A")

    Text to show if time is nil

Returns:

  • (String)

    HTML safe span with data attributes for JS conversion



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 176

def local_time(time, format: :full, fallback: "N/A")
  return fallback if time.nil?

  # Convert to UTC if not already
  utc_time = time.respond_to?(:utc) ? time.utc : time

  # ISO 8601 format for JavaScript parsing
  iso_time = utc_time.iso8601

  # Format presets for data-format attribute
  format_string = case format
  when :full
    "%B %d, %Y %I:%M:%S %p"  # December 31, 2024 11:59:59 PM
  when :short
    "%m/%d %I:%M%p"          # 12/31 11:59PM
  when :date_only
    "%B %d, %Y"              # December 31, 2024
  when :time_only
    "%I:%M:%S %p"            # 11:59:59 PM
  when :datetime
    "%b %d, %Y %H:%M"        # Dec 31, 2024 23:59
  else
    format.to_s
  end

  (
    :span,
    utc_time.strftime(format_string + " UTC"),  # Fallback for non-JS browsers
    class: "local-time",
    data: {
      utc: iso_time,
      format: format_string
    }
  )
end

#local_time_ago(time, fallback: "N/A") ⇒ String

Renders a relative time (“3 hours ago”) that updates automatically

Parameters:

  • time (Time, DateTime, nil)

    The timestamp to display

  • fallback (String) (defaults to: "N/A")

    Text to show if time is nil

Returns:

  • (String)

    HTML safe span with data attributes for JS conversion



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 216

def local_time_ago(time, fallback: "N/A")
  return fallback if time.nil?

  # Convert to UTC if not already
  utc_time = time.respond_to?(:utc) ? time.utc : time
  iso_time = utc_time.iso8601

  (
    :span,
    time_ago_in_words(time) + " ago",  # Fallback for non-JS browsers
    class: "local-time-ago",
    data: {
      utc: iso_time
    }
  )
end

#permitted_filter_params(extra_keys: []) ⇒ Hash

Returns a sanitized hash of filter params safe for query links

Parameters:

  • extra_keys (Array<Symbol>) (defaults to: [])

    Additional permitted keys for specific contexts

Returns:

  • (Hash)

    Whitelisted params for building URLs



114
115
116
117
118
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 114

def permitted_filter_params(extra_keys: [])
  base_keys = RailsErrorDashboard::ErrorsController::FILTERABLE_PARAMS + %i[page per_page days]
  allowed_keys = base_keys + Array(extra_keys)
  params.permit(*allowed_keys).to_h.symbolize_keys
end

#platform_color_var(platform) ⇒ String

Returns platform-specific color class

Parameters:

  • platform (String)

    Platform name (ios, android, web, api)

Returns:

  • (String)

    CSS color variable



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 71

def platform_color_var(platform)
  case platform&.downcase
  when "ios"
    "var(--platform-ios)"
  when "android"
    "var(--platform-android)"
  when "web"
    "var(--platform-web)"
  when "api"
    "var(--platform-api)"
  else
    "var(--text-color)"
  end
end

#platform_icon(platform) ⇒ String

Returns platform icon

Parameters:

  • platform (String)

    Platform name (ios, android, web, api)

Returns:

  • (String)

    Bootstrap icon class



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 89

def platform_icon(platform)
  case platform&.downcase
  when "ios"
    "bi-apple"
  when "android"
    "bi-android2"
  when "web"
    "bi-globe"
  when "api"
    "bi-server"
  else
    "bi-question-circle"
  end
end

#red_csp_nonceObject

Returns the host app’s CSP nonce (if any) so inline <script> tags pass strict CSP. Falls back to nil when the host has no CSP configured — in that case the script tag works without a nonce attribute. Strict CSPs (script-src ‘self’ ‘nonce-…’) require this; without it the script is blocked.



7
8
9
10
11
12
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 7

def red_csp_nonce
  return nil unless respond_to?(:content_security_policy_nonce)
  content_security_policy_nonce
rescue StandardError
  nil
end

#red_javascript_tag(&block) ⇒ Object

Wraps an inline <script> block with the host app’s CSP nonce when available. Use everywhere we have <script>…</script> in our views so they pass strict CSP.

<%= red_javascript_tag do %>
  console.log('hi');
<% end %>


20
21
22
23
24
25
26
27
28
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 20

def red_javascript_tag(&block)
  nonce = red_csp_nonce
  content = capture(&block)
  if nonce
    (:script, content.html_safe, nonce: nonce)
  else
    (:script, content.html_safe)
  end
end

#severity_color(severity) ⇒ String

Returns Bootstrap color class for error severity Uses Catppuccin Mocha colors in dark theme via CSS variables

Parameters:

  • severity (Symbol)

    The severity level (:critical, :high, :medium, :low, :info)

Returns:

  • (String)

    Bootstrap color class (danger, warning, info, secondary)



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 34

def severity_color(severity)
  case severity&.to_sym
  when :critical
    "danger"   # Maps to --ctp-red in dark mode
  when :high
    "warning"  # Maps to --ctp-peach in dark mode
  when :medium
    "info"     # Maps to --ctp-blue in dark mode
  when :low
    "secondary" # Maps to --ctp-overlay1 in dark mode
  else
    "secondary"
  end
end

#severity_color_var(severity) ⇒ String

Returns CSS variable for severity color (for inline styles) Useful when you need to set background-color or color directly

Parameters:

  • severity (Symbol)

    The severity level

Returns:

  • (String)

    CSS variable reference



53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 53

def severity_color_var(severity)
  case severity&.to_sym
  when :critical
    "var(--status-critical)"
  when :high
    "var(--status-warning)"
  when :medium
    "var(--status-info)"
  when :low
    "var(--text-tertiary)"
  else
    "var(--text-tertiary)"
  end
end

#sortable_header(label, column) ⇒ String

Generates a sortable column header link

Parameters:

  • label (String)

    The column label to display

  • column (String)

    The column name to sort by

Returns:

  • (String)

    HTML safe link with sort indicator



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'app/helpers/rails_error_dashboard/application_helper.rb', line 124

def sortable_header(label, column)
  current_sort = params[:sort_by]
  current_direction = params[:sort_direction] || "desc"

  # Determine new direction: if clicking same column, toggle; otherwise default to desc
  new_direction = if current_sort == column
    current_direction == "asc" ? "desc" : "asc"
  else
    "desc"
  end

  # Choose icon based on current state
  icon = if current_sort == column
    current_direction == "asc" ? "" : ""
  else
    ""  # Unsorted indicator
  end

  # Preserve whitelisted filter params while adding sort params
  link_params = permitted_filter_params.merge(sort_by: column, sort_direction: new_direction)

  link_to errors_path(link_params), class: "text-decoration-none" do
    (:span, "#{label} ", class: current_sort == column ? "fw-bold" : "") +
    (:span, icon, class: "text-muted small")
  end
end