Skip to content

Commit

Permalink
feat(app): add restart robot confirmation modal (#12850)
Browse files Browse the repository at this point in the history
add restart robot confirmation modal and move Navigation components under organisms
  • Loading branch information
koji authored Jun 5, 2023
1 parent 96c1389 commit 9b56c3e
Show file tree
Hide file tree
Showing 16 changed files with 279 additions and 123 deletions.
2 changes: 2 additions & 0 deletions app/src/assets/localization/en/device_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
"rename_robot": "Rename robot",
"requires_restarting_the_robot": "Updating the robot’s software requires restarting the robot",
"reset_to_factory_settings": "Reset to factory settings?",
"restart_now": "Restart now?",
"restart_robot_confirmation_description": "<span>It will take a few minutes for <bold>{{robotName}}</bold> to restart.</span>",
"restarting_robot": "Restarting robot...",
"returns_your_device_to_new_state": "This returns your device to a new state.",
"robot_calibration_data": "Robot Calibration Data",
Expand Down
3 changes: 2 additions & 1 deletion app/src/assets/localization/en/shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@
"refresh": "refresh",
"remember_my_selection_and_do_not_ask_again": "Remember my selection and don't ask again",
"reset_all": "Reset all",
"restart": "restart",
"resume": "resume",
"return": "return",
"reverse": "Reverse alphabetical",
"robot_is_busy_no_protocol_run_allowed": "This robot is busy and can’t run this protocol right now. <robotLink>Go to Robot</robotLink>",
"robot_is_analyzing": "Robot is analyzing",
"robot_is_busy_no_protocol_run_allowed": "This robot is busy and can’t run this protocol right now. <robotLink>Go to Robot</robotLink>",
"robot_is_busy": "Robot is busy",
"robot_is_reachable_but_not_responding": "This robot's API server is not responding correctly to requests at IP address {{hostname}}",
"robot_was_seen_but_is_unreachable": "This robot has been seen recently, but is currently not reachable at IP address {{hostname}}",
Expand Down
111 changes: 111 additions & 0 deletions app/src/organisms/Navigation/NavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as React from 'react'
import { useDispatch } from 'react-redux'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
COLORS,
Flex,
Icon,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'

import { StyledText } from '../../atoms/text'
import { MenuList } from '../../atoms/MenuList'
import { MenuItem } from '../../atoms/MenuList/MenuItem'
import { home, ROBOT } from '../../redux/robot-controls'
import { useLights } from '../Devices/hooks'
import { RestartRobotConfirmationModal } from './RestartRobotConfirmationModal'

import type { Dispatch } from '../../redux/types'

interface NavigationMenuProps {
onClick: React.MouseEventHandler
robotName: string
}

export function NavigationMenu(props: NavigationMenuProps): JSX.Element {
const { onClick, robotName } = props
const { t, i18n } = useTranslation(['devices_landing', 'robot_controls'])
const { lightsOn, toggleLights } = useLights()
const dispatch = useDispatch<Dispatch>()
const [
showRestartRobotConfirmationModal,
setShowRestartRobotConfirmationModal,
] = React.useState<boolean>(false)

const handleRestart = (): void => {
setShowRestartRobotConfirmationModal(true)
}

return (
<>
{showRestartRobotConfirmationModal ? (
<RestartRobotConfirmationModal
robotName={robotName}
setShowRestartRobotConfirmationModal={
setShowRestartRobotConfirmationModal
}
/>
) : null}
<MenuList onClick={onClick} isOnDevice={true}>
<MenuItem
key="home-robot-arm"
onClick={() => dispatch(home(robotName, ROBOT))}
>
<Flex alignItems={ALIGN_CENTER}>
<Icon
name="home-robot-arm"
aria-label="home-robot-arm_icon"
size="2.5rem"
/>
<StyledText
as="h4"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
marginLeft={SPACING.spacing12}
>
{t('home_robot_arm')}
</StyledText>
</Flex>
</MenuItem>
<MenuItem key="restart" onClick={handleRestart}>
<Flex alignItems={ALIGN_CENTER}>
<Icon
name="restart"
size="2.5rem"
color={COLORS.black}
aria-label="restart_icon"
/>
<StyledText
as="h4"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
marginLeft={SPACING.spacing12}
>
{t('robot_controls:restart_label')}
</StyledText>
</Flex>
</MenuItem>
<MenuItem key="light" onClick={toggleLights}>
<Flex alignItems={ALIGN_CENTER}>
<Icon
name="light"
size="2.5rem"
color={COLORS.black}
aria-label="light_icon"
/>
<StyledText
as="h4"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
marginLeft={SPACING.spacing12}
>
{i18n.format(
t(lightsOn ? 'lights_off' : 'lights_on'),
'capitalize'
)}
</StyledText>
</Flex>
</MenuItem>
</MenuList>
</>
)
}
77 changes: 77 additions & 0 deletions app/src/organisms/Navigation/RestartRobotConfirmationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'

import {
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
Flex,
SPACING,
} from '@opentrons/components'

import { StyledText } from '../../atoms/text'
import { SmallButton } from '../../atoms/buttons'
import { Modal } from '../../molecules/Modal/OnDeviceDisplay'
import { restartRobot } from '../../redux/robot-admin'

import { Dispatch } from '../../redux/types'
import { ModalHeaderBaseProps } from '../../molecules/Modal/OnDeviceDisplay/types'

interface RestartRobotConfirmationModalProps {
robotName: string
setShowRestartRobotConfirmationModal: (
showRestartRobotConfirmationModal: boolean
) => void
}
export function RestartRobotConfirmationModal({
robotName,
setShowRestartRobotConfirmationModal,
}: RestartRobotConfirmationModalProps): JSX.Element {
const { i18n, t } = useTranslation(['device_settings', 'shared'])
const modalHeader: ModalHeaderBaseProps = {
title: t('restart_now'),
iconName: 'ot-alert',
iconColor: COLORS.yellow2,
}
const dispatch = useDispatch<Dispatch>()

return (
<Modal header={modalHeader}>
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing40}
width="100%"
>
<Trans
t={t}
i18nKey="restart_robot_confirmation_description"
values={{ robotName: robotName }}
components={{
bold: <strong />,
span: (
<StyledText
as="p"
data-testid="restart_robot_confirmation_description"
/>
),
}}
/>
<Flex flexDirection={DIRECTION_ROW} gridGap={SPACING.spacing8}>
<SmallButton
flex="1"
buttonType="primary"
buttonText={t('shared:go_back')}
onClick={() => setShowRestartRobotConfirmationModal(false)}
/>
<SmallButton
flex="1"
buttonType="alert"
buttonText={i18n.format(t('shared:restart'), 'capitalize')}
onClick={() => dispatch(restartRobot(robotName))}
/>
</Flex>
</Flex>
</Modal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { i18n } from '../../../i18n'
import { useNetworkConnection } from '../../../pages/OnDeviceDisplay/hooks'
import { getLocalRobot } from '../../../redux/discovery'
import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__'
import { NavigationMenu } from '../Navigation/NavigationMenu'
import { Navigation } from '../Navigation'
import { NavigationMenu } from '../NavigationMenu'
import { Navigation } from '..'

jest.mock('../../../pages/OnDeviceDisplay/hooks/useNetworkConnection')
jest.mock('../../../redux/discovery')
jest.mock('../Navigation/NavigationMenu')
jest.mock('../NavigationMenu')

const mockGetLocalRobot = getLocalRobot as jest.MockedFunction<
typeof getLocalRobot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import * as React from 'react'
import { fireEvent } from '@testing-library/react'
import { i18n } from '../../../i18n'

import { renderWithProviders } from '@opentrons/components'
import { restartRobot } from '../../../redux/robot-admin'

import { i18n } from '../../../i18n'
import { home } from '../../../redux/robot-controls'
import { useLights } from '../../Devices/hooks'
import { NavigationMenu } from '../Navigation/NavigationMenu'
import { RestartRobotConfirmationModal } from '../RestartRobotConfirmationModal'
import { NavigationMenu } from '../NavigationMenu'

jest.mock('../../../redux/robot-admin')
jest.mock('../../../redux/robot-controls')
jest.mock('../../Devices/hooks')
jest.mock('../RestartRobotConfirmationModal')

const mockUseLights = useLights as jest.MockedFunction<typeof useLights>
const mockHome = home as jest.MockedFunction<typeof home>
const mockRestartRobot = restartRobot as jest.MockedFunction<
typeof restartRobot
>
const mockToggleLights = jest.fn()

const mockRestartRobotConfirmationModal = RestartRobotConfirmationModal as jest.MockedFunction<
typeof RestartRobotConfirmationModal
>

const render = (props: React.ComponentProps<typeof NavigationMenu>) => {
return renderWithProviders(<NavigationMenu {...props} />, {
i18nInstance: i18n,
Expand All @@ -35,30 +38,33 @@ describe('NavigationMenu', () => {
lightsOn: false,
toggleLights: mockToggleLights,
})
mockRestartRobotConfirmationModal.mockReturnValue(
<div>mock RestartRobotConfirmationModal</div>
)
})
it('should render the home menu item and clicking home robot arm, dispatches home', () => {
const { getByText, getByLabelText } = render(props)
fireEvent.click(getByLabelText('BackgroundOverlay_ModalShell'))
getByLabelText('BackgroundOverlay_ModalShell').click()
expect(props.onClick).toHaveBeenCalled()
const home = getByText('Home robot arm')
getByLabelText('home-robot-arm_icon')
fireEvent.click(home)
home.click()
expect(mockHome).toHaveBeenCalled()
})

it('should render the restart robot menu item and clicking it, dispatches restart robot', () => {
const { getByText, getByLabelText } = render(props)
const restart = getByText('Restart robot')
getByLabelText('restart_icon')
fireEvent.click(restart)
expect(mockRestartRobot).toHaveBeenCalled()
restart.click()
getByText('mock RestartRobotConfirmationModal')
})

it('should render the lights menu item with lights off and clicking it, calls useLights', () => {
const { getByText, getByLabelText } = render(props)
const lights = getByText('Lights on')
getByLabelText('light_icon')
fireEvent.click(lights)
lights.click()
expect(mockToggleLights).toHaveBeenCalled()
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react'

import { renderWithProviders } from '@opentrons/components'

import { i18n } from '../../../i18n'
import { restartRobot } from '../../../redux/robot-admin'
import { RestartRobotConfirmationModal } from '../RestartRobotConfirmationModal'

jest.mock('../../../redux/robot-admin')

const mockFunc = jest.fn()
const mockRestartRobot = restartRobot as jest.MockedFunction<
typeof restartRobot
>
const render = (
props: React.ComponentProps<typeof RestartRobotConfirmationModal>
) => {
return renderWithProviders(<RestartRobotConfirmationModal {...props} />, {
i18nInstance: i18n,
})
}

describe('RestartRobotConfirmationModal', () => {
let props: React.ComponentProps<typeof RestartRobotConfirmationModal>

beforeEach(() => {
props = {
robotName: 'mockRobotName',
setShowRestartRobotConfirmationModal: mockFunc,
}
})

it('should render text and buttons', () => {
const [{ getByText, getByTestId }] = render(props)
getByText('Restart now?')
getByTestId('restart_robot_confirmation_description')
getByText('Go back')
getByText('Restart')
})

it('should call a mock function when tapping go back button', () => {
const [{ getByText }] = render(props)
getByText('Go back').click()
expect(mockFunc).toHaveBeenCalled()
})

it('should call mock restart function when tapping restart', () => {
const [{ getByText }] = render(props)
getByText('Restart').click()
expect(mockRestartRobot).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import {
POSITION_STATIC,
} from '@opentrons/components'

import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons/constants'
import { useNetworkConnection } from '../../../pages/OnDeviceDisplay/hooks'
import { getLocalRobot } from '../../../redux/discovery'
import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants'
import { useNetworkConnection } from '../../pages/OnDeviceDisplay/hooks'
import { getLocalRobot } from '../../redux/discovery'
import { NavigationMenu } from './NavigationMenu'

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

interface NavigationProps {
routes: RouteProps[]
Expand Down
Loading

0 comments on commit 9b56c3e

Please sign in to comment.