Skip to content

Commit

Permalink
feat(app): list required fixtures in desktop protocol details (#13690)
Browse files Browse the repository at this point in the history
Add utilities to parse out and list required fixtures from an analysis' command list.

Closes RAUT-684

Co-authored-by: Jethary Rader <[email protected]>
  • Loading branch information
b-cooper and jerader authored Oct 3, 2023
1 parent 08e830c commit 4db3068
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 60 deletions.
2 changes: 1 addition & 1 deletion api-client/src/deck_configuration/__stubs__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import type { Fixture } from '../types'
export const DECK_CONFIG_STUB: { [fixtureLocation: string]: Fixture } = {
B3: { fixtureLocation: 'B3', loadName: 'standardSlot', fixtureId: uuidv4() },
C3: { fixtureLocation: 'C3', loadName: 'extensionSlot', fixtureId: uuidv4() },
D3: { fixtureLocation: 'D3', loadName: 'trashChute', fixtureId: uuidv4() },
D3: { fixtureLocation: 'D3', loadName: 'wasteChute', fixtureId: uuidv4() },
}
2 changes: 1 addition & 1 deletion api-client/src/deck_configuration/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO(bh, 2023-09-26): refine types and move to shared data when settled
export type FixtureName = 'extensionSlot' | 'standardSlot' | 'trashChute'
export type FixtureName = 'extensionSlot' | 'standardSlot' | 'wasteChute'
export type FixtureLocation = 'B3' | 'C3' | 'D3'

export interface Fixture {
Expand Down
56 changes: 55 additions & 1 deletion api-client/src/protocols/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import {
parseLiquidsInLoadOrder,
parseLabwareInfoByLiquidId,
parseInitialLoadedLabwareByAdapter,
parseInitialLoadedFixturesByCutout,
} from '../utils'
import { simpleAnalysisFileFixture } from '../__fixtures__'

import type { RunTimeCommand } from '@opentrons/shared-data'
import {
LoadFixtureRunTimeCommand,
RunTimeCommand,
STAGING_AREA_LOAD_NAME,
STANDARD_SLOT_LOAD_NAME,
WASTE_CHUTE_LOAD_NAME,
} from '@opentrons/shared-data'

const mockRunTimeCommands: RunTimeCommand[] = simpleAnalysisFileFixture.commands as any
const mockLoadLiquidRunTimeCommands = [
Expand Down Expand Up @@ -359,6 +366,53 @@ describe('parseInitialLoadedModulesBySlot', () => {
)
})
})
describe('parseInitialLoadedFixturesByCutout', () => {
it('returns fixtures loaded in cutouts', () => {
const loadFixtureCommands: LoadFixtureRunTimeCommand[] = [
{
id: 'fakeId1',
commandType: 'loadFixture',
params: {
loadName: STAGING_AREA_LOAD_NAME,
location: { cutout: 'B3' },
},
createdAt: 'fake_timestamp',
startedAt: 'fake_timestamp',
completedAt: 'fake_timestamp',
status: 'succeeded',
},
{
id: 'fakeId2',
commandType: 'loadFixture',
params: { loadName: WASTE_CHUTE_LOAD_NAME, location: { cutout: 'D3' } },
createdAt: 'fake_timestamp',
startedAt: 'fake_timestamp',
completedAt: 'fake_timestamp',
status: 'succeeded',
},
{
id: 'fakeId3',
commandType: 'loadFixture',
params: {
loadName: STANDARD_SLOT_LOAD_NAME,
location: { cutout: 'C3' },
},
createdAt: 'fake_timestamp',
startedAt: 'fake_timestamp',
completedAt: 'fake_timestamp',
status: 'succeeded',
},
]
const expected = {
B3: loadFixtureCommands[0],
D3: loadFixtureCommands[1],
C3: loadFixtureCommands[2],
}
expect(parseInitialLoadedFixturesByCutout(loadFixtureCommands)).toEqual(
expected
)
})
})
describe('parseLiquidsInLoadOrder', () => {
it('returns liquids in loaded order', () => {
const expected = [
Expand Down
24 changes: 22 additions & 2 deletions api-client/src/protocols/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
LoadModuleRunTimeCommand,
LoadPipetteRunTimeCommand,
LoadLiquidRunTimeCommand,
LoadFixtureRunTimeCommand,
} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup'

interface PipetteNamesByMount {
Expand Down Expand Up @@ -210,14 +211,14 @@ interface LoadedModulesBySlot {
export function parseInitialLoadedModulesBySlot(
commands: RunTimeCommand[]
): LoadedModulesBySlot {
const loadLabwareCommandsReversed = commands
const loadModuleCommandsReversed = commands
.filter(
(command): command is LoadModuleRunTimeCommand =>
command.commandType === 'loadModule'
)
.reverse()
return reduce<LoadModuleRunTimeCommand, LoadedModulesBySlot>(
loadLabwareCommandsReversed,
loadModuleCommandsReversed,
(acc, command) =>
'slotName' in command.params.location
? { ...acc, [command.params.location.slotName]: command }
Expand All @@ -226,6 +227,25 @@ export function parseInitialLoadedModulesBySlot(
)
}

interface LoadedFixturesBySlot {
[slotName: string]: LoadFixtureRunTimeCommand
}
export function parseInitialLoadedFixturesByCutout(
commands: RunTimeCommand[]
): LoadedFixturesBySlot {
const loadFixtureCommandsReversed = commands
.filter(
(command): command is LoadFixtureRunTimeCommand =>
command.commandType === 'loadFixture'
)
.reverse()
return reduce<LoadFixtureRunTimeCommand, LoadedFixturesBySlot>(
loadFixtureCommandsReversed,
(acc, command) => ({ ...acc, [command.params.location.cutout]: command }),
{}
)
}

export interface LiquidsById {
[liquidId: string]: {
displayName: string
Expand Down
94 changes: 56 additions & 38 deletions app/src/organisms/ProtocolDetails/RobotConfigurationDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TYPOGRAPHY,
} from '@opentrons/components'
import {
getFixtureDisplayName,
getModuleDisplayName,
getModuleType,
getPipetteNameSpecs,
Expand All @@ -25,14 +26,19 @@ import { StyledText } from '../../atoms/text'
import { getRobotTypeDisplayName } from '../ProtocolsLanding/utils'
import { getSlotsForThermocycler } from './utils'

import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup'
import type { PipetteName, RobotType } from '@opentrons/shared-data'
import type {
LoadModuleRunTimeCommand,
LoadFixtureRunTimeCommand,
PipetteName,
RobotType,
} from '@opentrons/shared-data'

interface RobotConfigurationDetailsProps {
leftMountPipetteName: PipetteName | null
rightMountPipetteName: PipetteName | null
extensionInstrumentName: string | null
requiredModuleDetails: LoadModuleRunTimeCommand[] | null
requiredModuleDetails: LoadModuleRunTimeCommand[]
requiredFixtureDetails: LoadFixtureRunTimeCommand[]
isLoading: boolean
robotType: RobotType | null
}
Expand All @@ -45,6 +51,7 @@ export const RobotConfigurationDetails = (
rightMountPipetteName,
extensionInstrumentName,
requiredModuleDetails,
requiredFixtureDetails,
isLoading,
robotType,
} = props
Expand Down Expand Up @@ -138,41 +145,52 @@ export const RobotConfigurationDetails = (
/>
</>
) : null}
{requiredModuleDetails != null
? requiredModuleDetails.map((module, index) => {
return (
<React.Fragment key={index}>
<Divider marginY={SPACING.spacing12} width="100%" />
<RobotConfigurationDetailsItem
label={t('run_details:module_slot_number', {
slot_number:
getModuleType(module.params.model) ===
THERMOCYCLER_MODULE_TYPE
? getSlotsForThermocycler(robotType)
: module.params.location.slotName,
})}
item={
<>
<ModuleIcon
key={index}
moduleType={getModuleType(module.params.model)}
marginRight={SPACING.spacing4}
alignSelf={ALIGN_CENTER}
color={COLORS.darkGreyEnabled}
height={SIZE_1}
minWidth={SIZE_1}
minHeight={SIZE_1}
/>
<StyledText as="p">
{getModuleDisplayName(module.params.model)}
</StyledText>
</>
}
/>
</React.Fragment>
)
})
: null}
{requiredModuleDetails.map((module, index) => {
return (
<React.Fragment key={`module_${index}`}>
<Divider marginY={SPACING.spacing12} width="100%" />
<RobotConfigurationDetailsItem
label={
getModuleType(module.params.model) === THERMOCYCLER_MODULE_TYPE
? getSlotsForThermocycler(robotType)
: module.params.location.slotName
}
item={
<>
<ModuleIcon
key={index}
moduleType={getModuleType(module.params.model)}
marginRight={SPACING.spacing4}
alignSelf={ALIGN_CENTER}
color={COLORS.darkGreyEnabled}
height={SIZE_1}
minWidth={SIZE_1}
minHeight={SIZE_1}
/>
<StyledText as="p">
{getModuleDisplayName(module.params.model)}
</StyledText>
</>
}
/>
</React.Fragment>
)
})}
{requiredFixtureDetails.map((fixture, index) => {
return (
<React.Fragment key={`fixture_${index}`}>
<Divider marginY={SPACING.spacing12} width="100%" />
<RobotConfigurationDetailsItem
label={fixture.params.location.cutout}
item={
<StyledText as="p">
{getFixtureDisplayName(fixture.params.loadName)}
</StyledText>
}
/>
</React.Fragment>
)
})}
</Flex>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: OT2_STANDARD_MODEL,
Expand All @@ -86,7 +87,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: FLEX_STANDARD_MODEL,
Expand All @@ -100,7 +102,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: OT2_STANDARD_MODEL,
Expand All @@ -116,7 +119,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: null,
rightMountPipetteName: 'p10_single',
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: OT2_STANDARD_MODEL,
Expand All @@ -132,7 +136,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: FLEX_STANDARD_MODEL,
Expand All @@ -145,7 +150,8 @@ describe('RobotConfigurationDetails', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: false,
robotType: OT2_STANDARD_MODEL,
Expand All @@ -160,20 +166,22 @@ describe('RobotConfigurationDetails', () => {
rightMountPipetteName: 'p10_single',
extensionInstrumentName: null,
requiredModuleDetails: mockRequiredModuleDetails,
requiredFixtureDetails: [],
isLoading: false,
robotType: OT2_STANDARD_MODEL,
}

const { getByText } = render(props)
getByText('Slot 1')
getByText('1')
getByText('Magnetic Module GEN2')
})

it('renders loading for both pipettes when it is in a loading state', () => {
props = {
leftMountPipetteName: 'p10_single',
rightMountPipetteName: null,
requiredModuleDetails: null,
requiredModuleDetails: [],
requiredFixtureDetails: [],
extensionInstrumentName: null,
isLoading: true,
robotType: OT2_STANDARD_MODEL,
Expand Down
Loading

0 comments on commit 4db3068

Please sign in to comment.