Skip to content

Commit 88c4d31

Browse files
authored
Fix handling of connections across nesting layers (#1293)
1 parent fca4d59 commit 88c4d31

7 files changed

Lines changed: 2377 additions & 23 deletions

File tree

src/main/frontend/pipeline-graph-view/pipeline-graph/main/NestedPipelineGraphLayout.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,30 @@ describe("NestedPipelineGraphLayout", () => {
9494
});
9595
});
9696

97+
describe("gh1286_wrapped_1st_skipped.jenkinsfile", () => {
98+
const raw =
99+
'[{"name":"Wrapper","state":"success","id":"4","type":"STAGE","children":[{"name":"Skipped 1","state":"skipped","id":"6","type":"STAGE","children":[]},{"name":"Not skipped","state":"success","id":"10","type":"STAGE","children":[]}]},{"name":"Next","state":"success","id":"17","type":"STAGE","children":[]}]';
100+
101+
it("should render layout", () => {
102+
shouldMatchSnapshot(raw, false);
103+
});
104+
it("should render collapsed layout", () => {
105+
shouldMatchSnapshot(raw, true);
106+
});
107+
});
108+
109+
describe("gh1286_wrapped_2nd_skipped.jenkinsfile", () => {
110+
const raw =
111+
'[{"name":"Wrapper","state":"success","id":"4","type":"STAGE","children":[{"name":"Not skipped","state":"success","id":"6","type":"STAGE","children":[]},{"name":"Skipped 1","state":"skipped","id":"11","type":"STAGE","children":[]}]},{"name":"Next","state":"success","id":"17","type":"STAGE","children":[]}]';
112+
113+
it("should render layout", () => {
114+
shouldMatchSnapshot(raw, false);
115+
});
116+
it("should render collapsed layout", () => {
117+
shouldMatchSnapshot(raw, true);
118+
});
119+
});
120+
97121
describe("gh1286_wrapped_all_skipped.jenkinsfile", () => {
98122
const raw =
99123
'[{"name":"Wrapper","state":"success","id":"4","type":"STAGE","children":[{"name":"Skipped 1","state":"skipped","id":"6","type":"STAGE","children":[]},{"name":"Skipped 2","state":"skipped","id":"10","type":"STAGE","children":[]}]},{"name":"Next","state":"success","id":"16","type":"STAGE","children":[]}]';

src/main/frontend/pipeline-graph-view/pipeline-graph/main/NestedPipelineGraphLayout.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,13 @@ function buildGraphNested(
219219
}
220220
const isChainedParallel =
221221
isParallel &&
222-
stage.children.length === 1 &&
222+
stage.children.length > 0 &&
223223
stage.children[0].children.length > 0 &&
224224
stage.children[0].children[0].type === "PARALLEL" &&
225225
stage.name === stage.children[0].name;
226226
const isSkipped = stage.state === Result.skipped;
227227
const firstChildIsSkipped =
228-
hasChildren && stage.children[0].state === Result.skipped;
228+
effectiveFirstChildStage(stage.children)?.state === Result.skipped;
229229

230230
const hasBranchLabel =
231231
isParallel &&
@@ -302,6 +302,13 @@ function buildGraphNested(
302302
}
303303
}
304304

305+
function effectiveFirstChildStage(children: StageInfo[]): StageInfo | null {
306+
if (children.length === 0) return null;
307+
if (children[0].type === "PARALLEL") return children[0];
308+
if (children[0].children.length === 0) return children[0];
309+
return effectiveFirstChildStage(children[0].children);
310+
}
311+
305312
function computePositions(
306313
node: GraphNode,
307314
extraXp: number,
@@ -348,6 +355,17 @@ function computeConnections(node: GraphNode): CompositeConnection[] {
348355
return connections;
349356
}
350357

358+
function resolveNestedSequentialGraphNode(node: GraphNode): GraphNode {
359+
// This node is a leaf.
360+
if (node.children.length === 0) return node;
361+
// Parallel nesting: Connect directly to parallel children.
362+
if (node.hasParallel) return node;
363+
// Sequential nesting with first child skipped: Connect to parent (non-skipped) before starting the skipped curve.
364+
if (node.firstChildIsSkipped) return node;
365+
// Sequential nesting: Connect directly to 1st child, recursively resolve it.
366+
return resolveNestedSequentialGraphNode(node.children[0]);
367+
}
368+
351369
function computeTailNodes(
352370
connections: CompositeConnection[],
353371
node: GraphNode,
@@ -363,17 +381,6 @@ function computeTailNodes(
363381
// Collect nodes in a Set. With two skipped nodes next to each other, we need to deduplicate them.
364382
const sourceNodes = new Set<GraphNode>();
365383
const skippedNodes = new Set<GraphNode>();
366-
const resolveDestination = (node: GraphNode): GraphNode[] => {
367-
if (node.hasParallel) {
368-
// Connect directly to parallel children
369-
return node.children;
370-
}
371-
if (node.children.length > 0) {
372-
// Connect directly to 1st child, recusively resolve it.
373-
return resolveDestination(node.children[0]);
374-
}
375-
return [node];
376-
};
377384
const connect = (
378385
tailNodes: GraphNode[],
379386
destination: GraphNode,
@@ -386,22 +393,26 @@ function computeTailNodes(
386393
skippedNodes.add(node);
387394
}
388395
}
389-
const destinationNodes = resolveDestination(destination);
396+
destination = resolveNestedSequentialGraphNode(destination);
397+
const destinationNodes = destination.hasParallel
398+
? destination.children
399+
: [destination];
390400
if (!destinationNodes.some((n) => !n.isSkipped)) {
391401
for (const node of destinationNodes) skippedNodes.add(node);
392402
return;
393403
}
404+
if (!sourceNodes.size) throw new Error("bug: empty sourceNodes");
394405
connections.push({
395406
sourceNodes: Array.from(sourceNodes),
396407
destinationNodes,
397408
skippedNodes: Array.from(skippedNodes),
398-
hasBranchLabels: destinationNodes.some((n) => n.hasBranchLabel),
409+
hasBranchLabels: destination.shiftX > 0, // shift curved connections
399410
});
400411
sourceNodes.clear();
401412
skippedNodes.clear();
402413
};
403-
if (node.isParallel) {
404-
// Non-parallel stages will have been connected already via resolveDestination.
414+
if (node.isParallel || node.firstChildIsSkipped) {
415+
// See comments in resolveNestedSequentialGraphNode.
405416
connect([node], node.children[0], true);
406417
}
407418
for (let i = 0; i < node.children.length - 1; i++) {

src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraphModel.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ export type GraphNode = {
112112
firstChildIsSkipped?: boolean;
113113
hasBigLabel?: boolean;
114114
hasBranchLabel?: boolean;
115-
hasChildWithBranchLabel?: boolean;
116115
hasParallel?: boolean;
117116
hasSmallLabel?: boolean;
118117
hasTiming?: boolean;

0 commit comments

Comments
 (0)