Skip to content

Commit 53453c0

Browse files
authored
fix: display app templates correctly in build preview (#10994)
* fix: appropriately display display_app apps in template build preview * added display apps to build preview * added test, consolidated names * handling empty state
1 parent 81a3b36 commit 53453c0

File tree

8 files changed

+194
-23
lines changed

8 files changed

+194
-23
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { MockWorkspaceAgent } from "testHelpers/entities";
2+
import { WorkspaceAgent, DisplayApps, DisplayApp } from "api/typesGenerated";
3+
import { renderComponent } from "testHelpers/renderHelpers";
4+
import { AgentRowPreview } from "./AgentRowPreview";
5+
import { screen } from "@testing-library/react";
6+
import { DisplayAppNameMap } from "./AppLink/AppLink";
7+
8+
const AllDisplayAppsAndModule = MockWorkspaceAgent;
9+
const VSCodeNoInsiders = {
10+
...MockWorkspaceAgent,
11+
display_apps: [
12+
"ssh_helper",
13+
"port_forwarding_helper",
14+
"vscode",
15+
"web_terminal",
16+
] as DisplayApp[],
17+
};
18+
const VSCodeWithInsiders = {
19+
...MockWorkspaceAgent,
20+
display_apps: [
21+
"ssh_helper",
22+
"port_forwarding_helper",
23+
"vscode",
24+
"vscode_insiders",
25+
"web_terminal",
26+
] as DisplayApp[],
27+
};
28+
const NoVSCode = {
29+
...MockWorkspaceAgent,
30+
display_apps: [
31+
"ssh_helper",
32+
"port_forwarding_helper",
33+
"web_terminal",
34+
] as DisplayApp[],
35+
};
36+
37+
const NoModulesJustApps = {
38+
...MockWorkspaceAgent,
39+
apps: [],
40+
};
41+
42+
const NoAppsJustModules = {
43+
...MockWorkspaceAgent,
44+
display_apps: [] as DisplayApp[],
45+
};
46+
47+
const EmptyAppPreview = {
48+
...MockWorkspaceAgent,
49+
apps: [],
50+
display_apps: [] as DisplayApp[],
51+
};
52+
53+
describe("AgentRowPreviewApps", () => {
54+
it.each<{
55+
workspaceAgent: WorkspaceAgent;
56+
testName: string;
57+
}>([
58+
{
59+
workspaceAgent: AllDisplayAppsAndModule,
60+
testName: "AllDisplayAppsAndModule",
61+
},
62+
{
63+
workspaceAgent: VSCodeNoInsiders,
64+
testName: "VSCodeNoInsiders",
65+
},
66+
{
67+
workspaceAgent: VSCodeWithInsiders,
68+
testName: "VSCodeWithInsiders",
69+
},
70+
{
71+
workspaceAgent: NoVSCode,
72+
testName: "NoVSCode",
73+
},
74+
{
75+
workspaceAgent: NoModulesJustApps,
76+
testName: "NoModulesJustApps",
77+
},
78+
{
79+
workspaceAgent: NoAppsJustModules,
80+
testName: "NoAppsJustModules",
81+
},
82+
{
83+
workspaceAgent: EmptyAppPreview,
84+
testName: "EmptyAppPreview",
85+
},
86+
])(
87+
`<AgentRowPreview agent={$testName} /> displays appropriately`,
88+
({ workspaceAgent }) => {
89+
renderComponent(<AgentRowPreview agent={workspaceAgent} />);
90+
workspaceAgent.apps.forEach((module) => {
91+
expect(screen.getByText(module.display_name)).toBeInTheDocument();
92+
});
93+
workspaceAgent.display_apps
94+
.filter((app) => app !== "vscode" && app !== "vscode_insiders") // these get special treatment
95+
.forEach((app) => {
96+
expect(screen.getByText(DisplayAppNameMap[app])).toBeInTheDocument();
97+
});
98+
99+
// test VS Code display
100+
if (workspaceAgent.display_apps.includes("vscode")) {
101+
expect(
102+
screen.getByText(DisplayAppNameMap["vscode"]),
103+
).toBeInTheDocument();
104+
} else if (workspaceAgent.display_apps.includes("vscode_insiders")) {
105+
expect(
106+
screen.getByText(DisplayAppNameMap["vscode_insiders"]),
107+
).toBeInTheDocument();
108+
} else {
109+
expect(screen.queryByText("vscode")).not.toBeInTheDocument();
110+
expect(screen.queryByText("vscode_insiders")).not.toBeInTheDocument();
111+
}
112+
113+
// difference between all possible display apps and those displayed
114+
const excludedApps = DisplayApps.filter(
115+
(a) => !workspaceAgent.display_apps.includes(a),
116+
);
117+
118+
excludedApps.forEach((app) => {
119+
expect(
120+
screen.queryByText(DisplayAppNameMap[app]),
121+
).not.toBeInTheDocument();
122+
});
123+
124+
// test empty state
125+
if (
126+
workspaceAgent.display_apps.length === 0 &&
127+
workspaceAgent.apps.length === 0
128+
) {
129+
expect(screen.getByText("None")).toBeInTheDocument();
130+
}
131+
},
132+
);
133+
});

site/src/components/Resources/AgentRowPreview.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { type Interpolation, type Theme } from "@emotion/react";
22
import { type FC } from "react";
33
import type { WorkspaceAgent } from "api/typesGenerated";
44
import { Stack } from "../Stack/Stack";
5-
import { AppPreviewLink } from "./AppLink/AppPreviewLink";
5+
import { AppPreview } from "./AppLink/AppPreview";
6+
import { BaseIcon } from "./AppLink/BaseIcon";
7+
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
8+
import { DisplayAppNameMap } from "./AppLink/AppLink";
69

710
interface AgentRowPreviewStyles {
811
// Helpful when there are more than one row so the values are aligned
@@ -86,10 +89,43 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
8689
spacing={0.5}
8790
wrap="wrap"
8891
>
92+
{/* We display all modules returned in agent.apps */}
8993
{agent.apps.map((app) => (
90-
<AppPreviewLink key={app.slug} app={app} />
94+
<AppPreview key={app.slug}>
95+
<>
96+
<BaseIcon app={app} />
97+
{app.display_name}
98+
</>
99+
</AppPreview>
91100
))}
92-
{agent.apps.length === 0 && (
101+
{/* Additionally, we display any apps that are visible, e.g.
102+
apps that are included in agent.display_apps */}
103+
{agent.display_apps.includes("web_terminal") && (
104+
<AppPreview>{DisplayAppNameMap["web_terminal"]}</AppPreview>
105+
)}
106+
{agent.display_apps.includes("ssh_helper") && (
107+
<AppPreview>{DisplayAppNameMap["ssh_helper"]}</AppPreview>
108+
)}
109+
{agent.display_apps.includes("port_forwarding_helper") && (
110+
<AppPreview>
111+
{DisplayAppNameMap["port_forwarding_helper"]}
112+
</AppPreview>
113+
)}
114+
{/* VSCode display apps (vscode, vscode_insiders) get special presentation */}
115+
{agent.display_apps.includes("vscode") ? (
116+
<AppPreview>
117+
<VSCodeIcon sx={{ width: 12, height: 12 }} />
118+
{DisplayAppNameMap["vscode"]}
119+
</AppPreview>
120+
) : (
121+
agent.display_apps.includes("vscode_insiders") && (
122+
<AppPreview>
123+
<VSCodeIcon sx={{ width: 12, height: 12 }} />
124+
{DisplayAppNameMap["vscode_insiders"]}
125+
</AppPreview>
126+
)
127+
)}
128+
{agent.apps.length === 0 && agent.display_apps.length === 0 && (
93129
<span css={styles.agentDataValue}>None</span>
94130
)}
95131
</Stack>

site/src/components/Resources/AppLink/AppLink.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ import { generateRandomString } from "utils/random";
1313
import { BaseIcon } from "./BaseIcon";
1414
import { ShareIcon } from "./ShareIcon";
1515

16+
export const DisplayAppNameMap: Record<TypesGen.DisplayApp, string> = {
17+
port_forwarding_helper: "Ports",
18+
ssh_helper: "SSH",
19+
vscode: "VS Code Desktop",
20+
vscode_insiders: "VS Code Insiders",
21+
web_terminal: "Terminal",
22+
};
23+
1624
const Language = {
1725
appTitle: (appName: string, identifier: string): string =>
1826
`${appName} - ${identifier}`,

site/src/components/Resources/AppLink/AppPreviewLink.tsx renamed to site/src/components/Resources/AppLink/AppPreview.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import { Stack } from "components/Stack/Stack";
2-
import { type FC } from "react";
3-
import type * as TypesGen from "api/typesGenerated";
4-
import { BaseIcon } from "./BaseIcon";
5-
import { ShareIcon } from "./ShareIcon";
2+
import { type FC, type PropsWithChildren } from "react";
63

7-
interface AppPreviewProps {
8-
app: TypesGen.WorkspaceApp;
9-
}
10-
11-
export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
4+
export const AppPreview: FC<PropsWithChildren> = ({ children }) => {
125
return (
136
<Stack
147
css={(theme) => ({
@@ -29,9 +22,7 @@ export const AppPreviewLink: FC<AppPreviewProps> = ({ app }) => {
2922
direction="row"
3023
spacing={1}
3124
>
32-
<BaseIcon app={app} />
33-
{app.display_name}
34-
<ShareIcon app={app} />
25+
{children}
3526
</Stack>
3627
);
3728
};

site/src/components/Resources/PortForwardButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
PopoverContent,
2727
PopoverTrigger,
2828
} from "components/Popover/Popover";
29+
import { DisplayAppNameMap } from "./AppLink/AppLink";
2930

3031
export interface PortForwardButtonProps {
3132
host: string;
@@ -50,7 +51,7 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
5051
<Popover>
5152
<PopoverTrigger>
5253
<AgentButton disabled={!portsQuery.data}>
53-
Ports
54+
{DisplayAppNameMap["port_forwarding_helper"]}
5455
{portsQuery.data ? (
5556
<div css={styles.portCount}>{portsQuery.data.ports.length}</div>
5657
) : (

site/src/components/Resources/SSHButton/SSHButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "components/Popover/Popover";
1616
import { Stack } from "components/Stack/Stack";
1717
import { AgentButton } from "../AgentButton";
18+
import { DisplayAppNameMap } from "../AppLink/AppLink";
1819

1920
export interface SSHButtonProps {
2021
workspaceName: string;
@@ -34,7 +35,7 @@ export const SSHButton: FC<PropsWithChildren<SSHButtonProps>> = ({
3435
return (
3536
<Popover isDefaultOpen={isDefaultOpen}>
3637
<PopoverTrigger>
37-
<AgentButton>SSH</AgentButton>
38+
<AgentButton>{DisplayAppNameMap["ssh_helper"]}</AgentButton>
3839
</PopoverTrigger>
3940

4041
<PopoverContent horizontal="right" classes={{ paper }}>

site/src/components/Resources/TerminalLink/TerminalLink.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { AgentButton } from "components/Resources/AgentButton";
33
import { FC } from "react";
44
import * as TypesGen from "api/typesGenerated";
55
import { generateRandomString } from "utils/random";
6+
import { DisplayAppNameMap } from "../AppLink/AppLink";
67

78
export const Language = {
8-
linkText: "Terminal",
99
terminalTitle: (identifier: string): string => `Terminal - ${identifier}`,
1010
};
1111

@@ -46,7 +46,7 @@ export const TerminalLink: FC<React.PropsWithChildren<TerminalLinkProps>> = ({
4646
}}
4747
data-testid="terminal"
4848
>
49-
<AgentButton>{Language.linkText}</AgentButton>
49+
<AgentButton>{DisplayAppNameMap["web_terminal"]}</AgentButton>
5050
</Link>
5151
);
5252
};

site/src/components/Resources/VSCodeDesktopButton/VSCodeDesktopButton.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useLocalStorage } from "hooks";
99
import Menu from "@mui/material/Menu";
1010
import MenuItem from "@mui/material/MenuItem";
1111
import { DisplayApp } from "api/typesGenerated";
12+
import { DisplayAppNameMap } from "../AppLink/AppLink";
1213

1314
export interface VSCodeDesktopButtonProps {
1415
userName: string;
@@ -97,7 +98,7 @@ export const VSCodeDesktopButton: FC<
9798
}}
9899
>
99100
<VSCodeIcon css={{ width: 12, height: 12 }} />
100-
VS Code Desktop
101+
{DisplayAppNameMap["vscode"]}
101102
</MenuItem>
102103
<MenuItem
103104
css={{ fontSize: 14 }}
@@ -106,7 +107,7 @@ export const VSCodeDesktopButton: FC<
106107
}}
107108
>
108109
<VSCodeInsidersIcon css={{ width: 12, height: 12 }} />
109-
VS Code Insiders
110+
{DisplayAppNameMap["vscode_insiders"]}
110111
</MenuItem>
111112
</Menu>
112113
</div>
@@ -156,7 +157,7 @@ const VSCodeButton = ({
156157
});
157158
}}
158159
>
159-
VS Code Desktop
160+
{DisplayAppNameMap["vscode"]}
160161
</AgentButton>
161162
);
162163
};
@@ -200,7 +201,7 @@ const VSCodeInsidersButton = ({
200201
});
201202
}}
202203
>
203-
VS Code Insiders
204+
{DisplayAppNameMap["vscode_insiders"]}
204205
</AgentButton>
205206
);
206207
};

0 commit comments

Comments
 (0)