mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
fix: update default value handling for dynamic defaults (#17609)
resolves coder/preview#102
This commit is contained in:
@ -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),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user