mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat(site): Add workspace settings page (#6612)
This commit is contained in:
@ -7,7 +7,6 @@ import GroupsPage from "pages/GroupsPage/GroupsPage"
|
||||
import LoginPage from "pages/LoginPage/LoginPage"
|
||||
import { SetupPage } from "pages/SetupPage/SetupPage"
|
||||
import { TemplateSettingsPage } from "pages/TemplateSettingsPage/TemplateSettingsPage"
|
||||
import { WorkspaceBuildParametersPage } from "pages/WorkspaceBuildParametersPage/WorkspaceBuildParametersPage"
|
||||
import TemplatesPage from "pages/TemplatesPage/TemplatesPage"
|
||||
import UsersPage from "pages/UsersPage/UsersPage"
|
||||
import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage"
|
||||
@ -126,6 +125,9 @@ const CreateTemplatePage = lazy(
|
||||
const TemplateVariablesPage = lazy(
|
||||
() => import("./pages/TemplateVariablesPage/TemplateVariablesPage"),
|
||||
)
|
||||
const WorkspaceSettingsPage = lazy(
|
||||
() => import("./pages/WorkspaceSettingsPage/WorkspaceSettingsPage"),
|
||||
)
|
||||
|
||||
export const AppRouter: FC = () => {
|
||||
return (
|
||||
@ -230,10 +232,7 @@ export const AppRouter: FC = () => {
|
||||
path="change-version"
|
||||
element={<WorkspaceChangeVersionPage />}
|
||||
/>
|
||||
<Route
|
||||
path="build-parameters"
|
||||
element={<WorkspaceBuildParametersPage />}
|
||||
/>
|
||||
<Route path="settings" element={<WorkspaceSettingsPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
|
@ -506,6 +506,13 @@ export const createWorkspace = async (
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const patchWorkspace = async (
|
||||
workspaceId: string,
|
||||
data: TypesGen.UpdateWorkspaceRequest,
|
||||
) => {
|
||||
await axios.patch(`/api/v2/workspaces/${workspaceId}`, data)
|
||||
}
|
||||
|
||||
export const getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
|
||||
const response = await axios.get("/api/v2/buildinfo")
|
||||
return response.data
|
||||
|
@ -54,9 +54,9 @@ export const ChangeVersionButton: FC<
|
||||
)
|
||||
}
|
||||
|
||||
export const BuildParametersButton: FC<
|
||||
React.PropsWithChildren<WorkspaceAction>
|
||||
> = ({ handleAction }) => {
|
||||
export const SettingsButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({
|
||||
handleAction,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("workspacePage")
|
||||
|
||||
@ -67,7 +67,7 @@ export const BuildParametersButton: FC<
|
||||
startIcon={<SettingsOutlined />}
|
||||
onClick={handleAction}
|
||||
>
|
||||
{t("actionButton.buildParameters")}
|
||||
{t("actionButton.settings")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ type FormProps = HTMLProps<HTMLFormElement> & {
|
||||
}
|
||||
|
||||
export const Form: FC<FormProps> = ({ direction, className, ...formProps }) => {
|
||||
const styles = useStyles()
|
||||
const styles = useStyles({ direction })
|
||||
|
||||
return (
|
||||
<FormContext.Provider value={{ direction }}>
|
||||
@ -136,6 +136,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
|
||||
formSectionInfo: {
|
||||
width: "100%",
|
||||
maxWidth: ({ direction }: FormContextValue = {}) =>
|
||||
direction === "horizontal" ? 312 : undefined,
|
||||
flexShrink: 0,
|
||||
|
@ -43,7 +43,7 @@ export interface WorkspaceProps {
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
handleChangeVersion: () => void
|
||||
handleBuildParameters: () => void
|
||||
handleSettings: () => void
|
||||
isUpdating: boolean
|
||||
workspace: TypesGen.Workspace
|
||||
resources?: TypesGen.WorkspaceResource[]
|
||||
@ -55,7 +55,6 @@ export interface WorkspaceProps {
|
||||
buildInfo?: TypesGen.BuildInfoResponse
|
||||
applicationsHost?: string
|
||||
template?: TypesGen.Template
|
||||
templateParameters?: TypesGen.TemplateVersionParameter[]
|
||||
quota_budget?: number
|
||||
}
|
||||
|
||||
@ -70,7 +69,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
handleChangeVersion,
|
||||
handleBuildParameters,
|
||||
handleSettings,
|
||||
workspace,
|
||||
isUpdating,
|
||||
resources,
|
||||
@ -82,7 +81,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
buildInfo,
|
||||
applicationsHost,
|
||||
template,
|
||||
templateParameters,
|
||||
quota_budget,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
@ -126,9 +124,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
/>
|
||||
<WorkspaceActions
|
||||
workspaceStatus={workspace.latest_build.status}
|
||||
hasTemplateParameters={
|
||||
templateParameters ? templateParameters.length > 0 : false
|
||||
}
|
||||
isOutdated={workspace.outdated}
|
||||
handleStart={handleStart}
|
||||
handleStop={handleStop}
|
||||
@ -136,7 +131,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancel={handleCancel}
|
||||
handleChangeVersion={handleChangeVersion}
|
||||
handleBuildParameters={handleBuildParameters}
|
||||
handleSettings={handleSettings}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
</Stack>
|
||||
|
@ -12,7 +12,6 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
workspaceStatus={
|
||||
props.workspaceStatus ?? Mocks.MockWorkspace.latest_build.status
|
||||
}
|
||||
hasTemplateParameters={props.hasTemplateParameters ?? false}
|
||||
isOutdated={props.isOutdated ?? false}
|
||||
handleStart={jest.fn()}
|
||||
handleStop={jest.fn()}
|
||||
@ -20,7 +19,7 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
handleUpdate={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
handleChangeVersion={jest.fn()}
|
||||
handleBuildParameters={jest.fn()}
|
||||
handleSettings={jest.fn()}
|
||||
isUpdating={false}
|
||||
/>,
|
||||
)
|
||||
@ -32,7 +31,6 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
workspaceStatus={
|
||||
props.workspaceStatus ?? Mocks.MockWorkspace.latest_build.status
|
||||
}
|
||||
hasTemplateParameters={props.hasTemplateParameters ?? false}
|
||||
isOutdated={props.isOutdated ?? false}
|
||||
handleStart={jest.fn()}
|
||||
handleStop={jest.fn()}
|
||||
@ -40,7 +38,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
handleUpdate={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
handleChangeVersion={jest.fn()}
|
||||
handleBuildParameters={jest.fn()}
|
||||
handleSettings={jest.fn()}
|
||||
isUpdating={false}
|
||||
/>,
|
||||
)
|
||||
@ -91,20 +89,6 @@ describe("WorkspaceActions", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
describe("when the workspace with rich parameters is started", () => {
|
||||
it("primary is stop; secondary is build parameters", async () => {
|
||||
await renderAndClick({
|
||||
workspaceStatus: Mocks.MockWorkspace.latest_build.status,
|
||||
hasTemplateParameters: true,
|
||||
})
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(
|
||||
t("actionButton.stop", { ns: "workspacePage" }),
|
||||
)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(
|
||||
t("actionButton.buildParameters", { ns: "workspacePage" }),
|
||||
)
|
||||
})
|
||||
})
|
||||
describe("when the workspace is stopping", () => {
|
||||
it("primary is stopping; cancel is available; no secondary", async () => {
|
||||
await renderComponent({
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
ChangeVersionButton,
|
||||
DeleteButton,
|
||||
DisabledButton,
|
||||
BuildParametersButton,
|
||||
SettingsButton,
|
||||
StartButton,
|
||||
StopButton,
|
||||
UpdateButton,
|
||||
@ -16,7 +16,6 @@ import { ButtonMapping, ButtonTypesEnum, buttonAbilities } from "./constants"
|
||||
|
||||
export interface WorkspaceActionsProps {
|
||||
workspaceStatus: WorkspaceStatus
|
||||
hasTemplateParameters: boolean
|
||||
isOutdated: boolean
|
||||
handleStart: () => void
|
||||
handleStop: () => void
|
||||
@ -24,14 +23,13 @@ export interface WorkspaceActionsProps {
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
handleChangeVersion: () => void
|
||||
handleBuildParameters: () => void
|
||||
handleSettings: () => void
|
||||
isUpdating: boolean
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
workspaceStatus,
|
||||
hasTemplateParameters,
|
||||
isOutdated,
|
||||
handleStart,
|
||||
handleStop,
|
||||
@ -39,14 +37,11 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
handleChangeVersion,
|
||||
handleBuildParameters,
|
||||
handleSettings,
|
||||
isUpdating,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspacePage")
|
||||
const { canCancel, canAcceptJobs, actions } = buttonAbilities(
|
||||
workspaceStatus,
|
||||
hasTemplateParameters,
|
||||
)
|
||||
const { canCancel, canAcceptJobs, actions } = buttonAbilities(workspaceStatus)
|
||||
const canBeUpdated = isOutdated && canAcceptJobs
|
||||
|
||||
// A mapping of button type to the corresponding React component
|
||||
@ -58,8 +53,8 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
[ButtonTypesEnum.changeVersion]: (
|
||||
<ChangeVersionButton handleAction={handleChangeVersion} />
|
||||
),
|
||||
[ButtonTypesEnum.buildParameters]: (
|
||||
<BuildParametersButton handleAction={handleBuildParameters} />
|
||||
[ButtonTypesEnum.settings]: (
|
||||
<SettingsButton handleAction={handleSettings} />
|
||||
),
|
||||
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
|
||||
[ButtonTypesEnum.starting]: (
|
||||
|
@ -12,7 +12,7 @@ export enum ButtonTypesEnum {
|
||||
update = "update",
|
||||
updating = "updating",
|
||||
changeVersion = "changeVersion",
|
||||
buildParameters = "buildParameters",
|
||||
settings = "settings",
|
||||
// disabled buttons
|
||||
canceling = "canceling",
|
||||
deleted = "deleted",
|
||||
@ -31,19 +31,8 @@ interface WorkspaceAbilities {
|
||||
|
||||
export const buttonAbilities = (
|
||||
status: WorkspaceStatus,
|
||||
hasTemplateParameters: boolean,
|
||||
): WorkspaceAbilities => {
|
||||
if (hasTemplateParameters) {
|
||||
return statusToAbilities[status]
|
||||
}
|
||||
|
||||
const all = statusToAbilities[status]
|
||||
return {
|
||||
...all,
|
||||
actions: all.actions.filter(
|
||||
(action) => action !== ButtonTypesEnum.buildParameters,
|
||||
),
|
||||
}
|
||||
return statusToAbilities[status]
|
||||
}
|
||||
|
||||
const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
@ -55,7 +44,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
running: {
|
||||
actions: [
|
||||
ButtonTypesEnum.stop,
|
||||
ButtonTypesEnum.buildParameters,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
@ -70,7 +59,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
stopped: {
|
||||
actions: [
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.buildParameters,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
@ -81,7 +70,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
actions: [
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.stop,
|
||||
ButtonTypesEnum.buildParameters,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
@ -92,7 +81,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
failed: {
|
||||
actions: [
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.buildParameters,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
|
@ -12,7 +12,6 @@ import templateSettingsPage from "./templateSettingsPage.json"
|
||||
import templateVariablesPage from "./templateVariablesPage.json"
|
||||
import templateVersionPage from "./templateVersionPage.json"
|
||||
import loginPage from "./loginPage.json"
|
||||
import workspaceBuildParametersPage from "./workspaceBuildParametersPage.json"
|
||||
import workspaceChangeVersionPage from "./workspaceChangeVersionPage.json"
|
||||
import workspaceSchedulePage from "./workspaceSchedulePage.json"
|
||||
import appearanceSettings from "./appearanceSettings.json"
|
||||
@ -21,6 +20,7 @@ import starterTemplatePage from "./starterTemplatePage.json"
|
||||
import createTemplatePage from "./createTemplatePage.json"
|
||||
import userSettingsPage from "./userSettingsPage.json"
|
||||
import tokensPage from "./tokensPage.json"
|
||||
import workspaceSettingsPage from "./workspaceSettingsPage.json"
|
||||
|
||||
export const en = {
|
||||
common,
|
||||
@ -37,7 +37,6 @@ export const en = {
|
||||
templateVariablesPage,
|
||||
templateVersionPage,
|
||||
loginPage,
|
||||
workspaceBuildParametersPage,
|
||||
workspaceChangeVersionPage,
|
||||
workspaceSchedulePage,
|
||||
appearanceSettings,
|
||||
@ -46,4 +45,5 @@ export const en = {
|
||||
createTemplatePage,
|
||||
userSettingsPage,
|
||||
tokensPage,
|
||||
workspaceSettingsPage,
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"title": "Workspace build parameters",
|
||||
"detail": "Those values were provided by the workspace owner.",
|
||||
"noParametersDefined": "This template does not use any rich parameters.",
|
||||
"validationNumberNotInRange": "Value must be between {{min}} and {{max}}.",
|
||||
"validationPatternNotMatched": "{{error}} (value does not match the pattern {{pattern}}).",
|
||||
"updateWorkspace": "Update workspace",
|
||||
"validationNumberNotIncreasing": "The value must be equal or greater than the previous one {{last}}.",
|
||||
"validationNumberNotDecreasing": "The value must be equal or lower than the previous one {{last}}."
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
"stopping": "Stopping...",
|
||||
"deleting": "Deleting...",
|
||||
"changeVersion": "Change version",
|
||||
"buildParameters": "Build parameters"
|
||||
"settings": "Settings"
|
||||
},
|
||||
"disabledButton": {
|
||||
"canceling": "Canceling",
|
||||
|
9
site/src/i18n/en/workspaceSettingsPage.json
Normal file
9
site/src/i18n/en/workspaceSettingsPage.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Workspace Settings",
|
||||
"defaultErrorMessage": "Error on update workspace settings",
|
||||
"nameLabel": "Name",
|
||||
"generalInfo": "General info",
|
||||
"generalInfoDescription": "The name of your new workspace.",
|
||||
"parameters": "Parameters",
|
||||
"parametersDescription": "Those values are provided by your template's Terraform configuration. Values can be changed after creating the workspace."
|
||||
}
|
@ -24,6 +24,7 @@ import { makeStyles } from "@material-ui/core/styles"
|
||||
import {
|
||||
selectInitialRichParametersValues,
|
||||
useValidationSchemaForRichParameters,
|
||||
workspaceBuildParameterValue,
|
||||
} from "util/richParameters"
|
||||
|
||||
export enum CreateWorkspaceErrors {
|
||||
@ -401,13 +402,3 @@ const useStyles = makeStyles((theme) => ({
|
||||
marginRight: -theme.spacing(10),
|
||||
},
|
||||
}))
|
||||
|
||||
export const workspaceBuildParameterValue = (
|
||||
workspaceBuildParameters: TypesGen.WorkspaceBuildParameter[],
|
||||
parameter: TypesGen.TemplateVersionParameter,
|
||||
): string => {
|
||||
const buildParameter = workspaceBuildParameters.find((buildParameter) => {
|
||||
return buildParameter.name === parameter.name
|
||||
})
|
||||
return (buildParameter && buildParameter.value) || ""
|
||||
}
|
||||
|
@ -1,210 +0,0 @@
|
||||
import { fireEvent, screen } from "@testing-library/react"
|
||||
import {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter5,
|
||||
MockWorkspace,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
MockWorkspaceBuildParameter5,
|
||||
renderWithAuth,
|
||||
} from "testHelpers/renderHelpers"
|
||||
import * as API from "api/api"
|
||||
import i18next from "i18next"
|
||||
import { WorkspaceBuildParametersPage } from "./WorkspaceBuildParametersPage"
|
||||
|
||||
const { t } = i18next
|
||||
|
||||
const pageTitleText = t("title", { ns: "workspaceBuildParametersPage" })
|
||||
const validationNumberNotInRangeText = t("validationNumberNotInRange", {
|
||||
ns: "workspaceBuildParametersPage",
|
||||
min: "1",
|
||||
max: "3",
|
||||
})
|
||||
const validationNumberNotIncreasing = t("validationNumberNotIncreasing", {
|
||||
ns: "workspaceBuildParametersPage",
|
||||
last: "3",
|
||||
})
|
||||
const validationNumberNotDecreasing = t("validationNumberNotDecreasing", {
|
||||
ns: "workspaceBuildParametersPage",
|
||||
last: "5",
|
||||
})
|
||||
|
||||
const renderWorkspaceBuildParametersPage = () => {
|
||||
return renderWithAuth(<WorkspaceBuildParametersPage />, {
|
||||
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/build-parameters`,
|
||||
path: `/@:ownerName/:workspaceName/build-parameters`,
|
||||
})
|
||||
}
|
||||
|
||||
describe("WorkspaceBuildParametersPage", () => {
|
||||
it("renders without rich parameters", async () => {
|
||||
jest.spyOn(API, "getWorkspace").mockResolvedValueOnce(MockWorkspace)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([])
|
||||
jest
|
||||
.spyOn(API, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
renderWorkspaceBuildParametersPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const goBackButton = await screen.findByText("Go back")
|
||||
expect(goBackButton).toBeDefined()
|
||||
})
|
||||
|
||||
it("renders with rich parameter", async () => {
|
||||
jest.spyOn(API, "getWorkspace").mockResolvedValueOnce(MockWorkspace)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
|
||||
renderWorkspaceBuildParametersPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const firstParameter = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter1.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(firstParameter).toBeDefined()
|
||||
|
||||
const secondParameter = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameter).toBeDefined()
|
||||
})
|
||||
|
||||
it("rich parameter: number validation fails", async () => {
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
renderWorkspaceBuildParametersPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
const secondParameter = await screen.findByText(
|
||||
MockTemplateVersionParameter2.description,
|
||||
)
|
||||
expect(secondParameter).toBeDefined()
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
fireEvent.change(secondParameterField, {
|
||||
target: { value: "4" },
|
||||
})
|
||||
fireEvent.submit(secondParameter)
|
||||
|
||||
const validationError = await screen.findByText(
|
||||
validationNumberNotInRangeText,
|
||||
)
|
||||
expect(validationError).toBeDefined()
|
||||
})
|
||||
|
||||
it("rich parameter: number is not monotonically increasing", async () => {
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
])
|
||||
renderWorkspaceBuildParametersPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
const secondParameter = await screen.findByText(
|
||||
MockTemplateVersionParameter2.description,
|
||||
)
|
||||
expect(secondParameter).toBeDefined()
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter2.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
fireEvent.change(secondParameterField, {
|
||||
target: { value: "1" },
|
||||
})
|
||||
fireEvent.submit(secondParameter)
|
||||
|
||||
const validationError = await screen.findByText(
|
||||
validationNumberNotIncreasing,
|
||||
)
|
||||
expect(validationError).toBeDefined()
|
||||
})
|
||||
|
||||
it("rich parameter: number is not monotonically decreasing", async () => {
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionRichParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter5,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "getWorkspaceBuildParameters")
|
||||
.mockResolvedValueOnce([
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter5,
|
||||
])
|
||||
renderWorkspaceBuildParametersPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
const secondParameter = await screen.findByText(
|
||||
MockTemplateVersionParameter5.description,
|
||||
)
|
||||
expect(secondParameter).toBeDefined()
|
||||
|
||||
const secondParameterField = await screen.findByLabelText(
|
||||
MockTemplateVersionParameter5.name,
|
||||
{ exact: false },
|
||||
)
|
||||
expect(secondParameterField).toBeDefined()
|
||||
|
||||
fireEvent.change(secondParameterField, {
|
||||
target: { value: "6" },
|
||||
})
|
||||
fireEvent.submit(secondParameter)
|
||||
|
||||
const validationError = await screen.findByText(
|
||||
validationNumberNotDecreasing,
|
||||
)
|
||||
expect(validationError).toBeDefined()
|
||||
})
|
||||
})
|
@ -1,82 +0,0 @@
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { pageTitle } from "util/page"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { workspaceBuildParametersMachine } from "xServices/workspace/workspaceBuildParametersXService"
|
||||
import {
|
||||
UpdateWorkspaceErrors,
|
||||
WorkspaceBuildParametersPageView,
|
||||
} from "./WorkspaceBuildParametersPageView"
|
||||
import { orderedTemplateParameters } from "pages/CreateWorkspacePage/CreateWorkspacePage"
|
||||
|
||||
export const WorkspaceBuildParametersPage: FC = () => {
|
||||
const { t } = useTranslation("workspaceBuildParametersPage")
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { owner: workspaceOwner, workspace: workspaceName } = useParams() as {
|
||||
owner: string
|
||||
workspace: string
|
||||
}
|
||||
const [state, send] = useMachine(workspaceBuildParametersMachine, {
|
||||
context: {
|
||||
workspaceOwner,
|
||||
workspaceName,
|
||||
},
|
||||
actions: {
|
||||
onUpdateWorkspace: (_, event) => {
|
||||
navigate(
|
||||
`/@${event.data.workspace_owner_name}/${event.data.workspace_name}`,
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
const {
|
||||
selectedWorkspace,
|
||||
templateParameters,
|
||||
workspaceBuildParameters,
|
||||
getWorkspaceError,
|
||||
getTemplateParametersError,
|
||||
getWorkspaceBuildParametersError,
|
||||
updateWorkspaceError,
|
||||
} = state.context
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle(t("title"))}</title>
|
||||
</Helmet>
|
||||
<WorkspaceBuildParametersPageView
|
||||
workspace={selectedWorkspace}
|
||||
templateParameters={orderedTemplateParameters(templateParameters)}
|
||||
workspaceBuildParameters={workspaceBuildParameters}
|
||||
isLoading={
|
||||
state.matches("gettingWorkspace") ||
|
||||
state.matches("gettingTemplateParameters") ||
|
||||
state.matches("gettingWorkspaceBuildParameters")
|
||||
}
|
||||
updatingWorkspace={state.matches("updatingWorkspace")}
|
||||
hasErrors={state.matches("error")}
|
||||
updateWorkspaceErrors={{
|
||||
[UpdateWorkspaceErrors.GET_WORKSPACE_ERROR]: getWorkspaceError,
|
||||
[UpdateWorkspaceErrors.GET_TEMPLATE_PARAMETERS_ERROR]:
|
||||
getTemplateParametersError,
|
||||
[UpdateWorkspaceErrors.GET_WORKSPACE_BUILD_PARAMETERS_ERROR]:
|
||||
getWorkspaceBuildParametersError,
|
||||
[UpdateWorkspaceErrors.UPDATE_WORKSPACE_ERROR]: updateWorkspaceError,
|
||||
}}
|
||||
onCancel={() => {
|
||||
// Go back
|
||||
navigate(-1)
|
||||
}}
|
||||
onSubmit={(request) => {
|
||||
send({
|
||||
type: "UPDATE_WORKSPACE",
|
||||
request,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter3,
|
||||
MockTemplateVersionParameter4,
|
||||
MockWorkspace,
|
||||
} from "testHelpers/entities"
|
||||
import {
|
||||
WorkspaceBuildParametersPageView,
|
||||
WorkspaceBuildParametersPageViewProps,
|
||||
} from "./WorkspaceBuildParametersPageView"
|
||||
|
||||
export default {
|
||||
title: "pages/WorkspaceBuildParametersPageView",
|
||||
component: WorkspaceBuildParametersPageView,
|
||||
} as ComponentMeta<typeof WorkspaceBuildParametersPageView>
|
||||
|
||||
const Template: Story<WorkspaceBuildParametersPageViewProps> = (args) => (
|
||||
<WorkspaceBuildParametersPageView {...args} />
|
||||
)
|
||||
|
||||
export const NoRichParametersDefined = Template.bind({})
|
||||
NoRichParametersDefined.args = {
|
||||
workspace: MockWorkspace,
|
||||
templateParameters: [],
|
||||
workspaceBuildParameters: [],
|
||||
updateWorkspaceErrors: {},
|
||||
initialTouched: {
|
||||
name: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const RichParametersDefined = Template.bind({})
|
||||
RichParametersDefined.args = {
|
||||
workspace: MockWorkspace,
|
||||
templateParameters: [
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter3,
|
||||
MockTemplateVersionParameter4,
|
||||
],
|
||||
workspaceBuildParameters: [],
|
||||
updateWorkspaceErrors: {},
|
||||
initialTouched: {
|
||||
name: true,
|
||||
},
|
||||
}
|
@ -1,367 +0,0 @@
|
||||
import { FC } from "react"
|
||||
import { FullPageForm } from "components/FullPageForm/FullPageForm"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as TypesGen from "api/typesGenerated"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { getFormHelpers } from "util/formUtils"
|
||||
import { FormikContextType, FormikTouched, useFormik } from "formik"
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"
|
||||
import { workspaceBuildParameterValue } from "pages/CreateWorkspacePage/CreateWorkspacePageView"
|
||||
import { FormFooter } from "components/FormFooter/FormFooter"
|
||||
import * as Yup from "yup"
|
||||
import { Maybe } from "components/Conditionals/Maybe"
|
||||
import { GoBackButton } from "components/GoBackButton/GoBackButton"
|
||||
import { useValidationSchemaForRichParameters } from "util/richParameters"
|
||||
|
||||
export enum UpdateWorkspaceErrors {
|
||||
GET_WORKSPACE_ERROR = "getWorkspaceError",
|
||||
GET_TEMPLATE_PARAMETERS_ERROR = "getTemplateParametersError",
|
||||
GET_WORKSPACE_BUILD_PARAMETERS_ERROR = "getWorkspaceBuildParametersError",
|
||||
UPDATE_WORKSPACE_ERROR = "updateWorkspaceError",
|
||||
}
|
||||
|
||||
export interface WorkspaceBuildParametersPageViewProps {
|
||||
workspace?: TypesGen.Workspace
|
||||
templateParameters?: TypesGen.TemplateVersionParameter[]
|
||||
workspaceBuildParameters?: TypesGen.WorkspaceBuildParameter[]
|
||||
|
||||
isLoading: boolean
|
||||
initialTouched?: FormikTouched<TypesGen.CreateWorkspaceRequest>
|
||||
updatingWorkspace: boolean
|
||||
onCancel: () => void
|
||||
onSubmit: (req: TypesGen.CreateWorkspaceBuildRequest) => void
|
||||
|
||||
hasErrors: boolean
|
||||
updateWorkspaceErrors: Partial<Record<UpdateWorkspaceErrors, Error | unknown>>
|
||||
}
|
||||
|
||||
export const WorkspaceBuildParametersPageView: FC<
|
||||
React.PropsWithChildren<WorkspaceBuildParametersPageViewProps>
|
||||
> = (props) => {
|
||||
const { t } = useTranslation("workspaceBuildParametersPage")
|
||||
const styles = useStyles()
|
||||
const formFooterStyles = useFormFooterStyles()
|
||||
|
||||
const initialRichParameterValues = selectInitialRichParametersValues(
|
||||
props.templateParameters,
|
||||
props.workspaceBuildParameters,
|
||||
)
|
||||
|
||||
const form: FormikContextType<TypesGen.CreateWorkspaceBuildRequest> =
|
||||
useFormik<TypesGen.CreateWorkspaceBuildRequest>({
|
||||
initialValues: {
|
||||
template_version_id: props.workspace
|
||||
? props.workspace.latest_build.template_version_id
|
||||
: "",
|
||||
transition: "start",
|
||||
rich_parameter_values: initialRichParameterValues,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
rich_parameter_values: useValidationSchemaForRichParameters(
|
||||
"workspaceBuildParametersPage",
|
||||
props.templateParameters,
|
||||
initialRichParameterValues,
|
||||
),
|
||||
}),
|
||||
enableReinitialize: true,
|
||||
initialTouched: props.initialTouched,
|
||||
onSubmit: (request) => {
|
||||
props.onSubmit(
|
||||
stripImmutableParameters(request, props.templateParameters),
|
||||
)
|
||||
form.setSubmitting(false)
|
||||
},
|
||||
})
|
||||
|
||||
const getFieldHelpers = getFormHelpers<TypesGen.CreateWorkspaceBuildRequest>(
|
||||
form,
|
||||
props.updateWorkspaceErrors[UpdateWorkspaceErrors.UPDATE_WORKSPACE_ERROR],
|
||||
)
|
||||
|
||||
{
|
||||
props.hasErrors && (
|
||||
<Stack>
|
||||
{Boolean(
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_WORKSPACE_ERROR
|
||||
],
|
||||
) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_WORKSPACE_ERROR
|
||||
]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{Boolean(
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_TEMPLATE_PARAMETERS_ERROR
|
||||
],
|
||||
) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_TEMPLATE_PARAMETERS_ERROR
|
||||
]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{Boolean(
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_WORKSPACE_BUILD_PARAMETERS_ERROR
|
||||
],
|
||||
) && (
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.GET_WORKSPACE_BUILD_PARAMETERS_ERROR
|
||||
]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<FullPageForm title={t("title")} detail={t("detail")}>
|
||||
<Maybe
|
||||
condition={Boolean(
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.UPDATE_WORKSPACE_ERROR
|
||||
],
|
||||
)}
|
||||
>
|
||||
<AlertBanner
|
||||
severity="error"
|
||||
error={
|
||||
props.updateWorkspaceErrors[
|
||||
UpdateWorkspaceErrors.UPDATE_WORKSPACE_ERROR
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Maybe>
|
||||
|
||||
<Maybe
|
||||
condition={Boolean(
|
||||
!props.isLoading &&
|
||||
props.templateParameters &&
|
||||
props.templateParameters.length === 0,
|
||||
)}
|
||||
>
|
||||
<div className={styles.formSection}>
|
||||
<AlertBanner severity="info" text={t("noParametersDefined")} />
|
||||
<div className={styles.goBackSection}>
|
||||
<GoBackButton onClick={props.onCancel} />
|
||||
</div>
|
||||
</div>
|
||||
</Maybe>
|
||||
|
||||
{!props.isLoading &&
|
||||
props.templateParameters &&
|
||||
props.templateParameters.length > 0 &&
|
||||
props.workspaceBuildParameters && (
|
||||
<div className={styles.formSection}>
|
||||
<form onSubmit={form.handleSubmit}>
|
||||
<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.filter((p) => !p.mutable).length >
|
||||
0 && (
|
||||
<div className={styles.formSectionParameterTitle}>
|
||||
Immutable parameters
|
||||
</div>
|
||||
)}
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
!parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={!parameter.mutable || 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,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
||||
{props.templateParameters.filter((p) => p.mutable).length >
|
||||
0 && (
|
||||
<div className={styles.formSectionParameterTitle}>
|
||||
Mutable parameters
|
||||
</div>
|
||||
)}
|
||||
{props.templateParameters.map(
|
||||
(parameter, index) =>
|
||||
parameter.mutable && (
|
||||
<RichParameterInput
|
||||
{...getFieldHelpers(
|
||||
"rich_parameter_values[" + index + "].value",
|
||||
)}
|
||||
disabled={!parameter.mutable || 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,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<FormFooter
|
||||
styles={formFooterStyles}
|
||||
onCancel={props.onCancel}
|
||||
isLoading={props.updatingWorkspace}
|
||||
submitLabel={t("updateWorkspace")}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</FullPageForm>
|
||||
)
|
||||
}
|
||||
|
||||
const selectInitialRichParametersValues = (
|
||||
templateParameters?: TypesGen.TemplateVersionParameter[],
|
||||
workspaceBuildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||
): TypesGen.WorkspaceBuildParameter[] => {
|
||||
const defaults: TypesGen.WorkspaceBuildParameter[] = []
|
||||
if (!templateParameters) {
|
||||
return defaults
|
||||
}
|
||||
|
||||
templateParameters.forEach((parameter) => {
|
||||
if (parameter.options.length > 0) {
|
||||
let parameterValue = parameter.options[0].value
|
||||
if (workspaceBuildParameters) {
|
||||
const foundBuildParameter = workspaceBuildParameters.find(
|
||||
(buildParameter) => {
|
||||
return buildParameter.name === parameter.name
|
||||
},
|
||||
)
|
||||
if (foundBuildParameter) {
|
||||
parameterValue = foundBuildParameter.value
|
||||
}
|
||||
}
|
||||
|
||||
const buildParameter: TypesGen.WorkspaceBuildParameter = {
|
||||
name: parameter.name,
|
||||
value: parameterValue,
|
||||
}
|
||||
defaults.push(buildParameter)
|
||||
return
|
||||
}
|
||||
|
||||
let parameterValue = parameter.default_value
|
||||
if (workspaceBuildParameters) {
|
||||
const foundBuildParameter = workspaceBuildParameters.find(
|
||||
(buildParameter) => {
|
||||
return buildParameter.name === parameter.name
|
||||
},
|
||||
)
|
||||
if (foundBuildParameter) {
|
||||
parameterValue = foundBuildParameter.value
|
||||
}
|
||||
}
|
||||
|
||||
const buildParameter: TypesGen.WorkspaceBuildParameter = {
|
||||
name: parameter.name,
|
||||
value: parameterValue || "",
|
||||
}
|
||||
defaults.push(buildParameter)
|
||||
})
|
||||
return defaults
|
||||
}
|
||||
|
||||
const stripImmutableParameters = (
|
||||
request: TypesGen.CreateWorkspaceBuildRequest,
|
||||
templateParameters?: TypesGen.TemplateVersionParameter[],
|
||||
): TypesGen.CreateWorkspaceBuildRequest => {
|
||||
if (!templateParameters || !request.rich_parameter_values) {
|
||||
return request
|
||||
}
|
||||
|
||||
const mutableBuildParameters = request.rich_parameter_values.filter(
|
||||
(buildParameter) =>
|
||||
templateParameters.find(
|
||||
(templateParameter) => templateParameter.name === buildParameter.name,
|
||||
)?.mutable,
|
||||
)
|
||||
|
||||
return {
|
||||
...request,
|
||||
rich_parameter_values: mutableBuildParameters,
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
goBackSection: {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
marginTop: 32,
|
||||
},
|
||||
formSection: {
|
||||
marginTop: 20,
|
||||
},
|
||||
|
||||
formSectionFields: {
|
||||
width: "100%",
|
||||
},
|
||||
formSectionParameterTitle: {
|
||||
fontSize: 20,
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
margin: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
}))
|
||||
|
||||
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),
|
||||
},
|
||||
},
|
||||
}))
|
@ -46,7 +46,6 @@ export const WorkspaceReadyPage = ({
|
||||
const {
|
||||
workspace,
|
||||
template,
|
||||
templateParameters,
|
||||
builds,
|
||||
getBuildsError,
|
||||
buildError,
|
||||
@ -113,7 +112,7 @@ export const WorkspaceReadyPage = ({
|
||||
handleUpdate={() => workspaceSend({ type: "UPDATE" })}
|
||||
handleCancel={() => workspaceSend({ type: "CANCEL" })}
|
||||
handleChangeVersion={() => navigate("change-version")}
|
||||
handleBuildParameters={() => navigate("build-parameters")}
|
||||
handleSettings={() => navigate("settings")}
|
||||
resources={workspace.latest_build.resources}
|
||||
builds={builds}
|
||||
canUpdateWorkspace={canUpdateWorkspace}
|
||||
@ -127,7 +126,6 @@ export const WorkspaceReadyPage = ({
|
||||
buildInfo={buildInfo}
|
||||
applicationsHost={applicationsHost}
|
||||
template={template}
|
||||
templateParameters={templateParameters}
|
||||
quota_budget={quotaState.context.quota?.budget}
|
||||
/>
|
||||
<DeleteDialog
|
||||
|
109
site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx
Normal file
109
site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
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 "util/richParameters"
|
||||
import { WorkspaceSettings, WorkspaceSettingsFormValue } from "./data"
|
||||
import * as Yup from "yup"
|
||||
import { nameValidator, getFormHelpers, onChangeTrimmed } from "util/formUtils"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
|
||||
export const WorkspaceSettingsForm: FC<{
|
||||
isSubmitting: boolean
|
||||
settings: WorkspaceSettings
|
||||
error: unknown
|
||||
onCancel: () => void
|
||||
onSubmit: (values: WorkspaceSettingsFormValue) => void
|
||||
}> = ({ onCancel, onSubmit, settings, error, isSubmitting }) => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const mutableParameters = settings.templateVersionRichParameters.filter(
|
||||
(param) => param.mutable,
|
||||
)
|
||||
const form = useFormik<WorkspaceSettingsFormValue>({
|
||||
onSubmit,
|
||||
initialValues: {
|
||||
name: settings.workspace.name,
|
||||
rich_parameter_values: mutableParameters.map((parameter) => {
|
||||
const buildParameter = settings.buildParameters.find(
|
||||
(p) => p.name === parameter.name,
|
||||
)
|
||||
if (!buildParameter) {
|
||||
throw new Error("Missing build parameter for " + parameter.name)
|
||||
}
|
||||
return buildParameter
|
||||
}),
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: nameValidator(t("nameLabel")),
|
||||
rich_parameter_values: useValidationSchemaForRichParameters(
|
||||
"createWorkspacePage",
|
||||
settings.templateVersionRichParameters,
|
||||
),
|
||||
}),
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<WorkspaceSettingsFormValue>(
|
||||
form,
|
||||
error,
|
||||
)
|
||||
|
||||
return (
|
||||
<HorizontalForm onSubmit={form.handleSubmit} data-testid="form">
|
||||
<FormSection
|
||||
title={t("generalInfo")}
|
||||
description={t("generalInfoDescription")}
|
||||
>
|
||||
<FormFields>
|
||||
<TextField
|
||||
{...getFieldHelpers("name")}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={onChangeTrimmed(form)}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={t("nameLabel")}
|
||||
variant="outlined"
|
||||
/>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
{mutableParameters.length > 0 && (
|
||||
<FormSection
|
||||
title={t("parameters")}
|
||||
description={t("parametersDescription")}
|
||||
>
|
||||
<FormFields>
|
||||
{settings.templateVersionRichParameters.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>
|
||||
)
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockWorkspace,
|
||||
MockWorkspaceBuild,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
renderWithAuth,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers"
|
||||
import WorkspaceSettingsPage from "./WorkspaceSettingsPage"
|
||||
import { screen, waitFor, within } from "@testing-library/react"
|
||||
import * as api from "api/api"
|
||||
|
||||
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()
|
||||
renderWithAuth(<WorkspaceSettingsPage />, {
|
||||
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 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(() => {
|
||||
expect(patchWorkspaceSpy).toHaveBeenCalledWith(MockWorkspace.id, {
|
||||
name: "new-name",
|
||||
})
|
||||
expect(postWorkspaceBuildSpy).toHaveBeenCalledWith(MockWorkspace.id, {
|
||||
transition: "start",
|
||||
rich_parameter_values: [
|
||||
{ name: MockTemplateVersionParameter1.name, value: "new-value" },
|
||||
{ name: MockTemplateVersionParameter2.name, value: "1" },
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
@ -0,0 +1,49 @@
|
||||
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 "util/page"
|
||||
import { useUpdateWorkspaceSettings, useWorkspaceSettings } from "./data"
|
||||
import { WorkspaceSettingsPageView } from "./WorkspaceSettingsPageView"
|
||||
|
||||
const WorkspaceSettingsPage = () => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
const { username, workspace: workspaceName } = useParams() as {
|
||||
username: string
|
||||
workspace: string
|
||||
}
|
||||
const {
|
||||
data: settings,
|
||||
error,
|
||||
isLoading,
|
||||
} = useWorkspaceSettings(username, workspaceName)
|
||||
const navigate = useNavigate()
|
||||
const updateSettings = useUpdateWorkspaceSettings(settings?.workspace.id, {
|
||||
onSuccess: ({ name }) => {
|
||||
navigate(`/@${username}/${name}`)
|
||||
},
|
||||
onError: (error) =>
|
||||
displayError(getErrorMessage(error, t("defaultErrorMessage"))),
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle(t("title"))}</title>
|
||||
</Helmet>
|
||||
|
||||
<WorkspaceSettingsPageView
|
||||
formError={updateSettings.error}
|
||||
loadingError={error}
|
||||
isLoading={isLoading}
|
||||
isSubmitting={updateSettings.isLoading}
|
||||
settings={settings}
|
||||
onCancel={() => navigate(-1)}
|
||||
onSubmit={updateSettings.mutate}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkspaceSettingsPage
|
@ -0,0 +1,41 @@
|
||||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import {
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockWorkspace,
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
} from "testHelpers/entities"
|
||||
import {
|
||||
WorkspaceSettingsPageView,
|
||||
WorkspaceSettingsPageViewProps,
|
||||
} from "./WorkspaceSettingsPageView"
|
||||
|
||||
export default {
|
||||
title: "pages/WorkspaceSettingsPageView",
|
||||
component: WorkspaceSettingsPageView,
|
||||
args: {
|
||||
formError: undefined,
|
||||
loadingError: undefined,
|
||||
isLoading: false,
|
||||
isSubmitting: false,
|
||||
settings: {
|
||||
workspace: MockWorkspace,
|
||||
buildParameters: [
|
||||
MockWorkspaceBuildParameter1,
|
||||
MockWorkspaceBuildParameter2,
|
||||
],
|
||||
templateVersionRichParameters: [
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof WorkspaceSettingsPageView>
|
||||
|
||||
const Template: Story<WorkspaceSettingsPageViewProps> = (args) => (
|
||||
<WorkspaceSettingsPageView {...args} />
|
||||
)
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {}
|
@ -0,0 +1,47 @@
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { WorkspaceSettings, WorkspaceSettingsFormValue } from "./data"
|
||||
import { WorkspaceSettingsForm } from "./WorkspaceSettingsForm"
|
||||
|
||||
export type WorkspaceSettingsPageViewProps = {
|
||||
formError: unknown
|
||||
loadingError: unknown
|
||||
isLoading: boolean
|
||||
isSubmitting: boolean
|
||||
settings: WorkspaceSettings | undefined
|
||||
onCancel: () => void
|
||||
onSubmit: (formValues: WorkspaceSettingsFormValue) => void
|
||||
}
|
||||
|
||||
export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
isSubmitting,
|
||||
settings,
|
||||
formError,
|
||||
loadingError,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspaceSettingsPage")
|
||||
|
||||
return (
|
||||
<FullPageHorizontalForm title={t("title")} onCancel={onCancel}>
|
||||
<>
|
||||
{loadingError && <AlertBanner error={loadingError} severity="error" />}
|
||||
{isLoading && <Loader />}
|
||||
{settings && (
|
||||
<WorkspaceSettingsForm
|
||||
error={formError}
|
||||
isSubmitting={isSubmitting}
|
||||
settings={settings}
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</FullPageHorizontalForm>
|
||||
)
|
||||
}
|
73
site/src/pages/WorkspaceSettingsPage/data.ts
Normal file
73
site/src/pages/WorkspaceSettingsPage/data.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import {
|
||||
getWorkspaceByOwnerAndName,
|
||||
getWorkspaceBuildParameters,
|
||||
getTemplateVersionRichParameters,
|
||||
patchWorkspace,
|
||||
postWorkspaceBuild,
|
||||
} from "api/api"
|
||||
import { WorkspaceBuildParameter } from "api/typesGenerated"
|
||||
|
||||
const getWorkspaceSettings = async (owner: string, name: string) => {
|
||||
const workspace = await getWorkspaceByOwnerAndName(owner, name)
|
||||
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 = (owner: string, workspace: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["workspaceSettings", owner, workspace],
|
||||
queryFn: () => getWorkspaceSettings(owner, 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,
|
||||
})
|
||||
}
|
@ -146,3 +146,13 @@ export const useValidationSchemaForRichParameters = (
|
||||
)
|
||||
.required()
|
||||
}
|
||||
|
||||
export const workspaceBuildParameterValue = (
|
||||
workspaceBuildParameters: WorkspaceBuildParameter[],
|
||||
parameter: TemplateVersionParameter,
|
||||
): string => {
|
||||
const buildParameter = workspaceBuildParameters.find((buildParameter) => {
|
||||
return buildParameter.name === parameter.name
|
||||
})
|
||||
return (buildParameter && buildParameter.value) || ""
|
||||
}
|
||||
|
@ -1,223 +0,0 @@
|
||||
import {
|
||||
getTemplateVersionRichParameters,
|
||||
getWorkspaceByOwnerAndName,
|
||||
getWorkspaceBuildParameters,
|
||||
postWorkspaceBuild,
|
||||
} from "api/api"
|
||||
import {
|
||||
CreateWorkspaceBuildRequest,
|
||||
Template,
|
||||
TemplateVersionParameter,
|
||||
Workspace,
|
||||
WorkspaceBuild,
|
||||
WorkspaceBuildParameter,
|
||||
} from "api/typesGenerated"
|
||||
import { assign, createMachine } from "xstate"
|
||||
|
||||
type WorkspaceBuildParametersContext = {
|
||||
workspaceOwner: string
|
||||
workspaceName: string
|
||||
|
||||
selectedWorkspace?: Workspace
|
||||
selectedTemplate?: Template
|
||||
templateParameters?: TemplateVersionParameter[]
|
||||
workspaceBuildParameters?: WorkspaceBuildParameter[]
|
||||
|
||||
createWorkspaceBuildRequest?: CreateWorkspaceBuildRequest
|
||||
|
||||
getWorkspaceError?: Error | unknown
|
||||
getTemplateParametersError?: Error | unknown
|
||||
getWorkspaceBuildParametersError?: Error | unknown
|
||||
updateWorkspaceError?: Error | unknown
|
||||
}
|
||||
|
||||
type UpdateWorkspaceEvent = {
|
||||
type: "UPDATE_WORKSPACE"
|
||||
request: CreateWorkspaceBuildRequest
|
||||
}
|
||||
|
||||
export const workspaceBuildParametersMachine = createMachine(
|
||||
{
|
||||
id: "workspaceBuildParametersState",
|
||||
predictableActionArguments: true,
|
||||
tsTypes:
|
||||
{} as import("./workspaceBuildParametersXService.typegen").Typegen0,
|
||||
schema: {
|
||||
context: {} as WorkspaceBuildParametersContext,
|
||||
events: {} as UpdateWorkspaceEvent,
|
||||
services: {} as {
|
||||
getWorkspace: {
|
||||
data: Workspace
|
||||
}
|
||||
getTemplateParameters: {
|
||||
data: TemplateVersionParameter[]
|
||||
}
|
||||
getWorkspaceBuildParameters: {
|
||||
data: WorkspaceBuildParameter[]
|
||||
}
|
||||
updateWorkspace: {
|
||||
data: WorkspaceBuild
|
||||
}
|
||||
},
|
||||
},
|
||||
initial: "gettingWorkspace",
|
||||
states: {
|
||||
gettingWorkspace: {
|
||||
entry: "clearGetWorkspaceError",
|
||||
invoke: {
|
||||
src: "getWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignWorkspace"],
|
||||
target: "gettingTemplateParameters",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignGetWorkspaceError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
gettingTemplateParameters: {
|
||||
entry: "clearGetTemplateParametersError",
|
||||
invoke: {
|
||||
src: "getTemplateParameters",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignTemplateParameters"],
|
||||
target: "gettingWorkspaceBuildParameters",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignGetTemplateParametersError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
gettingWorkspaceBuildParameters: {
|
||||
entry: "clearGetWorkspaceBuildParametersError",
|
||||
invoke: {
|
||||
src: "getWorkspaceBuildParameters",
|
||||
onDone: {
|
||||
actions: ["assignWorkspaceBuildParameters"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignGetWorkspaceBuildParametersError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
fillingParams: {
|
||||
on: {
|
||||
UPDATE_WORKSPACE: {
|
||||
actions: ["assignCreateWorkspaceBuildRequest"],
|
||||
target: "updatingWorkspace",
|
||||
},
|
||||
},
|
||||
},
|
||||
updatingWorkspace: {
|
||||
entry: "clearUpdateWorkspaceError",
|
||||
invoke: {
|
||||
src: "updateWorkspace",
|
||||
onDone: {
|
||||
actions: ["onUpdateWorkspace"],
|
||||
target: "updated",
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignUpdateWorkspaceError"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
},
|
||||
},
|
||||
updated: {
|
||||
entry: "onUpdateWorkspace",
|
||||
type: "final",
|
||||
},
|
||||
error: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
services: {
|
||||
getWorkspace: (context) => {
|
||||
const { workspaceOwner, workspaceName } = context
|
||||
return getWorkspaceByOwnerAndName(workspaceOwner, workspaceName)
|
||||
},
|
||||
getTemplateParameters: (context) => {
|
||||
const { selectedWorkspace } = context
|
||||
|
||||
if (!selectedWorkspace) {
|
||||
throw new Error("No workspace selected")
|
||||
}
|
||||
|
||||
return getTemplateVersionRichParameters(
|
||||
selectedWorkspace.latest_build.template_version_id,
|
||||
)
|
||||
},
|
||||
getWorkspaceBuildParameters: (context) => {
|
||||
const { selectedWorkspace } = context
|
||||
|
||||
if (!selectedWorkspace) {
|
||||
throw new Error("No workspace selected")
|
||||
}
|
||||
|
||||
return getWorkspaceBuildParameters(selectedWorkspace.latest_build.id)
|
||||
},
|
||||
updateWorkspace: (context) => {
|
||||
const { selectedWorkspace, createWorkspaceBuildRequest } = context
|
||||
|
||||
if (!selectedWorkspace) {
|
||||
throw new Error("No workspace selected")
|
||||
}
|
||||
|
||||
if (!createWorkspaceBuildRequest) {
|
||||
throw new Error("No workspace build request")
|
||||
}
|
||||
|
||||
return postWorkspaceBuild(
|
||||
selectedWorkspace.id,
|
||||
createWorkspaceBuildRequest,
|
||||
)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
assignWorkspace: assign({
|
||||
selectedWorkspace: (_, event) => event.data,
|
||||
}),
|
||||
assignTemplateParameters: assign({
|
||||
templateParameters: (_, event) => event.data,
|
||||
}),
|
||||
assignWorkspaceBuildParameters: assign({
|
||||
workspaceBuildParameters: (_, event) => event.data,
|
||||
}),
|
||||
|
||||
assignCreateWorkspaceBuildRequest: assign({
|
||||
createWorkspaceBuildRequest: (_, event) => event.request,
|
||||
}),
|
||||
assignGetWorkspaceError: assign({
|
||||
getWorkspaceError: (_, event) => event.data,
|
||||
}),
|
||||
clearGetWorkspaceError: assign({
|
||||
getWorkspaceError: (_) => undefined,
|
||||
}),
|
||||
assignGetTemplateParametersError: assign({
|
||||
getTemplateParametersError: (_, event) => event.data,
|
||||
}),
|
||||
clearGetTemplateParametersError: assign({
|
||||
getTemplateParametersError: (_) => undefined,
|
||||
}),
|
||||
clearGetWorkspaceBuildParametersError: assign({
|
||||
getWorkspaceBuildParametersError: (_) => undefined,
|
||||
}),
|
||||
assignGetWorkspaceBuildParametersError: assign({
|
||||
getWorkspaceBuildParametersError: (_, event) => event.data,
|
||||
}),
|
||||
clearUpdateWorkspaceError: assign({
|
||||
updateWorkspaceError: (_) => undefined,
|
||||
}),
|
||||
assignUpdateWorkspaceError: assign({
|
||||
updateWorkspaceError: (_, event) => event.data,
|
||||
}),
|
||||
},
|
||||
},
|
||||
)
|
@ -55,7 +55,6 @@ export interface WorkspaceContext {
|
||||
eventSource?: EventSource
|
||||
workspace?: TypesGen.Workspace
|
||||
template?: TypesGen.Template
|
||||
templateParameters?: TypesGen.TemplateVersionParameter[]
|
||||
build?: TypesGen.WorkspaceBuild
|
||||
getWorkspaceError?: Error | unknown
|
||||
getTemplateWarning: Error | unknown
|
||||
@ -203,7 +202,7 @@ export const workspaceMachine = createMachine(
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignTemplate", "clearGetTemplateWarning"],
|
||||
target: "gettingTemplateParameters",
|
||||
target: "gettingPermissions",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
@ -218,31 +217,6 @@ export const workspaceMachine = createMachine(
|
||||
},
|
||||
tags: "loading",
|
||||
},
|
||||
gettingTemplateParameters: {
|
||||
invoke: {
|
||||
src: "getTemplateParameters",
|
||||
id: "getTemplateParameters",
|
||||
onDone: [
|
||||
{
|
||||
actions: [
|
||||
"assignTemplateParameters",
|
||||
"clearGetTemplateParametersWarning",
|
||||
],
|
||||
target: "gettingPermissions",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: [
|
||||
"assignGetTemplateParametersWarning",
|
||||
"displayGetTemplateParametersWarning",
|
||||
],
|
||||
target: "error",
|
||||
},
|
||||
],
|
||||
},
|
||||
tags: "loading",
|
||||
},
|
||||
gettingPermissions: {
|
||||
invoke: {
|
||||
src: "checkPermissions",
|
||||
@ -524,9 +498,6 @@ export const workspaceMachine = createMachine(
|
||||
assignTemplate: assign({
|
||||
template: (_, event) => event.data,
|
||||
}),
|
||||
assignTemplateParameters: assign({
|
||||
templateParameters: (_, event) => event.data,
|
||||
}),
|
||||
assignPermissions: assign({
|
||||
// Setting event.data as Permissions to be more stricted. So we know
|
||||
// what permissions we asked for.
|
||||
@ -587,15 +558,6 @@ export const workspaceMachine = createMachine(
|
||||
clearGetTemplateWarning: assign({
|
||||
getTemplateWarning: (_) => undefined,
|
||||
}),
|
||||
assignGetTemplateParametersWarning: assign({
|
||||
getTemplateParametersWarning: (_, event) => event.data,
|
||||
}),
|
||||
displayGetTemplateParametersWarning: () => {
|
||||
displayError(Language.getTemplateParametersWarning)
|
||||
},
|
||||
clearGetTemplateParametersWarning: assign({
|
||||
getTemplateParametersWarning: (_) => undefined,
|
||||
}),
|
||||
// Timeline
|
||||
assignBuilds: assign({
|
||||
builds: (_, event) => event.data,
|
||||
@ -667,15 +629,6 @@ export const workspaceMachine = createMachine(
|
||||
throw Error("Cannot get template without workspace")
|
||||
}
|
||||
},
|
||||
getTemplateParameters: async (context) => {
|
||||
if (context.workspace) {
|
||||
return await API.getTemplateVersionRichParameters(
|
||||
context.workspace.latest_build.template_version_id,
|
||||
)
|
||||
} else {
|
||||
throw Error("Cannot get template parameters without workspace")
|
||||
}
|
||||
},
|
||||
updateWorkspace:
|
||||
({ workspace }, { buildParameters }) =>
|
||||
async (send) => {
|
||||
|
Reference in New Issue
Block a user