Skip to content

Commit

Permalink
feat(app): orchestration component for new quick transfer flow (#14808)
Browse files Browse the repository at this point in the history
  • Loading branch information
smb2268 authored and Carlos-fernandez committed May 20, 2024
1 parent 8212642 commit 683c72a
Show file tree
Hide file tree
Showing 16 changed files with 535 additions and 10 deletions.
4 changes: 4 additions & 0 deletions app/src/App/OnDeviceDisplayApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { RobotDashboard } from '../pages/RobotDashboard'
import { RobotSettingsDashboard } from '../pages/RobotSettingsDashboard'
import { ProtocolDashboard } from '../pages/ProtocolDashboard'
import { ProtocolDetails } from '../pages/ProtocolDetails'
import { QuickTransferFlow } from '../organisms/QuickTransferFlow'
import { RunningProtocol } from '../pages/RunningProtocol'
import { RunSummary } from '../pages/RunSummary'
import { UpdateRobot } from '../pages/UpdateRobot/UpdateRobot'
Expand Down Expand Up @@ -73,6 +74,7 @@ export const ON_DEVICE_DISPLAY_PATHS = [
'/network-setup/wifi',
'/protocols',
'/protocols/:protocolId',
'/quick-transfer',
'/robot-settings',
'/robot-settings/rename-robot',
'/robot-settings/update-robot',
Expand Down Expand Up @@ -109,6 +111,8 @@ function getPathComponent(
return <ProtocolDashboard />
case '/protocols/:protocolId':
return <ProtocolDetails />
case `/quick-transfer`:
return <QuickTransferFlow />
case '/robot-settings':
return <RobotSettingsDashboard />
case '/robot-settings/rename-robot':
Expand Down
2 changes: 2 additions & 0 deletions app/src/assets/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import protocol_details from './protocol_details.json'
import protocol_info from './protocol_info.json'
import protocol_list from './protocol_list.json'
import protocol_setup from './protocol_setup.json'
import quick_transfer from './quick_transfer.json'
import robot_calibration from './robot_calibration.json'
import robot_controls from './robot_controls.json'
import run_details from './run_details.json'
Expand Down Expand Up @@ -51,6 +52,7 @@ export const en = {
protocol_info,
protocol_list,
protocol_setup,
quick_transfer,
robot_calibration,
robot_controls,
run_details,
Expand Down
18 changes: 18 additions & 0 deletions app/src/assets/localization/en/quick_transfer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"create_new_transfer": "Create new quick transfer",
"select_attached_pipette": "Select attached pipette",
"select_dest_labware": "Select destination labware",
"select_dest_wells": "Select destination wells",
"select_source_labware": "Select source labware",
"select_source_wells": "Select source wells",
"select_tip_rack": "Select tip rack",
"set_aspirate_volume": "Set aspirate volume",
"set_dispense_volume": "Set dispense volume",
"set_transfer_volume": "Set transfer volume",
"use_deck_slots": "<block>Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.</block><block>Make sure that your deck configuration is up to date to avoid collisions.</block>",
"tip_rack": "Tip rack",
"labware": "Labware",
"pipette_currently_attached": "Quick transfer options depend on the pipettes currently attached to your robot.",
"well_selection": "Well selection",
"well_ratio": "Quick transfers with multiple source wells can either be one-to-one (select {{wells}} for this transfer) or consolidate (select 1 destination well)."
}
19 changes: 19 additions & 0 deletions app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,30 @@ const Template: Story<React.ComponentProps<typeof ChildNavigation>> = args => (
export const Default = Template.bind({})
Default.args = {
header: 'Header',
onClickBack: () => {},
}

export const TitleNoBackButton = Template.bind({})
TitleNoBackButton.args = {
header: 'Header',
onClickBack: undefined,
}

export const TitleWithNormalSmallButton = Template.bind({})
TitleWithNormalSmallButton.args = {
header: 'Header',
buttonText: 'ButtonText',
onClickButton: () => {},
onClickBack: () => {},
}

export const TitleWithNormalSmallButtonDisabled = Template.bind({})
TitleWithNormalSmallButtonDisabled.args = {
header: 'Header',
buttonText: 'ButtonText',
onClickButton: () => {},
onClickBack: () => {},
buttonIsDisabled: true,
}

export const TitleWithLinkButton = Template.bind({})
Expand All @@ -32,6 +49,7 @@ TitleWithLinkButton.args = {
iconName: 'information',
iconPlacement: 'startIcon',
onClickButton: () => {},
onClickBack: () => {},
}

export const TitleWithTwoButtons = Template.bind({})
Expand All @@ -47,4 +65,5 @@ TitleWithTwoButtons.args = {
buttonText: 'ButtonText',
onClickButton: () => {},
secondaryButtonProps,
onClickBack: () => {},
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,26 @@ describe('ChildNavigation', () => {
fireEvent.click(secondaryButton)
expect(mockOnClickSecondaryButton).toHaveBeenCalled()
})
it.fails(
'should not render back button if onClickBack does not exist',
() => {
props = {
...props,
onClickBack: undefined,
}
render(props)
screen.getByTestId('ChildNavigation_Back_Button')
}
)
it('should render button as disabled', () => {
props = {
...props,
buttonText: 'mock button',
onClickButton: mockOnClickButton,
buttonIsDisabled: true,
}
render(props)
const button = screen.getByTestId('ChildNavigation_Primary_Button')
expect(button).toBeDisabled()
})
})
26 changes: 17 additions & 9 deletions app/src/organisms/ChildNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants'
import { SmallButton } from '../../atoms/buttons'
import { InlineNotification } from '../../atoms/InlineNotification'

import type { IconName } from '@opentrons/components'
import type { IconName, StyleProps } from '@opentrons/components'
import type { InlineNotificationProps } from '../../atoms/InlineNotification'
import type {
IconPlacement,
SmallButtonTypes,
} from '../../atoms/buttons/SmallButton'

interface ChildNavigationProps {
interface ChildNavigationProps extends StyleProps {
header: string
onClickBack: React.MouseEventHandler
onClickBack?: React.MouseEventHandler
buttonText?: React.ReactNode
inlineNotification?: InlineNotificationProps
onClickButton?: React.MouseEventHandler
buttonType?: SmallButtonTypes
buttonIsDisabled?: boolean
iconName?: IconName
iconPlacement?: IconPlacement
secondaryButtonProps?: React.ComponentProps<typeof SmallButton>
Expand All @@ -49,6 +50,8 @@ export function ChildNavigation({
iconName,
iconPlacement,
secondaryButtonProps,
buttonIsDisabled,
...styleProps
}: ChildNavigationProps): JSX.Element {
return (
<Flex
Expand All @@ -62,14 +65,17 @@ export function ChildNavigation({
left="0"
width="100%"
backgroundColor={COLORS.white}
{...styleProps}
>
<Flex gridGap={SPACING.spacing16} justifyContent={JUSTIFY_FLEX_START}>
<IconButton
onClick={onClickBack}
data-testid="ChildNavigation_Back_Button"
>
<Icon name="back" size="3rem" color={COLORS.black90} />
</IconButton>
{onClickBack != null ? (
<IconButton
onClick={onClickBack}
data-testid="ChildNavigation_Back_Button"
>
<Icon name="back" size="3rem" color={COLORS.black90} />
</IconButton>
) : null}
<StyledText as="h2" fontWeight={TYPOGRAPHY.fontWeightBold}>
{header}
</StyledText>
Expand All @@ -87,6 +93,8 @@ export function ChildNavigation({
onClick={onClickButton}
iconName={iconName}
iconPlacement={iconPlacement}
disabled={buttonIsDisabled}
data-testid="ChildNavigation_Primary_Button"
/>
</Flex>
) : null}
Expand Down
74 changes: 74 additions & 0 deletions app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react'
import { useTranslation, Trans } from 'react-i18next'
import {
Flex,
SPACING,
StyledText,
DeckConfigurator,
TYPOGRAPHY,
DIRECTION_COLUMN,
} from '@opentrons/components'
import { SmallButton } from '../../atoms/buttons'
import { useDeckConfigurationQuery } from '@opentrons/react-api-client'
import { ChildNavigation } from '../ChildNavigation'

interface CreateNewTransferProps {
onNext: () => void
exitButtonProps: React.ComponentProps<typeof SmallButton>
}

export function CreateNewTransfer(props: CreateNewTransferProps): JSX.Element {
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])
const deckConfig = useDeckConfigurationQuery().data ?? []
return (
<Flex>
<ChildNavigation
header={t('create_new_transfer')}
buttonText={i18n.format(t('shared:continue'), 'capitalize')}
onClickButton={props.onNext}
secondaryButtonProps={props.exitButtonProps}
top={SPACING.spacing8}
/>
<Flex
marginTop={SPACING.spacing80}
flexDirection={DIRECTION_COLUMN}
padding={`0 ${SPACING.spacing60} ${SPACING.spacing40} ${SPACING.spacing60}`}
>
<Flex gridGap={SPACING.spacing16}>
<Flex
width="50%"
paddingTop={SPACING.spacing32}
marginTop={SPACING.spacing32}
flexDirection={DIRECTION_COLUMN}
>
<Trans
t={t}
i18nKey="use_deck_slots"
components={{
block: (
<StyledText
css={TYPOGRAPHY.level4HeaderRegular}
marginBottom={SPACING.spacing16}
/>
),
}}
/>
</Flex>
<Flex width="50%">
<DeckConfigurator
deckConfig={deckConfig}
readOnly
handleClickAdd={() => {}}
handleClickRemove={() => {}}
additionalStaticFixtures={[
{ location: 'cutoutB2', label: t('tip_rack') },
{ location: 'cutoutC2', label: t('labware') },
{ location: 'cutoutD2', label: t('labware') },
]}
/>
</Flex>
</Flex>
</Flex>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react'
import { fireEvent, screen } from '@testing-library/react'
import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest'
import { DeckConfigurator } from '@opentrons/components'

import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
import { CreateNewTransfer } from '../CreateNewTransfer'

import type * as OpentronsComponents from '@opentrons/components'

vi.mock('@opentrons/components', async importOriginal => {
const actual = await importOriginal<typeof OpentronsComponents>()
return {
...actual,
DeckConfigurator: vi.fn(),
}
})
const render = (props: React.ComponentProps<typeof CreateNewTransfer>) => {
return renderWithProviders(<CreateNewTransfer {...props} />, {
i18nInstance: i18n,
})
}

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

beforeEach(() => {
props = {
onNext: vi.fn(),
exitButtonProps: {
buttonType: 'tertiaryLowLight',
buttonText: 'Exit',
onClick: vi.fn(),
},
}
})
afterEach(() => {
vi.resetAllMocks()
})

it('renders the create new transfer screen and header', () => {
render(props)
screen.getByText('Create new quick transfer')
screen.getByText(
'Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.'
)
screen.getByText(
'Make sure that your deck configuration is up to date to avoid collisions.'
)
expect(vi.mocked(DeckConfigurator)).toHaveBeenCalled()
})
it('renders exit and continue buttons and they work as expected', () => {
render(props)
const exitBtn = screen.getByText('Exit')
fireEvent.click(exitBtn)
expect(props.exitButtonProps.onClick).toHaveBeenCalled()
const continueBtn = screen.getByText('Continue')
fireEvent.click(continueBtn)
expect(props.onNext).toHaveBeenCalled()
})
})
9 changes: 9 additions & 0 deletions app/src/organisms/QuickTransferFlow/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const ACTIONS = {
SELECT_PIPETTE: 'SELECT_PIPETTE',
SELECT_TIP_RACK: 'SELECT_TIP_RACK',
SET_SOURCE_LABWARE: 'SET_SOURCE_LABWARE',
SET_SOURCE_WELLS: 'SET_SOURCE_WELLS',
SET_DEST_LABWARE: 'SET_DEST_LABWARE',
SET_DEST_WELLS: 'SET_DEST_WELLS',
SET_VOLUME: 'SET_VOLUME',
} as const
Loading

0 comments on commit 683c72a

Please sign in to comment.