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

refactor(app, api-client): add utilities for parsing deck config via addressable areas #13947

Merged
merged 6 commits into from
Nov 9, 2023
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
58 changes: 58 additions & 0 deletions api-client/src/protocols/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ModuleModel,
PipetteName,
RunTimeCommand,
AddressableAreaName,
} from '@opentrons/shared-data'

interface PipetteNamesByMount {
Expand All @@ -25,11 +26,11 @@
export function parseInitialPipetteNamesByMount(
commands: RunTimeCommand[]
): PipetteNamesByMount {
const rightPipetteName = commands.find(

Check warning on line 29 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 29 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
(command): command is LoadPipetteRunTimeCommand =>
command.commandType === 'loadPipette' && command.params.mount === 'right'
)?.params.pipetteName as PipetteName | undefined
const leftPipetteName = commands.find(

Check warning on line 33 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 33 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
(command): command is LoadPipetteRunTimeCommand =>
command.commandType === 'loadPipette' && command.params.mount === 'left'
)?.params.pipetteName as PipetteName | undefined
Expand Down Expand Up @@ -244,6 +245,63 @@
)
}

export function parseAllAddressableAreas(
commands: RunTimeCommand[]
): AddressableAreaName[] {
return commands.reduce<AddressableAreaName[]>((acc, command) => {
if (
command.commandType === 'moveLabware' &&
command.params.newLocation !== 'offDeck' &&
'slotName' in command.params.newLocation &&
!acc.includes(command.params.newLocation.slotName as AddressableAreaName)

Check warning on line 256 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 256 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
) {
return [
...acc,
command.params.newLocation.slotName as AddressableAreaName,

Check warning on line 260 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 260 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
} else if (
command.commandType === 'moveLabware' &&
command.params.newLocation !== 'offDeck' &&
'addressableAreaName' in command.params.newLocation &&
!acc.includes(
command.params.newLocation.addressableAreaName as AddressableAreaName

Check warning on line 267 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 267 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
)
) {
return [
...acc,
command.params.newLocation.addressableAreaName as AddressableAreaName,

Check warning on line 272 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 272 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
} else if (
(command.commandType === 'loadLabware' ||
command.commandType === 'loadModule') &&
command.params.location !== 'offDeck' &&
'slotName' in command.params.location &&
!acc.includes(command.params.location.slotName as AddressableAreaName)

Check warning on line 279 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 279 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
) {
return [...acc, command.params.location.slotName as AddressableAreaName]

Check warning on line 281 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 281 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
} else if (
command.commandType === 'loadLabware' &&
command.params.location !== 'offDeck' &&
'addressableAreaName' in command.params.location &&
!acc.includes(
command.params.location.addressableAreaName as AddressableAreaName

Check warning on line 287 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 287 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
)
) {
return [
...acc,
command.params.location.addressableAreaName as AddressableAreaName,

Check warning on line 292 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 292 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
}
// TODO(BC, 11/6/23): once moveToAddressableArea command exists add it back here
// else if (command.commandType === 'moveToAddressableArea') {
Comment on lines +295 to +296
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moveToAddressableArea command exists in types on edge

// ...
// }
else {
return acc
}
}, [])
}

export interface LiquidsById {
[liquidId: string]: {
displayName: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ export function getLabwareLocationCombos(
})
} else {
return appendLocationComboIfUniq(acc, {
location: command.params.location,
location: {
slotName:
'addressableAreaName' in command.params.location
? command.params.location.addressableAreaName
: command.params.location.slotName,
},
definitionUri,
labwareId: command.result.labwareId,
})
Expand Down Expand Up @@ -107,7 +112,12 @@ export function getLabwareLocationCombos(
})
} else {
return appendLocationComboIfUniq(acc, {
location: command.params.newLocation,
location: {
slotName:
'addressableAreaName' in command.params.newLocation
? command.params.newLocation.addressableAreaName
: command.params.newLocation.slotName,
},
definitionUri: labwareEntity.definitionUri,
labwareId: command.params.labwareId,
})
Expand Down Expand Up @@ -191,7 +201,10 @@ function resolveAdapterLocation(
} else {
adapterOffsetLocation = {
definitionUri: labwareDefUri,
slotName: labwareEntity.location.slotName,
slotName:
'addressableAreaName' in labwareEntity.location
? labwareEntity.location.addressableAreaName
: labwareEntity.location.slotName,
}
}
return {
Expand Down
5 changes: 4 additions & 1 deletion app/src/organisms/CommandText/LoadCommandText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ export const LoadCommandText = ({
? t('load_labware_info_protocol_setup_off_deck', { labware })
: t('load_labware_info_protocol_setup_no_module', {
labware,
slot_name: command.params.location?.slotName,
slot_name:
'addressableAreaName' in command.params.location
? command.params.location.addressableAreaName
: command.params.location.slotName,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
slotName: adapter.location.slotName,
definitionUri: adapter.definitionUri,
}
} else if ('addressableAreaName' in adapter.location) {
return {

Check warning on line 43 in app/src/organisms/Devices/ProtocolRun/utils/getLabwareOffsetLocation.ts

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Devices/ProtocolRun/utils/getLabwareOffsetLocation.ts#L43

Added line #L43 was not covered by tests
slotName: adapter.location.addressableAreaName,
definitionUri: adapter.definitionUri,
}
} else if ('moduleId' in adapter.location) {
const moduleIdUnderAdapter = adapter.location.moduleId
const moduleModel = modules.find(
Expand All @@ -52,7 +57,12 @@
return { slotName, moduleModel, definitionUri: adapter.definitionUri }
}
} else {
return { slotName: labwareLocation.slotName }
return {
slotName:
'addressableAreaName' in labwareLocation
? labwareLocation.addressableAreaName
: labwareLocation.slotName,
}
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ export const getLabwareRenderInfo = (
)
}
// TODO(bh, 2023-10-19): convert this to deck definition v4 addressableAreas
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this TODO be deleted now?

const slotName = location.slotName.toString()
const slotName =
'addressableAreaName' in location
? location.addressableAreaName
: location.slotName
// TODO(bh, 2023-10-19): remove slotPosition when render info no longer relies on directly
const slotPosition = getSlotPosition(deckDef, slotName)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
return { slotName: 'Off deck', labwareName }
} else if ('slotName' in labwareLocation) {
return { slotName: labwareLocation.slotName, labwareName }
} else if ('addressableAreaName' in labwareLocation) {
return { slotName: labwareLocation.addressableAreaName, labwareName }

Check warning on line 50 in app/src/organisms/Devices/ProtocolRun/utils/getLocationInfoNames.ts

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Devices/ProtocolRun/utils/getLocationInfoNames.ts#L50

Added line #L50 was not covered by tests
} else if ('moduleId' in labwareLocation) {
const loadModuleCommandUnderLabware = loadModuleCommands?.find(
command => command.result?.moduleId === labwareLocation.moduleId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export function getRunLabwareRenderInfo(
}

if (location !== 'offDeck') {
const slotName = location.slotName
const slotName =
'addressableAreaName' in location
? location.addressableAreaName
: location.slotName
const slotPosition =
deckDef.locations.orderedSlots.find(slot => slot.id === slotName)
?.position ?? []
Expand Down
5 changes: 4 additions & 1 deletion app/src/organisms/LabwarePositionCheck/utils/labware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@
).location.slotName
}
} else {
slot = loc.slotName
slot =

Check warning on line 195 in app/src/organisms/LabwarePositionCheck/utils/labware.ts

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/LabwarePositionCheck/utils/labware.ts#L195

Added line #L195 was not covered by tests
'addressableAreaName' in loc
? loc.addressableAreaName
: loc.slotName
}
return [
...innerAcc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
useModulesQuery,
} from '@opentrons/react-api-client'
import { renderWithProviders } from '@opentrons/components'
import { getDeckDefFromRobotType } from '@opentrons/shared-data'
import ot3StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'

import { i18n } from '../../../i18n'
Expand All @@ -25,7 +24,6 @@ import {
} from '../__fixtures__'

jest.mock('@opentrons/react-api-client')
jest.mock('@opentrons/shared-data/js/helpers')
jest.mock(
'../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis'
)
Expand All @@ -37,9 +35,6 @@ const mockUseCreateLiveCommandMutation = useCreateLiveCommandMutation as jest.Mo
const mockUseModulesQuery = useModulesQuery as jest.MockedFunction<
typeof useModulesQuery
>
const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction<
typeof getDeckDefFromRobotType
>
const mockUseMostRecentCompletedAnalysis = useMostRecentCompletedAnalysis as jest.MockedFunction<
typeof useMostRecentCompletedAnalysis
>
Expand Down Expand Up @@ -72,9 +67,6 @@ describe('ProtocolSetupLabware', () => {
when(mockUseMostRecentCompletedAnalysis)
.calledWith(RUN_ID)
.mockReturnValue(mockRecentAnalysis)
when(mockGetDeckDefFromRobotType)
.calledWith('OT-3 Standard')
.mockReturnValue(ot3StandardDeckDef as any)
when(mockGetProtocolModulesInfo)
.calledWith(mockRecentAnalysis, ot3StandardDeckDef as any)
.mockReturnValue(mockProtocolModuleInfo)
Expand Down
142 changes: 142 additions & 0 deletions app/src/resources/deck_configuration/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { RunTimeCommand } from '@opentrons/shared-data'
import {
FLEX_SIMPLEST_DECK_CONFIG,
getSimplestDeckConfigForProtocolCommands,
} from '../utils'

const RUN_TIME_COMMAND_STUB_MIXIN: Pick<
RunTimeCommand,
'id' | 'createdAt' | 'startedAt' | 'completedAt' | 'status'
> = {
id: 'fake_id',
createdAt: 'fake_createdAt',
startedAt: 'fake_startedAt',
completedAt: 'fake_createdAt',
status: 'succeeded',
}

describe('getSimplestDeckConfigForProtocolCommands', () => {
it('returns simplest deck if no commands alter addressable areas', () => {
expect(getSimplestDeckConfigForProtocolCommands([])).toEqual(
FLEX_SIMPLEST_DECK_CONFIG
)
})
it('returns staging area fixtures if commands address column 4 areas', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'A4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'B4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'C4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'D4' },
version: 1,
namespace: 'fake_namespace',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 8),
{
cutoutId: 'cutoutA3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['A4'],
},
{
cutoutId: 'cutoutB3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['B4'],
},
{
cutoutId: 'cutoutC3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['C4'],
},
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['D4'],
},
])
})
it('returns simplest cutout fixture where many are possible', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'gripperWasteChute' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 11),
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'wasteChuteRightAdapterNoCover',
requiredAddressableAreas: ['gripperWasteChute'],
},
])
})
it('returns compatible cutout fixture where multiple addressable requirements present', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'gripperWasteChute' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'D4' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 11),
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'stagingAreaSlotWithWasteChuteRightAdapterNoCover',
requiredAddressableAreas: ['gripperWasteChute', 'D4'],
},
])
})
})
Loading
Loading