Skip to content

Commit

Permalink
feat(app): never hide module/liquid setup (#13134)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader authored Jul 20, 2023
1 parent a211d08 commit 8efc586
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 96 deletions.
2 changes: 2 additions & 0 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"liquid_setup_step_description": "View liquid starting locations and volumes",
"liquid_setup_step_title": "Initial Liquid Setup",
"liquids_not_in_setup": "Liquids not in setup",
"liquids_not_in_the_protocol": "no liquids are specified for this protocol.",
"liquids": "Liquids",
"list_view": "List View",
"loading_data": "Loading data...",
Expand Down Expand Up @@ -122,6 +123,7 @@
"no_data": "no data",
"no_labware_offset_data": "No Labware Offset Data yet",
"no_modules_used_in_this_protocol": "No modules used in this protocol",
"no_modules_specified": "no modules are specified for this protocol.",
"no_tiprack_loaded": "Protocol must load a tip rack",
"no_tiprack_used": "Protocol must pick up a tip",
"no_usb_connection_required": "No USB connection required",
Expand Down
30 changes: 30 additions & 0 deletions app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react'
import {
Flex,
DIRECTION_COLUMN,
COLORS,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import { StyledText } from '../../../atoms/text'

interface EmptySetupStepProps {
title: React.ReactNode
description: string
label: string
}

export function EmptySetupStep(props: EmptySetupStepProps): JSX.Element {
const { title, description, label } = props
return (
<Flex flexDirection={DIRECTION_COLUMN} color={COLORS.successDisabled}>
<StyledText css={TYPOGRAPHY.h6SemiBold} marginBottom={SPACING.spacing2}>
{label}
</StyledText>
<StyledText css={TYPOGRAPHY.h3SemiBold} marginBottom={SPACING.spacing4}>
{title}
</StyledText>
<StyledText as="p">{description}</StyledText>
</Flex>
)
}
82 changes: 41 additions & 41 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { SetupRobotCalibration } from './SetupRobotCalibration'
import { SetupModules } from './SetupModules'
import { SetupStep } from './SetupStep'
import { SetupLiquids } from './SetupLiquids'
import { EmptySetupStep } from './EmptySetupStep'

const ROBOT_CALIBRATION_STEP_KEY = 'robot_calibration_step' as const
const MODULE_SETUP_KEY = 'module_setup_step' as const
const LPC_KEY = 'labware_position_check_step' as const
Expand All @@ -52,7 +54,7 @@ export function ProtocolRunSetup({
robotName,
runId,
}: ProtocolRunSetupProps): JSX.Element | null {
const { t } = useTranslation('protocol_setup')
const { t, i18n } = useTranslation('protocol_setup')
const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId)
const storedProtocolAnalysis = useStoredProtocolAnalysis(runId)
const protocolData = robotProtocolAnalysis ?? storedProtocolAnalysis
Expand All @@ -73,37 +75,22 @@ export function ProtocolRunSetup({

React.useEffect(() => {
let nextStepKeysInOrder = stepsKeysInOrder
const showModuleSetup = protocolData != null && modules.length > 0
const showLiquidSetup =
protocolData != null && protocolData.liquids?.length > 0

if (showModuleSetup && showLiquidSetup) {
if (protocolData != null) {
nextStepKeysInOrder = [
ROBOT_CALIBRATION_STEP_KEY,
MODULE_SETUP_KEY,
LPC_KEY,
LABWARE_SETUP_KEY,
LIQUID_SETUP_KEY,
]
} else if (showModuleSetup) {
nextStepKeysInOrder = [
ROBOT_CALIBRATION_STEP_KEY,
MODULE_SETUP_KEY,
LPC_KEY,
LABWARE_SETUP_KEY,
]
} else if (showLiquidSetup) {
nextStepKeysInOrder = [
ROBOT_CALIBRATION_STEP_KEY,
LPC_KEY,
LABWARE_SETUP_KEY,
LIQUID_SETUP_KEY,
]
}
setStepKeysInOrder(nextStepKeysInOrder)
}, [Boolean(protocolData), protocolData?.commands])

if (robot == null) return null
const hasLiquids = protocolData != null && protocolData.liquids?.length > 0
const hasModules = protocolData != null && modules.length > 0

const StepDetailMap: Record<
StepKey,
Expand Down Expand Up @@ -138,9 +125,11 @@ export function ProtocolRunSetup({
runId={runId}
/>
),
description: t(`${MODULE_SETUP_KEY}_description`, {
count: modules.length,
}),
description: !hasModules
? i18n.format(t('no_modules_specified'), 'capitalize')
: t(`${MODULE_SETUP_KEY}_description`, {
count: modules.length,
}),
},
[LPC_KEY]: {
stepInternals: (
Expand Down Expand Up @@ -176,7 +165,9 @@ export function ProtocolRunSetup({
runId={runId}
/>
),
description: t(`${LIQUID_SETUP_KEY}_description`),
description: hasLiquids
? t(`${LIQUID_SETUP_KEY}_description`)
: i18n.format(t('liquids_not_in_the_protocol'), 'capitalize'),
},
}

Expand All @@ -198,24 +189,33 @@ export function ProtocolRunSetup({
) : (
stepsKeysInOrder.map((stepKey, index) => (
<Flex flexDirection={DIRECTION_COLUMN} key={stepKey}>
<SetupStep
expanded={stepKey === expandedStepKey}
label={t('step', { index: index + 1 })}
title={t(`${stepKey}_title`)}
description={StepDetailMap[stepKey].description}
toggleExpanded={() =>
stepKey === expandedStepKey
? setExpandedStepKey(null)
: setExpandedStepKey(stepKey)
}
calibrationStatusComplete={
stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted
? calibrationStatus.complete
: null
}
>
{StepDetailMap[stepKey].stepInternals}
</SetupStep>
{(stepKey === 'liquid_setup_step' && !hasLiquids) ||
(stepKey === 'module_setup_step' && !hasModules) ? (
<EmptySetupStep
title={t(`${stepKey}_title`)}
description={StepDetailMap[stepKey].description}
label={t('step', { index: index + 1 })}
/>
) : (
<SetupStep
expanded={stepKey === expandedStepKey}
label={t('step', { index: index + 1 })}
title={t(`${stepKey}_title`)}
description={StepDetailMap[stepKey].description}
toggleExpanded={() =>
stepKey === expandedStepKey
? setExpandedStepKey(null)
: setExpandedStepKey(stepKey)
}
calibrationStatusComplete={
stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted
? calibrationStatus.complete
: null
}
>
{StepDetailMap[stepKey].stepInternals}
</SetupStep>
)}
{index !== stepsKeysInOrder.length - 1 ? (
<Line marginTop={SPACING.spacing24} />
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../../i18n'
import { EmptySetupStep } from '../EmptySetupStep'

const render = (props: React.ComponentProps<typeof EmptySetupStep>) => {
return renderWithProviders(<EmptySetupStep {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('EmptySetupStep', () => {
let props: React.ComponentProps<typeof EmptySetupStep>
beforeEach(() => {
props = {
title: 'mockTitle',
description: 'mockDescription',
label: 'mockLabel',
}
})

it('should render the title, description, and label', () => {
const { getByText } = render(props)
getByText('mockTitle')
getByText('mockDescription')
getByText('mockLabel')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
partialComponentPropsMatcher,
renderWithProviders,
} from '@opentrons/components'
import {
ProtocolAnalysisOutput,
protocolHasLiquids,
} from '@opentrons/shared-data'
import noModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/simpleV4.json'
import withModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/testModulesProtocol.json'

Expand All @@ -25,18 +29,15 @@ import { SetupRobotCalibration } from '../SetupRobotCalibration'
import { SetupLiquids } from '../SetupLiquids'
import { ProtocolRunSetup } from '../ProtocolRunSetup'
import { SetupModules } from '../SetupModules'

import {
ProtocolAnalysisOutput,
protocolHasLiquids,
} from '@opentrons/shared-data'
import { EmptySetupStep } from '../EmptySetupStep'

jest.mock('@opentrons/api-client')
jest.mock('../../hooks')
jest.mock('../SetupLabware')
jest.mock('../SetupRobotCalibration')
jest.mock('../SetupModules')
jest.mock('../SetupLiquids')
jest.mock('../EmptySetupStep')
jest.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis')
jest.mock('@opentrons/shared-data/js/helpers/parseProtocolData')

Expand Down Expand Up @@ -75,6 +76,9 @@ const mockSetupLiquids = SetupLiquids as jest.MockedFunction<
const mockProtocolHasLiquids = protocolHasLiquids as jest.MockedFunction<
typeof protocolHasLiquids
>
const mockEmptySetupStep = EmptySetupStep as jest.MockedFunction<
typeof EmptySetupStep
>

const ROBOT_NAME = 'otie'
const RUN_ID = '1'
Expand Down Expand Up @@ -137,6 +141,7 @@ describe('ProtocolRunSetup', () => {
.mockReturnValue(<span>Mock SetupLabware</span>)
when(mockSetupModules).mockReturnValue(<div>Mock SetupModules</div>)
when(mockSetupLiquids).mockReturnValue(<div>Mock SetupLiquids</div>)
when(mockEmptySetupStep).mockReturnValue(<div>Mock EmptySetupStep</div>)
})
afterEach(() => {
resetAllWhenMocks()
Expand Down Expand Up @@ -207,9 +212,9 @@ describe('ProtocolRunSetup', () => {
labwareSetup.click()
expect(getByText('Mock SetupLabware')).toBeVisible()
})
it('does NOT render module setup', () => {
const { queryByText } = render()
expect(queryByText(/module setup/i)).toBeNull()
it('renders the empty states for modules and liquids when no modules in protocol', () => {
const { getAllByText } = render()
getAllByText('Mock EmptySetupStep')
})

it('defaults to no step expanded', () => {
Expand Down Expand Up @@ -239,7 +244,7 @@ describe('ProtocolRunSetup', () => {
mockProtocolHasLiquids.mockReturnValue(true)

const { getByText } = render()
getByText('STEP 3')
getByText('STEP 5')
getByText('Initial Liquid Setup')
getByText('View liquid starting locations and volumes')
getByText('Mock SetupLiquids')
Expand Down
69 changes: 52 additions & 17 deletions app/src/organisms/ProtocolDetails/ProtocolLiquidsDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { css } from 'styled-components'
import {
parseLabwareInfoByLiquidId,
parseLiquidsInLoadOrder,
} from '@opentrons/api-client'
import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components'
import {
ALIGN_CENTER,
COLORS,
DIRECTION_COLUMN,
Flex,
Icon,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import { Divider } from '../../atoms/structure'
import { StyledText } from '../../atoms/text'
import { LiquidsListItemDetails } from '../Devices/ProtocolRun/SetupLiquids/SetupLiquidsList'

import type { Liquid, RunTimeCommand } from '@opentrons/shared-data'
Expand All @@ -19,6 +29,7 @@ export const ProtocolLiquidsDetails = (
props: ProtocolLiquidsDetailsProps
): JSX.Element => {
const { commands, liquids } = props
const { i18n, t } = useTranslation('protocol_details')
const liquidsInLoadOrder = parseLiquidsInLoadOrder(liquids, commands)
const labwareByLiquidId = parseLabwareInfoByLiquidId(commands)
const HIDE_SCROLLBAR = css`
Expand All @@ -34,22 +45,46 @@ export const ProtocolLiquidsDetails = (
overflowY="auto"
data-testid="LiquidsDetailsTab"
>
{liquidsInLoadOrder?.map((liquid, index) => {
return (
<React.Fragment key={liquid.id}>
<Flex flexDirection={DIRECTION_COLUMN} marginY={SPACING.spacing16}>
<LiquidsListItemDetails
liquidId={liquid.id}
displayColor={liquid.displayColor}
displayName={liquid.displayName}
description={liquid.description}
labwareByLiquidId={labwareByLiquidId}
/>
</Flex>
{index < liquidsInLoadOrder.length - 1 && <Divider />}
</React.Fragment>
)
})}
{liquids.length > 0 ? (
liquidsInLoadOrder?.map((liquid, index) => {
return (
<React.Fragment key={liquid.id}>
<Flex
flexDirection={DIRECTION_COLUMN}
marginY={SPACING.spacing16}
>
<LiquidsListItemDetails
liquidId={liquid.id}
displayColor={liquid.displayColor}
displayName={liquid.displayName}
description={liquid.description}
labwareByLiquidId={labwareByLiquidId}
/>
</Flex>
{index < liquidsInLoadOrder.length - 1 && <Divider />}
</React.Fragment>
)
})
) : (
<Flex
paddingTop={SPACING.spacing16}
paddingBottom={SPACING.spacing32}
textAlign={TYPOGRAPHY.textAlignCenter}
gridGap={SPACING.spacing12}
flexDirection={DIRECTION_COLUMN}
>
<Icon
color={COLORS.medGreyEnabled}
alignSelf={ALIGN_CENTER}
size="1.25rem"
name="ot-alert"
aria-label="ProtocolLIquidsDetails_noLiquidsIcon"
/>
<StyledText as="p" fontWeight={TYPOGRAPHY.fontWeightSemiBold}>
{i18n.format(t('liquids_not_in_protocol'), 'capitalize')}
</StyledText>
</Flex>
)}
</Flex>
)
}
Loading

0 comments on commit 8efc586

Please sign in to comment.