Skip to content
29 changes: 18 additions & 11 deletions app/src/organisms/ODD/QuickTransferFlow/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useToaster } from '/app/organisms/ToasterOven'

import { CONSOLIDATE, DISTRIBUTE } from './constants'

import type { CutoutConfig } from '@opentrons/shared-data'
import type { QuickTransferSummaryState } from './types'

interface OverviewProps {
Expand Down Expand Up @@ -49,6 +50,22 @@ export function Overview(props: OverviewProps): JSX.Element | null {
}
const pathCopy = pathCopyMap[state.path]

const dropTipLocationCopy = (location: CutoutConfig | string): string => {
if (location != null && typeof location !== 'string') {
return t(
`${
location.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE
? 'trashBin'
: 'wasteChute'
}_location`,
{
slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[location.cutoutId],
}
)
}
return t('tip_rack')
}

const displayItems = [
{
option: t('pipette'),
Expand Down Expand Up @@ -83,17 +100,7 @@ export function Overview(props: OverviewProps): JSX.Element | null {
},
{
option: t('tip_drop_location'),
value: t(
`${
state.dropTipLocation?.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE
? 'trashBin'
: 'wasteChute'
}_location`,
{
slotName:
FLEX_SINGLE_SLOT_BY_CUTOUT_ID[state.dropTipLocation?.cutoutId],
}
),
value: dropTipLocationCopy(state.dropTipLocation),
},
{
option: t('liquid_class'),
Expand Down
32 changes: 29 additions & 3 deletions app/src/organisms/ODD/QuickTransferFlow/SelectTipDropLocation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@opentrons/components'
import {
FLEX_SINGLE_SLOT_BY_CUTOUT_ID,
getLabwareDefURI,
TRASH_BIN_ADAPTER_FIXTURE,
WASTE_CHUTE_FIXTURES,
} from '@opentrons/shared-data'
Expand Down Expand Up @@ -41,8 +42,10 @@ export function SelectTipDropLocation({
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])
const deckConfig = useNotifyDeckConfigurationQuery().data ?? []
const [selectedTipDropLocation, setSelectedTipDropLocation] = useState<
CutoutConfig | undefined
>()
CutoutConfig | string | undefined
>(undefined)

const isTipRackReturnEnabled = state.tipRack != null && state.pipette != null

const tipDropLocationOptions = deckConfig.filter(
cutoutConfig =>
Expand All @@ -56,6 +59,12 @@ export function SelectTipDropLocation({
})
}

const handleSelectTipRack = (): void => {
if (state.tipRack != null) {
setSelectedTipDropLocation(getLabwareDefURI(state.tipRack))
}
}

const handleClickNext = (): void => {
dispatch({
type: 'SET_DROP_TIP_LOCATION',
Expand Down Expand Up @@ -88,7 +97,11 @@ export function SelectTipDropLocation({
{tipDropLocationOptions.map(option => (
<RadioButton
key={option.cutoutId}
isSelected={selectedTipDropLocation?.cutoutId === option.cutoutId}
isSelected={
selectedTipDropLocation != null &&
typeof selectedTipDropLocation !== 'string' &&
selectedTipDropLocation?.cutoutId === option.cutoutId
}
onChange={() => {
setSelectedTipDropLocation(option)
}}
Expand All @@ -105,6 +118,19 @@ export function SelectTipDropLocation({
)}
/>
))}
{isTipRackReturnEnabled && state.tipRack != null ? (
<RadioButton
key="tiprack"
isSelected={
selectedTipDropLocation != null &&
typeof selectedTipDropLocation === 'string' &&
selectedTipDropLocation === getLabwareDefURI(state.tipRack)
}
buttonLabel={t('tip_rack')}
buttonValue={getLabwareDefURI(state.tipRack)}
onChange={handleSelectTipRack}
/>
) : null}
</Flex>
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { WATER_LIQUID_CLASS_NAME } from '@opentrons/shared-data'
import {
TRASH_BIN_ADAPTER_FIXTURE,
WATER_LIQUID_CLASS_NAME,
} from '@opentrons/shared-data'

import { renderWithProviders } from '/app/__testing-utils__'
import { i18n } from '/app/i18n'
Expand Down Expand Up @@ -56,14 +59,19 @@ describe('Overview', () => {
render(props)
screen.getByText('Pipette')
screen.getByText('Pipette display name')
screen.getByText('Tip rack')
screen.getByText('Tip rack display name')
screen.getByText('Source labware')
screen.getByText('Source labware name')
screen.getByText('Destination labware')
screen.getByText('Destination labware name')
screen.getByText('Volume per well')
screen.getByText('25µL')
screen.getByText('Pipette path')
screen.getByText('Tip change frequency')
screen.getByText('Tip drop location')
screen.getByText('Liquid class')
// one is for item title, one is for the value of tip drop location
expect(screen.getAllByText('Tip rack')).toHaveLength(2)
})
it('renders the correct volume wording for n to 1 transfer', () => {
props = {
Expand Down Expand Up @@ -116,13 +124,46 @@ describe('Overview', () => {
} as any,
transferType: 'distribute',
volume: 25,
dropTipLocation: {
cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE,
cutoutId: 'cutoutA3',
},
} as any,
}
render(props)
screen.getByText('Dispense volume per well')
})

it('should render correct items when liquid classes are enabled', () => {
props = {
state: {
pipette: {
displayName: 'Pipette display name',
} as any,
tipRack: {
metadata: {
displayName: 'Tip rack display name',
},
} as any,
source: {
metadata: {
displayName: 'Source labware name',
},
} as any,
destination: {
metadata: {
displayName: 'Destination labware name',
},
} as any,
transferType: 'distribute',
volume: 25,
dropTipLocation: {
cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE,
cutoutId: 'cutoutA3',
},
liquidClassName: WATER_LIQUID_CLASS_NAME,
} as any,
}
render(props)
screen.getByText('Pipette')
screen.getByText('Pipette display name')
Expand All @@ -135,6 +176,7 @@ describe('Overview', () => {
screen.getByText('Pipette path')
screen.getByText('Tip change frequency')
screen.getByText('Tip drop location')
screen.getByText('Trash bin in A3')
screen.getByText('Liquid class')
screen.getByText('Aqueous')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { fireEvent, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data'
import {
getLabwareDefURI,
TRASH_BIN_ADAPTER_FIXTURE,
} from '@opentrons/shared-data'

import { renderWithProviders } from '/app/__testing-utils__'
import { i18n } from '/app/i18n'
import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration'

import mockQuickTransferState from '../QuickTransferAdvancedSettings/__fixtures__/QuickTransferState.json'
import { SelectTipDropLocation } from '../SelectTipDropLocation'

import type { ComponentProps } from 'react'
import type { LabwareDefinition2 } from '@opentrons/shared-data'
import type { QuickTransferWizardState } from '../types'

vi.mock('/app/resources/deck_configuration')

Expand Down Expand Up @@ -42,7 +48,7 @@ describe('SelectTipDropLocation', () => {
buttonText: 'Exit',
onClick: vi.fn(),
},
state: {},
state: mockQuickTransferState as QuickTransferWizardState,
dispatch: vi.fn(),
}
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({
Expand All @@ -57,6 +63,7 @@ describe('SelectTipDropLocation', () => {
screen.getByText('Continue')
screen.getByText('Trash bin in A3')
screen.getByText('Waste chute in C3')
screen.getByText('Tip rack')
})

it('should call mock function when tappin exit button', () => {
Expand All @@ -65,7 +72,7 @@ describe('SelectTipDropLocation', () => {
expect(props.exitButtonProps.onClick).toHaveBeenCalled()
})

it('should call mock function when tappin continue button', () => {
it('should call mock function when tapping trash bin option and continue button', () => {
render(props)
fireEvent.click(screen.getByText('Trash bin in A3'))
fireEvent.click(screen.getByText('Continue'))
Expand All @@ -78,4 +85,17 @@ describe('SelectTipDropLocation', () => {
},
})
})

it('should call mock function when tapping tip rack option and continue button', () => {
render(props)
fireEvent.click(screen.getByText('Tip rack'))
fireEvent.click(screen.getByText('Continue'))
expect(props.onNext).toHaveBeenCalled()
expect(props.dispatch).toHaveBeenCalledWith({
type: 'SET_DROP_TIP_LOCATION',
location: getLabwareDefURI(
mockQuickTransferState.tipRack as LabwareDefinition2
),
})
})
})
5 changes: 3 additions & 2 deletions app/src/organisms/ODD/QuickTransferFlow/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CONSOLIDATE, DISTRIBUTE, TRANSFER } from './constants'

import type { CutoutConfig } from '@opentrons/shared-data'
import type {
QuickTransferSummaryAction,
QuickTransferSummaryState,
Expand Down Expand Up @@ -105,7 +106,7 @@ export function quickTransferWizardReducer(
case 'SET_DROP_TIP_LOCATION': {
return {
...state,
dropTipLocation: action.location,
dropTipLocation: action.location as CutoutConfig | string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe refactor CutoutConfig | string to type for reuse

}
}
case 'SET_LIQUID_CLASS': {
Expand Down Expand Up @@ -260,7 +261,7 @@ export function quickTransferSummaryReducer(
case 'SET_DROP_TIP_LOCATION': {
return {
...state,
dropTipLocation: action.location,
dropTipLocation: action.location as CutoutConfig | string,
}
}
case 'SET_PUSH_OUT': {
Expand Down
6 changes: 3 additions & 3 deletions app/src/organisms/ODD/QuickTransferFlow/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface QuickTransferWizardState {
// Note added for liquid classes in Quick Transfer
path?: PathOption
changeTip?: ChangeTipOptions
dropTipLocation?: CutoutConfig
dropTipLocation?: CutoutConfig | string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see type comment above

liquidClassName?: string
}
export type PathOption = 'single' | 'multiAspirate' | 'multiDispense'
Expand Down Expand Up @@ -120,7 +120,7 @@ export interface QuickTransferSummaryState {
}
airGapDispense?: number
changeTip: ChangeTipOptions
dropTipLocation: CutoutConfig
dropTipLocation: CutoutConfig | string
liquidClassName: string
conditionAspirate?: number
disposalVolumeDispenseSettings?: {
Expand Down Expand Up @@ -290,7 +290,7 @@ interface SetChangeTip {
}
interface SetDropTipLocation {
type: typeof ACTIONS.SET_DROP_TIP_LOCATION
location: CutoutConfig
location: CutoutConfig | string
}

interface SetLiquidClassAction {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data'
import {
DEST_WELL_BLOWOUT_DESTINATION,
SOURCE_WELL_BLOWOUT_DESTINATION,
} from '@opentrons/step-generation'

import type { CutoutConfig } from '@opentrons/shared-data'
import type { BlowOutLocation } from '../types'

export const convertBlowoutLocation = (
location: string | undefined,
dropTipLocation: CutoutConfig | string
): BlowOutLocation | undefined => {
if (location == null) return undefined

switch (location) {
case 'source':
return SOURCE_WELL_BLOWOUT_DESTINATION
case 'destination':
return DEST_WELL_BLOWOUT_DESTINATION
case 'trash':
return typeof dropTipLocation !== 'string' &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean? When happens when dropTipLocation IS a string? And what value would dropTipLocation contain if it is a string?

'cutoutId' in dropTipLocation
? dropTipLocation
: {
cutoutId: 'cutoutA3',
cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE,
}
default:
return undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ export function getInvariantContextAndRobotState(
let wasteChuteEntities: WasteChuteEntities = {}

if (
typeof quickTransferState.dropTipLocation !== 'string' &&
quickTransferState.dropTipLocation.cutoutFixtureId ===
TRASH_BIN_ADAPTER_FIXTURE
TRASH_BIN_ADAPTER_FIXTURE
) {
const trashLocation = quickTransferState.dropTipLocation.cutoutId
const trashId = `${uuid()}_trashBin`
Expand Down Expand Up @@ -213,6 +214,7 @@ export function getInvariantContextAndRobotState(
}

if (
typeof quickTransferState.dropTipLocation !== 'string' &&
WASTE_CHUTE_FIXTURES.includes(
quickTransferState.dropTipLocation.cutoutFixtureId
)
Expand Down Expand Up @@ -338,12 +340,16 @@ export function generateQuickTransferArgs(
const dropTipTrashBinLocationEntity = Object.values(
invariantContext.trashBinEntities
).find(
entity => entity.location === quickTransferState.dropTipLocation.cutoutId
entity =>
typeof quickTransferState.dropTipLocation !== 'string' &&
entity.location === quickTransferState.dropTipLocation.cutoutId
)
const dropTipWasteChuteLocationEntity = Object.values(
invariantContext.wasteChuteEntities
).find(
entity => entity.location === quickTransferState.dropTipLocation.cutoutId
entity =>
typeof quickTransferState.dropTipLocation !== 'string' &&
entity.location === quickTransferState.dropTipLocation.cutoutId
)
const dropTipLocation =
dropTipTrashBinLocationEntity?.id ??
Expand Down
Loading
Loading