From 6c48e3d96cf1edcaceb49f54b12aa89639339cdc Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Fri, 13 Sep 2024 14:17:52 -0400 Subject: [PATCH] refactor(app): Add hook for on-the-fly maintenance run commands (#16249) Closes EXEC-699 In pre-release work, we decided to add a drop tip CTA option of "skip and home pipettes". In order to do this, we needed to create a maintenance run, initiate commands, and then close the maintenance context. There wasn't any general purpose way of doing this (we always had a wizard flow when issuing commands up to this point), so the solution was to commandeer existing drop tip wizard hooks to do this for us. While that works, it has a few issues: * It was pretty hackily implemented, and it kind of had to be without a dedicated hook. * It makes following drop tip wizard more difficult to follow. * If we every want to issue commands to a robot outside of a wizard flow, we have to add to the tech debt. Since 8.1 should entail some drop tip wizard refactoring, this seems like a pretty good thing to refactor. This adds a new useRobotControlCommands, which is a refactor of what the more specific drop tip wizard useDropTipMaintenanceRun. useDropTipMaintenanceRun was half doing this functionality plus other things (useDropTipMaintenanceRun now only does the other things!). This PR cleans up a lot of the cruft/paves the way for more drop tip refactors in 8.1 that were a result of the refactor. --- .../hooks/useRunHeaderDropTip.ts | 22 +++- .../modals/ProtocolDropTipModal.tsx | 26 ++--- .../__tests__/ProtocolDropTipModal.test.tsx | 19 ++- .../DropTipWizardFlows/DropTipWizardFlows.tsx | 61 ++++++++++ .../DropTipWizardFlows/TipsAttachedModal.tsx | 31 +++-- .../__tests__/DropTipWizardFlows.test.ts | 21 ++++ .../__tests__/TipsAttachedModal.test.tsx | 12 +- .../useTipAttachmentStatus.test.tsx} | 56 ++------- .../DropTipWizardFlows/hooks/index.ts | 1 + .../hooks/useDropTipCommands.ts | 5 +- .../hooks/useDropTipCreateCommands.ts | 2 +- .../hooks/useDropTipMaintenanceRun.tsx | 56 +++------ .../hooks/useDropTipWithType.ts | 6 +- .../hooks/useHomePipettes.ts | 84 ++++--------- .../getPipettesWithTipAttached.test.ts | 0 .../getPipettesWithTipAttached.ts | 0 .../useTipAttachmentStatus/index.ts} | 62 +--------- app/src/organisms/DropTipWizardFlows/index.ts | 10 ++ .../getAddressableAreaFromConfig.ts | 0 .../DropTipWizardFlows/utils/index.ts | 1 + .../RecoveryOptions/ManageTips.tsx | 6 +- .../hooks/useRecoveryTipStatus.ts | 3 +- .../organisms/GripperWizardFlows/index.tsx | 10 +- .../LabwarePositionCheckComponent.tsx | 6 +- app/src/organisms/ModuleWizardFlows/index.tsx | 10 +- .../organisms/PipetteWizardFlows/index.tsx | 6 +- app/src/pages/ODD/RunSummary/index.tsx | 19 +-- app/src/redux/robot-controls/actions.ts | 6 + .../resources/maintenance_runs/hooks/index.ts | 2 + .../hooks/useChainMaintenanceCommands.ts | 34 ++++++ .../hooks/useRobotControlCommands.ts | 110 ++++++++++++++++++ app/src/resources/maintenance_runs/index.ts | 3 +- .../maintenance_runs/notifications/index.ts | 1 + .../useNotifyCurrentMaintenanceRun.ts | 4 +- app/src/resources/runs/hooks.ts | 40 +------ 35 files changed, 402 insertions(+), 333 deletions(-) create mode 100644 app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx create mode 100644 app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts rename app/src/organisms/DropTipWizardFlows/{__tests__/DropTipWizardFlows.test.tsx => hooks/__tests__/useTipAttachmentStatus.test.tsx} (71%) rename app/src/organisms/DropTipWizardFlows/{ => hooks/useTipAttachmentStatus}/__tests__/getPipettesWithTipAttached.test.ts (100%) rename app/src/organisms/DropTipWizardFlows/{ => hooks/useTipAttachmentStatus}/getPipettesWithTipAttached.ts (100%) rename app/src/organisms/DropTipWizardFlows/{index.tsx => hooks/useTipAttachmentStatus/index.ts} (69%) create mode 100644 app/src/organisms/DropTipWizardFlows/index.ts rename app/src/organisms/DropTipWizardFlows/{ => utils}/getAddressableAreaFromConfig.ts (100%) create mode 100644 app/src/organisms/DropTipWizardFlows/utils/index.ts create mode 100644 app/src/resources/maintenance_runs/hooks/index.ts create mode 100644 app/src/resources/maintenance_runs/hooks/useChainMaintenanceCommands.ts create mode 100644 app/src/resources/maintenance_runs/hooks/useRobotControlCommands.ts create mode 100644 app/src/resources/maintenance_runs/notifications/index.ts rename app/src/resources/maintenance_runs/{ => notifications}/useNotifyCurrentMaintenanceRun.ts (83%) diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts index 36426fb7bb7..013ae24f3aa 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts @@ -15,8 +15,12 @@ import { isTerminalRunStatus } from '../../utils' import type { RobotType } from '@opentrons/shared-data' import type { Run, RunStatus } from '@opentrons/api-client' -import type { DropTipWizardFlowsProps } from '../../../../../DropTipWizardFlows' +import type { + DropTipWizardFlowsProps, + PipetteWithTip, +} from '../../../../../DropTipWizardFlows' import type { UseProtocolDropTipModalResult } from '../modals' +import type { PipetteDetails } from '../../../../../../resources/maintenance_runs' export type RunHeaderDropTipWizProps = | { showDTWiz: true; dtWizProps: DropTipWizardFlowsProps } @@ -66,9 +70,7 @@ export function useRunHeaderDropTip({ toggleDTWiz, isRunCurrent, currentRunId: runId, - instrumentModelSpecs: aPipetteWithTip?.specs, - mount: aPipetteWithTip?.mount, - robotType, + pipetteInfo: buildPipetteDetails(aPipetteWithTip), onSkipAndHome: () => { closeCurrentRun() }, @@ -133,3 +135,15 @@ export function useRunHeaderDropTip({ return { dropTipModalUtils, dropTipWizardUtils: buildDTWizUtils() } } + +// TODO(jh, 09-12-24): Consolidate this with the same utility that exists elsewhere. +function buildPipetteDetails( + aPipetteWithTip: PipetteWithTip | null +): PipetteDetails | null { + return aPipetteWithTip != null + ? { + pipetteId: aPipetteWithTip.specs.name, + mount: aPipetteWithTip.mount, + } + : null +} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx index f1273d63779..68e6c3601b6 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx @@ -16,16 +16,18 @@ import { } from '@opentrons/components' import { TextOnlyButton } from '../../../../../../atoms/buttons' -import { useHomePipettes } from '../../../../../DropTipWizardFlows/hooks' +import { useHomePipettes } from '../../../../../DropTipWizardFlows' import type { PipetteData } from '@opentrons/api-client' import type { IconProps } from '@opentrons/components' -import type { UseHomePipettesProps } from '../../../../../DropTipWizardFlows/hooks' -import type { TipAttachmentStatusResult } from '../../../../../DropTipWizardFlows' +import type { + UseHomePipettesProps, + TipAttachmentStatusResult, +} from '../../../../../DropTipWizardFlows' type UseProtocolDropTipModalProps = Pick< UseHomePipettesProps, - 'robotType' | 'instrumentModelSpecs' | 'mount' + 'pipetteInfo' > & { areTipsAttached: TipAttachmentStatusResult['areTipsAttached'] toggleDTWiz: () => void @@ -48,22 +50,20 @@ export function useProtocolDropTipModal({ toggleDTWiz, isRunCurrent, onSkipAndHome, - ...homePipetteProps + pipetteInfo, }: UseProtocolDropTipModalProps): UseProtocolDropTipModalResult { const [showModal, setShowModal] = React.useState(areTipsAttached) - const { homePipettes, isHomingPipettes } = useHomePipettes({ - ...homePipetteProps, - isRunCurrent, - onHome: () => { + const { homePipettes, isHoming } = useHomePipettes({ + pipetteInfo, + onSettled: () => { onSkipAndHome() - setShowModal(false) }, }) // Close the modal if a different app closes the run context. React.useEffect(() => { - if (isRunCurrent && !isHomingPipettes) { + if (isRunCurrent && !isHoming) { setShowModal(areTipsAttached) } else if (!isRunCurrent) { setShowModal(false) @@ -71,7 +71,7 @@ export function useProtocolDropTipModal({ }, [isRunCurrent, areTipsAttached, showModal]) // Continue to show the modal if a client dismisses the maintenance run on a different app. const onSkip = (): void => { - homePipettes() + void homePipettes() } const onBeginRemoval = (): void => { @@ -85,7 +85,7 @@ export function useProtocolDropTipModal({ modalProps: { onSkip, onBeginRemoval, - isDisabled: isHomingPipettes, + isDisabled: isHoming, }, } : { showModal: false, modalProps: null } diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx index 3016b41064f..0104497089d 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx @@ -2,12 +2,9 @@ import * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { renderHook, act, screen, fireEvent } from '@testing-library/react' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' - import { renderWithProviders } from '../../../../../../../__testing-utils__' import { i18n } from '../../../../../../../i18n' -import { mockLeftSpecs } from '../../../../../../../redux/pipettes/__fixtures__' -import { useHomePipettes } from '../../../../../../DropTipWizardFlows/hooks' +import { useHomePipettes } from '../../../../../../DropTipWizardFlows' import { useProtocolDropTipModal, ProtocolDropTipModal, @@ -15,7 +12,7 @@ import { import type { Mock } from 'vitest' -vi.mock('../../../../../../DropTipWizardFlows/hooks') +vi.mock('../../../../../../DropTipWizardFlows') describe('useProtocolDropTipModal', () => { let props: Parameters[0] @@ -28,15 +25,17 @@ describe('useProtocolDropTipModal', () => { isRunCurrent: true, onSkipAndHome: vi.fn(), currentRunId: 'MOCK_ID', - mount: 'left', - instrumentModelSpecs: mockLeftSpecs, - robotType: FLEX_ROBOT_TYPE, + pipetteInfo: { + pipetteId: '123', + pipetteName: 'MOCK_NAME', + mount: 'left', + }, } mockHomePipettes = vi.fn() vi.mocked(useHomePipettes).mockReturnValue({ homePipettes: mockHomePipettes, - isHomingPipettes: false, + isHoming: false, }) }) @@ -96,7 +95,7 @@ describe('useProtocolDropTipModal', () => { it('should set isDisabled to true when isHomingPipettes is true', () => { vi.mocked(useHomePipettes).mockReturnValue({ homePipettes: mockHomePipettes, - isHomingPipettes: true, + isHoming: true, }) const { result } = renderHook(() => useProtocolDropTipModal(props)) diff --git a/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx b/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx new file mode 100644 index 00000000000..555ba854c36 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx @@ -0,0 +1,61 @@ +import * as React from 'react' + +import { useDropTipRouting, useDropTipWithType } from './hooks' +import { DropTipWizard } from './DropTipWizard' + +import type { PipetteModelSpecs, RobotType } from '@opentrons/shared-data' +import type { PipetteData } from '@opentrons/api-client' +import type { FixitCommandTypeUtils, IssuedCommandsType } from './types' + +/** Provides the user toggle for rendering Drop Tip Wizard Flows. + * + * NOTE: Rendering these flows is independent of whether tips are actually attached. First use useTipAttachmentStatus + * to get tip attachment status. + */ +export function useDropTipWizardFlows(): { + showDTWiz: boolean + toggleDTWiz: () => void +} { + const [showDTWiz, setShowDTWiz] = React.useState(false) + + const toggleDTWiz = (): void => { + setShowDTWiz(!showDTWiz) + } + + return { showDTWiz, toggleDTWiz } +} + +export interface DropTipWizardFlowsProps { + robotType: RobotType + mount: PipetteData['mount'] + instrumentModelSpecs: PipetteModelSpecs + /* isTakeover allows for optionally specifying a different callback if a different client cancels the "setup" type flow. */ + closeFlow: (isTakeover?: boolean) => void + /* Optional. If provided, DT will issue "fixit" commands and render alternate Error Recovery compatible views. */ + fixitCommandTypeUtils?: FixitCommandTypeUtils +} + +export function DropTipWizardFlows( + props: DropTipWizardFlowsProps +): JSX.Element { + const { fixitCommandTypeUtils } = props + + const issuedCommandsType: IssuedCommandsType = + fixitCommandTypeUtils != null ? 'fixit' : 'setup' + + const dropTipWithTypeUtils = useDropTipWithType({ + ...props, + issuedCommandsType, + }) + + const dropTipRoutingUtils = useDropTipRouting(fixitCommandTypeUtils) + + return ( + + ) +} diff --git a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx index 7c9c5a11823..36a50f8e47f 100644 --- a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx +++ b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx @@ -19,17 +19,13 @@ import { useHomePipettes } from './hooks' import type { HostConfig } from '@opentrons/api-client' import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' -import type { PipetteWithTip } from '.' -import type { UseHomePipettesProps } from './hooks' +import type { UseHomePipettesProps, PipetteWithTip } from './hooks' +import type { PipetteDetails } from '../../resources/maintenance_runs' -type TipsAttachedModalProps = Pick< - UseHomePipettesProps, - 'robotType' | 'instrumentModelSpecs' | 'mount' | 'isRunCurrent' -> & { +type TipsAttachedModalProps = Pick & { aPipetteWithTip: PipetteWithTip host: HostConfig | null setTipStatusResolved: (onEmpty?: () => void) => Promise - onSkipAndHome: () => void } export const handleTipsAttachedModal = ( @@ -53,9 +49,10 @@ const TipsAttachedModal = NiceModal.create( const { mount, specs } = aPipetteWithTip const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() - const { homePipettes, isHomingPipettes } = useHomePipettes({ + const { homePipettes, isHoming } = useHomePipettes({ ...homePipetteProps, - onHome: () => { + pipetteInfo: buildPipetteDetails(aPipetteWithTip), + onSettled: () => { modal.remove() void setTipStatusResolved() }, @@ -105,13 +102,13 @@ const TipsAttachedModal = NiceModal.create( buttonType="secondary" buttonText={t('skip_and_home_pipette')} onClick={onHomePipettes} - disabled={isHomingPipettes} + disabled={isHoming} /> @@ -130,3 +127,15 @@ const TipsAttachedModal = NiceModal.create( ) } ) + +// TODO(jh, 09-12-24): Consolidate this with the same utility that exists elsewhere. +function buildPipetteDetails( + aPipetteWithTip: PipetteWithTip | null +): PipetteDetails | null { + return aPipetteWithTip != null + ? { + pipetteId: aPipetteWithTip.specs.name, + mount: aPipetteWithTip.mount, + } + : null +} diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts new file mode 100644 index 00000000000..bf85054259a --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect, vi } from 'vitest' +import { renderHook, act } from '@testing-library/react' + +import { useDropTipWizardFlows } from '..' + +vi.mock('../DropTipWizard') +vi.mock('../hooks') + +describe('useDropTipWizardFlows', () => { + it('should toggle showDTWiz state', () => { + const { result } = renderHook(() => useDropTipWizardFlows()) + + expect(result.current.showDTWiz).toBe(false) + + act(() => { + result.current.toggleDTWiz() + }) + + expect(result.current.showDTWiz).toBe(true) + }) +}) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx index 40a2b075e7c..8dd0251038b 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx @@ -7,15 +7,15 @@ import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' import { handleTipsAttachedModal } from '../TipsAttachedModal' -import { FLEX_ROBOT_TYPE, LEFT } from '@opentrons/shared-data' +import { LEFT } from '@opentrons/shared-data' import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' import { useCloseCurrentRun } from '../../ProtocolUpload/hooks' import { useDropTipWizardFlows } from '..' +import type { Mock } from 'vitest' import type { PipetteModelSpecs } from '@opentrons/shared-data' import type { HostConfig } from '@opentrons/api-client' -import type { Mock } from 'vitest' -import type { PipetteWithTip } from '..' +import type { PipetteWithTip } from '../hooks' vi.mock('../../ProtocolUpload/hooks') vi.mock('..') @@ -52,11 +52,7 @@ const render = (aPipetteWithTip: PipetteWithTip) => { host: MOCK_HOST, aPipetteWithTip, setTipStatusResolved: mockSetTipStatusResolved, - robotType: FLEX_ROBOT_TYPE, - mount: 'left', - instrumentModelSpecs: mockPipetteInfo.pipetteSpecs as any, - onSkipAndHome: vi.fn(), - isRunCurrent: true, + onSettled: vi.fn(), }) } data-testid="testButton" diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useTipAttachmentStatus.test.tsx similarity index 71% rename from app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx rename to app/src/organisms/DropTipWizardFlows/hooks/__tests__/useTipAttachmentStatus.test.tsx index 99d08eaa579..208caefb4dc 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useTipAttachmentStatus.test.tsx @@ -1,23 +1,18 @@ import * as React from 'react' -import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' -import { screen, renderHook, act } from '@testing-library/react' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' -import { - useTipAttachmentStatus, - useDropTipWizardFlows, - DropTipWizardFlows, -} from '..' -import { getPipettesWithTipAttached } from '../getPipettesWithTipAttached' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { act, renderHook } from '@testing-library/react' + import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { DropTipWizard } from '../DropTipWizard' import { useInstrumentsQuery } from '@opentrons/react-api-client' +import { mockPipetteInfo } from '../../../../redux/pipettes/__fixtures__' +import { getPipettesWithTipAttached } from '../useTipAttachmentStatus/getPipettesWithTipAttached' +import { DropTipWizard } from '../../DropTipWizard' +import { useTipAttachmentStatus } from '../useTipAttachmentStatus' + import type { Mock } from 'vitest' import type { PipetteModelSpecs } from '@opentrons/shared-data' -import type { PipetteWithTip } from '..' +import type { PipetteWithTip } from '../useTipAttachmentStatus' vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() @@ -26,10 +21,9 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getPipetteModelSpecs: vi.fn(), } }) -vi.mock('../DropTipWizard') -vi.mock('../getPipettesWithTipAttached') -vi.mock('../hooks') vi.mock('@opentrons/react-api-client') +vi.mock('../useTipAttachmentStatus/getPipettesWithTipAttached') +vi.mock('../../DropTipWizard') const MOCK_ACTUAL_PIPETTE = { ...mockPipetteInfo.pipetteSpecs, @@ -124,31 +118,3 @@ describe('useTipAttachmentStatus', () => { expect(onEmptyCacheMock).toHaveBeenCalled() }) }) - -describe('useDropTipWizardFlows', () => { - it('should toggle showDTWiz state', () => { - const { result } = renderHook(() => useDropTipWizardFlows()) - - expect(result.current.showDTWiz).toBe(false) - - act(() => { - result.current.toggleDTWiz() - }) - - expect(result.current.showDTWiz).toBe(true) - }) -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -describe('DropTipWizardFlows', () => { - it('should render DropTipWizard', () => { - render({} as any) - - screen.getByText('MOCK DROP TIP WIZ') - }) -}) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/index.ts b/app/src/organisms/DropTipWizardFlows/hooks/index.ts index fdb5964eace..890578280cc 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/index.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/index.ts @@ -1,6 +1,7 @@ export * from './errors' export * from './useDropTipWithType' export * from './useHomePipettes' +export * from './useTipAttachmentStatus' export { useDropTipRouting } from './useDropTipRouting' export { useDropTipWithType } from './useDropTipWithType' diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts index 3b0fa137afa..5c073588ee8 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts @@ -3,7 +3,7 @@ import * as React from 'react' import { useDeleteMaintenanceRunMutation } from '@opentrons/react-api-client' import { MANAGED_PIPETTE_ID, POSITION_AND_BLOWOUT } from '../constants' -import { getAddressableAreaFromConfig } from '../getAddressableAreaFromConfig' +import { getAddressableAreaFromConfig } from '../utils' import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' import type { CreateCommand, @@ -33,7 +33,6 @@ type UseDropTipSetupCommandsParams = UseDTWithTypeParams & { setErrorDetails: (errorDetails: SetRobotErrorDetailsParams) => void toggleIsExiting: () => void fixitCommandTypeUtils?: FixitCommandTypeUtils - toggleClientEndRun: () => void } export interface UseDropTipCommandsResult { @@ -58,7 +57,6 @@ export function useDropTipCommands({ instrumentModelSpecs, robotType, fixitCommandTypeUtils, - toggleClientEndRun, }: UseDropTipSetupCommandsParams): UseDropTipCommandsResult { const isFlex = robotType === FLEX_ROBOT_TYPE const [hasSeenClose, setHasSeenClose] = React.useState(false) @@ -89,7 +87,6 @@ export function useDropTipCommands({ console.error(error.message) }) .finally(() => { - toggleClientEndRun() closeFlow() deleteMaintenanceRun(activeMaintenanceRunId) }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts index 10112ea740b..5b84e3a81dd 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts @@ -1,7 +1,6 @@ import { useCreateMaintenanceCommandMutation } from '@opentrons/react-api-client' import { - useChainMaintenanceCommands, useChainRunCommands, useCreateRunCommandMutation, } from '../../../resources/runs' @@ -10,6 +9,7 @@ import type { CreateCommand } from '@opentrons/shared-data' import type { CommandData } from '@opentrons/api-client' import type { UseDTWithTypeParams, SetRobotErrorDetailsParams } from '.' import type { FixitCommandTypeUtils } from '../types' +import { useChainMaintenanceCommands } from '../../../resources/maintenance_runs' export interface RunCommandByCommandTypeParams { command: CreateCommand diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx index 67f4fa7957a..64f3f05ec96 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx @@ -1,10 +1,10 @@ import * as React from 'react' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' import { - useCreateTargetedMaintenanceRunMutation, useChainMaintenanceCommands, -} from '../../../resources/runs' + useNotifyCurrentMaintenanceRun, +} from '../../../resources/maintenance_runs' +import { useCreateTargetedMaintenanceRunMutation } from '../../../resources/runs' import { buildLoadPipetteCommand } from './useDropTipCommands' import type { PipetteModelSpecs } from '@opentrons/shared-data' @@ -20,13 +20,8 @@ export type UseDropTipMaintenanceRunParams = Omit< setErrorDetails?: (errorDetails: SetRobotErrorDetailsParams) => void instrumentModelSpecs?: PipetteModelSpecs mount?: PipetteData['mount'] - /* Optionally control when a drop tip maintenance run is created. */ - enabled?: boolean } -// TODO(jh, 08-08-24): useDropTipMaintenanceRun is a bit overloaded now that we are using it create maintenance runs -// on-the-fly for one-off commands outside of a run. Consider refactoring. - // Manages the maintenance run state if the flow is utilizing "setup" type commands. export function useDropTipMaintenanceRun({ issuedCommandsType, @@ -34,11 +29,7 @@ export function useDropTipMaintenanceRun({ instrumentModelSpecs, setErrorDetails, closeFlow, - enabled, -}: UseDropTipMaintenanceRunParams): { - activeMaintenanceRunId: string | null - toggleClientEndRun: () => void -} { +}: UseDropTipMaintenanceRunParams): string | null { const isMaintenanceRunType = issuedCommandsType === 'setup' const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = React.useState< @@ -57,20 +48,16 @@ export function useDropTipMaintenanceRun({ instrumentModelName: instrumentModelSpecs?.name, setErrorDetails, setCreatedMaintenanceRunId, - enabled, }) - const toggleClientEndRun = useMonitorMaintenanceRunForDeletion({ + useMonitorMaintenanceRunForDeletion({ isMaintenanceRunType, activeMaintenanceRunId, createdMaintenanceRunId, closeFlow, }) - return { - activeMaintenanceRunId: activeMaintenanceRunId ?? null, - toggleClientEndRun, - } + return activeMaintenanceRunId ?? null } type UseCreateDropTipMaintenanceRunParams = Omit< @@ -88,7 +75,6 @@ function useCreateDropTipMaintenanceRun({ instrumentModelName, setErrorDetails, setCreatedMaintenanceRunId, - enabled, }: UseCreateDropTipMaintenanceRunParams): void { const { chainRunCommands } = useChainMaintenanceCommands() @@ -115,13 +101,11 @@ function useCreateDropTipMaintenanceRun({ }, }) - const isEnabled = enabled ?? true React.useEffect(() => { if ( issuedCommandsType === 'setup' && mount != null && - instrumentModelName != null && - isEnabled + instrumentModelName != null ) { createTargetedMaintenanceRun({}).catch((e: Error) => { if (setErrorDetails != null) { @@ -131,18 +115,16 @@ function useCreateDropTipMaintenanceRun({ } }) } else { - if (mount != null || instrumentModelName != null) { - console.warn( - 'Could not create maintenance run due to missing pipette data.' - ) - } + console.warn( + 'Could not create maintenance run due to missing pipette data.' + ) } - }, [enabled, mount, instrumentModelName]) + }, [mount, instrumentModelName]) } interface UseMonitorMaintenanceRunForDeletionParams { isMaintenanceRunType: boolean - closeFlow: (isTakeover?: boolean) => void + closeFlow: () => void createdMaintenanceRunId: string | null activeMaintenanceRunId?: string } @@ -154,15 +136,12 @@ function useMonitorMaintenanceRunForDeletion({ createdMaintenanceRunId, activeMaintenanceRunId, closeFlow, -}: UseMonitorMaintenanceRunForDeletionParams): () => void { +}: UseMonitorMaintenanceRunForDeletionParams): void { const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, ] = React.useState(false) const [closedOnce, setClosedOnce] = React.useState(false) - const [closedByThisClient, setClosedByThisClient] = React.useState( - false - ) React.useEffect(() => { if (isMaintenanceRunType && !closedOnce) { @@ -174,16 +153,11 @@ function useMonitorMaintenanceRunForDeletion({ } if ( activeMaintenanceRunId !== createdMaintenanceRunId && - monitorMaintenanceRunForDeletion && - !closedByThisClient + monitorMaintenanceRunForDeletion ) { - closeFlow(true) + closeFlow() setClosedOnce(true) } } }, [isMaintenanceRunType, createdMaintenanceRunId, activeMaintenanceRunId]) - - return () => { - setClosedByThisClient(!closedByThisClient) - } } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts index 63b3ab05da3..cb7064ac722 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts @@ -41,10 +41,7 @@ export function useDropTipWithType( const { isExiting, toggleIsExiting } = useIsExitingDT(issuedCommandsType) const { errorDetails, setErrorDetails } = useErrorDetails() - const { - activeMaintenanceRunId, - toggleClientEndRun, - } = useDropTipMaintenanceRun({ + const activeMaintenanceRunId = useDropTipMaintenanceRun({ ...params, setErrorDetails, }) @@ -63,7 +60,6 @@ export function useDropTipWithType( setErrorDetails, toggleIsExiting, fixitCommandTypeUtils, - toggleClientEndRun, }) useRegisterPipetteFixitType({ ...params, ...dtCreateCommandUtils }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts b/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts index cb3576c7105..2a828dcfedf 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts @@ -1,76 +1,34 @@ -import * as React from 'react' +import { useRobotControlCommands } from '../../../resources/maintenance_runs' -import { - useCreateMaintenanceCommandMutation, - useDeleteMaintenanceRunMutation, -} from '@opentrons/react-api-client' - -import { useDropTipMaintenanceRun } from './useDropTipMaintenanceRun' - -import type { UseDropTipMaintenanceRunParams } from './useDropTipMaintenanceRun' import type { CreateCommand } from '@opentrons/shared-data' - -export type UseHomePipettesProps = Omit< - UseDropTipMaintenanceRunParams, - 'issuedCommandsType' | 'closeFlow' -> & { - onHome: () => void - isRunCurrent: boolean +import type { + UseRobotControlCommandsProps, + UseRobotControlCommandsResult, +} from '../../../resources/maintenance_runs' + +interface UseHomePipettesResult { + isHoming: UseRobotControlCommandsResult['isExecuting'] + homePipettes: UseRobotControlCommandsResult['executeCommands'] } +export type UseHomePipettesProps = Pick< + UseRobotControlCommandsProps, + 'pipetteInfo' | 'onSettled' +> +// TODO(jh, 09-12-24): Find a better place for this hook to live. +// Home pipettes except for plungers. export function useHomePipettes( props: UseHomePipettesProps -): { - homePipettes: () => void - isHomingPipettes: boolean -} { - const [isHomingPipettes, setIsHomingPipettes] = React.useState(false) - const { deleteMaintenanceRun } = useDeleteMaintenanceRunMutation() - - const { activeMaintenanceRunId } = useDropTipMaintenanceRun({ +): UseHomePipettesResult { + const { executeCommands, isExecuting } = useRobotControlCommands({ ...props, - issuedCommandsType: 'setup', - enabled: isHomingPipettes, - closeFlow: props.onHome, + commands: [HOME_EXCEPT_PLUNGERS], + continuePastCommandFailure: true, }) - const isMaintenanceRunActive = activeMaintenanceRunId != null - - // Home the pipette after user click once a maintenance run has been created. - React.useEffect(() => { - if (isMaintenanceRunActive && isHomingPipettes && props.isRunCurrent) { - void homePipettesCmd().finally(() => { - props.onHome() - deleteMaintenanceRun(activeMaintenanceRunId) - }) - } - }, [isMaintenanceRunActive, isHomingPipettes, props.isRunCurrent]) - - const { createMaintenanceCommand } = useCreateMaintenanceCommandMutation() - - const homePipettesCmd = React.useCallback(() => { - if (activeMaintenanceRunId != null) { - return createMaintenanceCommand( - { - maintenanceRunId: activeMaintenanceRunId, - command: HOME_EXCEPT_PLUNGERS, - waitUntilComplete: true, - }, - { onSettled: () => Promise.resolve() } - ) - } else { - return Promise.reject( - new Error( - "'Unable to create a maintenance run when attempting to home pipettes." - ) - ) - } - }, [createMaintenanceCommand, activeMaintenanceRunId]) return { - homePipettes: () => { - setIsHomingPipettes(true) - }, - isHomingPipettes, + isHoming: isExecuting, + homePipettes: executeCommands, } } diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/getPipettesWithTipAttached.test.ts b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts similarity index 100% rename from app/src/organisms/DropTipWizardFlows/__tests__/getPipettesWithTipAttached.test.ts rename to app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts diff --git a/app/src/organisms/DropTipWizardFlows/getPipettesWithTipAttached.ts b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts similarity index 100% rename from app/src/organisms/DropTipWizardFlows/getPipettesWithTipAttached.ts rename to app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts diff --git a/app/src/organisms/DropTipWizardFlows/index.tsx b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/index.ts similarity index 69% rename from app/src/organisms/DropTipWizardFlows/index.tsx rename to app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/index.ts index f559aaa8e31..80d1bb1913c 100644 --- a/app/src/organisms/DropTipWizardFlows/index.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/index.ts @@ -1,70 +1,14 @@ import * as React from 'react' import head from 'lodash/head' +import { useInstrumentsQuery } from '@opentrons/react-api-client' import { getPipetteModelSpecs } from '@opentrons/shared-data' import { getPipettesWithTipAttached } from './getPipettesWithTipAttached' -import { useDropTipRouting, useDropTipWithType } from './hooks' -import { DropTipWizard } from './DropTipWizard' -import type { PipetteModelSpecs, RobotType } from '@opentrons/shared-data' -import type { Mount, PipetteData } from '@opentrons/api-client' -import type { FixitCommandTypeUtils, IssuedCommandsType } from './types' +import type { Mount } from '@opentrons/api-client' +import type { PipetteModelSpecs } from '@opentrons/shared-data' import type { GetPipettesWithTipAttached } from './getPipettesWithTipAttached' -import { useInstrumentsQuery } from '@opentrons/react-api-client' - -/** Provides the user toggle for rendering Drop Tip Wizard Flows. - * - * NOTE: Rendering these flows is independent of whether tips are actually attached. First use useTipAttachmentStatus - * to get tip attachment status. - */ -export function useDropTipWizardFlows(): { - showDTWiz: boolean - toggleDTWiz: () => void -} { - const [showDTWiz, setShowDTWiz] = React.useState(false) - - const toggleDTWiz = (): void => { - setShowDTWiz(!showDTWiz) - } - - return { showDTWiz, toggleDTWiz } -} - -export interface DropTipWizardFlowsProps { - robotType: RobotType - mount: PipetteData['mount'] - instrumentModelSpecs: PipetteModelSpecs - /* isTakeover allows for optionally specifying a different callback if a different client cancels the "setup" type flow. */ - closeFlow: (isTakeover?: boolean) => void - /* Optional. If provided, DT will issue "fixit" commands and render alternate Error Recovery compatible views. */ - fixitCommandTypeUtils?: FixitCommandTypeUtils -} - -export function DropTipWizardFlows( - props: DropTipWizardFlowsProps -): JSX.Element { - const { fixitCommandTypeUtils } = props - - const issuedCommandsType: IssuedCommandsType = - fixitCommandTypeUtils != null ? 'fixit' : 'setup' - - const dropTipWithTypeUtils = useDropTipWithType({ - ...props, - issuedCommandsType, - }) - - const dropTipRoutingUtils = useDropTipRouting(fixitCommandTypeUtils) - - return ( - - ) -} const INSTRUMENTS_POLL_MS = 5000 diff --git a/app/src/organisms/DropTipWizardFlows/index.ts b/app/src/organisms/DropTipWizardFlows/index.ts new file mode 100644 index 00000000000..0030fa29a5a --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/index.ts @@ -0,0 +1,10 @@ +export * from './DropTipWizardFlows' +export { useTipAttachmentStatus, useHomePipettes } from './hooks' +export * from './TipsAttachedModal' + +export type { + UseHomePipettesProps, + TipAttachmentStatusResult, + PipetteWithTip, +} from './hooks' +export type { FixitCommandTypeUtils } from './types' diff --git a/app/src/organisms/DropTipWizardFlows/getAddressableAreaFromConfig.ts b/app/src/organisms/DropTipWizardFlows/utils/getAddressableAreaFromConfig.ts similarity index 100% rename from app/src/organisms/DropTipWizardFlows/getAddressableAreaFromConfig.ts rename to app/src/organisms/DropTipWizardFlows/utils/getAddressableAreaFromConfig.ts diff --git a/app/src/organisms/DropTipWizardFlows/utils/index.ts b/app/src/organisms/DropTipWizardFlows/utils/index.ts new file mode 100644 index 00000000000..742cff97db1 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/utils/index.ts @@ -0,0 +1 @@ +export * from './getAddressableAreaFromConfig' diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx index 5075d7e53f7..fa159677903 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx @@ -24,9 +24,11 @@ import { DropTipWizardFlows } from '../../DropTipWizardFlows' import { DT_ROUTES } from '../../DropTipWizardFlows/constants' import { SelectRecoveryOption } from './SelectRecoveryOption' -import type { PipetteWithTip } from '../../DropTipWizardFlows' import type { RecoveryContentProps, RecoveryRoute, RouteStep } from '../types' -import type { FixitCommandTypeUtils } from '../../DropTipWizardFlows/types' +import type { + FixitCommandTypeUtils, + PipetteWithTip, +} from '../../DropTipWizardFlows' // The Drop Tip flow entry point. Includes entry from SelectRecoveryOption and CancelRun. export function ManageTips(props: RecoveryContentProps): JSX.Element { diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts index eff2565a2eb..505ba6aff7c 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts @@ -3,13 +3,12 @@ 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, PipetteData } from '@opentrons/api-client' import type { - TipAttachmentStatusResult, PipetteWithTip, + TipAttachmentStatusResult, } from '../../DropTipWizardFlows' interface UseRecoveryTipStatusProps { diff --git a/app/src/organisms/GripperWizardFlows/index.tsx b/app/src/organisms/GripperWizardFlows/index.tsx index 044232f9f42..84573117e39 100644 --- a/app/src/organisms/GripperWizardFlows/index.tsx +++ b/app/src/organisms/GripperWizardFlows/index.tsx @@ -15,16 +15,16 @@ import { useCreateMaintenanceCommandMutation, useDeleteMaintenanceRunMutation, } from '@opentrons/react-api-client' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { + useChainMaintenanceCommands, + useNotifyCurrentMaintenanceRun, +} from '../../resources/maintenance_runs' import { getTopPortalEl } from '../../App/portal' import { WizardHeader } from '../../molecules/WizardHeader' import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' import { FirmwareUpdateModal } from '../FirmwareUpdateModal' import { getIsOnDevice } from '../../redux/config' -import { - useChainMaintenanceCommands, - useCreateTargetedMaintenanceRunMutation, -} from '../../resources/runs' +import { useCreateTargetedMaintenanceRunMutation } from '../../resources/runs' import { getGripperWizardSteps } from './getGripperWizardSteps' import { GRIPPER_FLOW_TYPES, SECTIONS } from './constants' import { BeforeBeginning } from './BeforeBeginning' diff --git a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx index 0f5fea72ebe..b63c87ecdf9 100644 --- a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx @@ -23,10 +23,12 @@ import { DetachProbe } from './DetachProbe' import { PickUpTip } from './PickUpTip' import { ReturnTip } from './ReturnTip' import { ResultsSummary } from './ResultsSummary' -import { useChainMaintenanceCommands } from '../../resources/runs' import { FatalError } from './FatalErrorModal' import { RobotMotionLoader } from './RobotMotionLoader' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { + useChainMaintenanceCommands, + useNotifyCurrentMaintenanceRun, +} from '../../resources/maintenance_runs' import { getLabwarePositionCheckSteps } from './getLabwarePositionCheckSteps' import type { diff --git a/app/src/organisms/ModuleWizardFlows/index.tsx b/app/src/organisms/ModuleWizardFlows/index.tsx index 4b0694d3aa5..6e18d0ab851 100644 --- a/app/src/organisms/ModuleWizardFlows/index.tsx +++ b/app/src/organisms/ModuleWizardFlows/index.tsx @@ -17,10 +17,7 @@ import { import { getTopPortalEl } from '../../App/portal' import { WizardHeader } from '../../molecules/WizardHeader' import { useAttachedPipettesFromInstrumentsQuery } from '../../organisms/Devices/hooks' -import { - useChainMaintenanceCommands, - useCreateTargetedMaintenanceRunMutation, -} from '../../resources/runs' +import { useCreateTargetedMaintenanceRunMutation } from '../../resources/runs' import { getIsOnDevice } from '../../redux/config' import { SimpleWizardBody, @@ -35,7 +32,10 @@ import { SelectLocation } from './SelectLocation' import { Success } from './Success' import { DetachProbe } from './DetachProbe' import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { + useChainMaintenanceCommands, + useNotifyCurrentMaintenanceRun, +} from '../../resources/maintenance_runs' import type { AttachedModule, CommandData } from '@opentrons/api-client' import { RUN_STATUS_FAILED } from '@opentrons/api-client' diff --git a/app/src/organisms/PipetteWizardFlows/index.tsx b/app/src/organisms/PipetteWizardFlows/index.tsx index 8e2693d9c37..846af77c58b 100644 --- a/app/src/organisms/PipetteWizardFlows/index.tsx +++ b/app/src/organisms/PipetteWizardFlows/index.tsx @@ -16,11 +16,11 @@ import { ApiHostProvider, } from '@opentrons/react-api-client' +import { useCreateTargetedMaintenanceRunMutation } from '../../resources/runs' import { - useCreateTargetedMaintenanceRunMutation, useChainMaintenanceCommands, -} from '../../resources/runs' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' + useNotifyCurrentMaintenanceRun, +} from '../../resources/maintenance_runs' import { getTopPortalEl } from '../../App/portal' import { WizardHeader } from '../../molecules/WizardHeader' import { FirmwareUpdateModal } from '../FirmwareUpdateModal' diff --git a/app/src/pages/ODD/RunSummary/index.tsx b/app/src/pages/ODD/RunSummary/index.tsx index b8f35f9c34f..dea605476c2 100644 --- a/app/src/pages/ODD/RunSummary/index.tsx +++ b/app/src/pages/ODD/RunSummary/index.tsx @@ -40,7 +40,6 @@ import { useDeleteRunMutation, useRunCommandErrors, } from '@opentrons/react-api-client' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { useRunTimestamps, @@ -67,8 +66,10 @@ import { useIsRunCurrent, useNotifyRunQuery, } from '../../../resources/runs' -import { handleTipsAttachedModal } from '../../../organisms/DropTipWizardFlows/TipsAttachedModal' -import { useTipAttachmentStatus } from '../../../organisms/DropTipWizardFlows' +import { + useTipAttachmentStatus, + handleTipsAttachedModal, +} from '../../../organisms/DropTipWizardFlows' import { useRecoveryAnalytics } from '../../../organisms/ErrorRecoveryFlows/hooks' import type { IconName } from '@opentrons/components' @@ -283,11 +284,7 @@ export function RunSummary(): JSX.Element { setTipStatusResolved: setTipStatusResolvedAndRoute(handleReturnToDash), host, aPipetteWithTip, - instrumentModelSpecs: aPipetteWithTip.specs, - mount: aPipetteWithTip.mount, - robotType: FLEX_ROBOT_TYPE, - isRunCurrent, - onSkipAndHome: () => { + onSettled: () => { closeCurrentRunIfValid(() => { navigate('/dashboard') }) @@ -308,11 +305,7 @@ export function RunSummary(): JSX.Element { setTipStatusResolved: setTipStatusResolvedAndRoute(handleRunAgain), host, aPipetteWithTip, - instrumentModelSpecs: aPipetteWithTip.specs, - mount: aPipetteWithTip.mount, - robotType: FLEX_ROBOT_TYPE, - isRunCurrent, - onSkipAndHome: () => { + onSettled: () => { runAgain() }, }) diff --git a/app/src/redux/robot-controls/actions.ts b/app/src/redux/robot-controls/actions.ts index 816475a1738..1d29782b964 100644 --- a/app/src/redux/robot-controls/actions.ts +++ b/app/src/redux/robot-controls/actions.ts @@ -66,6 +66,9 @@ type HomeActionCreator = (( ) => Types.HomeAction) & ((robotName: string, target: 'pipette', mount: Mount) => Types.HomeAction) +/** + * @deprecated: Prefer performing single robot commands via maintenance run. See useRobotControlCommands. + */ export const home: HomeActionCreator = ( robotName: string, target: 'robot' | 'pipette', @@ -98,6 +101,9 @@ export const homeFailure = ( meta, }) +/** + * @deprecated: Prefer performing single robot commands via maintenance run. See useRobotControlCommands. + */ export const move = ( robotName: string, position: Types.MovePosition, diff --git a/app/src/resources/maintenance_runs/hooks/index.ts b/app/src/resources/maintenance_runs/hooks/index.ts new file mode 100644 index 00000000000..7d0230c9a0c --- /dev/null +++ b/app/src/resources/maintenance_runs/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useChainMaintenanceCommands' +export * from './useRobotControlCommands' diff --git a/app/src/resources/maintenance_runs/hooks/useChainMaintenanceCommands.ts b/app/src/resources/maintenance_runs/hooks/useChainMaintenanceCommands.ts new file mode 100644 index 00000000000..2f3a6e82434 --- /dev/null +++ b/app/src/resources/maintenance_runs/hooks/useChainMaintenanceCommands.ts @@ -0,0 +1,34 @@ +import * as React from 'react' + +import { useCreateMaintenanceCommandMutation } from '@opentrons/react-api-client' + +import { chainMaintenanceCommandsRecursive } from '../../runs' + +import type { CreateCommand } from '@opentrons/shared-data' + +export function useChainMaintenanceCommands(): { + chainRunCommands: ( + maintenanceRunId: string, + commands: CreateCommand[], + continuePastCommandFailure: boolean + ) => ReturnType + isCommandMutationLoading: boolean +} { + const [isLoading, setIsLoading] = React.useState(false) + const { createMaintenanceCommand } = useCreateMaintenanceCommandMutation() + return { + chainRunCommands: ( + maintenanceRunId, + commands: CreateCommand[], + continuePastCommandFailure: boolean + ) => + chainMaintenanceCommandsRecursive( + maintenanceRunId, + commands, + createMaintenanceCommand, + continuePastCommandFailure, + setIsLoading + ), + isCommandMutationLoading: isLoading, + } +} diff --git a/app/src/resources/maintenance_runs/hooks/useRobotControlCommands.ts b/app/src/resources/maintenance_runs/hooks/useRobotControlCommands.ts new file mode 100644 index 00000000000..427fff40183 --- /dev/null +++ b/app/src/resources/maintenance_runs/hooks/useRobotControlCommands.ts @@ -0,0 +1,110 @@ +import * as React from 'react' + +import { useDeleteMaintenanceRunMutation } from '@opentrons/react-api-client' + +import { useChainMaintenanceCommands } from './useChainMaintenanceCommands' +import { useCreateTargetedMaintenanceRunMutation } from '../../runs' + +import type { CreateCommand } from '@opentrons/shared-data' +import type { MaintenanceRun, Mount } from '@opentrons/api-client' + +export interface PipetteDetails { + mount: Mount + pipetteId: string + pipetteName?: string +} + +export interface UseRobotControlCommandsResult { + /* Creates the maintenance run, executes the commands utilizing the maintenance run context, then deletes the maintenance run. */ + executeCommands: () => Promise + /** + * Whether executeCommands is currently executing. This becomes "true" as the maintenance run is created and only + * becomes "false" after the maintenance run is deleted. + */ + isExecuting: boolean +} + +export interface UseRobotControlCommandsProps { + pipetteInfo: PipetteDetails | null + commands: CreateCommand[] + continuePastCommandFailure: boolean + /* An onSettled callback executed after the deletion of the maintenance run. */ + onSettled?: () => void +} +// Issue commands to the robot, creating an on-the-fly maintenance run for the duration of the issued commands, loading +// the relevant pipette if necessary. Commands are then executed, and regardless of the success status of those commands, +// the maintenance run is subsequently deleted. +export function useRobotControlCommands({ + pipetteInfo, + commands, + continuePastCommandFailure, + onSettled, +}: UseRobotControlCommandsProps): UseRobotControlCommandsResult { + const [isExecuting, setIsExecuting] = React.useState(false) + + const { chainRunCommands } = useChainMaintenanceCommands() + const { + mutateAsync: deleteMaintenanceRun, + } = useDeleteMaintenanceRunMutation() + + const { + createTargetedMaintenanceRun, + } = useCreateTargetedMaintenanceRunMutation({ + onSuccess: response => { + const runId = response.data.id as string + + const loadPipetteIfSupplied = (): Promise => { + if (pipetteInfo !== null) { + const loadPipetteCommand = buildLoadPipetteCommand(pipetteInfo) + return chainRunCommands(runId, [loadPipetteCommand], false) + .then(() => Promise.resolve()) + .catch((error: Error) => { + console.error(error.message) + }) + } + return Promise.resolve() + } + + // Execute the command(s) + loadPipetteIfSupplied() + .then(() => + chainRunCommands(runId, commands, continuePastCommandFailure) + ) + .catch((error: Error) => { + console.error(error.message) + }) + .finally(() => + deleteMaintenanceRun(runId).catch((error: Error) => { + console.error('Failed to delete maintenance run:', error.message) + }) + ) + .finally(() => { + onSettled?.() + setIsExecuting(false) + }) + }, + onError: (error: Error) => { + console.error(error.message) + setIsExecuting(false) + }, + }) + + const executeCommands = (): Promise => { + setIsExecuting(true) + return createTargetedMaintenanceRun({}) + } + + return { executeCommands, isExecuting } +} + +const buildLoadPipetteCommand = ( + pipetteDetails: PipetteDetails +): CreateCommand => { + return { + commandType: 'loadPipette', + params: { + ...pipetteDetails, + pipetteName: pipetteDetails.pipetteName ?? 'managedPipetteId', + }, + } +} diff --git a/app/src/resources/maintenance_runs/index.ts b/app/src/resources/maintenance_runs/index.ts index ecd7a95a94d..23d730f2402 100644 --- a/app/src/resources/maintenance_runs/index.ts +++ b/app/src/resources/maintenance_runs/index.ts @@ -1 +1,2 @@ -export * from './useNotifyCurrentMaintenanceRun' +export * from './notifications' +export * from './hooks' diff --git a/app/src/resources/maintenance_runs/notifications/index.ts b/app/src/resources/maintenance_runs/notifications/index.ts new file mode 100644 index 00000000000..ecd7a95a94d --- /dev/null +++ b/app/src/resources/maintenance_runs/notifications/index.ts @@ -0,0 +1 @@ +export * from './useNotifyCurrentMaintenanceRun' diff --git a/app/src/resources/maintenance_runs/useNotifyCurrentMaintenanceRun.ts b/app/src/resources/maintenance_runs/notifications/useNotifyCurrentMaintenanceRun.ts similarity index 83% rename from app/src/resources/maintenance_runs/useNotifyCurrentMaintenanceRun.ts rename to app/src/resources/maintenance_runs/notifications/useNotifyCurrentMaintenanceRun.ts index 65e3eac02fa..2d43d7b21c5 100644 --- a/app/src/resources/maintenance_runs/useNotifyCurrentMaintenanceRun.ts +++ b/app/src/resources/maintenance_runs/notifications/useNotifyCurrentMaintenanceRun.ts @@ -1,10 +1,10 @@ import { useCurrentMaintenanceRun } from '@opentrons/react-api-client' -import { useNotifyDataReady } from '../useNotifyDataReady' +import { useNotifyDataReady } from '../../useNotifyDataReady' import type { UseQueryResult } from 'react-query' import type { MaintenanceRun } from '@opentrons/api-client' -import type { QueryOptionsWithPolling } from '../useNotifyDataReady' +import type { QueryOptionsWithPolling } from '../../useNotifyDataReady' export function useNotifyCurrentMaintenanceRun( options: QueryOptionsWithPolling = {} diff --git a/app/src/resources/runs/hooks.ts b/app/src/resources/runs/hooks.ts index 5b41edd6d4c..ffbf86abe28 100644 --- a/app/src/resources/runs/hooks.ts +++ b/app/src/resources/runs/hooks.ts @@ -4,27 +4,26 @@ import { useSelector } from 'react-redux' import { useCreateCommandMutation, useCreateLiveCommandMutation, - useCreateMaintenanceCommandMutation, useCreateMaintenanceRunMutation, } from '@opentrons/react-api-client' import { - chainRunCommandsRecursive, - chainMaintenanceCommandsRecursive, chainLiveCommandsRecursive, + chainRunCommandsRecursive, setCommandIntent, } from './utils' import { getIsOnDevice } from '../../redux/config' import { useMaintenanceRunTakeover } from '../../organisms/TakeoverModal' +import type { CreateCommand } from '@opentrons/shared-data' +import type { HostConfig } from '@opentrons/api-client' +import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' import type { + CreateMaintenanceRunType, UseCreateMaintenanceRunMutationOptions, UseCreateMaintenanceRunMutationResult, - CreateMaintenanceRunType, + useCreateMaintenanceCommandMutation, } from '@opentrons/react-api-client' -import type { CreateCommand } from '@opentrons/shared-data' -import type { HostConfig } from '@opentrons/api-client' -import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' export type CreateCommandMutate = ReturnType< typeof useCreateCommandMutation @@ -94,33 +93,6 @@ export function useChainRunCommands( } } -export function useChainMaintenanceCommands(): { - chainRunCommands: ( - maintenanceRunId: string, - commands: CreateCommand[], - continuePastCommandFailure: boolean - ) => ReturnType - isCommandMutationLoading: boolean -} { - const [isLoading, setIsLoading] = React.useState(false) - const { createMaintenanceCommand } = useCreateMaintenanceCommandMutation() - return { - chainRunCommands: ( - maintenanceRunId, - commands: CreateCommand[], - continuePastCommandFailure: boolean - ) => - chainMaintenanceCommandsRecursive( - maintenanceRunId, - commands, - createMaintenanceCommand, - continuePastCommandFailure, - setIsLoading - ), - isCommandMutationLoading: isLoading, - } -} - export function useChainLiveCommands(): { chainLiveCommands: ( commands: ModulePrepCommandsType[],