diff --git a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts index e177ce6a1..bbb844ffb 100644 --- a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts @@ -14,18 +14,26 @@ import { KEY_SEPARATOR } from '../constants'; import type { InputsType } from '~/routes/configuration'; -import type { FormObjectValue, FormValue } from '../types'; +import type { Restrictions, FormObjectValue, FormValue, ArrayRestrictions } from '../types'; +import { isArrayRestrictions, isObjectRestrictions } from '../types/helpers'; /** * Convert flat form values back to nested configuration object format. * @param {InputsType} formValues - The flat form values with prefixed keys. + * @param {Restrictions} restrictions - The object with restrictions. + * Used to determine if the current value is an object or an array * @param {string} prefix - The prefix to remove from keys (e.g., '/configuration'). * @returns {FormValue} The nested configuration object. */ export const convertFormValuesToConfigObject = ( formValues: InputsType, + restrictions: Restrictions | undefined, prefix: string, ): FormValue => { + if (restrictions === undefined) { + throw new Error('Missing restrictions parameter'); + } + const result: FormValue = {}; for (const [key, value] of Object.entries(formValues)) { @@ -48,17 +56,27 @@ export const convertFormValuesToConfigObject = ( continue; } - let current = result; + let currentValue = result; // pointer for currentValue place in the configuration + let currentRestrictions = restrictions; // pointer for currentRestrictions for (let i = 0; i < keys.length - 1; i++) { const currentKey = keys[i]; - if (!(currentKey in current) || typeof current[currentKey] !== 'object') { - current[currentKey] = {}; + currentRestrictions = isObjectRestrictions(currentRestrictions) + ? currentRestrictions[currentKey] + : (currentRestrictions as ArrayRestrictions)[0][Number(currentKey)]; + + if (!(currentKey in currentValue)) { + // we need to create next level of nesting + if (isObjectRestrictions(currentRestrictions)) { + currentValue[currentKey] = {}; + } else if (isArrayRestrictions(currentRestrictions)) { + currentValue[currentKey] = Array(currentRestrictions[0].length); + } } - current = current[currentKey] as FormObjectValue; + currentValue = currentValue[currentKey] as FormObjectValue; } const finalKey = keys[keys.length - 1]; - current[finalKey] = value.toString(); + currentValue[finalKey] = value; } return result; diff --git a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts index ba4c60884..437cb0e38 100644 --- a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts @@ -31,6 +31,12 @@ export const getDefaultValuesFromConfigObject = ( } if (isPrimitiveValue(val)) { + if (val === 'true') { + return { [prefix]: true }; + } + if (val === 'false') { + return { [prefix]: false }; + } return { [prefix]: val }; } @@ -41,5 +47,14 @@ export const getDefaultValuesFromConfigObject = ( result = { ...result, ...getDefaultValuesFromConfigObject(value, newPrefix) }; } + // this is an exception where the empty object / empty array is the leaf + // of the Configuration Form tree, because it is empty + // however we still need to render that in the UI, so this bit of info needs to be present + if (entries.length === 0) { + const emptyPrefix = `${prefix}${KEY_SEPARATOR}`; + // bypass typescript since this is an exception from the usual logic of the application + result = { [emptyPrefix]: val as unknown as string }; + } + return result; }; diff --git a/Configuration/webapp/app/hooks/useConfigurationForm.tsx b/Configuration/webapp/app/hooks/useConfigurationForm.tsx index a898d24ee..aa46e693b 100644 --- a/Configuration/webapp/app/hooks/useConfigurationForm.tsx +++ b/Configuration/webapp/app/hooks/useConfigurationForm.tsx @@ -18,7 +18,7 @@ import { useForm, type KeepStateOptions, type SubmitHandler } from 'react-hook-f import { getDefaultValuesFromConfigObject } from '~/components/form/utils/getDefaultValuesFromConfigObject'; import { convertFormValuesToConfigObject } from '~/components/form/utils/convertFormValuesToConfigObject'; import { useLocation } from 'react-router'; -import type { FormValue } from '~/components/form/types'; +import type { FormValue, Restrictions } from '~/components/form/types'; import { useConfigurationMutation } from '~/api/mutations/useConfigurationMutation'; const RESET_PROPS: KeepStateOptions = { keepDirty: false }; @@ -34,9 +34,11 @@ const RESET_PROPS: KeepStateOptions = { keepDirty: false }; export const useConfigurationForm = ({ configuration, configurationName, + configurationRestrictions, }: { configuration: FormValue | undefined; configurationName: string; + configurationRestrictions: Restrictions | undefined; }) => { const { pathname } = useLocation(); const mutation = useConfigurationMutation(configurationName); @@ -51,7 +53,11 @@ export const useConfigurationForm = ({ }); const onSubmit: SubmitHandler = (data) => { - const configurationData = convertFormValuesToConfigObject(data, pathname); + const configurationData = convertFormValuesToConfigObject( + data, + configurationRestrictions, + pathname, + ); mutation.mutate(configurationData); }; diff --git a/Configuration/webapp/app/routes/configuration.tsx b/Configuration/webapp/app/routes/configuration.tsx index 2ed155cc6..4a625622f 100644 --- a/Configuration/webapp/app/routes/configuration.tsx +++ b/Configuration/webapp/app/routes/configuration.tsx @@ -44,6 +44,7 @@ const ConfigurationPage = () => { } = useConfigurationForm({ configuration, configurationName, + configurationRestrictions, }); const { showModal, handleProceed, handleSaveAndProceed, handleCancel } = useUnsavedChangesBlocker(