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

feat(app): Protocol Details ODD support for lids & lid stacks #17842

Merged
merged 5 commits into from
Mar 20, 2025
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
105 changes: 18 additions & 87 deletions app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,92 +18,23 @@
StyledText,
useMenuHandleClickOutside,
} from '@opentrons/components'
import { getLabwareDefURI } from '@opentrons/shared-data'
import { Divider } from '/app/atoms/structure'
import { getTopPortalEl } from '/app/App/portal'
import { LabwareDetails } from '/app/organisms/Desktop/Labware/LabwareDetails'

import { getRequiredLabwareDetailsFromLoadCommands } from '/app/transformations/commands'
import type { MouseEventHandler } from 'react'
import type {
LoadLabwareRunTimeCommand,
LoadLidStackRunTimeCommand,
LoadLidRunTimeCommand,
LabwareDefinition2,
} from '@opentrons/shared-data'
import type { RunTimeCommand } from '@opentrons/shared-data'
import type { LabwareDefAndDate } from '/app/local-resources/labware'

interface ProtocolLabwareDetailsProps {
loadLabwareCommands: Array<
| LoadLabwareRunTimeCommand
| LoadLidStackRunTimeCommand
| LoadLidRunTimeCommand
> | null
}

export const ProtocolLabwareDetails = (
props: ProtocolLabwareDetailsProps
): JSX.Element => {
const { loadLabwareCommands } = props
export const ProtocolLabwareDetails = (props: {

Check warning on line 29 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L29 was not covered by tests
commands: RunTimeCommand[]
}): JSX.Element => {
const { commands } = props

Check warning on line 32 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx#L31-L32

Added lines #L31 - L32 were not covered by tests
const { t } = useTranslation('protocol_details')

const labwareAndLidDetails =
loadLabwareCommands != null
? [
...loadLabwareCommands
.reduce((acc, command) => {
if (command.result?.definition == null) return acc
else if (command.commandType === 'loadLid') return acc
else if (command.commandType === 'loadLidStack') {
if (!acc.has(getLabwareDefURI(command.result.definition))) {
acc.set(getLabwareDefURI(command.result.definition), {
...command,
quantity: 0,
})
}
acc.get(
getLabwareDefURI(command.result?.definition)
).quantity += command.result?.labwareIds.length
return acc
} else {
let defUri = getLabwareDefURI(command.result?.definition)
const lidCommand = loadLabwareCommands.find(
c =>
c.commandType === 'loadLid' &&
c.params.location !== 'offDeck' &&
c.params.location !== 'systemLocation' &&
'labwareId' in c.params.location &&
c.params.location.labwareId === command.result?.labwareId
)
if (
lidCommand != null &&
lidCommand.result?.definition != null
) {
defUri = `${defUri}_${getLabwareDefURI(
lidCommand.result.definition
)}`

if (!acc.has(defUri)) {
acc.set(defUri, {
...command,
quantity: 0,
lid: lidCommand.result.definition,
})
}
} else {
if (!acc.has(defUri)) {
acc.set(defUri, {
...command,
quantity: 0,
})
}
}
acc.get(defUri).quantity++
return acc
}
}, new Map())
.values(),
]
: []
const labwareAndLidDetails = getRequiredLabwareDetailsFromLoadCommands(
commands
)

Check warning on line 37 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx#L35-L37

Added lines #L35 - L37 were not covered by tests

return (
<>
Expand All @@ -130,11 +61,11 @@
{labwareAndLidDetails?.map((labware, index) => (
<ProtocolLabwareDetailItem
key={index}
namespace={labware.params.namespace}
displayName={labware.result?.definition?.metadata?.displayName}
namespace={labware.labwareDef.namespace}
displayName={labware.labwareDef.metadata.displayName}

Check warning on line 65 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx#L64-L65

Added lines #L64 - L65 were not covered by tests
quantity={labware.quantity}
labware={{ definition: labware.result?.definition }}
lid={labware.lid}
labware={{ definition: labware.labwareDef }}
lidDisplayName={labware.lidDisplayName}

Check warning on line 68 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx#L67-L68

Added lines #L67 - L68 were not covered by tests
data-testid={`ProtocolLabwareDetails_item_${index}`}
/>
))}
Expand All @@ -149,16 +80,16 @@
interface ProtocolLabwareDetailItemProps {
namespace: string
displayName: string
quantity: string
lid?: LabwareDefinition2
quantity: number
lidDisplayName?: string
labware: LabwareDefAndDate
}

export const ProtocolLabwareDetailItem = (
props: ProtocolLabwareDetailItemProps
): JSX.Element => {
const { t } = useTranslation('protocol_details')
const { namespace, displayName, quantity, labware, lid } = props
const { namespace, displayName, quantity, labware, lidDisplayName } = props

Check warning on line 92 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L92 was not covered by tests
return (
<>
<Divider width="100%" />
Expand Down Expand Up @@ -192,13 +123,13 @@
>
{displayName}
</StyledText>
{lid != null ? (
{lidDisplayName != null ? (

Check warning on line 126 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L126 was not covered by tests
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
paddingRight={SPACING.spacing32}
>
{t('with_lid_name', { lid: lid.metadata.displayName })}
{t('with_lid_name', { lid: lidDisplayName })}

Check warning on line 132 in app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L132 was not covered by tests
</StyledText>
) : null}
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('ProtocolLabwareDetails', () => {
let props: ComponentProps<typeof ProtocolLabwareDetails>
beforeEach(() => {
props = {
loadLabwareCommands: mockRequiredLabwareDetails,
commands: mockRequiredLabwareDetails,
}
})

Expand Down Expand Up @@ -148,7 +148,7 @@ describe('ProtocolLabwareDetails', () => {

it('should render mock infoscreen when no labware', () => {
props = {
loadLabwareCommands: [],
commands: [],
}
render(props)
screen.getByText('mock InfoScreen')
Expand Down
25 changes: 2 additions & 23 deletions app/src/organisms/Desktop/ProtocolDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,7 @@
import { ProtocolParameters } from './ProtocolParameters'
import { AnnotatedSteps } from './AnnotatedSteps'

import type {
JsonConfig,
PythonConfig,
LoadLabwareRunTimeCommand,
LoadLidRunTimeCommand,
LoadLidStackRunTimeCommand,
} from '@opentrons/shared-data'
import type { JsonConfig, PythonConfig } from '@opentrons/shared-data'
import type {
GroupedCommands,
StoredProtocolData,
Expand Down Expand Up @@ -279,21 +273,6 @@
: null
)

const loadLabwareCommands =
mostRecentAnalysis?.commands.filter(
(
command
): command is
| LoadLabwareRunTimeCommand
| LoadLidRunTimeCommand
| LoadLidStackRunTimeCommand =>
['loadLabware', 'loadLid', 'loadLidStack'].includes(
command.commandType
) &&
command.result?.definition != null &&
command.result?.definition.parameters.format !== 'trash'
) ?? []

const protocolDisplayName = getProtocolDisplayName(
protocolKey,
srcFileNames,
Expand Down Expand Up @@ -341,7 +320,7 @@

const contentsByTabName = {
labware: (
<ProtocolLabwareDetails loadLabwareCommands={loadLabwareCommands} />
<ProtocolLabwareDetails commands={mostRecentAnalysis?.commands ?? []} />

Check warning on line 323 in app/src/organisms/Desktop/ProtocolDetails/index.tsx

View check run for this annotation

Codecov / codecov/patch

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

Added line #L323 was not covered by tests
),
robot_config: (
<RobotConfigurationDetails
Expand Down
90 changes: 0 additions & 90 deletions app/src/pages/ODD/ProtocolDetails/Deck.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { useState } from 'react'
import last from 'lodash/last'

import { Flex, ProtocolDeck, SPACING } from '@opentrons/components'
import {
useProtocolAnalysisAsDocumentQuery,
useProtocolQuery,
} from '@opentrons/react-api-client'
import { getLabwareDefURI } from '@opentrons/shared-data'

import { LabwareStackModal } from '/app/molecules/LabwareStackModal'
import { SingleLabwareModal } from '/app/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal'
import { getLabwareSetupItemGroups } from '/app/transformations/commands'

import type {
LabwareDefinition2,
LabwareLocation,
} from '@opentrons/shared-data'

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

const [
showLabwareDetailsModal,
setShowLabwareDetailsModal,
] = useState<boolean>(false)
const [selectedLabware, setSelectedLabware] = useState<
| (LabwareDefinition2 & {
location: LabwareLocation
nickName: string | null
id: string
})
| null
>(null)

const { onDeckItems } = getLabwareSetupItemGroups(
mostRecentAnalysis?.commands ?? []
)

const handleLabwareClick = (
labwareDef: LabwareDefinition2,
labwareId: string
): void => {
const foundLabware = mostRecentAnalysis?.labware.find(
labware => labware.id === labwareId
)
if (foundLabware != null) {
const location = onDeckItems.find(
item => item.labwareId === foundLabware.id
)?.initialLocation
const nickName = onDeckItems.find(
item => getLabwareDefURI(item.definition) === foundLabware.definitionUri
)?.nickName
if (location != null) {
setSelectedLabware({
...labwareDef,
location: location,
nickName: nickName ?? null,
id: labwareId,
})
setShowLabwareDetailsModal(true)
} else {
console.warn('no initial labware location found')
}
}
}

const selectedLabwareIsTopOfStack = mostRecentAnalysis?.commands.some(
command =>
command.commandType === 'loadLabware' &&
command.result?.labwareId === selectedLabware?.id &&
typeof command.params.location === 'object' &&
('moduleId' in command.params.location ||
'labwareId' in command.params.location)
)

return (
<>
{showLabwareDetailsModal &&
!selectedLabwareIsTopOfStack &&
selectedLabware != null ? (
<SingleLabwareModal
selectedLabware={selectedLabware}
onOutsideClick={() => {
setShowLabwareDetailsModal(false)
setSelectedLabware(null)
}}
mostRecentAnalysis={mostRecentAnalysis ?? null}
/>
) : null}
{showLabwareDetailsModal &&
selectedLabware != null &&
selectedLabwareIsTopOfStack ? (
<LabwareStackModal
labwareIdTop={selectedLabware?.id}
commands={mostRecentAnalysis?.commands ?? null}
closeModal={() => {
setSelectedLabware(null)
setShowLabwareDetailsModal(false)
}}
/>
) : null}
<Flex height="26.9375rem" paddingY={SPACING.spacing24}>
{mostRecentAnalysis != null ? (
<ProtocolDeck
protocolAnalysis={mostRecentAnalysis}
handleLabwareClick={handleLabwareClick}
baseDeckProps={{ showSlotLabels: true }}
/>
) : null}
Expand Down
Loading
Loading