Skip to content

Commit

Permalink
feat(uptime): Add span count next to trace IDs (#86084)
Browse files Browse the repository at this point in the history
Looks like this

<img alt="clipboard.png" width="1047"
src="https://i.imgur.com/BZORdNz.png" />

<img width="283" alt="image"
src="https://github.com/user-attachments/assets/6439dd67-7ce9-458e-bf86-67ff4f04ef48"
/>
<img width="282" alt="image"
src="https://github.com/user-attachments/assets/973cb41d-5fa6-4bb0-a881-e79f69ab5cc7"
/>
  • Loading branch information
evanpurkhiser authored Feb 28, 2025
1 parent 1a80654 commit f52b937
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 25 deletions.
6 changes: 1 addition & 5 deletions static/app/views/alerts/rules/uptime/details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ import useApi from 'sentry/utils/useApi';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {makeAlertsPathname} from 'sentry/views/alerts/pathnames';
import {
CheckStatus,
type CheckStatusBucket,
type UptimeRule,
} from 'sentry/views/alerts/rules/uptime/types';
import {
setUptimeRuleData,
useUptimeRule,
Expand All @@ -35,6 +30,7 @@ import {
import {UptimeDetailsSidebar} from './detailsSidebar';
import {DetailsTimeline} from './detailsTimeline';
import {StatusToggleButton} from './statusToggleButton';
import {CheckStatus, type CheckStatusBucket, type UptimeRule} from './types';
import {UptimeChecksTable} from './uptimeChecksTable';
import {UptimeIssues} from './uptimeIssues';

Expand Down
96 changes: 84 additions & 12 deletions static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';

import Tag from 'sentry/components/badge/tag';
import {DateTime} from 'sentry/components/dateTime';
import Duration from 'sentry/components/duration';
import type {GridColumnOrder} from 'sentry/components/gridEditable';
import GridEditable from 'sentry/components/gridEditable';
import ExternalLink from 'sentry/components/links/externalLink';
import Link from 'sentry/components/links/link';
import Placeholder from 'sentry/components/placeholder';
import {Tooltip} from 'sentry/components/tooltip';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {getShortEventId} from 'sentry/utils/events';
import type {UptimeCheck} from 'sentry/views/alerts/rules/uptime/types';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import type {UptimeCheck, UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
import {useEAPSpans} from 'sentry/views/insights/common/queries/useDiscover';
import {
reasonToText,
statusToText,
Expand All @@ -19,6 +24,7 @@ import {

type Props = {
uptimeChecks: UptimeCheck[];
uptimeRule: UptimeRule;
};

/**
Expand All @@ -27,7 +33,28 @@ type Props = {
*/
const EMPTY_TRACE = '00000000000000000000000000000000';

export function UptimeChecksGrid({uptimeChecks}: Props) {
export function UptimeChecksGrid({uptimeRule, uptimeChecks}: Props) {
const traceIds = uptimeChecks?.map(check => check.traceId) ?? [];

const {data: spanCounts, isPending: spanCountLoading} = useEAPSpans(
{
limit: 10,
enabled: traceIds.length > 0,
search: new MutableSearch('').addDisjunctionFilterValues('trace', traceIds),
fields: ['trace', 'count()'],
},
'uptime_checks'
);

const traceSpanCounts = spanCountLoading
? undefined
: Object.fromEntries(
traceIds.map(traceId => [
traceId,
Number(spanCounts.find(row => row.trace === traceId)?.['count()'] ?? 0),
])
);

return (
<GridEditable
emptyMessage={t('No matching uptime checks found')}
Expand All @@ -44,7 +71,12 @@ export function UptimeChecksGrid({uptimeChecks}: Props) {
grid={{
renderHeadCell: (col: GridColumnOrder) => <Cell>{col.name}</Cell>,
renderBodyCell: (column, dataRow) => (
<CheckInBodyCell column={column} check={dataRow} />
<CheckInBodyCell
column={column}
uptimeRule={uptimeRule}
check={dataRow}
spanCount={traceSpanCounts?.[dataRow.traceId]}
/>
),
}}
/>
Expand All @@ -54,9 +86,13 @@ export function UptimeChecksGrid({uptimeChecks}: Props) {
function CheckInBodyCell({
check,
column,
spanCount,
uptimeRule,
}: {
check: UptimeCheck;
column: GridColumnOrder<keyof UptimeCheck>;
spanCount: number | undefined;
uptimeRule: UptimeRule;
}) {
const theme = useTheme();

Expand Down Expand Up @@ -112,15 +148,52 @@ function CheckInBodyCell({
</Cell>
);
}
case 'traceId':
case 'traceId': {
if (traceId === EMPTY_TRACE) {
return <Cell />;
}

const learnMore = (
<ExternalLink href="https://docs.sentry.io/product/alerts/uptime-monitoring/uptime-tracing/" />
);

const badge =
spanCount === undefined ? (
<Placeholder height="20px" width="70px" />
) : spanCount === 0 ? (
<Tag
type="default"
borderStyle="dashed"

Check failure on line 166 in static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx

View workflow job for this annotation

GitHub Actions / self-hosted

Type '{ children: string; type: "default"; borderStyle: string; tooltipProps: { isHoverable: true; }; tooltipText: Element; }' is not assignable to type 'IntrinsicAttributes & { css?: Interpolation<Theme>; } & TagProps & { theme?: Theme | undefined; }'.
tooltipProps={{isHoverable: true}}
tooltipText={
uptimeRule.traceSampling
? tct(
'No spans found in this trace. Configure your SDKs to see correlated spans across services. [learnMore:Learn more].',
{learnMore}
)
: tct(
'Span sampling is disabled. Enable sampling to collect trace data. [learnMore:Learn more].',
{learnMore}
)
}
>
{t('0 spans')}
</Tag>
) : (
<Tag type="info">{t('%s spans', spanCount)}</Tag>
);

return (
<LinkCell to={`/performance/trace/${traceId}/`}>
{getShortEventId(String(traceId))}
</LinkCell>
<TraceCell>
{spanCount ? (
<Link to={`/performance/trace/${traceId}/`}>{getShortEventId(traceId)}</Link>
) : (
getShortEventId(traceId)
)}
{badge}
</TraceCell>
);
}
default:
return <Cell>{check[column.key]}</Cell>;
}
Expand All @@ -139,9 +212,8 @@ const TimeCell = styled(Cell)`
text-decoration-style: dotted;
`;

const LinkCell = styled(Link)`
text-decoration: underline;
text-decoration-color: ${p => p.theme.subText};
cursor: pointer;
text-decoration-style: dotted;
const TraceCell = styled(Cell)`
display: grid;
grid-template-columns: 65px auto;
gap: ${space(1)};
`;
2 changes: 1 addition & 1 deletion static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function UptimeChecksTable({uptimeRule}: UptimeChecksTableProps) {
{isPending ? (
<LoadingIndicator />
) : (
<UptimeChecksGrid uptimeChecks={uptimeChecks} />
<UptimeChecksGrid uptimeRule={uptimeRule} uptimeChecks={uptimeChecks} />
)}
<Pagination pageLinks={getResponseHeader?.('Link')} />
</Fragment>
Expand Down
8 changes: 6 additions & 2 deletions static/app/views/insights/uptime/utils/useUptimeRule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type QueryClient,
setApiQueryData,
useApiQuery,
type UseApiQueryOptions,
} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import type {UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
Expand All @@ -12,13 +13,16 @@ interface UseUptimeRuleOptions {
uptimeRuleId: string;
}

export function useUptimeRule({projectSlug, uptimeRuleId}: UseUptimeRuleOptions) {
export function useUptimeRule(
{projectSlug, uptimeRuleId}: UseUptimeRuleOptions,
options: Partial<UseApiQueryOptions<UptimeRule>> = {}
) {
const organization = useOrganization();

const queryKey: ApiQueryKey = [
`/projects/${organization.slug}/${projectSlug}/uptime/${uptimeRuleId}/`,
];
return useApiQuery<UptimeRule>(queryKey, {staleTime: 0});
return useApiQuery<UptimeRule>(queryKey, {staleTime: 0, ...options});
}

interface SetUptimeRuleDataOptions {
Expand Down
9 changes: 6 additions & 3 deletions static/app/views/issueDetails/groupUptimeChecks.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';
import {RouterFixture} from 'sentry-fixture/routerFixture';
import {UptimeCheckFixture} from 'sentry-fixture/uptimeCheck';
import {UptimeRuleFixture} from 'sentry-fixture/uptimeRule';

import {render, screen} from 'sentry-test/reactTestingLibrary';

Expand Down Expand Up @@ -48,6 +49,10 @@ describe('GroupUptimeChecks', () => {
url: `/organizations/${organization.slug}/issues/${group.id}/events/recommended/`,
body: event,
});
MockApiClient.addMockResponse({
url: `/projects/org-slug/project-slug/uptime/123/`,
body: UptimeRuleFixture(),
});
});

it('renders the empty uptime check table', async () => {
Expand Down Expand Up @@ -81,9 +86,7 @@ describe('GroupUptimeChecks', () => {
expect(screen.getByRole('time')).toHaveTextContent(/Jan 1, 2025/);
expect(screen.getByText(statusToText[uptimeCheck.checkStatus])).toBeInTheDocument();
expect(screen.getByText(`${uptimeCheck.durationMs}ms`)).toBeInTheDocument();
expect(
screen.getByRole('link', {name: getShortEventId(uptimeCheck.traceId)})
).toHaveAttribute('href', `/performance/trace/${uptimeCheck.traceId}/`);
expect(screen.getByText(getShortEventId(uptimeCheck.traceId))).toBeInTheDocument();
expect(screen.getByText(uptimeCheck.regionName)).toBeInTheDocument();
});
});
14 changes: 12 additions & 2 deletions static/app/views/issueDetails/groupUptimeChecks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {EventListTable} from 'sentry/views/issueDetails/streamline/eventListTabl
import {useUptimeIssueAlertId} from 'sentry/views/issueDetails/streamline/issueUptimeCheckTimeline';
import {useGroup} from 'sentry/views/issueDetails/useGroup';

import {useUptimeRule} from '../insights/uptime/utils/useUptimeRule';

export default function GroupUptimeChecks() {
const organization = useOrganization();
const {groupId} = useParams<{groupId: string}>();
Expand All @@ -30,6 +32,14 @@ export default function GroupUptimeChecks() {
const canFetchUptimeChecks =
Boolean(organization.slug) && Boolean(group?.project.slug) && Boolean(uptimeAlertId);

const {data: uptimeRule} = useUptimeRule(
{
projectSlug: group?.project.slug ?? '',
uptimeRuleId: uptimeAlertId ?? '',
},
{enabled: canFetchUptimeChecks}
);

const {data: uptimeChecks, getResponseHeader} = useUptimeChecks(
{
orgSlug: organization.slug,
Expand All @@ -47,7 +57,7 @@ export default function GroupUptimeChecks() {
return <LoadingError onRetry={refetchGroup} />;
}

if (isGroupPending || uptimeChecks === undefined) {
if (isGroupPending || uptimeChecks === undefined || uptimeRule === undefined) {
return <LoadingIndicator />;
}

Expand All @@ -67,7 +77,7 @@ export default function GroupUptimeChecks() {
previousDisabled,
}}
>
<UptimeChecksGrid uptimeChecks={uptimeChecks} />
<UptimeChecksGrid uptimeRule={uptimeRule} uptimeChecks={uptimeChecks} />
</EventListTable>
);
}

0 comments on commit f52b937

Please sign in to comment.