Skip to content

Commit e6f11a3

Browse files
refactor(site): add minor improvements to the schedule controls (#10756)
Demo: https://github.com/coder/coder/assets/3165839/d6ea83c0-6390-42d9-bd48-3438fc8685db
1 parent 20c2dda commit e6f11a3

File tree

2 files changed

+92
-19
lines changed

2 files changed

+92
-19
lines changed

site/src/pages/WorkspacePage/WorkspaceStats.tsx

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { css } from "@emotion/css";
22
import { type Interpolation, type Theme } from "@emotion/react";
3-
import Link from "@mui/material/Link";
3+
import Link, { LinkProps } from "@mui/material/Link";
44
import { WorkspaceOutdatedTooltip } from "components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip";
5-
import { type FC } from "react";
5+
import { forwardRef, type FC } from "react";
66
import { Link as RouterLink } from "react-router-dom";
77
import {
88
getDisplayWorkspaceTemplateName,
@@ -26,6 +26,8 @@ import {
2626
} from "components/Popover/Popover";
2727
import { workspaceQuota } from "api/queries/workspaceQuota";
2828
import { useQuery } from "react-query";
29+
import Tooltip from "@mui/material/Tooltip";
30+
import _ from "lodash";
2931

3032
const Language = {
3133
workspaceDetails: "Workspace Details",
@@ -120,16 +122,15 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
120122
css={styles.statsItem}
121123
label={getScheduleLabel(workspace)}
122124
value={
123-
<span css={styles.scheduleValue}>
124-
<Link
125-
component={RouterLink}
126-
to="settings/schedule"
127-
title="Schedule settings"
128-
>
129-
{isWorkspaceOn(workspace)
130-
? autostopDisplay(workspace)
131-
: autostartDisplay(workspace.autostart_schedule)}
132-
</Link>
125+
<div css={styles.scheduleValue}>
126+
{isWorkspaceOn(workspace) ? (
127+
<AutoStopDisplay workspace={workspace} />
128+
) : (
129+
<ScheduleSettingsLink>
130+
{autostartDisplay(workspace.autostart_schedule)}
131+
</ScheduleSettingsLink>
132+
)}
133+
133134
{canUpdateWorkspace && canEditDeadline(workspace) && (
134135
<span css={styles.scheduleControls}>
135136
<Popover>
@@ -178,7 +179,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
178179
</Popover>
179180
</span>
180181
)}
181-
</span>
182+
</div>
182183
}
183184
/>
184185
)}
@@ -220,6 +221,7 @@ const AddTimeContent = (props: {
220221
}}
221222
>
222223
<TextField
224+
autoFocus
223225
name="hours"
224226
type="number"
225227
size="small"
@@ -268,6 +270,7 @@ export const DecreaseTimeContent = (props: {
268270
}}
269271
>
270272
<TextField
273+
autoFocus
271274
name="hours"
272275
type="number"
273276
size="small"
@@ -292,6 +295,47 @@ export const DecreaseTimeContent = (props: {
292295
);
293296
};
294297

298+
const AutoStopDisplay = (props: { workspace: Workspace }) => {
299+
const { workspace } = props;
300+
const display = autostopDisplay(workspace);
301+
302+
if (display.tooltip) {
303+
return (
304+
<Tooltip title={display.tooltip}>
305+
<ScheduleSettingsLink
306+
css={(theme) => ({
307+
color: isShutdownSoon(workspace)
308+
? `${theme.palette.warning.light} !important`
309+
: undefined,
310+
})}
311+
>
312+
{display.message}
313+
</ScheduleSettingsLink>
314+
</Tooltip>
315+
);
316+
}
317+
318+
return <ScheduleSettingsLink>{display.message}</ScheduleSettingsLink>;
319+
};
320+
321+
const ScheduleSettingsLink = forwardRef<HTMLAnchorElement, LinkProps>(
322+
(props, ref) => {
323+
return (
324+
<Link
325+
ref={ref}
326+
component={RouterLink}
327+
to="settings/schedule"
328+
css={{
329+
"&:first-letter": {
330+
textTransform: "uppercase",
331+
},
332+
}}
333+
{...props}
334+
/>
335+
);
336+
},
337+
);
338+
295339
export const canEditDeadline = (workspace: Workspace): boolean => {
296340
return isWorkspaceOn(workspace) && Boolean(workspace.latest_build.deadline);
297341
};
@@ -307,7 +351,19 @@ export const shouldDisplayScheduleLabel = (workspace: Workspace): boolean => {
307351
};
308352

309353
const getScheduleLabel = (workspace: Workspace) => {
310-
return isWorkspaceOn(workspace) ? "Stops at" : "Starts at";
354+
return isWorkspaceOn(workspace) ? "Stops" : "Starts at";
355+
};
356+
357+
const isShutdownSoon = (workspace: Workspace): boolean => {
358+
const deadline = workspace.latest_build.deadline;
359+
if (!deadline) {
360+
return false;
361+
}
362+
const deadlineDate = new Date(deadline);
363+
const now = new Date();
364+
const diff = deadlineDate.getTime() - now.getTime();
365+
const oneHour = 1000 * 60 * 60;
366+
return diff < oneHour;
311367
};
312368

313369
const timePopoverFieldInputStyles = css`
@@ -369,6 +425,7 @@ const styles = {
369425

370426
timePopoverTitle: {
371427
fontWeight: 600,
428+
marginBottom: 8,
372429
},
373430

374431
timePopoverDescription: (theme) => ({
@@ -380,6 +437,7 @@ const styles = {
380437
alignItems: "center",
381438
gap: 8,
382439
padding: "8px 0",
440+
marginTop: 12,
383441
},
384442

385443
timePopoverField: {

site/src/utils/schedule.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ export const isShuttingDown = (
8888
return isWorkspaceOn(workspace) && now.isAfter(deadline);
8989
};
9090

91-
export const autostopDisplay = (workspace: Workspace): string => {
91+
export const autostopDisplay = (
92+
workspace: Workspace,
93+
): {
94+
message: string;
95+
tooltip?: string;
96+
} => {
9297
const ttl = workspace.ttl_ms;
9398

9499
if (isWorkspaceOn(workspace) && workspace.latest_build.deadline) {
@@ -100,19 +105,29 @@ export const autostopDisplay = (workspace: Workspace): string => {
100105

101106
const deadline = dayjs(workspace.latest_build.deadline).utc();
102107
if (isShuttingDown(workspace, deadline)) {
103-
return Language.workspaceShuttingDownLabel;
108+
return {
109+
message: Language.workspaceShuttingDownLabel,
110+
};
104111
} else {
105-
return deadline.tz(dayjs.tz.guess()).format("MMMM D, YYYY h:mm A");
112+
const deadlineTz = deadline.tz(dayjs.tz.guess());
113+
return {
114+
message: deadlineTz.fromNow(),
115+
tooltip: deadlineTz.format("MMMM D, YYYY h:mm A"),
116+
};
106117
}
107118
} else if (!ttl || ttl < 1) {
108119
// If the workspace is not on, and the ttl is 0 or undefined, then the
109120
// workspace is set to manually shutdown.
110-
return Language.manual;
121+
return {
122+
message: Language.manual,
123+
};
111124
} else {
112125
// The workspace has a ttl set, but is either in an unknown state or is
113126
// not running. Therefore, we derive from workspace.ttl.
114127
const duration = dayjs.duration(ttl, "milliseconds");
115-
return `${duration.humanize()} ${Language.afterStart}`;
128+
return {
129+
message: `${duration.humanize()} ${Language.afterStart}`,
130+
};
116131
}
117132
};
118133

0 commit comments

Comments
 (0)