Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol-designer): create container for all tipracks #14848

Merged
merged 3 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
)
Loading