diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index 72113ce8f504b..35c5763c23d25 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -84,6 +84,7 @@ export const DynamicParameter: FC = ({ value={value} onChange={onChange} disabled={disabled} + isPreset={isPreset} /> ) : ( void; disabled?: boolean; id: string; + isPreset?: boolean; } const DebouncedParameterField: FC = ({ @@ -239,6 +241,7 @@ const DebouncedParameterField: FC = ({ onChange, disabled, id, + isPreset, }) => { const [localValue, setLocalValue] = useState( value !== undefined ? value : validValue(parameter.value), @@ -251,19 +254,26 @@ const DebouncedParameterField: FC = ({ // This is necessary in the case of fields being set by preset parameters useEffect(() => { - if (value !== undefined && value !== prevValueRef.current) { + if (isPreset && value !== undefined && value !== prevValueRef.current) { setLocalValue(value); prevValueRef.current = value; } - }, [value]); + }, [value, isPreset]); useEffect(() => { - if (prevDebouncedValueRef.current !== undefined) { + // Only call onChangeEvent if debouncedLocalValue is different from the previously committed value + // and it's not the initial undefined state. + if ( + prevDebouncedValueRef.current !== undefined && + prevDebouncedValueRef.current !== debouncedLocalValue + ) { onChangeEvent(debouncedLocalValue); } + // Update the ref to the current debounced value for the next comparison prevDebouncedValueRef.current = debouncedLocalValue; }, [debouncedLocalValue, onChangeEvent]); + const textareaRef = useRef(null); const resizeTextarea = useEffectEvent(() => { @@ -513,7 +523,9 @@ const ParameterField: FC = ({ max={parameter.validations[0]?.validation_max ?? 100} disabled={disabled} /> - {parameter.value.value} + + {Number.isFinite(Number(value)) ? value : "0"} + ); case "error": diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx index 781f8b12e8c67..e80542f3144da 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx @@ -26,6 +26,7 @@ import { useMutation, useQuery } from "react-query"; import { useNavigate } from "react-router-dom"; import { docs } from "utils/docs"; import { pageTitle } from "utils/page"; +import type { AutofillBuildParameter } from "utils/richParameters"; import { type WorkspacePermissions, workspaceChecks, @@ -39,11 +40,27 @@ const WorkspaceParametersPageExperimental: FC = () => { const navigate = useNavigate(); const experimentalFormContext = useContext(ExperimentalFormContext); + // autofill the form with the workspace build parameters from the latest build + const { + data: latestBuildParameters, + isLoading: latestBuildParametersLoading, + } = useQuery({ + queryKey: ["workspaceBuilds", workspace.latest_build.id, "parameters"], + queryFn: () => API.getWorkspaceBuildParameters(workspace.latest_build.id), + }); + const [latestResponse, setLatestResponse] = useState(null); const wsResponseId = useRef(-1); const ws = useRef(null); const [wsError, setWsError] = useState(null); + const initialParamsSentRef = useRef(false); + + const autofillParameters: AutofillBuildParameter[] = + latestBuildParameters?.map((p) => ({ + ...p, + source: "active_build", + })) ?? []; const sendMessage = useEffectEvent((formValues: Record) => { const request: DynamicParametersRequest = { @@ -57,11 +74,34 @@ const WorkspaceParametersPageExperimental: FC = () => { } }); + // On page load, sends initial workspace build parameters to the websocket. + // This ensures the backend has the form's complete initial state, + // vital for rendering dynamic UI elements dependent on initial parameter values. + const sendInitialParameters = useEffectEvent(() => { + if (initialParamsSentRef.current) return; + if (autofillParameters.length === 0) return; + + const initialParamsToSend: Record = {}; + for (const param of autofillParameters) { + if (param.name && param.value) { + initialParamsToSend[param.name] = param.value; + } + } + if (Object.keys(initialParamsToSend).length === 0) return; + + sendMessage(initialParamsToSend); + initialParamsSentRef.current = true; + }); + const onMessage = useEffectEvent((response: DynamicParametersResponse) => { if (latestResponse && latestResponse?.id >= response.id) { return; } + if (!initialParamsSentRef.current && response.parameters?.length > 0) { + sendInitialParameters(); + } + setLatestResponse(response); }); @@ -149,6 +189,7 @@ const WorkspaceParametersPageExperimental: FC = () => { const error = wsError || updateParameters.error; if ( + latestBuildParametersLoading || !latestResponse || (ws.current && ws.current.readyState === WebSocket.CONNECTING) ) { @@ -202,6 +243,7 @@ const WorkspaceParametersPageExperimental: FC = () => { {sortedParams.length > 0 ? ( = ({ workspace, + autofillParameters, parameters, diagnostics, canChangeVersions, @@ -42,17 +45,32 @@ export const WorkspaceParametersPageViewExperimental: FC< sendMessage, onCancel, }) => { + const autofillByName = Object.fromEntries( + autofillParameters.map((param) => [param.name, param]), + ); + const initialTouched = parameters.reduce( + (touched, parameter) => { + if (autofillByName[parameter.name] !== undefined) { + touched[parameter.name] = true; + } + return touched; + }, + {} as Record, + ); const form = useFormik({ onSubmit, initialValues: { - rich_parameter_values: getInitialParameterValues(parameters), + rich_parameter_values: getInitialParameterValues( + parameters, + autofillParameters, + ), }, + initialTouched, validationSchema: useValidationSchemaForDynamicParameters(parameters), enableReinitialize: false, validateOnChange: true, validateOnBlur: true, }); - // Group parameters by ephemeral status const ephemeralParameters = parameters.filter((p) => p.ephemeral); const standardParameters = parameters.filter((p) => !p.ephemeral); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy