diff --git a/app/src/components/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.js b/app/src/components/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.js index 03f99cc5128c..d91c3afd27ba 100644 --- a/app/src/components/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.js +++ b/app/src/components/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.js @@ -1,6 +1,5 @@ // @flow import * as React from 'react' -import { Provider } from 'react-redux' import uniqueId from 'lodash/uniqueId' import { mountWithStore } from '@opentrons/components/__utils__' import { act } from 'react-dom/test-utils' @@ -10,7 +9,6 @@ import * as Sessions from '../../../sessions' import { mockPipetteOffsetCalibrationSessionAttributes } from '../../../sessions/__fixtures__' import { useCalibratePipetteOffset } from '../useCalibratePipetteOffset' -import { mount } from 'enzyme' import type { State } from '../../../types' import type { SessionType } from '../../../sessions' @@ -32,13 +30,20 @@ const mockGetRequestById: JestMockFn< describe('useCalibratePipetteOffset hook', () => { let startCalibration let CalWizardComponent - let store const robotName = 'robotName' const mountString = 'left' + const onComplete = jest.fn() + const TestUseCalibratePipetteOffset = () => { const [_startCalibration, _CalWizardComponent] = useCalibratePipetteOffset( robotName, - mountString + { + mount: mountString, + shouldPerformTipLength: false, + hasCalibrationBlock: false, + tipRackDefinition: null, + }, + onComplete ) React.useEffect(() => { startCalibration = _startCalibration @@ -69,7 +74,12 @@ describe('useCalibratePipetteOffset hook', () => { ...Sessions.ensureSession( robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, - { mount: mountString } + { + mount: mountString, + shouldPerformTipLength: false, + hasCalibrationBlock: false, + tipRackDefinition: null, + } ), meta: { requestId: expect.any(String) }, }) @@ -85,12 +95,9 @@ describe('useCalibratePipetteOffset hook', () => { currentStep: Sessions.PIP_OFFSET_STEP_CALIBRATION_COMPLETE, }, } - const { store, wrapper } = mountWithStore( - , - { - initialState: { robotApi: {} }, - } - ) + const { wrapper } = mountWithStore(, { + initialState: { robotApi: {} }, + }) mockGetRobotSessionOfType.mockReturnValue(mockPipOffsetCalSession) mockGetRequestById.mockReturnValue({ status: RobotApi.SUCCESS, @@ -105,8 +112,11 @@ describe('useCalibratePipetteOffset hook', () => { wrapper.setProps({}) expect(CalWizardComponent).not.toBe(null) - wrapper.find('button[children="exit"]').invoke('onClick')() + wrapper + .find('button[title="Return tip to tip rack and exit"]') + .invoke('onClick')() wrapper.setProps({}) expect(CalWizardComponent).toBe(null) + expect(onComplete).toHaveBeenCalled() }) }) diff --git a/app/src/components/CalibratePipetteOffset/useCalibratePipetteOffset.js b/app/src/components/CalibratePipetteOffset/useCalibratePipetteOffset.js index 0083cd5093cc..4881b9244a15 100644 --- a/app/src/components/CalibratePipetteOffset/useCalibratePipetteOffset.js +++ b/app/src/components/CalibratePipetteOffset/useCalibratePipetteOffset.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react' import { useSelector } from 'react-redux' -import { SecondaryBtn, SPACING_2 } from '@opentrons/components' import * as RobotApi from '../../robot-api' import * as Sessions from '../../sessions' @@ -10,8 +9,8 @@ import type { State } from '../../types' import type { SessionCommandString, PipetteOffsetCalibrationSession, + PipetteOffsetCalibrationSessionParams, } from '../../sessions/types' -import type { Mount } from '../../pipettes/types' import type { RequestState } from '../../robot-api/types' import { Portal } from '../portal' @@ -24,7 +23,7 @@ const spinnerCommandBlockList: Array = [ export function useCalibratePipetteOffset( robotName: string, - mount: Mount, + sessionParams: $Shape, onComplete: (() => mixed) | null = null ): [() => void, React.Node | null] { const [showWizard, setShowWizard] = React.useState(false) @@ -74,24 +73,47 @@ export function useCalibratePipetteOffset( : null )?.status === RobotApi.SUCCESS + const closeWizard = React.useCallback(() => { + setShowWizard(false) + onComplete && onComplete() + }, [onComplete]) + React.useEffect(() => { if (shouldOpen) { setShowWizard(true) createRequestId.current = null } if (shouldClose) { - setShowWizard(false) - onComplete && onComplete() + closeWizard() deleteRequestId.current = null } - }, [shouldOpen, shouldClose]) + }, [shouldOpen, shouldClose, closeWizard]) + const { + mount, + shouldPerformTipLength = false, + hasCalibrationBlock = false, + tipRackDefinition = null, + } = sessionParams const handleStartPipOffsetCalSession = () => { + console.log('HANDLE START') + console.table({ + robotName, + mount, + shouldPerformTipLength, + hasCalibrationBlock, + tipRackDefinition, + }) dispatchRequests( Sessions.ensureSession( robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, - { mount } + { + mount, + shouldPerformTipLength, + hasCalibrationBlock, + tipRackDefinition, + } ) ) } @@ -118,7 +140,7 @@ export function useCalibratePipetteOffset( setShowWizard(false)} + closeWizard={closeWizard} showSpinner={showSpinner} dispatchRequests={dispatchRequests} /> diff --git a/app/src/components/ChangePipette/ConfirmPipette.js b/app/src/components/ChangePipette/ConfirmPipette.js index d3d3bc8262f7..01b7a3f16118 100644 --- a/app/src/components/ChangePipette/ConfirmPipette.js +++ b/app/src/components/ChangePipette/ConfirmPipette.js @@ -2,14 +2,7 @@ import * as React from 'react' import cx from 'classnames' -import { - Icon, - PrimaryBtn, - ModalPage, - SPACING_2, - SPACING_4, -} from '@opentrons/components' -import { PipetteOffsetCalibrationControl } from '../InstrumentSettings/PipetteOffsetCalibrationControl' +import { Icon, PrimaryBtn, ModalPage, SPACING_2 } from '@opentrons/components' import { getDiagramsSrc } from './InstructionStep' import { CheckPipettesButton } from './CheckPipettesButton' import styles from './styles.css' @@ -21,7 +14,6 @@ import type { } from '@opentrons/shared-data' import type { Mount } from '../../pipettes/types' import type { PipetteOffsetCalibration } from '../../calibration/types' -import { CalibratePipetteOffset } from '../CalibratePipetteOffset' const EXIT_BUTTON_MESSAGE = 'exit pipette setup' const EXIT_BUTTON_MESSAGE_WRONG = 'keep pipette and exit setup' @@ -54,9 +46,6 @@ export function ConfirmPipette(props: Props): React.Node { actualPipette, actualPipetteOffset, back, - robotName, - mount, - startPipetteOffsetCalibration, } = props return ( diff --git a/app/src/components/ChangePipette/index.js b/app/src/components/ChangePipette/index.js index a7f70524e60c..add428ec0e88 100644 --- a/app/src/components/ChangePipette/index.js +++ b/app/src/components/ChangePipette/index.js @@ -1,10 +1,13 @@ // @flow import * as React from 'react' import { useSelector, useDispatch } from 'react-redux' -import last from 'lodash/last' import { getPipetteNameSpecs, shouldLevel } from '@opentrons/shared-data' -import { useDispatchApiRequest, getRequestById, PENDING } from '../../robot-api' +import { + useDispatchApiRequests, + getRequestById, + SUCCESS, +} from '../../robot-api' import { getCalibrationForPipette } from '../../calibration' import { getAttachedPipettes } from '../../pipettes' import { @@ -13,8 +16,10 @@ import { getMovementStatus, HOMING, MOVING, + ROBOT, PIPETTE, CHANGE_PIPETTE, + HOME, } from '../../robot-controls' import { useCalibratePipetteOffset } from '../CalibratePipetteOffset' @@ -53,7 +58,16 @@ const MOUNT = 'mount' export function ChangePipette(props: Props): React.Node { const { robotName, mount, closeModal } = props const dispatch = useDispatch() - const [dispatchApiRequest, requestIds] = useDispatchApiRequest() + const homePipRequestId = React.useRef(null) + const [dispatchApiRequests] = useDispatchApiRequests(dispatchedAction => { + if ( + dispatchedAction.type === HOME && + dispatchedAction.payload.target === PIPETTE + ) { + // track final home pipette request, its success closes modal + homePipRequestId.current = dispatchedAction.meta.requestId + } + }) const [wizardStep, setWizardStep] = React.useState(CLEAR_DECK) const [wantedName, setWantedName] = React.useState(null) const [confirmExit, setConfirmExit] = React.useState(false) @@ -72,25 +86,28 @@ export function ChangePipette(props: Props): React.Node { return getMovementStatus(state, robotName) }) - const homeRequest = useSelector((state: State) => { - return getRequestById(state, last(requestIds)) - })?.status + const homePipSuccess = + useSelector((state: State) => { + return homePipRequestId.current + ? getRequestById(state, homePipRequestId.current) + : null + })?.status === SUCCESS React.useEffect(() => { - if (homeRequest && homeRequest !== PENDING) { + if (homePipSuccess) { closeModal() } - }, [homeRequest, closeModal]) + }, [homePipSuccess, closeModal]) - const homeAndExit = React.useCallback( - () => dispatchApiRequest(home(robotName, PIPETTE, mount)), - [dispatchApiRequest, robotName, mount] + const homePipAndExit = React.useCallback( + () => dispatchApiRequests(home(robotName, PIPETTE, mount)), + [dispatchApiRequests, robotName, mount] ) const [ startPipetteOffsetCalibration, PipetteOffsetCalibrationWizard, - ] = useCalibratePipetteOffset(robotName, mount, closeModal) + ] = useCalibratePipetteOffset(robotName, { mount }, closeModal) const baseProps = { title: PIPETTE_SETUP, @@ -139,7 +156,7 @@ export function ChangePipette(props: Props): React.Node { {confirmExit && ( setConfirmExit(false)} - exit={homeAndExit} + exit={homePipAndExit} /> )} { + // home before cal flow to account for skips when attaching pipette + setWizardStep(CALIBRATE_PIPETTE) + dispatchApiRequests(home(robotName, ROBOT)) + startPipetteOffsetCalibration() + } + if (success && wantedPipette && shouldLevel(wantedPipette)) { return ( setWizardStep(INSTRUCTIONS), - exit: homeAndExit, + exit: homePipAndExit, actualPipetteOffset: actualPipetteOffset, - startPipetteOffsetCalibration: () => { - startPipetteOffsetCalibration() - setWizardStep(CALIBRATE_PIPETTE) - }, + startPipetteOffsetCalibration: launchPOC, }} /> ) @@ -192,12 +213,9 @@ export function ChangePipette(props: Props): React.Node { setWizardStep(INSTRUCTIONS) }, back: () => setWizardStep(INSTRUCTIONS), - exit: homeAndExit, + exit: homePipAndExit, actualPipetteOffset: actualPipetteOffset, - startPipetteOffsetCalibration: () => { - startPipetteOffsetCalibration() - setWizardStep(CALIBRATE_PIPETTE) - }, + startPipetteOffsetCalibration: launchPOC, }} /> ) diff --git a/app/src/components/InstrumentSettings/PipetteInfo.js b/app/src/components/InstrumentSettings/PipetteInfo.js index 4f042c70c2bd..91f6d432a848 100644 --- a/app/src/components/InstrumentSettings/PipetteInfo.js +++ b/app/src/components/InstrumentSettings/PipetteInfo.js @@ -68,7 +68,7 @@ export function PipetteInfo(props: PipetteInfoProps): React.Node { const [ startPipetteOffsetCalibration, PipetteOffsetCalibrationWizard, - ] = useCalibratePipetteOffset(robotName, mount) + ] = useCalibratePipetteOffset(robotName, { mount }) const pipImage = ( = [ - Sessions.sharedCalCommands.JOG, -] - -const BUTTON_TEXT = 'Calibrate offset' - -export function PipetteOffsetCalibrationControl(props: Props): React.Node { - const { robotName, mount } = props - - const [showWizard, setShowWizard] = React.useState(false) - - const trackedRequestId = React.useRef(null) - const deleteRequestId = React.useRef(null) - const createRequestId = React.useRef(null) - - const [dispatchRequests] = RobotApi.useDispatchApiRequests( - dispatchedAction => { - if (dispatchedAction.type === Sessions.ENSURE_SESSION) { - createRequestId.current = dispatchedAction.meta.requestId - } else if ( - dispatchedAction.type === Sessions.DELETE_SESSION && - pipOffsetCalSession?.id === dispatchedAction.payload.sessionId - ) { - deleteRequestId.current = dispatchedAction.meta.requestId - } else if ( - dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || - !spinnerCommandBlockList.includes( - dispatchedAction.payload.command.command - ) - ) { - trackedRequestId.current = dispatchedAction.meta.requestId - } - } - ) - - const showSpinner = - useSelector(state => - trackedRequestId.current - ? RobotApi.getRequestById(state, trackedRequestId.current) - : null - )?.status === RobotApi.PENDING - - const shouldClose = - useSelector(state => - deleteRequestId.current - ? RobotApi.getRequestById(state, deleteRequestId.current) - : null - )?.status === RobotApi.SUCCESS - - const shouldOpen = - useSelector((state: State) => - createRequestId.current - ? RobotApi.getRequestById(state, createRequestId.current) - : null - )?.status === RobotApi.SUCCESS - - React.useEffect(() => { - if (shouldOpen) { - setShowWizard(true) - createRequestId.current = null - } - if (shouldClose) { - setShowWizard(false) - deleteRequestId.current = null - } - }, [shouldOpen, shouldClose]) - - const hasCalibrationBlock = false - const shouldPerformTipLength = false - const tipRackDefinition = null - const handleStartPipOffsetCalSession = () => { - dispatchRequests( - Sessions.ensureSession( - robotName, - Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, - { - mount, - shouldPerformTipLength, - hasCalibrationBlock, - tipRackDefinition, - } - ) - ) - } - - const pipOffsetCalSession = useSelector< - State, - PipetteOffsetCalibrationSession | null - >((state: State) => { - const session: Sessions.Session | null = Sessions.getRobotSessionOfType( - state, - robotName, - Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION - ) - return session && - session.sessionType === Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION - ? session - : null - }) - - const [ - startPipetteOffsetCalibration, - PipetteOffsetCalibrationWizard, - ] = useCalibratePipetteOffset() - - return ( - <> - - {BUTTON_TEXT} - - {showWizard && ( - - setShowWizard(false)} - showSpinner={showSpinner} - dispatchRequests={dispatchRequests} - /> - - )} - - ) -}