Bug 1676437 - Collect attribution data for Onboarding r=pdahiya
authorNan Jiang <njiang028@gmail.com>
Wed, 18 Nov 2020 18:21:33 +0000
changeset 557862 a5578b488bdb8d9158d895a028307681b92eda87
parent 557861 2cef3eb1c0a367cb3ebe462283e5a374fa6faaf6
child 557863 efac962c38701f3aa6a485823937afdc9e5bf064
push id37962
push userapavel@mozilla.com
push dateWed, 18 Nov 2020 21:51:58 +0000
treeherdermozilla-central@9d797387f57c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspdahiya
bugs1676437
milestone85.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1676437 - Collect attribution data for Onboarding r=pdahiya Differential Revision: https://phabricator.services.mozilla.com/D97236
browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm
browser/components/newtab/docs/v2-system-addon/data_dictionary.md
browser/components/newtab/docs/v2-system-addon/data_events.md
browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js
--- a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm
+++ b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm
@@ -8,16 +8,17 @@ const { XPCOMUtils } = ChromeUtils.impor
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   PingCentre: "resource:///modules/PingCentre.jsm",
   ClientID: "resource://gre/modules/ClientID.jsm",
   Services: "resource://gre/modules/Services.jsm",
   TelemetrySession: "resource://gre/modules/TelemetrySession.jsm",
+  AttributionCode: "resource:///modules/AttributionCode.jsm",
 });
 XPCOMUtils.defineLazyServiceGetters(this, {
   gUUIDGenerator: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
 });
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "structuredIngestionEndpointBase",
   "browser.newtabpage.activity-stream.telemetry.structuredIngestion.endpoint",
@@ -60,29 +61,49 @@ class AboutWelcomeTelemetry {
     const uuid = gUUIDGenerator.generateUUID().toString();
     // Structured Ingestion does not support the UUID generated by gUUIDGenerator,
     // because it contains leading and trailing braces. Need to trim them first.
     const docID = uuid.slice(1, -1);
     const extension = `${STRUCTURED_INGESTION_NAMESPACE_MS}/${PING_TYPE}/${PING_VERSION}/${docID}`;
     return `${structuredIngestionEndpointBase}/${extension}`;
   }
 
+  /**
+   * Attach browser attribution data to a ping payload.
+   *
+   * It intentionally queries the *cached* attribution data other than calling
+   * `getAttrDataAsync()` in order to minimize the overhead here.
+   * For the same reason, we are not querying the attribution data from
+   * `TelemetryEnvironment.currentEnvironment.settings`.
+   *
+   * In practice, it's very likely that the attribution data is already read
+   * and cached at some point by `AboutWelcomeParent`, so it should be able to
+   * read the cached results for the most if not all of the pings.
+   */
+  _maybeAttachAttribution(ping) {
+    const attribution = AttributionCode.getCachedAttributionData();
+    if (attribution && Object.keys(attribution).length) {
+      ping.attribution = attribution;
+    }
+    return ping;
+  }
+
   async _createPing(event) {
     if (event.event_context && typeof event.event_context === "object") {
       event.event_context = JSON.stringify(event.event_context);
     }
     let ping = {
       ...event,
       addon_version: Services.appinfo.appBuildID,
       locale: Services.locale.appLocaleAsBCP47,
       client_id: await telemetryClientId,
       browser_session_id: browserSessionId,
     };
 
-    return ping;
+    return this._maybeAttachAttribution(ping);
   }
 
   async sendTelemetry(event) {
     if (!this.telemetryEnabled) {
       return;
     }
 
     const ping = await this._createPing(event);
--- a/browser/components/newtab/docs/v2-system-addon/data_dictionary.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_dictionary.md
@@ -338,17 +338,31 @@ Schema definitions/validations that can 
 | ``full_recalc``            | [required] Is it a full SPOCS recalculation: 0: false; 1: true. Recalculation case: 1). fetch SPOCS from Pocket endpoint. Non-recalculation cases:   |                  |
 |                            | 1). An impression updates the SPOCS; 2). Any action that triggers the ``selectLayoutRender``                                                         | :one:            |
 +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
 | ``experiments``            | [Optional] An object to record all active experiments (an empty object will be sent if there is no active experiment). The experiments IDs are       | :one:            |
 |                            | stored as keys, and the value object stores the branch information.                                                                                  |                  |
 |                            | `Example: {"experiment_1": {"branch": "control"}, "experiment_2": {"branch": "treatment"}}`. This deprecates the `shield_id` used in Activity Stream |                  |
 |                            | and Messaging System.                                                                                                                                |                  |
 +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
-| `browser_session_id`       | [Optional] The unique identifier for a browser session, retrieved from TelemetrySession                                                              | :one:            |
+| ``browser_session_id``     | [Optional] The unique identifier for a browser session, retrieved from TelemetrySession                                                              | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.source``     | [Optional] Referring partner domain, when install happens via a known partner                                                                        | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.medium``     | [Optional] Category of the source, such as 'organic' for a search engine                                                                             | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.campaign``   | [Optional] Identifier of the particular campaign that led to the download of the product                                                             | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.content``    | [Optional] Identifier to indicate the particular link within a campaign                                                                              | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.experiment`` | [Optional] Funnel experiment identifier, see bug 1567339                                                                                             | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.variantion`` | [Optional] Funnel experiment variant identifier, see bug 1567339                                                                                     | :one:            |
++----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
+| ``attribution.ua``         | [Optional] Derived user agent, see bug 1595063                                                                                                       | :one:            |
 +----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+------------------+
 ```
 
 **Where:**
 
 * :one: Firefox data
 * :two: HTTP protocol data
 * :three: server augmented data
--- a/browser/components/newtab/docs/v2-system-addon/data_events.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_events.md
@@ -508,67 +508,16 @@ A user event ping includes some basic me
 ```js
 {
   "event": "PREVIEW_REQUEST",
   "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
   "source": "TOP_SITES"
 }
 ```
 
-### Onboarding user events on about:welcome
-
-#### Form Submit Events
-
-```js
-{
-  "event": ["SUBMIT_EMAIL" | "SUBMIT_SIGNIN" | "SKIPPED_SIGNIN"],
-  "value": {
-    "has_flow_params": false,
-  }
-
-  // Basic metadata
-  "action": "activity_stream_event",
-  "page": "about:welcome",
-  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
-  "session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
-  "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
-  "addon_version": "20180710100040",
-  "locale": "en-US",
-  "experiments": {
-    "experiment_1": {"branch": "control"},
-    "experiment_2": {"branch": "treatment"}
-  },
-  "user_prefs": 7
-}
-```
-
-#### Firefox Accounts Metrics flow errors
-
-```js
-{
-  "event": ["FXA_METRICS_FETCH_ERROR" | "FXA_METRICS_ERROR"],
-  "value": 500, // Only FXA_METRICS_FETCH_ERROR provides this value, this value is any valid HTTP status code except 200.
-
-  // Basic metadata
-  "action": "activity_stream_event",
-  "page": "about:welcome",
-  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
-  "session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
-  "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
-  "addon_version": "20180710100040",
-  "locale": "en-US",
-  "experiments": {
-    "experiment_1": {"branch": "control"},
-    "experiment_2": {"branch": "treatment"}
-  },
-  "user_prefs": 7
-}
-```
-
-
 ## Session end pings
 
 When a session ends, the browser will send a `"activity_stream_session"` ping to our metrics servers. This ping contains the length of the session, a unique reason for why the session ended, and some additional metadata.
 
 ### Basic event
 
 All `"activity_stream_session"` pings have the following basic shape. Some fields are variable.
 
@@ -1190,66 +1139,90 @@ CFR impression ping has two forms, in wh
   "source": "CFR",
   // message_id should be a bucket ID in the release channel, we may not use the
   // individual ID, such as addon ID, per legal's request
   "message_id": "bucket_id",
   "event": "IMPRESSION"
 }
 ```
 
-#### Onboarding impression
-```js
-{
-  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
-  "action": "onboarding_user_event",
-  "impression_id": "n/a",
-  "source": "FIRST_RUN",
-  "addon_version": "20180710100040",
-  "locale": "en-US",
-  "experiments": {
-    "experiment_1": {"branch": "control"},
-    "experiment_2": {"branch": "treatment"}
-  },
-  "message_id": "EXTENDED_TRIPLETS_1",
-  "event": "IMPRESSION",
-  "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
-  "event_context": { "page": ["about:welcome" | "about:home" | "about:newtab"] }
-}
-```
-
 #### Onboarding Simplified Welcome impression
 ```js
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "version": "76.0a1",
   "locale": "en-US",
   "experiments": {},
   "release_channel": "default",
   "addon_version": "20200330194034"
   "message_id": "ABOUT_WELCOME",
   "event": "IMPRESSION",
   "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
-  "event_context": { "page": "about:welcome" }
+  "event_context": { "page": "about:welcome" },
+  "attribution": {
+    "source": "mozilla.org",
+    "medium": "referral",
+    "campaign": "Firefox-Brand-US-Mozilla-Org",
+    "content": "test-addon@github.io",
+    "experiment": "ua-onboarding",
+    "variation": "chrome",
+    "ua": "firefox"
+  }
 }
 ```
+
+#### Onboarding Simplified Welcome click button ping
+```js
+{
+  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
+  "version": "76.0a1",
+  "locale": "en-US",
+  "experiments": {},
+  "release_channel": "default",
+  "addon_version": "20200330194034"
+  "message_id": "ABOUT_WELCOME",
+  "event": "CLICK_BUTTION",
+  "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
+  "event_context": { "page": "about:welcome", "source": ["primary_button", "secondary_button" },
+  "attribution": {
+    "source": "mozilla.org",
+    "medium": "referral",
+    "campaign": "Firefox-Brand-US-Mozilla-Org",
+    "content": "test-addon@github.io",
+    "experiment": "ua-onboarding",
+    "variation": "chrome",
+    "ua": "firefox"
+  }
+}
+```
+
 #### Onboarding Simplified Welcome Session End ping
 ```js
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "version": "76.0a1",
   "locale": "en-US",
   "experiments": {},
   "release_channel": "default",
   "addon_version": "20200330194034"
   "message_id": "ABOUT_WELCOME",
   "id": "ABOUT_WELCOME",
   "event": "SESSION_END",
   "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
   "event_context": { "page": "about:welcome", "reason":
-    ["welcome-window-closed" | "welcome-tab-closed" | "app-shut-down" | "address-bar-navigated" | "unknown"]}
+    ["welcome-window-closed" | "welcome-tab-closed" | "app-shut-down" | "address-bar-navigated" | "unknown"]},
+  "attribution": {
+    "source": "mozilla.org",
+    "medium": "referral",
+    "campaign": "Firefox-Brand-US-Mozilla-Org",
+    "content": "test-addon@github.io",
+    "experiment": "ua-onboarding",
+    "variation": "chrome",
+    "ua": "firefox"
+  }
 }
 ```
 
 ### User interaction pings
 
 This reports the user's interaction with Activity Stream Router.
 
 #### Snippets interaction pings
@@ -1265,36 +1238,16 @@ This reports the user's interaction with
     "experiment_2": {"branch": "treatment"}
   },
   "source": "NEWTAB_FOOTER_BAR",
   "message_id": "some_snippet_id",
   "event": ["CLICK_BUTTION" | "BLOCK"]
 }
 ```
 
-#### Onboarding interaction pings
-```js
-{
-  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
-  "action": "onboarding_user_event",
-  "addon_version": "20180710100040",
-  "impression_id": "n/a",
-  "locale": "en-US",
-  "experiments": {
-    "experiment_1": {"branch": "control"},
-    "experiment_2": {"branch": "treatment"}
-  },
-  "source": "ONBOARDING",
-  "message_id": "onboarding_message_1",
-  "event": ["IMPRESSION" | "CLICK_BUTTION" | "INSTALL" | "BLOCK"],
-  "browser_session_id": "e7e52665-7db3-f348-9918-e93160eb2ef3",
-  "event_context": { "page": ["about:welcome" | "about:home" | "about:newtab"] }
-}
-```
-
 #### CFR interaction pings for all the prerelease channels and shield experiment
 ```js
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "action": "cfr_user_event",
   "addon_version": "20180710100040",
   "impression_id": "n/a",
   "locale": "en-US",
--- a/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js
+++ b/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js
@@ -2,16 +2,19 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 "use strict";
 
 const { AboutWelcomeTelemetry } = ChromeUtils.import(
   "resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm"
 );
+const { AttributionCode } = ChromeUtils.import(
+  "resource:///modules/AttributionCode.jsm"
+);
 const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
 const TELEMETRY_PREF = "browser.newtabpage.activity-stream.telemetry";
 
 add_task(function test_enabled() {
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref(TELEMETRY_PREF);
   });
   Services.prefs.setBoolPref(TELEMETRY_PREF, true);
@@ -41,8 +44,44 @@ add_task(async function test_pingPayload
 
   equal(stub.callCount, 1, "Call was made");
   // check the endpoint
   ok(
     stub.firstCall.args[1].includes("/messaging-system/onboarding"),
     "Endpoint is correct"
   );
 });
+
+add_task(function test_mayAttachAttribution() {
+  const sandbox = sinon.createSandbox();
+  const AWTelemetry = new AboutWelcomeTelemetry();
+
+  sandbox.stub(AttributionCode, "getCachedAttributionData").returns(null);
+
+  let ping = AWTelemetry._maybeAttachAttribution({});
+
+  equal(ping.attribution, undefined, "Should not set attribution if it's null");
+
+  sandbox.restore();
+  sandbox.stub(AttributionCode, "getCachedAttributionData").returns({});
+  ping = AWTelemetry._maybeAttachAttribution({});
+
+  equal(
+    ping.attribution,
+    undefined,
+    "Should not set attribution if it's empty"
+  );
+
+  const attr = {
+    source: "google.com",
+    medium: "referral",
+    campaign: "Firefox-Brand-US-Chrome",
+    content: "(not set)",
+    experiment: "(not set)",
+    variation: "(not set)",
+    ua: "chrome",
+  };
+  sandbox.restore();
+  sandbox.stub(AttributionCode, "getCachedAttributionData").returns(attr);
+  ping = AWTelemetry._maybeAttachAttribution({});
+
+  equal(ping.attribution, attr, "Should set attribution if it presents");
+});