Module: ChronoForge::Dashboard::DashboardHelper
- Defined in:
- app/helpers/chrono_forge/dashboard/dashboard_helper.rb
Constant Summary collapse
- STATE_ORDER =
Display order for state counts: active work first, terminal last. Any unknown states are appended so a new core state never silently vanishes.
%w[running idle stalled failed completed].freeze
- KIND_LABELS =
Short, readable label for a parsed step kind.
{ execute: "execute", sleep: "wait", wait: "wait until", continue: "continue if", repeat_coordination: "repeat", repeat_run: "run", lifecycle: "workflow", branch: "branch", merge: "merge", unknown: "step" }.freeze
- META_SKIP =
Human-friendly [label, value] pairs of a step’s metadata for the timeline — surfaces things like a wait’s resume time, a wait_until timeout, or a durably_repeat’s last execution. Keys are humanized; values are stringified (the view truncates). Blank values are dropped. Internal bookkeeping surfaced elsewhere (the linked error is rendered inline; branch poll state + spawn cursors show in the Branches panel), so they’d just be noise in the timeline’s metadata line. poll_token is the merge poller’s fencing token — pure plumbing, never user-facing.
%w[error_log_id poll poll_token cursors].freeze
Instance Method Summary collapse
-
#cf_absolute_time? ⇒ Boolean
Whether the viewer prefers absolute timestamps (cookie-persisted nav toggle).
-
#cf_ago(t) ⇒ Object
A timestamp shown relative (“3 minutes ago”) or absolute (raw ISO8601) per the viewer’s preference, with the other form available on hover.
- #cf_badge(state) ⇒ Object
-
#cf_bar_width(value, max) ⇒ Object
Class name for a stacked-bar segment, width quantized to 5% steps so it stays CSP-safe (no inline style — see .cf-bar-0.0..100 in tailwind.css).
-
#cf_capped(count, cap) ⇒ Object
A capped count: shows “5000+” once the count saturates its cap.
-
#cf_chip(extra = nil) ⇒ Object
Shared “chip” treatment for inline nav/action links (metrics, details, repetitions, open, pagination, back) — a subtle bordered button, never an underlined text link.
- #cf_dot(state) ⇒ Object
-
#cf_duration(from, to) ⇒ Object
Human duration between two times (e.g. “1m 04s”); “—” if unfinished.
- #cf_kind_label(kind) ⇒ Object
-
#cf_latency_summary(latencies) ⇒ Object
Concise latency summary (avg + most recent) from a list of run seconds.
- #cf_meta_pairs(metadata) ⇒ Object
-
#cf_pct(rate) ⇒ Object
A rate (0.0–1.0) as a percentage; “—” if nil.
- #cf_poll_interval ⇒ Object
- #cf_poll_label(secs) ⇒ Object
-
#cf_poll_options ⇒ Object
Auto-refresh interval in seconds (0 = off).
-
#cf_secs(secs) ⇒ Object
Human duration from a number of seconds, scaled to the two most-significant units (e.g. “45s”, “1m 04s”, “3h 12m”, “2d 21h”); “—” if nil.
-
#cf_state_badge(workflow, wait = nil) ⇒ Object
State badge, upgraded to “scheduled” for an idle workflow parked on a wait whose wake time is still in the future — so genuinely-scheduled work doesn’t read as “stuck idle”.
- #cf_state_order(keys) ⇒ Object
-
#cf_status_color(status) ⇒ Object
Text color for an execution-log status (pending/completed/failed).
- #cf_time(t) ⇒ Object
Instance Method Details
#cf_absolute_time? ⇒ Boolean
Whether the viewer prefers absolute timestamps (cookie-persisted nav toggle).
45 46 47 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 45 def cf_absolute_time? [:cf_time_format] == "absolute" end |
#cf_ago(t) ⇒ Object
A timestamp shown relative (“3 minutes ago”) or absolute (raw ISO8601) per the viewer’s preference, with the other form available on hover.
66 67 68 69 70 71 72 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 66 def cf_ago(t) return "—" unless t rel = "#{time_ago_in_words(t)} ago" abs = t.iso8601 shown, hover = cf_absolute_time? ? [abs, rel] : [rel, abs] tag.span(shown, title: hover, class: "cursor-help") end |
#cf_badge(state) ⇒ Object
12 13 14 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 12 def cf_badge(state) tag.span(state, class: "cf-pill cf-pill-#{state}") end |
#cf_bar_width(value, max) ⇒ Object
Class name for a stacked-bar segment, width quantized to 5% steps so it stays CSP-safe (no inline style — see .cf-bar-ChronoForge::Dashboard::DashboardHelper.0.0..100 in tailwind.css).
93 94 95 96 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 93 def (value, max) pct = (max.to_f.zero? ? 0 : (value / max.to_f * 100)) "cf-bar-#{(pct / 5).round * 5}" end |
#cf_capped(count, cap) ⇒ Object
A capped count: shows “5000+” once the count saturates its cap.
40 41 42 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 40 def cf_capped(count, cap) (count >= cap) ? "#{cap}+" : count.to_s end |
#cf_chip(extra = nil) ⇒ Object
Shared “chip” treatment for inline nav/action links (metrics, details, repetitions, open, pagination, back) — a subtle bordered button, never an underlined text link. Pass extra utility classes (margins, truncation).
19 20 21 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 19 def cf_chip(extra = nil) ["inline-flex items-center rounded-md border border-zinc-200 px-2 py-0.5 text-xs text-zinc-600 hover:bg-zinc-50", extra].compact.join(" ") end |
#cf_dot(state) ⇒ Object
31 32 33 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 31 def cf_dot(state) tag.span(class: "cf-dot cf-dot-#{state}") end |
#cf_duration(from, to) ⇒ Object
Human duration between two times (e.g. “1m 04s”); “—” if unfinished.
75 76 77 78 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 75 def cf_duration(from, to) return "—" unless from && to cf_secs((to - from).to_i) end |
#cf_kind_label(kind) ⇒ Object
122 123 124 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 122 def cf_kind_label(kind) KIND_LABELS.fetch(kind, kind.to_s) end |
#cf_latency_summary(latencies) ⇒ Object
Concise latency summary (avg + most recent) from a list of run seconds.
109 110 111 112 113 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 109 def cf_latency_summary(latencies) return "—" if latencies.blank? avg = (latencies.sum.to_f / latencies.size).round "avg #{avg}s · last #{latencies.last}s" end |
#cf_meta_pairs(metadata) ⇒ Object
136 137 138 139 140 141 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 136 def () return [] unless .is_a?(Hash) .reject { |k, v| v.nil? || v == "" || META_SKIP.include?(k.to_s) } .map { |k, v| [k.to_s.tr("_", " "), v.to_s] } end |
#cf_pct(rate) ⇒ Object
A rate (0.0–1.0) as a percentage; “—” if nil. Keeps tiny non-zero rates visible (a 0.0008% workflow-failure rate shows “<0.01%”, never “0%”).
100 101 102 103 104 105 106 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 100 def cf_pct(rate) return "—" if rate.nil? pct = rate * 100 return "0%" if pct.zero? return "<0.01%" if pct < 0.01 (pct < 1) ? "#{pct.round(2)}%" : "#{pct.round}%" end |
#cf_poll_interval ⇒ Object
53 54 55 56 57 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 53 def cf_poll_interval raw = [:cf_poll_interval] return raw.to_i if raw.present? && raw.match?(/\A\d+\z/) ChronoForge::Dashboard.config.polling_interval.to_i end |
#cf_poll_label(secs) ⇒ Object
59 60 61 62 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 59 def cf_poll_label(secs) return "off" if secs.zero? (secs % 60 == 0) ? "#{secs / 60}m" : "#{secs}s" end |
#cf_poll_options ⇒ Object
Auto-refresh interval in seconds (0 = off). A cookie-persisted nav control overrides the configured default per viewer; options come from config.
51 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 51 def = ChronoForge::Dashboard.config. |
#cf_secs(secs) ⇒ Object
Human duration from a number of seconds, scaled to the two most-significant units (e.g. “45s”, “1m 04s”, “3h 12m”, “2d 21h”); “—” if nil.
82 83 84 85 86 87 88 89 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 82 def cf_secs(secs) return "—" if secs.nil? secs = secs.to_i return "#{secs}s" if secs < 60 return "#{secs / 60}m #{(secs % 60).to_s.rjust(2, "0")}s" if secs < 3600 return "#{secs / 3600}h #{(secs % 3600 / 60).to_s.rjust(2, "0")}m" if secs < 86400 "#{secs / 86400}d #{(secs % 86400 / 3600).to_s.rjust(2, "0")}h" end |
#cf_state_badge(workflow, wait = nil) ⇒ Object
State badge, upgraded to “scheduled” for an idle workflow parked on a wait whose wake time is still in the future — so genuinely-scheduled work doesn’t read as “stuck idle”.
26 27 28 29 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 26 def cf_state_badge(workflow, wait = nil) return cf_badge("scheduled") if workflow.idle? && wait&.scheduled? cf_badge(workflow.state) end |
#cf_state_order(keys) ⇒ Object
8 9 10 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 8 def cf_state_order(keys) (STATE_ORDER & keys) + (keys - STATE_ORDER) end |
#cf_status_color(status) ⇒ Object
Text color for an execution-log status (pending/completed/failed).
144 145 146 147 148 149 150 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 144 def cf_status_color(status) case status when "completed" then "text-emerald-600" when "failed" then "text-rose-600" else "text-zinc-500" end end |
#cf_time(t) ⇒ Object
35 36 37 |
# File 'app/helpers/chrono_forge/dashboard/dashboard_helper.rb', line 35 def cf_time(t) t&.iso8601 || "—" end |