Skip to content

Commit b06ef0a

Browse files
presleypgreyscaled
andauthored
feat: Workspace StatusBar (#1362)
* Move component and prep * Make WorkspaceSection more reusable * Lay out elements * Layout tweaks * Add outdated to Workspace type * Fill out status bar component * Format * Add transition to types * Add api handlers for build toggle * Format * Parallelize machine * Lay out basics of build submachine * Pipe start and stop events through - needs status * Attempt at a machine It's so big, but collapsing start and stop made it hard to distinguish retry from toggle * Update mock * Render status and buttons * Fix type error on template page * Move Settings * Format * Keep refreshed workspace * Make it switch workspaces * Lint * Fix relative api path * Test * Fix polling * Add loading workspace state * Format * Add stub settings page * Format * Lint * Get rid of let * Add update * Make start use version id Important for update * Fix imports * Add polling for outdated * Rely on context instead of finite state for status * Handle canceling * Fix tests * Format * Display errors so users know when button presses didn't work * Fix api typo, remove logging * Lint * Simplify type Co-authored-by: G r e y <grey@coder.com> * Add type, extract helper Co-authored-by: G r e y <grey@coder.com>
1 parent e990a9a commit b06ef0a

File tree

17 files changed

+725
-125
lines changed

17 files changed

+725
-125
lines changed

site/src/AppRouter.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { TemplatesPage } from "./pages/TemplatesPages/TemplatesPage"
1818
import { CreateUserPage } from "./pages/UsersPage/CreateUserPage/CreateUserPage"
1919
import { UsersPage } from "./pages/UsersPage/UsersPage"
2020
import { WorkspacePage } from "./pages/WorkspacePage/WorkspacePage"
21+
import { WorkspaceSettingsPage } from "./pages/WorkspaceSettingsPage/WorkspaceSettingsPage"
2122

2223
const TerminalPage = React.lazy(() => import("./pages/TerminalPage/TerminalPage"))
2324

@@ -75,14 +76,24 @@ export const AppRouter: React.FC = () => (
7576
</Route>
7677

7778
<Route path="workspaces">
78-
<Route
79-
path=":workspace"
80-
element={
81-
<AuthAndFrame>
82-
<WorkspacePage />
83-
</AuthAndFrame>
84-
}
85-
/>
79+
<Route path=":workspace">
80+
<Route
81+
index
82+
element={
83+
<AuthAndFrame>
84+
<WorkspacePage />
85+
</AuthAndFrame>
86+
}
87+
/>
88+
<Route
89+
path="edit"
90+
element={
91+
<AuthAndFrame>
92+
<WorkspaceSettingsPage />
93+
</AuthAndFrame>
94+
}
95+
/>
96+
</Route>
8697
</Route>
8798

8899
<Route path="users">

site/src/api/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios, { AxiosRequestHeaders } from "axios"
22
import { mutate } from "swr"
3+
import { WorkspaceBuildTransition } from "./types"
34
import * as TypesGen from "./typesGenerated"
45

56
const CONTENT_TYPE_JSON: AxiosRequestHeaders = {
@@ -132,6 +133,21 @@ export const getWorkspaceResources = async (workspaceBuildID: string): Promise<T
132133
return response.data
133134
}
134135

136+
const postWorkspaceBuild =
137+
(transition: WorkspaceBuildTransition) =>
138+
async (workspaceId: string, template_version_id?: string): Promise<TypesGen.WorkspaceBuild> => {
139+
const payload = {
140+
transition,
141+
template_version_id,
142+
}
143+
const response = await axios.post(`/api/v2/workspaces/${workspaceId}/builds`, payload)
144+
return response.data
145+
}
146+
147+
export const startWorkspace = postWorkspaceBuild("start")
148+
export const stopWorkspace = postWorkspaceBuild("stop")
149+
export const deleteWorkspace = postWorkspaceBuild("delete")
150+
135151
export const createUser = async (user: TypesGen.CreateUserRequest): Promise<TypesGen.User> => {
136152
const response = await axios.post<TypesGen.User>("/api/v2/users", user)
137153
return response.data

site/src/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export interface ReconnectingPTYRequest {
1010
readonly height?: number
1111
readonly width?: number
1212
}
13+
14+
export type WorkspaceBuildTransition = "start" | "stop" | "delete"
Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { action } from "@storybook/addon-actions"
12
import { Story } from "@storybook/react"
23
import React from "react"
3-
import { MockOrganization, MockTemplate, MockWorkspace } from "../../testHelpers/renderHelpers"
4+
import { MockOrganization, MockOutdatedWorkspace, MockTemplate, MockWorkspace } from "../../testHelpers/renderHelpers"
45
import { Workspace, WorkspaceProps } from "./Workspace"
56

67
export default {
@@ -11,9 +12,43 @@ export default {
1112

1213
const Template: Story<WorkspaceProps> = (args) => <Workspace {...args} />
1314

14-
export const Example = Template.bind({})
15-
Example.args = {
15+
export const Started = Template.bind({})
16+
Started.args = {
1617
organization: MockOrganization,
1718
template: MockTemplate,
1819
workspace: MockWorkspace,
20+
handleStart: action("start"),
21+
handleStop: action("stop"),
22+
handleRetry: action("retry"),
23+
workspaceStatus: "started",
1924
}
25+
26+
export const Starting = Template.bind({})
27+
Starting.args = { ...Started.args, workspaceStatus: "starting" }
28+
29+
export const Stopped = Template.bind({})
30+
Stopped.args = { ...Started.args, workspaceStatus: "stopped" }
31+
32+
export const Stopping = Template.bind({})
33+
Stopping.args = { ...Started.args, workspaceStatus: "stopping" }
34+
35+
export const Error = Template.bind({})
36+
Error.args = { ...Started.args, workspaceStatus: "error" }
37+
38+
export const BuildLoading = Template.bind({})
39+
BuildLoading.args = { ...Started.args, workspaceStatus: "loading" }
40+
41+
export const Deleting = Template.bind({})
42+
Deleting.args = { ...Started.args, workspaceStatus: "deleting" }
43+
44+
export const Deleted = Template.bind({})
45+
Deleted.args = { ...Started.args, workspaceStatus: "deleted" }
46+
47+
export const Canceling = Template.bind({})
48+
Canceling.args = { ...Started.args, workspaceStatus: "canceling" }
49+
50+
export const NoBreadcrumb = Template.bind({})
51+
NoBreadcrumb.args = { ...Started.args, template: undefined }
52+
53+
export const Outdated = Template.bind({})
54+
Outdated.args = { ...Started.args, workspace: MockOutdatedWorkspace }

site/src/components/Workspace/Workspace.test.tsx

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,51 @@
1-
import Box from "@material-ui/core/Box"
2-
import Paper from "@material-ui/core/Paper"
31
import { makeStyles } from "@material-ui/core/styles"
42
import Typography from "@material-ui/core/Typography"
5-
import CloudCircleIcon from "@material-ui/icons/CloudCircle"
63
import React from "react"
7-
import { Link } from "react-router-dom"
84
import * as TypesGen from "../../api/typesGenerated"
5+
import { WorkspaceStatus } from "../../pages/WorkspacePage/WorkspacePage"
96
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
107
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
11-
import * as Constants from "./constants"
8+
import { WorkspaceStatusBar } from "../WorkspaceStatusBar/WorkspaceStatusBar"
129

1310
export interface WorkspaceProps {
14-
organization: TypesGen.Organization
11+
organization?: TypesGen.Organization
1512
workspace: TypesGen.Workspace
16-
template: TypesGen.Template
13+
template?: TypesGen.Template
14+
handleStart: () => void
15+
handleStop: () => void
16+
handleRetry: () => void
17+
handleUpdate: () => void
18+
workspaceStatus: WorkspaceStatus
1719
}
1820

1921
/**
2022
* Workspace is the top-level component for viewing an individual workspace
2123
*/
22-
export const Workspace: React.FC<WorkspaceProps> = ({ organization, template, workspace }) => {
24+
export const Workspace: React.FC<WorkspaceProps> = ({
25+
organization,
26+
template,
27+
workspace,
28+
handleStart,
29+
handleStop,
30+
handleRetry,
31+
handleUpdate,
32+
workspaceStatus,
33+
}) => {
2334
const styles = useStyles()
2435

2536
return (
2637
<div className={styles.root}>
2738
<div className={styles.vertical}>
28-
<WorkspaceHeader organization={organization} template={template} workspace={workspace} />
39+
<WorkspaceStatusBar
40+
organization={organization}
41+
template={template}
42+
workspace={workspace}
43+
handleStart={handleStart}
44+
handleStop={handleStop}
45+
handleRetry={handleRetry}
46+
handleUpdate={handleUpdate}
47+
workspaceStatus={workspaceStatus}
48+
/>
2949
<div className={styles.horizontal}>
3050
<div className={styles.sidebarContainer}>
3151
<WorkspaceSection title="Applications">
@@ -55,40 +75,6 @@ export const Workspace: React.FC<WorkspaceProps> = ({ organization, template, wo
5575
)
5676
}
5777

58-
/**
59-
* Component for the header at the top of the workspace page
60-
*/
61-
export const WorkspaceHeader: React.FC<WorkspaceProps> = ({ organization, template, workspace }) => {
62-
const styles = useStyles()
63-
64-
const templateLink = `/templates/${organization.name}/${template.name}`
65-
66-
return (
67-
<Paper elevation={0} className={styles.section}>
68-
<div className={styles.horizontal}>
69-
<WorkspaceHeroIcon />
70-
<div className={styles.vertical}>
71-
<Typography variant="h4">{workspace.name}</Typography>
72-
<Typography variant="body2" color="textSecondary">
73-
<Link to={templateLink}>{template.name}</Link>
74-
</Typography>
75-
</div>
76-
</div>
77-
</Paper>
78-
)
79-
}
80-
81-
/**
82-
* Component to render the 'Hero Icon' in the header of a workspace
83-
*/
84-
export const WorkspaceHeroIcon: React.FC = () => {
85-
return (
86-
<Box mr="1em">
87-
<CloudCircleIcon width={Constants.TitleIconSize} height={Constants.TitleIconSize} />
88-
</Box>
89-
)
90-
}
91-
9278
/**
9379
* Temporary placeholder component until we have the sections implemented
9480
* Can be removed once the Workspace page has all the necessary sections
@@ -101,7 +87,7 @@ const Placeholder: React.FC = () => {
10187
)
10288
}
10389

104-
export const useStyles = makeStyles((theme) => {
90+
export const useStyles = makeStyles(() => {
10591
return {
10692
root: {
10793
display: "flex",
@@ -115,12 +101,6 @@ export const useStyles = makeStyles((theme) => {
115101
display: "flex",
116102
flexDirection: "column",
117103
},
118-
section: {
119-
border: `1px solid ${theme.palette.divider}`,
120-
borderRadius: Constants.CardRadius,
121-
padding: Constants.CardPadding,
122-
margin: theme.spacing(1),
123-
},
124104
sidebarContainer: {
125105
display: "flex",
126106
flexDirection: "column",
@@ -129,9 +109,5 @@ export const useStyles = makeStyles((theme) => {
129109
timelineContainer: {
130110
flex: 1,
131111
},
132-
icon: {
133-
width: Constants.TitleIconSize,
134-
height: Constants.TitleIconSize,
135-
},
136112
}
137113
})

site/src/components/Workspace/constants.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

site/src/components/WorkspaceSection/WorkspaceSection.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@ import Paper from "@material-ui/core/Paper"
22
import { makeStyles } from "@material-ui/core/styles"
33
import Typography from "@material-ui/core/Typography"
44
import React from "react"
5-
import { CardPadding, CardRadius } from "../Workspace/constants"
5+
import { CardPadding, CardRadius } from "../../theme/constants"
66

77
export interface WorkspaceSectionProps {
8-
title: string
8+
title?: string
99
}
1010

1111
export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({ title, children }) => {
1212
const styles = useStyles()
1313

1414
return (
1515
<Paper elevation={0} className={styles.root}>
16-
<div className={styles.headerContainer}>
17-
<div className={styles.header}>
18-
<Typography variant="h6">{title}</Typography>
16+
{title && (
17+
<div className={styles.headerContainer}>
18+
<div className={styles.header}>
19+
<Typography variant="h6">{title}</Typography>
20+
</div>
1921
</div>
20-
</div>
22+
)}
2123

2224
<div className={styles.contents}>{children}</div>
2325
</Paper>

0 commit comments

Comments
 (0)