Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,533 changes: 942 additions & 591 deletions core/trino-web-ui/src/main/resources/webapp-preview/package-lock.json

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions core/trino-web-ui/src/main/resources/webapp-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,37 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1",
"@mui/icons-material": "^6.4.2",
"@mui/material": "^6.4.2",
"@mui/x-charts": "^7.25.0",
"axios": "^1.7.9",
"@fontsource/roboto": "^5.2.5",
"@mui/icons-material": "^6.4.7",
"@mui/material": "^6.4.7",
"@mui/x-charts": "^7.27.1",
"axios": "^1.8.2",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.1.5",
"react-router-dom": "^7.3.0",
"react-syntax-highlighter": "^15.6.1",
"sass": "^1.83.4",
"sass": "^1.85.1",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@types/eslint__js": "^8.42.3",
"@types/lodash": "^4.17.15",
"@types/lodash": "^4.17.16",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.19.0",
"eslint": "^9.22.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.22.0",
"vite": "^6.0.11"
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"typescript": "^5.8.2",
"typescript-eslint": "^8.26.0",
"vite": "^6.2.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import { routers } from './router.tsx'
import { useConfigStore, Theme as ThemeStore } from './store'
import { darkTheme, lightTheme } from './theme'
import trinoLogo from './assets/trino.svg'
import { WorkerStatus } from './components/WorkerStatus.tsx'
import { QueryDetails } from './components/QueryDetails'
import { WorkerStatus } from './components/WorkerStatus'

const App = () => {
const config = useConfigStore()
Expand Down Expand Up @@ -76,6 +77,7 @@ const Screen = () => {
return [<Route {...router.routeProps} key={router.itemKey} />]
})}
<Route path="/workers/:nodeId" element={<WorkerStatus />} />
<Route path="/queries/:queryId" element={<QueryDetails />} />
<Route path="/" element={<Navigate to="/dashboard" />} />
<Route path="*" element={<NotFound />} key={'*'} />
</Routes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,37 @@ export interface QueryStats {
finishingTime: string
fullyBlocked: boolean
internalNetworkInputDataSize: string
failedInternalNetworkInputDataSize: string
peakTotalMemoryReservation: string
peakUserMemoryReservation: string
peakRevocableMemoryReservation: string
physicalInputPositions: number
failedPhysicalInputPositions: number
physicalInputDataSize: string
failedPhysicalInputDataSize: string
physicalInputReadTime: string
failedPhysicalInputReadTime: string
physicalWrittenDataSize: string
failedPhysicalWrittenDataSize: string
internalNetworkInputPositions: number
failedInternalNetworkInputPositions: number
planningTime: string
planningCpuTime: string
progressPercentage: number
queuedDrivers: number
queuedTime: string
rawInputDataSize: string
rawInputPositions: number
processedInputPositions: number
failedProcessedInputPositions: number
processedInputDataSize: string
failedProcessedInputDataSize: string
outputPositions: number
failedOutputPositions: number
outputDataSize: string
failedOutputDataSize: string
writtenPositions: number
logicalWrittenDataSize: string
runningDrivers: number
runningPercentage: number
spilledDataSize: string
Expand All @@ -116,26 +136,84 @@ export interface QueryStats {
blockedReasons: string[]
}

export interface QueryInfo {
clientTags: string[]
export interface Warning {
message: string
warningCode: {
name: string
}
}

export interface StackInfo {
message: string
type: string
suppressed: StackInfo[]
stack: string[]
cause: StackInfo
}

export interface QueryInfoBase {
queryId: string
queryStats: QueryStats
queryTextPreview: string
queryType: string
state: string
scheduled: boolean
memoryPool: string
errorType: string
errorCode: {
code: string
name: string
}
warnings: Warning[]
failureInfo: StackInfo
}

export interface QueryInfo extends QueryInfoBase {
clientTags: string[]
queryTextPreview: string
resourceGroupId: string[]
retryPolicy: string
scheduled: boolean
self: string
sessionPrincipal: string
sessionSource: string
sessionUser: string
state: string
memoryPool: string
queryDataEncoding: string
errorType: string
errorCode: {
name: string
}
}

export interface Session {
queryId: string
transactionId: string
clientTransactionSupport: boolean
user: string
originalUser: string
groups: string[]
originalUserGroups: string[]
principal: string
enabledRoles: string[]
source: string
catalog: string
schema: string
timeZoneKey: number
locale: string
remoteUserAddress: string
userAgent: string
clientTags: string[]
clientCapabilities: string[]
start: string
protocolName: string
timeZone: string
queryDataEncoding: string
systemProperties: { [key: string]: string | number | boolean }
catalogProperties: { [key: string]: string | number | boolean }
}

export interface QueryStatusInfo extends QueryInfoBase {
session: Session
query: string
preparedQuery: string
resourceGroupId: string[]
retryPolicy: string
pruned: boolean
finalQueryInfo: boolean
}

export async function statsApi(): Promise<ApiResponse<Stats>> {
Expand All @@ -153,3 +231,7 @@ export async function workerStatusApi(nodeId: string): Promise<ApiResponse<Worke
export async function queryApi(): Promise<ApiResponse<QueryInfo[]>> {
return await api.get<QueryInfo[]>('/ui/api/query')
}

export async function queryStatusApi(queryId: string): Promise<ApiResponse<QueryStatusInfo>> {
return await api.get<QueryStatusInfo>(`/ui/api/query/${queryId}`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ export interface ICodeBlockProps {
code: string
language: string
height?: string
noBottomBorder?: boolean
}

export const CodeBlock = (props: ICodeBlockProps) => {
const config = useConfigStore()
const { code, language, height } = props
const { code, language, height, noBottomBorder } = props
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')

const styleToUse = () => {
Expand All @@ -46,7 +47,7 @@ export const CodeBlock = (props: ICodeBlockProps) => {
padding: 0,
borderRadius: 0,
border: `1px solid ${theme.palette.mode === 'dark' ? '#3f3f3f' : '#ddd'}`,
borderBottom: 'none',
borderBottom: noBottomBorder ? 'none' : '',
width: '100%',
height: {
xs: '100%',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ export const RootLayout = (props: { children: React.ReactNode }) => {
<Box sx={{ overflowX: 'hidden', flexGrow: 1 }}>
<List>
{routers.map((routerItem: RouterItem) =>
routerItem.hidden ? (
<></>
) : (
routerItem.hidden ? null : (
<ListItem key={routerItem.text} disablePadding>
{/*@ts-expect-error TS2769*/}
<ListItemButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { ReactNode, useState } from 'react'
import { useLocation, useParams } from 'react-router-dom'
import { Alert, Box, Divider, Grid2 as Grid, Tabs, Tab, Typography } from '@mui/material'
import { QueryOverview } from './QueryOverview'
import { Texts } from '../constant.ts'

const tabValues = ['overview', 'livePlan', 'stagePerformance', 'splits', 'json', 'references'] as const
type TabValue = (typeof tabValues)[number]
const tabComponentMap: Record<TabValue, ReactNode> = {
overview: <QueryOverview />,
livePlan: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
stagePerformance: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
splits: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
json: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
references: <Alert severity="error">{Texts.Error.NotImplemented}</Alert>,
}
export const QueryDetails = () => {
const { queryId } = useParams()
const location = useLocation()
const queryParams = new URLSearchParams(location.search)
const requestedTab = queryParams.get('tab')
const [tabValue, setTabValue] = useState<TabValue>(
tabValues.includes(requestedTab as TabValue) ? (requestedTab as TabValue) : 'overview'
)

const handleTabChange = (_: React.SyntheticEvent, newTab: TabValue) => {
setTabValue(newTab)
}

return (
<>
<Box sx={{ pb: 2 }}>
<Typography variant="h4">Query details</Typography>
</Box>

<>
<Grid container sx={{ pt: 2 }} alignItems="center">
<Grid size={{ xs: 12, lg: 4 }}>
<Typography variant="h6">{queryId}</Typography>
</Grid>
<Grid size={{ xs: 12, lg: 8 }}>
<Box display="flex" justifyContent={{ xs: 'flex-start', lg: 'flex-end' }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab value="overview" label="Overview" />
<Tab value="livePlan" label="Live plan" disabled />
<Tab value="stagePerformance" label="Stage performance" disabled />
<Tab value="splits" label="Splits" disabled />
<Tab value="json" label="JSON" disabled />
<Tab value="references" label="References" disabled />
</Tabs>
</Box>
</Grid>
</Grid>
<Divider />

<div>{tabComponentMap[tabValue]}</div>
</>
</>
)
}
Loading