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')} + + + )}