From 8efc58683e37ca7158c2bc193de98f351741e3c6 Mon Sep 17 00:00:00 2001
From: Jethary Rader <66035149+jerader@users.noreply.github.com>
Date: Thu, 20 Jul 2023 09:55:43 -0400
Subject: [PATCH] feat(app): never hide module/liquid setup (#13134)
closes RAUT-570 RAUT-574
---
.../localization/en/protocol_setup.json | 2 +
.../Devices/ProtocolRun/EmptySetupStep.tsx | 30 +++++++
.../Devices/ProtocolRun/ProtocolRunSetup.tsx | 82 +++++++++----------
.../__tests__/EmptySetupStep.test.tsx | 28 +++++++
.../__tests__/ProtocolRunSetup.test.tsx | 23 ++++--
.../ProtocolLiquidsDetails.tsx | 69 ++++++++++++----
.../__tests__/ProtocolLiquidsDetails.test.tsx | 25 +++++-
app/src/organisms/ProtocolDetails/index.tsx | 47 +++++------
8 files changed, 210 insertions(+), 96 deletions(-)
create mode 100644 app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx
create mode 100644 app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx
diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json
index 0c05d05564a..58601cf0426 100644
--- a/app/src/assets/localization/en/protocol_setup.json
+++ b/app/src/assets/localization/en/protocol_setup.json
@@ -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...",
@@ -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",
diff --git a/app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx b/app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx
new file mode 100644
index 00000000000..4fcebc7fb44
--- /dev/null
+++ b/app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx
@@ -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 (
+
+
+ {label}
+
+
+ {title}
+
+ {description}
+
+ )
+}
diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx
index d92057c17c5..951bcd88be0 100644
--- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx
@@ -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
@@ -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
@@ -73,11 +75,8 @@ 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,
@@ -85,25 +84,13 @@ export function ProtocolRunSetup({
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,
@@ -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: (
@@ -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'),
},
}
@@ -198,24 +189,33 @@ export function ProtocolRunSetup({
) : (
stepsKeysInOrder.map((stepKey, index) => (
-
- stepKey === expandedStepKey
- ? setExpandedStepKey(null)
- : setExpandedStepKey(stepKey)
- }
- calibrationStatusComplete={
- stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted
- ? calibrationStatus.complete
- : null
- }
- >
- {StepDetailMap[stepKey].stepInternals}
-
+ {(stepKey === 'liquid_setup_step' && !hasLiquids) ||
+ (stepKey === 'module_setup_step' && !hasModules) ? (
+
+ ) : (
+
+ stepKey === expandedStepKey
+ ? setExpandedStepKey(null)
+ : setExpandedStepKey(stepKey)
+ }
+ calibrationStatusComplete={
+ stepKey === ROBOT_CALIBRATION_STEP_KEY && !runHasStarted
+ ? calibrationStatus.complete
+ : null
+ }
+ >
+ {StepDetailMap[stepKey].stepInternals}
+
+ )}
{index !== stepsKeysInOrder.length - 1 ? (
) : null}
diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx
new file mode 100644
index 00000000000..496197aaf98
--- /dev/null
+++ b/app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx
@@ -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) => {
+ return renderWithProviders(, {
+ i18nInstance: i18n,
+ })[0]
+}
+
+describe('EmptySetupStep', () => {
+ let props: React.ComponentProps
+ 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')
+ })
+})
diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx
index 4eed9f2f00b..28410d69ebb 100644
--- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx
@@ -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'
@@ -25,11 +29,7 @@ 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')
@@ -37,6 +37,7 @@ 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')
@@ -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'
@@ -137,6 +141,7 @@ describe('ProtocolRunSetup', () => {
.mockReturnValue(Mock SetupLabware)
when(mockSetupModules).mockReturnValue(Mock SetupModules
)
when(mockSetupLiquids).mockReturnValue(Mock SetupLiquids
)
+ when(mockEmptySetupStep).mockReturnValue(Mock EmptySetupStep
)
})
afterEach(() => {
resetAllWhenMocks()
@@ -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', () => {
@@ -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')
diff --git a/app/src/organisms/ProtocolDetails/ProtocolLiquidsDetails.tsx b/app/src/organisms/ProtocolDetails/ProtocolLiquidsDetails.tsx
index d51f9386c65..acc3d6bdc27 100644
--- a/app/src/organisms/ProtocolDetails/ProtocolLiquidsDetails.tsx
+++ b/app/src/organisms/ProtocolDetails/ProtocolLiquidsDetails.tsx
@@ -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'
@@ -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`
@@ -34,22 +45,46 @@ export const ProtocolLiquidsDetails = (
overflowY="auto"
data-testid="LiquidsDetailsTab"
>
- {liquidsInLoadOrder?.map((liquid, index) => {
- return (
-
-
-
-
- {index < liquidsInLoadOrder.length - 1 && }
-
- )
- })}
+ {liquids.length > 0 ? (
+ liquidsInLoadOrder?.map((liquid, index) => {
+ return (
+
+
+
+
+ {index < liquidsInLoadOrder.length - 1 && }
+
+ )
+ })
+ ) : (
+
+
+
+ {i18n.format(t('liquids_not_in_protocol'), 'capitalize')}
+
+
+ )}
)
}
diff --git a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx b/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx
index d19a65c188b..e657317e2e7 100644
--- a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx
+++ b/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx
@@ -1,11 +1,12 @@
import * as React from 'react'
import { renderWithProviders } from '@opentrons/components'
-import { ProtocolLiquidsDetails } from '../ProtocolLiquidsDetails'
-import { LiquidsListItemDetails } from '../../Devices/ProtocolRun/SetupLiquids/SetupLiquidsList'
import {
parseLiquidsInLoadOrder,
parseLabwareInfoByLiquidId,
} from '@opentrons/api-client'
+import { i18n } from '../../../i18n'
+import { ProtocolLiquidsDetails } from '../ProtocolLiquidsDetails'
+import { LiquidsListItemDetails } from '../../Devices/ProtocolRun/SetupLiquids/SetupLiquidsList'
jest.mock('../../Devices/ProtocolRun/SetupLiquids/SetupLiquidsList')
jest.mock('@opentrons/api-client')
@@ -23,12 +24,24 @@ const mockParseLabwareInfoByLiquidId = parseLabwareInfoByLiquidId as jest.Mocked
>
const render = (props: React.ComponentProps) => {
- return renderWithProviders()
+ return renderWithProviders(, {
+ i18nInstance: i18n,
+ })
}
describe('ProtocolLiquidsDetails', () => {
let props: React.ComponentProps
beforeEach(() => {
+ props = {
+ commands: [],
+ liquids: [
+ {
+ id: 'mockLiquid',
+ displayName: 'mockDisplayName',
+ description: 'mockDescription',
+ },
+ ],
+ }
mockLiquidsListItemDetails.mockReturnValue(
mock liquids list item
)
@@ -48,4 +61,10 @@ describe('ProtocolLiquidsDetails', () => {
const [{ getAllByText }] = render(props)
getAllByText('mock liquids list item')
})
+ it('renders the correct info for no liquids in the protocol', () => {
+ props.liquids = []
+ const [{ getByText, getByLabelText }] = render(props)
+ getByText('No liquids are specified for this protocol')
+ getByLabelText('ProtocolLIquidsDetails_noLiquidsIcon')
+ })
})
diff --git a/app/src/organisms/ProtocolDetails/index.tsx b/app/src/organisms/ProtocolDetails/index.tsx
index e3657011c6f..434465f8324 100644
--- a/app/src/organisms/ProtocolDetails/index.tsx
+++ b/app/src/organisms/ProtocolDetails/index.tsx
@@ -36,10 +36,7 @@ import {
parseInitialLoadedLabwareBySlot,
parseInitialLoadedLabwareByModuleId,
} from '@opentrons/api-client'
-import {
- getGripperDisplayName,
- protocolHasLiquids,
-} from '@opentrons/shared-data'
+import { getGripperDisplayName } from '@opentrons/shared-data'
import { Portal } from '../../App/portal'
import { Divider } from '../../atoms/structure'
@@ -144,7 +141,7 @@ interface ReadMoreContentProps {
const ReadMoreContent = (props: ReadMoreContentProps): JSX.Element => {
const { metadata, protocolType } = props
- const { t } = useTranslation('protocol_details')
+ const { t, i18n } = useTranslation('protocol_details')
const [isReadMore, setIsReadMore] = React.useState(true)
const description = isEmpty(metadata.description)
@@ -167,10 +164,11 @@ const ReadMoreContent = (props: ReadMoreContentProps): JSX.Element => {
role="button"
css={TYPOGRAPHY.linkPSemiBold}
marginTop={SPACING.spacing8}
- textTransform={TYPOGRAPHY.textTransformCapitalize}
onClick={() => setIsReadMore(!isReadMore)}
>
- {isReadMore ? t('read_more') : t('read_less')}
+ {isReadMore
+ ? i18n.format(t('read_more'), 'capitalize')
+ : i18n.format(t('read_less'), 'capitalize')}
)}
@@ -185,7 +183,7 @@ export function ProtocolDetails(
const trackEvent = useTrackEvent()
const dispatch = useDispatch()
const { protocolKey, srcFileNames, mostRecentAnalysis, modified } = props
- const { t } = useTranslation(['protocol_details', 'shared'])
+ const { t, i18n } = useTranslation(['protocol_details', 'shared'])
const [currentTab, setCurrentTab] = React.useState<
'robot_config' | 'labware' | 'liquids'
>('robot_config')
@@ -556,8 +554,8 @@ export function ProtocolDetails(
isCurrent={currentTab === 'robot_config'}
onClick={() => setCurrentTab('robot_config')}
>
-
- {t('robot_configuration')}
+
+ {i18n.format(t('robot_configuration'), 'capitalize')}
setCurrentTab('labware')}
>
-
- {t('labware')}
+
+ {i18n.format(t('labware'), 'capitalize')}
- {mostRecentAnalysis != null &&
- protocolHasLiquids(mostRecentAnalysis) && (
- setCurrentTab('liquids')}
- >
-
- {t('liquids')}
-
-
- )}
+ {mostRecentAnalysis != null && (
+ setCurrentTab('liquids')}
+ >
+
+ {i18n.format(t('liquids'), 'capitalize')}
+
+
+ )}