Skip to content

Commit

Permalink
feat(app): complete error handling for calibration on Opentrons Flex (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader committed May 12, 2023
1 parent db9e6c5 commit 1e81662
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 18 deletions.
6 changes: 4 additions & 2 deletions app/src/assets/localization/en/pipette_wizard_flows.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
"critical_unskippable_step": "this is a critical step that cannot be skipped",
"detach_96_attach_mount": "Detach 96-Channel Pipette and Attach {{mount}} Pipette",
"detach_96_channel": "Detach 96-Channel Pipette",
"detach_mount_attach_96": "Detach {{mount}} Pipette and Attach 96-Channel Pipette",
"detach_pipettes_attach_96": "Detach Pipettes and Attach 96-Channel Pipette",
"detach_and_reattach": "Detach and reattach pipette",
"detach_mount_attach_96": "Detach {{mount}} Pipette and Attach 96-Channel Pipette",
"detach_mounting_plate_instructions": "Hold onto the plate so it does not fall. Then remove the pins on the plate from the slots on the gantry carriage.",
"detach_pipette_to_attach_96": "Detach {{pipetteName}} and attach 96-Channel pipette",
"detach_pipette": "detach {{mount}} pipette",
"detach_pipettes_attach_96": "Detach Pipettes and Attach 96-Channel Pipette",
"detach_z_axis_screw_again": "detach the z-axis screw before attaching the 96-Channel Pipette.",
"detach": "Detaching Pipette",
"error_encountered": "Error encountered",
"exit_cal": "Exit calibration",
"gantry_empty_for_96_channel_success": "Now that both mounts are empty, you can begin the 96-Channel Pipette attachment process.",
"get_started_detach": "<block>To get started, remove labware from the deck and clean up the working area to make detachment easier. Also gather the needed equipment shown to the right.</block>",
"grab_screwdriver": "While continuing to hold in place, grab your 2.5mm driver and tighten screws as shown in the animation. Test the pipette attachment by giving it a wiggle before pressing continue",
Expand All @@ -49,6 +50,7 @@
"next": "next",
"ninety_six_channel": "{{ninetySix}} pipette",
"ninety_six_detached_success": "{{pipetteName}} pipette successfully detached",
"pip_cal_failed": "pipette calibration failed",
"pip_cal_success": "{{pipetteName}} successfully attached and calibrated",
"pip_recal_success": "{{pipetteName}} successfully recalibrated",
"pipette_attached": "{{pipetteName}} successfully attached",
Expand Down
15 changes: 7 additions & 8 deletions app/src/organisms/PipetteWizardFlows/AttachProbe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { Trans, useTranslation } from 'react-i18next'
import {
Flex,
TYPOGRAPHY,
COLORS,
SPACING,
RESPONSIVENESS,
} from '@opentrons/components'
import { NINETY_SIX_CHANNEL } from '@opentrons/shared-data'
import { StyledText } from '../../atoms/text'
import { SimpleWizardBody } from '../../molecules/SimpleWizardBody'
import { CalibrationErrorModal } from './CalibrationErrorModal'
import { GenericWizardTile } from '../../molecules/GenericWizardTile'
import { InProgressModal } from '../../molecules/InProgressModal/InProgressModal'
import pipetteProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Probing_1.webm'
Expand Down Expand Up @@ -42,8 +41,8 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => {
isRobotMoving,
goBack,
isExiting,
errorMessage,
setShowErrorMessage,
errorMessage,
isOnDevice,
selectedPipette,
flowType,
Expand Down Expand Up @@ -122,12 +121,12 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => {
)}
</InProgressModal>
)

return errorMessage != null ? (
<SimpleWizardBody
isSuccess={false}
iconColor={COLORS.errorEnabled}
header={t('error_encountered')}
subHeader={errorMessage}
<CalibrationErrorModal
proceed={proceed}
isOnDevice={isOnDevice}
errorMessage={errorMessage}
/>
) : (
<GenericWizardTile
Expand Down
38 changes: 38 additions & 0 deletions app/src/organisms/PipetteWizardFlows/CalibrationErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { COLORS, PrimaryButton } from '@opentrons/components'
import { SmallButton } from '../../atoms/buttons'
import { SimpleWizardBody } from '../../molecules/SimpleWizardBody'

interface CalibrationErrorModalProps {
proceed: () => void
isOnDevice: boolean | null
errorMessage: string
}

export function CalibrationErrorModal(
props: CalibrationErrorModalProps
): JSX.Element {
const { proceed, isOnDevice, errorMessage } = props
const { t, i18n } = useTranslation(['pipette_wizard_flows', 'shared'])
return (
<SimpleWizardBody
iconColor={COLORS.errorEnabled}
header={i18n.format(t('pip_cal_failed'), 'capitalize')}
subHeader={errorMessage}
isSuccess={false}
>
{isOnDevice ? (
<SmallButton
onClick={proceed}
buttonText={i18n.format(t('next'), 'capitalize')}
buttonType="primary"
/>
) : (
<PrimaryButton onClick={proceed}>
{i18n.format(t('next'), 'capitalize')}
</PrimaryButton>
)}
</SimpleWizardBody>
)
}
7 changes: 5 additions & 2 deletions app/src/organisms/PipetteWizardFlows/DetachProbe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const DetachProbe = (props: DetachProbeProps): JSX.Element => {
mount,
flowType,
attachedPipettes,
errorMessage,
} = props
const { t, i18n } = useTranslation('pipette_wizard_flows')
const pipetteWizardStep = { mount, flowType, section: SECTIONS.DETACH_PROBE }
Expand All @@ -37,9 +38,11 @@ export const DetachProbe = (props: DetachProbeProps): JSX.Element => {
{i18n.format(t('remove_probe'), 'capitalize')}
</StyledText>
}
proceedButtonText={t('complete_cal')}
proceedButtonText={
errorMessage != null ? t('exit_cal') : t('complete_cal')
}
proceed={proceed}
back={goBack}
back={errorMessage != null ? undefined : goBack}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ import {
mockAttachedPipetteInformation,
} from '../../../redux/pipettes/__fixtures__'
import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__'
import { CalibrationErrorModal } from '../CalibrationErrorModal'
import { FLOWS } from '../constants'
import { AttachProbe } from '../AttachProbe'

jest.mock('../CalibrationErrorModal')

const mockCalibrationErrorModal = CalibrationErrorModal as jest.MockedFunction<
typeof CalibrationErrorModal
>

const render = (props: React.ComponentProps<typeof AttachProbe>) => {
return renderWithProviders(<AttachProbe {...props} />, {
i18nInstance: i18n,
Expand All @@ -37,6 +44,9 @@ describe('AttachProbe', () => {
selectedPipette: SINGLE_MOUNT_PIPETTES,
isOnDevice: false,
}
mockCalibrationErrorModal.mockReturnValue(
<div>mock calibration error modal</div>
)
})
it('returns the correct information, buttons work as expected', async () => {
const { getByText, getByTestId, getByRole, getByLabelText } = render(props)
Expand Down Expand Up @@ -119,8 +129,7 @@ describe('AttachProbe', () => {
errorMessage: 'error shmerror',
}
const { getByText } = render(props)
getByText('Error encountered')
getByText('error shmerror')
getByText('mock calibration error modal')
})

it('renders the correct text when is on device', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../i18n'
import { CalibrationErrorModal } from '../CalibrationErrorModal'

const render = (props: React.ComponentProps<typeof CalibrationErrorModal>) => {
return renderWithProviders(<CalibrationErrorModal {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('CalibrationErrorModal', () => {
let props: React.ComponentProps<typeof CalibrationErrorModal>
it('returns the correct information for cal modal error and pressing proceed calls props', () => {
props = {
proceed: jest.fn(),
errorMessage: 'error shmerror',
isOnDevice: false,
}
const { getByText, getByRole } = render(props)
getByText('Pipette calibration failed')
getByText('error shmerror')
getByRole('button', { name: 'Next' }).click()
expect(props.proceed).toHaveBeenCalled()
})
it('renders the on device button with correct text', () => {
props = {
proceed: jest.fn(),
errorMessage: 'error shmerror',
isOnDevice: true,
}
const { getByText, getByLabelText } = render(props)
getByText('Pipette calibration failed')
getByText('error shmerror')
getByLabelText('SmallButton_primary').click()
expect(props.proceed).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { fireEvent } from '@testing-library/react'
import { fireEvent, screen } from '@testing-library/react'
import { renderWithProviders } from '@opentrons/components'
import { LEFT, SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data'
import { i18n } from '../../../i18n'
Expand Down Expand Up @@ -61,4 +61,20 @@ describe('DetachProbe', () => {
const { getByText } = render(props)
getByText('mock in progress')
})
it('returns the correct information when there is an error message', () => {
props = {
...props,
errorMessage: 'error shmerror',
}
const { getByText, getByTestId, getByRole } = render(props)
getByText('Remove calibration probe')
getByText(
'Unlatch the calibration probe, remove it from the nozzle, and return it to its storage location.'
)
getByTestId('Pipette_Detach_Probe_1.webm')
const proceedBtn = getByRole('button', { name: 'Exit calibration' })
fireEvent.click(proceedBtn)
expect(props.proceed).toHaveBeenCalled()
expect(screen.queryByLabelText('back')).not.toBeInTheDocument()
})
})
18 changes: 15 additions & 3 deletions app/src/organisms/PipetteWizardFlows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ export const PipetteWizardFlows = (
modalContent = showConfirmExit ? (
exitModal
) : (
<DetachProbe {...currentStep} {...calibrateBaseProps} />
<DetachProbe
{...currentStep}
{...calibrateBaseProps}
proceed={errorMessage != null ? handleCleanUpAndClose : proceed}
/>
)
} else if (currentStep.section === SECTIONS.RESULTS) {
onExit = confirmExit
Expand Down Expand Up @@ -295,20 +299,28 @@ export const PipetteWizardFlows = (
(selectedPipette === NINETY_SIX_CHANNEL &&
currentStep.section === SECTIONS.DETACH_PIPETTE)

const isCalibrationErrorExiting =
currentStep.section === SECTIONS.ATTACH_PROBE && errorMessage != null

let exitWizardButton = onExit
if (isRobotMoving || showUnskippableStepModal) {
if (isRobotMoving || showUnskippableStepModal || isCalibrationErrorExiting) {
exitWizardButton = undefined
} else if (is96ChannelUnskippableStep) {
exitWizardButton = () => setIsUnskippableStep(true)
} else if (showConfirmExit || errorMessage != null) {
exitWizardButton = handleCleanUpAndClose
}

const progressBarForCalError =
currentStep.section === SECTIONS.DETACH_PROBE && errorMessage != null

const wizardHeader = (
<WizardHeader
exitDisabled={isRobotMoving || isFetchingPipettes}
title={wizardTitle}
currentStep={currentStepIndex}
currentStep={
progressBarForCalError ? currentStepIndex - 1 : currentStepIndex
}
totalSteps={totalStepCount}
onExit={exitWizardButton}
/>
Expand Down

0 comments on commit 1e81662

Please sign in to comment.