Skip to content

Commit

Permalink
refactor(app): Wire up Error Recovery flows on desktop (#15560)
Browse files Browse the repository at this point in the history
  • Loading branch information
TamarZanzouri authored and aaron-kulkarni committed Jul 3, 2024
1 parent 671b714 commit f571ee5
Show file tree
Hide file tree
Showing 18 changed files with 291 additions and 149 deletions.
5 changes: 3 additions & 2 deletions app/src/assets/localization/en/error_recovery.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"before_you_begin": "Before you begin",
"begin_removal": "Begin removal",
"blowout_failed": "Blowout failed",
"overpressure_is_usually_caused": "Overpressure is usually caused by a tip contacting labware, a clog, or moving viscous liquid too quickly. If the issue persists, cancel the run and make the necessary changes to the protocol.",
"cancel_run": "Cancel run",
"canceling_run": "Canceling run",
"change_location": "Change location",
Expand All @@ -16,8 +15,10 @@
"continue_run_now": "Continue run now",
"continue_to_drop_tip": "Continue to drop tip",
"error": "Error",
"error_on_robot": "Error on {{robot}}",
"failed_dispense_step_not_completed": "<block>The failed dispense step will not be completed. The run will continue from the next step.</block><block>Close the robot door before proceeding.</block>",
"failed_step": "Failed step",
"first_take_any_necessary_actions": "<block>First, take any necessary actions to prepare the robot to retry the failed step.</block><block>Then, close the robot door before proceeding.</block>",
"go_back": "Go back",
"if_tips_are_attached": "If tips are attached, you can choose to blow out any aspirated liquid and drop tips before the run is terminated.",
"ignore_all_errors_of_this_type": "Ignore all errors of this type",
Expand All @@ -31,6 +32,7 @@
"manually_fill_well_and_skip": "Manually fill well and skip to next step",
"next_step": "Next step",
"no_liquid_detected": "No liquid detected",
"overpressure_is_usually_caused": "Overpressure is usually caused by a tip contacting labware, a clog, or moving viscous liquid too quickly. If the issue persists, cancel the run and make the necessary changes to the protocol.",
"pick_up_tips": "Pick up tips",
"pipette_overpressure": "Pipette overpressure",
"preserve_aspirated_liquid": "First, do you need to preserve aspirated liquid?",
Expand All @@ -42,7 +44,6 @@
"replace_tips_and_select_location": "It's best to replace tips and select the last location used for tip pickup.",
"replace_used_tips_in_rack_location": "Replace used tips in rack location {{location}}",
"replace_with_new_tip_rack": "Replace with new tip rack",
"first_take_any_necessary_actions": "<block>First, take any necessary actions to prepare the robot to retry the failed step.</block><block>Then, close the robot door before proceeding.</block>",
"retry_now": "Retry now",
"retry_step": "Retry step",
"retry_with_new_tips": "Retry with new tips",
Expand Down
10 changes: 8 additions & 2 deletions app/src/molecules/InterventionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const MODAL_ODD_STYLE = {
height: '35.5rem',
} as const

const HEADER_STYLE = {
const BASE_HEADER_STYLE = {
alignItems: ALIGN_CENTER,
padding: `${SPACING.spacing20} ${SPACING.spacing32}`,
color: COLORS.white,
Expand All @@ -80,6 +80,11 @@ const HEADER_STYLE = {
'data-testid': '__otInterventionModalHeader',
} as const

const DESKTOP_HEADER_STYLE = {
...BASE_HEADER_STYLE,
height: '3.25rem',
}

const WRAPPER_STYLE = {
position: POSITION_ABSOLUTE,
left: '0',
Expand Down Expand Up @@ -129,6 +134,7 @@ export function InterventionModal({

const isOnDevice = useSelector(getIsOnDevice)
const modalStyle = isOnDevice ? MODAL_ODD_STYLE : MODAL_DESKTOP_STYLE
const headerStyle = isOnDevice ? BASE_HEADER_STYLE : DESKTOP_HEADER_STYLE

return (
<Flex {...WRAPPER_STYLE}>
Expand All @@ -141,7 +147,7 @@ export function InterventionModal({
}}
>
<Flex
{...HEADER_STYLE}
{...headerStyle}
backgroundColor={headerColor}
justifyContent={headerJustifyContent}
onClick={iconHeadingOnClick}
Expand Down
13 changes: 13 additions & 0 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ import { useDeckConfigurationCompatibility } from '../../../resources/deck_confi
import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { useMostRecentRunId } from '../../ProtocolUpload/hooks/useMostRecentRunId'
import { useNotifyRunQuery } from '../../../resources/runs'
import {
useErrorRecoveryFlows,
ErrorRecoveryFlows,
} from '../../ErrorRecoveryFlows'

import type { Run, RunError, RunStatus } from '@opentrons/api-client'
import type { IconName } from '@opentrons/components'
Expand Down Expand Up @@ -175,6 +179,7 @@ export function ProtocolRunHeader({
robotProtocolAnalysis
)
const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility)
const { isERActive, failedCommand } = useErrorRecoveryFlows(runId, runStatus)

const doorSafetySetting = robotSettings.find(
setting => setting.id === 'enableDoorSafetySwitch'
Expand Down Expand Up @@ -290,6 +295,14 @@ export function ProtocolRunHeader({

return (
<>
{isERActive ? (
<ErrorRecoveryFlows
isFlex={true}
runId={runId}
failedCommand={failedCommand}
protocolAnalysis={robotProtocolAnalysis}
/>
) : null}
{showRunFailedModal ? (
<RunFailedModal
robotName={robotName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ import {
useDropTipWizardFlows,
useTipAttachmentStatus,
} from '../../../DropTipWizardFlows'
import {
useErrorRecoveryFlows,
ErrorRecoveryFlows,
} from '../../../ErrorRecoveryFlows'

import type { UseQueryResult } from 'react-query'
import type * as ReactRouterDom from 'react-router-dom'
Expand Down Expand Up @@ -146,6 +150,7 @@ vi.mock('../../../../resources/deck_configuration/hooks')
vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis')
vi.mock('../../../ProtocolUpload/hooks/useMostRecentRunId')
vi.mock('../../../../resources/runs')
vi.mock('../../../ErrorRecoveryFlows')

const ROBOT_NAME = 'otie'
const RUN_ID = '95e67900-bc9f-4fbf-92c6-cc4d7226a51b'
Expand Down Expand Up @@ -362,6 +367,13 @@ describe('ProtocolRunHeader', () => {
robot_serial: MOCK_ROBOT_SERIAL_NUMBER,
},
})
vi.mocked(useErrorRecoveryFlows).mockReturnValue({
isERActive: false,
failedCommand: {},
} as any)
vi.mocked(ErrorRecoveryFlows).mockReturnValue(
<div>MOCK_ERROR_RECOVERY</div>
)
})

afterEach(() => {
Expand Down Expand Up @@ -1029,4 +1041,14 @@ describe('ProtocolRunHeader', () => {
expect(mockDetermineTipStatus).not.toHaveBeenCalled()
})
})

it('renders Error Recovery Flows when isERActive is true', () => {
vi.mocked(useErrorRecoveryFlows).mockReturnValue({
isERActive: true,
failedCommand: {},
} as any)

render()
screen.getByText('MOCK_ERROR_RECOVERY')
})
})
49 changes: 25 additions & 24 deletions app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import * as React from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'

import { LegacyStyledText } from '@opentrons/components'

import { getTopPortalEl } from '../../App/portal'
import { InterventionModal } from '../../molecules/InterventionModal'
import { BeforeBeginning } from './BeforeBeginning'
import { RecoveryError } from './RecoveryError'
import {
Expand All @@ -20,7 +17,11 @@ import {
SkipStepNewTips,
IgnoreErrorSkipStep,
} from './RecoveryOptions'
import { useErrorDetailsModal, ErrorDetailsModal } from './shared'
import {
useErrorDetailsModal,
ErrorDetailsModal,
RecoveryInterventionModal,
} from './shared'
import { RecoveryInProgress } from './RecoveryInProgress'
import { getErrorKind } from './utils'
import { RECOVERY_MAP } from './constants'
Expand Down Expand Up @@ -84,7 +85,8 @@ export function ErrorRecoveryWizard(

export function ErrorRecoveryComponent(
props: RecoveryContentProps
): JSX.Element | null {
): JSX.Element {
const { route, step } = props.recoveryMap
const { t } = useTranslation('error_recovery')
const { showModal, toggleModal } = useErrorDetailsModal()

Expand All @@ -101,25 +103,24 @@ export function ErrorRecoveryComponent(
</LegacyStyledText>
)

if (props.isOnDevice) {
return createPortal(
<InterventionModal
iconName="information"
iconHeading={buildIconHeading()}
titleHeading={buildTitleHeading()}
iconHeadingOnClick={toggleModal}
type="error"
>
{showModal ? (
<ErrorDetailsModal {...props} toggleModal={toggleModal} />
) : null}
<ErrorRecoveryContent {...props} />
</InterventionModal>,
getTopPortalEl()
)
} else {
return null
}
const isLargeDesktopStyle =
route === RECOVERY_MAP.DROP_TIP_FLOWS.ROUTE &&
step !== RECOVERY_MAP.DROP_TIP_FLOWS.STEPS.BEGIN_REMOVAL

return (
<RecoveryInterventionModal
iconHeading={buildIconHeading()}
titleHeading={buildTitleHeading()}
iconHeadingOnClick={toggleModal}
iconName="information"
desktopType={isLargeDesktopStyle ? 'desktop-large' : 'desktop-small'}
>
{showModal ? (
<ErrorDetailsModal {...props} toggleModal={toggleModal} />
) : null}
<ErrorRecoveryContent {...props} />
</RecoveryInterventionModal>
)
}

export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element {
Expand Down
Loading

0 comments on commit f571ee5

Please sign in to comment.