From 6b2f4d4dde2855d9db5c5b0a68910fd168433d3f Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Thu, 19 Dec 2024 21:30:49 -0800 Subject: [PATCH 1/7] attempt to use context for experiment data and glean --- .../dashboard/[[...slug]]/page.tsx | 47 +-- .../(dashboard)/settings/[[...slug]]/page.tsx | 39 ++- .../user/welcome/[[...slug]]/page.tsx | 19 +- .../(redesign)/(public)/page.tsx | 51 +-- .../api/v1/user/welcome-scan/create/route.ts | 2 +- src/app/functions/server/getExperiments.ts | 11 +- src/app/hooks/useGlean.ts | 27 ++ src/contextProviders/experiments.tsx | 28 ++ src/telemetry/metrics.yaml | 308 ++++++++++++++++++ 9 files changed, 453 insertions(+), 79 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..152ffc3239e 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 @@ -43,6 +43,7 @@ import { getExperiments } from "../../../../../../../functions/server/getExperim import { getLocale } from "../../../../../../../functions/universal/getLocale"; import { getL10n } from "../../../../../../../functions/l10n/serverComponents"; import { getDataBrokerRemovalTimeEstimates } from "../../../../../../../functions/server/getDataBrokerRemovalTimeEstimates"; +import { ExperimentsProvider } from "../../../../../../../../contextProviders/experiments"; const dashboardTabSlugs = ["action-needed", "fixed"]; @@ -152,27 +153,29 @@ export default async function DashboardPage({ params, searchParams }: Props) { const signInCount = await getSignInCount(session.user.subscriber.id); return ( - + + + ); } 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..86b98b5e4e7 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 @@ -27,6 +27,7 @@ import { checkSession } from "../../../../../../../functions/server/checkSession import { checkUserHasMonthlySubscription } from "../../../../../../../functions/server/user"; import { getEmailPreferenceForPrimaryEmail } from "../../../../../../../../db/tables/subscriber_email_preferences"; import { CONST_SETTINGS_TAB_SLUGS } from "../../../../../../../../constants"; +import { ExperimentsProvider } from "../../../../../../../../contextProviders/experiments"; type Props = { params: { @@ -112,23 +113,25 @@ export default async function SettingsPage({ params, searchParams }: Props) { ); return ( - + + + ); } 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..20972e53e77 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 @@ -16,6 +16,7 @@ import { getExperimentationId } from "../../../../../../functions/server/getExpe import { getExperiments } from "../../../../../../functions/server/getExperiments"; import { getLocale } from "../../../../../../functions/universal/getLocale"; import { getL10n } from "../../../../../../functions/l10n/serverComponents"; +import { ExperimentsProvider } from "../../../../../../../contextProviders/experiments"; const FreeScanSlug = "free-scan"; @@ -71,13 +72,15 @@ export default async function Onboarding({ params, searchParams }: Props) { }); return ( - + + + ); } diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index b05e69286d9..12096430b60 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -21,6 +21,7 @@ import { getExperimentationId } from "../../../functions/server/getExperimentati import { getExperiments } from "../../../functions/server/getExperiments"; import { getLocale } from "../../../functions/universal/getLocale"; import { AccountsMetricsFlowProvider } from "../../../../contextProviders/accounts-metrics-flow"; +import { ExperimentsProvider } from "../../../../contextProviders/experiments"; type Props = { searchParams: { @@ -53,28 +54,32 @@ export default async function Page({ searchParams }: Props) { typeof oneRepActivations === "undefined" || oneRepActivations > monthlySubscribersQuota; return ( - - - + + + + + ); } 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 40f55ffb15f..66dc85ff271 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) { @@ -74,13 +74,10 @@ 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, ex }); captureException(ex); - return defaultExperimentData["Features"]; + return defaultExperimentData; } } diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index e37ba9c9cfc..dbe7a52ff72 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -9,9 +9,11 @@ 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(); const isPremiumUser = hasPremium(session.data?.user); const record = useCallback( @@ -33,10 +35,35 @@ export const useGlean = () => { // Record the `plan_tier` key on all events. // `plan_tier` is set on every metric, but it's too much work for TypeScript // to infer that — hence the type assertion. + // + // TODO can we fix this? It looks like these are only for button->click which is misleading. (data as GleanMetricMap["button"]["click"]).plan_tier = isPremiumUser ? "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. + // + // TODO can we fix this? It looks like these are only for button->click which is misleading. + console.debug({ experimentData }); + 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); }, diff --git a/src/contextProviders/experiments.tsx b/src/contextProviders/experiments.tsx new file mode 100644 index 00000000000..288a873867d --- /dev/null +++ b/src/contextProviders/experiments.tsx @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use client"; + +import { ReactNode, createContext, useContext } from "react"; +import { ExperimentData } from "../telemetry/generated/nimbus/experiments"; + +interface ExperimentsProviderProps { + children: ReactNode; + experimentData: ExperimentData; +} + +export const ExperimentsContext = createContext(null); + +export const ExperimentsProvider = ({ + children, + experimentData, +}: ExperimentsProviderProps) => { + return ( + + {children} + + ); +}; + +export const useExperiments = () => useContext(ExperimentsContext); 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 From f94d1229a7f930b714713fd935c1e54d10c0ca86 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 11:38:16 -0800 Subject: [PATCH 2/7] searchParams is unavaiable in layout, store in x-header instead --- .../dashboard/[[...slug]]/page.tsx | 47 ++++++++--------- .../(dashboard)/settings/[[...slug]]/page.tsx | 39 +++++++------- .../user/welcome/[[...slug]]/page.tsx | 19 +++---- .../(redesign)/(public)/page.tsx | 51 +++++++++---------- src/app/hooks/useGlean.ts | 5 +- src/app/layout.tsx | 22 +++++++- src/contextProviders/experiments.tsx | 9 +++- src/middleware.ts | 8 +++ 8 files changed, 110 insertions(+), 90 deletions(-) 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 152ffc3239e..398d533014b 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 @@ -43,7 +43,6 @@ import { getExperiments } from "../../../../../../../functions/server/getExperim import { getLocale } from "../../../../../../../functions/universal/getLocale"; import { getL10n } from "../../../../../../../functions/l10n/serverComponents"; import { getDataBrokerRemovalTimeEstimates } from "../../../../../../../functions/server/getDataBrokerRemovalTimeEstimates"; -import { ExperimentsProvider } from "../../../../../../../../contextProviders/experiments"; const dashboardTabSlugs = ["action-needed", "fixed"]; @@ -153,29 +152,27 @@ export default async function DashboardPage({ params, searchParams }: Props) { const signInCount = await getSignInCount(session.user.subscriber.id); return ( - - - + ); } 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 86b98b5e4e7..3ccd74f268f 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 @@ -27,7 +27,6 @@ import { checkSession } from "../../../../../../../functions/server/checkSession import { checkUserHasMonthlySubscription } from "../../../../../../../functions/server/user"; import { getEmailPreferenceForPrimaryEmail } from "../../../../../../../../db/tables/subscriber_email_preferences"; import { CONST_SETTINGS_TAB_SLUGS } from "../../../../../../../../constants"; -import { ExperimentsProvider } from "../../../../../../../../contextProviders/experiments"; type Props = { params: { @@ -113,25 +112,23 @@ export default async function SettingsPage({ params, searchParams }: Props) { ); return ( - - - + ); } 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 20972e53e77..409b60ecd59 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 @@ -16,7 +16,6 @@ import { getExperimentationId } from "../../../../../../functions/server/getExpe import { getExperiments } from "../../../../../../functions/server/getExperiments"; import { getLocale } from "../../../../../../functions/universal/getLocale"; import { getL10n } from "../../../../../../functions/l10n/serverComponents"; -import { ExperimentsProvider } from "../../../../../../../contextProviders/experiments"; const FreeScanSlug = "free-scan"; @@ -72,15 +71,13 @@ export default async function Onboarding({ params, searchParams }: Props) { }); return ( - - - + ); } diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index 12096430b60..b05e69286d9 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -21,7 +21,6 @@ import { getExperimentationId } from "../../../functions/server/getExperimentati import { getExperiments } from "../../../functions/server/getExperiments"; import { getLocale } from "../../../functions/universal/getLocale"; import { AccountsMetricsFlowProvider } from "../../../../contextProviders/accounts-metrics-flow"; -import { ExperimentsProvider } from "../../../../contextProviders/experiments"; type Props = { searchParams: { @@ -54,32 +53,28 @@ export default async function Page({ searchParams }: Props) { typeof oneRepActivations === "undefined" || oneRepActivations > monthlySubscribersQuota; return ( - - - - - + + + ); } diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index dbe7a52ff72..bf5e57c8b56 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -28,6 +28,7 @@ export const useGlean = () => { const mod = (await import( `../../telemetry/generated/${eventModule}` )) as Record; + console.debug("Glean useCallback"); // Instead of the specific type definitions we generated in the npm script // `build-glean-types`, Glean takes a non-specific "ExtraArgs" type as // parameter to `record`. @@ -46,7 +47,7 @@ export const useGlean = () => { // to infer that — hence the type assertion. // // TODO can we fix this? It looks like these are only for button->click which is misleading. - console.debug({ experimentData }); + console.debug({ event, experimentData }); if (experimentData) { (data as GleanMetricMap["button"]["click"]).nimbus_user_id = experimentData["Enrollments"]["nimbus_user_id"]; @@ -67,7 +68,7 @@ export const useGlean = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any mod[event].record(data as any); }, - [isPremiumUser], + [isPremiumUser, experimentData], ); return record; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 653c2b6bda7..fa134ec5763 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,6 +17,9 @@ 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"; const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); @@ -54,6 +57,19 @@ 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", + }); return ( @@ -64,12 +80,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" && ( { + console.debug("ExperimentsProvider:", { + children, + experimentData: JSON.stringify(experimentData), + }); return ( {children} @@ -25,4 +29,7 @@ export const ExperimentsProvider = ({ ); }; -export const useExperiments = () => useContext(ExperimentsContext); +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, From fd6e2c76ccdbf3c9067eac4a14fb43c128ab7840 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 11:44:56 -0800 Subject: [PATCH 3/7] pass Features key --- .../user/(dashboard)/settings/[[...slug]]/page.tsx | 2 +- .../(authenticated)/user/welcome/[[...slug]]/page.tsx | 2 +- src/app/(proper_react)/(redesign)/(public)/page.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) 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 b05e69286d9..ed109dd3c01 100644 --- a/src/app/(proper_react)/(redesign)/(public)/page.tsx +++ b/src/app/(proper_react)/(redesign)/(public)/page.tsx @@ -54,14 +54,14 @@ export default async function Page({ searchParams }: Props) { oneRepActivations > monthlySubscribersQuota; return ( ); From 43b1d9ceabac8e8843509e3713675273666ed6a6 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 11:50:50 -0800 Subject: [PATCH 4/7] Remove TODOs per flozia --- src/app/hooks/useGlean.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index bf5e57c8b56..d7fe185b951 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -36,8 +36,6 @@ export const useGlean = () => { // Record the `plan_tier` key on all events. // `plan_tier` is set on every metric, but it's too much work for TypeScript // to infer that — hence the type assertion. - // - // TODO can we fix this? It looks like these are only for button->click which is misleading. (data as GleanMetricMap["button"]["click"]).plan_tier = isPremiumUser ? "Plus" : "Free"; @@ -45,8 +43,6 @@ export const useGlean = () => { // 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. - // - // TODO can we fix this? It looks like these are only for button->click which is misleading. console.debug({ event, experimentData }); if (experimentData) { (data as GleanMetricMap["button"]["click"]).nimbus_user_id = From 4a7bd55f548776d4ae5570dec30cbdaa1570fdc8 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 11:52:28 -0800 Subject: [PATCH 5/7] remove debugging --- src/app/hooks/useGlean.ts | 2 -- src/contextProviders/experiments.tsx | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index d7fe185b951..cff3d6c8c0c 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -28,7 +28,6 @@ export const useGlean = () => { const mod = (await import( `../../telemetry/generated/${eventModule}` )) as Record; - console.debug("Glean useCallback"); // Instead of the specific type definitions we generated in the npm script // `build-glean-types`, Glean takes a non-specific "ExtraArgs" type as // parameter to `record`. @@ -43,7 +42,6 @@ export const useGlean = () => { // 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. - console.debug({ event, experimentData }); if (experimentData) { (data as GleanMetricMap["button"]["click"]).nimbus_user_id = experimentData["Enrollments"]["nimbus_user_id"]; diff --git a/src/contextProviders/experiments.tsx b/src/contextProviders/experiments.tsx index ae74a294cb9..0e9e5dcd35f 100644 --- a/src/contextProviders/experiments.tsx +++ b/src/contextProviders/experiments.tsx @@ -18,10 +18,6 @@ export const ExperimentsProvider = ({ children, experimentData, }: ExperimentsProviderProps) => { - console.debug("ExperimentsProvider:", { - children, - experimentData: JSON.stringify(experimentData), - }); return ( {children} From d3500a70bbe4395c7771fb43094ecbc5c72d4a67 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 12:11:58 -0800 Subject: [PATCH 6/7] use features key for enrollmentdata --- .../user/(dashboard)/dashboard/[[...slug]]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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} From 26a76d1ab7276b42963d361c58f79b4635c15ea1 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 20 Dec 2024 12:14:13 -0800 Subject: [PATCH 7/7] Report to sentry if experimentationId does not match --- src/app/layout.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fa134ec5763..223af998da0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -20,6 +20,7 @@ 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" }); @@ -71,6 +72,13 @@ export default async function RootLayout({ 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 (