import {Fragment, useEffect} from 'react'; import Alert from 'sentry/components/alert'; import {Flex} from 'sentry/components/container/flex'; import DetailedError from 'sentry/components/errors/detailedError'; import NotFound from 'sentry/components/errors/notFound'; import * as Layout from 'sentry/components/layouts/thirds'; import List from 'sentry/components/list'; import ListItem from 'sentry/components/list/listItem'; import {LocalStorageReplayPreferences} from 'sentry/components/replays/preferences/replayPreferences'; import {Provider as ReplayContextProvider} from 'sentry/components/replays/replayContext'; import {IconDelete} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import {decodeScalar} from 'sentry/utils/queryString'; import type {TimeOffsetLocationQueryParams} from 'sentry/utils/replays/hooks/useInitialTimeOffsetMs'; import useInitialTimeOffsetMs from 'sentry/utils/replays/hooks/useInitialTimeOffsetMs'; import useLogReplayDataLoaded from 'sentry/utils/replays/hooks/useLogReplayDataLoaded'; import useMarkReplayViewed from 'sentry/utils/replays/hooks/useMarkReplayViewed'; import useReplayPageview from 'sentry/utils/replays/hooks/useReplayPageview'; import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader'; import {ReplayPreferencesContextProvider} from 'sentry/utils/replays/playback/providers/replayPreferencesContext'; import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames'; import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {useUser} from 'sentry/utils/useUser'; import ReplaysLayout from 'sentry/views/replays/detail/layout'; import Page from 'sentry/views/replays/detail/page'; import ReplayTransactionContext from 'sentry/views/replays/detail/trace/replayTransactionContext'; type Props = RouteComponentProps< {replaySlug: string}, {}, any, TimeOffsetLocationQueryParams >; function ReplayDetails({params: {replaySlug}}: Props) { const user = useUser(); const location = useLocation(); const organization = useOrganization(); const {slug: orgSlug} = organization; // TODO: replayId is known ahead of time and useReplayData is parsing it from the replaySlug // once we fix the route params and links we should fix this to accept replayId and stop returning it const { errors, fetchError, fetching, onRetry, projectSlug, replay, replayId, replayRecord, } = useReplayReader({ replaySlug, orgSlug, }); const replayErrors = errors.filter(e => e.title !== 'User Feedback'); const isVideoReplay = replay?.isVideoReplay(); useReplayPageview('replay.details-time-spent'); useRouteAnalyticsEventNames('replay_details.viewed', 'Replay Details: Viewed'); useRouteAnalyticsParams({ organization, referrer: decodeScalar(location.query.referrer), user_email: user.email, tab: location.query.t_main, mobile: isVideoReplay, }); useLogReplayDataLoaded({fetchError, fetching, projectSlug, replay}); const {mutate: markAsViewed} = useMarkReplayViewed(); useEffect(() => { if ( !fetchError && replayRecord && !replayRecord.has_viewed && projectSlug && !fetching && replayId ) { markAsViewed({projectSlug, replayId}); } }, [ fetchError, fetching, markAsViewed, organization, projectSlug, replayId, replayRecord, ]); const initialTimeOffsetMs = useInitialTimeOffsetMs({ orgSlug, projectSlug, replayId, replayStartTimestampMs: replayRecord?.started_at?.getTime(), }); const rrwebFrames = replay?.getRRWebFrames(); // The replay data takes a while to load in, which causes `isVideoReplay` // to return an early `false`, which used to cause UI jumping. // One way to check whether it's finished loading is by checking the length // of the rrweb frames, which should always be > 1 for any given replay. // By default, the 1 frame is replay.end const isLoading = !rrwebFrames || (rrwebFrames && rrwebFrames.length <= 1); if (replayRecord?.is_archived) { return ( {t('This replay has been deleted.')} ); } if (fetchError) { if (fetchError.status === 404) { return ( ); } const reasons = [ t('The replay is still processing'), t('The replay has been deleted by a member in your organization'), t('There is an internal systems error'), ]; return (

{t('This could be due to these reasons:')}

{reasons.map((reason, i) => ( {reason} ))} } />
); } return ( ); } export default ReplayDetails;