mirror of
https://github.com/coder/coder.git
synced 2025-07-23 21:32:07 +00:00
refactor(site): Highlight immutable parameters and do a few tweaks (#6490)
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
} from "components/FormFooter/FormFooter"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { FC, HTMLProps, PropsWithChildren } from "react"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
|
||||
export const HorizontalForm: FC<
|
||||
PropsWithChildren & HTMLProps<HTMLFormElement>
|
||||
@@ -21,12 +22,16 @@ export const HorizontalForm: FC<
|
||||
}
|
||||
|
||||
export const FormSection: FC<
|
||||
PropsWithChildren & { title: string; description: string | JSX.Element }
|
||||
> = ({ children, title, description }) => {
|
||||
PropsWithChildren & {
|
||||
title: string
|
||||
description: string | JSX.Element
|
||||
className?: string
|
||||
}
|
||||
> = ({ children, title, description, className }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<div className={combineClasses([styles.formSection, className])}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>{title}</h2>
|
||||
<div className={styles.formSectionInfoDescription}>{description}</div>
|
||||
|
@@ -29,8 +29,8 @@ const createTemplateVersionParameter = (
|
||||
validation_regex: "",
|
||||
validation_min: 0,
|
||||
validation_max: 0,
|
||||
validation_monotonic: "",
|
||||
|
||||
validation_monotonic: "increasing",
|
||||
description_plaintext: "",
|
||||
...partial,
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ const createTemplateVersionParameter = (
|
||||
export const Basic = Template.bind({})
|
||||
Basic.args = {
|
||||
initialValue: "initial-value",
|
||||
id: "project_name",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "project_name",
|
||||
description:
|
||||
@@ -48,6 +49,7 @@ Basic.args = {
|
||||
export const NumberType = Template.bind({})
|
||||
NumberType.args = {
|
||||
initialValue: "4",
|
||||
id: "number_parameter",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "number_parameter",
|
||||
type: "number",
|
||||
@@ -58,6 +60,7 @@ NumberType.args = {
|
||||
export const BooleanType = Template.bind({})
|
||||
BooleanType.args = {
|
||||
initialValue: "false",
|
||||
id: "bool_parameter",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "bool_parameter",
|
||||
type: "bool",
|
||||
@@ -68,6 +71,7 @@ BooleanType.args = {
|
||||
export const OptionsType = Template.bind({})
|
||||
OptionsType.args = {
|
||||
initialValue: "first_option",
|
||||
id: "options_parameter",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "options_parameter",
|
||||
type: "string",
|
||||
@@ -94,3 +98,68 @@ OptionsType.args = {
|
||||
],
|
||||
}),
|
||||
}
|
||||
|
||||
export const IconLabel = Template.bind({})
|
||||
IconLabel.args = {
|
||||
initialValue: "initial-value",
|
||||
id: "project_name",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "project_name",
|
||||
description:
|
||||
"Customize the name of a Google Cloud project that will be created!",
|
||||
icon: "/emojis/1f30e.png",
|
||||
}),
|
||||
}
|
||||
|
||||
export const NoDescription = Template.bind({})
|
||||
NoDescription.args = {
|
||||
initialValue: "",
|
||||
id: "region",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "Region",
|
||||
description: "",
|
||||
description_plaintext: "",
|
||||
type: "string",
|
||||
mutable: false,
|
||||
default_value: "",
|
||||
icon: "/emojis/1f30e.png",
|
||||
options: [
|
||||
{
|
||||
name: "Pittsburgh",
|
||||
description: "",
|
||||
value: "us-pittsburgh",
|
||||
icon: "/emojis/1f1fa-1f1f8.png",
|
||||
},
|
||||
{
|
||||
name: "Helsinki",
|
||||
description: "",
|
||||
value: "eu-helsinki",
|
||||
icon: "/emojis/1f1eb-1f1ee.png",
|
||||
},
|
||||
{
|
||||
name: "Sydney",
|
||||
description: "",
|
||||
value: "ap-sydney",
|
||||
icon: "/emojis/1f1e6-1f1fa.png",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
|
||||
export const DescriptionWithLinks = Template.bind({})
|
||||
DescriptionWithLinks.args = {
|
||||
initialValue: "",
|
||||
id: "coder-repository-directory",
|
||||
parameter: createTemplateVersionParameter({
|
||||
name: "Coder Repository Directory",
|
||||
description:
|
||||
"The directory specified will be created and [coder/coder](https://github.com/coder/coder) will be automatically cloned into it 🪄.",
|
||||
description_plaintext:
|
||||
"The directory specified will be created and coder/coder (https://github.com/coder/coder) will be automatically cloned into it 🪄.",
|
||||
type: "string",
|
||||
mutable: true,
|
||||
default_value: "~/coder",
|
||||
icon: "",
|
||||
options: [],
|
||||
}),
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import FormControlLabel from "@material-ui/core/FormControlLabel"
|
||||
import Radio from "@material-ui/core/Radio"
|
||||
import RadioGroup from "@material-ui/core/RadioGroup"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import TextField, { TextFieldProps } from "@material-ui/core/TextField"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { FC, useState } from "react"
|
||||
import { TemplateVersionParameter } from "../../api/typesGenerated"
|
||||
@@ -14,54 +14,48 @@ const isBoolean = (parameter: TemplateVersionParameter) => {
|
||||
}
|
||||
|
||||
export interface ParameterLabelProps {
|
||||
index: number
|
||||
id: string
|
||||
parameter: TemplateVersionParameter
|
||||
}
|
||||
|
||||
const ParameterLabel: FC<ParameterLabelProps> = ({ index, parameter }) => {
|
||||
const ParameterLabel: FC<ParameterLabelProps> = ({ id, parameter }) => {
|
||||
const styles = useStyles()
|
||||
const hasDescription = parameter.description && parameter.description !== ""
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span className={styles.labelNameWithIcon}>
|
||||
<label htmlFor={id}>
|
||||
<Stack direction="row" alignItems="center">
|
||||
{parameter.icon && (
|
||||
<span className={styles.iconWrapper}>
|
||||
<span className={styles.labelIconWrapper}>
|
||||
<img
|
||||
className={styles.icon}
|
||||
className={styles.labelIcon}
|
||||
alt="Parameter icon"
|
||||
src={parameter.icon}
|
||||
style={{
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span className={styles.labelName}>
|
||||
<label htmlFor={`rich_parameter_values[${index}].value`}>
|
||||
{parameter.name}
|
||||
</label>
|
||||
</span>
|
||||
</span>
|
||||
{parameter.description && (
|
||||
<span className={styles.labelDescription}>
|
||||
<MemoizedMarkdown>{parameter.description}</MemoizedMarkdown>
|
||||
</span>
|
||||
)}
|
||||
{!parameter.mutable && (
|
||||
<div className={styles.labelImmutable}>
|
||||
This parameter cannot be changed after creating workspace.
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{hasDescription ? (
|
||||
<Stack spacing={0.5}>
|
||||
<span className={styles.labelCaption}>{parameter.name}</span>
|
||||
<span className={styles.labelPrimary}>
|
||||
<MemoizedMarkdown>{parameter.description}</MemoizedMarkdown>
|
||||
</span>
|
||||
</Stack>
|
||||
) : (
|
||||
<span className={styles.labelPrimary}>{parameter.name}</span>
|
||||
)}
|
||||
</Stack>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
export interface RichParameterInputProps {
|
||||
export type RichParameterInputProps = TextFieldProps & {
|
||||
index: number
|
||||
disabled?: boolean
|
||||
parameter: TemplateVersionParameter
|
||||
onChange: (value: string) => void
|
||||
initialValue?: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export const RichParameterInput: FC<RichParameterInputProps> = ({
|
||||
@@ -70,16 +64,16 @@ export const RichParameterInput: FC<RichParameterInputProps> = ({
|
||||
onChange,
|
||||
parameter,
|
||||
initialValue,
|
||||
...props
|
||||
...fieldProps
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Stack direction="column" spacing={0.75}>
|
||||
<ParameterLabel index={index} parameter={parameter} />
|
||||
<ParameterLabel id={fieldProps.id} parameter={parameter} />
|
||||
<div className={styles.input}>
|
||||
<RichParameterField
|
||||
{...props}
|
||||
{...fieldProps}
|
||||
index={index}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
@@ -140,7 +134,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
||||
value={option.value}
|
||||
control={<Radio color="primary" size="small" disableRipple />}
|
||||
label={
|
||||
<span>
|
||||
<span className={styles.radioOption}>
|
||||
{option.icon && (
|
||||
<img
|
||||
className={styles.optionIcon}
|
||||
@@ -180,24 +174,25 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const iconSize = 20
|
||||
const optionIconSize = 24
|
||||
const optionIconSize = 20
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
labelName: {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
display: "block",
|
||||
marginBottom: theme.spacing(1.0),
|
||||
},
|
||||
labelNameWithIcon: {
|
||||
label: {
|
||||
marginBottom: theme.spacing(0.5),
|
||||
},
|
||||
labelDescription: {
|
||||
labelCaption: {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
labelPrimary: {
|
||||
fontSize: 16,
|
||||
color: theme.palette.text.primary,
|
||||
display: "block",
|
||||
fontWeight: 600,
|
||||
|
||||
"& p": {
|
||||
margin: 0,
|
||||
lineHeight: "20px", // Keep the same as ParameterInput
|
||||
},
|
||||
},
|
||||
labelImmutable: {
|
||||
marginTop: theme.spacing(0.5),
|
||||
@@ -213,18 +208,23 @@ const useStyles = makeStyles((theme) => ({
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
iconWrapper: {
|
||||
float: "left",
|
||||
labelIconWrapper: {
|
||||
width: theme.spacing(2.5),
|
||||
height: theme.spacing(2.5),
|
||||
display: "block",
|
||||
},
|
||||
icon: {
|
||||
maxHeight: iconSize,
|
||||
width: iconSize,
|
||||
marginRight: theme.spacing(1.0),
|
||||
labelIcon: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
},
|
||||
radioOption: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
},
|
||||
optionIcon: {
|
||||
maxHeight: optionIconSize,
|
||||
width: optionIconSize,
|
||||
marginRight: theme.spacing(1.0),
|
||||
float: "left",
|
||||
},
|
||||
}))
|
||||
|
@@ -179,6 +179,7 @@ describe("CreateWorkspacePage", () => {
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
@@ -212,6 +213,7 @@ describe("CreateWorkspacePage", () => {
|
||||
|
||||
const thirdParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter3.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(thirdParameterField).toBeDefined()
|
||||
fireEvent.change(thirdParameterField, {
|
||||
|
@@ -133,6 +133,35 @@ RichParameters.args = {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter3,
|
||||
{
|
||||
name: "Region",
|
||||
description: "",
|
||||
description_plaintext: "",
|
||||
type: "string",
|
||||
mutable: false,
|
||||
default_value: "",
|
||||
icon: "/emojis/1f30e.png",
|
||||
options: [
|
||||
{
|
||||
name: "Pittsburgh",
|
||||
description: "",
|
||||
value: "us-pittsburgh",
|
||||
icon: "/emojis/1f1fa-1f1f8.png",
|
||||
},
|
||||
{
|
||||
name: "Helsinki",
|
||||
description: "",
|
||||
value: "eu-helsinki",
|
||||
icon: "/emojis/1f1eb-1f1ee.png",
|
||||
},
|
||||
{
|
||||
name: "Sydney",
|
||||
description: "",
|
||||
value: "ap-sydney",
|
||||
icon: "/emojis/1f1e6-1f1fa.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
createWorkspaceErrors: {},
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import * as TypesGen from "api/typesGenerated"
|
||||
import { FormFooter } from "components/FormFooter/FormFooter"
|
||||
import { ParameterInput } from "components/ParameterInput/ParameterInput"
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
@@ -11,11 +10,17 @@ import { useTranslation } from "react-i18next"
|
||||
import { getFormHelpers, nameValidator, onChangeTrimmed } from "util/formUtils"
|
||||
import * as Yup from "yup"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
|
||||
import { SelectedTemplate } from "./SelectedTemplate"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { GitAuth } from "components/GitAuth/GitAuth"
|
||||
import {
|
||||
FormFields,
|
||||
FormSection,
|
||||
FormFooter,
|
||||
HorizontalForm,
|
||||
} from "components/HorizontalForm/HorizontalForm"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
|
||||
export enum CreateWorkspaceErrors {
|
||||
GET_TEMPLATES_ERROR = "getTemplatesError",
|
||||
@@ -49,8 +54,6 @@ export interface CreateWorkspacePageViewProps {
|
||||
export const CreateWorkspacePageView: FC<
|
||||
React.PropsWithChildren<CreateWorkspacePageViewProps>
|
||||
> = (props) => {
|
||||
const styles = useStyles()
|
||||
const formFooterStyles = useFormFooterStyles()
|
||||
const [parameterValues, setParameterValues] = useState<
|
||||
Record<string, string>
|
||||
>(props.defaultParameterValues ?? {})
|
||||
@@ -67,8 +70,8 @@ export const CreateWorkspacePageView: FC<
|
||||
// to disappear.
|
||||
setGitAuthErrors({})
|
||||
}, [props.templateGitAuth])
|
||||
|
||||
const { t } = useTranslation("createWorkspacePage")
|
||||
const styles = useStyles()
|
||||
|
||||
const form: FormikContextType<TypesGen.CreateWorkspaceRequest> =
|
||||
useFormik<TypesGen.CreateWorkspaceRequest>({
|
||||
@@ -203,313 +206,195 @@ export const CreateWorkspacePageView: FC<
|
||||
|
||||
return (
|
||||
<FullPageHorizontalForm title="New workspace" onCancel={props.onCancel}>
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
<Stack direction="column" spacing={10} className={styles.formSections}>
|
||||
{/* General info */}
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>General info</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
The template and name of your new workspace.
|
||||
</p>
|
||||
</div>
|
||||
<HorizontalForm onSubmit={form.handleSubmit}>
|
||||
{/* General info */}
|
||||
<FormSection
|
||||
title="General info"
|
||||
description="The template and name of your new workspace."
|
||||
>
|
||||
<FormFields>
|
||||
{props.selectedTemplate && (
|
||||
<SelectedTemplate template={props.selectedTemplate} />
|
||||
)}
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={1}
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
{props.selectedTemplate && (
|
||||
<SelectedTemplate template={props.selectedTemplate} />
|
||||
)}
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={t("nameLabel")}
|
||||
variant="outlined"
|
||||
/>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={t("nameLabel")}
|
||||
variant="outlined"
|
||||
{/* Workspace owner */}
|
||||
{props.canCreateForUser && (
|
||||
<FormSection
|
||||
title="Workspace owner"
|
||||
description="The user that is going to own this workspace. If you are admin, you can create workspace for others."
|
||||
>
|
||||
<FormFields>
|
||||
<UserAutocomplete
|
||||
value={props.owner}
|
||||
onChange={props.setOwner}
|
||||
label={t("ownerLabel")}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
|
||||
{/* Workspace owner */}
|
||||
{props.canCreateForUser && (
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>Workspace owner</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
The user that is going to own this workspace. If you are
|
||||
admin, you can create workspace for others.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={1}
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
<UserAutocomplete
|
||||
value={props.owner}
|
||||
onChange={props.setOwner}
|
||||
label={t("ownerLabel")}
|
||||
{/* Template git auth */}
|
||||
{props.templateGitAuth && props.templateGitAuth.length > 0 && (
|
||||
<FormSection
|
||||
title="Git Authentication"
|
||||
description="This template requires authentication to automatically perform Git operations on create."
|
||||
>
|
||||
<FormFields>
|
||||
{props.templateGitAuth.map((auth, index) => (
|
||||
<GitAuth
|
||||
key={index}
|
||||
authenticateURL={auth.authenticate_url}
|
||||
authenticated={auth.authenticated}
|
||||
type={auth.type}
|
||||
error={gitAuthErrors[auth.id]}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
|
||||
{/* Template git auth */}
|
||||
{props.templateGitAuth && props.templateGitAuth.length > 0 && (
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>
|
||||
Git Authentication
|
||||
</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
This template requires authentication to automatically perform
|
||||
Git operations on create.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={2}
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
{props.templateGitAuth.map((auth, index) => (
|
||||
<GitAuth
|
||||
key={index}
|
||||
authenticateURL={auth.authenticate_url}
|
||||
authenticated={auth.authenticated}
|
||||
type={auth.type}
|
||||
error={gitAuthErrors[auth.id]}
|
||||
{/* Template params */}
|
||||
{props.templateSchema && props.templateSchema.length > 0 && (
|
||||
<FormSection
|
||||
title="Template params"
|
||||
description="Those values are provided by your template's Terraform configuration."
|
||||
>
|
||||
<FormFields>
|
||||
{props.templateSchema
|
||||
// We only want to show schema that have redisplay_value equals true
|
||||
.filter((schema) => schema.redisplay_value)
|
||||
.map((schema) => (
|
||||
<ParameterInput
|
||||
disabled={form.isSubmitting}
|
||||
key={schema.id}
|
||||
defaultValue={parameterValues[schema.name]}
|
||||
onChange={(value) => {
|
||||
setParameterValues({
|
||||
...parameterValues,
|
||||
[schema.name]: value,
|
||||
})
|
||||
}}
|
||||
schema={schema}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
|
||||
{/* Mutable rich parameters */}
|
||||
{props.templateParameters &&
|
||||
props.templateParameters.filter((p) => p.mutable).length > 0 && (
|
||||
<FormSection
|
||||
title="Parameters"
|
||||
description="Those values are provided by your template's Terraform configuration. Values can be changed after creating the workspace."
|
||||
>
|
||||
<FormFields>
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={form.isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue("rich_parameter_values." + index, {
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
})
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
initialRichParameterValues,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
|
||||
{/* Template params */}
|
||||
{props.templateSchema && props.templateSchema.length > 0 && (
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>Template params</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
Those values are provided by your template‘s Terraform
|
||||
configuration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={4} // Spacing here is diff because the fields here don't have the MUI floating label spacing
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
{props.templateSchema
|
||||
// We only want to show schema that have redisplay_value equals true
|
||||
.filter((schema) => schema.redisplay_value)
|
||||
.map((schema) => (
|
||||
<ParameterInput
|
||||
disabled={form.isSubmitting}
|
||||
key={schema.id}
|
||||
defaultValue={parameterValues[schema.name]}
|
||||
onChange={(value) => {
|
||||
setParameterValues({
|
||||
...parameterValues,
|
||||
[schema.name]: value,
|
||||
})
|
||||
}}
|
||||
schema={schema}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
{/* Immutable rich parameters */}
|
||||
{props.templateParameters &&
|
||||
props.templateParameters.filter((p) => !p.mutable).length > 0 && (
|
||||
<FormSection
|
||||
title="Immutable parameters"
|
||||
className={styles.warningSection}
|
||||
description={
|
||||
<>
|
||||
Those values are also parameters provided from your Terraform
|
||||
configuration but they{" "}
|
||||
<strong className={styles.warningText}>
|
||||
cannot be changed after creating the workspace.
|
||||
</strong>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormFields>
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
!parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={form.isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue("rich_parameter_values." + index, {
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
})
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
initialRichParameterValues,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
|
||||
{/* Immutable rich parameters */}
|
||||
{props.templateParameters &&
|
||||
props.templateParameters.filter((p) => !p.mutable).length > 0 && (
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>
|
||||
Immutable parameters
|
||||
</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
Those values are provided by your template‘s Terraform
|
||||
configuration. Values cannot be changed after creating the
|
||||
workspace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={4} // Spacing here is diff because the fields here don't have the MUI floating label spacing
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
!parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={form.isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue(
|
||||
"rich_parameter_values." + index,
|
||||
{
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
},
|
||||
)
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
initialRichParameterValues,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mutable rich parameters */}
|
||||
{props.templateParameters &&
|
||||
props.templateParameters.filter((p) => p.mutable).length > 0 && (
|
||||
<div className={styles.formSection}>
|
||||
<div className={styles.formSectionInfo}>
|
||||
<h2 className={styles.formSectionInfoTitle}>
|
||||
Mutable parameters
|
||||
</h2>
|
||||
<p className={styles.formSectionInfoDescription}>
|
||||
Those values are provided by your template‘s Terraform
|
||||
configuration. Values can be changed after creating the
|
||||
workspace.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
spacing={4} // Spacing here is diff because the fields here don't have the MUI floating label spacing
|
||||
className={styles.formSectionFields}
|
||||
>
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={form.isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue(
|
||||
"rich_parameter_values." + index,
|
||||
{
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
},
|
||||
)
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
initialRichParameterValues,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
<FormFooter
|
||||
styles={formFooterStyles}
|
||||
onCancel={props.onCancel}
|
||||
isLoading={props.creatingWorkspace}
|
||||
submitLabel={t("createWorkspace")}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
<FormFooter
|
||||
onCancel={props.onCancel}
|
||||
isLoading={props.creatingWorkspace}
|
||||
submitLabel={t("createWorkspace")}
|
||||
/>
|
||||
</HorizontalForm>
|
||||
</FullPageHorizontalForm>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
formSections: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
gap: theme.spacing(8),
|
||||
},
|
||||
warningText: {
|
||||
color: theme.palette.warning.light,
|
||||
},
|
||||
|
||||
formSection: {
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: theme.spacing(15),
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
|
||||
formSectionInfo: {
|
||||
width: 312,
|
||||
flexShrink: 0,
|
||||
position: "sticky",
|
||||
top: theme.spacing(3),
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
width: "100%",
|
||||
position: "initial",
|
||||
},
|
||||
},
|
||||
|
||||
formSectionInfoTitle: {
|
||||
fontSize: 20,
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
margin: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
|
||||
formSectionInfoDescription: {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "160%",
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
formSectionFields: {
|
||||
width: "100%",
|
||||
},
|
||||
}))
|
||||
|
||||
const useFormFooterStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
minWidth: theme.spacing(23),
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
flexDirection: "row-reverse",
|
||||
gap: theme.spacing(2),
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
warningSection: {
|
||||
border: `1px solid ${theme.palette.warning.light}`,
|
||||
borderRadius: 8,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: theme.spacing(10),
|
||||
marginLeft: -theme.spacing(10),
|
||||
marginRight: -theme.spacing(10),
|
||||
},
|
||||
}))
|
||||
|
||||
|
@@ -80,11 +80,13 @@ describe("WorkspaceBuildParametersPage", () => {
|
||||
|
||||
const firstParameter = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter1.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(firstParameter).toBeDefined()
|
||||
|
||||
const secondParameter = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameter).toBeDefined()
|
||||
})
|
||||
@@ -113,6 +115,7 @@ describe("WorkspaceBuildParametersPage", () => {
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
@@ -151,6 +154,7 @@ describe("WorkspaceBuildParametersPage", () => {
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
@@ -189,6 +193,7 @@ describe("WorkspaceBuildParametersPage", () => {
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter5.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
|
Reference in New Issue
Block a user