feat: Improve display of large sizes and volumes of highlighted attributes (#1442)

This commit is contained in:
Drew Davis 2025-12-04 14:12:07 -05:00 committed by GitHub
parent ff422206c5
commit 238c36fdd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 99 additions and 47 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
feat: Improve display of large sizes and volumes of highlighted attributes

View file

@ -1,10 +1,12 @@
import { useContext, useMemo } from 'react';
import { useContext, useMemo, useState } from 'react';
import { TSource } from '@hyperdx/common-utils/dist/types';
import { Flex } from '@mantine/core';
import { Anchor, Flex } from '@mantine/core';
import { RowSidePanelContext } from './DBRowSidePanel';
import EventTag from './EventTag';
const DEFAULT_ATTRIBUTES_TO_SHOW = 12;
export type HighlightedAttribute = {
source: TSource;
displayedKey: string;
@ -18,6 +20,8 @@ export function DBHighlightedAttributesList({
}: {
attributes: HighlightedAttribute[];
}) {
const [isExpanded, setIsExpanded] = useState(false);
const {
onPropertyAddClick,
generateSearchUrl,
@ -25,15 +29,19 @@ export function DBHighlightedAttributesList({
} = useContext(RowSidePanelContext);
const sortedAttributes = useMemo(() => {
return attributes.sort(
(a, b) =>
a.displayedKey.localeCompare(b.displayedKey) ||
a.value.localeCompare(b.value),
);
}, [attributes]);
return attributes
.sort(
(a, b) =>
a.displayedKey.localeCompare(b.displayedKey) ||
a.value.localeCompare(b.value),
)
.slice(0, isExpanded ? attributes.length : DEFAULT_ATTRIBUTES_TO_SHOW);
}, [attributes, isExpanded]);
const hiddenAttributesCount = attributes.length - sortedAttributes.length;
return (
<Flex wrap="wrap" gap="2px" mb="md">
<Flex wrap="wrap" gap="2px" mb="md" align="baseline">
{sortedAttributes.map(({ displayedKey, value, sql, lucene, source }) => (
<EventTag
displayedKey={displayedKey}
@ -62,6 +70,11 @@ export function DBHighlightedAttributesList({
}
/>
))}
{attributes.length > DEFAULT_ATTRIBUTES_TO_SHOW && (
<Anchor size="xs" onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? 'Show Less' : `Show ${hiddenAttributesCount} More...`}
</Anchor>
)}
</Flex>
);
}

View file

@ -10,7 +10,7 @@ import {
SourceKind,
TSource,
} from '@hyperdx/common-utils/dist/types';
import { Anchor, Box, Divider, Group, Text } from '@mantine/core';
import { Anchor, Box, Code, Divider, Group, Text } from '@mantine/core';
import { ContactSupportText } from '@/components/ContactSupportText';
import useOffsetPaginatedQuery from '@/hooks/useOffsetPaginatedQuery';
@ -245,28 +245,37 @@ export function useEventsAroundFocus({
/** A lucene expression that identifies rows to be hidden. Hidden rows will be returned with a `__hdx_hidden: true` column. */
hiddenRowExpression?: string;
}) {
let isFetching = false;
const { config, alias, type } = useMemo(
() => getConfig(tableSource, traceId, hiddenRowExpression),
[tableSource, traceId, hiddenRowExpression],
);
const { data: beforeSpanData, isFetching: isBeforeSpanFetching } =
useEventsData({
config,
dateRangeStartInclusive: true,
dateRange: [dateRange[0], focusDate],
enabled,
});
const { data: afterSpanData, isFetching: isAfterSpanFetching } =
useEventsData({
config,
dateRangeStartInclusive: false,
dateRange: [focusDate, dateRange[1]],
enabled,
});
isFetching = isFetching || isBeforeSpanFetching || isAfterSpanFetching;
const {
data: beforeSpanData,
isFetching: isBeforeSpanFetching,
error: beforeSpanError,
} = useEventsData({
config,
dateRangeStartInclusive: true,
dateRange: [dateRange[0], focusDate],
enabled,
});
const {
data: afterSpanData,
isFetching: isAfterSpanFetching,
error: afterSpanError,
} = useEventsData({
config,
dateRangeStartInclusive: false,
dateRange: [focusDate, dateRange[1]],
enabled,
});
const isFetching = isBeforeSpanFetching || isAfterSpanFetching;
const meta = beforeSpanData?.meta ?? afterSpanData?.meta;
const error = beforeSpanError || afterSpanError;
const rowWhere = useRowWhere({ meta, aliasMap: alias });
const rows = useMemo(() => {
// Sometimes meta has not loaded yet
@ -294,6 +303,7 @@ export function useEventsAroundFocus({
rows,
meta,
isFetching,
error,
};
}
@ -352,6 +362,7 @@ export function DBTraceWaterfallChartContainer({
rows: traceRowsData,
isFetching: traceIsFetching,
meta: traceRowsMeta,
error: traceError,
} = useEventsAroundFocus({
tableSource: traceTableSource,
focusDate,
@ -364,6 +375,7 @@ export function DBTraceWaterfallChartContainer({
rows: logRowsData,
isFetching: logIsFetching,
meta: logRowsMeta,
error: logError,
} = useEventsAroundFocus({
// search data if logTableModel exist
// search invalid date range if no logTableModel(react hook need execute no matter what)
@ -376,6 +388,8 @@ export function DBTraceWaterfallChartContainer({
});
const isFetching = traceIsFetching || logIsFetching;
const error = traceError || logError;
const rows: any[] = useMemo(
() => [...traceRowsData, ...logRowsData],
[traceRowsData, logRowsData],
@ -799,6 +813,9 @@ export function DBTraceWaterfallChartContainer({
)}
</span>
</Group>
{!isFetching && !error && (
<DBHighlightedAttributesList attributes={highlightedAttributeValues} />
)}
<div
style={{
position: 'relative',
@ -808,6 +825,20 @@ export function DBTraceWaterfallChartContainer({
>
{isFetching ? (
<div className="my-3">Loading Traces...</div>
) : error ? (
<Box mt="lg">
<Text my="sm" size="sm">
An error occurred while fetching trace data:
</Text>
<Code
block
style={{
whiteSpace: 'pre-wrap',
}}
>
{error.message}
</Code>
</Box>
) : rows == null ? (
<div>
An unknown error occurred. <ContactSupportText />
@ -816,9 +847,6 @@ export function DBTraceWaterfallChartContainer({
<div className="my-3">No matching spans or logs found</div>
) : (
<>
<DBHighlightedAttributesList
attributes={highlightedAttributeValues}
/>
<TimelineChart
style={{
overflowY: 'auto',

View file

@ -2,7 +2,7 @@ import { useState } from 'react';
import Link from 'next/link';
import SqlString from 'sqlstring';
import { SearchConditionLanguage } from '@hyperdx/common-utils/dist/types';
import { Button, Popover, Stack, Tooltip } from '@mantine/core';
import { Button, Group, Popover, Stack, Text, Tooltip } from '@mantine/core';
import { IconLink } from '@tabler/icons-react';
import { isLinkableUrl } from '@/utils/highlightedAttributes';
@ -62,14 +62,15 @@ export default function EventTag({
onChange={setOpened}
>
<Popover.Target>
{isLink ? (
<Tooltip
label={value}
withArrow
maw={400}
multiline
style={{ wordBreak: 'break-word' }}
>
<Tooltip
label={value}
withArrow
maw={400}
multiline
style={{ wordBreak: 'break-word' }}
position="bottom"
>
{isLink ? (
<a
href={encodeURI(value)}
target="_blank"
@ -79,15 +80,20 @@ export default function EventTag({
{displayedKey || name}
<IconLink size={14} className="ms-1" />
</a>
</Tooltip>
) : (
<div
className="bg-highlighted px-2 py-0.5 me-1 my-1 cursor-pointer"
onClick={() => setOpened(!opened)}
>
{displayedKey || name}: {value}
</div>
)}
) : (
<Group
className="bg-highlighted px-2 py-0.5 me-1 my-1 cursor-pointer"
wrap="nowrap"
gap={0}
onClick={() => setOpened(!opened)}
>
{displayedKey || name}:
<Text size="xs" ms={4} maw={300} truncate>
{value}
</Text>
</Group>
)}
</Tooltip>
</Popover.Target>
<Popover.Dropdown p={2}>
<Stack gap={0} justify="stretch">