Skip to content

Commit

Permalink
Merge pull request #208 from alphagov/link-tracking-plugins
Browse files Browse the repository at this point in the history
Create simplest download and external link tracking plugins
  • Loading branch information
edds committed Aug 5, 2015
2 parents 20973d1 + b2b1c8d commit b911a47
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 13 deletions.
55 changes: 47 additions & 8 deletions docs/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,6 @@ function setPixelDensityDimension(pixelDensity) {
}
```

## Print tracking

Pull `print-intent.js` into your project, and initialise it after analytics (see [Create an analytics tracker, above](#create-an-analytics-tracker)), to track when users are attempting to print content.

## Error tracking

Pull `error-tracking.js` into your project, and initialise it after analytics (see [Create an analytics tracker, above](#create-an-analytics-tracker)), to track JavaScript errors.

## Tracking across domains

Once an Analytics instance has been created, tracking across domains can be set up
Expand All @@ -159,3 +151,50 @@ GOVUK.analytics.addLinkedTrackerDomain(trackerIdHere, nameForTracker, domainToLi

Once this is done hits to that page will be tracked in both your local and the
named tracker, and sessions will persist to the other domain.

## Plugins

Plugins are namespaced to `GOVUK.analyticsPlugins`. They should be pulled in by your project and initialised after `GOVUK.analytics` (see [Create an analytics tracker, above](#create-an-analytics-tracker)).

### Print tracking (`print-intent.js`)

Track when users are attempting to print content. The plugin sends a `Print intent` event and a `/print` prefixed pageview:

Example event:

Category | Action
---------|-------
Print Intent | `/current/page`

Example pageview:

`/print/current/page`

### Error tracking (`error-tracking.js`)

Track JavaScript errors, capturing the error message, file and line number. These events don’t affect bounce rate.

Category | Action | Label | Value
---------|--------|-------|-------
JavaScript Error | The error message | file.js: line number | 1

### External link tracking (`external-link-tracker.js`)

The tracker will send an analytics event for clicks on links beginning, `http` and linking outside of the current host. By default the plugin uses Google Analytics’ `transport: beacon` method so that events are tracked even if the page unloads.

Category | Action | Label
---------|--------|-------
External Link Clicked | http://www.some-external-website.com | Link text


### Download link tracking (`download-link-tracker.js`)

The tracker will send a pageview for clicks on any link that matches the selector passed in. A selector must be provided. By default the plugin uses Google Analytics’ `transport: beacon` method so that pageviews are tracked even if the page unloads.

```js
GOVUK.analyticsPlugins.downloadTracker({selector: 'a[rel=download]'});
```

Page | Page title
-----|-----------
`/some/upload/attachment/file.pdf` | Link text
2 changes: 1 addition & 1 deletion javascripts/govuk/analytics/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
GOVUK.GoogleAnalyticsUniversalTracker.load();
};

Analytics.prototype.trackPageview = function(path, title) {
Analytics.prototype.trackPageview = function(path, title, options) {
this.sendToTrackers('trackPageview', arguments);
};

Expand Down
30 changes: 30 additions & 0 deletions javascripts/govuk/analytics/download-link-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(function() {
"use strict";
GOVUK.analyticsPlugins = GOVUK.analyticsPlugins || {};
GOVUK.analyticsPlugins.downloadLinkTracker = function (options) {
var options = options || {},
downloadLinkSelector = options.selector;

if (downloadLinkSelector) {
$('body').on('click', downloadLinkSelector, trackDownload);
}

function trackDownload(evt) {
var $link = getLinkFromEvent(evt),
href = $link.attr('href'),
linkText = $.trim($link.text());

GOVUK.analytics.trackPageview(href, linkText, {transport: 'beacon'});
}

function getLinkFromEvent(evt) {
var $target = $(evt.target);

if (!$target.is('a')) {
$target = $target.parents('a');
}

return $target;
}
}
}());
38 changes: 38 additions & 0 deletions javascripts/govuk/analytics/external-link-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
(function() {
"use strict";
GOVUK.analyticsPlugins = GOVUK.analyticsPlugins || {};
GOVUK.analyticsPlugins.externalLinkTracker = function () {

var currentHost = GOVUK.analyticsPlugins.externalLinkTracker.getHostname(),
externalLinkSelector = 'a[href^="http"]:not(a[href*="' + currentHost + '"])';

$('body').on('click', externalLinkSelector, trackClickEvent);

function trackClickEvent(evt) {
var $link = getLinkFromEvent(evt),
options = {transport: 'beacon'},
href = $link.attr('href'),
linkText = $.trim($link.text());

if (linkText) {
options.label = linkText;
}

GOVUK.analytics.trackEvent('External Link Clicked', href, options);
}

function getLinkFromEvent(evt) {
var $target = $(evt.target);

if (!$target.is('a')) {
$target = $target.parents('a');
}

return $target;
}
}

GOVUK.analyticsPlugins.externalLinkTracker.getHostname = function() {
return window.location.hostname;
}
}());
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
};

// https://developers.google.com/analytics/devguides/collection/analyticsjs/pages
GoogleAnalyticsUniversalTracker.prototype.trackPageview = function(path, title) {
GoogleAnalyticsUniversalTracker.prototype.trackPageview = function(path, title, options) {
var options = options || {};

if (typeof path === "string") {
var pageviewObject = {
page: path
Expand All @@ -33,6 +35,14 @@
if (typeof title === "string") {
pageviewObject.title = title;
}

// Set the transport method for the pageview
// Typically used for enabling `navigator.sendBeacon` when the page might be unloading
// https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#transport
if (options.transport) {
pageviewObject.transport = options.transport;
}

sendToGa('send', 'pageview', pageviewObject);
} else {
sendToGa('send', 'pageview');
Expand Down Expand Up @@ -75,6 +85,13 @@
evt.nonInteraction = 1;
}

// Set the transport method for the event
// Typically used for enabling `navigator.sendBeacon` when the page might be unloading
// https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#transport
if (options.transport) {
evt.transport = options.transport;
}

sendToGa('send', evt);
};

Expand Down
9 changes: 6 additions & 3 deletions spec/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ var manifest = {
'../../javascripts/govuk/stop-scrolling-at-footer.js',
'../../javascripts/govuk/selection-buttons.js',
'../../javascripts/govuk/analytics/google-analytics-universal-tracker.js',
'../../javascripts/govuk/analytics/analytics.js'

'../../javascripts/govuk/analytics/analytics.js',
'../../javascripts/govuk/analytics/external-link-tracker.js',
'../../javascripts/govuk/analytics/download-link-tracker.js'
],
test : [
'../unit/MultivariateTestSpec.js',
'../unit/PrimaryLinksSpec.js',
'../unit/StickAtTopWhenScrollingSpec.js',
'../unit/SelectionButtonSpec.js',
'../unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js',
'../unit/analytics/AnalyticsSpec.js'
'../unit/analytics/AnalyticsSpec.js',
'../unit/analytics/ExternalLinkTrackerSpec.js',
'../unit/analytics/DownloadLinkTrackerSpec.js'
]
};
4 changes: 4 additions & 0 deletions spec/unit/MultivariateTestSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ describe("MultivariateTest", function() {
spyOn(GOVUK.analytics, "trackEvent");
});

afterEach(function() {
delete GOVUK.analytics;
});

describe("#run", function() {
it("should pick a random cohort on first run", function() {
GOVUK.cookie.and.returnValue(null);
Expand Down
61 changes: 61 additions & 0 deletions spec/unit/analytics/DownloadLinkTrackerSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
describe("GOVUK.analyticsPlugins.downloadLinkTracker", function() {
var $links;

beforeEach(function() {
$links = $('\
<div class="download-links">\
<a href="/one.pdf">PDF</a>\
<a href="/two.xslt">Spreadsheet</a>\
<a href="/something/uploads/system/three.doc">Document</a>\
<a href="/an/image/link.png"><img src="/img" /></a>\
</div>\
<div class="normal-links">\
<a href="/normal-link">Normal link</a>\
<a href="/another-link">Another link</a>\
</div>');

$('html').on('click', function(evt) { evt.preventDefault(); });
$('body').append($links);
GOVUK.analytics = {trackPageview:function(){}};
GOVUK.analyticsPlugins.downloadLinkTracker({selector: 'a[href$=".pdf"], a[href$=".xslt"], a[href$=".doc"], a[href$=".png"]'});
});

afterEach(function() {
$('html').off();
$('body').off();
$links.remove();
delete GOVUK.analytics;
});

it('listens to clicks on links that match the selector', function() {
spyOn(GOVUK.analytics, 'trackPageview');

$('.download-links a').each(function() {
$(this).trigger('click');
expect(GOVUK.analytics.trackPageview).toHaveBeenCalled();
GOVUK.analytics.trackPageview.calls.reset();
});

$('.normal-links a').each(function() {
$(this).trigger('click');
expect(GOVUK.analytics.trackPageview).not.toHaveBeenCalled();
GOVUK.analytics.trackPageview.calls.reset();
});
});

it('listens to click events on elements within download links', function() {
spyOn(GOVUK.analytics, 'trackPageview');

$('.download-links a img').trigger('click');
expect(GOVUK.analytics.trackPageview).toHaveBeenCalledWith('/an/image/link.png', '', {transport: 'beacon'});
});

it('tracks a download link as a pageview with a custom title', function() {
spyOn(GOVUK.analytics, 'trackPageview');
$('.download-links a').trigger('click');

expect(GOVUK.analytics.trackPageview).toHaveBeenCalledWith('/one.pdf', 'PDF', {transport: 'beacon'});
expect(GOVUK.analytics.trackPageview).toHaveBeenCalledWith('/two.xslt', 'Spreadsheet', {transport: 'beacon'});
expect(GOVUK.analytics.trackPageview).toHaveBeenCalledWith('/something/uploads/system/three.doc', 'Document', {transport: 'beacon'});
});
});
69 changes: 69 additions & 0 deletions spec/unit/analytics/ExternalLinkTrackerSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
describe("GOVUK.analyticsPlugins.externalLinkTracker", function() {
var $links;

beforeEach(function() {
$links = $('\
<div class="external-links">\
<a href="http://www.nationalarchives.gov.uk"> National Archives </a>\
<a href="https://www.nationalarchives.gov.uk"></a>\
<a href="https://www.nationalarchives.gov.uk/one.pdf">National Archives PDF</a>\
<a href="https://www.nationalarchives.gov.uk/an/image/link.png"><img src="/img" /></a>\
</div>\
<div class="internal-links">\
<a href="/some-path">Local link</a>\
<a href="http://fake-hostname.com/some-path">Another local link</a>\
</div>');

$('html').on('click', function(evt) { evt.preventDefault(); });
$('body').append($links);
GOVUK.analytics = {trackEvent:function(){}};

spyOn(GOVUK.analyticsPlugins.externalLinkTracker, 'getHostname').and.returnValue('fake-hostname.com');
GOVUK.analyticsPlugins.externalLinkTracker();
});

afterEach(function() {
$('html').off();
$('body').off();
$links.remove();
delete GOVUK.analytics;
});

it('listens to click events on only external links', function() {
spyOn(GOVUK.analytics, 'trackEvent');

$('.external-links a').each(function() {
$(this).trigger('click');
expect(GOVUK.analytics.trackEvent).toHaveBeenCalled();
GOVUK.analytics.trackEvent.calls.reset();
});

$('.internal-links a').each(function() {
$(this).trigger('click');
expect(GOVUK.analytics.trackEvent).not.toHaveBeenCalled();
GOVUK.analytics.trackEvent.calls.reset();
});
});

it('listens to click events on elements within external links', function() {
spyOn(GOVUK.analytics, 'trackEvent');

$('.external-links a img').trigger('click');
expect(GOVUK.analytics.trackEvent).toHaveBeenCalledWith(
'External Link Clicked', 'https://www.nationalarchives.gov.uk/an/image/link.png', {transport: 'beacon'});
});

it('tracks an external link\'s href and link text', function() {
spyOn(GOVUK.analytics, 'trackEvent');
$('.external-links a').trigger('click');

expect(GOVUK.analytics.trackEvent).toHaveBeenCalledWith(
'External Link Clicked', 'http://www.nationalarchives.gov.uk', {transport: 'beacon', label: 'National Archives'});

expect(GOVUK.analytics.trackEvent).toHaveBeenCalledWith(
'External Link Clicked', 'https://www.nationalarchives.gov.uk', {transport: 'beacon'});

expect(GOVUK.analytics.trackEvent).toHaveBeenCalledWith(
'External Link Clicked', 'https://www.nationalarchives.gov.uk/one.pdf', {transport: 'beacon', label: 'National Archives PDF'});
});
});
12 changes: 12 additions & 0 deletions spec/unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ describe("GOVUK.GoogleAnalyticsUniversalTracker", function() {
universal.trackPageview('/nicholas-page', 'Nicholas Page');
expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', {page: '/nicholas-page', title: 'Nicholas Page'}]);
});

it('can set the transport method on a pageview', function() {
universal.trackPageview('/t', 'T', {transport: 'beacon'});
expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', {page: '/t', title: 'T', transport: 'beacon'}]);
});
});

describe('when events are tracked', function() {
Expand Down Expand Up @@ -101,6 +106,13 @@ describe("GOVUK.GoogleAnalyticsUniversalTracker", function() {
['send', {hitType: 'event', eventCategory: 'category', eventAction: 'action', page: '/path/to/page'}]
);
});

it('can set the transport method on an event', function() {
universal.trackEvent('category', 'action', {transport: 'beacon'});
expect(window.ga.calls.mostRecent().args).toEqual(
['send', {hitType: 'event', eventCategory: 'category', eventAction: 'action', transport: 'beacon'}]
);
});
});

describe('when social events are tracked', function() {
Expand Down

0 comments on commit b911a47

Please sign in to comment.