Skip to content

Commit 93939b5

Browse files
committed
Add query details main component with tabs
1 parent 58ecb8f commit 93939b5

File tree

12 files changed

+1703
-636
lines changed

12 files changed

+1703
-636
lines changed

core/trino-web-ui/src/main/resources/webapp-preview/package-lock.json

Lines changed: 523 additions & 515 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/trino-web-ui/src/main/resources/webapp-preview/package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,37 +18,37 @@
1818
"dependencies": {
1919
"@emotion/react": "^11.14.0",
2020
"@emotion/styled": "^11.14.0",
21-
"@fontsource/roboto": "^5.1.1",
22-
"@mui/icons-material": "^6.4.2",
23-
"@mui/material": "^6.4.2",
24-
"@mui/x-charts": "^7.25.0",
25-
"axios": "^1.7.9",
21+
"@fontsource/roboto": "^5.2.5",
22+
"@mui/icons-material": "^6.4.7",
23+
"@mui/material": "^6.4.7",
24+
"@mui/x-charts": "^7.27.1",
25+
"axios": "^1.8.2",
2626
"lodash": "^4.17.21",
2727
"react": "^18.3.1",
2828
"react-dom": "^18.3.1",
29-
"react-router-dom": "^7.1.5",
29+
"react-router-dom": "^7.3.0",
3030
"react-syntax-highlighter": "^15.6.1",
31-
"sass": "^1.83.4",
31+
"sass": "^1.85.1",
3232
"zustand": "^5.0.3"
3333
},
3434
"devDependencies": {
3535
"@eslint/js": "^9.14.0",
3636
"@types/eslint__js": "^8.42.3",
37-
"@types/lodash": "^4.17.15",
37+
"@types/lodash": "^4.17.16",
3838
"@types/react": "^18.3.18",
3939
"@types/react-dom": "^18.3.5",
4040
"@types/react-syntax-highlighter": "^15.5.13",
4141
"@typescript-eslint/eslint-plugin": "^8.14.0",
4242
"@typescript-eslint/parser": "^8.14.0",
4343
"@vitejs/plugin-react": "^4.3.4",
44-
"eslint": "^9.19.0",
44+
"eslint": "^9.22.0",
4545
"eslint-plugin-react": "^7.37.4",
46-
"eslint-plugin-react-hooks": "^5.1.0",
47-
"eslint-plugin-react-refresh": "^0.4.18",
48-
"globals": "^15.14.0",
49-
"prettier": "^3.4.2",
50-
"typescript": "^5.7.3",
51-
"typescript-eslint": "^8.22.0",
52-
"vite": "^6.0.11"
46+
"eslint-plugin-react-hooks": "^5.2.0",
47+
"eslint-plugin-react-refresh": "^0.4.19",
48+
"globals": "^15.15.0",
49+
"prettier": "^3.5.3",
50+
"typescript": "^5.8.2",
51+
"typescript-eslint": "^8.26.0",
52+
"vite": "^6.2.1"
5353
}
5454
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import { routers } from './router.tsx'
3232
import { useConfigStore, Theme as ThemeStore } from './store'
3333
import { darkTheme, lightTheme } from './theme'
3434
import trinoLogo from './assets/trino.svg'
35-
import { WorkerStatus } from './components/WorkerStatus.tsx'
35+
import { QueryDetails } from './components/QueryDetails'
36+
import { WorkerStatus } from './components/WorkerStatus'
3637

3738
const App = () => {
3839
const config = useConfigStore()
@@ -76,6 +77,7 @@ const Screen = () => {
7677
return [<Route {...router.routeProps} key={router.itemKey} />]
7778
})}
7879
<Route path="/workers/:nodeId" element={<WorkerStatus />} />
80+
<Route path="/queries/:queryId" element={<QueryDetails />} />
7981
<Route path="/" element={<Navigate to="/dashboard" />} />
8082
<Route path="*" element={<NotFound />} key={'*'} />
8183
</Routes>

core/trino-web-ui/src/main/resources/webapp-preview/src/api/webapp/api.ts

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,37 @@ export interface QueryStats {
9494
finishingTime: string
9595
fullyBlocked: boolean
9696
internalNetworkInputDataSize: string
97+
failedInternalNetworkInputDataSize: string
9798
peakTotalMemoryReservation: string
9899
peakUserMemoryReservation: string
100+
peakRevocableMemoryReservation: string
101+
physicalInputPositions: number
102+
failedPhysicalInputPositions: number
99103
physicalInputDataSize: string
104+
failedPhysicalInputDataSize: string
100105
physicalInputReadTime: string
106+
failedPhysicalInputReadTime: string
101107
physicalWrittenDataSize: string
108+
failedPhysicalWrittenDataSize: string
109+
internalNetworkInputPositions: number
110+
failedInternalNetworkInputPositions: number
102111
planningTime: string
112+
planningCpuTime: string
103113
progressPercentage: number
104114
queuedDrivers: number
105115
queuedTime: string
106116
rawInputDataSize: string
107117
rawInputPositions: number
118+
processedInputPositions: number
119+
failedProcessedInputPositions: number
120+
processedInputDataSize: string
121+
failedProcessedInputDataSize: string
122+
outputPositions: number
123+
failedOutputPositions: number
124+
outputDataSize: string
125+
failedOutputDataSize: string
126+
writtenPositions: number
127+
logicalWrittenDataSize: string
108128
runningDrivers: number
109129
runningPercentage: number
110130
spilledDataSize: string
@@ -116,26 +136,84 @@ export interface QueryStats {
116136
blockedReasons: string[]
117137
}
118138

119-
export interface QueryInfo {
120-
clientTags: string[]
139+
export interface Warning {
140+
message: string
141+
warningCode: {
142+
name: string
143+
}
144+
}
145+
146+
export interface StackInfo {
147+
message: string
148+
type: string
149+
suppressed: StackInfo[]
150+
stack: string[]
151+
cause: StackInfo
152+
}
153+
154+
export interface QueryInfoBase {
121155
queryId: string
122156
queryStats: QueryStats
123-
queryTextPreview: string
124157
queryType: string
158+
state: string
159+
scheduled: boolean
160+
memoryPool: string
161+
errorType: string
162+
errorCode: {
163+
code: string
164+
name: string
165+
}
166+
warnings: Warning[]
167+
failureInfo: StackInfo
168+
}
169+
170+
export interface QueryInfo extends QueryInfoBase {
171+
clientTags: string[]
172+
queryTextPreview: string
125173
resourceGroupId: string[]
126174
retryPolicy: string
127-
scheduled: boolean
128175
self: string
129176
sessionPrincipal: string
130177
sessionSource: string
131178
sessionUser: string
132-
state: string
133-
memoryPool: string
134179
queryDataEncoding: string
135-
errorType: string
136-
errorCode: {
137-
name: string
138-
}
180+
}
181+
182+
export interface Session {
183+
queryId: string
184+
transactionId: string
185+
clientTransactionSupport: boolean
186+
user: string
187+
originalUser: string
188+
groups: string[]
189+
originalUserGroups: string[]
190+
principal: string
191+
enabledRoles: string[]
192+
source: string
193+
catalog: string
194+
schema: string
195+
timeZoneKey: number
196+
locale: string
197+
remoteUserAddress: string
198+
userAgent: string
199+
clientTags: string[]
200+
clientCapabilities: string[]
201+
start: string
202+
protocolName: string
203+
timeZone: string
204+
queryDataEncoding: string
205+
systemProperties: { [key: string]: string | number | boolean }
206+
catalogProperties: { [key: string]: string | number | boolean }
207+
}
208+
209+
export interface QueryStatusInfo extends QueryInfoBase {
210+
session: Session
211+
query: string
212+
preparedQuery: string
213+
resourceGroupId: string[]
214+
retryPolicy: string
215+
pruned: boolean
216+
finalQueryInfo: boolean
139217
}
140218

141219
export async function statsApi(): Promise<ApiResponse<Stats>> {
@@ -153,3 +231,7 @@ export async function workerStatusApi(nodeId: string): Promise<ApiResponse<Worke
153231
export async function queryApi(): Promise<ApiResponse<QueryInfo[]>> {
154232
return await api.get<QueryInfo[]>('/ui/api/query')
155233
}
234+
235+
export async function queryStatusApi(queryId: string): Promise<ApiResponse<QueryStatusInfo>> {
236+
return await api.get<QueryStatusInfo>(`/ui/api/query/${queryId}`)
237+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ export interface ICodeBlockProps {
2020
code: string
2121
language: string
2222
height?: string
23+
noBottomBorder?: boolean
2324
}
2425

2526
export const CodeBlock = (props: ICodeBlockProps) => {
2627
const config = useConfigStore()
27-
const { code, language, height } = props
28+
const { code, language, height, noBottomBorder } = props
2829
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
2930

3031
const styleToUse = () => {
@@ -46,7 +47,7 @@ export const CodeBlock = (props: ICodeBlockProps) => {
4647
padding: 0,
4748
borderRadius: 0,
4849
border: `1px solid ${theme.palette.mode === 'dark' ? '#3f3f3f' : '#ddd'}`,
49-
borderBottom: 'none',
50+
borderBottom: noBottomBorder ? 'none' : '',
5051
width: '100%',
5152
height: {
5253
xs: '100%',

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,7 @@ export const RootLayout = (props: { children: React.ReactNode }) => {
291291
<Box sx={{ overflowX: 'hidden', flexGrow: 1 }}>
292292
<List>
293293
{routers.map((routerItem: RouterItem) =>
294-
routerItem.hidden ? (
295-
<></>
296-
) : (
294+
routerItem.hidden ? null : (
297295
<ListItem key={routerItem.text} disablePadding>
298296
{/*@ts-expect-error TS2769*/}
299297
<ListItemButton
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
import React, { ReactNode, useState } from 'react'
15+
import { useLocation, useParams } from 'react-router-dom'
16+
import { Alert, Box, Divider, Grid2 as Grid, Tabs, Tab, Typography } from '@mui/material'
17+
import { QueryOverview } from './QueryOverview'
18+
import { Texts } from '../constant.ts'
19+
20+
const tabValues = ['overview', 'livePlan', 'stagePerformance', 'splits', 'json', 'references'] as const
21+
type TabValue = (typeof tabValues)[number]
22+
const tabComponentMap: Record<TabValue, ReactNode> = {
23+
overview: <QueryOverview />,
24+
livePlan: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
25+
stagePerformance: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
26+
splits: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
27+
json: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
28+
references: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
29+
}
30+
export const QueryDetails = () => {
31+
const { queryId } = useParams()
32+
const location = useLocation()
33+
const queryParams = new URLSearchParams(location.search)
34+
const requestedTab = queryParams.get('tab')
35+
const [tabValue, setTabValue] = useState<TabValue>(
36+
tabValues.includes(requestedTab as TabValue) ? (requestedTab as TabValue) : 'overview'
37+
)
38+
39+
const handleTabChange = (_: React.SyntheticEvent, newTab: TabValue) => {
40+
setTabValue(newTab)
41+
}
42+
43+
return (
44+
<>
45+
<Box sx={{ pb: 2 }}>
46+
<Typography variant="h4">Query details</Typography>
47+
</Box>
48+
49+
<>
50+
<Grid container sx={{ pt: 2 }} alignItems="center">
51+
<Grid size={{ xs: 12, lg: 4 }}>
52+
<Typography variant="h6">{queryId}</Typography>
53+
</Grid>
54+
<Grid size={{ xs: 12, lg: 8 }}>
55+
<Box display="flex" justifyContent={{ xs: 'flex-start', lg: 'flex-end' }}>
56+
<Tabs value={tabValue} onChange={handleTabChange}>
57+
<Tab value="overview" label="Overview" />
58+
<Tab value="livePlan" label="Live plan" disabled />
59+
<Tab value="stagePerformance" label="Stage performance" disabled />
60+
<Tab value="splits" label="Splits" disabled />
61+
<Tab value="json" label="JSON" disabled />
62+
<Tab value="references" label="References" disabled />
63+
</Tabs>
64+
</Box>
65+
</Grid>
66+
</Grid>
67+
<Divider />
68+
69+
<div>{tabComponentMap[tabValue]}</div>
70+
</>
71+
</>
72+
)
73+
}

0 commit comments

Comments
 (0)