Skip to content

Commit 7f883b4

Browse files
committed
dashboard: prune irrelevant noops
1 parent a98e1a5 commit 7f883b4

File tree

1 file changed

+92
-17
lines changed

1 file changed

+92
-17
lines changed

rush-plugins/rush-serve-plugin/dashboard.html

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,51 +2188,126 @@ <h3>Phases</h3>
21882188
return arr[Symbol.iterator]();
21892189
}
21902190

2191-
// Derive array of operations used for graph layout with pruning of noop leaves.
2191+
// Derive array of operations used for graph layout with pruning of noop nodes.
2192+
// New behavior: skip noop nodes that are leaves (no dependents) OR that have exactly
2193+
// one incoming edge OR exactly one outgoing edge. When pruning a noop, we will
2194+
// rewire dependencies so remaining ops connect directly (i.e., bypass removed noops).
21922195
function computeGraphOperations() {
21932196
// Stable layout: always all operations; filtering only adjusts classes
21942197
const base = Array.from(operations.values());
21952198
computeFilterSets();
21962199
if (!base.length) return base;
2197-
// Build dependency reverse map (dependents list) inside the filtered set.
2200+
// Build lookup and static dependents map (dependents = incoming adjacency)
21982201
const byName = new Map();
21992202
base.forEach((o) => byName.set(o.name, o));
2200-
const dependents = new Map(); // name -> Set of dependent op names
2203+
const dependents = new Map(); // name -> Set of direct dependent op names
22012204
base.forEach((o) => {
22022205
(o.dependencies || []).forEach((d) => {
22032206
if (!byName.has(d)) return; // dependency outside filtered set not considered for pruning
22042207
(dependents.get(d) || dependents.set(d, new Set()).get(d)).add(o.name);
22052208
});
22062209
});
2207-
// We want to remove noop operations that have no dependents OR whose dependents are all pruned noop as well.
2208-
// Algorithm: iterative pruning queue starting with noop nodes with zero dependents.
2210+
2211+
// Active set initially contains all nodes; we'll iteratively remove noop nodes
2212+
// that meet the pruning criteria.
22092213
const active = new Set(base.map((o) => o.name));
22102214
const noopSet = new Set(base.filter((o) => o.noop).map((o) => o.name));
2211-
const dependentsCount = new Map();
2212-
active.forEach((n) => dependentsCount.set(n, (dependents.get(n) || new Set()).size));
2215+
2216+
// Compute initial counts (incoming = number of dependents, outgoing = number of deps)
2217+
const incomingCount = new Map();
2218+
const outgoingCount = new Map();
2219+
base.forEach((o) => {
2220+
incomingCount.set(o.name, (dependents.get(o.name) || new Set()).size);
2221+
const out = (o.dependencies || []).filter((d) => byName.has(d)).length;
2222+
outgoingCount.set(o.name, out);
2223+
});
2224+
2225+
// Queue noop nodes that are leaves or have exactly one incoming or one outgoing edge.
22132226
const queue = [];
22142227
active.forEach((n) => {
2215-
if (noopSet.has(n) && dependentsCount.get(n) === 0) queue.push(n);
2228+
if (!noopSet.has(n)) return;
2229+
const inc = incomingCount.get(n) || 0;
2230+
const out = outgoingCount.get(n) || 0;
2231+
if (inc === 0 || inc === 1 || out === 1) queue.push(n);
22162232
});
2233+
22172234
while (queue.length) {
22182235
const name = queue.pop();
22192236
if (!active.has(name)) continue;
2220-
// Remove this noop leaf
2237+
// Remove this noop node
22212238
active.delete(name);
2222-
// Decrement dependency counts for its dependencies (making them closer to leaves)
22232239
const op = byName.get(name);
22242240
if (!op) continue;
2241+
2242+
// For each dependency of the removed node, decrement its incoming count
22252243
for (const dep of op.dependencies || []) {
22262244
if (!active.has(dep)) continue;
2227-
const cnt = dependentsCount.get(dep);
2228-
if (cnt === undefined) continue;
2229-
dependentsCount.set(dep, cnt - 1);
2230-
if (cnt - 1 === 0 && noopSet.has(dep)) {
2231-
queue.push(dep);
2232-
}
2245+
const prev = incomingCount.get(dep) || 0;
2246+
incomingCount.set(dep, Math.max(0, prev - 1));
2247+
const inc = incomingCount.get(dep);
2248+
const out = outgoingCount.get(dep) || 0;
2249+
if (noopSet.has(dep) && (inc === 0 || inc === 1 || out === 1)) queue.push(dep);
2250+
}
2251+
2252+
// For each dependent (nodes that depended on the removed node), decrement their outgoing count
2253+
const depsOf = dependents.get(name) || new Set();
2254+
for (const dependent of depsOf) {
2255+
if (!active.has(dependent)) continue;
2256+
const prevOut = outgoingCount.get(dependent) || 0;
2257+
outgoingCount.set(dependent, Math.max(0, prevOut - 1));
2258+
const inc = incomingCount.get(dependent) || 0;
2259+
const out = outgoingCount.get(dependent) || 0;
2260+
if (noopSet.has(dependent) && (inc === 0 || inc === 1 || out === 1)) queue.push(dependent);
22332261
}
22342262
}
2235-
return base.filter((o) => active.has(o.name));
2263+
2264+
// At this point `active` contains node names to keep. We must return op objects
2265+
// whose dependency lists are rewritten so that any dependency chains that went
2266+
// through removed noop nodes are bypassed.
2267+
const removed = new Set(base.map((o) => o.name).filter((n) => !active.has(n)));
2268+
2269+
// Memoize resolved dependencies to avoid repeated recursion.
2270+
const resolvedMemo = new Map();
2271+
function resolveDeps(name, seen) {
2272+
if (resolvedMemo.has(name)) return resolvedMemo.get(name);
2273+
if (seen.has(name)) return new Set(); // break cycles defensively
2274+
seen.add(name);
2275+
const op = byName.get(name);
2276+
const out = new Set();
2277+
if (!op) return out;
2278+
for (const d of op.dependencies || []) {
2279+
if (!byName.has(d)) continue;
2280+
if (active.has(d)) {
2281+
out.add(d);
2282+
} else {
2283+
// dependency was removed; splice through it
2284+
const sub = resolveDeps(d, seen);
2285+
for (const s of sub) out.add(s);
2286+
}
2287+
}
2288+
seen.delete(name);
2289+
resolvedMemo.set(name, out);
2290+
return out;
2291+
}
2292+
2293+
const result = base
2294+
.filter((o) => active.has(o.name))
2295+
.map((o) => {
2296+
// Compute effective dependencies for o by splicing through removed noops
2297+
const deps = new Set();
2298+
for (const d of o.dependencies || []) {
2299+
if (!byName.has(d)) continue;
2300+
if (active.has(d)) deps.add(d);
2301+
else {
2302+
const sub = resolveDeps(d, new Set());
2303+
for (const s of sub) deps.add(s);
2304+
}
2305+
}
2306+
// Return a shallow copy so callers can freely inspect/modify dependencies
2307+
return Object.assign({}, o, { dependencies: Array.from(deps) });
2308+
});
2309+
2310+
return result;
22362311
}
22372312

22382313
function render() {

0 commit comments

Comments
 (0)