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

View File

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