diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
index 8d65ef71417..0bfa08ce47b 100644
--- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
@@ -174,7 +174,6 @@ export function ProtocolRunHeader({
const [pipettesWithTip, setPipettesWithTip] = React.useState<
PipettesWithTip[]
>([])
- const [closeTerminalBanner, setCloseTerminalBanner] = React.useState(false)
const isResetRunLoadingRef = React.useRef(false)
const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity })
const highestPriorityError =
@@ -200,7 +199,7 @@ export function ProtocolRunHeader({
const { data: doorStatus } = useDoorQuery({
refetchInterval: EQUIPMENT_POLL_MS,
})
- let isDoorOpen = false
+ let isDoorOpen: boolean
if (isFlex) {
isDoorOpen = doorStatus?.data.status === 'open'
} else if (!isFlex && Boolean(doorSafetySetting?.value)) {
@@ -248,7 +247,9 @@ export function ProtocolRunHeader({
}
}, [protocolData, isRobotViewable, history])
+ // Side effects dependent on the current run state.
React.useEffect(() => {
+ // After a user-initiated stopped run, close the run current run automatically.
if (runStatus === RUN_STATUS_STOPPED && isRunCurrent && runId != null) {
trackProtocolRunEvent({
name: ANALYTICS_PROTOCOL_RUN_FINISH,
@@ -260,12 +261,6 @@ export function ProtocolRunHeader({
}
}, [runStatus, isRunCurrent, runId, closeCurrentRun])
- React.useEffect(() => {
- if (runStatus === RUN_STATUS_IDLE) {
- setCloseTerminalBanner(false)
- }
- }, [runStatus])
-
const startedAtTimestamp =
startedAt != null ? formatTimestamp(startedAt) : EMPTY_TIMESTAMP
@@ -310,7 +305,6 @@ export function ProtocolRunHeader({
properties: robotAnalyticsData ?? undefined,
})
closeCurrentRun()
- setCloseTerminalBanner(true)
}
return (
@@ -375,7 +369,7 @@ export function ProtocolRunHeader({
CANCELLABLE_STATUSES.includes(runStatus) ? (
{t('shared:close_robot_door')}
) : null}
- {mostRecentRunId === runId && !closeTerminalBanner ? (
+ {mostRecentRunId === runId ? (
) : null}
{mostRecentRunId === runId &&
@@ -479,7 +474,9 @@ export function ProtocolRunHeader({
setShowDropTipWizard(false)
setPipettesWithTip(prevPipettesWithTip => {
const pipettesWithTip = prevPipettesWithTip.slice(1) ?? []
- if (pipettesWithTip.length === 0) closeCurrentRun()
+ if (pipettesWithTip.length === 0) {
+ closeCurrentRun()
+ }
return pipettesWithTip
})
}}
@@ -570,6 +567,7 @@ interface ActionButtonProps {
isResetRunLoadingRef: React.MutableRefObject
}
+// TODO(jh, 04-22-2024): Refactor switch cases into separate factories to increase readability and testability.
function ActionButton(props: ActionButtonProps): JSX.Element {
const {
runId,
@@ -613,9 +611,7 @@ function ActionButton(props: ActionButtonProps): JSX.Element {
robotName,
runId
)
- const [showIsShakingModal, setShowIsShakingModal] = React.useState(
- false
- )
+ const [showIsShakingModal, setShowIsShakingModal] = React.useState(false)
const isSetupComplete =
isCalibrationComplete &&
isModuleCalibrationComplete &&
@@ -804,12 +800,14 @@ function ActionButton(props: ActionButtonProps): JSX.Element {
)
}
+// TODO(jh 04-24-2024): Split TerminalRunBanner into a RunSuccessBanner and RunFailedBanner.
interface TerminalRunProps {
runStatus: RunStatus | null
handleClearClick: () => void
isClosingCurrentRun: boolean
setShowRunFailedModal: (showRunFailedModal: boolean) => void
isResetRunLoading: boolean
+ isRunCurrent: boolean
highestPriorityError?: RunError | null
}
function TerminalRunBanner(props: TerminalRunProps): JSX.Element | null {
@@ -820,51 +818,64 @@ function TerminalRunBanner(props: TerminalRunProps): JSX.Element | null {
setShowRunFailedModal,
highestPriorityError,
isResetRunLoading,
+ isRunCurrent,
} = props
const { t } = useTranslation('run_details')
- const handleClick = (): void => {
+ const handleRunSuccessClick = (): void => {
+ handleClearClick()
+ }
+
+ const handleFailedRunClick = (): void => {
handleClearClick()
setShowRunFailedModal(true)
}
- if (
- isResetRunLoading === false &&
- (runStatus === RUN_STATUS_FAILED || runStatus === RUN_STATUS_SUCCEEDED)
- ) {
+ const buildSuccessBanner = (): JSX.Element => {
return (
- <>
- {runStatus === RUN_STATUS_SUCCEEDED ? (
-
-
- {t('run_completed')}
-
-
- ) : (
-
-
-
- {t('error_info', {
- errorType: highestPriorityError?.errorType,
- errorCode: highestPriorityError?.errorCode,
- })}
-
+
+
+ {t('run_completed')}
+
+
+ )
+ }
-
- {t('view_error')}
-
-
-
- )}
- >
+ const buildErrorBanner = (): JSX.Element => {
+ return (
+
+
+
+ {t('error_info', {
+ errorType: highestPriorityError?.errorType,
+ errorCode: highestPriorityError?.errorCode,
+ })}
+
+
+
+ {t('view_error')}
+
+
+
)
}
- return null
+
+ if (
+ runStatus === RUN_STATUS_SUCCEEDED &&
+ isRunCurrent &&
+ !isResetRunLoading
+ ) {
+ return buildSuccessBanner()
+ } else if (runStatus === RUN_STATUS_FAILED && !isResetRunLoading) {
+ return buildErrorBanner()
+ } else {
+ return null
+ }
}
diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx
index 65ea98c906f..3b6f0f9025b 100644
--- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx
@@ -814,7 +814,7 @@ describe('ProtocolRunHeader', () => {
screen.getByText('Run completed.')
})
- it('clicking close on a terminal run banner closes the run context and dismisses the banner', async () => {
+ it('clicking close on a terminal run banner closes the run context', async () => {
when(vi.mocked(useNotifyRunQuery))
.calledWith(RUN_ID)
.thenReturn({
@@ -827,9 +827,20 @@ describe('ProtocolRunHeader', () => {
fireEvent.click(screen.getByTestId('Banner_close-button'))
expect(mockCloseCurrentRun).toBeCalled()
- await waitFor(() => {
- expect(screen.queryByText('Run completed.')).not.toBeInTheDocument()
- })
+ })
+
+ it('does not display the "run successful" banner if the successful run is not current', async () => {
+ when(vi.mocked(useNotifyRunQuery))
+ .calledWith(RUN_ID)
+ .thenReturn({
+ data: { data: { ...mockSucceededRun, current: false } },
+ } as UseQueryResult)
+ when(vi.mocked(useRunStatus))
+ .calledWith(RUN_ID)
+ .thenReturn(RUN_STATUS_SUCCEEDED)
+ render()
+
+ expect(screen.queryByText('Run completed.')).not.toBeInTheDocument()
})
it('if a heater shaker is shaking, clicking on start run should render HeaterShakerIsRunningModal', async () => {