diff --git a/app/src/assets/localization/en/devices_landing.json b/app/src/assets/localization/en/devices_landing.json index 9dd87af81a4..60a25974fec 100644 --- a/app/src/assets/localization/en/devices_landing.json +++ b/app/src/assets/localization/en/devices_landing.json @@ -24,6 +24,7 @@ "lights_on": "lights on", "loading": "loading", "looking_for_robots": "Looking for robots", + "ninety_six_mount": "Left + Right Mount", "make_sure_robot_is_connected": "Make sure the robot is connected to this computer", "modules": "Modules", "no_robots_found": "No robots found", diff --git a/app/src/assets/localization/en/pipette_wizard_flows.json b/app/src/assets/localization/en/pipette_wizard_flows.json index b78f8759518..01023b9fcc8 100644 --- a/app/src/assets/localization/en/pipette_wizard_flows.json +++ b/app/src/assets/localization/en/pipette_wizard_flows.json @@ -31,6 +31,7 @@ "detach_and_retry": "detach and retry", "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_next_pipette": "Detach next pipette", "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", diff --git a/app/src/organisms/ChangePipette/ConfirmPipette.tsx b/app/src/organisms/ChangePipette/ConfirmPipette.tsx index eb465d058b4..0e6e46211a1 100644 --- a/app/src/organisms/ChangePipette/ConfirmPipette.tsx +++ b/app/src/organisms/ChangePipette/ConfirmPipette.tsx @@ -111,7 +111,8 @@ export function ConfirmPipette(props: ConfirmPipetteProps): JSX.Element { return !confirmPipetteLevel && (wrongWantedPipette != null || (props.wantedPipette != null && success)) && actualPipette != null && - actualPipette.channels === 8 ? ( + actualPipette.channels === 8 && + actualPipette.displayCategory === 'GEN2' ? ( { {pipetteWizardFlow != null ? ( { setSelectedPipette(SINGLE_MOUNT_PIPETTES) setPipetteWizardFlow(null) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx b/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx index 862d65104d5..e01abe60dc6 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx @@ -122,7 +122,11 @@ export function SetupFlexPipetteCalibrationItem({ button={button} calibratedDate={pipetteCalDate} subText={subText} - label={t(`devices_landing:${mount}_mount`)} + label={ + requestedPipetteSpecs?.channels === 96 + ? t('devices_landing:ninety_six_mount') + : t(`devices_landing:${mount}_mount`) + } title={requestedPipetteSpecs?.displayName} id={`PipetteCalibration_${mount}MountTitle`} runId={runId} diff --git a/app/src/organisms/PipetteWizardFlows/Carriage.tsx b/app/src/organisms/PipetteWizardFlows/Carriage.tsx index 213a2b4ae1f..f0c70d1069b 100644 --- a/app/src/organisms/PipetteWizardFlows/Carriage.tsx +++ b/app/src/organisms/PipetteWizardFlows/Carriage.tsx @@ -1,20 +1,73 @@ import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import capitalize from 'lodash/capitalize' -import { SPACING, PrimaryButton } from '@opentrons/components' +import { + TYPOGRAPHY, + COLORS, + SPACING, + PrimaryButton, +} from '@opentrons/components' import { StyledText } from '../../atoms/text' import { SmallButton } from '../../atoms/buttons' import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' import { getPipetteAnimations96 } from './utils' import { BODY_STYLE, FLOWS, SECTIONS } from './constants' import type { PipetteWizardStepProps } from './types' export const Carriage = (props: PipetteWizardStepProps): JSX.Element | null => { - const { goBack, flowType, isOnDevice, proceed } = props + const { + goBack, + flowType, + isOnDevice, + proceed, + chainRunCommands, + errorMessage, + setShowErrorMessage, + } = props const { t, i18n } = useTranslation(['pipette_wizard_flows', 'shared']) - return ( + const handleReattachCarriageProceed = (): void => { + chainRunCommands?.( + [ + { + commandType: 'home' as const, + params: { + axes: ['rightZ'], + }, + }, + ], + false + ) + .then(() => { + proceed() + }) + .catch(error => { + setShowErrorMessage(error.message) + }) + } + + return errorMessage != null ? ( + , + bold: ( + + ), + }} + /> + } + /> + ) : ( { proceedButton={ isOnDevice ? ( ) : ( - + {capitalize(t('shared:continue'))} ) diff --git a/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx b/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx index 2ca8266602c..d802f4b78b7 100644 --- a/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx +++ b/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx @@ -1,8 +1,10 @@ import * as React from 'react' -import { useTranslation } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import { RIGHT } from '@opentrons/shared-data' +import { TYPOGRAPHY, COLORS } from '@opentrons/components' import { StyledText } from '../../atoms/text' import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' import { Skeleton } from '../../atoms/Skeleton' import { InProgressModal } from '../../molecules/InProgressModal/InProgressModal' import { CheckPipetteButton } from './CheckPipetteButton' @@ -28,6 +30,8 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { chainRunCommands, isOnDevice, flowType, + errorMessage, + setShowErrorMessage, } = props const { t, i18n } = useTranslation(['pipette_wizard_flows', 'shared']) const pipetteWizardStep = { @@ -41,6 +45,12 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { const handle96ChannelProceed = (): void => { chainRunCommands?.( [ + { + commandType: 'home' as const, + params: { + axes: ['leftZ'], + }, + }, { commandType: 'calibration/moveToMaintenancePosition' as const, params: { @@ -54,8 +64,8 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { .then(() => { proceed() }) - .catch(() => { - proceed() + .catch(error => { + setShowErrorMessage(error.message) }) } const channel = memoizedAttachedPipettes[mount]?.data.channels @@ -80,7 +90,26 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { } if (isRobotMoving) return - return ( + return errorMessage != null ? ( + , + bold: ( + + ), + }} + /> + } + /> + ) : ( > hasCalData: boolean requiredPipette?: LoadedPipette + nextMount?: string } export const Results = (props: ResultsProps): JSX.Element => { @@ -55,6 +57,7 @@ export const Results = (props: ResultsProps): JSX.Element => { isRobotMoving, requiredPipette, setShowErrorMessage, + nextMount, } = props const { t, i18n } = useTranslation(['pipette_wizard_flows', 'shared']) const pipetteName = @@ -120,10 +123,15 @@ export const Results = (props: ResultsProps): JSX.Element => { header = t('ninety_six_detached_success', { pipetteName: NINETY_SIX_CHANNEL, }) - } else { + } else if ( + attachedPipettes[LEFT] == null && + attachedPipettes[RIGHT] == null + ) { header = t('all_pipette_detached') subHeader = t('gantry_empty_for_96_channel_success') buttonText = t('attach_pip') + } else { + buttonText = t('detach_next_pip') } } } @@ -134,11 +142,39 @@ export const Results = (props: ResultsProps): JSX.Element => { const handleProceed = (): void => { if (currentStepIndex === totalStepCount || !isSuccess) { handleCleanUpAndClose() + } else if (isSuccess && nextMount != null) { + // move the gantry into the correct position for the next step of strung together flows + chainRunCommands?.( + [ + { + commandType: 'home' as const, + params: { + axes: ['leftZ', 'rightZ'], + }, + }, + { + commandType: 'calibration/moveToMaintenancePosition' as const, + params: { + mount: nextMount === 'right' ? RIGHT : 'left', + maintenancePosition: + nextMount === 'both' ? 'attachPlate' : 'attachInstrument', + }, + }, + ], + false + ) + .then(() => { + proceed() + }) + .catch(error => { + setShowErrorMessage(error.message) + }) } else if ( isSuccess && flowType === FLOWS.ATTACH && currentStepIndex !== totalStepCount ) { + // proceeding to attach probe for calibration const axes: MotorAxes = mount === LEFT ? ['leftPlunger'] : ['rightPlunger'] chainRunCommands?.( diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx index 56af823d796..5721fdd539c 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx @@ -61,6 +61,8 @@ describe('Results', () => { it('renders the correct information when pipette cal is a success for calibrate flow', () => { props = { ...props, + currentStepIndex: 6, + totalStepCount: 6, hasCalData: true, } const { getByText, getByRole } = render(props) @@ -71,7 +73,7 @@ describe('Results', () => { getByText('Exit') const exit = getByRole('button', { name: 'Results_exit' }) fireEvent.click(exit) - expect(props.proceed).toHaveBeenCalled() + expect(props.handleCleanUpAndClose).toHaveBeenCalled() }) it('renders the correct information when pipette wizard is a success for attach flow', async () => { @@ -84,9 +86,8 @@ describe('Results', () => { const image = getByRole('img', { name: 'Success Icon' }) expect(image.getAttribute('src')).toEqual('icon_success.png') getByRole('img', { name: 'Success Icon' }) - getByText('Calibrate pipette') - const exit = getByRole('button', { name: 'Results_exit' }) - fireEvent.click(exit) + getByRole('button', { name: 'Results_exit' }) + fireEvent.click(getByText('Calibrate pipette')) expect(props.chainRunCommands).toHaveBeenCalledWith( [ { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx index 6bbe76cfba0..5ad45bf2b5e 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx @@ -201,7 +201,12 @@ describe('getPipetteWizardStepsForProtocol', () => { mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: LEFT, + }, { section: SECTIONS.MOUNT_PIPETTE, mount: LEFT, @@ -250,7 +255,12 @@ describe('getPipetteWizardStepsForProtocol', () => { mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, @@ -309,7 +319,12 @@ describe('getPipetteWizardStepsForProtocol', () => { mount: RIGHT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: RIGHT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, @@ -368,13 +383,23 @@ describe('getPipetteWizardStepsForProtocol', () => { mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: RIGHT, + }, { section: SECTIONS.DETACH_PIPETTE, mount: RIGHT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: RIGHT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx index 279df671ebd..4de038c7e71 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx @@ -344,7 +344,7 @@ describe('usePipetteFlowWizardHeaderText', () => { } ) expect(result.current).toEqual( - 'Detach Right Pipette and Attach 96-Channel Pipette' + 'Detach Left Pipette and Attach 96-Channel Pipette' ) }) it('should return correct title for detaching pipette and attaching 96 channel from pipette info from right mount', () => { @@ -371,7 +371,7 @@ describe('usePipetteFlowWizardHeaderText', () => { } ) expect(result.current).toEqual( - 'Detach Left Pipette and Attach 96-Channel Pipette' + 'Detach Right Pipette and Attach 96-Channel Pipette' ) }) it('should return correct title for detaching 2 pipettes and attaching 96 channel from pipette info', () => { diff --git a/app/src/organisms/PipetteWizardFlows/getPipetteWizardSteps.ts b/app/src/organisms/PipetteWizardFlows/getPipetteWizardSteps.ts index a492822e891..65ebe88bc0a 100644 --- a/app/src/organisms/PipetteWizardFlows/getPipetteWizardSteps.ts +++ b/app/src/organisms/PipetteWizardFlows/getPipetteWizardSteps.ts @@ -97,23 +97,23 @@ export const getPipetteWizardSteps = ( }, { section: SECTIONS.FIRMWARE_UPDATE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, { section: SECTIONS.ATTACH_PROBE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.DETACH_PROBE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.RESULTS, - mount: mount, + mount: LEFT, flowType: FLOWS.CALIBRATE, }, ] @@ -127,43 +127,43 @@ export const getPipetteWizardSteps = ( const ALL_STEPS = [ { section: SECTIONS.BEFORE_BEGINNING, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.CARRIAGE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.MOUNTING_PLATE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.MOUNT_PIPETTE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.FIRMWARE_UPDATE, - mount: mount, + mount: LEFT, flowType: flowType, }, - { section: SECTIONS.RESULTS, mount: mount, flowType: FLOWS.ATTACH }, + { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, { section: SECTIONS.ATTACH_PROBE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.DETACH_PROBE, - mount: mount, + mount: LEFT, flowType: flowType, }, { section: SECTIONS.RESULTS, - mount: mount, + mount: LEFT, flowType: FLOWS.CALIBRATE, }, ] diff --git a/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts b/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts index 8a6cd324fb6..305d69b5e6f 100644 --- a/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts +++ b/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts @@ -72,7 +72,12 @@ export const getPipetteWizardStepsForProtocol = ( mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: mount, + }, { section: SECTIONS.MOUNT_PIPETTE, mount: mount, @@ -165,13 +170,23 @@ export const getPipetteWizardStepsForProtocol = ( mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: RIGHT, + }, { section: SECTIONS.DETACH_PIPETTE, mount: RIGHT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: RIGHT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, @@ -229,7 +244,12 @@ export const getPipetteWizardStepsForProtocol = ( mount: LEFT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, @@ -287,7 +307,12 @@ export const getPipetteWizardStepsForProtocol = ( mount: RIGHT, flowType: FLOWS.DETACH, }, - { section: SECTIONS.RESULTS, mount: RIGHT, flowType: FLOWS.DETACH }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, { section: SECTIONS.CARRIAGE, mount: LEFT, diff --git a/app/src/organisms/PipetteWizardFlows/hooks.tsx b/app/src/organisms/PipetteWizardFlows/hooks.tsx index 31c31e66bee..f7fe127d623 100644 --- a/app/src/organisms/PipetteWizardFlows/hooks.tsx +++ b/app/src/organisms/PipetteWizardFlows/hooks.tsx @@ -105,12 +105,12 @@ export function usePipetteFlowWizardHeaderText( attachedPipettes[LEFT] != null && attachedPipettes[RIGHT] == null ) { - return t('detach_mount_attach_96', { mount: capitalize(RIGHT) }) + return t('detach_mount_attach_96', { mount: capitalize(LEFT) }) } else if ( attachedPipettes[LEFT] == null && attachedPipettes[RIGHT] != null ) { - return t('detach_mount_attach_96', { mount: capitalize(LEFT) }) + return t('detach_mount_attach_96', { mount: capitalize(RIGHT) }) } else { return t('detach_pipettes_attach_96') } diff --git a/app/src/organisms/PipetteWizardFlows/types.ts b/app/src/organisms/PipetteWizardFlows/types.ts index 3aaeb10d8bd..f658406e3e3 100644 --- a/app/src/organisms/PipetteWizardFlows/types.ts +++ b/app/src/organisms/PipetteWizardFlows/types.ts @@ -38,7 +38,7 @@ export interface AttachProbeStep extends BaseStep { export interface ResultsStep extends BaseStep { section: typeof SECTIONS.RESULTS - recalibrate?: boolean + nextMount?: string } export interface MountPipetteStep extends BaseStep { section: typeof SECTIONS.MOUNT_PIPETTE