Skip to content

Commit a2be7c0

Browse files
authored
fix: create and read workspace page (#1294)
* Change name of existing workspace call * Add new api call (has handler already) * WorkspacesPage -> WorkspacePage * starting to replace swr * Add other api calls * Fix api call * Replace swr with xstate * Format * Test - wip * Fix route in template page * Fix endpoint in create workspace * Fix tests * Lint
1 parent 3dbcddc commit a2be7c0

File tree

14 files changed

+261
-66
lines changed

14 files changed

+261
-66
lines changed

site/src/AppRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { TemplatePage } from "./pages/TemplatesPages/OrganizationPage/TemplatePa
1919
import { TemplatesPage } from "./pages/TemplatesPages/TemplatesPage"
2020
import { CreateUserPage } from "./pages/UsersPage/CreateUserPage/CreateUserPage"
2121
import { UsersPage } from "./pages/UsersPage/UsersPage"
22-
import { WorkspacePage } from "./pages/WorkspacesPage/WorkspacesPage"
22+
import { WorkspacePage } from "./pages/WorkspacePage/WorkspacePage"
2323

2424
const TerminalPage = React.lazy(() => import("./pages/TerminalPage/TerminalPage"))
2525

site/src/api/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const provisioners: Types.Provisioner[] = [
2020

2121
export namespace Workspace {
2222
export const create = async (request: Types.CreateWorkspaceRequest): Promise<Types.Workspace> => {
23-
const response = await fetch(`/api/v2/users/me/workspaces`, {
23+
const response = await fetch(`/api/v2/organizations/${request.organization_id}/workspaces`, {
2424
method: "POST",
2525
headers: {
2626
"Content-Type": "application/json",
@@ -80,12 +80,27 @@ export const getUsers = async (): Promise<TypesGen.User[]> => {
8080
return response.data
8181
}
8282

83+
export const getOrganization = async (organizationId: string): Promise<Types.Organization> => {
84+
const response = await axios.get<Types.Organization>(`/api/v2/organizations/${organizationId}`)
85+
return response.data
86+
}
87+
8388
export const getOrganizations = async (): Promise<Types.Organization[]> => {
8489
const response = await axios.get<Types.Organization[]>("/api/v2/users/me/organizations")
8590
return response.data
8691
}
8792

88-
export const getWorkspace = async (
93+
export const getTemplate = async (templateId: string): Promise<Types.Template> => {
94+
const response = await axios.get<Types.Template>(`/api/v2/templates/${templateId}`)
95+
return response.data
96+
}
97+
98+
export const getWorkspace = async (workspaceId: string): Promise<Types.Workspace> => {
99+
const response = await axios.get<Types.Workspace>(`/api/v2/workspaces/${workspaceId}`)
100+
return response.data
101+
}
102+
103+
export const getWorkspaceByOwnerAndName = async (
89104
organizationID: string,
90105
username = "me",
91106
workspaceName: string,

site/src/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface CreateTemplateRequest {
6161
export interface CreateWorkspaceRequest {
6262
name: string
6363
template_id: string
64+
organization_id: string
6465
}
6566

6667
export interface WorkspaceBuild {

site/src/forms/CreateWorkspaceForm.test.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render, screen } from "@testing-library/react"
22
import React from "react"
3-
import { MockTemplate, MockWorkspace } from "../testHelpers"
3+
import { MockOrganization, MockTemplate, MockWorkspace } from "../testHelpers"
44
import { CreateWorkspaceForm } from "./CreateWorkspaceForm"
55

66
describe("CreateWorkspaceForm", () => {
@@ -10,7 +10,14 @@ describe("CreateWorkspaceForm", () => {
1010
const onCancel = () => Promise.resolve()
1111

1212
// When
13-
render(<CreateWorkspaceForm template={MockTemplate} onSubmit={onSubmit} onCancel={onCancel} />)
13+
render(
14+
<CreateWorkspaceForm
15+
template={MockTemplate}
16+
onSubmit={onSubmit}
17+
onCancel={onCancel}
18+
organization_id={MockOrganization.id}
19+
/>,
20+
)
1421

1522
// Then
1623
// Simple smoke test to verify form renders

site/src/forms/CreateWorkspaceForm.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ export interface CreateWorkspaceForm {
1515
template: Template
1616
onSubmit: (request: CreateWorkspaceRequest) => Promise<Workspace>
1717
onCancel: () => void
18+
organization_id: string
1819
}
1920

2021
const validationSchema = Yup.object({
2122
name: Yup.string().required("Name is required"),
2223
})
2324

24-
export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({ template, onSubmit, onCancel }) => {
25+
export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({
26+
template,
27+
onSubmit,
28+
onCancel,
29+
organization_id,
30+
}) => {
2531
const styles = useStyles()
2632

2733
const form: FormikContextType<{ name: string }> = useFormik<{ name: string }>({
@@ -34,6 +40,7 @@ export const CreateWorkspaceForm: React.FC<CreateWorkspaceForm> = ({ template, o
3440
return onSubmit({
3541
template_id: template.id,
3642
name: name,
43+
organization_id,
3744
})
3845
},
3946
})

site/src/pages/TemplatesPages/OrganizationPage/TemplatePage/CreateWorkspacePage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { makeStyles } from "@material-ui/core/styles"
2-
import React, { useCallback } from "react"
2+
import { useSelector } from "@xstate/react"
3+
import React, { useCallback, useContext } from "react"
34
import { useNavigate, useParams } from "react-router-dom"
45
import useSWR from "swr"
56
import * as API from "../../../../api"
@@ -8,12 +9,17 @@ import { ErrorSummary } from "../../../../components/ErrorSummary/ErrorSummary"
89
import { FullScreenLoader } from "../../../../components/Loader/FullScreenLoader"
910
import { CreateWorkspaceForm } from "../../../../forms/CreateWorkspaceForm"
1011
import { unsafeSWRArgument } from "../../../../util"
12+
import { selectOrgId } from "../../../../xServices/auth/authSelectors"
13+
import { XServiceContext } from "../../../../xServices/StateContext"
1114

1215
export const CreateWorkspacePage: React.FC = () => {
1316
const { organization: organizationName, template: templateName } = useParams()
1417
const navigate = useNavigate()
1518
const styles = useStyles()
1619

20+
const xServices = useContext(XServiceContext)
21+
const myOrgId = useSelector(xServices.authXService, selectOrgId)
22+
1723
const { data: organizationInfo, error: organizationError } = useSWR<Types.Organization, Error>(
1824
() => `/api/v2/users/me/organizations/${organizationName}`,
1925
)
@@ -44,9 +50,13 @@ export const CreateWorkspacePage: React.FC = () => {
4450
return <FullScreenLoader />
4551
}
4652

53+
if (!myOrgId) {
54+
return <ErrorSummary error={Error("no organization id")} />
55+
}
56+
4757
return (
4858
<div className={styles.root}>
49-
<CreateWorkspaceForm onCancel={onCancel} onSubmit={onSubmit} template={template} />
59+
<CreateWorkspaceForm onCancel={onCancel} onSubmit={onSubmit} template={template} organization_id={myOrgId} />
5060
</div>
5161
)
5262
}

site/src/pages/TemplatesPages/OrganizationPage/TemplatePage/TemplatePage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export const TemplatePage: React.FC = () => {
2626

2727
// This just grabs all workspaces... and then later filters them to match the
2828
// current template.
29-
const { data: workspaces, error: workspacesError } = useSWR<Workspace[], Error>(() => `/api/v2/users/me/workspaces`)
29+
30+
const { data: workspaces, error: workspacesError } = useSWR<Workspace[], Error>(
31+
() => `/api/v2/organizations/${unsafeSWRArgument(organizationInfo).id}/workspaces`,
32+
)
3033

3134
if (organizationError) {
3235
return <ErrorSummary error={organizationError} />
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { screen } from "@testing-library/react"
2+
import React from "react"
3+
import { MockTemplate, MockWorkspace, renderWithAuth } from "../../testHelpers"
4+
import { WorkspacePage } from "./WorkspacePage"
5+
6+
describe("Workspace Page", () => {
7+
it("shows a workspace", async () => {
8+
renderWithAuth(<WorkspacePage />, { route: `/workspaces/${MockWorkspace.id}`, path: "/workspaces/:workspace" })
9+
const workspaceName = await screen.findByText(MockWorkspace.name)
10+
const templateName = await screen.findByText(MockTemplate.name)
11+
expect(workspaceName).toBeDefined()
12+
expect(templateName).toBeDefined()
13+
})
14+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useActor } from "@xstate/react"
2+
import React, { useContext, useEffect } from "react"
3+
import { useParams } from "react-router-dom"
4+
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
5+
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
6+
import { Margins } from "../../components/Margins/Margins"
7+
import { Stack } from "../../components/Stack/Stack"
8+
import { Workspace } from "../../components/Workspace/Workspace"
9+
import { firstOrItem } from "../../util/array"
10+
import { XServiceContext } from "../../xServices/StateContext"
11+
12+
export const WorkspacePage: React.FC = () => {
13+
const { workspace: workspaceQueryParam } = useParams()
14+
const workspaceId = firstOrItem(workspaceQueryParam, null)
15+
16+
const xServices = useContext(XServiceContext)
17+
const [workspaceState, workspaceSend] = useActor(xServices.workspaceXService)
18+
const { workspace, template, organization, getWorkspaceError, getTemplateError, getOrganizationError } =
19+
workspaceState.context
20+
21+
/**
22+
* Get workspace, template, and organization on mount and whenever workspaceId changes.
23+
* workspaceSend should not change.
24+
*/
25+
useEffect(() => {
26+
workspaceId && workspaceSend({ type: "GET_WORKSPACE", workspaceId })
27+
}, [workspaceId, workspaceSend])
28+
29+
if (workspaceState.matches("error")) {
30+
return <ErrorSummary error={getWorkspaceError || getTemplateError || getOrganizationError} />
31+
} else if (!workspace || !template || !organization) {
32+
return <FullScreenLoader />
33+
} else {
34+
return (
35+
<Margins>
36+
<Stack spacing={4}>
37+
<Workspace organization={organization} template={template} workspace={workspace} />
38+
</Stack>
39+
</Margins>
40+
)
41+
}
42+
}

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

Lines changed: 0 additions & 54 deletions
This file was deleted.

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