Skip to content

Commit fa3180d

Browse files
authored
feat(app): Protocol Details ODD support for lids & lid stacks (#17842)
1 parent 49662eb commit fa3180d

File tree

14 files changed

+268
-481
lines changed

14 files changed

+268
-481
lines changed

app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

+18-87
Original file line numberDiff line numberDiff line change
@@ -18,92 +18,23 @@ import {
1818
StyledText,
1919
useMenuHandleClickOutside,
2020
} from '@opentrons/components'
21-
import { getLabwareDefURI } from '@opentrons/shared-data'
2221
import { Divider } from '/app/atoms/structure'
2322
import { getTopPortalEl } from '/app/App/portal'
2423
import { LabwareDetails } from '/app/organisms/Desktop/Labware/LabwareDetails'
25-
24+
import { getRequiredLabwareDetailsFromLoadCommands } from '/app/transformations/commands'
2625
import type { MouseEventHandler } from 'react'
27-
import type {
28-
LoadLabwareRunTimeCommand,
29-
LoadLidStackRunTimeCommand,
30-
LoadLidRunTimeCommand,
31-
LabwareDefinition2,
32-
} from '@opentrons/shared-data'
26+
import type { RunTimeCommand } from '@opentrons/shared-data'
3327
import type { LabwareDefAndDate } from '/app/local-resources/labware'
3428

35-
interface ProtocolLabwareDetailsProps {
36-
loadLabwareCommands: Array<
37-
| LoadLabwareRunTimeCommand
38-
| LoadLidStackRunTimeCommand
39-
| LoadLidRunTimeCommand
40-
> | null
41-
}
42-
43-
export const ProtocolLabwareDetails = (
44-
props: ProtocolLabwareDetailsProps
45-
): JSX.Element => {
46-
const { loadLabwareCommands } = props
29+
export const ProtocolLabwareDetails = (props: {
30+
commands: RunTimeCommand[]
31+
}): JSX.Element => {
32+
const { commands } = props
4733
const { t } = useTranslation('protocol_details')
4834

49-
const labwareAndLidDetails =
50-
loadLabwareCommands != null
51-
? [
52-
...loadLabwareCommands
53-
.reduce((acc, command) => {
54-
if (command.result?.definition == null) return acc
55-
else if (command.commandType === 'loadLid') return acc
56-
else if (command.commandType === 'loadLidStack') {
57-
if (!acc.has(getLabwareDefURI(command.result.definition))) {
58-
acc.set(getLabwareDefURI(command.result.definition), {
59-
...command,
60-
quantity: 0,
61-
})
62-
}
63-
acc.get(
64-
getLabwareDefURI(command.result?.definition)
65-
).quantity += command.result?.labwareIds.length
66-
return acc
67-
} else {
68-
let defUri = getLabwareDefURI(command.result?.definition)
69-
const lidCommand = loadLabwareCommands.find(
70-
c =>
71-
c.commandType === 'loadLid' &&
72-
c.params.location !== 'offDeck' &&
73-
c.params.location !== 'systemLocation' &&
74-
'labwareId' in c.params.location &&
75-
c.params.location.labwareId === command.result?.labwareId
76-
)
77-
if (
78-
lidCommand != null &&
79-
lidCommand.result?.definition != null
80-
) {
81-
defUri = `${defUri}_${getLabwareDefURI(
82-
lidCommand.result.definition
83-
)}`
84-
85-
if (!acc.has(defUri)) {
86-
acc.set(defUri, {
87-
...command,
88-
quantity: 0,
89-
lid: lidCommand.result.definition,
90-
})
91-
}
92-
} else {
93-
if (!acc.has(defUri)) {
94-
acc.set(defUri, {
95-
...command,
96-
quantity: 0,
97-
})
98-
}
99-
}
100-
acc.get(defUri).quantity++
101-
return acc
102-
}
103-
}, new Map())
104-
.values(),
105-
]
106-
: []
35+
const labwareAndLidDetails = getRequiredLabwareDetailsFromLoadCommands(
36+
commands
37+
)
10738

10839
return (
10940
<>
@@ -130,11 +61,11 @@ export const ProtocolLabwareDetails = (
13061
{labwareAndLidDetails?.map((labware, index) => (
13162
<ProtocolLabwareDetailItem
13263
key={index}
133-
namespace={labware.params.namespace}
134-
displayName={labware.result?.definition?.metadata?.displayName}
64+
namespace={labware.labwareDef.namespace}
65+
displayName={labware.labwareDef.metadata.displayName}
13566
quantity={labware.quantity}
136-
labware={{ definition: labware.result?.definition }}
137-
lid={labware.lid}
67+
labware={{ definition: labware.labwareDef }}
68+
lidDisplayName={labware.lidDisplayName}
13869
data-testid={`ProtocolLabwareDetails_item_${index}`}
13970
/>
14071
))}
@@ -149,16 +80,16 @@ export const ProtocolLabwareDetails = (
14980
interface ProtocolLabwareDetailItemProps {
15081
namespace: string
15182
displayName: string
152-
quantity: string
153-
lid?: LabwareDefinition2
83+
quantity: number
84+
lidDisplayName?: string
15485
labware: LabwareDefAndDate
15586
}
15687

15788
export const ProtocolLabwareDetailItem = (
15889
props: ProtocolLabwareDetailItemProps
15990
): JSX.Element => {
16091
const { t } = useTranslation('protocol_details')
161-
const { namespace, displayName, quantity, labware, lid } = props
92+
const { namespace, displayName, quantity, labware, lidDisplayName } = props
16293
return (
16394
<>
16495
<Divider width="100%" />
@@ -192,13 +123,13 @@ export const ProtocolLabwareDetailItem = (
192123
>
193124
{displayName}
194125
</StyledText>
195-
{lid != null ? (
126+
{lidDisplayName != null ? (
196127
<StyledText
197128
desktopStyle="bodyDefaultRegular"
198129
color={COLORS.grey60}
199130
paddingRight={SPACING.spacing32}
200131
>
201-
{t('with_lid_name', { lid: lid.metadata.displayName })}
132+
{t('with_lid_name', { lid: lidDisplayName })}
202133
</StyledText>
203134
) : null}
204135
</Flex>

app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('ProtocolLabwareDetails', () => {
7777
let props: ComponentProps<typeof ProtocolLabwareDetails>
7878
beforeEach(() => {
7979
props = {
80-
loadLabwareCommands: mockRequiredLabwareDetails,
80+
commands: mockRequiredLabwareDetails,
8181
}
8282
})
8383

@@ -148,7 +148,7 @@ describe('ProtocolLabwareDetails', () => {
148148

149149
it('should render mock infoscreen when no labware', () => {
150150
props = {
151-
loadLabwareCommands: [],
151+
commands: [],
152152
}
153153
render(props)
154154
screen.getByText('mock InfoScreen')

app/src/organisms/Desktop/ProtocolDetails/index.tsx

+2-23
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,7 @@ import { RobotConfigurationDetails } from './RobotConfigurationDetails'
7272
import { ProtocolParameters } from './ProtocolParameters'
7373
import { AnnotatedSteps } from './AnnotatedSteps'
7474

75-
import type {
76-
JsonConfig,
77-
PythonConfig,
78-
LoadLabwareRunTimeCommand,
79-
LoadLidRunTimeCommand,
80-
LoadLidStackRunTimeCommand,
81-
} from '@opentrons/shared-data'
75+
import type { JsonConfig, PythonConfig } from '@opentrons/shared-data'
8276
import type {
8377
GroupedCommands,
8478
StoredProtocolData,
@@ -279,21 +273,6 @@ export function ProtocolDetails(
279273
: null
280274
)
281275

282-
const loadLabwareCommands =
283-
mostRecentAnalysis?.commands.filter(
284-
(
285-
command
286-
): command is
287-
| LoadLabwareRunTimeCommand
288-
| LoadLidRunTimeCommand
289-
| LoadLidStackRunTimeCommand =>
290-
['loadLabware', 'loadLid', 'loadLidStack'].includes(
291-
command.commandType
292-
) &&
293-
command.result?.definition != null &&
294-
command.result?.definition.parameters.format !== 'trash'
295-
) ?? []
296-
297276
const protocolDisplayName = getProtocolDisplayName(
298277
protocolKey,
299278
srcFileNames,
@@ -341,7 +320,7 @@ export function ProtocolDetails(
341320

342321
const contentsByTabName = {
343322
labware: (
344-
<ProtocolLabwareDetails loadLabwareCommands={loadLabwareCommands} />
323+
<ProtocolLabwareDetails commands={mostRecentAnalysis?.commands ?? []} />
345324
),
346325
robot_config: (
347326
<RobotConfigurationDetails

app/src/pages/ODD/ProtocolDetails/Deck.tsx

-90
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
import { useState } from 'react'
21
import last from 'lodash/last'
32

43
import { Flex, ProtocolDeck, SPACING } from '@opentrons/components'
54
import {
65
useProtocolAnalysisAsDocumentQuery,
76
useProtocolQuery,
87
} from '@opentrons/react-api-client'
9-
import { getLabwareDefURI } from '@opentrons/shared-data'
10-
11-
import { LabwareStackModal } from '/app/molecules/LabwareStackModal'
12-
import { SingleLabwareModal } from '/app/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal'
13-
import { getLabwareSetupItemGroups } from '/app/transformations/commands'
14-
15-
import type {
16-
LabwareDefinition2,
17-
LabwareLocation,
18-
} from '@opentrons/shared-data'
198

209
export const Deck = (props: { protocolId: string }): JSX.Element => {
2110
const { data: protocolData } = useProtocolQuery(props.protocolId)
@@ -27,91 +16,12 @@ export const Deck = (props: { protocolId: string }): JSX.Element => {
2716
{ enabled: protocolData != null }
2817
)
2918

30-
const [
31-
showLabwareDetailsModal,
32-
setShowLabwareDetailsModal,
33-
] = useState<boolean>(false)
34-
const [selectedLabware, setSelectedLabware] = useState<
35-
| (LabwareDefinition2 & {
36-
location: LabwareLocation
37-
nickName: string | null
38-
id: string
39-
})
40-
| null
41-
>(null)
42-
43-
const { onDeckItems } = getLabwareSetupItemGroups(
44-
mostRecentAnalysis?.commands ?? []
45-
)
46-
47-
const handleLabwareClick = (
48-
labwareDef: LabwareDefinition2,
49-
labwareId: string
50-
): void => {
51-
const foundLabware = mostRecentAnalysis?.labware.find(
52-
labware => labware.id === labwareId
53-
)
54-
if (foundLabware != null) {
55-
const location = onDeckItems.find(
56-
item => item.labwareId === foundLabware.id
57-
)?.initialLocation
58-
const nickName = onDeckItems.find(
59-
item => getLabwareDefURI(item.definition) === foundLabware.definitionUri
60-
)?.nickName
61-
if (location != null) {
62-
setSelectedLabware({
63-
...labwareDef,
64-
location: location,
65-
nickName: nickName ?? null,
66-
id: labwareId,
67-
})
68-
setShowLabwareDetailsModal(true)
69-
} else {
70-
console.warn('no initial labware location found')
71-
}
72-
}
73-
}
74-
75-
const selectedLabwareIsTopOfStack = mostRecentAnalysis?.commands.some(
76-
command =>
77-
command.commandType === 'loadLabware' &&
78-
command.result?.labwareId === selectedLabware?.id &&
79-
typeof command.params.location === 'object' &&
80-
('moduleId' in command.params.location ||
81-
'labwareId' in command.params.location)
82-
)
83-
8419
return (
8520
<>
86-
{showLabwareDetailsModal &&
87-
!selectedLabwareIsTopOfStack &&
88-
selectedLabware != null ? (
89-
<SingleLabwareModal
90-
selectedLabware={selectedLabware}
91-
onOutsideClick={() => {
92-
setShowLabwareDetailsModal(false)
93-
setSelectedLabware(null)
94-
}}
95-
mostRecentAnalysis={mostRecentAnalysis ?? null}
96-
/>
97-
) : null}
98-
{showLabwareDetailsModal &&
99-
selectedLabware != null &&
100-
selectedLabwareIsTopOfStack ? (
101-
<LabwareStackModal
102-
labwareIdTop={selectedLabware?.id}
103-
commands={mostRecentAnalysis?.commands ?? null}
104-
closeModal={() => {
105-
setSelectedLabware(null)
106-
setShowLabwareDetailsModal(false)
107-
}}
108-
/>
109-
) : null}
11021
<Flex height="26.9375rem" paddingY={SPACING.spacing24}>
11122
{mostRecentAnalysis != null ? (
11223
<ProtocolDeck
11324
protocolAnalysis={mostRecentAnalysis}
114-
handleLabwareClick={handleLabwareClick}
11525
baseDeckProps={{ showSlotLabels: true }}
11626
/>
11727
) : null}

0 commit comments

Comments
 (0)