fix(#2465): render multi-agent DAG nodes as per-agent subgraph lines
renderQueueEntry flattened entry.nodes into one arrow-joined chain regardless of which agent each node belongs to. A multi-agent DAG (e.g. hivectl restart --graceful with several agents) runs independent per-agent subgraphs concurrently, no cross-agent deps, so joining them all into one sequential-looking chain misrepresented the actual DAG shape (mara's report: 'expected one dag that forks after the start node into the per agent sub dags'). Group nodes by n.agent (stable, first-seen order) and render one .rqe-nodes line per agent, with a small agent-label chip when the DAG spans more than one. Single-agent DAGs (the common case) collapse back to exactly the prior one-line render — no visible change there.
This commit is contained in:
parent
0d40583991
commit
45cdd62116
2 changed files with 47 additions and 18 deletions
|
|
@ -307,27 +307,52 @@ function renderQueueEntry(entry, _byId, isChild) {
|
||||||
// state glyph, live step label, and build-log link. This is the
|
// state glyph, live step label, and build-log link. This is the
|
||||||
// node-aware render that makes queue jumps / partial progress
|
// node-aware render that makes queue jumps / partial progress
|
||||||
// visible (e.g. reconcile running while swap failed).
|
// visible (e.g. reconcile running while swap failed).
|
||||||
|
//
|
||||||
|
// A DAG can span multiple agents (e.g. a hive-wide restart) as
|
||||||
|
// independent per-agent subgraphs with no cross-agent deps — they run
|
||||||
|
// concurrently, not sequentially. Joining every node in array order
|
||||||
|
// with a single `→` chain misrepresents that as one long sequential
|
||||||
|
// pipeline. Group by `n.agent` (stable, first-seen order) and render
|
||||||
|
// each agent's subgraph on its own line instead; single-agent DAGs
|
||||||
|
// (the common case) collapse back to exactly the old one-line render.
|
||||||
const nodes = entry.nodes || [];
|
const nodes = entry.nodes || [];
|
||||||
if (nodes.length) {
|
if (nodes.length) {
|
||||||
const chain = el('div', { class: 'rqe-nodes' });
|
const groups = [];
|
||||||
nodes.forEach((n, i) => {
|
const groupByAgent = new Map();
|
||||||
if (i > 0) chain.append(el('span', { class: 'rqe-node-arrow' }, ' → '));
|
for (const n of nodes) {
|
||||||
const chip = el('span', {
|
let g = groupByAgent.get(n.agent);
|
||||||
class: 'rqe-node rqe-node-' + n.state,
|
if (!g) {
|
||||||
title: n.kind + ' · ' + n.state + (n.error ? ' — ' + n.error : ''),
|
g = [];
|
||||||
},
|
groupByAgent.set(n.agent, g);
|
||||||
(QUEUE_STATE_GLYPH[n.state] || '?') + ' ' + (NODE_KIND_LABEL[n.kind] || n.kind));
|
groups.push([n.agent, g]);
|
||||||
chain.append(chip);
|
|
||||||
if (n.build_log_id != null) {
|
|
||||||
chain.append(el('a', {
|
|
||||||
class: 'rqe-log-link rqe-node-log',
|
|
||||||
href: '/builds.html?id=' + n.build_log_id + '#buildlogs',
|
|
||||||
target: '_blank',
|
|
||||||
title: 'view build log #' + n.build_log_id,
|
|
||||||
}, '⎙'));
|
|
||||||
}
|
}
|
||||||
});
|
g.push(n);
|
||||||
li.append(chain);
|
}
|
||||||
|
const multiAgent = groups.length > 1;
|
||||||
|
for (const [agent, groupNodes] of groups) {
|
||||||
|
const chain = el('div', { class: 'rqe-nodes' });
|
||||||
|
if (multiAgent) {
|
||||||
|
chain.append(el('code', { class: 'rqe-node-agent-label' }, agent));
|
||||||
|
}
|
||||||
|
groupNodes.forEach((n, i) => {
|
||||||
|
if (i > 0) chain.append(el('span', { class: 'rqe-node-arrow' }, ' → '));
|
||||||
|
const chip = el('span', {
|
||||||
|
class: 'rqe-node rqe-node-' + n.state,
|
||||||
|
title: n.kind + ' · ' + n.state + (n.error ? ' — ' + n.error : ''),
|
||||||
|
},
|
||||||
|
(QUEUE_STATE_GLYPH[n.state] || '?') + ' ' + (NODE_KIND_LABEL[n.kind] || n.kind));
|
||||||
|
chain.append(chip);
|
||||||
|
if (n.build_log_id != null) {
|
||||||
|
chain.append(el('a', {
|
||||||
|
class: 'rqe-log-link rqe-node-log',
|
||||||
|
href: '/builds.html?id=' + n.build_log_id + '#buildlogs',
|
||||||
|
target: '_blank',
|
||||||
|
title: 'view build log #' + n.build_log_id,
|
||||||
|
}, '⎙'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
li.append(chain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const running = runningNode(entry);
|
const running = runningNode(entry);
|
||||||
if (running && running.step) {
|
if (running && running.step) {
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,10 @@
|
||||||
.rqe-node-cancelled { opacity: 0.55; text-decoration: line-through; }
|
.rqe-node-cancelled { opacity: 0.55; text-decoration: line-through; }
|
||||||
.rqe-node-arrow { color: var(--muted); }
|
.rqe-node-arrow { color: var(--muted); }
|
||||||
.rqe-node-log { margin-left: 0.1em; text-decoration: none; }
|
.rqe-node-log { margin-left: 0.1em; text-decoration: none; }
|
||||||
|
/* Per-agent subgraph label, shown only when a DAG's nodes span more than
|
||||||
|
one agent (independent concurrent subgraphs, one `.rqe-nodes` line each —
|
||||||
|
see renderQueueEntry in builds.js). */
|
||||||
|
.rqe-node-agent-label { color: var(--amber); margin-right: 0.3em; }
|
||||||
.rqe-step {
|
.rqe-step {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
margin: 0.1em 0 0 1.8em;
|
margin: 0.1em 0 0 1.8em;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue