Skip to content

Commit 4341403

Browse files
chore: simplify workspaces data fetching (coder#17703)
We've been using an abstraction that was not necessary to fetch workspaces data. I also took sometime to use the new useWorkspaceUpdate hook in the update workspace tooltip that was missing some important steps like confirmation.
1 parent 2695f4e commit 4341403

File tree

9 files changed

+103
-229
lines changed

9 files changed

+103
-229
lines changed

site/src/components/Badge/Badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const badgeVariants = cva(
1919
warning:
2020
"border border-solid border-border-warning bg-surface-orange text-content-warning shadow",
2121
destructive:
22-
"border border-solid border-border-destructive bg-surface-red text-content-highlight-red shadow",
22+
"border border-solid border-border-destructive bg-surface-red text-highlight-red shadow",
2323
},
2424
size: {
2525
xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5",

site/src/hooks/usePagination.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const usePagination = ({
99
const [searchParams, setSearchParams] = searchParamsResult;
1010
const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1;
1111
const limit = DEFAULT_RECORDS_PER_PAGE;
12-
const offset = calcOffset(page, limit);
12+
const offset = page <= 0 ? 0 : (page - 1) * limit;
1313

1414
const goToPage = (page: number) => {
1515
searchParams.set("page", page.toString());
@@ -23,7 +23,3 @@ export const usePagination = ({
2323
offset,
2424
};
2525
};
26-
27-
export const calcOffset = (page: number, limit: number) => {
28-
return page <= 0 ? 0 : (page - 1) * limit;
29-
};

site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { action } from "@storybook/addon-actions";
21
import type { Meta, StoryObj } from "@storybook/react";
32
import { expect, userEvent, waitFor, within } from "@storybook/test";
4-
import { MockTemplate, MockTemplateVersion } from "testHelpers/entities";
3+
import {
4+
MockTemplate,
5+
MockTemplateVersion,
6+
MockWorkspace,
7+
} from "testHelpers/entities";
58
import { withDashboardProvider } from "testHelpers/storybook";
69
import { WorkspaceOutdatedTooltip } from "./WorkspaceOutdatedTooltip";
710

@@ -18,9 +21,11 @@ const meta: Meta<typeof WorkspaceOutdatedTooltip> = {
1821
],
1922
},
2023
args: {
21-
onUpdateVersion: action("onUpdateVersion"),
22-
templateName: MockTemplate.display_name,
23-
latestVersionId: MockTemplateVersion.id,
24+
workspace: {
25+
...MockWorkspace,
26+
template_name: MockTemplate.display_name,
27+
template_active_version_id: MockTemplateVersion.id,
28+
},
2429
},
2530
};
2631

@@ -29,14 +34,12 @@ type Story = StoryObj<typeof WorkspaceOutdatedTooltip>;
2934

3035
const Example: Story = {
3136
play: async ({ canvasElement, step }) => {
32-
const screen = within(canvasElement);
37+
const body = within(canvasElement.ownerDocument.body);
3338

3439
await step("activate hover trigger", async () => {
35-
await userEvent.hover(screen.getByRole("button"));
40+
await userEvent.hover(body.getByRole("button"));
3641
await waitFor(() =>
37-
expect(
38-
screen.getByText(MockTemplateVersion.message),
39-
).toBeInTheDocument(),
42+
expect(body.getByText(MockTemplateVersion.message)).toBeInTheDocument(),
4043
);
4144
});
4245
},

site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import InfoIcon from "@mui/icons-material/InfoOutlined";
33
import RefreshIcon from "@mui/icons-material/Refresh";
44
import Link from "@mui/material/Link";
55
import Skeleton from "@mui/material/Skeleton";
6+
import { getErrorDetail, getErrorMessage } from "api/errors";
67
import { templateVersion } from "api/queries/templates";
8+
import type { Workspace } from "api/typesGenerated";
9+
import { displayError } from "components/GlobalSnackbar/utils";
710
import {
811
HelpTooltip,
912
HelpTooltipAction,
@@ -17,102 +20,99 @@ import { usePopover } from "components/deprecated/Popover/Popover";
1720
import { linkToTemplate, useLinks } from "modules/navigation";
1821
import type { FC } from "react";
1922
import { useQuery } from "react-query";
20-
21-
const Language = {
22-
outdatedLabel: "Outdated",
23-
versionTooltipText:
24-
"This workspace version is outdated and a newer version is available.",
25-
updateVersionLabel: "Update",
26-
};
23+
import {
24+
WorkspaceUpdateDialogs,
25+
useWorkspaceUpdate,
26+
} from "../WorkspaceUpdateDialogs";
2727

2828
interface TooltipProps {
29-
organizationName: string;
30-
templateName: string;
31-
latestVersionId: string;
32-
onUpdateVersion: () => void;
33-
ariaLabel?: string;
29+
workspace: Workspace;
3430
}
3531

3632
export const WorkspaceOutdatedTooltip: FC<TooltipProps> = (props) => {
3733
return (
3834
<HelpTooltip>
39-
<HelpTooltipTrigger
40-
size="small"
41-
aria-label="More info"
42-
hoverEffect={false}
43-
>
35+
<HelpTooltipTrigger size="small" hoverEffect={false}>
4436
<InfoIcon css={styles.icon} />
37+
<span className="sr-only">Outdated info</span>
4538
</HelpTooltipTrigger>
46-
4739
<WorkspaceOutdatedTooltipContent {...props} />
4840
</HelpTooltip>
4941
);
5042
};
5143

52-
const WorkspaceOutdatedTooltipContent: FC<TooltipProps> = ({
53-
organizationName,
54-
templateName,
55-
latestVersionId,
56-
onUpdateVersion,
57-
ariaLabel,
58-
}) => {
44+
const WorkspaceOutdatedTooltipContent: FC<TooltipProps> = ({ workspace }) => {
5945
const getLink = useLinks();
6046
const theme = useTheme();
6147
const popover = usePopover();
6248
const { data: activeVersion } = useQuery({
63-
...templateVersion(latestVersionId),
49+
...templateVersion(workspace.template_active_version_id),
6450
enabled: popover.open,
6551
});
52+
const updateWorkspace = useWorkspaceUpdate({
53+
workspace,
54+
latestVersion: activeVersion,
55+
onError: (error) => {
56+
displayError(
57+
getErrorMessage(error, "Error updating workspace"),
58+
getErrorDetail(error),
59+
);
60+
},
61+
});
6662

6763
const versionLink = `${getLink(
68-
linkToTemplate(organizationName, templateName),
64+
linkToTemplate(workspace.organization_name, workspace.template_name),
6965
)}`;
7066

7167
return (
72-
<HelpTooltipContent>
73-
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>
74-
<HelpTooltipText>{Language.versionTooltipText}</HelpTooltipText>
68+
<>
69+
<HelpTooltipContent disablePortal={false}>
70+
<HelpTooltipTitle>Outdated</HelpTooltipTitle>
71+
<HelpTooltipText>
72+
This workspace version is outdated and a newer version is available.
73+
</HelpTooltipText>
7574

76-
<div css={styles.container}>
77-
<div css={{ lineHeight: "1.6" }}>
78-
<div css={styles.bold}>New version</div>
79-
<div>
80-
{activeVersion ? (
81-
<Link
82-
href={`${versionLink}/versions/${activeVersion.name}`}
83-
target="_blank"
84-
css={{ color: theme.palette.primary.light }}
85-
>
86-
{activeVersion.name}
87-
</Link>
88-
) : (
89-
<Skeleton variant="text" height={20} width={100} />
90-
)}
75+
<div css={styles.container}>
76+
<div css={{ lineHeight: "1.6" }}>
77+
<div css={styles.bold}>New version</div>
78+
<div>
79+
{activeVersion ? (
80+
<Link
81+
href={`${versionLink}/versions/${activeVersion.name}`}
82+
target="_blank"
83+
css={{ color: theme.palette.primary.light }}
84+
>
85+
{activeVersion.name}
86+
</Link>
87+
) : (
88+
<Skeleton variant="text" height={20} width={100} />
89+
)}
90+
</div>
9191
</div>
92-
</div>
9392

94-
<div css={{ lineHeight: "1.6" }}>
95-
<div css={styles.bold}>Message</div>
96-
<div>
97-
{activeVersion ? (
98-
activeVersion.message || "No message"
99-
) : (
100-
<Skeleton variant="text" height={20} width={150} />
101-
)}
93+
<div css={{ lineHeight: "1.6" }}>
94+
<div css={styles.bold}>Message</div>
95+
<div>
96+
{activeVersion ? (
97+
activeVersion.message || "No message"
98+
) : (
99+
<Skeleton variant="text" height={20} width={150} />
100+
)}
101+
</div>
102102
</div>
103103
</div>
104-
</div>
105104

106-
<HelpTooltipLinksGroup>
107-
<HelpTooltipAction
108-
icon={RefreshIcon}
109-
onClick={onUpdateVersion}
110-
ariaLabel={ariaLabel}
111-
>
112-
{Language.updateVersionLabel}
113-
</HelpTooltipAction>
114-
</HelpTooltipLinksGroup>
115-
</HelpTooltipContent>
105+
<HelpTooltipLinksGroup>
106+
<HelpTooltipAction
107+
icon={RefreshIcon}
108+
onClick={updateWorkspace.update}
109+
>
110+
Update
111+
</HelpTooltipAction>
112+
</HelpTooltipLinksGroup>
113+
</HelpTooltipContent>
114+
<WorkspaceUpdateDialogs {...updateWorkspace.dialogs} />
115+
</>
116116
);
117117
};
118118

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1+
import { workspaces } from "api/queries/workspaces";
12
import type { Template, Workspace } from "api/typesGenerated";
23
import { compareAsc } from "date-fns";
3-
import { calcOffset } from "hooks/usePagination";
4-
import { useWorkspacesData } from "pages/WorkspacesPage/data";
4+
import { useQuery } from "react-query";
55
import type { TemplateScheduleFormValues } from "./formHelpers";
66

77
export const useWorkspacesToGoDormant = (
88
template: Template,
99
formValues: TemplateScheduleFormValues,
1010
fromDate: Date,
1111
) => {
12-
const { data } = useWorkspacesData({
13-
offset: calcOffset(0, 0),
14-
limit: 0,
15-
q: `template:${template.name}`,
16-
});
12+
const { data } = useQuery(
13+
workspaces({
14+
q: `template:${template.name}`,
15+
}),
16+
);
1717

1818
return data?.workspaces?.filter((workspace: Workspace) => {
1919
if (!formValues.time_til_dormant_ms) {
@@ -40,11 +40,12 @@ export const useWorkspacesToBeDeleted = (
4040
formValues: TemplateScheduleFormValues,
4141
fromDate: Date,
4242
) => {
43-
const { data } = useWorkspacesData({
44-
offset: calcOffset(0, 0),
45-
limit: 0,
46-
q: `template:${template.name} dormant:true`,
47-
});
43+
const { data } = useQuery(
44+
workspaces({
45+
q: `template:${template.name} dormant:true`,
46+
}),
47+
);
48+
4849
return data?.workspaces?.filter((workspace: Workspace) => {
4950
if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) {
5051
return false;

site/src/pages/WorkspacesPage/WorkspacesPage.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getErrorDetail, getErrorMessage } from "api/errors";
22
import { workspacePermissionsByOrganization } from "api/queries/organizations";
33
import { templates } from "api/queries/templates";
4+
import { workspaces } from "api/queries/workspaces";
45
import type { Workspace } from "api/typesGenerated";
56
import { useFilter } from "components/Filter/Filter";
67
import { useUserFilterMenu } from "components/Filter/UserFilter";
@@ -19,7 +20,6 @@ import { BatchDeleteConfirmation } from "./BatchDeleteConfirmation";
1920
import { BatchUpdateConfirmation } from "./BatchUpdateConfirmation";
2021
import { WorkspacesPageView } from "./WorkspacesPageView";
2122
import { useBatchActions } from "./batchActions";
22-
import { useWorkspaceUpdate, useWorkspacesData } from "./data";
2323
import { useStatusFilterMenu, useTemplateFilterMenu } from "./filter/menus";
2424

2525
function useSafeSearchParams() {
@@ -45,9 +45,7 @@ const WorkspacesPage: FC = () => {
4545
const pagination = usePagination({ searchParamsResult });
4646
const { permissions, user: me } = useAuthenticated();
4747
const { entitlements } = useDashboard();
48-
4948
const templatesQuery = useQuery(templates());
50-
5149
const workspacePermissionsQuery = useQuery(
5250
workspacePermissionsByOrganization(
5351
templatesQuery.data?.map((template) => template.organization_id),
@@ -73,12 +71,17 @@ const WorkspacesPage: FC = () => {
7371
onFilterChange: () => pagination.goToPage(1),
7472
});
7573

76-
const { data, error, queryKey, refetch } = useWorkspacesData({
74+
const workspacesQueryOptions = workspaces({
7775
...pagination,
7876
q: filterProps.filter.query,
7977
});
78+
const { data, error, refetch } = useQuery({
79+
...workspacesQueryOptions,
80+
refetchInterval: (_, query) => {
81+
return query.state.error ? false : 5_000;
82+
},
83+
});
8084

81-
const updateWorkspace = useWorkspaceUpdate(queryKey);
8285
const [checkedWorkspaces, setCheckedWorkspaces] = useState<
8386
readonly Workspace[]
8487
>([]);
@@ -123,17 +126,14 @@ const WorkspacesPage: FC = () => {
123126
limit={pagination.limit}
124127
onPageChange={pagination.goToPage}
125128
filterProps={filterProps}
126-
onUpdateWorkspace={(workspace) => {
127-
updateWorkspace.mutate(workspace);
128-
}}
129129
isRunningBatchAction={batchActions.isLoading}
130130
onDeleteAll={() => setConfirmingBatchAction("delete")}
131131
onUpdateAll={() => setConfirmingBatchAction("update")}
132132
onStartAll={() => batchActions.startAll(checkedWorkspaces)}
133133
onStopAll={() => batchActions.stopAll(checkedWorkspaces)}
134134
onActionSuccess={async () => {
135135
await queryClient.invalidateQueries({
136-
queryKey,
136+
queryKey: workspacesQueryOptions.queryKey,
137137
});
138138
}}
139139
onActionError={(error) => {

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export interface WorkspacesPageViewProps {
5353
page: number;
5454
limit: number;
5555
onPageChange: (page: number) => void;
56-
onUpdateWorkspace: (workspace: Workspace) => void;
5756
onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void;
5857
isRunningBatchAction: boolean;
5958
onDeleteAll: () => void;
@@ -76,7 +75,6 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({
7675
count,
7776
filterProps,
7877
onPageChange,
79-
onUpdateWorkspace,
8078
page,
8179
checkedWorkspaces,
8280
onCheckChange,
@@ -223,7 +221,6 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({
223221
canCreateTemplate={canCreateTemplate}
224222
workspaces={workspaces}
225223
isUsingFilter={filterProps.filter.used}
226-
onUpdateWorkspace={onUpdateWorkspace}
227224
checkedWorkspaces={checkedWorkspaces}
228225
onCheckChange={onCheckChange}
229226
canCheckWorkspaces={canCheckWorkspaces}

0 commit comments

Comments
 (0)