refactor: Move schedule info to the sidebar (#1665)

This commit is contained in:
Bruno Quaresma
2022-05-23 09:41:04 -05:00
committed by GitHub
parent 1f03277f1c
commit 9f3a6d631c
4 changed files with 158 additions and 61 deletions
+23 -6
View File
@@ -8,6 +8,7 @@ import { BuildsTable } from "../BuildsTable/BuildsTable"
import { Resources } from "../Resources/Resources"
import { Stack } from "../Stack/Stack"
import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions"
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"
@@ -64,12 +65,18 @@ export const Workspace: React.FC<WorkspaceProps> = ({
</div>
</div>
<Stack spacing={3}>
<WorkspaceStats workspace={workspace} />
<Resources resources={resources} getResourcesError={getResourcesError} />
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
<BuildsTable builds={builds} className={styles.timelineTable} />
</WorkspaceSection>
<Stack direction="row" spacing={3} className={styles.layout}>
<Stack spacing={3} className={styles.main}>
<WorkspaceStats workspace={workspace} />
<Resources resources={resources} getResourcesError={getResourcesError} />
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
<BuildsTable builds={builds} className={styles.timelineTable} />
</WorkspaceSection>
</Stack>
<Stack spacing={3} className={styles.sidebar}>
<WorkspaceSchedule workspace={workspace} />
</Stack>
</Stack>
</div>
)
@@ -99,6 +106,16 @@ export const useStyles = makeStyles((theme) => {
fontFamily: "inherit",
marginTop: theme.spacing(0.5),
},
layout: {
alignItems: "flex-start",
},
main: {
width: "100%",
},
sidebar: {
width: theme.spacing(32),
flexShrink: 0,
},
timelineContents: {
margin: 0,
},
@@ -0,0 +1,17 @@
import { Story } from "@storybook/react"
import React from "react"
import { MockWorkspace } from "../../testHelpers/renderHelpers"
import { WorkspaceSchedule, WorkspaceScheduleProps } from "./WorkspaceSchedule"
export default {
title: "components/WorkspaceSchedule",
component: WorkspaceSchedule,
argTypes: {},
}
const Template: Story<WorkspaceScheduleProps> = (args) => <WorkspaceSchedule {...args} />
export const Example = Template.bind({})
Example.args = {
workspace: MockWorkspace,
}
@@ -0,0 +1,118 @@
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import ScheduleIcon from "@material-ui/icons/Schedule"
import cronstrue from "cronstrue"
import dayjs from "dayjs"
import duration from "dayjs/plugin/duration"
import relativeTime from "dayjs/plugin/relativeTime"
import React from "react"
import { Workspace } from "../../api/typesGenerated"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
import { extractTimezone, stripTimezone } from "../../util/schedule"
import { Stack } from "../Stack/Stack"
dayjs.extend(duration)
dayjs.extend(relativeTime)
const autoStartLabel = (schedule: string): string => {
const prefix = "Start"
if (schedule) {
return `${prefix} (${extractTimezone(schedule)})`
} else {
return prefix
}
}
const autoStartDisplay = (schedule: string): string => {
if (schedule) {
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
}
return "Manual"
}
const autoStopDisplay = (workspace: Workspace): string => {
const latest = workspace.latest_build
if (!workspace.ttl || workspace.ttl < 1) {
return "Manual"
}
if (latest.transition === "start") {
const now = dayjs()
const updatedAt = dayjs(latest.updated_at)
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
if (now.isAfter(deadline)) {
return "Workspace is shutting down now"
}
return now.to(deadline)
}
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
return `${duration.humanize()} after start`
}
export interface WorkspaceScheduleProps {
workspace: Workspace
}
export const WorkspaceSchedule: React.FC<WorkspaceScheduleProps> = ({ workspace }) => {
const styles = useStyles()
return (
<div className={styles.schedule}>
<Stack spacing={2}>
<Typography variant="body1" className={styles.title}>
<ScheduleIcon className={styles.scheduleIcon} />
Schedule
</Typography>
<div>
<span className={styles.scheduleLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
<span className={styles.scheduleValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
</div>
<div>
<span className={styles.scheduleLabel}>Shutdown</span>
<span className={styles.scheduleValue}>{autoStopDisplay(workspace)}</span>
</div>
<div>
<Link className={styles.scheduleAction}>Edit schedule</Link>
</div>
</Stack>
</div>
)
}
const useStyles = makeStyles((theme) => ({
schedule: {
fontFamily: MONOSPACE_FONT_FAMILY,
},
title: {
fontWeight: 600,
fontFamily: "inherit",
display: "flex",
alignItems: "center",
},
scheduleIcon: {
width: 16,
height: 16,
marginRight: theme.spacing(1),
},
scheduleLabel: {
fontSize: 12,
textTransform: "uppercase",
display: "block",
fontWeight: 600,
color: theme.palette.text.secondary,
},
scheduleValue: {
fontSize: 16,
marginTop: theme.spacing(0.25),
display: "inline-block",
color: theme.palette.text.secondary,
},
scheduleAction: {
cursor: "pointer",
},
}))
@@ -1,58 +1,13 @@
import Link from "@material-ui/core/Link"
import { makeStyles, useTheme } from "@material-ui/core/styles"
import cronstrue from "cronstrue"
import dayjs from "dayjs"
import duration from "dayjs/plugin/duration"
import relativeTime from "dayjs/plugin/relativeTime"
import React from "react"
import { Link as RouterLink } from "react-router-dom"
import { Workspace } from "../../api/typesGenerated"
import { CardRadius, MONOSPACE_FONT_FAMILY } from "../../theme/constants"
import { combineClasses } from "../../util/combineClasses"
import { extractTimezone, stripTimezone } from "../../util/schedule"
import { getDisplayStatus } from "../../util/workspace"
dayjs.extend(duration)
dayjs.extend(relativeTime)
const autoStartLabel = (schedule: string): string => {
const prefix = "Start"
if (schedule) {
return `${prefix} (${extractTimezone(schedule)})`
} else {
return prefix
}
}
const autoStartDisplay = (schedule: string): string => {
if (schedule) {
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
}
return "Manual"
}
const autoStopDisplay = (workspace: Workspace): string => {
const latest = workspace.latest_build
if (!workspace.ttl || workspace.ttl < 1) {
return "Manual"
}
if (latest.transition === "start") {
const now = dayjs()
const updatedAt = dayjs(latest.updated_at)
const deadline = updatedAt.add(workspace.ttl / 1_000_000, "ms")
if (now.isAfter(deadline)) {
return "workspace is shutting down now"
}
return now.to(deadline)
}
const duration = dayjs.duration(workspace.ttl / 1_000_000, "milliseconds")
return `${duration.humanize()} after start`
}
export interface WorkspaceStatsProps {
workspace: Workspace
}
@@ -99,16 +54,6 @@ export const WorkspaceStats: React.FC<WorkspaceStatsProps> = ({ workspace }) =>
<span className={styles.statsLabel}>Last Built</span>
<span className={styles.statsValue}>{dayjs().to(dayjs(workspace.latest_build.created_at))}</span>
</div>
<div className={styles.statsDivider} />
<div className={styles.statItem}>
<span className={styles.statsLabel}>{autoStartLabel(workspace.autostart_schedule)}</span>
<span className={styles.statsValue}>{autoStartDisplay(workspace.autostart_schedule)}</span>
</div>
<div className={styles.statsDivider} />
<div className={styles.statItem}>
<span className={styles.statsLabel}>Shutdown</span>
<span className={styles.statsValue}>{autoStopDisplay(workspace)}</span>
</div>
</div>
)
}