mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: consolidate workspace buttons/kira pilot (#2996)
* added workspace cta dropdown resolves #2748 * added tests * fixed failing tests * clean up snapshots
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import Button from "@material-ui/core/Button"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import CloudQueueIcon from "@material-ui/icons/CloudQueue"
|
||||
import CropSquareIcon from "@material-ui/icons/CropSquare"
|
||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
|
||||
import HighlightOffIcon from "@material-ui/icons/HighlightOff"
|
||||
import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"
|
||||
import { FC } from "react"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { WorkspaceStatus } from "../../util/workspace"
|
||||
import { WorkspaceActionButton } from "../WorkspaceActionButton/WorkspaceActionButton"
|
||||
|
||||
export const Language = {
|
||||
start: "Start",
|
||||
stop: "Stop",
|
||||
delete: "Delete",
|
||||
cancel: "Cancel",
|
||||
update: "Update",
|
||||
}
|
||||
|
||||
interface WorkspaceAction {
|
||||
handleAction: () => void
|
||||
}
|
||||
|
||||
export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<PlayCircleOutlineIcon />}
|
||||
onClick={handleAction}
|
||||
label={Language.start}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<CropSquareIcon />}
|
||||
onClick={handleAction}
|
||||
label={Language.stop}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<DeleteOutlineIcon />}
|
||||
onClick={handleAction}
|
||||
label={Language.delete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type UpdateAction = WorkspaceAction & {
|
||||
workspace: Workspace
|
||||
workspaceStatus: WorkspaceStatus
|
||||
}
|
||||
|
||||
export const UpdateButton: FC<UpdateAction> = ({ handleAction, workspace, workspaceStatus }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
/**
|
||||
* Jobs submitted while another job is in progress will be discarded,
|
||||
* so check whether workspace job status has reached completion (whether successful or not).
|
||||
*/
|
||||
const canAcceptJobs = (workspaceStatus: WorkspaceStatus) =>
|
||||
["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus)
|
||||
|
||||
return (
|
||||
<>
|
||||
{workspace.outdated && canAcceptJobs(workspaceStatus) && (
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
startIcon={<CloudQueueIcon />}
|
||||
onClick={handleAction}
|
||||
>
|
||||
{Language.update}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const CancelButton: FC<WorkspaceAction> = ({ handleAction }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<HighlightOffIcon />}
|
||||
onClick={handleAction}
|
||||
label={Language.cancel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
actionButton: {
|
||||
// Set fixed width for the action buttons so they will not change the size
|
||||
// during the transitions
|
||||
width: theme.spacing(16),
|
||||
border: "none",
|
||||
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
|
||||
},
|
||||
}))
|
||||
@@ -0,0 +1,79 @@
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { Story } from "@storybook/react"
|
||||
import * as Mocks from "../../testHelpers/entities"
|
||||
import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions"
|
||||
|
||||
export default {
|
||||
title: "components/WorkspaceActions",
|
||||
component: WorkspaceActions,
|
||||
}
|
||||
|
||||
const Template: Story<WorkspaceActionsProps> = (args) => <WorkspaceActions {...args} />
|
||||
|
||||
const defaultArgs = {
|
||||
handleStart: action("start"),
|
||||
handleStop: action("stop"),
|
||||
handleDelete: action("delete"),
|
||||
handleUpdate: action("update"),
|
||||
handleCancel: action("cancel"),
|
||||
}
|
||||
|
||||
export const Starting = Template.bind({})
|
||||
Starting.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockStartingWorkspace,
|
||||
}
|
||||
|
||||
export const Started = Template.bind({})
|
||||
Started.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockWorkspace,
|
||||
}
|
||||
|
||||
export const Stopping = Template.bind({})
|
||||
Stopping.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockStoppingWorkspace,
|
||||
}
|
||||
|
||||
export const Stopped = Template.bind({})
|
||||
Stopped.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockStoppedWorkspace,
|
||||
}
|
||||
|
||||
export const Canceling = Template.bind({})
|
||||
Canceling.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockCancelingWorkspace,
|
||||
}
|
||||
|
||||
export const Canceled = Template.bind({})
|
||||
Canceled.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockCanceledWorkspace,
|
||||
}
|
||||
|
||||
export const Deleting = Template.bind({})
|
||||
Deleting.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockDeletingWorkspace,
|
||||
}
|
||||
|
||||
export const Deleted = Template.bind({})
|
||||
Deleted.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockDeletedWorkspace,
|
||||
}
|
||||
|
||||
export const Outdated = Template.bind({})
|
||||
Outdated.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockOutdatedWorkspace,
|
||||
}
|
||||
|
||||
export const Errored = Template.bind({})
|
||||
Errored.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockFailedWorkspace,
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import * as Mocks from "../../testHelpers/entities"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { Language } from "./ActionCtas"
|
||||
import { WorkspaceStateEnum } from "./constants"
|
||||
import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions"
|
||||
|
||||
const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
render(
|
||||
<WorkspaceActions
|
||||
workspace={props.workspace ?? Mocks.MockWorkspace}
|
||||
handleStart={jest.fn()}
|
||||
handleStop={jest.fn()}
|
||||
handleDelete={jest.fn()}
|
||||
handleUpdate={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
/>,
|
||||
)
|
||||
const trigger = await screen.findByTestId("workspace-actions-button")
|
||||
trigger.click()
|
||||
}
|
||||
|
||||
describe("WorkspaceActions", () => {
|
||||
describe("when the workspace is starting", () => {
|
||||
it("primary is cancel; no secondary", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockStartingWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
|
||||
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
|
||||
})
|
||||
})
|
||||
describe("when the workspace is started", () => {
|
||||
it("primary is stop; secondary is delete", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.stop)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
|
||||
})
|
||||
})
|
||||
describe("when the workspace is stopping", () => {
|
||||
it("primary is cancel; no secondary", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockStoppingWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
|
||||
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
|
||||
})
|
||||
})
|
||||
describe("when the workspace is canceling", () => {
|
||||
it("primary is canceling; no secondary", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockCancelingWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(WorkspaceStateEnum.canceling)
|
||||
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
|
||||
})
|
||||
})
|
||||
describe("when the workspace is canceled", () => {
|
||||
it("primary is start; secondary are stop, delete", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockCanceledWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.stop)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
|
||||
})
|
||||
})
|
||||
describe("when the workspace is errored", () => {
|
||||
it("primary is start; secondary is delete", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockFailedWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
|
||||
})
|
||||
})
|
||||
describe("when the workspace is deleting", () => {
|
||||
it("primary is cancel; no secondary", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockDeletingWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
|
||||
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
|
||||
})
|
||||
})
|
||||
describe("when the workspace is deleted", () => {
|
||||
it("primary is deleted; no secondary", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockDeletedWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(WorkspaceStateEnum.deleted)
|
||||
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
|
||||
})
|
||||
})
|
||||
describe("when the workspace is outdated", () => {
|
||||
it("primary is start; secondary are delete, update", async () => {
|
||||
await renderAndClick({ workspace: Mocks.MockOutdatedWorkspace })
|
||||
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
|
||||
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.update)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,50 +1,12 @@
|
||||
import Button from "@material-ui/core/Button"
|
||||
import Popover from "@material-ui/core/Popover"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import CancelIcon from "@material-ui/icons/Cancel"
|
||||
import CloudDownloadIcon from "@material-ui/icons/CloudDownload"
|
||||
import DeleteIcon from "@material-ui/icons/Delete"
|
||||
import PlayArrowRoundedIcon from "@material-ui/icons/PlayArrowRounded"
|
||||
import StopIcon from "@material-ui/icons/Stop"
|
||||
import { FC } from "react"
|
||||
import { FC, ReactNode, useEffect, useRef, useState } from "react"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { getWorkspaceStatus, WorkspaceStatus } from "../../util/workspace"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
import { WorkspaceActionButton } from "../WorkspaceActionButton/WorkspaceActionButton"
|
||||
|
||||
export const Language = {
|
||||
stop: "Stop",
|
||||
stopping: "Stopping",
|
||||
start: "Start",
|
||||
starting: "Starting",
|
||||
delete: "Delete",
|
||||
deleting: "Deleting",
|
||||
cancel: "Cancel action",
|
||||
update: "Update",
|
||||
}
|
||||
|
||||
/**
|
||||
* Jobs submitted while another job is in progress will be discarded,
|
||||
* so check whether workspace job status has reached completion (whether successful or not).
|
||||
*/
|
||||
const canAcceptJobs = (workspaceStatus: WorkspaceStatus) =>
|
||||
["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus)
|
||||
|
||||
/**
|
||||
* Jobs that are in progress (queued or pending) can be canceled.
|
||||
* @param workspaceStatus WorkspaceStatus
|
||||
* @returns boolean
|
||||
*/
|
||||
const canCancelJobs = (workspaceStatus: WorkspaceStatus) =>
|
||||
["starting", "stopping", "deleting"].includes(workspaceStatus)
|
||||
|
||||
const canStart = (workspaceStatus: WorkspaceStatus) =>
|
||||
["stopped", "canceled", "error"].includes(workspaceStatus)
|
||||
|
||||
const canStop = (workspaceStatus: WorkspaceStatus) =>
|
||||
["started", "canceled", "error"].includes(workspaceStatus)
|
||||
|
||||
const canDelete = (workspaceStatus: WorkspaceStatus) =>
|
||||
["started", "stopped", "canceled", "error"].includes(workspaceStatus)
|
||||
import { getWorkspaceStatus } from "../../util/workspace"
|
||||
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
|
||||
import { CancelButton, DeleteButton, StartButton, StopButton, UpdateButton } from "./ActionCtas"
|
||||
import { ButtonTypesEnum, WorkspaceStateActions, WorkspaceStateEnum } from "./constants"
|
||||
|
||||
export interface WorkspaceActionsProps {
|
||||
workspace: Workspace
|
||||
@@ -64,62 +26,135 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
handleCancel,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const workspaceStatus = getWorkspaceStatus(workspace.latest_build)
|
||||
const anchorRef = useRef<HTMLButtonElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const id = isOpen ? "action-popover" : undefined
|
||||
|
||||
const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus(
|
||||
workspace.latest_build,
|
||||
)
|
||||
const workspaceState = WorkspaceStateEnum[workspaceStatus]
|
||||
const actions = WorkspaceStateActions[workspaceState]
|
||||
|
||||
/**
|
||||
* Ensures we close the popover before calling any action handler
|
||||
*/
|
||||
useEffect(() => {
|
||||
setIsOpen(false)
|
||||
return () => {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}, [workspaceStatus])
|
||||
|
||||
const disabledButton = (
|
||||
<Button disabled className={styles.actionButton}>
|
||||
{workspaceState}
|
||||
</Button>
|
||||
)
|
||||
|
||||
type ButtonMapping = {
|
||||
[key in ButtonTypesEnum]: ReactNode
|
||||
}
|
||||
|
||||
// A mapping of button type to the corresponding React component
|
||||
const buttonMapping: ButtonMapping = {
|
||||
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
|
||||
[ButtonTypesEnum.stop]: <StopButton handleAction={handleStop} />,
|
||||
[ButtonTypesEnum.delete]: <DeleteButton handleAction={handleDelete} />,
|
||||
[ButtonTypesEnum.update]: (
|
||||
<UpdateButton
|
||||
handleAction={handleUpdate}
|
||||
workspace={workspace}
|
||||
workspaceStatus={workspaceStatus}
|
||||
/>
|
||||
),
|
||||
[ButtonTypesEnum.cancel]: <CancelButton handleAction={handleCancel} />,
|
||||
[ButtonTypesEnum.canceling]: disabledButton,
|
||||
[ButtonTypesEnum.disabled]: disabledButton,
|
||||
[ButtonTypesEnum.queued]: disabledButton,
|
||||
[ButtonTypesEnum.error]: disabledButton,
|
||||
[ButtonTypesEnum.loading]: disabledButton,
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing={1}>
|
||||
{canStart(workspaceStatus) && (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<PlayArrowRoundedIcon />}
|
||||
onClick={handleStart}
|
||||
label={Language.start}
|
||||
/>
|
||||
)}
|
||||
{canStop(workspaceStatus) && (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<StopIcon />}
|
||||
onClick={handleStop}
|
||||
label={Language.stop}
|
||||
/>
|
||||
)}
|
||||
{canDelete(workspaceStatus) && (
|
||||
<WorkspaceActionButton
|
||||
className={styles.actionButton}
|
||||
icon={<DeleteIcon />}
|
||||
onClick={handleDelete}
|
||||
label={Language.delete}
|
||||
/>
|
||||
)}
|
||||
{canCancelJobs(workspaceStatus) && (
|
||||
<WorkspaceActionButton
|
||||
className={styles.cancelActionButton}
|
||||
icon={<CancelIcon />}
|
||||
onClick={handleCancel}
|
||||
label={Language.cancel}
|
||||
/>
|
||||
)}
|
||||
{workspace.outdated && canAcceptJobs(workspaceStatus) && (
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
startIcon={<CloudDownloadIcon />}
|
||||
onClick={handleUpdate}
|
||||
>
|
||||
{Language.update}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
<span className={styles.buttonContainer}>
|
||||
{/* primary workspace CTA */}
|
||||
<span data-testid="primary-cta">{buttonMapping[actions.primary]}</span>
|
||||
|
||||
{/* popover toggle button */}
|
||||
<Button
|
||||
data-testid="workspace-actions-button"
|
||||
aria-controls="workspace-actions-menu"
|
||||
aria-haspopup="true"
|
||||
className={styles.dropdownButton}
|
||||
ref={anchorRef}
|
||||
disabled={!actions.secondary.length}
|
||||
onClick={() => {
|
||||
setIsOpen(true)
|
||||
}}
|
||||
>
|
||||
{isOpen ? <CloseDropdown /> : <OpenDropdown />}
|
||||
</Button>
|
||||
|
||||
<Popover
|
||||
classes={{ paper: styles.popoverPaper }}
|
||||
id={id}
|
||||
open={isOpen}
|
||||
anchorEl={anchorRef.current}
|
||||
onClose={() => setIsOpen(false)}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
>
|
||||
{/* secondary workspace CTAs */}
|
||||
<span data-testid="secondary-ctas">
|
||||
{actions.secondary.map((action) => (
|
||||
<div key={action} className={styles.popoverActionButton}>
|
||||
{buttonMapping[action]}
|
||||
</div>
|
||||
))}
|
||||
</span>
|
||||
</Popover>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
buttonContainer: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px`,
|
||||
display: "inline-block",
|
||||
},
|
||||
dropdownButton: {
|
||||
border: "none",
|
||||
borderLeft: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0px`,
|
||||
minWidth: "unset",
|
||||
width: "35px",
|
||||
"& .MuiButton-label": {
|
||||
marginRight: "8px",
|
||||
},
|
||||
},
|
||||
actionButton: {
|
||||
// Set fixed width for the action buttons so they will not change the size
|
||||
// during the transitions
|
||||
width: theme.spacing(16),
|
||||
border: "none",
|
||||
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
|
||||
},
|
||||
cancelActionButton: {
|
||||
width: theme.spacing(27),
|
||||
popoverActionButton: {
|
||||
"& .MuiButtonBase-root": {
|
||||
backgroundColor: "unset",
|
||||
justifyContent: "start",
|
||||
padding: "0px",
|
||||
},
|
||||
},
|
||||
popoverPaper: {
|
||||
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(3)}px`,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// all the possible states returned by the API
|
||||
export enum WorkspaceStateEnum {
|
||||
starting = "Starting",
|
||||
started = "Started",
|
||||
stopping = "Stopping",
|
||||
stopped = "Stopped",
|
||||
canceling = "Canceling",
|
||||
canceled = "Canceled",
|
||||
deleting = "Deleting",
|
||||
deleted = "Deleted",
|
||||
queued = "Queued",
|
||||
error = "Error",
|
||||
loading = "Loading",
|
||||
}
|
||||
|
||||
// the button types we have
|
||||
export enum ButtonTypesEnum {
|
||||
start,
|
||||
stop,
|
||||
delete,
|
||||
update,
|
||||
cancel,
|
||||
error,
|
||||
// disabled buttons
|
||||
canceling,
|
||||
disabled,
|
||||
queued,
|
||||
loading,
|
||||
}
|
||||
|
||||
type StateActionsType = {
|
||||
[key in WorkspaceStateEnum]: {
|
||||
primary: ButtonTypesEnum
|
||||
secondary: ButtonTypesEnum[]
|
||||
}
|
||||
}
|
||||
|
||||
// A mapping of workspace state to button type
|
||||
// 'Primary' actions are the main ctas
|
||||
// 'Secondary' actions are ctas housed within the popover
|
||||
export const WorkspaceStateActions: StateActionsType = {
|
||||
[WorkspaceStateEnum.starting]: {
|
||||
primary: ButtonTypesEnum.cancel,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.started]: {
|
||||
primary: ButtonTypesEnum.stop,
|
||||
secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update],
|
||||
},
|
||||
[WorkspaceStateEnum.stopping]: {
|
||||
primary: ButtonTypesEnum.cancel,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.stopped]: {
|
||||
primary: ButtonTypesEnum.start,
|
||||
secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update],
|
||||
},
|
||||
[WorkspaceStateEnum.canceled]: {
|
||||
primary: ButtonTypesEnum.start,
|
||||
secondary: [ButtonTypesEnum.stop, ButtonTypesEnum.delete, ButtonTypesEnum.update],
|
||||
},
|
||||
// in the case of an error
|
||||
[WorkspaceStateEnum.error]: {
|
||||
primary: ButtonTypesEnum.start, // give the user the ability to start a workspace again
|
||||
secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update], // allows the user to delete or update
|
||||
},
|
||||
/**
|
||||
* disabled states
|
||||
*/
|
||||
[WorkspaceStateEnum.canceling]: {
|
||||
primary: ButtonTypesEnum.canceling,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.deleting]: {
|
||||
primary: ButtonTypesEnum.cancel,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.deleted]: {
|
||||
primary: ButtonTypesEnum.disabled,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.queued]: {
|
||||
primary: ButtonTypesEnum.queued,
|
||||
secondary: [],
|
||||
},
|
||||
[WorkspaceStateEnum.loading]: {
|
||||
primary: ButtonTypesEnum.loading,
|
||||
secondary: [],
|
||||
},
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { fireEvent, screen, waitFor, within } from "@testing-library/react"
|
||||
import { rest } from "msw"
|
||||
import * as api from "../../api/api"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { Language } from "../../components/WorkspaceActions/WorkspaceActions"
|
||||
import { Language } from "../../components/WorkspaceActions/ActionCtas"
|
||||
import {
|
||||
MockBuilds,
|
||||
MockCanceledWorkspace,
|
||||
@@ -43,6 +43,9 @@ const renderWorkspacePage = async () => {
|
||||
|
||||
const testButton = async (label: string, actionMock: jest.SpyInstance) => {
|
||||
await renderWorkspacePage()
|
||||
// open the workspace action popover so we have access to all available ctas
|
||||
const trigger = await screen.findByTestId("workspace-actions-button")
|
||||
trigger.click()
|
||||
// REMARK: exact here because the "Start" button and "START" label for
|
||||
// workspace schedule could otherwise conflict.
|
||||
const button = await screen.findByText(label, { exact: true })
|
||||
@@ -87,6 +90,11 @@ describe("Workspace Page", () => {
|
||||
.spyOn(api, "deleteWorkspace")
|
||||
.mockResolvedValueOnce(MockWorkspaceBuild)
|
||||
await renderWorkspacePage()
|
||||
|
||||
// open the workspace action popover so we have access to all available ctas
|
||||
const trigger = await screen.findByTestId("workspace-actions-button")
|
||||
trigger.click()
|
||||
|
||||
const button = await screen.findByText(Language.delete)
|
||||
await waitFor(() => fireEvent.click(button))
|
||||
const confirmDialog = await screen.findByRole("dialog")
|
||||
|
||||
Reference in New Issue
Block a user