Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(uptime): Add span count next to trace IDs #86084

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
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"
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)};
`;
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>
);
}
Loading