design: Make service map drill-down links more obvious (#1738)

# Summary

The buttons within the service map tooltips were hard to recognize as buttons/links. I've added an icon and give them a hover state.

## Before

<img width="277" height="124" alt="Screenshot 2026-02-13 at 2 28 14 PM" src="https://github.com/user-attachments/assets/256b0b7d-b6eb-44e6-8a69-c0bf2b15db17" />

## After

<img width="202" height="197" alt="Screenshot 2026-02-13 at 2 27 26 PM" src="https://github.com/user-attachments/assets/27e26ff9-b644-4d14-8217-cf4e7fd53d84" />
This commit is contained in:
Drew Davis 2026-02-13 15:48:22 -05:00 committed by GitHub
parent 35494dc032
commit 69f0b487fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 50 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
design: Make service map drill-down links more obvious

View file

@ -6,15 +6,10 @@
}
.toolbar {
padding: 4px 8px;
padding: 4px;
border-radius: 4px;
background-color: #f0f0f0;
border: 1px solid #ccc;
color: #111;
.linkButton {
font-size: small;
}
background-color: var(--color-bg-header);
border: 1px solid var(--color-border);
}
.serviceNode {

View file

@ -1,6 +1,8 @@
import { useCallback } from 'react';
import SqlString from 'sqlstring';
import { TSource } from '@hyperdx/common-utils/dist/types';
import { UnstyledButton } from '@mantine/core';
import { Button, Group, Stack, UnstyledButton } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
import { formatApproximateNumber, navigateToTraceSearch } from './utils';
@ -21,53 +23,63 @@ export default function ServiceMapTooltip({
serviceName: string;
isSingleTrace?: boolean;
}) {
const requestText = `${isSingleTrace ? totalRequests : formatApproximateNumber(totalRequests)} request${
totalRequests !== 1 ? 's' : ''
}`;
const errorsText = `${errorPercentage.toFixed(2)}% errors`;
const handleRequestsClick = useCallback(() => {
navigateToTraceSearch({
dateRange,
source,
where: SqlString.format("? = ? AND ? IN ('Server', 'Consumer')", [
SqlString.raw(source.serviceNameExpression ?? 'ServiceName'),
serviceName,
SqlString.raw(source.spanKindExpression ?? 'SpanKind'),
]),
});
}, [dateRange, source, serviceName]);
const handleErrorsClick = useCallback(() => {
navigateToTraceSearch({
dateRange,
source,
where: SqlString.format(
"? = ? AND ? IN ('Server', 'Consumer') AND ? = 'Error'",
[
SqlString.raw(source.serviceNameExpression ?? 'ServiceName'),
serviceName,
SqlString.raw(source.spanKindExpression ?? 'SpanKind'),
SqlString.raw(source.statusCodeExpression ?? 'StatusCode'),
],
),
});
}, [dateRange, source, serviceName]);
return (
<div className={styles.toolbar}>
<UnstyledButton
onClick={() =>
navigateToTraceSearch({
dateRange,
source,
where: SqlString.format("? = ? AND ? IN ('Server', 'Consumer')", [
SqlString.raw(source.serviceNameExpression ?? 'ServiceName'),
serviceName,
SqlString.raw(source.spanKindExpression ?? 'SpanKind'),
]),
})
}
className={styles.linkButton}
<Stack className={styles.toolbar} gap={0}>
<Button
onClick={handleRequestsClick}
variant="subtle"
size="xs"
color="var(--color-text)"
rightSection={<IconSearch size={16} />}
>
{isSingleTrace ? totalRequests : formatApproximateNumber(totalRequests)}{' '}
request
{totalRequests !== 1 ? 's' : ''}
</UnstyledButton>
{requestText}
</Button>
{errorPercentage > 0 ? (
<>
{', '}
<UnstyledButton
onClick={() =>
navigateToTraceSearch({
dateRange,
source,
where: SqlString.format(
"? = ? AND ? IN ('Server', 'Consumer') AND ? = 'Error'",
[
SqlString.raw(
source.serviceNameExpression ?? 'ServiceName',
),
serviceName,
SqlString.raw(source.spanKindExpression ?? 'SpanKind'),
SqlString.raw(source.statusCodeExpression ?? 'StatusCode'),
],
),
})
}
className={styles.linkButton}
<Button
onClick={handleErrorsClick}
variant="subtle"
size="xs"
color="var(--color-text-danger)"
rightSection={<IconSearch size={16} />}
>
{errorPercentage.toFixed(2)}% error
</UnstyledButton>
{errorsText}
</Button>
</>
) : null}
</div>
</Stack>
);
}