diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx index e260e030a3c99..8268ded111b59 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx @@ -136,7 +136,7 @@ const CreateWorkspacePageExperimental: FC = () => { return; } - if (!initialParamsSentRef.current && response.parameters.length > 0) { + if (!initialParamsSentRef.current && response.parameters?.length > 0) { sendInitialParameters([...response.parameters]); } diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx index 7d22316bfe4f7..bfeecc173f3e3 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx @@ -113,6 +113,27 @@ export const CreateWorkspacePageViewExperimental: FC< setSuggestedName(() => generateWorkspaceName()); }, []); + const autofillByName = Object.fromEntries( + autofillParameters.map((param) => [param.name, param]), + ); + + // Only touched fields are sent to the websocket + // Autofilled parameters are marked as touched since they have been modified + const initialTouched = parameters.reduce( + (touched, parameter) => { + if (autofillByName[parameter.name] !== undefined) { + touched[parameter.name] = true; + } + return touched; + }, + {} as Record, + ); + + // The form parameters values hold the working state of the parameters that will be submitted when creating a workspace + // 1. The form parameter values are initialized from the websocket response when the form is mounted + // 2. Only touched form fields are sent to the websocket, a field is touched if edited by the user or set by autofill + // 3. The websocket response may add or remove parameters, these are added or removed from the form values in the useSyncFormParameters hook + // 4. All existing form parameters are updated to match the websocket response in the useSyncFormParameters hook const form: FormikContextType = useFormik({ initialValues: { @@ -123,6 +144,7 @@ export const CreateWorkspacePageViewExperimental: FC< autofillParameters, ), }, + initialTouched, validationSchema: Yup.object({ name: nameValidator("Workspace Name"), rich_parameter_values: @@ -140,10 +162,6 @@ export const CreateWorkspacePageViewExperimental: FC< }, }); - const autofillByName = Object.fromEntries( - autofillParameters.map((param) => [param.name, param]), - ); - useEffect(() => { if (error) { window.scrollTo(0, 0); @@ -250,6 +268,12 @@ export const CreateWorkspacePageViewExperimental: FC< sendDynamicParamsRequest(parameter, value); }; + useSyncFormParameters({ + parameters, + formValues: form.values.rich_parameter_values ?? [], + setFieldValue: form.setFieldValue, + }); + return ( <>
@@ -568,3 +592,52 @@ const Diagnostics: FC = ({ diagnostics }) => {
); }; + +type UseSyncFormParametersProps = { + parameters: readonly PreviewParameter[]; + formValues: readonly TypesGen.WorkspaceBuildParameter[]; + setFieldValue: ( + field: string, + value: TypesGen.WorkspaceBuildParameter[], + ) => void; +}; + +function useSyncFormParameters({ + parameters, + formValues, + setFieldValue, +}: UseSyncFormParametersProps) { + // Form values only needs to be updated when parameters change + // Keep track of form values in a ref to avoid unnecessary updates to rich_parameter_values + const formValuesRef = useRef(formValues); + + useEffect(() => { + formValuesRef.current = formValues; + }, [formValues]); + + useEffect(() => { + if (!parameters) return; + const currentFormValues = formValuesRef.current; + + const newParameterValues = parameters.map((param) => { + return { + name: param.name, + value: param.value.valid ? param.value.value : "", + }; + }); + + const isChanged = + currentFormValues.length !== newParameterValues.length || + newParameterValues.some( + (p) => + !currentFormValues.find( + (formValue) => + formValue.name === p.name && formValue.value === p.value, + ), + ); + + if (isChanged) { + setFieldValue("rich_parameter_values", newParameterValues); + } + }, [parameters, setFieldValue]); +}