diff --git a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js index dbd1a3ce5fa..5fe639a053f 100644 --- a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js +++ b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js @@ -6,7 +6,6 @@ import { act } from 'react-dom/test-utils' import { getDeckDefinitions } from '@opentrons/components/src/deck/getDeckDefinitions' -import { getRequestById } from '../../../robot-api' import * as Sessions from '../../../sessions' import { mockPipetteOffsetCalibrationSessionAttributes } from '../../../sessions/__fixtures__' @@ -21,7 +20,6 @@ import { CompleteConfirmation, } from '../../CalibrationPanels' -import type { State } from '../../../types' import type { PipetteOffsetCalibrationStep } from '../../../sessions/types' jest.mock('@opentrons/components/src/deck/getDeckDefinitions') @@ -40,11 +38,6 @@ const mockGetDeckDefinitions: JestMockFn< $Call > = getDeckDefinitions -const mockGetRequestById: JestMockFn< - [State, string], - $Call -> = getRequestById - describe('CalibratePipetteOffset', () => { let mockStore let render @@ -93,12 +86,15 @@ describe('CalibratePipetteOffset', () => { ...mockPipetteOffsetCalibrationSessionAttributes, } - render = () => { + render = (props = {}) => { + const { showSpinner = false } = props return mount( {}} + closeWizard={jest.fn()} + dispatchRequests={jest.fn()} + showSpinner={showSpinner} />, { wrappingComponent: Provider, @@ -142,12 +138,13 @@ describe('CalibratePipetteOffset', () => { expect(wrapper.find('ConfirmExitModal').exists()).toBe(true) }) - it('renders spinner when last tracked request is pending, and not present otherwise', () => { - const wrapper = render() + it('does not render spinner when showSpinner is false', () => { + const wrapper = render({ showSpinner: false }) expect(wrapper.find('SpinnerModalPage').exists()).toBe(false) + }) - mockGetRequestById.mockReturnValue({ status: 'pending' }) - wrapper.setProps({}) + it('renders spinner when showSpinner is true', () => { + const wrapper = render({ showSpinner: true }) expect(wrapper.find('SpinnerModalPage').exists()).toBe(true) }) }) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 242f0e8969c..74e6a702ee9 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -1,8 +1,6 @@ // @flow // Pipette Offset Calibration Orchestration Component import * as React from 'react' -import { useSelector, useDispatch } from 'react-redux' -import last from 'lodash/last' import { getPipetteModelSpecs } from '@opentrons/shared-data' import { @@ -11,15 +9,11 @@ import { useConditionalConfirm, } from '@opentrons/components' -import type { State } from '../../types' import type { - SessionCommandString, - SessionCommandData, DeckCalibrationLabware, + SessionCommandParams, } from '../../sessions/types' import * as Sessions from '../../sessions' -import { useDispatchApiRequest, getRequestById, PENDING } from '../../robot-api' -import type { RequestState } from '../../robot-api/types' import { Introduction, DeckSetup, @@ -64,46 +58,21 @@ const PANEL_STYLE_BY_STEP: { export function CalibratePipetteOffset( props: CalibratePipetteOffsetParentProps ): React.Node { - const { session, robotName, closeWizard } = props + const { + session, + robotName, + closeWizard, + dispatchRequests, + showSpinner, + } = props const { currentStep, instrument, labware } = session?.details || {} - const [dispatchRequest, requestIds] = useDispatchApiRequest() - const dispatch = useDispatch() - - const requestStatus = useSelector(state => - getRequestById(state, last(requestIds)) - )?.status - - function sendCommand( - command: SessionCommandString, - data: SessionCommandData = {}, - loadingSpinner: boolean = true - ) { - if (session === null) return - const sessionCommand = Sessions.createSessionCommand( - robotName, - session.id, - { command, data } - ) - if (loadingSpinner) { - dispatchRequest(sessionCommand) - } else { - dispatch(sessionCommand) - } - } - - function deleteSession() { - session?.id && - dispatchRequest(Sessions.deleteSession(robotName, session.id)) - closeWizard() - } const { showConfirmation: showConfirmExit, confirm: confirmExit, cancel: cancelExit, } = useConditionalConfirm(() => { - sendCommand(Sessions.deckCalCommands.EXIT) - deleteSession() + cleanUpAndExit() }, true) const isMulti = React.useMemo(() => { @@ -111,6 +80,31 @@ export function CalibratePipetteOffset( return spec ? spec.channels > 1 : false }, [instrument]) + function sendCommands(...commands: Array) { + if (session?.id) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data || {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } + } + + function cleanUpAndExit() { + if (session?.id) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } + closeWizard() + } + const tipRack: DeckCalibrationLabware | null = (labware && labware.find(l => l.isTiprack)) ?? null @@ -123,7 +117,7 @@ export function CalibratePipetteOffset( back: { onClick: confirmExit, title: EXIT, children: EXIT }, } - if (requestStatus === PENDING) { + if (showSpinner) { return } @@ -135,8 +129,8 @@ export function CalibratePipetteOffset( contentsClassName={PANEL_STYLE_BY_STEP[currentStep]} > void, + dispatchRequests: ( + ...Array<{ ...Action, meta: { requestId: string } }> + ) => void, + showSpinner: boolean, |} export type CalibratePipetteOffsetChildProps = {| - sendSessionCommand: ( - command: SessionCommandString, - data?: SessionCommandData, - loadingSpinner?: boolean - ) => void, + sendSessionCommands: (...Array) => void, deleteSession: () => void, tipRack: PipetteOffsetCalibrationLabware, isMulti: boolean, diff --git a/app/src/components/CalibrationPanels/CompleteConfirmation.js b/app/src/components/CalibrationPanels/CompleteConfirmation.js index b880173730f..3264fb9b79b 100644 --- a/app/src/components/CalibrationPanels/CompleteConfirmation.js +++ b/app/src/components/CalibrationPanels/CompleteConfirmation.js @@ -31,14 +31,8 @@ export function CompleteConfirmation(props: CalibrationPanelProps): React.Node { const { sessionType } = props const { headerText } = contentsBySessionType[sessionType] - // TODO: BC 2020-09-04 avoid potential race condition by having an epic send the delete - // session command upon a successful exit response const exitSession = () => { - props.sendSessionCommand(Sessions.sharedCalCommands.EXIT) - // TODO: IMMEDIATELY use actualy epic for managing chained dependent commands - setTimeout(() => { - props.deleteSession() - }, 300) + props.cleanUpAndExit() } return ( getDeckDefinitions()['ot2_standard'], []) - const { tipRack, sendSessionCommand } = props + const { tipRack, sendCommands } = props const proceed = () => { - sendSessionCommand(Sessions.sharedCalCommands.MOVE_TO_TIP_RACK) + sendCommands({ command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK }) } return ( diff --git a/app/src/components/CalibrationPanels/Introduction.js b/app/src/components/CalibrationPanels/Introduction.js index 798e3cd2672..467a063b57f 100644 --- a/app/src/components/CalibrationPanels/Introduction.js +++ b/app/src/components/CalibrationPanels/Introduction.js @@ -78,11 +78,11 @@ const contentsBySessionType: { } export function Introduction(props: CalibrationPanelProps): React.Node { - const { tipRack, sendSessionCommand, sessionType } = props + const { tipRack, sendCommands, sessionType } = props const { showConfirmation, confirm: proceed, cancel } = useConditionalConfirm( () => { - sendSessionCommand(Sessions.sharedCalCommands.LOAD_LABWARE) + sendCommands({ command: Sessions.sharedCalCommands.LOAD_LABWARE }) }, true ) diff --git a/app/src/components/CalibrationPanels/SaveXYPoint.js b/app/src/components/CalibrationPanels/SaveXYPoint.js index edc44744fba..2e1538c7f9e 100644 --- a/app/src/components/CalibrationPanels/SaveXYPoint.js +++ b/app/src/components/CalibrationPanels/SaveXYPoint.js @@ -121,7 +121,7 @@ const contentsBySessionTypeByCurrentStep: { } export function SaveXYPoint(props: CalibrationPanelProps): React.Node { - const { isMulti, mount, sendSessionCommand, currentStep, sessionType } = props + const { isMulti, mount, sendCommands, currentStep, sessionType } = props const { slotNumber, @@ -136,21 +136,21 @@ export function SaveXYPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.deckCalCommands.JOG, - { + sendCommands({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: formatJogVector(axis, dir, step), }, - false - ) + }) } const savePoint = () => { - sendSessionCommand(Sessions.sharedCalCommands.SAVE_OFFSET) - // TODO: IMMEDIATELY use actualy epic for managing chained dependent commands - setTimeout(() => { - moveCommandString && sendSessionCommand(moveCommandString) - }, 300) + let commands = [{ command: Sessions.sharedCalCommands.SAVE_OFFSET }] + + if (moveCommandString) { + commands = [...commands, { command: moveCommandString }] + } + sendCommands(...commands) } return ( diff --git a/app/src/components/CalibrationPanels/SaveZPoint.js b/app/src/components/CalibrationPanels/SaveZPoint.js index f9cb1dd3c6e..5f8c98efea9 100644 --- a/app/src/components/CalibrationPanels/SaveZPoint.js +++ b/app/src/components/CalibrationPanels/SaveZPoint.js @@ -69,7 +69,7 @@ const contentsBySessionType: { } export function SaveZPoint(props: CalibrationPanelProps): React.Node { - const { isMulti, mount, sendSessionCommand, sessionType } = props + const { isMulti, mount, sendCommands, sessionType } = props const { headerText, buttonText } = contentsBySessionType[sessionType] @@ -79,21 +79,19 @@ export function SaveZPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.deckCalCommands.JOG, - { + sendCommands({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: formatJogVector(axis, dir, step), }, - false - ) + }) } const savePoint = () => { - sendSessionCommand(Sessions.sharedCalCommands.SAVE_OFFSET) - // TODO: IMMEDIATELY use actualy epic for managing chained dependent commands - setTimeout(() => { - sendSessionCommand(Sessions.sharedCalCommands.MOVE_TO_POINT_ONE) - }, 300) + sendCommands( + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_POINT_ONE } + ) } return ( diff --git a/app/src/components/CalibrationPanels/TipConfirmation.js b/app/src/components/CalibrationPanels/TipConfirmation.js index 6112baedbae..76f1622630c 100644 --- a/app/src/components/CalibrationPanels/TipConfirmation.js +++ b/app/src/components/CalibrationPanels/TipConfirmation.js @@ -18,13 +18,13 @@ const CONFIRM_TIP_YES_BUTTON_TEXT = 'Yes, move to slot 5' const CONFIRM_TIP_NO_BUTTON_TEXT = 'No, try again' export function TipConfirmation(props: CalibrationPanelProps): React.Node { - const { sendSessionCommand } = props + const { sendCommands } = props const invalidateTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.INVALIDATE_TIP) + sendCommands({ command: Sessions.sharedCalCommands.INVALIDATE_TIP }) } const confirmTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.MOVE_TO_DECK) + sendCommands({ command: Sessions.sharedCalCommands.MOVE_TO_DECK }) } return ( diff --git a/app/src/components/CalibrationPanels/TipPickUp.js b/app/src/components/CalibrationPanels/TipPickUp.js index 057a53ebd33..0a92af0b993 100644 --- a/app/src/components/CalibrationPanels/TipPickUp.js +++ b/app/src/components/CalibrationPanels/TipPickUp.js @@ -50,7 +50,7 @@ const ASSET_MAP = { single: singleDemoAsset, } export function TipPickUp(props: CalibrationPanelProps): React.Node { - const { sendSessionCommand, tipRack, isMulti } = props + const { sendCommands, tipRack, isMulti } = props const tipRackDef = React.useMemo( () => getLatestLabwareDef(tipRack?.loadName), @@ -70,17 +70,16 @@ export function TipPickUp(props: CalibrationPanelProps): React.Node { ) const pickUpTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.PICK_UP_TIP) + sendCommands({ command: Sessions.sharedCalCommands.PICK_UP_TIP }) } const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.sharedCalCommands.JOG, - { + sendCommands({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: formatJogVector(axis, dir, step), }, - false - ) + }) } return ( diff --git a/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js b/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js index e21e65f359a..6a079c7865e 100644 --- a/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js +++ b/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js @@ -9,14 +9,13 @@ import { CompleteConfirmation } from '../CompleteConfirmation' describe('CompleteConfirmation', () => { let render - const mockSendCommand = jest.fn() - const mockDeleteSession = jest.fn() + const mockSendCommands = jest.fn() + const mockCleanUpAndExit = jest.fn() const getContinueButton = wrapper => wrapper.find('button[title="Return tip to tip rack and exit"]') beforeEach(() => { - jest.useFakeTimers() render = ( props: $Shape> = {} ) => { @@ -24,8 +23,8 @@ describe('CompleteConfirmation', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockCleanUpAndExit, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -34,8 +33,8 @@ describe('CompleteConfirmation', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -45,8 +44,6 @@ describe('CompleteConfirmation', () => { afterEach(() => { jest.resetAllMocks() - jest.clearAllTimers() - jest.useRealTimers() }) it('clicking continue sends exit command and deletes session', () => { @@ -55,11 +52,8 @@ describe('CompleteConfirmation', () => { expect(wrapper.find('ConfirmClearDeckModal').exists()).toBe(false) getContinueButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockDeleteSession).toHaveBeenCalled() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.EXIT - ) + + expect(mockCleanUpAndExit).toHaveBeenCalled() }) it('pip offset cal session type shows correct text', () => { diff --git a/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js b/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js index 04caaa218b9..632a32be8ad 100644 --- a/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js +++ b/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js @@ -16,7 +16,7 @@ jest.mock('@opentrons/components/src/deck/RobotWorkSpace', () => ({ describe('DeckSetup', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() beforeEach(() => { @@ -25,8 +25,8 @@ describe('DeckSetup', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_LABWARE_LOADED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -35,8 +35,8 @@ describe('DeckSetup', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -54,8 +54,8 @@ describe('DeckSetup', () => { act(() => wrapper.find('button').invoke('onClick')()) wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.MOVE_TO_TIP_RACK - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK, + }) }) }) diff --git a/app/src/components/CalibrationPanels/__tests__/Introduction.test.js b/app/src/components/CalibrationPanels/__tests__/Introduction.test.js index 97501dfd9b4..eaaa7200595 100644 --- a/app/src/components/CalibrationPanels/__tests__/Introduction.test.js +++ b/app/src/components/CalibrationPanels/__tests__/Introduction.test.js @@ -9,7 +9,7 @@ import { Introduction } from '../Introduction' describe('Introduction', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() const getContinueButton = wrapper => @@ -27,8 +27,8 @@ describe('Introduction', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -37,8 +37,8 @@ describe('Introduction', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -60,9 +60,9 @@ describe('Introduction', () => { getConfirmDeckClearButton(wrapper).invoke('onClick')() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.LOAD_LABWARE - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.deckCalCommands.LOAD_LABWARE, + }) }) it('clicking continue launches clear deck warning then cancel closes modal', () => { @@ -76,9 +76,9 @@ describe('Introduction', () => { getCancelDeckClearButton(wrapper).invoke('onClick')() expect(wrapper.find('ConfirmClearDeckModal').exists()).toBe(false) - expect(mockSendCommand).not.toHaveBeenCalledWith( - Sessions.deckCalCommands.LOAD_LABWARE - ) + expect(mockSendCommands).not.toHaveBeenCalledWith({ + command: Sessions.deckCalCommands.LOAD_LABWARE, + }) }) it('pip offset cal session type shows correct text', () => { diff --git a/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js b/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js index 3e122a62dcc..7b7e67ed66b 100644 --- a/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js +++ b/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js @@ -15,7 +15,7 @@ const currentStepBySlot = { describe('SaveXYPoint', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() const getSaveButton = (wrapper, direction) => @@ -27,14 +27,13 @@ describe('SaveXYPoint', () => { const getVideo = wrapper => wrapper.find(`source`) beforeEach(() => { - jest.useFakeTimers() render = (props = {}) => { const { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SAVING_POINT_ONE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -43,8 +42,8 @@ describe('SaveXYPoint', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -53,8 +52,6 @@ describe('SaveXYPoint', () => { }) afterEach(() => { jest.resetAllMocks() - jest.clearAllTimers() - jest.useRealTimers() }) it('displays proper asset', () => { @@ -133,13 +130,12 @@ describe('SaveXYPoint', () => { getJogButton(wrapper, direction).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.JOG, - { + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: jogVectorByDirection[direction], }, - false - ) + }) }) const unavailableJogDirections = ['up', 'down'] @@ -158,13 +154,14 @@ describe('SaveXYPoint', () => { getSaveButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.SAVE_OFFSET - ) - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.MOVE_TO_POINT_TWO + expect(mockSendCommands).toHaveBeenCalledWith( + { + command: Sessions.sharedCalCommands.SAVE_OFFSET, + }, + { + command: Sessions.deckCalCommands.MOVE_TO_POINT_TWO, + } ) }) @@ -177,13 +174,14 @@ describe('SaveXYPoint', () => { expect(wrapper.text()).toContain('slot 3') getSaveButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.SAVE_OFFSET - ) - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.MOVE_TO_POINT_THREE + expect(mockSendCommands).toHaveBeenCalledWith( + { + command: Sessions.sharedCalCommands.SAVE_OFFSET, + }, + { + command: Sessions.deckCalCommands.MOVE_TO_POINT_THREE, + } ) }) @@ -196,13 +194,14 @@ describe('SaveXYPoint', () => { expect(wrapper.text()).toContain('slot 7') getSaveButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.SAVE_OFFSET - ) - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.MOVE_TO_TIP_RACK + expect(mockSendCommands).toHaveBeenCalledWith( + { + command: Sessions.sharedCalCommands.SAVE_OFFSET, + }, + { + command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK, + } ) }) @@ -217,9 +216,9 @@ describe('SaveXYPoint', () => { getSaveButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.SAVE_OFFSET - ) + + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.SAVE_OFFSET, + }) }) }) diff --git a/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js b/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js index 3b634855598..b6818816b12 100644 --- a/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js +++ b/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js @@ -9,7 +9,7 @@ import { SaveZPoint } from '../SaveZPoint' describe('SaveZPoint', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() const getSaveButton = wrapper => wrapper.find('button[title="save"]') @@ -20,14 +20,13 @@ describe('SaveZPoint', () => { const getVideo = wrapper => wrapper.find(`source`) beforeEach(() => { - jest.useFakeTimers() render = (props = {}) => { const { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_JOGGING_TO_DECK, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -36,8 +35,8 @@ describe('SaveZPoint', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -46,8 +45,6 @@ describe('SaveZPoint', () => { }) afterEach(() => { jest.resetAllMocks() - jest.clearAllTimers() - jest.useRealTimers() }) it('displays proper asset', () => { @@ -87,13 +84,12 @@ describe('SaveZPoint', () => { getJogButton(wrapper, direction).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.JOG, - { + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.deckCalCommands.JOG, + data: { vector: jogVectorByDirection[direction], }, - false - ) + }) }) const unavailableJogDirections = ['left', 'right', 'back', 'forward'] @@ -107,13 +103,14 @@ describe('SaveZPoint', () => { getSaveButton(wrapper).invoke('onClick')() wrapper.update() - jest.runAllTimers() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.SAVE_OFFSET - ) - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.deckCalCommands.MOVE_TO_POINT_ONE + expect(mockSendCommands).toHaveBeenCalledWith( + { + command: Sessions.deckCalCommands.SAVE_OFFSET, + }, + { + command: Sessions.deckCalCommands.MOVE_TO_POINT_ONE, + } ) }) diff --git a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js index 48e692dd425..b85f7027f0f 100644 --- a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js +++ b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js @@ -9,7 +9,7 @@ import { TipConfirmation } from '../TipConfirmation' describe('TipConfirmation', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() const getConfirmTipButton = wrapper => @@ -26,8 +26,8 @@ describe('TipConfirmation', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_INSPECTING_TIP, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -36,8 +36,8 @@ describe('TipConfirmation', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -55,17 +55,17 @@ describe('TipConfirmation', () => { getConfirmTipButton(wrapper).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.MOVE_TO_DECK - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.MOVE_TO_DECK, + }) }) it('clicking invalidate tip send invalidate tip command', () => { const wrapper = render() getInvalidateTipButton(wrapper).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.INVALIDATE_TIP - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.INVALIDATE_TIP, + }) }) }) diff --git a/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js b/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js index 25fb2cee29a..a3a19761f01 100644 --- a/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js +++ b/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js @@ -9,7 +9,7 @@ import { TipPickUp } from '../TipPickUp' describe('TipPickUp', () => { let render - const mockSendCommand = jest.fn() + const mockSendCommands = jest.fn() const mockDeleteSession = jest.fn() const getPickUpTipButton = wrapper => @@ -24,8 +24,8 @@ describe('TipPickUp', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_PREPARING_PIPETTE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -34,8 +34,8 @@ describe('TipPickUp', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + sendCommands={sendCommands} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> @@ -63,11 +63,10 @@ describe('TipPickUp', () => { getJogButton(wrapper, direction).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.JOG, - { vector: jogVectorsByDirection[direction] }, - false - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: jogVectorsByDirection[direction] }, + }) }) }) it('clicking pick up tip sends pick up tip command', () => { @@ -75,8 +74,8 @@ describe('TipPickUp', () => { getPickUpTipButton(wrapper).invoke('onClick')() wrapper.update() - expect(mockSendCommand).toHaveBeenCalledWith( - Sessions.sharedCalCommands.PICK_UP_TIP - ) + expect(mockSendCommands).toHaveBeenCalledWith({ + command: Sessions.sharedCalCommands.PICK_UP_TIP, + }) }) }) diff --git a/app/src/components/CalibrationPanels/types.js b/app/src/components/CalibrationPanels/types.js index cfbc384878c..c5e69121cb4 100644 --- a/app/src/components/CalibrationPanels/types.js +++ b/app/src/components/CalibrationPanels/types.js @@ -1,7 +1,6 @@ // @flow import type { - SessionCommandString, - SessionCommandData, + SessionCommandParams, SessionType, CalibrationSessionStep, } from '../../sessions/types' @@ -10,12 +9,8 @@ import type { PipetteOffsetCalibrationLabware } from '../../sessions/pipette-off import type { TipLengthCalibrationLabware } from '../../sessions/tip-length-calibration/types' export type CalibrationPanelProps = {| - sendSessionCommand: ( - command: SessionCommandString, - data?: SessionCommandData, - loadingSpinner?: boolean - ) => void, - deleteSession: () => void, + sendCommands: (...Array) => void, + cleanUpAndExit: () => void, tipRack: | DeckCalibrationLabware | PipetteOffsetCalibrationLabware diff --git a/app/src/components/CalibrationPanels/utils.js b/app/src/components/CalibrationPanels/utils.js index 20073997a72..5d417a764c7 100644 --- a/app/src/components/CalibrationPanels/utils.js +++ b/app/src/components/CalibrationPanels/utils.js @@ -1,5 +1,6 @@ // @flow import type { JogAxis } from '../../http-api-client' +import type { VectorTuple } from '../../sessions/types' const ORDERED_AXES: [JogAxis, JogAxis, JogAxis] = ['x', 'y', 'z'] @@ -8,7 +9,7 @@ export function formatJogVector( axis: string, direction: number, step: number -): [number, number, number] { +): VectorTuple { const vector = [0, 0, 0] const index = ORDERED_AXES.findIndex(a => a === axis) if (index >= 0) { diff --git a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js index f51a2bbb591..5f4d9e830cf 100644 --- a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js +++ b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js @@ -1,14 +1,18 @@ // @flow import * as React from 'react' import { useSelector } from 'react-redux' -import last from 'lodash/last' import { SecondaryBtn, SPACING_2 } from '@opentrons/components' import * as RobotApi from '../../robot-api' import * as Sessions from '../../sessions' import type { State } from '../../types' +import type { + SessionCommandString, + PipetteOffsetCalibrationSession, +} from '../../sessions/types' import type { Mount } from '../../pipettes/types' +import type { RequestState } from '../../robot-api/types' import { Portal } from '../portal' import { CalibratePipetteOffset } from '../CalibratePipetteOffset' @@ -18,6 +22,11 @@ type Props = {| mount: Mount, |} +// pipette calibration commands for which the full page spinner should not appear +const spinnerCommandBlockList: Array = [ + Sessions.sharedCalCommands.JOG, +] + const BUTTON_TEXT = 'Calibrate offset' export function PipetteOffsetCalibrationControl(props: Props): React.Node { @@ -25,20 +34,64 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { const [showWizard, setShowWizard] = React.useState(false) - const [dispatchRequest, requestIds] = RobotApi.useDispatchApiRequest() - const requestState = useSelector((state: State) => { - const reqId = last(requestIds) ?? null - return reqId ? RobotApi.getRequestById(state, reqId) : null - }) - const requestStatus = requestState?.status ?? null + 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 - // TODO: BC 2020-08-17 specifically track the success of the session response React.useEffect(() => { - if (requestStatus === RobotApi.SUCCESS) setShowWizard(true) - }, [requestStatus]) + if (shouldOpen) { + setShowWizard(true) + createRequestId.current = null + } + if (shouldClose) { + setShowWizard(false) + deleteRequestId.current = null + } + }, [shouldOpen, shouldClose]) const handleStartPipOffsetCalSession = () => { - dispatchRequest( + dispatchRequests( Sessions.ensureSession( robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, @@ -47,19 +100,19 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { ) } - const pipOffsetCalSession = useSelector((state: State) => { + const pipOffsetCalSession = useSelector< + State, + PipetteOffsetCalibrationSession | null + >((state: State) => { const session: Sessions.Session | null = Sessions.getRobotSessionOfType( state, robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION ) - if ( - session && + return session && session.sessionType === Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION - ) { - return session - } - return null + ? session + : null }) return ( @@ -78,6 +131,8 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { session={pipOffsetCalSession} robotName={robotName} closeWizard={() => setShowWizard(false)} + showSpinner={showSpinner} + dispatchRequests={dispatchRequests} /> )} diff --git a/app/src/robot-api/__tests__/hooks.test.js b/app/src/robot-api/__tests__/hooks.test.js index 7976728226f..ca07c7dc1b8 100644 --- a/app/src/robot-api/__tests__/hooks.test.js +++ b/app/src/robot-api/__tests__/hooks.test.js @@ -1,20 +1,16 @@ // @flow import * as React from 'react' -import * as ReactRedux from 'react-redux' -import { act } from 'react-dom/test-utils' import uniqueId from 'lodash/uniqueId' -import { mount } from 'enzyme' -import { useDispatchApiRequest } from '../hooks' +import { mountWithStore } from '@opentrons/components/__utils__' +import { PENDING, SUCCESS } from '../constants' +import { useDispatchApiRequest, useDispatchApiRequests } from '../hooks' -jest.mock('react-redux') jest.mock('lodash/uniqueId') -const mockUseDispatch: JestMockFn<[], (mixed) => void> = ReactRedux.useDispatch const mockUniqueId: JestMockFn<[string | void], string> = uniqueId describe('useDispatchApiRequest', () => { - let wrapper - let mockDispatch + let render const TestUseDispatchApiRequest = () => { const mockAction: any = { type: 'mockAction', meta: {} } @@ -29,11 +25,9 @@ describe('useDispatchApiRequest', () => { beforeEach(() => { let mockIdCounter = 0 - mockDispatch = jest.fn() mockUniqueId.mockImplementation(() => `mockId_${mockIdCounter++}`) - mockUseDispatch.mockImplementation(() => mockDispatch) - wrapper = mount() + render = () => mountWithStore() }) afterEach(() => { @@ -41,31 +35,134 @@ describe('useDispatchApiRequest', () => { }) it('adds meta.requestId to action and dispatches it', () => { - expect(mockDispatch).toHaveBeenCalledTimes(0) + const { wrapper, store } = render() + expect(store.dispatch).toHaveBeenCalledTimes(0) - act(() => wrapper.find('button').invoke('onClick')()) + wrapper.find('button').invoke('onClick')() wrapper.update() - expect(mockDispatch).toHaveBeenCalledWith({ + expect(store.dispatch).toHaveBeenCalledWith({ type: 'mockAction', meta: { requestId: 'mockId_0' }, }) }) it('adds requestId to requestIds list', () => { - act(() => wrapper.find('button').invoke('onClick')()) + const { wrapper } = render() + wrapper.find('button').invoke('onClick')() wrapper.update() expect(wrapper.text()).toEqual('mockId_0') }) it('can dispatch multiple requests', () => { - act(() => wrapper.find('button').invoke('onClick')()) + const { wrapper, store } = render() + wrapper.find('button').invoke('onClick')() wrapper.update() - act(() => wrapper.find('button').invoke('onClick')()) + wrapper.find('button').invoke('onClick')() wrapper.update() - expect(mockDispatch).toHaveBeenCalledTimes(2) + expect(store.dispatch).toHaveBeenCalledTimes(2) expect(wrapper.text()).toEqual('mockId_0 mockId_1') }) }) + +describe('useDispatchApiRequests', () => { + let render + + const TestUseDispatchApiRequests = props => { + const mockAction: any = { type: 'mockAction', meta: {} } + const mockOtherAction: any = { type: 'mockOtherAction', meta: {} } + const [dispatchRequests] = useDispatchApiRequests() + + return ( + + ) + } + + beforeEach(() => { + let mockIdCounter = 0 + mockUniqueId.mockImplementation(() => `mockId_${mockIdCounter++}`) + + render = () => + mountWithStore(, { + initialState: { + robotApi: {}, + }, + }) + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('dispatches first request first', () => { + const { store, wrapper } = render() + store.getState.mockReturnValue({ + robotApi: { + mockId_0: { + status: PENDING, + }, + }, + }) + wrapper.find('button').invoke('onClick')() + wrapper.update() + + expect(store.dispatch).toHaveBeenCalledTimes(1) + expect(store.dispatch).toHaveBeenCalledWith({ + type: 'mockAction', + meta: { requestId: 'mockId_0' }, + }) + }) + + it('dispatches second if first not pending', () => { + const { store, wrapper } = render() + store.getState.mockReturnValue({ + robotApi: { + mockId_0: { + status: SUCCESS, + response: { method: 'GET', ok: true, path: '/test', status: 200 }, + }, + }, + }) + wrapper.find('button').invoke('onClick')() + wrapper.update() + + expect(store.dispatch).toHaveBeenCalledTimes(2) + expect(store.dispatch).toHaveBeenCalledWith({ + type: 'mockAction', + meta: { requestId: 'mockId_0' }, + }) + expect(store.dispatch).toHaveBeenCalledWith({ + type: 'mockOtherAction', + meta: { requestId: 'mockId_1' }, + }) + }) + + it('dispatches first and second, but waits for third if second is pending', () => { + const { store, wrapper } = render() + store.getState.mockReturnValue({ + robotApi: { + mockId_0: { + status: SUCCESS, + response: { method: 'GET', ok: true, path: '/test', status: 200 }, + }, + mockId_1: { status: PENDING }, + }, + }) + wrapper.find('button').invoke('onClick')() + wrapper.update() + + expect(store.dispatch).toHaveBeenCalledTimes(2) + expect(store.dispatch).toHaveBeenCalledWith({ + type: 'mockAction', + meta: { requestId: 'mockId_0' }, + }) + expect(store.dispatch).toHaveBeenCalledWith({ + type: 'mockOtherAction', + meta: { requestId: 'mockId_1' }, + }) + }) +}) diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 5cfbb564428..2f604a9e8dd 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -1,9 +1,14 @@ // @flow // hooks for components that depend on API state -import { useReducer, useCallback } from 'react' -import { useDispatch } from 'react-redux' +import { useReducer, useCallback, useRef, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' import uniqueId from 'lodash/uniqueId' +import type { State, Action } from '../types' +import { PENDING } from './constants' +import { getRequestById } from './selectors' +import type { RequestState } from './types' + /** * React hook to attach a unique request ID to and dispatch an API action * Note: dispatching will trigger a re-render of the component @@ -27,10 +32,9 @@ import uniqueId from 'lodash/uniqueId' * ) * } */ -export function useDispatchApiRequest(): [ - (A) => void, - Array -] { +export function useDispatchApiRequest< + A: { ...Action, meta: { requestId: string } } +>(): [(A) => A, Array] { const dispatch = useDispatch<(A) => void>() // TODO(mc, 2019-11-06): evaluate whether or not this can be a ref @@ -40,15 +44,74 @@ export function useDispatchApiRequest(): [ ) const dispatchApiRequest = useCallback( - (a: A) => { + (a: A): A => { const requestId = uniqueId('robotApi_request_') const action = { ...a, meta: { ...a.meta, requestId } } addRequestId(requestId) dispatch(action) + return action }, [dispatch] ) return [dispatchApiRequest, requestIds] } + +/** + * React hook to attach a unique request ID to and sequentially + * dispatch multiple API actions upon completion of the last request. + * One optional parameter for function to be called with tracked action + * upon dispatch of said action. + * Note: dispatching will trigger a re-render of the component + * + * @returns {[action => mixed, Array]} tuple of dispatch function and dispatched request IDs + * + * @example + * import { useDispatchApiRequests } from '../../robot-api' + * import { fetchPipettes } from '../../pipettes' + * import { fetchModules } from '../../modules' + * + * type Props = {| robotName: string |} + * + * export function FetchPipettesButton(props: Props) { + * const { robotName } = props + * const [dispatchRequests, requestIds] = useDispatchApiRequests() + * + * return ( + * + * ) + * } + */ +export function useDispatchApiRequests< + A: { ...Action, meta: { requestId: string } } +>( + onDispatchedRequest: (A => void) | null = null +): [(...Array) => void, Array] { + const [dispatchRequest, requestIds] = useDispatchApiRequest() + + const trackedRequestId = useRef(null) + const [unrequestedQueue, setUnrequestedQueue] = useState>([]) + + const trackedRequestIsPending = + useSelector(state => { + return trackedRequestId.current + ? getRequestById(state, trackedRequestId.current) + : null + })?.status === PENDING + + if (unrequestedQueue.length > 0 && !trackedRequestIsPending) { + const action = dispatchRequest(unrequestedQueue[0]) + if (onDispatchedRequest) onDispatchedRequest(action) + trackedRequestId.current = action.meta.requestId + setUnrequestedQueue(unrequestedQueue.slice(1)) + } + + const dispatchApiRequests = (...a: Array) => { + setUnrequestedQueue([...unrequestedQueue, ...a]) + } + + return [dispatchApiRequests, requestIds] +} diff --git a/app/src/sessions/__fixtures__/index.js b/app/src/sessions/__fixtures__/index.js index 2dbb18c7fab..01baa1b507d 100644 --- a/app/src/sessions/__fixtures__/index.js +++ b/app/src/sessions/__fixtures__/index.js @@ -60,7 +60,7 @@ export const mockSession: Types.Session = { export const mockSessionCommand: Types.SessionCommandAttributes = { command: 'calibration.jog', - data: { someData: 32 }, + data: { vector: [32, 0, 0] }, } export const mockSessionCommandAttributes: Types.SessionCommandAttributes = { diff --git a/app/src/sessions/epic/__tests__/createSessionCommandEpic.test.js b/app/src/sessions/epic/__tests__/createSessionCommandEpic.test.js index e4cdf405206..70cf74c7158 100644 --- a/app/src/sessions/epic/__tests__/createSessionCommandEpic.test.js +++ b/app/src/sessions/epic/__tests__/createSessionCommandEpic.test.js @@ -35,7 +35,7 @@ describe('createSessionCommandEpic', () => { attributes: { command: 'calibration.jog', data: { - someData: 32, + vector: [32, 0, 0], }, }, }, diff --git a/app/src/sessions/types.js b/app/src/sessions/types.js index ffea454edf6..7693a3b5f3c 100644 --- a/app/src/sessions/types.js +++ b/app/src/sessions/types.js @@ -37,6 +37,7 @@ import * as CalCheckConstants from './calibration-check/constants' import * as TipCalConstants from './tip-length-calibration/constants' import * as DeckCalConstants from './deck-calibration/constants' import * as PipOffsetCalConstants from './pipette-offset-calibration/constants' +import * as CommonCalConstants from './common-calibration/constants' export type * from './calibration-check/types' export type * from './tip-length-calibration/types' @@ -60,6 +61,7 @@ export type SessionCommandString = | $Values | $Values | $Values + | $Values export type CalibrationSessionStep = | CalCheckTypes.RobotCalibrationCheckStep @@ -67,9 +69,13 @@ export type CalibrationSessionStep = | DeckCalTypes.DeckCalibrationStep | PipOffsetCalTypes.PipetteOffsetCalibrationStep -// TODO(al, 2020-05-11): data should be properly typed with all -// known command types -export type SessionCommandData = { ... } +export type VectorTuple = [number, number, number] + +export type SessionCommandData = {| vector: VectorTuple |} | {||} +export type SessionCommandParams = { + command: SessionCommandString, + data?: SessionCommandData, +} export type CalibrationCheckSessionResponseAttributes = {| sessionType: SESSION_TYPE_CALIBRATION_CHECK, @@ -341,7 +347,6 @@ export type CalibrationCheckIntercomData = {| succeeded: boolean, |} -type VectorTuple = [number, number, number] export type CalibrationCheckAnalyticsData = {| ...CalibrationCheckCommonEventData, comparingFirstPipetteHeightDifferenceVector?: VectorTuple,