From 25a3d3b77c5689ed93c694f4e6278abf3c6693aa Mon Sep 17 00:00:00 2001 From: groovecoder Date: Tue, 18 Jun 2024 10:08:50 -0500 Subject: [PATCH] for MPP-2822: add gtag to Layout --- .env-dist | 1 + METRICS.md | 177 +++++++++--------- api/views/privaterelay.py | 2 + frontend/package.json | 1 + frontend/src/apiMocks/mockData.ts | 1 + .../components/GoogleAnalyticsWorkaround.tsx | 108 +++++++++++ frontend/src/components/layout/Layout.tsx | 10 + frontend/src/hooks/api/runtimeData.ts | 1 + package-lock.json | 112 +++++------ privaterelay/settings.py | 1 + 10 files changed, 266 insertions(+), 148 deletions(-) create mode 100644 frontend/src/components/GoogleAnalyticsWorkaround.tsx diff --git a/.env-dist b/.env-dist index 76f3c87bd..d7d54fdf4 100644 --- a/.env-dist +++ b/.env-dist @@ -4,6 +4,7 @@ FXA_PROFILE_ENDPOINT=https://profile.stage.mozaws.net/v1 FXA_BASE_ORIGIN=https://accounts.stage.mozaws.net FXA_ACCOUNTS_ENDPOINT=https://api-accounts.stage.mozaws.net/v1 GOOGLE_ANALYTICS_ID="UA-77033033-33" +GA4_MEASUREMENT_ID="G-YXT33S87LT" GOOGLE_APPLICATION_CREDENTIALS="gcp_key.json" GOOGLE_CLOUD_PROFILER_CREDENTIALS_B64="" BASKET_ORIGIN="https://basket-dev.allizom.org" diff --git a/METRICS.md b/METRICS.md index ca973b5fd..6113e3026 100644 --- a/METRICS.md +++ b/METRICS.md @@ -14,33 +14,34 @@ This is the Analytics plan for Firefox Relay. It documents our use of Google Ana **Demographic:** ->From which country does the majority of our traffic originate? +> From which country does the majority of our traffic originate? ->Which browsers are most commonly used to access the Firefox Relay website? +> Which browsers are most commonly used to access the Firefox Relay website? ->Which devices are most commonly used to access the Firefox Relay website? +> Which devices are most commonly used to access the Firefox Relay website? ->Which browser is running the Firefox Relay the add-on? +> Which browser is running the Firefox Relay the add-on? **User Behavior:** ->Do users delete aliases? +> Do users delete aliases? ->Do users create aliases? +> Do users create aliases? ->How do users create aliases? From the Relay website dashboard? The context menu? The input icon? +> How do users create aliases? From the Relay website dashboard? The context menu? The input icon? ->Do users open the extension panel? +> Do users open the extension panel? ->Do users change the forwarding settings for their aliases? +> Do users change the forwarding settings for their aliases? ->Do users who have not installed the Relay add-on, choose to install the add-on? +> Do users who have not installed the Relay add-on, choose to install the add-on? ->When do users decide to upgrade to Premium? +> When do users decide to upgrade to Premium?   ## Extension Event Collection + Events are reported using [Google Analytics Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1). We collect data for the following extension events: @@ -59,7 +60,6 @@ We collect data for the following extension events: - When panel navigation arrow icons are clicked - ### In-page events: - When the Relay icon is injected into an email input @@ -78,12 +78,10 @@ We collect data for the following extension events: - When the user clicks an outbound link or button - ### Context Menu events: - When an alias is generated via the context menu - ### Modal events: - When the modal opens @@ -92,13 +90,11 @@ We collect data for the following extension events: - When "Manage All Aliases" is clicked - -   ## Website Event Collection -Events are reported using [Google Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs). +Events are reported using [Google Analytics](https://developers.google.com/analytics/devguides/collection/ga4). We collect data for the following events: @@ -116,120 +112,125 @@ We collect data for the following events: - When a user changes the forwarding settings for an alias - ### Sign Up Buttons & Links -**`Sign In Button`** -  **`Add to Firefox Button`** -  **`Join the Waitlist Button`** +**`Sign In Button`** -  **`Add to Firefox Button`** -  **`Join the Waitlist Button`** - When a button appears on the page - * `hitType` : event - * `eventCategory` : Button ID - * `eventAction` : View - * `eventLabel` : Page location ID. + + - `hitType` : event + - `eventCategory` : Button ID + - `eventAction` : View + - `eventLabel` : Page location ID. - When a user clicks a link or button. - * `hitType` : event - * `eventCategory` : Button or Link ID - * `eventAction` : Engage - * `eventLabel` : Page location ID. - + - `hitType` : event + - `eventCategory` : Button or Link ID + - `eventAction` : Engage + - `eventLabel` : Page location ID. ### Firefox Apps menu (referred to internally as the Bento menu) - When a user opens the Firefox Apps menu - * `hitType` : event - * `eventCategory` : bento - * `eventAction` : bento-opened - * `eventLabel` : fx-monitor + + - `hitType` : event + - `eventCategory` : bento + - `eventAction` : bento-opened + - `eventLabel` : fx-monitor - When a user closes the Firefox Apps menu - * `hitType` : event - * `eventCategory` : bento - * `eventAction` : bento-closed - * `eventLabel` : fx-monitor + + - `hitType` : event + - `eventCategory` : bento + - `eventAction` : bento-closed + - `eventLabel` : fx-monitor - When a user clicks on one of the Firefox Apps menu links - * `hitType` : event - * `eventCategory` : bento - * `eventAction` : bento-app-link-click - * `eventLabel` : link identifier + - `hitType` : event + - `eventCategory` : bento + - `eventAction` : bento-app-link-click + - `eventLabel` : link identifier ### Interview recruitment (This is only shown occasionally, when we're trying to recruit people to join in user research.) - When the recruitment link appears on the page - * `hitType` : event - * `eventCategory` : Recruitment - * `eventAction` : View - * `eventLabel` : Recruitment text + + - `hitType` : event + - `eventCategory` : Recruitment + - `eventAction` : View + - `eventLabel` : Recruitment text - When the recruitment link is clicked - * `hitType` : event - * `eventCategory` : Recruitment - * `eventAction` : Engage - * `eventLabel` : Recruitment text + - `hitType` : event + - `eventCategory` : Recruitment + - `eventAction` : Engage + - `eventLabel` : Recruitment text ### Net Promoter Score (NPS)/Customer Satisfaction (CSAT) surveys - When a CSAT survey answer is selected - * `hitType` : event - * `eventCategory` : CSAT Survey - * `eventAction` : submitted - * `eventLabel` : The given answer - * `value` : A numeric value representing the given answer - * `dimension3` : Whether the given answer respresents satisfaction, neutral feeling, or dissastisfaction. - * `dimension4` : The given answer - * `metric10` : Always "1" (to count the number of answers) - * `metric11` : A numeric value representing `dimenstion4` - * `metric12` : A numeric value representing `dimenstion3` + + - `hitType` : event + - `eventCategory` : CSAT Survey + - `eventAction` : submitted + - `eventLabel` : The given answer + - `value` : A numeric value representing the given answer + - `dimension3` : Whether the given answer respresents satisfaction, neutral feeling, or dissastisfaction. + - `dimension4` : The given answer + - `metric10` : Always "1" (to count the number of answers) + - `metric11` : A numeric value representing `dimenstion4` + - `metric12` : A numeric value representing `dimenstion3` - When an NPS survey answer is selected - * `hitType` : event - * `eventCategory` : NPS Survey - * `eventAction` : submitted - * `eventLabel` : A label for the category of the given answer - * `value` : A numeric value representing the given answer - * `dimension1` : A label for the category of the given answer - * `metric10` : Always "1" (to count the number of answers) - * `metric11` : The given answer - * `metric12` : A numeric value representing the category of the given answer + - `hitType` : event + - `eventCategory` : NPS Survey + - `eventAction` : submitted + - `eventLabel` : A label for the category of the given answer + - `value` : A numeric value representing the given answer + - `dimension1` : A label for the category of the given answer + - `metric10` : Always "1" (to count the number of answers) + - `metric11` : The given answer + - `metric12` : A numeric value representing the category of the given answer ### Banners - When a user clicks the link in one of the banners - * `hitType` : event - * `eventCategory` : Outbound - * `eventAction` : Click - * `eventLabel` : link content + - `hitType` : event + - `eventCategory` : Outbound + - `eventAction` : Click + - `eventLabel` : link content ### Links to upgrade to Premium - When the link appears on the page - * `hitType` : event - * `eventCategory` : Purchase Button - * `eventAction` : View - * `eventLabel` : link identifier + + - `hitType` : event + - `eventCategory` : Purchase Button + - `eventAction` : View + - `eventLabel` : link identifier - When a user clicks the link - * `hitType` : event - * `eventCategory` : Purchase Button - * `eventAction` : Engage - * `eventLabel` : link identifier + - `hitType` : event + - `eventCategory` : Purchase Button + - `eventAction` : Engage + - `eventLabel` : link identifier ### The onboarding flow for new Premium subscribers - When a button/link to continue to the next step scrolls into view - * `hitType` : event - * `eventCategory` : Premium Onboarding - * `eventAction` : View - * `eventLabel` : link identifier + + - `hitType` : event + - `eventCategory` : Premium Onboarding + - `eventAction` : View + - `eventLabel` : link identifier - When a user clicks a button/link to continue to the next step - * `hitType` : event - * `eventCategory` : Premium Onboarding - * `eventAction` : Engage - * `eventLabel` : link identifier + - `hitType` : event + - `eventCategory` : Premium Onboarding + - `eventAction` : Engage + - `eventLabel` : link identifier ## Opt Out of Google Analytics Tracking @@ -237,4 +238,4 @@ We collect data for the following events: Before initializing Google Analytics, we check the user's browser settings for a **DNT** signal. If the **DNT** header is enabled, Analytics is never initialized and is not used to collect data for that session. ->[How do I turn on the Do Not Track feature?](https://support.mozilla.org/en-US/kb/how-do-i-turn-do-not-track-feature) +> [How do I turn on the Do Not Track feature?](https://support.mozilla.org/en-US/kb/how-do-i-turn-do-not-track-feature) diff --git a/api/views/privaterelay.py b/api/views/privaterelay.py index df8bd7ff9..cb219e994 100644 --- a/api/views/privaterelay.py +++ b/api/views/privaterelay.py @@ -200,6 +200,7 @@ def _get_example_plan(plan: Literal["premium", "phones", "bundle"]) -> dict[str, "FXA_ORIGIN": "https://accounts.firefox.com", "PERIODICAL_PREMIUM_PRODUCT_ID": "prod_XXXXXXXXXXXXXX", "GOOGLE_ANALYTICS_ID": "UA-########-##", + "GA4_MEASUREMENT_ID": "G-XXXXXXXXX", "BUNDLE_PRODUCT_ID": "prod_XXXXXXXXXXXXXX", "PHONE_PRODUCT_ID": "prod_XXXXXXXXXXXXXX", "PERIODICAL_PREMIUM_PLANS": _get_example_plan("premium"), @@ -235,6 +236,7 @@ def runtime_data(request): "FXA_ORIGIN": settings.FXA_BASE_ORIGIN, "PERIODICAL_PREMIUM_PRODUCT_ID": settings.PERIODICAL_PREMIUM_PROD_ID, "GOOGLE_ANALYTICS_ID": settings.GOOGLE_ANALYTICS_ID, + "GA4_MEASUREMENT_ID": settings.GA4_MEASUREMENT_ID, "BUNDLE_PRODUCT_ID": settings.BUNDLE_PROD_ID, "PHONE_PRODUCT_ID": settings.PHONE_PROD_ID, "PERIODICAL_PREMIUM_PLANS": get_countries_info_from_request_and_mapping( diff --git a/frontend/package.json b/frontend/package.json index 9bacdfb88..1077baba2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@fluent/langneg": "^0.7.0", "@fluent/react": "^0.15.2", "@mozilla-protocol/core": "^17.0.1", + "@next/third-parties": "^14.2.4", "@stripe/stripe-js": "^3.5.0", "chokidar-cli": "^3.0.0", "cldr-localenames-modern": "^45.0.0", diff --git a/frontend/src/apiMocks/mockData.ts b/frontend/src/apiMocks/mockData.ts index 21d160fdb..987762a61 100644 --- a/frontend/src/apiMocks/mockData.ts +++ b/frontend/src/apiMocks/mockData.ts @@ -13,6 +13,7 @@ export const mockedRuntimeData: RuntimeData = { FXA_ORIGIN: "https://fxa-mock.com", BASKET_ORIGIN: "https://basket-mock.com", GOOGLE_ANALYTICS_ID: "UA-123456789-0", + GA4_MEASUREMENT_ID: "G-YXT33S87LT", PERIODICAL_PREMIUM_PRODUCT_ID: "prod_123456789", PHONE_PRODUCT_ID: "prod_123456789", BUNDLE_PRODUCT_ID: "prod_123456789", diff --git a/frontend/src/components/GoogleAnalyticsWorkaround.tsx b/frontend/src/components/GoogleAnalyticsWorkaround.tsx new file mode 100644 index 000000000..09f61fcb7 --- /dev/null +++ b/frontend/src/components/GoogleAnalyticsWorkaround.tsx @@ -0,0 +1,108 @@ +// Based on: https://github.com/mozilla/blurts-server/blob/8fb1587f1af40b25fed455c79faf138c4be2d0d6/src/app/components/client/GoogleAnalyticsWorkaround.tsx +// License for this specific file: +/* +The MIT License (MIT) + +Copyright (c) 2024 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +"use client"; + +import { GAParams } from "@next/third-parties/dist/types/google"; +import Script, { ScriptProps } from "next/script"; +import { useEffect } from "react"; + +// We don't send Analytics events in tests: +/* c8 ignore start */ + +let currDataLayerName: string | undefined = undefined; + +/** + * This component is based on from `@next/third-parties`, but accepting a nonce + * + * @param props + */ +export const GoogleAnalyticsWorkaround = ( + props: GAParams & { nonce?: ScriptProps["nonce"]; debugMode?: boolean }, +) => { + const { gaId, dataLayerName = "dataLayer", nonce, debugMode } = props; + + if (currDataLayerName === undefined) { + currDataLayerName = dataLayerName; + } + + useEffect(() => { + if (typeof performance.mark !== "function") { + return; + } + // performance.mark is being used as a feature use signal. While it is traditionally used for performance + // benchmarking it is low overhead and thus considered safe to use in production and it is a widely available + // existing API. + // The performance measurement will be handled by Chrome Aurora + + performance.mark("mark_feature_usage", { + detail: { + feature: "next-third-parties-ga", + }, + }); + }, []); + + return ( + <> +