mirror of https://github.com/coder/coder.git
refactor(site): Show immutable parameters in the settings (#7383)
This commit is contained in:
parent
434c4be9f1
commit
2ea438cf4f
|
@ -55,6 +55,12 @@ const WorkspaceSchedulePage = lazy(
|
|||
"./pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage"
|
||||
),
|
||||
)
|
||||
const WorkspaceParametersPage = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage"
|
||||
),
|
||||
)
|
||||
const TerminalPage = lazy(() => import("./pages/TerminalPage/TerminalPage"))
|
||||
const TemplatePermissionsPage = lazy(
|
||||
() =>
|
||||
|
@ -291,6 +297,10 @@ export const AppRouter: FC = () => {
|
|||
/>
|
||||
<Route path="settings" element={<WorkspaceSettingsLayout />}>
|
||||
<Route index element={<WorkspaceSettingsPage />} />
|
||||
<Route
|
||||
path="parameters"
|
||||
element={<WorkspaceParametersPage />}
|
||||
/>
|
||||
<Route
|
||||
path="schedule"
|
||||
element={<WorkspaceSchedulePage />}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { FC, ElementType, PropsWithChildren, ReactNode } from "react"
|
|||
import { Link, NavLink } from "react-router-dom"
|
||||
import { combineClasses } from "utils/combineClasses"
|
||||
import GeneralIcon from "@material-ui/icons/SettingsOutlined"
|
||||
import ParameterIcon from "@material-ui/icons/CodeOutlined"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
|
||||
const SidebarNavItem: FC<
|
||||
|
@ -65,6 +66,12 @@ export const Sidebar: React.FC<{ username: string; workspace: Workspace }> = ({
|
|||
<SidebarNavItem href="" icon={<SidebarNavItemIcon icon={GeneralIcon} />}>
|
||||
General
|
||||
</SidebarNavItem>
|
||||
<SidebarNavItem
|
||||
href="parameters"
|
||||
icon={<SidebarNavItemIcon icon={ParameterIcon} />}
|
||||
>
|
||||
Parameters
|
||||
</SidebarNavItem>
|
||||
<SidebarNavItem
|
||||
href="schedule"
|
||||
icon={<SidebarNavItemIcon icon={ScheduleIcon} />}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
import {
|
||||
FormFields,
|
||||
FormFooter,
|
||||
FormSection,
|
||||
HorizontalForm,
|
||||
} from "components/Form/Form"
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"
|
||||
import { useFormik } from "formik"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
useValidationSchemaForRichParameters,
|
||||
workspaceBuildParameterValue,
|
||||
} from "utils/richParameters"
|
||||
import * as Yup from "yup"
|
||||
import { getFormHelpers } from "utils/formUtils"
|
||||
import {
|
||||
TemplateVersionParameter,
|
||||
WorkspaceBuildParameter,
|
||||
} from "api/typesGenerated"
|
||||
|
||||
export type WorkspaceParametersFormValues = {
|
||||
rich_parameter_values: WorkspaceBuildParameter[]
|
||||
}
|
||||
|
||||
export const WorkspaceParametersForm: FC<{
|
||||
isSubmitting: boolean
|
||||
templateVersionRichParameters: TemplateVersionParameter[]
|
||||
buildParameters: WorkspaceBuildParameter[]
|
||||
error: unknown
|
||||
onCancel: () => void
|
||||
onSubmit: (values: WorkspaceParametersFormValues) => void
|
||||
}> = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
templateVersionRichParameters,
|
||||
buildParameters,
|
||||
error,
|
||||
isSubmitting,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const mutableParameters = templateVersionRichParameters.filter(
|
||||
(param) => param.mutable === true,
|
||||
)
|
||||
const immutableParameters = templateVersionRichParameters.filter(
|
||||
(param) => param.mutable === false,
|
||||
)
|
||||
const form = useFormik<WorkspaceParametersFormValues>({
|
||||
onSubmit,
|
||||
initialValues: {
|
||||
rich_parameter_values: mutableParameters.map((parameter) => {
|
||||
const buildParameter = buildParameters.find(
|
||||
(p) => p.name === parameter.name,
|
||||
)
|
||||
if (!buildParameter) {
|
||||
return {
|
||||
name: parameter.name,
|
||||
value: parameter.default_value,
|
||||
}
|
||||
}
|
||||
return buildParameter
|
||||
}),
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
rich_parameter_values: useValidationSchemaForRichParameters(
|
||||
"createWorkspacePage",
|
||||
templateVersionRichParameters,
|
||||
),
|
||||
}),
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<WorkspaceParametersFormValues>(
|
||||
form,
|
||||
error,
|
||||
)
|
||||
|
||||
return (
|
||||
<HorizontalForm onSubmit={form.handleSubmit} data-testid="form">
|
||||
{mutableParameters.length > 0 && (
|
||||
<FormSection
|
||||
title={t("parameters")}
|
||||
description={t("parametersDescription")}
|
||||
>
|
||||
<FormFields>
|
||||
{mutableParameters.map((parameter, index) => (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={async (value) => {
|
||||
await form.setFieldValue("rich_parameter_values." + index, {
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
})
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
buildParameters,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
{/* They are displayed here only for visibility purposes */}
|
||||
{immutableParameters.length > 0 && (
|
||||
<FormSection
|
||||
title="Immutable parameters"
|
||||
description={
|
||||
<>
|
||||
These parameters are also provided by your Terraform configuration
|
||||
but they{" "}
|
||||
<strong>cannot be changed after creating the workspace.</strong>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormFields>
|
||||
{immutableParameters.map((parameter, index) => (
|
||||
<RichParameterInput
|
||||
disabled
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={async () => {
|
||||
throw new Error(
|
||||
"Cannot change immutable parameter after creation",
|
||||
)
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
buildParameters,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
|
||||
</HorizontalForm>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import {
|
||||
WorkspaceParametersPageView,
|
||||
WorkspaceParametersPageViewProps,
|
||||
} from "./WorkspaceParametersPage"
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import {
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter3,
|
||||
MockWorkspaceBuildParameter3,
|
||||
} from "testHelpers/entities"
|
||||
|
||||
export default {
|
||||
title: "pages/WorkspaceParametersPageView",
|
||||
component: WorkspaceParametersPageView,
|
||||
args: {
|
||||
submitError: undefined,
|
||||
isSubmitting: false,
|
||||
onCancel: action("cancel"),
|
||||
data: {
|
||||
buildParameters: [
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
MockWorkspaceBuildParameter3,
|
||||
],
|
||||
templateVersionRichParameters: [
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
{
|
||||
...MockTemplateVersionParameter3,
|
||||
mutable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof WorkspaceParametersPageView>
|
||||
|
||||
const Template: Story<WorkspaceParametersPageViewProps> = (args) => (
|
||||
<WorkspaceParametersPageView {...args} />
|
||||
)
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {}
|
|
@ -0,0 +1,73 @@
|
|||
import userEvent from "@testing-library/user-event"
|
||||
import {
|
||||
renderWithWorkspaceSettingsLayout,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers"
|
||||
import WorkspaceParametersPage from "./WorkspaceParametersPage"
|
||||
import { screen, waitFor, within } from "@testing-library/react"
|
||||
import * as api from "api/api"
|
||||
import {
|
||||
MockWorkspace,
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
MockWorkspaceBuild,
|
||||
} from "testHelpers/entities"
|
||||
|
||||
test("Submit the workspace settings page successfully", async () => {
|
||||
// Mock the API calls that loads data
|
||||
jest
|
||||
.spyOn(api, "getWorkspaceByOwnerAndName")
|
||||
.mockResolvedValueOnce(MockWorkspace)
|
||||
jest
|
||||
.spyOn(api, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
])
|
||||
jest
|
||||
.spyOn(api, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
// Mock the API calls that submit data
|
||||
const postWorkspaceBuildSpy = jest
|
||||
.spyOn(api, "postWorkspaceBuild")
|
||||
.mockResolvedValue(MockWorkspaceBuild)
|
||||
// Setup event and rendering
|
||||
const user = userEvent.setup()
|
||||
renderWithWorkspaceSettingsLayout(<WorkspaceParametersPage />, {
|
||||
route: "/@test-user/test-workspace/settings",
|
||||
path: "/@:username/:workspace/settings",
|
||||
// Need this because after submit the user is redirected
|
||||
extraRoutes: [{ path: "/@:username/:workspace", element: <div /> }],
|
||||
})
|
||||
await waitForLoaderToBeRemoved()
|
||||
// Fill the form and submit
|
||||
const form = screen.getByTestId("form")
|
||||
const parameter1 = within(form).getByLabelText(
|
||||
MockWorkspaceBuildParameter1.name,
|
||||
{ exact: false },
|
||||
)
|
||||
await user.clear(parameter1)
|
||||
await user.type(parameter1, "new-value")
|
||||
const parameter2 = within(form).getByLabelText(
|
||||
MockWorkspaceBuildParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
await user.clear(parameter2)
|
||||
await user.type(parameter2, "1")
|
||||
await user.click(within(form).getByRole("button", { name: "Submit" }))
|
||||
// Assert that the API calls were made with the correct data
|
||||
await waitFor(() => {
|
||||
expect(postWorkspaceBuildSpy).toHaveBeenCalledWith(MockWorkspace.id, {
|
||||
transition: "start",
|
||||
rich_parameter_values: [
|
||||
{ name: MockTemplateVersionParameter1.name, value: "new-value" },
|
||||
{ name: MockTemplateVersionParameter2.name, value: "1" },
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,115 @@
|
|||
import {
|
||||
getTemplateVersionRichParameters,
|
||||
getWorkspaceBuildParameters,
|
||||
postWorkspaceBuild,
|
||||
} from "api/api"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { pageTitle } from "utils/page"
|
||||
import { useWorkspaceSettingsContext } from "../WorkspaceSettingsLayout"
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import {
|
||||
WorkspaceParametersFormValues,
|
||||
WorkspaceParametersForm,
|
||||
} from "./WorkspaceParametersForm"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils"
|
||||
import { FC } from "react"
|
||||
|
||||
const getWorkspaceParameters = async (workspace: Workspace) => {
|
||||
const latestBuild = workspace.latest_build
|
||||
const [templateVersionRichParameters, buildParameters] = await Promise.all([
|
||||
getTemplateVersionRichParameters(latestBuild.template_version_id),
|
||||
getWorkspaceBuildParameters(latestBuild.id),
|
||||
])
|
||||
return {
|
||||
templateVersionRichParameters,
|
||||
buildParameters,
|
||||
}
|
||||
}
|
||||
|
||||
const WorkspaceParametersPage = () => {
|
||||
const { workspace } = useWorkspaceSettingsContext()
|
||||
const query = useQuery({
|
||||
queryKey: ["workspaceSettings", workspace.id],
|
||||
queryFn: () => getWorkspaceParameters(workspace),
|
||||
})
|
||||
const navigate = useNavigate()
|
||||
const mutation = useMutation({
|
||||
mutationFn: (formValues: WorkspaceParametersFormValues) =>
|
||||
postWorkspaceBuild(workspace.id, {
|
||||
transition: "start",
|
||||
rich_parameter_values: formValues.rich_parameter_values,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
displaySuccess(
|
||||
"Parameters updated successfully",
|
||||
"A new build was started to apply the new parameters",
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle([workspace.name, "Parameters"])}</title>
|
||||
</Helmet>
|
||||
|
||||
<WorkspaceParametersPageView
|
||||
data={query.data}
|
||||
submitError={mutation.error}
|
||||
isSubmitting={mutation.isLoading}
|
||||
onSubmit={mutation.mutate}
|
||||
onCancel={() => {
|
||||
navigate("../..")
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export type WorkspaceParametersPageViewProps = {
|
||||
data: Awaited<ReturnType<typeof getWorkspaceParameters>> | undefined
|
||||
submitError: unknown
|
||||
isSubmitting: boolean
|
||||
onSubmit: (formValues: WorkspaceParametersFormValues) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export const WorkspaceParametersPageView: FC<
|
||||
WorkspaceParametersPageViewProps
|
||||
> = ({ data, submitError, isSubmitting, onSubmit, onCancel }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader className={styles.pageHeader}>
|
||||
<PageHeaderTitle>Workspace parameters</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
|
||||
{data ? (
|
||||
<WorkspaceParametersForm
|
||||
buildParameters={data.buildParameters}
|
||||
templateVersionRichParameters={data.templateVersionRichParameters}
|
||||
error={submitError}
|
||||
isSubmitting={isSubmitting}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
pageHeader: {
|
||||
paddingTop: 0,
|
||||
},
|
||||
}))
|
||||
|
||||
export default WorkspaceParametersPage
|
|
@ -4,56 +4,37 @@ import {
|
|||
FormSection,
|
||||
HorizontalForm,
|
||||
} from "components/Form/Form"
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"
|
||||
import { useFormik } from "formik"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
useValidationSchemaForRichParameters,
|
||||
workspaceBuildParameterValue,
|
||||
} from "utils/richParameters"
|
||||
import { WorkspaceSettings, WorkspaceSettingsFormValue } from "./data"
|
||||
import * as Yup from "yup"
|
||||
import { nameValidator, getFormHelpers, onChangeTrimmed } from "utils/formUtils"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
|
||||
export type WorkspaceSettingsFormValues = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const WorkspaceSettingsForm: FC<{
|
||||
isSubmitting: boolean
|
||||
settings: WorkspaceSettings
|
||||
workspace: Workspace
|
||||
error: unknown
|
||||
onCancel: () => void
|
||||
onSubmit: (values: WorkspaceSettingsFormValue) => void
|
||||
}> = ({ onCancel, onSubmit, settings, error, isSubmitting }) => {
|
||||
onSubmit: (values: WorkspaceSettingsFormValues) => void
|
||||
}> = ({ onCancel, onSubmit, workspace, error, isSubmitting }) => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const mutableParameters = settings.templateVersionRichParameters.filter(
|
||||
(param) => param.mutable,
|
||||
)
|
||||
const form = useFormik<WorkspaceSettingsFormValue>({
|
||||
|
||||
const form = useFormik<WorkspaceSettingsFormValues>({
|
||||
onSubmit,
|
||||
initialValues: {
|
||||
name: settings.workspace.name,
|
||||
rich_parameter_values: mutableParameters.map((parameter) => {
|
||||
const buildParameter = settings.buildParameters.find(
|
||||
(p) => p.name === parameter.name,
|
||||
)
|
||||
if (!buildParameter) {
|
||||
return {
|
||||
name: parameter.name,
|
||||
value: parameter.default_value,
|
||||
}
|
||||
}
|
||||
return buildParameter
|
||||
}),
|
||||
name: workspace.name,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: nameValidator(t("nameLabel")),
|
||||
rich_parameter_values: useValidationSchemaForRichParameters(
|
||||
"createWorkspacePage",
|
||||
settings.templateVersionRichParameters,
|
||||
),
|
||||
}),
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<WorkspaceSettingsFormValue>(
|
||||
const getFieldHelpers = getFormHelpers<WorkspaceSettingsFormValues>(
|
||||
form,
|
||||
error,
|
||||
)
|
||||
|
@ -76,36 +57,6 @@ export const WorkspaceSettingsForm: FC<{
|
|||
/>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
{mutableParameters.length > 0 && (
|
||||
<FormSection
|
||||
title={t("parameters")}
|
||||
description={t("parametersDescription")}
|
||||
>
|
||||
<FormFields>
|
||||
{mutableParameters.map((parameter, index) => (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={isSubmitting}
|
||||
index={index}
|
||||
key={parameter.name}
|
||||
onChange={async (value) => {
|
||||
await form.setFieldValue("rich_parameter_values." + index, {
|
||||
name: parameter.name,
|
||||
value: value,
|
||||
})
|
||||
}}
|
||||
parameter={parameter}
|
||||
initialValue={workspaceBuildParameterValue(
|
||||
settings.buildParameters,
|
||||
parameter,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)}
|
||||
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
|
||||
</HorizontalForm>
|
||||
)
|
||||
|
|
|
@ -6,39 +6,17 @@ import {
|
|||
import WorkspaceSettingsPage from "./WorkspaceSettingsPage"
|
||||
import { screen, waitFor, within } from "@testing-library/react"
|
||||
import * as api from "api/api"
|
||||
import {
|
||||
MockWorkspace,
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
MockWorkspaceBuild,
|
||||
} from "testHelpers/entities"
|
||||
import { MockWorkspace } from "testHelpers/entities"
|
||||
|
||||
test("Submit the workspace settings page successfully", async () => {
|
||||
// Mock the API calls that loads data
|
||||
jest
|
||||
.spyOn(api, "getWorkspaceByOwnerAndName")
|
||||
.mockResolvedValueOnce(MockWorkspace)
|
||||
jest
|
||||
.spyOn(api, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
])
|
||||
jest
|
||||
.spyOn(api, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
// Mock the API calls that submit data
|
||||
const patchWorkspaceSpy = jest
|
||||
.spyOn(api, "patchWorkspace")
|
||||
.mockResolvedValue()
|
||||
const postWorkspaceBuildSpy = jest
|
||||
.spyOn(api, "postWorkspaceBuild")
|
||||
.mockResolvedValue(MockWorkspaceBuild)
|
||||
// Setup event and rendering
|
||||
const user = userEvent.setup()
|
||||
renderWithWorkspaceSettingsLayout(<WorkspaceSettingsPage />, {
|
||||
|
@ -53,18 +31,6 @@ test("Submit the workspace settings page successfully", async () => {
|
|||
const name = within(form).getByLabelText("Name")
|
||||
await user.clear(name)
|
||||
await user.type(within(form).getByLabelText("Name"), "new-name")
|
||||
const parameter1 = within(form).getByLabelText(
|
||||
MockWorkspaceBuildParameter1.name,
|
||||
{ exact: false },
|
||||
)
|
||||
await user.clear(parameter1)
|
||||
await user.type(parameter1, "new-value")
|
||||
const parameter2 = within(form).getByLabelText(
|
||||
MockWorkspaceBuildParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
await user.clear(parameter2)
|
||||
await user.type(parameter2, "1")
|
||||
await user.click(within(form).getByRole("button", { name: "Submit" }))
|
||||
// Assert that the API calls were made with the correct data
|
||||
await waitFor(() => {
|
||||
|
@ -72,11 +38,4 @@ test("Submit the workspace settings page successfully", async () => {
|
|||
name: "new-name",
|
||||
})
|
||||
})
|
||||
expect(postWorkspaceBuildSpy).toHaveBeenCalledWith(MockWorkspace.id, {
|
||||
transition: "start",
|
||||
rich_parameter_values: [
|
||||
{ name: MockTemplateVersionParameter1.name, value: "new-value" },
|
||||
{ name: MockTemplateVersionParameter2.name, value: "1" },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import { getErrorMessage } from "api/errors"
|
||||
import { displayError } from "components/GlobalSnackbar/utils"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { pageTitle } from "utils/page"
|
||||
import { useUpdateWorkspaceSettings, useWorkspaceSettings } from "./data"
|
||||
import { useWorkspaceSettingsContext } from "./WorkspaceSettingsLayout"
|
||||
import { WorkspaceSettingsPageView } from "./WorkspaceSettingsPageView"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils"
|
||||
import { patchWorkspace } from "api/api"
|
||||
import { WorkspaceSettingsFormValues } from "./WorkspaceSettingsForm"
|
||||
|
||||
const WorkspaceSettingsPage = () => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const { username, workspace: workspaceName } = useParams() as {
|
||||
username: string
|
||||
workspace: string
|
||||
}
|
||||
const { workspace } = useWorkspaceSettingsContext()
|
||||
const { data: settings, error, isLoading } = useWorkspaceSettings(workspace)
|
||||
const navigate = useNavigate()
|
||||
const updateSettings = useUpdateWorkspaceSettings(workspace.id, {
|
||||
onSuccess: ({ name }) => {
|
||||
navigate(`/@${username}/${name}`)
|
||||
const mutation = useMutation({
|
||||
mutationFn: (formValues: WorkspaceSettingsFormValues) =>
|
||||
patchWorkspace(workspace.id, { name: formValues.name }),
|
||||
onSuccess: (_, formValues) => {
|
||||
displaySuccess("Workspace updated successfully")
|
||||
navigate(`/@${username}/${formValues.name}/settings`)
|
||||
},
|
||||
onError: (error) =>
|
||||
displayError(getErrorMessage(error, t("defaultErrorMessage"))),
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -32,13 +31,11 @@ const WorkspaceSettingsPage = () => {
|
|||
</Helmet>
|
||||
|
||||
<WorkspaceSettingsPageView
|
||||
formError={updateSettings.error}
|
||||
loadingError={error}
|
||||
isLoading={isLoading}
|
||||
isSubmitting={updateSettings.isLoading}
|
||||
settings={settings}
|
||||
error={mutation.error}
|
||||
isSubmitting={mutation.isLoading}
|
||||
workspace={workspace}
|
||||
onCancel={() => navigate(`/@${username}/${workspaceName}`)}
|
||||
onSubmit={updateSettings.mutate}
|
||||
onSubmit={mutation.mutate}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,35 +1,19 @@
|
|||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockWorkspace,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
} from "testHelpers/entities"
|
||||
import { MockWorkspace } from "testHelpers/entities"
|
||||
import {
|
||||
WorkspaceSettingsPageView,
|
||||
WorkspaceSettingsPageViewProps,
|
||||
} from "./WorkspaceSettingsPageView"
|
||||
import { action } from "@storybook/addon-actions"
|
||||
|
||||
export default {
|
||||
title: "pages/WorkspaceSettingsPageView",
|
||||
component: WorkspaceSettingsPageView,
|
||||
args: {
|
||||
formError: undefined,
|
||||
loadingError: undefined,
|
||||
isLoading: false,
|
||||
error: undefined,
|
||||
isSubmitting: false,
|
||||
settings: {
|
||||
workspace: MockWorkspace,
|
||||
buildParameters: [
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
],
|
||||
templateVersionRichParameters: [
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
],
|
||||
},
|
||||
workspace: MockWorkspace,
|
||||
onCancel: action("cancel"),
|
||||
},
|
||||
} as ComponentMeta<typeof WorkspaceSettingsPageView>
|
||||
|
||||
|
|
|
@ -1,30 +1,24 @@
|
|||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"
|
||||
import { FC } from "react"
|
||||
import { ComponentProps, FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { WorkspaceSettings, WorkspaceSettingsFormValue } from "./data"
|
||||
import { WorkspaceSettingsForm } from "./WorkspaceSettingsForm"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
|
||||
export type WorkspaceSettingsPageViewProps = {
|
||||
formError: unknown
|
||||
loadingError: unknown
|
||||
isLoading: boolean
|
||||
error: unknown
|
||||
isSubmitting: boolean
|
||||
settings: WorkspaceSettings | undefined
|
||||
workspace: Workspace
|
||||
onCancel: () => void
|
||||
onSubmit: (formValues: WorkspaceSettingsFormValue) => void
|
||||
onSubmit: ComponentProps<typeof WorkspaceSettingsForm>["onSubmit"]
|
||||
}
|
||||
|
||||
export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
isSubmitting,
|
||||
settings,
|
||||
formError,
|
||||
loadingError,
|
||||
error,
|
||||
workspace,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const styles = useStyles()
|
||||
|
@ -35,17 +29,13 @@ export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
|
|||
<PageHeaderTitle>{t("title")}</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
|
||||
{loadingError && <AlertBanner error={loadingError} severity="error" />}
|
||||
{isLoading && <Loader />}
|
||||
{settings && (
|
||||
<WorkspaceSettingsForm
|
||||
error={formError}
|
||||
isSubmitting={isSubmitting}
|
||||
settings={settings}
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
)}
|
||||
<WorkspaceSettingsForm
|
||||
error={error}
|
||||
isSubmitting={isSubmitting}
|
||||
workspace={workspace}
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import {
|
||||
getWorkspaceBuildParameters,
|
||||
getTemplateVersionRichParameters,
|
||||
patchWorkspace,
|
||||
postWorkspaceBuild,
|
||||
} from "api/api"
|
||||
import { Workspace, WorkspaceBuildParameter } from "api/typesGenerated"
|
||||
|
||||
const getWorkspaceSettings = async (workspace: Workspace) => {
|
||||
const latestBuild = workspace.latest_build
|
||||
const [templateVersionRichParameters, buildParameters] = await Promise.all([
|
||||
getTemplateVersionRichParameters(latestBuild.template_version_id),
|
||||
getWorkspaceBuildParameters(latestBuild.id),
|
||||
])
|
||||
return {
|
||||
workspace,
|
||||
templateVersionRichParameters,
|
||||
buildParameters,
|
||||
}
|
||||
}
|
||||
|
||||
export const useWorkspaceSettings = (workspace: Workspace) => {
|
||||
return useQuery({
|
||||
queryKey: ["workspaceSettings", workspace.id],
|
||||
queryFn: () => getWorkspaceSettings(workspace),
|
||||
})
|
||||
}
|
||||
|
||||
export type WorkspaceSettings = Awaited<ReturnType<typeof getWorkspaceSettings>>
|
||||
|
||||
export type WorkspaceSettingsFormValue = {
|
||||
name: string
|
||||
rich_parameter_values: WorkspaceBuildParameter[]
|
||||
}
|
||||
|
||||
const updateWorkspaceSettings = async (
|
||||
workspaceId: string,
|
||||
formValues: WorkspaceSettingsFormValue,
|
||||
) => {
|
||||
await Promise.all([
|
||||
patchWorkspace(workspaceId, { name: formValues.name }),
|
||||
postWorkspaceBuild(workspaceId, {
|
||||
transition: "start",
|
||||
rich_parameter_values: formValues.rich_parameter_values,
|
||||
}),
|
||||
])
|
||||
|
||||
return formValues // So we can get then on the onSuccess callback
|
||||
}
|
||||
|
||||
export const useUpdateWorkspaceSettings = (
|
||||
workspaceId?: string,
|
||||
options?: {
|
||||
onSuccess?: (
|
||||
result: Awaited<ReturnType<typeof updateWorkspaceSettings>>,
|
||||
) => void
|
||||
onError?: (error: unknown) => void
|
||||
},
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (formValues: WorkspaceSettingsFormValue) => {
|
||||
if (!workspaceId) {
|
||||
throw new Error("No workspace id")
|
||||
}
|
||||
return updateWorkspaceSettings(workspaceId, formValues)
|
||||
},
|
||||
onSuccess: options?.onSuccess,
|
||||
onError: options?.onError,
|
||||
})
|
||||
}
|
|
@ -1532,6 +1532,11 @@ export const MockWorkspaceBuildParameter2: TypesGen.WorkspaceBuildParameter = {
|
|||
value: "3",
|
||||
}
|
||||
|
||||
export const MockWorkspaceBuildParameter3: TypesGen.WorkspaceBuildParameter = {
|
||||
name: MockTemplateVersionParameter3.name,
|
||||
value: "my-database",
|
||||
}
|
||||
|
||||
export const MockWorkspaceBuildParameter5: TypesGen.WorkspaceBuildParameter = {
|
||||
name: MockTemplateVersionParameter5.name,
|
||||
value: "5",
|
||||
|
|
Loading…
Reference in New Issue