Skip to content

Commit aeba4d4

Browse files
feat(web): gate app builder behind PostHog feature flag (#3059)
* feat(web): gate app builder behind feature flag Introduce the `app-builder-feature` flag to control access to the App Builder. The feature is now hidden in the sidebars and returns a 404 not found page if the flag is disabled, unless the application is running in development mode. * fix(web): check app builder feature flag by user id instead of organization id --------- Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
1 parent 3f29bec commit aeba4d4

6 files changed

Lines changed: 66 additions & 14 deletions

File tree

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { notFound } from 'next/navigation';
12
import { AppBuilderPage } from '@/components/app-builder/AppBuilderPage';
3+
import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags';
24
import { getUserFromAuthOrRedirect } from '@/lib/user.server';
35

46
type Props = {
@@ -7,7 +9,16 @@ type Props = {
79

810
export default async function ProjectPage({ params }: Props) {
911
const { projectId } = await params;
10-
await getUserFromAuthOrRedirect(`/users/sign_in?callbackPath=/app-builder/${projectId}`);
12+
const user = await getUserFromAuthOrRedirect(
13+
`/users/sign_in?callbackPath=/app-builder/${projectId}`
14+
);
15+
16+
const isAppBuilderEnabled = await isFeatureFlagEnabled('app-builder-feature', user.id);
17+
const isDevelopment = process.env.NODE_ENV === 'development';
18+
19+
if (!isAppBuilderEnabled && !isDevelopment) {
20+
return notFound();
21+
}
1122

1223
return <AppBuilderPage organizationId={undefined} projectId={projectId} />;
1324
}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
import { notFound } from 'next/navigation';
12
import { AppBuilderPage } from '@/components/app-builder/AppBuilderPage';
3+
import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags';
24
import { getUserFromAuthOrRedirect } from '@/lib/user.server';
35

46
export default async function CreatePage() {
5-
await getUserFromAuthOrRedirect('/users/sign_in?callbackPath=/app-builder');
7+
const user = await getUserFromAuthOrRedirect('/users/sign_in?callbackPath=/app-builder');
8+
9+
const isAppBuilderEnabled = await isFeatureFlagEnabled('app-builder-feature', user.id);
10+
const isDevelopment = process.env.NODE_ENV === 'development';
11+
12+
if (!isAppBuilderEnabled && !isDevelopment) {
13+
return notFound();
14+
}
615

716
return <AppBuilderPage organizationId={undefined} projectId={undefined} />;
817
}

apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export default function OrganizationAppSidebar({
5757

5858
// Feature flags
5959
const isAutoTriageFeatureEnabled = useFeatureFlagEnabled('auto-triage-feature');
60+
const isAppBuilderEnabled = useFeatureFlagEnabled('app-builder-feature');
6061
const isDevelopment = process.env.NODE_ENV === 'development';
6162

6263
// Get current organization role and data
@@ -159,11 +160,15 @@ export default function OrganizationAppSidebar({
159160
url: string;
160161
className?: string;
161162
}> = [
162-
{
163-
title: 'App Builder',
164-
icon: Plus,
165-
url: `/organizations/${organizationId}/app-builder`,
166-
},
163+
...(isAppBuilderEnabled || isDevelopment
164+
? [
165+
{
166+
title: 'App Builder',
167+
icon: Plus,
168+
url: `/organizations/${organizationId}/app-builder`,
169+
},
170+
]
171+
: []),
167172
{
168173
title: 'Cloud Agent',
169174
icon: Cloud,

apps/web/src/app/(app)/components/PersonalAppSidebar.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default function PersonalAppSidebar(props: React.ComponentProps<typeof Si
5050
// Feature flags
5151
const isAutoTriageFeatureEnabled = useFeatureFlagEnabled('auto-triage-feature');
5252
const isGastownEnabled = useFeatureFlagEnabled('gastown-access');
53+
const isAppBuilderEnabled = useFeatureFlagEnabled('app-builder-feature');
5354
const isDevelopment = process.env.NODE_ENV === 'development';
5455

5556
// Dashboard group
@@ -112,11 +113,15 @@ export default function PersonalAppSidebar(props: React.ComponentProps<typeof Si
112113
url: string;
113114
className?: string;
114115
}> = [
115-
{
116-
title: 'App Builder',
117-
icon: Plus,
118-
url: '/app-builder',
119-
},
116+
...(isAppBuilderEnabled || isDevelopment
117+
? [
118+
{
119+
title: 'App Builder',
120+
icon: Plus,
121+
url: '/app-builder',
122+
},
123+
]
124+
: []),
120125
{
121126
title: 'Cloud Agent',
122127
icon: Cloud,

apps/web/src/app/(app)/organizations/[id]/app-builder/[projectId]/page.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { redirect } from 'next/navigation';
1+
import { notFound, redirect } from 'next/navigation';
22
import { AppBuilderPage } from '@/components/app-builder/AppBuilderPage';
33
import { getAuthorizedOrgContext } from '@/lib/organizations/organization-auth';
4+
import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags';
45
import { signInUrlWithCallbackPath } from '@/lib/user.server';
56

67
type Props = {
@@ -19,5 +20,15 @@ export default async function OrgAppBuilderProjectPage({ params }: Props) {
1920
redirect('/profile');
2021
}
2122

23+
const isAppBuilderEnabled = await isFeatureFlagEnabled(
24+
'app-builder-feature',
25+
result.data.user.id
26+
);
27+
const isDevelopment = process.env.NODE_ENV === 'development';
28+
29+
if (!isAppBuilderEnabled && !isDevelopment) {
30+
return notFound();
31+
}
32+
2233
return <AppBuilderPage organizationId={organizationId} projectId={projectId} />;
2334
}

apps/web/src/app/(app)/organizations/[id]/app-builder/page.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { redirect } from 'next/navigation';
1+
import { notFound, redirect } from 'next/navigation';
22
import { AppBuilderPage } from '@/components/app-builder/AppBuilderPage';
33
import { getAuthorizedOrgContext } from '@/lib/organizations/organization-auth';
4+
import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags';
45
import { signInUrlWithCallbackPath } from '@/lib/user.server';
56

67
type Props = {
@@ -19,5 +20,15 @@ export default async function OrgAppBuilderPage({ params }: Props) {
1920
redirect('/profile');
2021
}
2122

23+
const isAppBuilderEnabled = await isFeatureFlagEnabled(
24+
'app-builder-feature',
25+
result.data.user.id
26+
);
27+
const isDevelopment = process.env.NODE_ENV === 'development';
28+
29+
if (!isAppBuilderEnabled && !isDevelopment) {
30+
return notFound();
31+
}
32+
2233
return <AppBuilderPage organizationId={organizationId} projectId={undefined} />;
2334
}

0 commit comments

Comments
 (0)