Skip to content

feat: consolidate workspace buttons/kira pilot #2996

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 5 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
115 changes: 115 additions & 0 deletions site/src/components/WorkspaceActions/ActionCtas.tsx
Original file line number Diff line number Diff line change
@@ -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`,
},
}))
79 changes: 79 additions & 0 deletions site/src/components/WorkspaceActions/WorkspaceActions.stories.tsx
Original file line number Diff line number Diff line change
@@ -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,
}
89 changes: 89 additions & 0 deletions site/src/components/WorkspaceActions/WorkspaceActions.test.tsx
Original file line number Diff line number Diff line change
@@ -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)
})
})
})
Loading