From 47ea9a7b314e7d5345d0b6bdc8cb1df79a86320c Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 17 Jan 2025 21:26:05 +0100 Subject: [PATCH] Undo reverts (#5507) * Reapply "MNTOR-3814 - use context to fetch experiment data from Cirrus (#5440)" (#5505) This reverts commit 80e717803e382cfdbe6d752d8a0433c45534939f. * Revert "handle nimbus user id not set (#5503)" This reverts commit 4de104f3ef3f61bb040eaedcfb92c7335c6bd148. --- .../dashboard/[[...slug]]/page.tsx | 2 +- .../(dashboard)/settings/[[...slug]]/page.tsx | 2 +- .../user/welcome/[[...slug]]/page.tsx | 2 +- .../(redesign)/(public)/page.tsx | 20 +- .../api/v1/user/welcome-scan/create/route.ts | 2 +- src/app/functions/server/getExperiments.ts | 11 +- src/app/hooks/useGlean.ts | 27 +- src/app/layout.tsx | 30 +- src/contextProviders/experiments.tsx | 31 ++ src/middleware.ts | 8 + src/telemetry/metrics.yaml | 308 ++++++++++++++++++ 11 files changed, 420 insertions(+), 23 deletions(-) create mode 100644 src/contextProviders/experiments.tsx diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx index 398d533014b..71237978a95 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/dashboard/[[...slug]]/page.tsx @@ -167,7 +167,7 @@ export default async function DashboardPage({ params, searchParams }: Props) { totalNumberOfPerformedScans={profileStats?.total} isNewUser={isNewUser} elapsedTimeInDaysSinceInitialScan={elapsedTimeInDaysSinceInitialScan} - experimentData={experimentData} + experimentData={experimentData["Features"]} activeTab={activeTab} hasFirstMonitoringScan={hasFirstMonitoringScan} signInCount={signInCount} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/[[...slug]]/page.tsx index 3ccd74f268f..a90f861e91b 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/(dashboard)/settings/[[...slug]]/page.tsx @@ -125,7 +125,7 @@ export default async function SettingsPage({ params, searchParams }: Props) { yearlySubscriptionUrl={`${yearlySubscriptionUrl}&${additionalSubplatParams.toString()}`} subscriptionBillingAmount={getSubscriptionBillingAmount()} enabledFeatureFlags={enabledFeatureFlags} - experimentData={experimentData} + experimentData={experimentData["Features"]} lastScanDate={lastOneRepScan?.created_at} isMonthlySubscriber={isMonthlySubscriber} activeTab={activeTab} diff --git a/src/app/(proper_react)/(redesign)/(authenticated)/user/welcome/[[...slug]]/page.tsx b/src/app/(proper_react)/(redesign)/(authenticated)/user/welcome/[[...slug]]/page.tsx index 409b60ecd59..f1c73fce090 100644 --- a/src/app/(proper_react)/(redesign)/(authenticated)/user/welcome/[[...slug]]/page.tsx +++ b/src/app/(proper_react)/(redesign)/(authenticated)/user/welcome/[[...slug]]/page.tsx @@ -77,7 +77,7 @@ export default async function Onboarding({ params, searchParams }: Props) { breachesTotalCount={allBreachesCount} stepId={firstSlug === FreeScanSlug ? "enterInfo" : "getStarted"} previousRoute={previousRoute} - experimentData={experimentData} + experimentData={experimentData["Features"]} /> ); } diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index bf8090cac58..92e0960be44 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -59,14 +59,14 @@ export default async function Page({ searchParams }: Props) { oneRepActivations > monthlySubscribersQuota; return ( {enabledFeatureFlags.includes("LandingPageRedesign") && - experimentData["landing-page-redesign-plus-eligible-experiment"] - .enabled && - experimentData["landing-page-redesign-plus-eligible-experiment"] - .variant === "redesign" ? ( + experimentData["Features"][ + "landing-page-redesign-plus-eligible-experiment" + ].enabled && + experimentData["Features"][ + "landing-page-redesign-plus-eligible-experiment" + ].variant === "redesign" ? ( ) : ( )} diff --git a/src/app/api/v1/user/welcome-scan/create/route.ts b/src/app/api/v1/user/welcome-scan/create/route.ts index 9ca69454ce4..856e6be710b 100644 --- a/src/app/api/v1/user/welcome-scan/create/route.ts +++ b/src/app/api/v1/user/welcome-scan/create/route.ts @@ -98,7 +98,7 @@ export async function POST( previewMode: searchParams.get("nimbus_preview") === "true", }); const optionalInfoExperimentData = - experimentData["welcome-scan-optional-info"]; + experimentData["Features"]["welcome-scan-optional-info"]; const profileData: CreateProfileRequest = { first_name: firstName, diff --git a/src/app/functions/server/getExperiments.ts b/src/app/functions/server/getExperiments.ts index 91b7868e841..205a036af4f 100644 --- a/src/app/functions/server/getExperiments.ts +++ b/src/app/functions/server/getExperiments.ts @@ -28,9 +28,9 @@ export async function getExperiments(params: { locale: string; countryCode: string; previewMode: boolean; -}): Promise { +}): Promise { if (["local"].includes(process.env.APP_ENV ?? "local")) { - return localExperimentData["Features"]; + return localExperimentData; } if (!process.env.NIMBUS_SIDECAR_URL) { @@ -87,10 +87,7 @@ export async function getExperiments(params: { experimentData = json; } - return ( - (experimentData as ExperimentData["Features"]) ?? - defaultExperimentData["Features"] - ); + return (experimentData as ExperimentData) ?? defaultExperimentData; } catch (ex) { logger.error("Could not connect to Cirrus", { serverUrl, @@ -99,6 +96,6 @@ export async function getExperiments(params: { params, }); captureException(ex); - return defaultExperimentData["Features"]; + return defaultExperimentData; } } diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index 8e5af988a7d..4a966ddab58 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -9,9 +9,14 @@ import EventMetricType from "@mozilla/glean/private/metrics/event"; import type { GleanMetricMap } from "../../telemetry/generated/_map"; import { useSession } from "next-auth/react"; import { hasPremium } from "../functions/universal/user"; +import { useExperiments } from "../../contextProviders/experiments"; export const useGlean = () => { const session = useSession(); + const experimentData = useExperiments(); + // Telemetry recording is mocked in our unit tests, therefore we + // do not have test coverage for this method. + /* c8 ignore start */ const isPremiumUser = hasPremium(session.data?.user); const record = useCallback( async < @@ -36,10 +41,30 @@ export const useGlean = () => { ? "Plus" : "Free"; + // Record the `nimbus_*` keys on all events. + // `nimbus_*` is set on every metric, but it's too much work for TypeScript + // to infer that — hence the type assertion. + if (experimentData) { + (data as GleanMetricMap["button"]["click"]).nimbus_user_id = + experimentData["Enrollments"]["nimbus_user_id"]; + (data as GleanMetricMap["button"]["click"]).nimbus_app_id = + experimentData["Enrollments"]["app_id"]; + (data as GleanMetricMap["button"]["click"]).nimbus_experiment = + experimentData["Enrollments"]["experiment"]; + (data as GleanMetricMap["button"]["click"]).nimbus_branch = + experimentData["Enrollments"]["branch"]; + (data as GleanMetricMap["button"]["click"]).nimbus_experiment_type = + experimentData["Enrollments"]["experiment_type"]; + (data as GleanMetricMap["button"]["click"]).nimbus_is_preview = + experimentData["Enrollments"]["is_preview"].toString(); + } else { + console.warn("No experiment data available for Glean"); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any mod[event].record(data as any); }, - [isPremiumUser], + [isPremiumUser, experimentData], ); /* c8 ignore end */ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 653c2b6bda7..223af998da0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,6 +17,10 @@ import { GoogleAnalyticsWorkaround } from "./components/client/GoogleAnalyticsWo import StripeScript from "./components/client/StripeScript"; import { GleanScript } from "./components/client/GleanScript"; import { getExperimentationId } from "./functions/server/getExperimentationId"; +import { getExperiments } from "./functions/server/getExperiments"; +import { getCountryCode } from "./functions/server/getCountryCode"; +import { ExperimentsProvider } from "../contextProviders/experiments"; +import * as Sentry from "@sentry/nextjs"; const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); @@ -54,6 +58,26 @@ export default async function RootLayout({ const nonce = headers().get("x-nonce") ?? ""; const currentLocale = getLocale(getL10nBundles()); const session = await getServerSession(); + const headersList = headers(); + const countryCode = getCountryCode(headersList); + + // Check for Nimbus preview mode. Note that this requires a full page reload + // to activate: https://nextjs.org/docs/app/api-reference/file-conventions/layout#caveats + const nimbusPreviewMode = headers().get("x-nimbus-preview-mode"); + const experimentationId = getExperimentationId(session?.user ?? null); + const experimentData = await getExperiments({ + experimentationId: experimentationId, + countryCode: countryCode, + locale: currentLocale, + previewMode: nimbusPreviewMode === "true", + }); + + const nimbus_user_id = experimentData["Enrollments"].nimbus_user_id; + if (nimbus_user_id !== experimentationId) { + Sentry.captureMessage( + `Nimbus user ID from Cirrus: [${nimbus_user_id}] did not match experimentationId: [${experimentationId}]`, + ); + } return ( @@ -64,12 +88,14 @@ export default async function RootLayout({ data-ga4-measurement-id={CONST_GA4_MEASUREMENT_ID} data-node-env={process.env.NODE_ENV} > - {children} + + {children} + {headers().get("DNT") !== "1" && ( (null); + +export const ExperimentsProvider = ({ + children, + experimentData, +}: ExperimentsProviderProps) => { + return ( + + {children} + + ); +}; + +export const useExperiments = () => { + const context = useContext(ExperimentsContext); + return context; +}; diff --git a/src/middleware.ts b/src/middleware.ts index 81771ac3f9a..b713ef80b39 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -29,6 +29,14 @@ export function middleware(request: NextRequest) { existingExperimentationId?.value ?? `guest-${crypto.randomUUID()}`; requestHeaders.set("x-experimentation-id", experimentationId); + // Check for Nimbus preview mode. Note that this requires a full page reload + // to activate: https://nextjs.org/docs/app/api-reference/file-conventions/layout#caveats + const nimbusPreviewMode = request.nextUrl.searchParams.get("nimbus_preview"); + requestHeaders.set( + "x-nimbus-preview-mode", + nimbusPreviewMode === "true" ? "true" : "false", + ); + const response = NextResponse.next({ request: { headers: requestHeaders, diff --git a/src/telemetry/metrics.yaml b/src/telemetry/metrics.yaml index 322d759f7f4..e7755eb247d 100644 --- a/src/telemetry/metrics.yaml +++ b/src/telemetry/metrics.yaml @@ -56,6 +56,24 @@ page: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string dashboard: view: @@ -102,6 +120,24 @@ dashboard: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string popup: view: @@ -135,6 +171,24 @@ popup: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string exit: type: event @@ -167,6 +221,24 @@ popup: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string banner: view: @@ -200,6 +272,24 @@ banner: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string button: click: @@ -233,6 +323,24 @@ button: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string field: focus: @@ -266,6 +374,24 @@ field: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string link: click: @@ -299,6 +425,24 @@ link: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string upgrade_intent: click: @@ -332,6 +476,24 @@ upgrade_intent: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string success: type: event @@ -362,6 +524,24 @@ upgrade_intent: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string expand: click: @@ -395,6 +575,24 @@ expand: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string collapse: click: @@ -428,6 +626,24 @@ collapse: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string cta_button: click: @@ -461,6 +677,24 @@ cta_button: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string view: type: event @@ -493,6 +727,24 @@ cta_button: plan_tier: description: Which tier of plan the user is on [Free, Plus] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string csat_survey: view: @@ -526,6 +778,25 @@ csat_survey: automated_removal_period: description: The time period since the first automated removal for the user. [initial, 3-months, 6-months, 12-months] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string + dismiss: type: event description: | @@ -557,6 +828,25 @@ csat_survey: automated_removal_period: description: The time period since the first automated removal for the user. [initial, 3-months, 6-months, 12-months] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string + click: type: event description: | @@ -591,3 +881,21 @@ csat_survey: automated_removal_period: description: The time period since the first automated removal for the user. [initial, 3-months, 6-months, 12-months] type: string + nimbus_user_id: + description: Nimbus user ID + type: string + nimbus_app_id: + description: Nimbus application ID + type: string + nimbus_experiment: + description: Nimbus experiment name + type: string + nimbus_branch: + description: Nimbus branch + type: string + nimbus_experiment_type: + description: Nimbus experiment type + type: string + nimbus_is_preview: + description: Nimbus preview mode enabled + type: string