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

Commit

Permalink
fix(metrics): POST metrics if *any* field has changed since the last …
Browse files Browse the repository at this point in the history
…send.

This fixes a problem where clicks on the app store marketing buttons were not
recorded if metrics were already flushed but no new events were logged.

The only two fields that do *not* cause metrics to be flushed are
`duration` and `flushTime` since these will be different on every send.

fixes #4479
  • Loading branch information
Shane Tomlinson committed Jan 10, 2017
1 parent f44dcb5 commit eb2fd6b
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 21 deletions.
35 changes: 30 additions & 5 deletions app/scripts/lib/metrics.js
Expand Up @@ -167,10 +167,12 @@ define(function (require, exports, module) {

var filteredData = this.getFilteredData();

if (! this._isFlushRequired(filteredData)) {
if (! this._isFlushRequired(filteredData, this._lastFlushedData)) {
return p();
}

this._lastFlushedData = filteredData;

this._speedTrap.events.clear();
this._speedTrap.timers.clear();

Expand All @@ -185,9 +187,30 @@ define(function (require, exports, module) {
.then(sent => sent || send());
},

_isFlushRequired (data) {
return data.events.length !== 0 ||
Object.keys(data.timers).length !== 0;
/**
* Check if a flush is required for the given `data`. A flush is
* required if any data has changed since the last flush.
*
* @param {Object} data - potential data to flush
* @param {Object} lastFlushedData - last data that was flushed.
* @returns {Boolean}
* @private
*/
_isFlushRequired (data, lastFlushedData) {
if (! lastFlushedData) {
return true;
}
// Only check fields that are in the new payload. `data` could be
// a subset of `_lastFlushedData`, in which case no flush should occur.
return _.any(data, (value, key) => {
// these keys are distinct every flush attempt, ignore.
if (key === 'duration' || key === 'flushTime') {
return false;
}

// _.isEqual does a deep comparision of objects and arrays.
return ! _.isEqual(lastFlushedData[key], value);
});
},

_clearInactivityFlushTimeout () {
Expand Down Expand Up @@ -245,7 +268,9 @@ define(function (require, exports, module) {
utm_term: this._utmTerm, //eslint-disable-line camelcase
});

return allData;
// Create a deep copy of the data so that any modifications to contained
// objects or arrays do not affect the returned copy of the data.
return JSON.parse(JSON.stringify(allData));
},

/**
Expand Down
48 changes: 32 additions & 16 deletions app/tests/spec/lib/metrics.js
Expand Up @@ -19,6 +19,9 @@ define(function (require, exports, module) {
const TestHelpers = require('../../lib/helpers');
const WindowMock = require('../../mocks/window');

const MARKETING_CAMPAIGN = 'campaign1';
const MARKETING_CAMPAIGN_URL = 'https://accounts.firefox.com';

describe('lib/metrics', function () {
var metrics;
var windowMock;
Expand Down Expand Up @@ -468,17 +471,33 @@ define(function (require, exports, module) {
});
});

describe('flush, no events or timers', function () {
describe('flush, no data has changed, flush again', () => {
beforeEach(function () {
sandbox.stub(environment, 'hasSendBeacon', function () {
return true;
});
sandbox.stub(windowMock.navigator, 'sendBeacon', function () {});
return metrics.flush();
sandbox.stub(environment, 'hasSendBeacon', () => true);
sandbox.stub(windowMock.navigator, 'sendBeacon', () => true);
return metrics.flush()
.then(() => metrics.flush());
});

it('sends data once', function () {
assert.equal(windowMock.navigator.sendBeacon.callCount, 1);
});
});

describe('flush, data has changed, flush again', () => {
beforeEach(function () {
sandbox.stub(environment, 'hasSendBeacon', () => true);
sandbox.stub(windowMock.navigator, 'sendBeacon', () => true);
metrics.logMarketingImpression(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL);
return metrics.flush()
.then(() => {
metrics.logMarketingClick(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL);
return metrics.flush();
});
});

it('does not send data', function () {
assert.equal(windowMock.navigator.sendBeacon.callCount, 0);
it('sends data both times', function () {
assert.equal(windowMock.navigator.sendBeacon.callCount, 2);
});
});

Expand Down Expand Up @@ -599,14 +618,11 @@ define(function (require, exports, module) {

describe('logMarketingImpression & logMarketingClick', function () {
it('logs the marketing impression and click', function () {
var campaign = 'campaign1';
var url = 'https://accounts.firefox.com';

assert.isUndefined(metrics.getMarketingImpression(campaign, url));
metrics.logMarketingImpression(campaign, url);
assert.isFalse(metrics.getMarketingImpression(campaign, url).clicked);
metrics.logMarketingClick(campaign, url);
assert.isTrue(metrics.getMarketingImpression(campaign, url).clicked);
assert.isUndefined(metrics.getMarketingImpression(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL));
metrics.logMarketingImpression(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL);
assert.isFalse(metrics.getMarketingImpression(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL).clicked);
metrics.logMarketingClick(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL);
assert.isTrue(metrics.getMarketingImpression(MARKETING_CAMPAIGN, MARKETING_CAMPAIGN_URL).clicked);
});
});

Expand Down

0 comments on commit eb2fd6b

Please sign in to comment.