Skip to content

Commit

Permalink
fix(app): Fix "retry new tips" during Error Recovery overpressure flow (
Browse files Browse the repository at this point in the history
  • Loading branch information
mjhuff authored Aug 14, 2024
1 parent 1b29655 commit afba276
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 23 deletions.
39 changes: 26 additions & 13 deletions app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function useDropTipCommands({
const [hasSeenClose, setHasSeenClose] = React.useState(false)
const [jogQueue, setJogQueue] = React.useState<Array<() => Promise<void>>>([])
const [isJogging, setIsJogging] = React.useState(false)
const pipetteId = fixitCommandTypeUtils?.pipetteId ?? null

const { deleteMaintenanceRun } = useDeleteMaintenanceRunMutation()
const deckConfig = useNotifyDeckConfigurationQuery().data ?? []
Expand Down Expand Up @@ -110,7 +111,10 @@ export function useDropTipCommands({
)

if (addressableAreaFromConfig != null) {
const moveToAACommand = buildMoveToAACommand(addressableAreaFromConfig)
const moveToAACommand = buildMoveToAACommand(
addressableAreaFromConfig,
pipetteId
)
return chainRunCommands(
isFlex
? [UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand]
Expand Down Expand Up @@ -152,7 +156,11 @@ export function useDropTipCommands({
return runCommand({
command: {
commandType: 'moveRelative',
params: { pipetteId: MANAGED_PIPETTE_ID, distance: step * dir, axis },
params: {
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,
distance: step * dir,
axis,
},
},
waitUntilComplete: true,
timeout: JOG_COMMAND_TIMEOUT_MS,
Expand Down Expand Up @@ -204,8 +212,8 @@ export function useDropTipCommands({
return new Promise((resolve, reject) => {
chainRunCommands(
currentStep === POSITION_AND_BLOWOUT
? buildBlowoutCommands(instrumentModelSpecs, isFlex)
: buildDropTipInPlaceCommand(isFlex),
? buildBlowoutCommands(instrumentModelSpecs, isFlex, pipetteId)
: buildDropTipInPlaceCommand(isFlex, pipetteId),
true
)
.then((commandData: CommandData[]) => {
Expand Down Expand Up @@ -291,32 +299,35 @@ const UPDATE_ESTIMATORS_EXCEPT_PLUNGERS: CreateCommand = {
}

const buildDropTipInPlaceCommand = (
isFlex: boolean
isFlex: boolean,
pipetteId: string | null
): Array<DropTipInPlaceCreateCommand | UnsafeDropTipInPlaceCreateCommand> =>
isFlex
? [
{
commandType: 'unsafe/dropTipInPlace',
params: { pipetteId: MANAGED_PIPETTE_ID },
params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID },
},
]
: [
{
commandType: 'dropTipInPlace',
params: { pipetteId: MANAGED_PIPETTE_ID },
params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID },
},
]

const buildBlowoutCommands = (
specs: PipetteModelSpecs,
isFlex: boolean
isFlex: boolean,
pipetteId: string | null
): CreateCommand[] =>
isFlex
? [
{
commandType: 'unsafe/blowOutInPlace',
params: {
pipetteId: MANAGED_PIPETTE_ID,
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,

flowRate: Math.min(
specs.defaultBlowOutFlowRate.value,
MAXIMUM_BLOWOUT_FLOW_RATE_UL_PER_S
Expand All @@ -326,27 +337,29 @@ const buildBlowoutCommands = (
{
commandType: 'prepareToAspirate',
params: {
pipetteId: MANAGED_PIPETTE_ID,
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,
},
},
]
: [
{
commandType: 'blowOutInPlace',
params: {
pipetteId: MANAGED_PIPETTE_ID,
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,

flowRate: specs.defaultBlowOutFlowRate.value,
},
},
]

const buildMoveToAACommand = (
addressableAreaFromConfig: AddressableAreaName
addressableAreaFromConfig: AddressableAreaName,
pipetteId: string | null
): CreateCommand => {
return {
commandType: 'moveToAddressableArea',
params: {
pipetteId: MANAGED_PIPETTE_ID,
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,
stayAtHighestPossibleZ: true,
addressableAreaName: addressableAreaFromConfig,
offset: { x: 0, y: 0, z: 0 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import type { FixitCommandTypeUtils } from '../../DropTipWizardFlows/types'
export function ManageTips(props: RecoveryContentProps): JSX.Element {
const { recoveryMap } = props

routeAlternativelyIfNoPipette(props)

const buildContent = (): JSX.Element => {
const { DROP_TIP_FLOWS } = RECOVERY_MAP
const { step, route } = recoveryMap
Expand Down Expand Up @@ -320,3 +322,41 @@ export function useDropTipFlowUtils({
reportMap: updateSubMap,
}
}

// Handle cases in which there is no pipette that could be used for drop tip wizard by routing
// to the next step or to option selection, if no special routing is provided.
function routeAlternativelyIfNoPipette(props: RecoveryContentProps): void {
const {
routeUpdateActions,
currentRecoveryOptionUtils,
tipStatusUtils,
} = props
const { proceedToRouteAndStep } = routeUpdateActions
const { selectedRecoveryOption } = currentRecoveryOptionUtils
const {
RETRY_NEW_TIPS,
SKIP_STEP_WITH_NEW_TIPS,
OPTION_SELECTION,
} = RECOVERY_MAP

if (tipStatusUtils.aPipetteWithTip == null)
switch (selectedRecoveryOption) {
case RETRY_NEW_TIPS.ROUTE: {
proceedToRouteAndStep(
selectedRecoveryOption,
RETRY_NEW_TIPS.STEPS.REPLACE_TIPS
)
break
}
case SKIP_STEP_WITH_NEW_TIPS.ROUTE: {
proceedToRouteAndStep(
selectedRecoveryOption,
SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS
)
break
}
default: {
proceedToRouteAndStep(OPTION_SELECTION.ROUTE)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,59 @@ describe('ManageTips', () => {

screen.getByText('MOCK DROP TIP FLOWS')
})

describe('routeAlternativelyIfNoPipette', () => {
it('should route to RETRY_NEW_TIPS.STEPS.REPLACE_TIPS when selectedRecoveryOption is RETRY_NEW_TIPS.ROUTE and no pipette with tip', () => {
props.tipStatusUtils.aPipetteWithTip = null
props.currentRecoveryOptionUtils.selectedRecoveryOption =
RETRY_NEW_TIPS.ROUTE

render(props)

expect(mockProceedToRouteAndStep).toHaveBeenCalledWith(
RETRY_NEW_TIPS.ROUTE,
RETRY_NEW_TIPS.STEPS.REPLACE_TIPS
)
})

it('should route to SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS when selectedRecoveryOption is SKIP_STEP_WITH_NEW_TIPS.ROUTE and no pipette with tip', () => {
props.tipStatusUtils.aPipetteWithTip = null
props.currentRecoveryOptionUtils.selectedRecoveryOption =
RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE

render(props)

expect(mockProceedToRouteAndStep).toHaveBeenCalledWith(
RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE,
RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS
)
})

it('should route to OPTION_SELECTION.ROUTE when selectedRecoveryOption is not RETRY_NEW_TIPS or SKIP_STEP_WITH_NEW_TIPS and no pipette with tip', () => {
props.tipStatusUtils.aPipetteWithTip = null
props.currentRecoveryOptionUtils.selectedRecoveryOption =
RECOVERY_MAP.CANCEL_RUN.ROUTE

render(props)

expect(mockProceedToRouteAndStep).toHaveBeenCalledWith(
RECOVERY_MAP.OPTION_SELECTION.ROUTE
)
})

it('should not route alternatively when there is a pipette with tip', () => {
props.tipStatusUtils.aPipetteWithTip = {
mount: 'left',
specs: MOCK_ACTUAL_PIPETTE,
}
props.currentRecoveryOptionUtils.selectedRecoveryOption =
RETRY_NEW_TIPS.ROUTE

render(props)

expect(mockProceedToRouteAndStep).not.toHaveBeenCalled()
})
})
})

describe('useDropTipFlowUtils', () => {
Expand Down
13 changes: 7 additions & 6 deletions app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,17 @@ export function useERUtils({
robotType,
})

const failedPipetteInfo = getFailedCommandPipetteInfo({
failedCommandByRunRecord,
runRecord,
attachedInstruments,
})

const tipStatusUtils = useRecoveryTipStatus({
runId,
runRecord,
attachedInstruments,
failedPipetteInfo,
})

const routeUpdateActions = useRouteUpdateActions({
Expand All @@ -118,12 +125,6 @@ export function useERUtils({
setRecoveryMap: setRM,
})

const failedPipetteInfo = getFailedCommandPipetteInfo({
failedCommandByRunRecord,
runRecord,
attachedInstruments,
})

const failedLabwareUtils = useFailedLabwareUtils({
failedCommandByRunRecord,
protocolAnalysis,
Expand Down
43 changes: 40 additions & 3 deletions app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import * as React from 'react'
import head from 'lodash/head'

import { useHost } from '@opentrons/react-api-client'
import { getPipetteModelSpecs } from '@opentrons/shared-data'

import { useTipAttachmentStatus } from '../../DropTipWizardFlows'

import type { Run, Instruments } from '@opentrons/api-client'
import type { Run, Instruments, PipetteData } from '@opentrons/api-client'
import type {
TipAttachmentStatusResult,
PipetteWithTip,
} from '../../DropTipWizardFlows'

interface UseRecoveryTipStatusProps {
runId: string
failedPipetteInfo: PipetteData | null
attachedInstruments?: Instruments
runRecord?: Run
}
Expand All @@ -27,6 +30,10 @@ export function useRecoveryTipStatus(
props: UseRecoveryTipStatusProps
): RecoveryTipStatusUtils {
const [isLoadingTipStatus, setIsLoadingTipStatus] = React.useState(false)
const [
failedCommandPipette,
setFailedCommandPipette,
] = React.useState<PipetteWithTip | null>(null)
const host = useHost()

const tipAttachmentStatusUtils = useTipAttachmentStatus({
Expand All @@ -37,17 +44,47 @@ export function useRecoveryTipStatus(

const determineTipStatusWithLoading = (): Promise<PipetteWithTip[]> => {
const { determineTipStatus } = tipAttachmentStatusUtils
const { failedPipetteInfo } = props
setIsLoadingTipStatus(true)

return determineTipStatus().then(pipettesWithTips => {
return determineTipStatus().then(pipettesWithTip => {
// In cases in which determineTipStatus doesn't think a tip could be attached to any pipette, supply the pipette
// involved in the failed command, if any.
let failedCommandPipettes: PipetteWithTip[]
const specs =
failedPipetteInfo != null
? getPipetteModelSpecs(failedPipetteInfo.instrumentModel)
: null

if (
pipettesWithTip.length === 0 &&
failedPipetteInfo != null &&
specs != null
) {
const currentPipette: PipetteWithTip = {
mount: failedPipetteInfo.mount,
specs,
}

failedCommandPipettes = [currentPipette]
} else {
failedCommandPipettes = pipettesWithTip
}

setIsLoadingTipStatus(false)
setFailedCommandPipette(head(failedCommandPipettes) ?? null)
console.log(
'=>(useRecoveryTipStatus.ts:76) failedCommandPipettes',
failedCommandPipettes
)

return Promise.resolve(pipettesWithTips)
return Promise.resolve(pipettesWithTip)
})
}

return {
...tipAttachmentStatusUtils,
aPipetteWithTip: failedCommandPipette,
determineTipStatus: determineTipStatusWithLoading,
isLoadingTipStatus,
runId: props.runId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ const STYLE = css`
width: 100%;
height: 100%;
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
gap: none;
gap: 0;
}
`

0 comments on commit afba276

Please sign in to comment.