Skip to content

Commit f54d385

Browse files
feat(site): add auto mode on create workspace form (#8651)
1 parent bc55ffd commit f54d385

16 files changed

+547
-560
lines changed

site/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"ts-prune": "0.10.3",
9696
"tzdata": "1.0.30",
9797
"ua-parser-js": "1.0.33",
98+
"unique-names-generator": "4.7.1",
9899
"uuid": "9.0.0",
99100
"vite": "4.4.2",
100101
"xstate": "4.38.1",

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
MockTemplateVersionParameter2,
1313
MockTemplateVersionParameter3,
1414
MockTemplateVersionGitAuth,
15+
MockOrganization,
1516
} from "testHelpers/entities"
1617
import {
1718
renderWithAuth,
@@ -217,4 +218,29 @@ describe("CreateWorkspacePage", () => {
217218

218219
await screen.findByText("You must authenticate to create a workspace!")
219220
})
221+
222+
it("auto create a workspace if uses mode=auto", async () => {
223+
const param = "first_parameter"
224+
const paramValue = "It works!"
225+
const createWorkspaceSpy = jest.spyOn(API, "createWorkspace")
226+
227+
renderWithAuth(<CreateWorkspacePage />, {
228+
route:
229+
"/templates/" +
230+
MockTemplate.name +
231+
`/workspace?param.${param}=${paramValue}&mode=auto`,
232+
path: "/templates/:template/workspace",
233+
})
234+
235+
await waitFor(() => {
236+
expect(createWorkspaceSpy).toBeCalledWith(
237+
MockOrganization.id,
238+
"me",
239+
expect.objectContaining({
240+
template_id: MockTemplate.id,
241+
rich_parameter_values: [{ name: param, value: paramValue }],
242+
}),
243+
)
244+
})
245+
})
220246
})
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,113 @@
11
import { useMachine } from "@xstate/react"
2-
import { TemplateVersionParameter } from "api/typesGenerated"
2+
import {
3+
Template,
4+
TemplateVersionGitAuth,
5+
TemplateVersionParameter,
6+
WorkspaceBuildParameter,
7+
} from "api/typesGenerated"
38
import { useMe } from "hooks/useMe"
49
import { useOrganizationId } from "hooks/useOrganizationId"
510
import { FC } from "react"
611
import { Helmet } from "react-helmet-async"
712
import { useNavigate, useParams, useSearchParams } from "react-router-dom"
813
import { pageTitle } from "utils/page"
9-
import { createWorkspaceMachine } from "xServices/createWorkspace/createWorkspaceXService"
1014
import {
11-
CreateWorkspaceErrors,
12-
CreateWorkspacePageView,
13-
} from "./CreateWorkspacePageView"
15+
CreateWSPermissions,
16+
CreateWorkspaceMode,
17+
createWorkspaceMachine,
18+
} from "xServices/createWorkspace/createWorkspaceXService"
19+
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
20+
import { Loader } from "components/Loader/Loader"
21+
import { ErrorAlert } from "components/Alert/ErrorAlert"
22+
import {
23+
uniqueNamesGenerator,
24+
animals,
25+
colors,
26+
NumberDictionary,
27+
} from "unique-names-generator"
1428

1529
const CreateWorkspacePage: FC = () => {
1630
const organizationId = useOrganizationId()
1731
const { template: templateName } = useParams() as { template: string }
18-
const navigate = useNavigate()
1932
const me = useMe()
33+
const navigate = useNavigate()
34+
const [searchParams] = useSearchParams()
35+
const defaultBuildParameters = getDefaultBuildParameters(searchParams)
36+
const mode = (searchParams.get("mode") ?? "form") as CreateWorkspaceMode
2037
const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, {
2138
context: {
2239
organizationId,
2340
templateName,
24-
owner: me,
41+
mode,
42+
defaultBuildParameters,
43+
defaultName:
44+
mode === "auto" ? generateUniqueName() : searchParams.get("name") ?? "",
2545
},
2646
actions: {
2747
onCreateWorkspace: (_, event) => {
2848
navigate(`/@${event.data.owner_name}/${event.data.name}`)
2949
},
3050
},
3151
})
32-
const {
33-
templates,
34-
templateParameters,
35-
templateGitAuth,
36-
selectedTemplate,
37-
getTemplateGitAuthError,
38-
getTemplatesError,
39-
createWorkspaceError,
40-
permissions,
41-
owner,
42-
} = createWorkspaceState.context
43-
const [searchParams] = useSearchParams()
44-
const defaultParameterValues = getDefaultParameterValues(searchParams)
45-
const name = getName(searchParams)
52+
const { template, error, parameters, permissions, gitAuth, defaultName } =
53+
createWorkspaceState.context
54+
const title = createWorkspaceState.matches("autoCreating")
55+
? "Creating workspace..."
56+
: "Create Workspace"
4657

4758
return (
4859
<>
4960
<Helmet>
50-
<title>{pageTitle("Create Workspace")}</title>
61+
<title>{pageTitle(title)}</title>
5162
</Helmet>
52-
<CreateWorkspacePageView
53-
name={name}
54-
defaultParameterValues={defaultParameterValues}
55-
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
56-
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
57-
hasTemplateErrors={createWorkspaceState.matches("error")}
58-
templateName={templateName}
59-
templates={templates}
60-
selectedTemplate={selectedTemplate}
61-
templateParameters={orderedTemplateParameters(templateParameters)}
62-
templateGitAuth={templateGitAuth}
63-
createWorkspaceErrors={{
64-
[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]: getTemplatesError,
65-
[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]: createWorkspaceError,
66-
[CreateWorkspaceErrors.GET_TEMPLATE_GITAUTH_ERROR]:
67-
getTemplateGitAuthError,
68-
}}
69-
canCreateForUser={permissions?.createWorkspaceForUser}
70-
owner={owner}
71-
setOwner={(user) => {
72-
send({
73-
type: "SELECT_OWNER",
74-
owner: user,
75-
})
76-
}}
77-
onCancel={() => {
78-
// Go back
79-
navigate(-1)
80-
}}
81-
onSubmit={(request) => {
82-
send({
83-
type: "CREATE_WORKSPACE",
84-
request,
85-
owner,
86-
})
87-
}}
88-
/>
63+
{Boolean(
64+
createWorkspaceState.matches("loadingFormData") ||
65+
createWorkspaceState.matches("autoCreating"),
66+
) && <Loader />}
67+
{createWorkspaceState.matches("loadError") && (
68+
<ErrorAlert error={error} />
69+
)}
70+
{createWorkspaceState.matches("idle") && (
71+
<CreateWorkspacePageView
72+
defaultName={defaultName}
73+
defaultOwner={me}
74+
defaultBuildParameters={defaultBuildParameters}
75+
error={error}
76+
template={template as Template}
77+
gitAuth={gitAuth as TemplateVersionGitAuth[]}
78+
permissions={permissions as CreateWSPermissions}
79+
parameters={parameters as TemplateVersionParameter[]}
80+
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
81+
onCancel={() => {
82+
navigate(-1)
83+
}}
84+
onSubmit={(request, owner) => {
85+
send({
86+
type: "CREATE_WORKSPACE",
87+
request,
88+
owner,
89+
})
90+
}}
91+
/>
92+
)}
8993
</>
9094
)
9195
}
9296

93-
const getName = (urlSearchParams: URLSearchParams): string => {
94-
return urlSearchParams.get("name") ?? ""
95-
}
97+
export default CreateWorkspacePage
9698

97-
const getDefaultParameterValues = (
99+
const getDefaultBuildParameters = (
98100
urlSearchParams: URLSearchParams,
99-
): Record<string, string> => {
100-
const paramValues: Record<string, string> = {}
101+
): WorkspaceBuildParameter[] => {
102+
const buildValues: WorkspaceBuildParameter[] = []
101103
Array.from(urlSearchParams.keys())
102104
.filter((key) => key.startsWith("param."))
103105
.forEach((key) => {
104-
const paramName = key.replace("param.", "")
105-
const paramValue = urlSearchParams.get(key)
106-
paramValues[paramName] = paramValue ?? ""
106+
const name = key.replace("param.", "")
107+
const value = urlSearchParams.get(key) ?? ""
108+
buildValues.push({ name, value })
107109
})
108-
return paramValues
110+
return buildValues
109111
}
110112

111113
export const orderedTemplateParameters = (
@@ -122,4 +124,12 @@ export const orderedTemplateParameters = (
122124
return [...immutables, ...mutables]
123125
}
124126

125-
export default CreateWorkspacePage
127+
const generateUniqueName = () => {
128+
const numberDictionary = NumberDictionary.generate({ min: 0, max: 99 })
129+
return uniqueNamesGenerator({
130+
dictionaries: [colors, animals, numberDictionary],
131+
separator: "-",
132+
length: 3,
133+
style: "lowerCase",
134+
})
135+
}

0 commit comments

Comments
 (0)