Skip to content

Commit 9c9e42d

Browse files
committed
Fix query plan default viewport in preview UI
1 parent d0e2314 commit 9c9e42d

File tree

4 files changed

+94
-28
lines changed

4 files changed

+94
-28
lines changed

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

Lines changed: 35 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,13 @@ export const QueryLivePlan = () => {
9495
}
9596
}
9697

98+
const focusViewportToFirstStage = () => {
99+
const viewportTarget = getViewportFocusedOnNode(nodes, { targetNodeId: 'stage-0' })
100+
if (viewportTarget) {
101+
setViewport(viewportTarget)
102+
}
103+
}
104+
97105
return (
98106
<>
99107
{loading && <CircularProgress />}
@@ -115,21 +123,30 @@ export const QueryLivePlan = () => {
115123
ref={containerRef}
116124
sx={{ width: '100%', height: '80vh', border: '1px solid #ccc' }}
117125
>
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>
126+
{nodes.length > 0 ? (
127+
<ReactFlow
128+
nodes={nodes}
129+
edges={edges}
130+
onNodesChange={onNodesChange}
131+
onEdgesChange={onEdgesChange}
132+
nodeTypes={nodeTypes}
133+
minZoom={0.1}
134+
proOptions={{ hideAttribution: true }}
135+
viewport={viewport}
136+
onViewportChange={setViewport}
137+
fitView
138+
>
139+
<HelpMessage
140+
layoutDirection={layoutDirection}
141+
onLayoutDirectionChange={setLayoutDirection}
142+
onOriginClick={focusViewportToFirstStage}
143+
/>
144+
</ReactFlow>
145+
) : (
146+
<Typography sx={{ p: 1 }} fontSize="small">
147+
Rendering...
148+
</Typography>
149+
)}
133150
</Box>
134151
</Box>
135152
</Grid>

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

Lines changed: 14 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,13 @@ export const QueryStagePerformance = () => {
135136
},
136137
}
137138

139+
const focusViewportToFirstPipeline = () => {
140+
const viewportTarget = getViewportFocusedOnNode(nodes, { targetNodeId: 'pipeline-0' })
141+
if (viewportTarget) {
142+
setViewport(viewportTarget)
143+
}
144+
}
145+
138146
const handleStageIdChange = (event: SelectChangeEvent) => {
139147
setStagePlanId(event.target.value as string)
140148
}
@@ -169,11 +177,14 @@ export const QueryStagePerformance = () => {
169177
nodeTypes={nodeTypes}
170178
minZoom={0.1}
171179
proOptions={{ hideAttribution: true }}
172-
defaultViewport={{ x: 200, y: 20, zoom: 0.8 }}
180+
viewport={viewport}
181+
onViewportChange={setViewport}
182+
fitView
173183
>
174184
<HelpMessage
175185
layoutDirection={layoutDirection}
176186
onLayoutDirectionChange={setLayoutDirection}
187+
onOriginClick={focusViewportToFirstPipeline}
177188
additionalContent={
178189
<Box sx={{ mt: 2 }}>
179190
<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: 20 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,22 @@ 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 }
191+
): Viewport | undefined => {
192+
if (nodes.length === 0) {
193+
return undefined
194+
}
195+
196+
const { targetNodeId, zoom = 0.8, padding = 20 } = options ?? {}
197+
const targetNode = targetNodeId ? nodes.find((node) => node.id === targetNodeId) : undefined
198+
const focusNode = targetNode ?? nodes[0]
199+
200+
return {
201+
x: focusNode.position.x * -1 * zoom + padding,
202+
y: focusNode.position.y * -1 * zoom + padding,
203+
zoom,
204+
}
205+
}

0 commit comments

Comments
 (0)