jobq graph: state filter on /api/jobq/graph + multi-select checkboxes
GET /api/jobq/graph gains a states query param (comma-separated hive_jobq::State names): narrows the served root groups to the named states, keeping a group whole (filtering by a root's own state, which is already its subtree's rolled-up answer). Absent, empty, or fully unrecognised is the identity filter, matching prior behaviour. hive-jobq-graph.js gains a row of per-state checkboxes above the tree, re-fetching the endpoint with the selection on toggle. Default selection hides Done and Skipped. Server-side filtering (not client-side hiding) so hive-jobq-graph-update's node list, and everything downstream of it in builds.js (count pill, live-log panel), only ever sees what's actually shown.
This commit is contained in:
parent
ec476dfaae
commit
1e13b88c8c
6 changed files with 236 additions and 27 deletions
|
|
@ -651,24 +651,57 @@ 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.
|
||||
#[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",
|
||||
params(JobqGraphQuery),
|
||||
responses(
|
||||
(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 — a consumer \
|
||||
renders the graph without knowing what any node means.",
|
||||
(`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"
|
||||
)]
|
||||
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>> {
|
||||
axum::Json(state.coord.job_queue.graph_snapshot())
|
||||
let states = parse_states(q.states.as_deref());
|
||||
axum::Json(state.coord.job_queue.graph_snapshot(states.as_deref()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
|
|
|
|||
Loading…
Reference in a new issue