From 74d6358d58c4d8be08475f87d05f8fa246b01dd1 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 9 May 2025 14:47:27 +0000 Subject: [PATCH 1/3] feat: display user apps in the workspaces table --- .../pages/WorkspacesPage/WorkspacesTable.tsx | 69 ++++++++++++++++--- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index ad031d67ad229..b7a3912f08350 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -20,6 +20,7 @@ import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton"; import { Button } from "components/Button/Button"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { VSCodeIcon } from "components/Icons/VSCodeIcon"; import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon"; import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; @@ -63,6 +64,7 @@ import { getVSCodeHref, openAppInNewWindow, } from "modules/apps/apps"; +import { useAppLink } from "modules/apps/useAppLink"; import { useDashboard } from "modules/dashboard/useDashboard"; import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus"; import { WorkspaceDormantBadge } from "modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge"; @@ -622,6 +624,9 @@ const PrimaryAction: FC = ({ ); }; +// The total number of apps that can be displayed in the workspace row +const WORKSPACE_APPS_SLOTS = 4; + type WorkspaceAppsProps = { workspace: Workspace; }; @@ -647,11 +652,18 @@ const WorkspaceApps: FC = ({ workspace }) => { return null; } + const builtinApps = new Set(agent.display_apps); + builtinApps.delete("port_forwarding_helper"); + builtinApps.delete("ssh_helper"); + + const remainingSlots = WORKSPACE_APPS_SLOTS - builtinApps.size; + const userApps = agent.apps.slice(0, remainingSlots); + const buttons: ReactNode[] = []; - if (agent.display_apps.includes("vscode")) { + if (builtinApps.has("vscode")) { buttons.push( - = ({ workspace }) => { })} > - , + , ); } - if (agent.display_apps.includes("vscode_insiders")) { + if (builtinApps.has("vscode_insiders")) { buttons.push( - = ({ workspace }) => { })} > - , + , ); } - if (agent.display_apps.includes("web_terminal")) { + for (const app of userApps) { + buttons.push( + , + ); + } + + if (builtinApps.has("web_terminal")) { const href = getTerminalHref({ username: workspace.owner_name, workspace: workspace.name, agent: agent.name, }); buttons.push( - { @@ -704,21 +727,45 @@ const WorkspaceApps: FC = ({ workspace }) => { label="Open Terminal" > - , + , ); } return buttons; }; -type AppLinkProps = PropsWithChildren<{ +type IconAppLinkProps = { + app: WorkspaceApp; + workspace: Workspace; + agent: WorkspaceAgent; +}; + +const IconAppLink: FC = ({ app, workspace, agent }) => { + const link = useAppLink(app, { + workspace, + agent, + }); + + return ( + + + + ); +}; + +type BaseIconLinkProps = PropsWithChildren<{ label: string; href: string; isLoading?: boolean; onClick?: (e: React.MouseEvent) => void; }>; -const AppLink: FC = ({ +const BaseIconLink: FC = ({ href, isLoading, label, From b1ce074f9bbda3c4bec8d8f1f8905466930bc8cf Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 9 May 2025 17:54:41 +0000 Subject: [PATCH 2/3] Fix non icon --- site/src/pages/WorkspacesPage/WorkspacesTable.tsx | 2 +- site/src/testHelpers/entities.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index b7a3912f08350..e4680e54c02ac 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -753,7 +753,7 @@ const IconAppLink: FC = ({ app, workspace, agent }) => { href={link.href} onClick={link.onClick} > - + ); }; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 084bb78345d8f..a8db5f55879c4 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -896,17 +896,10 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = { id: "test-app", slug: "test-app", display_name: "Test App", - icon: "", subdomain: false, health: "disabled", external: false, - url: "", sharing_level: "owner", - healthcheck: { - url: "", - interval: 0, - threshold: 0, - }, hidden: false, open_in: "slim-window", statuses: [], From 398678aa987102559c7e9835163dcb01d6ae439d Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 9 May 2025 18:07:00 +0000 Subject: [PATCH 3/3] Add use case for multiple apps --- .../WorkspacesPageView.stories.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index f9dc50d0cbff3..dc1b9c2f4fb82 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -23,6 +23,7 @@ import { MockTemplate, MockUserOwner, MockWorkspace, + MockWorkspaceAgent, MockWorkspaceAppStatus, mockApiError, } from "testHelpers/entities"; @@ -299,6 +300,42 @@ export const InvalidPageNumber: Story = { }, }; +export const MultipleApps: Story = { + args: { + workspaces: [ + { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + resources: [ + { + ...MockWorkspace.latest_build.resources[0], + agents: [ + { + ...MockWorkspaceAgent, + apps: [ + { + ...MockWorkspaceAgent.apps[0], + display_name: "App 1", + id: "app-1", + }, + { + ...MockWorkspaceAgent.apps[0], + display_name: "App 2", + id: "app-2", + }, + ], + }, + ], + }, + ], + }, + }, + ], + count: allWorkspaces.length, + }, +}; + export const ShowOrganizations: Story = { args: { workspaces: [{ ...MockWorkspace, organization_name: "limbus-co" }],