Skip to content

Commit 177bda3

Browse files
jaaydenhEmyrk
andauthored
fix: autofill with workspace build parameters from the latest build (coder#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>
1 parent e4648b6 commit 177bda3

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const DynamicParameter: FC<DynamicParameterProps> = ({
8484
value={value}
8585
onChange={onChange}
8686
disabled={disabled}
87+
isPreset={isPreset}
8788
/>
8889
) : (
8990
<ParameterField
@@ -231,6 +232,7 @@ interface DebouncedParameterFieldProps {
231232
onChange: (value: string) => void;
232233
disabled?: boolean;
233234
id: string;
235+
isPreset?: boolean;
234236
}
235237

236238
const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
@@ -239,6 +241,7 @@ const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
239241
onChange,
240242
disabled,
241243
id,
244+
isPreset,
242245
}) => {
243246
const [localValue, setLocalValue] = useState(
244247
value !== undefined ? value : validValue(parameter.value),
@@ -251,19 +254,26 @@ const DebouncedParameterField: FC<DebouncedParameterFieldProps> = ({
251254

252255
// This is necessary in the case of fields being set by preset parameters
253256
useEffect(() => {
254-
if (value !== undefined && value !== prevValueRef.current) {
257+
if (isPreset && value !== undefined && value !== prevValueRef.current) {
255258
setLocalValue(value);
256259
prevValueRef.current = value;
257260
}
258-
}, [value]);
261+
}, [value, isPreset]);
259262

260263
useEffect(() => {
261-
if (prevDebouncedValueRef.current !== undefined) {
264+
// Only call onChangeEvent if debouncedLocalValue is different from the previously committed value
265+
// and it's not the initial undefined state.
266+
if (
267+
prevDebouncedValueRef.current !== undefined &&
268+
prevDebouncedValueRef.current !== debouncedLocalValue
269+
) {
262270
onChangeEvent(debouncedLocalValue);
263271
}
264272

273+
// Update the ref to the current debounced value for the next comparison
265274
prevDebouncedValueRef.current = debouncedLocalValue;
266275
}, [debouncedLocalValue, onChangeEvent]);
276+
267277
const textareaRef = useRef<HTMLTextAreaElement>(null);
268278

269279
const resizeTextarea = useEffectEvent(() => {
@@ -513,7 +523,9 @@ const ParameterField: FC<ParameterFieldProps> = ({
513523
max={parameter.validations[0]?.validation_max ?? 100}
514524
disabled={disabled}
515525
/>
516-
<span className="w-4 font-medium">{parameter.value.value}</span>
526+
<span className="w-4 font-medium">
527+
{Number.isFinite(Number(value)) ? value : "0"}
528+
</span>
517529
</div>
518530
);
519531
case "error":

site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { useMutation, useQuery } from "react-query";
2626
import { useNavigate } from "react-router-dom";
2727
import { docs } from "utils/docs";
2828
import { pageTitle } from "utils/page";
29+
import type { AutofillBuildParameter } from "utils/richParameters";
2930
import {
3031
type WorkspacePermissions,
3132
workspaceChecks,
@@ -39,11 +40,27 @@ const WorkspaceParametersPageExperimental: FC = () => {
3940
const navigate = useNavigate();
4041
const experimentalFormContext = useContext(ExperimentalFormContext);
4142

43+
// autofill the form with the workspace build parameters from the latest build
44+
const {
45+
data: latestBuildParameters,
46+
isLoading: latestBuildParametersLoading,
47+
} = useQuery({
48+
queryKey: ["workspaceBuilds", workspace.latest_build.id, "parameters"],
49+
queryFn: () => API.getWorkspaceBuildParameters(workspace.latest_build.id),
50+
});
51+
4252
const [latestResponse, setLatestResponse] =
4353
useState<DynamicParametersResponse | null>(null);
4454
const wsResponseId = useRef<number>(-1);
4555
const ws = useRef<WebSocket | null>(null);
4656
const [wsError, setWsError] = useState<Error | null>(null);
57+
const initialParamsSentRef = useRef(false);
58+
59+
const autofillParameters: AutofillBuildParameter[] =
60+
latestBuildParameters?.map((p) => ({
61+
...p,
62+
source: "active_build",
63+
})) ?? [];
4764

4865
const sendMessage = useEffectEvent((formValues: Record<string, string>) => {
4966
const request: DynamicParametersRequest = {
@@ -57,11 +74,34 @@ const WorkspaceParametersPageExperimental: FC = () => {
5774
}
5875
});
5976

77+
// On page load, sends initial workspace build parameters to the websocket.
78+
// This ensures the backend has the form's complete initial state,
79+
// vital for rendering dynamic UI elements dependent on initial parameter values.
80+
const sendInitialParameters = useEffectEvent(() => {
81+
if (initialParamsSentRef.current) return;
82+
if (autofillParameters.length === 0) return;
83+
84+
const initialParamsToSend: Record<string, string> = {};
85+
for (const param of autofillParameters) {
86+
if (param.name && param.value) {
87+
initialParamsToSend[param.name] = param.value;
88+
}
89+
}
90+
if (Object.keys(initialParamsToSend).length === 0) return;
91+
92+
sendMessage(initialParamsToSend);
93+
initialParamsSentRef.current = true;
94+
});
95+
6096
const onMessage = useEffectEvent((response: DynamicParametersResponse) => {
6197
if (latestResponse && latestResponse?.id >= response.id) {
6298
return;
6399
}
64100

101+
if (!initialParamsSentRef.current && response.parameters?.length > 0) {
102+
sendInitialParameters();
103+
}
104+
65105
setLatestResponse(response);
66106
});
67107

@@ -149,6 +189,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
149189
const error = wsError || updateParameters.error;
150190

151191
if (
192+
latestBuildParametersLoading ||
152193
!latestResponse ||
153194
(ws.current && ws.current.readyState === WebSocket.CONNECTING)
154195
) {
@@ -202,6 +243,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
202243
{sortedParams.length > 0 ? (
203244
<WorkspaceParametersPageViewExperimental
204245
workspace={workspace}
246+
autofillParameters={autofillParameters}
205247
canChangeVersions={canChangeVersions}
206248
parameters={sortedParams}
207249
diagnostics={latestResponse.diagnostics}

site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import {
1616
} from "modules/workspaces/DynamicParameter/DynamicParameter";
1717
import type { FC } from "react";
1818
import { docs } from "utils/docs";
19+
import type { AutofillBuildParameter } from "utils/richParameters";
1920

2021
type WorkspaceParametersPageViewExperimentalProps = {
2122
workspace: Workspace;
23+
autofillParameters: AutofillBuildParameter[];
2224
parameters: PreviewParameter[];
2325
diagnostics: PreviewParameter["diagnostics"];
2426
canChangeVersions: boolean;
@@ -34,6 +36,7 @@ export const WorkspaceParametersPageViewExperimental: FC<
3436
WorkspaceParametersPageViewExperimentalProps
3537
> = ({
3638
workspace,
39+
autofillParameters,
3740
parameters,
3841
diagnostics,
3942
canChangeVersions,
@@ -42,17 +45,32 @@ export const WorkspaceParametersPageViewExperimental: FC<
4245
sendMessage,
4346
onCancel,
4447
}) => {
48+
const autofillByName = Object.fromEntries(
49+
autofillParameters.map((param) => [param.name, param]),
50+
);
51+
const initialTouched = parameters.reduce(
52+
(touched, parameter) => {
53+
if (autofillByName[parameter.name] !== undefined) {
54+
touched[parameter.name] = true;
55+
}
56+
return touched;
57+
},
58+
{} as Record<string, boolean>,
59+
);
4560
const form = useFormik({
4661
onSubmit,
4762
initialValues: {
48-
rich_parameter_values: getInitialParameterValues(parameters),
63+
rich_parameter_values: getInitialParameterValues(
64+
parameters,
65+
autofillParameters,
66+
),
4967
},
68+
initialTouched,
5069
validationSchema: useValidationSchemaForDynamicParameters(parameters),
5170
enableReinitialize: false,
5271
validateOnChange: true,
5372
validateOnBlur: true,
5473
});
55-
5674
// Group parameters by ephemeral status
5775
const ephemeralParameters = parameters.filter((p) => p.ephemeral);
5876
const standardParameters = parameters.filter((p) => !p.ephemeral);

0 commit comments

Comments
 (0)