Skip to content

Commit

Permalink
feat(app): add Deck configuration screen to ProtocolSetup (#13808)
Browse files Browse the repository at this point in the history
* feat(app): add Deck configuration screen to ProtocolSetup
  • Loading branch information
koji authored Oct 24, 2023
1 parent f67a9f6 commit 072dced
Show file tree
Hide file tree
Showing 13 changed files with 474 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react'
import { QueryClient, QueryClientProvider } from 'react-query'
import { touchScreenViewport } from '../../DesignTokens/constants'
import { AddDeckConfigurationModal } from './AddDeckConfigurationModal'
import { AddFixtureModal } from './AddFixtureModal'
import type { Story, Meta } from '@storybook/react'

export default {
title: 'ODD/Organisms/AddDeckConfigurationModal',
title: 'ODD/Organisms/AddFixtureModal',
argTypes: {
modalSize: {
options: ['small', 'medium', 'large'],
Expand All @@ -17,11 +17,9 @@ export default {
} as Meta

const queryClient = new QueryClient()
const Template: Story<
React.ComponentProps<typeof AddDeckConfigurationModal>
> = args => (
const Template: Story<React.ComponentProps<typeof AddFixtureModal>> = args => (
<QueryClientProvider client={queryClient}>
<AddDeckConfigurationModal {...args} />
<AddFixtureModal {...args} />
</QueryClientProvider>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,21 @@ import type { Cutout, FixtureLoadName } from '@opentrons/shared-data'
import type { ModalHeaderBaseProps } from '../../molecules/Modal/types'
import type { LegacyModalProps } from '../../molecules/LegacyModal'

interface AddDeckConfigurationModalProps {
interface AddFixtureModalProps {
fixtureLocation: Cutout
setShowAddFixtureModal: (showAddFixtureModal: boolean) => void
providedFixtureOptions?: FixtureLoadName[]
isOnDevice?: boolean
}

export function AddDeckConfigurationModal({
export function AddFixtureModal({
fixtureLocation,
setShowAddFixtureModal,
providedFixtureOptions,
isOnDevice = false,
}: AddDeckConfigurationModalProps): JSX.Element {
}: AddFixtureModalProps): JSX.Element | null {
const { t } = useTranslation('device_details')
const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation()

const modalHeader: ModalHeaderBaseProps = {
title: t('add_to_slot', { slotName: fixtureLocation }),
Expand All @@ -59,8 +62,6 @@ export function AddDeckConfigurationModal({
width: '23.125rem',
}

const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation()

const availableFixtures: FixtureLoadName[] = [TRASH_BIN_LOAD_NAME]
if (
fixtureLocation === 'A3' ||
Expand All @@ -73,6 +74,8 @@ export function AddDeckConfigurationModal({
availableFixtures.push(STAGING_AREA_LOAD_NAME, WASTE_CHUTE_LOAD_NAME)
}

const fixtureOptions = providedFixtureOptions ?? availableFixtures

const handleClickAdd = (fixtureLoadName: FixtureLoadName): void => {
updateDeckConfiguration({
fixtureLocation,
Expand All @@ -91,7 +94,7 @@ export function AddDeckConfigurationModal({
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing32}>
<StyledText as="p">{t('add_to_slot_description')}</StyledText>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
{availableFixtures.map(fixture => (
{fixtureOptions.map(fixture => (
<React.Fragment key={fixture}>
<AddFixtureButton
fixtureLoadName={fixture}
Expand All @@ -107,7 +110,7 @@ export function AddDeckConfigurationModal({
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing16}>
<StyledText as="p">{t('add_fixture_description')}</StyledText>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
{availableFixtures.map(fixture => (
{fixtureOptions.map(fixture => (
<React.Fragment key={fixture}>
<Flex
flexDirection={DIRECTION_ROW}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { i18n } from '../../../i18n'
import { renderWithProviders } from '@opentrons/components'
import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client'
import { TRASH_BIN_LOAD_NAME } from '@opentrons/shared-data'
import { AddDeckConfigurationModal } from '../AddDeckConfigurationModal'
import { AddFixtureModal } from '../AddFixtureModal'

jest.mock('@opentrons/react-api-client')
const mockSetShowAddFixtureModal = jest.fn()
Expand All @@ -15,16 +15,14 @@ const mockUseUpdateDeckConfigurationMutation = useUpdateDeckConfigurationMutatio
typeof useUpdateDeckConfigurationMutation
>

const render = (
props: React.ComponentProps<typeof AddDeckConfigurationModal>
) => {
return renderWithProviders(<AddDeckConfigurationModal {...props} />, {
const render = (props: React.ComponentProps<typeof AddFixtureModal>) => {
return renderWithProviders(<AddFixtureModal {...props} />, {
i18nInstance: i18n,
})
}

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

beforeEach(() => {
props = {
Expand Down Expand Up @@ -52,8 +50,8 @@ describe('Touchscreen AddDeckConfigurationModal', () => {
it.todo('should a mock function when tapping a button')
})

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

beforeEach(() => {
props = {
Expand Down
4 changes: 2 additions & 2 deletions app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {

import { StyledText } from '../../atoms/text'
import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal'
import { AddDeckConfigurationModal } from './AddDeckConfigurationModal'
import { AddFixtureModal } from './AddFixtureModal'

import type { Cutout } from '@opentrons/shared-data'

Expand Down Expand Up @@ -74,7 +74,7 @@ export function DeviceDetailsDeckConfiguration({
return (
<>
{showAddFixtureModal && targetFixtureLocation != null ? (
<AddDeckConfigurationModal
<AddFixtureModal
fixtureLocation={targetFixtureLocation}
setShowAddFixtureModal={setShowAddFixtureModal}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react'
import { when, resetAllWhenMocks } from 'jest-when'

import { renderWithProviders, DeckConfigurator } from '@opentrons/components'
import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client'

import { i18n } from '../../../i18n'
import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { ProtocolSetupDeckConfiguration } from '..'

jest.mock('@opentrons/components/src/hardware-sim/DeckConfigurator/index')
jest.mock('@opentrons/react-api-client')
jest.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis')

const mockSetSetupScreen = jest.fn()
const mockUpdateDeckConfiguration = jest.fn()
const PROTOCOL_DETAILS = {
displayName: 'fake protocol',
protocolData: [],
protocolKey: 'fakeProtocolKey',
robotType: 'OT-3 Standard' as const,
}

const mockDeckConfigurator = DeckConfigurator as jest.MockedFunction<
typeof DeckConfigurator
>
const mockUseMostRecentCompletedAnalysis = useMostRecentCompletedAnalysis as jest.MockedFunction<
typeof useMostRecentCompletedAnalysis
>
const mockUseUpdateDeckConfigurationMutation = useUpdateDeckConfigurationMutation as jest.MockedFunction<
typeof useUpdateDeckConfigurationMutation
>

const render = (
props: React.ComponentProps<typeof ProtocolSetupDeckConfiguration>
) => {
return renderWithProviders(<ProtocolSetupDeckConfiguration {...props} />, {
i18nInstance: i18n,
})
}

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

beforeEach(() => {
props = {
fixtureLocation: 'D3',
runId: 'mockRunId',
setSetupScreen: mockSetSetupScreen,
providedFixtureOptions: [],
}
mockDeckConfigurator.mockReturnValue(<div>mock DeckConfigurator</div>)
when(mockUseMostRecentCompletedAnalysis)
.calledWith('mockRunId')
.mockReturnValue(PROTOCOL_DETAILS.protocolData as any)
mockUseUpdateDeckConfigurationMutation.mockReturnValue({
updateDeckConfiguration: mockUpdateDeckConfiguration,
} as any)
})

afterEach(() => {
resetAllWhenMocks()
})

it('should render text, button, and DeckConfigurator', () => {
const [{ getByText }] = render(props)
getByText('Deck configuration')
getByText('mock DeckConfigurator')
})

it('should call a mock function when tapping the back button', () => {
const [{ getByTestId }] = render(props)
getByTestId('ChildNavigation_Back_Button').click()
expect(mockSetSetupScreen).toHaveBeenCalledWith('modules')
})
})
152 changes: 152 additions & 0 deletions app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'

import {
DeckConfigurator,
DIRECTION_COLUMN,
Flex,
JUSTIFY_CENTER,
SPACING,
} from '@opentrons/components'
import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client'
import {
STANDARD_SLOT_LOAD_NAME,
WASTE_CHUTE_LOAD_NAME,
} from '@opentrons/shared-data'

import { ChildNavigation } from '../ChildNavigation'
import { AddFixtureModal } from '../DeviceDetailsDeckConfiguration/AddFixtureModal'
import { DeckConfigurationDiscardChangesModal } from '../DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal'
import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { Portal } from '../../App/portal'

import type {
Cutout,
DeckConfiguration,
Fixture,
FixtureLoadName,
LoadFixtureRunTimeCommand,
} from '@opentrons/shared-data'
import type { SetupScreens } from '../../pages/OnDeviceDisplay/ProtocolSetup'

interface ProtocolSetupDeckConfigurationProps {
fixtureLocation: Cutout
runId: string
setSetupScreen: React.Dispatch<React.SetStateAction<SetupScreens>>
providedFixtureOptions: FixtureLoadName[]
}

export function ProtocolSetupDeckConfiguration({
fixtureLocation,
runId,
setSetupScreen,
providedFixtureOptions,
}: ProtocolSetupDeckConfigurationProps): JSX.Element {
const { t } = useTranslation(['protocol_setup', 'devices_landing', 'shared'])

const [
showConfigurationModal,
setShowConfigurationModal,
] = React.useState<boolean>(true)
const [
targetFixtureLocation,
setTargetFixtureLocation,
] = React.useState<Cutout>(fixtureLocation)
const [
showDiscardChangeModal,
setShowDiscardChangeModal,
] = React.useState<boolean>(false)

const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId)
const STUBBED_LOAD_FIXTURE: LoadFixtureRunTimeCommand = {
id: 'stubbed_load_fixture',
commandType: 'loadFixture',
params: {
fixtureId: 'stubbedFixtureId',
loadName: WASTE_CHUTE_LOAD_NAME,
location: { cutout: 'D3' },
},
createdAt: 'fakeTimestamp',
startedAt: 'fakeTimestamp',
completedAt: 'fakeTimestamp',
status: 'succeeded',
}

const requiredFixtureDetails =
mostRecentAnalysis?.commands != null
? [
// parseInitialLoadedFixturesByCutout(mostRecentAnalysis.commands),
STUBBED_LOAD_FIXTURE,
]
: []

const deckConfig =
(requiredFixtureDetails.map(
(fixture): Fixture | false =>
fixture.params.fixtureId != null && {
fixtureId: fixture.params.fixtureId,
fixtureLocation: fixture.params.location.cutout,
loadName: fixture.params.loadName,
}
) as DeckConfiguration) ?? []

const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation()

const handleClickAdd = (fixtureLocation: Cutout): void => {
setTargetFixtureLocation(fixtureLocation)
setShowConfigurationModal(true)
}

const handleClickRemove = (fixtureLocation: Cutout): void => {
updateDeckConfiguration({
fixtureLocation,
loadName: STANDARD_SLOT_LOAD_NAME,
})
}

const handleClickConfirm = (): void => {
// ToDo (kk:10/17/2023) add a function for the confirmation in a following PR for RAUT-804
}

return (
<>
<Portal level="top">
{showDiscardChangeModal ? (
<DeckConfigurationDiscardChangesModal
setShowConfirmationModal={setShowDiscardChangeModal}
/>
) : null}
{showConfigurationModal &&
(fixtureLocation != null || targetFixtureLocation != null) ? (
<AddFixtureModal
fixtureLocation={targetFixtureLocation}
setShowAddFixtureModal={setShowConfigurationModal}
providedFixtureOptions={providedFixtureOptions}
isOnDevice
/>
) : null}
</Portal>
<Flex flexDirection={DIRECTION_COLUMN}>
<ChildNavigation
header={t('devices_landing:deck_configuration')}
onClickBack={() => setSetupScreen('modules')}
buttonText={t('shared:confirm')}
onClickButton={handleClickConfirm}
/>
<Flex
marginTop="7.75rem"
paddingX={SPACING.spacing40}
paddingBottom={SPACING.spacing40}
justifyContent={JUSTIFY_CENTER}
>
{/* DeckConfigurator will be replaced by BaseDeck when RAUT-793 is ready */}
<DeckConfigurator
deckConfig={deckConfig}
handleClickAdd={handleClickAdd}
handleClickRemove={handleClickRemove}
/>
</Flex>
</Flex>
</>
)
}
Loading

0 comments on commit 072dced

Please sign in to comment.