From 5efb48d99544879b94e02e402f36b5818c481b63 Mon Sep 17 00:00:00 2001
From: Jethary Rader <66035149+jerader@users.noreply.github.com>
Date: Tue, 2 Apr 2024 15:26:33 -0400
Subject: [PATCH] feat(app, shared-data): create odd boolean and choice
selection screen (#14775)
closes AUTH-123
---
.../localization/en/protocol_setup.json | 2 +
.../ProtocolRunRunTimeParameters.tsx | 5 +-
.../ProtocolSetupParameters/ChooseEnum.tsx | 87 +++++++++++++++++++
.../ViewOnlyParameters.tsx | 4 +-
....test.tsx => AnalysisFailedModal.test.tsx} | 0
.../__tests__/ChooseEnum.test.tsx | 79 +++++++++++++++++
.../ProtocolSetupParameters.test.tsx | 15 ++++
.../ProtocolSetupParameters/index.tsx | 72 ++++++++++++---
app/src/pages/ProtocolDetails/Parameters.tsx | 4 +-
.../src/molecules/ParametersTable/index.tsx | 4 +-
.../formatRunTimeParameterValue.test.ts | 14 +--
.../formatRunTimeParameterDefaultValue.ts | 36 ++++++++
.../js/helpers/formatRunTimeParameterValue.ts | 21 ++---
shared-data/js/helpers/index.ts | 1 +
14 files changed, 304 insertions(+), 40 deletions(-)
create mode 100644 app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx
rename app/src/organisms/ProtocolSetupParameters/__tests__/{AnalysisFailedModa.test.tsx => AnalysisFailedModal.test.tsx} (100%)
create mode 100644 app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx
create mode 100644 shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts
diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json
index 371ce03a791..99b496a3479 100644
--- a/app/src/assets/localization/en/protocol_setup.json
+++ b/app/src/assets/localization/en/protocol_setup.json
@@ -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",
@@ -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.",
diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
index d16d7b8b8cb..af94400b80f 100644
--- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
@@ -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,
@@ -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'
@@ -151,7 +150,7 @@ const StyledTableRowComponent = (
- {formatRunTimeParameterValue(parameter, t)}
+ {formatRunTimeParameterDefaultValue(parameter, t)}
{parameter.value !== parameter.default ? (
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 (
+ <>
+
+ resetValueDisabled
+ ? makeSnackbar(t('no_custom_values'))
+ : setParameter(parameter.default, parameter.variableName)
+ }
+ />
+
+
+ {parameter.description}
+
+
+ {options?.map(option => {
+ return (
+ handleOnClick(option.value)}
+ isSelected={option.value === rawValue}
+ />
+ )
+ })}
+
+ >
+ )
+}
diff --git a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
index e8aca7d8c9c..09dcaf26c47 100644
--- a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx
@@ -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,
@@ -94,7 +94,7 @@ export function ViewOnlyParameters({
gridGap={SPACING.spacing8}
>
- {formatRunTimeParameterValue(parameter, t)}
+ {formatRunTimeParameterDefaultValue(parameter, t)}
{hasCustomValue ? (
) => {
+ return renderWithProviders(, {
+ i18nInstance: i18n,
+ })
+}
+describe('ChooseEnum', () => {
+ let props: React.ComponentProps
+
+ 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}`)
+ })
+})
diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx b/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx
index 4873745356c..1dc55314d59 100644
--- a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx
@@ -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 => {
@@ -39,6 +41,7 @@ describe('ProtocolSetupParameters', () => {
labwareOffsets: [],
runTimeParameters: mockRunTimeParameterData,
}
+ vi.mocked(ChooseEnum).mockReturnValue(mock ChooseEnum
)
vi.mocked(useHost).mockReturnValue(MOCK_HOST_CONFIG)
when(vi.mocked(useCreateRunMutation))
.calledWith(expect.anything())
@@ -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])
diff --git a/app/src/organisms/ProtocolSetupParameters/index.tsx b/app/src/organisms/ProtocolSetupParameters/index.tsx
index a3cc0687b17..1312844b2ab 100644
--- a/app/src/organisms/ProtocolSetupParameters/index.tsx
+++ b/app/src/organisms/ProtocolSetupParameters/index.tsx
@@ -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'
@@ -176,6 +177,10 @@ export function ProtocolSetupParameters({
const history = useHistory()
const host = useHost()
const queryClient = useQueryClient()
+ const [
+ chooseValueScreen,
+ setChooseValueScreen,
+ ] = React.useState(null)
const [resetValuesModal, showResetValuesModal] = React.useState(
false
)
@@ -187,6 +192,30 @@ export function ProtocolSetupParameters({
runTimeParametersOverrides,
setRunTimeParametersOverrides,
] = React.useState(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
@@ -199,17 +228,8 @@ export function ProtocolSetupParameters({
const handleConfirmValues = (): void => {
createRun({ protocolId, labwareOffsets })
}
-
- return (
+ let children = (
<>
- {resetValuesModal ? (
- showResetValuesModal(false)}
- />
- ) : null}
-
history.goBack()}
@@ -230,14 +250,18 @@ export function ProtocolSetupParameters({
gridGap={SPACING.spacing8}
paddingX={SPACING.spacing40}
>
- {parameters.map((parameter, index) => {
+ {runTimeParametersOverrides.map((parameter, index) => {
return (
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"
@@ -248,4 +272,28 @@ export function ProtocolSetupParameters({
>
)
+ if (chooseValueScreen != null && chooseValueScreen.type === 'str') {
+ children = (
+ setChooseValueScreen(null)}
+ parameter={chooseValueScreen}
+ setParameter={updateParameters}
+ rawValue={chooseValueScreen.value}
+ />
+ )
+ }
+ // TODO(jr, 4/1/24): add the int/float component
+
+ return (
+ <>
+ {resetValuesModal ? (
+ showResetValuesModal(false)}
+ />
+ ) : null}
+ {children}
+ >
+ )
}
diff --git a/app/src/pages/ProtocolDetails/Parameters.tsx b/app/src/pages/ProtocolDetails/Parameters.tsx
index c43b56d7242..b8cbfa71155 100644
--- a/app/src/pages/ProtocolDetails/Parameters.tsx
+++ b/app/src/pages/ProtocolDetails/Parameters.tsx
@@ -1,7 +1,7 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
-import { formatRunTimeParameterValue } from '@opentrons/shared-data'
+import { formatRunTimeParameterDefaultValue } from '@opentrons/shared-data'
import {
BORDERS,
COLORS,
@@ -118,7 +118,7 @@ export const Parameters = (props: { protocolId: string }): JSX.Element => {
- {formatRunTimeParameterValue(parameter, t)}
+ {formatRunTimeParameterDefaultValue(parameter, t)}
diff --git a/components/src/molecules/ParametersTable/index.tsx b/components/src/molecules/ParametersTable/index.tsx
index 358b09c65c0..671646f19d0 100644
--- a/components/src/molecules/ParametersTable/index.tsx
+++ b/components/src/molecules/ParametersTable/index.tsx
@@ -1,6 +1,6 @@
import * as React from 'react'
import styled from 'styled-components'
-import { formatRunTimeParameterValue } from '@opentrons/shared-data'
+import { formatRunTimeParameterDefaultValue } from '@opentrons/shared-data'
import { BORDERS } from '../../helix-design-system'
import { SPACING, TYPOGRAPHY } from '../../ui-style-constants/index'
import { StyledText } from '../../atoms/StyledText'
@@ -69,7 +69,7 @@ export function ParametersTable({
- {formatRunTimeParameterValue(parameter, t)}
+ {formatRunTimeParameterDefaultValue(parameter, t)}
diff --git a/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts b/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts
index fec7bd7f4b2..a405d5845d3 100644
--- a/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts
+++ b/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts
@@ -1,5 +1,5 @@
import { describe, it, expect, vi } from 'vitest'
-import { formatRunTimeParameterValue } from '../formatRunTimeParameterValue'
+import { formatRunTimeParameterDefaultValue } from '../formatRunTimeParameterDefaultValue'
import type { RunTimeParameter } from '../../types'
@@ -9,7 +9,7 @@ const capitalizeFirstLetter = (str: string): string => {
const mockTFunction = vi.fn(str => capitalizeFirstLetter(str))
-describe('utils-formatRunTimeParameterValue', () => {
+describe('utils-formatRunTimeParameterDefaultValue', () => {
it('should return value with suffix when type is int', () => {
const mockData = {
value: 6,
@@ -21,7 +21,7 @@ describe('utils-formatRunTimeParameterValue', () => {
max: 10,
default: 6,
} as RunTimeParameter
- const result = formatRunTimeParameterValue(mockData, mockTFunction)
+ const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction)
expect(result).toEqual('6')
})
@@ -37,7 +37,7 @@ describe('utils-formatRunTimeParameterValue', () => {
max: 10.0,
default: 6.5,
} as RunTimeParameter
- const result = formatRunTimeParameterValue(mockData, mockTFunction)
+ const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction)
expect(result).toEqual('6.5 mL')
})
@@ -60,7 +60,7 @@ describe('utils-formatRunTimeParameterValue', () => {
],
default: 'left',
} as RunTimeParameter
- const result = formatRunTimeParameterValue(mockData, mockTFunction)
+ const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction)
expect(result).toEqual('Left')
})
@@ -73,7 +73,7 @@ describe('utils-formatRunTimeParameterValue', () => {
type: 'bool',
default: true,
} as RunTimeParameter
- const result = formatRunTimeParameterValue(mockData, mockTFunction)
+ const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction)
expect(result).toEqual('On')
})
@@ -86,7 +86,7 @@ describe('utils-formatRunTimeParameterValue', () => {
type: 'bool',
default: false,
} as RunTimeParameter
- const result = formatRunTimeParameterValue(mockData, mockTFunction)
+ const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction)
expect(result).toEqual('Off')
})
})
diff --git a/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts b/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts
new file mode 100644
index 00000000000..78de4e78f02
--- /dev/null
+++ b/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts
@@ -0,0 +1,36 @@
+import type { RunTimeParameter } from '../types'
+
+export const formatRunTimeParameterDefaultValue = (
+ runTimeParameter: RunTimeParameter,
+ t?: any
+): string => {
+ const { type, default: defaultValue } = runTimeParameter
+ const suffix =
+ 'suffix' in runTimeParameter && runTimeParameter.suffix != null
+ ? runTimeParameter.suffix
+ : null
+ switch (type) {
+ case 'int':
+ case 'float':
+ return suffix !== null
+ ? `${defaultValue.toString()} ${suffix}`
+ : defaultValue.toString()
+ case 'bool':
+ if (t != null) {
+ return Boolean(defaultValue) ? t('on') : t('off')
+ } else {
+ return Boolean(defaultValue) ? 'On' : 'Off'
+ }
+ case 'str':
+ if ('choices' in runTimeParameter && runTimeParameter.choices != null) {
+ const choice = runTimeParameter.choices.find(
+ choice => choice.value === defaultValue
+ )
+ if (choice != null) {
+ return choice.displayName
+ }
+ }
+ break
+ }
+ return ''
+}
diff --git a/shared-data/js/helpers/formatRunTimeParameterValue.ts b/shared-data/js/helpers/formatRunTimeParameterValue.ts
index ed154bbcf8a..0aa0b72a194 100644
--- a/shared-data/js/helpers/formatRunTimeParameterValue.ts
+++ b/shared-data/js/helpers/formatRunTimeParameterValue.ts
@@ -1,10 +1,10 @@
-import { RunTimeParameter } from '../types'
+import type { RunTimeParameter } from '../types'
export const formatRunTimeParameterValue = (
runTimeParameter: RunTimeParameter,
- t?: any
+ t: any
): string => {
- const { type, default: defaultValue } = runTimeParameter
+ const { type, value } = runTimeParameter
const suffix =
'suffix' in runTimeParameter && runTimeParameter.suffix != null
? runTimeParameter.suffix
@@ -13,18 +13,15 @@ export const formatRunTimeParameterValue = (
case 'int':
case 'float':
return suffix !== null
- ? `${defaultValue.toString()} ${suffix}`
- : defaultValue.toString()
- case 'bool':
- if (t != null) {
- return Boolean(defaultValue) ? t('on') : t('off')
- } else {
- return Boolean(defaultValue) ? 'On' : 'Off'
- }
+ ? `${value.toString()} ${suffix}`
+ : value.toString()
+ case 'bool': {
+ return Boolean(value) ? t('on') : t('off')
+ }
case 'str':
if ('choices' in runTimeParameter && runTimeParameter.choices != null) {
const choice = runTimeParameter.choices.find(
- choice => choice.value === defaultValue
+ choice => choice.value === value
)
if (choice != null) {
return choice.displayName
diff --git a/shared-data/js/helpers/index.ts b/shared-data/js/helpers/index.ts
index 2d78f16ca1f..a65a83085de 100644
--- a/shared-data/js/helpers/index.ts
+++ b/shared-data/js/helpers/index.ts
@@ -28,6 +28,7 @@ export * from './getOccludedSlotCountForModule'
export * from './labwareInference'
export * from './getAddressableAreasInProtocol'
export * from './getSimplestFlexDeckConfig'
+export * from './formatRunTimeParameterDefaultValue'
export * from './formatRunTimeParameterValue'
export const getLabwareDefIsStandard = (def: LabwareDefinition2): boolean =>