Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6ef3f8a

Browse files
BrunoQuaresmakylecarbs
authored andcommitted
refactor: Update create workspace flow to allow creation from the workspaces page (#1684)
1 parent aaf08d5 commit 6ef3f8a

File tree

11 files changed

+366
-260
lines changed

11 files changed

+366
-260
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
"drpcserver",
1616
"Dsts",
1717
"fatih",
18+
"Formik",
1819
"goarch",
1920
"gographviz",
2021
"goleak",
2122
"gossh",
2223
"gsyslog",
2324
"hashicorp",
2425
"hclsyntax",
26+
"httpapi",
2527
"httpmw",
2628
"idtoken",
2729
"Iflag",
@@ -63,6 +65,7 @@
6365
"tfjson",
6466
"tfstate",
6567
"trimprefix",
68+
"typegen",
6669
"unconvert",
6770
"Untar",
6871
"VMID",

site/src/AppRouter.tsx

+10-11
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ export const AppRouter: React.FC = () => (
5656
</AuthAndFrame>
5757
}
5858
/>
59+
60+
<Route
61+
path="new"
62+
element={
63+
<RequireAuth>
64+
<CreateWorkspacePage />
65+
</RequireAuth>
66+
}
67+
/>
68+
5969
<Route path=":workspace">
6070
<Route
6171
index
@@ -85,17 +95,6 @@ export const AppRouter: React.FC = () => (
8595
</AuthAndFrame>
8696
}
8797
/>
88-
89-
<Route path=":template">
90-
<Route
91-
path="new"
92-
element={
93-
<RequireAuth>
94-
<CreateWorkspacePage />
95-
</RequireAuth>
96-
}
97-
/>
98-
</Route>
9998
</Route>
10099

101100
<Route path="users">

site/src/components/FormFooter/FormFooter.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const FormFooter: React.FC<FormFooterProps> = ({
3535
const styles = useStyles()
3636
return (
3737
<div className={styles.footer}>
38-
<Button className={styles.button} onClick={onCancel} variant="outlined">
38+
<Button type="button" className={styles.button} onClick={onCancel} variant="outlined">
3939
{Language.cancelLabel}
4040
</Button>
4141
<LoadingButton loading={isLoading} className={styles.button} variant="contained" color="primary" type="submit">

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

+11-8
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@ import { reach, StringSchema } from "yup"
55
import * as API from "../../api/api"
66
import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter"
77
import { MockTemplate, MockWorkspace } from "../../testHelpers/entities"
8-
import { history, render } from "../../testHelpers/renderHelpers"
8+
import { renderWithAuth } from "../../testHelpers/renderHelpers"
99
import CreateWorkspacePage from "./CreateWorkspacePage"
1010
import { Language, validationSchema } from "./CreateWorkspacePageView"
1111

12+
const renderCreateWorkspacePage = () => {
13+
return renderWithAuth(<CreateWorkspacePage />, {
14+
route: "/workspaces/new?template=" + MockTemplate.name,
15+
path: "/workspaces/new",
16+
})
17+
}
18+
1219
const fillForm = async ({ name = "example" }: { name?: string }) => {
1320
const nameField = await screen.findByLabelText(Language.nameLabel)
1421
await userEvent.type(nameField, name)
@@ -19,25 +26,21 @@ const fillForm = async ({ name = "example" }: { name?: string }) => {
1926
const nameSchema = reach(validationSchema, "name") as StringSchema
2027

2128
describe("CreateWorkspacePage", () => {
22-
beforeEach(() => {
23-
history.replace("/templates/" + MockTemplate.name + "/new")
24-
})
25-
2629
it("renders", async () => {
27-
render(<CreateWorkspacePage />)
30+
renderCreateWorkspacePage()
2831
const element = await screen.findByText("Create workspace")
2932
expect(element).toBeDefined()
3033
})
3134

3235
it("shows validation error message", async () => {
33-
render(<CreateWorkspacePage />)
36+
renderCreateWorkspacePage()
3437
await fillForm({ name: "$$$" })
3538
const errorMessage = await screen.findByText(Language.nameMatches)
3639
expect(errorMessage).toBeDefined()
3740
})
3841

3942
it("succeeds", async () => {
40-
render(<CreateWorkspacePage />)
43+
renderCreateWorkspacePage()
4144
// You have to spy the method before it is used.
4245
jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace)
4346
await fillForm({ name: "test" })

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

+48-25
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,59 @@
1-
import { useMachine } from "@xstate/react"
2-
import React from "react"
3-
import { useNavigate } from "react-router"
4-
import { useParams } from "react-router-dom"
5-
import { createWorkspace } from "../../api/api"
6-
import { templateMachine } from "../../xServices/template/templateXService"
1+
import { useActor, useMachine } from "@xstate/react"
2+
import React, { useContext } from "react"
3+
import { useNavigate, useSearchParams } from "react-router-dom"
4+
import { Template } from "../../api/typesGenerated"
5+
import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService"
6+
import { XServiceContext } from "../../xServices/StateContext"
77
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
88

9+
const useOrganizationId = () => {
10+
const xServices = useContext(XServiceContext)
11+
const [authState] = useActor(xServices.authXService)
12+
const organizationId = authState.context.me?.organization_ids[0]
13+
14+
if (!organizationId) {
15+
throw new Error("No organization ID found")
16+
}
17+
18+
return organizationId
19+
}
20+
921
const CreateWorkspacePage: React.FC = () => {
10-
const { template } = useParams()
11-
const [templateState] = useMachine(templateMachine, {
12-
context: {
13-
name: template,
22+
const organizationId = useOrganizationId()
23+
const [searchParams] = useSearchParams()
24+
const preSelectedTemplateName = searchParams.get("template")
25+
const navigate = useNavigate()
26+
const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, {
27+
context: { organizationId, preSelectedTemplateName },
28+
actions: {
29+
onCreateWorkspace: (_, event) => {
30+
navigate("/workspaces/" + event.data.id)
31+
},
1432
},
1533
})
16-
const navigate = useNavigate()
17-
const loading = templateState.hasTag("loading")
18-
if (!templateState.context.template || !templateState.context.templateSchema) {
19-
return null
20-
}
2134

2235
return (
2336
<CreateWorkspacePageView
24-
template={templateState.context.template}
25-
templateSchema={templateState.context.templateSchema}
26-
loading={loading}
27-
onCancel={() => navigate("/templates")}
28-
onSubmit={async (req) => {
29-
if (!templateState.context.template) {
30-
throw new Error("template isn't valid")
31-
}
32-
const workspace = await createWorkspace(templateState.context.template.organization_id, req)
33-
navigate("/workspaces/" + workspace.id)
37+
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
38+
loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")}
39+
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
40+
templates={createWorkspaceState.context.templates}
41+
selectedTemplate={createWorkspaceState.context.selectedTemplate}
42+
templateSchema={createWorkspaceState.context.templateSchema}
43+
onCancel={() => {
44+
navigate(preSelectedTemplateName ? "/templates" : "/workspaces")
45+
}}
46+
onSubmit={(request) => {
47+
send({
48+
type: "CREATE_WORKSPACE",
49+
request,
50+
})
51+
}}
52+
onSelectTemplate={(template: Template) => {
53+
send({
54+
type: "SELECT_TEMPLATE",
55+
template,
56+
})
3457
}}
3558
/>
3659
)

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
import { ComponentMeta, Story } from "@storybook/react"
22
import React from "react"
3-
import { createParameterSchema } from "../../components/ParameterInput/ParameterInput.stories"
3+
import { ParameterSchema } from "../../api/typesGenerated"
44
import { MockTemplate } from "../../testHelpers/entities"
55
import { CreateWorkspacePageView, CreateWorkspacePageViewProps } from "./CreateWorkspacePageView"
66

7+
const createParameterSchema = (partial: Partial<ParameterSchema>): ParameterSchema => {
8+
return {
9+
id: "000000",
10+
job_id: "000000",
11+
allow_override_destination: false,
12+
allow_override_source: true,
13+
created_at: "",
14+
default_destination_scheme: "none",
15+
default_refresh: "",
16+
default_source_scheme: "data",
17+
default_source_value: "default-value",
18+
name: "parameter name",
19+
description: "Some description!",
20+
redisplay_value: false,
21+
validation_condition: "",
22+
validation_contains: [],
23+
validation_error: "",
24+
validation_type_system: "",
25+
validation_value_type: "",
26+
...partial,
27+
}
28+
}
29+
730
export default {
831
title: "pages/CreateWorkspacePageView",
932
component: CreateWorkspacePageView,
@@ -13,13 +36,15 @@ const Template: Story<CreateWorkspacePageViewProps> = (args) => <CreateWorkspace
1336

1437
export const NoParameters = Template.bind({})
1538
NoParameters.args = {
16-
template: MockTemplate,
39+
templates: [MockTemplate],
40+
selectedTemplate: MockTemplate,
1741
templateSchema: [],
1842
}
1943

2044
export const Parameters = Template.bind({})
2145
Parameters.args = {
22-
template: MockTemplate,
46+
templates: [MockTemplate],
47+
selectedTemplate: MockTemplate,
2348
templateSchema: [
2449
createParameterSchema({
2550
name: "region",

0 commit comments

Comments
 (0)