Skip to content

Commit

Permalink
feat(protocol-designer): create container for all tipracks (#14848)
Browse files Browse the repository at this point in the history
closes AUTH-313
  • Loading branch information
jerader authored Apr 9, 2024
1 parent 61b1371 commit 476149e
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,62 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { FormGroup, DropdownField } from '@opentrons/components'
import {
FormGroup,
DropdownField,
useHoverTooltip,
Tooltip,
Box,
} from '@opentrons/components'
import { selectors as uiLabwareSelectors } from '../../../ui/labware'
import styles from '../StepEditForm.module.css'

import { getPipetteEntities } from '../../../step-forms/selectors'
import type { FieldProps } from '../types'

export function TiprackField(props: FieldProps): JSX.Element {
const { name, value, onFieldBlur, onFieldFocus, updateValue } = props
const { t } = useTranslation('form')
import styles from '../StepEditForm.module.css'

interface TiprackFieldProps extends FieldProps {
pipetteId?: unknown
}
export function TiprackField(props: TiprackFieldProps): JSX.Element {
const {
name,
value,
onFieldBlur,
onFieldFocus,
updateValue,
pipetteId,
} = props
const { t } = useTranslation(['form', 'tooltip'])
const [targetProps, tooltipProps] = useHoverTooltip()
const pipetteEntities = useSelector(getPipetteEntities)
const options = useSelector(uiLabwareSelectors.getTiprackOptions)
const defaultTipracks =
pipetteId != null ? pipetteEntities[pipetteId as string].tiprackDefURI : []
const pipetteOptions = options.filter(option =>
defaultTipracks.includes(option.defURI)
)
const hasMissingTiprack = defaultTipracks.length > pipetteOptions.length

return (
<FormGroup
label={t('step_edit_form.tipRack')}
className={styles.large_field}
>
<DropdownField
options={options}
name={name}
value={String(value) != null ? String(value) : null}
onBlur={onFieldBlur}
onFocus={onFieldFocus}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
updateValue(e.currentTarget.value)
}}
/>
</FormGroup>
<Box {...targetProps}>
<FormGroup
label={t('step_edit_form.tipRack')}
className={styles.large_field}
>
<DropdownField
options={pipetteOptions}
name={name}
value={String(value) != null ? String(value) : null}
onBlur={onFieldBlur}
onFocus={onFieldFocus}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
updateValue(e.currentTarget.value)
}}
/>
</FormGroup>
{hasMissingTiprack ? (
<Tooltip {...tooltipProps}>{t('tooltip:missing_tiprack')}</Tooltip>
) : null}
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from 'react'
import { describe, it, vi, beforeEach } from 'vitest'
import { screen } from '@testing-library/react'
import { i18n } from '../../../../localization'
import { getPipetteEntities } from '../../../../step-forms/selectors'
import { renderWithProviders } from '../../../../__testing-utils__'
import { getTiprackOptions } from '../../../../ui/labware/selectors'
import { TiprackField } from '../TiprackField'

vi.mock('../../../../ui/labware/selectors')
vi.mock('../../../../step-forms/selectors')

const render = (props: React.ComponentProps<typeof TiprackField>) => {
return renderWithProviders(<TiprackField {...props} />, {
i18nInstance: i18n,
})[0]
}
const mockMockId = 'mockId'
describe('TiprackField', () => {
let props: React.ComponentProps<typeof TiprackField>

beforeEach(() => {
props = {
disabled: false,
value: null,
name: 'tipRackt',
updateValue: vi.fn(),
onFieldBlur: vi.fn(),
onFieldFocus: vi.fn(),
pipetteId: mockMockId,
}
vi.mocked(getPipetteEntities).mockReturnValue({
[mockMockId]: {
name: 'p50_single_flex',
spec: {} as any,
id: mockMockId,
tiprackLabwareDef: [],
tiprackDefURI: ['mockDefURI1', 'mockDefURI2'],
},
})
vi.mocked(getTiprackOptions).mockReturnValue([
{
value: 'mockValue',
name: 'tiprack1',
defURI: 'mockDefURI1',
},
{
value: 'mockValue',
name: 'tiprack2',
defURI: 'mockDefURI2',
},
])
})
it('renders the dropdown field and texts', () => {
render(props)
screen.getByText('tip rack')
screen.getByText('tiprack1')
screen.getByText('tiprack2')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export const MixForm = (props: StepFormProps): JSX.Element => {
</div>
<div className={styles.form_row}>
<PipetteField {...propsForFields.pipette} />
<TiprackField {...propsForFields.tipRack} />
<TiprackField
{...propsForFields.tipRack}
pipetteId={propsForFields.pipette.value}
/>
{is96Channel ? (
<Configure96ChannelField {...propsForFields.nozzles} />
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ export const MoveLiquidForm = (props: StepFormProps): JSX.Element => {
</div>
<div className={styles.form_row}>
<PipetteField {...propsForFields.pipette} />
<TiprackField {...propsForFields.tipRack} />
<TiprackField
{...propsForFields.tipRack}
pipetteId={propsForFields.pipette.value}
/>
{is96Channel ? (
<Configure96ChannelField {...propsForFields.nozzles} />
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,15 @@ export function CreateFileWizard(): JSX.Element | null {
const newTiprackModels: string[] = uniq(
pipettes.flatMap(pipette => pipette.tiprackDefURI)
)
const FLEX_MIDDLE_SLOTS = ['C2', 'B2', 'A2']
const OT2_MIDDLE_SLOTS = ['2', '5', '8', '11']
newTiprackModels.forEach((tiprackDefURI, index) => {
const ot2Slots = index === 0 ? '2' : '5'
const flexSlots = index === 0 ? 'C2' : 'B2'
dispatch(
labwareIngredActions.createContainer({
slot:
values.fields.robotType === FLEX_ROBOT_TYPE
? flexSlots
: ot2Slots,
? FLEX_MIDDLE_SLOTS[index]
: OT2_MIDDLE_SLOTS[index],
labwareDefURI: tiprackDefURI,
adapterUnderLabwareDefURI:
values.pipettesByMount.left.pipetteName === 'p1000_96'
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/src/localization/en/tooltip.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"disabled_you_can_add_one_type": "Only one module of each type is allowed on the deck at a time",
"not_enough_space_for_temp": "There is not enough space on the deck to add more temperature modules",
"not_in_beta": "ⓘ Coming Soon",
"missing_tiprack": "Missing a tiprack? Make sure it is added to the deck",

"step_description": {
"heaterShaker": "Set heat, shake, or labware latch commands for the Heater-Shaker module",
Expand Down
14 changes: 10 additions & 4 deletions protocol-designer/src/ui/labware/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,17 +241,22 @@ export const getDisposalOptions = createSelector(
}
)

export const getTiprackOptions: Selector<Options> = createSelector(
export interface TiprackOption {
name: string
value: string
defURI: string
}
export const getTiprackOptions: Selector<TiprackOption[]> = createSelector(
stepFormSelectors.getLabwareEntities,
getLabwareNicknamesById,
(labwareEntities, nicknamesById) => {
const options = reduce(
labwareEntities,
(
acc: Options,
acc: TiprackOption[],
labwareEntity: LabwareEntity,
labwareId: string
): Options => {
): TiprackOption[] => {
const labwareDefURI = labwareEntity.labwareDefURI
const optionValues = acc.map(option => option.value)

Expand All @@ -266,12 +271,13 @@ export const getTiprackOptions: Selector<Options> = createSelector(
{
name: nicknamesById[labwareId],
value: labwareId,
defURI: labwareDefURI,
},
]
}
},
[]
)
return _sortLabwareDropdownOptions(options)
return options
}
)

0 comments on commit 476149e

Please sign in to comment.