mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1132222 - Add more metrics reporting to the Loop standalone UI. r=dmose
This commit is contained in:
parent
c9e64b58de
commit
ed049b31db
@ -468,6 +468,17 @@ loop.shared.actions = (function() {
|
||||
LeaveRoom: Action.define("leaveRoom", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to record a link click for metrics purposes.
|
||||
*/
|
||||
RecordClick: Action.define("recordClick", {
|
||||
// Note: for ToS and Privacy links, this should be the link, for
|
||||
// other links this should be a generic description so that we don't
|
||||
// record what users are clicking, just the information about the fact
|
||||
// they clicked the link in that spot (e.g. "Shared URL").
|
||||
linkInfo: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Requires detailed information on sad feedback.
|
||||
*/
|
||||
|
@ -29,6 +29,7 @@
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-36116321-15', 'auto');
|
||||
ga('set', 'anonymizeIp', true);
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
@ -129,6 +130,7 @@
|
||||
<script type="text/javascript" src="js/standaloneMozLoop.js"></script>
|
||||
<script type="text/javascript" src="js/fxOSMarketplace.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneRoomViews.js"></script>
|
||||
<script type="text/javascript" src="js/standaloneMetricsStore.js"></script>
|
||||
<script type="text/javascript" src="js/webapp.js"></script>
|
||||
|
||||
<script>
|
||||
|
@ -0,0 +1,210 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.store = loop.store || {};
|
||||
|
||||
/**
|
||||
* The standalone metrics store is used to log activities to
|
||||
* analytics.
|
||||
*
|
||||
* Where possible we log events via receiving actions. However, some
|
||||
* combinations of actions and events require us to listen directly to
|
||||
* changes in the activeRoomStore so that we gain the benefit of the logic
|
||||
* in that store.
|
||||
*/
|
||||
loop.store.StandaloneMetricsStore = (function() {
|
||||
"use strict";
|
||||
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
|
||||
loop.store.metrics = loop.store.metrics || {};
|
||||
|
||||
var METRICS_GA_CATEGORY = loop.store.METRICS_GA_CATEGORY = {
|
||||
general: "/conversation/ interactions",
|
||||
download: "Firefox Downloads"
|
||||
};
|
||||
|
||||
var METRICS_GA_ACTIONS = loop.store.METRICS_GA_ACTIONS = {
|
||||
audioMute: "audio mute",
|
||||
button: "button click",
|
||||
download: "download button click",
|
||||
faceMute: "face mute",
|
||||
link: "link click",
|
||||
pageLoad: "page load messages",
|
||||
success: "success",
|
||||
support: "support link click"
|
||||
}
|
||||
|
||||
var StandaloneMetricsStore = loop.store.createStore({
|
||||
actions: [
|
||||
"gotMediaPermission",
|
||||
"joinRoom",
|
||||
"leaveRoom",
|
||||
"mediaConnected",
|
||||
"recordClick"
|
||||
],
|
||||
|
||||
/**
|
||||
* Initializes the store and starts listening to the activeRoomStore.
|
||||
*
|
||||
* @param {Object} options Options for the store, should include a
|
||||
* reference to the activeRoomStore.
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
|
||||
if (!options.activeRoomStore) {
|
||||
throw new Error("Missing option activeRoomStore");
|
||||
}
|
||||
|
||||
// Don't bother listening if we're not storing metrics.
|
||||
// I'd love for ga to be an option, but that messes up the function
|
||||
// working properly.
|
||||
if (window.ga) {
|
||||
this.activeRoomStore = options.activeRoomStore;
|
||||
|
||||
this.listenTo(options.activeRoomStore, "change",
|
||||
this._onActiveRoomStoreChange.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns initial state data for this store. These are mainly reflections
|
||||
* of activeRoomStore so we can match initial states for change tracking
|
||||
* purposes.
|
||||
*
|
||||
* @return {Object} The initial store state.
|
||||
*/
|
||||
getInitialStoreState: function() {
|
||||
return {
|
||||
audioMuted: false,
|
||||
roomState: ROOM_STATES.INIT,
|
||||
videoMuted: false
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves an event to ga.
|
||||
*
|
||||
* @param {String} category The category for the event.
|
||||
* @param {String} action The type of action.
|
||||
* @param {String} label The label detailing the action.
|
||||
*/
|
||||
_storeEvent: function(category, action, label) {
|
||||
// ga might not be defined if donottrack is enabled, see index.html.
|
||||
if (!window.ga) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For now all we need to do is forward onto ga.
|
||||
window.ga("send", "event", category, action, label);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles media permssion being obtained.
|
||||
*/
|
||||
gotMediaPermission: function() {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
|
||||
"Media granted");
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the user clicking the join room button.
|
||||
*/
|
||||
joinRoom: function() {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Join the conversation");
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the user clicking the leave room button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Leave conversation");
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles notification that two-way media has been achieved.
|
||||
*/
|
||||
mediaConnected: function() {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
|
||||
"Media connected")
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles recording link clicks.
|
||||
*
|
||||
* @param {sharedActions.RecordClick} actionData The data associated with
|
||||
* the link.
|
||||
*/
|
||||
recordClick: function(actionData) {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick,
|
||||
actionData.linkInfo);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles notifications that the activeRoomStore has changed, updating
|
||||
* the metrics for room state and mute state as necessary.
|
||||
*/
|
||||
_onActiveRoomStoreChange: function() {
|
||||
var roomStore = this.activeRoomStore.getStoreState();
|
||||
|
||||
this._checkRoomState(roomStore.roomState, roomStore.failureReason);
|
||||
|
||||
this._checkMuteState("audio", roomStore.audioMuted);
|
||||
this._checkMuteState("video", roomStore.videoMuted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles checking of the room state to look for events we need to send.
|
||||
*
|
||||
* @param {String} roomState The new room state.
|
||||
* @param {String} failureReason Optional, if the room is in the failure
|
||||
* state, this should contain the reason for
|
||||
* the failure.
|
||||
*/
|
||||
_checkRoomState: function(roomState, failureReason) {
|
||||
if (this._storeState.roomState === roomState) {
|
||||
return;
|
||||
}
|
||||
this._storeState.roomState = roomState;
|
||||
|
||||
if (roomState === ROOM_STATES.FAILED &&
|
||||
failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID) {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
|
||||
"Link expired or invalid");
|
||||
}
|
||||
|
||||
if (roomState === ROOM_STATES.FULL) {
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
|
||||
"Room full");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles check of the mute state to look for events we need to send.
|
||||
*
|
||||
* @param {String} type The type of mute being adjusted, i.e. "audio" or
|
||||
* "video".
|
||||
* @param {Boolean} muted The new state of mute
|
||||
*/
|
||||
_checkMuteState: function(type, muted) {
|
||||
var muteItem = type + "Muted";
|
||||
if (this._storeState[muteItem] === muted) {
|
||||
return;
|
||||
}
|
||||
this._storeState[muteItem] = muted;
|
||||
|
||||
var muteType = type === "audio" ? METRICS_GA_ACTIONS.audioMute : METRICS_GA_ACTIONS.faceMute;
|
||||
var muteState = muted ? "mute" : "unmute";
|
||||
|
||||
this._storeEvent(METRICS_GA_CATEGORY.general, muteType, muteState);
|
||||
}
|
||||
});
|
||||
|
||||
return StandaloneMetricsStore;
|
||||
})();
|
@ -160,11 +160,23 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
|
||||
var StandaloneRoomHeader = React.createClass({displayName: "StandaloneRoomHeader",
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
recordClick: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Support link click"
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("header", null,
|
||||
React.createElement("h1", null, mozL10n.get("clientShortname2")),
|
||||
React.createElement("a", {target: "_blank", href: loop.config.generalSupportUrl},
|
||||
React.createElement("a", {href: loop.config.generalSupportUrl,
|
||||
onClick: this.recordClick,
|
||||
target: "_blank"},
|
||||
React.createElement("i", {className: "icon icon-help"})
|
||||
)
|
||||
)
|
||||
@ -173,7 +185,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
|
||||
var StandaloneRoomFooter = React.createClass({displayName: "StandaloneRoomFooter",
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
@ -189,10 +208,21 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("footer", null,
|
||||
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()}}),
|
||||
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()},
|
||||
onClick: this.recordClick}),
|
||||
React.createElement("div", {className: "footer-logo"})
|
||||
)
|
||||
);
|
||||
@ -201,10 +231,17 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
var StandaloneRoomContextItem = React.createClass({displayName: "StandaloneRoomContextItem",
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
receivingScreenShare: React.PropTypes.bool,
|
||||
roomContextUrl: React.PropTypes.object
|
||||
},
|
||||
|
||||
recordClick: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.roomContextUrl ||
|
||||
!this.props.roomContextUrl.location) {
|
||||
@ -225,7 +262,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
React.createElement("img", {src: this.props.roomContextUrl.thumbnail}),
|
||||
React.createElement("div", {className: "standalone-context-url-description-wrapper"},
|
||||
this.props.roomContextUrl.description,
|
||||
React.createElement("br", null), React.createElement("a", {href: location}, location)
|
||||
React.createElement("br", null), React.createElement("a", {href: location,
|
||||
onClick: this.recordClick,
|
||||
target: "_blank"}, location)
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -234,6 +273,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
var StandaloneRoomContextView = React.createClass({displayName: "StandaloneRoomContextView",
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
receivingScreenShare: React.PropTypes.bool.isRequired,
|
||||
roomContextUrls: React.PropTypes.array,
|
||||
roomName: React.PropTypes.string,
|
||||
@ -259,6 +299,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
React.createElement("div", {className: "standalone-room-info"},
|
||||
React.createElement("h2", {className: "room-name"}, this.props.roomName),
|
||||
React.createElement(StandaloneRoomContextItem, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
receivingScreenShare: this.props.receivingScreenShare,
|
||||
roomContextUrl: roomContextUrl})
|
||||
)
|
||||
@ -517,7 +558,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
return (
|
||||
React.createElement("div", {className: "room-conversation-wrapper"},
|
||||
React.createElement("div", {className: "beta-logo"}),
|
||||
React.createElement(StandaloneRoomHeader, null),
|
||||
React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}),
|
||||
React.createElement(StandaloneRoomInfoArea, {roomState: this.state.roomState,
|
||||
failureReason: this.state.failureReason,
|
||||
joinRoom: this.joinRoom,
|
||||
@ -527,6 +568,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
React.createElement("div", {className: "video-layout-wrapper"},
|
||||
React.createElement("div", {className: "conversation room-conversation"},
|
||||
React.createElement(StandaloneRoomContextView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
receivingScreenShare: this.state.receivingScreenShare,
|
||||
roomContextUrls: this.state.roomContextUrls,
|
||||
roomName: this.state.roomName,
|
||||
@ -556,7 +598,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
React.createElement(loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView, {
|
||||
marketplaceSrc: this.state.marketplaceSrc,
|
||||
onMarketplaceMessage: this.state.onMarketplaceMessage}),
|
||||
React.createElement(StandaloneRoomFooter, null)
|
||||
React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -564,6 +606,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
return {
|
||||
StandaloneRoomContextView: StandaloneRoomContextView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomView: StandaloneRoomView
|
||||
};
|
||||
})(navigator.mozL10n);
|
||||
|
@ -160,11 +160,23 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
|
||||
var StandaloneRoomHeader = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
recordClick: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Support link click"
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<header>
|
||||
<h1>{mozL10n.get("clientShortname2")}</h1>
|
||||
<a target="_blank" href={loop.config.generalSupportUrl}>
|
||||
<a href={loop.config.generalSupportUrl}
|
||||
onClick={this.recordClick}
|
||||
target="_blank">
|
||||
<i className="icon icon-help"></i>
|
||||
</a>
|
||||
</header>
|
||||
@ -173,7 +185,14 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
|
||||
var StandaloneRoomFooter = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_getContent: function() {
|
||||
// We use this technique of static markup as it means we get
|
||||
// just one overall string for L10n to define the structure of
|
||||
// the whole item.
|
||||
return mozL10n.get("legal_text_and_links", {
|
||||
"clientShortname": mozL10n.get("clientShortname2"),
|
||||
"terms_of_use_url": React.renderToStaticMarkup(
|
||||
@ -189,10 +208,21 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
});
|
||||
},
|
||||
|
||||
recordClick: function(event) {
|
||||
// Check for valid href, as this is clicking on the paragraph -
|
||||
// so the user may be clicking on the text rather than the link.
|
||||
if (event.target && event.target.href) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: event.target.href
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<footer>
|
||||
<p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
|
||||
<p dangerouslySetInnerHTML={{__html: this._getContent()}}
|
||||
onClick={this.recordClick}></p>
|
||||
<div className="footer-logo" />
|
||||
</footer>
|
||||
);
|
||||
@ -201,10 +231,17 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
var StandaloneRoomContextItem = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
receivingScreenShare: React.PropTypes.bool,
|
||||
roomContextUrl: React.PropTypes.object
|
||||
},
|
||||
|
||||
recordClick: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.roomContextUrl ||
|
||||
!this.props.roomContextUrl.location) {
|
||||
@ -225,7 +262,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
<img src={this.props.roomContextUrl.thumbnail} />
|
||||
<div className="standalone-context-url-description-wrapper">
|
||||
{this.props.roomContextUrl.description}
|
||||
<br /><a href={location}>{location}</a>
|
||||
<br /><a href={location}
|
||||
onClick={this.recordClick}
|
||||
target="_blank">{location}</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -234,6 +273,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
var StandaloneRoomContextView = React.createClass({
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
receivingScreenShare: React.PropTypes.bool.isRequired,
|
||||
roomContextUrls: React.PropTypes.array,
|
||||
roomName: React.PropTypes.string,
|
||||
@ -259,6 +299,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
<div className="standalone-room-info">
|
||||
<h2 className="room-name">{this.props.roomName}</h2>
|
||||
<StandaloneRoomContextItem
|
||||
dispatcher={this.props.dispatcher}
|
||||
receivingScreenShare={this.props.receivingScreenShare}
|
||||
roomContextUrl={roomContextUrl} />
|
||||
</div>
|
||||
@ -517,7 +558,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
<div className="beta-logo" />
|
||||
<StandaloneRoomHeader />
|
||||
<StandaloneRoomHeader dispatcher={this.props.dispatcher} />
|
||||
<StandaloneRoomInfoArea roomState={this.state.roomState}
|
||||
failureReason={this.state.failureReason}
|
||||
joinRoom={this.joinRoom}
|
||||
@ -527,6 +568,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation room-conversation">
|
||||
<StandaloneRoomContextView
|
||||
dispatcher={this.props.dispatcher}
|
||||
receivingScreenShare={this.state.receivingScreenShare}
|
||||
roomContextUrls={this.state.roomContextUrls}
|
||||
roomName={this.state.roomName}
|
||||
@ -556,7 +598,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
<loop.fxOSMarketplaceViews.FxOSHiddenMarketplaceView
|
||||
marketplaceSrc={this.state.marketplaceSrc}
|
||||
onMarketplaceMessage={this.state.onMarketplaceMessage} />
|
||||
<StandaloneRoomFooter />
|
||||
<StandaloneRoomFooter dispatcher={this.props.dispatcher} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -564,6 +606,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
|
||||
return {
|
||||
StandaloneRoomContextView: StandaloneRoomContextView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomView: StandaloneRoomView
|
||||
};
|
||||
})(navigator.mozL10n);
|
||||
|
@ -1087,8 +1087,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: feedbackClient
|
||||
});
|
||||
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
|
||||
activeRoomStore: activeRoomStore
|
||||
});
|
||||
|
||||
loop.store.StoreMixin.register({feedbackStore: feedbackStore});
|
||||
loop.store.StoreMixin.register({
|
||||
feedbackStore: feedbackStore,
|
||||
// This isn't used in any views, but is saved here to ensure it
|
||||
// is kept alive.
|
||||
standaloneMetricsStore: standaloneMetricsStore
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||
|
@ -1087,8 +1087,16 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
|
||||
feedbackClient: feedbackClient
|
||||
});
|
||||
var standaloneMetricsStore = new loop.store.StandaloneMetricsStore(dispatcher, {
|
||||
activeRoomStore: activeRoomStore
|
||||
});
|
||||
|
||||
loop.store.StoreMixin.register({feedbackStore: feedbackStore});
|
||||
loop.store.StoreMixin.register({
|
||||
feedbackStore: feedbackStore,
|
||||
// This isn't used in any views, but is saved here to ensure it
|
||||
// is kept alive.
|
||||
standaloneMetricsStore: standaloneMetricsStore
|
||||
});
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
dispatcher.dispatch(new sharedActions.WindowUnload());
|
||||
|
@ -60,12 +60,14 @@
|
||||
<script src="../../standalone/content/js/standaloneMozLoop.js"></script>
|
||||
<script src="../../standalone/content/js/fxOSMarketplace.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneRoomViews.js"></script>
|
||||
<script src="../../standalone/content/js/standaloneMetricsStore.js"></script>
|
||||
<script src="../../standalone/content/js/webapp.js"></script>
|
||||
<!-- Test scripts -->
|
||||
<script src="standalone_client_test.js"></script>
|
||||
<script src="standaloneAppStore_test.js"></script>
|
||||
<script src="standaloneMozLoop_test.js"></script>
|
||||
<script src="standaloneRoomViews_test.js"></script>
|
||||
<script src="standaloneMetricsStore_test.js"></script>
|
||||
<script src="webapp_test.js"></script>
|
||||
<script src="multiplexGum_test.js"></script>
|
||||
<script>
|
||||
|
@ -0,0 +1,142 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var expect = chai.expect;
|
||||
|
||||
describe("loop.store.StandaloneMetricsStore", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox, dispatcher, store, fakeActiveRoomStore;
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var METRICS_GA_CATEGORY = loop.store.METRICS_GA_CATEGORY;
|
||||
var METRICS_GA_ACTIONS = loop.store.METRICS_GA_ACTIONS;
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
dispatcher = new loop.Dispatcher();
|
||||
|
||||
window.ga = sinon.stub();
|
||||
|
||||
var fakeStore = loop.store.createStore({
|
||||
getInitialStoreState: function() {
|
||||
return {
|
||||
audioMuted: false,
|
||||
roomState: ROOM_STATES.INIT,
|
||||
videoMuted: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
fakeActiveRoomStore = new fakeStore(dispatcher);
|
||||
|
||||
store = new loop.store.StandaloneMetricsStore(dispatcher, {
|
||||
activeRoomStore: fakeActiveRoomStore
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
delete window.ga;
|
||||
});
|
||||
|
||||
describe("Action Handlers", function() {
|
||||
beforeEach(function() {
|
||||
});
|
||||
|
||||
it("should log an event on GotMediaPermission", function() {
|
||||
store.gotMediaPermission();
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
|
||||
"Media granted");
|
||||
});
|
||||
|
||||
it("should log an event on JoinRoom", function() {
|
||||
store.joinRoom();
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Join the conversation");
|
||||
});
|
||||
|
||||
it("should log an event on LeaveRoom", function() {
|
||||
store.leaveRoom();
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
|
||||
"Leave conversation");
|
||||
});
|
||||
|
||||
it("should log an event on MediaConnected", function() {
|
||||
store.mediaConnected();
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.success,
|
||||
"Media connected");
|
||||
});
|
||||
|
||||
it("should log an event on RecordClick", function() {
|
||||
store.recordClick(new sharedActions.RecordClick({
|
||||
linkInfo: "fake"
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.linkClick,
|
||||
"fake");
|
||||
})
|
||||
});
|
||||
|
||||
describe("Store Change Handlers", function() {
|
||||
it("should log an event on room full", function() {
|
||||
fakeActiveRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
|
||||
"Room full");
|
||||
});
|
||||
|
||||
it("should log an event when the room is expired or invalid", function() {
|
||||
fakeActiveRoomStore.setStoreState({
|
||||
roomState: ROOM_STATES.FAILED,
|
||||
failureReason: FAILURE_DETAILS.EXPIRED_OR_INVALID
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.pageLoad,
|
||||
"Link expired or invalid");
|
||||
});
|
||||
|
||||
it("should log an event when video mute is changed", function() {
|
||||
fakeActiveRoomStore.setStoreState({
|
||||
videoMuted: true
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.faceMute,
|
||||
"mute");
|
||||
});
|
||||
|
||||
it("should log an event when audio mute is changed", function() {
|
||||
fakeActiveRoomStore.setStoreState({
|
||||
audioMuted: true
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(window.ga);
|
||||
sinon.assert.calledWithExactly(window.ga,
|
||||
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.audioMute,
|
||||
"mute");
|
||||
});
|
||||
});
|
||||
});
|
@ -45,7 +45,10 @@ describe("loop.standaloneRoomViews", function() {
|
||||
});
|
||||
|
||||
function mountTestComponent(extraProps) {
|
||||
var props = _.extend({ receivingScreenShare: false }, extraProps);
|
||||
var props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
receivingScreenShare: false
|
||||
}, extraProps);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneRoomContextView, props));
|
||||
@ -98,6 +101,48 @@ describe("loop.standaloneRoomViews", function() {
|
||||
|
||||
expect(view.getDOMNode().querySelector(".standalone-context-url")).eql(null);
|
||||
});
|
||||
|
||||
it("should dispatch a RecordClick action when the link is clicked", function() {
|
||||
var view = mountTestComponent({
|
||||
roomName: "Mark's room",
|
||||
roomContextUrls: [{
|
||||
description: "Mark's super page",
|
||||
location: "http://invalid.com",
|
||||
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
||||
}]
|
||||
});
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode()
|
||||
.querySelector(".standalone-context-url-description-wrapper > a"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomHeader", function() {
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneRoomHeader, {
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
}
|
||||
|
||||
it("should dispatch a RecordClick action when the support link is clicked", function() {
|
||||
var view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode().querySelector("a"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RecordClick({
|
||||
linkInfo: "Support link click"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomView", function() {
|
||||
|
Loading…
Reference in New Issue
Block a user