Skip to content

Commit

Permalink
fix(app): Home pipettes when skipping Drop Tip wizard (#15947)
Browse files Browse the repository at this point in the history
Drop tip wizard CTAs now home the pipette if skipping the flow and close the current run context in both the "skip" and "remove tips" cases. Copy is updated throughout all drop tip flow entry points as well.

This is the first time the app directly POSTs a one-off command via a maintenance run. We do POST one-off commands elsewhere in the app, but we do so using older, less-supported endpoints. If these one-off commands become more common, we should consider creating a dedicated API for doing so (more hooks!). To solve the immediate problem now, I refactored useDropTipMaintenanceRun.

The real complexity of this PR is managing all the interactions with closing run context and ensuring other clients not engaging with the drop tip flows see/don't see the drop tip CTAs when appropriate. It's probably easiest to follow this PR commit-by-commit.

NOTE: If a user completes a run with tips attached on both pipettes intentionally (the protocol itself has tips attached for both pipettes at the end of the run), the wizard won't display for both pipettes, only the left pipette. The run context closes as soon as the home happens, so there's no second CTA. The skip command won't home the plungers, so that's safe, but current behavior is the user will have to do drop tips via the Instruments page. I imagine this is quite the edge case, but it's worth noting. I can re-address this when I update the tip detection logic shortly.

Here's a complete breakdown of all the changes:

Drop Tip Wizard: Error Recovery, Desktop and ODD

*DT Wiz flows remain unchanged for all ER options that are not "cancel run".
*For "cancel run", only the copy changes for what was once "Skip" to something along the lines of "skip and home pipette." The pipette will home during cancel.

Drop Tip Wizard: Post-Run, Desktop and ODD

*If the user has tips attached AND does not see dtwiz within error recovery when doing "cancel run", they will see an unskippable modal (no X button in the upper right) that says "skip and home pipettes" and "begin drop tip wizard".
*Once the drop tip flows are successfully completed, the run context closes.
*If a user clicks the upper-right corner "exit" button within, the confirmation screen now contains copy alerting the user that the pipette will home after you confirm the exit.
*Once the user confirms they want to exit, the pipette will home, and the run context will close.

Drop Tip Wizard: Instruments Page, Desktop and ODD

*The upper-right corner exit confirmation page copy and homing behavior is the same as the post-run behavior. We do not close any sort of run context here.
*Note that the drop-tip banner will no longer be present on the desktop, post-run.
  • Loading branch information
mjhuff authored Aug 12, 2024
1 parent e54341a commit 9335894
Show file tree
Hide file tree
Showing 21 changed files with 387 additions and 281 deletions.
3 changes: 2 additions & 1 deletion app/src/assets/localization/en/drop_tip_wizard.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"drop_tip_failed": "The drop tip could not be completed. Contact customer support for assistance.",
"drop_tips": "drop tips",
"error_dropping_tips": "Error dropping tips",
"exit_screen_title": "Exit before completing drop tip?",
"exit_and_home_pipette": "Exit and home pipette",
"getting_ready": "Getting ready…",
"go_back": "go back",
"jog_too_far": "Jog too far?",
Expand All @@ -34,6 +34,7 @@
"select_drop_tip_slot": "<block>You can return tips to a tip rack or dispose of them.</block><br/><block>Select the slot where you want to drop the tips on the deck map to the right. Once confirmed, the gantry will move to the chosen slot.</block>",
"select_drop_tip_slot_odd": "<block>You can return tips to a tip rack or dispose of them.</block><br/><block>After the gantry moves to the chosen slot, use the jog controls to move the pipette to the exact position for dropping tips.</block>",
"skip": "Skip",
"skip_and_home_pipette": "Skip and home pipette",
"stand_back_blowing_out": "Stand back, robot is blowing out liquid",
"stand_back_dropping_tips": "Stand back, robot is dropping tips",
"stand_back_robot_in_motion": "Stand back, robot is in motion",
Expand Down
4 changes: 2 additions & 2 deletions app/src/assets/localization/en/error_recovery.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"another_app_controlling_robot": "The robot’s touchscreen or another computer with the app is currently controlling this robot.",
"are_you_sure_you_want_to_cancel": "Are you sure you want to cancel?",
"at_step": "At step",
"back_to_menu": "Back to menu",
"another_app_controlling_robot": "The robot’s touchscreen or another computer with the app is currently controlling this robot.",
"before_you_begin": "Before you begin",
"begin_removal": "Begin removal",
"blowout_failed": "Blowout failed",
Expand Down Expand Up @@ -64,7 +64,7 @@
"robot_will_retry_with_tips": "The robot will retry the failed step with new tips.",
"run_paused": "Run paused",
"select_tip_pickup_location": "Select tip pick-up location",
"skip": "Skip",
"skip_and_home_pipette": "Skip and home pipette",
"skip_to_next_step": "Skip to next step",
"skip_to_next_step_new_tips": "Skip to next step with new tips",
"skip_to_next_step_same_tips": "Skip to next step with same tips",
Expand Down
58 changes: 0 additions & 58 deletions app/src/organisms/Devices/ProtocolRun/ProtocolDropTipBanner.tsx

This file was deleted.

51 changes: 41 additions & 10 deletions app/src/organisms/Devices/ProtocolRun/ProtocolDropTipModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,88 @@ import {
LegacyModalShell,
} from '../../../molecules/LegacyModal'
import { TextOnlyButton } from '../../../atoms/buttons'
import { useHomePipettes } from '../../DropTipWizardFlows/hooks'

import type { PipetteData } from '@opentrons/api-client'
import type { IconProps } from '@opentrons/components'
import type { UseHomePipettesProps } from '../../DropTipWizardFlows/hooks'
import type { TipAttachmentStatusResult } from '../../DropTipWizardFlows'

interface UseProtocolDropTipModalProps {
type UseProtocolDropTipModalProps = Pick<
UseHomePipettesProps,
'robotType' | 'instrumentModelSpecs' | 'mount'
> & {
areTipsAttached: TipAttachmentStatusResult['areTipsAttached']
toggleDTWiz: () => void
currentRunId: string
onClose: () => void
/* True if the most recent run is the current run */
isMostRecentRunCurrent: boolean
isRunCurrent: boolean
}

interface UseProtocolDropTipModalResult {
showDTModal: boolean
onDTModalSkip: () => void
onDTModalRemoval: () => void
isDisabled: boolean
}

// Wraps functionality required for rendering the related modal.
export function useProtocolDropTipModal({
areTipsAttached,
toggleDTWiz,
isMostRecentRunCurrent,
isRunCurrent,
onClose,
...homePipetteProps
}: UseProtocolDropTipModalProps): UseProtocolDropTipModalResult {
const [showDTModal, setShowDTModal] = React.useState(areTipsAttached)

const { homePipettes, isHomingPipettes } = useHomePipettes({
...homePipetteProps,
onComplete: () => {
onClose()
setShowDTModal(false)
},
})

// Close the modal if a different app closes the run context.
React.useEffect(() => {
if (isMostRecentRunCurrent) {
if (isRunCurrent && !isHomingPipettes) {
setShowDTModal(areTipsAttached)
} else {
} else if (!isRunCurrent) {
setShowDTModal(false)
}
}, [areTipsAttached, isMostRecentRunCurrent])
}, [isRunCurrent, areTipsAttached, showDTModal]) // Continue to show the modal if a client dismisses the maintenance run on a different app.

const onDTModalSkip = (): void => {
setShowDTModal(false)
homePipettes()
}

const onDTModalRemoval = (): void => {
toggleDTWiz()
setShowDTModal(false)
}

return { showDTModal, onDTModalSkip, onDTModalRemoval }
return {
showDTModal,
onDTModalSkip,
onDTModalRemoval,
isDisabled: isHomingPipettes,
}
}

interface ProtocolDropTipModalProps {
onSkip: UseProtocolDropTipModalResult['onDTModalSkip']
onBeginRemoval: UseProtocolDropTipModalResult['onDTModalRemoval']
isDisabled: UseProtocolDropTipModalResult['isDisabled']
mount?: PipetteData['mount']
}

export function ProtocolDropTipModal({
onSkip,
onBeginRemoval,
mount,
isDisabled,
}: ProtocolDropTipModalProps): JSX.Element {
const { t } = useTranslation('drop_tip_wizard')

Expand Down Expand Up @@ -117,8 +144,12 @@ export function ProtocolDropTipModal({
/>
</StyledText>
<Flex gridGap={SPACING.spacing24} justifyContent={JUSTIFY_END}>
<TextOnlyButton onClick={onSkip} buttonText={t('skip')} />
<PrimaryButton onClick={onBeginRemoval}>
<TextOnlyButton
onClick={onSkip}
buttonText={t('skip_and_home_pipette')}
disabled={isDisabled}
/>
<PrimaryButton onClick={onBeginRemoval} disabled={isDisabled}>
{t('begin_removal')}
</PrimaryButton>
</Flex>
Expand Down
42 changes: 24 additions & 18 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update'
import { getRobotSettings } from '../../../redux/robot-settings'
import { getRobotSerialNumber } from '../../../redux/discovery'
import { ProtocolAnalysisErrorBanner } from './ProtocolAnalysisErrorBanner'
import { ProtocolDropTipBanner } from './ProtocolDropTipBanner'
import {
DropTipWizardFlows,
useDropTipWizardFlows,
Expand All @@ -67,8 +66,8 @@ import {
} from '../../../redux/analytics'
import { getIsHeaterShakerAttached } from '../../../redux/config'
import { Tooltip } from '../../../atoms/Tooltip'
import { useCloseCurrentRun } from '../../../organisms/ProtocolUpload/hooks'
import { ConfirmCancelModal } from '../../../organisms/RunDetails/ConfirmCancelModal'
import { useCloseCurrentRun } from '../../ProtocolUpload/hooks'
import { ConfirmCancelModal } from '../../RunDetails/ConfirmCancelModal'
import { HeaterShakerIsRunningModal } from '../HeaterShakerIsRunningModal'
import {
useRunControls,
Expand Down Expand Up @@ -123,6 +122,7 @@ import type { State } from '../../../redux/types'
import type { HeaterShakerModule } from '../../../redux/modules/types'

const EQUIPMENT_POLL_MS = 5000
const CURRENT_RUN_POLL_MS = 5000
const CANCELLABLE_STATUSES = [
RUN_STATUS_RUNNING,
RUN_STATUS_PAUSED,
Expand Down Expand Up @@ -165,7 +165,10 @@ export function ProtocolRunHeader({
const isRobotViewable = useIsRobotViewable(robotName)
const runStatus = useRunStatus(runId)
const { analysisErrors } = useProtocolAnalysisErrors(runId)
const isRunCurrent = Boolean(useNotifyRunQuery(runId)?.data?.data?.current)
const isRunCurrent = Boolean(
useNotifyRunQuery(runId, { refetchInterval: CURRENT_RUN_POLL_MS })?.data
?.data?.current
)
const mostRecentRunId = useMostRecentRunId()
const { closeCurrentRun, isClosingCurrentRun } = useCloseCurrentRun()
const { startedAt, stoppedAt, completedAt } = useRunTimestamps(runId)
Expand All @@ -177,7 +180,6 @@ export function ProtocolRunHeader({
RUN_STATUSES_TERMINAL.includes(runStatus) &&
isRunCurrent,
})
const [showDropTipBanner, setShowDropTipBanner] = React.useState(true)
const isResetRunLoadingRef = React.useRef(false)
const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity })
const highestPriorityError =
Expand Down Expand Up @@ -229,18 +231,25 @@ export function ProtocolRunHeader({
showDTModal,
onDTModalSkip,
onDTModalRemoval,
isDisabled: areDTModalBtnsDisabled,
} = useProtocolDropTipModal({
areTipsAttached,
toggleDTWiz,
isMostRecentRunCurrent: mostRecentRunId === runId,
isRunCurrent,
currentRunId: runId,
instrumentModelSpecs: aPipetteWithTip?.specs,
mount: aPipetteWithTip?.mount,
robotType,
onClose: () => {
closeCurrentRun()
},
})

const enteredER = runRecord?.data.hasEverEnteredErrorRecovery

React.useEffect(() => {
if (isFlex) {
if (runStatus === RUN_STATUS_IDLE) {
setShowDropTipBanner(true)
resetTipStatus()
} else if (
runStatus != null &&
Expand Down Expand Up @@ -419,21 +428,12 @@ export function ProtocolRunHeader({
isRunCurrent={isRunCurrent}
/>
) : null}
{mostRecentRunId === runId && showDropTipBanner && areTipsAttached ? (
<ProtocolDropTipBanner
onLaunchWizardClick={toggleDTWiz}
onCloseClick={() => {
resetTipStatus()
setShowDropTipBanner(false)
closeCurrentRun()
}}
/>
) : null}
{showDTModal ? (
<ProtocolDropTipModal
onSkip={onDTModalSkip}
onBeginRemoval={onDTModalRemoval}
mount={aPipetteWithTip?.mount}
isDisabled={areDTModalBtnsDisabled}
/>
) : null}
<Box display="grid" gridTemplateColumns="4fr 3fr 3fr 4fr">
Expand Down Expand Up @@ -514,7 +514,13 @@ export function ProtocolRunHeader({
robotType={isFlex ? FLEX_ROBOT_TYPE : OT2_ROBOT_TYPE}
mount={aPipetteWithTip.mount}
instrumentModelSpecs={aPipetteWithTip.specs}
closeFlow={() => setTipStatusResolved().then(toggleDTWiz)}
closeFlow={() =>
setTipStatusResolved()
.then(toggleDTWiz)
.then(() => {
closeCurrentRun()
})
}
/>
) : null}
</Flex>
Expand Down

This file was deleted.

Loading

0 comments on commit 9335894

Please sign in to comment.