Skip to content

Commit c4915d4

Browse files
authored
feat: Add custom trace-level attributes above trace waterfall (#1356)
Ref HDX-2752 # Summary This PR adds a new Source setting which specifies additional Span-Level attributes which should be displayed above the Trace Waterfall. The attributes may be specified for both Log and Trace kind sources. These are displayed on the trace panel rather than at the top of the side panel because they are trace level (from any span in the trace), whereas the top of the side panel shows information specific to the span. In a followup PR, we'll add row-level attributes, which will appear at the top of the side panel. Notes: 1. The attributes are pulled from the log and trace source (if configured for each) despite the search page only being set to query one of those sources. If an attribute value comes from the log source while the trace source is currently configured on the search page, the attribute tag will not offer the option to show the "Add to search" button and instead will just show a "search this value" option, which navigates the search page to search the value in the correct source. ## Demo First, source is configured with highlighted attributes: <img width="954" height="288" alt="Screenshot 2025-11-14 at 3 02 25 PM" src="https://github.com/user-attachments/assets/e5eccbfa-3389-4521-83df-d63fcb13232a" /> Values for those attributes within the trace show up on the trace panel above the waterfall: <img width="1257" height="152" alt="Screenshot 2025-11-14 at 3 02 59 PM" src="https://github.com/user-attachments/assets/7e3e9d87-5f3a-409b-9b08-054d6e460970" /> Values are searchable when clicked, and if a lucene version of the property is provided, the lucene version will be used in the search box <img width="1334" height="419" alt="Screenshot 2025-11-14 at 3 03 10 PM" src="https://github.com/user-attachments/assets/50d98f99-5056-48ce-acca-4219286a68f7" />
1 parent 3f29e33 commit c4915d4

18 files changed

+960
-108
lines changed

.changeset/honest-mice-applaud.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@hyperdx/common-utils": patch
3+
"@hyperdx/api": patch
4+
"@hyperdx/app": patch
5+
---
6+
7+
feat: Add custom trace-level attributes above trace waterfall

packages/api/src/models/source.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export const Source = mongoose.model<ISource>(
6666
statusCodeExpression: String,
6767
statusMessageExpression: String,
6868
spanEventsValueExpression: String,
69+
highlightedTraceAttributeExpressions: {
70+
type: mongoose.Schema.Types.Array,
71+
},
6972

7073
metricTables: {
7174
type: {

packages/app/src/DBSearchPage.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
DisplayType,
3535
Filter,
3636
SourceKind,
37+
TSource,
3738
} from '@hyperdx/common-utils/dist/types';
3839
import {
3940
ActionIcon,
@@ -1091,21 +1092,32 @@ function DBSearchPage() {
10911092
({
10921093
where,
10931094
whereLanguage,
1095+
source,
10941096
}: {
10951097
where: SearchConfig['where'];
10961098
whereLanguage: SearchConfig['whereLanguage'];
1099+
source?: TSource;
10971100
}) => {
10981101
const qParams = new URLSearchParams({
1099-
where: where || searchedConfig.where || '',
11001102
whereLanguage: whereLanguage || 'sql',
11011103
from: searchedTimeRange[0].getTime().toString(),
11021104
to: searchedTimeRange[1].getTime().toString(),
1103-
select: searchedConfig.select || '',
1104-
source: searchedSource?.id || '',
1105-
filters: JSON.stringify(searchedConfig.filters ?? []),
11061105
isLive: 'false',
11071106
liveInterval: interval.toString(),
11081107
});
1108+
1109+
// When generating a search based on a different source,
1110+
// filters and select for the current source are not preserved.
1111+
if (source && source.id !== searchedSource?.id) {
1112+
qParams.append('where', where || '');
1113+
qParams.append('source', source.id);
1114+
} else {
1115+
qParams.append('select', searchedConfig.select || '');
1116+
qParams.append('where', where || searchedConfig.where || '');
1117+
qParams.append('filters', JSON.stringify(searchedConfig.filters ?? []));
1118+
qParams.append('source', searchedSource?.id || '');
1119+
}
1120+
11091121
return `/search?${qParams.toString()}`;
11101122
},
11111123
[
@@ -1829,6 +1841,7 @@ function DBSearchPage() {
18291841
dbSqlRowTableConfig,
18301842
isChildModalOpen: isDrawerChildModalOpen,
18311843
setChildModalOpen: setDrawerChildModalOpen,
1844+
source: searchedSource,
18321845
}}
18331846
config={dbSqlRowTableConfig}
18341847
sourceId={searchedConfig.source}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useContext, useMemo } from 'react';
2+
import { TSource } from '@hyperdx/common-utils/dist/types';
3+
import { Flex } from '@mantine/core';
4+
5+
import { RowSidePanelContext } from './DBRowSidePanel';
6+
import EventTag from './EventTag';
7+
8+
export type HighlightedAttribute = {
9+
source: TSource;
10+
displayedKey: string;
11+
value: string;
12+
sql: string;
13+
lucene?: string;
14+
};
15+
16+
export function DBHighlightedAttributesList({
17+
attributes = [],
18+
}: {
19+
attributes: HighlightedAttribute[];
20+
}) {
21+
const {
22+
onPropertyAddClick,
23+
generateSearchUrl,
24+
source: contextSource,
25+
} = useContext(RowSidePanelContext);
26+
27+
const sortedAttributes = useMemo(() => {
28+
return attributes.sort(
29+
(a, b) =>
30+
a.displayedKey.localeCompare(b.displayedKey) ||
31+
a.value.localeCompare(b.value),
32+
);
33+
}, [attributes]);
34+
35+
return (
36+
<Flex wrap="wrap" gap="2px" mb="md">
37+
{sortedAttributes.map(({ displayedKey, value, sql, lucene, source }) => (
38+
<EventTag
39+
displayedKey={displayedKey}
40+
name={lucene ? lucene : sql}
41+
nameLanguage={lucene ? 'lucene' : 'sql'}
42+
value={value as string}
43+
key={`${displayedKey}-${value}-${source.id}`}
44+
{...(onPropertyAddClick && contextSource?.id === source.id
45+
? {
46+
onPropertyAddClick,
47+
sqlExpression: sql,
48+
}
49+
: {
50+
onPropertyAddClick: undefined,
51+
sqlExpression: undefined,
52+
})}
53+
generateSearchUrl={
54+
generateSearchUrl
55+
? (query, queryLanguage) =>
56+
generateSearchUrl({
57+
where: query || '',
58+
whereLanguage: queryLanguage ?? 'lucene',
59+
source,
60+
})
61+
: undefined
62+
}
63+
/>
64+
))}
65+
</Flex>
66+
);
67+
}

packages/app/src/components/DBRowOverviewPanel.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ export function RowOverviewPanel({
8181
: {};
8282

8383
const _generateSearchUrl = useCallback(
84-
(query?: string, timeRange?: [Date, Date]) => {
84+
(query?: string, queryLanguage?: 'sql' | 'lucene') => {
8585
return (
8686
generateSearchUrl?.({
8787
where: query,
88-
whereLanguage: 'lucene',
88+
whereLanguage: queryLanguage,
8989
}) ?? '/'
9090
);
9191
},
@@ -195,8 +195,6 @@ export function RowOverviewPanel({
195195
<Box px="md">
196196
<NetworkPropertySubpanel
197197
eventAttributes={flattenedEventAttributes}
198-
onPropertyAddClick={onPropertyAddClick}
199-
generateSearchUrl={_generateSearchUrl}
200198
/>
201199
</Box>
202200
</Accordion.Panel>

packages/app/src/components/DBRowSidePanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ export type RowSidePanelContextProps = {
4646
generateSearchUrl?: ({
4747
where,
4848
whereLanguage,
49+
source,
4950
}: {
5051
where: SearchConfig['where'];
5152
whereLanguage: SearchConfig['whereLanguage'];
53+
source?: TSource;
5254
}) => string;
5355
generateChartUrl?: (config: {
5456
aggFn: string;
@@ -61,6 +63,7 @@ export type RowSidePanelContextProps = {
6163
dbSqlRowTableConfig?: ChartConfigWithDateRange;
6264
isChildModalOpen?: boolean;
6365
setChildModalOpen?: (open: boolean) => void;
66+
source?: TSource;
6467
};
6568

6669
export const RowSidePanelContext = createContext<RowSidePanelContextProps>({});

packages/app/src/components/DBRowSidePanelHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ export default function DBRowSidePanelHeader({
175175
const maxBoxHeight = 120;
176176

177177
const _generateSearchUrl = useCallback(
178-
(query?: string, timeRange?: [Date, Date]) => {
178+
(query?: string, queryLanguage?: 'sql' | 'lucene') => {
179179
return (
180180
generateSearchUrl?.({
181181
where: query,
182-
whereLanguage: 'lucene',
182+
whereLanguage: queryLanguage,
183183
}) ?? '/'
184184
);
185185
},

0 commit comments

Comments
 (0)