Skip to content

Commit

Permalink
Merge branch 'edge' into EXEC-771-ER-manual-move-labware
Browse files Browse the repository at this point in the history
  • Loading branch information
TamarZanzouri committed Oct 15, 2024
2 parents 1876c09 + a3826db commit 3b5d342
Show file tree
Hide file tree
Showing 36 changed files with 1,202 additions and 172 deletions.
10 changes: 6 additions & 4 deletions app/src/assets/localization/en/error_recovery.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
"change_tip_pickup_location": "Change tip pick-up location",
"choose_a_recovery_action": "Choose a recovery action",
"close_door_to_resume": "Close robot door to resume",
"close_robot_door": "Close the robot door",
"close_the_robot_door": "Close the robot door, and then resume the recovery action.",
"confirm": "Confirm",
"continue": "Continue",
"continue_run_now": "Continue run now",
"continue_to_drop_tip": "Continue to drop tip",
"ensure_lw_is_accurately_placed": "Ensure labware is accurately placed in the slot to prevent further errors",
"door_open_gripper_home": "The robot door must be closed for the gripper to home its Z-axis before you can continue manually moving labware.",
"ensure_lw_is_accurately_placed": "Ensure labware is accurately placed in the slot to prevent further errors.",
"error": "Error",
"error_details": "Error details",
"error_on_robot": "Error on {{robot}}",
Expand All @@ -38,7 +40,7 @@
"ignore_error_and_skip": "Ignore error and skip to next step",
"ignore_only_this_error": "Ignore only this error",
"ignore_similar_errors_later_in_run": "Ignore similar errors later in the run?",
"labware_released_from_current_height": "The labware will be released from its current height",
"labware_released_from_current_height": "The labware will be released from its current height.",
"launch_recovery_mode": "Launch Recovery Mode",
"manually_fill_liquid_in_well": "Manually fill liquid in well {{well}}",
"manually_fill_well_and_skip": "Manually fill well and skip to next step",
Expand All @@ -63,8 +65,8 @@
"remove_any_attached_tips": "Remove any attached tips",
"replace_tips_and_select_loc_partial_tip": "Replace tips and select the last location used for partial tip pickup.",
"replace_tips_and_select_location": "It's best to replace tips and select the last location used for tip pickup.",
"replace_used_tips_in_rack_location": "Replace used tips in rack location {{location}} in slot {{slot}}",
"replace_with_new_tip_rack": "Replace with new tip rack in slot {{slot}}",
"replace_used_tips_in_rack_location": "Replace used tips in rack location {{location}} in Slot {{slot}}",
"replace_with_new_tip_rack": "Replace with new tip rack in Slot {{slot}}",
"resume": "Resume",
"retry_now": "Retry now",
"retry_step": "Retry step",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
} from '@opentrons/shared-data'
import type { CommandTextData } from '../types'

// TODO(jh, 10-14-24): Refactor this util and related copy utils out of Command.
export function getLabwareDisplayLocation(
commandTextData: Omit<CommandTextData, 'commands'>,
allRunDefs: LabwareDefinition2[],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR,
RUN_STATUS_AWAITING_RECOVERY_PAUSED,
RUN_STATUS_BLOCKED_BY_OPEN_DOOR,
RUN_STATUS_STOPPED,
} from '@opentrons/api-client'
Expand Down Expand Up @@ -32,7 +33,8 @@ export function getShowGenericRunHeaderBanners({
isDoorOpen &&
runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR &&
runStatus !== RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR &&
isCancellableStatus(runStatus)
runStatus !== RUN_STATUS_AWAITING_RECOVERY_PAUSED
isCancellableStatus(runStatus)

const showDoorOpenDuringRunBanner =
runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR
Expand Down
13 changes: 11 additions & 2 deletions app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ import {
useErrorDetailsModal,
ErrorDetailsModal,
RecoveryInterventionModal,
RecoveryDoorOpenSpecial,
} from './shared'
import { RecoveryInProgress } from './RecoveryInProgress'
import { getErrorKind } from './utils'
import { RECOVERY_MAP } from './constants'
import { useHomeGripperZAxis } from './hooks'

import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data'
import type { RecoveryRoute, RouteStep, RecoveryContentProps } from './types'
import type { ERUtilsResults, useRetainedFailedCommandBySource } from './hooks'
import type { ErrorRecoveryFlowsProps } from '.'
import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics'
import type { ERUtilsResults, useRetainedFailedCommandBySource } from './hooks'

export interface UseERWizardResult {
hasLaunchedRecovery: boolean
Expand Down Expand Up @@ -88,6 +90,8 @@ export function ErrorRecoveryWizard(
routeUpdateActions,
})

useHomeGripperZAxis(props)

return <ErrorRecoveryComponent errorKind={errorKind} {...props} />
}

Expand Down Expand Up @@ -136,7 +140,6 @@ export function ErrorRecoveryComponent(
</StyledText>
)

// TODO(jh, 07-29-24): Make RecoveryDoorOpen render logic equivalent to RecoveryTakeover. Do not nest it in RecoveryWizard.
const buildInterventionContent = (): JSX.Element => {
if (isProhibitedDoorOpen) {
return <RecoveryDoorOpen {...props} />
Expand Down Expand Up @@ -233,6 +236,10 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element {
return <RecoveryDoorOpen {...props} />
}

const buildRecoveryDoorOpenSpecial = (): JSX.Element => {
return <RecoveryDoorOpenSpecial {...props} />
}

switch (props.recoveryMap.route) {
case RECOVERY_MAP.OPTION_SELECTION.ROUTE:
return buildSelectRecoveryOption()
Expand Down Expand Up @@ -260,6 +267,8 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element {
return buildManualMoveLwAndSkip()
case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE:
return buildManualReplaceLwAndRetry()
case RECOVERY_MAP.ROBOT_DOOR_OPEN_SPECIAL.ROUTE:
return buildRecoveryDoorOpenSpecial()
case RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE:
case RECOVERY_MAP.ROBOT_RESUMING.ROUTE:
case RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE:
Expand Down
77 changes: 56 additions & 21 deletions app/src/organisms/ErrorRecoveryFlows/RecoveryInProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export function RecoveryInProgress({
recoveryMap,
recoveryCommands,
routeUpdateActions,
doorStatusUtils,
currentRecoveryOptionUtils,
}: RecoveryContentProps): JSX.Element {
const {
ROBOT_CANCELING,
Expand All @@ -37,6 +39,8 @@ export function RecoveryInProgress({
recoveryMap,
recoveryCommands,
routeUpdateActions,
doorStatusUtils,
currentRecoveryOptionUtils,
})

const buildDescription = (): RobotMovingRoute => {
Expand Down Expand Up @@ -76,47 +80,78 @@ export function RecoveryInProgress({
)
}

const GRIPPER_RELEASE_COUNTDOWN_S = 5
export const GRIPPER_RELEASE_COUNTDOWN_S = 3

type UseGripperReleaseProps = Pick<
RecoveryContentProps,
'recoveryMap' | 'recoveryCommands' | 'routeUpdateActions'
| 'currentRecoveryOptionUtils'
| 'recoveryCommands'
| 'routeUpdateActions'
| 'doorStatusUtils'
| 'recoveryMap'
>

// Handles the gripper release copy and action, which operates on an interval. At T=0, release the labware then proceed
// to the next step in the active route.
// to the next step in the active route if the door is open (which should be a route to handle the door), or to the next
// CTA route if the door is closed.
export function useGripperRelease({
recoveryMap,
currentRecoveryOptionUtils,
recoveryCommands,
routeUpdateActions,
doorStatusUtils,
recoveryMap,
}: UseGripperReleaseProps): number {
const { releaseGripperJaws } = recoveryCommands
const { selectedRecoveryOption } = currentRecoveryOptionUtils
const {
proceedToRouteAndStep,
proceedNextStep,
handleMotionRouting,
stashedMap,
} = routeUpdateActions
const { isDoorOpen } = doorStatusUtils
const { MANUAL_MOVE_AND_SKIP, MANUAL_REPLACE_AND_RETRY } = RECOVERY_MAP
const [countdown, setCountdown] = useState(GRIPPER_RELEASE_COUNTDOWN_S)

const proceedToValidNextStep = (): void => {
switch (stashedMap?.route) {
case MANUAL_MOVE_AND_SKIP.ROUTE:
void proceedToRouteAndStep(
MANUAL_MOVE_AND_SKIP.ROUTE,
MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE
)
break
case MANUAL_REPLACE_AND_RETRY.ROUTE:
void proceedToRouteAndStep(
MANUAL_REPLACE_AND_RETRY.ROUTE,
MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE
)
break
default:
console.error('Unhandled post grip-release routing.')
void proceedNextStep()
if (isDoorOpen) {
switch (selectedRecoveryOption) {
case MANUAL_MOVE_AND_SKIP.ROUTE:
void proceedToRouteAndStep(
MANUAL_MOVE_AND_SKIP.ROUTE,
MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME
)
break
case MANUAL_REPLACE_AND_RETRY.ROUTE:
void proceedToRouteAndStep(
MANUAL_REPLACE_AND_RETRY.ROUTE,
MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME
)
break
default: {
console.error(
'Unhandled post grip-release routing when door is open.'
)
void proceedToRouteAndStep(RECOVERY_MAP.OPTION_SELECTION.ROUTE)
}
}
} else {
switch (selectedRecoveryOption) {
case MANUAL_MOVE_AND_SKIP.ROUTE:
void proceedToRouteAndStep(
MANUAL_MOVE_AND_SKIP.ROUTE,
MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE
)
break
case MANUAL_REPLACE_AND_RETRY.ROUTE:
void proceedToRouteAndStep(
MANUAL_REPLACE_AND_RETRY.ROUTE,
MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE
)
break
default:
console.error('Unhandled post grip-release routing.')
void proceedNextStep()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
GripperReleaseLabware,
SkipStepInfo,
TwoColLwInfoAndDeck,
RecoveryDoorOpenSpecial,
} from '../shared'
import { SelectRecoveryOption } from './SelectRecoveryOption'

Expand All @@ -20,6 +21,8 @@ export function ManualMoveLwAndSkip(props: RecoveryContentProps): JSX.Element {
return <GripperIsHoldingLabware {...props} />
case MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE:
return <GripperReleaseLabware {...props} />
case MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME:
return <RecoveryDoorOpenSpecial {...props} />
case MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE:
return <TwoColLwInfoAndDeck {...props} />
case MANUAL_MOVE_AND_SKIP.STEPS.SKIP:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
GripperReleaseLabware,
TwoColLwInfoAndDeck,
RetryStepInfo,
RecoveryDoorOpenSpecial,
} from '../shared'
import { SelectRecoveryOption } from './SelectRecoveryOption'

Expand All @@ -22,6 +23,8 @@ export function ManualReplaceLwAndRetry(
return <GripperIsHoldingLabware {...props} />
case MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE:
return <GripperReleaseLabware {...props} />
case MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME:
return <RecoveryDoorOpenSpecial {...props} />
case MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE:
return <TwoColLwInfoAndDeck {...props} />
case MANUAL_REPLACE_AND_RETRY.STEPS.RETRY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ vi.mock('../../shared', () => ({
GripperReleaseLabware: vi.fn(() => <div>MOCK_GRIPPER_RELEASE_LABWARE</div>),
TwoColLwInfoAndDeck: vi.fn(() => <div>MOCK_TWO_COL_LW_INFO_AND_DECK</div>),
SkipStepInfo: vi.fn(() => <div>MOCK_SKIP_STEP_INFO</div>),
RecoveryDoorOpenSpecial: vi.fn(() => <div>MOCK_DOOR_OPEN_SPECIAL</div>),
}))

vi.mock('../SelectRecoveryOption', () => ({
Expand Down Expand Up @@ -51,6 +52,13 @@ describe('ManualMoveLwAndSkip', () => {
screen.getByText('MOCK_GRIPPER_RELEASE_LABWARE')
})

it(`renders RecoveryDoorOpenSpecial for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME} step`, () => {
props.recoveryMap.step =
RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME
render(props)
screen.getByText('MOCK_DOOR_OPEN_SPECIAL')
})

it(`renders TwoColLwInfoAndDeck for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE} step`, () => {
props.recoveryMap.step = RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE
render(props)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ vi.mock('../../shared', () => ({
GripperReleaseLabware: vi.fn(() => <div>MOCK_GRIPPER_RELEASE_LABWARE</div>),
TwoColLwInfoAndDeck: vi.fn(() => <div>MOCK_TWO_COL_LW_INFO_AND_DECK</div>),
RetryStepInfo: vi.fn(() => <div>MOCK_RETRY_STEP_INFO</div>),
RecoveryDoorOpenSpecial: vi.fn(() => <div>MOCK_DOOR_OPEN_SPECIAL</div>),
}))

vi.mock('../SelectRecoveryOption', () => ({
Expand Down Expand Up @@ -54,6 +55,13 @@ describe('ManualReplaceLwAndRetry', () => {
screen.getByText('MOCK_GRIPPER_RELEASE_LABWARE')
})

it(`renders RecoveryDoorOpenSpecial for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME} step`, () => {
props.recoveryMap.step =
RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME
render(props)
screen.getByText('MOCK_DOOR_OPEN_SPECIAL')
})

it(`renders TwoColLwInfoAndDeck for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE} step`, () => {
props.recoveryMap.step =
RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE
Expand Down
1 change: 1 addition & 0 deletions app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export function RecoverySplash(props: RecoverySplashProps): JSX.Element | null {
const isDisabled = (): boolean => {
switch (runStatus) {
case RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR:
case RUN_STATUS_AWAITING_RECOVERY_PAUSED:
return true
default:
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,26 @@ import {
import { RecoveryInProgress } from '../RecoveryInProgress'
import { RecoveryError } from '../RecoveryError'
import { RecoveryDoorOpen } from '../RecoveryDoorOpen'
import { useErrorDetailsModal, ErrorDetailsModal } from '../shared'
import {
useErrorDetailsModal,
ErrorDetailsModal,
RecoveryDoorOpenSpecial,
} from '../shared'

import type { Mock } from 'vitest'

vi.mock('../RecoveryOptions')
vi.mock('../RecoveryInProgress')
vi.mock('../RecoveryError')
vi.mock('../RecoveryDoorOpen')
vi.mock('../hooks')
vi.mock('../shared', async importOriginal => {
const actual = await importOriginal<typeof ErrorDetailsModal>()
return {
...actual,
useErrorDetailsModal: vi.fn(),
ErrorDetailsModal: vi.fn(),
RecoveryDoorOpenSpecial: vi.fn(),
}
})
describe('useERWizard', () => {
Expand Down Expand Up @@ -181,6 +187,7 @@ describe('ErrorRecoveryContent', () => {
DROP_TIP_FLOWS,
ERROR_WHILE_RECOVERING,
ROBOT_DOOR_OPEN,
ROBOT_DOOR_OPEN_SPECIAL,
ROBOT_RELEASING_LABWARE,
MANUAL_REPLACE_AND_RETRY,
MANUAL_MOVE_AND_SKIP,
Expand Down Expand Up @@ -218,6 +225,9 @@ describe('ErrorRecoveryContent', () => {
<div>MOCK_IGNORE_ERROR_SKIP_STEP</div>
)
vi.mocked(RecoveryDoorOpen).mockReturnValue(<div>MOCK_DOOR_OPEN</div>)
vi.mocked(RecoveryDoorOpenSpecial).mockReturnValue(
<div>MOCK_DOOR_OPEN_SPECIAL</div>
)
})

it(`returns SelectRecoveryOption when the route is ${OPTION_SELECTION.ROUTE}`, () => {
Expand Down Expand Up @@ -485,6 +495,19 @@ describe('ErrorRecoveryContent', () => {

screen.getByText('MOCK_DOOR_OPEN')
})

it(`returns RecoveryDoorOpenSpecial when the route is ${ROBOT_DOOR_OPEN_SPECIAL.ROUTE}`, () => {
props = {
...props,
recoveryMap: {
...props.recoveryMap,
route: ROBOT_DOOR_OPEN_SPECIAL.ROUTE,
},
}
renderRecoveryContent(props)

screen.getByText('MOCK_DOOR_OPEN_SPECIAL')
})
})

describe('useInitialPipetteHome', () => {
Expand Down
Loading

0 comments on commit 3b5d342

Please sign in to comment.