diff --git a/api-client/src/runs/types.ts b/api-client/src/runs/types.ts index 9dccbc700a7..472b8ae0395 100644 --- a/api-client/src/runs/types.ts +++ b/api-client/src/runs/types.ts @@ -130,9 +130,13 @@ export interface LabwareOffsetCreateData { vector: VectorOffset } -export interface RunTimeParameterCreateData { - [key: string]: string | boolean | number -} +type FileRunTimeParameterCreateData = Record + +type ValueRunTimeParameterCreateData = Record + +export type RunTimeParameterCreateData = + | FileRunTimeParameterCreateData + | ValueRunTimeParameterCreateData export interface CommandData { data: RunTimeCommand diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index fafd2c98038..3806753c5a1 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -3,11 +3,14 @@ "both_mounts": "Both Mounts", "choices": "{{count}} choices", "choose_robot_to_run": "Choose Robot to Run\n{{protocol_name}}", + "choose_file": "Choose file", "clear_and_proceed_to_setup": "Clear and proceed to setup", "connect_modules_to_see_controls": "Connect modules to see controls", "connected": "connected", "connection_status": "connection status", "creation_method": "creation method", + "csv_file": "CSV file", + "csv_required": "This protocol requires a CSV to proceed.", "deck_view": "Deck View", "default_value": "Default Value", "delete_protocol_perm": "{{name}} and its run history will be permanently deleted.", @@ -15,6 +18,8 @@ "delete_this_protocol": "Delete this protocol?", "description": "description", "extension_mount": "extension mount", + "file_must_be_csv": "File must be .csv", + "file_required": "File required", "go_to_labware_definition": "Go to labware definition", "gripper_pick_up_count_description": "individual move labware commands that use the gripper.", "gripper_pick_up_count": "Grip Count", @@ -32,6 +37,7 @@ "location": "location", "modules": "modules", "name": "Name", + "n_a": "—", "no_available_robots_found": "No available robots found", "no_custom_values": "No custom values specified", "no_parameters": "No parameters specified in this protocol", @@ -60,6 +66,7 @@ "range": "Range", "read_less": "read less", "read_more": "read more", + "requires_upload": "Requires upload", "restore_defaults": "Restore default values", "right_mount": "right mount", "robot_configuration": "robot configuration", diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index 21a519dc25b..6cea580b4d9 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -222,6 +222,7 @@ "recalibrating_tip_length_not_available": "Recalibrating a tip length is not available once a run has started", "recommended": "Recommended", "required_instrument_calibrations": "required instrument calibrations", + "required": "Required", "required_tip_racks_title": "Required Tip Length Calibrations", "reset_parameter_values_body": "This will discard any changes you have made. All parameters will have their default values.", "reset_parameter_values": "Reset parameter values?", diff --git a/app/src/organisms/ChooseProtocolSlideout/index.tsx b/app/src/organisms/ChooseProtocolSlideout/index.tsx index e67f22c8e3e..1e6163a8b43 100644 --- a/app/src/organisms/ChooseProtocolSlideout/index.tsx +++ b/app/src/organisms/ChooseProtocolSlideout/index.tsx @@ -29,8 +29,10 @@ import { useTooltip, } from '@opentrons/components' import { ApiHostProvider } from '@opentrons/react-api-client' +import { sortRuntimeParameters } from '@opentrons/shared-data' import { useLogger } from '../../logger' +import { useFeatureFlag } from '../../redux/config' import { OPENTRONS_USB } from '../../redux/discovery' import { getStoredProtocols } from '../../redux/protocol-storage' import { appShellRequestor } from '../../redux/shell/remote' @@ -40,15 +42,17 @@ import { ToggleButton } from '../../atoms/buttons' import { InputField } from '../../atoms/InputField' import { DropdownMenu } from '../../atoms/MenuList/DropdownMenu' import { MiniCard } from '../../molecules/MiniCard' +import { UploadInput } from '../../molecules/UploadInput' import { useTrackCreateProtocolRunEvent } from '../Devices/hooks' import { useCreateRunFromProtocol } from '../ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets' import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +import { FileCard } from '../ChooseRobotSlideout/FileCard' +import { getRunTimeParameterValuesForRun } from '../Devices/utils' import { getAnalysisStatus } from '../ProtocolsLanding/utils' -import type { RunTimeParameterCreateData } from '@opentrons/api-client' -import type { RunTimeParameter } from '@opentrons/shared-data' import type { DropdownOption } from '@opentrons/components' +import type { RunTimeParameter } from '@opentrons/shared-data' import type { Robot } from '../../redux/discovery/types' import type { StoredProtocolData } from '../../redux/protocol-storage' import type { State } from '../../redux/types' @@ -100,6 +104,7 @@ export function ChooseProtocolSlideoutComponent( const [currentPage, setCurrentPage] = React.useState(1) const [hasParamError, setHasParamError] = React.useState(false) const [isInputFocused, setIsInputFocused] = React.useState(false) + const enableCsvFile = useFeatureFlag('enableCsvFile') React.useEffect(() => { setRunTimeParametersOverrides( @@ -172,13 +177,7 @@ export function ChooseProtocolSlideoutComponent( definitionUri, })) : [], - runTimeParametersOverrides.reduce( - (acc, param) => - param.value !== param.default - ? { ...acc, [param.variableName]: param.value } - : acc, - {} - ) + getRunTimeParameterValuesForRun(runTimeParametersOverrides) ) const handleProceed: React.MouseEventHandler = () => { if (selectedProtocol != null) { @@ -193,164 +192,271 @@ export function ChooseProtocolSlideoutComponent( } const isRestoreDefaultsLinkEnabled = - runTimeParametersOverrides?.some( - parameter => parameter.value !== parameter.default + runTimeParametersOverrides?.some(parameter => + parameter.type === 'csv_file' + ? true + : parameter.value !== parameter.default ) ?? false const errors: string[] = [] const runTimeParametersInputs = - 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, + runTimeParametersOverrides != null + ? sortRuntimeParameters(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] } - } - return parameter - }) - setRunTimeParametersOverrides(clone) - }} - title={runtimeParam.displayName} - 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()}` - const error = - (Number.isNaN(value) && !isInputFocused) || - value < runtimeParam.min || - value > runtimeParam.max - ? t(`protocol_details:value_out_of_range`, { - min: - runtimeParam.type === 'int' - ? runtimeParam.min - : runtimeParam.min.toFixed(1), - max: - runtimeParam.type === 'int' - ? runtimeParam.max - : runtimeParam.max.toFixed(1), - }) - : null - if (error != null) { - errors.push(error) - } - return ( - { - setIsInputFocused(false) - }} - onFocus={() => { - setIsInputFocused(true) - }} - onChange={e => { - const clone = runTimeParametersOverrides.map((parameter, i) => { - if (i === index) { - return { - ...parameter, - value: - runtimeParam.type === 'int' - ? Math.round(e.target.valueAsNumber) - : e.target.valueAsNumber, + onClick={choice => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + 'choices' in parameter + ) { + return { + ...parameter, + value: + dropdownOptions.find( + option => option.value === choice + )?.value ?? parameter.default, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone) + }} + title={runtimeParam.displayName} + width="100%" + dropdownType="neutral" + tooltipText={runtimeParam.description} + /> + ) + } else if ( + runtimeParam.type === 'int' || + runtimeParam.type === 'float' + ) { + const value = runtimeParam.value as number + const id = `InputField_${runtimeParam.variableName}_${index}` + const error = + (Number.isNaN(value) && !isInputFocused) || + value < runtimeParam.min || + value > runtimeParam.max + ? t(`value_out_of_range`, { + min: + runtimeParam.type === 'int' + ? runtimeParam.min + : runtimeParam.min.toFixed(1), + max: + runtimeParam.type === 'int' + ? runtimeParam.max + : runtimeParam.max.toFixed(1), + }) + : null + if (error != null) { + errors.push(error) + } + return ( + - ) - } else if (runtimeParam.type === 'bool') { - return ( - - - {runtimeParam.displayName} - - - { - const clone = runTimeParametersOverrides.map( - (parameter, i) => { - if (i === index) { + id={id} + error={error} + onBlur={() => { + setIsInputFocused(false) + }} + onFocus={() => { + setIsInputFocused(true) + }} + onChange={e => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + (parameter.type === 'int' || parameter.type === 'float') + ) { return { ...parameter, - value: !parameter.value, + value: + runtimeParam.type === 'int' + ? Math.round(e.target.valueAsNumber) + : e.target.valueAsNumber, } } return parameter - } - ) - setRunTimeParametersOverrides(clone) - }} - height="0.813rem" - label={ - Boolean(runtimeParam.value) - ? t('protocol_details:on') - : t('protocol_details:off') - } - paddingTop={SPACING.spacing2} // manual alignment of SVG with value label - /> - - {Boolean(runtimeParam.value) - ? t('protocol_details:on') - : t('protocol_details:off')} - - - - {runtimeParam.description} - - + }) + setRunTimeParametersOverrides?.(clone) + }} + /> + ) + } else if (runtimeParam.type === 'bool') { + return ( + + + {runtimeParam.displayName} + + + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName && + parameter.type === 'bool' + ) { + return { + ...parameter, + value: !parameter.value, + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + height="0.813rem" + label={ + runtimeParam.value + ? t('protocol_details:on') + : t('protocol_details:off') + } + paddingTop={SPACING.spacing2} // manual alignment of SVG with value label + /> + + {runtimeParam.value + ? t('protocol_details:on') + : t('protocol_details:off')} + + + + {runtimeParam.description} + + + ) + } else if (runtimeParam.type === 'csv_file') { + const error = + runtimeParam.file?.file?.type === 'text/csv' + ? null + : t('protocol_details:file_must_be_csv') + if (error != null) { + errors.push(error) + } + return !enableCsvFile ? null : ( + + + + {t('protocol_details:csv_file')} + + + {t('protocol_details:csv_required')} + + + {runtimeParam.file == null ? ( + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName + ) { + return { + ...parameter, + file: { file: file }, + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + dragAndDropText={ + + + ), + }} + /> + + } + /> + ) : ( + + )} + + ) + } + } ) - } - }) ?? null + : null const resetRunTimeParameters = (): void => { - setRunTimeParametersOverrides( - runTimeParametersOverrides?.map(parameter => ({ - ...parameter, - value: parameter.default, - })) + const clone = runTimeParametersOverrides.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : { ...parameter, value: parameter.default } ) + setRunTimeParametersOverrides(clone as RunTimeParameter[]) } const pageTwoBody = ( diff --git a/app/src/organisms/ChooseRobotSlideout/FileCard.tsx b/app/src/organisms/ChooseRobotSlideout/FileCard.tsx new file mode 100644 index 00000000000..bfd4b862020 --- /dev/null +++ b/app/src/organisms/ChooseRobotSlideout/FileCard.tsx @@ -0,0 +1,95 @@ +import React from 'react' +import { css } from 'styled-components' +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + SPACING, + StyledText, + truncateString, +} from '@opentrons/components' +import type { CsvFileParameter, RunTimeParameter } from '@opentrons/shared-data' + +interface FileCardProps { + error: string | null + fileRunTimeParameter: CsvFileParameter + runTimeParametersOverrides: RunTimeParameter[] + setRunTimeParametersOverrides?: (rtpOverrides: RunTimeParameter[]) => void +} + +export function FileCard(props: FileCardProps): JSX.Element { + const { + error, + fileRunTimeParameter, + runTimeParametersOverrides, + setRunTimeParametersOverrides, + } = props + + return ( + + + + {truncateString(fileRunTimeParameter?.file?.file?.name ?? '', 35, 18)} + + + { + const clone = runTimeParametersOverrides.map((parameter, i) => { + if ( + fileRunTimeParameter.variableName === + parameter.variableName && + parameter.type === 'csv_file' + ) { + return { + ...parameter, + file: null, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone) + }} + > + + + + + {error != null ? ( + + {error} + + ) : null} + + ) +} diff --git a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx b/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx index 2b0c5c3086e..8056ee693ea 100644 --- a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx +++ b/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx @@ -227,14 +227,16 @@ describe('ChooseRobotSlideout', () => { }) screen.getByText(param.displayName) - if (param.type === 'bool') { - screen.getByText(param.description) - } - if (param.type === 'int') { - screen.getByText(`${param.min}-${param.max}`) - } - if (param.type === 'float') { - screen.getByText(`${param.min.toFixed(1)}-${param.max.toFixed(1)}`) + if (!('choices' in param)) { + if (param.type === 'bool') { + screen.getByText(param.description) + } + if (param.type === 'int') { + screen.getByText(`${param.min}-${param.max}`) + } + if (param.type === 'float') { + screen.getByText(`${param.min.toFixed(1)}-${param.max.toFixed(1)}`) + } } }) }) diff --git a/app/src/organisms/ChooseRobotSlideout/index.tsx b/app/src/organisms/ChooseRobotSlideout/index.tsx index c590dccb5a4..ea34ba6fadb 100644 --- a/app/src/organisms/ChooseRobotSlideout/index.tsx +++ b/app/src/organisms/ChooseRobotSlideout/index.tsx @@ -27,7 +27,12 @@ import { useTooltip, } from '@opentrons/components' -import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' +import { + FLEX_ROBOT_TYPE, + OT2_ROBOT_TYPE, + sortRuntimeParameters, +} from '@opentrons/shared-data' +import { useFeatureFlag } from '../../redux/config' import { getConnectableRobots, getReachableRobots, @@ -45,6 +50,8 @@ import { AvailableRobotOption } from './AvailableRobotOption' import { InputField } from '../../atoms/InputField' import { DropdownMenu } from '../../atoms/MenuList/DropdownMenu' import { Tooltip } from '../../atoms/Tooltip' +import { UploadInput } from '../../molecules/UploadInput' +import { FileCard } from './FileCard' import type { RobotType, RunTimeParameter } from '@opentrons/shared-data' import type { SlideoutProps } from '../../atoms/Slideout' @@ -144,6 +151,7 @@ export function ChooseRobotSlideout( setHasParamError, resetRunTimeParameters, } = props + const enableCsvFile = useFeatureFlag('enableCsvFile') const dispatch = useDispatch() const isScanning = useSelector((state: State) => getScanning(state)) @@ -350,166 +358,257 @@ export function ChooseRobotSlideout( const errors: string[] = [] 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, + runTimeParametersOverrides != null + ? sortRuntimeParameters(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] } - } - return parameter - }) - if (setRunTimeParametersOverrides != null) { - setRunTimeParametersOverrides(clone) + onClick={choice => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + 'choices' in parameter + ) { + return { + ...parameter, + value: + dropdownOptions.find( + option => option.value === choice + )?.value ?? parameter.default, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone) + }} + title={runtimeParam.displayName} + width="100%" + dropdownType="neutral" + tooltipText={runtimeParam.description} + /> + ) + } else if ( + runtimeParam.type === 'int' || + runtimeParam.type === 'float' + ) { + const value = runtimeParam.value as number + const id = `InputField_${ + runtimeParam.variableName + }_${index.toString()}` + const error = + (Number.isNaN(value) && !isInputFocused) || + value < runtimeParam.min || + value > runtimeParam.max + ? t(`value_out_of_range`, { + min: + runtimeParam.type === 'int' + ? runtimeParam.min + : runtimeParam.min.toFixed(1), + max: + runtimeParam.type === 'int' + ? runtimeParam.max + : runtimeParam.max.toFixed(1), + }) + : null + if (error != null) { + errors.push(error) } - }} - title={runtimeParam.displayName} - width="100%" - dropdownType="neutral" - tooltipText={runtimeParam.description} - /> - ) - } else if (runtimeParam.type === 'int' || runtimeParam.type === 'float') { - const value = runtimeParam.value as number - const id = `InputField_${runtimeParam.variableName}_${index.toString()}` - const error = - (Number.isNaN(value) && !isInputFocused) || - value < runtimeParam.min || - value > runtimeParam.max - ? t(`value_out_of_range`, { - min: - runtimeParam.type === 'int' - ? runtimeParam.min - : runtimeParam.min.toFixed(1), - max: - runtimeParam.type === 'int' - ? runtimeParam.max - : runtimeParam.max.toFixed(1), - }) - : null - if (error != null) { - errors.push(error) - } - return ( - { - setIsInputFocused(false) - }} - onFocus={() => { - setIsInputFocused(true) - }} - onChange={e => { - const clone = runTimeParametersOverrides.map((parameter, i) => { - if (i === index) { - return { - ...parameter, - value: - runtimeParam.type === 'int' - ? Math.round(e.target.valueAsNumber) - : e.target.valueAsNumber, + return ( + - ) - } else if (runtimeParam.type === 'bool') { - return ( - - - {runtimeParam.displayName} - - - { - const clone = runTimeParametersOverrides.map( - (parameter, i) => { - if (i === index) { + id={id} + error={error} + onBlur={() => { + setIsInputFocused(false) + }} + onFocus={() => { + setIsInputFocused(true) + }} + onChange={e => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + (parameter.type === 'int' || parameter.type === 'float') + ) { return { ...parameter, - value: !parameter.value, + value: + runtimeParam.type === 'int' + ? Math.round(e.target.valueAsNumber) + : e.target.valueAsNumber, } } 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} - - + }) + setRunTimeParametersOverrides?.(clone) + }} + /> + ) + } else if (runtimeParam.type === 'bool') { + return ( + + + {runtimeParam.displayName} + + + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName && + parameter.type === 'bool' + ) { + return { + ...parameter, + value: !parameter.value, + } + } + return parameter + } + ) + 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} + + + ) + } else if (runtimeParam.type === 'csv_file') { + const error = + runtimeParam.file?.file?.type === 'text/csv' + ? null + : t('file_must_be_csv') + if (error != null) { + errors.push(error) + } + return !enableCsvFile ? null : ( + + + + {t('csv_file')} + + {t('csv_required')} + + {runtimeParam.file == null ? ( + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName + ) { + return { + ...parameter, + file: { file: file }, + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + dragAndDropText={ + + , + }} + /> + + } + /> + ) : ( + + )} + + ) + } + } ) - } - }) ?? null - - if (setHasParamError != null) { - setHasParamError(errors.length > 0) - } + : null - const isRestoreDefaultsLinkEnabled = + const hasEmptyRtpFile = runTimeParametersOverrides?.some( - parameter => parameter.value !== parameter.default + runtimeParam => + runtimeParam.type === 'csv_file' && runtimeParam.file == null ) ?? false + setHasParamError?.(errors.length > 0 || hasEmptyRtpFile) + + const isRestoreDefaultsLinkEnabled = + runTimeParametersOverrides?.some(parameter => { + return parameter.type === 'csv_file' + ? parameter.file != null + : parameter.value !== parameter.default + }) ?? false const pageTwoBody = runTimeParametersOverrides != null ? ( diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx index eb6dd3048de..ad383f7647d 100644 --- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx +++ b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx @@ -18,6 +18,7 @@ import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' import { OPENTRONS_USB } from '../../redux/discovery' import { appShellRequestor } from '../../redux/shell/remote' import { useTrackCreateProtocolRunEvent } from '../Devices/hooks' +import { getRunTimeParameterValuesForRun } from '../Devices/utils' import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets' import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { ChooseRobotSlideout } from '../ChooseRobotSlideout' @@ -60,6 +61,7 @@ export function ChooseRobotToRunProtocolSlideoutComponent( ) const runTimeParameters = storedProtocolData.mostRecentAnalysis?.runTimeParameters ?? [] + const [ runTimeParametersOverrides, setRunTimeParametersOverrides, @@ -70,6 +72,16 @@ export function ChooseRobotToRunProtocolSlideoutComponent( mostRecentAnalysis, selectedRobot?.ip ?? null ) + + // TODO (nd: 06/13/2024): send these data files to robot and use returned IDs in RTP overrides + // const dataFilesForProtocol = runTimeParametersOverrides.reduce( + // (acc, parameter) => + // parameter.type === 'csv_file' && parameter.file?.file != null + // ? [...acc, parameter.file.file] + // : acc, + // [] + // ) + const { createRunFromProtocolSource, runCreationError, @@ -110,13 +122,7 @@ export function ChooseRobotToRunProtocolSlideoutComponent( definitionUri, })) : [], - runTimeParametersOverrides.reduce( - (acc, param) => - param.value !== param.default - ? { ...acc, [param.variableName]: param.value } - : acc, - {} - ) + getRunTimeParameterValuesForRun(runTimeParametersOverrides) ) const handleProceed: React.MouseEventHandler = () => { trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' }) @@ -188,12 +194,12 @@ export function ChooseRobotToRunProtocolSlideoutComponent( ) const resetRunTimeParameters = (): void => { - setRunTimeParametersOverrides( - runTimeParametersOverrides?.map(parameter => ({ - ...parameter, - value: parameter.default, - })) + const clone = runTimeParametersOverrides.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : { ...parameter, value: parameter.default } ) + setRunTimeParametersOverrides(clone as RunTimeParameter[]) } return ( diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx index a20e19478a3..a9fa2d8badf 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx @@ -33,21 +33,10 @@ import { Tooltip } from '../../../atoms/Tooltip' import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { useRunStatus } from '../../RunTimeControl/hooks' import { useNotifyRunQuery } from '../../../resources/runs' -import { useFeatureFlag } from '../../../redux/config' import type { RunTimeParameter } from '@opentrons/shared-data' import type { RunStatus } from '@opentrons/api-client' -// ToDo (kk:06/07/2024) this will be removed when be is ready -const mockCsvFileParameter = { - value: 'mock.csv', - displayName: 'My CSV File', - variableName: 'CSVFILE', - description: 'CSV File for a protocol', - type: 'csv_file' as const, - default: 'mock.csv', -} - interface ProtocolRunRuntimeParametersProps { runId: string } @@ -65,18 +54,13 @@ export function ProtocolRunRuntimeParameters({ // because the most recent analysis may not reflect the selected run (e.g. cloning a run // from a historical protocol run from the device details page) const run = useNotifyRunQuery(runId).data - const enableCsvFile = useFeatureFlag('enableCsvFile') const runTimeParameters = (isRunTerminal ? run?.data?.runTimeParameters : mostRecentAnalysis?.runTimeParameters) ?? [] - // ToDo (06/06/2024) this will be removed when be is ready - if (enableCsvFile && !runTimeParameters.includes(mockCsvFileParameter)) { - runTimeParameters.push(mockCsvFileParameter) - } const hasRunTimeParameters = runTimeParameters.length > 0 - const hasCustomRunTimeParameterValues = runTimeParameters.some( - parameter => parameter.value !== parameter.default + const hasCustomRunTimeParameterValues = runTimeParameters.some(parameter => + parameter.type !== 'csv_file' ? parameter.value !== parameter.default : true ) const runActions = run?.data.actions @@ -216,9 +200,12 @@ const StyledTableRowComponent = ( - {formatRunTimeParameterValue(parameter, t)} + {parameter.type === 'csv_file' + ? parameter.file?.file?.name ?? '' + : formatRunTimeParameterValue(parameter, t)} - {parameter.value !== parameter.default ? ( + {parameter.type === 'csv_file' || + parameter.default !== parameter.value ? ( ) => { @@ -176,6 +186,10 @@ describe('ProtocolRunRuntimeParameters', () => { it('should render csv row if a protocol requires a csv', () => { vi.mocked(useFeatureFlag).mockReturnValue(true) + vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ + runTimeParameters: [...mockRunTimeParameterData, mockCsvRtp], + } as CompletedProtocolAnalysis) + render(props) screen.getByText('CSV File') screen.getByText('mock.csv') diff --git a/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts b/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts index 31e4df921b6..5cf38a7f0d6 100644 --- a/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts +++ b/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts @@ -56,8 +56,8 @@ export const parseProtocolRunAnalyticsData = ( ? protocolAnalysis?.runTimeParameters?.length > 0 : false, protocolHasRunTimeParameterCustomValues: - protocolAnalysis?.runTimeParameters?.some( - param => param.value !== param.default + protocolAnalysis?.runTimeParameters?.some(param => + param.type === 'csv_file' ? true : param.value !== param.default ) ?? false, robotType: protocolAnalysis?.robotType != null diff --git a/app/src/organisms/Devices/utils.ts b/app/src/organisms/Devices/utils.ts index 61c133f176b..ff81ac079e9 100644 --- a/app/src/organisms/Devices/utils.ts +++ b/app/src/organisms/Devices/utils.ts @@ -95,11 +95,15 @@ export function getShowPipetteCalibrationWarning( export function getRunTimeParameterValuesForRun( runTimeParameters: RunTimeParameter[] ): RunTimeParameterCreateData { - return runTimeParameters.reduce( - (acc, param) => - param.value !== param.default + return runTimeParameters.reduce((acc, param) => { + if (param.type === 'csv_file') { + // TODO (nd: 06/13/2024) transform file parameter to RunTimeParamterCreateData + // return { ...acc, [param.variableName]: { id: param.file?.id } } + return acc + } else { + return param.value !== param.default ? { ...acc, [param.variableName]: param.value } - : acc, - {} - ) + : acc + } + }, {}) } diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx b/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx index 949d7fba5f6..aae386561b2 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx +++ b/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx @@ -11,11 +11,11 @@ import { import { RadioButton } from '../../atoms/buttons' import { useToaster } from '../ToasterOven' import { ChildNavigation } from '../ChildNavigation' -import type { RunTimeParameter } from '@opentrons/shared-data' +import type { ChoiceParameter } from '@opentrons/shared-data' interface ChooseEnumProps { handleGoBack: () => void - parameter: RunTimeParameter + parameter: ChoiceParameter setParameter: (value: boolean | string | number, variableName: string) => void rawValue: number | string | boolean } @@ -25,7 +25,7 @@ export function ChooseEnum({ parameter, setParameter, rawValue, -}: ChooseEnumProps): JSX.Element { +}: ChooseEnumProps): JSX.Element | null { const { makeSnackbar } = useToaster() const { t } = useTranslation(['protocol_setup', 'shared']) diff --git a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx b/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx index b49151f883b..70d494f1007 100644 --- a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx +++ b/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx @@ -38,11 +38,12 @@ export function ResetValuesModal({ // ToDo (kk:03/18/2024) reset values function will be implemented const handleResetValues = (): void => { - setRunTimeParametersOverrides( - runTimeParametersOverrides.map(param => { - return { ...param, value: param.default } - }) + const clone = runTimeParametersOverrides.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : { ...parameter, value: parameter.default } ) + setRunTimeParametersOverrides(clone as RunTimeParameter[]) handleGoBack() } diff --git a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx index f155779a39b..c0572b1eae0 100644 --- a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx +++ b/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx @@ -1,6 +1,9 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { formatRunTimeParameterValue } from '@opentrons/shared-data' +import { + formatRunTimeParameterValue, + sortRuntimeParameters, +} from '@opentrons/shared-data' import { ALIGN_CENTER, BORDERS, @@ -67,7 +70,7 @@ export function ViewOnlyParameters({ {t('value')} - {parameters.map((parameter, index) => { + {sortRuntimeParameters(parameters).map((parameter, index) => { return ( {formatRunTimeParameterValue(parameter, t)} - {parameter.value !== parameter.default ? ( + {parameter.type === 'csv_file' || + parameter.value !== parameter.default ? ( (null) + ] = React.useState(null) const [ showNumericalInputScreen, setShowNumericalInputScreen, @@ -56,10 +64,13 @@ export function ProtocolSetupParameters({ runTimeParametersOverrides, setRunTimeParametersOverrides, ] = React.useState( - // present defaults rather than last-set value - runTimeParameters.map(param => { - return { ...param, value: param.default } - }) + runTimeParameters.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : // TODO (nd: 06/13/2024) create individual ChoiceParameter types for correct narrowing + // eslint-disable-next-line + ({ ...parameter, value: parameter.default } as ValueRunTimeParameter) + ) ) const updateParameters = ( @@ -72,13 +83,13 @@ export function ProtocolSetupParameters({ } return parameter }) - setRunTimeParametersOverrides(updatedParameters) + setRunTimeParametersOverrides(updatedParameters as RunTimeParameter[]) if (chooseValueScreen && chooseValueScreen.variableName === variableName) { const updatedParameter = updatedParameters.find( parameter => parameter.variableName === variableName ) - if (updatedParameter != null) { - setChooseValueScreen(updatedParameter) + if (updatedParameter != null && 'choices' in updatedParameter) { + setChooseValueScreen(updatedParameter as ChoiceParameter) } } if ( @@ -165,24 +176,35 @@ export function ProtocolSetupParameters({ paddingX={SPACING.spacing40} paddingBottom={SPACING.spacing40} > - {runTimeParametersOverrides.map((parameter, index) => { - return ( - - { - handleSetParameter(parameter) - }} - detail={formatRunTimeParameterValue(parameter, t)} - description={parameter.description} - fontSize="h4" - disabled={startSetup} - /> - - ) - })} + {sortRuntimeParameters(runTimeParametersOverrides).map( + (parameter, index) => { + return ( + + { + handleSetParameter(parameter) + }} + detail={ + parameter.type === 'csv_file' + ? t('required') + : formatRunTimeParameterValue(parameter, t) + } + description={ + parameter.type === 'csv_file' ? null : parameter.description + } + fontSize="h4" + disabled={startSetup} + /> + + ) + } + )} ) diff --git a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts b/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts index ecf4fabcad5..08a4891aa1e 100644 --- a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts +++ b/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts @@ -6,8 +6,9 @@ import { useCreateProtocolAnalysisMutation, } from '@opentrons/react-api-client' import { useNotifyRunQuery } from '../../../resources/runs' +import { getRunTimeParameterValuesForRun } from '../../Devices/utils' -import type { Run, RunTimeParameterCreateData } from '@opentrons/api-client' +import type { Run } from '@opentrons/api-client' interface UseCloneRunResult { cloneRun: () => void @@ -45,12 +46,8 @@ export function useCloneRun( const cloneRun = (): void => { if (runRecord != null) { const { protocolId, labwareOffsets, runTimeParameters } = runRecord.data - const runTimeParameterValues = runTimeParameters.reduce( - (acc, param) => - param.value !== param.default - ? { ...acc, [param.variableName]: param.value } - : acc, - {} + const runTimeParameterValues = getRunTimeParameterValuesForRun( + runTimeParameters ) if (triggerAnalysis && protocolKey != null) { createProtocolAnalysis({ protocolKey, runTimeParameterValues }) diff --git a/app/src/pages/ProtocolDetails/Parameters.tsx b/app/src/pages/ProtocolDetails/Parameters.tsx index 516d64c13cf..ab81d463a2e 100644 --- a/app/src/pages/ProtocolDetails/Parameters.tsx +++ b/app/src/pages/ProtocolDetails/Parameters.tsx @@ -5,6 +5,7 @@ import { formatRunTimeParameterDefaultValue, formatRunTimeParameterMinMax, orderRuntimeParameterRangeOptions, + sortRuntimeParameters, } from '@opentrons/shared-data' import { BORDERS, @@ -82,6 +83,9 @@ export const Parameters = (props: { protocolId: string }): JSX.Element => { case 'str': { return range ?? t('num_choices', { num: numChoices }) } + case 'csv_file': { + return t('n_a') + } default: // Should never hit this case return '' @@ -110,7 +114,7 @@ export const Parameters = (props: { protocolId: string }): JSX.Element => { - {runTimeParameters.map((parameter, index) => { + {sortRuntimeParameters(runTimeParameters).map((parameter, index) => { return ( @@ -120,7 +124,9 @@ export const Parameters = (props: { protocolId: string }): JSX.Element => { - {formatRunTimeParameterDefaultValue(parameter, t)} + {parameter.type === 'csv_file' + ? t('file_required') + : formatRunTimeParameterDefaultValue(parameter, t)} diff --git a/app/src/pages/ProtocolSetup/index.tsx b/app/src/pages/ProtocolSetup/index.tsx index 94a52c7ec4f..914d2990100 100644 --- a/app/src/pages/ProtocolSetup/index.tsx +++ b/app/src/pages/ProtocolSetup/index.tsx @@ -115,9 +115,11 @@ interface ProtocolSetupStepProps { // display the reason the setup step is disabled disabledReason?: string | null // optional description - description?: string - // optional removal of the icon - hasIcon?: boolean + description?: string | null + // optional removal of the left icon + hasLeftIcon?: boolean + // optional removal of the right icon + hasRightIcon?: boolean // optional enlarge the font size fontSize?: string } @@ -131,7 +133,8 @@ export function ProtocolSetupStep({ disabled = false, disabledReason, description, - hasIcon = true, + hasRightIcon = true, + hasLeftIcon = true, fontSize = 'p', }: ProtocolSetupStepProps): JSX.Element { const backgroundColorByStepStatus = { @@ -193,7 +196,8 @@ export function ProtocolSetupStep({ {status !== 'general' && !disabled && status !== 'inform' && - !disabled ? ( + !disabled && + hasLeftIcon ? ( {title} - - {description} - + {description != null ? ( + + {description} + + ) : null} - {disabled || !hasIcon ? null : ( + {disabled || !hasRightIcon ? null : ( 0 const hasCustomRunTimeParameters = runTimeParameters.some( - parameter => parameter.value !== parameter.default + parameter => + parameter.type === 'csv_file' || parameter.value !== parameter.default ) const [ diff --git a/components/src/molecules/ParametersTable/index.tsx b/components/src/molecules/ParametersTable/index.tsx index 5ae0d36d550..0e1f0a43719 100644 --- a/components/src/molecules/ParametersTable/index.tsx +++ b/components/src/molecules/ParametersTable/index.tsx @@ -7,11 +7,12 @@ import { } from '@opentrons/shared-data' import { BORDERS, COLORS } from '../../helix-design-system' import { SPACING, TYPOGRAPHY } from '../../ui-style-constants/index' +import { Chip } from '../../atoms/Chip' import { StyledText } from '../../atoms/StyledText' import { Tooltip, useHoverTooltip } from '../../tooltips' import { Icon } from '../../icons' import { Flex } from '../../primitives' -import { DISPLAY_INLINE } from '../../styles' +import { DISPLAY_INLINE, FLEX_MAX_CONTENT } from '../../styles' import type { RunTimeParameter } from '@opentrons/shared-data' @@ -65,32 +66,44 @@ export function ParametersTable({ - {runTimeParameters.map((parameter: RunTimeParameter, index: number) => { - return ( - - - - - {formatRunTimeParameterDefaultValue(parameter, t)} - - - - {formatRange(parameter)} - - + {runTimeParameters + .sort((a, b) => + a.type === 'csv_file' && b.type !== 'csv_file' ? -1 : 0 ) - })} + .map((parameter: RunTimeParameter, index: number) => { + const isLast = index === runTimeParameters.length - 1 + return ( + + + + {parameter.type === 'csv_file' ? ( + + ) : ( + + {formatRunTimeParameterDefaultValue(parameter, t)} + + )} + + + + {parameter.type === 'csv_file' + ? t('n_a') + : formatRange(parameter)} + + + + ) + })} ) diff --git a/shared-data/js/helpers/__tests__/formatRunTimeParameterDefaultValue.test.ts b/shared-data/js/helpers/__tests__/formatRunTimeParameterDefaultValue.test.ts index d83239e3ec9..48b75488722 100644 --- a/shared-data/js/helpers/__tests__/formatRunTimeParameterDefaultValue.test.ts +++ b/shared-data/js/helpers/__tests__/formatRunTimeParameterDefaultValue.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from 'vitest' import { formatRunTimeParameterDefaultValue } from '../formatRunTimeParameterDefaultValue' -import type { RunTimeParameter } from '../../types' +import type { ValueRunTimeParameter } from '../../types' const capitalizeFirstLetter = (str: string): string => { return str.charAt(0).toUpperCase() + str.slice(1) @@ -21,7 +21,7 @@ describe('formatRunTimeParameterDefaultValue', () => { max: 10, default: 6, suffix: 'samples', - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('6 samples') }) @@ -37,7 +37,7 @@ describe('formatRunTimeParameterDefaultValue', () => { min: 1.5, max: 10.0, default: 6.5, - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('6.5 mL') }) @@ -60,7 +60,7 @@ describe('formatRunTimeParameterDefaultValue', () => { }, ], default: 'left', - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('Left') }) @@ -86,7 +86,7 @@ describe('formatRunTimeParameterDefaultValue', () => { }, ], default: 5, - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('5 mL') }) @@ -112,7 +112,7 @@ describe('formatRunTimeParameterDefaultValue', () => { }, ], default: 5.0, - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('5 mL') }) @@ -125,7 +125,7 @@ describe('formatRunTimeParameterDefaultValue', () => { description: 'deactivate temperature on the module', type: 'bool', default: true, - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('On') }) @@ -138,7 +138,7 @@ describe('formatRunTimeParameterDefaultValue', () => { description: 'Is this a dry or wet run? Wet is true, dry is false', type: 'bool', default: false, - } as RunTimeParameter + } as ValueRunTimeParameter const result = formatRunTimeParameterDefaultValue(mockData, mockTFunction) expect(result).toEqual('Off') }) diff --git a/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts b/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts index 7cdbf25c2e6..a722fc84b65 100644 --- a/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts +++ b/shared-data/js/helpers/__tests__/formatRunTimeParameterValue.test.ts @@ -144,12 +144,11 @@ describe('utils-formatRunTimeParameterDefaultValue', () => { it('should return value when type is csv', () => { const mockData = { - value: 'mock.csv', + file: { id: 'test', file: { name: 'mock.csv' } as File }, displayName: 'My CSV File', variableName: 'CSVFILE', description: 'CSV File for a protocol', type: 'csv_file' as const, - default: 'mock.csv', } as RunTimeParameter const result = formatRunTimeParameterValue(mockData, mockTFunction) expect(result).toEqual('mock.csv') diff --git a/shared-data/js/helpers/__tests__/sortRunTimeParameters.test.ts b/shared-data/js/helpers/__tests__/sortRunTimeParameters.test.ts index 4841ea24713..4b2d11aae59 100644 --- a/shared-data/js/helpers/__tests__/sortRunTimeParameters.test.ts +++ b/shared-data/js/helpers/__tests__/sortRunTimeParameters.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest' import { sortRuntimeParameters } from '../sortRunTimeParameters' -import type { RunTimeParameter } from '../..' +import type { CsvFileParameter, RunTimeParameter } from '../..' const mockRunTimeParameters: RunTimeParameter[] = [ { @@ -57,13 +57,12 @@ const mockRunTimeParameters: RunTimeParameter[] = [ }, ] -const mockCsvFileParameter: RunTimeParameter = { - value: 'mock.csv', +const mockCsvFileParameter: CsvFileParameter = { + file: { id: 'test', file: { name: 'mock.csv' } as File }, displayName: 'My CSV File', variableName: 'CSVFILE', description: 'CSV File for a protocol', type: 'csv_file' as const, - default: 'mock.csv', } describe('sortRuntimeParameters', () => { diff --git a/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts b/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts index 3ac5cda5bfa..22b86e60405 100644 --- a/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts +++ b/shared-data/js/helpers/formatRunTimeParameterDefaultValue.ts @@ -1,4 +1,4 @@ -import type { RunTimeParameter } from '../types' +import type { ValueRunTimeParameter } from '../types' /** * Formats the runtime parameter's default value. @@ -11,7 +11,7 @@ import type { RunTimeParameter } from '../types' */ export const formatRunTimeParameterDefaultValue = ( - runTimeParameter: RunTimeParameter, + runTimeParameter: ValueRunTimeParameter, t?: any ): string => { const { type, default: defaultValue } = runTimeParameter diff --git a/shared-data/js/helpers/formatRunTimeParameterValue.ts b/shared-data/js/helpers/formatRunTimeParameterValue.ts index 9ca0eac1f02..92ff97b99e0 100644 --- a/shared-data/js/helpers/formatRunTimeParameterValue.ts +++ b/shared-data/js/helpers/formatRunTimeParameterValue.ts @@ -3,7 +3,7 @@ import type { RunTimeParameter } from '../types' /** * Formats the runtime parameter value. * - * @param {RunTimeParameter} runTimeParameter - The runtime parameter to be formatted. + * @param {ValueRunTimeParameter} runTimeParameter - The value runtime parameter to be formatted. * @param {Function} t - A function for localization. * * @returns {string} The formatted runtime parameter value. @@ -14,7 +14,11 @@ export const formatRunTimeParameterValue = ( runTimeParameter: RunTimeParameter, t: any ): string => { - const { type, value } = runTimeParameter + const { type } = runTimeParameter + const value = + runTimeParameter.type === 'csv_file' + ? runTimeParameter.file?.file?.name ?? '' + : runTimeParameter.value const suffix = 'suffix' in runTimeParameter && runTimeParameter.suffix != null ? runTimeParameter.suffix diff --git a/shared-data/js/helpers/sortRunTimeParameters.ts b/shared-data/js/helpers/sortRunTimeParameters.ts index 8d8c5bad9d3..6801b7e029a 100644 --- a/shared-data/js/helpers/sortRunTimeParameters.ts +++ b/shared-data/js/helpers/sortRunTimeParameters.ts @@ -10,14 +10,7 @@ import type { RunTimeParameter } from '..' export const sortRuntimeParameters = ( runTimeParameters: RunTimeParameter[] ): RunTimeParameter[] => { - const copyRunTimeParameters = [...runTimeParameters] - const csvIndex = copyRunTimeParameters.findIndex( - param => param.type === 'csv_file' + return [...runTimeParameters].sort((a, b) => + a.type === 'csv_file' && b.type !== 'csv_file' ? -1 : 0 ) - if (csvIndex !== -1) { - const csvParam = copyRunTimeParameters.splice(csvIndex, 1)[0] - copyRunTimeParameters.unshift(csvParam) - return copyRunTimeParameters - } - return runTimeParameters } diff --git a/shared-data/js/types.ts b/shared-data/js/types.ts index 0afed5ea851..5da690f2fb3 100644 --- a/shared-data/js/types.ts +++ b/shared-data/js/types.ts @@ -621,6 +621,7 @@ export interface NumberParameter extends BaseRunTimeParameter { min: number max: number default: number + value: number } export interface Choice { @@ -628,40 +629,38 @@ export interface Choice { value: number | boolean | string } -interface ChoiceParameter extends BaseRunTimeParameter { - type: RunTimeParameterType +export interface ChoiceParameter extends BaseRunTimeParameter { + type: NumberParameterType | BooleanParameterType | StringParameterType choices: Choice[] default: number | boolean | string + value: number | boolean | string } interface BooleanParameter extends BaseRunTimeParameter { type: BooleanParameterType default: boolean + value: boolean } -interface CsvFileParameter extends BaseRunTimeParameter { +export interface CsvFileParameter extends BaseRunTimeParameter { type: CsvFileParameterType - default: string + file?: { id?: string; file?: File | null } | null } type NumberParameterType = 'int' | 'float' type BooleanParameterType = 'bool' type StringParameterType = 'str' type CsvFileParameterType = 'csv_file' -type RunTimeParameterType = - | NumberParameter - | BooleanParameterType - | StringParameterType - | CsvFileParameterType interface BaseRunTimeParameter { displayName: string variableName: string description: string - value: number | boolean | string suffix?: string } +export type ValueRunTimeParameter = Exclude + export type RunTimeParameter = | BooleanParameter | ChoiceParameter