Skip to content

Commit

Permalink
refactor(app): Migrate error recovery utils (#15281)
Browse files Browse the repository at this point in the history
Works toward EXEC-424. Split up error recovery utils into separate files.
  • Loading branch information
mjhuff authored May 29, 2024
1 parent 58a43e7 commit d535a2a
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 218 deletions.
28 changes: 18 additions & 10 deletions app/src/molecules/InterventionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import {
POSITION_STICKY,
SPACING,
} from '@opentrons/components'
import type { IconName } from '@opentrons/components'

import { getIsOnDevice } from '../../redux/config'

import type { IconName } from '@opentrons/components'

export type ModalType = 'intervention-required' | 'error'

const BASE_STYLE = {
Expand Down Expand Up @@ -97,15 +98,22 @@ export interface InterventionModalProps {
children: React.ReactNode
}

export function InterventionModal(props: InterventionModalProps): JSX.Element {
const modalType = props.type ?? 'intervention-required'
export function InterventionModal({
type,
titleHeading,
iconHeadingOnClick,
iconName,
iconHeading,
children,
}: InterventionModalProps): JSX.Element {
const modalType = type ?? 'intervention-required'
const headerColor =
modalType === 'error' ? ERROR_COLOR : INTERVENTION_REQUIRED_COLOR
const border = `${BORDER_STYLE_BASE} ${
modalType === 'error' ? ERROR_COLOR : INTERVENTION_REQUIRED_COLOR
}`
const headerJustifyContent =
props.titleHeading != null ? JUSTIFY_SPACE_BETWEEN : undefined
titleHeading != null ? JUSTIFY_SPACE_BETWEEN : undefined

const isOnDevice = useSelector(getIsOnDevice)
const modalStyle = isOnDevice ? MODAL_ODD_STYLE : MODAL_DESKTOP_STYLE
Expand All @@ -124,17 +132,17 @@ export function InterventionModal(props: InterventionModalProps): JSX.Element {
{...HEADER_STYLE}
backgroundColor={headerColor}
justifyContent={headerJustifyContent}
onClick={props.iconHeadingOnClick}
onClick={iconHeadingOnClick}
>
{props.titleHeading}
{titleHeading}
<Flex alignItems={ALIGN_CENTER} gridGap={SPACING.spacing12}>
{props.iconName != null ? (
<Icon name={props.iconName} size={SPACING.spacing32} />
{iconName != null ? (
<Icon name={iconName} size={SPACING.spacing32} />
) : null}
{props.iconHeading != null ? props.iconHeading : null}
{iconHeading != null ? iconHeading : null}
</Flex>
</Flex>
{props.children}
{children}
</Box>
</Flex>
</Flex>
Expand Down
6 changes: 2 additions & 4 deletions app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react'
import { createPortal } from 'react-dom'
import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'

import { StyledText } from '@opentrons/components'

Expand All @@ -17,12 +18,9 @@ import type { FailedCommand, IRecoveryMap, RecoveryContentProps } from './types'
import type {
useRouteUpdateActions,
UseRouteUpdateActionsResult,
} from './utils'
import type {
useRecoveryCommands,
UseRecoveryCommandsResult,
} from './useRecoveryCommands'
import { useTranslation } from 'react-i18next'
} from './utils'

interface UseERWizardResult {
hasLaunchedRecovery: boolean
Expand Down
7 changes: 5 additions & 2 deletions app/src/organisms/ErrorRecoveryFlows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import {
import { useFeatureFlag } from '../../redux/config'
import { ErrorRecoveryWizard, useERWizard } from './ErrorRecoveryWizard'
import { useRunPausedSplash, RunPausedSplash } from './RunPausedSplash'
import { useCurrentlyRecoveringFrom, useRouteUpdateActions } from './utils'
import { useRecoveryCommands } from './useRecoveryCommands'
import {
useCurrentlyRecoveringFrom,
useRouteUpdateActions,
useRecoveryCommands,
} from './utils'
import { RECOVERY_MAP } from './constants'

import type { RunStatus } from '@opentrons/api-client'
Expand Down
6 changes: 4 additions & 2 deletions app/src/organisms/ErrorRecoveryFlows/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { RunCommandSummary } from '@opentrons/api-client'
import type { ERROR_KINDS, RECOVERY_MAP, INVALID } from './constants'
import type { UseRouteUpdateActionsResult } from './utils'
import type { UseRecoveryCommandsResult } from './useRecoveryCommands'
import type {
UseRouteUpdateActionsResult,
UseRecoveryCommandsResult,
} from './utils'

export type FailedCommand = RunCommandSummary
export type InvalidStep = typeof INVALID
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest'

import { ERROR_KINDS } from '../../constants'
import { getErrorKind } from '../getErrorKind'

describe('getErrorKind', () => {
it(`returns ${ERROR_KINDS.GENERAL_ERROR} if the errorType isn't handled explicitly`, () => {
const mockErrorType = 'NON_HANDLED_ERROR'
const result = getErrorKind(mockErrorType)
expect(result).toEqual(ERROR_KINDS.GENERAL_ERROR)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { vi, describe, it, expect } from 'vitest'
import { renderHook } from '@testing-library/react'

import { useCommandQuery } from '@opentrons/react-api-client'
import {
RUN_STATUS_AWAITING_RECOVERY,
RUN_STATUS_IDLE,
} from '@opentrons/api-client'

import { useNotifyAllCommandsQuery } from '../../../../resources/runs'
import { useCurrentlyRecoveringFrom } from '../useCurrentlyRecoveringFrom'

vi.mock('@opentrons/react-api-client')
vi.mock('../../../../resources/runs')

const MOCK_RUN_ID = 'runId'
const MOCK_COMMAND_ID = 'commandId'

describe('useCurrentlyRecoveringFrom', () => {
it('disables all queries if the run is not awaiting-recovery', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {
currentlyRecoveringFrom: {
meta: {
runId: MOCK_RUN_ID,
commandId: MOCK_COMMAND_ID,
},
},
},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({
data: { data: 'mockCommandDetails' },
} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_IDLE)
)

expect(vi.mocked(useNotifyAllCommandsQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
{ cursor: null, pageLength: 0 },
{ enabled: false, refetchInterval: 5000 }
)
expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
MOCK_COMMAND_ID,
{ enabled: false }
)
expect(result.current).toStrictEqual(null)
})

it('returns null if there is no currentlyRecoveringFrom command', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY)
)

expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(null, null, {
enabled: false,
})
expect(result.current).toStrictEqual(null)
})

it('fetches and returns the currentlyRecoveringFrom command, given that there is one', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {
currentlyRecoveringFrom: {
meta: {
runId: MOCK_RUN_ID,
commandId: MOCK_COMMAND_ID,
},
},
},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({
data: { data: 'mockCommandDetails' },
} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY)
)

expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
MOCK_COMMAND_ID,
{ enabled: true }
)
expect(result.current).toStrictEqual('mockCommandDetails')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
useStopRunMutation,
} from '@opentrons/react-api-client'

import { useChainRunCommands } from '../../../resources/runs'
import { useChainRunCommands } from '../../../../resources/runs'
import {
useRecoveryCommands,
HOME_PIPETTE_Z_AXES,
} from '../useRecoveryCommands'

vi.mock('@opentrons/react-api-client')
vi.mock('../../../resources/runs')
vi.mock('../../../../resources/runs')

const mockFailedCommand = {
id: 'MOCK_ID',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { renderHook } from '@testing-library/react'

import { ERROR_KINDS, INVALID, RECOVERY_MAP } from '../constants'
import {
getErrorKind,
getRecoveryRouteNavigation,
useRouteUpdateActions,
useCurrentlyRecoveringFrom,
} from '../utils'
import { useNotifyAllCommandsQuery } from '../../../resources/runs'
getRecoveryRouteNavigation,
} from '../useRouteUpdateActions'
import { INVALID, RECOVERY_MAP } from '../../constants'

import type { Mock } from 'vitest'
import type { GetRouteUpdateActionsParams } from '../utils'
import { useCommandQuery } from '@opentrons/react-api-client'
import {
RUN_STATUS_AWAITING_RECOVERY,
RUN_STATUS_IDLE,
} from '@opentrons/api-client'

vi.mock('@opentrons/react-api-client')
vi.mock('../../../resources/runs')

describe('getErrorKind', () => {
it(`returns ${ERROR_KINDS.GENERAL_ERROR} if the errorType isn't handled explicitly`, () => {
const mockErrorType = 'NON_HANDLED_ERROR'
const result = getErrorKind(mockErrorType)
expect(result).toEqual(ERROR_KINDS.GENERAL_ERROR)
})
})

describe('getRecoveryRouteNavigation', () => {
it(`getNextStep and getPrevStep return ${INVALID} if the recovery route does not contain multiple steps`, () => {
const { ROBOT_IN_MOTION } = RECOVERY_MAP
const { getNextStep, getPrevStep } = getRecoveryRouteNavigation(
ROBOT_IN_MOTION.ROUTE
)
const nextStepResult = getNextStep(ROBOT_IN_MOTION.STEPS.IN_MOTION)
const prevStepResult = getPrevStep(ROBOT_IN_MOTION.STEPS.IN_MOTION)

expect(nextStepResult).toEqual(INVALID)
expect(prevStepResult).toEqual(INVALID)
})
})
import type { GetRouteUpdateActionsParams } from '../useRouteUpdateActions'

describe('useRouteUpdateActions', () => {
const { OPTION_SELECTION } = RECOVERY_MAP
Expand Down Expand Up @@ -182,88 +149,16 @@ describe('useRouteUpdateActions', () => {
})
})

const MOCK_RUN_ID = 'runId'
const MOCK_COMMAND_ID = 'commandId'

describe('useCurrentlyRecoveringFrom', () => {
it('disables all queries if the run is not awaiting-recovery', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {
currentlyRecoveringFrom: {
meta: {
runId: MOCK_RUN_ID,
commandId: MOCK_COMMAND_ID,
},
},
},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({
data: { data: 'mockCommandDetails' },
} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_IDLE)
)

expect(vi.mocked(useNotifyAllCommandsQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
{ cursor: null, pageLength: 0 },
{ enabled: false, refetchInterval: 5000 }
)
expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
MOCK_COMMAND_ID,
{ enabled: false }
)
expect(result.current).toStrictEqual(null)
})

it('returns null if there is no currentlyRecoveringFrom command', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY)
)

expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(null, null, {
enabled: false,
})
expect(result.current).toStrictEqual(null)
})

it('fetches and returns the currentlyRecoveringFrom command, given that there is one', () => {
vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({
data: {
links: {
currentlyRecoveringFrom: {
meta: {
runId: MOCK_RUN_ID,
commandId: MOCK_COMMAND_ID,
},
},
},
},
} as any)
vi.mocked(useCommandQuery).mockReturnValue({
data: { data: 'mockCommandDetails' },
} as any)

const { result } = renderHook(() =>
useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY)
describe('getRecoveryRouteNavigation', () => {
it(`getNextStep and getPrevStep return ${INVALID} if the recovery route does not contain multiple steps`, () => {
const { ROBOT_IN_MOTION } = RECOVERY_MAP
const { getNextStep, getPrevStep } = getRecoveryRouteNavigation(
ROBOT_IN_MOTION.ROUTE
)
const nextStepResult = getNextStep(ROBOT_IN_MOTION.STEPS.IN_MOTION)
const prevStepResult = getPrevStep(ROBOT_IN_MOTION.STEPS.IN_MOTION)

expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith(
MOCK_RUN_ID,
MOCK_COMMAND_ID,
{ enabled: true }
)
expect(result.current).toStrictEqual('mockCommandDetails')
expect(nextStepResult).toEqual(INVALID)
expect(prevStepResult).toEqual(INVALID)
})
})
Loading

0 comments on commit d535a2a

Please sign in to comment.