8000 fix: create and read workspace page by presleyp · Pull Request #1294 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

fix: create and read workspace page #1294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
8000
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Replace swr with xstate
  • Loading branch information
presleyp committed May 4, 2022
commit 495aba680e81f3daec3cebff47e1d5be6f84207e
40 changes: 24 additions & 16 deletions site/src/pages/WorkspacePage/WorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import React from "react"
import { useActor } from "@xstate/react"
import React, { useContext, useEffect } from "react"
import { useParams } from "react-router-dom"
import * as Types from "../../api/types"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { Margins } from "../../components/Margins/Margins"
import { Stack } from "../../components/Stack/Stack"
import { Workspace } from "../../components/Workspace/Workspace"
import { firstOrItem } from "../../util/array"
import { XServiceContext } from "../../xServices/StateContext"


export const WorkspacePage: React.FC = () => {
const { workspace: workspaceQueryParam } = useParams()
const workspaceParam = firstOrItem(workspaceQueryParam, null)
const workspaceId = firstOrItem(workspaceQueryParam, null)

const [workspaceState, workspaceSend] = useActor(workspaceXService)
const xServices = useContext(XServiceContext)
const [workspaceState, workspaceSend] = useActor(xServices.workspaceXService)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this uses a global XState service. Would useMachine be more appropriate here since the machine should only be active for the lifecycle of this page?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking it would be active for longer but maybe that's not actually desired since it's for a specific workspace. I can make it local for now and useMachine and we can hoist it later if needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neato neato

Copy link
Contributor Author
@presleyp presleyp May 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kylecarbs Actually I think we'll want it to outlive the component because we're using separate pages for things like the create/edit form and the parts of the timeline. Does that sound right?

const { workspace, template, organization, getWorkspaceError, getTemplateError, getOrganizationError } = workspaceState.context

if (state.matches('error')) {
return <ErrorSummary error={getWorkspaceError || getTemplateError || getOrganizationError } />
}
/**
* Get workspace, template, and organization on mount and whenever workspaceId changes.
* workspaceSend should not change.
*/
useEffect(() => {
workspaceId && workspaceSend({ type: "GET_WORKSPACE", workspaceId })
}, [workspaceId, workspaceSend])

if (!workspace || !template || !organization) {
if (workspaceState.matches('error')) {
return <ErrorSummary error={getWorkspaceError || getTemplateError || getOrganizationError } />
} else if (!workspace || !template || !organization) {
return <FullScreenLoader />
} else {
return (
<Margins>
<Stack spacing={4}>
<Workspace organization={organization} template={template} workspace={workspace} />
</Stack>
</Margins>
)
}

return (
<Margins>
<Stack spacing={4}>
<Workspace organization={organization} template={template} workspace={workspace} />
</Stack>
</Margins>
)
}
3 changes: 3 additions & 0 deletions site/src/xServices/StateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { ActorRefFrom } from "xstate"
import { authMachine } from "./auth/authXService"
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
import { usersMachine } from "./users/usersXService"
import { workspaceMachine } from "./workspace/workspaceXService"

interface XServiceContextType {
authXService: ActorRefFrom<typeof authMachine>
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
usersXService: ActorRefFrom<typeof usersMachine>
workspaceXService: ActorRefFrom<typeof workspaceMachine>
}

/**
Expand All @@ -34,6 +36,7 @@ export const XServiceProvider: React.FC = ({ children }) => {
authXService: useInterpret(authMachine),
buildInfoXService: useInterpret(buildInfoMachine),
usersXService: useInterpret(() => usersMachine.withConfig({ actions: { redirectToUsersPage } })),
workspaceXService: useInterpret(workspaceMachine)
}}
>
{children}
Expand Down
110 changes: 93 additions & 17 deletions site/src/xServices/workspace/workspaceXService.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { assign, createMachine } from "xstate"
import * as API from "../../api"
import * as Types from "../../api/types"
import * as TypesGen from "../../api/typesGenerated"

`/api/v2/workspaces/${workspaceParam}`
`/api/v2/templates/${unsafeSWRArgument(workspace).template_id}`
`/api/v2/organizations/${unsafeSWRArgument(template).organization_id}`

interface WorkspaceContext {
workspace?: Types.Workspace,
template?: Types.Template,
organization?: Types.Organization
getWorkspaceError?: Error | unknown,
getTemplateError?: Error | unknown,
getOrganizationError?: Error | unknown,
}

type WorkspaceEvent = { type: "GET_WORKSPACE", workspaceName: string, organizationID: string }
type WorkspaceEvent = { type: "GET_WORKSPACE", workspaceId: string }

export const workspaceMachine = createMachine({
tsTypes: {} as import("./workspaceXService.typegen").Typegen0,
Expand Down Expand Up @@ -44,22 +42,100 @@ export const workspaceMachine = createMachine({
invoke: {
src: "getWorkspace",
id: 'getWorkspace',
onDone: {},
onError: {}
}
onDone: {
target: "gettingTemplate",
actions: ["assignWorkspace", "clearGetWorkspaceError"]
},
onError: {
target: "error",
actions: "assignGetWorkspaceError"
}
},
tags: "loading"
},
gettingTemplate: {
invoke: {
src: "getTemplate",
id: 'getTemplate',
onDone: {
target: "gettingOrganization",
actions: ["assignTemplate", "clearGetTemplateError"]
},
onError: {
target: "error",
actions: "assignGetTemplateError"
}
},
tags: "loading"
},
gettingOrganization: {
invoke: {
src: "getOrganization",
id: 'getOrganization',
onDone: {
target: "idle",
actions: ["assignOrganization", "clearGetOrganizationError"]
},
onError: {
target: "error",
actions: "assignGetOrganizationError"
},
},
tags: "loading"
},
gettingTemplate: {},
gettingOrganization: {},
error: {}
error: {
on: {
GET_WORKSPACE: "gettingWorkspace"
}
}
}
}, {
actions: {},
actions: {
assignWorkspace: assign({
workspace: (_, event) => event.data
}),
assignGetWorkspaceError: assign({
getWorkspaceError: (_, event) => event.data
}),
clearGetWorkspaceError: (context) => (
assign({ ...context, getWorkspaceError: undefined })
),
assignTemplate: assign({
template: (_, event) => event.data
}),
assignGetTemplateError: assign({
getTemplateError: (_, event) => event.data
}),
clearGetTemplateError: (context) => (
assign({ ...context, getTemplateError: undefined })
),
assignOrganization: assign({
organization: (_, event) => event.data
}),
assignGetOrganizationError: assign({
getOrganizationError: (_, event) => event.data
}),
clearGetOrganizationError: (context) => (
assign({ ...context, getOrganizationError: undefined })
)
},
services: {
getWorkspace: async (_, event) => {
return await API.getWorkspace(
event.organizationID,
"me",
event.workspaceName)
return await API.getWorkspace(event.workspaceId)
},
getTemplate: async (context) => {
if (context.workspace) {
return await API.getTemplate(context.workspace.template_id)
} else {
throw Error("Cannot get template without workspace")
}
},
getOrganization: async (context) => {
if (context.template) {
return await API.getOrganization(context.template.organization_id)
} else {
throw Error("Cannot get organization without template")
}
}
}
})
0