From 210df884c3131cac88330ab2520c9dd688773611 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 15 Sep 2020 18:56:10 -0400 Subject: [PATCH 01/13] calibration panel save z using new chained hook --- .../CalibratePipetteOffset/index.js | 62 +++++++++++------- .../CalibratePipetteOffset/types.js | 12 ++-- .../CalibrationPanels/SaveZPoint.js | 18 +++-- app/src/robot-api/hooks.js | 65 ++++++++++++++++++- 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 242f0e8969c..21d6512b435 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -2,7 +2,6 @@ // 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 { @@ -18,7 +17,11 @@ import type { DeckCalibrationLabware, } from '../../sessions/types' import * as Sessions from '../../sessions' -import { useDispatchApiRequest, getRequestById, PENDING } from '../../robot-api' +import { + useDispatchApiRequests, + getRequestById, + PENDING, +} from '../../robot-api' import type { RequestState } from '../../robot-api/types' import { Introduction, @@ -32,9 +35,14 @@ import { } from '../CalibrationPanels' import styles from '../CalibrateDeck/styles.css' -import type { CalibratePipetteOffsetParentProps } from './types' +import type { CalibratePipetteOffsetParentProps, CommandToSend } from './types' import type { CalibrationPanelProps } from '../CalibrationPanels/types' +// session commands for which the full page spinner should not appear +const spinnerCommandBlockList: Array = [ + Sessions.sharedCalCommands.JOG, +] + const PIPETTE_OFFSET_CALIBRATION_SUBTITLE = 'Pipette offset calibration' const EXIT = 'exit' @@ -66,34 +74,38 @@ export function CalibratePipetteOffset( ): React.Node { const { session, robotName, closeWizard } = props const { currentStep, instrument, labware } = session?.details || {} - const [dispatchRequest, requestIds] = useDispatchApiRequest() + const trackedRequestId = React.useRef(null) + const [dispatchRequests, requestIds] = useDispatchApiRequests( + dispatchedAction => { + if ( + dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || + !spinnerCommandBlockList.includes(dispatchedAction.payload.command) + ) { + trackedRequestId.current = dispatchedAction.meta.requestId + } + } + ) const dispatch = useDispatch() - const requestStatus = useSelector(state => - getRequestById(state, last(requestIds)) - )?.status + const showSpinner = + useSelector(state => + getRequestById(state, trackedRequestId) + )?.status === PENDING - function sendCommand( - command: SessionCommandString, - data: SessionCommandData = {}, - loadingSpinner: boolean = true - ) { + function sendCommands(...comms: Array) { if (session === null) return - const sessionCommand = Sessions.createSessionCommand( - robotName, - session.id, - { command, data } + const sessionCommands = comms.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data || {}, + }) ) - if (loadingSpinner) { - dispatchRequest(sessionCommand) - } else { - dispatch(sessionCommand) - } + dispatchRequests(...sessionCommands) } function deleteSession() { session?.id && - dispatchRequest(Sessions.deleteSession(robotName, session.id)) + dispatchRequests(Sessions.deleteSession(robotName, session.id)) closeWizard() } @@ -102,7 +114,7 @@ export function CalibratePipetteOffset( confirm: confirmExit, cancel: cancelExit, } = useConditionalConfirm(() => { - sendCommand(Sessions.deckCalCommands.EXIT) + sendCommands(Sessions.deckCalCommands.EXIT) deleteSession() }, true) @@ -123,7 +135,7 @@ export function CalibratePipetteOffset( back: { onClick: confirmExit, title: EXIT, children: EXIT }, } - if (requestStatus === PENDING) { + if (showSpinner) { return } @@ -135,7 +147,7 @@ export function CalibratePipetteOffset( contentsClassName={PANEL_STYLE_BY_STEP[currentStep]} > void, |} +export type CommandToSend = { + command: SessionCommandString, + data?: SessionCommandData, + loadingSpinner?: 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/SaveZPoint.js b/app/src/components/CalibrationPanels/SaveZPoint.js index f9cb1dd3c6e..09ee1396110 100644 --- a/app/src/components/CalibrationPanels/SaveZPoint.js +++ b/app/src/components/CalibrationPanels/SaveZPoint.js @@ -79,21 +79,19 @@ export function SaveZPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.deckCalCommands.JOG, - { + sendSessionCommand({ + command: Sessions.deckCalCommands.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) + sendSessionCommand( + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_POINT_ONE } + ) } return ( diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 5cfbb564428..3e3857c515f 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -1,8 +1,11 @@ // @flow // hooks for components that depend on API state -import { useReducer, useCallback } from 'react' -import { useDispatch } from 'react-redux' +import { useReducer, useCallback, useEffect, useRef } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { getRequestById } from './selectors' import uniqueId from 'lodash/uniqueId' +import last from 'lodash/last' +import { PENDING } from './constants' /** * React hook to attach a unique request ID to and dispatch an API action @@ -40,15 +43,71 @@ 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] = useDispatchApiRequest() + * + * return ( + * + * ) + * } + */ +export function useDispatchApiRequests< + A: Array<{ meta: { requestId: string } }> +>(onDispatch: A => void = null): [(A) => void, Array] { + const [dispatchRequest, requestIds] = useDispatchApiRequest() + + const trackedRequestId = useRef(null) + const unrequestedQueue = useRef>([]) + + const trackedRequestIsPending = + useSelector(state => + getRequestById(state, trackedRequestId) + )?.status === PENDING + + if (!trackedRequestIsPending && unrequestedQueue.current.length > 0) { + const action = dispatchRequest(unrequestedQueue.current[0]) + if (onDispatch) onDispatch(action) + trackedRequestId.current = action.meta.requestId + unrequestedQueue.current = unrequestedQueue.current.slice(1) // dequeue + } + + const dispatchApiRequests = (...a: Array) => { + console.log('called', a) + unrequestedQueue.current = a + } + + return [dispatchApiRequests, requestIds] +} From 00154ef5ae0949ba280fc5a992037e86e9c75cc9 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Tue, 15 Sep 2020 19:29:38 -0400 Subject: [PATCH 02/13] debug wiring up of chained hook --- app/src/components/CalibratePipetteOffset/index.js | 12 +++++++++--- app/src/robot-api/hooks.js | 10 +++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 21d6512b435..1236ae56cd0 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -89,7 +89,7 @@ export function CalibratePipetteOffset( const showSpinner = useSelector(state => - getRequestById(state, trackedRequestId) + getRequestById(state, trackedRequestId.current) )?.status === PENDING function sendCommands(...comms: Array) { @@ -114,8 +114,14 @@ export function CalibratePipetteOffset( confirm: confirmExit, cancel: cancelExit, } = useConditionalConfirm(() => { - sendCommands(Sessions.deckCalCommands.EXIT) - deleteSession() + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.deckCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + closeWizard() }, true) const isMulti = React.useMemo(() => { diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 3e3857c515f..160aecdd912 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -94,19 +94,23 @@ export function useDispatchApiRequests< const trackedRequestIsPending = useSelector(state => - getRequestById(state, trackedRequestId) + getRequestById(state, trackedRequestId.current) )?.status === PENDING - if (!trackedRequestIsPending && unrequestedQueue.current.length > 0) { + const triggerNext = () => { + console.log('in trigger', unrequestedQueue.current) const action = dispatchRequest(unrequestedQueue.current[0]) if (onDispatch) onDispatch(action) trackedRequestId.current = action.meta.requestId unrequestedQueue.current = unrequestedQueue.current.slice(1) // dequeue } + if (unrequestedQueue.current.length > 0 && !trackedRequestIsPending) { + triggerNext() + } const dispatchApiRequests = (...a: Array) => { - console.log('called', a) unrequestedQueue.current = a + triggerNext() } return [dispatchApiRequests, requestIds] From dbe7f5d096b084ffa17719e6a8d7f2cecc6b9e0f Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 16 Sep 2020 14:58:46 -0400 Subject: [PATCH 03/13] migrate calibration panels over to send commands --- .../CalibratePipetteOffset/index.js | 42 +++++++++++-------- .../CalibratePipetteOffset/types.js | 9 +--- .../CalibrationPanels/CompleteConfirmation.js | 8 +--- .../components/CalibrationPanels/DeckSetup.js | 2 +- .../CalibrationPanels/Introduction.js | 2 +- .../CalibrationPanels/SaveXYPoint.js | 20 ++++----- .../CalibrationPanels/SaveZPoint.js | 2 +- .../CalibrationPanels/TipConfirmation.js | 4 +- .../components/CalibrationPanels/TipPickUp.js | 11 +++-- app/src/components/CalibrationPanels/types.js | 7 +--- app/src/components/CalibrationPanels/utils.js | 3 +- app/src/robot-api/hooks.js | 18 +++++--- app/src/sessions/types.js | 10 ++++- 13 files changed, 71 insertions(+), 67 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 1236ae56cd0..ff1e6af116d 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -15,6 +15,7 @@ import type { SessionCommandString, SessionCommandData, DeckCalibrationLabware, + SessionCommandParams, } from '../../sessions/types' import * as Sessions from '../../sessions' import { @@ -35,7 +36,7 @@ import { } from '../CalibrationPanels' import styles from '../CalibrateDeck/styles.css' -import type { CalibratePipetteOffsetParentProps, CommandToSend } from './types' +import type { CalibratePipetteOffsetParentProps } from './types' import type { CalibrationPanelProps } from '../CalibrationPanels/types' // session commands for which the full page spinner should not appear @@ -79,7 +80,9 @@ export function CalibratePipetteOffset( dispatchedAction => { if ( dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || - !spinnerCommandBlockList.includes(dispatchedAction.payload.command) + !spinnerCommandBlockList.includes( + dispatchedAction.payload.command.command + ) ) { trackedRequestId.current = dispatchedAction.meta.requestId } @@ -89,23 +92,33 @@ export function CalibratePipetteOffset( const showSpinner = useSelector(state => - getRequestById(state, trackedRequestId.current) + trackedRequestId.current + ? getRequestById(state, trackedRequestId.current) + : null )?.status === PENDING - function sendCommands(...comms: Array) { + function sendCommands(...commands: Array) { if (session === null) return - const sessionCommands = comms.map(c => + const sessionCommandActions = commands.map(c => Sessions.createSessionCommand(robotName, session.id, { command: c.command, data: c.data || {}, }) ) - dispatchRequests(...sessionCommands) + console.log('in send commands', commands, sessionCommandActions) + dispatchRequests(...sessionCommandActions) } - function deleteSession() { - session?.id && - dispatchRequests(Sessions.deleteSession(robotName, session.id)) + function cleanUp() { + if (session?.id) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } closeWizard() } @@ -114,14 +127,7 @@ export function CalibratePipetteOffset( confirm: confirmExit, cancel: cancelExit, } = useConditionalConfirm(() => { - dispatchRequests( - Sessions.createSessionCommand(robotName, session.id, { - command: Sessions.deckCalCommands.EXIT, - data: {}, - }), - Sessions.deleteSession(robotName, session.id) - ) - closeWizard() + cleanUp() }, true) const isMulti = React.useMemo(() => { @@ -154,7 +160,7 @@ export function CalibratePipetteOffset( > void, |} -export type CommandToSend = { - command: SessionCommandString, - data?: SessionCommandData, - loadingSpinner?: boolean, -} - export type CalibratePipetteOffsetChildProps = {| - sendSessionCommands: (Array) => 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..26b8c56c55c 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.deleteSession() } return ( { - sendSessionCommand(Sessions.sharedCalCommands.MOVE_TO_TIP_RACK) + sendSessionCommand({ 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..565844b06c2 100644 --- a/app/src/components/CalibrationPanels/Introduction.js +++ b/app/src/components/CalibrationPanels/Introduction.js @@ -82,7 +82,7 @@ export function Introduction(props: CalibrationPanelProps): React.Node { const { showConfirmation, confirm: proceed, cancel } = useConditionalConfirm( () => { - sendSessionCommand(Sessions.sharedCalCommands.LOAD_LABWARE) + sendSessionCommand({ command: Sessions.sharedCalCommands.LOAD_LABWARE }) }, true ) diff --git a/app/src/components/CalibrationPanels/SaveXYPoint.js b/app/src/components/CalibrationPanels/SaveXYPoint.js index edc44744fba..fb97b495281 100644 --- a/app/src/components/CalibrationPanels/SaveXYPoint.js +++ b/app/src/components/CalibrationPanels/SaveXYPoint.js @@ -136,21 +136,21 @@ export function SaveXYPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.deckCalCommands.JOG, - { + sendSessionCommand({ + 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 }] + } + sendSessionCommand(...commands) } return ( diff --git a/app/src/components/CalibrationPanels/SaveZPoint.js b/app/src/components/CalibrationPanels/SaveZPoint.js index 09ee1396110..2353fc98281 100644 --- a/app/src/components/CalibrationPanels/SaveZPoint.js +++ b/app/src/components/CalibrationPanels/SaveZPoint.js @@ -80,7 +80,7 @@ export function SaveZPoint(props: CalibrationPanelProps): React.Node { const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { sendSessionCommand({ - command: Sessions.deckCalCommands.JOG, + command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), }, diff --git a/app/src/components/CalibrationPanels/TipConfirmation.js b/app/src/components/CalibrationPanels/TipConfirmation.js index 6112baedbae..c1614a5de37 100644 --- a/app/src/components/CalibrationPanels/TipConfirmation.js +++ b/app/src/components/CalibrationPanels/TipConfirmation.js @@ -21,10 +21,10 @@ export function TipConfirmation(props: CalibrationPanelProps): React.Node { const { sendSessionCommand } = props const invalidateTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.INVALIDATE_TIP) + sendSessionCommand({ command: Sessions.sharedCalCommands.INVALIDATE_TIP }) } const confirmTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.MOVE_TO_DECK) + sendSessionCommand({ 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..25956839024 100644 --- a/app/src/components/CalibrationPanels/TipPickUp.js +++ b/app/src/components/CalibrationPanels/TipPickUp.js @@ -70,17 +70,16 @@ export function TipPickUp(props: CalibrationPanelProps): React.Node { ) const pickUpTip = () => { - sendSessionCommand(Sessions.sharedCalCommands.PICK_UP_TIP) + sendSessionCommand({ command: Sessions.sharedCalCommands.PICK_UP_TIP }) } const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand( - Sessions.sharedCalCommands.JOG, - { + sendSessionCommand({ + command: Sessions.sharedCalCommands.JOG, + data: { vector: formatJogVector(axis, dir, step), }, - false - ) + }) } return ( diff --git a/app/src/components/CalibrationPanels/types.js b/app/src/components/CalibrationPanels/types.js index cfbc384878c..5d489587a92 100644 --- a/app/src/components/CalibrationPanels/types.js +++ b/app/src/components/CalibrationPanels/types.js @@ -2,6 +2,7 @@ import type { SessionCommandString, SessionCommandData, + SessionCommandParams, SessionType, CalibrationSessionStep, } from '../../sessions/types' @@ -10,11 +11,7 @@ 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, + sendSessionCommand: (...Array) => void, deleteSession: () => void, tipRack: | DeckCalibrationLabware 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/robot-api/hooks.js b/app/src/robot-api/hooks.js index 160aecdd912..374f70d18ca 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -2,10 +2,13 @@ // hooks for components that depend on API state import { useReducer, useCallback, useEffect, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { getRequestById } from './selectors' import uniqueId from 'lodash/uniqueId' import last from 'lodash/last' + +import type { State } 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 @@ -31,7 +34,7 @@ import { PENDING } from './constants' * } */ export function useDispatchApiRequest(): [ - (A) => void, + (A) => A, Array ] { const dispatch = useDispatch<(A) => void>() @@ -84,9 +87,9 @@ export function useDispatchApiRequest(): [ * ) * } */ -export function useDispatchApiRequests< - A: Array<{ meta: { requestId: string } }> ->(onDispatch: A => void = null): [(A) => void, Array] { +export function useDispatchApiRequests( + onDispatch: (A => void) | null = null +): [(...Array) => void, Array] { const [dispatchRequest, requestIds] = useDispatchApiRequest() const trackedRequestId = useRef(null) @@ -94,7 +97,9 @@ export function useDispatchApiRequests< const trackedRequestIsPending = useSelector(state => - getRequestById(state, trackedRequestId.current) + trackedRequestId.current + ? getRequestById(state, trackedRequestId.current) + : null )?.status === PENDING const triggerNext = () => { @@ -110,6 +115,7 @@ export function useDispatchApiRequests< const dispatchApiRequests = (...a: Array) => { unrequestedQueue.current = a + console.log('in dars', a) triggerNext() } diff --git a/app/src/sessions/types.js b/app/src/sessions/types.js index ffea454edf6..ac5e513e52b 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 @@ -69,7 +71,11 @@ export type CalibrationSessionStep = // TODO(al, 2020-05-11): data should be properly typed with all // known command types -export type SessionCommandData = { ... } +export type SessionCommandData = { vector: VectorTuple } | {} +export type SessionCommandParams = { + command: SessionCommandString, + data?: SessionCommandData, +} export type CalibrationCheckSessionResponseAttributes = {| sessionType: SESSION_TYPE_CALIBRATION_CHECK, @@ -341,7 +347,7 @@ export type CalibrationCheckIntercomData = {| succeeded: boolean, |} -type VectorTuple = [number, number, number] +export type VectorTuple = [number, number, number] export type CalibrationCheckAnalyticsData = {| ...CalibrationCheckCommonEventData, comparingFirstPipetteHeightDifferenceVector?: VectorTuple, From 11122d8d7ba6d89231f8f813e5d6ea05657d57e3 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 16 Sep 2020 18:13:41 -0400 Subject: [PATCH 04/13] make it so clean up only occurs after successful delete --- .../__tests__/CalibratePipetteOffset.test.js | 4 +- .../CalibratePipetteOffset/index.js | 75 ++++++++----------- .../CalibratePipetteOffset/types.js | 5 ++ .../CalibrationPanels/CompleteConfirmation.js | 2 +- .../__tests__/CompleteConfirmation.test.js | 4 +- .../__tests__/DeckSetup.test.js | 4 +- .../__tests__/Introduction.test.js | 4 +- .../__tests__/SaveXYPoint.test.js | 4 +- .../__tests__/SaveZPoint.test.js | 4 +- .../__tests__/TipConfirmation.test.js | 4 +- .../__tests__/TipPickUp.test.js | 4 +- app/src/components/CalibrationPanels/types.js | 2 +- .../PipetteOffsetCalibrationControl.js | 52 ++++++++++++- app/src/robot-api/hooks.js | 35 ++++----- app/src/sessions/__fixtures__/index.js | 2 +- app/src/sessions/types.js | 2 +- 16 files changed, 122 insertions(+), 85 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js index dbd1a3ce5fa..e6b7a1ab713 100644 --- a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js +++ b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js @@ -98,7 +98,9 @@ describe('CalibratePipetteOffset', () => { {}} + closeWizard={jest.fn()} + dispatchRequests={jest.fn()} + showSpinner={false} />, { wrappingComponent: Provider, diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index ff1e6af116d..19fc783f9b5 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -73,43 +73,41 @@ 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 trackedRequestId = React.useRef(null) - const [dispatchRequests, requestIds] = useDispatchApiRequests( - dispatchedAction => { - if ( - dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || - !spinnerCommandBlockList.includes( - dispatchedAction.payload.command.command - ) - ) { - trackedRequestId.current = dispatchedAction.meta.requestId - } - } - ) - const dispatch = useDispatch() - const showSpinner = - useSelector(state => - trackedRequestId.current - ? getRequestById(state, trackedRequestId.current) - : null - )?.status === PENDING + const { + showConfirmation: showConfirmExit, + confirm: confirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + cleanUpAndExit() + }, true) + + const isMulti = React.useMemo(() => { + const spec = instrument && getPipetteModelSpecs(instrument.model) + return spec ? spec.channels > 1 : false + }, [instrument]) function sendCommands(...commands: Array) { - if (session === null) return - const sessionCommandActions = commands.map(c => - Sessions.createSessionCommand(robotName, session.id, { - command: c.command, - data: c.data || {}, - }) - ) - console.log('in send commands', commands, sessionCommandActions) - dispatchRequests(...sessionCommandActions) + if (session?.id) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data || {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } } - function cleanUp() { + function cleanUpAndExit() { if (session?.id) { dispatchRequests( Sessions.createSessionCommand(robotName, session.id, { @@ -122,19 +120,6 @@ export function CalibratePipetteOffset( closeWizard() } - const { - showConfirmation: showConfirmExit, - confirm: confirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - cleanUp() - }, true) - - const isMulti = React.useMemo(() => { - const spec = instrument && getPipetteModelSpecs(instrument.model) - return spec ? spec.channels > 1 : false - }, [instrument]) - const tipRack: DeckCalibrationLabware | null = (labware && labware.find(l => l.isTiprack)) ?? null @@ -160,7 +145,7 @@ export function CalibratePipetteOffset( > void, + dispatchRequests: ( + ...Array<{ ...Action, meta: { requestId: string } }> + ) => void, + showSpinner: boolean, |} export type CalibratePipetteOffsetChildProps = {| diff --git a/app/src/components/CalibrationPanels/CompleteConfirmation.js b/app/src/components/CalibrationPanels/CompleteConfirmation.js index 26b8c56c55c..3264fb9b79b 100644 --- a/app/src/components/CalibrationPanels/CompleteConfirmation.js +++ b/app/src/components/CalibrationPanels/CompleteConfirmation.js @@ -32,7 +32,7 @@ export function CompleteConfirmation(props: CalibrationPanelProps): React.Node { const { headerText } = contentsBySessionType[sessionType] const exitSession = () => { - props.deleteSession() + props.cleanUpAndExit() } return ( { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -35,7 +35,7 @@ describe('CompleteConfirmation', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js b/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js index 04caaa218b9..3a8b4527562 100644 --- a/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js +++ b/app/src/components/CalibrationPanels/__tests__/DeckSetup.test.js @@ -26,7 +26,7 @@ describe('DeckSetup', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_LABWARE_LOADED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -36,7 +36,7 @@ describe('DeckSetup', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/Introduction.test.js b/app/src/components/CalibrationPanels/__tests__/Introduction.test.js index 97501dfd9b4..fd6aef8c766 100644 --- a/app/src/components/CalibrationPanels/__tests__/Introduction.test.js +++ b/app/src/components/CalibrationPanels/__tests__/Introduction.test.js @@ -28,7 +28,7 @@ describe('Introduction', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -38,7 +38,7 @@ describe('Introduction', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js b/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js index 3e122a62dcc..a0ac0a8ace6 100644 --- a/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js +++ b/app/src/components/CalibrationPanels/__tests__/SaveXYPoint.test.js @@ -34,7 +34,7 @@ describe('SaveXYPoint', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SAVING_POINT_ONE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -44,7 +44,7 @@ describe('SaveXYPoint', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js b/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js index 3b634855598..5b58b3a8eb8 100644 --- a/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js +++ b/app/src/components/CalibrationPanels/__tests__/SaveZPoint.test.js @@ -27,7 +27,7 @@ describe('SaveZPoint', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_JOGGING_TO_DECK, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -37,7 +37,7 @@ describe('SaveZPoint', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js index 48e692dd425..75a7a8c9859 100644 --- a/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js +++ b/app/src/components/CalibrationPanels/__tests__/TipConfirmation.test.js @@ -27,7 +27,7 @@ describe('TipConfirmation', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_INSPECTING_TIP, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -37,7 +37,7 @@ describe('TipConfirmation', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js b/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js index 25fb2cee29a..66f80b5864e 100644 --- a/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js +++ b/app/src/components/CalibrationPanels/__tests__/TipPickUp.test.js @@ -25,7 +25,7 @@ describe('TipPickUp', () => { isMulti = false, tipRack = mockDeckCalTipRack, sendSessionCommand = mockSendCommand, - deleteSession = mockDeleteSession, + cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_PREPARING_PIPETTE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -35,7 +35,7 @@ describe('TipPickUp', () => { mount={pipMount} tipRack={tipRack} sendSessionCommand={sendSessionCommand} - deleteSession={deleteSession} + cleanUpAndExit={cleanUpAndExit} currentStep={currentStep} sessionType={sessionType} /> diff --git a/app/src/components/CalibrationPanels/types.js b/app/src/components/CalibrationPanels/types.js index 5d489587a92..28535436c4c 100644 --- a/app/src/components/CalibrationPanels/types.js +++ b/app/src/components/CalibrationPanels/types.js @@ -12,7 +12,7 @@ import type { TipLengthCalibrationLabware } from '../../sessions/tip-length-cali export type CalibrationPanelProps = {| sendSessionCommand: (...Array) => void, - deleteSession: () => void, + cleanUpAndExit: () => void, tipRack: | DeckCalibrationLabware | PipetteOffsetCalibrationLabware diff --git a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js index f51a2bbb591..f65a5104dee 100644 --- a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js +++ b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js @@ -8,7 +8,9 @@ import * as RobotApi from '../../robot-api' import * as Sessions from '../../sessions' import type { State } from '../../types' +import type { SessionCommandString } 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 +20,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,7 +32,42 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { const [showWizard, setShowWizard] = React.useState(false) - const [dispatchRequest, requestIds] = RobotApi.useDispatchApiRequest() + const trackedRequestId = React.useRef(null) + const deleteRequestId = React.useRef(null) + + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( + dispatchedAction => { + if ( + dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || + !spinnerCommandBlockList.includes( + dispatchedAction.payload.command.command + ) + ) { + trackedRequestId.current = dispatchedAction.meta.requestId + } + if ( + dispatchedAction.type === Sessions.DELETE_SESSION && + pipOffsetCalSession?.id == dispatchedAction.payload.sessionId + ) { + deleteRequestId.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 requestState = useSelector((state: State) => { const reqId = last(requestIds) ?? null return reqId ? RobotApi.getRequestById(state, reqId) : null @@ -35,10 +77,14 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { // TODO: BC 2020-08-17 specifically track the success of the session response React.useEffect(() => { if (requestStatus === RobotApi.SUCCESS) setShowWizard(true) + if (shouldClose) { + setShowWizard(false) + deleteRequestId.current = null + } }, [requestStatus]) const handleStartPipOffsetCalSession = () => { - dispatchRequest( + dispatchRequests( Sessions.ensureSession( robotName, Sessions.SESSION_TYPE_PIPETTE_OFFSET_CALIBRATION, @@ -78,6 +124,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/hooks.js b/app/src/robot-api/hooks.js index 374f70d18ca..337dac80715 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -1,11 +1,11 @@ // @flow // hooks for components that depend on API state -import { useReducer, useCallback, useEffect, useRef } from 'react' +import { useReducer, useCallback, useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import uniqueId from 'lodash/uniqueId' import last from 'lodash/last' -import type { State } from '../types' +import type { State, Action } from '../types' import { PENDING } from './constants' import { getRequestById } from './selectors' import type { RequestState } from './types' @@ -33,10 +33,9 @@ import type { RequestState } from './types' * ) * } */ -export function useDispatchApiRequest(): [ - (A) => A, - 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 @@ -87,13 +86,15 @@ export function useDispatchApiRequest(): [ * ) * } */ -export function useDispatchApiRequests( - onDispatch: (A => void) | null = null +export function useDispatchApiRequests< + A: { ...Action, meta: { requestId: string } } +>( + onDispatchedRequest: (A => void) | null ): [(...Array) => void, Array] { const [dispatchRequest, requestIds] = useDispatchApiRequest() const trackedRequestId = useRef(null) - const unrequestedQueue = useRef>([]) + const [unrequestedQueue, setUnrequestedQueue] = useState>([]) const trackedRequestIsPending = useSelector(state => @@ -102,21 +103,17 @@ export function useDispatchApiRequests( : null )?.status === PENDING - const triggerNext = () => { - console.log('in trigger', unrequestedQueue.current) - const action = dispatchRequest(unrequestedQueue.current[0]) - if (onDispatch) onDispatch(action) + if (unrequestedQueue.length > 0 && !trackedRequestIsPending) { + console.log('in trigger', unrequestedQueue, trackedRequestId) + const action = dispatchRequest(unrequestedQueue[0]) + if (onDispatchedRequest) onDispatchedRequest(action) trackedRequestId.current = action.meta.requestId - unrequestedQueue.current = unrequestedQueue.current.slice(1) // dequeue - } - if (unrequestedQueue.current.length > 0 && !trackedRequestIsPending) { - triggerNext() + setUnrequestedQueue(unrequestedQueue.slice(1)) } const dispatchApiRequests = (...a: Array) => { - unrequestedQueue.current = a console.log('in dars', a) - triggerNext() + 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/types.js b/app/src/sessions/types.js index ac5e513e52b..f09f9f11bc7 100644 --- a/app/src/sessions/types.js +++ b/app/src/sessions/types.js @@ -71,7 +71,7 @@ export type CalibrationSessionStep = // TODO(al, 2020-05-11): data should be properly typed with all // known command types -export type SessionCommandData = { vector: VectorTuple } | {} +export type SessionCommandData = {| vector: VectorTuple |} | {||} export type SessionCommandParams = { command: SessionCommandString, data?: SessionCommandData, From 791064dbe171605ec73665a27dd7bfe572c0a161 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 16 Sep 2020 19:05:11 -0400 Subject: [PATCH 05/13] fix(app): make cal commands chain instead of race In order to avoid various race conditions created by sequential commands being requested of the robot during/before/after calibration sessions, create mechanism to ensure that they are dispatched sequentially. Also ensure that wizard only closes on successful deletion, to avoid orphaned session. Closes #6535 --- .../__tests__/CalibratePipetteOffset.test.js | 21 +++--- .../CalibratePipetteOffset/index.js | 15 ----- .../CalibratePipetteOffset/types.js | 2 - .../components/CalibrationPanels/DeckSetup.js | 4 +- .../CalibrationPanels/Introduction.js | 4 +- .../CalibrationPanels/SaveXYPoint.js | 6 +- .../CalibrationPanels/SaveZPoint.js | 6 +- .../CalibrationPanels/TipConfirmation.js | 6 +- .../components/CalibrationPanels/TipPickUp.js | 6 +- .../__tests__/CompleteConfirmation.test.js | 20 ++---- .../__tests__/DeckSetup.test.js | 12 ++-- .../__tests__/Introduction.test.js | 18 ++--- .../__tests__/SaveXYPoint.test.js | 65 +++++++++---------- .../__tests__/SaveZPoint.test.js | 31 ++++----- .../__tests__/TipConfirmation.test.js | 18 ++--- .../__tests__/TipPickUp.test.js | 21 +++--- app/src/components/CalibrationPanels/types.js | 4 +- .../PipetteOffsetCalibrationControl.js | 4 +- app/src/robot-api/hooks.js | 5 +- .../createSessionCommandEpic.test.js | 2 +- app/src/sessions/types.js | 3 +- 21 files changed, 118 insertions(+), 155 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js index e6b7a1ab713..ae9c8068ae7 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,14 +86,15 @@ describe('CalibratePipetteOffset', () => { ...mockPipetteOffsetCalibrationSessionAttributes, } - render = () => { + render = (props = {}) => { + const { showSpinner = false } = props return mount( , { wrappingComponent: Provider, @@ -144,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 not shown', () => { + const wrapper = render({ showSpinner: false }) expect(wrapper.find('SpinnerModalPage').exists()).toBe(false) + }) - mockGetRequestById.mockReturnValue({ status: 'pending' }) - wrapper.setProps({}) + it('renders spinner when last tracked request is pending', () => { + 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 19fc783f9b5..3876f5fd9b3 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -1,7 +1,6 @@ // @flow // Pipette Offset Calibration Orchestration Component import * as React from 'react' -import { useSelector, useDispatch } from 'react-redux' import { getPipetteModelSpecs } from '@opentrons/shared-data' import { @@ -10,20 +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 { - useDispatchApiRequests, - getRequestById, - PENDING, -} from '../../robot-api' -import type { RequestState } from '../../robot-api/types' import { Introduction, DeckSetup, @@ -39,11 +29,6 @@ import styles from '../CalibrateDeck/styles.css' import type { CalibratePipetteOffsetParentProps } from './types' import type { CalibrationPanelProps } from '../CalibrationPanels/types' -// session commands for which the full page spinner should not appear -const spinnerCommandBlockList: Array = [ - Sessions.sharedCalCommands.JOG, -] - const PIPETTE_OFFSET_CALIBRATION_SUBTITLE = 'Pipette offset calibration' const EXIT = 'exit' diff --git a/app/src/components/CalibratePipetteOffset/types.js b/app/src/components/CalibratePipetteOffset/types.js index d4e71b5f15d..1c798a17ca0 100644 --- a/app/src/components/CalibratePipetteOffset/types.js +++ b/app/src/components/CalibratePipetteOffset/types.js @@ -1,8 +1,6 @@ // @flow import type { Action } from '../../types' import type { - SessionCommandString, - SessionCommandData, SessionCommandParams, PipetteOffsetCalibrationSession, } from '../../sessions/types' diff --git a/app/src/components/CalibrationPanels/DeckSetup.js b/app/src/components/CalibrationPanels/DeckSetup.js index bacca2eb77b..008ab7e31cc 100644 --- a/app/src/components/CalibrationPanels/DeckSetup.js +++ b/app/src/components/CalibrationPanels/DeckSetup.js @@ -31,10 +31,10 @@ const DECK_SETUP_BUTTON_TEXT = 'Confirm placement and continue' export function DeckSetup(props: CalibrationPanelProps): React.Node { const deckDef = React.useMemo(() => getDeckDefinitions()['ot2_standard'], []) - const { tipRack, sendSessionCommand } = props + const { tipRack, sendCommands } = props const proceed = () => { - sendSessionCommand({ command: 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 565844b06c2..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({ command: 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 fb97b495281..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,7 +136,7 @@ export function SaveXYPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand({ + sendCommands({ command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), @@ -150,7 +150,7 @@ export function SaveXYPoint(props: CalibrationPanelProps): React.Node { if (moveCommandString) { commands = [...commands, { command: moveCommandString }] } - sendSessionCommand(...commands) + sendCommands(...commands) } return ( diff --git a/app/src/components/CalibrationPanels/SaveZPoint.js b/app/src/components/CalibrationPanels/SaveZPoint.js index 2353fc98281..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,7 +79,7 @@ export function SaveZPoint(props: CalibrationPanelProps): React.Node { ) const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand({ + sendCommands({ command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), @@ -88,7 +88,7 @@ export function SaveZPoint(props: CalibrationPanelProps): React.Node { } const savePoint = () => { - sendSessionCommand( + sendCommands( { command: Sessions.sharedCalCommands.SAVE_OFFSET }, { command: Sessions.sharedCalCommands.MOVE_TO_POINT_ONE } ) diff --git a/app/src/components/CalibrationPanels/TipConfirmation.js b/app/src/components/CalibrationPanels/TipConfirmation.js index c1614a5de37..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({ command: Sessions.sharedCalCommands.INVALIDATE_TIP }) + sendCommands({ command: Sessions.sharedCalCommands.INVALIDATE_TIP }) } const confirmTip = () => { - sendSessionCommand({ command: 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 25956839024..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,11 +70,11 @@ export function TipPickUp(props: CalibrationPanelProps): React.Node { ) const pickUpTip = () => { - sendSessionCommand({ command: Sessions.sharedCalCommands.PICK_UP_TIP }) + sendCommands({ command: Sessions.sharedCalCommands.PICK_UP_TIP }) } const jog = (axis: JogAxis, dir: JogDirection, step: JogStep) => { - sendSessionCommand({ + sendCommands({ command: Sessions.sharedCalCommands.JOG, data: { vector: formatJogVector(axis, dir, step), diff --git a/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js b/app/src/components/CalibrationPanels/__tests__/CompleteConfirmation.test.js index f6b7e1bf66b..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, - cleanUpAndExit = mockDeleteSession, + sendCommands = mockSendCommands, + cleanUpAndExit = mockCleanUpAndExit, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, } = props @@ -34,7 +33,7 @@ describe('CompleteConfirmation', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 3a8b4527562..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,7 +25,7 @@ describe('DeckSetup', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_LABWARE_LOADED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -35,7 +35,7 @@ describe('DeckSetup', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 fd6aef8c766..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,7 +27,7 @@ describe('Introduction', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SESSION_STARTED, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -37,7 +37,7 @@ describe('Introduction', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 a0ac0a8ace6..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,13 +27,12 @@ describe('SaveXYPoint', () => { const getVideo = wrapper => wrapper.find(`source`) beforeEach(() => { - jest.useFakeTimers() render = (props = {}) => { const { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_SAVING_POINT_ONE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -43,7 +42,7 @@ describe('SaveXYPoint', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 5b58b3a8eb8..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,13 +20,12 @@ describe('SaveZPoint', () => { const getVideo = wrapper => wrapper.find(`source`) beforeEach(() => { - jest.useFakeTimers() render = (props = {}) => { const { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_JOGGING_TO_DECK, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -36,7 +35,7 @@ describe('SaveZPoint', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 75a7a8c9859..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,7 +26,7 @@ describe('TipConfirmation', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_INSPECTING_TIP, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -36,7 +36,7 @@ describe('TipConfirmation', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 66f80b5864e..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,7 +24,7 @@ describe('TipPickUp', () => { pipMount = 'left', isMulti = false, tipRack = mockDeckCalTipRack, - sendSessionCommand = mockSendCommand, + sendCommands = mockSendCommands, cleanUpAndExit = mockDeleteSession, currentStep = Sessions.DECK_STEP_PREPARING_PIPETTE, sessionType = Sessions.SESSION_TYPE_DECK_CALIBRATION, @@ -34,7 +34,7 @@ describe('TipPickUp', () => { isMulti={isMulti} mount={pipMount} tipRack={tipRack} - sendSessionCommand={sendSessionCommand} + 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 28535436c4c..c5e69121cb4 100644 --- a/app/src/components/CalibrationPanels/types.js +++ b/app/src/components/CalibrationPanels/types.js @@ -1,7 +1,5 @@ // @flow import type { - SessionCommandString, - SessionCommandData, SessionCommandParams, SessionType, CalibrationSessionStep, @@ -11,7 +9,7 @@ import type { PipetteOffsetCalibrationLabware } from '../../sessions/pipette-off import type { TipLengthCalibrationLabware } from '../../sessions/tip-length-calibration/types' export type CalibrationPanelProps = {| - sendSessionCommand: (...Array) => void, + sendCommands: (...Array) => void, cleanUpAndExit: () => void, tipRack: | DeckCalibrationLabware diff --git a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js index f65a5104dee..87f684b1d67 100644 --- a/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js +++ b/app/src/components/InstrumentSettings/PipetteOffsetCalibrationControl.js @@ -47,7 +47,7 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { } if ( dispatchedAction.type === Sessions.DELETE_SESSION && - pipOffsetCalSession?.id == dispatchedAction.payload.sessionId + pipOffsetCalSession?.id === dispatchedAction.payload.sessionId ) { deleteRequestId.current = dispatchedAction.meta.requestId } @@ -81,7 +81,7 @@ export function PipetteOffsetCalibrationControl(props: Props): React.Node { setShowWizard(false) deleteRequestId.current = null } - }, [requestStatus]) + }, [requestStatus, shouldClose]) const handleStartPipOffsetCalSession = () => { dispatchRequests( diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 337dac80715..0166b03a011 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -1,9 +1,8 @@ // @flow // hooks for components that depend on API state -import { useReducer, useCallback, useEffect, useRef, useState } from 'react' +import { useReducer, useCallback, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import uniqueId from 'lodash/uniqueId' -import last from 'lodash/last' import type { State, Action } from '../types' import { PENDING } from './constants' @@ -104,7 +103,6 @@ export function useDispatchApiRequests< )?.status === PENDING if (unrequestedQueue.length > 0 && !trackedRequestIsPending) { - console.log('in trigger', unrequestedQueue, trackedRequestId) const action = dispatchRequest(unrequestedQueue[0]) if (onDispatchedRequest) onDispatchedRequest(action) trackedRequestId.current = action.meta.requestId @@ -112,7 +110,6 @@ export function useDispatchApiRequests< } const dispatchApiRequests = (...a: Array) => { - console.log('in dars', a) setUnrequestedQueue([...unrequestedQueue, ...a]) } 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 f09f9f11bc7..1c10fef5b0c 100644 --- a/app/src/sessions/types.js +++ b/app/src/sessions/types.js @@ -69,6 +69,8 @@ export type CalibrationSessionStep = | DeckCalTypes.DeckCalibrationStep | PipOffsetCalTypes.PipetteOffsetCalibrationStep +export type VectorTuple = [number, number, number] + // TODO(al, 2020-05-11): data should be properly typed with all // known command types export type SessionCommandData = {| vector: VectorTuple |} | {||} @@ -347,7 +349,6 @@ export type CalibrationCheckIntercomData = {| succeeded: boolean, |} -export type VectorTuple = [number, number, number] export type CalibrationCheckAnalyticsData = {| ...CalibrationCheckCommonEventData, comparingFirstPipetteHeightDifferenceVector?: VectorTuple, From 4334ffb2369c3bf9c304d100da81ce35c7de678b Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 16 Sep 2020 19:20:02 -0400 Subject: [PATCH 06/13] clarify test name --- .../__tests__/CalibratePipetteOffset.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js index ae9c8068ae7..5fe639a053f 100644 --- a/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js +++ b/app/src/components/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.js @@ -138,12 +138,12 @@ describe('CalibratePipetteOffset', () => { expect(wrapper.find('ConfirmExitModal').exists()).toBe(true) }) - it('does not render spinner when not shown', () => { + it('does not render spinner when showSpinner is false', () => { const wrapper = render({ showSpinner: false }) expect(wrapper.find('SpinnerModalPage').exists()).toBe(false) }) - it('renders spinner when last tracked request is pending', () => { + it('renders spinner when showSpinner is true', () => { const wrapper = render({ showSpinner: true }) expect(wrapper.find('SpinnerModalPage').exists()).toBe(true) }) From 98ca27a55d541e07741a78eb1035a98375012333 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Wed, 16 Sep 2020 19:22:10 -0400 Subject: [PATCH 07/13] fix prop name --- app/src/components/CalibratePipetteOffset/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/components/CalibratePipetteOffset/index.js b/app/src/components/CalibratePipetteOffset/index.js index 3876f5fd9b3..74e6a702ee9 100644 --- a/app/src/components/CalibratePipetteOffset/index.js +++ b/app/src/components/CalibratePipetteOffset/index.js @@ -129,7 +129,7 @@ export function CalibratePipetteOffset( contentsClassName={PANEL_STYLE_BY_STEP[currentStep]} > Date: Wed, 16 Sep 2020 19:24:18 -0400 Subject: [PATCH 08/13] remove comment --- app/src/sessions/types.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/sessions/types.js b/app/src/sessions/types.js index 1c10fef5b0c..7693a3b5f3c 100644 --- a/app/src/sessions/types.js +++ b/app/src/sessions/types.js @@ -71,8 +71,6 @@ export type CalibrationSessionStep = export type VectorTuple = [number, number, number] -// TODO(al, 2020-05-11): data should be properly typed with all -// known command types export type SessionCommandData = {| vector: VectorTuple |} | {||} export type SessionCommandParams = { command: SessionCommandString, From 39b67ea43c588d7854280bb0613a1f6332385112 Mon Sep 17 00:00:00 2001 From: Brian Arthur Cooper Date: Thu, 17 Sep 2020 12:32:17 -0400 Subject: [PATCH 09/13] Update documentation comment for new hook Co-authored-by: Seth Foster --- app/src/robot-api/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 0166b03a011..48207ab88a5 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -79,7 +79,7 @@ export function useDispatchApiRequest< * const [dispatchRequests, requestIds] = useDispatchApiRequest() * * return ( - * * ) From d44209ac4fc3a303b7c32a4bc4bde784e87ea979 Mon Sep 17 00:00:00 2001 From: Brian Arthur Cooper Date: Thu, 17 Sep 2020 12:32:42 -0400 Subject: [PATCH 10/13] update example in hook comment Co-authored-by: Seth Foster --- app/src/robot-api/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/robot-api/hooks.js b/app/src/robot-api/hooks.js index 48207ab88a5..e5fd9bdefd3 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -76,7 +76,7 @@ export function useDispatchApiRequest< * * export function FetchPipettesButton(props: Props) { * const { robotName } = props - * const [dispatchRequests, requestIds] = useDispatchApiRequest() + * const [dispatchRequests, requestIds] = 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 e5fd9bdefd3..2f604a9e8dd 100644 --- a/app/src/robot-api/hooks.js +++ b/app/src/robot-api/hooks.js @@ -88,7 +88,7 @@ export function useDispatchApiRequest< export function useDispatchApiRequests< A: { ...Action, meta: { requestId: string } } >( - onDispatchedRequest: (A => void) | null + onDispatchedRequest: (A => void) | null = null ): [(...Array) => void, Array] { const [dispatchRequest, requestIds] = useDispatchApiRequest() @@ -96,11 +96,11 @@ export function useDispatchApiRequests< const [unrequestedQueue, setUnrequestedQueue] = useState>([]) const trackedRequestIsPending = - useSelector(state => - trackedRequestId.current + useSelector(state => { + return trackedRequestId.current ? getRequestById(state, trackedRequestId.current) : null - )?.status === PENDING + })?.status === PENDING if (unrequestedQueue.length > 0 && !trackedRequestIsPending) { const action = dispatchRequest(unrequestedQueue[0]) From 46c4993792ea0100b1af85e7b4bedd80615e1329 Mon Sep 17 00:00:00 2001 From: Brian Cooper Date: Thu, 17 Sep 2020 16:47:38 -0400 Subject: [PATCH 12/13] remove unused imports --- app/src/robot-api/__tests__/hooks.test.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/robot-api/__tests__/hooks.test.js b/app/src/robot-api/__tests__/hooks.test.js index ffbc44990fa..ca07c7dc1b8 100644 --- a/app/src/robot-api/__tests__/hooks.test.js +++ b/app/src/robot-api/__tests__/hooks.test.js @@ -1,14 +1,10 @@ // @flow import * as React from 'react' import uniqueId from 'lodash/uniqueId' -import { mount } from 'enzyme' import { mountWithStore } from '@opentrons/components/__utils__' import { PENDING, SUCCESS } from '../constants' import { useDispatchApiRequest, useDispatchApiRequests } from '../hooks' -import type { State } from '../../types' -import { initializeState } from '@opentrons/discovery-client/src/store' - jest.mock('lodash/uniqueId') const mockUniqueId: JestMockFn<[string | void], string> = uniqueId @@ -52,7 +48,7 @@ describe('useDispatchApiRequest', () => { }) it('adds requestId to requestIds list', () => { - const { wrapper, store } = render() + const { wrapper } = render() wrapper.find('button').invoke('onClick')() wrapper.update() @@ -77,7 +73,7 @@ describe('useDispatchApiRequests', () => { const TestUseDispatchApiRequests = props => { const mockAction: any = { type: 'mockAction', meta: {} } const mockOtherAction: any = { type: 'mockOtherAction', meta: {} } - const [dispatchRequests, requestIds] = useDispatchApiRequests() + const [dispatchRequests] = useDispatchApiRequests() return (