Bug 1413830 - PART 1: track new events for onboarding telemetry;r=fischer,francois draft
authorFred Lin <gasolin@gmail.com>
Tue, 31 Oct 2017 12:01:30 +0800
changeset 701762 51c1b8e62f52eaa061071940376b70c4e9fe67cb
parent 701745 b2455f1f54613ef8a3d15c710f2bce90122d6ea8
child 701763 8dbe4a4a68c981a7a6ae0527390c630b949c14c1
push id90268
push userbmo:gasolin@mozilla.com
push dateWed, 22 Nov 2017 07:24:14 +0000
reviewersfischer, francois
bugs1413830
milestone59.0a1
Bug 1413830 - PART 1: track new events for onboarding telemetry;r=fischer,francois MozReview-Commit-ID: 3jNAmOcrvEa
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
@@ -28,31 +28,56 @@ function isEmptyString(str) {
   return typeof str == "string" && str === "";
 }
 
 // Validate the content is an interger
 function isInteger(i) {
   return Number.isInteger(i);
 }
 
-// Validate the category value is within the list
+// Validate the content is a positive interger
+function isPositiveInteger(i) {
+  return Number.isInteger(i) && i > 0;
+}
+
+// Validate the number is -1
+function isMinusOne(num) {
+  return num === -1;
+}
+
+// valid category value is within the list
 function isValidCategory(category) {
   return ["overlay-interactions", "notification-interactions"].includes(category);
 }
 
 // Validate the page value is within the list
 function isValidPage(page) {
   return ["about:newtab", "about:home"].includes(page);
 }
 
 // Validate the tour_type value is within the list
 function isValidTourType(type) {
   return ["new", "update"].includes(type);
 }
 
+// Validate the bubble state value is within the list
+function isValidBubbleState(str) {
+  return ["bubble", "dot", "hide"].includes(str);
+}
+
+// Validate the logo state value is within the list
+function isValidLogoState(str) {
+  return ["logo", "watermark"].includes(str);
+}
+
+// Validate the notification state value is within the list
+function isValidNotificationState(str) {
+  return ["show", "hide", "finished"].includes(str);
+}
+
 // Validate the column must be defined per ping
 function definePerPing(column) {
   return function() {
     throw new Error(`Must define the '${column}' validator per ping because it is not the same for all pings`);
   };
 }
 
 // Basic validators for session events
@@ -100,65 +125,174 @@ const BASIC_EVENT_SCHEMA = {
  * To save server space and make query easier, we track session begin and end but only send pings
  * when session end. Therefore the server will get single "onboarding/overlay/notification-session"
  * event which includes both `session_begin` and `session_end` timestamp.
  *
  * We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
  * of analytics engineer's request.
  */
 const EVENT_WHITELIST = {
+  // track when notification is shown
+  "notification-appear": {
+    topic: "firefox-onboarding-event2",
+    category: "notification-interactions",
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+      width: isMinusOne,
+    }),
+    sendImpression: true,
+    sendLogoState: true,
+  },
   // track when click the notification close button
   "notification-close-button-click": {
     topic: "firefox-onboarding-event2",
     category: "notification-interactions",
-    validators: BASIC_EVENT_SCHEMA,
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isEmptyString,
+      target_tour_id: isEmptyString,
+      width: isMinusOne,
+    }),
+    sendImpression: true,
+    sendLogoState: true,
   },
   // track when click the notification Call-To-Action button
   "notification-cta-click": {
     topic: "firefox-onboarding-event2",
     category: "notification-interactions",
-    validators: BASIC_EVENT_SCHEMA,
+    validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
+      bubble_state: isValidBubbleState,
+      current_tour_id: hasString,
+      logo_state: isValidLogoState,
+      notification_impression: isPositiveInteger,
+      notification_state: isEmptyString,
+      target_tour_id: hasString,
+      width: isPositiveInteger,
+    }),
+    sendImpression: true,
+    sendLogoState: true,
   },
   // The real event name send to the server, alias of notification-session-end
   "notification-session": {
     topic: "firefox-onboarding-session2",
     category: "notification-interactions",
     validators: Object.assign({}, BASIC_SESSION_SCHEMA, {
       category: isValidCategory,
     }),
   },
   // track when notification is shown
   "notification-session-begin": {topic: "internal"},
   // track when the notification closed
   "notification-session-end": {topic: "internal"},
+  // track when click the onboarding logo or the watermark
+  "onboarding-logo-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    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,
+      width: isPositiveInteger,
+    }),
+    sendLogoState: true,
+  },
   // init onboarding session with session_key and page url
   "onboarding-register-session": {topic: "internal"},
   // The real event name send to the server, alias of onboarding-session-end
   "onboarding-session": {
     topic: "firefox-onboarding-session2",
     category: "overlay-interactions",
     validators: Object.assign({}, BASIC_SESSION_SCHEMA, {
       category: isEmptyString,
     }),
   },
   // track when the onboarding script inited
   "onboarding-session-begin": {topic: "internal"},
   // track when the onboarding script destoryed
   "onboarding-session-end": {topic: "internal"},
+  // track when click the overlay close button
+  "overlay-close-button-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    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,
+      width: isMinusOne,
+    }),
+  },
+  // track when close the overlay via click outside of the tour content area
+  "overlay-close-outside-click": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    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,
+      width: isMinusOne,
+    }),
+  },
   // track when click the overlay Call-To-Action button
   "overlay-cta-click": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
-    validators: BASIC_EVENT_SCHEMA,
+    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,
+      width: isMinusOne,
+    }),
+  },
+  // track when a tour is displayed on the overlay
+  "overlay-current-tour": {
+    topic: "firefox-onboarding-event2",
+    category: "overlay-interactions",
+    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,
+      width: isMinusOne,
+    }),
   },
   // track when click or auto select the overlay navigate item
   "overlay-nav-click": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
-    validators: BASIC_EVENT_SCHEMA,
+    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,
+      width: isMinusOne,
+    }),
   },
   // The real event name send to the server, alias of overlay-session-end
   "overlay-session": {
     topic: "firefox-onboarding-session2",
     category: "overlay-interactions",
     validators:  Object.assign({}, BASIC_SESSION_SCHEMA, {
       category: isValidCategory,
     }),
@@ -166,17 +300,25 @@ const EVENT_WHITELIST = {
   // track when the overlay is shown
   "overlay-session-begin": {topic: "internal"},
   // track when the overlay is closed
   "overlay-session-end":  {topic: "internal"},
   // track when click the overlay "skip tour" button
   "overlay-skip-tour": {
     topic: "firefox-onboarding-event2",
     category: "overlay-interactions",
-    validators: BASIC_EVENT_SCHEMA,
+    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,
+      width: isMinusOne,
+    }),
   },
 };
 
 const OLD_EVENT_WHITELIST = {
   // track when click the notification close button
   "notification-close-button-click": {topic: "firefox-onboarding-event", category: "notification-interactions"},
   // track when click the notification Call-To-Action button
   "notification-cta-click": {topic: "firefox-onboarding-event", category: "notification-interactions"},
@@ -207,18 +349,23 @@ const ONBOARDING_ID = "onboarding";
 let OnboardingTelemetry = {
   sessionProbe: null,
   eventProbe: null,
   state: {
     sessions: {},
   },
 
   init(startupData) {
-    this.sessionProbe = new PingCentre({topic: "firefox-onboarding-session"});
-    this.eventProbe = new PingCentre({topic: "firefox-onboarding-event"});
+    if (NEW_TABLE) {
+      this.sessionProbe = new PingCentre({topic: "firefox-onboarding-session2"});
+      this.eventProbe = new PingCentre({topic: "firefox-onboarding-event2"});
+    } else {
+      this.sessionProbe = new PingCentre({topic: "firefox-onboarding-session"});
+      this.eventProbe = new PingCentre({topic: "firefox-onboarding-event"});
+    }
     this.state.addon_version = startupData.version;
   },
 
   // register per tab session data
   registerNewTelemetrySession(data) {
     let { page, session_key, tour_type } = data;
     if (this.state.sessions[session_key]) {
       return;
@@ -232,22 +379,169 @@ let OnboardingTelemetry = {
       page,
       session_id,
       tour_type,
     };
   },
 
   process(data) {
     if (NEW_TABLE) {
-      throw new Error("Will implement in bug 1413830");
+      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);
+    }
+
+    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_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_begin = Date.now();
+          break;
+        case "notification-session-end":
+          data.event = "notification-session";
+          this._sendPing("firefox-onboarding-session2", data);
+          break;
+      }
+    } else {
+      this._sendPing(topic, data);
+    }
+  },
+
+  // send out pings by topic
+  _sendPing(topic, data) {
+    let {
+      addon_version,
+    } = this.state;
+    let {
+      current_tour_id = "",
+      event,
+      session_key,
+      target_tour_id = "",
+      width = -1,
+    } = data;
+    let {
+      notification_session_begin,
+      onboarding_session_begin,
+      overlay_session_begin,
+      page,
+      session_id,
+      tour_type,
+    } = this.state.sessions[session_key];
+    let {
+      category,
+      sendImpression,
+      sendLogoState,
+    } = EVENT_WHITELIST[event];
+    let session_begin;
+    let payload;
+    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_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_begin = overlay_session_begin;
+            break;
+          case "notification-session":
+            if (!notification_session_begin) {
+              throw new Error(`should fire notification-session-begin event before ${event}`);
+            }
+            session_begin = notification_session_begin;
+            break;
+        }
+
+        let session_end = Date.now();
+        payload = {
+          addon_version,
+          category,
+          page,
+          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":
+        let timestamp = Date.now();
+        payload = {
+          addon_version,
+          category,
+          current_tour_id,
+          logo_state: "",
+          page,
+          session_id,
+          target_tour_id,
+          timestamp,
+          tour_type,
+          type: event,
+          width,
+        };
+        if (sendImpression) {
+          payload.notification_impression =
+            Services.prefs.getIntPref("browser.onboarding.notification.prompt-count", 0);
+        } else {
+          payload.notification_impression = -1;
+        }
+        if (sendLogoState) {
+          // the field is used to identify how user open the overlay (through default logo or watermark),
+          // the number of open from notification can be retrieved via `notification-cta-click` event
+          let logo_state = Services.prefs.getStringPref("browser.onboarding.state", "default");
+          if (logo_state === "default") {
+            logo_state = "logo";
+          }
+          payload.logo_state = logo_state;
+        }
+        this._validatePayload(payload);
+        this.eventProbe && this.eventProbe.sendPing(payload,
+          {filter: ONBOARDING_ID});
+        break;
+    }
+  },
+
   processOldPings(data) {
     let { event, session_key } = data;
     let topic = OLD_EVENT_WHITELIST[event] && OLD_EVENT_WHITELIST[event].topic;
     if (!topic) {
       throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(OLD_EVENT_WHITELIST)}`);
     }
 
     if (event === "onboarding-register-session") {
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -411,29 +411,24 @@ class Onboarding {
     });
     telemetry({
       event: "onboarding-session-begin",
       session_key: this._session_key,
     });
   }
 
   _resizeUI() {
-    let width = this._window.document.body.getBoundingClientRect().width;
-    if (width < ONBOARDING_MIN_WIDTH_PX) {
+    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;
     }
 
     this._initUI();
-    if (this._isFirstSession && width >= SPEECH_BUBBLE_MIN_WIDTH_PX) {
-      this._overlayIcon.classList.add("onboarding-speech-bubble");
-    } else {
-      this._overlayIcon.classList.remove("onboarding-speech-bubble");
-    }
   }
 
   _initUI() {
     if (this.uiInitialized) {
       return;
     }
     this.uiInitialized = true;
     this._tourItems = [];
@@ -468,22 +463,22 @@ class Onboarding {
     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._renderUI();
         }
       };
       doc.addEventListener("visibilitychange", onVisible);
     } else {
-      this.showNotification();
+      this._renderUI();
     }
   }
 
   _initPrefObserver() {
     if (this._prefsObserved) {
       return;
     }
 
@@ -526,74 +521,100 @@ class Onboarding {
    * Find a tour that should be selected. It is either a first tour that was not
    * yet complete or the first one in the tab list.
    */
   get _firstUncompleteTour() {
     return this._tours.find(tour => !this.isTourCompleted(tour.id)) ||
            this._tours[0];
   }
 
+  /*
+   * Return currently showing tour navigation item
+   */
+  get _activeTourId() {
+    return this._tourItems.find(item => item.classList.contains("onboarding-active")).id;
+  }
+
   handleClick(target) {
     let { id, classList } = target;
     // Only containers receive pointer events in onboarding tour tab list,
     // actual semantic tab is their first child.
     if (classList.contains("onboarding-tour-item-container")) {
       ({ id, classList } = target.firstChild);
     }
 
     switch (id) {
       case "onboarding-overlay-button":
         this.showOverlay();
         this.gotoPage(this._firstUncompleteTour.id);
+        telemetry({
+          event: "onboarding-logo-click",
+          width: this.width,
+          session_key: this._session_key,
+        });
         break;
       case "onboarding-skip-tour-button":
         this.hideNotification();
         this.hideOverlay();
         this.skipTour();
         break;
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
+        let eventName = id === "onboarding-overlay-close-btn" ?
+          "overlay-close-button-click" : "overlay-close-outside-click";
+        telemetry({
+          event: eventName,
+          current_tour_id: this._activeTourId,
+          session_key: this._session_key,
+        });
         this.hideOverlay();
         break;
       case "onboarding-notification-close-btn":
-        let tour_id = this._notificationBar.dataset.targetTourId;
+        let current_tour_id = this._notificationBar.dataset.targetTourId;
         this.hideNotification();
-        this._removeTourFromNotificationQueue(tour_id);
+        this._removeTourFromNotificationQueue(current_tour_id);
         telemetry({
           event: "notification-close-button-click",
-          tour_id,
+          current_tour_id,
           session_key: this._session_key,
         });
         break;
       case "onboarding-notification-action-btn":
         let tourId = this._notificationBar.dataset.targetTourId;
         this.showOverlay();
         this.gotoPage(tourId);
         telemetry({
           event: "notification-cta-click",
-          tour_id: tourId,
+          current_tour_id: tourId,
+          width: this.width,
           session_key: this._session_key,
         });
         this._removeTourFromNotificationQueue(tourId);
         break;
     }
     if (classList.contains("onboarding-tour-item")) {
+      telemetry({
+        event: "overlay-nav-click",
+        current_tour_id: this._activeTourId,
+        target_tour_id: id,
+        session_key: this._session_key,
+      });
       this.gotoPage(id);
       // Keep focus (not visible) on current item for potential keyboard
       // navigation.
       target.focus();
     } else if (classList.contains("onboarding-tour-action-button")) {
-      let activeItem = this._tourItems.find(item => item.classList.contains("onboarding-active"));
-      this.setToursCompleted([ activeItem.id ]);
+      this.setToursCompleted([ this._activeTourId ]);
       telemetry({
         event: "overlay-cta-click",
-        tour_id: activeItem.id,
+        current_tour_id: this._activeTourId,
+        target_tour_id: this._activeTourId,
         session_key: this._session_key,
       });
     }
   }
 
   /**
    * Wrap keyboard focus within the dialog.
    * When moving forward, focus on the first element when the current focused
@@ -792,43 +813,47 @@ class Onboarding {
         delete this._overlayIcon.dataset.keyboardFocus;
         this._overlayIcon.focus();
       } else {
         this._window.document.activeElement.blur();
       }
     }
   }
 
+  /**
+   * Switch to proper tour.
+   * @param {String} tourId specify which tour should be switched.
+   */
   gotoPage(tourId) {
     let targetPageId = `${tourId}-page`;
     for (let page of this._tourPages) {
       if (page.id === targetPageId) {
         page.style.display = "";
         page.dispatchEvent(new this._window.CustomEvent("beforeshow"));
       } else {
         page.style.display = "none";
       }
     }
     for (let tab of this._tourItems) {
       if (tab.id == tourId) {
         tab.classList.add("onboarding-active");
         tab.setAttribute("aria-selected", true);
         telemetry({
-          event: "overlay-nav-click",
-          tour_id: tourId,
+          event: "overlay-current-tour",
+          current_tour_id: tourId,
           session_key: this._session_key,
         });
 
         // Some tours should complete instantly upon showing.
         if (tab.getAttribute("data-instant-complete")) {
           this.setToursCompleted([tourId]);
           // Also track auto-completed tour so we can filter data with the same event.
           telemetry({
             event: "overlay-cta-click",
-            tour_id: tourId,
+            current_tour_id: tourId,
             session_key: this._session_key,
           });
         }
       } else {
         tab.classList.remove("onboarding-active");
         tab.setAttribute("aria-selected", false);
       }
     }
@@ -978,16 +1003,26 @@ class Onboarding {
       sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.tour-ids-queue",
         value: queue
       }]);
     }
     return queue ? queue.split(",") : [];
   }
 
+  _renderUI() {
+    // log the onboarding-session when its on screen
+    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;
@@ -1070,31 +1105,44 @@ class Onboarding {
         value: promptCount + 1
       });
     }
     sendMessageToChrome("set-prefs", params);
     telemetry({
       event: "notification-session-begin",
       session_key: this._session_key
     });
+    telemetry({
+      event: "notification-appear",
+      current_tour_id: targetTourId,
+      session_key: this._session_key
+    });
   }
 
   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,
         });
       }
     }
   }
 
+  showSpeechBubble() {
+    if (this._isFirstSession && this.width >= SPEECH_BUBBLE_MIN_WIDTH_PX) {
+      this._overlayIcon.classList.add("onboarding-speech-bubble");
+    } else {
+      this._overlayIcon.classList.remove("onboarding-speech-bubble");
+    }
+  }
+
   _renderNotificationBar() {
     let footer = this._window.document.createElement("footer");
     footer.id = "onboarding-notification-bar";
     footer.setAttribute("aria-live", "polite");
     footer.setAttribute("aria-labelledby", "onboarding-notification-tour-title");
     // We use `innerHTML` for more friendly reading.
     // The security should be fine because this is not from an external input.
     footer.innerHTML = `
@@ -1124,16 +1172,17 @@ class Onboarding {
       },
       {
         name: "browser.onboarding.state",
         value: ICON_STATE_WATERMARK
       }
     ]);
     telemetry({
       event: "overlay-skip-tour",
+      current_tour_id: this._activeTourId,
       session_key: this._session_key
     });
   }
 
   _renderOverlay() {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-overlay";
     // We use `innerHTML` for more friendly reading.
--- a/browser/extensions/onboarding/data_events.md
+++ b/browser/extensions/onboarding/data_events.md
@@ -14,108 +14,137 @@ For reference, Onyx is a Mozilla owned s
 
 ```js
 {
   // These fields are sent from the client
   "addon_version": "1.0.0",
   "category": ["overlay-interactions"|"notification-interactions"],
   "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e",
   "locale": "en-US",
-  "event": ["onboarding_session" | "overlay_session" | "notification_session"],
+  "type": ["onboarding_session" | "overlay_session" | "notification_session"],
   "page": ["about:newtab" | "about:home"],
   "session_begin": 1505440017018,
   "session_end": 1505440021992,
   "session_id": "{12dasd-213asda-213dkakj}",
-  "tour_id": ["onboarding-tour-private-browsing" | "onboarding-tour-addons"|...], // tour ids defined in 'onboardingTourset'
-  "tour_source": ["default" | "watermark"],
   "tour_type" ["new" | "update"],
 
   // These fields are generated on the server
   "date": "2016-03-07",
   "ip": "10.192.171.13",
   "ua": "python-requests/2.9.1",
   "receive_at": 1457396660000
 }
 ```
 
+| KEY | DESCRIPTION | &nbsp; |
+|-----|-------------|:-----:|
+| `addon_version` | [Required] The version of the Onboarding addon. | :one:
+| `category` | 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:
+| `session_begin` | Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one:
+| `session_end` | 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:
+| `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": ["overlay-interactions"|"notification-interactions"],
   "client_id": "374dc4d8-0cb2-4ac5-a3cf-c5a9bc3c602e",
-  "timestamp": 1505440017019,
-  "event": ["notification-cta-click" | "overlay-cta-click" | "overlay-nav-click" | "overlay-skip-tour"],
-  "impression": [0-8],
   "locale": "en-US",
+  "logo_state": ["logo" | "watermark"],
+  "notification_impression": [1-8],
   "page": ["about:newtab" | "about:home"],
   "session_id": "{12dasd-213asda-213dkakj}",
+  "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'
-  "tour_source": ["default" | "watermark"],
+  "timestamp": 1505440017019,
   "tour_type" ["new" | "update"],
+  "type": ["notification-cta-click" | "overlay-cta-click" | "overlay-nav-click" | "overlay-skip-tour"...],
+  "width": 960,
 
   // These fields are generated on the server
   "ip": "10.192.171.13",
   "ua": "python-requests/2.9.1",
   "receive_at": 1457396660000,
   "date": "2016-03-07",
 }
 ```
 
 
 | 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:
-| `event` | [Required] The type of event. allowed event strings are defined in the below section | :one:
-| `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:
+| `current_tour_id` | 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` | [Required] 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_begin` | Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one:
 | `session_end` | 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:
+| `target_tour_id` | 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` | Timestamp in (integer) milliseconds when the event triggered | :one:
-| `tour_id` | 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:
-| `tour_source` | [Required] One of ["default", "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:
 | `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` | Current browser window width | :one:
 
 **Where:**
 
 :one: Firefox data
 :two: HTTP protocol data
 
 ## Events
 
 Here are all allowed `event` strings that defined in `OnboardingTelemetry::EVENT_WHITELIST`.
 ### Session events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
 | `onboarding-register-session` | internal event triggered to register a new page session. Called when the onboarding script is inited in a browser tab. Will not send out any data. |
 | `onboarding-session-begin` | internal event triggered when the onboarding script is inited, will not send out any data. |
-| `onboarding-session-end` | internal event triggered when the onboarding script is destroyed. `onboarding-session` event is sent to the server. |
-| `onboarding-session` | event is sent when the onboarding script is destroyed |
+| `onboarding-session-end` | internal event triggered when the onboarding script is destroyed. |
+| `onboarding-session` | event is sent when the onboarding script is destroyed. |
+
+### Onboarding events
+
+| EVENT | DESCRIPTION |
+|-----------|---------------------|
+| `onboarding-logo-click` | triggered when user opens the overlay via clicking the logo. |
 
 ### Overlay events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
-| `overlay-session-begin` | internal event triggered when user open the overlay, will not send out any data. |
-| `overlay-session-end` | internal event is triggered when user closes the overlay. `overlay-session` event is sent to the server. |
-| `overlay-session` | event is sent when user close the overlay |
-| `overlay-nav-click` | event is sent when clicking or auto select the overlay navigate item |
-| `overlay-cta-click` | event is sent when user click the overlay CTA button |
-| `overlay-skip-tour` | event is sent when clicking the overlay `skip tour` button |
+| `overlay-close-button-click` | triggered when close the overlay via click the overlay close button. |
+| `overlay-close-outside-click` | triggered when close the overlay via click outside of the tour content area. |
+| `overlay-cta-click` | event is sent when click the overlay CTA button. |
+| `overlay-current-tour` | event is sent when any tour is displayed on the overlay, can be used to calculate the drop rate. |
+| `overlay-nav-click` | event is sent when click the overlay navigate item. |
+| `overlay-session-begin` | internal event triggered when open the overlay, will not send out any data. |
+| `overlay-session-end` | internal event is triggered when closes the overlay. `overlay-session` event is sent to the server. |
+| `overlay-session` | event is sent when close the overlay. |
+| `overlay-skip-tour` | event is sent when clicking the overlay `skip tour` button. |
 
 ### Notification events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
+| `notification-appear` | event is sent when one notification appears. |
 | `notification-session-begin` | internal event triggered when user open the notification, will not send out any data. |
-| `notification-session-end` | internal event is triggered when user closes the notification. `notification-session` event is sent to the server. |
+| `notification-session-end` | internal event is triggered when user closes the notification. |
 | `notification-session` | event is sent when user closes the notification |
 | `notification-close-button-click` | event is sent when clicking the notification close button |
 | `notification-cta-click` | event is sent when clicking the notification CTA button |