import {Fragment, useCallback, useMemo, useState} from 'react';
import styled from '@emotion/styled';
import debounce from 'lodash/debounce';
import {Button} from 'sentry/components/button';
import Count from 'sentry/components/count';
import EmptyStateWarning, {EmptyStreamWrapper} from 'sentry/components/emptyStateWarning';
import ExternalLink from 'sentry/components/links/externalLink';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import PerformanceDuration from 'sentry/components/performanceDuration';
import {SPAN_PROPS_DOCS_URL} from 'sentry/constants';
import {IconChevron} from 'sentry/icons/iconChevron';
import {IconWarning} from 'sentry/icons/iconWarning';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import {decodeList} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
import useProjects from 'sentry/utils/useProjects';
import type {TraceResult} from './hooks/useTraces';
import {
Description,
ProjectBadgeWrapper,
ProjectsRenderer,
SpanTimeRenderer,
TraceBreakdownRenderer,
TraceIdRenderer,
TraceIssuesRenderer,
} from './fieldRenderers';
import {SpanTable} from './spansTable';
import {
BreakdownPanelItem,
EmptyStateText,
EmptyValueContainer,
StyledPanel,
StyledPanelHeader,
StyledPanelItem,
TracePanelContent,
WrappingText,
} from './styles';
import {areQueriesEmpty} from './utils';
interface TracesTableProps {
isEmpty: boolean;
isError: boolean;
isLoading: boolean;
queries: string[];
data?: TraceResult[];
}
export function TracesTable({
isEmpty,
isError,
isLoading,
queries,
data,
}: TracesTableProps) {
return (
{t('Trace ID')}
{t('Trace Root')}
{areQueriesEmpty(queries) ? t('Total Spans') : t('Matching Spans')}
{t('Timeline')}
{t('Duration')}
{t('Timestamp')}
{t('Issues')}
{isLoading && (
)}
{isError && ( // TODO: need an error state
)}
{isEmpty && (
{t('No trace results found')}
{tct('Try adjusting your filters or refer to [docSearchProps].', {
docSearchProps: (
{t('docs for search properties')}
),
})}
)}
{data?.map((trace, i) => (
))}
);
}
function TraceRow({defaultExpanded, trace}: {defaultExpanded; trace: TraceResult}) {
const {selection} = usePageFilters();
const {projects} = useProjects();
const [expanded, setExpanded] = useState(defaultExpanded);
const [highlightedSliceName, _setHighlightedSliceName] = useState('');
const location = useLocation();
const organization = useOrganization();
const queries = useMemo(() => {
return decodeList(location.query.query);
}, [location.query.query]);
const setHighlightedSliceName = useMemo(
() =>
debounce(sliceName => _setHighlightedSliceName(sliceName), 100, {
leading: true,
}),
[_setHighlightedSliceName]
);
const onClickExpand = useCallback(() => setExpanded(e => !e), [setExpanded]);
const selectedProjects = useMemo(() => {
const selectedProjectIds = new Set(
selection.projects.map(project => project.toString())
);
return new Set(
projects
.filter(project => selectedProjectIds.has(project.id))
.map(project => project.slug)
);
}, [projects, selection.projects]);
const traceProjects = useMemo(() => {
const seenProjects: Set = new Set();
const leadingProjects: string[] = [];
const trailingProjects: string[] = [];
for (let i = 0; i < trace.breakdowns.length; i++) {
const project = trace.breakdowns[i].project;
if (!defined(project) || seenProjects.has(project)) {
continue;
}
seenProjects.add(project);
// Priotize projects that are selected in the page filters
if (selectedProjects.has(project)) {
leadingProjects.push(project);
} else {
trailingProjects.push(project);
}
}
return [...leadingProjects, ...trailingProjects];
}, [selectedProjects, trace]);
return (
}
aria-label={t('Toggle trace details')}
aria-expanded={expanded}
size="zero"
borderless
onClick={() =>
trackAnalytics('trace_explorer.toggle_trace_details', {
organization,
expanded,
source: 'trace explorer',
})
}
/>
{
event.stopPropagation();
trackAnalytics('trace_explorer.open_trace', {
organization,
source: 'trace explorer',
});
}}
location={location}
/>
0
? traceProjects
: trace.project
? [trace.project]
: []
}
/>
{trace.name ? (
{trace.name}
) : (
{t('Missing Trace Root')}
)}
{areQueriesEmpty(queries) ? (
) : (
tct('[numerator][space]of[space][denominator]', {
numerator: ,
denominator: ,
space: ,
})
)}
setHighlightedSliceName('')}
>
trackAnalytics('trace_explorer.open_in_issues', {
organization,
})
}
/>
{expanded && (
)}
);
}
const StyledButton = styled(Button)`
margin-right: ${space(0.5)};
`;