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.


![image](https://github.com/user-attachments/assets/6c8f690c-7cf6-44a9-9657-65039b2b3cb7)
This commit is contained in:
Sas Swart
2025-04-01 22:51:42 +02:00
committed by GitHub
parent 88bae05223
commit 00e1ea4ccf
4 changed files with 152 additions and 43 deletions

View File

@@ -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,
},
};

View File

@@ -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}

View File

@@ -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 }) => {

View File

@@ -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>