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): liquid updates in protocol details and setup for odd & desktop #15882

Merged
merged 7 commits into from
Aug 5, 2024
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
5 changes: 5 additions & 0 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"heater_shaker_extra_attention": "Use latch controls for easy placement of labware.",
"heater_shaker_labware_list_view": "To add labware, use the toggle to control the latch",
"how_offset_data_works": "How labware offsets work",
"individiual_well_volume": "Individual well volume",
"initial_liquids_num_plural": "{{count}} initial liquids",
"initial_liquids_num": "{{count}} initial liquid",
"initial_location": "Initial Location",
Expand Down Expand Up @@ -115,6 +116,8 @@
"learn_more_about_offset_data": "Learn more about Labware Offset Data",
"learn_more_about_robot_cal_link": "Learn more about robot calibration",
"learn_more": "Learn more",
"liquid_information": "Liquid information",
"liquid_name": "Liquid name",
"liquid_setup_step_description": "View liquid starting locations and volumes",
"liquid_setup_step_title": "Liquids",
"liquids_not_in_setup": "No liquids used in this protocol",
Expand Down Expand Up @@ -265,6 +268,7 @@
"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.",
"tip_length_cal_title": "Tip Length Calibration",
"tip_length_calibration": "tip length calibration",
"total_liquid_volume": "Total volume",
"update_deck_config": "Update deck configuration",
"update_deck": "Update deck",
"updated": "Updated",
Expand All @@ -276,6 +280,7 @@
"value_out_of_range": "Value must be between {{min}}-{{max}}",
"value": "Value",
"values_are_view_only": "Values are view-only",
"variable_well_amount": "Variable well amount",
"view_current_offsets": "View current offsets",
"view_moam": "View setup instructions for placing modules of the same type to the robot.",
"view_setup_instructions": "View setup instructions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import {
ALIGN_CENTER,
BORDERS,
Box,
DeckInfoLabel,
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
Flex,
JUSTIFY_CENTER,
JUSTIFY_FLEX_START,
JUSTIFY_SPACE_BETWEEN,
LiquidIcon,
SIZE_AUTO,
SPACING,
LegacyStyledText,
TYPOGRAPHY,
StyledText,
} from '@opentrons/components'
import { getModuleDisplayName, MICRO_LITERS } from '@opentrons/shared-data'
Expand All @@ -29,18 +29,17 @@ import {
ANALYTICS_EXPAND_LIQUID_SETUP_ROW,
ANALYTICS_OPEN_LIQUID_LABWARE_DETAIL_MODAL,
} from '../../../../redux/analytics'
import { useIsFlex } from '../../hooks'
import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import { getLocationInfoNames } from '../utils/getLocationInfoNames'
import { LiquidsLabwareDetailsModal } from './LiquidsLabwareDetailsModal'
import {
getTotalVolumePerLiquidId,
getTotalVolumePerLiquidLabwarePair,
} from './utils'
import { getTotalVolumePerLiquidId, getVolumePerWell } from './utils'

import type { LabwareByLiquidId } from '@opentrons/api-client'

interface SetupLiquidsListProps {
runId: string
robotName: string
}

const HIDE_SCROLLBAR = css`
Expand All @@ -60,8 +59,10 @@ export const CARD_OUTLINE_BORDER_STYLE = css`
`

export function SetupLiquidsList(props: SetupLiquidsListProps): JSX.Element {
const { runId } = props
const { runId, robotName } = props
const protocolData = useMostRecentCompletedAnalysis(runId)
const { t } = useTranslation('protocol_setup')
const isFlex = useIsFlex(robotName)

const liquidsInLoadOrder = parseLiquidsInLoadOrder(
protocolData?.liquids ?? [],
Expand All @@ -77,6 +78,29 @@ export function SetupLiquidsList(props: SetupLiquidsListProps): JSX.Element {
data-testid="SetupLiquidsList_ListView"
gridGap={SPACING.spacing8}
>
<Flex
flexDirection={DIRECTION_ROW}
justifyContent={JUSTIFY_SPACE_BETWEEN}
gridGap={SPACING.spacing16}
marginTop={SPACING.spacing16}
marginBottom={SPACING.spacing8}
>
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
marginLeft={SPACING.spacing16}
>
{t('liquid_information')}
</StyledText>
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
marginLeft="auto"
marginRight={SPACING.spacing16}
>
{t('total_liquid_volume')}
</StyledText>
</Flex>
{liquidsInLoadOrder?.map(liquid => (
<LiquidsListItem
key={liquid.id}
Expand All @@ -85,6 +109,7 @@ export function SetupLiquidsList(props: SetupLiquidsListProps): JSX.Element {
displayColor={liquid.displayColor}
displayName={liquid.displayName}
runId={props.runId}
isFlex={isFlex}
/>
))}
</Flex>
Expand All @@ -97,10 +122,18 @@ interface LiquidsListItemProps {
displayColor: string
displayName: string
runId: string
isFlex: boolean
}

export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element {
const { liquidId, description, displayColor, displayName, runId } = props
const {
liquidId,
description,
displayColor,
displayName,
runId,
isFlex,
} = props
const { t } = useTranslation('protocol_setup')
const [openItem, setOpenItem] = React.useState(false)
const [liquidDetailsLabwareId, setLiquidDetailsLabwareId] = React.useState<
Expand Down Expand Up @@ -164,30 +197,30 @@ export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element {
marginTop={SPACING.spacing16}
marginBottom={SPACING.spacing8}
>
<LegacyStyledText
as="label"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
marginLeft={SPACING.spacing16}
width="8.125rem"
>
{t('location')}
</LegacyStyledText>
<LegacyStyledText
as="label"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
</StyledText>
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
marginRight={SPACING.spacing32}
>
{t('labware_name')}
</LegacyStyledText>
<LegacyStyledText
as="label"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
width="4.25rem"
</StyledText>
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey60}
width="9rem"
marginLeft="auto"
marginRight={SPACING.spacing16}
>
{t('volume')}
</LegacyStyledText>
{t('individiual_well_volume')}
</StyledText>
</Flex>
{labwareByLiquidId[liquidId].map((labware, index) => {
const {
Expand Down Expand Up @@ -219,27 +252,22 @@ export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element {
justifyContent={JUSTIFY_FLEX_START}
gridGap={SPACING.spacing16}
>
<Flex>
<LegacyStyledText
as="p"
fontWeight={TYPOGRAPHY.fontWeightRegular}
minWidth="8.125rem"
alignSelf={ALIGN_CENTER}
>
{slotName}
</LegacyStyledText>
<Flex minWidth="8.125rem" alignSelf={ALIGN_CENTER}>
{isFlex ? (
<DeckInfoLabel deckLabel={slotName} />
) : (
<StyledText desktopStyle="bodyDefaultRegular">
{slotName}
</StyledText>
)}
</Flex>
<Flex flexDirection={DIRECTION_COLUMN}>
<LegacyStyledText
as="p"
fontWeight={TYPOGRAPHY.fontWeightRegular}
>
<StyledText desktopStyle="bodyDefaultRegular">
{labwareName}
</LegacyStyledText>
</StyledText>
{adapterName != null ? (
<LegacyStyledText
as="p"
fontWeight={TYPOGRAPHY.fontWeightRegular}
<StyledText
desktopStyle="bodyDefaultRegular"
color={COLORS.grey50}
>
{moduleModel != null
Expand All @@ -250,23 +278,27 @@ export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element {
: t('on_adapter', {
adapterName: adapterName,
})}
</LegacyStyledText>
</StyledText>
) : null}
</Flex>
<LegacyStyledText
as="p"
fontWeight={TYPOGRAPHY.fontWeightRegular}
minWidth="4.25rem"
<StyledText
desktopStyle="bodyDefaultRegular"
minWidth="8.75rem"
marginLeft={SPACING.spacingAuto}
alignSelf={ALIGN_CENTER}
>
{getTotalVolumePerLiquidLabwarePair(
{getVolumePerWell(
liquidId,
labware.labwareId,
labwareByLiquidId
).toFixed(1)}{' '}
{MICRO_LITERS}
</LegacyStyledText>
) == null
? t('variable_well_amount')
: `${getVolumePerWell(
liquidId,
labware.labwareId,
labwareByLiquidId
)} ${MICRO_LITERS}`}
</StyledText>
</Flex>
</Box>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import {
ANALYTICS_EXPAND_LIQUID_SETUP_ROW,
ANALYTICS_OPEN_LIQUID_LABWARE_DETAIL_MODAL,
} from '../../../../../redux/analytics'
import { useIsFlex } from '../../../hooks'
import { getLocationInfoNames } from '../../utils/getLocationInfoNames'
import { SetupLiquidsList } from '../SetupLiquidsList'
import {
getTotalVolumePerLiquidId,
getTotalVolumePerLiquidLabwarePair,
} from '../utils'
import { getTotalVolumePerLiquidId, getVolumePerWell } from '../utils'
import { LiquidsLabwareDetailsModal } from '../LiquidsLabwareDetailsModal'
import { useNotifyRunQuery } from '../../../../../resources/runs'

Expand Down Expand Up @@ -58,6 +56,7 @@ const MOCK_LABWARE_INFO_BY_LIQUID_ID = {

vi.mock('../utils')
vi.mock('../../utils/getLocationInfoNames')
vi.mock('../../../hooks')
vi.mock('../LiquidsLabwareDetailsModal')
vi.mock('@opentrons/api-client')
vi.mock('../../../../../redux/analytics')
Expand All @@ -73,9 +72,10 @@ let mockTrackEvent: Mock
describe('SetupLiquidsList', () => {
let props: React.ComponentProps<typeof SetupLiquidsList>
beforeEach(() => {
props = { runId: '123' }
props = { runId: '123', robotName: 'test_flex' }
vi.mocked(getTotalVolumePerLiquidId).mockReturnValue(400)
vi.mocked(getTotalVolumePerLiquidLabwarePair).mockReturnValue(200)
vi.mocked(useIsFlex).mockReturnValue(false)
vi.mocked(getVolumePerWell).mockReturnValue(200)
vi.mocked(getLocationInfoNames).mockReturnValue({
labwareName: 'mock labware name',
slotName: '4',
Expand All @@ -98,6 +98,11 @@ describe('SetupLiquidsList', () => {
vi.mocked(useNotifyRunQuery).mockReturnValue({} as any)
})

it('renders the table headers', () => {
render(props)
screen.getByText('Liquid information')
screen.getByText('Total volume')
})
it('renders the total volume of the liquid, sample display name, and description', () => {
render(props)
screen.getAllByText(nestedTextMatcher('400.0 µL'))
Expand All @@ -117,8 +122,8 @@ describe('SetupLiquidsList', () => {
})
screen.getByText('Location')
screen.getByText('Labware name')
screen.getByText('Volume')
screen.getAllByText(nestedTextMatcher('200.0 µL'))
screen.getByText('Individual well volume')
screen.getByText('200 µL')
screen.getByText('4')
screen.getByText('mock labware name')
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function SetupLiquids({
>
{toggleGroup}
{selectedValue === t('list_view') ? (
<SetupLiquidsList runId={runId} />
<SetupLiquidsList runId={runId} robotName={robotName} />
) : (
<SetupLiquidsMap runId={runId} protocolAnalysis={protocolAnalysis} />
)}
Expand Down
16 changes: 16 additions & 0 deletions app/src/organisms/Devices/ProtocolRun/SetupLiquids/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ export function getTotalVolumePerLiquidId(
return parseFloat(totalVolume.toFixed(1))
}

export function getVolumePerWell(
liquidId: string,
labwareId: string,
labwareByLiquidId: LabwareByLiquidId
): number | null {
const labwareInfo = labwareByLiquidId[liquidId]
const volumes = labwareInfo
.filter(labware => labware.labwareId === labwareId)
.flatMap(labware => Object.values(labware.volumeByWell))
if (new Set(volumes).size === 1) {
return parseFloat(volumes[0].toFixed(1))
} else {
return null
}
}

export function getTotalVolumePerLiquidLabwarePair(
liquidId: string,
labwareId: string,
Expand Down
Loading
Loading