Skip to content

Commit 6a2bed3

Browse files
authored
fix(app): creates required protocol deck config fixture utility (#14017)
creates utility getRequiredDeckConfig to generate list of required protocol deck config fixtures for rendering in fixture lists. fixes a couple bugs rendering empty fixture tables. also removes unnecessary getDeckDefFromRobotTypeV4
1 parent fa75bf5 commit 6a2bed3

File tree

11 files changed

+135
-88
lines changed

11 files changed

+135
-88
lines changed

app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import {
1818
import { Line } from '../../../atoms/structure'
1919
import { StyledText } from '../../../atoms/text'
2020
import { InfoMessage } from '../../../molecules/InfoMessage'
21-
import { getSimplestDeckConfigForProtocolCommands } from '../../../resources/deck_configuration/utils'
21+
import {
22+
getRequiredDeckConfig,
23+
getSimplestDeckConfigForProtocolCommands,
24+
} from '../../../resources/deck_configuration/utils'
2225
import {
2326
useIsFlex,
2427
useRobot,
@@ -121,7 +124,9 @@ export function ProtocolRunSetup({
121124
protocolAnalysis?.commands ?? []
122125
)
123126

124-
const hasFixtures = protocolDeckConfig.length > 0
127+
const requiredDeckConfig = getRequiredDeckConfig(protocolDeckConfig)
128+
129+
const hasFixtures = requiredDeckConfig.length > 0
125130

126131
let moduleDescription: string = t(`${MODULE_SETUP_KEY}_description`, {
127132
count: modules.length,

app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { fireEvent } from '@testing-library/react'
33
import { when } from 'jest-when'
44
import { renderWithProviders } from '@opentrons/components'
55
import { i18n } from '../../../../../i18n'
6+
import { mockTemperatureModule } from '../../../../../redux/modules/__fixtures__'
7+
import { getRequiredDeckConfig } from '../../../../../resources/deck_configuration/utils'
68
import {
79
useIsFlex,
810
useRunHasStarted,
@@ -13,13 +15,13 @@ import { SetupModuleAndDeck } from '../index'
1315
import { SetupModulesList } from '../SetupModulesList'
1416
import { SetupModulesMap } from '../SetupModulesMap'
1517
import { SetupFixtureList } from '../SetupFixtureList'
16-
import { mockTemperatureModule } from '../../../../../redux/modules/__fixtures__'
1718

1819
jest.mock('../../../hooks')
1920
jest.mock('../SetupModulesList')
2021
jest.mock('../SetupModulesMap')
2122
jest.mock('../SetupFixtureList')
2223
jest.mock('../../../../../redux/config')
24+
jest.mock('../../../../../resources/deck_configuration/utils')
2325

2426
const mockUseIsFlex = useIsFlex as jest.MockedFunction<typeof useIsFlex>
2527
const mockUseRunHasStarted = useRunHasStarted as jest.MockedFunction<
@@ -40,6 +42,9 @@ const mockSetupFixtureList = SetupFixtureList as jest.MockedFunction<
4042
const mockSetupModulesMap = SetupModulesMap as jest.MockedFunction<
4143
typeof SetupModulesMap
4244
>
45+
const mockGetRequiredDeckConfig = getRequiredDeckConfig as jest.MockedFunction<
46+
typeof getRequiredDeckConfig
47+
>
4348
const MOCK_ROBOT_NAME = 'otie'
4449
const MOCK_RUN_ID = '1'
4550

@@ -73,6 +78,7 @@ describe('SetupModuleAndDeck', () => {
7378
.calledWith(MOCK_ROBOT_NAME, MOCK_RUN_ID)
7479
.mockReturnValue({ complete: true })
7580
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(false)
81+
when(mockGetRequiredDeckConfig).mockReturnValue([])
7682
})
7783

7884
it('renders the list and map view buttons', () => {
@@ -123,13 +129,29 @@ describe('SetupModuleAndDeck', () => {
123129

124130
it('should render the SetupModulesList and SetupFixtureList component when clicking List View for Flex', () => {
125131
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(true)
132+
when(mockGetRequiredDeckConfig).mockReturnValue([
133+
{
134+
cutoutId: 'cutoutA1',
135+
cutoutFixtureId: 'trashBinAdapter',
136+
requiredAddressableAreas: ['movableTrashA1'],
137+
},
138+
])
126139
const { getByRole, getByText } = render(props)
127140
const button = getByRole('button', { name: 'List View' })
128141
fireEvent.click(button)
129142
getByText('Mock setup modules list')
130143
getByText('Mock setup fixture list')
131144
})
132145

146+
it('should not render the SetupFixtureList component when there are no required fixtures', () => {
147+
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(true)
148+
const { getByRole, getByText, queryByText } = render(props)
149+
const button = getByRole('button', { name: 'List View' })
150+
fireEvent.click(button)
151+
getByText('Mock setup modules list')
152+
expect(queryByText('Mock setup fixture list')).toBeNull()
153+
})
154+
133155
it('should render the SetupModulesMap component when clicking Map View', () => {
134156
const { getByRole, getByText } = render(props)
135157
const button = getByRole('button', { name: 'Map View' })

app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import {
88
useHoverTooltip,
99
PrimaryButton,
1010
} from '@opentrons/components'
11-
import { FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS } from '@opentrons/shared-data'
1211

1312
import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup'
1413
import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks'
14+
import { getRequiredDeckConfig } from '../../../../resources/deck_configuration/utils'
1515
import { Tooltip } from '../../../../atoms/Tooltip'
1616
import {
1717
useRunHasStarted,
@@ -57,16 +57,8 @@ export const SetupModuleAndDeck = ({
5757
commands
5858
)
5959

60-
const nonSingleSlotDeckConfigCompatibility = deckConfigCompatibility.filter(
61-
({ requiredAddressableAreas }) =>
62-
// required AA list includes a non-single-slot AA
63-
!requiredAddressableAreas.every(aa =>
64-
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(aa)
65-
)
66-
)
67-
// fixture includes at least 1 required AA
68-
const requiredDeckConfigCompatibility = nonSingleSlotDeckConfigCompatibility.filter(
69-
fixture => fixture.requiredAddressableAreas.length > 0
60+
const requiredDeckConfigCompatibility = getRequiredDeckConfig(
61+
deckConfigCompatibility
7062
)
7163

7264
return (
@@ -78,9 +70,11 @@ export const SetupModuleAndDeck = ({
7870
{hasModules ? (
7971
<SetupModulesList robotName={robotName} runId={runId} />
8072
) : null}
81-
<SetupFixtureList
82-
deckConfigCompatibility={requiredDeckConfigCompatibility}
83-
/>
73+
{requiredDeckConfigCompatibility.length > 0 ? (
74+
<SetupFixtureList
75+
deckConfigCompatibility={requiredDeckConfigCompatibility}
76+
/>
77+
) : null}
8478
</>
8579
) : (
8680
<SetupModulesMap runId={runId} />

app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import withModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/test
1212

1313
import { i18n } from '../../../../i18n'
1414
import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__'
15-
import { getSimplestDeckConfigForProtocolCommands } from '../../../../resources/deck_configuration/utils'
15+
import {
16+
getRequiredDeckConfig,
17+
getSimplestDeckConfigForProtocolCommands,
18+
} from '../../../../resources/deck_configuration/utils'
1619
import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
1720
import {
1821
useIsFlex,
@@ -83,6 +86,9 @@ const mockEmptySetupStep = EmptySetupStep as jest.MockedFunction<
8386
const mockGetSimplestDeckConfigForProtocolCommands = getSimplestDeckConfigForProtocolCommands as jest.MockedFunction<
8487
typeof getSimplestDeckConfigForProtocolCommands
8588
>
89+
const mockGetRequiredDeckConfig = getRequiredDeckConfig as jest.MockedFunction<
90+
typeof getRequiredDeckConfig
91+
>
8692

8793
const ROBOT_NAME = 'otie'
8894
const RUN_ID = '1'
@@ -147,6 +153,7 @@ describe('ProtocolRunSetup', () => {
147153
when(mockSetupLiquids).mockReturnValue(<div>Mock SetupLiquids</div>)
148154
when(mockEmptySetupStep).mockReturnValue(<div>Mock EmptySetupStep</div>)
149155
when(mockGetSimplestDeckConfigForProtocolCommands).mockReturnValue([])
156+
when(mockGetRequiredDeckConfig).mockReturnValue([])
150157
})
151158
afterEach(() => {
152159
resetAllWhenMocks()

app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ describe('ProtocolSetupModulesAndDeck', () => {
195195
})
196196

197197
it('should render text and buttons', () => {
198+
mockGetAttachedProtocolModuleMatches.mockReturnValue([
199+
{
200+
...mockProtocolModuleInfo[0],
201+
attachedModuleMatch: calibratedMockApiHeaterShaker,
202+
},
203+
])
198204
const [{ getByRole, getByText }] = render()
199205
getByText('Module')
200206
getByText('Location')

app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ export function ProtocolSetupModulesAndDeck({
364364
protocolModulesInfo
365365
)
366366

367+
const hasModules = attachedProtocolModuleMatches.length > 0
368+
367369
const {
368370
missingModuleIds,
369371
remainingAttachedModules,
@@ -421,56 +423,60 @@ export function ProtocolSetupModulesAndDeck({
421423
/>
422424
) : null}
423425
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing32}>
424-
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
425-
<Flex
426-
color={COLORS.darkBlack70}
427-
fontSize={TYPOGRAPHY.fontSize22}
428-
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
429-
gridGap={SPACING.spacing24}
430-
lineHeight={TYPOGRAPHY.lineHeight28}
431-
paddingX={SPACING.spacing24}
432-
>
433-
<StyledText flex="4 0 0">{t('module')}</StyledText>
434-
<StyledText flex="2 0 0">{t('location')}</StyledText>
435-
<StyledText flex="3 0 0"> {t('status')}</StyledText>
436-
</Flex>
437-
{attachedProtocolModuleMatches.map(module => {
438-
// check for duplicate module model in list of modules for protocol
439-
const isDuplicateModuleModel = protocolModulesInfo
440-
// filter out current module
441-
.filter(otherModule => otherModule.moduleId !== module.moduleId)
442-
// check for existence of another module of same model
443-
.some(
444-
otherModule =>
445-
otherModule.moduleDef.model === module.moduleDef.model
446-
)
426+
{hasModules ? (
427+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
428+
<Flex
429+
color={COLORS.darkBlack70}
430+
fontSize={TYPOGRAPHY.fontSize22}
431+
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
432+
gridGap={SPACING.spacing24}
433+
lineHeight={TYPOGRAPHY.lineHeight28}
434+
paddingX={SPACING.spacing24}
435+
>
436+
<StyledText flex="4 0 0">{t('module')}</StyledText>
437+
<StyledText flex="2 0 0">{t('location')}</StyledText>
438+
<StyledText flex="3 0 0"> {t('status')}</StyledText>
439+
</Flex>
440+
{attachedProtocolModuleMatches.map(module => {
441+
// check for duplicate module model in list of modules for protocol
442+
const isDuplicateModuleModel = protocolModulesInfo
443+
// filter out current module
444+
.filter(
445+
otherModule => otherModule.moduleId !== module.moduleId
446+
)
447+
// check for existence of another module of same model
448+
.some(
449+
otherModule =>
450+
otherModule.moduleDef.model === module.moduleDef.model
451+
)
447452

448-
const cutoutIdForSlotName = getCutoutIdForSlotName(
449-
module.slotName,
450-
deckDef
451-
)
453+
const cutoutIdForSlotName = getCutoutIdForSlotName(
454+
module.slotName,
455+
deckDef
456+
)
452457

453-
return (
454-
<RowModule
455-
key={module.moduleId}
456-
module={module}
457-
isDuplicateModuleModel={isDuplicateModuleModel}
458-
setShowMultipleModulesModal={setShowMultipleModulesModal}
459-
calibrationStatus={calibrationStatus}
460-
chainLiveCommands={chainLiveCommands}
461-
isLoading={isCommandMutationLoading}
462-
prepCommandErrorMessage={prepCommandErrorMessage}
463-
setPrepCommandErrorMessage={setPrepCommandErrorMessage}
464-
conflictedFixture={deckConfig?.find(
465-
fixture =>
466-
fixture.cutoutId === cutoutIdForSlotName &&
467-
fixture.cutoutFixtureId != null &&
468-
!SINGLE_SLOT_FIXTURES.includes(fixture.cutoutFixtureId)
469-
)}
470-
/>
471-
)
472-
})}
473-
</Flex>
458+
return (
459+
<RowModule
460+
key={module.moduleId}
461+
module={module}
462+
isDuplicateModuleModel={isDuplicateModuleModel}
463+
setShowMultipleModulesModal={setShowMultipleModulesModal}
464+
calibrationStatus={calibrationStatus}
465+
chainLiveCommands={chainLiveCommands}
466+
isLoading={isCommandMutationLoading}
467+
prepCommandErrorMessage={prepCommandErrorMessage}
468+
setPrepCommandErrorMessage={setPrepCommandErrorMessage}
469+
conflictedFixture={deckConfig?.find(
470+
fixture =>
471+
fixture.cutoutId === cutoutIdForSlotName &&
472+
fixture.cutoutFixtureId != null &&
473+
!SINGLE_SLOT_FIXTURES.includes(fixture.cutoutFixtureId)
474+
)}
475+
/>
476+
)
477+
})}
478+
</Flex>
479+
) : null}
474480
<FixtureTable
475481
robotType={FLEX_ROBOT_TYPE}
476482
mostRecentAnalysis={mostRecentAnalysis}

app/src/resources/deck_configuration/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { parseAllAddressableAreas } from '@opentrons/api-client'
22
import { useDeckConfigurationQuery } from '@opentrons/react-api-client'
33
import {
44
FLEX_ROBOT_TYPE,
5-
getDeckDefFromRobotTypeV4,
5+
getDeckDefFromRobotType,
66
} from '@opentrons/shared-data'
77

88
import {
@@ -26,7 +26,7 @@ export function useDeckConfigurationCompatibility(
2626
): CutoutConfigAndCompatibility[] {
2727
const deckConfig = useDeckConfigurationQuery().data ?? []
2828
if (robotType !== FLEX_ROBOT_TYPE) return []
29-
const deckDef = getDeckDefFromRobotTypeV4(robotType)
29+
const deckDef = getDeckDefFromRobotType(robotType)
3030
const allAddressableAreas = parseAllAddressableAreas(protocolCommands)
3131
return deckConfig.reduce<CutoutConfigAndCompatibility[]>(
3232
(acc, { cutoutId, cutoutFixtureId }) => {

app/src/resources/deck_configuration/utils.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
import { parseAllAddressableAreas } from '@opentrons/api-client'
22
import {
33
FLEX_ROBOT_TYPE,
4+
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS,
45
getAddressableAreaFromSlotId,
5-
getDeckDefFromRobotTypeV4,
6+
getDeckDefFromRobotType,
67
} from '@opentrons/shared-data'
78

89
import type {
10+
CutoutConfig,
911
CutoutId,
1012
RunTimeCommand,
11-
CutoutFixtureId,
1213
CutoutFixture,
1314
AddressableAreaName,
1415
DeckDefinition,
1516
} from '@opentrons/shared-data'
1617

17-
export interface CutoutConfigProtocolSpec {
18-
cutoutId: CutoutId
19-
cutoutFixtureId: CutoutFixtureId | null
18+
export interface CutoutConfigProtocolSpec extends CutoutConfig {
2019
requiredAddressableAreas: AddressableAreaName[]
2120
}
2221

@@ -87,7 +86,7 @@ export function getSimplestDeckConfigForProtocolCommands(
8786
protocolAnalysisCommands: RunTimeCommand[]
8887
): CutoutConfigProtocolSpec[] {
8988
// TODO(BC, 2023-11-06): abstract out the robot type
90-
const deckDef = getDeckDefFromRobotTypeV4(FLEX_ROBOT_TYPE)
89+
const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
9190

9291
const addressableAreas = parseAllAddressableAreas(protocolAnalysisCommands)
9392
const simplestDeckConfig = addressableAreas.reduce<
@@ -226,3 +225,21 @@ export function getSimplestFixtureForAddressableAreas(
226225
)
227226
return nextCompatibleCutoutFixtures?.[0] ?? null
228227
}
228+
229+
export function getRequiredDeckConfig<T extends CutoutConfigProtocolSpec>(
230+
deckConfigProtocolSpec: T[]
231+
): T[] {
232+
const nonSingleSlotDeckConfigCompatibility = deckConfigProtocolSpec.filter(
233+
({ requiredAddressableAreas }) =>
234+
// required AA list includes a non-single-slot AA
235+
!requiredAddressableAreas.every(aa =>
236+
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(aa)
237+
)
238+
)
239+
// fixture includes at least 1 required AA
240+
const requiredDeckConfigProtocolSpec = nonSingleSlotDeckConfigCompatibility.filter(
241+
fixture => fixture.requiredAddressableAreas.length > 0
242+
)
243+
244+
return requiredDeckConfigProtocolSpec
245+
}

protocol-designer/src/step-forms/reducers/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
PipetteName,
2222
THERMOCYCLER_MODULE_TYPE,
2323
WASTE_CHUTE_ADDRESSABLE_AREAS,
24-
getDeckDefFromRobotTypeV4,
24+
getDeckDefFromRobotType,
2525
AddressableAreaName,
2626
CutoutId,
2727
} from '@opentrons/shared-data'
@@ -1331,7 +1331,7 @@ export const additionalEquipmentInvariantProperties = handleActions<NormalizedAd
13311331
): NormalizedAdditionalEquipmentById => {
13321332
const { file } = action.payload
13331333
const isFlex = file.robot.model === FLEX_ROBOT_TYPE
1334-
const deckDef = getDeckDefFromRobotTypeV4(FLEX_ROBOT_TYPE)
1334+
const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
13351335
const cutoutFixtures = deckDef.cutoutFixtures
13361336
const providesAddressableAreasForAddressableArea = cutoutFixtures.find(
13371337
cutoutFixture => cutoutFixture.id.includes('stagingAreaRightSlot')

0 commit comments

Comments
 (0)