Skip to content

Commit e55553d

Browse files
committed
add 2 new navigation icons - slowest node, and node with alert
1 parent ffa7bb7 commit e55553d

File tree

2 files changed

+144
-3
lines changed

2 files changed

+144
-3
lines changed

spark-plugin/example_3_5_1/src/main/scala/io/dataflint/example/SqlPlanStressTestExample.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ object SqlPlanStressTestExample extends App {
1212
.config("spark.ui.port", "10000")
1313
.config("spark.dataflint.telemetry.enabled", value = false)
1414
.config("spark.sql.maxMetadataStringLength", "10000")
15-
.config("spark.eventLog.enabled", "true")
1615
.master("local[*]")
1716
.getOrCreate()
1817

spark-ui/src/components/SqlFlow/SqlFlow.tsx

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ReactFlow, {
88
useNodesState,
99
} from "reactflow";
1010

11-
import { CenterFocusStrong, Info as InfoIcon, ZoomIn, ZoomOut } from "@mui/icons-material";
11+
import { CenterFocusStrong, Info as InfoIcon, Speed, Warning, ZoomIn, ZoomOut } from "@mui/icons-material";
1212
import {
1313
Alert,
1414
Box,
@@ -36,6 +36,25 @@ import { StageNode, StageNodeName } from "./StageNode";
3636
const options = { hideAttribution: true };
3737
const nodeTypes = { [StageNodeName]: StageNode };
3838

39+
// Types for navigation data
40+
interface NodeWithAlert {
41+
nodeId: string;
42+
position: { x: number; y: number };
43+
alert: any;
44+
}
45+
46+
interface BiggestDurationNode {
47+
nodeId: string;
48+
position: { x: number; y: number };
49+
durationPercentage: number;
50+
}
51+
52+
interface NavigationData {
53+
nodesWithAlerts: NodeWithAlert[];
54+
biggestDurationNode: BiggestDurationNode | null;
55+
nodesByDuration: BiggestDurationNode[];
56+
}
57+
3958
const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
4059
sparkSQL,
4160
}): JSX.Element => {
@@ -50,6 +69,8 @@ const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
5069
const [searchParams] = useSearchParams();
5170
const nodeIdsParam = searchParams.get('nodeids');
5271
const initialFocusApplied = useRef<string | null>(null);
72+
const [currentAlertIndex, setCurrentAlertIndex] = useState(0);
73+
const [currentDurationIndex, setCurrentDurationIndex] = useState(0);
5374

5475
const dispatch = useAppDispatch();
5576
const graphFilter = useAppSelector((state) => state.general.sqlMode);
@@ -68,6 +89,33 @@ const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
6889
return { totalNodes, totalEdges, highlightedNodes };
6990
}, [nodes, edges, nodeIdsParam, sparkSQL]);
7091

92+
// Memoized calculations for navigation features
93+
const navigationData = useMemo((): NavigationData => {
94+
if (!sparkSQL || !nodes.length) return { nodesWithAlerts: [], biggestDurationNode: null, nodesByDuration: [] };
95+
96+
// Find nodes with alerts
97+
const nodesWithAlerts: NodeWithAlert[] = nodes.filter(node => node.data?.alert).map(node => ({
98+
nodeId: node.id,
99+
position: node.position,
100+
alert: node.data.alert
101+
}));
102+
103+
// Get all nodes with duration percentage and sort by duration (highest first)
104+
const nodesByDuration: BiggestDurationNode[] = nodes
105+
.filter(node => node.data?.node?.durationPercentage !== undefined)
106+
.map(node => ({
107+
nodeId: node.id,
108+
position: node.position,
109+
durationPercentage: node.data.node.durationPercentage!
110+
}))
111+
.sort((a, b) => b.durationPercentage - a.durationPercentage);
112+
113+
// Find node with biggest duration percentage (first in sorted array)
114+
const biggestDurationNode: BiggestDurationNode | null = nodesByDuration.length > 0 ? nodesByDuration[0] : null;
115+
116+
return { nodesWithAlerts, biggestDurationNode, nodesByDuration };
117+
}, [nodes, sparkSQL]);
118+
71119
// Effect for metric updates only
72120
React.useEffect(() => {
73121
if (!sparkSQL) return;
@@ -160,7 +208,15 @@ const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
160208
}
161209
}, [instance, edges, nodeIdsParam]);
162210

163-
useEffect(() => { }, [nodes]);
211+
// Reset alert index when nodes change
212+
useEffect(() => {
213+
setCurrentAlertIndex(0);
214+
}, [navigationData.nodesWithAlerts.length]);
215+
216+
// Reset duration index when nodes change
217+
useEffect(() => {
218+
setCurrentDurationIndex(0);
219+
}, [navigationData.nodesByDuration.length]);
164220

165221
const onConnect = useCallback(
166222
(params: any) =>
@@ -192,6 +248,38 @@ const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
192248
}
193249
}, [instance]);
194250

251+
// Cycle through nodes by duration percentage (highest to lowest)
252+
const handleFocusNextDuration = useCallback(() => {
253+
if (instance && navigationData.nodesByDuration.length > 0) {
254+
const nextIndex = (currentDurationIndex + 1) % navigationData.nodesByDuration.length;
255+
setCurrentDurationIndex(nextIndex);
256+
257+
const node = navigationData.nodesByDuration[nextIndex];
258+
const nodeWidth = 280;
259+
const nodeHeight = 280;
260+
const centerX = node.position.x + nodeWidth / 2;
261+
const centerY = node.position.y + nodeHeight / 2;
262+
263+
instance.setCenter(centerX, centerY, { zoom: 0.75 });
264+
}
265+
}, [instance, navigationData.nodesByDuration, currentDurationIndex]);
266+
267+
// Cycle through nodes with alerts
268+
const handleFocusNextAlert = useCallback(() => {
269+
if (instance && navigationData.nodesWithAlerts.length > 0) {
270+
const nextIndex = (currentAlertIndex + 1) % navigationData.nodesWithAlerts.length;
271+
setCurrentAlertIndex(nextIndex);
272+
273+
const node = navigationData.nodesWithAlerts[nextIndex];
274+
const nodeWidth = 280;
275+
const nodeHeight = 280;
276+
const centerX = node.position.x + nodeWidth / 2;
277+
const centerY = node.position.y + nodeHeight / 2;
278+
279+
instance.setCenter(centerX, centerY, { zoom: 0.75 });
280+
}
281+
}, [instance, navigationData.nodesWithAlerts, currentAlertIndex]);
282+
195283
if (error) {
196284
return (
197285
<Box
@@ -287,6 +375,60 @@ const SqlFlow: FC<{ sparkSQL: EnrichedSparkSQL }> = ({
287375
<CenterFocusStrong />
288376
</IconButton>
289377
</Tooltip>
378+
379+
<Tooltip
380+
title={navigationData.nodesByDuration.length > 0
381+
? `Focus on biggest node duration (${currentDurationIndex + 1}/${navigationData.nodesByDuration.length}) - ${navigationData.nodesByDuration[currentDurationIndex]?.durationPercentage.toFixed(1)}%`
382+
: "No duration data available"
383+
}
384+
arrow
385+
placement="left"
386+
>
387+
<IconButton
388+
onClick={handleFocusNextDuration}
389+
disabled={navigationData.nodesByDuration.length === 0}
390+
sx={{
391+
backgroundColor: "rgba(245, 247, 250, 0.95)",
392+
color: navigationData.nodesByDuration.length > 0 ? "#424242" : "#bdbdbd",
393+
border: "1px solid rgba(0, 0, 0, 0.15)",
394+
"&:hover": {
395+
backgroundColor: navigationData.nodesByDuration.length > 0 ? "rgba(245, 247, 250, 1)" : "rgba(245, 247, 250, 0.95)"
396+
},
397+
"&:disabled": {
398+
backgroundColor: "rgba(245, 247, 250, 0.5)",
399+
}
400+
}}
401+
>
402+
<Speed />
403+
</IconButton>
404+
</Tooltip>
405+
406+
<Tooltip
407+
title={navigationData.nodesWithAlerts.length > 0
408+
? `Focus on alerts (${currentAlertIndex + 1}/${navigationData.nodesWithAlerts.length})`
409+
: "No alerts found"
410+
}
411+
arrow
412+
placement="left"
413+
>
414+
<IconButton
415+
onClick={handleFocusNextAlert}
416+
disabled={navigationData.nodesWithAlerts.length === 0}
417+
sx={{
418+
backgroundColor: "rgba(245, 247, 250, 0.95)",
419+
color: navigationData.nodesWithAlerts.length > 0 ? "#424242" : "#bdbdbd",
420+
border: "1px solid rgba(0, 0, 0, 0.15)",
421+
"&:hover": {
422+
backgroundColor: navigationData.nodesWithAlerts.length > 0 ? "rgba(245, 247, 250, 1)" : "rgba(245, 247, 250, 0.95)"
423+
},
424+
"&:disabled": {
425+
backgroundColor: "rgba(245, 247, 250, 0.5)",
426+
}
427+
}}
428+
>
429+
<Warning />
430+
</IconButton>
431+
</Tooltip>
290432
</Box>
291433

292434
<ReactFlow

0 commit comments

Comments
 (0)