fix: update default value handling for dynamic defaults (#17609)

resolves coder/preview#102
This commit is contained in:
Jaayden Halko
2025-05-06 11:40:31 +01:00
committed by GitHub
parent 4587082fcf
commit ec003b7cf9
2 changed files with 68 additions and 41 deletions

View File

@ -32,7 +32,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "components/Tooltip/Tooltip"; } from "components/Tooltip/Tooltip";
import { Info, Settings, TriangleAlert } from "lucide-react"; import { Info, Settings, TriangleAlert } from "lucide-react";
import { type FC, useId } from "react"; import { type FC, useEffect, useId, useState } from "react";
import type { AutofillBuildParameter } from "utils/richParameters"; import type { AutofillBuildParameter } from "utils/richParameters";
import * as Yup from "yup"; import * as Yup from "yup";
@ -164,14 +164,18 @@ const ParameterField: FC<ParameterFieldProps> = ({
id, id,
}) => { }) => {
const value = validValue(parameter.value); const value = validValue(parameter.value);
const defaultValue = validValue(parameter.default_value); const [localValue, setLocalValue] = useState(value);
useEffect(() => {
setLocalValue(value);
}, [value]);
switch (parameter.form_type) { switch (parameter.form_type) {
case "dropdown": case "dropdown":
return ( return (
<Select <Select
onValueChange={onChange} onValueChange={onChange}
defaultValue={defaultValue} value={value}
disabled={disabled} disabled={disabled}
required={parameter.required} required={parameter.required}
> >
@ -194,31 +198,48 @@ const ParameterField: FC<ParameterFieldProps> = ({
); );
case "multi-select": { case "multi-select": {
let values: string[] = [];
if (value) {
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
values = parsed;
}
} catch (e) {
console.error(
"Error parsing parameter value with form_type multi-select",
e,
);
}
}
// Map parameter options to MultiSelectCombobox options format // Map parameter options to MultiSelectCombobox options format
const comboboxOptions: Option[] = parameter.options.map((opt) => ({ const options: Option[] = parameter.options.map((opt) => ({
value: opt.value.value, value: opt.value.value,
label: opt.name, label: opt.name,
disable: false, disable: false,
})); }));
const defaultOptions: Option[] = JSON.parse(defaultValue).map( const optionMap = new Map(
(val: string) => { parameter.options.map((opt) => [opt.value.value, opt.name]),
const option = parameter.options.find((o) => o.value.value === val);
return {
value: val,
label: option?.name || val,
disable: false,
};
},
); );
const selectedOptions: Option[] = values.map((val) => {
return {
value: val,
label: optionMap.get(val) || val,
disable: false,
};
});
return ( return (
<MultiSelectCombobox <MultiSelectCombobox
inputProps={{ inputProps={{
id: `${id}-${parameter.name}`, id: `${id}-${parameter.name}`,
}} }}
options={comboboxOptions} options={options}
defaultOptions={defaultOptions} defaultOptions={selectedOptions}
onChange={(newValues) => { onChange={(newValues) => {
const values = newValues.map((option) => option.value); const values = newValues.map((option) => option.value);
onChange(JSON.stringify(values)); onChange(JSON.stringify(values));
@ -251,11 +272,7 @@ const ParameterField: FC<ParameterFieldProps> = ({
case "radio": case "radio":
return ( return (
<RadioGroup <RadioGroup onValueChange={onChange} disabled={disabled} value={value}>
onValueChange={onChange}
disabled={disabled}
defaultValue={defaultValue}
>
{parameter.options.map((option) => ( {parameter.options.map((option) => (
<div <div
key={option.value.value} key={option.value.value}
@ -282,7 +299,6 @@ const ParameterField: FC<ParameterFieldProps> = ({
<Checkbox <Checkbox
id={parameter.name} id={parameter.name}
checked={value === "true"} checked={value === "true"}
defaultChecked={defaultValue === "true"} // TODO: defaultChecked is always overridden by checked
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
onChange(checked ? "true" : "false"); onChange(checked ? "true" : "false");
}} }}
@ -299,14 +315,11 @@ const ParameterField: FC<ParameterFieldProps> = ({
<div className="flex flex-row items-baseline gap-3"> <div className="flex flex-row items-baseline gap-3">
<Slider <Slider
className="mt-2" className="mt-2"
defaultValue={[ value={[Number(localValue ?? 0)]}
Number( onValueChange={([value]) => {
parameter.default_value.valid setLocalValue(value.toString());
? parameter.default_value.value onChange(value.toString());
: 0, }}
),
]}
onValueChange={([value]) => onChange(value.toString())}
min={parameter.validations[0]?.validation_min ?? 0} min={parameter.validations[0]?.validation_min ?? 0}
max={parameter.validations[0]?.validation_max ?? 100} max={parameter.validations[0]?.validation_max ?? 100}
disabled={disabled} disabled={disabled}
@ -319,8 +332,11 @@ const ParameterField: FC<ParameterFieldProps> = ({
return ( return (
<Textarea <Textarea
className="max-w-2xl" className="max-w-2xl"
defaultValue={defaultValue} value={localValue}
onChange={(e) => onChange(e.target.value)} onChange={(e) => {
setLocalValue(e.target.value);
onChange(e.target.value);
}}
onInput={(e) => { onInput={(e) => {
const target = e.currentTarget; const target = e.currentTarget;
target.style.maxHeight = "700px"; target.style.maxHeight = "700px";
@ -354,8 +370,11 @@ const ParameterField: FC<ParameterFieldProps> = ({
return ( return (
<Input <Input
type={inputType} type={inputType}
defaultValue={defaultValue} value={localValue}
onChange={(e) => onChange(e.target.value)} onChange={(e) => {
setLocalValue(e.target.value);
onChange(e.target.value);
}}
disabled={disabled} disabled={disabled}
required={parameter.required} required={parameter.required}
placeholder={ placeholder={
@ -435,7 +454,7 @@ export const getInitialParameterValues = (
if (parameter.ephemeral) { if (parameter.ephemeral) {
return { return {
name: parameter.name, name: parameter.name,
value: validValue(parameter.default_value), value: validValue(parameter.value),
}; };
} }
@ -450,7 +469,7 @@ export const getInitialParameterValues = (
isValidParameterOption(parameter, autofillParam) && isValidParameterOption(parameter, autofillParam) &&
autofillParam.value autofillParam.value
? autofillParam.value ? autofillParam.value
: validValue(parameter.default_value), : validValue(parameter.value),
}; };
}); });
}; };

View File

@ -213,17 +213,23 @@ export const CreateWorkspacePageViewExperimental: FC<
parameters, parameters,
]); ]);
// send the last user modified parameter and all touched parameters to the websocket
const sendDynamicParamsRequest = ( const sendDynamicParamsRequest = (
parameter: PreviewParameter, parameter: PreviewParameter,
value: string, value: string,
) => { ) => {
const formInputs = Object.fromEntries( const formInputs: { [k: string]: string } = {};
form.values.rich_parameter_values?.map((value) => {
return [value.name, value.value];
}) ?? [],
);
// Update the input for the changed parameter
formInputs[parameter.name] = value; formInputs[parameter.name] = value;
const parameters = form.values.rich_parameter_values ?? [];
for (const [fieldName, isTouched] of Object.entries(form.touched)) {
if (isTouched && fieldName !== parameter.name) {
const param = parameters.find((p) => p.name === fieldName);
if (param?.value) {
formInputs[fieldName] = param.value;
}
}
}
sendMessage(formInputs); sendMessage(formInputs);
}; };
@ -238,6 +244,7 @@ export const CreateWorkspacePageViewExperimental: FC<
name: parameter.name, name: parameter.name,
value, value,
}); });
form.setFieldTouched(parameter.name, true);
sendDynamicParamsRequest(parameter, value); sendDynamicParamsRequest(parameter, value);
}, },
500, 500,
@ -255,6 +262,7 @@ export const CreateWorkspacePageViewExperimental: FC<
name: parameter.name, name: parameter.name,
value, value,
}); });
form.setFieldTouched(parameter.name, true);
sendDynamicParamsRequest(parameter, value); sendDynamicParamsRequest(parameter, value);
} }
}; };