Class: ChronoForge::Dashboard::RepetitionsQuery

Inherits:
Object
  • Object
show all
Defined in:
app/queries/chrono_forge/dashboard/repetitions_query.rb

Overview

The per-iteration run logs of a single durably_repeat step.

These live on their own page rather than in the timeline: a long-running periodic step can accumulate many runs — mostly catch-up “tombstones” (expired/retried repetitions the engine marks failed and moves past) — and inlining them would both bury the timeline and load an unbounded set.

All access is keyed on ‘[workflow_id, step_name LIKE ’durably_repeat$step$%‘]`, which rides the unique `[workflow_id, step_name]` index as a range scan, so the summary counts and keyset pages stay cheap regardless of history depth.

Constant Summary collapse

DEFAULT_PER =
50
MAX_PER =
200
CATCHUP_SCAN_CAP =

Bound the metadata scan used to count fast-forwarded ticks (see #summary): a pre-upgrade step may carry a long history of legacy per-tick rows.

1_000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(workflow:, step:, before: nil, after: nil, per: DEFAULT_PER) ⇒ RepetitionsQuery

Returns a new instance of RepetitionsQuery.



20
21
22
23
24
25
26
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 20

def initialize(workflow:, step:, before: nil, after: nil, per: DEFAULT_PER)
  @workflow = workflow
  @step = step
  @before = before.presence&.to_i
  @after = after.presence&.to_i
  @per = per.to_i.clamp(1, MAX_PER)
end

Instance Attribute Details

#perObject (readonly)

Returns the value of attribute per.



28
29
30
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 28

def per
  @per
end

#stepObject (readonly)

Returns the value of attribute step.



28
29
30
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 28

def step
  @step
end

#workflowObject (readonly)

Returns the value of attribute workflow.



28
29
30
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 28

def workflow
  @workflow
end

Instance Method Details

#has_next?Boolean

Returns:

  • (Boolean)


35
36
37
38
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 35

def has_next?
  load
  @has_next
end

#has_prev?Boolean

Returns:

  • (Boolean)


40
41
42
43
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 40

def has_prev?
  load
  @has_prev
end

#next_cursorObject



45
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 45

def next_cursor = records.last&.id

#prev_cursorObject



46
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 46

def prev_cursor = records.first&.id

#recordsObject



30
31
32
33
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 30

def records
  load
  @records
end

#scopeObject



71
72
73
74
75
76
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 71

def scope
  @workflow.execution_logs.where(
    "step_name LIKE ?",
    "durably_repeat#{StepNameParser::DELIM}#{@step}#{StepNameParser::DELIM}%"
  )
end

#summaryObject

Cheap roll-up (counts + last run) without loading run rows. Grouping by the ‘state` enum yields string-label keys (“completed”/“failed”), not integers.

‘tombstones` is the number of catch-up rows (cheap group count). `skipped_ticks` is the true number of skipped ticks: a fast-forward summary row collapses N expired ticks into one failed row tagged `fast_forwarded: N`, so it counts as N, while a legacy per-tick tombstone counts as 1. They diverge only once a fast-forward has happened.



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/queries/chrono_forge/dashboard/repetitions_query.rb', line 57

def summary
  @summary ||= begin
    by_state = scope.group(:state).count
    failed = by_state["failed"].to_i
    {
      iterations: by_state.values.sum,
      completed: by_state["completed"].to_i,
      tombstones: failed,
      skipped_ticks: failed.zero? ? 0 : skipped_tick_count,
      last_run_at: scope.maximum(:started_at)
    }
  end
end