Skip to content

Commit 4fac55b

Browse files
NicolappsConvex, Inc.
authored and
Convex, Inc.
committed
Fix self-hosted dashboard UI when working on a cloud deployment (#35837)
On the project settings page, we disable a few features when using the self-hosted dashboard. But if someone is working on a cloud deployment, we should redirect them to the cloud dashboard if they want to use these features. This PR does that. Note that this only detects cloud deployments if they are not using a custom domain. I think this is fine for now. GitOrigin-RevId: 49347a8a3e66f29c792e29dcb887a3592d68e0a4
1 parent 3d235c3 commit 4fac55b

File tree

3 files changed

+413
-33
lines changed

3 files changed

+413
-33
lines changed

npm-packages/dashboard-common/src/elements/Sidebar.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export function SidebarLink({
114114
proBadge,
115115
small,
116116
tip,
117+
target,
117118
}: {
118119
collapsed?: boolean;
119120
href: string;
@@ -125,6 +126,7 @@ export function SidebarLink({
125126
proBadge?: boolean;
126127
small?: boolean;
127128
tip?: string;
129+
target?: "_blank";
128130
}) {
129131
const { query: currentQuery } = useRouter();
130132
return (
@@ -148,6 +150,7 @@ export function SidebarLink({
148150
isDisabled: disabled,
149151
small,
150152
})}
153+
target={target}
151154
>
152155
{Icon && (
153156
<Icon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import "@testing-library/jest-dom";
4+
import { DeploymentInfoContext } from "@common/lib/deploymentContext";
5+
import { mockDeploymentInfo } from "@common/lib/mockDeploymentInfo";
6+
import { SettingsSidebar } from "./SettingsSidebar";
7+
8+
jest.mock("@common/lib/useNents", () => ({
9+
useNents: jest.fn().mockReturnValue({ nents: [] }),
10+
}));
11+
12+
jest.mock("next/router", () => ({
13+
useRouter: jest.fn().mockReturnValue({
14+
query: {},
15+
}),
16+
}));
17+
18+
describe("SettingsSidebar", () => {
19+
describe("cloud dashboard (dashboard.convex.dev)", () => {
20+
beforeEach(() => {
21+
render(
22+
<DeploymentInfoContext.Provider
23+
value={{
24+
...mockDeploymentInfo,
25+
isSelfHosted: false,
26+
teamsURI: "/t/test-team",
27+
projectsURI: "/t/test-team/test-project",
28+
deploymentsURI: "/t/test-team/test-project/fine-marmot-266",
29+
}}
30+
>
31+
<SettingsSidebar selectedPage="url-and-deploy-key" />
32+
</DeploymentInfoContext.Provider>,
33+
);
34+
});
35+
36+
test("First tab has correct URL and is enabled", async () => {
37+
const link = await screen.findByRole("link", {
38+
name: "URL & Deploy Key",
39+
});
40+
41+
expect(link).toHaveAttribute(
42+
"href",
43+
"/t/test-team/test-project/fine-marmot-266/settings",
44+
);
45+
expect(link).not.toHaveAttribute("target");
46+
expect(link).not.toBeDisabled();
47+
});
48+
49+
test("Standard tab has correct URL and is enabled", async () => {
50+
const link = await screen.findByRole("link", {
51+
name: "Environment Variables",
52+
});
53+
54+
expect(link).toHaveAttribute(
55+
"href",
56+
"/t/test-team/test-project/fine-marmot-266/settings/environment-variables",
57+
);
58+
expect(link).not.toHaveAttribute("target");
59+
expect(link).not.toBeDisabled();
60+
});
61+
62+
test("Backups link has correct URL and is enabled", async () => {
63+
const link = await screen.findByRole("link", {
64+
name: "Backup & Restore",
65+
});
66+
67+
expect(link).toHaveAttribute(
68+
"href",
69+
"/t/test-team/test-project/fine-marmot-266/settings/backups",
70+
);
71+
expect(link).not.toHaveAttribute("target");
72+
expect(link).not.toBeDisabled();
73+
});
74+
75+
test("Integrations link has correct URL and is enabled", async () => {
76+
const link = await screen.findByRole("link", {
77+
name: "Integrations",
78+
});
79+
80+
expect(link).toHaveAttribute(
81+
"href",
82+
"/t/test-team/test-project/fine-marmot-266/settings/integrations",
83+
);
84+
expect(link).not.toHaveAttribute("target");
85+
expect(link).not.toBeDisabled();
86+
});
87+
88+
test("Project Settings link has correct URL and is enabled", async () => {
89+
const link = await screen.findByRole("link", {
90+
name: "Project Settings",
91+
});
92+
93+
expect(link).toHaveAttribute(
94+
"href",
95+
"/t/test-team/test-project/settings",
96+
);
97+
expect(link).not.toHaveAttribute("target");
98+
expect(link).not.toBeDisabled();
99+
});
100+
101+
test("Project Usage link has correct URL and is enabled", async () => {
102+
const link = await screen.findByRole("link", {
103+
name: "Project Usage",
104+
});
105+
106+
expect(link).toHaveAttribute(
107+
"href",
108+
"/t/test-team/settings/usage?projectSlug=project",
109+
);
110+
expect(link).not.toHaveAttribute("target");
111+
expect(link).not.toBeDisabled();
112+
});
113+
});
114+
115+
describe("self-hosted dashboard with self-hosted deployment", () => {
116+
beforeEach(() => {
117+
render(
118+
<DeploymentInfoContext.Provider
119+
value={{
120+
...mockDeploymentInfo,
121+
isSelfHosted: true,
122+
teamsURI: "",
123+
projectsURI: "",
124+
deploymentsURI: "",
125+
}}
126+
>
127+
<SettingsSidebar selectedPage="url-and-deploy-key" />
128+
</DeploymentInfoContext.Provider>,
129+
);
130+
});
131+
132+
test("First tab has correct URL and is enabled", async () => {
133+
const link = await screen.findByRole("link", {
134+
name: "URL & Deploy Key",
135+
});
136+
137+
expect(link).toHaveAttribute("href", "/settings");
138+
expect(link).not.toHaveAttribute("target");
139+
expect(link).not.toBeDisabled();
140+
});
141+
142+
test("Standard tab has correct URL and is enabled", async () => {
143+
const link = await screen.findByRole("link", {
144+
name: "Environment Variables",
145+
});
146+
147+
expect(link).toHaveAttribute("href", "/settings/environment-variables");
148+
expect(link).not.toHaveAttribute("target");
149+
expect(link).not.toBeDisabled();
150+
});
151+
152+
test("Backups link is disabled in self-hosted deployment", async () => {
153+
const disabledLink = await screen.findByRole("button", {
154+
name: "Backup & Restore",
155+
});
156+
expect(disabledLink).toHaveAttribute("aria-disabled", "true");
157+
});
158+
159+
test("Integrations link is disabled in self-hosted deployment", async () => {
160+
const disabledLink = await screen.findByRole("button", {
161+
name: "Integrations",
162+
});
163+
expect(disabledLink).toHaveAttribute("aria-disabled", "true");
164+
});
165+
166+
test("Project Settings link is disabled in self-hosted deployment", async () => {
167+
const disabledLink = await screen.findByRole("button", {
168+
name: "Project Settings",
169+
});
170+
expect(disabledLink).toHaveAttribute("aria-disabled", "true");
171+
});
172+
173+
test("Project Usage link is disabled in self-hosted deployment", async () => {
174+
const disabledLink = await screen.findByRole("button", {
175+
name: "Project Usage",
176+
});
177+
expect(disabledLink).toHaveAttribute("aria-disabled", "true");
178+
});
179+
});
180+
181+
describe("self-hosted dashboard with cloud deployment", () => {
182+
beforeEach(() => {
183+
render(
184+
<DeploymentInfoContext.Provider
185+
value={{
186+
...mockDeploymentInfo,
187+
isSelfHosted: true,
188+
teamsURI: "",
189+
projectsURI: "",
190+
deploymentsURI: "",
191+
ok: true,
192+
deploymentUrl: "https://fine-marmot-266.convex.cloud",
193+
adminKey: "test-admin-key",
194+
}}
195+
>
196+
<SettingsSidebar selectedPage="url-and-deploy-key" />
197+
</DeploymentInfoContext.Provider>,
198+
);
199+
});
200+
201+
test("First tab has correct URL and is enabled", async () => {
202+
const link = await screen.findByRole("link", {
203+
name: "URL & Deploy Key",
204+
});
205+
206+
expect(link).toHaveAttribute("href", "/settings");
207+
expect(link).not.toHaveAttribute("target");
208+
expect(link).not.toBeDisabled();
209+
});
210+
211+
test("Standard tab has correct URL and is enabled", async () => {
212+
const link = await screen.findByRole("link", {
213+
name: "Environment Variables",
214+
});
215+
216+
expect(link).toHaveAttribute("href", "/settings/environment-variables");
217+
expect(link).not.toHaveAttribute("target");
218+
expect(link).not.toBeDisabled();
219+
});
220+
221+
test("Backups link has correct URL and is enabled", async () => {
222+
const link = await screen.findByRole("link", {
223+
name: "Backup & Restore",
224+
});
225+
226+
expect(link).toHaveAttribute(
227+
"href",
228+
"https://dashboard.convex.dev/d/fine-marmot-266/settings/backups",
229+
);
230+
expect(link).toHaveAttribute("target", "_blank");
231+
expect(link).not.toBeDisabled();
232+
});
233+
234+
test("Integrations link has correct URL and is enabled", async () => {
235+
const link = await screen.findByRole("link", {
236+
name: "Integrations",
237+
});
238+
239+
expect(link).toHaveAttribute(
240+
"href",
241+
"https://dashboard.convex.dev/d/fine-marmot-266/settings/integrations",
242+
);
243+
expect(link).toHaveAttribute("target", "_blank");
244+
expect(link).not.toBeDisabled();
245+
});
246+
247+
test("Project Settings link has correct URL and is enabled", async () => {
248+
const link = await screen.findByRole("link", {
249+
name: "Project Settings",
250+
});
251+
252+
expect(link).toHaveAttribute(
253+
"href",
254+
"https://dashboard.convex.dev/dp/fine-marmot-266/settings",
255+
);
256+
expect(link).toHaveAttribute("target", "_blank");
257+
expect(link).not.toBeDisabled();
258+
});
259+
260+
test("Project Usage link has correct URL and is enabled", async () => {
261+
const link = await screen.findByRole("link", {
262+
name: "Project Usage",
263+
});
264+
265+
expect(link).toHaveAttribute(
266+
"href",
267+
"https://dashboard.convex.dev/dp/fine-marmot-266/usage",
268+
);
269+
expect(link).toHaveAttribute("target", "_blank");
270+
expect(link).not.toBeDisabled();
271+
});
272+
});
273+
274+
describe("paused team", () => {
275+
const pausedTeamContext = {
276+
...mockDeploymentInfo,
277+
useTeamUsageState: jest.fn().mockReturnValue("Paused"),
278+
};
279+
280+
beforeEach(() => {
281+
render(
282+
<DeploymentInfoContext.Provider value={pausedTeamContext}>
283+
<SettingsSidebar selectedPage="url-and-deploy-key" />
284+
</DeploymentInfoContext.Provider>,
285+
);
286+
});
287+
288+
test("Pause Deployment link is locked when team is paused", async () => {
289+
const disabledLink = await screen.findByRole("button", {
290+
name: "Pause Deployment",
291+
});
292+
expect(disabledLink).toHaveAttribute("aria-disabled", "true");
293+
});
294+
});
295+
});

0 commit comments

Comments
 (0)