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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 26 additions & 0 deletions
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
})
Lines changed: 80 additions & 70 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy