Skip to content

Commit e26ba1a

Browse files
feat(site): do not show popover on update deadline (#11921)
1 parent dcab6fa commit e26ba1a

File tree

3 files changed

+243
-247
lines changed

3 files changed

+243
-247
lines changed

site/src/api/queries/workspaces.ts

+11-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as API from "api/api";
2-
import { QueryClient, type QueryOptions } from "react-query";
2+
import {
3+
QueryClient,
4+
UseMutationOptions,
5+
type QueryOptions,
6+
} from "react-query";
37
import { putWorkspaceExtension } from "api/api";
4-
import dayjs from "dayjs";
5-
import { getDeadline, getMaxDeadline, getMinDeadline } from "utils/schedule";
8+
import { Dayjs } from "dayjs";
69
import {
710
type WorkspaceBuildParameter,
811
type Workspace,
@@ -103,25 +106,12 @@ export function workspaces(config: WorkspacesRequest = {}) {
103106
} as const satisfies QueryOptions<WorkspacesResponse>;
104107
}
105108

106-
export const decreaseDeadline = (workspace: Workspace) => {
107-
return {
108-
mutationFn: (hours: number) => {
109-
const proposedDeadline = getDeadline(workspace).subtract(hours, "hours");
110-
const newDeadline = dayjs.max(proposedDeadline, getMinDeadline());
111-
return putWorkspaceExtension(workspace.id, newDeadline);
112-
},
113-
};
114-
};
115-
116-
export const increaseDeadline = (workspace: Workspace) => {
109+
export const updateDeadline = (
110+
workspace: Workspace,
111+
): UseMutationOptions<void, unknown, Dayjs> => {
117112
return {
118-
mutationFn: (hours: number) => {
119-
const proposedDeadline = getDeadline(workspace).add(hours, "hours");
120-
const newDeadline = dayjs.min(
121-
proposedDeadline,
122-
getMaxDeadline(workspace),
123-
);
124-
return putWorkspaceExtension(workspace.id, newDeadline);
113+
mutationFn: (deadline: Dayjs) => {
114+
return putWorkspaceExtension(workspace.id, deadline);
125115
},
126116
};
127117
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
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";
7+
import { RouterProvider, createMemoryRouter } from "react-router-dom";
8+
import userEvent from "@testing-library/user-event";
9+
import { server } from "testHelpers/server";
10+
import { rest } from "msw";
11+
import dayjs from "dayjs";
12+
import * as API from "api/api";
13+
import { GlobalSnackbar } from "components/GlobalSnackbar/GlobalSnackbar";
14+
15+
const Wrapper = () => {
16+
const { data: workspace } = useQuery(
17+
workspaceByOwnerAndName(MockWorkspace.owner_name, MockWorkspace.name),
18+
);
19+
20+
if (!workspace) {
21+
return null;
22+
}
23+
24+
return <WorkspaceScheduleControls workspace={workspace} canUpdateSchedule />;
25+
};
26+
27+
const BASE_DEADLINE = dayjs().add(3, "hour");
28+
29+
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+
);
47+
render(
48+
<ThemeProvider>
49+
<QueryClientProvider client={new QueryClient()}>
50+
<RouterProvider
51+
router={createMemoryRouter([{ path: "/", element: <Wrapper /> }])}
52+
/>
53+
</QueryClientProvider>
54+
<GlobalSnackbar />
55+
</ThemeProvider>,
56+
);
57+
await screen.findByTestId("schedule-controls");
58+
expect(screen.getByText("Stop in 3 hours")).toBeInTheDocument();
59+
};
60+
61+
test("add 3 hours to deadline", async () => {
62+
const user = userEvent.setup();
63+
const updateDeadlineSpy = jest
64+
.spyOn(API, "putWorkspaceExtension")
65+
.mockResolvedValue();
66+
67+
await renderScheduleControls();
68+
69+
const addButton = screen.getByRole("button", {
70+
name: /add 1 hour to deadline/i,
71+
});
72+
await user.click(addButton);
73+
await user.click(addButton);
74+
await user.click(addButton);
75+
await screen.findByText(
76+
"Workspace shutdown time has been successfully updated.",
77+
);
78+
expect(screen.getByText("Stop in 6 hours")).toBeInTheDocument();
79+
80+
// Mocks are used here because the 'usedDeadline' is a dayjs object, which
81+
// can't be directly compared.
82+
const usedWorkspaceId = updateDeadlineSpy.mock.calls[0][0];
83+
const usedDeadline = updateDeadlineSpy.mock.calls[0][1];
84+
expect(usedWorkspaceId).toEqual(MockWorkspace.id);
85+
expect(usedDeadline.toISOString()).toEqual(
86+
BASE_DEADLINE.add(3, "hour").toISOString(),
87+
);
88+
});
89+
90+
test("remove 3 hours to deadline", async () => {
91+
const user = userEvent.setup();
92+
const updateDeadlineSpy = jest
93+
.spyOn(API, "putWorkspaceExtension")
94+
.mockResolvedValue();
95+
96+
await renderScheduleControls();
97+
98+
const subButton = screen.getByRole("button", {
99+
name: /subtract 1 hour from deadline/i,
100+
});
101+
await user.click(subButton);
102+
await user.click(subButton);
103+
await screen.findByText(
104+
"Workspace shutdown time has been successfully updated.",
105+
);
106+
expect(screen.getByText("Stop in an hour")).toBeInTheDocument();
107+
108+
// Mocks are used here because the 'usedDeadline' is a dayjs object, which
109+
// can't be directly compared.
110+
const usedWorkspaceId = updateDeadlineSpy.mock.calls[0][0];
111+
const usedDeadline = updateDeadlineSpy.mock.calls[0][1];
112+
expect(usedWorkspaceId).toEqual(MockWorkspace.id);
113+
expect(usedDeadline.toISOString()).toEqual(
114+
BASE_DEADLINE.subtract(2, "hour").toISOString(),
115+
);
116+
});
117+
118+
test("rollback to previous deadline on error", async () => {
119+
const user = userEvent.setup();
120+
const initialScheduleMessage = "Stop in 3 hours";
121+
jest.spyOn(API, "putWorkspaceExtension").mockRejectedValue({});
122+
123+
await renderScheduleControls();
124+
125+
const addButton = screen.getByRole("button", {
126+
name: /add 1 hour to deadline/i,
127+
});
128+
await user.click(addButton);
129+
await user.click(addButton);
130+
await user.click(addButton);
131+
await screen.findByText(
132+
"We couldn't update your workspace shutdown time. Please try again.",
133+
);
134+
// In case of an error, the schedule message should remain unchanged
135+
expect(screen.getByText(initialScheduleMessage)).toBeInTheDocument();
136+
});
137+
138+
test("request is only sent once when clicking multiple times", async () => {
139+
const user = userEvent.setup();
140+
const updateDeadlineSpy = jest
141+
.spyOn(API, "putWorkspaceExtension")
142+
.mockResolvedValue();
143+
144+
await renderScheduleControls();
145+
146+
const addButton = screen.getByRole("button", {
147+
name: /add 1 hour to deadline/i,
148+
});
149+
await user.click(addButton);
150+
await user.click(addButton);
151+
await user.click(addButton);
152+
await screen.findByText(
153+
"Workspace shutdown time has been successfully updated.",
154+
);
155+
expect(updateDeadlineSpy).toHaveBeenCalledTimes(1);
156+
});

0 commit comments

Comments
 (0)