Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/selfish-rings-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---

feat: Improve Service Maps
34 changes: 33 additions & 1 deletion packages/app/src/components/DBRowSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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';
Expand Down Expand Up @@ -70,6 +71,7 @@ enum Tab {
Parsed = 'parsed',
Debug = 'debug',
Trace = 'trace',
ServiceMap = 'serviceMap',
Context = 'context',
Replay = 'replay',
Infrastructure = 'infrastructure',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -303,6 +307,14 @@ const DBRowSidePanel = ({
text: 'Trace',
value: Tab.Trace,
},
...(enableServiceMap
? [
{
text: 'Service Map',
value: Tab.ServiceMap,
},
]
: []),
{
text: 'Surrounding Context',
value: Tab.Context,
Expand Down Expand Up @@ -370,6 +382,26 @@ const DBRowSidePanel = ({
</Box>
</ErrorBoundary>
)}
{displayedTab === Tab.ServiceMap && enableServiceMap && (
<ErrorBoundary
onError={err => {
console.error(err);
}}
fallbackRender={() => (
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent p-4">
An error occurred while rendering this event.
</div>
)}
>
<Flex p="sm" flex={1}>
<ServiceMapSidePanel
traceId={traceId}
traceTableSourceId={traceSourceId}
dateRange={oneHourRange}
/>
</Flex>
</ErrorBoundary>
)}
{displayedTab === Tab.Parsed && (
<ErrorBoundary
onError={err => {
Expand Down
22 changes: 0 additions & 22 deletions packages/app/src/components/DBTracePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,28 +209,6 @@ export default function DBTracePanel({
)}
{traceSourceData != null && eventRowWhere != null && (
<>
<Group>
<Text size="sm" c="dark.2" my="sm">
Service Map
</Text>
<Badge
size="xs"
color="gray.4"
autoContrast
radius="sm"
className="align-text-bottom"
>
Beta
</Badge>
</Group>
<div style={{ height: '300px', width: '100%', display: 'flex' }}>
<ServiceMap
traceId={traceId}
traceTableSource={traceSourceData}
dateRange={dateRange}
/>
</div>
<Divider my="md" />
<Text size="sm" c="dark.2" my="sm">
Event Details
</Text>
Expand Down
46 changes: 37 additions & 9 deletions packages/app/src/components/ServiceMap/ServiceMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
NodeChange,
Position,
ReactFlow,
ReactFlowProvider,
useReactFlow,
} from '@xyflow/react';

import useServiceMap, { ServiceAggregation } from '@/hooks/useServiceMap';
Expand Down Expand Up @@ -74,6 +76,7 @@ interface ServiceMapPresentationProps {
error: Error | null;
dateRange: [Date, Date];
source: TSource;
isSingleTrace?: boolean;
}

function ServiceMapPresentation({
Expand All @@ -82,9 +85,16 @@ function ServiceMapPresentation({
error,
dateRange,
source,
isSingleTrace,
}: ServiceMapPresentationProps) {
const [nodes, setNodes] = useState<Node[]>([]);
const [edges, setEdges] = useState<Edge[]>([]);
const { fitView } = useReactFlow();

// Fit the data to the viewport whenever input service information changes
useEffect(() => {
fitView();
}, [fitView, services]);

const onNodesChange = useCallback(
(changes: NodeChange<Node>[]) =>
Expand Down Expand Up @@ -115,6 +125,7 @@ function ServiceMapPresentation({
dateRange,
source,
maxErrorPercentage,
isSingleTrace,
},
position: { x: index * 150, y: 100 },
type: 'service',
Expand Down Expand Up @@ -143,6 +154,7 @@ function ServiceMapPresentation({
source,
dateRange,
serviceName,
isSingleTrace,
},
};
},
Expand All @@ -153,16 +165,27 @@ function ServiceMapPresentation({

setNodes(nodeWithLayout);
setEdges(edges);
}, [services, dateRange, source, maxErrorPercentage]);
}, [services, dateRange, source, maxErrorPercentage, isSingleTrace]);

if (isLoading) {
return (
<Center className={`${styles.graphContainer} h-100`}>
<Center className={`${styles.graphContainer} h-100 w-100`}>
<Loader size="lg" />
</Center>
);
}

if (services && services.size === 0) {
return (
<Center className="w-100 h-100">
<Text size="sm" c="gray.5">
No services found. The Service Map shows links between services with
related Client- and Server-kind spans.
</Text>
</Center>
);
}

if (error) {
return (
<Box>
Expand Down Expand Up @@ -222,13 +245,15 @@ interface ServiceMapProps {
traceTableSource: TSource;
dateRange: [Date, Date];
samplingFactor?: number;
isSingleTrace?: boolean;
}

export default function ServiceMap({
traceId,
traceTableSource,
dateRange,
samplingFactor = 1,
isSingleTrace,
}: ServiceMapProps) {
const {
isLoading,
Expand All @@ -252,12 +277,15 @@ export default function ServiceMap({
}, [error]);

return (
<ServiceMapPresentation
services={services}
isLoading={isLoading}
error={error}
dateRange={dateRange}
source={traceTableSource}
/>
<ReactFlowProvider>
<ServiceMapPresentation
services={services}
isLoading={isLoading}
error={error}
dateRange={dateRange}
source={traceTableSource}
isSingleTrace={isSingleTrace}
/>
</ReactFlowProvider>
);
}
12 changes: 10 additions & 2 deletions packages/app/src/components/ServiceMap/ServiceMapEdge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ServiceMapEdgeData = {
dateRange: [Date, Date];
source: TSource;
serviceName: string;
isSingleTrace?: boolean;
};

export default function ServiceMapEdge(
Expand All @@ -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 (
<>
Expand All @@ -44,6 +51,7 @@ export default function ServiceMapEdge(
source={source}
dateRange={dateRange}
serviceName={serviceName}
isSingleTrace={isSingleTrace}
/>
</EdgeToolbar>
</>
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/components/ServiceMap/ServiceMapNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ServiceMapNodeData = ServiceAggregation & {
dateRange: [Date, Date];
source: TSource;
maxErrorPercentage: number;
isSingleTrace?: boolean;
};

export default function ServiceMapNode(
Expand All @@ -28,6 +29,7 @@ export default function ServiceMapNode(
source,
dateRange,
maxErrorPercentage,
isSingleTrace,
} = data;

const { backgroundColor, borderColor } = getNodeColors(
Expand All @@ -45,6 +47,7 @@ export default function ServiceMapNode(
source={source}
dateRange={dateRange}
serviceName={serviceName}
isSingleTrace={isSingleTrace}
/>
</NodeToolbar>
<div className={`${styles.serviceNode}`}>
Expand Down
47 changes: 47 additions & 0 deletions packages/app/src/components/ServiceMap/ServiceMapSidePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 (
<Stack w="100%">
<Group gap={0}>
<Text size="sm" c="gray.2" ps="sm">
Service Map
</Text>
<Badge
size="xs"
ms="xs"
color="gray.4"
autoContrast
radius="sm"
className="align-text-bottom"
>
Beta
</Badge>
</Group>
{traceTableSource ? (
<ServiceMap
traceTableSource={traceTableSource}
traceId={traceId}
dateRange={dateRange}
isSingleTrace
/>
) : null}
</Stack>
);
}
5 changes: 4 additions & 1 deletion packages/app/src/components/ServiceMap/ServiceMapTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={styles.toolbar}>
Expand All @@ -35,7 +37,8 @@ export default function ServiceMapTooltip({
}
className={styles.linkButton}
>
{formatApproximateNumber(totalRequests)} request
{isSingleTrace ? totalRequests : formatApproximateNumber(totalRequests)}{' '}
request
{totalRequests !== 1 ? 's' : ''}
</UnstyledButton>
{errorPercentage > 0 ? (
Expand Down
Loading