diff --git a/browser/components/loop/content/js/roomStore.js b/browser/components/loop/content/js/roomStore.js index 6ee977c763cc..8dc9765db966 100644 --- a/browser/components/loop/content/js/roomStore.js +++ b/browser/components/loop/content/js/roomStore.js @@ -286,6 +286,15 @@ loop.store = loop.store || {}; roomToken: createdRoom.roomToken })); this._mozLoop.telemetryAddValue("LOOP_ROOM_CREATE", buckets.CREATE_SUCCESS); + + // Since creating a room with context is only possible from the panel, + // we can record that as the action here. + var URLs = roomCreationData.decryptedContext.urls; + if (URLs && URLs.length) { + buckets = this._mozLoop.ROOM_CONTEXT_ADD; + this._mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_ADD", + buckets.ADD_FROM_PANEL); + } }.bind(this)); }, @@ -535,6 +544,8 @@ loop.store = loop.store || {}; return; } + var hadContextBefore = !!oldRoomURL; + this.setStoreState({error: null}); this._mozLoop.rooms.update(actionData.roomToken, roomData, function(error, data) { @@ -542,6 +553,15 @@ loop.store = loop.store || {}; new sharedActions.UpdateRoomContextError({ error: error }) : new sharedActions.UpdateRoomContextDone(); this.dispatchAction(action); + + if (!err && !hadContextBefore) { + // Since updating the room context data is only possible from the + // conversation window, we can assume that any newly added URL was + // done from there. + var buckets = this._mozLoop.ROOM_CONTEXT_ADD; + this._mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_ADD", + buckets.ADD_FROM_CONVERSATION); + } }.bind(this)); }.bind(this)); }, diff --git a/browser/components/loop/content/js/roomViews.js b/browser/components/loop/content/js/roomViews.js index b61373c5b130..e791fed75724 100644 --- a/browser/components/loop/content/js/roomViews.js +++ b/browser/components/loop/content/js/roomViews.js @@ -398,7 +398,10 @@ loop.roomViews = (function(mozL10n) { return; } - this.props.mozLoop.openURL(url.location); + var mozLoop = this.props.mozLoop; + mozLoop.openURL(url.location); + + mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1); }, handleCheckboxChange: function(state) { diff --git a/browser/components/loop/content/js/roomViews.jsx b/browser/components/loop/content/js/roomViews.jsx index 94a43cd368e8..6756f738f0e8 100644 --- a/browser/components/loop/content/js/roomViews.jsx +++ b/browser/components/loop/content/js/roomViews.jsx @@ -398,7 +398,10 @@ loop.roomViews = (function(mozL10n) { return; } - this.props.mozLoop.openURL(url.location); + var mozLoop = this.props.mozLoop; + mozLoop.openURL(url.location); + + mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1); }, handleCheckboxChange: function(state) { diff --git a/browser/components/loop/modules/MozLoopAPI.jsm b/browser/components/loop/modules/MozLoopAPI.jsm index 92e19dd673fa..deb866f6f73f 100644 --- a/browser/components/loop/modules/MozLoopAPI.jsm +++ b/browser/components/loop/modules/MozLoopAPI.jsm @@ -695,6 +695,13 @@ function injectLoopAPI(targetWindow) { } }, + ROOM_CONTEXT_ADD: { + enumerable: true, + get: function() { + return Cu.cloneInto(ROOM_CONTEXT_ADD, targetWindow); + } + }, + fxAEnabled: { enumerable: true, get: function() { diff --git a/browser/components/loop/modules/MozLoopService.jsm b/browser/components/loop/modules/MozLoopService.jsm index b6cb250552c2..0f1b68bb9ebd 100644 --- a/browser/components/loop/modules/MozLoopService.jsm +++ b/browser/components/loop/modules/MozLoopService.jsm @@ -75,6 +75,16 @@ const ROOM_DELETE = { DELETE_FAIL: 1 }; +/** + * Values that we segment room context action telemetry probes into. + * + * @type {{ADD_FROM_PANEL: Number, ADD_FROM_CONVERSATION: Number}} + */ +const ROOM_CONTEXT_ADD = { + ADD_FROM_PANEL: 0, + ADD_FROM_CONVERSATION: 1 +}; + // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error". const PREF_LOG_LEVEL = "loop.debug.loglevel"; @@ -90,7 +100,7 @@ Cu.importGlobalProperties(["URL"]); this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE", "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE", "SHARING_ROOM_URL", - "ROOM_CREATE", "ROOM_DELETE"]; + "ROOM_CREATE", "ROOM_DELETE", "ROOM_CONTEXT_ADD"]; XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI", "resource:///modules/loop/MozLoopAPI.jsm"); diff --git a/browser/components/loop/test/desktop-local/roomStore_test.js b/browser/components/loop/test/desktop-local/roomStore_test.js index 67e2080d98c9..eceb061f3c1d 100644 --- a/browser/components/loop/test/desktop-local/roomStore_test.js +++ b/browser/components/loop/test/desktop-local/roomStore_test.js @@ -90,6 +90,10 @@ describe("loop.store.RoomStore", function () { DELETE_SUCCESS: 0, DELETE_FAIL: 1 }, + ROOM_CONTEXT_ADD: { + ADD_FROM_PANEL: 0, + ADD_FROM_CONVERSATION: 1 + }, copyString: function() {}, getLoopPref: function(pref) { return pref; @@ -233,14 +237,15 @@ describe("loop.store.RoomStore", function () { var fakeNameTemplate = "Conversation {{conversationLabel}}"; var fakeLocalRoomId = "777"; var fakeOwner = "fake@invalid"; - var fakeRoomCreationData = { - nameTemplate: fakeNameTemplate, - roomOwner: fakeOwner - }; + var fakeRoomCreationData; beforeEach(function() { sandbox.stub(dispatcher, "dispatch"); store.setStoreState({pendingCreation: false, rooms: []}); + fakeRoomCreationData = { + nameTemplate: fakeNameTemplate, + roomOwner: fakeOwner + }; }); it("should clear any existing room errors", function() { @@ -253,6 +258,24 @@ describe("loop.store.RoomStore", function () { "create-room-error"); }); + it("should log a telemetry event when the operation with context is successful", function() { + sandbox.stub(fakeMozLoop.rooms, "create", function(data, cb) { + cb(null, {roomToken: "fakeToken"}); + }); + + fakeRoomCreationData.urls = [{ + location: "http://invalid.com", + description: "fakeSite", + thumbnail: "fakeimage.png" + }]; + + store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData)); + + sinon.assert.calledTwice(fakeMozLoop.telemetryAddValue); + sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue, + "LOOP_ROOM_CONTEXT_ADD", 0); + }); + it("should request creation of a new room", function() { sandbox.stub(fakeMozLoop.rooms, "create"); diff --git a/browser/components/loop/test/desktop-local/roomViews_test.js b/browser/components/loop/test/desktop-local/roomViews_test.js index f036966720d7..14beb1e77f52 100644 --- a/browser/components/loop/test/desktop-local/roomViews_test.js +++ b/browser/components/loop/test/desktop-local/roomViews_test.js @@ -29,6 +29,7 @@ describe("loop.roomViews", function () { previews: [], title: "" }), + openURL: sinon.stub(), rooms: { get: sinon.stub().callsArgWith(1, null, { roomToken: "fakeToken", @@ -39,7 +40,8 @@ describe("loop.roomViews", function () { } }), update: sinon.stub().callsArgWith(2, null) - } + }, + telemetryAddValue: sinon.stub() }; fakeWindow = { @@ -936,5 +938,54 @@ describe("loop.roomViews", function () { expect(node.querySelector(".room-context-comments").value).to.eql(""); }); }); + + describe("#handleContextClick", function() { + var fakeEvent; + + beforeEach(function() { + fakeEvent = { + preventDefault: sinon.stub(), + stopPropagation: sinon.stub() + }; + }); + + it("should not attempt to open a URL when none is attached", function() { + view = mountTestComponent({ + roomData: { + roomToken: "fakeToken", + roomName: "fakeName" + } + }); + + view.handleContextClick(fakeEvent); + + sinon.assert.calledOnce(fakeEvent.preventDefault); + sinon.assert.calledOnce(fakeEvent.stopPropagation); + + sinon.assert.notCalled(fakeMozLoop.openURL); + sinon.assert.notCalled(fakeMozLoop.telemetryAddValue); + }); + + it("should open a URL", function() { + view = mountTestComponent({ + roomData: { + roomToken: "fakeToken", + roomName: "fakeName", + roomContextUrls: [fakeContextURL] + } + }); + + view.handleContextClick(fakeEvent); + + sinon.assert.calledOnce(fakeEvent.preventDefault); + sinon.assert.calledOnce(fakeEvent.stopPropagation); + + sinon.assert.calledOnce(fakeMozLoop.openURL); + sinon.assert.calledWithExactly(fakeMozLoop.openURL, fakeContextURL.location); + sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue); + sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue, + "LOOP_ROOM_CONTEXT_CLICK", 1); + }); + }); }); }); diff --git a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js index 84757efcff76..4a70557fbb89 100644 --- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js +++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js @@ -144,3 +144,36 @@ add_task(function* test_mozLoop_telemetryAdd_roomDelete_buckets() { Assert.strictEqual(snapshot.counts[ACTION_TYPES.DELETE_FAIL], 2, "SHARING_ROOM_URL.DELETE_FAIL"); }); + +add_task(function* test_mozLoop_telemetryAdd_roomContextAdd_buckets() { + let histogramId = "LOOP_ROOM_CONTEXT_ADD"; + let histogram = Services.telemetry.getHistogramById(histogramId); + const ACTION_TYPES = gMozLoopAPI.ROOM_CONTEXT_ADD; + + histogram.clear(); + for (let value of [ACTION_TYPES.ADD_FROM_PANEL, + ACTION_TYPES.ADD_FROM_CONVERSATION, + ACTION_TYPES.ADD_FROM_CONVERSATION]) { + gMozLoopAPI.telemetryAddValue(histogramId, value); + } + + let snapshot = histogram.snapshot(); + Assert.strictEqual(snapshot.counts[ACTION_TYPES.ADD_FROM_PANEL], 1, + "SHARING_ROOM_URL.CREATE_SUCCESS"); + Assert.strictEqual(snapshot.counts[ACTION_TYPES.ADD_FROM_CONVERSATION], 2, + "SHARING_ROOM_URL.ADD_FROM_CONVERSATION"); +}); + +add_task(function* test_mozLoop_telemetryAdd_roomContextClick() { + let histogramId = "LOOP_ROOM_CONTEXT_CLICK"; + let histogram = Services.telemetry.getHistogramById(histogramId); + + histogram.clear(); + + let snapshot; + for (let i = 1; i < 4; ++i) { + gMozLoopAPI.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1); + snapshot = histogram.snapshot(); + Assert.strictEqual(snapshot.counts[0], i); + } +}); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 4f0ad162d784..4e85d14d5591 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7640,6 +7640,21 @@ "releaseChannelCollection": "opt-out", "description": "Number of times a room delete action is performed (0=DELETE_SUCCESS, 2=DELETE_FAIL)" }, + "LOOP_ROOM_CONTEXT_ADD": { + "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 context action is performed (0=ADD_FROM_PANEL, 1=ADD_FROM_CONVERSATION)" + }, + "LOOP_ROOM_CONTEXT_CLICK": { + "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"], + "expires_in_version": "44", + "kind": "count", + "releaseChannelCollection": "opt-out", + "description": "Number times room context is clicked to visit the attached URL" + }, "E10S_AUTOSTART": { "expires_in_version": "never", "kind": "boolean",