diff --git a/static/app/views/alerts/rules/uptime/details.tsx b/static/app/views/alerts/rules/uptime/details.tsx index c8cc0fb148ef18..fa9b7022c8c378 100644 --- a/static/app/views/alerts/rules/uptime/details.tsx +++ b/static/app/views/alerts/rules/uptime/details.tsx @@ -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, @@ -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'; diff --git a/static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx b/static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx index 3305a3c53fc8ad..93784fb3332fb6 100644 --- a/static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx +++ b/static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx @@ -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, @@ -19,6 +24,7 @@ import { type Props = { uptimeChecks: UptimeCheck[]; + uptimeRule: UptimeRule; }; /** @@ -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 ( {col.name}, renderBodyCell: (column, dataRow) => ( - + ), }} /> @@ -54,9 +86,13 @@ export function UptimeChecksGrid({uptimeChecks}: Props) { function CheckInBodyCell({ check, column, + spanCount, + uptimeRule, }: { check: UptimeCheck; column: GridColumnOrder; + spanCount: number | undefined; + uptimeRule: UptimeRule; }) { const theme = useTheme(); @@ -112,15 +148,52 @@ function CheckInBodyCell({ ); } - case 'traceId': + case 'traceId': { if (traceId === EMPTY_TRACE) { return ; } + + const learnMore = ( + + ); + + const badge = + spanCount === undefined ? ( + + ) : spanCount === 0 ? ( + + {t('0 spans')} + + ) : ( + {t('%s spans', spanCount)} + ); + return ( - - {getShortEventId(String(traceId))} - + + {spanCount ? ( + {getShortEventId(traceId)} + ) : ( + getShortEventId(traceId) + )} + {badge} + ); + } default: return {check[column.key]}; } @@ -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)}; `; diff --git a/static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx b/static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx index c984a4e565a977..163542beee24f7 100644 --- a/static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx +++ b/static/app/views/alerts/rules/uptime/uptimeChecksTable.tsx @@ -52,7 +52,7 @@ export function UptimeChecksTable({uptimeRule}: UptimeChecksTableProps) { {isPending ? ( ) : ( - + )} diff --git a/static/app/views/insights/uptime/utils/useUptimeRule.tsx b/static/app/views/insights/uptime/utils/useUptimeRule.tsx index 2b18dddab540f5..5d0428ad24a0b0 100644 --- a/static/app/views/insights/uptime/utils/useUptimeRule.tsx +++ b/static/app/views/insights/uptime/utils/useUptimeRule.tsx @@ -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'; @@ -12,13 +13,16 @@ interface UseUptimeRuleOptions { uptimeRuleId: string; } -export function useUptimeRule({projectSlug, uptimeRuleId}: UseUptimeRuleOptions) { +export function useUptimeRule( + {projectSlug, uptimeRuleId}: UseUptimeRuleOptions, + options: Partial> = {} +) { const organization = useOrganization(); const queryKey: ApiQueryKey = [ `/projects/${organization.slug}/${projectSlug}/uptime/${uptimeRuleId}/`, ]; - return useApiQuery(queryKey, {staleTime: 0}); + return useApiQuery(queryKey, {staleTime: 0, ...options}); } interface SetUptimeRuleDataOptions { diff --git a/static/app/views/issueDetails/groupUptimeChecks.spec.tsx b/static/app/views/issueDetails/groupUptimeChecks.spec.tsx index 16cb21af2a07db..6f71a4d547d467 100644 --- a/static/app/views/issueDetails/groupUptimeChecks.spec.tsx +++ b/static/app/views/issueDetails/groupUptimeChecks.spec.tsx @@ -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'; @@ -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 () => { @@ -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(); }); }); diff --git a/static/app/views/issueDetails/groupUptimeChecks.tsx b/static/app/views/issueDetails/groupUptimeChecks.tsx index fb8eaacab45c9e..015a576c6171fd 100644 --- a/static/app/views/issueDetails/groupUptimeChecks.tsx +++ b/static/app/views/issueDetails/groupUptimeChecks.tsx @@ -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}>(); @@ -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, @@ -47,7 +57,7 @@ export default function GroupUptimeChecks() { return ; } - if (isGroupPending || uptimeChecks === undefined) { + if (isGroupPending || uptimeChecks === undefined || uptimeRule === undefined) { return ; } @@ -67,7 +77,7 @@ export default function GroupUptimeChecks() { previousDisabled, }} > - + ); }