mirror of
https://github.com/coder/coder.git
synced 2025-07-30 22:19:53 +00:00
feat: add the ability to hide preset parameters (#17168)
This PR adds the ability to hide presets on the workspace creation form. When showing them, a clear indication is now made as to which inputs were preset and which weren't. 
This commit is contained in:
@@ -374,3 +374,44 @@ export const SmallBasicWithDisplayName: Story = {
|
||||
size: "small",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPreset: Story = {
|
||||
args: {
|
||||
value: "preset-value",
|
||||
id: "project_name",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "project_name",
|
||||
description:
|
||||
"Customize the name of a Google Cloud project that will be created!",
|
||||
}),
|
||||
isPreset: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPresetAndImmutable: Story = {
|
||||
args: {
|
||||
value: "preset-value",
|
||||
id: "project_name",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "project_name",
|
||||
description:
|
||||
"Customize the name of a Google Cloud project that will be created!",
|
||||
mutable: false,
|
||||
}),
|
||||
isPreset: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithPresetAndOptional: Story = {
|
||||
args: {
|
||||
value: "preset-value",
|
||||
id: "project_name",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "project_name",
|
||||
description:
|
||||
"Customize the name of a Google Cloud project that will be created!",
|
||||
required: false,
|
||||
}),
|
||||
isPreset: true,
|
||||
},
|
||||
};
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import ErrorOutline from "@mui/icons-material/ErrorOutline";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import Button from "@mui/material/Button";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import FormHelperText from "@mui/material/FormHelperText";
|
||||
@@ -122,9 +123,10 @@ const styles = {
|
||||
|
||||
export interface ParameterLabelProps {
|
||||
parameter: TemplateVersionParameter;
|
||||
isPreset?: boolean;
|
||||
}
|
||||
|
||||
const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
|
||||
const ParameterLabel: FC<ParameterLabelProps> = ({ parameter, isPreset }) => {
|
||||
const hasDescription = parameter.description && parameter.description !== "";
|
||||
const displayName = parameter.display_name
|
||||
? parameter.display_name
|
||||
@@ -146,6 +148,13 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isPreset && (
|
||||
<Tooltip title="This value was set by a preset">
|
||||
<Pill type="info" icon={<SettingsIcon />}>
|
||||
Preset
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -187,6 +196,7 @@ export type RichParameterInputProps = Omit<
|
||||
parameterAutofill?: AutofillBuildParameter;
|
||||
onChange: (value: string) => void;
|
||||
size?: Size;
|
||||
isPreset?: boolean;
|
||||
};
|
||||
|
||||
const autofillDescription: Partial<Record<AutofillSource, ReactNode>> = {
|
||||
@@ -198,6 +208,7 @@ export const RichParameterInput: FC<RichParameterInputProps> = ({
|
||||
parameter,
|
||||
parameterAutofill,
|
||||
onChange,
|
||||
isPreset,
|
||||
...fieldProps
|
||||
}) => {
|
||||
const autofillSource = parameterAutofill?.source;
|
||||
@@ -211,7 +222,7 @@ export const RichParameterInput: FC<RichParameterInputProps> = ({
|
||||
className={size}
|
||||
data-testid={`parameter-field-${parameter.name}`}
|
||||
>
|
||||
<ParameterLabel parameter={parameter} />
|
||||
<ParameterLabel parameter={parameter} isPreset={isPreset} />
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
<RichParameterField
|
||||
{...fieldProps}
|
||||
|
@@ -159,6 +159,28 @@ export const PresetSelected: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const PresetSelectedWithHiddenParameters: Story = {
|
||||
args: PresetsButNoneSelected.args,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
// Select a preset
|
||||
await userEvent.click(canvas.getByLabelText("Preset"));
|
||||
await userEvent.click(canvas.getByText("Preset 1"));
|
||||
},
|
||||
};
|
||||
|
||||
export const PresetSelectedWithVisibleParameters: Story = {
|
||||
args: PresetsButNoneSelected.args,
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
// Select a preset
|
||||
await userEvent.click(canvas.getByLabelText("Preset"));
|
||||
await userEvent.click(canvas.getByText("Preset 1"));
|
||||
// Toggle off the show preset parameters switch
|
||||
await userEvent.click(canvas.getByLabelText("Show preset parameters"));
|
||||
},
|
||||
};
|
||||
|
||||
export const PresetReselected: Story = {
|
||||
args: PresetsButNoneSelected.args,
|
||||
play: async ({ canvasElement }) => {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import FormHelperText from "@mui/material/FormHelperText";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
@@ -24,6 +25,7 @@ import { Pill } from "components/Pill/Pill";
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Switch } from "components/Switch/Switch";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { type FormikContextType, useFormik } from "formik";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
@@ -101,6 +103,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
const [suggestedName, setSuggestedName] = useState(() =>
|
||||
generateWorkspaceName(),
|
||||
);
|
||||
const [showPresetParameters, setShowPresetParameters] = useState(false);
|
||||
|
||||
const rerollSuggestedName = useCallback(() => {
|
||||
setSuggestedName(() => generateWorkspaceName());
|
||||
@@ -273,33 +276,6 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{presets.length > 0 && (
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<span css={styles.description}>
|
||||
Select a preset to get started
|
||||
</span>
|
||||
<FeatureStageBadge contentType={"beta"} size="md" />
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<SelectFilter
|
||||
label="Preset"
|
||||
options={presetOptions}
|
||||
onSelect={(option) => {
|
||||
const index = presetOptions.findIndex(
|
||||
(preset) => preset.value === option?.value,
|
||||
);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
setSelectedPresetIndex(index);
|
||||
}}
|
||||
placeholder="Select a preset"
|
||||
selectedOption={presetOptions[selectedPresetIndex]}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
<div>
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
@@ -373,30 +349,89 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
hence they require additional vertical spacing for better readability and
|
||||
user experience. */}
|
||||
<FormFields css={{ gap: 36 }}>
|
||||
{presets.length > 0 && (
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<span css={styles.description}>
|
||||
Select a preset to get started
|
||||
</span>
|
||||
<FeatureStageBadge contentType={"beta"} size="md" />
|
||||
</Stack>
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<SelectFilter
|
||||
label="Preset"
|
||||
options={presetOptions}
|
||||
onSelect={(option) => {
|
||||
const index = presetOptions.findIndex(
|
||||
(preset) => preset.value === option?.value,
|
||||
);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
setSelectedPresetIndex(index);
|
||||
}}
|
||||
placeholder="Select a preset"
|
||||
selectedOption={presetOptions[selectedPresetIndex]}
|
||||
/>
|
||||
</Stack>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
id="show-preset-parameters"
|
||||
checked={showPresetParameters}
|
||||
onCheckedChange={setShowPresetParameters}
|
||||
/>
|
||||
<label
|
||||
htmlFor="show-preset-parameters"
|
||||
css={styles.description}
|
||||
>
|
||||
Show preset parameters
|
||||
</label>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{parameters.map((parameter, index) => {
|
||||
const parameterField = `rich_parameter_values.${index}`;
|
||||
const parameterInputName = `${parameterField}.value`;
|
||||
const isPresetParameter = presetParameterNames.includes(
|
||||
parameter.name,
|
||||
);
|
||||
const isDisabled =
|
||||
disabledParams?.includes(
|
||||
parameter.name.toLowerCase().replace(/ /g, "_"),
|
||||
) ||
|
||||
creatingWorkspace ||
|
||||
presetParameterNames.includes(parameter.name);
|
||||
isPresetParameter;
|
||||
|
||||
// Hide preset parameters if showPresetParameters is false
|
||||
if (!showPresetParameters && isPresetParameter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(parameterInputName)}
|
||||
onChange={async (value) => {
|
||||
await form.setFieldValue(parameterField, {
|
||||
name: parameter.name,
|
||||
value,
|
||||
});
|
||||
}}
|
||||
key={parameter.name}
|
||||
parameter={parameter}
|
||||
parameterAutofill={autofillByName[parameter.name]}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<div key={parameter.name}>
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(parameterInputName)}
|
||||
onChange={async (value) => {
|
||||
await form.setFieldValue(parameterField, {
|
||||
name: parameter.name,
|
||||
value,
|
||||
});
|
||||
}}
|
||||
parameter={parameter}
|
||||
parameterAutofill={autofillByName[parameter.name]}
|
||||
disabled={isDisabled}
|
||||
isPreset={isPresetParameter}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</FormFields>
|
||||
|
Reference in New Issue
Block a user