Skip to content

Commit 2b4adba

Browse files
committed
Fix query plan default viewport in preview UI
1 parent d0e2314 commit 2b4adba

File tree

4 files changed

+102
-28
lines changed

4 files changed

+102
-28
lines changed

core/trino-web-ui/src/main/resources/webapp-preview/src/components/QueryLivePlan.tsx

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
*/
1414
import { useParams } from 'react-router-dom'
1515
import { useEffect, useRef, useState } from 'react'
16-
import { Alert, Box, CircularProgress, Grid } from '@mui/material'
17-
import { ReactFlow, type Edge, type Node, useNodesState, useEdgesState } from '@xyflow/react'
16+
import { Alert, Box, CircularProgress, Grid, Typography } from '@mui/material'
17+
import { ReactFlow, type Edge, type Node, useNodesState, useEdgesState, type Viewport } from '@xyflow/react'
1818
import '@xyflow/react/dist/style.css'
1919
import { queryStatusApi, QueryStatusInfo } from '../api/webapp/api.ts'
2020
import { QueryProgressBar } from './QueryProgressBar'
21-
import { nodeTypes, getLayoutedPlanFlowElements } from './flow/layout'
21+
import { nodeTypes, getLayoutedPlanFlowElements, getViewportFocusedOnNode } from './flow/layout'
2222
import { HelpMessage } from './flow/HelpMessage'
2323
import { getPlanFlowElements } from './flow/flowUtils'
2424
import { IQueryStatus, LayoutDirectionType } from './flow/types'
@@ -36,6 +36,7 @@ export const QueryLivePlan = () => {
3636
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([])
3737
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([])
3838
const [layoutDirection, setLayoutDirection] = useState<LayoutDirectionType>('BT')
39+
const [viewport, setViewport] = useState<Viewport>()
3940

4041
const [loading, setLoading] = useState<boolean>(true)
4142
const [error, setError] = useState<string | null>(null)
@@ -94,6 +95,16 @@ export const QueryLivePlan = () => {
9495
}
9596
}
9697

98+
const focusViewportToFirstStage = () => {
99+
const viewportTarget = getViewportFocusedOnNode(nodes, {
100+
targetNodeId: 'stage-0',
101+
containerWidth: containerRef.current?.clientWidth ?? 0,
102+
})
103+
if (viewportTarget) {
104+
setViewport(viewportTarget)
105+
}
106+
}
107+
97108
return (
98109
<>
99110
{loading && <CircularProgress />}
@@ -115,21 +126,30 @@ export const QueryLivePlan = () => {
115126
ref={containerRef}
116127
sx={{ width: '100%', height: '80vh', border: '1px solid #ccc' }}
117128
>
118-
<ReactFlow
119-
nodes={nodes}
120-
edges={edges}
121-
onNodesChange={onNodesChange}
122-
onEdgesChange={onEdgesChange}
123-
nodeTypes={nodeTypes}
124-
minZoom={0.1}
125-
proOptions={{ hideAttribution: true }}
126-
defaultViewport={{ x: 200, y: 20, zoom: 0.8 }}
127-
>
128-
<HelpMessage
129-
layoutDirection={layoutDirection}
130-
onLayoutDirectionChange={setLayoutDirection}
131-
/>
132-
</ReactFlow>
129+
{nodes.length > 0 ? (
130+
<ReactFlow
131+
nodes={nodes}
132+
edges={edges}
133+
onNodesChange={onNodesChange}
134+
onEdgesChange={onEdgesChange}
135+
nodeTypes={nodeTypes}
136+
minZoom={0.1}
137+
proOptions={{ hideAttribution: true }}
138+
viewport={viewport}
139+
onViewportChange={setViewport}
140+
fitView
141+
>
142+
<HelpMessage
143+
layoutDirection={layoutDirection}
144+
onLayoutDirectionChange={setLayoutDirection}
145+
onOriginClick={focusViewportToFirstStage}
146+
/>
147+
</ReactFlow>
148+
) : (
149+
<Typography sx={{ p: 1 }} fontSize="small">
150+
Rendering...
151+
</Typography>
152+
)}
133153
</Box>
134154
</Box>
135155
</Grid>

core/trino-web-ui/src/main/resources/webapp-preview/src/components/QueryStagePerformance.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import {
2424
Select,
2525
SelectChangeEvent,
2626
} from '@mui/material'
27-
import { type Edge, type Node, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react'
27+
import { type Edge, type Node, ReactFlow, useEdgesState, useNodesState, type Viewport } from '@xyflow/react'
2828
import { queryStatusApi, QueryStatusInfo, QueryStage } from '../api/webapp/api.ts'
2929
import { ApiResponse } from '../api/base.ts'
3030
import { Texts } from '../constant.ts'
3131
import { QueryProgressBar } from './QueryProgressBar.tsx'
3232
import { HelpMessage } from './flow/HelpMessage'
33-
import { nodeTypes, getLayoutedStagePerformanceElements } from './flow/layout'
33+
import { nodeTypes, getLayoutedStagePerformanceElements, getViewportFocusedOnNode } from './flow/layout'
3434
import { LayoutDirectionType } from './flow/types'
3535
import { getStagePerformanceFlowElements } from './flow/flowUtils.ts'
3636

@@ -53,6 +53,7 @@ export const QueryStagePerformance = () => {
5353
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([])
5454
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([])
5555
const [layoutDirection, setLayoutDirection] = useState<LayoutDirectionType>('BT')
56+
const [viewport, setViewport] = useState<Viewport>()
5657

5758
const [loading, setLoading] = useState<boolean>(true)
5859
const [error, setError] = useState<string | null>(null)
@@ -135,6 +136,16 @@ export const QueryStagePerformance = () => {
135136
},
136137
}
137138

139+
const focusViewportToFirstPipeline = () => {
140+
const viewportTarget = getViewportFocusedOnNode(nodes, {
141+
targetNodeId: 'pipeline-0',
142+
containerWidth: containerRef.current?.clientWidth ?? 0,
143+
})
144+
if (viewportTarget) {
145+
setViewport(viewportTarget)
146+
}
147+
}
148+
138149
const handleStageIdChange = (event: SelectChangeEvent) => {
139150
setStagePlanId(event.target.value as string)
140151
}
@@ -169,11 +180,14 @@ export const QueryStagePerformance = () => {
169180
nodeTypes={nodeTypes}
170181
minZoom={0.1}
171182
proOptions={{ hideAttribution: true }}
172-
defaultViewport={{ x: 200, y: 20, zoom: 0.8 }}
183+
viewport={viewport}
184+
onViewportChange={setViewport}
185+
fitView
173186
>
174187
<HelpMessage
175188
layoutDirection={layoutDirection}
176189
onLayoutDirectionChange={setLayoutDirection}
190+
onOriginClick={focusViewportToFirstPipeline}
177191
additionalContent={
178192
<Box sx={{ mt: 2 }}>
179193
<FormControl size="small" sx={{ minWidth: 200 }}>

core/trino-web-ui/src/main/resources/webapp-preview/src/components/flow/HelpMessage.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,33 @@
1111
* See the License for the specific language governing permissions and
1212
* limitations under the License.
1313
*/
14-
import { Box, Typography, ToggleButtonGroup, ToggleButton } from '@mui/material'
14+
import { Box, Button, Typography, ToggleButtonGroup, ToggleButton } from '@mui/material'
1515
import React from 'react'
1616
import { LayoutDirectionType } from './types'
1717

1818
interface IHelpMessageProps {
1919
layoutDirection: LayoutDirectionType
2020
onLayoutDirectionChange: (layoutDirection: LayoutDirectionType) => void
21+
onOriginClick: () => void
2122
additionalContent?: React.ReactNode
2223
}
2324

24-
export const HelpMessage = ({ layoutDirection, onLayoutDirectionChange, additionalContent }: IHelpMessageProps) => {
25+
export const HelpMessage = ({
26+
layoutDirection,
27+
onLayoutDirectionChange,
28+
onOriginClick,
29+
additionalContent,
30+
}: IHelpMessageProps) => {
2531
const handleLayoutChange = (_event: React.MouseEvent<HTMLElement>, newDirection: LayoutDirectionType | null) => {
2632
if (newDirection !== null) {
2733
onLayoutDirectionChange(newDirection)
2834
}
2935
}
3036

37+
const handleOriginClick = () => {
38+
onOriginClick()
39+
}
40+
3141
return (
3242
<Box
3343
sx={{
@@ -47,9 +57,19 @@ export const HelpMessage = ({ layoutDirection, onLayoutDirectionChange, addition
4757
alignItems: 'center',
4858
}}
4959
>
50-
<Typography variant="body2" color="text.secondary" sx={{ mb: 0 }}>
51-
Scroll to zoom in/out
52-
</Typography>
60+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
61+
<Typography variant="body2" color="text.secondary" sx={{ mb: 0 }}>
62+
Scroll to zoom in/out
63+
</Typography>
64+
<Button
65+
onClick={handleOriginClick}
66+
size="small"
67+
variant="text"
68+
sx={{ minWidth: 'auto', px: 1.5, py: 0.25 }}
69+
>
70+
Origin
71+
</Button>
72+
</Box>
5373

5474
<ToggleButtonGroup
5575
value={layoutDirection}
@@ -65,7 +85,6 @@ export const HelpMessage = ({ layoutDirection, onLayoutDirectionChange, addition
6585
<Typography variant="caption">Horizontal</Typography>
6686
</ToggleButton>
6787
</ToggleButtonGroup>
68-
6988
{additionalContent}
7089
</Box>
7190
)

core/trino-web-ui/src/main/resources/webapp-preview/src/components/flow/layout.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* limitations under the License.
1313
*/
1414
import Dagre from '@dagrejs/dagre'
15-
import { type Edge, type Node } from '@xyflow/react'
15+
import { type Edge, type Node, type Viewport } from '@xyflow/react'
1616
import { RemoteExchangeNode } from './RemoteExchangeNode.tsx'
1717
import { PlanFragmentNode } from './PlanFragmentNode.tsx'
1818
import { OperatorNode } from './OperatorNode.tsx'
@@ -184,3 +184,24 @@ export const getLayoutedStagePerformanceElements = (nodes: Node[], edges: Edge[]
184184
edges,
185185
}
186186
}
187+
188+
export const getViewportFocusedOnNode = (
189+
nodes: Node[],
190+
options?: { targetNodeId?: string; zoom?: number; padding?: number; containerWidth?: number }
191+
): Viewport | undefined => {
192+
if (nodes.length === 0) {
193+
return undefined
194+
}
195+
const { targetNodeId, zoom = 0.8, padding = 20, containerWidth = 0 } = options ?? {}
196+
const targetNode = targetNodeId ? nodes.find((node) => node.id === targetNodeId) : undefined
197+
const focusNode = targetNode ?? nodes[0]
198+
const focusNodeWidth = focusNode.measured?.width ?? focusNode.width ?? 0
199+
return {
200+
// If width is known then center horizontally in container otherwise align to left
201+
x: focusNodeWidth
202+
? containerWidth / 2 - (focusNode.position.x + focusNodeWidth / 2) * zoom
203+
: -focusNode.position.x * zoom + padding,
204+
y: -focusNode.position.y * zoom + padding,
205+
zoom,
206+
}
207+
}

0 commit comments

Comments
 (0)