Skip to content

Commit 9ed3487

Browse files
authored
feat: batch workspace updates (#11583)
1 parent 156aaba commit 9ed3487

16 files changed

+893
-84
lines changed

site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const styles = {
6666
}),
6767
dialogContent: (theme) => ({
6868
color: theme.palette.text.secondary,
69-
padding: 40,
69+
padding: "40px 40px 20px",
7070
}),
7171
dialogTitle: (theme) => ({
7272
margin: 0,

site/src/components/Dialogs/Dialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ const styles = {
8080
},
8181

8282
"&:hover:not(:disabled)": {
83-
backgroundColor: theme.experimental.roles.danger.disabled.fill,
84-
borderColor: theme.experimental.roles.danger.disabled.outline,
83+
backgroundColor: theme.experimental.roles.danger.hover.fill,
84+
borderColor: theme.experimental.roles.danger.hover.outline,
8585
},
8686

8787
"&.Mui-disabled": {

site/src/components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ export const WorkspaceOutdatedTooltip: FC<TooltipProps> = (props) => {
4747
);
4848
};
4949

50-
export const WorkspaceOutdatedTooltipContent = (props: TooltipProps) => {
50+
export const WorkspaceOutdatedTooltipContent: FC<TooltipProps> = ({
51+
onUpdateVersion,
52+
ariaLabel,
53+
latestVersionId,
54+
templateName,
55+
}) => {
5156
const popover = usePopover();
52-
const { onUpdateVersion, ariaLabel, latestVersionId, templateName } = props;
5357
const { data: activeVersion } = useQuery({
5458
...templateVersion(latestVersionId),
5559
enabled: popover.isOpen,

site/src/pages/UserSettingsPage/TokensPage/ConfirmDeleteDialog.stories.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ export const DeleteDialog: Story = {
3232
args: {
3333
queryKey: ["tokens"],
3434
token: MockToken,
35-
setToken: () => {
36-
return null;
37-
},
35+
setToken: () => null,
3836
},
3937
};

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useDashboard } from "components/Dashboard/DashboardProvider";
22
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
3-
import { FC, useEffect, useState } from "react";
3+
import { type FC, useEffect, useState } from "react";
44
import { Helmet } from "react-helmet-async";
55
import { useNavigate } from "react-router-dom";
66
import { Workspace } from "./Workspace";
@@ -42,11 +42,11 @@ interface WorkspaceReadyPageProps {
4242
permissions: WorkspacePermissions;
4343
}
4444

45-
export const WorkspaceReadyPage = ({
45+
export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
4646
workspace,
4747
template,
4848
permissions,
49-
}: WorkspaceReadyPageProps): JSX.Element => {
49+
}) => {
5050
const navigate = useNavigate();
5151
const queryClient = useQueryClient();
5252
const { buildInfo } = useDashboard();

site/src/pages/WorkspacesPage/BatchDelete.stories.tsx renamed to site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { action } from "@storybook/addon-actions";
22
import type { Meta, StoryObj } from "@storybook/react";
3+
import { chromatic } from "testHelpers/chromatic";
34
import { MockWorkspace, MockUser2 } from "testHelpers/entities";
4-
import { BatchDeleteConfirmation } from "./BatchActions";
5+
import { BatchDeleteConfirmation } from "./BatchDeleteConfirmation";
56

67
const meta: Meta<typeof BatchDeleteConfirmation> = {
7-
title: "pages/WorkspacesPage/BatchDelete",
8+
title: "pages/WorkspacesPage/BatchDeleteConfirmation",
9+
parameters: { chromatic },
810
component: BatchDeleteConfirmation,
911
args: {
1012
onClose: action("onClose"),
@@ -35,4 +37,4 @@ type Story = StoryObj<typeof BatchDeleteConfirmation>;
3537

3638
const Example: Story = {};
3739

38-
export { Example as BatchDelete };
40+
export { Example as BatchDeleteConfirmation };

site/src/pages/WorkspacesPage/BatchActions.tsx renamed to site/src/pages/WorkspacesPage/BatchDeleteConfirmation.tsx

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,15 @@ import PersonOutlinedIcon from "@mui/icons-material/PersonOutlined";
22
import ScheduleIcon from "@mui/icons-material/Schedule";
33
import { visuallyHidden } from "@mui/utils";
44
import dayjs from "dayjs";
5-
import "dayjs/plugin/relativeTime";
6-
import { type Interpolation, type Theme } from "@emotion/react";
5+
import relativeTime from "dayjs/plugin/relativeTime";
6+
import { useTheme, type Interpolation, type Theme } from "@emotion/react";
77
import { type FC, type ReactNode, useState } from "react";
8-
import { useMutation } from "react-query";
9-
import { deleteWorkspace, startWorkspace, stopWorkspace } from "api/api";
108
import type { Workspace } from "api/typesGenerated";
119
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
12-
import { displayError } from "components/GlobalSnackbar/utils";
13-
import { getResourceIconPath } from "utils/workspace";
1410
import { Stack } from "components/Stack/Stack";
11+
import { getResourceIconPath } from "utils/workspace";
1512

16-
interface UseBatchActionsProps {
17-
onSuccess: () => Promise<void>;
18-
}
19-
20-
export function useBatchActions(options: UseBatchActionsProps) {
21-
const { onSuccess } = options;
22-
23-
const startAllMutation = useMutation({
24-
mutationFn: async (workspaces: Workspace[]) => {
25-
return Promise.all(
26-
workspaces.map((w) =>
27-
startWorkspace(w.id, w.latest_build.template_version_id),
28-
),
29-
);
30-
},
31-
onSuccess,
32-
onError: () => {
33-
displayError("Failed to start workspaces");
34-
},
35-
});
36-
37-
const stopAllMutation = useMutation({
38-
mutationFn: async (workspaces: Workspace[]) => {
39-
return Promise.all(workspaces.map((w) => stopWorkspace(w.id)));
40-
},
41-
onSuccess,
42-
onError: () => {
43-
displayError("Failed to stop workspaces");
44-
},
45-
});
46-
47-
const deleteAllMutation = useMutation({
48-
mutationFn: async (workspaces: Workspace[]) => {
49-
return Promise.all(workspaces.map((w) => deleteWorkspace(w.id)));
50-
},
51-
onSuccess,
52-
onError: () => {
53-
displayError("Failed to delete workspaces");
54-
},
55-
});
56-
57-
return {
58-
startAll: startAllMutation.mutateAsync,
59-
stopAll: stopAllMutation.mutateAsync,
60-
deleteAll: deleteAllMutation.mutateAsync,
61-
isLoading:
62-
startAllMutation.isLoading ||
63-
stopAllMutation.isLoading ||
64-
deleteAllMutation.isLoading,
65-
};
66-
}
13+
dayjs.extend(relativeTime);
6714

6815
type BatchDeleteConfirmationProps = {
6916
checkedWorkspaces: Workspace[];
@@ -182,6 +129,8 @@ const Consequences: FC = () => {
182129
};
183130

184131
const Workspaces: FC<StageProps> = ({ workspaces }) => {
132+
const theme = useTheme();
133+
185134
const mostRecent = workspaces.reduce(
186135
(latestSoFar, against) => {
187136
if (!latestSoFar) {
@@ -209,7 +158,9 @@ const Workspaces: FC<StageProps> = ({ workspaces }) => {
209158
alignItems="center"
210159
justifyContent="space-between"
211160
>
212-
<span css={{ fontWeight: 500, color: "#fff" }}>
161+
<span
162+
css={{ fontWeight: 500, color: theme.experimental.l1.text }}
163+
>
213164
{workspace.name}
214165
</span>
215166
<Stack css={{ gap: 0, fontSize: 14, width: 128 }}>
@@ -234,7 +185,12 @@ const Workspaces: FC<StageProps> = ({ workspaces }) => {
234185
</li>
235186
))}
236187
</ul>
237-
<Stack justifyContent="center" direction="row" css={{ fontSize: 14 }}>
188+
<Stack
189+
justifyContent="center"
190+
direction="row"
191+
wrap="wrap"
192+
css={{ gap: "6px 20px", fontSize: 14 }}
193+
>
238194
<Stack direction="row" alignItems="center" spacing={1}>
239195
<PersonIcon />
240196
<span>{ownersCount}</span>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { action } from "@storybook/addon-actions";
2+
import type { Meta, StoryObj } from "@storybook/react";
3+
import { useQueryClient } from "react-query";
4+
import { chromatic } from "testHelpers/chromatic";
5+
import {
6+
MockWorkspace,
7+
MockRunningOutdatedWorkspace,
8+
MockDormantOutdatedWorkspace,
9+
MockOutdatedWorkspace,
10+
MockTemplateVersion,
11+
MockUser2,
12+
} from "testHelpers/entities";
13+
import {
14+
BatchUpdateConfirmation,
15+
type Update,
16+
} from "./BatchUpdateConfirmation";
17+
18+
const workspaces = [
19+
{ ...MockRunningOutdatedWorkspace, id: "1" },
20+
{ ...MockDormantOutdatedWorkspace, id: "2" },
21+
{ ...MockOutdatedWorkspace, id: "3" },
22+
{ ...MockOutdatedWorkspace, id: "4" },
23+
{ ...MockWorkspace, id: "5" },
24+
{
25+
...MockRunningOutdatedWorkspace,
26+
id: "6",
27+
owner_id: MockUser2.id,
28+
owner_name: MockUser2.username,
29+
},
30+
];
31+
32+
const updates = new Map<string, Update>();
33+
for (const it of workspaces) {
34+
const versionId = it.template_active_version_id;
35+
const version = updates.get(versionId);
36+
37+
if (version) {
38+
version.affected_workspaces.push(it);
39+
continue;
40+
}
41+
42+
updates.set(versionId, {
43+
...MockTemplateVersion,
44+
template_display_name: it.template_display_name,
45+
affected_workspaces: [it],
46+
});
47+
}
48+
49+
const meta: Meta<typeof BatchUpdateConfirmation> = {
50+
title: "pages/WorkspacesPage/BatchUpdateConfirmation",
51+
parameters: { chromatic },
52+
component: BatchUpdateConfirmation,
53+
decorators: [
54+
(Story) => {
55+
const queryClient = useQueryClient();
56+
for (const [id, it] of updates) {
57+
queryClient.setQueryData(["batchUpdate", id], it);
58+
}
59+
return <Story />;
60+
},
61+
],
62+
args: {
63+
onClose: action("onClose"),
64+
onConfirm: action("onConfirm"),
65+
open: true,
66+
checkedWorkspaces: workspaces,
67+
},
68+
};
69+
70+
export default meta;
71+
type Story = StoryObj<typeof BatchUpdateConfirmation>;
72+
73+
const Example: Story = {};
74+
75+
export { Example as BatchUpdateConfirmation };

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy