Skip to content

Commit

Permalink
feat(app): liquid updates in protocol details and setup for odd & des…
Browse files Browse the repository at this point in the history
  • Loading branch information
smb2268 authored Aug 5, 2024
1 parent f93ac41 commit 4500e21
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 128 deletions.
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

0 comments on commit 4500e21

Please sign in to comment.