Skip to content

Commit bf1410e

Browse files
feat(uptime): Add span count next to trace IDs (take 2) (#86118)
Reapply of f52b937
1 parent a4f8819 commit bf1410e

File tree

6 files changed

+109
-25
lines changed

6 files changed

+109
-25
lines changed

static/app/views/alerts/rules/uptime/details.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ import useApi from 'sentry/utils/useApi';
2222
import useOrganization from 'sentry/utils/useOrganization';
2323
import useProjects from 'sentry/utils/useProjects';
2424
import {makeAlertsPathname} from 'sentry/views/alerts/pathnames';
25-
import {
26-
CheckStatus,
27-
type CheckStatusBucket,
28-
type UptimeRule,
29-
} from 'sentry/views/alerts/rules/uptime/types';
3025
import {
3126
setUptimeRuleData,
3227
useUptimeRule,
@@ -35,6 +30,7 @@ import {
3530
import {UptimeDetailsSidebar} from './detailsSidebar';
3631
import {DetailsTimeline} from './detailsTimeline';
3732
import {StatusToggleButton} from './statusToggleButton';
33+
import {CheckStatus, type CheckStatusBucket, type UptimeRule} from './types';
3834
import {UptimeChecksTable} from './uptimeChecksTable';
3935
import {UptimeIssues} from './uptimeIssues';
4036

static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import {useTheme} from '@emotion/react';
22
import styled from '@emotion/styled';
33

4+
import Tag from 'sentry/components/badge/tag';
45
import {DateTime} from 'sentry/components/dateTime';
56
import Duration from 'sentry/components/duration';
67
import type {GridColumnOrder} from 'sentry/components/gridEditable';
78
import GridEditable from 'sentry/components/gridEditable';
9+
import ExternalLink from 'sentry/components/links/externalLink';
810
import Link from 'sentry/components/links/link';
11+
import Placeholder from 'sentry/components/placeholder';
912
import {Tooltip} from 'sentry/components/tooltip';
1013
import {t, tct} from 'sentry/locale';
1114
import {space} from 'sentry/styles/space';
1215
import {getShortEventId} from 'sentry/utils/events';
13-
import type {UptimeCheck} from 'sentry/views/alerts/rules/uptime/types';
16+
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
17+
import type {UptimeCheck, UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
18+
import {useEAPSpans} from 'sentry/views/insights/common/queries/useDiscover';
1419
import {
1520
reasonToText,
1621
statusToText,
@@ -19,6 +24,7 @@ import {
1924

2025
type Props = {
2126
uptimeChecks: UptimeCheck[];
27+
uptimeRule: UptimeRule;
2228
};
2329

2430
/**
@@ -27,7 +33,28 @@ type Props = {
2733
*/
2834
const EMPTY_TRACE = '00000000000000000000000000000000';
2935

30-
export function UptimeChecksGrid({uptimeChecks}: Props) {
36+
export function UptimeChecksGrid({uptimeRule, uptimeChecks}: Props) {
37+
const traceIds = uptimeChecks?.map(check => check.traceId) ?? [];
38+
39+
const {data: spanCounts, isPending: spanCountLoading} = useEAPSpans(
40+
{
41+
limit: 10,
42+
enabled: traceIds.length > 0,
43+
search: new MutableSearch('').addDisjunctionFilterValues('trace', traceIds),
44+
fields: ['trace', 'count()'],
45+
},
46+
'uptime_checks'
47+
);
48+
49+
const traceSpanCounts = spanCountLoading
50+
? undefined
51+
: Object.fromEntries(
52+
traceIds.map(traceId => [
53+
traceId,
54+
Number(spanCounts.find(row => row.trace === traceId)?.['count()'] ?? 0),
55+
])
56+
);
57+
3158
return (
3259
<GridEditable
3360
emptyMessage={t('No matching uptime checks found')}
@@ -44,7 +71,12 @@ export function UptimeChecksGrid({uptimeChecks}: Props) {
4471
grid={{
4572
renderHeadCell: (col: GridColumnOrder) => <Cell>{col.name}</Cell>,
4673
renderBodyCell: (column, dataRow) => (
47-
<CheckInBodyCell column={column} check={dataRow} />
74+
<CheckInBodyCell
75+
column={column}
76+
uptimeRule={uptimeRule}
77+
check={dataRow}
78+
spanCount={traceSpanCounts?.[dataRow.traceId]}
79+
/>
4880
),
4981
}}
5082
/>
@@ -54,9 +86,13 @@ export function UptimeChecksGrid({uptimeChecks}: Props) {
5486
function CheckInBodyCell({
5587
check,
5688
column,
89+
spanCount,
90+
uptimeRule,
5791
}: {
5892
check: UptimeCheck;
5993
column: GridColumnOrder<keyof UptimeCheck>;
94+
spanCount: number | undefined;
95+
uptimeRule: UptimeRule;
6096
}) {
6197
const theme = useTheme();
6298

@@ -112,15 +148,51 @@ function CheckInBodyCell({
112148
</Cell>
113149
);
114150
}
115-
case 'traceId':
151+
case 'traceId': {
116152
if (traceId === EMPTY_TRACE) {
117153
return <Cell />;
118154
}
155+
156+
const learnMore = (
157+
<ExternalLink href="https://docs.sentry.io/product/alerts/uptime-monitoring/uptime-tracing/" />
158+
);
159+
160+
const badge =
161+
spanCount === undefined ? (
162+
<Placeholder height="20px" width="70px" />
163+
) : spanCount === 0 ? (
164+
<Tag
165+
type="default"
166+
tooltipProps={{isHoverable: true}}
167+
tooltipText={
168+
uptimeRule.traceSampling
169+
? tct(
170+
'No spans found in this trace. Configure your SDKs to see correlated spans across services. [learnMore:Learn more].',
171+
{learnMore}
172+
)
173+
: tct(
174+
'Span sampling is disabled. Enable sampling to collect trace data. [learnMore:Learn more].',
175+
{learnMore}
176+
)
177+
}
178+
>
179+
{t('0 spans')}
180+
</Tag>
181+
) : (
182+
<Tag type="info">{t('%s spans', spanCount)}</Tag>
183+
);
184+
119185
return (
120-
<LinkCell to={`/performance/trace/${traceId}/`}>
121-
{getShortEventId(String(traceId))}
122-
</LinkCell>
186+
<TraceCell>
187+
{spanCount ? (
188+
<Link to={`/performance/trace/${traceId}/`}>{getShortEventId(traceId)}</Link>
189+
) : (
190+
getShortEventId(traceId)
191+
)}
192+
{badge}
193+
</TraceCell>
123194
);
195+
}
124196
default:
125197
return <Cell>{check[column.key]}</Cell>;
126198
}
@@ -139,9 +211,8 @@ const TimeCell = styled(Cell)`
139211
text-decoration-style: dotted;
140212
`;
141213

142-
const LinkCell = styled(Link)`
143-
text-decoration: underline;
144-
text-decoration-color: ${p => p.theme.subText};
145-
cursor: pointer;
146-
text-decoration-style: dotted;
214+
const TraceCell = styled(Cell)`
215+
display: grid;
216+
grid-template-columns: 65px auto;
217+
gap: ${space(1)};
147218
`;

static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function UptimeChecksTable({uptimeRule}: UptimeChecksTableProps) {
5252
{isPending ? (
5353
<LoadingIndicator />
5454
) : (
55-
<UptimeChecksGrid uptimeChecks={uptimeChecks} />
55+
<UptimeChecksGrid uptimeRule={uptimeRule} uptimeChecks={uptimeChecks} />
5656
)}
5757
<Pagination pageLinks={getResponseHeader?.('Link')} />
5858
</Fragment>

static/app/views/insights/uptime/utils/useUptimeRule.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type QueryClient,
44
setApiQueryData,
55
useApiQuery,
6+
type UseApiQueryOptions,
67
} from 'sentry/utils/queryClient';
78
import useOrganization from 'sentry/utils/useOrganization';
89
import type {UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
@@ -12,13 +13,16 @@ interface UseUptimeRuleOptions {
1213
uptimeRuleId: string;
1314
}
1415

15-
export function useUptimeRule({projectSlug, uptimeRuleId}: UseUptimeRuleOptions) {
16+
export function useUptimeRule(
17+
{projectSlug, uptimeRuleId}: UseUptimeRuleOptions,
18+
options: Partial<UseApiQueryOptions<UptimeRule>> = {}
19+
) {
1620
const organization = useOrganization();
1721

1822
const queryKey: ApiQueryKey = [
1923
`/projects/${organization.slug}/${projectSlug}/uptime/${uptimeRuleId}/`,
2024
];
21-
return useApiQuery<UptimeRule>(queryKey, {staleTime: 0});
25+
return useApiQuery<UptimeRule>(queryKey, {staleTime: 0, ...options});
2226
}
2327

2428
interface SetUptimeRuleDataOptions {

static/app/views/issueDetails/groupUptimeChecks.spec.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {OrganizationFixture} from 'sentry-fixture/organization';
44
import {ProjectFixture} from 'sentry-fixture/project';
55
import {RouterFixture} from 'sentry-fixture/routerFixture';
66
import {UptimeCheckFixture} from 'sentry-fixture/uptimeCheck';
7+
import {UptimeRuleFixture} from 'sentry-fixture/uptimeRule';
78

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

@@ -48,6 +49,10 @@ describe('GroupUptimeChecks', () => {
4849
url: `/organizations/${organization.slug}/issues/${group.id}/events/recommended/`,
4950
body: event,
5051
});
52+
MockApiClient.addMockResponse({
53+
url: `/projects/org-slug/project-slug/uptime/123/`,
54+
body: UptimeRuleFixture(),
55+
});
5156
});
5257

5358
it('renders the empty uptime check table', async () => {
@@ -81,9 +86,7 @@ describe('GroupUptimeChecks', () => {
8186
expect(screen.getByRole('time')).toHaveTextContent(/Jan 1, 2025/);
8287
expect(screen.getByText(statusToText[uptimeCheck.checkStatus])).toBeInTheDocument();
8388
expect(screen.getByText(`${uptimeCheck.durationMs}ms`)).toBeInTheDocument();
84-
expect(
85-
screen.getByRole('link', {name: getShortEventId(uptimeCheck.traceId)})
86-
).toHaveAttribute('href', `/performance/trace/${uptimeCheck.traceId}/`);
89+
expect(screen.getByText(getShortEventId(uptimeCheck.traceId))).toBeInTheDocument();
8790
expect(screen.getByText(uptimeCheck.regionName)).toBeInTheDocument();
8891
});
8992
});

static/app/views/issueDetails/groupUptimeChecks.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {EventListTable} from 'sentry/views/issueDetails/streamline/eventListTabl
1313
import {useUptimeIssueAlertId} from 'sentry/views/issueDetails/streamline/issueUptimeCheckTimeline';
1414
import {useGroup} from 'sentry/views/issueDetails/useGroup';
1515

16+
import {useUptimeRule} from '../insights/uptime/utils/useUptimeRule';
17+
1618
export default function GroupUptimeChecks() {
1719
const organization = useOrganization();
1820
const {groupId} = useParams<{groupId: string}>();
@@ -30,6 +32,14 @@ export default function GroupUptimeChecks() {
3032
const canFetchUptimeChecks =
3133
Boolean(organization.slug) && Boolean(group?.project.slug) && Boolean(uptimeAlertId);
3234

35+
const {data: uptimeRule} = useUptimeRule(
36+
{
37+
projectSlug: group?.project.slug ?? '',
38+
uptimeRuleId: uptimeAlertId ?? '',
39+
},
40+
{enabled: canFetchUptimeChecks}
41+
);
42+
3343
const {data: uptimeChecks, getResponseHeader} = useUptimeChecks(
3444
{
3545
orgSlug: organization.slug,
@@ -47,7 +57,7 @@ export default function GroupUptimeChecks() {
4757
return <LoadingError onRetry={refetchGroup} />;
4858
}
4959

50-
if (isGroupPending || uptimeChecks === undefined) {
60+
if (isGroupPending || uptimeChecks === undefined || uptimeRule === undefined) {
5161
return <LoadingIndicator />;
5262
}
5363

@@ -67,7 +77,7 @@ export default function GroupUptimeChecks() {
6777
previousDisabled,
6878
}}
6979
>
70-
<UptimeChecksGrid uptimeChecks={uptimeChecks} />
80+
<UptimeChecksGrid uptimeRule={uptimeRule} uptimeChecks={uptimeChecks} />
7181
</EventListTable>
7282
);
7383
}

0 commit comments

Comments
 (0)