refactor(site): improve parameters field (#11802)

This commit is contained in:
Bruno Quaresma
2024-01-26 11:31:52 -03:00
committed by GitHub
parent 4c71cccbc3
commit 0ba035a16d
6 changed files with 141 additions and 175 deletions
@@ -19,7 +19,7 @@ const createTemplateVersionParameter = (
name: "first_parameter",
description: "This is first parameter.",
type: "string",
mutable: false,
mutable: true,
default_value: "default string",
icon: "/icon/folder.svg",
options: [],
@@ -47,6 +47,46 @@ export const Basic: Story = {
},
};
export const Optional: Story = {
args: {
value: "initial-value",
id: "project_name",
parameter: createTemplateVersionParameter({
required: false,
name: "project_name",
description:
"Customize the name of a Google Cloud project that will be created!",
}),
},
};
export const Immutable: Story = {
args: {
value: "initial-value",
id: "project_name",
parameter: createTemplateVersionParameter({
mutable: false,
name: "project_name",
description:
"Customize the name of a Google Cloud project that will be created!",
}),
},
};
export const WithError: Story = {
args: {
id: "number_parameter",
parameter: createTemplateVersionParameter({
name: "number_parameter",
type: "number",
description: "Numeric parameter",
default_value: "",
}),
error: true,
helperText: "Number must be greater than 5",
},
};
export const NumberType: Story = {
args: {
value: "4",
@@ -10,6 +10,8 @@ import { MemoizedMarkdown } from "components/Markdown/Markdown";
import { Stack } from "components/Stack/Stack";
import { MultiTextField } from "./MultiTextField";
import { ExternalImage } from "components/ExternalImage/ExternalImage";
import { Pill } from "components/Pill/Pill";
import ErrorOutline from "@mui/icons-material/ErrorOutline";
const isBoolean = (parameter: TemplateVersionParameter) => {
return parameter.type === "bool";
@@ -31,7 +33,11 @@ const styles = {
labelPrimary: (theme) => ({
fontSize: 16,
color: theme.palette.text.primary,
fontWeight: 600,
fontWeight: 500,
display: "flex",
alignItems: "center",
flexWrap: "wrap",
gap: 8,
"& p": {
margin: 0,
@@ -42,6 +48,11 @@ const styles = {
fontSize: 14,
},
}),
optionalLabel: (theme) => ({
fontSize: 14,
color: theme.palette.text.disabled,
fontWeight: 500,
}),
textField: {
".small & .MuiInputBase-root": {
height: 36,
@@ -102,6 +113,25 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
? parameter.display_name
: parameter.name;
const labelPrimary = (
<span css={styles.labelPrimary}>
{displayName}
{!parameter.required && (
<Tooltip title="If no value is specified, the system will default to the value set by the administrator.">
<span css={styles.optionalLabel}>(optional)</span>
</Tooltip>
)}
{!parameter.mutable && (
<Tooltip title="This value cannot be modified after the workspace has been created.">
<Pill type="warning" icon={<ErrorOutline />}>
Immutable
</Pill>
</Tooltip>
)}
</span>
);
return (
<label htmlFor={parameter.name}>
<Stack direction="row" alignItems="center">
@@ -117,13 +147,13 @@ const ParameterLabel: FC<ParameterLabelProps> = ({ parameter }) => {
{hasDescription ? (
<Stack spacing={0}>
<span css={styles.labelPrimary}>{displayName}</span>
{labelPrimary}
<MemoizedMarkdown css={styles.labelCaption}>
{parameter.description}
</MemoizedMarkdown>
</Stack>
) : (
<span css={styles.labelPrimary}>{displayName}</span>
labelPrimary
)}
</Stack>
</label>
@@ -14,9 +14,11 @@ export type TemplateParametersSectionProps = {
) => Omit<RichParameterInputProps, "parameter" | "index">;
} & Pick<ComponentProps<typeof FormSection>, "classes">;
export const MutableTemplateParametersSection: FC<
TemplateParametersSectionProps
> = ({ templateParameters, getInputProps, ...formSectionProps }) => {
export const TemplateParametersSection: FC<TemplateParametersSectionProps> = ({
templateParameters,
getInputProps,
...formSectionProps
}) => {
const hasMutableParameters =
templateParameters.filter((p) => p.mutable).length > 0;
@@ -45,40 +47,3 @@ export const MutableTemplateParametersSection: FC<
</>
);
};
export const ImmutableTemplateParametersSection: FC<
TemplateParametersSectionProps
> = ({ templateParameters, getInputProps, ...formSectionProps }) => {
const hasImmutableParameters =
templateParameters.filter((p) => !p.mutable).length > 0;
return (
<>
{hasImmutableParameters && (
<FormSection
{...formSectionProps}
title="Immutable parameters"
description={
<>
These settings <strong>cannot be changed</strong> after creating
the workspace.
</>
}
>
<FormFields>
{templateParameters.map(
(parameter, index) =>
!parameter.mutable && (
<RichParameterInput
{...getInputProps(parameter, index)}
key={parameter.name}
parameter={parameter}
/>
),
)}
</FormFields>
</FormSection>
)}
</>
);
};
@@ -1,5 +1,4 @@
import { css } from "@emotion/css";
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
import { type Interpolation, type Theme } from "@emotion/react";
import TextField from "@mui/material/TextField";
import type * as TypesGen from "api/typesGenerated";
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
@@ -21,10 +20,6 @@ import {
getInitialRichParameterValues,
useValidationSchemaForRichParameters,
} from "utils/richParameters";
import {
ImmutableTemplateParametersSection,
MutableTemplateParametersSection,
} from "components/TemplateParameters/TemplateParameters";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Stack } from "components/Stack/Stack";
import {
@@ -44,6 +39,7 @@ import {
PageHeaderSubtitle,
} from "components/PageHeader/PageHeader";
import { Pill } from "components/Pill/Pill";
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
export const Language = {
duplicationWarning:
@@ -90,7 +86,6 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
onSubmit,
onCancel,
}) => {
const theme = useTheme();
const [owner, setOwner] = useState(defaultOwner);
const [searchParams] = useSearchParams();
const disabledParamsList = searchParams?.get("disable_params")?.split(",");
@@ -222,65 +217,41 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
</FormFields>
</FormSection>
{parameters && (
<>
<MutableTemplateParametersSection
templateParameters={parameters}
getInputProps={(parameter, index) => {
return {
...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
),
onChange: async (value) => {
await form.setFieldValue(
"rich_parameter_values." + index,
{
{parameters.length > 0 && (
<FormSection
title="Parameters"
description="These are the settings used by your template. Please note that immutable parameters cannot be modified once the workspace is created."
>
{/*
Opted not to use FormFields in order to increase spacing.
This decision was made because rich parameter inputs are more visually dense than standard text fields.
*/}
<div css={{ display: "flex", flexDirection: "column", gap: 36 }}>
{parameters.map((parameter, index) => {
const parameterField = `rich_parameter_values.${index}`;
const parameterInputName = `${parameterField}.value`;
const isDisabled =
disabledParamsList?.includes(
parameter.name.toLowerCase().replace(/ /g, "_"),
) || creatingWorkspace;
return (
<RichParameterInput
{...getFieldHelpers(parameterInputName)}
onChange={async (value) => {
await form.setFieldValue(parameterField, {
name: parameter.name,
value: value,
},
);
},
disabled:
disabledParamsList?.includes(
parameter.name.toLowerCase().replace(/ /g, "_"),
) || creatingWorkspace,
};
}}
/>
<ImmutableTemplateParametersSection
templateParameters={parameters}
classes={{
root: css`
border: 1px solid ${theme.palette.warning.light};
border-radius: 8px;
background-color: ${theme.palette.background.paper};
padding: 80px;
margin-left: -80px;
margin-right: -80px;
`,
}}
getInputProps={(parameter, index) => {
return {
...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
),
onChange: async (value) => {
await form.setFieldValue(
"rich_parameter_values." + index,
{
name: parameter.name,
value: value,
},
);
},
disabled:
disabledParamsList?.includes(
parameter.name.toLowerCase().replace(/ /g, "_"),
) || creatingWorkspace,
};
}}
/>
</>
value,
});
}}
key={parameter.name}
parameter={parameter}
disabled={isDisabled}
/>
);
})}
</div>
</FormSection>
)}
<FormFooter
@@ -10,17 +10,13 @@ import { Template, TemplateVersionParameter } from "api/typesGenerated";
import { FormSection, VerticalForm } from "components/Form/Form";
import { Loader } from "components/Loader/Loader";
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
import {
ImmutableTemplateParametersSection,
MutableTemplateParametersSection,
TemplateParametersSectionProps,
} from "components/TemplateParameters/TemplateParameters";
import { useClipboard } from "hooks/useClipboard";
import { type FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { getInitialRichParameterValues } from "utils/richParameters";
import { paramsUsedToCreateWorkspace } from "utils/workspace";
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
type ButtonValues = Record<string, string>;
@@ -64,22 +60,6 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
const buttonUrl = `${createWorkspaceUrl}?${createWorkspaceParams.toString()}`;
const buttonMkdCode = `[![Open in Coder](${deploymentUrl}/open-in-coder.svg)](${buttonUrl})`;
const clipboard = useClipboard(buttonMkdCode);
const getInputProps: TemplateParametersSectionProps["getInputProps"] = (
parameter,
) => {
if (!buttonValues) {
throw new Error("buttonValues is undefined");
}
return {
value: buttonValues[`param.${parameter.name}`] ?? "",
onChange: (value) => {
setButtonValues((buttonValues) => ({
...buttonValues,
[`param.${parameter.name}`]: value,
}));
},
};
};
// template parameters is async so we need to initialize the values after it
// is loaded
@@ -135,16 +115,28 @@ export const TemplateEmbedPageView: FC<TemplateEmbedPageViewProps> = ({
</FormSection>
{templateParameters.length > 0 && (
<>
<MutableTemplateParametersSection
templateParameters={templateParameters}
getInputProps={getInputProps}
/>
<ImmutableTemplateParametersSection
templateParameters={templateParameters}
getInputProps={getInputProps}
/>
</>
<div
css={{ display: "flex", flexDirection: "column", gap: 36 }}
>
{templateParameters.map((parameter) => {
const parameterValue =
buttonValues[`param.${parameter.name}`] ?? "";
return (
<RichParameterInput
value={parameterValue}
onChange={async (value) => {
setButtonValues((buttonValues) => ({
...buttonValues,
[`param.${parameter.name}`]: value,
}));
}}
key={parameter.name}
parameter={parameter}
/>
);
})}
</div>
)}
</VerticalForm>
</div>
@@ -69,9 +69,6 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
const hasNonEphemeralParameters = templateVersionRichParameters.some(
(parameter) => !parameter.ephemeral,
);
const hasImmutableParameters = templateVersionRichParameters.some(
(parameter) => !parameter.mutable,
);
const disabled =
workspace.outdated &&
@@ -97,12 +94,12 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
{templateVersionRichParameters.map((parameter, index) =>
// Since we are adding the values to the form based on the index
// we can't filter them to not loose the right index position
parameter.mutable && !parameter.ephemeral ? (
!parameter.ephemeral ? (
<RichParameterInput
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
disabled={isSubmitting || disabled}
disabled={isSubmitting || disabled || !parameter.mutable}
key={parameter.name}
onChange={async (value) => {
await form.setFieldValue(
@@ -152,36 +149,7 @@ export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
</FormFields>
</FormSection>
)}
{/* They are displayed here only for visibility purposes */}
{hasImmutableParameters && (
<FormSection
title="Immutable parameters"
description={
<>
These settings <strong>cannot be changed</strong> after creating
the workspace.
</>
}
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
!parameter.mutable ? (
<RichParameterInput
disabled
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
key={parameter.name}
parameter={parameter}
onChange={() => {
throw new Error("Immutable parameters cannot be changed");
}}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
<FormFooter
onCancel={onCancel}
isLoading={isSubmitting}