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)}; `;