Skip to content

Commit d91db7a

Browse files
atrakhConvex, Inc.
authored andcommitted
dashboard: add feature flag for dashboard version notification (#42823)
GitOrigin-RevId: 5cd64a9c6fd84836a29f0e3f5f57c0016e485aa9
1 parent 92c030e commit d91db7a

File tree

3 files changed

+112
-95
lines changed

3 files changed

+112
-95
lines changed

npm-packages/dashboard/src/hooks/useDashboardVersion.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,32 @@ import { Button } from "@ui/Button";
55
import { SymbolIcon } from "@radix-ui/react-icons";
66
import { captureMessage } from "@sentry/nextjs";
77
import { LocalDevCallout } from "@common/elements/LocalDevCallout";
8+
import { useLaunchDarkly } from "./useLaunchDarkly";
89

910
// To test that this works
1011
// set the following in your .env.local:
1112
// NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA=<SHA_THAT_ISN'T_THE_LATEST>
1213
// VERCEL_TOKEN=<VERCEL_ACCESS_TOKEN>
1314
export function useDashboardVersion() {
15+
const { enableNewDashboardVersionNotification } = useLaunchDarkly();
1416
const { data, error } = useSWR<{ sha?: string | null }>("/api/version", {
1517
// Refresh every hour.
1618
refreshInterval: 1000 * 60 * 60,
1719
// Refresh on focus at most every 10 minutes.
1820
focusThrottleInterval: 1000 * 60 * 10,
1921
shouldRetryOnError: false,
2022
fetcher: dashboardVersionFetcher,
23+
isPaused: () => !enableNewDashboardVersionNotification,
2124
});
2225

2326
const currentSha = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
24-
if (!error && data?.sha && currentSha && data?.sha !== currentSha) {
27+
if (
28+
enableNewDashboardVersionNotification &&
29+
!error &&
30+
data?.sha &&
31+
currentSha &&
32+
data?.sha !== currentSha
33+
) {
2534
toast(
2635
"info",
2736
<div className="flex flex-col">

npm-packages/dashboard/src/hooks/useLaunchDarkly.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ const flagDefaults: {
66
commandPaletteDeleteProjects: boolean;
77
singleSignOn: boolean;
88
workOsEnvironmentProvisioningDashboardUi: boolean;
9+
enableNewDashboardVersionNotification: boolean;
910
} = {
1011
commandPalette: false,
1112
commandPaletteDeleteProjects: false,
1213
singleSignOn: false,
1314
workOsEnvironmentProvisioningDashboardUi: false,
15+
enableNewDashboardVersionNotification: false,
1416
};
1517

1618
function kebabCaseKeys(object: typeof flagDefaults) {

npm-packages/dashboard/src/pages/_app.tsx

Lines changed: 100 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { useInitialData } from "hooks/useServerSideData";
1212
import { useRouterProgress } from "hooks/useRouterProgress";
1313
import Head from "next/head";
1414

15-
import { useDashboardVersion } from "hooks/useDashboardVersion";
1615
import { Favicon } from "@common/elements/Favicon";
1716
import { ThemeConsumer } from "@common/elements/ThemeConsumer";
1817
import { ToastContainer } from "@common/elements/ToastContainer";
@@ -36,6 +35,7 @@ import { useSSOLoginRequired } from "api/api";
3635
import { Sheet } from "@ui/Sheet";
3736
import { Button } from "@ui/Button";
3837
import { ExitIcon, LockClosedIcon } from "@radix-ui/react-icons";
38+
import { useDashboardVersion } from "hooks/useDashboardVersion";
3939

4040
declare global {
4141
interface Window {
@@ -56,25 +56,8 @@ const UNAUTHED_ROUTES = [
5656
];
5757

5858
export default function App({ Component, pageProps }: AppProps) {
59-
const [ssoLoginRequired] = useSSOLoginRequired();
60-
const router = useRouter();
61-
const pathWithoutQueryString = router.asPath.split("?")[0].split("#")[0];
62-
63-
const inUnauthedRoute = UNAUTHED_ROUTES.some((r) =>
64-
typeof r === "string" ? r === router.pathname : r.test(router.pathname),
65-
);
66-
// To share state across page transitions we load deployment data in this
67-
// shared App component if the path looks like a deployment.
68-
const inDeployment = router.pathname.startsWith(
69-
"/t/[team]/[project]/[deploymentName]",
70-
);
71-
72-
const [initialData] = useInitialData();
73-
7459
useRouterProgress();
7560

76-
useDashboardVersion();
77-
7861
return (
7962
<>
8063
<Head>
@@ -89,82 +72,7 @@ export default function App({ Component, pageProps }: AppProps) {
8972
<ThemeConsumer />
9073
<MaybeLaunchDarklyProvider>
9174
<LaunchDarklyConsumer>
92-
<>
93-
<RefreshSession />
94-
<SentryUserProvider>
95-
<ErrorBoundary fallback={Fallback}>
96-
<SWRConfig
97-
value={{
98-
...swrConfig(),
99-
fallback: { initialData },
100-
}}
101-
>
102-
<ToastContainer />
103-
104-
{inUnauthedRoute ? (
105-
<Component {...pageProps} />
106-
) : (
107-
<div className="flex h-screen flex-col">
108-
<CommandPalette />
109-
<DashboardHeader />
110-
{!!ssoLoginRequired &&
111-
ssoLoginRequired === router.query.team ? (
112-
<div className="flex h-full w-full items-center justify-center">
113-
<Sheet className="flex max-w-prose flex-col gap-4">
114-
<div className="flex items-center gap-2">
115-
<LockClosedIcon className="size-8" />
116-
<h3>Single Sign-On Login Required</h3>
117-
</div>
118-
<span className="flex flex-col gap-2">
119-
<p>
120-
This team requires you to log in with
121-
Single Sign-On to access it.
122-
</p>
123-
<p>
124-
You may log out and log back in through
125-
your Single Sign-On provider, or switch
126-
teams by using the selector on the top
127-
of this page.
128-
</p>
129-
</span>
130-
<Button
131-
className="ml-auto w-fit"
132-
href="/api/auth/logout"
133-
icon={<ExitIcon />}
134-
>
135-
Log Out
136-
</Button>
137-
</Sheet>
138-
</div>
139-
) : inDeployment ? (
140-
<DeploymentInfoProvider>
141-
<MaybeDeploymentApiProvider>
142-
<CurrentDeploymentDashboardLayout>
143-
<ErrorBoundary
144-
fallback={Fallback}
145-
key={pathWithoutQueryString}
146-
>
147-
<Component {...pageProps} />
148-
</ErrorBoundary>
149-
</CurrentDeploymentDashboardLayout>
150-
</MaybeDeploymentApiProvider>
151-
</DeploymentInfoProvider>
152-
) : (
153-
<DashboardLayout>
154-
<ErrorBoundary
155-
fallback={Fallback}
156-
key={pathWithoutQueryString}
157-
>
158-
<Component {...pageProps} />
159-
</ErrorBoundary>
160-
</DashboardLayout>
161-
)}
162-
</div>
163-
)}
164-
</SWRConfig>
165-
</ErrorBoundary>
166-
</SentryUserProvider>
167-
</>
75+
<AppInner Component={Component} pageProps={pageProps} />
16876
</LaunchDarklyConsumer>
16977
</MaybeLaunchDarklyProvider>
17078
</ThemeProvider>
@@ -174,3 +82,101 @@ export default function App({ Component, pageProps }: AppProps) {
17482
</>
17583
);
17684
}
85+
86+
function AppInner({ Component, pageProps }: Omit<AppProps, "router">) {
87+
const router = useRouter();
88+
89+
const [ssoLoginRequired] = useSSOLoginRequired();
90+
const pathWithoutQueryString = router.asPath.split("?")[0].split("#")[0];
91+
92+
const inUnauthedRoute = UNAUTHED_ROUTES.some((r) =>
93+
typeof r === "string" ? r === router.pathname : r.test(router.pathname),
94+
);
95+
// To share state across page transitions we load deployment data in this
96+
// shared App component if the path looks like a deployment.
97+
const inDeployment = router.pathname.startsWith(
98+
"/t/[team]/[project]/[deploymentName]",
99+
);
100+
101+
const [initialData] = useInitialData();
102+
103+
useDashboardVersion();
104+
105+
return (
106+
<>
107+
<RefreshSession />
108+
<SentryUserProvider>
109+
<ErrorBoundary fallback={Fallback}>
110+
<SWRConfig
111+
value={{
112+
...swrConfig(),
113+
fallback: { initialData },
114+
}}
115+
>
116+
<ToastContainer />
117+
118+
{inUnauthedRoute ? (
119+
<Component {...pageProps} />
120+
) : (
121+
<div className="flex h-screen flex-col">
122+
<CommandPalette />
123+
<DashboardHeader />
124+
{!!ssoLoginRequired &&
125+
ssoLoginRequired === router.query.team ? (
126+
<div className="flex h-full w-full items-center justify-center">
127+
<Sheet className="flex max-w-prose flex-col gap-4">
128+
<div className="flex items-center gap-2">
129+
<LockClosedIcon className="size-8" />
130+
<h3>Single Sign-On Login Required</h3>
131+
</div>
132+
<span className="flex flex-col gap-2">
133+
<p>
134+
This team requires you to log in with Single Sign-On
135+
to access it.
136+
</p>
137+
<p>
138+
You may log out and log back in through your Single
139+
Sign-On provider, or switch teams by using the
140+
selector on the top of this page.
141+
</p>
142+
</span>
143+
<Button
144+
className="ml-auto w-fit"
145+
href="/api/auth/logout"
146+
icon={<ExitIcon />}
147+
>
148+
Log Out
149+
</Button>
150+
</Sheet>
151+
</div>
152+
) : inDeployment ? (
153+
<DeploymentInfoProvider>
154+
<MaybeDeploymentApiProvider>
155+
<CurrentDeploymentDashboardLayout>
156+
<ErrorBoundary
157+
fallback={Fallback}
158+
key={pathWithoutQueryString}
159+
>
160+
<Component {...pageProps} />
161+
</ErrorBoundary>
162+
</CurrentDeploymentDashboardLayout>
163+
</MaybeDeploymentApiProvider>
164+
</DeploymentInfoProvider>
165+
) : (
166+
<DashboardLayout>
167+
<ErrorBoundary
168+
fallback={Fallback}
169+
key={pathWithoutQueryString}
170+
>
171+
<Component {...pageProps} />
172+
</ErrorBoundary>
173+
</DashboardLayout>
174+
)}
175+
</div>
176+
)}
177+
</SWRConfig>
178+
</ErrorBoundary>
179+
</SentryUserProvider>
180+
</>
181+
);
182+
}

0 commit comments

Comments
 (0)