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)) ?? ''
)
}
/>
);
}