diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx index a35276c7e8c..9f4bef400ee 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx @@ -164,7 +164,6 @@ export function ProtocolRunHeader({ const { startedAt, stoppedAt, completedAt } = useRunTimestamps(runId) const [showRunFailedModal, setShowRunFailedModal] = React.useState(false) const [showDropTipBanner, setShowDropTipBanner] = React.useState(true) - const [enteredER, setEnteredER] = React.useState(false) const isResetRunLoadingRef = React.useRef(false) const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) const highestPriorityError = @@ -224,6 +223,8 @@ export function ProtocolRunHeader({ isMostRecentRunCurrent: mostRecentRunId === runId, }) + const enteredER = runRecord?.data.hasEverEnteredErrorRecovery + React.useEffect(() => { if (isFlex) { if (runStatus === RUN_STATUS_IDLE) { @@ -232,7 +233,8 @@ export function ProtocolRunHeader({ } else if ( runStatus != null && // @ts-expect-error runStatus expected to possibly not be terminal - RUN_STATUSES_TERMINAL.includes(runStatus) + RUN_STATUSES_TERMINAL.includes(runStatus) && + enteredER === false ) { void determineTipStatus() } @@ -245,9 +247,14 @@ export function ProtocolRunHeader({ } }, [protocolData, isRobotViewable, navigate]) + React.useEffect(() => { + if (isRunCurrent && typeof enteredER === 'boolean') { + reportRecoveredRunResult(runStatus, enteredER) + } + }, [isRunCurrent, enteredER]) + // Side effects dependent on the current run state. React.useEffect(() => { - reportRecoveredRunResult(runStatus, enteredER) // After a user-initiated stopped run, close the run current run automatically. if (runStatus === RUN_STATUS_STOPPED && isRunCurrent && runId != null) { trackProtocolRunEvent({ @@ -258,9 +265,6 @@ export function ProtocolRunHeader({ }) closeCurrentRun() } - if (runStatus === RUN_STATUS_AWAITING_RECOVERY) { - setEnteredER(true) - } }, [runStatus, isRunCurrent, runId, closeCurrentRun]) const startedAtTimestamp = diff --git a/app/src/pages/RunSummary/index.tsx b/app/src/pages/RunSummary/index.tsx index ab08e986544..b47c1838164 100644 --- a/app/src/pages/RunSummary/index.tsx +++ b/app/src/pages/RunSummary/index.tsx @@ -65,6 +65,7 @@ import { formatTimeWithUtcLabel, useNotifyRunQuery } from '../../resources/runs' import { handleTipsAttachedModal } from '../../organisms/DropTipWizardFlows/TipsAttachedModal' import { useMostRecentRunId } from '../../organisms/ProtocolUpload/hooks/useMostRecentRunId' import { useTipAttachmentStatus } from '../../organisms/DropTipWizardFlows' +import { useRecoveryAnalytics } from '../../organisms/ErrorRecoveryFlows/hooks' import type { OnDeviceRouteParams } from '../../App/types' import type { PipetteWithTip } from '../../organisms/DropTipWizardFlows' @@ -109,7 +110,6 @@ export function RunSummary(): JSX.Element { ) const localRobot = useSelector(getLocalRobot) const robotName = localRobot?.name ?? 'no name' - const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) const onCloneRunSuccess = (): void => { if (isQuickTransfer) { @@ -117,10 +117,23 @@ export function RunSummary(): JSX.Element { } } + const { trackProtocolRunEvent } = useTrackProtocolRunEvent( + runId, + robotName as string + ) + const robotAnalyticsData = useRobotAnalyticsData(robotName as string) + const { reportRecoveredRunResult } = useRecoveryAnalytics() + + const enteredER = runRecord?.data.hasEverEnteredErrorRecovery + React.useEffect(() => { + if (isRunCurrent && typeof enteredER === 'boolean') { + reportRecoveredRunResult(runStatus, enteredER) + } + }, [isRunCurrent, enteredER]) + const { reset, isResetRunLoading } = useRunControls(runId, onCloneRunSuccess) const trackEvent = useTrackEvent() const { closeCurrentRun, isClosingCurrentRun } = useCloseCurrentRun() - const robotAnalyticsData = useRobotAnalyticsData(robotName) const [showRunFailedModal, setShowRunFailedModal] = React.useState( false ) @@ -151,10 +164,12 @@ export function RunSummary(): JSX.Element { isFlex: true, }) - // Determine tip status on initial render only. + // Determine tip status on initial render only. Error Recovery always handles tip status, so don't show it twice. React.useEffect(() => { - determineTipStatus() - }, []) + if (isRunCurrent && enteredER === false) { + void determineTipStatus() + } + }, [isRunCurrent, enteredER]) // TODO(jh, 08-02-24): Revisit useCurrentRunRoute and top level redirects. const queryClient = useQueryClient() @@ -164,7 +179,6 @@ export function RunSummary(): JSX.Element { queryClient.setQueryData([host, 'runs', runId, 'details'], () => undefined) navigate('/') } - // TODO(jh, 07-24-24): After EXEC-504, add reportRecoveredRunResult here. const returnToQuickTransfer = (): void => { if (!isRunCurrent) { @@ -215,7 +229,7 @@ export function RunSummary(): JSX.Element { } const handleRunAgain = (pipettesWithTip: PipetteWithTip[]): void => { - if (isRunCurrent && pipettesWithTip.length > 0) { + if (mostRecentRunId === runId && pipettesWithTip.length > 0) { void handleTipsAttachedModal({ setTipStatusResolved: setTipStatusResolvedAndRoute(handleRunAgain), host,