Bug 1191014 - Add google analytics event on tile clicks [r=dmose]
authorEd Lee <edilee@mozilla.com>
Mon, 10 Aug 2015 15:44:21 -0700
changeset 284638 09feeb70edac1484166ceebe4ff97fe6bf7ac6cd
parent 284637 9b4fa1dc91fd42a5ecc095bcf194ed98f51209d9
child 284639 b948eb69b69dd1c7e0bd0b47d7d44796511bac10
push id4321
push userjryans@gmail.com
push dateWed, 12 Aug 2015 13:57:32 +0000
reviewersdmose
bugs1191014
milestone43.0a1
Bug 1191014 - Add google analytics event on tile clicks [r=dmose]
browser/components/loop/standalone/content/js/standaloneRoomViews.js
browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
browser/components/loop/test/karma/head.js
browser/components/loop/test/karma/karma.coverage.desktop.js
browser/components/loop/test/karma/karma.coverage.shared_standalone.js
browser/components/loop/test/karma/stubs.js
browser/components/loop/test/shared/textChatView_test.js
browser/components/loop/test/standalone/standaloneRoomViews_test.js
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -85,16 +85,39 @@ loop.standaloneRoomViews = (function(moz
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       failureReason: React.PropTypes.string,
       isFirefox: React.PropTypes.bool.isRequired,
       joinRoom: React.PropTypes.func.isRequired,
       roomState: React.PropTypes.string.isRequired,
       roomUsed: React.PropTypes.bool.isRequired
     },
 
+    componentDidMount: function() {
+      // Watch for messages from the waiting-tile iframe
+      window.addEventListener("message", this.recordTileClick);
+    },
+
+    componentWillUnmount: function() {
+      window.removeEventListener("message", this.recordTileClick);
+    },
+
+    recordTileClick: function(event) {
+      if (event.data === "tile-click") {
+        this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+          linkInfo: "Tiles iframe click"
+        }));
+      }
+    },
+
+    recordTilesSupport: function() {
+      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+        linkInfo: "Tiles support link click"
+      }));
+    },
+
     _renderCallToActionLink: function() {
       if (this.props.isFirefox) {
         return (
           React.createElement("a", {className: "btn btn-info", href: loop.config.learnMoreUrl}, 
             mozL10n.get("rooms_room_full_call_to_action_label", {
               clientShortname: mozL10n.get("clientShortname2")
             })
           )
@@ -150,17 +173,17 @@ loop.standaloneRoomViews = (function(moz
           return (
             React.createElement("div", {className: "room-inner-info-area"}, 
               React.createElement("p", {className: "empty-room-message"}, 
                 mozL10n.get("rooms_only_occupant_label")
               ), 
               React.createElement("p", {className: "room-waiting-area"}, 
                 mozL10n.get("rooms_read_while_wait_offer"), 
                 React.createElement("a", {href: loop.config.tilesSupportUrl, 
-                  onClick: this.recordClick, 
+                  onClick: this.recordTilesSupport, 
                   rel: "noreferrer", 
                   target: "_blank"}, 
                   React.createElement("i", {className: "room-waiting-help"})
                 )
               ), 
               React.createElement("iframe", {className: "room-waiting-tile", src: loop.config.tilesIframeUrl})
             )
           );
@@ -516,11 +539,12 @@ loop.standaloneRoomViews = (function(moz
         )
       );
     }
   });
 
   return {
     StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
+    StandaloneRoomInfoArea: StandaloneRoomInfoArea,
     StandaloneRoomView: StandaloneRoomView
   };
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -85,16 +85,39 @@ loop.standaloneRoomViews = (function(moz
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       failureReason: React.PropTypes.string,
       isFirefox: React.PropTypes.bool.isRequired,
       joinRoom: React.PropTypes.func.isRequired,
       roomState: React.PropTypes.string.isRequired,
       roomUsed: React.PropTypes.bool.isRequired
     },
 
+    componentDidMount: function() {
+      // Watch for messages from the waiting-tile iframe
+      window.addEventListener("message", this.recordTileClick);
+    },
+
+    componentWillUnmount: function() {
+      window.removeEventListener("message", this.recordTileClick);
+    },
+
+    recordTileClick: function(event) {
+      if (event.data === "tile-click") {
+        this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+          linkInfo: "Tiles iframe click"
+        }));
+      }
+    },
+
+    recordTilesSupport: function() {
+      this.props.dispatcher.dispatch(new sharedActions.RecordClick({
+        linkInfo: "Tiles support link click"
+      }));
+    },
+
     _renderCallToActionLink: function() {
       if (this.props.isFirefox) {
         return (
           <a className="btn btn-info" href={loop.config.learnMoreUrl}>
             {mozL10n.get("rooms_room_full_call_to_action_label", {
               clientShortname: mozL10n.get("clientShortname2")
             })}
           </a>
@@ -150,17 +173,17 @@ loop.standaloneRoomViews = (function(moz
           return (
             <div className="room-inner-info-area">
               <p className="empty-room-message">
                 {mozL10n.get("rooms_only_occupant_label")}
               </p>
               <p className="room-waiting-area">
                 {mozL10n.get("rooms_read_while_wait_offer")}
                 <a href={loop.config.tilesSupportUrl}
-                  onClick={this.recordClick}
+                  onClick={this.recordTilesSupport}
                   rel="noreferrer"
                   target="_blank">
                   <i className="room-waiting-help"></i>
                 </a>
               </p>
               <iframe className="room-waiting-tile" src={loop.config.tilesIframeUrl} />
             </div>
           );
@@ -516,11 +539,12 @@ loop.standaloneRoomViews = (function(moz
         </div>
       );
     }
   });
 
   return {
     StandaloneRoomFooter: StandaloneRoomFooter,
     StandaloneRoomHeader: StandaloneRoomHeader,
+    StandaloneRoomInfoArea: StandaloneRoomInfoArea,
     StandaloneRoomView: StandaloneRoomView
   };
 })(navigator.mozL10n);
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/karma/head.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Used for desktop coverage tests because triggering methods on
+// DOMContentLoaded proved to lead to race conditions.
+
+sinon.stub(document, "addEventListener");
+console.log("[head.js] addEventListener stubbed to prevent race conditions");
+
+document.body.appendChild(document.createElement("div")).id = "fixtures";
+console.log("[head.js] div#fixtures added to attach DOM elements");
--- a/browser/components/loop/test/karma/karma.coverage.desktop.js
+++ b/browser/components/loop/test/karma/karma.coverage.desktop.js
@@ -11,17 +11,17 @@ module.exports = function(config) {
   // List of files / patterns to load in the browser.
   baseConfig.files = baseConfig.files.concat([
     "content/libs/l10n.js",
     "content/shared/libs/react-0.12.2.js",
     "content/shared/libs/jquery-2.1.4.js",
     "content/shared/libs/lodash-3.9.3.js",
     "content/shared/libs/backbone-1.2.1.js",
     "test/shared/vendor/*.js",
-    "test/karma/stubs.js", // Stub out DOM event listener due to races.
+    "test/karma/head.js", // Stub out DOM event listener due to races.
     "content/shared/js/utils.js",
     "content/shared/js/models.js",
     "content/shared/js/mixins.js",
     "content/shared/js/websocket.js",
     "content/shared/js/actions.js",
     "content/shared/js/otSdkDriver.js",
     "content/shared/js/validate.js",
     "content/shared/js/dispatcher.js",
--- a/browser/components/loop/test/karma/karma.coverage.shared_standalone.js
+++ b/browser/components/loop/test/karma/karma.coverage.shared_standalone.js
@@ -12,16 +12,17 @@ module.exports = function(config) {
   baseConfig.files = baseConfig.files.concat([
     "standalone/content/libs/l10n-gaia-02ca67948fe8.js",
     "content/shared/libs/jquery-2.1.4.js",
     "content/shared/libs/lodash-3.9.3.js",
     "content/shared/libs/backbone-1.2.1.js",
     "content/shared/libs/react-0.12.2.js",
     "content/shared/libs/sdk.js",
     "test/shared/vendor/*.js",
+    "test/karma/head.js", // Add test fixture container
     "content/shared/js/utils.js",
     "content/shared/js/store.js",
     "content/shared/js/models.js",
     "content/shared/js/mixins.js",
     "content/shared/js/crypto.js",
     "content/shared/js/websocket.js",
     "content/shared/js/validate.js",
     "content/shared/js/actions.js",
deleted file mode 100644
--- a/browser/components/loop/test/karma/stubs.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Used for desktop coverage tests because triggering methods on
-// DOMContentLoaded proved to lead to race conditions.
-
-sinon.stub(document, "addEventListener");
-console.log("[stubs.js] addEventListener stubbed to prevent race conditions");
--- a/browser/components/loop/test/shared/textChatView_test.js
+++ b/browser/components/loop/test/shared/textChatView_test.js
@@ -5,16 +5,17 @@ describe("loop.shared.views.TextChatView
   "use strict";
 
   var expect = chai.expect;
   var sharedActions = loop.shared.actions;
   var sharedViews = loop.shared.views;
   var TestUtils = React.addons.TestUtils;
   var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
   var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
+  var fixtures = document.querySelector("#fixtures");
 
   var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     fakeClock = sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
@@ -30,16 +31,17 @@ describe("loop.shared.views.TextChatView
 
     loop.store.StoreMixin.register({
       textChatStore: store
     });
   });
 
   afterEach(function() {
     sandbox.restore();
+    React.unmountComponentAtNode(fixtures);
   });
 
   describe("TextChatEntriesView", function() {
     var view, node;
 
     function mountTestComponent(extraProps) {
       var basicProps = {
         dispatcher: dispatcher,
@@ -217,44 +219,30 @@ describe("loop.shared.views.TextChatView
       });
       node = view.getDOMNode();
 
       expect(node.querySelectorAll(".text-chat-entry-timestamp").length)
           .to.eql(1);
     });
 
     describe("Scrolling", function() {
-      var fixtures;
-
       beforeEach(function() {
         sandbox.stub(window, "requestAnimationFrame", function(callback) {
           callback();
         });
 
-        fixtures = document.querySelector("#fixtures");
-        // If we're running code coverage in Karma, we might not have
-        // a fixtures element already.
-        if (!fixtures) {
-          fixtures = document.body.appendChild(document.createElement("div"));
-          fixtures.id = "fixtures";
-        }
-
         // We're using scrolling, so we need to mount as a real one.
         view = mountAsRealComponent({}, fixtures);
         sandbox.stub(view, "play");
 
         // We need some basic styling to ensure scrolling.
         view.getDOMNode().style.overflow = "scroll";
         view.getDOMNode().style["max-height"] = "4ch";
       });
 
-      afterEach(function() {
-        React.unmountComponentAtNode(fixtures);
-      });
-
       it("should scroll when a text message is added", function() {
         var messageList = [{
           type: CHAT_MESSAGE_TYPES.RECEIVED,
           contentType: CHAT_CONTENT_TYPES.TEXT,
           message: "Hello!",
           receivedTimestamp: "2015-06-25T17:53:55.357Z"
         }];
 
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -9,16 +9,17 @@ describe("loop.standaloneRoomViews", fun
   var TestUtils = React.addons.TestUtils;
 
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
+  var fixtures = document.querySelector("#fixtures");
 
   var sandbox, dispatcher, activeRoomStore, dispatch;
   var fakeWindow;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
     dispatch = sandbox.stub(dispatcher, "dispatch");
@@ -55,16 +56,17 @@ describe("loop.standaloneRoomViews", fun
 
     // Prevents audio request errors in the test console.
     sandbox.useFakeXMLHttpRequest();
   });
 
   afterEach(function() {
     loop.shared.mixins.setRootObject(window);
     sandbox.restore();
+    React.unmountComponentAtNode(fixtures);
   });
 
   describe("StandaloneRoomHeader", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneRoomHeader, {
             dispatcher: dispatcher
@@ -79,16 +81,49 @@ describe("loop.standaloneRoomViews", fun
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWithExactly(dispatcher.dispatch,
         new sharedActions.RecordClick({
           linkInfo: "Support link click"
         }));
     });
   });
 
+  describe("StandaloneRoomInfoArea in fixture", function() {
+    it("should dispatch a RecordClick action when the tile is clicked", function(done) {
+      // Point the iframe to a page that will auto-"click"
+      loop.config.tilesIframeUrl = "data:text/html,<script>parent.postMessage('tile-click', '*');</script>";
+
+      // Render the iframe into the fixture to cause it to load
+      React.render(
+        React.createElement(
+          loop.standaloneRoomViews.StandaloneRoomInfoArea, {
+            activeRoomStore: activeRoomStore,
+            dispatcher: dispatcher,
+            isFirefox: true,
+            joinRoom: sandbox.stub(),
+            roomState: ROOM_STATES.JOINED,
+            roomUsed: false
+          }), fixtures);
+
+      // Wait for the iframe to load and trigger a message that should also
+      // cause the RecordClick action
+      window.addEventListener("message", function onMessage() {
+        window.removeEventListener("message", onMessage);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.RecordClick({
+            linkInfo: "Tiles iframe click"
+          }));
+
+        done();
+      });
+    });
+  });
+
   describe("StandaloneRoomView", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneRoomView, {
         dispatcher: dispatcher,
         activeRoomStore: activeRoomStore,
         isFirefox: true
@@ -176,30 +211,16 @@ describe("loop.standaloneRoomViews", fun
         it("should display an empty room message on JOINED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .not.eql(null);
           });
 
-        it("should display a waiting room message and tile iframe on JOINED",
-          function() {
-            var DUMMY_TILE_URL = "http://tile/";
-            loop.config.tilesIframeUrl = DUMMY_TILE_URL;
-            activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
-
-            expect(view.getDOMNode().querySelector(".room-waiting-area"))
-              .not.eql(null);
-
-            var tile = view.getDOMNode().querySelector(".room-waiting-tile");
-            expect(tile).not.eql(null);
-            expect(tile.src).eql(DUMMY_TILE_URL);
-          });
-
         it("should display an empty room message on SESSION_CONNECTED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .not.eql(null);
           });
 
@@ -207,16 +228,43 @@ describe("loop.standaloneRoomViews", fun
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .eql(null);
           });
       });
 
+      describe("Empty room tile offer", function() {
+        it("should display a waiting room message and tile iframe on JOINED", function() {
+          var DUMMY_TILE_URL = "http://tile/";
+          loop.config.tilesIframeUrl = DUMMY_TILE_URL;
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
+
+          expect(view.getDOMNode().querySelector(".room-waiting-area")).not.eql(null);
+
+          var tile = view.getDOMNode().querySelector(".room-waiting-tile");
+          expect(tile).not.eql(null);
+          expect(tile.src).eql(DUMMY_TILE_URL);
+        });
+
+        it("should dispatch a RecordClick action when the tile support link is clicked", function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
+
+          TestUtils.Simulate.click(view.getDOMNode().querySelector(".room-waiting-area a"));
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          sinon.assert.calledWithExactly(dispatcher.dispatch,
+            new sharedActions.RecordClick({
+              linkInfo: "Tiles support link click"
+            }));
+        });
+
+      });
+
       describe("Prompt media message", function() {
         it("should display a prompt for user media on MEDIA_WAIT",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
 
             expect(view.getDOMNode().querySelector(".prompt-media-message"))
               .not.eql(null);
           });