From c4cb035269f55e17b6de73a9f593971c61ca6137 Mon Sep 17 00:00:00 2001
From: Nick Diehl <47604184+ncdiehl11@users.noreply.github.com>
Date: Mon, 25 Mar 2024 14:17:18 -0400
Subject: [PATCH] feat(app): populate ChooseRobotSlideout with runtime
parameters (#14706)
closes [AUTH-98](https://opentrons.atlassian.net/browse/AUTH-98)
closes [AUTH-99](https://opentrons.atlassian.net/browse/AUTH-101)
closes [AUTH-101](https://opentrons.atlassian.net/browse/AUTH-101)
---
.../localization/en/protocol_details.json | 2 +
app/src/atoms/InputField/index.tsx | 55 +++--
app/src/atoms/MenuList/DropdownMenu.tsx | 111 ++++++----
app/src/atoms/SelectField/Select.tsx | 24 ++-
app/src/atoms/SelectField/index.tsx | 29 ++-
.../__tests__/ChooseRobotSlideout.test.tsx | 75 +++++++
.../organisms/ChooseRobotSlideout/index.tsx | 194 +++++++++++++++++-
.../index.tsx | 65 +++++-
app/src/organisms/ProtocolDetails/index.tsx | 1 +
components/src/helix-design-system/borders.ts | 1 +
10 files changed, 485 insertions(+), 72 deletions(-)
diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json
index 757e9caab3d..cc6aa6c1e41 100644
--- a/app/src/assets/localization/en/protocol_details.json
+++ b/app/src/assets/localization/en/protocol_details.json
@@ -33,6 +33,7 @@
"modules": "modules",
"name": "Name",
"no_available_robots_found": "No available robots found",
+ "no_custom_values": "No custom values specified",
"no_parameters": "No parameters specified in this protocol",
"no_summary": "no summary specified for this protocol.",
"not_connected": "not connected",
@@ -58,6 +59,7 @@
"range": "Range",
"read_less": "read less",
"read_more": "read more",
+ "restore_defaults": "Restore default values",
"right_mount": "right mount",
"robot_configuration": "robot configuration",
"robot_is_busy_with_protocol": "{{robotName}} is busy with {{protocolName}} in {{runStatus}} state. Do you want to clear it and proceed?",
diff --git a/app/src/atoms/InputField/index.tsx b/app/src/atoms/InputField/index.tsx
index d67710557f6..a3f4d656133 100644
--- a/app/src/atoms/InputField/index.tsx
+++ b/app/src/atoms/InputField/index.tsx
@@ -13,6 +13,7 @@ import {
TYPOGRAPHY,
TEXT_ALIGN_RIGHT,
} from '@opentrons/components'
+import { StyledText } from '../text'
export const INPUT_TYPE_NUMBER = 'number' as const
export const INPUT_TYPE_TEXT = 'text' as const
@@ -183,7 +184,7 @@ function Input(props: InputFieldProps): JSX.Element {
`
const FORM_BOTTOM_SPACE_STYLE = css`
- padding-bottom: ${SPACING.spacing4};
+ padding: ${SPACING.spacing4} 0rem;
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
padding-bottom: 0;
}
@@ -230,40 +231,60 @@ function Input(props: InputFieldProps): JSX.Element {
return (
{props.title != null ? (
- {props.title}
+
+ {props.title}
+
) : null}
{
+ if (props.id != null) {
+ document.getElementById(props.id)?.focus()
+ }
+ }}
>
event.currentTarget.blur()} // prevent value change with scrolling
/>
{props.units != null ? (
{props.units}
) : null}
-
+ {props.error}
+
+ ) : null}
+
+ {props.caption != null ? (
+
- {props.caption != null ? (
- {props.caption}
- ) : null}
- {props.secondaryCaption != null ? (
- {props.secondaryCaption}
- ) : null}
- {props.error}
-
-
+ {props.caption}
+
+ ) : null}
+ {props.secondaryCaption != null ? (
+
+ {props.secondaryCaption}
+
+ ) : null}
)
}
diff --git a/app/src/atoms/MenuList/DropdownMenu.tsx b/app/src/atoms/MenuList/DropdownMenu.tsx
index 9693efa920a..cdd64634a13 100644
--- a/app/src/atoms/MenuList/DropdownMenu.tsx
+++ b/app/src/atoms/MenuList/DropdownMenu.tsx
@@ -13,6 +13,7 @@ import {
BORDERS,
Icon,
useOnClickOutside,
+ POSITION_RELATIVE,
} from '@opentrons/components'
import { StyledText } from '../text'
import { MenuItem } from './MenuItem'
@@ -22,13 +23,16 @@ export interface DropdownOption {
value: string
}
+export type DropdownBorder = 'rounded' | 'neutral'
+
export interface DropdownMenuProps {
filterOptions: DropdownOption[]
onClick: (value: string) => void
currentOption: DropdownOption
width?: string
- dropdownType?: 'rounded' | 'neutral'
+ dropdownType?: DropdownBorder
title?: string
+ caption?: string | null
}
// TODO: (smb: 4/15/22) refactor this to use html select for accessibility
@@ -41,6 +45,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
width = '9.125rem',
dropdownType = 'rounded',
title,
+ caption,
} = props
const [showDropdownMenu, setShowDropdownMenu] = React.useState(false)
const toggleSetShowDropdownMenu = (): void => {
@@ -65,6 +70,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
align-items: ${ALIGN_CENTER};
justify-content: ${JUSTIFY_SPACE_BETWEEN};
width: ${width};
+ height: 2.25rem;
&:hover {
border: 1px ${BORDERS.styleSolid}
@@ -88,53 +94,76 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
`
return (
-
+
{title !== null ? (
-
+
{title}
) : null}
- {
- e.preventDefault()
- toggleSetShowDropdownMenu()
- }}
- css={DROPDOWN_STYLE}
- ref={dropDownMenuWrapperRef}
- >
- {currentOption.name}
- {showDropdownMenu ? (
-
- ) : (
-
- )}
-
- {showDropdownMenu && (
+
{
+ e.preventDefault()
+ toggleSetShowDropdownMenu()
+ }}
+ css={DROPDOWN_STYLE}
>
- {filterOptions.map((option, index) => (
-
- ))}
+
+ {currentOption.name}
+
+ {showDropdownMenu ? (
+
+ ) : (
+
+ )}
- )}
+ {showDropdownMenu && (
+
+ {filterOptions.map((option, index) => (
+
+ ))}
+
+ )}
+
+ {caption != null ? (
+
+ {caption}
+
+ ) : null}
)
}
diff --git a/app/src/atoms/SelectField/Select.tsx b/app/src/atoms/SelectField/Select.tsx
index f0f49389b92..0c1d4ebdf61 100644
--- a/app/src/atoms/SelectField/Select.tsx
+++ b/app/src/atoms/SelectField/Select.tsx
@@ -20,6 +20,7 @@ import type {
CSSObjectWithLabel,
DropdownIndicatorProps,
} from 'react-select'
+import type { DropdownBorder } from '../MenuList/DropdownMenu'
export interface SelectOption {
value: string
@@ -31,29 +32,36 @@ export type SelectProps = ReactSelectProps
interface SelectComponentProps extends SelectProps {
width?: string
+ dropdownType?: DropdownBorder
}
const VOID_STYLE: unknown = undefined
const NO_STYLE_FN = (): CSSObjectWithLabel => VOID_STYLE as CSSObjectWithLabel
export function Select(props: SelectComponentProps): JSX.Element {
+ const { dropdownType, menuIsOpen, width } = props
const CLEAR_DEFAULT_STYLES_AND_SET_NEW_STYLES: StylesConfig = {
clearIndicator: NO_STYLE_FN,
control: (styles: CSSObjectWithLabel) => ({
...styles,
- borderRadius: BORDERS.borderRadiusFull,
- border: BORDERS.lineBorder,
- width: props.width ?? 'auto',
+ borderRadius:
+ dropdownType === 'rounded'
+ ? BORDERS.borderRadiusFull
+ : BORDERS.borderRadius4,
+ border: `1px ${BORDERS.styleSolid} ${
+ menuIsOpen ? COLORS.blue50 : COLORS.grey50
+ }`,
+ width: width ?? 'auto',
height: SPACING.spacing16,
borderColor: COLORS.grey30,
boxShadow: 'none',
padding: SPACING.spacing6,
flexDirection: DIRECTION_ROW,
'&:hover': {
- borderColor: COLORS.grey60,
+ borderColor: COLORS.grey50,
},
'&:active': {
- borderColor: COLORS.grey60,
+ borderColor: COLORS.blue50,
},
}),
container: (styles: CSSObjectWithLabel) => ({
@@ -83,7 +91,7 @@ export function Select(props: SelectComponentProps): JSX.Element {
menu: (styles: CSSObjectWithLabel) => ({
...styles,
backgroundColor: COLORS.white,
- width: props.width != null ? props.width : 'auto',
+ width: width != null ? width : 'auto',
boxShadowcha: '0px 1px 3px rgba(0, 0, 0, 0.2)',
borderRadius: '4px 4px 0px 0px',
marginTop: SPACING.spacing4,
@@ -155,9 +163,9 @@ function DropdownIndicator(
width={SPACING.spacing20}
>
{Boolean(props.selectProps.menuIsOpen) ? (
-
+
) : (
-
+
)}
diff --git a/app/src/atoms/SelectField/index.tsx b/app/src/atoms/SelectField/index.tsx
index 646ecd60378..c277432ec93 100644
--- a/app/src/atoms/SelectField/index.tsx
+++ b/app/src/atoms/SelectField/index.tsx
@@ -1,8 +1,15 @@
import * as React from 'react'
import find from 'lodash/find'
import { Select } from './Select'
-import { COLORS, Flex, TYPOGRAPHY } from '@opentrons/components'
+import {
+ COLORS,
+ DIRECTION_COLUMN,
+ Flex,
+ TYPOGRAPHY,
+ SPACING,
+} from '@opentrons/components'
import { css } from 'styled-components'
+import { StyledText } from '../text'
import type { SelectProps, SelectOption } from './Select'
import type { ActionMeta, MultiValue, SingleValue } from 'react-select'
@@ -24,6 +31,8 @@ export interface SelectFieldProps {
menuPosition?: SelectProps['menuPosition']
/** render function for the option label passed to react-select */
formatOptionLabel?: SelectProps['formatOptionLabel']
+ /** optional title */
+ title?: React.ReactNode
/** optional caption. hidden when `error` is given */
caption?: React.ReactNode
/** if included, use error style and display error instead of caption */
@@ -40,10 +49,12 @@ export interface SelectFieldProps {
isSearchable?: boolean
/** optional width to specify the width of the select field and dropdown menu */
width?: string
+ dropdownType?: 'rounded' | 'neutral'
}
const CAPTION_STYLE = css`
font-size: ${TYPOGRAPHY.fontSizeCaption};
+ padding-top: ${SPACING.spacing4};
&.error {
color: ${COLORS.red50};
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
@@ -64,6 +75,8 @@ export function SelectField(props: SelectFieldProps): JSX.Element {
onLoseFocus,
isSearchable = true,
width,
+ title,
+ dropdownType = 'rounded',
} = props
const allOptions = options.flatMap(og =>
'options' in og ? og.options : [og]
@@ -72,7 +85,16 @@ export function SelectField(props: SelectFieldProps): JSX.Element {
const caption = error != null || props.caption
return (
- <>
+
+ {title != null ? (
+
+ {title}
+
+ ) : null}
)
}
diff --git a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx b/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx
index 524c59d4f06..a6cda099349 100644
--- a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx
+++ b/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx
@@ -22,6 +22,7 @@ import { useFeatureFlag } from '../../../redux/config'
import { getNetworkInterfaces } from '../../../redux/networking'
import { ChooseRobotSlideout } from '..'
import { useNotifyService } from '../../../resources/useNotifyService'
+import { RunTimeParameter } from '@opentrons/shared-data'
vi.mock('../../../redux/discovery')
vi.mock('../../../redux/robot-update')
@@ -41,6 +42,60 @@ const render = (props: React.ComponentProps) => {
const mockSetSelectedRobot = vi.fn()
+const mockRunTimeParameters: RunTimeParameter[] = [
+ {
+ displayName: 'Dry Run',
+ value: false,
+ variableName: 'DRYRUN',
+ description: 'Is this a dry or wet run? Wet is true, dry is false',
+ type: 'boolean',
+ default: false,
+ },
+ {
+ value: 4,
+ displayName: 'Columns of Samples',
+ variableName: 'COLUMNS',
+ description: 'How many columns do you want?',
+ type: 'int',
+ min: 1,
+ max: 14,
+ default: 4,
+ },
+ {
+ value: 6.5,
+ displayName: 'EtoH Volume',
+ variableName: 'ETOH_VOLUME',
+ description: '70% ethanol volume',
+ type: 'float',
+ suffix: 'mL',
+ min: 1.5,
+ max: 10.0,
+ default: 6.5,
+ },
+ {
+ value: 'none',
+ displayName: 'Default Module Offsets',
+ variableName: 'DEFAULT_OFFSETS',
+ description: 'default module offsets for temp, H-S, and none',
+ type: 'str',
+ choices: [
+ {
+ displayName: 'No offsets',
+ value: 'none',
+ },
+ {
+ displayName: 'temp offset',
+ value: '1',
+ },
+ {
+ displayName: 'heater-shaker offset',
+ value: '2',
+ },
+ ],
+ default: 'none',
+ },
+]
+
describe('ChooseRobotSlideout', () => {
beforeEach(() => {
vi.mocked(useFeatureFlag).mockReturnValue(true)
@@ -155,6 +210,26 @@ describe('ChooseRobotSlideout', () => {
})
screen.getByText('Step 2 / 2')
})
+
+ mockRunTimeParameters.forEach((param, index) => {
+ it('renders runtime parameter with title and caption', () => {
+ render({
+ onCloseClick: vi.fn(),
+ isExpanded: true,
+ isSelectedRobotOnDifferentSoftwareVersion: false,
+ selectedRobot: null,
+ setSelectedRobot: mockSetSelectedRobot,
+ title: 'choose robot slideout title',
+ robotType: 'OT-2 Standard',
+ multiSlideout: { currentPage: 2 },
+ runTimeParametersOverrides: [param],
+ })
+
+ screen.getByText(param.displayName)
+ screen.getByText(param.description)
+ })
+ })
+
it('defaults to first available robot and allows an available robot to be selected', () => {
vi.mocked(getConnectableRobots).mockReturnValue([
{ ...mockConnectableRobot, name: 'otherRobot', ip: 'otherIp' },
diff --git a/app/src/organisms/ChooseRobotSlideout/index.tsx b/app/src/organisms/ChooseRobotSlideout/index.tsx
index 4e2e1ed694c..80d9e8aeeee 100644
--- a/app/src/organisms/ChooseRobotSlideout/index.tsx
+++ b/app/src/organisms/ChooseRobotSlideout/index.tsx
@@ -15,12 +15,15 @@ import {
Flex,
Icon,
JUSTIFY_CENTER,
+ JUSTIFY_END,
+ JUSTIFY_FLEX_START,
Link,
OVERFLOW_WRAP_ANYWHERE,
SIZE_1,
SIZE_4,
SPACING,
TYPOGRAPHY,
+ useHoverTooltip,
} from '@opentrons/components'
import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
@@ -37,14 +40,19 @@ import { Banner } from '../../atoms/Banner'
import { Slideout } from '../../atoms/Slideout'
import { MultiSlideout } from '../../atoms/Slideout/MultiSlideout'
import { StyledText } from '../../atoms/text'
+import { ToggleButton } from '../../atoms/buttons'
import { AvailableRobotOption } from './AvailableRobotOption'
+import { InputField } from '../../atoms/InputField'
+import { DropdownMenu } from '../../atoms/MenuList/DropdownMenu'
+import { Tooltip } from '../../atoms/Tooltip'
-import type { RobotType } from '@opentrons/shared-data'
+import type { RobotType, RunTimeParameter } from '@opentrons/shared-data'
import type { SlideoutProps } from '../../atoms/Slideout'
import type { UseCreateRun } from '../../organisms/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol'
import type { State, Dispatch } from '../../redux/types'
import type { Robot } from '../../redux/discovery/types'
import { useFeatureFlag } from '../../redux/config'
+import type { DropdownOption } from '../../atoms/MenuList/DropdownMenu'
export const CARD_OUTLINE_BORDER_STYLE = css`
border-style: ${BORDERS.styleSolid};
@@ -99,6 +107,8 @@ interface ChooseRobotSlideoutProps
robotType: RobotType | null
selectedRobot: Robot | null
setSelectedRobot: (robot: Robot | null) => void
+ runTimeParametersOverrides?: RunTimeParameter[]
+ setRunTimeParametersOverrides?: (parameters: RunTimeParameter[]) => void
isAnalysisError?: boolean
isAnalysisStale?: boolean
showIdleOnly?: boolean
@@ -126,10 +136,14 @@ export function ChooseRobotSlideout(
robotType,
showIdleOnly = false,
multiSlideout,
+ runTimeParametersOverrides,
+ setRunTimeParametersOverrides,
} = props
+
const enableRunTimeParametersFF = useFeatureFlag('enableRunTimeParameters')
const dispatch = useDispatch()
const isScanning = useSelector((state: State) => getScanning(state))
+ const [targetProps, tooltipProps] = useHoverTooltip()
const unhealthyReachableRobots = useSelector((state: State) =>
getReachableRobots(state)
@@ -316,7 +330,168 @@ export function ChooseRobotSlideout(
)
- const pageTwoBody = TODO
+ const runTimeParameters =
+ runTimeParametersOverrides?.map((runtimeParam, index) => {
+ if ('choices' in runtimeParam) {
+ const dropdownOptions = runtimeParam.choices.map(choice => {
+ return { name: choice.displayName, value: choice.value }
+ }) as DropdownOption[]
+ return (
+ {
+ return choice.value === runtimeParam.value
+ }) ?? dropdownOptions[0]
+ }
+ onClick={choice => {
+ const clone = runTimeParametersOverrides.map((parameter, i) => {
+ if (i === index) {
+ return {
+ ...parameter,
+ value:
+ dropdownOptions.find(option => option.value === choice)
+ ?.value ?? parameter.default,
+ }
+ }
+ return parameter
+ })
+ if (setRunTimeParametersOverrides != null) {
+ setRunTimeParametersOverrides(clone)
+ }
+ }}
+ title={runtimeParam.displayName}
+ caption={runtimeParam.description}
+ width="100%"
+ dropdownType="neutral"
+ />
+ )
+ } else if (runtimeParam.type === 'int' || runtimeParam.type === 'float') {
+ const value = runtimeParam.value as number
+ const id = `InputField_${runtimeParam.variableName}_${index.toString()}`
+ return (
+ {
+ const clone = runTimeParametersOverrides.map((parameter, i) => {
+ if (i === index) {
+ return {
+ ...parameter,
+ value:
+ runtimeParam.type === 'int'
+ ? Math.round(e.target.valueAsNumber)
+ : e.target.valueAsNumber,
+ }
+ }
+ return parameter
+ })
+ if (setRunTimeParametersOverrides != null) {
+ setRunTimeParametersOverrides(clone)
+ }
+ }}
+ />
+ )
+ } else if (runtimeParam.type === 'boolean') {
+ return (
+
+
+ {runtimeParam.displayName}
+
+
+ {
+ const clone = runTimeParametersOverrides.map(
+ (parameter, i) => {
+ if (i === index) {
+ return {
+ ...parameter,
+ value: !parameter.value,
+ }
+ }
+ return parameter
+ }
+ )
+ if (setRunTimeParametersOverrides != null) {
+ setRunTimeParametersOverrides(clone)
+ }
+ }}
+ height="0.813rem"
+ label={runtimeParam.value ? t('on') : t('off')}
+ paddingTop={SPACING.spacing2} // manual alignment of SVG with value label
+ />
+
+ {runtimeParam.value ? t('on') : t('off')}
+
+
+
+ {runtimeParam.description}
+
+
+ )
+ }
+ }) ?? null
+
+ const isRestoreDefaultsLinkEnabled =
+ runTimeParametersOverrides?.some(
+ parameter => parameter.value !== parameter.default
+ ) ?? false
+
+ const pageTwoBody =
+ runTimeParametersOverrides != null ? (
+
+
+ {
+ const clone = runTimeParametersOverrides.map(parameter => ({
+ ...parameter,
+ value: parameter.default,
+ }))
+ if (setRunTimeParametersOverrides != null) {
+ setRunTimeParametersOverrides(clone)
+ }
+ }}
+ paddingBottom={SPACING.spacing10}
+ {...targetProps}
+ >
+ {t('restore_defaults')}
+
+ {!isRestoreDefaultsLinkEnabled && (
+
+ {t('no_custom_values')}
+
+ )}
+
+
+ {runTimeParameters}
+
+
+ ) : null
return multiSlideout != null && enableRunTimeParametersFF ? (
)
}
+
+const ENABLED_LINK_CSS = css`
+ ${TYPOGRAPHY.linkPSemiBold}
+ cursor: pointer;
+`
+
+const DISABLED_LINK_CSS = css`
+ ${TYPOGRAPHY.linkPSemiBold}
+ color: ${COLORS.grey50};
+ cursor: default;
+
+ &:hover {
+ color: ${COLORS.grey50};
+ }
+`
diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx
index 4e37afe28b0..f9baea8cf3f 100644
--- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx
+++ b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx
@@ -23,11 +23,11 @@ import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets'
import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis'
import { ChooseRobotSlideout } from '../ChooseRobotSlideout'
import { useCreateRunFromProtocol } from './useCreateRunFromProtocol'
-
import type { StyleProps } from '@opentrons/components'
import type { State } from '../../redux/types'
import type { Robot } from '../../redux/discovery/types'
import type { StoredProtocolData } from '../../redux/protocol-storage'
+import type { RunTimeParameter } from '@opentrons/shared-data'
const _getFileBaseName = (filePath: string): string => {
return filePath.split('/').reverse()[0]
@@ -36,6 +36,7 @@ interface ChooseRobotToRunProtocolSlideoutProps extends StyleProps {
storedProtocolData: StoredProtocolData
onCloseClick: () => void
showSlideout: boolean
+ runTimeParameters?: RunTimeParameter[]
}
export function ChooseRobotToRunProtocolSlideoutComponent(
@@ -61,6 +62,66 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
selectedRobot?.name ?? ''
)
+ // TODO: (nd: 3/20/24) remove stubs and pull parameters from analysis
+
+ const mockRunTimeParameters: RunTimeParameter[] = [
+ {
+ displayName: 'Dry Run',
+ value: false,
+ variableName: 'DRYRUN',
+ description: 'Is this a dry or wet run? Wet is true, dry is false',
+ type: 'boolean',
+ default: false,
+ },
+ {
+ value: 4,
+ displayName: 'Columns of Samples',
+ variableName: 'COLUMNS',
+ description: 'How many columns do you want?',
+ type: 'int',
+ min: 1,
+ max: 14,
+ default: 4,
+ },
+ {
+ value: 6.5,
+ displayName: 'EtoH Volume',
+ variableName: 'ETOH_VOLUME',
+ description: '70% ethanol volume',
+ type: 'float',
+ suffix: 'mL',
+ min: 1.5,
+ max: 10.0,
+ default: 6.5,
+ },
+ {
+ value: 'none',
+ displayName: 'Default Module Offsets',
+ variableName: 'DEFAULT_OFFSETS',
+ description: 'default module offsets for temp, H-S, and none',
+ type: 'str',
+ choices: [
+ {
+ displayName: 'No offsets',
+ value: 'none',
+ },
+ {
+ displayName: 'temp offset',
+ value: '1',
+ },
+ {
+ displayName: 'heater-shaker offset',
+ value: '2',
+ },
+ ],
+ default: 'none',
+ },
+ ]
+ const [
+ runTimeParametersOverrides,
+ setRunTimeParametersOverrides,
+ ] = React.useState(mockRunTimeParameters)
+
const offsetCandidates = useOffsetCandidatesForAnalysis(
mostRecentAnalysis,
selectedRobot?.ip ?? null
@@ -173,6 +234,8 @@ export function ChooseRobotToRunProtocolSlideoutComponent(
title={t('choose_robot_to_run', {
protocol_name: protocolDisplayName,
})}
+ runTimeParametersOverrides={runTimeParametersOverrides}
+ setRunTimeParametersOverrides={setRunTimeParametersOverrides}
footer={
{enableRunTimeParametersFF ? (
diff --git a/app/src/organisms/ProtocolDetails/index.tsx b/app/src/organisms/ProtocolDetails/index.tsx
index b0b1842f9c5..8c5feb33ec9 100644
--- a/app/src/organisms/ProtocolDetails/index.tsx
+++ b/app/src/organisms/ProtocolDetails/index.tsx
@@ -394,6 +394,7 @@ export function ProtocolDetails(
onCloseClick={() => setShowChooseRobotToRunProtocolSlideout(false)}
showSlideout={showChooseRobotToRunProtocolSlideout}
storedProtocolData={props}
+ runTimeParameters={runTimeParameters}
/>