Skip to content

Commit 78e8102

Browse files
BrunoQuaresmakylecarbs
authored andcommitted
refactor: Move schedule info to the sidebar (#1665)
1 parent b456765 commit 78e8102

File tree

4 files changed

+158
-61
lines changed

4 files changed

+158
-61
lines changed

site/src/components/Workspace/Workspace.tsx

+23-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { BuildsTable } from "../BuildsTable/BuildsTable"
88
import { Resources } from "../Resources/Resources"
99
import { Stack } from "../Stack/Stack"
1010
import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions"
11+
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
1112
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
1213
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"
1314

@@ -64,12 +65,18 @@ export const Workspace: React.FC<WorkspaceProps> = ({
6465
</div>
6566
</div>
6667

67-
<Stack spacing={3}>
68-
<WorkspaceStats workspace={workspace} />
69-
<Resources resources={resources} getResourcesError={getResourcesError} />
70-
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
71-
<BuildsTable builds={builds} className={styles.timelineTable} />
72-
</WorkspaceSection>
68+
<Stack direction="row" spacing={3} className={styles.layout}>
69+
<Stack spacing={3} className={styles.main}>
70+
<WorkspaceStats workspace={workspace} />
71+
<Resources resources={resources} getResourcesError={getResourcesError} />
72+
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
73+
<BuildsTable builds={builds} className={styles.timelineTable} />
74+
</WorkspaceSection>
75+
</Stack>
76+
77+
<Stack spacing={3} className={styles.sidebar}>
78+
<WorkspaceSchedule workspace={workspace} />
79+
</Stack>
7380
</Stack>
7481
</div>
7582
)
@@ -99,6 +106,16 @@ export const useStyles = makeStyles((theme) => {
99106
fontFamily: "inherit",
100107
marginTop: theme.spacing(0.5),
101108
},
109+
layout: {
110+
alignItems: "flex-start",
111+
},
112+
main: {
113+
width: "100%",
114+
},
115+
sidebar: {
116+
width: theme.spacing(32),
117+
flexShrink: 0,
118+
},
102119
timelineContents: {
103120
margin: 0,
104121
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockWorkspace } from "../../testHelpers/renderHelpers"
4+
import { WorkspaceSchedule, WorkspaceScheduleProps } from "./WorkspaceSchedule"
5+
6+
export default {
7+
title: "components/WorkspaceSchedule",
8+
component: WorkspaceSchedule,
9+
argTypes: {},
10+
}
11+
12+
const Template: Story<WorkspaceScheduleProps> = (args) => <WorkspaceSchedule {...args} />
13+
14+
export const Example = Template.bind({})
15+
Example.args = {
16+
workspace: MockWorkspace,
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import Link from "@material-ui/core/Link"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import Typography from "@material-ui/core/Typography"
4+
import ScheduleIcon from "@material-ui/icons/Schedule"
5+
import cronstrue from "cronstrue"
6+
import dayjs from "dayjs"
7+
import duration from "dayjs/plugin/duration"
8+
import relativeTime from "dayjs/plugin/relativeTime"
9+
import React from "react"
10+
import { Workspace } from "../../api/typesGenerated"
11+
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
12+
import { extractTimezone, stripTimezone } from "../../util/schedule"
13+
import { Stack } from "../Stack/Stack"
14+
15+
dayjs.extend(duration)
16+
dayjs.extend(relativeTime)
17+
18+
const autoStartLabel = (schedule: string): string => {
19+
const prefix = "Start"
20+
21+
if (schedule) {
22+
return `${prefix} (${extractTimezone(schedule)})`
23+
} else {
24+
return prefix
25+
}
26+
}
27+
28+
const autoStartDisplay = (schedule: string): string => {
29+
if (schedule) {
30+
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
31+
}
32+
return "Manual"
33+
}
34+
35+
const autoStopDisplay = (workspace: Workspace): string => {
36+
const latest = workspace.latest_build
37+
38+
if (!workspace.ttl || workspace.ttl < 1) {
39+
return "Manual"
40+
}
41+
42+
if (latest.transition === "start") {
43+
const now = dayjs()
44+
const updatedAt = dayjs(latest.updated_at)
45+
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
46+
if (now.isAfter(deadline)) {
47+
return "Workspace is shutting down now"
48+
}
49+
return now.to(deadline)
50+
}
51+
52+
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
53+
return `${duration.humanize()} after start`
54+
}
55+
56+
export interface WorkspaceScheduleProps {
57+
workspace: Workspace
58+
}
59+
60+
export const WorkspaceSchedule: React.FC<WorkspaceScheduleProps> = ({ workspace }) => {
61+
const styles = useStyles()
62+
63+
return (
64+
<div className={styles.schedule}>
65+
<Stack spacing={2}>
66+
<Typography variant="body1" className={styles.title}>
67+
<ScheduleIcon className={styles.scheduleIcon} />
68+
Schedule
69+
</Typography>
70+
<div>
71+
<span className={styles.scheduleLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
72+
<span className={styles.scheduleValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
73+
</div>
74+
<div>
75+
<span className={styles.scheduleLabel}>Shutdown</span>
76+
<span className={styles.scheduleValue}>{autoStopDisplay(workspace)}</span>
77+
</div>
78+
<div>
79+
<Link className={styles.scheduleAction}>Edit schedule</Link>
80+
</div>
81+
</Stack>
82+
</div>
83+
)
84+
}
85+
86+
const useStyles = makeStyles((theme) => ({
87+
schedule: {
88+
fontFamily: MONOSPACE_FONT_FAMILY,
89+
},
90+
title: {
91+
fontWeight: 600,
92+
93+
fontFamily: "inherit",
94+
display: "flex",
95+
alignItems: "center",
96+
},
97+
scheduleIcon: {
98+
width: 16,
99+
height: 16,
100+
marginRight: theme.spacing(1),
101+
},
102+
scheduleLabel: {
103+
fontSize: 12,
104+
textTransform: "uppercase",
105+
display: "block",
106+
fontWeight: 600,
107+
color: theme.palette.text.secondary,
108+
},
109+
scheduleValue: {
110+
fontSize: 16,
111+
marginTop: theme.spacing(0.25),
112+
display: "inline-block",
113+
color: theme.palette.text.secondary,
114+
},
115+
scheduleAction: {
116+
cursor: "pointer",
117+
},
118+
}))

site/src/components/WorkspaceStats/WorkspaceStats.tsx

-55
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,13 @@
11
import Link from "@material-ui/core/Link"
22
import { makeStyles, useTheme } from "@material-ui/core/styles"
3-
import cronstrue from "cronstrue"
43
import dayjs from "dayjs"
5-
import duration from "dayjs/plugin/duration"
6-
import relativeTime from "dayjs/plugin/relativeTime"
74
import React from "react"
85
import { Link as RouterLink } from "react-router-dom"
96
import { Workspace } from "../../api/typesGenerated"
107
import { CardRadius, MONOSPACE_FONT_FAMILY } from "../../theme/constants"
118
import { combineClasses } from "../../util/combineClasses"
12-
import { extractTimezone, stripTimezone } from "../../util/schedule"
139
import { getDisplayStatus } from "../../util/workspace"
1410

15-
dayjs.extend(duration)
16-
dayjs.extend(relativeTime)
17-
18-
const autoStartLabel = (schedule: string): string => {
19-
const prefix = "Start"
20-
21-
if (schedule) {
22-
return `${prefix} (${extractTimezone(schedule)})`
23-
} else {
24-
return prefix
25-
}
26-
}
27-
28-
const autoStartDisplay = (schedule: string): string => {
29-
if (schedule) {
30-
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
31-
}
32-
return "Manual"
33-
}
34-
35-
const autoStopDisplay = (workspace: Workspace): string => {
36-
const latest = workspace.latest_build
37-
38-
if (!workspace.ttl || workspace.ttl < 1) {
39-
return "Manual"
40-
}
41-
42-
if (latest.transition === "start") {
43-
const now = dayjs()
44-
const updatedAt = dayjs(latest.updated_at)
45-
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
46-
if (now.isAfter(deadline)) {
47-
return "workspace is shutting down now"
48-
}
49-
return now.to(deadline)
50-
}
51-
52-
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
53-
return `${duration.humanize()} after start`
54-
}
55-
5611
export interface WorkspaceStatsProps {
5712
workspace: Workspace
5813
}
@@ -99,16 +54,6 @@ export const WorkspaceStats: React.FC<WorkspaceStatsProps> = ({ workspace }) =>
9954
<span className={styles.statsLabel}>Last Built</span>
10055
<span className={styles.statsValue}>{dayjs().to(dayjs(workspace.latest_build.created_at))}</span>
10156
</div>
102-
<div className={styles.statsDivider} />
103-
<div className={styles.statItem}>
104-
<span className={styles.statsLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
105-
<span className={styles.statsValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
106-
</div>
107-
<div className={styles.statsDivider} />
108-
<div className={styles.statItem}>
109-
<span className={styles.statsLabel}>Shutdown</span>
110-
<span className={styles.statsValue}>{autoStopDisplay(workspace)}</span>
111-
</div>
11257
</div>
11358
)
11459
}

0 commit comments

Comments
 (0)