Skip to content

Commit 035e1e2

Browse files
authored
Merge pull request #70 from CryptoLabInc/codeheaan-dev
[FIX] Show running workspace limit alert on workspace update
2 parents 23a4d29 + 83ffe89 commit 035e1e2

5 files changed

Lines changed: 73 additions & 10 deletions

File tree

coderd/workspacebuilds.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,12 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
411411
}, nil)
412412
if errors.Is(err, errRunningWorkspaceLimitExceeded) {
413413
maxRunning := api.DeploymentValues.MaxRunningWorkspacesPerUser.Value()
414+
message := fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to start another.", maxRunning)
415+
if createBuild.TemplateVersionID != uuid.Nil {
416+
message = fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to update this workspace.", maxRunning)
417+
}
414418
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
415-
Message: fmt.Sprintf("Running workspace limit reached (max %d per user). Stop one or more workspaces to start another.", maxRunning),
419+
Message: message,
416420
})
417421
return
418422
}

site/src/api/api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,19 @@ class ApiMethods {
20782078
throw new MissingBuildParameters(missingParameters, activeVersionId);
20792079
}
20802080

2081+
// If the workspace is currently running, stop it first so that the
2082+
// running workspace limit check on the server passes when we update
2083+
// with the new version.
2084+
if (workspace.latest_build.status === "running") {
2085+
const stopBuild = await this.stopWorkspace(workspace.id);
2086+
const awaitedStopBuild = await this.waitForBuild(stopBuild);
2087+
if (awaitedStopBuild?.status === "canceled") {
2088+
throw new Error(
2089+
"Workspace stop was canceled before the update could be applied.",
2090+
);
2091+
}
2092+
}
2093+
20812094
return this.postWorkspaceBuild(workspace.id, {
20822095
transition: "start",
20832096
template_version_id: activeVersionId,

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface WorkspaceProps {
5555
isOwner: boolean;
5656
timings?: TypesGen.WorkspaceBuildTimings;
5757
startWorkspaceError?: unknown;
58+
updateWorkspaceError?: unknown;
5859
}
5960

6061
/**
@@ -89,6 +90,7 @@ export const Workspace: FC<WorkspaceProps> = ({
8990
isOwner,
9091
timings,
9192
startWorkspaceError,
93+
updateWorkspaceError,
9294
}) => {
9395
const navigate = useNavigate();
9496
const theme = useTheme();
@@ -125,14 +127,10 @@ export const Workspace: FC<WorkspaceProps> = ({
125127
const { shouldShow: shouldShowWorkspaceReadyDelayAlert } =
126128
useWorkspaceReadyDelayAlert(timings, workspaceRunning);
127129

128-
const isRunningWorkspaceLimitError = Boolean(
129-
startWorkspaceError &&
130-
isApiError(startWorkspaceError) &&
131-
startWorkspaceError.response?.status === 409 &&
132-
startWorkspaceError.response?.data?.message?.includes(
133-
"Running workspace limit",
134-
),
135-
);
130+
const isStartRunningWorkspaceLimitError =
131+
isRunningWorkspaceLimitError(startWorkspaceError);
132+
const isUpdateRunningWorkspaceLimitError =
133+
isRunningWorkspaceLimitError(updateWorkspaceError);
136134

137135
return (
138136
<div
@@ -255,7 +253,7 @@ export const Workspace: FC<WorkspaceProps> = ({
255253
</Alert>
256254
)}
257255

258-
{isRunningWorkspaceLimitError && (
256+
{isStartRunningWorkspaceLimitError && (
259257
<Alert severity="warning">
260258
<AlertTitle>Running workspace limit reached</AlertTitle>
261259
<AlertDetail>
@@ -267,6 +265,18 @@ export const Workspace: FC<WorkspaceProps> = ({
267265
</Alert>
268266
)}
269267

268+
{isUpdateRunningWorkspaceLimitError && (
269+
<Alert severity="warning">
270+
<AlertTitle>Running workspace limit reached</AlertTitle>
271+
<AlertDetail>
272+
{getErrorMessage(
273+
updateWorkspaceError,
274+
"Running workspace limit reached (max 1 per user). Stop one or more workspaces to update this workspace.",
275+
)}
276+
</AlertDetail>
277+
</Alert>
278+
)}
279+
270280
{workspace.latest_build.job.error && (
271281
<Alert severity="error">
272282
<AlertTitle>Workspace build failed</AlertTitle>
@@ -342,6 +352,14 @@ const countAgents = (resource: TypesGen.WorkspaceResource) => {
342352
return resource.agents ? resource.agents.length : 0;
343353
};
344354

355+
const isRunningWorkspaceLimitError = (error: unknown): boolean =>
356+
Boolean(
357+
error &&
358+
isApiError(error) &&
359+
error.response?.status === 409 &&
360+
error.response?.data?.message?.includes("Running workspace limit"),
361+
);
362+
345363
const styles = {
346364
content: {
347365
padding: 32,

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,33 @@ describe("WorkspacePage", () => {
331331
});
332332
});
333333

334+
it("shows a running workspace limit warning when update fails with 409", async () => {
335+
jest
336+
.spyOn(API, "getWorkspaceByOwnerAndName")
337+
.mockResolvedValueOnce(MockOutdatedWorkspace);
338+
jest.spyOn(API, "updateWorkspace").mockRejectedValueOnce({
339+
isAxiosError: true,
340+
response: {
341+
status: 409,
342+
data: {
343+
message:
344+
"Running workspace limit reached (max 1 per user). Stop one or more workspaces to update this workspace.",
345+
},
346+
},
347+
});
348+
349+
await renderWorkspacePage(MockWorkspace);
350+
351+
const user = userEvent.setup();
352+
await user.click(screen.getByTestId("workspace-update-button"));
353+
const confirmButton = await screen.findByTestId("confirm-button");
354+
await user.click(confirmButton);
355+
356+
await screen.findByText(
357+
/Stop one or more workspaces to update this workspace/i,
358+
);
359+
});
360+
334361
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
335362
localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
336363
jest.spyOn(API, "getWorkspaceParameters").mockResolvedValue({

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
281281
isOwner={isOwner}
282282
timings={timingsQuery.data}
283283
startWorkspaceError={startWorkspaceMutation.error}
284+
updateWorkspaceError={updateWorkspaceMutation.error}
284285
/>
285286

286287
<WorkspaceDeleteDialog

0 commit comments

Comments
 (0)