Skip to content

Commit 473c399

Browse files
Do not compress the tree over a security node cluster. (#3016)
Do not compress the tree over a security node cluster. --------- Signed-off-by: BOUTIER Charly <[email protected]>
1 parent 913257b commit 473c399

File tree

2 files changed

+94
-23
lines changed

2 files changed

+94
-23
lines changed

src/components/graph/layout.ts

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import { NodePlacement } from './layout.type';
99
import { groupIdSuffix, LABELED_GROUP_TYPE } from './nodes/labeled-group-node.type';
10-
import { CurrentTreeNode, NetworkModificationNodeType } from './tree-node.type';
10+
import { CurrentTreeNode, isSecurityModificationNode, NetworkModificationNodeType } from './tree-node.type';
1111

1212
export const nodeWidth = 230;
1313
export const nodeHeight = 110;
@@ -109,8 +109,8 @@ function getNodePlacements(nodes: CurrentTreeNode[]): PlacementGrid {
109109
}
110110

111111
/**
112-
* Create a Map using row number as keys and column number as value. The column value
113-
* for each row is either the lowest or highest value among column values of the same row, for the provided nodes.
112+
* Create a Map using row number as keys and column number as value. The column value for each row is either
113+
* the lowest or highest (extreme) value among column values of the same row for the provided nodes.
114114
*
115115
* Example nodes and placements :
116116
* - NodeA {row:0, column:30}
@@ -119,36 +119,102 @@ function getNodePlacements(nodes: CurrentTreeNode[]): PlacementGrid {
119119
* - NodeD {row:2, column:40}
120120
* - NodeE {row:2, column:80}
121121
*
122-
* For these placements, the returned Map would be like this : {0 => 30, 1 => 10, 2 => 40}
122+
* For these placements, the returned Map would be, with getMax=false, like this : {0 => 30, 1 => 10, 2 => 40}
123+
* and with getMax=true, like this : {0 => 30, 1 => 50, 2 => 80}
124+
*
125+
* If there are security nodes in the tree, we consider that each group of security nodes share the
126+
* same lowest or highest value for each of their rows.
123127
*
124128
* @param nodes The nodes to process
125129
* @param placements The grid placements
130+
* @param nodeMap The map used to find a node's parent
126131
* @param getMax If true, returns maximum columns, if false returns minimum columns
127132
*/
128-
function getColumnsByRows(nodes: CurrentTreeNode[], placements: PlacementGrid, getMax: boolean): Map<number, number> {
133+
function getColumnsByRows(
134+
nodes: CurrentTreeNode[],
135+
placements: PlacementGrid,
136+
nodeMap: Map<string, { index: number; node: CurrentTreeNode }>,
137+
getMax: boolean
138+
): Map<number, number> {
129139
const columnsByRow: Map<number, number> = new Map();
140+
141+
// These two variables are used to process the groups of security nodes, to make them all match the same value.
142+
// When in a security group, we store the extreme value in currentExtremeValue, and the rows of the current
143+
// security group in rowsToRetroactivelyUpdate.
144+
// When we find a new extreme in a security group, we update all its rows with the new value.
145+
let currentExtremeValue: number;
146+
let rowsToRetroactivelyUpdate = [];
147+
148+
function isExtreme(contender: number, competition: number | undefined): boolean {
149+
if (competition === undefined) {
150+
return true;
151+
}
152+
return getMax ? contender > competition : contender < competition;
153+
}
154+
155+
function resetSecurityGroupContext() {
156+
rowsToRetroactivelyUpdate.length = 0;
157+
currentExtremeValue = getMax ? -Infinity : Infinity;
158+
}
159+
160+
resetSecurityGroupContext();
130161
nodes.forEach((node) => {
131162
const nodePlacement = placements.getPlacement(node.id);
132-
if (nodePlacement) {
133-
if (
134-
!columnsByRow.has(nodePlacement.row) ||
135-
(getMax
136-
? nodePlacement.column > columnsByRow.get(nodePlacement.row)!
137-
: nodePlacement.column < columnsByRow.get(nodePlacement.row)!)
138-
) {
139-
columnsByRow.set(nodePlacement.row, nodePlacement.column);
163+
if (!nodePlacement || !node.parentId) {
164+
return;
165+
}
166+
// Security nodes are grouped together and enclosed in a rectangle. Each of those rectangles
167+
// must not be overlapped by the compression algorithm. To do so, when we are in a security
168+
// group of nodes, we consider that every row of this security group has the extreme value
169+
// of the whole group, and when we assign a new extreme, we do so to all the rows of the group.
170+
171+
if (isSecurityModificationNode(node)) {
172+
// This test determines if we changed from a security group to another. If this is the case,
173+
// we reset the currentExtremeValue to only update rows for the new group and not the old one.
174+
if (!isSecurityModificationNode(nodeMap.get(node.parentId)?.node)) {
175+
resetSecurityGroupContext();
176+
}
177+
178+
// We mark each row of a security group in order to update them all when we find a new extreme.
179+
rowsToRetroactivelyUpdate.push(nodePlacement.row);
180+
181+
const contender = getMax
182+
? Math.max(currentExtremeValue, nodePlacement.column)
183+
: Math.min(currentExtremeValue, nodePlacement.column);
184+
185+
if (isExtreme(contender, columnsByRow.get(nodePlacement.row))) {
186+
rowsToRetroactivelyUpdate.forEach((row) => {
187+
columnsByRow.set(row, contender);
188+
});
189+
}
190+
191+
currentExtremeValue = contender;
192+
} else {
193+
resetSecurityGroupContext();
194+
195+
const contender = nodePlacement.column;
196+
if (isExtreme(contender, columnsByRow.get(nodePlacement.row))) {
197+
columnsByRow.set(nodePlacement.row, contender);
140198
}
141199
}
142200
});
143201
return columnsByRow;
144202
}
145203

146-
function getMinimumColumnByRows(nodes: CurrentTreeNode[], placements: PlacementGrid): Map<number, number> {
147-
return getColumnsByRows(nodes, placements, false);
204+
function getMinimumColumnByRows(
205+
nodes: CurrentTreeNode[],
206+
placements: PlacementGrid,
207+
nodeMap: Map<string, { index: number; node: CurrentTreeNode }>
208+
): Map<number, number> {
209+
return getColumnsByRows(nodes, placements, nodeMap, false);
148210
}
149211

150-
function getMaximumColumnByRows(nodes: CurrentTreeNode[], placements: PlacementGrid): Map<number, number> {
151-
return getColumnsByRows(nodes, placements, true);
212+
function getMaximumColumnByRows(
213+
nodes: CurrentTreeNode[],
214+
placements: PlacementGrid,
215+
nodeMap: Map<string, { index: number; node: CurrentTreeNode }>
216+
): Map<number, number> {
217+
return getColumnsByRows(nodes, placements, nodeMap, true);
152218
}
153219

154220
/**
@@ -226,7 +292,7 @@ function compressTreePlacements(
226292
for (let i = nodes.length - 1; i >= 0; i--) {
227293
const node = nodes[i];
228294
const children = childrenMap.get(node.id) || [];
229-
const childrenSize = children.reduce((sum, child) => sum + (subTreeSizeMap.get(child.id) || 0), 0);
295+
const childrenSize = children.reduce((sum, child) => sum + (subTreeSizeMap.get(child.id) ?? 0), 0);
230296
subTreeSizeMap.set(node.id, 1 + childrenSize);
231297
}
232298

@@ -251,8 +317,9 @@ function compressTreePlacements(
251317
// cases other branches could go under the left neighbor and make edges cross.
252318
const leftNodes = nodes.slice(0, currentNodeIndex);
253319

254-
const currentBranchMinimumColumnByRow = getMinimumColumnByRows(currentBranchNodes, placements);
255-
const leftBranchMaximumColumnByRow = getMaximumColumnByRows(leftNodes, placements);
320+
const currentBranchMinimumColumnByRow = getMinimumColumnByRows(currentBranchNodes, placements, nodeMap);
321+
322+
const leftBranchMaximumColumnByRow = getMaximumColumnByRows(leftNodes, placements, nodeMap);
256323

257324
const availableSpace = calculateAvailableSpace(leftBranchMaximumColumnByRow, currentBranchMinimumColumnByRow);
258325

@@ -341,7 +408,7 @@ function computeSecurityGroupNodes(
341408
}
342409

343410
// If currently not in a security group, and a security node is found, we initiate securityGroupBuilder
344-
if (!securityGroupBuilder.isInSecurityGroup && node.data.nodeType === NetworkModificationNodeType.SECURITY) {
411+
if (!securityGroupBuilder.isInSecurityGroup && isSecurityModificationNode(node)) {
345412
securityGroupBuilder = {
346413
isInSecurityGroup: true,
347414
securityGroupTopLeftPosition: nodePlacement,

src/components/graph/tree-node.type.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export type RootNode = Node<ReactFlowRootNodeData, NodeType.ROOT> & { id: UUID }
7979

8080
export type CurrentTreeNode = ModificationNode | RootNode;
8181

82-
export const isSecurityModificationNode = (node: CurrentTreeNode): node is ModificationNode => {
83-
return node.type === NodeType.NETWORK_MODIFICATION && node.data.nodeType === NetworkModificationNodeType.SECURITY;
82+
export const isSecurityModificationNode = (node: CurrentTreeNode | undefined): node is ModificationNode => {
83+
return (
84+
!!node &&
85+
node.type === NodeType.NETWORK_MODIFICATION &&
86+
node.data?.nodeType === NetworkModificationNodeType.SECURITY
87+
);
8488
};

0 commit comments

Comments
 (0)