mirror of https://github.com/coder/coder.git
chore(site): simplify the logic to load workspace initial data (#7772)
This commit is contained in:
parent
4de4e8ee21
commit
332362cf4b
|
@ -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={
|
||||
<>
|
||||
|
|
|
@ -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"],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue