Module: Pgbus::ApplicationHelper

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

Constant Summary collapse

WORKER_RATE_KEYS =

Order the rate keys are rendered in, and their i18n label suffixes.

%w[processed failed dequeued].freeze
WORKER_INTERNAL_METADATA_KEYS =

Metadata keys rendered specially elsewhere (throughput badge, health status) and therefore excluded from the generic key/value dump.

%w[rates jobs_processed jobs_failed in_flight loop_tick_at].freeze
BATCH_BADGE_BASE =
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
BATCH_BADGE_CSS =
{
  "finished" => "#{BATCH_BADGE_BASE} bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
  "processing" => "#{BATCH_BADGE_BASE} bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
  "pending" => "#{BATCH_BADGE_BASE} bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
}.freeze
LOCALE_NAMES =
{
  da: "Dansk",
  de: "Deutsch",
  en: "English",
  es: "Espa\u00f1ol",
  fi: "Suomi",
  fr: "Fran\u00e7ais",
  it: "Italiano",
  ja: "\u65E5\u672C\u8A9E",
  nb: "Norsk",
  nl: "Nederlands",
  pt: "Portugu\u00eas",
  sv: "Svenska"
}.freeze

Instance Method Summary collapse

Instance Method Details

#pgbus_batch_status_badge(status) ⇒ Object



141
142
143
144
# File 'app/helpers/pgbus/application_helper.rb', line 141

def pgbus_batch_status_badge(status)
  css = BATCH_BADGE_CSS[status] || BATCH_BADGE_CSS["pending"]
  tag.span(I18n.t("pgbus.helpers.batch_status.#{status}", default: status), class: css)
end

#pgbus_display_metadata(metadata) ⇒ Object

The subset of a process's metadata to render as generic key/value badges: everything except the throughput/health keys that have dedicated rendering.



41
42
43
44
45
# File 'app/helpers/pgbus/application_helper.rb', line 41

def ()
  return {} unless .is_a?(Hash)

  .reject { |k, _| WORKER_INTERNAL_METADATA_KEYS.include?(k.to_s) }
end

#pgbus_duration(seconds) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/helpers/pgbus/application_helper.rb', line 99

def pgbus_duration(seconds)
  return "" unless seconds

  seconds = seconds.to_i
  if seconds < 60
    "#{seconds}s"
  elsif seconds < 3600
    "#{seconds / 60}m #{seconds % 60}s"
  elsif seconds < 86_400
    "#{seconds / 3600}h #{(seconds % 3600) / 60}m"
  else
    "#{seconds / 86_400}d #{(seconds % 86_400) / 3600}h"
  end
end

#pgbus_json_preview(json_string, max_length: 120) ⇒ Object



159
160
161
162
163
164
165
# File 'app/helpers/pgbus/application_helper.rb', line 159

def pgbus_json_preview(json_string, max_length: 120)
  return "" unless json_string

  filtered = Pgbus::Web::PayloadFilter.filter_json(json_string)
  text = filtered.is_a?(String) ? filtered : JSON.generate(filtered)
  text.length > max_length ? "#{text[0...max_length]}..." : text
end

#pgbus_locale_flag(code) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/helpers/pgbus/application_helper.rb', line 251

def pgbus_locale_flag(code)
  case code.to_sym
  when :da then "\u{1F1E9}\u{1F1F0}"
  when :de then "\u{1F1E9}\u{1F1EA}"
  when :en then "\u{1F1EC}\u{1F1E7}"
  when :es then "\u{1F1EA}\u{1F1F8}"
  when :fi then "\u{1F1EB}\u{1F1EE}"
  when :fr then "\u{1F1EB}\u{1F1F7}"
  when :it then "\u{1F1EE}\u{1F1F9}"
  when :ja then "\u{1F1EF}\u{1F1F5}"
  when :nb then "\u{1F1F3}\u{1F1F4}"
  when :nl then "\u{1F1F3}\u{1F1F1}"
  when :pt then "\u{1F1F5}\u{1F1F9}"
  when :sv then "\u{1F1F8}\u{1F1EA}"
  else "\u{1F310}"
  end
end

#pgbus_locale_name(code) ⇒ Object



247
248
249
# File 'app/helpers/pgbus/application_helper.rb', line 247

def pgbus_locale_name(code)
  LOCALE_NAMES[code.to_sym] || code.to_s.upcase
end


222
223
224
225
226
227
228
229
230
# File 'app/helpers/pgbus/application_helper.rb', line 222

def pgbus_mobile_nav_link(label, path)
  active = request.path == path || (path != pgbus.root_path && request.path.start_with?(path))
  css = if active
          "block rounded-md px-3 py-2 text-base font-medium text-white bg-gray-800"
        else
          "block rounded-md px-3 py-2 text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700"
        end
  link_to label, path, class: css
end

#pgbus_ms_duration(millis) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/helpers/pgbus/application_helper.rb', line 114

def pgbus_ms_duration(millis)
  return "" unless millis

  millis = millis.to_i
  if millis < 1000
    "#{millis}ms"
  elsif millis < 60_000
    "#{(millis / 1000.0).round(1)}s"
  else
    "#{(millis / 60_000.0).round(1)}m"
  end
end


212
213
214
215
216
217
218
219
220
# File 'app/helpers/pgbus/application_helper.rb', line 212

def pgbus_nav_link(label, path)
  active = request.path == path || (path != pgbus.root_path && request.path.start_with?(path))
  css = if active
          "rounded-md px-3 py-2 text-sm font-medium text-white bg-gray-800"
        else
          "rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700"
        end
  link_to label, path, class: css
end

#pgbus_number(n) ⇒ Object



21
22
23
24
25
26
27
28
29
30
# File 'app/helpers/pgbus/application_helper.rb', line 21

def pgbus_number(n)
  return "0" unless n

  n = n.to_i
  case n
  when 0..999 then n.to_s
  when 1_000..999_999 then "#{(n / 1_000.0).round(1)}K"
  else "#{(n / 1_000_000.0).round(1)}M"
  end
end

#pgbus_parse_message(message) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/helpers/pgbus/application_helper.rb', line 146

def pgbus_parse_message(message)
  return {} unless message

  parsed = case message
           when Hash then message
           when String then JSON.parse(message)
           else {}
           end
  Pgbus::Web::PayloadFilter.filter(parsed)
rescue JSON::ParserError
  {}
end

#pgbus_paused_badge(paused) ⇒ Object



127
128
129
130
131
132
# File 'app/helpers/pgbus/application_helper.rb', line 127

def pgbus_paused_badge(paused)
  return unless paused

  tag.span(I18n.t("pgbus.helpers.paused_badge"),
           class: "inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800")
end

#pgbus_queue_badge(name) ⇒ Object



89
90
91
92
93
94
95
96
97
# File 'app/helpers/pgbus/application_helper.rb', line 89

def pgbus_queue_badge(name)
  if name.to_s.end_with?("_dlq")
    tag.span(I18n.t("pgbus.helpers.queue_badge.dlq"),
             class: "inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700")
  else
    tag.span(I18n.t("pgbus.helpers.queue_badge.queue"),
             class: "inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700")
  end
end

#pgbus_recurring_health_badge(task) ⇒ Object



190
191
192
193
194
195
196
197
198
# File 'app/helpers/pgbus/application_helper.rb', line 190

def pgbus_recurring_health_badge(task)
  if task[:last_run_at].nil?
    tag.span(I18n.t("pgbus.helpers.recurring_health.pending"),
             class: "inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800")
  else
    tag.span(I18n.t("pgbus.helpers.recurring_health.active"),
             class: "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800")
  end
end

#pgbus_refresh_intervalObject



167
168
169
# File 'app/helpers/pgbus/application_helper.rb', line 167

def pgbus_refresh_interval
  Pgbus.configuration.web_refresh_interval
end

#pgbus_status_badge(healthy_or_status) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/helpers/pgbus/application_helper.rb', line 69

def pgbus_status_badge(healthy_or_status)
  status = case healthy_or_status
           when true then :healthy
           when Symbol, String then healthy_or_status.to_sym
           else :stale
           end

  case status
  when :healthy
    tag.span(I18n.t("pgbus.helpers.status_badge.healthy"),
             class: "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800")
  when :stalled
    tag.span(I18n.t("pgbus.helpers.status_badge.stalled"),
             class: "inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800")
  else
    tag.span(I18n.t("pgbus.helpers.status_badge.stale"),
             class: "inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800")
  end
end

#pgbus_time_ago(time) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'app/helpers/pgbus/application_helper.rb', line 7

def pgbus_time_ago(time)
  return "" unless time

  time = Time.parse(time) if time.is_a?(String)
  seconds = (Time.now - time).to_i

  case seconds
  when 0..59 then "#{seconds}s ago"
  when 60..3599 then "#{seconds / 60}m ago"
  when 3600..86_399 then "#{seconds / 3600}h ago"
  else "#{seconds / 86_400}d ago"
  end
end

#pgbus_time_ago_future(time) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'app/helpers/pgbus/application_helper.rb', line 171

def pgbus_time_ago_future(time)
  return "" unless time

  time = Time.parse(time) if time.is_a?(String)
  seconds = (time - Time.now).to_i

  if seconds <= 0
    "now"
  elsif seconds < 60
    "in #{seconds}s"
  elsif seconds < 3600
    "in #{seconds / 60}m"
  elsif seconds < 86_400
    "in #{seconds / 3600}h"
  else
    "in #{seconds / 86_400}d"
  end
end

#pgbus_time_range_label(minutes) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
# File 'app/helpers/pgbus/application_helper.rb', line 200

def pgbus_time_range_label(minutes)
  minutes = [minutes.to_i, 1].max

  if minutes > 1440 && (minutes % 1440).zero?
    pgbus_pluralize_unit(minutes / 1440, "day")
  elsif minutes >= 60 && (minutes % 60).zero?
    pgbus_pluralize_unit(minutes / 60, "hour")
  else
    pgbus_pluralize_unit(minutes, "minute")
  end
end

#pgbus_worker_rates(metadata) ⇒ Object

Render a worker's per-second throughput rates (from heartbeat metadata) as a compact human-readable string, e.g. "12.4/s processed · 0.2/s failed". Zero rates are omitted; returns nil when there are no non-zero rates so callers can fall back to the raw metadata rendering.



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

def pgbus_worker_rates()
  return nil unless .is_a?(Hash)

  rates = ["rates"]
  return nil unless rates.is_a?(Hash)

  parts = WORKER_RATE_KEYS.filter_map do |key|
    value = rates[key].to_f
    next if value.zero?

    label = I18n.t("pgbus.processes.processes_table.rates.#{key}", default: key)
    "#{value}/s #{label}"
  end
  return nil if parts.empty?

  parts.join(" · ")
end