From d78b7445f000448488c63a0089d41008e83a25bf Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Thu, 9 Jan 2025 16:57:58 -0800 Subject: [PATCH 1/8] feat: check/apply churn coupon --- src/app/functions/server/applyCoupon.ts | 99 +++++++++++++++++++++---- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/src/app/functions/server/applyCoupon.ts b/src/app/functions/server/applyCoupon.ts index 9731976260a..626d411f7c6 100644 --- a/src/app/functions/server/applyCoupon.ts +++ b/src/app/functions/server/applyCoupon.ts @@ -12,16 +12,90 @@ import { import { applyCoupon } from "../../../utils/fxa"; export async function applyCurrentCouponCode(subscriber: SubscriberRow) { - logger.info("fxa_apply_coupon_code", { + logger.info("fxa_apply_current_coupon_code", { subscriber: subscriber.id, }); const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; if (!currentCouponCode) { logger.error( - "fxa_apply_coupon_code_failed", + "fxa_apply_current_coupon_code_failed", "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", ); + return { + success: false, + message: "Current coupon code not set", + }; + } + return await applyCouponWithCode(subscriber, currentCouponCode); +} +export async function applyChurnCouponCode(subscriber: SubscriberRow) { + logger.info("fxa_apply_churn_coupon_code", { + subscriber: subscriber.id, + }); + + const churnEmailCouponCode = process.env.CHURN_EMAIL_COUPON_CODE_ID; + if (!churnEmailCouponCode) { + logger.error( + "fxa_apply_churn_coupon_code_failed", + "Coupon code ID is not set. Please set the env var: CHURN_EMAIL_COUPON_CODE_ID", + ); + return { + success: false, + message: "Churn coupon code not set", + }; + } + return await applyCouponWithCode(subscriber, churnEmailCouponCode); +} + +export async function checkCurrentCouponCode( + subscriber: SubscriberRow | SerializedSubscriber, +) { + logger.info("fxa_check_current_coupon", { + subscriber: subscriber.id, + }); + + const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; + if (!currentCouponCode) { + logger.error("fxa_check_current_coupon_failed", { + exception: + "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + }); + return { + success: false, + }; + } + + return await checkCouponWithCode(subscriber, currentCouponCode); +} + +export async function checkChurnCouponCode( + subscriber: SubscriberRow | SerializedSubscriber, +) { + logger.info("fxa_check_churn_coupon", { + subscriber: subscriber.id, + }); + + const currentCouponCode = process.env.CHURN_EMAIL_COUPON_CODE_ID; + if (!currentCouponCode) { + logger.error("fxa_check_churn_coupon_failed", { + exception: + "Coupon code ID is not set. Please set the env var: CHURN_EMAIL_COUPON_CODE_ID", + }); + return { + success: false, + }; + } + + return await checkCouponWithCode(subscriber, currentCouponCode); +} + +export async function applyCouponWithCode( + subscriber: SubscriberRow, + couponCode: string, +) { + if (!couponCode) { + logger.error("fxa_apply_coupon_code_failed", "Coupon code is not passed."); return { success: false, message: "Coupon code not set", @@ -30,11 +104,11 @@ export async function applyCurrentCouponCode(subscriber: SubscriberRow) { try { if ( - !(await checkCouponForSubscriber(subscriber.id, currentCouponCode)) && + !(await checkCouponForSubscriber(subscriber.id, couponCode)) && subscriber.fxa_access_token ) { - await applyCoupon(subscriber.fxa_access_token, currentCouponCode); - await addCouponForSubscriber(subscriber.id, currentCouponCode); + await applyCoupon(subscriber.fxa_access_token, couponCode); + await addCouponForSubscriber(subscriber.id, couponCode); logger.info("fxa_apply_coupon_code_success"); return { success: true, @@ -55,18 +129,13 @@ export async function applyCurrentCouponCode(subscriber: SubscriberRow) { } } -export async function checkCurrentCouponCode( +export async function checkCouponWithCode( subscriber: SubscriberRow | SerializedSubscriber, + couponCode: string, ) { - logger.info("fxa_check_coupon", { - subscriber: subscriber.id, - }); - - const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID; - if (!currentCouponCode) { + if (!couponCode) { logger.error("fxa_check_coupon_failed", { - exception: - "Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID", + exception: "Coupon code is not passed in", }); return { success: false, @@ -75,7 +144,7 @@ export async function checkCurrentCouponCode( try { return { - success: await checkCouponForSubscriber(subscriber.id, currentCouponCode), + success: await checkCouponForSubscriber(subscriber.id, couponCode), }; } catch (ex) { logger.error("fxa_check_coupon_failed", { From c5ee15cdd680315b16cd380e8a25f7c428446e2b Mon Sep 17 00:00:00 2001 From: Florian Zia Date: Tue, 14 Jan 2025 15:31:17 +0100 Subject: [PATCH 2/8] feat: Add feature flag DataBrokerRemovalAttempts to conditionally show data broker removal attempts --- .../exposure_card/ExposureCard.stories.tsx | 5 ++++- .../client/exposure_card/ExposureCard.test.tsx | 18 +++++++++++++++++- .../client/exposure_card/ScanResultCard.tsx | 1 + src/db/tables/featureFlags.ts | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/components/client/exposure_card/ExposureCard.stories.tsx b/src/app/components/client/exposure_card/ExposureCard.stories.tsx index edbe1a316f7..f0d15237e81 100644 --- a/src/app/components/client/exposure_card/ExposureCard.stories.tsx +++ b/src/app/components/client/exposure_card/ExposureCard.stories.tsx @@ -87,7 +87,10 @@ export const DataBrokerRequestedRemoval: Story = { args: { exposureImg: FamilyTreeImage, exposureData: ScanMockItemRequestedRemoval, - enabledFeatureFlags: ["AdditionalRemovalStatuses"], + enabledFeatureFlags: [ + "AdditionalRemovalStatuses", + "DataBrokerRemovalAttempts", + ], }, }; diff --git a/src/app/components/client/exposure_card/ExposureCard.test.tsx b/src/app/components/client/exposure_card/ExposureCard.test.tsx index c45c515d440..f449aaf4bf4 100644 --- a/src/app/components/client/exposure_card/ExposureCard.test.tsx +++ b/src/app/components/client/exposure_card/ExposureCard.test.tsx @@ -150,7 +150,7 @@ describe("ScanResultCard", () => { expect(innerDescription).toBeInTheDocument(); }); - it("shows an additional note for “requested removal” status label", () => { + it("shows an additional note for “requested removal” status label if the feature flag `DataBrokerRemovalAttempts` is enabled", () => { const ComposedProgressCard = composeStory(DataBrokerRequestedRemoval, Meta); render(); const statusLabel = screen.getByText("Requested removal"); @@ -162,6 +162,22 @@ describe("ScanResultCard", () => { expect(labelNote).toBeInTheDocument(); }); + it("does not show an additional note for “requested removal” status label if the feature flag `DataBrokerRemovalAttempts` is not enabled", () => { + const ComposedProgressCard = composeStory(DataBrokerRequestedRemoval, Meta); + render( + , + ); + const statusLabel = screen.getByText("Requested removal"); + const statusLabelParent = statusLabel.parentElement as HTMLElement; + const labelNote = within(statusLabelParent).queryByText("Attempt", { + exact: false, + }); + + expect(labelNote).not.toBeInTheDocument(); + }); + it("hides the dt element if its dd counterpart has hideonmobile", () => { const ComposedProgressCard = composeStory(DataBrokerInProgress, Meta); render(); diff --git a/src/app/components/client/exposure_card/ScanResultCard.tsx b/src/app/components/client/exposure_card/ScanResultCard.tsx index ffe44ecbda6..0e6734e7c84 100644 --- a/src/app/components/client/exposure_card/ScanResultCard.tsx +++ b/src/app/components/client/exposure_card/ScanResultCard.tsx @@ -217,6 +217,7 @@ export const ScanResultCard = (props: ScanResultCardProps) => { const attemptCount = scanResult.optout_attempts ?? 0; const statusPillNote = props.enabledFeatureFlags?.includes("AdditionalRemovalStatuses") && + props.enabledFeatureFlags?.includes("DataBrokerRemovalAttempts") && !scanResult.manually_resolved && scanResult.status === "waiting_for_verification" && attemptCount >= 1 diff --git a/src/db/tables/featureFlags.ts b/src/db/tables/featureFlags.ts index 6056829fa83..2f5d8d2afab 100644 --- a/src/db/tables/featureFlags.ts +++ b/src/db/tables/featureFlags.ts @@ -57,6 +57,7 @@ export const featureFlagNames = [ "LandingPageRedesign", "EnableRemovalUnderMaintenanceStep", "CirrusV2", + "DataBrokerRemovalAttempts", ] as const; export type FeatureFlagName = (typeof featureFlagNames)[number]; From 0762ebdd2ca1f25463cb61149200ddd98379b7ed Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 14 Jan 2025 10:04:13 +0100 Subject: [PATCH 3/8] Add modal dialog to Storybook --- .../client/stories/ModalDialog.stories.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/app/components/client/stories/ModalDialog.stories.tsx diff --git a/src/app/components/client/stories/ModalDialog.stories.tsx b/src/app/components/client/stories/ModalDialog.stories.tsx new file mode 100644 index 00000000000..2dc2f3b707b --- /dev/null +++ b/src/app/components/client/stories/ModalDialog.stories.tsx @@ -0,0 +1,114 @@ +/* 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/. */ + +import type { Meta, StoryObj } from "@storybook/react"; + +import { useOverlayTriggerState } from "react-stately"; +import { useOverlayTrigger } from "react-aria"; +import Image from "next/image"; +import Illustration from "../assets/modal-default-img.svg"; +import { ModalOverlay } from "../dialog/ModalOverlay"; +import { Dialog } from "../dialog/Dialog"; +import { Button } from "../Button"; + +export type ModalDialogProps = { + withDialog?: boolean; + withTitle?: boolean; + withIllustration?: boolean; +}; + +const ModalDialog = (props: ModalDialogProps) => { + const modalState = useOverlayTriggerState({ defaultOpen: true }); + const modalTrigger = useOverlayTrigger({ type: "dialog" }, modalState); + return ( + <> + + {modalState.isOpen && ( + + {props.withDialog ? ( + + ) : undefined + } + onDismiss={() => modalState.close()} + > +

Some dialog content.

+
+ ) : ( + <> +

+ This is modal content; note that it's not possible to + interact with the content behind the modal overlay. Here's + a button to close the modal:   +

+ + + )} +
+ )} + + ); +}; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Design Systems/Molecules/Modal dialog", + component: ModalDialog, + argTypes: { + withDialog: { + control: "boolean", + name: "With dialog", + }, + withTitle: { + control: "boolean", + name: "With title", + }, + withIllustration: { + control: "boolean", + name: "With illustration", + }, + }, +}; +export default meta; +type Story = StoryObj; + +export const Modal: Story = { + name: "Modal without a dialog", + args: {}, +}; + +export const ModalWithDialog: Story = { + name: "Modal dialog", + args: { + withDialog: true, + }, +}; + +export const ModalDialogWithTitle: Story = { + name: "With title", + args: { + withDialog: true, + withTitle: true, + }, +}; + +export const ModalDialogWithIllustration: Story = { + name: "With title and illustration", + args: { + withDialog: true, + withTitle: true, + withIllustration: true, + }, +}; From f33c7a0e0f666ba887af567cc1833c2436f4f45b Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 14 Jan 2025 14:17:09 +0100 Subject: [PATCH 4/8] fixup! Add modal dialog to Storybook --- .../client/stories/ModalDialog.stories.tsx | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/app/components/client/stories/ModalDialog.stories.tsx b/src/app/components/client/stories/ModalDialog.stories.tsx index 2dc2f3b707b..8d193f5e91a 100644 --- a/src/app/components/client/stories/ModalDialog.stories.tsx +++ b/src/app/components/client/stories/ModalDialog.stories.tsx @@ -46,14 +46,28 @@ const ModalDialog = (props: ModalDialogProps) => { ) : ( <> -

- This is modal content; note that it's not possible to - interact with the content behind the modal overlay. Here's - a button to close the modal:   -

- +
+

+ This is modal content; note that it's not possible to + interact with the content behind the modal overlay. + Here's a button to close the modal:   +

+ +

+ Note that modal overlays aren't usually used on their + own. This is merely an example to help clarify the boundaries + between the Modal and Dialog components. +

+
)} From ca3bbf37f95cacf40f97d8ff7ac9cf02312a003d Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Tue, 14 Jan 2025 14:41:38 -0500 Subject: [PATCH 5/8] Add more Cirrus error logging info (#5483) * Add cirrus logging errors * remove middleware logging * revert code change in middleware * remove logging userId * remove logging experimentId --- .../functions/server/getExperimentationId.ts | 6 ++++++ src/app/functions/server/getExperiments.ts | 20 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/app/functions/server/getExperimentationId.ts b/src/app/functions/server/getExperimentationId.ts index 6e03a6efcea..5dc635aa34c 100644 --- a/src/app/functions/server/getExperimentationId.ts +++ b/src/app/functions/server/getExperimentationId.ts @@ -28,6 +28,9 @@ export function getExperimentationId( // If the user is logged in, use the Subscriber ID. const namespace = process.env.NIMBUS_UUID_NAMESPACE; if (!namespace) { + logger.error( + "NIMBUS_UUID_NAMESPACE environment variable is missing. Cannot generate experimentationId.", + ); throw new Error( "NIMBUS_UUID_NAMESPACE not set, cannot create experimentationId", ); @@ -47,6 +50,9 @@ export function getExperimentationId( ); return "guest-no-experimentation-id-set-by-monitor-middleware"; } + logger.info("Using experimentationId from header for guest user", { + experimentationId, + }); return experimentationId as ExperimentationId; } } diff --git a/src/app/functions/server/getExperiments.ts b/src/app/functions/server/getExperiments.ts index 66dc85ff271..205a036af4f 100644 --- a/src/app/functions/server/getExperiments.ts +++ b/src/app/functions/server/getExperiments.ts @@ -50,6 +50,11 @@ export async function getExperiments(params: { serverUrl.searchParams.set("nimbus_preview", "true"); } + logger.info("Sending request to Cirrus", { + serverUrl: serverUrl.toString(), + previewMode: params.previewMode, + }); + const response = await fetch(serverUrl, { headers: { "Content-Type": "application/json", @@ -65,6 +70,14 @@ export async function getExperiments(params: { }), }); + if (!response.ok) { + logger.error("Cirrus request failed", { + status: response.status, + url: serverUrl.toString(), + }); + throw new Error(`Cirrus request failed: ${response.statusText}`); + } + const json = await response.json(); let experimentData; @@ -76,7 +89,12 @@ export async function getExperiments(params: { return (experimentData as ExperimentData) ?? defaultExperimentData; } catch (ex) { - logger.error("Could not connect to Cirrus", { serverUrl, ex }); + logger.error("Could not connect to Cirrus", { + serverUrl, + ex, + flags, + params, + }); captureException(ex); return defaultExperimentData; } From 6f8a45b1f67ba458e74ab0f363eb63d01a65c9ef Mon Sep 17 00:00:00 2001 From: Kaitlyn Andres Date: Wed, 15 Jan 2025 10:20:42 -0500 Subject: [PATCH 6/8] MNTOR-3893 - Add new columns to qa_custom_brokers (#5497) --- .../20250113145507_new_broker_status_table.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/db/migrations/20250113145507_new_broker_status_table.js diff --git a/src/db/migrations/20250113145507_new_broker_status_table.js b/src/db/migrations/20250113145507_new_broker_status_table.js new file mode 100644 index 00000000000..e801b893504 --- /dev/null +++ b/src/db/migrations/20250113145507_new_broker_status_table.js @@ -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/. */ + + /** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ + export function up(knex) { + return knex.schema + .table("qa_custom_brokers", table => { + table.string('broker_status').nullable(); + table.string('scan_result_status').nullable(); + table.string('url').nullable(); + }); +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export function down(knex) { + return knex.schema.table('qa_custom_brokers',table => { + table.dropColumn('broker_status'); + table.dropColumn('scan_result_status'); + table.dropColumn('url'); + }); +} From 4de104f3ef3f61bb040eaedcfb92c7335c6bd148 Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Wed, 15 Jan 2025 11:30:42 -0800 Subject: [PATCH 7/8] handle nimbus user id not set (#5503) --- src/app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 223af998da0..3a70ab599db 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -72,7 +72,7 @@ export default async function RootLayout({ previewMode: nimbusPreviewMode === "true", }); - const nimbus_user_id = experimentData["Enrollments"].nimbus_user_id; + 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}]`, From 80e717803e382cfdbe6d752d8a0433c45534939f Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Wed, 15 Jan 2025 12:38:57 -0800 Subject: [PATCH 8/8] Revert "MNTOR-3814 - use context to fetch experiment data from Cirrus (#5440)" (#5505) This reverts commit 780dfd46db3063f8da50899b8384e0377ba218fc. --- .../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, 23 insertions(+), 420 deletions(-) delete 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 71237978a95..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 @@ -167,7 +167,7 @@ export default async function DashboardPage({ params, searchParams }: Props) { totalNumberOfPerformedScans={profileStats?.total} isNewUser={isNewUser} elapsedTimeInDaysSinceInitialScan={elapsedTimeInDaysSinceInitialScan} - experimentData={experimentData["Features"]} + experimentData={experimentData} 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 a90f861e91b..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 @@ -125,7 +125,7 @@ export default async function SettingsPage({ params, searchParams }: Props) { yearlySubscriptionUrl={`${yearlySubscriptionUrl}&${additionalSubplatParams.toString()}`} subscriptionBillingAmount={getSubscriptionBillingAmount()} enabledFeatureFlags={enabledFeatureFlags} - experimentData={experimentData["Features"]} + experimentData={experimentData} 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 f1c73fce090..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 @@ -77,7 +77,7 @@ export default async function Onboarding({ params, searchParams }: Props) { breachesTotalCount={allBreachesCount} stepId={firstSlug === FreeScanSlug ? "enterInfo" : "getStarted"} previousRoute={previousRoute} - experimentData={experimentData["Features"]} + experimentData={experimentData} /> ); } diff --git a/src/app/(proper_react)/(redesign)/(public)/page.tsx b/src/app/(proper_react)/(redesign)/(public)/page.tsx index 92e0960be44..bf8090cac58 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["Features"][ - "landing-page-redesign-plus-eligible-experiment" - ].enabled && - experimentData["Features"][ - "landing-page-redesign-plus-eligible-experiment" - ].variant === "redesign" ? ( + experimentData["landing-page-redesign-plus-eligible-experiment"] + .enabled && + experimentData["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 856e6be710b..9ca69454ce4 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["Features"]["welcome-scan-optional-info"]; + experimentData["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 205a036af4f..91b7868e841 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; + return localExperimentData["Features"]; } if (!process.env.NIMBUS_SIDECAR_URL) { @@ -87,7 +87,10 @@ export async function getExperiments(params: { experimentData = json; } - return (experimentData as ExperimentData) ?? defaultExperimentData; + return ( + (experimentData as ExperimentData["Features"]) ?? + defaultExperimentData["Features"] + ); } catch (ex) { logger.error("Could not connect to Cirrus", { serverUrl, @@ -96,6 +99,6 @@ export async function getExperiments(params: { params, }); captureException(ex); - return defaultExperimentData; + return defaultExperimentData["Features"]; } } diff --git a/src/app/hooks/useGlean.ts b/src/app/hooks/useGlean.ts index 4a966ddab58..8e5af988a7d 100644 --- a/src/app/hooks/useGlean.ts +++ b/src/app/hooks/useGlean.ts @@ -9,14 +9,9 @@ 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 < @@ -41,30 +36,10 @@ 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, experimentData], + [isPremiumUser], ); /* c8 ignore end */ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3a70ab599db..653c2b6bda7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,10 +17,6 @@ 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" }); @@ -58,26 +54,6 @@ 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 ( @@ -88,14 +64,12 @@ 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 b713ef80b39..81771ac3f9a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -29,14 +29,6 @@ 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 e7755eb247d..322d759f7f4 100644 --- a/src/telemetry/metrics.yaml +++ b/src/telemetry/metrics.yaml @@ -56,24 +56,6 @@ 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: @@ -120,24 +102,6 @@ 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: @@ -171,24 +135,6 @@ 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 @@ -221,24 +167,6 @@ 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: @@ -272,24 +200,6 @@ 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: @@ -323,24 +233,6 @@ 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: @@ -374,24 +266,6 @@ 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: @@ -425,24 +299,6 @@ 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: @@ -476,24 +332,6 @@ 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 @@ -524,24 +362,6 @@ 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: @@ -575,24 +395,6 @@ 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: @@ -626,24 +428,6 @@ 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: @@ -677,24 +461,6 @@ 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 @@ -727,24 +493,6 @@ 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: @@ -778,25 +526,6 @@ 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: | @@ -828,25 +557,6 @@ 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: | @@ -881,21 +591,3 @@ 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