hyperdx/packages/app/src/components/PatternTable.tsx
Karl Power 3ffafced5e
feat: show error details in search event patterns (#2065)
## Summary

- Shows query errors in search page event patterns in the same way as for event deltas.
- Previously, a loading state was shown indefinitely if there was an error.

### Screenshots or video



<img width="1375" height="554" alt="Screenshot 2026-04-07 at 16 14 37" src="https://github.com/user-attachments/assets/25417f1a-bfd3-44ca-bcd6-aa24156fad14" />


### How to test locally or on Vercel



1. Easiest to test locally by manually throwing from the `useQueriedChartConfig` query function.


### References



- Linear Issue: Closes HDX-3933
- Related PRs:
2026-04-07 15:26:06 +00:00

140 lines
3.7 KiB
TypeScript

import { useMemo, useState } from 'react';
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
import {
BuilderChartConfigWithDateRange,
SourceKind,
TSource,
} from '@hyperdx/common-utils/dist/types';
import { Box, Code, Container, Text } from '@mantine/core';
import { SQLPreview } from '@/components/ChartSQLPreview';
import { RawLogTable } from '@/components/DBRowTable';
import { useSearchTotalCount } from '@/components/SearchTotalCountChart';
import { Pattern, useGroupedPatterns } from '@/hooks/usePatterns';
import PatternSidePanel from './PatternSidePanel';
const emptyMap = new Map();
export default function PatternTable({
config,
totalCountConfig,
totalCountQueryKeyPrefix,
bodyValueExpression,
source,
}: {
config: BuilderChartConfigWithDateRange;
totalCountConfig: BuilderChartConfigWithDateRange;
bodyValueExpression: string;
totalCountQueryKeyPrefix: string;
source?: TSource;
}) {
const SAMPLES = 10_000;
const [selectedPattern, setSelectedPattern] = useState<Pattern | null>(null);
const {
error: totalCountError,
isLoading: isTotalCountLoading,
isTotalCountComplete,
totalCount,
} = useSearchTotalCount(totalCountConfig, totalCountQueryKeyPrefix);
const {
data: groupedResults,
isLoading: isGroupedPatternsLoading,
error: groupedPatternsError,
patternQueryConfig,
} = useGroupedPatterns({
config,
samples: SAMPLES,
bodyValueExpression,
severityTextExpression:
(source?.kind === SourceKind.Log && source.severityTextExpression) || '',
statusCodeExpression:
(source?.kind === SourceKind.Trace && source.statusCodeExpression) || '',
totalCount,
});
const isLoading =
isTotalCountLoading || !isTotalCountComplete || isGroupedPatternsLoading;
const error = totalCountError || groupedPatternsError;
const sortedGroupedResults = useMemo(() => {
return Object.values(groupedResults).sort(
(a, b) => b.count - a.count,
) as Pattern[];
}, [groupedResults]);
return error ? (
<Container style={{ overflow: 'auto' }}>
<Box mt="lg">
<Text my="sm" size="sm">
Error Message:
</Text>
<Code
block
style={{
whiteSpace: 'pre-wrap',
}}
>
{error.message}
</Code>
</Box>
{error instanceof ClickHouseQueryError && (
<Box mt="lg">
<Text my="sm" size="sm">
Original Query:
</Text>
<Code
block
style={{
whiteSpace: 'pre-wrap',
}}
>
<SQLPreview data={error.query} formatData />
</Code>
</Box>
)}
</Container>
) : (
<>
<RawLogTable
isLive={false}
wrapLines={true}
isLoading={isLoading}
rows={sortedGroupedResults ?? []}
displayedColumns={[
'__hdx_pattern_trend',
'countStr',
'severityText',
'pattern',
]}
onRowDetailsClick={row => setSelectedPattern(row as Pattern)}
hasNextPage={false}
fetchNextPage={() => {}}
highlightedLineId={''}
columnTypeMap={emptyMap}
generateRowId={row => ({ where: row.id, aliasWith: [] })}
columnNameMap={{
__hdx_pattern_trend: 'Trend',
countStr: 'Count',
pattern: 'Pattern',
severityText: 'Level',
}}
config={patternQueryConfig}
showExpandButton={false}
/>
{selectedPattern && source && (
<PatternSidePanel
isOpen
source={source}
pattern={selectedPattern}
bodyValueExpression={bodyValueExpression}
onClose={() => setSelectedPattern(null)}
/>
)}
</>
);
}