diff --git a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts index b8a5970a2f..79d29a2126 100644 --- a/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts +++ b/ui/app/(prowler)/attack-paths/(workflow)/query-builder/_lib/graph-utils.ts @@ -7,6 +7,8 @@ * Upstream: follows only ONE parent path (first parent at each level) to avoid lighting up siblings * Downstream: follows ALL children recursively * + * Uses pre-built adjacency maps for O(1) lookups instead of O(n) array searches per traversal step. + * * @param nodeId - The starting node ID * @param edges - Array of edges with sourceId and targetId * @returns Set of edge IDs in the format "sourceId-targetId" @@ -15,6 +17,23 @@ export const getPathEdges = ( nodeId: string, edges: Array<{ sourceId: string; targetId: string }>, ): Set => { + // Build adjacency maps once - O(n) + const parentMap = new Map(); + const childrenMap = new Map< + string, + Array<{ sourceId: string; targetId: string }> + >(); + + edges.forEach((edge) => { + // First parent only (matches original behavior of find()) + if (!parentMap.has(edge.targetId)) { + parentMap.set(edge.targetId, edge); + } + const children = childrenMap.get(edge.sourceId) || []; + children.push(edge); + childrenMap.set(edge.sourceId, children); + }); + const pathEdgeIds = new Set(); const visitedNodes = new Set(); @@ -24,8 +43,7 @@ export const getPathEdges = ( if (visitedNodes.has(`up-${currentNodeId}`)) return; visitedNodes.add(`up-${currentNodeId}`); - // Find the first parent edge only - const parentEdge = edges.find((edge) => edge.targetId === currentNodeId); + const parentEdge = parentMap.get(currentNodeId); // O(1) lookup if (parentEdge) { pathEdgeIds.add(`${parentEdge.sourceId}-${parentEdge.targetId}`); traverseUpstream(parentEdge.sourceId); @@ -37,11 +55,10 @@ export const getPathEdges = ( if (visitedNodes.has(`down-${currentNodeId}`)) return; visitedNodes.add(`down-${currentNodeId}`); - edges.forEach((edge) => { - if (edge.sourceId === currentNodeId) { - pathEdgeIds.add(`${edge.sourceId}-${edge.targetId}`); - traverseDownstream(edge.targetId); - } + const children = childrenMap.get(currentNodeId) || []; // O(1) lookup + children.forEach((edge) => { + pathEdgeIds.add(`${edge.sourceId}-${edge.targetId}`); + traverseDownstream(edge.targetId); }); };