mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
fix: autofill with workspace build parameters from the latest build (#18091)
Set the form parameters using autofill parameters based on the workspace build parameters for the latest build --------- Co-authored-by: Steven Masley <stevenmasley@gmail.com>
This commit is contained in:
@ -84,6 +84,7 @@ export const DynamicParameter: FC<DynamicParameterProps> = ({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
isPreset={isPreset}
|
||||
/>
|
||||
) : (
|
||||
<ParameterField
|
||||
@ -231,6 +232,7 @@ interface DebouncedParameterFieldProps {
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
id: string;
|
||||
isPreset?: boolean;
|
||||
}
|
||||
|
||||
const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
|
||||
@ -239,6 +241,7 @@ const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
|
||||
onChange,
|
||||
disabled,
|
||||
id,
|
||||
isPreset,
|
||||
}) => {
|
||||
const [localValue, setLocalValue] = useState(
|
||||
value !== undefined ? value : validValue(parameter.value),
|
||||
@ -251,19 +254,26 @@ const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
|
||||
|
||||
// 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<HTMLTextAreaElement>(null);
|
||||
|
||||
const resizeTextarea = useEffectEvent(() => {
|
||||
@ -513,7 +523,9 @@ const ParameterField: FC<ParameterFieldProps> = ({
|
||||
max={parameter.validations[0]?.validation_max ?? 100}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className="w-4 font-medium">{parameter.value.value}</span>
|
||||
<span className="w-4 font-medium">
|
||||
{Number.isFinite(Number(value)) ? value : "0"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case "error":
|
||||
|
@ -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<DynamicParametersResponse | null>(null);
|
||||
const wsResponseId = useRef<number>(-1);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const [wsError, setWsError] = useState<Error | null>(null);
|
||||
const initialParamsSentRef = useRef(false);
|
||||
|
||||
const autofillParameters: AutofillBuildParameter[] =
|
||||
latestBuildParameters?.map((p) => ({
|
||||
...p,
|
||||
source: "active_build",
|
||||
})) ?? [];
|
||||
|
||||
const sendMessage = useEffectEvent((formValues: Record<string, string>) => {
|
||||
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<string, string> = {};
|
||||
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 ? (
|
||||
<WorkspaceParametersPageViewExperimental
|
||||
workspace={workspace}
|
||||
autofillParameters={autofillParameters}
|
||||
canChangeVersions={canChangeVersions}
|
||||
parameters={sortedParams}
|
||||
diagnostics={latestResponse.diagnostics}
|
||||
|
@ -16,9 +16,11 @@ import {
|
||||
} from "modules/workspaces/DynamicParameter/DynamicParameter";
|
||||
import type { FC } from "react";
|
||||
import { docs } from "utils/docs";
|
||||
import type { AutofillBuildParameter } from "utils/richParameters";
|
||||
|
||||
type WorkspaceParametersPageViewExperimentalProps = {
|
||||
workspace: Workspace;
|
||||
autofillParameters: AutofillBuildParameter[];
|
||||
parameters: PreviewParameter[];
|
||||
diagnostics: PreviewParameter["diagnostics"];
|
||||
canChangeVersions: boolean;
|
||||
@ -34,6 +36,7 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
WorkspaceParametersPageViewExperimentalProps
|
||||
> = ({
|
||||
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<string, boolean>,
|
||||
);
|
||||
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);
|
||||
|
Reference in New Issue
Block a user