import {Fragment, useCallback, useMemo, useState} from 'react'; import {useTheme} from '@emotion/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 from 'sentry/components/emptyStateWarning'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; import Panel from 'sentry/components/panels/panel'; import PanelHeader from 'sentry/components/panels/panelHeader'; import PanelItem from 'sentry/components/panels/panelItem'; import PerformanceDuration from 'sentry/components/performanceDuration'; import {IconChevron} from 'sentry/icons/iconChevron'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {PageFilters} from 'sentry/types/core'; import {browserHistory} from 'sentry/utils/browserHistory'; import {useApiQuery} from 'sentry/utils/queryClient'; import {decodeInteger, decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {type Field, FIELDS, SORTS} from './data'; import { ProjectRenderer, SpanBreakdownSliceRenderer, SpanIdRenderer, TraceBreakdownContainer, TraceBreakdownRenderer, TraceIdRenderer, TraceIssuesRenderer, } from './fieldRenderers'; import {TracesSearchBar} from './tracesSearchBar'; import {normalizeTraces} from './utils'; const DEFAULT_PER_PAGE = 20; export function Content() { const location = useLocation(); const queries = useMemo(() => { return decodeList(location.query.query); }, [location.query.query]); const limit = useMemo(() => { return decodeInteger(location.query.perPage, DEFAULT_PER_PAGE); }, [location.query.perPage]); const handleSearch = useCallback( (searchIndex: number, searchQuery: string) => { const newQueries = [...queries]; if (newQueries.length === 0) { // In the odd case someone wants to add search bars before any query has been made, we add both the default one shown and a new one. newQueries[0] = ''; } newQueries[searchIndex] = searchQuery; browserHistory.push({ ...location, query: { ...location.query, cursor: undefined, query: typeof searchQuery === 'string' ? newQueries : queries, }, }); }, [location, queries] ); const handleClearSearch = useCallback( (searchIndex: number) => { const newQueries = [...queries]; if (typeof newQueries[searchIndex] !== undefined) { delete newQueries[searchIndex]; browserHistory.push({ ...location, query: { ...location.query, cursor: undefined, query: newQueries, }, }); return true; } return false; }, [location, queries] ); const traces = useTraces({ fields: [ ...FIELDS, ...SORTS.map(field => field.startsWith('-') ? (field.substring(1) as Field) : (field as Field) ), ], limit, query: queries, sort: SORTS, }); const isLoading = traces.isFetching; const isError = !isLoading && traces.isError; const isEmpty = !isLoading && !isError && (traces?.data?.data?.length ?? 0) === 0; const data = normalizeTraces(!isLoading && !isError ? traces?.data?.data : undefined); return ( {t('Trace ID')} {t('Trace Root')} {t('Total Spans')} {t('Breakdown')} {t('Trace Duration')} {t('Issues')} {isLoading && ( )} {isError && ( // TODO: need an error state )} {isEmpty && ( )} {data?.map(trace => )} ); } function TraceRow({trace}: {trace: TraceResult}) { const [expanded, setExpanded] = useState(false); const [highlightedSliceName, _setHighlightedSliceName] = useState(''); const setHighlightedSliceName = useMemo( () => debounce(sliceName => _setHighlightedSliceName(sliceName), 100, { leading: true, }), [_setHighlightedSliceName] ); return (