Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
refactor(client): move flow model resposibilities out of the views
Browse files Browse the repository at this point in the history
#4718

r=shane-tomlinson
  • Loading branch information
philbooth committed Feb 16, 2017
1 parent 313e69e commit 4071291
Show file tree
Hide file tree
Showing 63 changed files with 575 additions and 516 deletions.
10 changes: 5 additions & 5 deletions app/scripts/lib/app-start.js
Expand Up @@ -110,15 +110,15 @@ define(function (require, exports, module) {
// both the metrics and router depend on the language // both the metrics and router depend on the language
// fetched from config. // fetched from config.
.then(_.bind(this.initializeRelier, this)) .then(_.bind(this.initializeRelier, this))
// metrics depends on the relier. // iframe channel depends on the relier.
.then(_.bind(this.initializeMetrics, this))
// iframe channel depends on the relier and metrics
.then(_.bind(this.initializeIframeChannel, this)) .then(_.bind(this.initializeIframeChannel, this))
// fxaClient depends on the relier and // fxaClient depends on the relier and
// inter tab communication. // inter tab communication.
.then(_.bind(this.initializeFxaClient, this)) .then(_.bind(this.initializeFxaClient, this))
// depends on iframeChannel and interTabChannel // depends on iframeChannel and interTabChannel
.then(_.bind(this.initializeNotifier, this)) .then(_.bind(this.initializeNotifier, this))
// metrics depends on relier and notifier
.then(_.bind(this.initializeMetrics, this))
// assertionLibrary depends on fxaClient // assertionLibrary depends on fxaClient
.then(_.bind(this.initializeAssertionLibrary, this)) .then(_.bind(this.initializeAssertionLibrary, this))
// profileClient depends on fxaClient and assertionLibrary // profileClient depends on fxaClient and assertionLibrary
Expand Down Expand Up @@ -195,8 +195,10 @@ define(function (require, exports, module) {
isSampledUser: isSampledUser, isSampledUser: isSampledUser,
lang: this._config.lang, lang: this._config.lang,
migration: relier.get('migration'), migration: relier.get('migration'),
notifier: this._notifier,
screenHeight: screenInfo.screenHeight, screenHeight: screenInfo.screenHeight,
screenWidth: screenInfo.screenWidth, screenWidth: screenInfo.screenWidth,
sentryMetrics: this._sentryMetrics,
service: relier.get('service'), service: relier.get('service'),
uniqueUserId: this._getUniqueUserId(), uniqueUserId: this._getUniqueUserId(),
utmCampaign: relier.get('utmCampaign'), utmCampaign: relier.get('utmCampaign'),
Expand All @@ -205,7 +207,6 @@ define(function (require, exports, module) {
utmSource: relier.get('utmSource'), utmSource: relier.get('utmSource'),
utmTerm: relier.get('utmTerm') utmTerm: relier.get('utmTerm')
}); });
this._metrics.init();
}, },


initializeIframeChannel () { initializeIframeChannel () {
Expand All @@ -214,7 +215,6 @@ define(function (require, exports, module) {
const iframeChannel = new IframeChannel(); const iframeChannel = new IframeChannel();


iframeChannel.initialize({ iframeChannel.initialize({
metrics: this._metrics,
origin: parentOrigin, origin: parentOrigin,
window: this._window window: this._window
}); });
Expand Down
103 changes: 78 additions & 25 deletions app/scripts/lib/metrics.js
Expand Up @@ -19,10 +19,13 @@ define(function (require, exports, module) {


const $ = require('jquery'); const $ = require('jquery');
const _ = require('underscore'); const _ = require('underscore');
const Cocktail = require('cocktail');
const Constants = require('lib/constants'); const Constants = require('lib/constants');
const Backbone = require('backbone'); const Backbone = require('backbone');
const Duration = require('duration'); const Duration = require('duration');
const Environment = require('lib/environment'); const Environment = require('lib/environment');
const Flow = require('models/flow');
const NotifierMixin = require('lib/channels/notifier-mixin');
const p = require('lib/promise'); const p = require('lib/promise');
const speedTrap = require('speedTrap'); const speedTrap = require('speedTrap');
const Strings = require('lib/strings'); const Strings = require('lib/strings');
Expand Down Expand Up @@ -118,6 +121,7 @@ define(function (require, exports, module) {
this._referrer = this._window.document.referrer || NOT_REPORTED_VALUE; this._referrer = this._window.document.referrer || NOT_REPORTED_VALUE;
this._screenHeight = options.screenHeight || NOT_REPORTED_VALUE; this._screenHeight = options.screenHeight || NOT_REPORTED_VALUE;
this._screenWidth = options.screenWidth || NOT_REPORTED_VALUE; this._screenWidth = options.screenWidth || NOT_REPORTED_VALUE;
this._sentryMetrics = options.sentryMetrics;
this._service = options.service || NOT_REPORTED_VALUE; this._service = options.service || NOT_REPORTED_VALUE;
// if navigationTiming is supported, the baseTime will be from // if navigationTiming is supported, the baseTime will be from
// navigationTiming.navigationStart, otherwise Date.now(). // navigationTiming.navigationStart, otherwise Date.now().
Expand All @@ -129,12 +133,14 @@ define(function (require, exports, module) {
this._utmSource = options.utmSource || NOT_REPORTED_VALUE; this._utmSource = options.utmSource || NOT_REPORTED_VALUE;
this._utmTerm = options.utmTerm || NOT_REPORTED_VALUE; this._utmTerm = options.utmTerm || NOT_REPORTED_VALUE;
this._xhr = options.xhr || xhr; this._xhr = options.xhr || xhr;

this.initialize(options);
} }


_.extend(Metrics.prototype, Backbone.Events, { _.extend(Metrics.prototype, Backbone.Events, {
ALLOWED_FIELDS: ALLOWED_FIELDS, ALLOWED_FIELDS: ALLOWED_FIELDS,


init () { initialize () {
this._flush = _.bind(this.flush, this, true); this._flush = _.bind(this.flush, this, true);
$(this._window).on('unload', this._flush); $(this._window).on('unload', this._flush);
// iOS will not send events once the window is in the background, // iOS will not send events once the window is in the background,
Expand All @@ -153,6 +159,64 @@ define(function (require, exports, module) {
this._clearInactivityFlushTimeout(); this._clearInactivityFlushTimeout();
}, },


notifications: {
/* eslint-disable sorting/sort-object-props */
'flow.initialize': '_initializeFlowModel',
'flow.event': '_logFlowEvent'
/* eslint-enable sorting/sort-object-props */
},

/**
* @private
* Initialize the flow model. If it's already been initalized, do nothing.
* Initialization may fail if the required flow properties can't be found,
* either in the DOM or the resume token.
*/
_initializeFlowModel () {
if (this._flowModel) {
return;
}

const flowModel = new Flow({
sentryMetrics: this._sentryMetrics,
window: this._window
});

if (flowModel.has('flowId')) {
this._flowModel = flowModel;
}
},

/**
* @private
* Log a flow event. If there is no flow model, do nothing.
*
* @param {Object} data
* @param {String} data.event The name of the event.
* @param {String} [data.view] The name of the view, to be
* interpolated in the event name. If unset, the event is
* logged without a view name.
* @param {Boolean} [data.once] If set, emit this event via
* the `logEventOnce` method. Defaults to `false`.
*/
_logFlowEvent (data) {
if (! this._flowModel) {
// If there is no flow model, we're not in a recognised flow and
// we should not emit the event. This would be the case if a user
// lands on `/settings`, for instance. Only views that mixin the
// `flow-events-mixin` will initialise the flow model.
return;
}

const eventName = marshallFlowEvent(data.event, data.view);

if (data.once) {
this.logEventOnce(eventName);
} else {
this.logEvent(eventName);
}
},

/** /**
* Send the collected data to the backend. * Send the collected data to the backend.
* *
Expand Down Expand Up @@ -240,16 +304,17 @@ define(function (require, exports, module) {
* @returns {Object} * @returns {Object}
*/ */
getAllData () { getAllData () {
var loadData = this._speedTrap.getLoad(); const loadData = this._speedTrap.getLoad();
var unloadData = this._speedTrap.getUnload(); const unloadData = this._speedTrap.getUnload();
const flowData = this.getFlowEventMetadata();


var allData = _.extend({}, loadData, unloadData, { const allData = _.extend({}, loadData, unloadData, {
broker: this._brokerType, broker: this._brokerType,
context: this._context, context: this._context,
entrypoint: this._entrypoint, entrypoint: this._entrypoint,
experiments: flattenHashIntoArrayOfObjects(this._activeExperiments), experiments: flattenHashIntoArrayOfObjects(this._activeExperiments),
flowBeginTime: this._flowBeginTime, flowBeginTime: flowData.flowBeginTime,
flowId: this._flowId, flowId: flowData.flowId,
flushTime: Date.now(), flushTime: Date.now(),
isSampledUser: this._isSampledUser, isSampledUser: this._isSampledUser,
lang: this._lang, lang: this._lang,
Expand Down Expand Up @@ -503,23 +568,6 @@ define(function (require, exports, module) {
return this._isSampledUser; return this._isSampledUser;
}, },


logFlowBegin (flowId, flowBeginTime) {
// Don't emit a new flow.begin event unless flowId has changed.
if (flowId !== this._flowId) {
this._flowId = flowId;
this._flowBeginTime = flowBeginTime;
this.logFlowEvent('begin');
}
},

logFlowEvent (eventName, viewName) {
this.logEvent(marshallFlowEvent(eventName, viewName));
},

logFlowEventOnce (eventName, viewName) {
this.logEventOnce(marshallFlowEvent(eventName, viewName));
},

getFlowEventMetadata () { getFlowEventMetadata () {
const metadata = (this._flowModel && this._flowModel.attributes) || {}; const metadata = (this._flowModel && this._flowModel.attributes) || {};
return { return {
Expand All @@ -528,8 +576,8 @@ define(function (require, exports, module) {
}; };
}, },


setFlowModel (flowModel) { getFlowModel (flowModel) {
this._flowModel = flowModel; return this._flowModel;
}, },


/** /**
Expand All @@ -542,5 +590,10 @@ define(function (require, exports, module) {
} }
}); });


Cocktail.mixin(
Metrics,
NotifierMixin
);

module.exports = Metrics; module.exports = Metrics;
}); });
10 changes: 9 additions & 1 deletion app/scripts/lib/storage-metrics.js
Expand Up @@ -21,7 +21,15 @@ define(function (require, exports, module) {
// do nothing // do nothing
} }


_.extend(StorageMetrics.prototype, new Metrics(), { const notifier = {
off () {},
on () {},
trigger () {},
triggerAll () {},
triggerRemote () {}
};

_.extend(StorageMetrics.prototype, new Metrics({ notifier }), {
_send (data) { _send (data) {
var metrics = storage.get('metrics_all'); var metrics = storage.get('metrics_all');


Expand Down
2 changes: 1 addition & 1 deletion app/scripts/models/auth_brokers/fx-firstrun-v2.js
Expand Up @@ -15,7 +15,7 @@ define(function (require, exports, module) {
const _ = require('underscore'); const _ = require('underscore');
const Constants = require('lib/constants'); const Constants = require('lib/constants');
const FxFirstrunV1AuthenticationBroker = require('./fx-firstrun-v1'); const FxFirstrunV1AuthenticationBroker = require('./fx-firstrun-v1');
const NotifierMixin = require('views/mixins/notifier-mixin'); const NotifierMixin = require('lib/channels/notifier-mixin');


var proto = FxFirstrunV1AuthenticationBroker.prototype; var proto = FxFirstrunV1AuthenticationBroker.prototype;


Expand Down
12 changes: 8 additions & 4 deletions app/scripts/views/base.js
Expand Up @@ -13,7 +13,7 @@ define(function (require, exports, module) {
const domWriter = require('lib/dom-writer'); const domWriter = require('lib/dom-writer');
const ErrorUtils = require('lib/error-utils'); const ErrorUtils = require('lib/error-utils');
const ExternalLinksMixin = require('views/mixins/external-links-mixin'); const ExternalLinksMixin = require('views/mixins/external-links-mixin');
const NotifierMixin = require('views/mixins/notifier-mixin'); const NotifierMixin = require('lib/channels/notifier-mixin');
const NullMetrics = require('lib/null-metrics'); const NullMetrics = require('lib/null-metrics');
const Logger = require('lib/logger'); const Logger = require('lib/logger');
const p = require('lib/promise'); const p = require('lib/promise');
Expand Down Expand Up @@ -713,9 +713,13 @@ define(function (require, exports, module) {
* *
* @param {String} eventName * @param {String} eventName
* @param {String} viewName * @param {String} viewName
* @param {Object} data
*/ */
logFlowEvent (eventName, viewName) { logFlowEvent (eventName, viewName, data) {
this.metrics.logFlowEvent(eventName, viewName); this.notifier.trigger('flow.event', _.assign({}, data, {
event: eventName,
view: viewName
}));
}, },


/** /**
Expand All @@ -725,7 +729,7 @@ define(function (require, exports, module) {
* @param {String} viewName * @param {String} viewName
*/ */
logFlowEventOnce (eventName, viewName) { logFlowEventOnce (eventName, viewName) {
this.metrics.logFlowEventOnce(eventName, viewName); this.logFlowEvent(eventName, viewName, { once: true });
}, },


hideError () { hideError () {
Expand Down
1 change: 0 additions & 1 deletion app/scripts/views/confirm.js
Expand Up @@ -36,7 +36,6 @@ define(function (require, exports, module) {
// ephemeral properties like unwrapBKey and keyFetchToken // ephemeral properties like unwrapBKey and keyFetchToken
// that need to be sent to the browser. // that need to be sent to the browser.
this._account = this.user.initAccount(this.model.get('account')); this._account = this.user.initAccount(this.model.get('account'));
this.flow = this.model.get('flow');
}, },


getAccount () { getAccount () {
Expand Down
5 changes: 1 addition & 4 deletions app/scripts/views/mixins/flow-begin-mixin.js
Expand Up @@ -10,10 +10,7 @@ define(function (require, exports, module) {


module.exports = { module.exports = {
afterRender () { afterRender () {
const flowId = this.flow.get('flowId'); this.logFlowEventOnce('begin');
const flowBegin = this.flow.get('flowBegin');

this.metrics.logFlowBegin(flowId, flowBegin);
} }
}; };
}); });
7 changes: 1 addition & 6 deletions app/scripts/views/mixins/flow-events-mixin.js
Expand Up @@ -8,16 +8,11 @@ define(function (require, exports, module) {
'use strict'; 'use strict';


const $ = require('jquery'); const $ = require('jquery');
const Flow = require('models/flow');
const KEYS = require('lib/key-codes'); const KEYS = require('lib/key-codes');


module.exports = { module.exports = {
afterRender () { afterRender () {
this.flow = new Flow({ this.notifier.trigger('flow.initialize');
sentryMetrics: this.sentryMetrics,
window: this.window
});
this.metrics.setFlowModel(this.flow);
}, },


events: { events: {
Expand Down
8 changes: 6 additions & 2 deletions app/scripts/views/mixins/resume-token-mixin.js
Expand Up @@ -20,11 +20,15 @@ define(function (require, exports, module) {
*/ */
getResumeToken (account) { getResumeToken (account) {
var accountInfo = account.pickResumeTokenInfo(); var accountInfo = account.pickResumeTokenInfo();
// flow is only available in views that mix in the flow-events-mixin
var flowInfo = this.flow && this.flow.pickResumeTokenInfo();
var relierInfo = this.relier.pickResumeTokenInfo(); var relierInfo = this.relier.pickResumeTokenInfo();
var userInfo = this.user.pickResumeTokenInfo(); var userInfo = this.user.pickResumeTokenInfo();


let flowInfo;
const flowModel = this.metrics.getFlowModel();
if (flowModel) {
flowInfo = flowModel.pickResumeTokenInfo();
}

var resumeTokenInfo = _.extend( var resumeTokenInfo = _.extend(
{}, {},
flowInfo, flowInfo,
Expand Down
12 changes: 3 additions & 9 deletions app/scripts/views/mixins/signin-mixin.js
Expand Up @@ -105,16 +105,10 @@ define(function (require, exports, module) {


if (verificationReason === VerificationReasons.SIGN_IN && if (verificationReason === VerificationReasons.SIGN_IN &&
verificationMethod === VerificationMethods.EMAIL) { verificationMethod === VerificationMethods.EMAIL) {
return this.navigate('confirm_signin', { return this.navigate('confirm_signin', { account });
account: account,
flow: this.flow
});
} else {
return this.navigate('confirm', {
account: account,
flow: this.flow
});
} }

return this.navigate('confirm', { account });
} }


// If the account's uid changed, update the relier model or else // If the account's uid changed, update the relier model or else
Expand Down
5 changes: 1 addition & 4 deletions app/scripts/views/mixins/signup-mixin.js
Expand Up @@ -83,10 +83,7 @@ define(function (require, exports, module) {


return this.invokeBrokerMethod('afterSignUp', account) return this.invokeBrokerMethod('afterSignUp', account)
.then(() => { .then(() => {
this.navigate('confirm', { this.navigate('confirm', { account });
account: account,
flow: this.flow
});
}); });
}, },


Expand Down
2 changes: 1 addition & 1 deletion app/tests/spec/lib/app-start.js
Expand Up @@ -274,7 +274,7 @@ define(function (require, exports, module) {
user: userMock, user: userMock,
window: windowMock window: windowMock
}); });
appStart._metrics = new Metrics(); appStart._metrics = new Metrics({ notifier });
}); });


describe('fx-firstrun-v1', function () { describe('fx-firstrun-v1', function () {
Expand Down
Expand Up @@ -9,12 +9,12 @@ define(function (require, exports, module) {
const chai = require('chai'); const chai = require('chai');
const Cocktail = require('cocktail'); const Cocktail = require('cocktail');
const Notifier = require('lib/channels/notifier'); const Notifier = require('lib/channels/notifier');
const NotifierMixin = require('views/mixins/notifier-mixin'); const NotifierMixin = require('lib/channels/notifier-mixin');
const sinon = require('sinon'); const sinon = require('sinon');


var assert = chai.assert; var assert = chai.assert;


describe('views/mixins/notifier-mixin', function () { describe('lib/channels/notifier-mixin', function () {
var data = { uid: 'foo' }; var data = { uid: 'foo' };
var functionHandlerSpy; var functionHandlerSpy;
var notifier; var notifier;
Expand Down

0 comments on commit 4071291

Please sign in to comment.