Class: ChronoForge::Dashboard::BranchPresenter
- Inherits:
-
Object
- Object
- ChronoForge::Dashboard::BranchPresenter
- Defined in:
- app/presenters/chrono_forge/dashboard/branch_presenter.rb
Overview
Health of a single branch (a branch$
Constant Summary collapse
- CAP =
5000- BLOCKED_STATES =
%i[failed stalled].map { |s| ChronoForge::Workflow.states[s] }.freeze
- POLL_OVERDUE_GRACE =
A scheduled next poll this far past due means the BranchMergeJob poller likely never ran (queue latency aside) — a heuristic, hence "potential".
120
Instance Attribute Summary collapse
-
#log ⇒ Object
readonly
Returns the value of attribute log.
-
#merge_state ⇒ Object
readonly
Returns the value of attribute merge_state.
Instance Method Summary collapse
- #blocked ⇒ Object
- #cap ⇒ Object
- #eta_seconds ⇒ Object
- #exact_never_started ⇒ Object
-
#exact_pending ⇒ Object
The FULL (uncapped) pending / never-started counts the poller ALREADY records each pass — so the dashboard can show the real number instead of the capped "CAP+", with NO new query.
-
#exact_spawned ⇒ Object
Total spawned, counted once and cached by the poller when the branch sealed (see BranchProbe#spawned) — exact and free.
-
#initialize(log, merge_state = nil) ⇒ BranchPresenter
constructor
merge_state: :merged | :merging | nil (not yet merged).
- #last_polled_at ⇒ Object
- #last_rekick_at ⇒ Object
- #name ⇒ Object
-
#never_started ⇒ Object
Children dispatched but not yet started (idle, started_at nil) — how much of this branch hasn't been picked up yet.
- #next_poll_at ⇒ Object
- #pending ⇒ Object
-
#poll_overdue? ⇒ Boolean
next_poll_at is nil once the merge completes, so a finished merge never looks overdue; a non-nil time well in the past = the poller is likely dead.
-
#polled? ⇒ Boolean
The BranchMergeJob stamps its poll state onto the branch log's metadata (it can't be queried from the backend; ActiveJob has no such API).
- #polls ⇒ Object
-
#rate ⇒ Object
Live throughput/ETA from the last poll (the poller measures the branch's completion rate each pass).
-
#rekicks ⇒ Object
Dropped-child recovery: how many children the poller has rekicked, and when it last did (nil if never).
-
#sealed? ⇒ Boolean
The branch is "sealed" once its block closed (done dispatching children).
- #spawned ⇒ Object
- #throughput? ⇒ Boolean
Constructor Details
#initialize(log, merge_state = nil) ⇒ BranchPresenter
merge_state: :merged | :merging | nil (not yet merged)
11 12 13 14 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 11 def initialize(log, merge_state = nil) @log = log @merge_state = merge_state end |
Instance Attribute Details
#log ⇒ Object (readonly)
Returns the value of attribute log.
16 17 18 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 16 def log @log end |
#merge_state ⇒ Object (readonly)
Returns the value of attribute merge_state.
16 17 18 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 16 def merge_state @merge_state end |
Instance Method Details
#blocked ⇒ Object
27 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 27 def blocked = capped(children.where(state: BLOCKED_STATES)) |
#cap ⇒ Object
29 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 29 def cap = CAP |
#eta_seconds ⇒ Object
59 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 59 def eta_seconds = poll&.dig("eta_seconds") |
#exact_never_started ⇒ Object
73 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 73 def exact_never_started = poll&.dig("never_started") |
#exact_pending ⇒ Object
The FULL (uncapped) pending / never-started counts the poller ALREADY records each pass — so the dashboard can show the real number instead of the capped "CAP+", with NO new query. nil until the branch has been polled; callers fall back to the capped count. (The poll's "never_started" is the never-started count.)
71 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 71 def exact_pending = poll&.dig("pending") |
#exact_spawned ⇒ Object
Total spawned, counted once and cached by the poller when the branch sealed (see BranchProbe#spawned) — exact and free. nil until sealed+polled; the "Spawned" column then falls back to the capped live count.
78 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 78 def exact_spawned = poll&.dig("spawned") |
#last_polled_at ⇒ Object
41 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 41 def last_polled_at = parse_time(poll&.dig("last_polled_at")) |
#last_rekick_at ⇒ Object
84 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 84 def last_rekick_at = parse_time(poll&.dig("last_rekick_at")) |
#name ⇒ Object
18 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 18 def name = StepNameParser.parse(@log.step_name).name |
#never_started ⇒ Object
Children dispatched but not yet started (idle, started_at nil) — how much of this branch hasn't been picked up yet. Capped/index-only like the other counts.
65 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 65 def never_started = capped(children.where(state: ChronoForge::Workflow.states[:idle], started_at: nil)) |
#next_poll_at ⇒ Object
43 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 43 def next_poll_at = parse_time(poll&.dig("next_poll_at")) |
#pending ⇒ Object
25 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 25 def pending = capped(children.where.not(state: ChronoForge::Workflow.states[:completed])) |
#poll_overdue? ⇒ Boolean
next_poll_at is nil once the merge completes, so a finished merge never looks overdue; a non-nil time well in the past = the poller is likely dead.
49 50 51 52 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 49 def poll_overdue? t = next_poll_at t.present? && t < Time.current - POLL_OVERDUE_GRACE end |
#polled? ⇒ Boolean
The BranchMergeJob stamps its poll state onto the branch log's metadata (it can't be queried from the backend; ActiveJob has no such API).
39 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 39 def polled? = poll.present? |
#polls ⇒ Object
45 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 45 def polls = poll&.dig("polls").to_i |
#rate ⇒ Object
Live throughput/ETA from the last poll (the poller measures the branch's completion rate each pass). The wake poll records rate 0, so a positive rate means the branch is still draining — a merged/idle branch shows no gauge.
57 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 57 def rate = poll&.dig("rate").to_f |
#rekicks ⇒ Object
Dropped-child recovery: how many children the poller has rekicked, and when it last did (nil if never).
82 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 82 def rekicks = poll&.dig("rekick_total").to_i |
#sealed? ⇒ Boolean
The branch is "sealed" once its block closed (done dispatching children).
21 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 21 def sealed? = @log.completed? |
#spawned ⇒ Object
23 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 23 def spawned = capped(children) |
#throughput? ⇒ Boolean
61 |
# File 'app/presenters/chrono_forge/dashboard/branch_presenter.rb', line 61 def throughput? = rate > 0 |