import {Fragment} from 'react'; import {useTheme} from '@emotion/react'; import moment from 'moment-timezone'; import Count from 'sentry/components/count'; import {EmptyStreamWrapper} from 'sentry/components/emptyStateWarning'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PerformanceDuration from 'sentry/components/performanceDuration'; import {IconWarning} from 'sentry/icons/iconWarning'; import {t, tct} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import {trackAnalytics} from 'sentry/utils/analytics'; import {getUtcDateString} from 'sentry/utils/dates'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {usePageParams} from './hooks/usePageParams'; import type {TraceResult} from './hooks/useTraces'; import type {SpanResult} from './hooks/useTraceSpans'; import {useTraceSpans} from './hooks/useTraceSpans'; import {type Field, FIELDS, SORTS} from './data'; import { SpanBreakdownSliceRenderer, SpanDescriptionRenderer, SpanIdRenderer, SpanTimeRenderer, TraceBreakdownContainer, } from './fieldRenderers'; import { MoreMatchingSpans, SpanPanelContent, SpanTablePanelItem, StyledPanel, StyledPanelHeader, StyledPanelItem, StyledSpanPanelItem, } from './styles'; import {areQueriesEmpty, getSecondaryNameFromSpan, getStylingSliceName} from './utils'; const ONE_MINUTE = 60 * 1000; // in milliseconds export function SpanTable({ trace, setHighlightedSliceName, }: { setHighlightedSliceName: (sliceName: string) => void; trace: TraceResult; }) { const location = useLocation(); const organization = useOrganization(); const {queries, metricsMax, metricsMin, metricsOp, metricsQuery, mri} = usePageParams(location); const hasMetric = metricsOp && mri; const spansQuery = useTraceSpans({ trace, fields: [ ...FIELDS, ...SORTS.map(field => field.startsWith('-') ? (field.substring(1) as Field) : (field as Field) ), ], datetime: { // give a 1 minute buffer on each side so that start != end start: getUtcDateString(moment(trace.start - ONE_MINUTE)), end: getUtcDateString(moment(trace.end + ONE_MINUTE)), period: null, utc: true, }, limit: 10, query: queries, sort: SORTS, mri: hasMetric ? mri : undefined, metricsMax: hasMetric ? metricsMax : undefined, metricsMin: hasMetric ? metricsMin : undefined, metricsOp: hasMetric ? metricsOp : undefined, metricsQuery: hasMetric ? metricsQuery : undefined, }); const isLoading = spansQuery.isPending; const isError = !isLoading && spansQuery.isError; const hasData = !isLoading && !isError && (spansQuery?.data?.data?.length ?? 0) > 0; const spans = spansQuery.data?.data ?? []; return ( {t('Span ID')} {t('Span Description')} {t('Span Duration')} {t('Timestamp')} {isLoading && ( )} {isError && ( // TODO: need an error state )} {spans.map(span => ( ))} {hasData && spans.length < trace.matchingSpans && ( {tct('[more][space]more [matching]spans can be found in the trace.', { more: , space:  , matching: areQueriesEmpty(queries) ? '' : 'matching ', })} )} ); } function SpanRow({ organization, span, trace, setHighlightedSliceName, }: { organization: Organization; setHighlightedSliceName: (sliceName: string) => void; span: SpanResult; trace: TraceResult; }) { const theme = useTheme(); return ( trackAnalytics('trace_explorer.open_trace_span', { organization, source: 'trace explorer', }) } /> setHighlightedSliceName('')}> setHighlightedSliceName( getStylingSliceName(span.project, getSecondaryNameFromSpan(span)) ?? '' ) } /> ); }