Skip to content

Commit 9abf6ec

Browse files
authored
feat(site): show favorite workspaces in ui (#11875)
* Add Star beside workspace name to indicate favorite status in WorkspacesList * Add button in workspace top row to toggle workspace favorite status
1 parent acd22b2 commit 9abf6ec

File tree

13 files changed

+149
-1
lines changed

13 files changed

+149
-1
lines changed

site/src/api/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,3 +1688,11 @@ export const updateHealthSettings = async (
16881688
);
16891689
return response.data;
16901690
};
1691+
1692+
export const putFavoriteWorkspace = async (workspaceID: string) => {
1693+
await axios.put(`/api/v2/workspaces/${workspaceID}/favorite`);
1694+
};
1695+
1696+
export const deleteFavoriteWorkspace = async (workspaceID: string) => {
1697+
await axios.delete(`/api/v2/workspaces/${workspaceID}/favorite`);
1698+
};

site/src/api/queries/workspaces.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,30 @@ const updateWorkspaceBuild = async (
265265
queryKey: workspaceBuildsKey(build.workspace_id),
266266
});
267267
};
268+
269+
export const toggleFavorite = (
270+
workspace: Workspace,
271+
queryClient: QueryClient,
272+
) => {
273+
return {
274+
mutationFn: () => {
275+
if (workspace.favorite) {
276+
return API.deleteFavoriteWorkspace(workspace.id);
277+
} else {
278+
return API.putFavoriteWorkspace(workspace.id);
279+
}
280+
},
281+
onSuccess: async () => {
282+
queryClient.setQueryData(
283+
workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name),
284+
{ ...workspace, favorite: !workspace.favorite },
285+
);
286+
await queryClient.invalidateQueries({
287+
queryKey: workspaceByOwnerAndNameKey(
288+
workspace.owner_name,
289+
workspace.name,
290+
),
291+
});
292+
},
293+
};
294+
};

site/src/pages/WorkspacePage/Workspace.stories.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ export const Running: Story = {
9090
},
9191
};
9292

93+
export const Favorite: Story = {
94+
args: {
95+
...Running.args,
96+
workspace: Mocks.MockFavoriteWorkspace,
97+
},
98+
};
99+
93100
export const WithoutUpdateAccess: Story = {
94101
args: {
95102
...Running.args,

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface WorkspaceProps {
3333
handleSettings: () => void;
3434
handleChangeVersion: () => void;
3535
handleDormantActivate: () => void;
36+
handleToggleFavorite: () => void;
3637
isUpdating: boolean;
3738
isRestarting: boolean;
3839
workspace: TypesGen.Workspace;
@@ -64,6 +65,7 @@ export const Workspace: FC<WorkspaceProps> = ({
6465
handleSettings,
6566
handleChangeVersion,
6667
handleDormantActivate,
68+
handleToggleFavorite,
6769
workspace,
6870
isUpdating,
6971
isRestarting,
@@ -131,6 +133,7 @@ export const Workspace: FC<WorkspaceProps> = ({
131133
handleBuildRetryDebug={handleBuildRetryDebug}
132134
handleChangeVersion={handleChangeVersion}
133135
handleDormantActivate={handleDormantActivate}
136+
handleToggleFavorite={handleToggleFavorite}
134137
canRetryDebugMode={canRetryDebugMode}
135138
canChangeVersions={canChangeVersions}
136139
isUpdating={isUpdating}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import OutlinedBlockIcon from "@mui/icons-material/BlockOutlined";
99
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
1010
import RetryIcon from "@mui/icons-material/BuildOutlined";
1111
import RetryDebugIcon from "@mui/icons-material/BugReportOutlined";
12+
import Star from "@mui/icons-material/Star";
13+
import StarBorder from "@mui/icons-material/StarBorder";
1214
import { type FC } from "react";
1315
import type { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
1416
import { BuildParametersPopover } from "./BuildParametersPopover";
@@ -190,3 +192,24 @@ export const RetryButton: FC<RetryButtonProps> = ({
190192
</TopbarButton>
191193
);
192194
};
195+
196+
interface FavoriteButtonProps {
197+
onToggle: (workspaceID: string) => void;
198+
workspaceID: string;
199+
isFavorite: boolean;
200+
}
201+
202+
export const FavoriteButton: FC<FavoriteButtonProps> = ({
203+
onToggle: onToggle,
204+
workspaceID,
205+
isFavorite,
206+
}) => {
207+
return (
208+
<TopbarButton
209+
startIcon={isFavorite ? <Star /> : <StarBorder />}
210+
onClick={() => onToggle(workspaceID)}
211+
>
212+
{isFavorite ? "Unfavorite" : "Favorite"}
213+
</TopbarButton>
214+
);
215+
};

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
UpdateButton,
1313
ActivateButton,
1414
RetryButton,
15+
FavoriteButton,
1516
} from "./Buttons";
1617

1718
import Divider from "@mui/material/Divider";
@@ -30,6 +31,7 @@ import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined";
3031

3132
export interface WorkspaceActionsProps {
3233
workspace: Workspace;
34+
handleToggleFavorite: () => void;
3335
handleStart: (buildParameters?: WorkspaceBuildParameter[]) => void;
3436
handleStop: () => void;
3537
handleRestart: (buildParameters?: WorkspaceBuildParameter[]) => void;
@@ -51,6 +53,7 @@ export interface WorkspaceActionsProps {
5153

5254
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
5355
workspace,
56+
handleToggleFavorite,
5457
handleStart,
5558
handleStop,
5659
handleRestart,
@@ -131,6 +134,13 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
131134
activating: <ActivateButton loading handleAction={handleDormantActivate} />,
132135
retry: <RetryButton handleAction={handleRetry} />,
133136
retryDebug: <RetryButton debug handleAction={handleRetryDebug} />,
137+
toggleFavorite: (
138+
<FavoriteButton
139+
workspaceID={workspace.id}
140+
isFavorite={workspace.favorite}
141+
onToggle={handleToggleFavorite}
142+
/>
143+
),
134144
};
135145

136146
return (
@@ -150,6 +160,8 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
150160

151161
{showCancel && <CancelButton handleAction={handleCancel} />}
152162

163+
{buttonMapping.toggleFavorite}
164+
153165
<MoreMenu>
154166
<MoreMenuTrigger>
155167
<TopbarIconButton

site/src/pages/WorkspacePage/WorkspaceActions/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const actionTypes = [
1515
"updating",
1616
"activate",
1717
"activating",
18+
"toggleFavorite",
1819

1920
// There's no need for a retrying state because retrying starts a transition
2021
// into one of the starting, stopping, or deleting states (based on the

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
updateWorkspace,
2121
stopWorkspace,
2222
startWorkspace,
23+
toggleFavorite,
2324
cancelBuild,
2425
} from "api/queries/workspaces";
2526
import { Alert } from "components/Alert/Alert";
@@ -144,6 +145,11 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
144145
startWorkspace(workspace, queryClient),
145146
);
146147

148+
// Toggle workspace favorite
149+
const toggleFavoriteMutation = useMutation(
150+
toggleFavorite(workspace, queryClient),
151+
);
152+
147153
// Cancel build
148154
const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient));
149155

@@ -217,6 +223,9 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
217223
displayError(message);
218224
}
219225
}}
226+
handleToggleFavorite={() => {
227+
toggleFavoriteMutation.mutate();
228+
}}
220229
latestVersion={latestVersion}
221230
canChangeVersions={canChangeVersions}
222231
hideSSHButton={featureVisibility["browser_only"]}

site/src/pages/WorkspacePage/WorkspaceTopbar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface WorkspaceProps {
6363
template: TypesGen.Template;
6464
permissions: WorkspacePermissions;
6565
latestVersion?: TypesGen.TemplateVersion;
66+
handleToggleFavorite: () => void;
6667
}
6768

6869
export const WorkspaceTopbar: FC<WorkspaceProps> = ({
@@ -75,6 +76,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
7576
handleSettings,
7677
handleChangeVersion,
7778
handleDormantActivate,
79+
handleToggleFavorite,
7880
workspace,
7981
isUpdating,
8082
isRestarting,
@@ -278,6 +280,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
278280
handleRetryDebug={handleBuildRetryDebug}
279281
handleChangeVersion={handleChangeVersion}
280282
handleDormantActivate={handleDormantActivate}
283+
handleToggleFavorite={handleToggleFavorite}
281284
canRetryDebug={canRetryDebugMode}
282285
canChangeVersions={canChangeVersions}
283286
isUpdating={isUpdating}

site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ export const AllStates: Story = {
165165
},
166166
};
167167

168+
export const AllStatesWithFavorites: Story = {
169+
args: {
170+
workspaces: allWorkspaces.map((workspace, i) => ({
171+
...workspace,
172+
// NOTE: testing sort order is not relevant here.
173+
favorite: i % 2 === 0,
174+
})),
175+
count: allWorkspaces.length,
176+
},
177+
};
178+
168179
const icons = [
169180
"/icon/code.svg",
170181
"/icon/aws.svg",

0 commit comments

Comments
 (0)