Skip to content

Commit

Permalink
feat(app): feat add open door check during running a protocol for odd (
Browse files Browse the repository at this point in the history
…#13613)

add open door status check to display open door alert modal for odd and fix protocol run header
banner rendering bugin desktop app

fix RQA-1604
  • Loading branch information
koji authored Sep 20, 2023
1 parent 04da21d commit 6e0adab
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 28 deletions.
1 change: 1 addition & 0 deletions app/src/assets/localization/en/run_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"current_step": "Current Step",
"current_temperature": "Current: {{temperature}} °C",
"data_out_of_date": "This data is likely out of date",
"door_is_open": "Robot door is open",
"door_open_pause": "Current Step - Paused - Door Open",
"download_run_log": "Download run log",
"downloading_run_log": "Downloading run log",
Expand Down
7 changes: 5 additions & 2 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ export function ProtocolRunHeader({
robotName={robotName}
/>
)}

<Flex>
{protocolKey != null ? (
<Link to={`/protocols/${protocolKey}`}>
Expand All @@ -268,7 +267,11 @@ export function ProtocolRunHeader({
{runStatus === RUN_STATUS_STOPPED ? (
<Banner type="warning">{t('run_canceled')}</Banner>
) : null}
{isDoorOpen ? (
{/* Note: This banner is for before running a protocol */}
{isDoorOpen &&
runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR &&
runStatus != null &&
CANCELLABLE_STATUSES.includes(runStatus) ? (
<Banner type="warning">{t('shared:close_robot_door')}</Banner>
) : null}
{isRunCurrent ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react'

import { renderWithProviders } from '@opentrons/components'

import { i18n } from '../../../i18n'

import { OpenDoorAlertModal } from '..'

const render = () => {
return renderWithProviders(<OpenDoorAlertModal />, {
i18nInstance: i18n,
})
}

describe('OpenDoorAlertModal', () => {
it('should render text', () => {
const [{ getByText }] = render()
getByText('Robot door is open')
getByText('Close robot door to resume run')
})
})
55 changes: 55 additions & 0 deletions app/src/organisms/OpenDoorAlertModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
BORDERS,
COLORS,
DIRECTION_COLUMN,
Flex,
Icon,
JUSTIFY_CENTER,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import { Portal } from '../../App/portal'
import { StyledText } from '../../atoms/text'
import { Modal } from '../../molecules/Modal'

export function OpenDoorAlertModal(): JSX.Element {
const { t } = useTranslation('run_details')
return (
<Portal level="top">
<Modal>
<Flex
backgroundColor={COLORS.darkBlack20}
borderRadius={BORDERS.borderRadiusSize3}
flexDirection={DIRECTION_COLUMN}
padding={SPACING.spacing24}
alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing16}
width="100%"
justifyContent={JUSTIFY_CENTER}
>
<Icon name="ot-alert" size="2.5rem" />
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing4}
alignItems={ALIGN_CENTER}
width="100%"
>
<StyledText as="h4" fontWeight={TYPOGRAPHY.fontWeightBold}>
{t('door_is_open')}
</StyledText>
<StyledText
as="p"
textAlign={TYPOGRAPHY.textAlignCenter}
color={COLORS.darkBlack90}
>
{t('close_door_to_resume')}
</StyledText>
</Flex>
</Flex>
</Modal>
</Portal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { getDeckDefFromRobotType } from '@opentrons/shared-data'
import ot3StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'

import { i18n } from '../../../../i18n'
import { useToaster } from '../../../../organisms/ToasterOven'
import { mockRobotSideAnalysis } from '../../../../organisms/CommandText/__fixtures__'
import {
useAttachedModules,
Expand All @@ -37,6 +36,7 @@ import {
} from '../../../../organisms/RunTimeControl/hooks'
import { useIsHeaterShakerInProtocol } from '../../../../organisms/ModuleCard/hooks'
import { ConfirmAttachedModal } from '../ConfirmAttachedModal'
import { OpenDoorAlertModal } from '../../../../organisms/OpenDoorAlertModal'
import { ProtocolSetup } from '..'

import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
Expand Down Expand Up @@ -72,7 +72,7 @@ jest.mock('../../../../organisms/ProtocolSetupLiquids')
jest.mock('../../../../organisms/ModuleCard/hooks')
jest.mock('../../../../redux/config')
jest.mock('../ConfirmAttachedModal')
jest.mock('../../../../organisms/ToasterOven')
jest.mock('../../../../organisms/OpenDoorAlertModal')

const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction<
typeof getDeckDefFromRobotType
Expand Down Expand Up @@ -132,7 +132,9 @@ const mockConfirmAttachedModal = ConfirmAttachedModal as jest.MockedFunction<
const mockUseDoorQuery = useDoorQuery as jest.MockedFunction<
typeof useDoorQuery
>
const mockUseToaster = useToaster as jest.MockedFunction<typeof useToaster>
const mockOpenDoorAlertModal = OpenDoorAlertModal as jest.MockedFunction<
typeof OpenDoorAlertModal
>

const render = (path = '/') => {
return renderWithProviders(
Expand Down Expand Up @@ -198,7 +200,6 @@ const mockDoorStatus = {
doorRequiredClosedForProtocol: true,
},
}
const MOCK_MAKE_SNACKBAR = jest.fn()

describe('ProtocolSetup', () => {
let mockLaunchLPC: jest.Mock
Expand Down Expand Up @@ -279,11 +280,7 @@ describe('ProtocolSetup', () => {
<div>mock ConfirmAttachedModal</div>
)
mockUseDoorQuery.mockReturnValue({ data: mockDoorStatus } as any)
when(mockUseToaster)
.calledWith()
.mockReturnValue(({
makeSnackbar: MOCK_MAKE_SNACKBAR,
} as unknown) as any)
mockOpenDoorAlertModal.mockReturnValue(<div>mock OpenDoorAlertModal</div>)
})

afterEach(() => {
Expand Down Expand Up @@ -375,19 +372,15 @@ describe('ProtocolSetup', () => {
expect(getAllByTestId('Skeleton').length).toBeGreaterThan(0)
})

it('should render toast and make a button disabled when a robot door is open', () => {
it('should render open door alert modal when door is open', () => {
const mockOpenDoorStatus = {
data: {
status: 'open',
doorRequiredClosedForProtocol: true,
},
}
mockUseDoorQuery.mockReturnValue({ data: mockOpenDoorStatus } as any)
const [{ getByRole }] = render(`/runs/${RUN_ID}/setup/`)
expect(MOCK_MAKE_SNACKBAR).toBeCalledWith(
'Close the robot door before starting the run.',
7000
)
expect(getByRole('button', { name: 'play' })).toBeDisabled()
const [{ getByText }] = render(`/runs/${RUN_ID}/setup/`)
getByText('mock OpenDoorAlertModal')
})
})
12 changes: 3 additions & 9 deletions app/src/pages/OnDeviceDisplay/ProtocolSetup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ import {
} from '../../../redux/analytics'
import { getIsHeaterShakerAttached } from '../../../redux/config'
import { ConfirmAttachedModal } from './ConfirmAttachedModal'
import { getLatestCurrentOffsets } from '../../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils'
import { OpenDoorAlertModal } from '../../../organisms/OpenDoorAlertModal'

import type { OnDeviceRouteParams } from '../../../App/types'
import { getLatestCurrentOffsets } from '../../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils'

const FETCH_DOOR_STATUS_MS = 5000
const SNACK_BAR_DURATION_MS = 7000
interface ProtocolSetupStepProps {
onClickSetupStep: () => void
status: 'ready' | 'not ready' | 'general'
Expand Down Expand Up @@ -491,16 +491,10 @@ function PrepareToRun({
const isDoorOpen =
doorStatus?.data.status === 'open' &&
doorStatus?.data.doorRequiredClosedForProtocol
React.useEffect(() => {
// Note show snackbar when instruments and modules are all green
// but the robot door is open
if (isReadyToRun && isDoorOpen) {
makeSnackbar(t('shared:close_robot_door'), SNACK_BAR_DURATION_MS)
}
}, [isDoorOpen])

return (
<>
{isReadyToRun && isDoorOpen ? <OpenDoorAlertModal /> : null}
{/* Empty box to detect scrolling */}
<Flex ref={scrollRef} />
{/* Protocol Setup Header */}
Expand Down
9 changes: 8 additions & 1 deletion app/src/pages/OnDeviceDisplay/RunningProtocol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
useRunQuery,
useRunActionMutations,
} from '@opentrons/react-api-client'
import { RUN_STATUS_STOP_REQUESTED } from '@opentrons/api-client'
import {
RUN_STATUS_STOP_REQUESTED,
RUN_STATUS_BLOCKED_BY_OPEN_DOOR,
} from '@opentrons/api-client'

import { StepMeter } from '../../atoms/StepMeter'
import { useMostRecentCompletedAnalysis } from '../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis'
Expand All @@ -44,6 +47,7 @@ import {
import { CancelingRunModal } from '../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal'
import { ConfirmCancelRunModal } from '../../organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal'
import { getLocalRobot } from '../../redux/discovery'
import { OpenDoorAlertModal } from '../../organisms/OpenDoorAlertModal'

import type { OnDeviceRouteParams } from '../../App/types'

Expand Down Expand Up @@ -147,6 +151,9 @@ export function RunningProtocol(): JSX.Element {

return (
<>
{runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR ? (
<OpenDoorAlertModal />
) : null}
{runStatus === RUN_STATUS_STOP_REQUESTED ? <CancelingRunModal /> : null}
<Flex
flexDirection={DIRECTION_COLUMN}
Expand Down
15 changes: 15 additions & 0 deletions app/src/pages/OnDeviceDisplay/__tests__/RunningProtocol.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom'
import { when, resetAllWhenMocks } from 'jest-when'

import {
RUN_STATUS_BLOCKED_BY_OPEN_DOOR,
RUN_STATUS_IDLE,
RUN_STATUS_STOP_REQUESTED,
} from '@opentrons/api-client'
Expand All @@ -31,6 +32,7 @@ import {
import { CancelingRunModal } from '../../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal'
import { useTrackProtocolRunEvent } from '../../../organisms/Devices/hooks'
import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { OpenDoorAlertModal } from '../../../organisms/OpenDoorAlertModal'
import { RunningProtocol } from '../RunningProtocol'

import type { ProtocolAnalyses } from '@opentrons/api-client'
Expand All @@ -48,6 +50,7 @@ jest.mock('../../../redux/discovery')
jest.mock(
'../../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal'
)
jest.mock('../../../organisms/OpenDoorAlertModal')

const mockUseProtocolAnalysesQuery = useProtocolAnalysesQuery as jest.MockedFunction<
typeof useProtocolAnalysesQuery
Expand Down Expand Up @@ -86,6 +89,9 @@ const mockCancelingRunModal = CancelingRunModal as jest.MockedFunction<
const mockUseAllCommandsQuery = useAllCommandsQuery as jest.MockedFunction<
typeof useAllCommandsQuery
>
const mockOpenDoorAlertModal = OpenDoorAlertModal as jest.MockedFunction<
typeof OpenDoorAlertModal
>

const RUN_ID = 'run_id'
const PROTOCOL_ID = 'protocol_id'
Expand Down Expand Up @@ -173,6 +179,7 @@ describe('RunningProtocol', () => {
when(mockUseAllCommandsQuery)
.calledWith(RUN_ID, { cursor: null, pageLength: 1 })
.mockReturnValue(mockUseAllCommandsResponseNonDeterministic)
mockOpenDoorAlertModal.mockReturnValue(<div>mock OpenDoorAlertModal</div>)
})

afterEach(() => {
Expand All @@ -199,6 +206,14 @@ describe('RunningProtocol', () => {
getByText('mock CurrentRunningProtocolCommand')
})

it('should render open door alert modal, when run staus is blocked by open door', () => {
when(mockUseRunStatus)
.calledWith(RUN_ID, { refetchInterval: 5000 })
.mockReturnValue(RUN_STATUS_BLOCKED_BY_OPEN_DOOR)
const [{ getByText }] = render(`/runs/${RUN_ID}/run`)
getByText('mock OpenDoorAlertModal')
})

// ToDo (kj:04/04/2023) need to figure out the way to simulate swipe
it.todo('should render RunningProtocolCommandList when swiping left')
// const [{ getByText }] = render(`/runs/${RUN_ID}/run`)
Expand Down

0 comments on commit 6e0adab

Please sign in to comment.