address review: switch hive-c0re to hive-jobq-wire's shared parse_states/filter_nodes_by_state, note the generic shape in endpoint docs

This commit is contained in:
damocles 2026-08-16 16:51:37 +02:00 committed by mara
commit eae04ac2c5
3 changed files with 29 additions and 108 deletions

View file

@ -580,35 +580,23 @@ fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
out
}
/// `/api/jobq/graph` query string. Today's only field is `states`: a
/// comma-separated allow-list of `hive_jobq::State` names (`"Pending"`,
/// `"Running"`, ...). Empty / absent ⇒ no filter (current behaviour, every
/// visible root). Set ⇒ only **root** groups whose own state is named are
/// served — a root's state is already its subtree's rolled-up answer (see
/// `hive_jobq_wire`'s doc), so filtering the root filters the whole group.
/// Unknown tokens are silently ignored (an unrecognised name matches
/// nothing rather than erroring the whole request), mirroring
/// `DashboardStreamQuery::kinds` above.
/// `/api/jobq/graph` query string — the generic jobq/wire query shape (any
/// host serving a `GraphWire` projection over HTTP takes the same one; see
/// `hive_jobq_wire::parse_states`, which does the actual parsing this side
/// of the query string). Today's only field is `states`: a comma-separated
/// allow-list of `hive_jobq::State` names (`"Pending"`, `"Running"`, ...).
/// Empty / absent ⇒ no filter (current behaviour, every visible root). Set
/// ⇒ only **root** groups whose own state is named are served — a root's
/// state is already its subtree's rolled-up answer (see `hive_jobq_wire`'s
/// doc), so filtering the root filters the whole group. Unknown tokens are
/// silently ignored (an unrecognised name matches nothing rather than
/// erroring the whole request), mirroring `DashboardStreamQuery::kinds`
/// above.
#[derive(Deserialize, Default, IntoParams)]
pub(super) struct JobqGraphQuery {
states: Option<String>,
}
/// Parses [`JobqGraphQuery::states`] into the list
/// [`crate::job_queue::JobQueue::graph_snapshot`] wants. `None` when absent
/// or when every token failed to parse — both mean "no filter" rather than
/// "match nothing", so an empty/garbled query reads as the unfiltered call
/// it replaces rather than an empty result set.
fn parse_states(raw: Option<&str>) -> Option<Vec<hive_jobq::State>> {
let states: Vec<hive_jobq::State> = raw?
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.filter_map(|s| serde_json::from_value(serde_json::Value::String(s.to_owned())).ok())
.collect();
(!states.is_empty()).then_some(states)
}
#[utoipa::path(
get,
path = "/api/jobq/graph",
@ -617,10 +605,14 @@ fn parse_states(raw: Option<&str>) -> Option<Vec<hive_jobq::State>> {
(status = 200, description = "every node of every retained job group, \
as generic `hive_jobq` graph nodes: identity, the parent tree, \
dependency edges with their accepted-outcome sets, lifecycle, and \
one opaque per-node payload. Group roots ride as ordinary nodes \
(`parent: null`) and `Done` nodes are not filtered by default a \
consumer renders the graph without knowing what any node means. \
`?states=` narrows to root groups in the named states.",
one opaque per-node payload. This is the generic jobq/wire shape \
(`hive_jobq_wire::GraphWire::wire_snapshot`), not a hive-c0re-only \
projection a `swarm-controller` serving its own graph responds \
with the same shape at the same path. Group roots ride as \
ordinary nodes (`parent: null`) and `Done` nodes are not \
filtered by default a consumer renders the graph without \
knowing what any node means. `?states=` narrows to root groups \
in the named states.",
body = Vec<hive_jobq_wire::GraphNode>),
),
tag = "state_snapshot"
@ -629,7 +621,7 @@ pub(super) async fn jobq_graph(
State(state): State<AppState>,
axum::extract::Query(q): axum::extract::Query<JobqGraphQuery>,
) -> axum::Json<Vec<hive_jobq_wire::GraphNode>> {
let states = parse_states(q.states.as_deref());
let states = hive_jobq_wire::parse_states(q.states.as_deref());
axum::Json(state.coord.job_queue.graph_snapshot(states.as_deref()))
}
@ -639,7 +631,9 @@ pub(super) async fn jobq_graph(
responses(
(status = 200, description = "counts by lifecycle state over the same \
groups `/api/jobq/graph` serves, as `(state, nodes, roots)` \
triples. Every state is present, zero counts included, in a fixed \
triples the generic jobq/wire roll-up shape \
(`hive_jobq_wire::state_rollup`), same as `/api/jobq/graph` \
above. Every state is present, zero counts included, in a fixed \
order a consumer renders a summary (\"3 running · 2 queued\") \
without fetching the graph and without re-deriving the tally. \
`roots` counts groups, `nodes` counts every step at any depth: one \