import {
Fragment,
useCallback,
useLayoutEffect,
useMemo,
useReducer,
useState,
} from 'react';
import type {Location} from 'history';
import ButtonBar from 'sentry/components/buttonBar';
import DiscoverButton from 'sentry/components/discoverButton';
import * as Layout from 'sentry/components/layouts/thirds';
import NoProjectMessage from 'sentry/components/noProjectMessage';
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
import {t} from 'sentry/locale';
import type {EventTransaction, Organization} from 'sentry/types';
import {trackAnalytics} from 'sentry/utils/analytics';
import EventView from 'sentry/utils/discover/eventView';
import TraceMetaQuery, {
type TraceMetaQueryChildrenProps,
} from 'sentry/utils/performance/quickTrace/traceMetaQuery';
import type {
TraceFullDetailed,
TraceSplitResults,
} from 'sentry/utils/performance/quickTrace/types';
import {useApiQuery} from 'sentry/utils/queryClient';
import {decodeScalar} from 'sentry/utils/queryString';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import useProjects from 'sentry/utils/useProjects';
import {rovingTabIndexReducer} from 'sentry/views/performance/newTraceDetails/rovingTabIndex';
import Breadcrumb from '../breadcrumb';
import TraceDetailPanel from './newTraceDetailPanel';
import Trace from './trace';
import {TraceFooter} from './traceFooter';
import TraceHeader from './traceHeader';
import {TraceTree, type TraceTreeNode} from './traceTree';
import TraceWarnings from './traceWarnings';
import {useTrace} from './useTrace';
const DOCUMENT_TITLE = [t('Trace Details'), t('Performance')].join(' — ');
function maybeFocusRow() {
const focused_node = document.querySelector(".TraceRow[tabIndex='0']");
if (
focused_node &&
'focus' in focused_node &&
typeof focused_node.focus === 'function'
) {
focused_node.focus();
}
}
export function TraceView() {
const location = useLocation();
const organization = useOrganization();
const params = useParams<{traceSlug?: string}>();
const traceSlug = params.traceSlug?.trim() ?? '';
const queryParams = useMemo(() => {
const normalizedParams = normalizeDateTimeParams(location.query, {
allowAbsolutePageDatetime: true,
});
const start = decodeScalar(normalizedParams.start);
const end = decodeScalar(normalizedParams.end);
const statsPeriod = decodeScalar(normalizedParams.statsPeriod);
return {start, end, statsPeriod, useSpans: 1};
}, [location.query]);
const traceEventView = useMemo(() => {
const {start, end, statsPeriod} = queryParams;
return EventView.fromSavedQuery({
id: undefined,
name: `Events with Trace ID ${traceSlug}`,
fields: ['title', 'event.type', 'project', 'timestamp'],
orderby: '-timestamp',
query: `trace:${traceSlug}`,
projects: [ALL_ACCESS_PROJECTS],
version: 2,
start,
end,
range: statsPeriod,
});
}, [queryParams, traceSlug]);
const trace = useTrace();
return (
{metaResults => (
)}
);
}
type TraceViewContentProps = {
location: Location;
metaResults: TraceMetaQueryChildrenProps;
organization: Organization;
status: 'pending' | 'resolved' | 'error' | 'initial';
trace: TraceSplitResults | null;
traceEventView: EventView;
traceSlug: string;
};
function TraceViewContent(props: TraceViewContentProps) {
const {projects} = useProjects();
const root = props.trace?.transactions?.[0];
const rootEvent = useApiQuery(
[
`/organizations/${props.organization.slug}/events/${root?.project_slug}:${root?.event_id}/`,
{
query: {
referrer: 'trace-details-summary',
},
},
],
{
staleTime: 0,
enabled: (props.trace?.transactions.length ?? 0) > 0,
}
);
const tree = useMemo(() => {
if (props.status === 'pending' || rootEvent.status !== 'success') {
return TraceTree.Loading({
project_slug: projects?.[0]?.slug ?? '',
event_id: props.traceSlug,
});
}
if (props.trace) {
return TraceTree.FromTrace(props.trace, rootEvent.data);
}
return TraceTree.Empty();
}, [props.traceSlug, props.trace, props.status, projects, rootEvent]);
const traceType = useMemo(() => {
if (props.status !== 'resolved' || !tree) {
return null;
}
return TraceTree.GetTraceType(tree.root);
}, [props.status, tree]);
const [state, dispatch] = useReducer(rovingTabIndexReducer, {
index: null,
items: null,
node: null,
});
useLayoutEffect(() => {
return dispatch({
type: 'initialize',
items: tree.list.length - 1,
index: null,
node: null,
});
}, [tree.list.length]);
const [detailNode, setDetailNode] = useState | null>(
null
);
const onDetailClose = useCallback(() => {
setDetailNode(null);
maybeFocusRow();
}, []);
const onSetDetailNode = useCallback(
(node: TraceTreeNode | null) => {
setDetailNode(prevNode => {
return prevNode === node ? null : node;
});
maybeFocusRow();
},
[]
);
return (
{t('Trace ID: %s', props.traceSlug)}
{
trackAnalytics('performance_views.trace_view.open_in_discover', {
organization: props.organization,
});
}}
>
{t('Open in Discover')}
{traceType ? : null}
);
}