Skip to content

Commit 5f35529

Browse files
mikedldtimja
andauthored
Use tree representation for graph layout (#72)
Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com>
1 parent b5b2029 commit 5f35529

File tree

4 files changed

+274
-34
lines changed

4 files changed

+274
-34
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class PipelineGraph extends React.Component {
7676
onPipelineDataReceived,
7777
onPollingError,
7878
onPipelineComplete,
79-
this.props.path ?? "graph"
79+
this.props.path ?? "tree"
8080
);
8181
}
8282

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import { NodeInfo, Result, StageInfo, StageType } from "./PipelineGraphModel";
2+
import { createNodeColumns } from "./PipelineGraphLayout";
3+
4+
describe("PipelineGraphLayout", () => {
5+
describe("createNodeColumns", () => {
6+
const baseStage: StageInfo = {
7+
name: "",
8+
title: "",
9+
state: Result.success,
10+
completePercent: 50,
11+
id: 0,
12+
type: "STAGE",
13+
children: [],
14+
};
15+
16+
const makeStage = (
17+
id: number,
18+
name: string,
19+
children: Array<StageInfo> = []
20+
): StageInfo => {
21+
return { ...baseStage, id, name, children };
22+
};
23+
24+
const makeParallel = (
25+
id: number,
26+
name: string,
27+
children: Array<StageInfo> = []
28+
): StageInfo => {
29+
return {
30+
...baseStage,
31+
id,
32+
name,
33+
children,
34+
type: "PARALLEL" as StageType,
35+
};
36+
};
37+
38+
const makeNode = (
39+
id: number,
40+
name: string,
41+
seqContainerName: string | undefined = undefined
42+
) => {
43+
return {
44+
id,
45+
name,
46+
stage: { id, name },
47+
...(seqContainerName !== undefined ? { seqContainerName } : {}),
48+
};
49+
};
50+
51+
it("returns no columns if no stages", () => {
52+
const columns = createNodeColumns([], false);
53+
expect(columns).toEqual([]);
54+
});
55+
56+
it("returns proper columns (unstableSmokes)", () => {
57+
const columns = createNodeColumns(
58+
[
59+
makeStage(4, "unstable-one"),
60+
makeStage(15, "success"),
61+
makeStage(20, "unstable-two"),
62+
makeStage(26, "failure"),
63+
],
64+
false
65+
);
66+
67+
expect(columns).toMatchObject([
68+
{
69+
hasBranchLabels: false,
70+
topStage: { name: "unstable-one", id: 4 },
71+
rows: [[makeNode(4, "unstable-one")]],
72+
},
73+
{
74+
hasBranchLabels: false,
75+
topStage: { name: "success", id: 15 },
76+
rows: [[makeNode(15, "success")]],
77+
},
78+
{
79+
hasBranchLabels: false,
80+
topStage: { name: "unstable-two", id: 20 },
81+
rows: [[makeNode(20, "unstable-two")]],
82+
},
83+
{
84+
hasBranchLabels: false,
85+
topStage: { name: "failure", id: 26 },
86+
rows: [[makeNode(26, "failure")]],
87+
},
88+
]);
89+
});
90+
91+
it("returns proper columns (complexSmokes)", () => {
92+
const columns = createNodeColumns(
93+
[
94+
makeStage(6, "Non-Parallel Stage"),
95+
makeStage(11, "Parallel Stage", [
96+
makeParallel(15, "Branch A"),
97+
makeParallel(16, "Branch B"),
98+
makeParallel(17, "Branch C", [
99+
makeStage(25, "Nested 1"),
100+
makeStage(38, "Nested 2"),
101+
]),
102+
]),
103+
makeStage(49, "Skipped stage"),
104+
makeStage(53, "Parallel Stage 2", [
105+
makeParallel(57, "Branch A"),
106+
makeParallel(58, "Branch B"),
107+
makeParallel(59, "Branch C", [
108+
makeStage(67, "Nested 1"),
109+
makeStage(80, "Nested 2"),
110+
]),
111+
]),
112+
],
113+
false
114+
);
115+
116+
expect(columns).toMatchObject([
117+
{
118+
hasBranchLabels: false,
119+
topStage: { name: "Non-Parallel Stage", id: 6 },
120+
rows: [[makeNode(6, "Non-Parallel Stage")]],
121+
},
122+
{
123+
hasBranchLabels: true,
124+
topStage: { name: "Parallel Stage", id: 11 },
125+
rows: [
126+
[makeNode(15, "Branch A")],
127+
[makeNode(16, "Branch B")],
128+
[
129+
makeNode(25, "Nested 1", "Branch C"),
130+
makeNode(38, "Nested 2", "Branch C"),
131+
],
132+
],
133+
},
134+
{
135+
hasBranchLabels: false,
136+
topStage: { name: "Skipped stage", id: 49 },
137+
rows: [[makeNode(49, "Skipped stage")]],
138+
},
139+
{
140+
hasBranchLabels: true,
141+
topStage: { name: "Parallel Stage 2", id: 53 },
142+
rows: [
143+
[makeNode(57, "Branch A")],
144+
[makeNode(58, "Branch B")],
145+
[
146+
makeNode(67, "Nested 1", "Branch C"),
147+
makeNode(80, "Nested 2", "Branch C"),
148+
],
149+
],
150+
},
151+
]);
152+
});
153+
154+
it("returns proper columns (GH#63.1)", () => {
155+
const columns = createNodeColumns(
156+
[
157+
makeStage(6, "Test", [
158+
makeParallel(9, "Matrix - PLATFORM = '1'", [
159+
makeStage(20, "Stage 1"),
160+
makeStage(30, "Stage 2"),
161+
makeStage(40, "Stage 3"),
162+
]),
163+
makeParallel(10, "Matrix - PLATFORM = '2'", [
164+
makeStage(22, "Stage 1"),
165+
makeStage(32, "Stage 2"),
166+
makeStage(42, "Stage 3"),
167+
]),
168+
]),
169+
],
170+
false
171+
);
172+
173+
expect(columns).toMatchObject([
174+
{
175+
hasBranchLabels: true,
176+
topStage: { name: "Test", id: 6 },
177+
rows: [
178+
[
179+
makeNode(20, "Stage 1", "Matrix - PLATFORM = '1'"),
180+
makeNode(30, "Stage 2", "Matrix - PLATFORM = '1'"),
181+
makeNode(40, "Stage 3", "Matrix - PLATFORM = '1'"),
182+
],
183+
[
184+
makeNode(22, "Stage 1", "Matrix - PLATFORM = '2'"),
185+
makeNode(32, "Stage 2", "Matrix - PLATFORM = '2'"),
186+
makeNode(42, "Stage 3", "Matrix - PLATFORM = '2'"),
187+
],
188+
],
189+
},
190+
]);
191+
});
192+
193+
it("returns proper columns (GH#63.2)", () => {
194+
const columns = createNodeColumns(
195+
[
196+
makeStage(6, "build and run", [
197+
makeParallel(12, "darwin-amd64", [
198+
makeStage(24, "build"),
199+
makeStage(39, "run"),
200+
makeStage(55, "unit test"),
201+
]),
202+
makeParallel(11, "linux-amd64", [
203+
makeStage(22, "build"),
204+
makeStage(36, "run"),
205+
makeStage(53, "unit test"),
206+
makeStage(64, "load test"),
207+
makeStage(71, "deploy to storage"),
208+
]),
209+
makeParallel(10, "linux-armv6", [
210+
makeStage(20, "build"),
211+
makeStage(34, "run"),
212+
]),
213+
]),
214+
],
215+
false
216+
);
217+
218+
expect(columns).toMatchObject([
219+
{
220+
hasBranchLabels: true,
221+
topStage: { name: "build and run", id: 6 },
222+
rows: [
223+
[
224+
makeNode(24, "build", "darwin-amd64"),
225+
makeNode(39, "run", "darwin-amd64"),
226+
makeNode(55, "unit test", "darwin-amd64"),
227+
],
228+
[
229+
makeNode(22, "build", "linux-amd64"),
230+
makeNode(36, "run", "linux-amd64"),
231+
makeNode(53, "unit test", "linux-amd64"),
232+
makeNode(64, "load test", "linux-amd64"),
233+
makeNode(71, "deploy to storage", "linux-amd64"),
234+
],
235+
[
236+
makeNode(20, "build", "linux-armv6"),
237+
makeNode(34, "run", "linux-armv6"),
238+
],
239+
],
240+
},
241+
]);
242+
});
243+
});
244+
});

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

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,28 @@ export function layoutGraph(
8787
/**
8888
* Generate an array of columns, based on the top-level stages
8989
*/
90-
function createNodeColumns(
90+
export function createNodeColumns(
9191
topLevelStages: Array<StageInfo> = [],
9292
collasped: boolean
9393
): Array<NodeColumn> {
9494
const nodeColumns: Array<NodeColumn> = [];
9595

96+
const makeNodeForStage = (
97+
stage: StageInfo,
98+
seqContainerName: string | undefined = undefined
99+
): NodeInfo => {
100+
return {
101+
x: 0, // Layout is done later
102+
y: 0,
103+
name: stage.name,
104+
id: stage.id,
105+
stage,
106+
seqContainerName,
107+
isPlaceholder: false,
108+
key: "n_" + stage.id,
109+
};
110+
};
111+
96112
for (const topStage of topLevelStages) {
97113
// If stage has children, we don't draw a node for it, just its children
98114
const stagesForColumn =
@@ -108,38 +124,18 @@ function createNodeColumns(
108124
hasBranchLabels: false, // set below
109125
};
110126

111-
for (const firstStageForRow of stagesForColumn) {
127+
for (const nodeStage of stagesForColumn) {
112128
const rowNodes: Array<NodeInfo> = [];
113-
let nodeStage: StageInfo | undefined = firstStageForRow;
114-
if (!collasped) {
115-
while (nodeStage) {
116-
if (nodeStage.seqContainerName) {
117-
column.hasBranchLabels = true;
118-
}
119-
120-
rowNodes.push({
121-
x: 0, // Layout is done later
122-
y: 0,
123-
name: nodeStage.name,
124-
id: nodeStage.id,
125-
stage: nodeStage,
126-
isPlaceholder: false,
127-
key: "n_" + nodeStage.id,
128-
});
129-
nodeStage = nodeStage.nextSibling;
129+
if (!collasped && nodeStage.children && nodeStage.children.length) {
130+
column.hasBranchLabels = true;
131+
for (const childStage of nodeStage.children) {
132+
rowNodes.push(makeNodeForStage(childStage, nodeStage.name));
130133
}
131-
column.rows.push(rowNodes);
132134
} else {
133-
rowNodes.push({
134-
x: 0, // Layout is done later
135-
y: 0,
136-
name: nodeStage.name,
137-
id: nodeStage.id,
138-
stage: nodeStage,
139-
isPlaceholder: false,
140-
key: "n_" + nodeStage.id,
141-
});
142-
column.rows.push(rowNodes);
135+
rowNodes.push(makeNodeForStage(nodeStage));
136+
}
137+
column.rows.push(rowNodes);
138+
if (collasped) {
143139
break;
144140
}
145141
}
@@ -299,13 +295,13 @@ function createBranchLabels(
299295
if (column.hasBranchLabels) {
300296
for (const row of column.rows) {
301297
const firstNode = row[0];
302-
if (!firstNode.isPlaceholder && firstNode.stage.seqContainerName) {
298+
if (!firstNode.isPlaceholder && firstNode.seqContainerName) {
303299
labels.push({
304300
x: column.startX,
305301
y: firstNode.y,
306302
key: `branchLabel-${++count}`,
307303
node: firstNode,
308-
text: firstNode.stage.seqContainerName,
304+
text: firstNode.seqContainerName,
309305
});
310306
}
311307
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export interface StageInfo {
5353
children: Array<StageInfo>; // Used by the top-most stages with parallel branches
5454
nextSibling?: StageInfo; // Used within a parallel branch to denote sequential stages
5555
isSequential?: boolean;
56-
seqContainerName?: string; //used within a parallel branch to denote the name of the container of the parallel sequential stages
5756
}
5857

5958
interface BaseNodeInfo {
@@ -73,6 +72,7 @@ export interface StageNodeInfo extends BaseNodeInfo {
7372

7473
// -- Unique
7574
stage: StageInfo;
75+
seqContainerName?: string; // Used within a parallel branch to denote the name of the container of the parallel sequential stages
7676
}
7777

7878
export interface PlaceholderNodeInfo extends BaseNodeInfo {

0 commit comments

Comments
 (0)