Skip to content

Commit

Permalink
Merge pull request #431 from duyet/chore/ui-update
Browse files Browse the repository at this point in the history
feat: enhance query detail page with cluster support and improve UI components
  • Loading branch information
duyet authored Nov 25, 2024
2 parents 8eb72f3 + d3afc9a commit f36e38e
Show file tree
Hide file tree
Showing 12 changed files with 623 additions and 392 deletions.
8 changes: 6 additions & 2 deletions app/[host]/query/[query_id]/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export const config: QueryConfig = {
query_cache_usage,
query_duration_ms as duration_ms,
query_duration_ms / 1000 as duration,
event_time_microseconds,
query_start_time_microseconds,
-- The time in seconds since this stage started
(now() - query_start_time) as elapsed,
Expand Down Expand Up @@ -136,12 +138,14 @@ export const config: QueryConfig = {
toString(transaction_id) as transaction_id
FROM system.query_log
WHERE
query_id = {query_id: String}
ORDER BY query_start_time DESC
initial_query_id = {query_id: String}
ORDER BY event_time_microseconds
LIMIT 1000
`,
columns: [
'hostname',
'type',
'query_start_time_microseconds',
'readable_elapsed',
'duration_ms',
'readable_memory_usage',
Expand Down
108 changes: 108 additions & 0 deletions app/[host]/query/[query_id]/dropdown-cluster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { ErrorAlert } from '@/components/error-alert'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { fetchData } from '@/lib/clickhouse'

import { getHostIdCookie } from '@/lib/scoped-link'
import { CircleCheckIcon, CombineIcon } from 'lucide-react'
import { PageProps } from './types'

interface RowData {
cluster: string
replica_count: number
}

const sql = `
SELECT DISTINCT
cluster,
count(replica_num) AS replica_count
FROM system.clusters
GROUP BY 1
`

export async function DropdownCluster({
params,
searchParams,
className,
}: {
params: Awaited<PageProps['params']>
searchParams: Awaited<PageProps['searchParams']>
className?: string
}) {
const { query_id } = params
const { cluster } = searchParams
const hostId = await getHostIdCookie()
const path = `/${hostId}/query/${query_id}/`

try {
const { data } = await fetchData<RowData[]>({
query: sql,
format: 'JSONEachRow',
clickhouse_settings: {
use_query_cache: 0,
},
hostId,
})

if (!data.length) {
return null
}

return (
<div className={className}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
{!!cluster ? (
<span className="flex items-center gap-1">
<CircleCheckIcon className="size-3" />
{cluster}
</span>
) : (
<span className="flex items-center gap-1">
<CombineIcon className="size-3" />
Query Across Cluster
</span>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Query Across Cluster</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup value={cluster}>
{data.map((row) => (
<DropdownMenuRadioItem key={row.cluster} value={row.cluster}>
<a
href={
cluster === row.cluster
? path
: `${path}?cluster=${row.cluster}`
}
>
{row.cluster} ({row.replica_count} replicas)
</a>
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
} catch (error) {
return (
<ErrorAlert
title="ClickHouse Query Error"
message={`${error}`}
query={sql}
/>
)
}
}
14 changes: 13 additions & 1 deletion app/[host]/query/[query_id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const revalidate = 300

export default async function Page({ params, searchParams }: PageProps) {
const { query_id } = await params
const { cluster } = await searchParams

// Binding the query_id to the config
const queryConfig = {
Expand All @@ -21,14 +22,25 @@ export default async function Page({ params, searchParams }: PageProps) {
},
}

if (cluster) {
queryConfig.sql = queryConfig.sql.replace(
'FROM system.query_log',
`FROM clusterAllReplicas('${cluster}', system.query_log)`
)
}

return (
<div className="flex flex-col gap-4">
<Suspense fallback={<ChartSkeleton />}>
<RelatedCharts relatedCharts={queryConfig.relatedCharts} />
</Suspense>

<Suspense fallback={<TableSkeleton />}>
<QueryDetail queryConfig={queryConfig} params={await params} />
<QueryDetail
queryConfig={queryConfig}
params={await params}
searchParams={await searchParams}
/>
</Suspense>

<Suspense fallback={<TableSkeleton />}>
Expand Down
58 changes: 58 additions & 0 deletions app/[host]/query/[query_id]/query-detail-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Badge } from '@/components/ui/badge'
import { fetchData } from '@/lib/clickhouse'
import { getHostIdCookie } from '@/lib/scoped-link'
import { QueryConfig } from '@/types/query-config'
import { type RowData } from './config'
import { PageProps } from './types'

export async function QueryDetailBadge({
queryConfig,
params,
}: {
queryConfig: QueryConfig
params: Awaited<PageProps['params']>
searchParams: Awaited<PageProps['searchParams']>
}) {
try {
const queryParams = {
...queryConfig.defaultParams,
...params,
}
const { data } = await fetchData<RowData[]>({
query: queryConfig.sql,
format: 'JSONEachRow',
query_params: queryParams,
clickhouse_settings: {
use_query_cache: 0,
...queryConfig.clickhouseSettings,
},
hostId: await getHostIdCookie(),
})

if (!data.length) {
return <div className="text-xs text-muted-foreground">No data</div>
}

const { user } = data[0]
const finalType = data[data.length - 1].type
const query_duration_ms = data
.map((row) => parseInt(row.duration_ms))
.reduce((a, b) => a + b, 0)

return (
<>
<Badge className="ml-2" variant="outline" title="Query Duration (ms)">
{query_duration_ms} ms
</Badge>
<Badge className="ml-2" variant="outline" title="Query Type">
{finalType || 'Unknown'}
</Badge>
<Badge className="ml-2" variant="outline" title="User">
{user || 'Unknown'}
</Badge>
</>
)
} catch (error) {
return null
}
}
Loading

1 comment on commit f36e38e

@vercel
Copy link

@vercel vercel bot commented on f36e38e Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.