chore(site): simplify the logic to load workspace initial data (#7772)

This commit is contained in:
Bruno Quaresma 2023-06-01 14:20:17 -03:00 committed by GitHub
parent 4de4e8ee21
commit 332362cf4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 301 additions and 406 deletions

View File

@ -1,12 +1,11 @@
import { useState, FC, ReactNode, PropsWithChildren } from "react"
import { useState, FC, ReactNode } from "react"
import Collapse from "@mui/material/Collapse"
// eslint-disable-next-line no-restricted-imports -- It is the base component
import MuiAlert, { AlertProps as MuiAlertProps } from "@mui/material/Alert"
import Button from "@mui/material/Button"
import Box from "@mui/material/Box"
export interface AlertProps extends PropsWithChildren {
severity: MuiAlertProps["severity"]
export type AlertProps = MuiAlertProps & {
actions?: ReactNode
dismissible?: boolean
onRetry?: () => void
@ -20,12 +19,14 @@ export const Alert: FC<AlertProps> = ({
dismissible,
severity,
onDismiss,
...alertProps
}) => {
const [open, setOpen] = useState(true)
return (
<Collapse in={open}>
<MuiAlert
{...alertProps}
severity={severity}
action={
<>

View File

@ -1,19 +1,56 @@
import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
import { Meta, StoryObj } from "@storybook/react"
import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata"
import { ProvisionerJobLog } from "api/typesGenerated"
import * as Mocks from "testHelpers/entities"
import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace"
import { Workspace, WorkspaceErrors } from "./Workspace"
import { withReactContext } from "storybook-react-context"
import EventSource from "eventsourcemock"
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
export default {
const MockedAppearance = {
config: Mocks.MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
const meta: Meta<typeof Workspace> = {
title: "components/Workspace",
component: Workspace,
argTypes: {},
decorators: [
(Story) => (
<DashboardProviderContext.Provider
value={{
buildInfo: Mocks.MockBuildInfo,
entitlements: Mocks.MockEntitlementsWithScheduling,
experiments: Mocks.MockExperiments,
appearance: MockedAppearance,
}}
>
<ProxyContext.Provider
value={{
proxyLatencies: Mocks.MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
clearProxy: () => {
return
},
setProxy: () => {
return
},
refetchProxyLatencies: () => {
return
},
}}
>
<Story />
</ProxyContext.Provider>
</DashboardProviderContext.Provider>
),
withReactContext({
Context: WatchAgentMetadataContext,
initialState: (_: string): EventSource => {
@ -23,172 +60,149 @@ export default {
}),
],
}
export default meta
type Story = StoryObj<typeof Workspace>
const MockedAppearance = {
config: Mocks.MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
const Template: Story<WorkspaceProps> = (args) => (
<DashboardProviderContext.Provider
value={{
buildInfo: Mocks.MockBuildInfo,
entitlements: Mocks.MockEntitlementsWithScheduling,
experiments: Mocks.MockExperiments,
appearance: MockedAppearance,
}}
>
<ProxyContext.Provider
value={{
proxyLatencies: Mocks.MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
clearProxy: () => {
return
},
setProxy: () => {
return
},
refetchProxyLatencies: () => {
return
},
}}
>
<Workspace {...args} />
</ProxyContext.Provider>
</DashboardProviderContext.Provider>
)
export const Running = Template.bind({})
Running.args = {
scheduleProps: {
onDeadlineMinus: () => {
// do nothing, this is just for storybook
export const Running: Story = {
args: {
scheduleProps: {
onDeadlineMinus: () => {
// do nothing, this is just for storybook
},
onDeadlinePlus: () => {
// do nothing, this is just for storybook
},
maxDeadlineDecrease: 0,
maxDeadlineIncrease: 24,
},
onDeadlinePlus: () => {
// do nothing, this is just for storybook
},
maxDeadlineDecrease: 0,
maxDeadlineIncrease: 24,
},
workspace: Mocks.MockWorkspace,
handleStart: action("start"),
handleStop: action("stop"),
resources: [
Mocks.MockWorkspaceResource,
Mocks.MockWorkspaceResource2,
Mocks.MockWorkspaceResource3,
],
builds: [Mocks.MockWorkspaceBuild],
canUpdateWorkspace: true,
workspaceErrors: {},
buildInfo: Mocks.MockBuildInfo,
template: Mocks.MockTemplate,
}
export const WithoutUpdateAccess = Template.bind({})
WithoutUpdateAccess.args = {
...Running.args,
canUpdateWorkspace: false,
}
export const Starting = Template.bind({})
Starting.args = {
...Running.args,
workspace: Mocks.MockStartingWorkspace,
}
export const Stopped = Template.bind({})
Stopped.args = {
...Running.args,
workspace: Mocks.MockStoppedWorkspace,
}
export const Stopping = Template.bind({})
Stopping.args = {
...Running.args,
workspace: Mocks.MockStoppingWorkspace,
}
export const Failed = Template.bind({})
Failed.args = {
...Running.args,
workspace: Mocks.MockFailedWorkspace,
workspaceErrors: {
[WorkspaceErrors.BUILD_ERROR]: Mocks.mockApiError({
message: "A workspace build is already active.",
}),
workspace: Mocks.MockWorkspace,
handleStart: action("start"),
handleStop: action("stop"),
resources: [
Mocks.MockWorkspaceResource,
Mocks.MockWorkspaceResource2,
Mocks.MockWorkspaceResource3,
],
builds: [Mocks.MockWorkspaceBuild],
canUpdateWorkspace: true,
workspaceErrors: {},
buildInfo: Mocks.MockBuildInfo,
template: Mocks.MockTemplate,
},
}
export const FailedWithLogs = Template.bind({})
FailedWithLogs.args = {
...Running.args,
workspace: {
...Mocks.MockFailedWorkspace,
latest_build: {
...Mocks.MockFailedWorkspace.latest_build,
job: {
...Mocks.MockFailedWorkspace.latest_build.job,
error:
"recv workspace provision: plan terraform: terraform plan: exit status 1",
export const WithoutUpdateAccess: Story = {
args: {
...Running.args,
canUpdateWorkspace: false,
},
}
export const Starting: Story = {
args: {
...Running.args,
workspace: Mocks.MockStartingWorkspace,
},
}
export const Stopped: Story = {
args: {
...Running.args,
workspace: Mocks.MockStoppedWorkspace,
},
}
export const Stopping: Story = {
args: {
...Running.args,
workspace: Mocks.MockStoppingWorkspace,
},
}
export const Failed: Story = {
args: {
...Running.args,
workspace: Mocks.MockFailedWorkspace,
workspaceErrors: {
[WorkspaceErrors.BUILD_ERROR]: Mocks.mockApiError({
message: "A workspace build is already active.",
}),
},
},
}
export const FailedWithLogs: Story = {
args: {
...Running.args,
workspace: {
...Mocks.MockFailedWorkspace,
latest_build: {
...Mocks.MockFailedWorkspace.latest_build,
job: {
...Mocks.MockFailedWorkspace.latest_build.job,
error:
"recv workspace provision: plan terraform: terraform plan: exit status 1",
},
},
},
},
failedBuildLogs: makeFailedBuildLogs(),
}
export const Deleting = Template.bind({})
Deleting.args = {
...Running.args,
workspace: Mocks.MockDeletingWorkspace,
}
export const Deleted = Template.bind({})
Deleted.args = {
...Running.args,
workspace: Mocks.MockDeletedWorkspace,
}
export const Canceling = Template.bind({})
Canceling.args = {
...Running.args,
workspace: Mocks.MockCancelingWorkspace,
}
export const Canceled = Template.bind({})
Canceled.args = {
...Running.args,
workspace: Mocks.MockCanceledWorkspace,
}
export const Outdated = Template.bind({})
Outdated.args = {
...Running.args,
workspace: Mocks.MockOutdatedWorkspace,
}
export const GetBuildsError = Template.bind({})
GetBuildsError.args = {
...Running.args,
workspaceErrors: {
[WorkspaceErrors.GET_BUILDS_ERROR]: Mocks.mockApiError({
message: "There is a problem fetching builds.",
}),
failedBuildLogs: makeFailedBuildLogs(),
},
}
export const CancellationError = Template.bind({})
CancellationError.args = {
...Failed.args,
workspaceErrors: {
[WorkspaceErrors.CANCELLATION_ERROR]: Mocks.mockApiError({
message: "Job could not be canceled.",
}),
export const Deleting: Story = {
args: {
...Running.args,
workspace: Mocks.MockDeletingWorkspace,
},
}
export const Deleted: Story = {
args: {
...Running.args,
workspace: Mocks.MockDeletedWorkspace,
},
}
export const Canceling: Story = {
args: {
...Running.args,
workspace: Mocks.MockCancelingWorkspace,
},
}
export const Canceled: Story = {
args: {
...Running.args,
workspace: Mocks.MockCanceledWorkspace,
},
}
export const Outdated: Story = {
args: {
...Running.args,
workspace: Mocks.MockOutdatedWorkspace,
},
}
export const GetBuildsError: Story = {
args: {
...Running.args,
workspaceErrors: {
[WorkspaceErrors.GET_BUILDS_ERROR]: Mocks.mockApiError({
message: "There is a problem fetching builds.",
}),
},
},
}
export const CancellationError: Story = {
args: {
...Failed.args,
workspaceErrors: {
[WorkspaceErrors.CANCELLATION_ERROR]: Mocks.mockApiError({
message: "Job could not be canceled.",
}),
},
},
}
@ -683,8 +697,9 @@ function makeFailedBuildLogs(): ProvisionerJobLog[] {
]
}
export const WithDeprecatedParameters = Template.bind({})
WithDeprecatedParameters.args = {
...Running.args,
templateWarnings: ["DEPRECATED_PARAMETERS"],
export const WithDeprecatedParameters: Story = {
args: {
...Running.args,
templateWarnings: ["DEPRECATED_PARAMETERS"],
},
}

View File

@ -2,7 +2,7 @@ import MenuItem from "@mui/material/MenuItem"
import Menu from "@mui/material/Menu"
import { makeStyles } from "@mui/styles"
import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined"
import { FC, ReactNode, useRef, useState } from "react"
import { FC, Fragment, ReactNode, useRef, useState } from "react"
import { WorkspaceStatus } from "api/typesGenerated"
import {
ActionLoadingButton,
@ -102,7 +102,10 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
? buttonMapping[ButtonTypesEnum.updating]
: buttonMapping[ButtonTypesEnum.update])}
{isRestarting && buttonMapping[ButtonTypesEnum.restarting]}
{!isRestarting && actionsByStatus.map((action) => buttonMapping[action])}
{!isRestarting &&
actionsByStatus.map((action) => (
<Fragment key={action}>{buttonMapping[action]}</Fragment>
))}
{canCancel && <CancelButton handleAction={handleCancel} />}
<div>
<IconButton

View File

@ -1,4 +1,3 @@
import { makeStyles } from "@mui/styles"
import { useQuery } from "@tanstack/react-query"
import { useMachine } from "@xstate/react"
import { getWorkspaceBuildLogs } from "api/api"
@ -12,6 +11,9 @@ import { workspaceMachine } from "xServices/workspace/workspaceXService"
import { WorkspaceReadyPage } from "./WorkspaceReadyPage"
import { RequirePermission } from "components/RequirePermission/RequirePermission"
import { ErrorAlert } from "components/Alert/ErrorAlert"
import { useOrganizationId } from "hooks"
import { isAxiosError } from "axios"
import { Margins } from "components/Margins/Margins"
const useFailedBuildLogs = (workspace: Workspace | undefined) => {
const now = useRef(new Date())
@ -35,45 +37,31 @@ export const WorkspacePage: FC = () => {
username: string
workspace: string
}
const orgId = useOrganizationId()
const [workspaceState, workspaceSend] = useMachine(workspaceMachine, {
context: {
orgId,
workspaceName,
username,
},
})
const {
workspace,
getWorkspaceError,
getTemplateWarning,
getTemplateParametersWarning,
checkPermissionsError,
} = workspaceState.context
const { workspace, error } = workspaceState.context
const [quotaState] = useMachine(quotaMachine, { context: { username } })
const { getQuotaError } = quotaState.context
const styles = useStyles()
const failedBuildLogs = useFailedBuildLogs(workspace)
const pageError = error ?? getQuotaError
return (
<RequirePermission
isFeatureVisible={getWorkspaceError?.response?.status !== 404}
isFeatureVisible={
!(isAxiosError(pageError) && pageError.response?.status === 404)
}
>
<ChooseOne>
<Cond condition={workspaceState.matches("error")}>
<div className={styles.error}>
{Boolean(getWorkspaceError) && (
<ErrorAlert error={getWorkspaceError} />
)}
{Boolean(getTemplateWarning) && (
<ErrorAlert error={getTemplateWarning} />
)}
{Boolean(getTemplateParametersWarning) && (
<ErrorAlert error={getTemplateParametersWarning} />
)}
{Boolean(checkPermissionsError) && (
<ErrorAlert error={checkPermissionsError} />
)}
{Boolean(getQuotaError) && <ErrorAlert error={getQuotaError} />}
</div>
<Cond condition={Boolean(pageError)}>
<Margins>
<ErrorAlert error={pageError} sx={{ my: 2 }} />
</Margins>
</Cond>
<Cond
condition={
@ -97,10 +85,4 @@ export const WorkspacePage: FC = () => {
)
}
const useStyles = makeStyles((theme) => ({
error: {
margin: theme.spacing(2),
},
}))
export default WorkspacePage

View File

@ -1,6 +1,6 @@
/* eslint-disable eslint-comments/disable-enable-pair -- ignore */
/* eslint-disable @typescript-eslint/no-explicit-any -- We don't care about any here */
import { ComponentMeta, Story } from "@storybook/react"
import { Meta, StoryObj } from "@storybook/react"
import { DEFAULT_RECORDS_PER_PAGE } from "components/PaginationWidget/utils"
import dayjs from "dayjs"
import uniqueId from "lodash/uniqueId"
@ -16,11 +16,9 @@ import {
MockEntitlementsWithScheduling,
MockExperiments,
MockUser,
mockApiError,
} from "testHelpers/entities"
import {
WorkspacesPageView,
WorkspacesPageViewProps,
} from "./WorkspacesPageView"
import { WorkspacesPageView } from "./WorkspacesPageView"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
import { action } from "@storybook/addon-actions"
import { ComponentProps } from "react"
@ -102,49 +100,62 @@ const defaultFilterProps = {
},
} as ComponentProps<typeof WorkspacesPageView>["filterProps"]
export default {
const meta: Meta<typeof WorkspacesPageView> = {
title: "pages/WorkspacesPageView",
component: WorkspacesPageView,
args: {
limit: DEFAULT_RECORDS_PER_PAGE,
filterProps: defaultFilterProps,
},
} as ComponentMeta<typeof WorkspacesPageView>
const Template: Story<WorkspacesPageViewProps> = (args) => (
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<WorkspacesPageView {...args} />
</DashboardProviderContext.Provider>
)
export const AllStates = Template.bind({})
AllStates.args = {
workspaces: allWorkspaces,
count: allWorkspaces.length,
decorators: [
(Story) => (
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<Story />
</DashboardProviderContext.Provider>
),
],
}
export const OwnerHasNoWorkspaces = Template.bind({})
OwnerHasNoWorkspaces.args = {
workspaces: [],
count: 0,
}
export default meta
type Story = StoryObj<typeof WorkspacesPageView>
export const NoSearchResults = Template.bind({})
NoSearchResults.args = {
workspaces: [],
filterProps: {
...defaultFilterProps,
filter: {
...defaultFilterProps.filter,
query: "searchwithnoresults",
},
export const AllStates: Story = {
args: {
workspaces: allWorkspaces,
count: allWorkspaces.length,
},
}
export const OwnerHasNoWorkspaces: Story = {
args: {
workspaces: [],
count: 0,
},
}
export const NoSearchResults: Story = {
args: {
workspaces: [],
filterProps: {
...defaultFilterProps,
filter: {
...defaultFilterProps.filter,
query: "searchwithnoresults",
},
},
count: 0,
},
}
export const Error: Story = {
args: {
error: mockApiError({ message: "Something went wrong" }),
},
count: 0,
}

View File

@ -9,7 +9,6 @@ import {
displayError,
displaySuccess,
} from "../../components/GlobalSnackbar/utils"
import { AxiosError } from "axios"
const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => {
// Cloning builds to not change the origin object with the sort()
@ -41,29 +40,22 @@ const moreBuildsAvailable = (
return event.data.latest_build.updated_at !== latestBuildInTimeline.updated_at
}
const Language = {
getTemplateWarning:
"Error updating workspace: latest template could not be fetched.",
getTemplateParametersWarning:
"Error updating workspace: template parameters could not be fetched.",
buildError: "Workspace action failed.",
}
type Permissions = Record<keyof ReturnType<typeof permissionsToCheck>, boolean>
export interface WorkspaceContext {
// Initial data
orgId: string
username: string
workspaceName: string
error?: unknown
// our server side events instance
eventSource?: EventSource
workspace?: TypesGen.Workspace
template?: TypesGen.Template
permissions?: Permissions
templateVersion?: TypesGen.TemplateVersion
build?: TypesGen.WorkspaceBuild
getWorkspaceError?: AxiosError
getTemplateWarning: Error | unknown
getTemplateParametersWarning: Error | unknown
// Builds
builds?: TypesGen.WorkspaceBuild[]
getBuildsError?: Error | unknown
@ -72,9 +64,6 @@ export interface WorkspaceContext {
buildError?: Error | unknown
cancellationMessage?: Types.Message
cancellationError?: Error | unknown
// permissions
permissions?: Permissions
checkPermissionsError?: Error | unknown
// debug
createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"]
// SSH Config
@ -152,14 +141,8 @@ export const workspaceMachine = createMachine(
context: {} as WorkspaceContext,
events: {} as WorkspaceEvent,
services: {} as {
getWorkspace: {
data: TypesGen.Workspace
}
getTemplate: {
data: TypesGen.Template
}
getTemplateVersion: {
data: TypesGen.TemplateVersion
loadInitialWorkspaceData: {
data: Awaited<ReturnType<typeof loadInitialWorkspaceData>>
}
getTemplateParameters: {
data: TypesGen.TemplateVersionParameter[]
@ -188,98 +171,26 @@ export const workspaceMachine = createMachine(
getBuilds: {
data: TypesGen.WorkspaceBuild[]
}
checkPermissions: {
data: TypesGen.AuthorizationResponse
}
getSSHPrefix: {
data: TypesGen.SSHConfigResponse
}
},
},
initial: "gettingWorkspace",
initial: "loadInitialData",
states: {
gettingWorkspace: {
loadInitialData: {
entry: ["clearContext"],
invoke: {
src: "getWorkspace",
id: "getWorkspace",
onDone: [
{
actions: ["assignWorkspace", "clearGetWorkspaceError"],
target: "gettingTemplate",
},
],
src: "loadInitialWorkspaceData",
id: "loadInitialWorkspaceData",
onDone: [{ target: "ready", actions: ["assignInitialData"] }],
onError: [
{
actions: "assignGetWorkspaceError",
actions: "assignError",
target: "error",
},
],
},
tags: "loading",
},
gettingTemplate: {
invoke: {
src: "getTemplate",
id: "getTemplate",
onDone: [
{
actions: ["assignTemplate", "clearGetTemplateWarning"],
target: "gettingTemplateVersion",
},
],
onError: [
{
actions: [
"assignGetTemplateWarning",
"displayGetTemplateWarning",
],
target: "error",
},
],
},
tags: "loading",
},
gettingTemplateVersion: {
invoke: {
src: "getTemplateVersion",
id: "getTemplateVersion",
onDone: [
{
actions: ["assignTemplateVersion", "clearGetTemplateWarning"],
target: "gettingPermissions",
},
],
onError: [
{
actions: [
"assignGetTemplateWarning",
"displayGetTemplateWarning",
],
target: "error",
},
],
},
tags: "loading",
},
gettingPermissions: {
invoke: {
src: "checkPermissions",
id: "checkPermissions",
onDone: [
{
actions: ["assignPermissions", "clearGetPermissionsError"],
target: "ready",
},
],
onError: [
{
actions: "assignGetPermissionsError",
target: "error",
},
],
},
tags: "loading",
},
ready: {
type: "parallel",
@ -572,30 +483,14 @@ export const workspaceMachine = createMachine(
permissions: undefined,
eventSource: undefined,
}),
assignWorkspace: assign({
workspace: (_, event) => event.data,
assignInitialData: assign({
workspace: (_, event) => event.data.workspace,
template: (_, event) => event.data.template,
templateVersion: (_, event) => event.data.templateVersion,
permissions: (_, event) => event.data.permissions as Permissions,
}),
assignGetWorkspaceError: assign({
getWorkspaceError: (_, event) => event.data as AxiosError,
}),
clearGetWorkspaceError: (context) =>
assign({ ...context, getWorkspaceError: undefined }),
assignTemplate: assign({
template: (_, event) => event.data,
}),
assignTemplateVersion: assign({
templateVersion: (_, event) => event.data,
}),
assignPermissions: assign({
// Setting event.data as Permissions to be more stricted. So we know
// what permissions we asked for.
permissions: (_, event) => event.data as Permissions,
}),
assignGetPermissionsError: assign({
checkPermissionsError: (_, event) => event.data,
}),
clearGetPermissionsError: assign({
checkPermissionsError: (_) => undefined,
assignError: assign({
error: (_, event) => event.data,
}),
assignBuild: assign({
build: (_, event) => event.data,
@ -637,15 +532,6 @@ export const workspaceMachine = createMachine(
logWatchWorkspaceWarning: (_, event) => {
console.error("Watch workspace error:", event)
},
assignGetTemplateWarning: assign({
getTemplateWarning: (_, event) => event.data,
}),
displayGetTemplateWarning: () => {
displayError(Language.getTemplateWarning)
},
clearGetTemplateWarning: assign({
getTemplateWarning: (_) => undefined,
}),
// Timeline
assignBuilds: assign({
builds: (_, event) => event.data,
@ -706,27 +592,7 @@ export const workspaceMachine = createMachine(
Boolean(templateVersionIdToChange),
},
services: {
getWorkspace: async ({ username, workspaceName }) => {
return await API.getWorkspaceByOwnerAndName(username, workspaceName, {
include_deleted: true,
})
},
getTemplate: async (context) => {
if (context.workspace) {
return await API.getTemplate(context.workspace.template_id)
} else {
throw Error("Cannot get template without workspace")
}
},
getTemplateVersion: async (context) => {
if (context.template) {
return await API.getTemplateVersion(
context.template.active_version_id,
)
} else {
throw Error("Cannot get template version without template")
}
},
loadInitialWorkspaceData,
updateWorkspace:
({ workspace }, { buildParameters }) =>
async (send) => {
@ -846,17 +712,6 @@ export const workspaceMachine = createMachine(
throw Error("Cannot get builds without id")
}
},
checkPermissions: async ({ workspace, template }) => {
if (!workspace) {
throw new Error("Workspace is not set")
}
if (!template) {
throw new Error("Template is not set")
}
return await API.checkAuthorization({
checks: permissionsToCheck(workspace, template),
})
},
scheduleBannerMachine: workspaceScheduleBannerMachine,
getSSHPrefix: async () => {
return API.getDeploymentSSHConfig()
@ -864,3 +719,31 @@ export const workspaceMachine = createMachine(
},
},
)
async function loadInitialWorkspaceData({
orgId,
username,
workspaceName,
}: WorkspaceContext) {
const workspace = await API.getWorkspaceByOwnerAndName(
username,
workspaceName,
{
include_deleted: true,
},
)
const template = await API.getTemplateByName(orgId, workspace.template_name)
const [templateVersion, permissions] = await Promise.all([
API.getTemplateVersion(template.active_version_id),
API.checkAuthorization({
checks: permissionsToCheck(workspace, template),
}),
])
return {
workspace,
template,
templateVersion,
permissions,
}
}