Skip to content

Commit 830ab57

Browse files
committed
Add tests and fix missing references
1 parent b7beb76 commit 830ab57

File tree

12 files changed

+153
-185
lines changed

12 files changed

+153
-185
lines changed

site/src/modules/apps/apps.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
MockWorkspace,
3+
MockWorkspaceAgent,
4+
MockWorkspaceApp,
5+
} from "testHelpers/entities";
6+
import { SESSION_TOKEN_PLACEHOLDER, getAppHref } from "./apps";
7+
8+
describe("getAppHref", () => {
9+
it("returns the URL without changes when external app has regular URL", () => {
10+
const externalApp = {
11+
...MockWorkspaceApp,
12+
external: true,
13+
url: "https://example.com",
14+
};
15+
const href = getAppHref(externalApp, {
16+
host: "*.apps-host.tld",
17+
path: "/path-base",
18+
agent: MockWorkspaceAgent,
19+
workspace: MockWorkspace,
20+
});
21+
expect(href).toBe(externalApp.url);
22+
});
23+
24+
it("returns the URL with the session token replaced when external app needs session token ", () => {
25+
const externalApp = {
26+
...MockWorkspaceApp,
27+
external: true,
28+
url: `https://example.com?token=${SESSION_TOKEN_PLACEHOLDER}`,
29+
};
30+
const href = getAppHref(externalApp, {
31+
host: "*.apps-host.tld",
32+
path: "/path-base",
33+
agent: MockWorkspaceAgent,
34+
workspace: MockWorkspace,
35+
token: "user-session-token",
36+
});
37+
expect(href).toBe("https://example.com?token=user-session-token");
38+
});
39+
40+
it("returns a path when app doesn't use a subdomain", () => {
41+
const app = {
42+
...MockWorkspaceApp,
43+
subdomain: false,
44+
};
45+
const href = getAppHref(app, {
46+
host: "*.apps-host.tld",
47+
agent: MockWorkspaceAgent,
48+
workspace: MockWorkspace,
49+
path: "/path-base",
50+
});
51+
expect(href).toBe(
52+
"/path-base/@username/Test-Workspace.a-workspace-agent/apps/app-slug/",
53+
);
54+
});
55+
56+
it("includes the command in the URL when app has a command", () => {
57+
const app = {
58+
...MockWorkspaceApp,
59+
command: "ls -la",
60+
};
61+
const href = getAppHref(app, {
62+
host: "*.apps-host.tld",
63+
agent: MockWorkspaceAgent,
64+
workspace: MockWorkspace,
65+
path: "/path-base",
66+
});
67+
expect(href).toBe(
68+
"/path-base/@username/Test-Workspace.a-workspace-agent/terminal?command=ls%20-la",
69+
);
70+
});
71+
72+
it("uses the subdomain when app has a subdomain", () => {
73+
const app = {
74+
...MockWorkspaceApp,
75+
subdomain: true,
76+
subdomain_name: "hellocoder",
77+
};
78+
const href = getAppHref(app, {
79+
host: "*.apps-host.tld",
80+
agent: MockWorkspaceAgent,
81+
workspace: MockWorkspace,
82+
path: "/path-base",
83+
});
84+
expect(href).toBe("https://hellocoder.apps-host.tld/");
85+
});
86+
87+
it("returns a path when app has a subdomain but no subdomain name", () => {
88+
const app = {
89+
...MockWorkspaceApp,
90+
subdomain: true,
91+
subdomain_name: undefined,
92+
};
93+
const href = getAppHref(app, {
94+
host: "*.apps-host.tld",
95+
agent: MockWorkspaceAgent,
96+
workspace: MockWorkspace,
97+
path: "/path-base",
98+
});
99+
expect(href).toBe(
100+
"/path-base/@username/Test-Workspace.a-workspace-agent/apps/app-slug/",
101+
);
102+
});
103+
});

site/src/modules/apps/apps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
// with a brand-new session token from the backend.
99
// This only exists for external URLs, and should only
1010
// be used internally, and is highly subject to break.
11-
const SESSION_TOKEN_PLACEHOLDER = "$SESSION_TOKEN";
11+
export const SESSION_TOKEN_PLACEHOLDER = "$SESSION_TOKEN";
1212

1313
type GetVSCodeHrefParams = {
1414
owner: string;
@@ -73,7 +73,7 @@ type CreateAppHrefParams = {
7373
token?: string;
7474
};
7575

76-
export const createAppHref = (
76+
export const getAppHref = (
7777
app: WorkspaceApp,
7878
{ path, token, workspace, agent, host }: CreateAppHrefParams,
7979
): string => {

site/src/modules/resources/AgentRow.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import { PortForwardButton } from "./PortForwardButton";
4040
import { AgentSSHButton } from "./SSHButton/SSHButton";
4141
import { TerminalLink } from "./TerminalLink/TerminalLink";
4242
import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton";
43-
import { apiKey } from "api/queries/users";
4443

4544
export interface AgentRowProps {
4645
agent: WorkspaceAgent;
@@ -55,6 +54,7 @@ export interface AgentRowProps {
5554
onUpdateAgent: () => void;
5655
template: Template;
5756
storybookAgentMetadata?: WorkspaceAgentMetadata[];
57+
token?: string;
5858
}
5959

6060
export const AgentRow: FC<AgentRowProps> = ({
@@ -70,6 +70,7 @@ export const AgentRow: FC<AgentRowProps> = ({
7070
onUpdateAgent,
7171
storybookAgentMetadata,
7272
sshPrefix,
73+
token,
7374
}) => {
7475
// Apps visibility
7576
const visibleApps = agent.apps.filter((app) => !app.hidden);
@@ -164,8 +165,6 @@ export const AgentRow: FC<AgentRowProps> = ({
164165
refetchInterval: 10_000,
165166
});
166167

167-
const { data: apiKeyResponse } = useQuery(apiKey());
168-
169168
return (
170169
<Stack
171170
key={agent.id}
@@ -242,7 +241,7 @@ export const AgentRow: FC<AgentRowProps> = ({
242241
)}
243242
{visibleApps.map((app) => (
244243
<AppLink
245-
token={apiKeyResponse?.key}
244+
token={token}
246245
key={app.slug}
247246
app={app}
248247
agent={agent}

site/src/modules/resources/AppLink/AppLink.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useTheme } from "@emotion/react";
22
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
3-
import { API } from "api/api";
43
import type * as TypesGen from "api/typesGenerated";
54
import { displayError } from "components/GlobalSnackbar/utils";
65
import { Spinner } from "components/Spinner/Spinner";
@@ -11,17 +10,15 @@ import {
1110
TooltipTrigger,
1211
} from "components/Tooltip/Tooltip";
1312
import { useProxy } from "contexts/ProxyContext";
14-
import { type FC, useState } from "react";
15-
import { createAppLinkHref } from "utils/apps";
16-
import { generateRandomString } from "utils/random";
17-
import { AgentButton } from "../AgentButton";
18-
import { BaseIcon } from "./BaseIcon";
19-
import { ShareIcon } from "./ShareIcon";
2013
import {
21-
createAppHref,
14+
getAppHref,
2215
needsSessionToken,
2316
openAppInNewWindow,
2417
} from "modules/apps/apps";
18+
import { type FC, useState } from "react";
19+
import { AgentButton } from "../AgentButton";
20+
import { BaseIcon } from "./BaseIcon";
21+
import { ShareIcon } from "./ShareIcon";
2522

2623
export const DisplayAppNameMap: Record<TypesGen.DisplayApp, string> = {
2724
port_forwarding_helper: "Ports",
@@ -49,7 +46,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent, token }) => {
4946
const [iconError, setIconError] = useState(false);
5047
const theme = useTheme();
5148
const displayName = app.display_name ?? app.slug;
52-
const href = createAppHref(app, {
49+
const href = getAppHref(app, {
5350
agent,
5451
workspace,
5552
token,

site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
WorkspaceApp,
1515
} from "api/typesGenerated";
1616
import { useProxy } from "contexts/ProxyContext";
17-
import { createAppLinkHref } from "utils/apps";
17+
import { getAppHref } from "modules/apps/apps";
1818

1919
const formatURI = (uri: string) => {
2020
try {
@@ -61,11 +61,13 @@ export const WorkspaceAppStatus = ({
6161
status,
6262
agent,
6363
app,
64+
token,
6465
}: {
6566
workspace: Workspace;
6667
status?: APIWorkspaceAppStatus | null;
6768
app?: WorkspaceApp;
6869
agent?: WorkspaceAgent;
70+
token?: string;
6971
}) => {
7072
const theme = useTheme();
7173
const { proxy } = useProxy();
@@ -124,16 +126,13 @@ export const WorkspaceAppStatus = ({
124126

125127
let appHref: string | undefined;
126128
if (app && agent) {
127-
appHref = createAppLinkHref(
128-
window.location.protocol,
129-
preferredPathBase,
130-
appsHost,
131-
app.slug,
132-
workspace.owner_name,
133-
workspace,
129+
appHref = getAppHref(app, {
134130
agent,
135-
app,
136-
);
131+
workspace,
132+
token,
133+
host: appsHost,
134+
path: preferredPathBase,
135+
});
137136
}
138137

139138
return (

site/src/pages/WorkspacePage/AppStatuses.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import type {
1919
} from "api/typesGenerated";
2020
import { useProxy } from "contexts/ProxyContext";
2121
import { formatDistance, formatDistanceToNow } from "date-fns";
22+
import { getAppHref } from "modules/apps/apps";
2223
import type { FC } from "react";
23-
import { createAppLinkHref } from "utils/apps";
2424

2525
const getStatusColor = (
2626
theme: Theme,
@@ -138,6 +138,7 @@ export interface AppStatusesProps {
138138
agents: ReadonlyArray<WorkspaceAgent>;
139139
/** Optional reference date for calculating relative time. Defaults to Date.now(). Useful for Storybook. */
140140
referenceDate?: Date;
141+
token?: string;
141142
}
142143

143144
// Extend the API status type to include the app icon and the app itself
@@ -151,6 +152,7 @@ export const AppStatuses: FC<AppStatusesProps> = ({
151152
workspace,
152153
agents,
153154
referenceDate,
155+
token,
154156
}) => {
155157
const theme = useTheme();
156158
const { proxy } = useProxy();
@@ -198,16 +200,13 @@ export const AppStatuses: FC<AppStatusesProps> = ({
198200
const agent = agents.find((agent) => agent.id === status.agent_id);
199201

200202
if (currentApp && agent) {
201-
appHref = createAppLinkHref(
202-
window.location.protocol,
203-
preferredPathBase,
204-
appsHost,
205-
currentApp.slug,
206-
workspace.owner_name,
207-
workspace,
203+
appHref = getAppHref(currentApp, {
208204
agent,
209-
currentApp,
210-
);
205+
workspace,
206+
host: appsHost,
207+
path: preferredPathBase,
208+
token,
209+
});
211210
}
212211

213212
// Determine if app link should be shown

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface WorkspaceProps {
5454
latestVersion?: TypesGen.TemplateVersion;
5555
permissions: WorkspacePermissions;
5656
timings?: TypesGen.WorkspaceBuildTimings;
57+
token?: string;
5758
}
5859

5960
/**
@@ -86,6 +87,7 @@ export const Workspace: FC<WorkspaceProps> = ({
8687
latestVersion,
8788
permissions,
8889
timings,
90+
token,
8991
}) => {
9092
const navigate = useNavigate();
9193
const theme = useTheme();
@@ -271,6 +273,7 @@ export const Workspace: FC<WorkspaceProps> = ({
271273
>
272274
{selectedResource.agents?.map((agent) => (
273275
<AgentRow
276+
token={token}
274277
key={agent.id}
275278
agent={agent}
276279
workspace={workspace}
@@ -382,6 +385,7 @@ export const Workspace: FC<WorkspaceProps> = ({
382385
}}
383386
>
384387
<AppStatuses
388+
token={token}
385389
apps={
386390
selectedResource.agents?.flatMap(
387391
(agent) => agent.apps ?? [],

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { watchWorkspace } from "api/api";
22
import { checkAuthorization } from "api/queries/authCheck";
33
import { template as templateQueryOptions } from "api/queries/templates";
4+
import { apiKey } from "api/queries/users";
45
import { workspaceBuildsKey } from "api/queries/workspaceBuilds";
56
import { workspaceByOwnerAndName } from "api/queries/workspaces";
67
import type { Workspace } from "api/typesGenerated";
@@ -110,6 +111,8 @@ const WorkspacePage: FC = () => {
110111
workspaceQuery.error ?? templateQuery.error ?? permissionsQuery.error;
111112
const isLoading = !workspace || !template || !permissions;
112113

114+
const { data: apiKeyResponse } = useQuery(apiKey());
115+
113116
return (
114117
<>
115118
<AnnouncementBanners />
@@ -126,6 +129,7 @@ const WorkspacePage: FC = () => {
126129
<Loader />
127130
) : (
128131
<WorkspaceReadyPage
132+
token={apiKeyResponse?.key}
129133
workspace={workspace}
130134
template={template}
131135
permissions={permissions}

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ interface WorkspaceReadyPageProps {
4242
template: TypesGen.Template;
4343
workspace: TypesGen.Workspace;
4444
permissions: WorkspacePermissions;
45+
token?: string;
4546
}
4647

4748
export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
4849
workspace,
4950
template,
5051
permissions,
52+
token,
5153
}) => {
5254
const { metadata } = useEmbeddedMetadata();
5355
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
@@ -228,6 +230,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
228230
</Helmet>
229231

230232
<Workspace
233+
token={token}
231234
permissions={permissions}
232235
isUpdating={workspaceUpdate.isUpdating}
233236
isRestarting={isRestarting}

0 commit comments

Comments
 (0)