Skip to content

Commit 13d0dac

Browse files
authored
feat: display error if app is not installed (coder#16980)
Fixes: coder#13937
1 parent 13a3ddd commit 13d0dac

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
MockWorkspaceApp,
99
MockWorkspaceProxies,
1010
} from "testHelpers/entities";
11+
import { withGlobalSnackbar } from "testHelpers/storybook";
1112
import { AppLink } from "./AppLink";
1213

1314
const meta: Meta<typeof AppLink> = {
@@ -72,6 +73,19 @@ export const ExternalApp: Story = {
7273
},
7374
};
7475

76+
export const ExternalAppNotInstalled: Story = {
77+
decorators: [withGlobalSnackbar],
78+
args: {
79+
workspace: MockWorkspace,
80+
app: {
81+
...MockWorkspaceApp,
82+
external: true,
83+
url: "foobar-foobaz://open-me",
84+
},
85+
agent: MockWorkspaceAgent,
86+
},
87+
};
88+
7589
export const SharingLevelOwner: Story = {
7690
args: {
7791
workspace: MockWorkspace,

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import Link from "@mui/material/Link";
55
import Tooltip from "@mui/material/Tooltip";
66
import { API } from "api/api";
77
import type * as TypesGen from "api/typesGenerated";
8+
import { displayError } from "components/GlobalSnackbar/utils";
89
import { useProxy } from "contexts/ProxyContext";
10+
import { useEffect } from "react";
911
import { type FC, type MouseEvent, useState } from "react";
1012
import { createAppLinkHref } from "utils/apps";
1113
import { generateRandomString } from "utils/random";
@@ -152,6 +154,20 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
152154
url = href.replaceAll(magicTokenString, key.key);
153155
setFetchingSessionToken(false);
154156
}
157+
158+
// When browser recognizes the protocol and is able to navigate to the app,
159+
// it will blur away, and will stop the timer. Otherwise,
160+
// an error message will be displayed.
161+
const openAppExternallyFailedTimeout = 500;
162+
const openAppExternallyFailed = setTimeout(() => {
163+
displayError(
164+
`${app.display_name !== "" ? app.display_name : app.slug} must be installed first.`,
165+
);
166+
}, openAppExternallyFailedTimeout);
167+
window.addEventListener("blur", () => {
168+
clearTimeout(openAppExternallyFailed);
169+
});
170+
155171
window.location.href = url;
156172
return;
157173
}

0 commit comments

Comments
 (0)