diff --git a/apps/dashboard/src/@/analytics/posthog-server.ts b/apps/dashboard/src/@/analytics/posthog-server.ts index 87167f267be..cf6d93fc091 100644 --- a/apps/dashboard/src/@/analytics/posthog-server.ts +++ b/apps/dashboard/src/@/analytics/posthog-server.ts @@ -1,15 +1,16 @@ import "server-only"; +import { unstable_cache } from "next/cache"; import { PostHog } from "posthog-node"; -let posthogServer: PostHog | null = null; +let _posthogClient: PostHog | null = null; function getPostHogServer(): PostHog | null { - if (!posthogServer && process.env.NEXT_PUBLIC_POSTHOG_KEY) { - posthogServer = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + if (!_posthogClient && process.env.NEXT_PUBLIC_POSTHOG_KEY) { + _posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, { host: "https://us.i.posthog.com", }); } - return posthogServer; + return _posthogClient; } /** @@ -17,29 +18,40 @@ function getPostHogServer(): PostHog | null { * @param flagKey - The feature flag key * @param userEmail - The user's email address for filtering */ -export async function isFeatureFlagEnabled( - flagKey: string, - userEmail?: string, -): Promise { - // For localdev environments where Posthog is not running, enable all feature flags. - if (!posthogServer) { - return true; - } +export const isFeatureFlagEnabled = unstable_cache( + async (params: { + flagKey: string; + accountId: string; + email: string | undefined; + }): Promise => { + const posthogClient = getPostHogServer(); + if (!posthogClient) { + console.warn("Posthog client not set"); + return true; + } + + const { flagKey, accountId, email } = params; - try { - const client = getPostHogServer(); - if (client && userEmail) { - const isEnabled = await client.isFeatureEnabled(flagKey, userEmail, { - personProperties: { - email: userEmail, - }, - }); - if (isEnabled !== undefined) { - return isEnabled; + try { + if (posthogClient && accountId) { + const isEnabled = await posthogClient.isFeatureEnabled( + flagKey, + accountId, + { + personProperties: email ? { email } : undefined, + }, + ); + if (isEnabled !== undefined) { + return isEnabled; + } } + } catch (error) { + console.error(`Error checking feature flag ${flagKey}:`, error); } - } catch (error) { - console.error(`Error checking feature flag ${flagKey}:`, error); - } - return false; -} + return false; + }, + ["is-feature-flag-enabled"], + { + revalidate: 3600, // 1 hour + }, +); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx index cd92dbf8c23..3549bab49a8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx @@ -22,8 +22,14 @@ export function ProjectSidebarLayout(props: { layoutPath: string; engineLinkType: "cloud" | "dedicated"; children: React.ReactNode; + isCentralizedWebhooksFeatureFlagEnabled: boolean; }) { - const { layoutPath, engineLinkType, children } = props; + const { + layoutPath, + engineLinkType, + children, + isCentralizedWebhooksFeatureFlagEnabled, + } = props; return ( { + return pathname.startsWith(`${layoutPath}/webhooks`); + }, label: ( Webhooks New diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx index c1f517d3ff2..0e13ff01443 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx @@ -1,4 +1,5 @@ import { redirect } from "next/navigation"; +import { isFeatureFlagEnabled } from "@/analytics/posthog-server"; import { getAuthToken, getAuthTokenWalletAddress } from "@/api/auth-token"; import { getProject, getProjects, type Project } from "@/api/projects"; import { getTeamBySlug, getTeams } from "@/api/team"; @@ -56,10 +57,18 @@ export default async function ProjectLayout(props: { teamId: team.id, }); - const engineLinkType = await getEngineLinkType({ - authToken, - project, - }); + const [engineLinkType, isCentralizedWebhooksFeatureFlagEnabled] = + await Promise.all([ + getEngineLinkType({ + authToken, + project, + }), + isFeatureFlagEnabled({ + flagKey: "centralized-webhooks", + accountId: account.id, + email: account.email, + }), + ]); const isStaffMode = !teams.some((t) => t.slug === team.slug); @@ -81,6 +90,9 @@ export default async function ProjectLayout(props: { {props.children} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/loading.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/loading.tsx new file mode 100644 index 00000000000..0528bd15ae9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/loading.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; + +export default function Loading() { + return ; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx index 91f0e4aa5b4..9c23895b514 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx @@ -1,10 +1,12 @@ -import { notFound } from "next/navigation"; +import { notFound, redirect } from "next/navigation"; import { ResponsiveSearchParamsProvider } from "responsive-rsc"; +import { isFeatureFlagEnabled } from "@/analytics/posthog-server"; import { getWebhookLatency, getWebhookRequests } from "@/api/analytics"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/projects"; import { getWebhookConfigs } from "@/api/webhook-configs"; import { getFiltersFromSearchParams } from "@/lib/time"; +import { getValidAccount } from "../../../../../../account/settings/getAccount"; import { WebhooksAnalytics } from "./components/WebhooksAnalytics"; export default async function WebhooksAnalyticsPage(props: { @@ -16,14 +18,35 @@ export default async function WebhooksAnalyticsPage(props: { webhook?: string | undefined | string[]; }>; }) { - const [authToken, params] = await Promise.all([getAuthToken(), props.params]); + const [authToken, params, account] = await Promise.all([ + getAuthToken(), + props.params, + getValidAccount(), + ]); + + if (!account || !authToken) { + notFound(); + } - const project = await getProject(params.team_slug, params.project_slug); + const [isFeatureEnabled, project] = await Promise.all([ + isFeatureFlagEnabled({ + flagKey: "centralized-webhooks", + accountId: account.id, + email: account.email, + }), + getProject(params.team_slug, params.project_slug), + ]); - if (!project || !authToken) { + if (!project) { notFound(); } + if (!isFeatureEnabled) { + redirect( + `/team/${params.team_slug}/${params.project_slug}/webhooks/contracts`, + ); + } + const searchParams = await props.searchParams; const { range, interval } = getFiltersFromSearchParams({ defaultRange: "last-7", diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/contracts/loading.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/contracts/loading.tsx new file mode 100644 index 00000000000..0528bd15ae9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/contracts/loading.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; + +export default function Loading() { + return ; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx index 43cfed57ddb..852acc1ffa1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx @@ -10,10 +10,11 @@ export default async function WebhooksLayout(props: { }>; }) { const account = await getValidAccount(); - const isFeatureEnabled = await isFeatureFlagEnabled( - "webhook-analytics-tab", - account.email, - ); + const isFeatureEnabled = await isFeatureFlagEnabled({ + flagKey: "centralized-webhooks", + accountId: account.id, + email: account.email, + }); const params = await props.params; return ( @@ -29,33 +30,46 @@ export default async function WebhooksLayout(props: { - + {isFeatureEnabled ? ( + + ) : ( + + )} +
{props.children} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/loading.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/loading.tsx new file mode 100644 index 00000000000..0528bd15ae9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/loading.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; + +export default function Loading() { + return ; +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx index 653389e2c14..68e75ecbc7f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx @@ -1,37 +1,49 @@ -import { notFound } from "next/navigation"; +import { notFound, redirect } from "next/navigation"; +import { isFeatureFlagEnabled } from "@/analytics/posthog-server"; +import { getWebhookSummary } from "@/api/analytics"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/projects"; -import { getWebhookSummary } from "../../../../../../../@/api/analytics"; -import { - getAvailableTopics, - getWebhookConfigs, -} from "../../../../../../../@/api/webhook-configs"; +import { getAvailableTopics, getWebhookConfigs } from "@/api/webhook-configs"; +import { getValidAccount } from "../../../../../account/settings/getAccount"; import { WebhooksOverview } from "./components/overview"; -export default async function WebhooksPage({ - params, -}: { +export default async function WebhooksPage(props: { params: Promise<{ team_slug: string; project_slug: string }>; }) { - const [authToken, resolvedParams] = await Promise.all([ + const [authToken, params, account] = await Promise.all([ getAuthToken(), - params, + props.params, + getValidAccount(), ]); - const project = await getProject( - resolvedParams.team_slug, - resolvedParams.project_slug, - ); + if (!account || !authToken) { + notFound(); + } + + const [isFeatureEnabled, project] = await Promise.all([ + isFeatureFlagEnabled({ + flagKey: "centralized-webhooks", + accountId: account.id, + email: account.email, + }), + getProject(params.team_slug, params.project_slug), + ]); if (!project || !authToken) { notFound(); } + if (!isFeatureEnabled) { + redirect( + `/team/${params.team_slug}/${params.project_slug}/webhooks/contracts`, + ); + } + // Fetch webhook configs and topics in parallel const [webhookConfigsResult, topicsResult] = await Promise.all([ getWebhookConfigs({ - projectIdOrSlug: resolvedParams.project_slug, - teamIdOrSlug: resolvedParams.team_slug, + projectIdOrSlug: params.project_slug, + teamIdOrSlug: params.team_slug, }), getAvailableTopics(), ]); @@ -75,9 +87,9 @@ export default async function WebhooksPage({ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/universal-bridge/loading.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/universal-bridge/loading.tsx new file mode 100644 index 00000000000..0528bd15ae9 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/universal-bridge/loading.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; + +export default function Loading() { + return ; +}