Skip to content

Commit 5415917

Browse files
authored
fix(app): resolve module location conflicts through deck config during protocol setup on ODD (#14966)
Resolve location conflicts in the on device display's protocol setup flow by updating deck configuration accordingly. Closes PLAT-287, Closes PLAT-291
1 parent a9dcb20 commit 5415917

38 files changed

+384
-216
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"action_needed": "Action needed",
44
"adapter_slot_location_module": "Slot {{slotName}}, {{adapterName}} on {{moduleName}}",
55
"adapter_slot_location": "Slot {{slotName}}, {{adapterName}}",
6-
"add_fixture_to_deck": "Add this fixture to your deck configuration. It will be referenced during protocol analysis.",
76
"add_fixture": "Add {{fixtureName}} to deck configuration",
87
"additional_labware": "{{count}} additional labware",
98
"additional_off_deck_labware": "Additional Off-Deck Labware",
9+
"add_this_deck_hardware": "Add this deck hardware to your deck configuration. It will be referenced during protocol analysis.",
10+
"add_to_slot": "Add to slot {{slotName}}",
1011
"attach_gripper_failure_reason": "Attach the required gripper to continue",
1112
"attach_gripper": "attach gripper",
1213
"attach_module": "Attach module before calibrating",
@@ -38,6 +39,7 @@
3839
"calibration_status": "calibration status",
3940
"calibration": "Calibration",
4041
"cancel_and_restart_to_edit": "Cancel the run and restart setup to edit",
42+
"cancel_protocol_and_edit_deck_config": "Cancel protocol and edit deck configuration",
4143
"choose_enum": "Choose {{displayName}}",
4244
"closing": "Closing...",
4345
"complete_setup_before_proceeding": "complete setup before continuing run",
@@ -142,7 +144,6 @@
142144
"module_setup_step_title": "Modules",
143145
"module_slot_location": "Slot {{slotName}}, {{moduleName}}",
144146
"module": "Module",
145-
"modules_and_deck": "Modules & deck",
146147
"modules_connected_plural": "{{count}} modules attached",
147148
"modules_connected": "{{count}} module attached",
148149
"modules_setup_step_title": "Module Setup",
@@ -249,12 +250,14 @@
249250
"slot_number": "Slot Number",
250251
"status": "Status",
251252
"step": "STEP {{index}}",
253+
"there_are_no_unconfigured_modules": "There are no un-configured {{module}} connected to the robot. Plug one in or remove an existing {{module}}, move it to the right place, and update the deck configuration.",
252254
"tip_length_cal_description_bullet": "Perform Tip Length Calibration for each new tip type used on a pipette.",
253255
"tip_length_cal_description": "This measures the Z distance between the bottom of the tip and the pipette’s nozzle. If you redo the tip length calibration for the tip you used to calibrate a pipette, you will also have to redo that Pipette Offset Calibration.",
254256
"tip_length_cal_title": "Tip Length Calibration",
255257
"tip_length_calibration": "tip length calibration",
256258
"total_vol": "total volume",
257259
"update_deck": "Update deck",
260+
"update_deck_config": "Update deck configuration",
258261
"updated": "Updated",
259262
"usb_connected_no_port_info": "USB Port Connected",
260263
"usb_port_connected": "USB Port {{port}}",

app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ import type {
5959
import type { ModalHeaderBaseProps } from '../../molecules/Modal/types'
6060
import type { LegacyModalProps } from '../../molecules/LegacyModal'
6161

62-
// type CutoutContents = Omit<CutoutConfig, 'cutoutId'>
63-
6462
interface AddFixtureModalProps {
6563
cutoutId: CutoutId
6664
setShowAddFixtureModal: (showAddFixtureModal: boolean) => void

app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import {
1717
} from '@opentrons/shared-data'
1818

1919
import { getLabwareSetupItemGroups } from '../../../../pages/Protocols/utils'
20-
import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils'
21-
import { useAttachedModules } from '../../hooks'
2220
import { LabwareInfoOverlay } from '../LabwareInfoOverlay'
2321
import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo'
2422
import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo'
@@ -30,8 +28,6 @@ import type {
3028
ProtocolAnalysisOutput,
3129
} from '@opentrons/shared-data'
3230

33-
const ATTACHED_MODULE_POLL_MS = 5000
34-
3531
interface SetupLabwareMapProps {
3632
runId: string
3733
protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null
@@ -41,11 +37,6 @@ export function SetupLabwareMap({
4137
runId,
4238
protocolAnalysis,
4339
}: SetupLabwareMapProps): JSX.Element | null {
44-
const attachedModules =
45-
useAttachedModules({
46-
refetchInterval: ATTACHED_MODULE_POLL_MS,
47-
}) ?? []
48-
4940
// early return null if no protocol analysis
5041
if (protocolAnalysis == null) return null
5142

@@ -56,16 +47,11 @@ export function SetupLabwareMap({
5647

5748
const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef)
5849

59-
const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches(
60-
attachedModules,
61-
protocolModulesInfo
62-
)
63-
6450
const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter(
6551
commands
6652
)
6753

68-
const modulesOnDeck = attachedProtocolModuleMatches.map(module => {
54+
const modulesOnDeck = protocolModulesInfo.map(module => {
6955
const labwareInAdapterInMod =
7056
module.nestedLabwareId != null
7157
? initialLoadedLabwareByAdapter[module.nestedLabwareId]

app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,18 @@ import {
2121
THERMOCYCLER_MODULE_V1,
2222
} from '@opentrons/shared-data'
2323

24-
import { useAttachedModules } from '../../hooks'
2524
import { LabwareInfoOverlay } from '../LabwareInfoOverlay'
2625
import { LiquidsLabwareDetailsModal } from './LiquidsLabwareDetailsModal'
2726
import { getWellFillFromLabwareId } from './utils'
2827
import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo'
2928
import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList'
30-
import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils'
3129
import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo'
3230

3331
import type {
3432
CompletedProtocolAnalysis,
3533
ProtocolAnalysisOutput,
3634
} from '@opentrons/shared-data'
3735

38-
const ATTACHED_MODULE_POLL_MS = 5000
39-
4036
interface SetupLiquidsMapProps {
4137
runId: string
4238
protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null
@@ -50,10 +46,6 @@ export function SetupLiquidsMap(
5046
const [liquidDetailsLabwareId, setLiquidDetailsLabwareId] = React.useState<
5147
string | null
5248
>(null)
53-
const attachedModules =
54-
useAttachedModules({
55-
refetchInterval: ATTACHED_MODULE_POLL_MS,
56-
}) ?? []
5749

5850
if (protocolAnalysis == null) return null
5951

@@ -75,12 +67,7 @@ export function SetupLiquidsMap(
7567

7668
const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef)
7769

78-
const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches(
79-
attachedModules,
80-
protocolModulesInfo
81-
)
82-
83-
const modulesOnDeck = attachedProtocolModuleMatches.map(module => {
70+
const modulesOnDeck = protocolModulesInfo.map(module => {
8471
const labwareInAdapterInMod =
8572
module.nestedLabwareId != null
8673
? initialLoadedLabwareByAdapter[module.nestedLabwareId]

app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ describe('SetupLiquidsMap', () => {
212212
when(vi.mocked(getAttachedProtocolModuleMatches))
213213
.calledWith(
214214
mockFetchModulesSuccessActionPayloadModules,
215-
mockProtocolModuleInfo
215+
mockProtocolModuleInfo,
216+
[]
216217
)
217218
.thenReturn([
218219
{
@@ -299,7 +300,8 @@ describe('SetupLiquidsMap', () => {
299300
when(vi.mocked(getAttachedProtocolModuleMatches))
300301
.calledWith(
301302
mockFetchModulesSuccessActionPayloadModules,
302-
mockProtocolModuleInfo
303+
mockProtocolModuleInfo,
304+
[]
303305
)
304306
.thenReturn([
305307
{

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

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import * as React from 'react'
22
import { createPortal } from 'react-dom'
33
import { useTranslation } from 'react-i18next'
4+
import { useHistory } from 'react-router-dom'
45
import {
56
useDeckConfigurationQuery,
67
useModulesQuery,
78
} from '@opentrons/react-api-client'
89
import {
910
ALIGN_CENTER,
10-
COLORS,
1111
DIRECTION_COLUMN,
1212
DIRECTION_ROW,
1313
Flex,
14-
Icon,
14+
PrimaryButton,
1515
SPACING,
1616
StyledText,
1717
TYPOGRAPHY,
@@ -20,14 +20,19 @@ import {
2020
getFixtureDisplayName,
2121
getCutoutFixturesForModuleModel,
2222
MAGNETIC_BLOCK_V1,
23+
getModuleDisplayName,
2324
} from '@opentrons/shared-data'
2425
import { getTopPortalEl } from '../../../../App/portal'
2526
import { LegacyModal } from '../../../../molecules/LegacyModal'
2627
import { Modal } from '../../../../molecules/Modal'
28+
import { FixtureOption } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal'
29+
30+
import { SmallButton } from '../../../../atoms/buttons'
31+
import { useCloseCurrentRun } from '../../../ProtocolUpload/hooks'
2732

2833
import type { ModuleModel, DeckDefinition } from '@opentrons/shared-data'
29-
import { FixtureOption } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal'
3034

35+
const EQUIPMENT_POLL_MS = 5000
3136
interface ModuleFixtureOption {
3237
moduleModel: ModuleModel
3338
usbPort?: number
@@ -39,6 +44,8 @@ interface ChooseModuleToConfigureModalProps {
3944
deckDef: DeckDefinition
4045
isOnDevice: boolean
4146
requiredModuleModel: ModuleModel
47+
robotName: string
48+
displaySlotName: string
4249
}
4350

4451
export const ChooseModuleToConfigureModal = (
@@ -50,9 +57,14 @@ export const ChooseModuleToConfigureModal = (
5057
deckDef,
5158
requiredModuleModel,
5259
isOnDevice,
60+
robotName,
61+
displaySlotName,
5362
} = props
5463
const { t } = useTranslation(['protocol_setup', 'shared'])
55-
const attachedModules = useModulesQuery().data?.data ?? []
64+
const history = useHistory()
65+
const { closeCurrentRun } = useCloseCurrentRun()
66+
const attachedModules =
67+
useModulesQuery({ refetchInterval: EQUIPMENT_POLL_MS })?.data?.data ?? []
5668
const deckConfig = useDeckConfigurationQuery()?.data ?? []
5769
const unconfiguredModuleMatches =
5870
attachedModules.filter(
@@ -94,17 +106,52 @@ export const ChooseModuleToConfigureModal = (
94106
)
95107
}
96108
)
109+
const handleCancelRun = (): void => {
110+
closeCurrentRun()
111+
}
112+
const handleNavigateToDeviceDetails = (): void => {
113+
history.push(`/devices/${robotName}`)
114+
}
115+
const emptyState = (
116+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing16}>
117+
<StyledText as="p">
118+
{t('there_are_no_unconfigured_modules', {
119+
module: getModuleDisplayName(requiredModuleModel),
120+
})}
121+
</StyledText>
122+
{isOnDevice ? (
123+
<SmallButton
124+
onClick={handleCancelRun}
125+
buttonText={t('cancel_protocol_and_edit_deck_config')}
126+
/>
127+
) : (
128+
<PrimaryButton onClick={handleNavigateToDeviceDetails}>
129+
{t('update_deck_config')}
130+
</PrimaryButton>
131+
)}
132+
</Flex>
133+
)
134+
135+
const contents =
136+
fixtureOptions.length > 0 ? (
137+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing16}>
138+
<StyledText as="p">{t('add_this_deck_hardware')}</StyledText>
139+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
140+
{fixtureOptions}
141+
</Flex>
142+
</Flex>
143+
) : (
144+
emptyState
145+
)
97146

98147
return createPortal(
99148
isOnDevice ? (
100149
<Modal
101150
onOutsideClick={onCloseClick}
102151
header={{
103-
title: t('deck_conflict'),
152+
title: t('add_to_slot', { slotName: displaySlotName }),
104153
hasExitIcon: true,
105154
onClick: onCloseClick,
106-
iconName: 'ot-alert',
107-
iconColor: COLORS.yellow50,
108155
}}
109156
>
110157
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing32}>
@@ -114,7 +161,7 @@ export const ChooseModuleToConfigureModal = (
114161
paddingTop={SPACING.spacing8}
115162
gridGap={SPACING.spacing8}
116163
>
117-
{fixtureOptions}
164+
{contents}
118165
</Flex>
119166
</Flex>
120167
</Flex>
@@ -127,9 +174,8 @@ export const ChooseModuleToConfigureModal = (
127174
gridGap={SPACING.spacing10}
128175
alignItems={ALIGN_CENTER}
129176
>
130-
<Icon name="ot-alert" size="1rem" color={COLORS.yellow50} />
131177
<StyledText as="h3" fontWeight={TYPOGRAPHY.fontWeightSemiBold}>
132-
{t('deck_conflict')}
178+
{t('add_to_slot', { slotName: displaySlotName })}
133179
</StyledText>
134180
</Flex>
135181
}
@@ -143,7 +189,7 @@ export const ChooseModuleToConfigureModal = (
143189
paddingTop={SPACING.spacing8}
144190
gridGap={SPACING.spacing8}
145191
>
146-
{fixtureOptions}
192+
{contents}
147193
</Flex>
148194
</Flex>
149195
</Flex>

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ interface LocationConflictModalProps {
4848
onCloseClick: () => void
4949
cutoutId: CutoutId
5050
deckDef: DeckDefinition
51+
robotName: string
5152
missingLabwareDisplayName?: string | null
5253
requiredFixtureId?: CutoutFixtureId
5354
requiredModule?: ModuleModel
@@ -60,6 +61,7 @@ export const LocationConflictModal = (
6061
const {
6162
onCloseClick,
6263
cutoutId,
64+
robotName,
6365
missingLabwareDisplayName,
6466
requiredFixtureId,
6567
requiredModule,
@@ -153,18 +155,25 @@ export const LocationConflictModal = (
153155
protocolSpecifiesDisplayName = getModuleDisplayName(requiredModule)
154156
}
155157

156-
if (showModuleSelect && requiredModule) {
158+
const displaySlotName = isThermocycler
159+
? 'A1 + B1'
160+
: getCutoutDisplayName(cutoutId)
161+
162+
if (showModuleSelect && requiredModule != null) {
157163
return createPortal(
158164
<ChooseModuleToConfigureModal
159165
handleConfigureModule={handleConfigureModule}
160166
requiredModuleModel={requiredModule}
161167
onCloseClick={onCloseClick}
162168
isOnDevice={isOnDevice}
163169
deckDef={deckDef}
170+
robotName={robotName}
171+
displaySlotName={displaySlotName}
164172
/>,
165173
getTopPortalEl()
166174
)
167175
}
176+
168177
return createPortal(
169178
isOnDevice ? (
170179
<Modal
@@ -200,11 +209,7 @@ export const LocationConflictModal = (
200209
fontWeight={TYPOGRAPHY.fontWeightBold}
201210
paddingBottom={SPACING.spacing8}
202211
>
203-
{t('slot_location', {
204-
slotName: isThermocycler
205-
? 'A1 + B1'
206-
: getCutoutDisplayName(cutoutId),
207-
})}
212+
{t('slot_location', { slotName: displaySlotName })}
208213
</StyledText>
209214
<Flex
210215
flexDirection={DIRECTION_COLUMN}
@@ -303,11 +308,7 @@ export const LocationConflictModal = (
303308
fontSize={TYPOGRAPHY.fontSizeH4}
304309
fontWeight={TYPOGRAPHY.fontWeightBold}
305310
>
306-
{t('slot_location', {
307-
slotName: isThermocycler
308-
? 'A1 + B1'
309-
: getCutoutDisplayName(cutoutId),
310-
})}
311+
{t('slot_location', { slotName: displaySlotName })}
311312
</StyledText>
312313
<Flex
313314
flexDirection={DIRECTION_COLUMN}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const NotConfiguredModal = (
5757
width="27.75rem"
5858
>
5959
<Flex flexDirection={DIRECTION_COLUMN}>
60-
<StyledText as="p">{t('add_fixture_to_deck')}</StyledText>
60+
<StyledText as="p">{t('add_this_deck_hardware')}</StyledText>
6161
<Flex paddingTop={SPACING.spacing16} flexDirection={DIRECTION_COLUMN}>
6262
<Flex
6363
padding={`${SPACING.spacing8} ${SPACING.spacing16}`}

0 commit comments

Comments
 (0)