mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-06 08:47:18 +00:00
fix(ui): use horizontal attack path graph layout
This commit is contained in:
+5
-3
@@ -32,7 +32,7 @@ const buildNodeProps = (graphNode: GraphNode): NodeProps =>
|
||||
}) as unknown as NodeProps;
|
||||
|
||||
describe("FindingNode", () => {
|
||||
it("positions graph handles for vertical top-to-bottom edges", () => {
|
||||
it("positions graph handles for horizontal left-to-right edges", () => {
|
||||
// Given
|
||||
const props = buildNodeProps(
|
||||
buildFindingNode("critical", "Root key exposed"),
|
||||
@@ -44,8 +44,10 @@ describe("FindingNode", () => {
|
||||
// Then
|
||||
expect(hiddenHandlesMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
sourcePosition: Position.Right,
|
||||
sourceStyle: { left: 97, top: 26 },
|
||||
targetPosition: Position.Left,
|
||||
targetStyle: { left: 53, top: 26 },
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
|
||||
+6
-7
@@ -21,8 +21,8 @@ const BADGE_SIZE = 44;
|
||||
const BADGE_RADIUS = BADGE_SIZE / 2;
|
||||
const BADGE_CENTER_X = NODE_WIDTH / 2;
|
||||
const BADGE_CENTER_Y = 26;
|
||||
const BADGE_BOTTOM_Y = BADGE_CENTER_Y + BADGE_RADIUS;
|
||||
const BADGE_TOP_Y = BADGE_CENTER_Y - BADGE_RADIUS;
|
||||
const BADGE_LEFT_X = BADGE_CENTER_X - BADGE_RADIUS;
|
||||
const BADGE_RIGHT_X = BADGE_CENTER_X + BADGE_RADIUS;
|
||||
const ICON_SIZE = 28;
|
||||
const ICON_X = BADGE_CENTER_X - ICON_SIZE / 2;
|
||||
const ICON_Y = BADGE_CENTER_Y - ICON_SIZE / 2;
|
||||
@@ -76,11 +76,10 @@ export const FindingNode = ({ data, selected }: NodeProps) => {
|
||||
return (
|
||||
<>
|
||||
<HiddenHandles
|
||||
sourcePosition={Position.Bottom}
|
||||
sourceStyle={{ top: BADGE_BOTTOM_Y }}
|
||||
style={{ left: BADGE_CENTER_X }}
|
||||
targetPosition={Position.Top}
|
||||
targetStyle={{ top: BADGE_TOP_Y }}
|
||||
sourcePosition={Position.Right}
|
||||
sourceStyle={{ left: BADGE_RIGHT_X, top: BADGE_CENTER_Y }}
|
||||
targetPosition={Position.Left}
|
||||
targetStyle={{ left: BADGE_LEFT_X, top: BADGE_CENTER_Y }}
|
||||
/>
|
||||
<svg width={NODE_WIDTH} height={NODE_HEIGHT} className="overflow-visible">
|
||||
<circle
|
||||
|
||||
+2
-2
@@ -12,10 +12,10 @@ interface HiddenHandlesProps {
|
||||
}
|
||||
|
||||
export const HiddenHandles = ({
|
||||
sourcePosition = Position.Bottom,
|
||||
sourcePosition = Position.Right,
|
||||
sourceStyle,
|
||||
style,
|
||||
targetPosition = Position.Top,
|
||||
targetPosition = Position.Left,
|
||||
targetStyle,
|
||||
}: HiddenHandlesProps) => (
|
||||
<>
|
||||
|
||||
+5
-3
@@ -32,7 +32,7 @@ const buildNodeProps = (graphNode: GraphNode): NodeProps =>
|
||||
}) as unknown as NodeProps;
|
||||
|
||||
describe("ResourceNode", () => {
|
||||
it("positions graph handles for vertical top-to-bottom edges", () => {
|
||||
it("positions graph handles for horizontal left-to-right edges", () => {
|
||||
// Given
|
||||
const props = buildNodeProps(buildGraphNode("S3Bucket", "logs"));
|
||||
|
||||
@@ -42,8 +42,10 @@ describe("ResourceNode", () => {
|
||||
// Then
|
||||
expect(hiddenHandlesMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
sourcePosition: Position.Right,
|
||||
sourceStyle: { left: 90, top: 26 },
|
||||
targetPosition: Position.Left,
|
||||
targetStyle: { left: 46, top: 26 },
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
|
||||
+6
-7
@@ -22,8 +22,8 @@ const BADGE_SIZE = 44;
|
||||
const BADGE_RADIUS = BADGE_SIZE / 2;
|
||||
const BADGE_CENTER_X = NODE_WIDTH / 2;
|
||||
const BADGE_CENTER_Y = 26;
|
||||
const BADGE_BOTTOM_Y = BADGE_CENTER_Y + BADGE_RADIUS;
|
||||
const BADGE_TOP_Y = BADGE_CENTER_Y - BADGE_RADIUS;
|
||||
const BADGE_LEFT_X = BADGE_CENTER_X - BADGE_RADIUS;
|
||||
const BADGE_RIGHT_X = BADGE_CENTER_X + BADGE_RADIUS;
|
||||
const ICON_SIZE = 28;
|
||||
const ICON_X = BADGE_CENTER_X - ICON_SIZE / 2;
|
||||
const ICON_Y = BADGE_CENTER_Y - ICON_SIZE / 2;
|
||||
@@ -63,11 +63,10 @@ export const ResourceNode = ({ data, selected }: NodeProps) => {
|
||||
return (
|
||||
<>
|
||||
<HiddenHandles
|
||||
sourcePosition={Position.Bottom}
|
||||
sourceStyle={{ top: BADGE_BOTTOM_Y }}
|
||||
style={{ left: BADGE_CENTER_X }}
|
||||
targetPosition={Position.Top}
|
||||
targetStyle={{ top: BADGE_TOP_Y }}
|
||||
sourcePosition={Position.Right}
|
||||
sourceStyle={{ left: BADGE_RIGHT_X, top: BADGE_CENTER_Y }}
|
||||
targetPosition={Position.Left}
|
||||
targetStyle={{ left: BADGE_LEFT_X, top: BADGE_CENTER_Y }}
|
||||
/>
|
||||
<svg width={NODE_WIDTH} height={NODE_HEIGHT} className="overflow-visible">
|
||||
{glowRadius > 0 && (
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("layoutWithDagre", () => {
|
||||
expect(a).toEqual(b);
|
||||
});
|
||||
|
||||
it("spreads sibling nodes horizontally to use wide graph space", () => {
|
||||
it("places connected children to the right and stacks siblings within the horizontal rank", () => {
|
||||
const rootNode: GraphNode = {
|
||||
id: "root",
|
||||
labels: ["AWSAccount"],
|
||||
@@ -106,6 +106,9 @@ describe("layoutWithDagre", () => {
|
||||
})),
|
||||
);
|
||||
|
||||
const rootPosition = rfNodes.find(
|
||||
(candidate) => candidate.id === "root",
|
||||
)?.position;
|
||||
const siblingPositions = siblingNodes.map((node) => {
|
||||
const rfNode = rfNodes.find((candidate) => candidate.id === node.id);
|
||||
|
||||
@@ -121,10 +124,14 @@ describe("layoutWithDagre", () => {
|
||||
Math.max(...siblingPositions.map((position) => position.y)) -
|
||||
Math.min(...siblingPositions.map((position) => position.y));
|
||||
|
||||
expect(xSpread).toBeGreaterThan(ySpread);
|
||||
expect(rootPosition).toBeDefined();
|
||||
siblingPositions.forEach((position) => {
|
||||
expect(position.x).toBeGreaterThan(rootPosition?.x ?? 0);
|
||||
});
|
||||
expect(ySpread).toBeGreaterThan(xSpread);
|
||||
});
|
||||
|
||||
it("connects edges through top and bottom node sides for vertical layout", () => {
|
||||
it("connects edges through right and left node sides for horizontal layout", () => {
|
||||
const { rfNodes } = layoutWithDagre(
|
||||
[findingNode, resourceNode],
|
||||
[
|
||||
@@ -138,8 +145,8 @@ describe("layoutWithDagre", () => {
|
||||
);
|
||||
|
||||
rfNodes.forEach((node) => {
|
||||
expect(node.sourcePosition).toBe(Position.Bottom);
|
||||
expect(node.targetPosition).toBe(Position.Top);
|
||||
expect(node.sourcePosition).toBe(Position.Right);
|
||||
expect(node.targetPosition).toBe(Position.Left);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export const layoutWithDagre = (
|
||||
): { rfNodes: Node<NodeData>[]; rfEdges: Edge[] } => {
|
||||
const g = new Graph();
|
||||
g.setGraph({
|
||||
rankdir: "TB",
|
||||
rankdir: "LR",
|
||||
nodesep: 80,
|
||||
ranksep: 150,
|
||||
marginx: 50,
|
||||
@@ -112,8 +112,8 @@ export const layoutWithDagre = (
|
||||
x: dagreNode.x - width / 2,
|
||||
y: dagreNode.y - height / 2,
|
||||
},
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
data: { graphNode: node },
|
||||
width,
|
||||
height,
|
||||
|
||||
Reference in New Issue
Block a user