Bug 1127574 - Part 2: Add telemetry hooks for Hello rooms creation and deletion actions. r=Standard8, r=vladan, a=kglazko
authorMike de Boer <mdeboer@mozilla.com>
Mon, 06 Jul 2015 14:53:45 +0200
changeset 281460 58fd619fde28047703b2106a8d6c25320c2ac0e1
parent 281459 cf8f2ea74db0356d9d1c951d3df370223523712a
child 281461 82613d1611612bf1dc5fa31f9d2e22efc0256720
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, vladan, kglazko
bugs1127574
milestone41.0a2
Bug 1127574 - Part 2: Add telemetry hooks for Hello rooms creation and deletion actions. r=Standard8, r=vladan, a=kglazko
browser/components/loop/content/js/roomStore.js
browser/components/loop/content/shared/js/conversationStore.js
browser/components/loop/modules/MozLoopAPI.jsm
browser/components/loop/modules/MozLoopService.jsm
browser/components/loop/test/desktop-local/roomStore_test.js
browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
browser/components/loop/test/shared/conversationStore_test.js
toolkit/components/telemetry/Histograms.json
--- a/browser/components/loop/content/js/roomStore.js
+++ b/browser/components/loop/content/js/roomStore.js
@@ -270,24 +270,27 @@ loop.store = loop.store || {};
 
       if ("urls" in actionData) {
         roomCreationData.decryptedContext.urls = actionData.urls;
       }
 
       this._notifications.remove("create-room-error");
 
       this._mozLoop.rooms.create(roomCreationData, function(err, createdRoom) {
+        var buckets = this._mozLoop.ROOM_CREATE;
         if (err) {
+          this._mozLoop.telemetryAddValue("LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
           this.dispatchAction(new sharedActions.CreateRoomError({error: err}));
           return;
         }
 
         this.dispatchAction(new sharedActions.CreatedRoom({
           roomToken: createdRoom.roomToken
         }));
+        this._mozLoop.telemetryAddValue("LOOP_ROOM_CREATE", buckets.CREATE_SUCCESS);
       }.bind(this));
     },
 
     /**
      * Executed when a room has been created
      */
     createdRoom: function(actionData) {
       this.setStoreState({pendingCreation: false});
@@ -393,19 +396,22 @@ loop.store = loop.store || {};
 
     /**
      * Creates a new room.
      *
      * @param {sharedActions.DeleteRoom} actionData The action data.
      */
     deleteRoom: function(actionData) {
       this._mozLoop.rooms.delete(actionData.roomToken, function(err) {
+        var buckets = this._mozLoop.ROOM_DELETE;
         if (err) {
-         this.dispatchAction(new sharedActions.DeleteRoomError({error: err}));
+          this.dispatchAction(new sharedActions.DeleteRoomError({error: err}));
         }
+        this._mozLoop.telemetryAddValue("LOOP_ROOM_DELETE", buckets[err ?
+          "DELETE_FAIL" : "DELETE_SUCCESS"]);
       }.bind(this));
     },
 
     /**
      * Executed when a room deletion error occurs.
      *
      * @param {sharedActions.DeleteRoomError} actionData The action data.
      */
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -434,21 +434,24 @@ loop.store = loop.store || {};
      */
     fetchRoomEmailLink: function(actionData) {
       this.mozLoop.rooms.create({
         roomName: actionData.roomName,
         roomOwner: actionData.roomOwner,
         maxSize: loop.store.MAX_ROOM_CREATION_SIZE,
         expiresIn: loop.store.DEFAULT_EXPIRES_IN
       }, function(err, createdRoomData) {
+        var buckets = this.mozLoop.ROOM_CREATE;
         if (err) {
           this.trigger("error:emailLink");
+          this.mozLoop.telemetryAddValue("LOOP_ROOM_CREATE", buckets.CREATE_FAIL);
           return;
         }
         this.setStoreState({"emailLink": createdRoomData.roomUrl});
+        this.mozLoop.telemetryAddValue("LOOP_ROOM_CREATE", buckets.CREATE_SUCCESS);
       }.bind(this));
     },
 
     /**
      * Handles when the remote stream has been enabled and is supplied.
      *
      * @param  {sharedActions.RemoteVideoEnabled} actionData
      */
--- a/browser/components/loop/modules/MozLoopAPI.jsm
+++ b/browser/components/loop/modules/MozLoopAPI.jsm
@@ -676,16 +676,30 @@ function injectLoopAPI(targetWindow) {
 
     SHARING_ROOM_URL: {
       enumerable: true,
       get: function() {
         return Cu.cloneInto(SHARING_ROOM_URL, targetWindow);
       }
     },
 
+    ROOM_CREATE: {
+      enumerable: true,
+      get: function() {
+        return Cu.cloneInto(ROOM_CREATE, targetWindow);
+      }
+    },
+
+    ROOM_DELETE: {
+      enumerable: true,
+      get: function() {
+        return Cu.cloneInto(ROOM_DELETE, targetWindow);
+      }
+    },
+
     fxAEnabled: {
       enumerable: true,
       get: function() {
         return MozLoopService.fxAEnabled;
       },
     },
 
     /**
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -50,31 +50,52 @@ const SHARING_STATE_CHANGE = {
  */
 const SHARING_ROOM_URL = {
   COPY_FROM_PANEL: 0,
   COPY_FROM_CONVERSATION: 1,
   EMAIL_FROM_CALLFAILED: 2,
   EMAIL_FROM_CONVERSATION: 3
 };
 
+/**
+ * Values that we segment room create action telemetry probes into.
+ *
+ * @type {{CREATE_SUCCESS: Number, CREATE_FAIL: Number}}
+ */
+const ROOM_CREATE = {
+  CREATE_SUCCESS: 0,
+  CREATE_FAIL: 1
+};
+
+/**
+ * Values that we segment room delete action telemetry probes into.
+ *
+ * @type {{DELETE_SUCCESS: Number, DELETE_FAIL: Number}}
+ */
+const ROOM_DELETE = {
+  DELETE_SUCCESS: 0,
+  DELETE_FAIL: 1
+};
+
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "loop.debug.loglevel";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE",
-  "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE", "SHARING_ROOM_URL"];
+  "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE", "SHARING_ROOM_URL",
+  "ROOM_CREATE", "ROOM_DELETE"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
   "resource://gre/modules/media/RTCStatsReport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
   "resource:///modules/loop/utils.js", "utils");
--- a/browser/components/loop/test/desktop-local/roomStore_test.js
+++ b/browser/components/loop/test/desktop-local/roomStore_test.js
@@ -77,23 +77,32 @@ describe("loop.store.RoomStore", functio
     beforeEach(function() {
       fakeMozLoop = {
         SHARING_ROOM_URL: {
           COPY_FROM_PANEL: 0,
           COPY_FROM_CONVERSATION: 1,
           EMAIL_FROM_PANEL: 2,
           EMAIL_FROM_CONVERSATION: 3
         },
+        ROOM_CREATE: {
+          CREATE_SUCCESS: 0,
+          CREATE_FAIL: 1
+        },
+        ROOM_DELETE: {
+          DELETE_SUCCESS: 0,
+          DELETE_FAIL: 1
+        },
         copyString: function() {},
         getLoopPref: function(pref) {
           return pref;
         },
         notifyUITour: function() {},
         rooms: {
           create: function() {},
+          delete: function() {},
           getAll: function() {},
           open: function() {},
           rename: function() {},
           on: sandbox.stub()
         },
         telemetryAddValue: sinon.stub()
       };
       fakeNotifications = {
@@ -316,16 +325,41 @@ describe("loop.store.RoomStore", functio
           store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
 
           sinon.assert.calledOnce(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
             new sharedActions.CreateRoomError({
               error: err
             }));
         });
+
+      it("should log a telemetry event when the operation is successful", function() {
+        sandbox.stub(fakeMozLoop.rooms, "create", function(data, cb) {
+          cb(null, {roomToken: "fakeToken"});
+        });
+
+        store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
+
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue,
+          "LOOP_ROOM_CREATE", 0);
+      });
+
+      it("should log a telemetry event when the operation fails", function() {
+        var err = new Error("fake");
+        sandbox.stub(fakeMozLoop.rooms, "create", function(data, cb) {
+          cb(err);
+        });
+
+        store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
+
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue,
+          "LOOP_ROOM_CREATE", 1);
+      });
    });
 
    describe("#createdRoom", function() {
       beforeEach(function() {
         sandbox.stub(dispatcher, "dispatch");
       });
 
       it("should switch the pendingCreation state flag to false", function() {
@@ -371,16 +405,80 @@ describe("loop.store.RoomStore", functio
         sinon.assert.calledOnce(fakeNotifications.set);
         sinon.assert.calledWithMatch(fakeNotifications.set, {
           id: "create-room-error",
           level: "error"
         });
       });
     });
 
+    describe("#deleteRoom", function() {
+      var fakeRoomToken = "42abc";
+
+      beforeEach(function() {
+        sandbox.stub(dispatcher, "dispatch");
+      });
+
+      it("should request deletion of a room", function() {
+        sandbox.stub(fakeMozLoop.rooms, "delete");
+
+        store.deleteRoom(new sharedActions.DeleteRoom({
+          roomToken: fakeRoomToken
+        }));
+
+        sinon.assert.calledWith(fakeMozLoop.rooms.delete, fakeRoomToken);
+      });
+
+      it("should dispatch a DeleteRoomError action if the operation fails", function() {
+        var err = new Error("fake");
+        sandbox.stub(fakeMozLoop.rooms, "delete", function(roomToken, cb) {
+          cb(err);
+        });
+
+        store.deleteRoom(new sharedActions.DeleteRoom({
+          roomToken: fakeRoomToken
+        }));
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.DeleteRoomError({
+            error: err
+          }));
+      });
+
+      it("should log a telemetry event when the operation is successful", function() {
+        sandbox.stub(fakeMozLoop.rooms, "delete", function(roomToken, cb) {
+          cb();
+        });
+
+        store.deleteRoom(new sharedActions.DeleteRoom({
+          roomToken: fakeRoomToken
+        }));
+
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue,
+          "LOOP_ROOM_DELETE", 0);
+      });
+
+      it("should log a telemetry event when the operation fails", function() {
+        var err = new Error("fake");
+        sandbox.stub(fakeMozLoop.rooms, "delete", function(roomToken, cb) {
+          cb(err);
+        });
+
+        store.deleteRoom(new sharedActions.DeleteRoom({
+          roomToken: fakeRoomToken
+        }));
+
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue,
+          "LOOP_ROOM_DELETE", 1);
+      });
+    });
+
     describe("#copyRoomUrl", function() {
       it("should copy the room URL", function() {
         var copyString = sandbox.stub(fakeMozLoop, "copyString");
 
         store.copyRoomUrl(new sharedActions.CopyRoomUrl({
           roomUrl: "http://invalid",
           from: "conversation"
         }));
--- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -101,8 +101,46 @@ add_task(function* test_mozLoop_telemetr
     "SHARING_ROOM_URL.COPY_FROM_PANEL");
   Assert.strictEqual(snapshot.counts[SHARING_TYPES.COPY_FROM_CONVERSATION], 2,
     "SHARING_ROOM_URL.COPY_FROM_CONVERSATION");
   Assert.strictEqual(snapshot.counts[SHARING_TYPES.EMAIL_FROM_CALLFAILED], 3,
     "SHARING_ROOM_URL.EMAIL_FROM_CALLFAILED");
   Assert.strictEqual(snapshot.counts[SHARING_TYPES.EMAIL_FROM_CONVERSATION], 4,
     "SHARING_ROOM_URL.EMAIL_FROM_CONVERSATION");
 });
+
+add_task(function* test_mozLoop_telemetryAdd_roomCreate_buckets() {
+  let histogramId = "LOOP_ROOM_CREATE";
+  let histogram = Services.telemetry.getHistogramById(histogramId);
+  const ACTION_TYPES = gMozLoopAPI.ROOM_CREATE;
+
+  histogram.clear();
+  for (let value of [ACTION_TYPES.CREATE_SUCCESS,
+                     ACTION_TYPES.CREATE_FAIL,
+                     ACTION_TYPES.CREATE_FAIL]) {
+    gMozLoopAPI.telemetryAddValue(histogramId, value);
+  }
+
+  let snapshot = histogram.snapshot();
+  Assert.strictEqual(snapshot.counts[ACTION_TYPES.CREATE_SUCCESS], 1,
+    "SHARING_ROOM_URL.CREATE_SUCCESS");
+  Assert.strictEqual(snapshot.counts[ACTION_TYPES.CREATE_FAIL], 2,
+    "SHARING_ROOM_URL.CREATE_FAIL");
+});
+
+add_task(function* test_mozLoop_telemetryAdd_roomDelete_buckets() {
+  let histogramId = "LOOP_ROOM_DELETE";
+  let histogram = Services.telemetry.getHistogramById(histogramId);
+  const ACTION_TYPES = gMozLoopAPI.ROOM_DELETE;
+
+  histogram.clear();
+  for (let value of [ACTION_TYPES.DELETE_SUCCESS,
+                     ACTION_TYPES.DELETE_FAIL,
+                     ACTION_TYPES.DELETE_FAIL]) {
+    gMozLoopAPI.telemetryAddValue(histogramId, value);
+  }
+
+  let snapshot = histogram.snapshot();
+  Assert.strictEqual(snapshot.counts[ACTION_TYPES.DELETE_SUCCESS], 1,
+    "SHARING_ROOM_URL.DELETE_SUCCESS");
+  Assert.strictEqual(snapshot.counts[ACTION_TYPES.DELETE_FAIL], 2,
+    "SHARING_ROOM_URL.DELETE_FAIL");
+});
--- a/browser/components/loop/test/shared/conversationStore_test.js
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -34,26 +34,31 @@ describe("loop.store.ConversationStore",
       email: [{
         type: "home",
         value: "fakeEmail",
         pref: true
       }]
     };
 
     fakeMozLoop = {
+      ROOM_CREATE: {
+        CREATE_SUCCESS: 0,
+        CREATE_FAIL: 1
+      },
       getLoopPref: sandbox.stub(),
       addConversationContext: sandbox.stub(),
       calls: {
         setCallInProgress: sandbox.stub(),
         clearCallInProgress: sandbox.stub(),
         blockDirectCaller: sandbox.stub()
       },
       rooms: {
         create: sandbox.stub()
-      }
+      },
+      telemetryAddValue: sandbox.stub()
     };
 
     dispatcher = new loop.Dispatcher();
     client = {
       setupOutgoingCall: sinon.stub(),
       requestCallUrl: sinon.stub()
     };
     sdkDriver = {
@@ -1031,31 +1036,35 @@ describe("loop.store.ConversationStore",
           cb(null, {roomUrl: "http://fake.invalid/"});
         };
         store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
           roomOwner: "bob@invalid.tld",
           roomName: "FakeRoomName"
         }));
 
         expect(store.getStoreState("emailLink")).eql("http://fake.invalid/");
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue, "LOOP_ROOM_CREATE", 0);
       });
 
     it("should trigger an error:emailLink event in case of failure",
       function() {
         var trigger = sandbox.stub(store, "trigger");
         fakeMozLoop.rooms.create = function(roomData, cb) {
           cb(new Error("error"));
         };
         store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
           roomOwner: "bob@invalid.tld",
           roomName: "FakeRoomName"
         }));
 
         sinon.assert.calledOnce(trigger);
         sinon.assert.calledWithExactly(trigger, "error:emailLink");
+        sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
+        sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue, "LOOP_ROOM_CREATE", 1);
       });
   });
 
   describe("#windowUnload", function() {
     var fakeWs;
 
     beforeEach(function() {
       fakeWs = store._websocket = {
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7613,16 +7613,32 @@
   "LOOP_SHARING_ROOM_URL": {
     "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
     "expires_in_version": "44",
     "kind": "enumerated",
     "n_values": 8,
     "releaseChannelCollection": "opt-out",
     "description": "Number of times a room URL is shared (0=COPY_FROM_PANEL, 1=COPY_FROM_CONVERSATION, 2=EMAIL_FROM_CALLFAILED, 3=EMAIL_FROM_CONVERSATION)"
   },
+  "LOOP_ROOM_CREATE": {
+    "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
+    "expires_in_version": "44",
+    "kind": "enumerated",
+    "n_values": 4,
+    "releaseChannelCollection": "opt-out",
+    "description": "Number of times a room create action is performed (0=CREATE_SUCCESS, 1=CREATE_FAIL)"
+  },
+  "LOOP_ROOM_DELETE": {
+    "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
+    "expires_in_version": "44",
+    "kind": "enumerated",
+    "n_values": 4,
+    "releaseChannelCollection": "opt-out",
+    "description": "Number of times a room delete action is performed (0=DELETE_SUCCESS, 2=DELETE_FAIL)"
+  },
   "E10S_AUTOSTART": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether a session is set to autostart e10s windows"
   },
   "E10S_AUTOSTART_STATUS": {
     "expires_in_version": "never",
     "kind": "enumerated",