Skip to content

Commit

Permalink
fix(app): creates required protocol deck config fixture utility (#14017)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
brenthagen authored Nov 17, 2023
1 parent fa75bf5 commit 6a2bed3
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 88 deletions.
9 changes: 7 additions & 2 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {
import { Line } from '../../../atoms/structure'
import { StyledText } from '../../../atoms/text'
import { InfoMessage } from '../../../molecules/InfoMessage'
import { getSimplestDeckConfigForProtocolCommands } from '../../../resources/deck_configuration/utils'
import {
getRequiredDeckConfig,
getSimplestDeckConfigForProtocolCommands,
} from '../../../resources/deck_configuration/utils'
import {
useIsFlex,
useRobot,
Expand Down Expand Up @@ -121,7 +124,9 @@ export function ProtocolRunSetup({
protocolAnalysis?.commands ?? []
)

const hasFixtures = protocolDeckConfig.length > 0
const requiredDeckConfig = getRequiredDeckConfig(protocolDeckConfig)

const hasFixtures = requiredDeckConfig.length > 0

let moduleDescription: string = t(`${MODULE_SETUP_KEY}_description`, {
count: modules.length,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { fireEvent } from '@testing-library/react'
import { when } from 'jest-when'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../../../i18n'
import { mockTemperatureModule } from '../../../../../redux/modules/__fixtures__'
import { getRequiredDeckConfig } from '../../../../../resources/deck_configuration/utils'
import {
useIsFlex,
useRunHasStarted,
Expand All @@ -13,13 +15,13 @@ import { SetupModuleAndDeck } from '../index'
import { SetupModulesList } from '../SetupModulesList'
import { SetupModulesMap } from '../SetupModulesMap'
import { SetupFixtureList } from '../SetupFixtureList'
import { mockTemperatureModule } from '../../../../../redux/modules/__fixtures__'

jest.mock('../../../hooks')
jest.mock('../SetupModulesList')
jest.mock('../SetupModulesMap')
jest.mock('../SetupFixtureList')
jest.mock('../../../../../redux/config')
jest.mock('../../../../../resources/deck_configuration/utils')

const mockUseIsFlex = useIsFlex as jest.MockedFunction<typeof useIsFlex>
const mockUseRunHasStarted = useRunHasStarted as jest.MockedFunction<
Expand All @@ -40,6 +42,9 @@ const mockSetupFixtureList = SetupFixtureList as jest.MockedFunction<
const mockSetupModulesMap = SetupModulesMap as jest.MockedFunction<
typeof SetupModulesMap
>
const mockGetRequiredDeckConfig = getRequiredDeckConfig as jest.MockedFunction<
typeof getRequiredDeckConfig
>
const MOCK_ROBOT_NAME = 'otie'
const MOCK_RUN_ID = '1'

Expand Down Expand Up @@ -73,6 +78,7 @@ describe('SetupModuleAndDeck', () => {
.calledWith(MOCK_ROBOT_NAME, MOCK_RUN_ID)
.mockReturnValue({ complete: true })
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(false)
when(mockGetRequiredDeckConfig).mockReturnValue([])
})

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

it('should render the SetupModulesList and SetupFixtureList component when clicking List View for Flex', () => {
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(true)
when(mockGetRequiredDeckConfig).mockReturnValue([
{
cutoutId: 'cutoutA1',
cutoutFixtureId: 'trashBinAdapter',
requiredAddressableAreas: ['movableTrashA1'],
},
])
const { getByRole, getByText } = render(props)
const button = getByRole('button', { name: 'List View' })
fireEvent.click(button)
getByText('Mock setup modules list')
getByText('Mock setup fixture list')
})

it('should not render the SetupFixtureList component when there are no required fixtures', () => {
when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(true)
const { getByRole, getByText, queryByText } = render(props)
const button = getByRole('button', { name: 'List View' })
fireEvent.click(button)
getByText('Mock setup modules list')
expect(queryByText('Mock setup fixture list')).toBeNull()
})

it('should render the SetupModulesMap component when clicking Map View', () => {
const { getByRole, getByText } = render(props)
const button = getByRole('button', { name: 'Map View' })
Expand Down
22 changes: 8 additions & 14 deletions app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
useHoverTooltip,
PrimaryButton,
} from '@opentrons/components'
import { FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS } from '@opentrons/shared-data'

import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup'
import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks'
import { getRequiredDeckConfig } from '../../../../resources/deck_configuration/utils'
import { Tooltip } from '../../../../atoms/Tooltip'
import {
useRunHasStarted,
Expand Down Expand Up @@ -57,16 +57,8 @@ export const SetupModuleAndDeck = ({
commands
)

const nonSingleSlotDeckConfigCompatibility = deckConfigCompatibility.filter(
({ requiredAddressableAreas }) =>
// required AA list includes a non-single-slot AA
!requiredAddressableAreas.every(aa =>
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(aa)
)
)
// fixture includes at least 1 required AA
const requiredDeckConfigCompatibility = nonSingleSlotDeckConfigCompatibility.filter(
fixture => fixture.requiredAddressableAreas.length > 0
const requiredDeckConfigCompatibility = getRequiredDeckConfig(
deckConfigCompatibility
)

return (
Expand All @@ -78,9 +70,11 @@ export const SetupModuleAndDeck = ({
{hasModules ? (
<SetupModulesList robotName={robotName} runId={runId} />
) : null}
<SetupFixtureList
deckConfigCompatibility={requiredDeckConfigCompatibility}
/>
{requiredDeckConfigCompatibility.length > 0 ? (
<SetupFixtureList
deckConfigCompatibility={requiredDeckConfigCompatibility}
/>
) : null}
</>
) : (
<SetupModulesMap runId={runId} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import withModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/test

import { i18n } from '../../../../i18n'
import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__'
import { getSimplestDeckConfigForProtocolCommands } from '../../../../resources/deck_configuration/utils'
import {
getRequiredDeckConfig,
getSimplestDeckConfigForProtocolCommands,
} from '../../../../resources/deck_configuration/utils'
import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import {
useIsFlex,
Expand Down Expand Up @@ -83,6 +86,9 @@ const mockEmptySetupStep = EmptySetupStep as jest.MockedFunction<
const mockGetSimplestDeckConfigForProtocolCommands = getSimplestDeckConfigForProtocolCommands as jest.MockedFunction<
typeof getSimplestDeckConfigForProtocolCommands
>
const mockGetRequiredDeckConfig = getRequiredDeckConfig as jest.MockedFunction<
typeof getRequiredDeckConfig
>

const ROBOT_NAME = 'otie'
const RUN_ID = '1'
Expand Down Expand Up @@ -147,6 +153,7 @@ describe('ProtocolRunSetup', () => {
when(mockSetupLiquids).mockReturnValue(<div>Mock SetupLiquids</div>)
when(mockEmptySetupStep).mockReturnValue(<div>Mock EmptySetupStep</div>)
when(mockGetSimplestDeckConfigForProtocolCommands).mockReturnValue([])
when(mockGetRequiredDeckConfig).mockReturnValue([])
})
afterEach(() => {
resetAllWhenMocks()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ describe('ProtocolSetupModulesAndDeck', () => {
})

it('should render text and buttons', () => {
mockGetAttachedProtocolModuleMatches.mockReturnValue([
{
...mockProtocolModuleInfo[0],
attachedModuleMatch: calibratedMockApiHeaterShaker,
},
])
const [{ getByRole, getByText }] = render()
getByText('Module')
getByText('Location')
Expand Down
102 changes: 54 additions & 48 deletions app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ export function ProtocolSetupModulesAndDeck({
protocolModulesInfo
)

const hasModules = attachedProtocolModuleMatches.length > 0

const {
missingModuleIds,
remainingAttachedModules,
Expand Down Expand Up @@ -421,56 +423,60 @@ export function ProtocolSetupModulesAndDeck({
/>
) : null}
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing32}>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<Flex
color={COLORS.darkBlack70}
fontSize={TYPOGRAPHY.fontSize22}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
gridGap={SPACING.spacing24}
lineHeight={TYPOGRAPHY.lineHeight28}
paddingX={SPACING.spacing24}
>
<StyledText flex="4 0 0">{t('module')}</StyledText>
<StyledText flex="2 0 0">{t('location')}</StyledText>
<StyledText flex="3 0 0"> {t('status')}</StyledText>
</Flex>
{attachedProtocolModuleMatches.map(module => {
// check for duplicate module model in list of modules for protocol
const isDuplicateModuleModel = protocolModulesInfo
// filter out current module
.filter(otherModule => otherModule.moduleId !== module.moduleId)
// check for existence of another module of same model
.some(
otherModule =>
otherModule.moduleDef.model === module.moduleDef.model
)
{hasModules ? (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
<Flex
color={COLORS.darkBlack70}
fontSize={TYPOGRAPHY.fontSize22}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
gridGap={SPACING.spacing24}
lineHeight={TYPOGRAPHY.lineHeight28}
paddingX={SPACING.spacing24}
>
<StyledText flex="4 0 0">{t('module')}</StyledText>
<StyledText flex="2 0 0">{t('location')}</StyledText>
<StyledText flex="3 0 0"> {t('status')}</StyledText>
</Flex>
{attachedProtocolModuleMatches.map(module => {
// check for duplicate module model in list of modules for protocol
const isDuplicateModuleModel = protocolModulesInfo
// filter out current module
.filter(
otherModule => otherModule.moduleId !== module.moduleId
)
// check for existence of another module of same model
.some(
otherModule =>
otherModule.moduleDef.model === module.moduleDef.model
)

const cutoutIdForSlotName = getCutoutIdForSlotName(
module.slotName,
deckDef
)
const cutoutIdForSlotName = getCutoutIdForSlotName(
module.slotName,
deckDef
)

return (
<RowModule
key={module.moduleId}
module={module}
isDuplicateModuleModel={isDuplicateModuleModel}
setShowMultipleModulesModal={setShowMultipleModulesModal}
calibrationStatus={calibrationStatus}
chainLiveCommands={chainLiveCommands}
isLoading={isCommandMutationLoading}
prepCommandErrorMessage={prepCommandErrorMessage}
setPrepCommandErrorMessage={setPrepCommandErrorMessage}
conflictedFixture={deckConfig?.find(
fixture =>
fixture.cutoutId === cutoutIdForSlotName &&
fixture.cutoutFixtureId != null &&
!SINGLE_SLOT_FIXTURES.includes(fixture.cutoutFixtureId)
)}
/>
)
})}
</Flex>
return (
<RowModule
key={module.moduleId}
module={module}
isDuplicateModuleModel={isDuplicateModuleModel}
setShowMultipleModulesModal={setShowMultipleModulesModal}
calibrationStatus={calibrationStatus}
chainLiveCommands={chainLiveCommands}
isLoading={isCommandMutationLoading}
prepCommandErrorMessage={prepCommandErrorMessage}
setPrepCommandErrorMessage={setPrepCommandErrorMessage}
conflictedFixture={deckConfig?.find(
fixture =>
fixture.cutoutId === cutoutIdForSlotName &&
fixture.cutoutFixtureId != null &&
!SINGLE_SLOT_FIXTURES.includes(fixture.cutoutFixtureId)
)}
/>
)
})}
</Flex>
) : null}
<FixtureTable
robotType={FLEX_ROBOT_TYPE}
mostRecentAnalysis={mostRecentAnalysis}
Expand Down
4 changes: 2 additions & 2 deletions app/src/resources/deck_configuration/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { parseAllAddressableAreas } from '@opentrons/api-client'
import { useDeckConfigurationQuery } from '@opentrons/react-api-client'
import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotTypeV4,
getDeckDefFromRobotType,
} from '@opentrons/shared-data'

import {
Expand All @@ -26,7 +26,7 @@ export function useDeckConfigurationCompatibility(
): CutoutConfigAndCompatibility[] {
const deckConfig = useDeckConfigurationQuery().data ?? []
if (robotType !== FLEX_ROBOT_TYPE) return []
const deckDef = getDeckDefFromRobotTypeV4(robotType)
const deckDef = getDeckDefFromRobotType(robotType)
const allAddressableAreas = parseAllAddressableAreas(protocolCommands)
return deckConfig.reduce<CutoutConfigAndCompatibility[]>(
(acc, { cutoutId, cutoutFixtureId }) => {
Expand Down
29 changes: 23 additions & 6 deletions app/src/resources/deck_configuration/utils.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { parseAllAddressableAreas } from '@opentrons/api-client'
import {
FLEX_ROBOT_TYPE,
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS,
getAddressableAreaFromSlotId,
getDeckDefFromRobotTypeV4,
getDeckDefFromRobotType,
} from '@opentrons/shared-data'

import type {
CutoutConfig,
CutoutId,
RunTimeCommand,
CutoutFixtureId,
CutoutFixture,
AddressableAreaName,
DeckDefinition,
} from '@opentrons/shared-data'

export interface CutoutConfigProtocolSpec {
cutoutId: CutoutId
cutoutFixtureId: CutoutFixtureId | null
export interface CutoutConfigProtocolSpec extends CutoutConfig {
requiredAddressableAreas: AddressableAreaName[]
}

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

const addressableAreas = parseAllAddressableAreas(protocolAnalysisCommands)
const simplestDeckConfig = addressableAreas.reduce<
Expand Down Expand Up @@ -226,3 +225,21 @@ export function getSimplestFixtureForAddressableAreas(
)
return nextCompatibleCutoutFixtures?.[0] ?? null
}

export function getRequiredDeckConfig<T extends CutoutConfigProtocolSpec>(
deckConfigProtocolSpec: T[]
): T[] {
const nonSingleSlotDeckConfigCompatibility = deckConfigProtocolSpec.filter(
({ requiredAddressableAreas }) =>
// required AA list includes a non-single-slot AA
!requiredAddressableAreas.every(aa =>
FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(aa)
)
)
// fixture includes at least 1 required AA
const requiredDeckConfigProtocolSpec = nonSingleSlotDeckConfigCompatibility.filter(
fixture => fixture.requiredAddressableAreas.length > 0
)

return requiredDeckConfigProtocolSpec
}
4 changes: 2 additions & 2 deletions protocol-designer/src/step-forms/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
PipetteName,
THERMOCYCLER_MODULE_TYPE,
WASTE_CHUTE_ADDRESSABLE_AREAS,
getDeckDefFromRobotTypeV4,
getDeckDefFromRobotType,
AddressableAreaName,
CutoutId,
} from '@opentrons/shared-data'
Expand Down Expand Up @@ -1331,7 +1331,7 @@ export const additionalEquipmentInvariantProperties = handleActions<NormalizedAd
): NormalizedAdditionalEquipmentById => {
const { file } = action.payload
const isFlex = file.robot.model === FLEX_ROBOT_TYPE
const deckDef = getDeckDefFromRobotTypeV4(FLEX_ROBOT_TYPE)
const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
const cutoutFixtures = deckDef.cutoutFixtures
const providesAddressableAreasForAddressableArea = cutoutFixtures.find(
cutoutFixture => cutoutFixture.id.includes('stagingAreaRightSlot')
Expand Down
Loading

0 comments on commit 6a2bed3

Please sign in to comment.