Skip to content

refactor(site): Show immutable parameters in the settings #7383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() =>
Expand Down Expand Up @@ -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 />}
Expand Down
2 changes: 1 addition & 1 deletion site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ export const getWorkspaceBuildParameters = async (
return response.data
}
type Claims = {
license_expires?: jwt.NumericDate
license_expires?: number
account_type?: string
account_id?: string
trial: boolean
Expand Down
7 changes: 7 additions & 0 deletions site/src/pages/WorkspaceSettingsPage/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -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} />}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
)
}
Original file line number Diff line number Diff line change
@@ -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 = {}
Original file line number Diff line number Diff line change
@@ -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" },
],
})
})
})
Loading