Skip to content

Commit 9e053ce

Browse files
authored
feat: show update messages on workspace page (#9705)
1 parent efe8044 commit 9e053ce

File tree

9 files changed

+95
-196
lines changed

9 files changed

+95
-196
lines changed

site/src/api/queries/templates.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,17 @@ export const templateExamples = (orgId: string) => {
4949
queryFn: () => API.getTemplateExamples(orgId),
5050
};
5151
};
52+
53+
export const templateVersion = (versionId: string) => {
54+
return {
55+
queryKey: ["templateVersion", versionId],
56+
queryFn: () => API.getTemplateVersion(versionId),
57+
};
58+
};
59+
60+
export const templateVersions = (templateId: string) => {
61+
return {
62+
queryKey: ["templateVersions", templateId],
63+
queryFn: () => API.getTemplateVersions(templateId),
64+
};
65+
};

site/src/components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import InfoIcon from "@mui/icons-material/InfoOutlined";
1111
import { makeStyles } from "@mui/styles";
1212
import { colors } from "theme/colors";
1313
import { useQuery } from "@tanstack/react-query";
14-
import { getTemplate, getTemplateVersion } from "api/api";
14+
import { templateVersion } from "api/queries/templates";
1515
import Box from "@mui/material/Box";
1616
import Skeleton from "@mui/material/Skeleton";
1717
import Link from "@mui/material/Link";
@@ -25,28 +25,19 @@ export const Language = {
2525

2626
interface TooltipProps {
2727
onUpdateVersion: () => void;
28-
templateId: string;
28+
latestVersionId: string;
2929
templateName: string;
3030
ariaLabel?: string;
3131
}
3232

3333
export const WorkspaceOutdatedTooltip: FC<TooltipProps> = ({
3434
onUpdateVersion,
3535
ariaLabel,
36-
templateId,
36+
latestVersionId,
3737
templateName,
3838
}) => {
3939
const styles = useStyles();
40-
const { data: activeVersion } = useQuery({
41-
queryFn: async () => {
42-
const template = await getTemplate(templateId);
43-
const activeVersion = await getTemplateVersion(
44-
template.active_version_id,
45-
);
46-
return activeVersion;
47-
},
48-
queryKey: ["templates", templateId, "activeVersion"],
49-
});
40+
const { data: activeVersion } = useQuery(templateVersion(latestVersionId));
5041

5142
return (
5243
<HelpTooltip

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface WorkspaceProps {
6060
builds?: TypesGen.WorkspaceBuild[];
6161
templateWarnings?: TypesGen.TemplateVersionWarning[];
6262
canUpdateWorkspace: boolean;
63+
updateMessage?: string;
6364
canRetryDebugMode: boolean;
6465
canChangeVersions: boolean;
6566
hideSSHButton?: boolean;
@@ -93,6 +94,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
9394
resources,
9495
builds,
9596
canUpdateWorkspace,
97+
updateMessage,
9698
canRetryDebugMode,
9799
canChangeVersions,
98100
workspaceErrors,
@@ -219,6 +221,12 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
219221
className={styles.firstColumnSpacer}
220222
spacing={4}
221223
>
224+
{workspace.outdated && (
225+
<Alert severity="info">
226+
<AlertTitle>An update is available for your workspace</AlertTitle>
227+
{updateMessage && <AlertDetail>{updateMessage}</AlertDetail>}
228+
</Alert>
229+
)}
222230
{buildError}
223231
{cancellationError}
224232
{workspace.latest_build.status === "running" &&

site/src/pages/WorkspacePage/WorkspaceActions/Buttons.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export const UpdateButton: FC<WorkspaceAction> = ({
2424
return (
2525
<LoadingButton
2626
loading={loading}
27-
loadingIndicator="Updating..."
27+
loadingIndicator={<>Updating&hellip;</>}
2828
loadingPosition="start"
2929
data-testid="workspace-update-button"
3030
startIcon={<CloudQueueIcon />}
3131
onClick={handleAction}
3232
>
33-
Update
33+
Update&hellip;
3434
</LoadingButton>
3535
);
3636
};
@@ -42,7 +42,7 @@ export const ActivateButton: FC<WorkspaceAction> = ({
4242
return (
4343
<LoadingButton
4444
loading={loading}
45-
loadingIndicator="Activating..."
45+
loadingIndicator={<>Activating&hellip;</>}
4646
loadingPosition="start"
4747
startIcon={<PowerSettingsNewIcon />}
4848
onClick={handleAction}
@@ -70,7 +70,7 @@ export const StartButton: FC<
7070
>
7171
<LoadingButton
7272
loading={loading}
73-
loadingIndicator="Starting..."
73+
loadingIndicator={<>Starting&hellip;</>}
7474
loadingPosition="start"
7575
startIcon={<PlayCircleOutlineIcon />}
7676
onClick={() => handleAction()}
@@ -90,7 +90,7 @@ export const StopButton: FC<WorkspaceAction> = ({ handleAction, loading }) => {
9090
return (
9191
<LoadingButton
9292
loading={loading}
93-
loadingIndicator="Stopping..."
93+
loadingIndicator={<>Stopping&hellip;</>}
9494
loadingPosition="start"
9595
startIcon={<CropSquareIcon />}
9696
onClick={handleAction}
@@ -119,13 +119,13 @@ export const RestartButton: FC<
119119
>
120120
<LoadingButton
121121
loading={loading}
122-
loadingIndicator="Restarting..."
122+
loadingIndicator={<>Restarting&hellip;</>}
123123
loadingPosition="start"
124124
startIcon={<ReplayIcon />}
125125
onClick={() => handleAction()}
126126
data-testid="workspace-restart-button"
127127
>
128-
Restart
128+
Restart&hellip;
129129
</LoadingButton>
130130
<BuildParametersPopover
131131
workspace={workspace}

site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,15 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
148148
{canChangeVersions && (
149149
<MenuItem onClick={onMenuItemClick(handleChangeVersion)}>
150150
<HistoryOutlined />
151-
Change version
151+
Change version&hellip;
152152
</MenuItem>
153153
)}
154-
<MenuItem onClick={onMenuItemClick(handleDelete)}>
154+
<MenuItem
155+
onClick={onMenuItemClick(handleDelete)}
156+
data-testid="delete-button"
157+
>
155158
<DeleteOutlined />
156-
Delete
159+
Delete&hellip;
157160
</MenuItem>
158161
</Menu>
159162
</div>

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

Lines changed: 5 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import {
2424
MockEntitlementsWithScheduling,
2525
MockDeploymentConfig,
2626
} from "testHelpers/entities";
27-
import * as api from "../../api/api";
28-
import { Workspace } from "../../api/typesGenerated";
27+
import * as api from "api/api";
28+
import { Workspace } from "api/typesGenerated";
2929
import {
3030
renderWithAuth,
3131
waitForLoaderToBeRemoved,
32-
} from "../../testHelpers/renderHelpers";
33-
import { server } from "../../testHelpers/server";
32+
} from "testHelpers/renderHelpers";
33+
import { server } from "testHelpers/server";
3434
import { WorkspacePage } from "./WorkspacePage";
3535

3636
// It renders the workspace page and waits for it be loaded
@@ -113,7 +113,7 @@ describe("WorkspacePage", () => {
113113
await user.click(trigger);
114114

115115
// Click on delete
116-
const button = await screen.findByText("Delete");
116+
const button = await screen.findByTestId("delete-button");
117117
await user.click(button);
118118

119119
// Get dialog and confirm
@@ -172,28 +172,6 @@ describe("WorkspacePage", () => {
172172
});
173173
});
174174

175-
it("requests a stop without confirmation when the user presses Restart", async () => {
176-
const stopWorkspaceMock = jest
177-
.spyOn(api, "stopWorkspace")
178-
.mockResolvedValueOnce(MockWorkspaceBuild);
179-
window.localStorage.setItem(
180-
`${MockUser.id}_ignoredWarnings`,
181-
JSON.stringify({ restart: new Date().toISOString() }),
182-
);
183-
184-
// Render
185-
await renderWorkspacePage();
186-
187-
// Actions
188-
const user = userEvent.setup();
189-
await user.click(screen.getByTestId("workspace-restart-button"));
190-
191-
// Assertions
192-
await waitFor(() => {
193-
expect(stopWorkspaceMock).toBeCalled();
194-
});
195-
});
196-
197175
it("requests cancellation when the user presses Cancel", async () => {
198176
server.use(
199177
rest.get(
@@ -409,44 +387,4 @@ describe("WorkspacePage", () => {
409387
});
410388
});
411389
});
412-
413-
it("restart the workspace with one time parameters without the confirmation dialog", async () => {
414-
window.localStorage.setItem(
415-
`${MockUser.id}_ignoredWarnings`,
416-
JSON.stringify({
417-
restart: new Date().toISOString(),
418-
}),
419-
);
420-
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
421-
templateVersionRichParameters: [
422-
{
423-
...MockTemplateVersionParameter1,
424-
ephemeral: true,
425-
name: "rebuild",
426-
description: "Rebuild",
427-
required: false,
428-
},
429-
],
430-
buildParameters: [{ name: "rebuild", value: "false" }],
431-
});
432-
const restartWorkspaceSpy = jest.spyOn(api, "restartWorkspace");
433-
const user = userEvent.setup();
434-
await renderWorkspacePage();
435-
await user.click(screen.getByTestId("build-parameters-button"));
436-
const buildParametersForm = await screen.findByTestId(
437-
"build-parameters-form",
438-
);
439-
const rebuildField = within(buildParametersForm).getByLabelText("Rebuild", {
440-
exact: false,
441-
});
442-
await user.clear(rebuildField);
443-
await user.type(rebuildField, "true");
444-
await user.click(screen.getByTestId("build-parameters-submit"));
445-
await waitFor(() => {
446-
expect(restartWorkspaceSpy).toBeCalledWith({
447-
workspace: MockWorkspace,
448-
buildParameters: [{ name: "rebuild", value: "true" }],
449-
});
450-
});
451-
});
452390
});

0 commit comments

Comments
 (0)