import {useMemo} from 'react';
import styled from '@emotion/styled';
import Loading from 'sentry/components/loadingIndicator';
import Placeholder from 'sentry/components/placeholder';
import {IconSad} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import type EventView from 'sentry/utils/discover/eventView';
import type {TraceError} from 'sentry/utils/performance/quickTrace/types';
import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {useReplayTraceMeta} from 'sentry/views/performance/newTraceDetails/traceApi/useReplayTraceMeta';
import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace';
import {useTraceRootEvent} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {useTraceTree} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceTree';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import type {TracePreferencesState} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences';
import {loadTraceViewPreferences} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences';
import {TraceStateProvider} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider';
import {TraceWaterfall} from 'sentry/views/performance/newTraceDetails/traceWaterfall';
import TraceView, {
StyledTracePanel,
} from 'sentry/views/performance/traceDetails/traceView';
import {hasTraceData} from 'sentry/views/performance/traceDetails/utils';
import EmptyState from 'sentry/views/replays/detail/emptyState';
import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
import {
useFetchTransactions,
useTransactionData,
} from 'sentry/views/replays/detail/trace/replayTransactionContext';
import type {ReplayRecord} from 'sentry/views/replays/types';
import {useReplayTraces} from './useReplayTraces';
function TracesNotFound({performanceActive}: {performanceActive: boolean}) {
// We want to send the 'trace_status' data if the project actively uses and has access to the performance monitoring.
useRouteAnalyticsParams(performanceActive ? {trace_status: 'trace missing'} : {});
return (
{t('No traces found')}
);
}
function TraceFound({
organization,
performanceActive,
eventView,
traces,
orphanErrors,
}: {
eventView: EventView | null;
organization: Organization;
performanceActive: boolean;
traces: TraceTree.Transaction[] | null;
orphanErrors?: TraceError[];
}) {
const location = useLocation();
// We want to send the 'trace_status' data if the project actively uses and has access to the performance monitoring.
useRouteAnalyticsParams(performanceActive ? {trace_status: 'success'} : {});
return (
);
}
const DEFAULT_REPLAY_TRACE_VIEW_PREFERENCES: TracePreferencesState = {
drawer: {
minimized: false,
sizes: {
'drawer left': 0.33,
'drawer right': 0.33,
'drawer bottom': 0.4,
},
layoutOptions: [],
},
missing_instrumentation: true,
autogroup: {
parent: true,
sibling: true,
},
layout: 'drawer bottom',
list: {
width: 0.5,
},
};
function Trace({replay}: {replay: undefined | ReplayRecord}) {
const organization = useOrganization();
const {projects} = useProjects();
const {
state: {didInit, errors, isFetching, traces, orphanErrors},
eventView,
} = useTransactionData();
useFetchTransactions();
if (errors.length) {
// Same style as
return (
{t('Unable to retrieve traces')}
);
}
if (!replay || !didInit || (isFetching && !traces?.length) || !eventView) {
// Show the blank screen until we start fetching, thats when you get a spinner
return (
{isFetching ? : null}
);
}
const project = projects.find(p => p.id === replay.project_id);
const hasPerformance = project?.firstTransactionEvent === true;
const performanceActive =
organization.features.includes('performance-view') && hasPerformance;
if (!hasTraceData(traces, orphanErrors)) {
return ;
}
return (
);
}
export function NewTraceView({replay}: {replay: undefined | ReplayRecord}) {
const organization = useOrganization();
const {projects} = useProjects();
const {eventView, indexComplete, indexError, replayTraces} = useReplayTraces({
replayRecord: replay,
});
const firstTrace = replayTraces?.[0];
const trace = useTrace({
traceSlug: firstTrace?.traceSlug,
timestamp: firstTrace?.timestamp,
});
const rootEvent = useTraceRootEvent(trace.data ?? null);
const meta = useReplayTraceMeta(replay);
const tree = useTraceTree({
trace,
meta,
replay: replay ?? null,
});
const preferences = useMemo(
() =>
loadTraceViewPreferences('replay-trace-view-preferences') ||
DEFAULT_REPLAY_TRACE_VIEW_PREFERENCES,
[]
);
const otherReplayTraces = useMemo(() => {
if (!replayTraces) {
return [];
}
return replayTraces.slice(1);
}, [replayTraces]);
if (indexError) {
// Same style as
return (
{t('Unable to retrieve traces')}
);
}
if (!replay || !indexComplete || !replayTraces || !eventView) {
// Show the blank screen until we start fetching, thats when you get a spinner
return (
{!indexComplete ? : null}
);
}
const project = projects.find(p => p.id === replay.project_id);
const hasPerformance = project?.firstTransactionEvent === true;
const performanceActive =
organization.features.includes('performance-view') && hasPerformance;
if (replayTraces.length === 0) {
return ;
}
return (
);
}
// This has the gray background, to match other loaders on Replay Details
const StyledPlaceholder = styled(Placeholder)`
border: 1px solid ${p => p.theme.border};
border-radius: ${p => p.theme.borderRadius};
`;
// White background, to match the loaded component
const BorderedSection = styled(FluidHeight)`
border: 1px solid ${p => p.theme.border};
border-radius: ${p => p.theme.borderRadius};
`;
const OverflowScrollBorderedSection = styled(BorderedSection)`
overflow: scroll;
${StyledTracePanel} {
border: none;
}
`;
const TraceViewWaterfallWrapper = styled('div')`
display: flex;
flex-direction: column;
height: 100%;
`;
export default Trace;