@@ -2,20 +2,36 @@ import dagre from "dagre";
22import { Edge , Node , Position } from "reactflow" ;
33import { v4 as uuidv4 } from "uuid" ;
44import {
5+ Alert ,
6+ AlertsStore ,
57 EnrichedSparkSQL ,
68 EnrichedSqlEdge ,
7- EnrichedSqlNode ,
89 GraphFilter ,
10+ SQLAlertSourceData
911} from "../../interfaces/AppStore" ;
1012import { StageNodeName } from "./StageNode" ;
1113
14+ // Cache for layout results to avoid expensive recalculations
15+ const layoutCache = new Map < string , { layoutNodes : Node [ ] ; layoutEdges : Edge [ ] } > ( ) ;
16+
17+ // Cache for edge IDs to avoid regenerating UUIDs
18+ const edgeIdCache = new Map < string , string > ( ) ;
19+
1220const nodeWidth = 320 ;
1321const nodeHeight = 320 ;
1422
1523const getLayoutedElements = (
1624 nodes : Node [ ] ,
1725 edges : Edge [ ] ,
26+ cacheKey : string ,
1827) : { layoutNodes : Node [ ] ; layoutEdges : Edge [ ] } => {
28+ // Check cache first to avoid expensive Dagre layout calculation
29+ const cached = layoutCache . get ( cacheKey ) ;
30+ if ( cached ) {
31+ return cached ;
32+ }
33+
34+ // Create new Dagre graph for layout calculation
1935 const dagreGraph = new dagre . graphlib . Graph ( ) ;
2036 dagreGraph . setDefaultEdgeLabel ( ( ) => ( { } ) ) ;
2137 dagreGraph . setGraph ( { rankdir : "LR" } ) ;
@@ -28,46 +44,108 @@ const getLayoutedElements = (
2844 dagreGraph . setEdge ( edge . source , edge . target ) ;
2945 } ) ;
3046
47+ // This is the expensive operation - only do it if not cached
3148 dagre . layout ( dagreGraph ) ;
3249
33- nodes . forEach ( ( node ) => {
50+ // Create new nodes with positions (don't mutate input)
51+ const layoutNodes = nodes . map ( ( node ) => {
3452 const nodeWithPosition = dagreGraph . node ( node . id ) ;
35- node . targetPosition = Position . Left ;
36- node . sourcePosition = Position . Right ;
37-
38- // We are shifting the dagre node position (anchor=center center) to the top left
39- // so it matches the React Flow node anchor point (top left).
40- node . position = {
41- x : nodeWithPosition . x - nodeWidth / 2 ,
42- y : nodeWithPosition . y - nodeHeight / 2 ,
53+ return {
54+ ... node ,
55+ targetPosition : Position . Left ,
56+ sourcePosition : Position . Right ,
57+ position : {
58+ x : nodeWithPosition . x - nodeWidth / 2 ,
59+ y : nodeWithPosition . y - nodeHeight / 2 ,
60+ } ,
4361 } ;
44-
45- return node ;
4662 } ) ;
4763
48- return { layoutNodes : nodes , layoutEdges : edges } ;
64+ const result = { layoutNodes, layoutEdges : edges } ;
65+
66+ // Cache the result for future use
67+ layoutCache . set ( cacheKey , result ) ;
68+
69+ return result ;
4970} ;
5071
5172class SqlLayoutService {
5273 static SqlElementsToLayout (
5374 sql : EnrichedSparkSQL ,
5475 graphFilter : GraphFilter ,
76+ alerts ?: AlertsStore , // Alerts store for node-specific alerts
5577 ) : { layoutNodes : Node [ ] ; layoutEdges : Edge [ ] } {
78+ // Helper function to find alert for a specific node
79+ const findNodeAlert = ( nodeId : number ) : Alert | undefined => {
80+ return alerts ?. alerts . find (
81+ ( alert : Alert ) => {
82+ // Type guard to ensure we're dealing with SQL alerts
83+ if ( alert . source . type === "sql" ) {
84+ const sqlSource = alert . source as SQLAlertSourceData ;
85+ return sqlSource . sqlNodeId === nodeId && sqlSource . sqlId === sql . id ;
86+ }
87+ return false ;
88+ }
89+ ) ;
90+ } ;
91+
92+ // Create cache key based on SQL structure and filter
93+ const cacheKey = `${ sql . uniqueId } -${ graphFilter } ` ;
94+
95+ // Check if we have a cached result for this exact configuration
96+ const cached = layoutCache . get ( cacheKey ) ;
97+ if ( cached ) {
98+ // Update node data with current metrics and alerts while keeping cached layout
99+ const updatedNodes = cached . layoutNodes . map ( node => ( {
100+ ...node ,
101+ data : {
102+ ...node . data ,
103+ node : sql . nodes . find ( n => n . nodeId . toString ( ) === node . id ) || node . data . node ,
104+ sqlUniqueId : sql . uniqueId ,
105+ sqlMetricUpdateId : sql . metricUpdateId ,
106+ alert : findNodeAlert ( parseInt ( node . id ) ) , // Add alert for this node
107+ }
108+ } ) ) ;
109+
110+ return { layoutNodes : updatedNodes , layoutEdges : cached . layoutEdges } ;
111+ }
112+
56113 const { nodesIds, edges } = sql . filters [ graphFilter ] ;
57114
58- const flowNodes : Node [ ] = sql . nodes
59- . filter ( ( node ) => nodesIds . includes ( node . nodeId ) )
60- . map ( ( node : EnrichedSqlNode ) => {
115+ // Optimize node filtering and mapping
116+ const nodeMap = new Map ( sql . nodes . map ( node => [ node . nodeId , node ] ) ) ;
117+ const flowNodes : Node [ ] = nodesIds
118+ . map ( ( nodeId ) => {
119+ const node = nodeMap . get ( nodeId ) ;
120+ if ( ! node ) return null ;
121+
61122 return {
62123 id : node . nodeId . toString ( ) ,
63- data : { sqlId : sql . id , node : node } ,
124+ data : {
125+ sqlId : sql . id ,
126+ node : node ,
127+ sqlUniqueId : sql . uniqueId ,
128+ sqlMetricUpdateId : sql . metricUpdateId ,
129+ alert : findNodeAlert ( node . nodeId ) , // Add alert for this node
130+ } ,
64131 type : StageNodeName ,
65132 position : { x : 0 , y : 0 } ,
66- } ;
67- } ) ;
133+ } as Node ;
134+ } )
135+ . filter ( ( node ) : node is Node => node !== null ) ;
136+
137+ // Optimize edge creation with cached IDs
68138 const flowEdges : Edge [ ] = edges . map ( ( edge : EnrichedSqlEdge ) => {
139+ const edgeKey = `${ edge . fromId } -${ edge . toId } ` ;
140+ let edgeId = edgeIdCache . get ( edgeKey ) ;
141+
142+ if ( ! edgeId ) {
143+ edgeId = uuidv4 ( ) ;
144+ edgeIdCache . set ( edgeKey , edgeId ) ;
145+ }
146+
69147 return {
70- id : uuidv4 ( ) ,
148+ id : edgeId ,
71149 source : edge . fromId . toString ( ) ,
72150 animated : true ,
73151 target : edge . toId . toString ( ) ,
@@ -77,8 +155,16 @@ class SqlLayoutService {
77155 const { layoutNodes, layoutEdges } = getLayoutedElements (
78156 flowNodes ,
79157 flowEdges ,
158+ cacheKey ,
80159 ) ;
81- return { layoutNodes : layoutNodes , layoutEdges : layoutEdges } ;
160+
161+ return { layoutNodes, layoutEdges } ;
162+ }
163+
164+ // Method to clear cache when needed (e.g., on app restart)
165+ static clearCache ( ) : void {
166+ layoutCache . clear ( ) ;
167+ edgeIdCache . clear ( ) ;
82168 }
83169}
84170
0 commit comments