import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import {Tag} from 'sentry/components/core/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 {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, tickStyle, } from 'sentry/views/insights/uptime/timelineConfig'; type Props = { uptimeChecks: UptimeCheck[]; uptimeRule: UptimeRule; }; /** * This value is used when a trace was not recorded since the field is required. * It will never link to trace, so omit the row to avoid confusion. */ const EMPTY_TRACE = '00000000000000000000000000000000'; 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()'], }, 'api.uptime-checks-grid' ); 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) => ( ), }} /> ); } function CheckInBodyCell({ check, column, spanCount, uptimeRule, }: { check: UptimeCheck; column: GridColumnOrder; spanCount: number | undefined; uptimeRule: UptimeRule; }) { const theme = useTheme(); const { timestamp, scheduledCheckTime, durationMs, checkStatus, httpStatusCode, checkStatusReason, traceId, } = check; if (check[column.key] === undefined) { return ; } switch (column.key) { case 'timestamp': { return ( )} > ); } case 'durationMs': return ( ); case 'httpStatusCode': { if (httpStatusCode === null) { return {t('None')}; } return {httpStatusCode}; } case 'checkStatus': { const colorKey = tickStyle[checkStatus].labelColor ?? 'textColor'; return ( {statusToText[checkStatus]}{' '} {checkStatusReason && tct('([reason])', { reason: reasonToText[checkStatusReason](check), })} ); } case 'traceId': { if (traceId === EMPTY_TRACE) { return ; } const learnMore = ( ); const badge = spanCount === undefined ? ( ) : spanCount === 0 ? ( {t('0 spans')} ) : ( {t('%s spans', spanCount)} ); return ( {spanCount ? ( {getShortEventId(traceId)} ) : ( getShortEventId(traceId) )} {badge} ); } default: return {check[column.key]}; } } const Cell = styled('div')` display: flex; align-items: center; text-align: left; gap: ${space(1)}; `; const TimeCell = styled(Cell)` color: ${p => p.theme.subText}; text-decoration: underline; text-decoration-style: dotted; `; const TraceCell = styled(Cell)` display: grid; grid-template-columns: 65px max-content; gap: ${space(1)}; `;