Skip to content

Commit

Permalink
feat(app, shared-data): create odd boolean and choice selection screen (
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader authored Apr 2, 2024
1 parent 8ee0268 commit 5efb48d
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 40 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 @@ -38,6 +38,7 @@
"calibration_status": "calibration status",
"calibration": "Calibration",
"cancel_and_restart_to_edit": "Cancel the run and restart setup to edit",
"choose_enum": "Choose {{displayName}}",
"closing": "Closing...",
"complete_setup_before_proceeding": "complete setup before continuing run",
"configure": "Configure",
Expand Down Expand Up @@ -161,6 +162,7 @@
"must_have_labware_and_pip": "Protocol must load labware and a pipette",
"n_a": "N/A",
"name": "Name",
"no_custom_values": "No custom values specified",
"no_data": "no data",
"no_labware_offset_data": "no labware offset data yet",
"no_modules_or_fixtures": "No modules or fixtures are specified for this protocol.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'

import { formatRunTimeParameterDefaultValue } from '@opentrons/shared-data'
import {
ALIGN_CENTER,
BORDERS,
Expand All @@ -17,7 +17,6 @@ import {
useHoverTooltip,
Icon,
} from '@opentrons/components'
import { formatRunTimeParameterValue } from '@opentrons/shared-data'

import { Banner } from '../../../atoms/Banner'
import { Divider } from '../../../atoms/structure'
Expand Down Expand Up @@ -151,7 +150,7 @@ const StyledTableRowComponent = (
<StyledTableCell isLast={index === runTimeParametersLength - 1}>
<Flex flexDirection={DIRECTION_ROW} gridGap={SPACING.spacing16}>
<StyledText as="p">
{formatRunTimeParameterValue(parameter, t)}
{formatRunTimeParameterDefaultValue(parameter, t)}
</StyledText>
{parameter.value !== parameter.default ? (
<Chip
Expand Down
87 changes: 87 additions & 0 deletions app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
DIRECTION_COLUMN,
Flex,
SPACING,
StyledText,
TYPOGRAPHY,
} from '@opentrons/components'
import { RadioButton } from '../../atoms/buttons'
import { useToaster } from '../ToasterOven'
import { ChildNavigation } from '../ChildNavigation'
import type { RunTimeParameter } from '@opentrons/shared-data'

interface ChooseEnumProps {
handleGoBack: () => void
parameter: RunTimeParameter
setParameter: (value: boolean | string | number, variableName: string) => void
rawValue: number | string | boolean
}

export function ChooseEnum({
handleGoBack,
parameter,
setParameter,
rawValue,
}: ChooseEnumProps): JSX.Element {
const { makeSnackbar } = useToaster()

const { t } = useTranslation(['protocol_setup', 'shared'])
if (parameter.type !== 'str') {
console.error(
`parameter type is expected to be a string for parameter ${parameter.displayName}`
)
}
const options = parameter.type === 'str' ? parameter.choices : undefined
const handleOnClick = (newValue: string | number | boolean): void => {
setParameter(newValue, parameter.variableName)
}
const resetValueDisabled = parameter.default === rawValue

return (
<>
<ChildNavigation
header={t('choose_enum', { displayName: parameter.displayName })}
onClickBack={handleGoBack}
buttonType="tertiaryLowLight"
buttonText={t('restore_default')}
onClickButton={() =>
resetValueDisabled
? makeSnackbar(t('no_custom_values'))
: setParameter(parameter.default, parameter.variableName)
}
/>
<Flex
marginTop="7.75rem"
alignSelf={ALIGN_CENTER}
gridGap={SPACING.spacing8}
paddingX={SPACING.spacing40}
flexDirection={DIRECTION_COLUMN}
paddingBottom={SPACING.spacing40}
>
<StyledText
as="h4"
textAlign={TYPOGRAPHY.textAlignLeft}
marginBottom={SPACING.spacing16}
>
{parameter.description}
</StyledText>

{options?.map(option => {
return (
<RadioButton
key={`${option.value}`}
data-testid={`${option.value}`}
buttonLabel={option.displayName}
buttonValue={`${option.value}`}
onChange={() => handleOnClick(option.value)}
isSelected={option.value === rawValue}
/>
)
})}
</Flex>
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { formatRunTimeParameterValue } from '@opentrons/shared-data'
import { formatRunTimeParameterDefaultValue } from '@opentrons/shared-data'
import {
ALIGN_CENTER,
BORDERS,
Expand Down Expand Up @@ -94,7 +94,7 @@ export function ViewOnlyParameters({
gridGap={SPACING.spacing8}
>
<StyledText as="p" maxWidth="15rem" color={COLORS.grey60}>
{formatRunTimeParameterValue(parameter, t)}
{formatRunTimeParameterDefaultValue(parameter, t)}
</StyledText>
{hasCustomValue ? (
<Chip
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from 'react'
import { it, describe, beforeEach, vi, expect } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { COLORS } from '@opentrons/components'
import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
import { ChooseEnum } from '../ChooseEnum'

vi.mocked('../../ToasterOven')
const render = (props: React.ComponentProps<typeof ChooseEnum>) => {
return renderWithProviders(<ChooseEnum {...props} />, {
i18nInstance: i18n,
})
}
describe('ChooseEnum', () => {
let props: React.ComponentProps<typeof ChooseEnum>

beforeEach(() => {
props = {
setParameter: vi.fn(),
handleGoBack: vi.fn(),
parameter: {
displayName: 'Default Module Offsets',
variableName: 'DEFAULT_OFFSETS',
value: 'none',
description: '',
type: 'str',
choices: [
{
displayName: 'no offsets',
value: 'none',
},
{
displayName: 'temp offset',
value: '1',
},
{
displayName: 'heater-shaker offset',
value: '2',
},
],
default: 'none',
},
rawValue: '1',
}
})
it('renders the back icon and calls the prop', () => {
render(props)
fireEvent.click(screen.getAllByRole('button')[0])
expect(props.handleGoBack).toHaveBeenCalled()
})
it('calls the prop if reset default is clicked when the default has changed', () => {
render(props)
fireEvent.click(screen.getByText('Restore default values'))
expect(props.setParameter).toHaveBeenCalled()
})
it('calls does not call prop if reset default is clicked when the default has not changed', () => {
props = {
...props,
rawValue: 'none',
}
render(props)
fireEvent.click(screen.getByText('Restore default values'))
expect(props.setParameter).not.toHaveBeenCalled()
})
it('should render the text and buttons for choice param', () => {
render(props)
screen.getByText('no offsets')
screen.getByText('temp offset')
screen.getByText('heater-shaker offset')
const notSelectedOption = screen.getByRole('label', { name: 'no offsets' })
const selectedOption = screen.getByRole('label', {
name: 'temp offset',
})
expect(notSelectedOption).toHaveStyle(`background-color: ${COLORS.blue40}`)
expect(selectedOption).toHaveStyle(`background-color: ${COLORS.blue60}`)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { useCreateRunMutation, useHost } from '@opentrons/react-api-client'
import { i18n } from '../../../i18n'
import { renderWithProviders } from '../../../__testing-utils__'
import { ProtocolSetupParameters } from '..'
import { ChooseEnum } from '../ChooseEnum'
import { mockRunTimeParameterData } from '../../../pages/ProtocolDetails/fixtures'
import type * as ReactRouterDom from 'react-router-dom'
import type { HostConfig } from '@opentrons/api-client'

const mockGoBack = vi.fn()

vi.mock('../ChooseEnum')
vi.mock('@opentrons/react-api-client')
vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis')
vi.mock('react-router-dom', async importOriginal => {
Expand Down Expand Up @@ -39,6 +41,7 @@ describe('ProtocolSetupParameters', () => {
labwareOffsets: [],
runTimeParameters: mockRunTimeParameterData,
}
vi.mocked(ChooseEnum).mockReturnValue(<div>mock ChooseEnum</div>)
vi.mocked(useHost).mockReturnValue(MOCK_HOST_CONFIG)
when(vi.mocked(useCreateRunMutation))
.calledWith(expect.anything())
Expand All @@ -52,6 +55,18 @@ describe('ProtocolSetupParameters', () => {
screen.getByText('Dry Run')
screen.getByText('a dry run description')
})
it('renders the ChooseEnum component when a str param is selected', () => {
render(props)
fireEvent.click(screen.getByText('Default Module Offsets'))
screen.getByText('mock ChooseEnum')
})
it('renders the other setting when boolean param is selected', () => {
render(props)
screen.getByText('Off')
expect(screen.getAllByText('On')).toHaveLength(3)
fireEvent.click(screen.getByText('Dry Run'))
expect(screen.getAllByText('On')).toHaveLength(4)
})
it('renders the back icon and calls useHistory', () => {
render(props)
fireEvent.click(screen.getAllByRole('button')[0])
Expand Down
72 changes: 60 additions & 12 deletions app/src/organisms/ProtocolSetupParameters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { formatRunTimeParameterValue } from '@opentrons/shared-data'
import { ProtocolSetupStep } from '../../pages/ProtocolSetup'
import { ChildNavigation } from '../ChildNavigation'
import { ResetValuesModal } from './ResetValuesModal'
import { ChooseEnum } from './ChooseEnum'

import type { RunTimeParameter } from '@opentrons/shared-data'
import type { LabwareOffsetCreateData } from '@opentrons/api-client'
Expand Down Expand Up @@ -176,6 +177,10 @@ export function ProtocolSetupParameters({
const history = useHistory()
const host = useHost()
const queryClient = useQueryClient()
const [
chooseValueScreen,
setChooseValueScreen,
] = React.useState<RunTimeParameter | null>(null)
const [resetValuesModal, showResetValuesModal] = React.useState<boolean>(
false
)
Expand All @@ -187,6 +192,30 @@ export function ProtocolSetupParameters({
runTimeParametersOverrides,
setRunTimeParametersOverrides,
] = React.useState<RunTimeParameter[]>(parameters)

const updateParameters = (
value: boolean | string | number,
variableName: string
): void => {
const updatedParameters = parameters.map(parameter => {
if (parameter.variableName === variableName) {
return { ...parameter, value }
}
return parameter
})
setRunTimeParametersOverrides(updatedParameters)
if (chooseValueScreen && chooseValueScreen.variableName === variableName) {
const updatedParameter = updatedParameters.find(
parameter => parameter.variableName === variableName
)
if (updatedParameter != null) {
setChooseValueScreen(updatedParameter)
}
}
}

// TODO(jr, 3/20/24): modify useCreateRunMutation to take in optional run time parameters
// newRunTimeParameters will be the param to plug in!
const { createRun, isLoading } = useCreateRunMutation({
onSuccess: data => {
queryClient
Expand All @@ -199,17 +228,8 @@ export function ProtocolSetupParameters({
const handleConfirmValues = (): void => {
createRun({ protocolId, labwareOffsets })
}

return (
let children = (
<>
{resetValuesModal ? (
<ResetValuesModal
runTimeParametersOverrides={runTimeParametersOverrides}
setRunTimeParametersOverrides={setRunTimeParametersOverrides}
handleGoBack={() => showResetValuesModal(false)}
/>
) : null}

<ChildNavigation
header={t('parameters')}
onClickBack={() => history.goBack()}
Expand All @@ -230,14 +250,18 @@ export function ProtocolSetupParameters({
gridGap={SPACING.spacing8}
paddingX={SPACING.spacing40}
>
{parameters.map((parameter, index) => {
{runTimeParametersOverrides.map((parameter, index) => {
return (
<React.Fragment key={`${parameter.displayName}_${index}`}>
<ProtocolSetupStep
hasIcon={!(parameter.type === 'bool')}
status="general"
title={parameter.displayName}
onClickSetupStep={() => console.log('TODO: wire this up')}
onClickSetupStep={() =>
parameter.type === 'bool'
? updateParameters(!parameter.value, parameter.variableName)
: setChooseValueScreen(parameter)
}
detail={formatRunTimeParameterValue(parameter, t)}
description={parameter.description}
fontSize="h4"
Expand All @@ -248,4 +272,28 @@ export function ProtocolSetupParameters({
</Flex>
</>
)
if (chooseValueScreen != null && chooseValueScreen.type === 'str') {
children = (
<ChooseEnum
handleGoBack={() => setChooseValueScreen(null)}
parameter={chooseValueScreen}
setParameter={updateParameters}
rawValue={chooseValueScreen.value}
/>
)
}
// TODO(jr, 4/1/24): add the int/float component

return (
<>
{resetValuesModal ? (
<ResetValuesModal
runTimeParametersOverrides={runTimeParametersOverrides}
setRunTimeParametersOverrides={setRunTimeParametersOverrides}
handleGoBack={() => showResetValuesModal(false)}
/>
) : null}
{children}
</>
)
}
Loading

0 comments on commit 5efb48d

Please sign in to comment.