From 358b2951d82a69a7d3cff21967d237477944e622 Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 12:00:49 -0500 Subject: [PATCH 1/6] feat: Move side-panel service map to its own tab --- .../app/src/components/DBRowSidePanel.tsx | 34 +++++++++++++- packages/app/src/components/DBTracePanel.tsx | 22 --------- .../src/components/ServiceMap/ServiceMap.tsx | 2 +- .../ServiceMap/ServiceMapSidePanel.tsx | 46 +++++++++++++++++++ 4 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index 2d1d86ffd..cb0f1d8b3 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -14,7 +14,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useHotkeys } from 'react-hotkeys-hook'; import { TSource } from '@hyperdx/common-utils/dist/types'; import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types'; -import { Box, Drawer, Stack } from '@mantine/core'; +import { Box, Drawer, Flex, Stack } from '@mantine/core'; import { useClickOutside } from '@mantine/hooks'; import DBRowSidePanelHeader, { @@ -28,6 +28,7 @@ import TabBar from '@/TabBar'; import { SearchConfig } from '@/types'; import { useZIndex, ZIndexContext } from '@/zIndex'; +import ServiceMapSidePanel from './ServiceMap/ServiceMapSidePanel'; import ContextSubpanel from './ContextSidePanel'; import DBInfraPanel from './DBInfraPanel'; import { RowDataPanel, useRowData } from './DBRowDataPanel'; @@ -70,6 +71,7 @@ enum Tab { Parsed = 'parsed', Debug = 'debug', Trace = 'trace', + ServiceMap = 'serviceMap', Context = 'context', Replay = 'replay', Infrastructure = 'infrastructure', @@ -221,6 +223,8 @@ const DBRowSidePanel = ({ const traceSourceId = source.kind === 'trace' ? source.id : source.traceSourceId; + const enableServiceMap = traceId && traceSourceId; + const { rumSessionId, rumServiceName } = useSessionId({ sourceId: traceSourceId, traceId, @@ -303,6 +307,14 @@ const DBRowSidePanel = ({ text: 'Trace', value: Tab.Trace, }, + ...(enableServiceMap + ? [ + { + text: 'Service Map', + value: Tab.ServiceMap, + }, + ] + : []), { text: 'Surrounding Context', value: Tab.Context, @@ -370,6 +382,26 @@ const DBRowSidePanel = ({ )} + {displayedTab === Tab.ServiceMap && enableServiceMap && ( + { + console.error(err); + }} + fallbackRender={() => ( +
+ An error occurred while rendering this event. +
+ )} + > + + + +
+ )} {displayedTab === Tab.Parsed && ( { diff --git a/packages/app/src/components/DBTracePanel.tsx b/packages/app/src/components/DBTracePanel.tsx index 7dc6243ec..4ff92c0cc 100644 --- a/packages/app/src/components/DBTracePanel.tsx +++ b/packages/app/src/components/DBTracePanel.tsx @@ -209,28 +209,6 @@ export default function DBTracePanel({ )} {traceSourceData != null && eventRowWhere != null && ( <> - - - Service Map - - - Beta - - -
- -
- Event Details diff --git a/packages/app/src/components/ServiceMap/ServiceMap.tsx b/packages/app/src/components/ServiceMap/ServiceMap.tsx index 3069ec043..f14461c13 100644 --- a/packages/app/src/components/ServiceMap/ServiceMap.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMap.tsx @@ -157,7 +157,7 @@ function ServiceMapPresentation({ if (isLoading) { return ( -
+
); diff --git a/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx b/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx new file mode 100644 index 000000000..5d51b31f9 --- /dev/null +++ b/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx @@ -0,0 +1,46 @@ +import { Badge, Group, Stack, Text } from '@mantine/core'; + +import { useSource } from '@/source'; + +import ServiceMap from './ServiceMap'; + +interface ServiceMapSidePanelProps { + traceId: string; + dateRange: [Date, Date]; + traceTableSourceId: string; +} + +export default function ServiceMapSidePanel({ + traceId, + dateRange, + traceTableSourceId, +}: ServiceMapSidePanelProps) { + const { data: traceTableSource } = useSource({ id: traceTableSourceId }); + + return ( + + + + Service Map + + + Beta + + + {traceTableSource ? ( + + ) : null} + + ); +} From ae6714f3cc4b42236afdaf8a7f2a2b7c378774bb Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 12:15:09 -0500 Subject: [PATCH 2/6] fix: Fit service map bounds when changing data --- .../src/components/ServiceMap/ServiceMap.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/ServiceMap/ServiceMap.tsx b/packages/app/src/components/ServiceMap/ServiceMap.tsx index f14461c13..4f1003f67 100644 --- a/packages/app/src/components/ServiceMap/ServiceMap.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMap.tsx @@ -15,6 +15,8 @@ import { NodeChange, Position, ReactFlow, + ReactFlowProvider, + useReactFlow, } from '@xyflow/react'; import useServiceMap, { ServiceAggregation } from '@/hooks/useServiceMap'; @@ -85,6 +87,12 @@ function ServiceMapPresentation({ }: ServiceMapPresentationProps) { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); + const { fitView } = useReactFlow(); + + // Fit the data to the viewport whenever input service information changes + useEffect(() => { + fitView(); + }, [fitView, services]); const onNodesChange = useCallback( (changes: NodeChange[]) => @@ -252,12 +260,14 @@ export default function ServiceMap({ }, [error]); return ( - + + + ); } From 6dacbb9fdb515e8e0dd4c96972685455323b6482 Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 12:21:33 -0500 Subject: [PATCH 3/6] fix: Remove approx. from single-trace service maps --- .../app/src/components/ServiceMap/ServiceMap.tsx | 7 +++++++ .../app/src/components/ServiceMap/ServiceMapEdge.tsx | 12 ++++++++++-- .../app/src/components/ServiceMap/ServiceMapNode.tsx | 3 +++ .../components/ServiceMap/ServiceMapSidePanel.tsx | 1 + .../src/components/ServiceMap/ServiceMapTooltip.tsx | 5 ++++- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/ServiceMap/ServiceMap.tsx b/packages/app/src/components/ServiceMap/ServiceMap.tsx index 4f1003f67..952076508 100644 --- a/packages/app/src/components/ServiceMap/ServiceMap.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMap.tsx @@ -76,6 +76,7 @@ interface ServiceMapPresentationProps { error: Error | null; dateRange: [Date, Date]; source: TSource; + isSingleTrace?: boolean; } function ServiceMapPresentation({ @@ -84,6 +85,7 @@ function ServiceMapPresentation({ error, dateRange, source, + isSingleTrace, }: ServiceMapPresentationProps) { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); @@ -123,6 +125,7 @@ function ServiceMapPresentation({ dateRange, source, maxErrorPercentage, + isSingleTrace, }, position: { x: index * 150, y: 100 }, type: 'service', @@ -151,6 +154,7 @@ function ServiceMapPresentation({ source, dateRange, serviceName, + isSingleTrace, }, }; }, @@ -230,6 +234,7 @@ interface ServiceMapProps { traceTableSource: TSource; dateRange: [Date, Date]; samplingFactor?: number; + isSingleTrace?: boolean; } export default function ServiceMap({ @@ -237,6 +242,7 @@ export default function ServiceMap({ traceTableSource, dateRange, samplingFactor = 1, + isSingleTrace, }: ServiceMapProps) { const { isLoading, @@ -267,6 +273,7 @@ export default function ServiceMap({ error={error} dateRange={dateRange} source={traceTableSource} + isSingleTrace={isSingleTrace} /> ); diff --git a/packages/app/src/components/ServiceMap/ServiceMapEdge.tsx b/packages/app/src/components/ServiceMap/ServiceMapEdge.tsx index 806a6b50d..a2f826715 100644 --- a/packages/app/src/components/ServiceMap/ServiceMapEdge.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMapEdge.tsx @@ -15,6 +15,7 @@ export type ServiceMapEdgeData = { dateRange: [Date, Date]; source: TSource; serviceName: string; + isSingleTrace?: boolean; }; export default function ServiceMapEdge( @@ -26,8 +27,14 @@ export default function ServiceMapEdge( return null; } - const { totalRequests, errorPercentage, dateRange, serviceName, source } = - props.data; + const { + totalRequests, + errorPercentage, + dateRange, + serviceName, + source, + isSingleTrace, + } = props.data; return ( <> @@ -44,6 +51,7 @@ export default function ServiceMapEdge( source={source} dateRange={dateRange} serviceName={serviceName} + isSingleTrace={isSingleTrace} /> diff --git a/packages/app/src/components/ServiceMap/ServiceMapNode.tsx b/packages/app/src/components/ServiceMap/ServiceMapNode.tsx index 0838a81da..d0233337b 100644 --- a/packages/app/src/components/ServiceMap/ServiceMapNode.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMapNode.tsx @@ -13,6 +13,7 @@ export type ServiceMapNodeData = ServiceAggregation & { dateRange: [Date, Date]; source: TSource; maxErrorPercentage: number; + isSingleTrace?: boolean; }; export default function ServiceMapNode( @@ -28,6 +29,7 @@ export default function ServiceMapNode( source, dateRange, maxErrorPercentage, + isSingleTrace, } = data; const { backgroundColor, borderColor } = getNodeColors( @@ -45,6 +47,7 @@ export default function ServiceMapNode( source={source} dateRange={dateRange} serviceName={serviceName} + isSingleTrace={isSingleTrace} />
diff --git a/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx b/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx index 5d51b31f9..dfe192719 100644 --- a/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx @@ -39,6 +39,7 @@ export default function ServiceMapSidePanel({ traceTableSource={traceTableSource} traceId={traceId} dateRange={dateRange} + isSingleTrace /> ) : null} diff --git a/packages/app/src/components/ServiceMap/ServiceMapTooltip.tsx b/packages/app/src/components/ServiceMap/ServiceMapTooltip.tsx index 7bb59cac2..c1753852c 100644 --- a/packages/app/src/components/ServiceMap/ServiceMapTooltip.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMapTooltip.tsx @@ -12,12 +12,14 @@ export default function ServiceMapTooltip({ source, dateRange, serviceName, + isSingleTrace, }: { totalRequests: number; errorPercentage: number; source: TSource; dateRange: [Date, Date]; serviceName: string; + isSingleTrace?: boolean; }) { return (
@@ -35,7 +37,8 @@ export default function ServiceMapTooltip({ } className={styles.linkButton} > - {formatApproximateNumber(totalRequests)} request + {isSingleTrace ? totalRequests : formatApproximateNumber(totalRequests)}{' '} + request {totalRequests !== 1 ? 's' : ''} {errorPercentage > 0 ? ( From d123e297cd3435ca488e2992fcc09c1e3ee2f66a Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 12:31:55 -0500 Subject: [PATCH 4/6] style: Improve Service Map empty state --- packages/app/src/components/ServiceMap/ServiceMap.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/app/src/components/ServiceMap/ServiceMap.tsx b/packages/app/src/components/ServiceMap/ServiceMap.tsx index 952076508..aca8e76e2 100644 --- a/packages/app/src/components/ServiceMap/ServiceMap.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMap.tsx @@ -175,6 +175,17 @@ function ServiceMapPresentation({ ); } + if (services && services.size === 0) { + return ( +
+ + No services found. The Service Map shows links between services with + related Client- and Server-kind spans. + +
+ ); + } + if (error) { return ( From 6914bf9dcf876a9f1d5cf78c3b711f920a83b395 Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 23:08:35 -0500 Subject: [PATCH 5/6] chore: Add changeset --- .changeset/selfish-rings-sleep.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/selfish-rings-sleep.md diff --git a/.changeset/selfish-rings-sleep.md b/.changeset/selfish-rings-sleep.md new file mode 100644 index 000000000..7b6f33340 --- /dev/null +++ b/.changeset/selfish-rings-sleep.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +feat: Improve Service Maps From 30cfb84771da7c096aebf8bc73666ee29ad96cb4 Mon Sep 17 00:00:00 2001 From: Drew Davis Date: Tue, 11 Nov 2025 23:11:27 -0500 Subject: [PATCH 6/6] fix: Add useEffect dependency --- packages/app/src/components/ServiceMap/ServiceMap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/ServiceMap/ServiceMap.tsx b/packages/app/src/components/ServiceMap/ServiceMap.tsx index aca8e76e2..df9550be9 100644 --- a/packages/app/src/components/ServiceMap/ServiceMap.tsx +++ b/packages/app/src/components/ServiceMap/ServiceMap.tsx @@ -165,7 +165,7 @@ function ServiceMapPresentation({ setNodes(nodeWithLayout); setEdges(edges); - }, [services, dateRange, source, maxErrorPercentage]); + }, [services, dateRange, source, maxErrorPercentage, isSingleTrace]); if (isLoading) { return (