Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eebf0dd

Browse files
authored
feat: consolidate workspace buttons/kira pilot (#2996)
* added workspace cta dropdown resolves #2748 * added tests * fixed failing tests * clean up snapshots
1 parent aea3b3b commit eebf0dd

File tree

6 files changed

+507
-91
lines changed

6 files changed

+507
-91
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import Button from "@material-ui/core/Button"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import CloudQueueIcon from "@material-ui/icons/CloudQueue"
4+
import CropSquareIcon from "@material-ui/icons/CropSquare"
5+
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
6+
import HighlightOffIcon from "@material-ui/icons/HighlightOff"
7+
import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"
8+
import { FC } from "react"
9+
import { Workspace } from "../../api/typesGenerated"
10+
import { WorkspaceStatus } from "../../util/workspace"
11+
import { WorkspaceActionButton } from "../WorkspaceActionButton/WorkspaceActionButton"
12+
13+
export const Language = {
14+
start: "Start",
15+
stop: "Stop",
16+
delete: "Delete",
17+
cancel: "Cancel",
18+
update: "Update",
19+
}
20+
21+
interface WorkspaceAction {
22+
handleAction: () => void
23+
}
24+
25+
export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => {
26+
const styles = useStyles()
27+
28+
return (
29+
<WorkspaceActionButton
30+
className={styles.actionButton}
31+
icon={<PlayCircleOutlineIcon />}
32+
onClick={handleAction}
33+
label={Language.start}
34+
/>
35+
)
36+
}
37+
38+
export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => {
39+
const styles = useStyles()
40+
41+
return (
42+
<WorkspaceActionButton
43+
className={styles.actionButton}
44+
icon={<CropSquareIcon />}
45+
onClick={handleAction}
46+
label={Language.stop}
47+
/>
48+
)
49+
}
50+
51+
export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => {
52+
const styles = useStyles()
53+
54+
return (
55+
<WorkspaceActionButton
56+
className={styles.actionButton}
57+
icon={<DeleteOutlineIcon />}
58+
onClick={handleAction}
59+
label={Language.delete}
60+
/>
61+
)
62+
}
63+
64+
type UpdateAction = WorkspaceAction & {
65+
workspace: Workspace
66+
workspaceStatus: WorkspaceStatus
67+
}
68+
69+
export const UpdateButton: FC<UpdateAction> = ({ handleAction, workspace, workspaceStatus }) => {
70+
const styles = useStyles()
71+
72+
/**
73+
* Jobs submitted while another job is in progress will be discarded,
74+
* so check whether workspace job status has reached completion (whether successful or not).
75+
*/
76+
const canAcceptJobs = (workspaceStatus: WorkspaceStatus) =>
77+
["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus)
78+
79+
return (
80+
<>
81+
{workspace.outdated && canAcceptJobs(workspaceStatus) && (
82+
<Button
83+
className={styles.actionButton}
84+
startIcon={<CloudQueueIcon />}
85+
onClick={handleAction}
86+
>
87+
{Language.update}
88+
</Button>
89+
)}
90+
</>
91+
)
92+
}
93+
94+
export const CancelButton: FC<WorkspaceAction> = ({ handleAction }) => {
95+
const styles = useStyles()
96+
97+
return (
98+
<WorkspaceActionButton
99+
className={styles.actionButton}
100+
icon={<HighlightOffIcon />}
101+
onClick={handleAction}
102+
label={Language.cancel}
103+
/>
104+
)
105+
}
106+
107+
const useStyles = makeStyles((theme) => ({
108+
actionButton: {
109+
// Set fixed width for the action buttons so they will not change the size
110+
// during the transitions
111+
width: theme.spacing(16),
112+
border: "none",
113+
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
114+
},
115+
}))
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { action } from "@storybook/addon-actions"
2+
import { Story } from "@storybook/react"
3+
import * as Mocks from "../../testHelpers/entities"
4+
import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions"
5+
6+
export default {
7+
title: "components/WorkspaceActions",
8+
component: WorkspaceActions,
9+
}
10+
11+
const Template: Story<WorkspaceActionsProps> = (args) => <WorkspaceActions {...args} />
12+
13+
const defaultArgs = {
14+
handleStart: action("start"),
15+
handleStop: action("stop"),
16+
handleDelete: action("delete"),
17+
handleUpdate: action("update"),
18+
handleCancel: action("cancel"),
19+
}
20+
21+
export const Starting = Template.bind({})
22+
Starting.args = {
23+
...defaultArgs,
24+
workspace: Mocks.MockStartingWorkspace,
25+
}
26+
27+
export const Started = Template.bind({})
28+
Started.args = {
29+
...defaultArgs,
30+
workspace: Mocks.MockWorkspace,
31+
}
32+
33+
export const Stopping = Template.bind({})
34+
Stopping.args = {
35+
...defaultArgs,
36+
workspace: Mocks.MockStoppingWorkspace,
37+
}
38+
39+
export const Stopped = Template.bind({})
40+
Stopped.args = {
41+
...defaultArgs,
42+
workspace: Mocks.MockStoppedWorkspace,
43+
}
44+
45+
export const Canceling = Template.bind({})
46+
Canceling.args = {
47+
...defaultArgs,
48+
workspace: Mocks.MockCancelingWorkspace,
49+
}
50+
51+
export const Canceled = Template.bind({})
52+
Canceled.args = {
53+
...defaultArgs,
54+
workspace: Mocks.MockCanceledWorkspace,
55+
}
56+
57+
export const Deleting = Template.bind({})
58+
Deleting.args = {
59+
...defaultArgs,
60+
workspace: Mocks.MockDeletingWorkspace,
61+
}
62+
63+
export const Deleted = Template.bind({})
64+
Deleted.args = {
65+
...defaultArgs,
66+
workspace: Mocks.MockDeletedWorkspace,
67+
}
68+
69+
export const Outdated = Template.bind({})
70+
Outdated.args = {
71+
...defaultArgs,
72+
workspace: Mocks.MockOutdatedWorkspace,
73+
}
74+
75+
export const Errored = Template.bind({})
76+
Errored.args = {
77+
...defaultArgs,
78+
workspace: Mocks.MockFailedWorkspace,
79+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { screen } from "@testing-library/react"
2+
import * as Mocks from "../../testHelpers/entities"
3+
import { render } from "../../testHelpers/renderHelpers"
4+
import { Language } from "./ActionCtas"
5+
import { WorkspaceStateEnum } from "./constants"
6+
import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions"
7+
8+
const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
9+
render(
10+
<WorkspaceActions
11+
workspace={props.workspace ?? Mocks.MockWorkspace}
12+
handleStart={jest.fn()}
13+
handleStop={jest.fn()}
14+
handleDelete={jest.fn()}
15+
handleUpdate={jest.fn()}
16+
handleCancel={jest.fn()}
17+
/>,
18+
)
19+
const trigger = await screen.findByTestId("workspace-actions-button")
20+
trigger.click()
21+
}
22+
23+
describe("WorkspaceActions", () => {
24+
describe("when the workspace is starting", () => {
25+
it("primary is cancel; no secondary", async () => {
26+
await renderAndClick({ workspace: Mocks.MockStartingWorkspace })
27+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
28+
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
29+
})
30+
})
31+
describe("when the workspace is started", () => {
32+
it("primary is stop; secondary is delete", async () => {
33+
await renderAndClick({ workspace: Mocks.MockWorkspace })
34+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.stop)
35+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
36+
})
37+
})
38+
describe("when the workspace is stopping", () => {
39+
it("primary is cancel; no secondary", async () => {
40+
await renderAndClick({ workspace: Mocks.MockStoppingWorkspace })
41+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
42+
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
43+
})
44+
})
45+
describe("when the workspace is canceling", () => {
46+
it("primary is canceling; no secondary", async () => {
47+
await renderAndClick({ workspace: Mocks.MockCancelingWorkspace })
48+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(WorkspaceStateEnum.canceling)
49+
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
50+
})
51+
})
52+
describe("when the workspace is canceled", () => {
53+
it("primary is start; secondary are stop, delete", async () => {
54+
await renderAndClick({ workspace: Mocks.MockCanceledWorkspace })
55+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
56+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.stop)
57+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
58+
})
59+
})
60+
describe("when the workspace is errored", () => {
61+
it("primary is start; secondary is delete", async () => {
62+
await renderAndClick({ workspace: Mocks.MockFailedWorkspace })
63+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
64+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
65+
})
66+
})
67+
describe("when the workspace is deleting", () => {
68+
it("primary is cancel; no secondary", async () => {
69+
await renderAndClick({ workspace: Mocks.MockDeletingWorkspace })
70+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.cancel)
71+
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
72+
})
73+
})
74+
describe("when the workspace is deleted", () => {
75+
it("primary is deleted; no secondary", async () => {
76+
await renderAndClick({ workspace: Mocks.MockDeletedWorkspace })
77+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(WorkspaceStateEnum.deleted)
78+
expect(screen.queryByTestId("secondary-ctas")).toBeNull()
79+
})
80+
})
81+
describe("when the workspace is outdated", () => {
82+
it("primary is start; secondary are delete, update", async () => {
83+
await renderAndClick({ workspace: Mocks.MockOutdatedWorkspace })
84+
expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start)
85+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete)
86+
expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.update)
87+
})
88+
})
89+
})

0 commit comments

Comments
 (0)