Skip to content

Commit 06955fd

Browse files
feat(app): Display deck hardware in device details page with AA (#18713)
1 parent 2df5070 commit 06955fd

File tree

5 files changed

+167
-34
lines changed

5 files changed

+167
-34
lines changed

app/src/assets/localization/en/deck_configuration.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@
88
"temperature": "Temperature",
99
"thermocycler": "Thermocycler",
1010
"trash_bin": "Trash bin",
11-
"waste": "Waste"
11+
"waste": "Waste",
12+
"magnetic_block": "Magnetic block",
13+
"waste_chute": "Waste chute",
14+
"staging_area_slot": "Staging area slot",
15+
"module_in_port": "{{moduleName}} in USB-{{usbPortNumber}}"
1216
}

app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ import {
2222
} from '@opentrons/components'
2323
import { useModulesQuery } from '@opentrons/react-api-client'
2424
import {
25+
FAKE_STAGING_AREA_RIGHT_SLOT,
2526
FLEX_ROBOT_TYPE,
27+
getAAByAAId,
28+
getAAComboFixtureDisplayName,
29+
getAASlotDisplayName,
30+
getAASlotIdForAA,
2631
getCutoutDisplayName,
2732
getDeckDefFromRobotType,
2833
getFixtureDisplayName,
34+
replaceFixtureToFakeFixtureAndTransformCutoutFixturesToAA,
2935
SINGLE_SLOT_FIXTURES,
3036
} from '@opentrons/shared-data'
3137

@@ -40,6 +46,7 @@ import { useRunStatuses } from '/app/resources/runs'
4046

4147
import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal'
4248

49+
import type { TFunction } from 'i18next'
4350
import type { CutoutId } from '@opentrons/shared-data'
4451

4552
const DECK_CONFIG_REFETCH_INTERVAL = 5000
@@ -56,7 +63,7 @@ function getDisplayLocationForCutoutIds(cutouts: CutoutId[]): string {
5663
export function DeviceDetailsDeckConfiguration({
5764
robotName,
5865
}: DeviceDetailsDeckConfigurationProps): JSX.Element | null {
59-
const { t, i18n } = useTranslation('device_details')
66+
const { t, i18n } = useTranslation(['device_details', 'deck_configuration'])
6067
const [
6168
showSetupInstructionsModal,
6269
setShowSetupInstructionsModal,
@@ -67,6 +74,10 @@ export function DeviceDetailsDeckConfiguration({
6774
useNotifyDeckConfigurationQuery({
6875
refetchInterval: DECK_CONFIG_REFETCH_INTERVAL,
6976
}).data ?? []
77+
78+
const deckConfigWithAA = replaceFixtureToFakeFixtureAndTransformCutoutFixturesToAA(
79+
deckConfig
80+
)
7081
const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
7182
const { isRunRunning } = useRunStatuses()
7283
const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({
@@ -83,14 +94,28 @@ export function DeviceDetailsDeckConfiguration({
8394
} = useDeckConfigurationEditingTools(false)
8495

8596
// do not show standard slot in fixture display list
86-
const { displayList: fixtureDisplayList } = deckConfig.reduce<{
97+
const { displayList: fixtureDisplayList } = deckConfigWithAA.reduce<{
8798
displayList: Array<{ displayLocation: string; displayName: string }>
8899
groupedCutoutIds: CutoutId[]
89100
}>(
90-
(acc, { cutoutId, cutoutFixtureId, opentronsModuleSerialNumber }) => {
101+
(
102+
acc,
103+
{
104+
cutoutId,
105+
cutoutFixtureId,
106+
opentronsModuleSerialNumber,
107+
addressableAreaId,
108+
}
109+
) => {
110+
const areaInCheck = getAAByAAId(addressableAreaId, deckDef)
111+
const shouldShowAA =
112+
areaInCheck.areaType !== 'slot' &&
113+
areaInCheck.areaType !== 'fakeStagingSlot'
91114
if (
92115
cutoutFixtureId == null ||
93-
SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId)
116+
SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId) ||
117+
FAKE_STAGING_AREA_RIGHT_SLOT === cutoutFixtureId ||
118+
!shouldShowAA
94119
) {
95120
return acc
96121
}
@@ -101,7 +126,14 @@ export function DeviceDetailsDeckConfiguration({
101126
usbPort?.hubPort != null
102127
? `${usbPort.port}.${usbPort.hubPort}`
103128
: usbPort?.port
104-
const displayName = getFixtureDisplayName(cutoutFixtureId, portDisplay)
129+
const displayName =
130+
getAAComboFixtureDisplayName(
131+
cutoutFixtureId,
132+
addressableAreaId,
133+
deckDef,
134+
t as TFunction,
135+
portDisplay
136+
) ?? getFixtureDisplayName(cutoutFixtureId, portDisplay)
105137
const fixtureGroup =
106138
deckDef.cutoutFixtures.find(cf => cf.id === cutoutFixtureId)
107139
?.fixtureGroup ?? {}
@@ -126,12 +158,19 @@ export function DeviceDetailsDeckConfiguration({
126158
}
127159
}
128160
}
161+
const name = getAASlotIdForAA(
162+
cutoutId,
163+
cutoutFixtureId,
164+
addressableAreaId
165+
)
129166
return {
130167
...acc,
131168
displayList: [
132169
...acc.displayList,
133170
{
134-
displayLocation: getDisplayLocationForCutoutIds([cutoutId]),
171+
displayLocation: name
172+
? getAASlotDisplayName(name)
173+
: getDisplayLocationForCutoutIds([cutoutId]),
135174
displayName,
136175
},
137176
],

shared-data/js/__tests__/fixtures.test.ts

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { describe, expect, it } from 'vitest'
1+
import { useTranslation } from 'react-i18next'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
23

34
import {
45
FAKE_STAGING_AREA_RIGHT_SLOT,
@@ -18,7 +19,8 @@ import {
1819
WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE,
1920
} from '..'
2021
import {
21-
getAASlotNameForAA,
22+
getAAComboFixtureDisplayName,
23+
getAASlotIdForAA,
2224
getAAWithFakesFromCutoutFixtureId,
2325
getCutoutFixtureReplacementIfNeeded,
2426
getFlexDeckDefAAByFixtureIdForCutoutId,
@@ -30,6 +32,12 @@ import {
3032
} from '../fixtures'
3133
import { getDeckDefFromRobotType } from '../helpers'
3234

35+
import type { Mock } from 'vitest'
36+
37+
vi.mock('react-i18next', () => ({
38+
useTranslation: vi.fn(),
39+
}))
40+
3341
describe('getAAFromCutoutFixtureId', () => {
3442
it('Should get the aa for a cutoutId and a cutoutFixtureId', () => {
3543
const result = getAAWithFakesFromCutoutFixtureId(
@@ -385,7 +393,7 @@ describe('getReplacementFixtureForFakeFixture', () => {
385393

386394
describe('getAASlotNameForAA', () => {
387395
it('should get aa name for single right slot', () => {
388-
const result = getAASlotNameForAA(
396+
const result = getAASlotIdForAA(
389397
'cutoutA3',
390398
FAKE_STAGING_SLOT_WITH_MAG_BLOCK,
391399
'magneticBlockV1A3'
@@ -394,7 +402,7 @@ describe('getAASlotNameForAA', () => {
394402
})
395403

396404
it('should return aa name for single center slot', () => {
397-
const result = getAASlotNameForAA(
405+
const result = getAASlotIdForAA(
398406
'cutoutD1',
399407
TEMPERATURE_MODULE_V2_FIXTURE,
400408
'temperatureModuleV2D1'
@@ -403,7 +411,7 @@ describe('getAASlotNameForAA', () => {
403411
})
404412

405413
it('should get input aa name for single center slot', () => {
406-
const result = getAASlotNameForAA(
414+
const result = getAASlotIdForAA(
407415
'cutoutD2',
408416
MAGNETIC_BLOCK_V1_FIXTURE,
409417
'magneticBlockV1D2'
@@ -438,3 +446,67 @@ describe('replaceCutoutFixtureRemove', () => {
438446
expect(result).toEqual(WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE)
439447
})
440448
})
449+
450+
describe('getAAFixtureDisplayName', () => {
451+
let t: Mock
452+
453+
beforeEach(() => {
454+
t = vi.fn(key => key)
455+
456+
vi.mocked(useTranslation).mockReturnValue({ t } as any)
457+
})
458+
it('Should return flex stacker name when using combo fixtures and aa for stacker', () => {
459+
const name = getAAComboFixtureDisplayName(
460+
FLEX_STACKER_WTIH_WASTE_CHUTE_ADAPTER_NO_COVER_FIXTURE,
461+
'flexStackerModuleV1D4',
462+
getDeckDefFromRobotType('OT-3 Standard'),
463+
t,
464+
''
465+
)
466+
expect(name).toEqual('deck_configuration:module_in_port')
467+
})
468+
469+
it('Should return mag block name when using combo fixtures with mag block', () => {
470+
const name = getAAComboFixtureDisplayName(
471+
FLEX_STACKER_WITH_MAG_BLOCK_FIXTURE,
472+
'magneticBlockV1D3',
473+
getDeckDefFromRobotType('OT-3 Standard'),
474+
t,
475+
'deck_configuration'
476+
)
477+
expect(name).toEqual('deck_configuration:magnetic_block')
478+
})
479+
480+
it('Should return mag block name when using combo fixtures with mag block', () => {
481+
const name = getAAComboFixtureDisplayName(
482+
FLEX_STACKER_WITH_MAG_BLOCK_FIXTURE,
483+
'magneticBlockV1D3',
484+
getDeckDefFromRobotType('OT-3 Standard'),
485+
t,
486+
'deck_configuration'
487+
)
488+
expect(name).toEqual('deck_configuration:magnetic_block')
489+
})
490+
491+
it('Should return waste chute name when using waste chute fixture', () => {
492+
const name = getAAComboFixtureDisplayName(
493+
FAKE_WASTE_CHUTE_WITH_EMPTY_SLOT,
494+
'96ChannelWasteChute',
495+
getDeckDefFromRobotType('OT-3 Standard'),
496+
t,
497+
'deck_configuration'
498+
)
499+
expect(name).toEqual('deck_configuration:waste_chute')
500+
})
501+
502+
it('Should return null when not a combo fixture', () => {
503+
const name = getAAComboFixtureDisplayName(
504+
MAGNETIC_BLOCK_V1_FIXTURE,
505+
'magneticBlockV1D3',
506+
getDeckDefFromRobotType('OT-3 Standard'),
507+
t,
508+
'deck_configuration'
509+
)
510+
expect(name).toBe(null)
511+
})
512+
})

shared-data/js/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ export const MAGNETIC_BLOCK_FIXTURES: CutoutFixtureId[] = [
656656
FLEX_STACKER_WITH_MAG_BLOCK_FIXTURE,
657657
]
658658

659-
export const SINGLE_SLOT_FIXTURES: CutoutFixtureId[] = [
659+
export const SINGLE_SLOT_FIXTURES: CutoutFixtureIdsWithFakes[] = [
660660
SINGLE_LEFT_SLOT_FIXTURE,
661661
SINGLE_CENTER_SLOT_FIXTURE,
662662
SINGLE_RIGHT_SLOT_FIXTURE,

shared-data/js/fixtures.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
import { getCutoutIdForSlotName, getDeckDefFromRobotType } from './helpers'
6565
import { getModuleDisplayName } from './modules'
6666

67+
import type { TFunction } from 'i18next'
6768
import type { ModuleLocation } from '../command'
6869
import type {
6970
AddressableAreaName,
@@ -671,44 +672,56 @@ export function getAddressableAreaNamesFromLoadedModule(
671672
}, [])
672673
}
673674

674-
export function getAAFixtureDisplayName(
675+
export const getModuleDisplayNameWithPort = (
676+
usbPortNumber: number | string
677+
): string => {
678+
return `${getModuleDisplayName(
679+
FLEX_STACKER_MODULE_V1
680+
)} in USB-${usbPortNumber}`
681+
}
682+
683+
export function getAAComboFixtureDisplayName(
675684
cutoutFixtureId: CutoutFixtureIdsWithFakes | null,
676685
addressableAreaId: AddressableAreaNamesWithFakes,
686+
deckDef: DeckDefinition,
687+
t: TFunction,
677688
usbPortNumber?: number | string
678689
): string | null {
690+
const aaItem = getAAByAAId(addressableAreaId, deckDef)
691+
const translationFileName = 'deck_configuration'
679692
switch (cutoutFixtureId) {
680-
case STAGING_AREA_RIGHT_SLOT_FIXTURE:
681-
return 'Staging area slot'
682693
case FLEX_STACKER_WTIH_WASTE_CHUTE_ADAPTER_NO_COVER_FIXTURE:
683-
if (addressableAreaId.includes('flexStackerModuleV1')) {
694+
if (aaItem.areaType === 'flexStacker') {
684695
return usbPortNumber != null
685-
? `${getModuleDisplayName(
686-
FLEX_STACKER_MODULE_V1
687-
)} in USB-${usbPortNumber}`
696+
? t(`${translationFileName}:module_in_port`, {
697+
moduleName: getModuleDisplayName(FLEX_STACKER_MODULE_V1),
698+
usbPortNumber,
699+
})
688700
: `${getModuleDisplayName(FLEX_STACKER_MODULE_V1)}`
689701
} else {
690-
return 'Waste chute'
702+
return t(`${translationFileName}:waste_chute`)
691703
}
692704
case FLEX_STACKER_WITH_MAG_BLOCK_FIXTURE:
693-
if (addressableAreaId.includes('flexStackerModuleV1')) {
705+
if (aaItem.areaType === 'flexStacker') {
694706
return usbPortNumber != null
695-
? `${getModuleDisplayName(
696-
FLEX_STACKER_MODULE_V1
697-
)} in USB-${usbPortNumber}`
707+
? t(`${translationFileName}:module_in_port`, {
708+
moduleName: getModuleDisplayName(FLEX_STACKER_MODULE_V1),
709+
usbPortNumber,
710+
})
698711
: `${getModuleDisplayName(FLEX_STACKER_MODULE_V1)}`
699712
} else {
700-
return 'Magnetic block'
713+
return t(`${translationFileName}:magnetic_block`)
701714
}
702715
case FAKE_WASTE_CHUTE_WITH_EMPTY_SLOT:
703-
if (addressableAreaId.includes('WasteChute')) {
704-
return 'Waste chute'
716+
if (aaItem.areaType === 'wasteChute') {
717+
return t(`${translationFileName}:waste_chute`)
705718
}
706719
return null
707720
case STAGING_AREA_SLOT_WITH_WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE:
708-
if (addressableAreaId.includes('WasteChute')) {
709-
return 'Waste chute'
721+
if (aaItem.areaType === 'wasteChute') {
722+
return t(`${translationFileName}:waste_chute`)
710723
} else {
711-
return 'Staging area slot'
724+
return t(`${translationFileName}:staging_area_slot`)
712725
}
713726
default:
714727
return null
@@ -724,7 +737,6 @@ export function getFixtureDisplayName(
724737
case STAGING_AREA_RIGHT_SLOT_FIXTURE:
725738
return 'Staging area slot'
726739
case FAKE_STAGING_AREA_RIGHT_SLOT:
727-
// for debugging perpuses change to display name
728740
return 'Fake Staging area slot'
729741
case TRASH_BIN_ADAPTER_FIXTURE:
730742
return 'Trash bin'
@@ -950,7 +962,7 @@ export const replaceAAWithFakeAA = (
950962
}
951963
}
952964

953-
export const getAASlotNameForAA = (
965+
export const getAASlotIdForAA = (
954966
cutoutId: CutoutId,
955967
fixtureId: CutoutFixtureIdsWithFakes,
956968
addressableAreaId: AddressableAreaNamesWithFakes
@@ -965,6 +977,12 @@ export const getAASlotNameForAA = (
965977
: (addressableAreaId as AddressableAreaNamesWithFakes)
966978
}
967979

980+
export const getAASlotDisplayName = (
981+
addressableAreaId: AddressableAreaNamesWithFakes
982+
): string => {
983+
return addressableAreaId.replace('fake', '')
984+
}
985+
968986
/**
969987
* Given a cutout fixture find if should be changed to a combo fixture
970988
* @param addedCutoutConfigs: fixtures list selected to add to deck
@@ -1061,7 +1079,7 @@ export const replaceCutoutFixtureRemove = (
10611079
} else {
10621080
const updated = aaForCutoutAndFixture?.map(aa =>
10631081
aa === addressableAreaId
1064-
? getAASlotNameForAA(cutoutId, cutoutFixtureRemoved, aa)
1082+
? getAASlotIdForAA(cutoutId, cutoutFixtureRemoved, aa)
10651083
: aa
10661084
)
10671085
const match = Object.entries(addressableAreasById).find(([, value]) =>

0 commit comments

Comments
 (0)