Bug 1413830 - PART 2: properly handle sessions for onboarding telemetry;r=fischer,francois draft
authorFred Lin <gasolin@gmail.com>
Fri, 10 Nov 2017 12:06:37 +0800
changeset 703621 450732ee8726ff928e84b2e6e75aeacd5c07daf9
parent 703616 00912a0631a9c3d21e79865d4afc8a12e2195470
child 703622 a861f884c78b223d28fd8028ee7a7c5908749835
push id90891
push userbmo:gasolin@mozilla.com
push dateMon, 27 Nov 2017 07:51:07 +0000
reviewersfischer, francois
bugs1413830
milestone59.0a1
Bug 1413830 - PART 2: properly handle sessions for onboarding telemetry;r=fischer,francois MozReview-Commit-ID: ATyRNzky8R4
browser/extensions/onboarding/OnboardingTelemetry.jsm
browser/extensions/onboarding/content/onboarding.js
browser/extensions/onboarding/data_events.md
--- a/browser/extensions/onboarding/OnboardingTelemetry.jsm
+++ b/browser/extensions/onboarding/OnboardingTelemetry.jsm
@@ -133,71 +133,76 @@ const BASIC_EVENT_SCHEMA = {
  * We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
  * of analytics engineer's request.
  */
 const EVENT_WHITELIST = {
   // track when a notification appears.
   "notification-appear": {
     topic: "firefox-onboarding-event2",
     category: "notification-interactions",
+    parent: "notification",
     sendLogoState: true,
     sendNotificationImpression: true,
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isValidBubbleState,
       current_tour_id: hasString,
       logo_state: isValidLogoState,
       notification_impression: isPositiveInteger,
       notification_state: isValidNotificationState,
       target_tour_id: isEmptyString,
     }),
   },
   // track when a user clicks close notification button
   "notification-close-button-click": {
     topic: "firefox-onboarding-event2",
     category: "notification-interactions",
+    parent: "notification",
     sendLogoState: true,
     sendNotificationImpression: true,
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isValidBubbleState,
       current_tour_id: hasString,
       logo_state: isValidLogoState,
       notification_impression: isPositiveInteger,
       notification_state: isValidNotificationState,
       target_tour_id: hasString,
     }),
   },
   // track when a user clicks notification's Call-To-Action button
   "notification-cta-click": {
     topic: "firefox-onboarding-event2",
     category: "notification-interactions",
+    parent: "notification",
     sendLogoState: true,
     sendNotificationImpression: true,
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isValidBubbleState,
       current_tour_id: hasString,
       logo_state: isValidLogoState,
       notification_impression: isPositiveInteger,
       notification_state: isValidNotificationState,
       target_tour_id: hasString,
     }),
   },
   // track the start and end time of the notification session
   "notification-session": {
     topic: "firefox-onboarding-session2",
     category: "notification-interactions",
+    parent: "onboarding",
     validators: BASIC_SESSION_SCHEMA,
   },
   // track the start of a notification
   "notification-session-begin": {topic: "internal"},
   // track the end of a notification
   "notification-session-end": {topic: "internal"},
   // track when a user clicks the Firefox logo
   "onboarding-logo-click": {
     topic: "firefox-onboarding-event2",
     category: "logo-interactions",
+    parent: "onboarding",
     sendLogoState: true,
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isValidBubbleState,
       current_tour_id: isEmptyString,
       logo_state: isValidLogoState,
       notification_impression: isMinusOne,
       notification_state: isValidNotificationState,
       target_tour_id: isEmptyString,
@@ -227,55 +232,59 @@ const EVENT_WHITELIST = {
   // track onboarding start time (when user loads about:home or about:newtab)
   "onboarding-session-begin": {topic: "internal"},
   // track onboarding end time (when user unloads about:home or about:newtab)
   "onboarding-session-end": {topic: "internal"},
   // track when a user clicks the close overlay button
   "overlay-close-button-click": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
+    parent: "overlay",
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isEmptyString,
       current_tour_id: hasString,
       logo_state: isEmptyString,
       notification_impression: isMinusOne,
       notification_state: isEmptyString,
       target_tour_id: hasString,
     }),
   },
   // track when a user clicks outside the overlay area to end the tour
   "overlay-close-outside-click": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
+    parent: "overlay",
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isEmptyString,
       current_tour_id: hasString,
       logo_state: isEmptyString,
       notification_impression: isMinusOne,
       notification_state: isEmptyString,
       target_tour_id: hasString,
     }),
   },
   // track when a user clicks overlay's Call-To-Action button
   "overlay-cta-click": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
+    parent: "overlay",
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isEmptyString,
       current_tour_id: hasString,
       logo_state: isEmptyString,
       notification_impression: isMinusOne,
       notification_state: isEmptyString,
       target_tour_id: hasString,
     }),
   },
   // track when a tour is shown in the overlay
   "overlay-current-tour": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
+    parent: "overlay",
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isEmptyString,
       current_tour_id: hasString,
       logo_state: isEmptyString,
       notification_impression: isMinusOne,
       notification_state: isEmptyString,
       target_tour_id: isEmptyString,
     }),
@@ -305,26 +314,28 @@ const EVENT_WHITELIST = {
       notification_state: isEmptyString,
       target_tour_id: hasString,
     }),
   },
   // track the start and end time of the overlay session
   "overlay-session": {
     topic: "firefox-onboarding-session2",
     category: "overlay-interactions",
+    parent: "onboarding",
     validators:  BASIC_SESSION_SCHEMA,
   },
   // track the start of an overlay session
   "overlay-session-begin": {topic: "internal"},
   // track the end of an overlay session
   "overlay-session-end":  {topic: "internal"},
   // track when a user clicks 'Skip Tour' button in the overlay
   "overlay-skip-tour": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
+    parent: "overlay",
     validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
       bubble_state: isEmptyString,
       current_tour_id: hasString,
       logo_state: isEmptyString,
       notification_impression: isMinusOne,
       notification_state: isEmptyString,
       target_tour_id: isEmptyString,
     }),
@@ -402,56 +413,78 @@ let OnboardingTelemetry = {
     let session_id = gUUIDGenerator.generateUUID().toString();
     this.state.sessions[session_key] = {
       page,
       session_id,
       tour_type,
     };
   },
 
+  // register per tab session data
+  registerNewOnboardingSession(data) {
+    let { page, session_key, tour_type } = data;
+    if (this.state.sessions[session_key]) {
+      return;
+    }
+    // session_key and page url are must have
+    if (!session_key || !page || !tour_type) {
+      throw new Error("session_key, page url, and tour_type are required for onboarding-register-session");
+    }
+    let onboarding_session_id = gUUIDGenerator.generateUUID().toString();
+    this.state.sessions[session_key] = {
+      onboarding_session_id,
+      overlay_session_id: "",
+      notification_session_id: "",
+      page,
+      tour_type,
+    };
+  },
+
   process(data) {
     if (NEW_TABLE) {
       this.processPings(data);
     } else {
       this.processOldPings(data);
     }
   },
 
   processPings(data) {
     let { event, session_key } = data;
     let topic = EVENT_WHITELIST[event] && EVENT_WHITELIST[event].topic;
     if (!topic) {
       throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(EVENT_WHITELIST)}`);
     }
 
     if (event === "onboarding-register-session") {
-      this.registerNewTelemetrySession(data);
+      this.registerNewOnboardingSession(data);
     }
 
     if (!this.state.sessions[session_key]) {
       throw new Error(`should pass valid session_key`);
     }
 
     if (topic === "internal") {
       switch (event) {
         case "onboarding-session-begin":
           this.state.sessions[session_key].onboarding_session_begin = Date.now();
           break;
         case "onboarding-session-end":
           data.event = "onboarding-session";
           this._sendPing("firefox-onboarding-session2", data);
           break;
         case "overlay-session-begin":
+          this.state.sessions[session_key].overlay_session_id = gUUIDGenerator.generateUUID().toString();
           this.state.sessions[session_key].overlay_session_begin = Date.now();
           break;
         case "overlay-session-end":
           data.event = "overlay-session";
           this._sendPing("firefox-onboarding-session2", data);
           break;
         case "notification-session-begin":
+          this.state.sessions[session_key].notification_session_id = gUUIDGenerator.generateUUID().toString();
           this.state.sessions[session_key].notification_session_begin = Date.now();
           break;
         case "notification-session-end":
           data.event = "notification-session";
           this._sendPing("firefox-onboarding-session2", data);
           break;
       }
     } else {
@@ -468,76 +501,101 @@ let OnboardingTelemetry = {
       current_tour_id = "",
       event,
       session_key,
       target_tour_id = "",
       width,
     } = data;
     let {
       notification_session_begin,
+      notification_session_id,
       onboarding_session_begin,
+      onboarding_session_id,
       overlay_session_begin,
+      overlay_session_id,
       page,
-      session_id,
       tour_type,
     } = this.state.sessions[session_key];
     let {
       category,
+      parent,
       sendLogoState,
       sendNotificationImpression,
     } = EVENT_WHITELIST[event];
     let session_begin;
     let payload;
+    let session_id;
+    let root_session_id = onboarding_session_id;
+    let parent_session_id = onboarding_session_id;
     switch (topic) {
       case "firefox-onboarding-session2":
         switch (event) {
           case "onboarding-session":
             if (!onboarding_session_begin) {
               throw new Error(`should fire onboarding-session-begin event before ${event}`);
             }
+            session_id = onboarding_session_id;
+            root_session_id = onboarding_session_id;
+            parent_session_id = onboarding_session_id;
             session_begin = onboarding_session_begin;
             delete this.state.sessions[session_key];
             break;
           case "overlay-session":
             if (!overlay_session_begin) {
               throw new Error(`should fire overlay-session-begin event before ${event}`);
             }
+            session_id = overlay_session_id;
             session_begin = overlay_session_begin;
             break;
           case "notification-session":
             if (!notification_session_begin) {
               throw new Error(`should fire notification-session-begin event before ${event}`);
             }
+            session_id = notification_session_id;
             session_begin = notification_session_begin;
             break;
         }
 
         let session_end = Date.now();
         payload = {
           addon_version,
           category,
           page,
+          parent_session_id,
+          root_session_id,
           session_begin,
           session_end,
           session_id,
           tour_type,
           type: event,
         };
         this._validatePayload(payload);
         this.sessionProbe && this.sessionProbe.sendPing(payload,
           {filter: ONBOARDING_ID});
         break;
       case "firefox-onboarding-event2":
+        switch (parent) {
+          case "onboarding":
+            parent_session_id = onboarding_session_id;
+            break;
+          case "overlay":
+            parent_session_id = overlay_session_id;
+            break;
+          case "notification":
+            parent_session_id = notification_session_id;
+            break;
+        }
         let timestamp = Date.now();
         payload = {
           addon_version,
           category,
           current_tour_id,
           page,
-          session_id,
+          parent_session_id,
+          root_session_id,
           target_tour_id,
           timestamp,
           tour_type,
           type: event,
           width,
         };
         payload.notification_impression = sendNotificationImpression ?
           Services.prefs.getIntPref("browser.onboarding.notification.prompt-count", 0) : -1;
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -390,34 +390,31 @@ class Onboarding {
 
     // We want to create and append elements after CSS is loaded so
     // no flash of style changes and no additional reflow.
     await this._loadCSS();
     this._bundle = Services.strings.createBundle(BUNDLE_URI);
 
     this._loadJS(UITOUR_JS_URI);
 
-    this._window.addEventListener("resize", this);
-
-    // Destroy on unloading. This is to ensure we remove all the stuff we left.
-    // No any leak out there.
-    this._window.addEventListener("unload", () => this.destroy());
-
-    this.uiInitialized = false;
-    this._resizeTimerId =
-      this._window.requestIdleCallback(() => this._resizeUI());
     registerNewTelemetrySession({
       page: this._window.location.href,
       session_key: this._session_key,
       tour_type: this._tourType,
     });
-    telemetry({
-      event: "onboarding-session-begin",
-      session_key: this._session_key,
-    });
+
+    this._window.addEventListener("resize", this);
+
+    // Destroy on beforeunload. This is to ensure we remove all the stuff we left.
+    // No any leak out there.
+    this._window.addEventListener("beforeunload", () => this.destroy("beforeunload"));
+
+    this.uiInitialized = false;
+    this._resizeTimerId =
+      this._window.requestIdleCallback(() => this._resizeUI());
   }
 
   _resizeUI() {
     this.width = this._window.document.body.getBoundingClientRect().width;
     if (this.width < ONBOARDING_MIN_WIDTH_PX) {
       // Don't show the overlay UI before we get to a better, responsive design.
       this.destroy();
       return;
@@ -451,39 +448,39 @@ class Onboarding {
     body.appendChild(this._overlay);
 
     this._loadJS(TOUR_AGENT_JS_URI);
 
     this._initPrefObserver();
     this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
 
     // Doing tour notification takes some effort. Let's do it on idle.
-    this._window.requestIdleCallback(() => this._initNotification());
+    this._window.requestIdleCallback(() => this._initRender());
   }
 
   _getTourIDList() {
     let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
     return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
   }
 
-  _initNotification() {
+  _initRender() {
     let doc = this._window.document;
     if (doc.hidden) {
       // When the preloaded-browser feature is on,
       // it would preload a hidden about:newtab in the background.
       // We don't want to show notification in that hidden state.
       let onVisible = () => {
         if (!doc.hidden) {
           doc.removeEventListener("visibilitychange", onVisible);
-          this.showNotification();
+          this._renderUIOnVisible();
         }
       };
       doc.addEventListener("visibilitychange", onVisible);
     } else {
-      this.showNotification();
+      this._renderUIOnVisible();
     }
   }
 
   _initPrefObserver() {
     if (this._prefsObserved) {
       return;
     }
 
@@ -743,36 +740,48 @@ class Onboarding {
       case "click":
         this.handleClick(evt.target);
         break;
       default:
         break;
     }
   }
 
-  destroy() {
+  destroy(reason) {
     if (!this.uiInitialized) {
       return;
     }
     this.uiInitialized = false;
 
     this._overlayIcon.dispatchEvent(new this._window.CustomEvent("Agent:Destroy"));
 
     this._clearPrefObserver();
     this._overlayIcon.remove();
+    if (this._overlay.classList.contains("onboarding-opened")) {
+      telemetry({
+        event: "overlay-session-end",
+        session_key: this._session_key,
+      });
+    }
     this._overlay.remove();
     if (this._notificationBar) {
+      telemetry({
+        event: "notification-session-end",
+        session_key: this._session_key,
+      });
       this._notificationBar.remove();
     }
     this._tourItems = this._tourPages =
     this._overlayIcon = this._overlay = this._notificationBar = null;
-    telemetry({
-      event: "onboarding-session-end",
-      session_key: this._session_key,
-    });
+    if (reason === "beforeunload") {
+      telemetry({
+        event: "onboarding-session-end",
+        session_key: this._session_key,
+      });
+    }
   }
 
   _onIconStateChange(state) {
     switch (state) {
       case ICON_STATE_DEFAULT:
         this._overlayIcon.classList.remove("onboarding-watermark");
         break;
       case ICON_STATE_WATERMARK:
@@ -1019,16 +1028,29 @@ class Onboarding {
       sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.tour-ids-queue",
         value: queue
       }]);
     }
     return queue ? queue.split(",") : [];
   }
 
+  /**
+   * Render the UI when the tab is visible.
+   */
+  _renderUIOnVisible() {
+    // start log the onboarding-session when the tab is visible
+    telemetry({
+      event: "onboarding-session-begin",
+      session_key: this._session_key,
+    });
+
+    this.showNotification();
+  }
+
   showNotification() {
     if (Services.prefs.getBoolPref("browser.onboarding.notification.finished", false)) {
       return;
     }
 
     let lastTime = this._getLastTourChangeTime();
     if (this._muteNotificationOnFirstSession(lastTime)) {
       return;
@@ -1125,17 +1147,16 @@ class Onboarding {
   }
 
   hideNotification() {
     if (this._notificationBar) {
       if (this._notificationBar.classList.contains("onboarding-opened")) {
         this._notificationBar.classList.remove("onboarding-opened");
         telemetry({
           event: "notification-session-end",
-          tour_id: this._notificationBar.dataset.targetTourId,
           session_key: this._session_key,
         });
       }
     }
   }
 
   _renderNotificationBar() {
     let footer = this._window.document.createElement("footer");
--- a/browser/extensions/onboarding/data_events.md
+++ b/browser/extensions/onboarding/data_events.md
@@ -16,16 +16,18 @@ For reference, Onyx is a Mozilla owned s
 {
   // These fields are sent from the client
   "addon_version": "1.0.0",
   "category": ["onboarding-interactions"|"overlay-interactions"|"notification-interactions"],
   "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e",
   "locale": "en-US",
   "type": ["onboarding_session" | "overlay_session" | "notification_session"],
   "page": ["about:newtab" | "about:home"],
+  "parent_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}",
+  "root_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}",
   "session_begin": 1505440017018,
   "session_end": 1505440021992,
   "session_id": "{12dasd-213asda-213dkakj}",
   "tour_type" ["new" | "update"],
 
   // These fields are generated on the server
   "date": "2016-03-07",
   "ip": "10.192.171.13",
@@ -37,36 +39,39 @@ For reference, Onyx is a Mozilla owned s
 | KEY | DESCRIPTION | &nbsp; |
 |-----|-------------|:-----:|
 | `addon_version` | [Required] The version of the Onboarding addon. | :one:
 | `category` | [Required] Either ["", "overlay-interactions", "notification-interactions"] to identify which kind of the interaction | :one:
 | `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one:
 | `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
 | `locale` | The browser chrome's language (e.g. en-US). | :two:
 | `page` | [Required] One of ["about:newtab", "about:home"]| :one:
+| `parent_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which parent session. Events happen upon overlay will have the `overlay session uuid` as its `parent_session_id`. Events happen upon notification will have the `notification session uuid` as its `parent_session_id`. | :one:
+| `root_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which root session. Every event will have the same `onboarding session uuid` as its `root_session_id` when interact in the same tab. | :one:
 | `session_begin` | [Required] Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one:
 | `session_end` | [Required] Timestamp in (integer) milliseconds when onboarding/overlay/notification losing focus. | :one:
-| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when the overlay is opened/when notification is shown. | :one:
+| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session. We will log different uuid when onboarding is inited/when the overlay is opened/when notification is shown. | :one:
 | `tour_type` | [Required] One of ["new", "update"] indicates the user is a `new` user or the `update` user upgrade from the older version | :one:
 | `type` | [Required] The type of event. Allowed event strings are defined in the below section | :one:
 | `ua` | [Auto populated by Onyx] The user agent string. | :two:
 | `ver` | [Auto populated by Onyx] The version of the Onyx API the ping was sent to. | :one:
 
 # Example Onboarding `event` Log
 
 ```js
 {
   "addon_version": "1.0.0",
   "category": ["logo-interactions"|"overlay-interactions"|"notification-interactions"],
   "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e",
   "locale": "en-US",
   "logo_state": ["logo" | "watermark"],
   "notification_impression": [1-8],
   "page": ["about:newtab" | "about:home"],
-  "session_id": "{12dasd-213asda-213dkakj}",
+  "parent_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}",
+  "root_session_id": "{45cddbeb-2bec-4f3a-bada-fb87d4b79a6c}",
   "current_tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset'
   "target_tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset',
   "tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset'
   "timestamp": 1505440017019,
   "tour_type" ["new" | "update"],
   "type": ["notification-cta-click" | "overlay-cta-click" | "overlay-nav-click" | "overlay-skip-tour"...],
   "width": 950,
 
@@ -85,17 +90,18 @@ For reference, Onyx is a Mozilla owned s
 | `category` | [Required] Either ("overlay-interactions", "notification-interactions") to identify which kind of the interaction | :one:
 | `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one:
 | `current_tour_id` | [Optional] id of the current tour. The number of open from notification can be retrieved via 'notification-cta-click event'. We put "" when this field is not relevant to this event | :one:
 | `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
 | `locale` | The browser chrome's language (e.g. en-US). | :two:
 | `logo_state` | [Optional] One of ["logo", "watermark"] indicates the overlay is opened while in the default or the watermark state. Open from the notification bar is counted via 'notification-cta-click event'. | :one:
 | `notification_impression` | [Optional] An integer to record how many times the current notification tour is shown to the user. Each Notification tour can show not more than 8 times. We put `-1` when this field is not relevant to this event | :one:
 | `page` | [Required] One of ["about:newtab", "about:home"]| :one:
-| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when the overlay is opened/when notification is shown. | :one:
+| `parent_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which parent session. Events happen upon overlay will have the `overlay session uuid` as its `parent_session_id`. Events happen upon notification will have the `notification session uuid` as its `parent_session_id`. | :one:
+| `root_session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify this event belongs to which root session. Every event will have the same `onboarding session uuid` as its `root_session_id` when interact in the same tab. | :one:
 | `target_tour_id` | [Optional] id of the target switched tour. The number of open from notification can be retrieved via 'notification-cta-click event'. We put "" when this field is not relevant to this event | :one:
 | `timestamp` | [Required] Timestamp in (integer) milliseconds when the event triggered | :one:
 | `tour_type` | [Required] One of ["new", "update"] indicates the user is a `new` user or the `update` user upgrade from the older version | :one:
 | `type` | [Required] The type of event. Allowed event strings are defined in the below section | :one:
 | `ua` | [Auto populated by Onyx] The user agent string. | :two:
 | `ver` | [Auto populated by Onyx] The version of the Onyx API the ping was sent to. | :one:
 | `width` | [Required] Current browser window width rounded by 50 pixels. Collecting rounded values reduces the risk to use these values to derive a unique user identifier. | :one: