Skip to content

Commit cdc0ea2

Browse files
committed
yay!!!
1 parent b8e32a3 commit cdc0ea2

File tree

5 files changed

+67
-64
lines changed

5 files changed

+67
-64
lines changed

site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx

+12-33
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,28 @@
11
import { render, screen } from "@testing-library/react";
2-
import { ThemeProvider } from "contexts/ThemeProvider";
3-
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
4-
import { MockWorkspace } from "testHelpers/entities";
5-
import { WorkspaceScheduleControls } from "./WorkspaceScheduleControls";
6-
import { workspaceByOwnerAndName } from "api/queries/workspaces";
2+
import { type FC } from "react";
3+
import { QueryClient, QueryClientProvider } from "react-query";
74
import { RouterProvider, createMemoryRouter } from "react-router-dom";
85
import userEvent from "@testing-library/user-event";
9-
import { server } from "testHelpers/server";
10-
import { rest } from "msw";
116
import dayjs from "dayjs";
127
import * as API from "api/api";
138
import { GlobalSnackbar } from "components/GlobalSnackbar/GlobalSnackbar";
9+
import { ThemeProvider } from "contexts/ThemeProvider";
10+
import { MockTemplate, MockWorkspace } from "testHelpers/entities";
11+
import { WorkspaceScheduleControls } from "./WorkspaceScheduleControls";
1412

15-
const Wrapper = () => {
16-
const { data: workspace } = useQuery(
17-
workspaceByOwnerAndName(MockWorkspace.owner_name, MockWorkspace.name),
13+
const Wrapper: FC = () => {
14+
return (
15+
<WorkspaceScheduleControls
16+
workspace={MockWorkspace}
17+
template={MockTemplate}
18+
canUpdateSchedule
19+
/>
1820
);
19-
20-
if (!workspace) {
21-
return null;
22-
}
23-
24-
return <WorkspaceScheduleControls workspace={workspace} canUpdateSchedule />;
2521
};
2622

2723
const BASE_DEADLINE = dayjs().add(3, "hour");
2824

2925
const renderScheduleControls = async () => {
30-
server.use(
31-
rest.get(
32-
"/api/v2/users/:username/workspace/:workspaceName",
33-
(req, res, ctx) => {
34-
return res(
35-
ctx.status(200),
36-
ctx.json({
37-
...MockWorkspace,
38-
latest_build: {
39-
...MockWorkspace.latest_build,
40-
deadline: BASE_DEADLINE.toISOString(),
41-
},
42-
}),
43-
);
44-
},
45-
),
46-
);
4726
render(
4827
<ThemeProvider>
4928
<QueryClientProvider client={new QueryClient()}>

site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx

+22-19
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { type Interpolation, type Theme } from "@emotion/react";
2-
import Link, { LinkProps } from "@mui/material/Link";
2+
import Link, { type LinkProps } from "@mui/material/Link";
3+
import IconButton from "@mui/material/IconButton";
4+
import RemoveIcon from "@mui/icons-material/RemoveOutlined";
5+
import AddIcon from "@mui/icons-material/AddOutlined";
6+
import Tooltip from "@mui/material/Tooltip";
7+
import { visuallyHidden } from "@mui/utils";
8+
import { type Dayjs } from "dayjs";
39
import { forwardRef, type FC, useRef } from "react";
10+
import { useMutation, useQueryClient } from "react-query";
411
import { Link as RouterLink } from "react-router-dom";
512
import { isWorkspaceOn } from "utils/workspace";
6-
import type { Workspace } from "api/typesGenerated";
13+
import type { Template, Workspace } from "api/typesGenerated";
714
import {
815
autostartDisplay,
916
autostopDisplay,
@@ -12,28 +19,22 @@ import {
1219
getMaxDeadlineChange,
1320
getMinDeadline,
1421
} from "utils/schedule";
15-
import IconButton from "@mui/material/IconButton";
16-
import RemoveIcon from "@mui/icons-material/RemoveOutlined";
17-
import AddIcon from "@mui/icons-material/AddOutlined";
18-
import Tooltip from "@mui/material/Tooltip";
19-
import _ from "lodash";
2022
import { getErrorMessage } from "api/errors";
2123
import {
2224
updateDeadline,
2325
workspaceByOwnerAndNameKey,
2426
} from "api/queries/workspaces";
2527
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
26-
import { useMutation, useQueryClient } from "react-query";
27-
import { Dayjs } from "dayjs";
28-
import { visuallyHidden } from "@mui/utils";
2928

3029
export interface WorkspaceScheduleControlsProps {
3130
workspace: Workspace;
31+
template: Template;
3232
canUpdateSchedule: boolean;
3333
}
3434

3535
export const WorkspaceScheduleControls: FC<WorkspaceScheduleControlsProps> = ({
3636
workspace,
37+
template,
3738
canUpdateSchedule,
3839
}) => {
3940
const queryClient = useQueryClient();
@@ -90,7 +91,7 @@ export const WorkspaceScheduleControls: FC<WorkspaceScheduleControlsProps> = ({
9091
return (
9192
<div css={styles.scheduleValue} data-testid="schedule-controls">
9293
{isWorkspaceOn(workspace) ? (
93-
<AutoStopDisplay workspace={workspace} />
94+
<AutoStopDisplay workspace={workspace} template={template} />
9495
) : (
9596
<ScheduleSettingsLink>
9697
Starts at {autostartDisplay(workspace.autostart_schedule)}
@@ -133,22 +134,24 @@ export const WorkspaceScheduleControls: FC<WorkspaceScheduleControlsProps> = ({
133134

134135
interface AutoStopDisplayProps {
135136
workspace: Workspace;
137+
template: Template;
136138
}
137139

138-
const AutoStopDisplay: FC<AutoStopDisplayProps> = ({ workspace }) => {
139-
const display = autostopDisplay(workspace);
140+
const AutoStopDisplay: FC<AutoStopDisplayProps> = ({ workspace, template }) => {
141+
const display = autostopDisplay(workspace, template);
140142

141143
if (display.tooltip) {
142144
return (
143145
<Tooltip title={display.tooltip}>
144146
<ScheduleSettingsLink
145-
css={(theme) => ({
146-
color: isShutdownSoon(workspace)
147-
? `${theme.palette.warning.light} !important`
148-
: undefined,
149-
})}
147+
css={
148+
isShutdownSoon(workspace) &&
149+
((theme) => ({
150+
color: `${theme.palette.warning.light} !important`,
151+
}))
152+
}
150153
>
151-
Stop {display.message}
154+
{display.message}
152155
</ScheduleSettingsLink>
153156
</Tooltip>
154157
);

site/src/pages/WorkspacePage/WorkspaceTopbar.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
shouldDisplayScheduleControls,
3434
} from "./WorkspaceScheduleControls";
3535
import { WorkspacePermissions } from "./permissions";
36+
import { LastUsed } from "pages/WorkspacesPage/LastUsed";
3637

3738
export type WorkspaceError =
3839
| "getBuildsError"
@@ -228,11 +229,14 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
228229
</TopbarIcon>
229230
<WorkspaceScheduleControls
230231
workspace={workspace}
232+
template={template}
231233
canUpdateSchedule={canUpdateWorkspace}
232234
/>
233235
</TopbarData>
234236
)}
235237

238+
<LastUsed lastUsedAt={workspace.last_used_at} />
239+
236240
{quota && quota.budget > 0 && (
237241
<TopbarData>
238242
<TopbarIcon>

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,7 @@ export const WorkspaceSchedulePage: FC = () => {
8080
<Helmet>
8181
<title>{pageTitle([workspaceName, "Schedule"])}</title>
8282
</Helmet>
83-
<PageHeader
84-
css={{
85-
paddingTop: 0,
86-
}}
87-
>
83+
<PageHeader css={{ paddingTop: 0 }}>
8884
<PageHeaderTitle>Workspace Schedule</PageHeaderTitle>
8985
</PageHeader>
9086

site/src/utils/schedule.ts renamed to site/src/utils/schedule.tsx

+28-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import cronstrue from "cronstrue";
2-
import dayjs, { Dayjs } from "dayjs";
2+
import cronParser from "cron-parser";
3+
import dayjs, { type Dayjs } from "dayjs";
34
import duration from "dayjs/plugin/duration";
45
import relativeTime from "dayjs/plugin/relativeTime";
56
import timezone from "dayjs/plugin/timezone";
67
import utc from "dayjs/plugin/utc";
7-
import { Template, Workspace } from "api/typesGenerated";
8+
import { type ReactNode } from "react";
9+
import { Link } from "react-router-dom";
10+
import type { Template, Workspace } from "api/typesGenerated";
811
import { isWorkspaceOn } from "./workspace";
9-
import cronParser from "cron-parser";
1012

1113
// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're
1214
// sorted alphabetically.
@@ -90,9 +92,10 @@ export const isShuttingDown = (
9092

9193
export const autostopDisplay = (
9294
workspace: Workspace,
95+
template: Template,
9396
): {
94-
message: string;
95-
tooltip?: string;
97+
message: ReactNode;
98+
tooltip?: ReactNode;
9699
} => {
97100
const ttl = workspace.ttl_ms;
98101

@@ -110,9 +113,27 @@ export const autostopDisplay = (
110113
};
111114
} else {
112115
const deadlineTz = deadline.tz(dayjs.tz.guess());
116+
let reason: ReactNode = ` because the ${template.display_name} template has an autostop requirment`;
117+
if (template.autostop_requirement && template.allow_user_autostop) {
118+
reason = (
119+
<>
120+
{" "}
121+
because this workspace has enabled autostop. You can disable it from
122+
the{" "}
123+
<Link to="settings/schedule">Workspace Schedule settings page</Link>
124+
.
125+
</>
126+
);
127+
}
113128
return {
114-
message: deadlineTz.fromNow(),
115-
tooltip: deadlineTz.format("MMMM D, YYYY h:mm A"),
129+
message: `Stop ${deadlineTz.fromNow()}`,
130+
tooltip: (
131+
<>
132+
This workspace will be stopped on{" "}
133+
{deadlineTz.format("MMMM D, YYYY [at] h:mm A")}
134+
{reason}
135+
</>
136+
),
116137
};
117138
}
118139
} else if (!ttl || ttl < 1) {

0 commit comments

Comments
 (0)