Skip to content

Commit ca93614

Browse files
refactor: Make workspace status more visible (#3130)
1 parent 1b19a09 commit ca93614

File tree

9 files changed

+275
-85
lines changed

9 files changed

+275
-85
lines changed

site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
168168
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
169169
},
170170
errorRoot: {
171-
color: theme.palette.error.dark,
171+
color: theme.palette.error.main,
172172
},
173173
inputStyles: {
174174
height: "100%",

site/src/components/Workspace/Workspace.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { makeStyles } from "@material-ui/core/styles"
2+
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
23
import React, { FC } from "react"
34
import { useNavigate } from "react-router-dom"
45
import * as TypesGen from "../../api/typesGenerated"
@@ -77,6 +78,7 @@ export const Workspace: FC<WorkspaceProps> = ({
7778
</Stack>
7879
}
7980
>
81+
<WorkspaceStatusBadge build={workspace.latest_build} className={styles.statusBadge} />
8082
<PageHeaderTitle>{workspace.name}</PageHeaderTitle>
8183
<PageHeaderSubtitle>{workspace.owner_name}</PageHeaderSubtitle>
8284
</PageHeader>
@@ -118,6 +120,9 @@ const spacerWidth = 300
118120

119121
export const useStyles = makeStyles((theme) => {
120122
return {
123+
statusBadge: {
124+
marginBottom: theme.spacing(3),
125+
},
121126
firstColumnSpacer: {
122127
flex: 2,
123128
},

site/src/components/WorkspaceStats/WorkspaceStats.tsx

+1-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { FC } from "react"
55
import { Link as RouterLink } from "react-router-dom"
66
import { combineClasses } from "util/combineClasses"
77
import { createDayString } from "util/createDayString"
8-
import { getDisplayStatus, getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
8+
import { getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
99
import { Workspace } from "../../api/typesGenerated"
1010
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
1111

@@ -28,7 +28,6 @@ export interface WorkspaceStatsProps {
2828
export const WorkspaceStats: FC<WorkspaceStatsProps> = ({ workspace, handleUpdate }) => {
2929
const styles = useStyles()
3030
const theme = useTheme()
31-
const status = getDisplayStatus(theme, workspace.latest_build)
3231
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(workspace.latest_build)
3332

3433
return (
@@ -69,15 +68,6 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({ workspace, handleUpdat
6968
<span className={styles.statsLabel}>{Language.byLabel}</span>
7069
<span className={styles.statsValue}>{initiatedBy}</span>
7170
</div>
72-
<div className={styles.statsDivider} />
73-
<div className={styles.statItem}>
74-
<span className={styles.statsLabel}>{Language.statusLabel}</span>
75-
<span className={styles.statsValue}>
76-
<span style={{ color: status.color }} role="status">
77-
{status.status}
78-
</span>
79-
</span>
80-
</div>
8171
</div>
8272
)
8373
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Story } from "@storybook/react"
2+
import {
3+
MockCanceledWorkspace,
4+
MockCancelingWorkspace,
5+
MockDeletedWorkspace,
6+
MockDeletingWorkspace,
7+
MockFailedWorkspace,
8+
MockQueuedWorkspace,
9+
MockStartingWorkspace,
10+
MockStoppedWorkspace,
11+
MockStoppingWorkspace,
12+
MockWorkspace,
13+
} from "testHelpers/renderHelpers"
14+
import { WorkspaceStatusBadge, WorkspaceStatusBadgeProps } from "./WorkspaceStatusBadge"
15+
16+
export default {
17+
title: "components/WorkspaceStatusBadge",
18+
component: WorkspaceStatusBadge,
19+
}
20+
21+
const Template: Story<WorkspaceStatusBadgeProps> = (args) => <WorkspaceStatusBadge {...args} />
22+
23+
export const Running = Template.bind({})
24+
Running.args = {
25+
build: MockWorkspace.latest_build,
26+
}
27+
28+
export const Starting = Template.bind({})
29+
Starting.args = {
30+
build: MockStartingWorkspace.latest_build,
31+
}
32+
33+
export const Stopped = Template.bind({})
34+
Stopped.args = {
35+
build: MockStoppedWorkspace.latest_build,
36+
}
37+
38+
export const Stopping = Template.bind({})
39+
Stopping.args = {
40+
build: MockStoppingWorkspace.latest_build,
41+
}
42+
43+
export const Deleting = Template.bind({})
44+
Deleting.args = {
45+
build: MockDeletingWorkspace.latest_build,
46+
}
47+
48+
export const Deleted = Template.bind({})
49+
Deleted.args = {
50+
build: MockDeletedWorkspace.latest_build,
51+
}
52+
53+
export const Canceling = Template.bind({})
54+
Canceling.args = {
55+
build: MockCancelingWorkspace.latest_build,
56+
}
57+
58+
export const Canceled = Template.bind({})
59+
Canceled.args = {
60+
build: MockCanceledWorkspace.latest_build,
61+
}
62+
63+
export const Failed = Template.bind({})
64+
Failed.args = {
65+
build: MockFailedWorkspace.latest_build,
66+
}
67+
68+
export const Queued = Template.bind({})
69+
Queued.args = {
70+
build: MockQueuedWorkspace.latest_build,
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import CircularProgress from "@material-ui/core/CircularProgress"
2+
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"
3+
import ErrorIcon from "@material-ui/icons/ErrorOutline"
4+
import PlayIcon from "@material-ui/icons/PlayArrowOutlined"
5+
import StopIcon from "@material-ui/icons/StopOutlined"
6+
import { WorkspaceBuild } from "api/typesGenerated"
7+
import React from "react"
8+
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
9+
import { combineClasses } from "util/combineClasses"
10+
import { getWorkspaceStatus } from "util/workspace"
11+
12+
const StatusLanguage = {
13+
loading: "Loading",
14+
started: "Running",
15+
starting: "Starting",
16+
stopping: "Stopping",
17+
stopped: "Stopped",
18+
deleting: "Deleting",
19+
deleted: "Deleted",
20+
canceling: "Canceling action",
21+
canceled: "Canceled action",
22+
failed: "Failed",
23+
queued: "Queued",
24+
}
25+
26+
const LoadingIcon: React.FC = () => {
27+
return <CircularProgress size={10} style={{ color: "#FFF" }} />
28+
}
29+
30+
export const getStatus = (
31+
theme: Theme,
32+
build: WorkspaceBuild,
33+
): {
34+
borderColor: string
35+
backgroundColor: string
36+
text: string
37+
icon: React.ReactNode
38+
} => {
39+
const status = getWorkspaceStatus(build)
40+
switch (status) {
41+
case undefined:
42+
return {
43+
borderColor: theme.palette.text.secondary,
44+
backgroundColor: theme.palette.text.secondary,
45+
text: StatusLanguage.loading,
46+
icon: <LoadingIcon />,
47+
}
48+
case "started":
49+
return {
50+
borderColor: theme.palette.success.main,
51+
backgroundColor: theme.palette.success.dark,
52+
text: StatusLanguage.started,
53+
icon: <PlayIcon />,
54+
}
55+
case "starting":
56+
return {
57+
borderColor: theme.palette.success.main,
58+
backgroundColor: theme.palette.success.dark,
59+
text: StatusLanguage.starting,
60+
icon: <LoadingIcon />,
61+
}
62+
case "stopping":
63+
return {
64+
borderColor: theme.palette.warning.main,
65+
backgroundColor: theme.palette.warning.dark,
66+
text: StatusLanguage.stopping,
67+
icon: <LoadingIcon />,
68+
}
69+
case "stopped":
70+
return {
71+
borderColor: theme.palette.warning.main,
72+
backgroundColor: theme.palette.warning.dark,
73+
text: StatusLanguage.stopped,
74+
icon: <StopIcon />,
75+
}
76+
case "deleting":
77+
return {
78+
borderColor: theme.palette.warning.main,
79+
backgroundColor: theme.palette.warning.dark,
80+
text: StatusLanguage.deleting,
81+
icon: <LoadingIcon />,
82+
}
83+
case "deleted":
84+
return {
85+
borderColor: theme.palette.error.main,
86+
backgroundColor: theme.palette.error.dark,
87+
text: StatusLanguage.deleted,
88+
icon: <ErrorIcon />,
89+
}
90+
case "canceling":
91+
return {
92+
borderColor: theme.palette.warning.main,
93+
backgroundColor: theme.palette.warning.dark,
94+
text: StatusLanguage.canceling,
95+
icon: <LoadingIcon />,
96+
}
97+
case "canceled":
98+
return {
99+
borderColor: theme.palette.warning.main,
100+
backgroundColor: theme.palette.warning.dark,
101+
text: StatusLanguage.canceled,
102+
icon: <ErrorIcon />,
103+
}
104+
case "error":
105+
return {
106+
borderColor: theme.palette.error.main,
107+
backgroundColor: theme.palette.error.dark,
108+
text: StatusLanguage.failed,
109+
icon: <ErrorIcon />,
110+
}
111+
case "queued":
112+
return {
113+
borderColor: theme.palette.info.main,
114+
backgroundColor: theme.palette.info.dark,
115+
text: StatusLanguage.queued,
116+
icon: <LoadingIcon />,
117+
}
118+
}
119+
throw new Error("unknown text " + status)
120+
}
121+
122+
export type WorkspaceStatusBadgeProps = {
123+
build: WorkspaceBuild
124+
className?: string
125+
}
126+
127+
export const WorkspaceStatusBadge: React.FC<WorkspaceStatusBadgeProps> = ({ build, className }) => {
128+
const styles = useStyles()
129+
const theme = useTheme()
130+
const { text, icon, ...colorStyles } = getStatus(theme, build)
131+
return (
132+
<div
133+
className={combineClasses([styles.wrapper, className])}
134+
style={{ ...colorStyles }}
135+
role="status"
136+
>
137+
<div className={styles.iconWrapper}>{icon}</div>
138+
{text}
139+
</div>
140+
)
141+
}
142+
143+
const useStyles = makeStyles((theme) => ({
144+
wrapper: {
145+
fontFamily: MONOSPACE_FONT_FAMILY,
146+
display: "inline-flex",
147+
alignItems: "center",
148+
borderWidth: 1,
149+
borderStyle: "solid",
150+
borderRadius: 99999,
151+
fontSize: 14,
152+
fontWeight: 500,
153+
color: "#FFF",
154+
height: theme.spacing(3),
155+
paddingLeft: theme.spacing(0.75),
156+
paddingRight: theme.spacing(1.5),
157+
whiteSpace: "nowrap",
158+
},
159+
160+
iconWrapper: {
161+
marginRight: theme.spacing(0.5),
162+
width: theme.spacing(2),
163+
height: theme.spacing(2),
164+
lineHeight: 0,
165+
display: "flex",
166+
alignItems: "center",
167+
justifyContent: "center",
168+
169+
"& > svg": {
170+
width: theme.spacing(2),
171+
height: theme.spacing(2),
172+
},
173+
},
174+
}))

site/src/components/WorkspacesTable/WorkspacesRow.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import TableRow from "@material-ui/core/TableRow"
33
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
44
import useTheme from "@material-ui/styles/useTheme"
55
import { useActor } from "@xstate/react"
6+
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
67
import { FC } from "react"
78
import { useNavigate } from "react-router-dom"
89
import { createDayString } from "util/createDayString"
9-
import { getDisplayStatus, getDisplayWorkspaceBuildInitiatedBy } from "../../util/workspace"
10+
import { getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
1011
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
1112
import { AvatarData } from "../AvatarData/AvatarData"
1213
import {
@@ -28,7 +29,6 @@ export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ w
2829
const theme: Theme = useTheme()
2930
const [workspaceState, send] = useActor(workspaceRef)
3031
const { data: workspace } = workspaceState.context
31-
const status = getDisplayStatus(theme, workspace.latest_build)
3232
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(workspace.latest_build)
3333
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
3434

@@ -74,7 +74,7 @@ export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ w
7474
</TableCellLink>
7575

7676
<TableCellLink to={workspacePageLink}>
77-
<span style={{ color: status.color }}>{status.status}</span>
77+
<WorkspaceStatusBadge build={workspace.latest_build} />
7878
</TableCellLink>
7979
<TableCellLink to={workspacePageLink}>
8080
<div className={styles.arrowCell}>

site/src/testHelpers/entities.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ export const MockRunningProvisionerJob: TypesGen.ProvisionerJob = {
9898
...MockProvisionerJob,
9999
status: "running",
100100
}
101-
101+
export const MockPendingProvisionerJob: TypesGen.ProvisionerJob = {
102+
...MockProvisionerJob,
103+
status: "pending",
104+
}
102105
export const MockTemplateVersion: TypesGen.TemplateVersion = {
103106
id: "test-template-version",
104107
created_at: "2022-05-17T17:39:01.382927298Z",
@@ -236,6 +239,15 @@ export const MockDeletedWorkspace: TypesGen.Workspace = {
236239

237240
export const MockOutdatedWorkspace: TypesGen.Workspace = { ...MockFailedWorkspace, outdated: true }
238241

242+
export const MockQueuedWorkspace: TypesGen.Workspace = {
243+
...MockWorkspace,
244+
latest_build: {
245+
...MockWorkspaceBuild,
246+
job: MockPendingProvisionerJob,
247+
transition: "start",
248+
},
249+
}
250+
239251
export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
240252
id: "test-app",
241253
name: "test-app",

site/src/theme/palettes.ts

+7
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@ export const darkPalette: PaletteOptions = {
2424
divider: "hsl(221, 32%, 26%)",
2525
warning: {
2626
main: "hsl(20, 79%, 53%)",
27+
dark: "#57250C",
2728
},
2829
success: {
2930
main: "hsl(142, 58%, 41%)",
31+
dark: "#205027",
3032
},
3133
info: {
3234
main: "hsl(219, 67%, 54%)",
35+
dark: "#0C2551",
36+
},
37+
error: {
38+
main: "#D8666C",
39+
dark: "#511112",
3340
},
3441
}

0 commit comments

Comments
 (0)