//! Read-only views of the swarm-level job graph. use axum::{Json, extract::State}; use hive_jobq_wire::GraphWire as _; use serde::Deserialize; use crate::routes::AppState; /// Query params for `GET /api/jobq/graph` — `?states=` narrows to root /// groups in the named states, same shape `hive_jobq_wire::parse_states` /// parses. #[derive(Deserialize, utoipa::IntoParams)] pub(crate) struct JobqGraphQuery { states: Option, } /// Every node of every root group in the swarm-level job graph. Nothing /// filters "done and old" here — with zero nodes ever submitted there is /// nothing to bound yet, so `graph.roots()` (everything) is the whole /// roster passed to [`hive_jobq_wire::GraphWire::wire_snapshot`]. #[utoipa::path( get, path = "/api/jobq/graph", params(JobqGraphQuery), responses((status = 200, description = "every node of every root group, as generic \ `hive_jobq` graph nodes. `?states=` narrows to root groups in the named states.", body = Vec)), tag = "jobq" )] pub(crate) async fn get_jobq_graph( State(state): State, axum::extract::Query(q): axum::extract::Query, ) -> Json> { let states = hive_jobq_wire::parse_states(q.states.as_deref()); let sched = state .jobq .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let graph = sched.graph(); let roots: Vec = graph.roots().map(|n| n.id).collect(); let nodes = graph.wire_snapshot(roots); Json(hive_jobq_wire::filter_nodes_by_state( nodes, states.as_deref(), )) } /// Counts by lifecycle state over the same groups `/api/jobq/graph` serves. #[utoipa::path( get, path = "/api/jobq/rollup", responses((status = 200, description = "counts by lifecycle state", body = Vec)), tag = "jobq" )] pub(crate) async fn get_jobq_rollup( State(state): State, ) -> Json> { let sched = state .jobq .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let graph = sched.graph(); let roots: Vec = graph.roots().map(|n| n.id).collect(); Json(hive_jobq_wire::state_rollup(graph, roots)) }