Skip to content

Commit

Permalink
Merge pull request googleanalytics#169 from googleanalytics/initial-p…
Browse files Browse the repository at this point in the history
…ageview

Ensure plugin hits get sent after all plugins are loaded
  • Loading branch information
philipwalton authored Apr 11, 2017
2 parents 7eed8e7 + ef8ef1d commit f4b5551
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 64 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ This document lists the changes between each minor and patch versions. For chang

### 2.3.1 (2017-04-09)

- Rename misspelled `pageloadMetricIndex` option to `pageloadsMetricIndex`
- Rename misspelled `pageLoadMetricIndex` option to `pageLoadsMetricIndex`

### 2.3.0 (2017-04-07)

- Add a `sendInitialPageview` option to the `pageVisibilityTracker` plugin (#167)
- Add a `pageloadMetricIndex` option to the `pageVisibilityTracker` plugin (#167)
- Add a `pageLoadMetricIndex` option to the `pageVisibilityTracker` plugin (#167)

### 2.2.0 (2017-03-28)

Expand All @@ -22,7 +22,7 @@ This document lists the changes between each minor and patch versions. For chang

### 2.1.0 (2017-03-06)

- Fix a double-pageview bug on pageload after session timeout (#150)
- Fix a double-pageview bug on page load after session timeout (#150)
- Add a `visibleThreshold` option to `pageVisibilityTracker` (#148)

### 2.0.4 (2017-02-24)
Expand Down
2 changes: 1 addition & 1 deletion docs/common-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ga('require', 'urlChangeTracker', {
}
});
ga('send', 'pageview', {
dimension1: 'pageload'
dimension1: 'page load'
});
```

Expand Down
4 changes: 2 additions & 2 deletions docs/plugins/url-change-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ ga('require', 'urlChangeTracker', {

### Differentiating between virtual pageviews and the initial pageview

If you want to be able to report on pageviews sent by the `urlChangeTracker` separately from pageviews sent in the initial pageload, you can use a [custom dimension](https://support.google.com/analytics/answer/2709828) to add additional metadata to the pageview hit.
If you want to be able to report on pageviews sent by the `urlChangeTracker` separately from pageviews sent in the initial page load, you can use a [custom dimension](https://support.google.com/analytics/answer/2709828) to add additional metadata to the pageview hit.

The following code uses the `fieldsObj` option to set a custom dimension at index 1 for all pageview hits sent by the `urlChangeTracker` plugin:

Expand All @@ -131,6 +131,6 @@ ga('require', 'urlChangeTracker', {
}
});
ga('send', 'pageview', {
dimension1: 'pageload'
dimension1: 'page load'
});
```
8 changes: 6 additions & 2 deletions lib/plugins/impression-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@ class ImpressionTracker {
// IntersectionObserver instance specific to that threshold.
this.thresholdMap = {};

// Once the DOM is ready, start observing for changes.
domReady(() => this.observeElements(this.opts.elements));
// Once the DOM is ready, start observing for changes (if present).
domReady(() => {
if (this.opts.elements) {
this.observeElements(this.opts.elements);
}
});
}

/**
Expand Down
65 changes: 35 additions & 30 deletions lib/plugins/page-visibility-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import provide from '../provide';
import Session from '../session';
import Store from '../store';
import {plugins, trackUsage} from '../usage';
import {assign, createFieldsObj, isObject, now, uuid} from '../utilities';
import {assign, createFieldsObj, deferUntilPluginsLoaded,
isObject, now, uuid} from '../utilities';


const HIDDEN = 'hidden';
Expand Down Expand Up @@ -86,16 +87,22 @@ class PageVisibilityTracker {

window.addEventListener('unload', this.handleWindowUnload);
document.addEventListener('visibilitychange', this.handleChange);

if (document.visibilityState == VISIBLE) {
if (this.opts.sendInitialPageview) {
this.sendPageview({isPageLoad: true});
this.isInitialPageviewSent_ = true;
this.handleChange();

// Postpone sending any hits until the next call stack, which allows all
// autotrack plugins to be required sync before any hits are sent.
deferUntilPluginsLoaded(this.tracker, () => {
if (document.visibilityState == VISIBLE) {
if (this.opts.sendInitialPageview) {
this.sendPageview({isPageLoad: true});
this.isInitialPageviewSent_ = true;
}
} else {
if (this.opts.sendInitialPageview && this.opts.pageLoadsMetricIndex) {
this.sendPageLoad();
}
}
this.handleChange();
} else {
this.sendPageload();
}
});
}

/**
Expand Down Expand Up @@ -126,9 +133,9 @@ class PageVisibilityTracker {
// If the visibilityState has changed to visible and the initial pageview
// has not been sent (and the `sendInitialPageview` option is `true`).
// Send the initial pageview now.
if (!this.isInitialPageviewSent_ &&
this.opts.sendInitialPageview &&
document.visibilityState == VISIBLE) {
if (this.lastPageState &&
document.visibilityState == VISIBLE &&
this.opts.sendInitialPageview && !this.isInitialPageviewSent_) {
this.sendPageview();
this.isInitialPageviewSent_ = true;
}
Expand Down Expand Up @@ -245,23 +252,21 @@ class PageVisibilityTracker {
}

/**
* Sends a page load event if a custom page load metric is being used.
* Sends a page load event.
*/
sendPageload() {
if (this.opts.sendInitialPageview && this.opts.pageLoadsMetricIndex) {
/** @type {FieldsObj} */
const defaultFields = {
transport: 'beacon',
eventCategory: 'Page Visibility',
eventAction: 'page load',
eventLabel: NULL_DIMENSION,
['metric' + this.opts.pageLoadsMetricIndex]: 1,
nonInteraction: true,
};
this.tracker.send('event',
createFieldsObj(defaultFields, this.opts.fieldsObj,
this.tracker, this.opts.hitFilter));
}
sendPageLoad() {
/** @type {FieldsObj} */
const defaultFields = {
transport: 'beacon',
eventCategory: 'Page Visibility',
eventAction: 'page load',
eventLabel: NULL_DIMENSION,
['metric' + this.opts.pageLoadsMetricIndex]: 1,
nonInteraction: true,
};
this.tracker.send('event',
createFieldsObj(defaultFields, this.opts.fieldsObj,
this.tracker, this.opts.hitFilter));
}

/**
Expand All @@ -279,7 +284,7 @@ class PageVisibilityTracker {
if (hitTime) {
defaultFields.queueTime = now() - hitTime;
}
if (isPageLoad) {
if (isPageLoad && this.opts.pageLoadsMetricIndex) {
defaultFields['metric' + this.opts.pageLoadsMetricIndex] = 1;
}

Expand Down
42 changes: 42 additions & 0 deletions lib/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@


import {getAttributes} from 'dom-utils';
import MethodChain from './method-chain';


/**
Expand Down Expand Up @@ -141,6 +142,47 @@ export function withTimeout(callback, wait = 2000) {
return fn;
}

// Maps trackers to queue by tracking ID.
const queueMap = {};

/**
* Queues a function for execution in the next call stack, or immediately
* before any send commands are executed on the tracker. This allows
* autotrack plugins to defer running commands until after all other plugins
* are required but before any other hits are sent.
* @param {!Tracker} tracker
* @param {!Function} fn
*/
export function deferUntilPluginsLoaded(tracker, fn) {
const trackingId = tracker.get('trackingId');
const ref = queueMap[trackingId] = queueMap[trackingId] || {};

const processQueue = () => {
clearTimeout(ref.timeout);
if (ref.send) {
MethodChain.remove(tracker, 'send', ref.send);
}
delete queueMap[trackingId];

ref.queue.forEach((fn) => fn());
};

clearTimeout(ref.timeout);
ref.timeout = setTimeout(processQueue, 0);
ref.queue = ref.queue || [];
ref.queue.push(fn);

if (!ref.send) {
ref.send = (originalMethod) => {
return (...args) => {
processQueue();
originalMethod(...args);
};
};
MethodChain.add(tracker, 'send', ref.send);
}
}


/**
* A small shim of Object.assign that aims for brevity over spec-compliant
Expand Down
31 changes: 31 additions & 0 deletions test/e2e/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,35 @@ describe('index', function() {
// '3ff' = '1111111111' in hex
assert.strictEqual(lastHit[constants.USAGE_PARAM], '3ff');
});

it('tracks usage when plugins send hits before other plugins are required',
() => {
browser.url('/test/e2e/fixtures/autotrack.html');
browser.execute(ga.run, 'create', DEFAULT_TRACKER_FIELDS);
browser.execute(ga.logHitData, testId);

// Run all require commands in the same execute() block so they occur in
// the same call stack (as most people do in their tracking snippet).
browser.execute(() => {
ga('require', 'cleanUrlTracker');
ga('require', 'eventTracker');
ga('require', 'impressionTracker');
ga('require', 'outboundLinkTracker');
ga('require', 'mediaQueryTracker');
ga('require', 'outboundFormTracker');
ga('require', 'pageVisibilityTracker', {sendInitialPageview: true});
ga('require', 'socialWidgetTracker');
ga('require', 'urlChangeTracker');
ga('require', 'maxScrollTracker');
});

browser.waitUntil(log.hitCountIsAtLeast(1));

const lastHit = log.getHits().slice(-1)[0];
assert.strictEqual(lastHit.did, constants.DEV_ID);
assert.strictEqual(lastHit[constants.VERSION_PARAM], pkg.version);

// '3ff' = '1111111111' in hex
assert.strictEqual(lastHit[constants.USAGE_PARAM], '3ff');
});
});
Loading

0 comments on commit f4b5551

Please sign in to comment.