Skip to content

Commit

Permalink
fix(app): fix estimator update command failure copy during drop tip (#…
Browse files Browse the repository at this point in the history
…16884)

Closes RQA-3583

Currently, the app inspects an errorType on a failed drop tip command to determine whether to throw the special case "home the gantry" error modal. Recently, the error type changed for failed update estimator commands, so the special modal was broken. Instead of special-casing the errorType for failed estimator commands, let's just assume that any failed estimator command is caused by an unknown position error and pop the special modal.

Note that this also handles any error in the same manner with a "MustHomeError" errorType, too.
  • Loading branch information
mjhuff authored Nov 19, 2024
1 parent 31a819d commit 888a0ab
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ describe('useDropTipCommandErrors', () => {

act(() => {
result.current({
runCommandError: {
errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR,
} as any,
type: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR,
message: 'remove_the_tips_manually',
header: 'cant_safely_drop_tips',
})
})

Expand Down
15 changes: 3 additions & 12 deletions app/src/organisms/DropTipWizardFlows/hooks/errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import type { RunCommandError } from '@opentrons/shared-data'
import type { ErrorDetails } from '../types'

export interface SetRobotErrorDetailsParams {
runCommandError?: RunCommandError
message?: string
message: string | null
header?: string
type?: RunCommandError['errorType']
}
Expand All @@ -23,16 +22,8 @@ export function useDropTipCommandErrors(
): (cbProps: SetRobotErrorDetailsParams) => void {
const { t } = useTranslation('drop_tip_wizard')

return ({
runCommandError,
message,
header,
type,
}: SetRobotErrorDetailsParams) => {
if (
runCommandError?.errorType ===
DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR
) {
return ({ message, header, type }: SetRobotErrorDetailsParams) => {
if (type === DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR) {
const headerText = t('cant_safely_drop_tips')
const messageText = t('remove_the_tips_manually')

Expand Down
146 changes: 87 additions & 59 deletions app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { useState, useEffect } from 'react'

import { useDeleteMaintenanceRunMutation } from '@opentrons/react-api-client'

import { DT_ROUTES, MANAGED_PIPETTE_ID } from '../constants'
import {
DROP_TIP_SPECIAL_ERROR_TYPES,
DT_ROUTES,
MANAGED_PIPETTE_ID,
} from '../constants'
import { getAddressableAreaFromConfig } from '../utils'
import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration'
import type {
CreateCommand,
AddressableAreaName,
PipetteModelSpecs,
RunCommandError,
} from '@opentrons/shared-data'
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'
import type { CommandData, PipetteData } from '@opentrons/api-client'
Expand Down Expand Up @@ -129,17 +134,25 @@ export function useDropTipCommands({
issuedCommandsType
)

return chainRunCommands(
isFlex
? [
ENGAGE_AXES,
UPDATE_ESTIMATORS_EXCEPT_PLUNGERS,
Z_HOME,
moveToAACommand,
]
: [Z_HOME, moveToAACommand],
false
)
if (isFlex) {
return chainRunCommands(
[ENGAGE_AXES, UPDATE_ESTIMATORS_EXCEPT_PLUNGERS],
false
)
.catch(error => {
// If one of the engage/estimator commands fails, we can safely assume it's because the position is
// unknown, so show the special error modal.
throw {
...error,
errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR,
}
})
.then(() => {
return chainRunCommands([Z_HOME, moveToAACommand], false)
})
} else {
return chainRunCommands([Z_HOME, moveToAACommand], false)
}
})
.then((commandData: CommandData[]) => {
const error = commandData[0].data.error
Expand All @@ -149,12 +162,12 @@ export function useDropTipCommands({
}
resolve()
})
.catch(error => {
.catch((error: RunCommandError) => {
if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') {
fixitCommandTypeUtils.errorOverrides.generalFailure()
} else {
setErrorDetails({
runCommandError: error,
type: error.errorType ?? null,
message: error.detail
? `Error moving to position: ${error.detail}`
: 'Error moving to position: invalid addressable area.',
Expand Down Expand Up @@ -224,51 +237,70 @@ export function useDropTipCommands({
proceed: () => void
): Promise<void> => {
return new Promise((resolve, reject) => {
chainRunCommands(
currentRoute === DT_ROUTES.BLOWOUT
? buildBlowoutCommands(instrumentModelSpecs, isFlex, pipetteId)
: buildDropTipInPlaceCommand(isFlex, pipetteId),
false
)
.then((commandData: CommandData[]) => {
const error = commandData[0].data.error
if (error != null) {
if (
fixitCommandTypeUtils != null &&
issuedCommandsType === 'fixit'
) {
if (currentRoute === DT_ROUTES.BLOWOUT) {
fixitCommandTypeUtils.errorOverrides.blowoutFailed()
} else {
fixitCommandTypeUtils.errorOverrides.tipDropFailed()
}
}

setErrorDetails({
runCommandError: error,
message: `Error moving to position: ${error.detail}`,
})
} else {
proceed()
resolve()
}
})
.catch((error: Error) => {
if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') {
if (currentRoute === DT_ROUTES.BLOWOUT) {
fixitCommandTypeUtils.errorOverrides.blowoutFailed()
} else {
fixitCommandTypeUtils.errorOverrides.tipDropFailed()
}
}
const isBlowoutRoute = currentRoute === DT_ROUTES.BLOWOUT

const handleError = (error: RunCommandError | Error): void => {
if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') {
isBlowoutRoute
? fixitCommandTypeUtils.errorOverrides.blowoutFailed()
: fixitCommandTypeUtils.errorOverrides.tipDropFailed()
} else {
const operation = isBlowoutRoute ? 'blowout' : 'drop tip'
const type = 'errorType' in error ? error.errorType : undefined
const messageDetail =
'message' in error ? error.message : error.detail

setErrorDetails({
message: `Error issuing ${
currentRoute === DT_ROUTES.BLOWOUT ? 'blowout' : 'drop tip'
} command: ${error.message}`,
type,
message:
messageDetail != null
? `Error during ${operation}: ${messageDetail}`
: null,
})
resolve()
}
reject(error)
}

// Throw any errors in the response body if any.
const handleSuccess = (commandData: CommandData[]): void => {
const error = commandData[0].data.error
if (error != null) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw error
}
proceed()
resolve()
}

// For Flex, we need extra preparation steps
const prepareFlexBlowout = (): Promise<CommandData[]> => {
return chainRunCommands(
[ENGAGE_AXES, UPDATE_PLUNGER_ESTIMATORS],
false
).catch(error => {
throw {
...error,
errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR,
}
})
}

const executeCommands = (): Promise<CommandData[]> => {
const commands = isBlowoutRoute
? buildBlowoutCommands(instrumentModelSpecs, isFlex, pipetteId)
: buildDropTipInPlaceCommand(isFlex, pipetteId)

return chainRunCommands(commands, false)
}

if (isBlowoutRoute && isFlex) {
prepareFlexBlowout()
.then(executeCommands)
.then(handleSuccess)
.catch(handleError)
} else {
executeCommands().then(handleSuccess).catch(handleError)
}
})
}

Expand Down Expand Up @@ -356,13 +388,10 @@ const buildBlowoutCommands = (
): CreateCommand[] =>
isFlex
? [
ENGAGE_AXES,
UPDATE_PLUNGER_ESTIMATORS,
{
commandType: 'unsafe/blowOutInPlace',
params: {
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,

flowRate: Math.min(
specs.defaultBlowOutFlowRate.value,
MAXIMUM_BLOWOUT_FLOW_RATE_UL_PER_S
Expand All @@ -376,7 +405,6 @@ const buildBlowoutCommands = (
commandType: 'blowOutInPlace',
params: {
pipetteId: pipetteId ?? MANAGED_PIPETTE_ID,

flowRate: specs.defaultBlowOutFlowRate.value,
},
},
Expand Down

0 comments on commit 888a0ab

Please sign in to comment.