From 5fa6d6d522fa9a4996c3ffd50c4d6808f6b0615a Mon Sep 17 00:00:00 2001 From: Jon Wyatt Date: Mon, 9 Mar 2015 10:04:03 +0000 Subject: [PATCH 1/8] Bump Jasmine to 2.0 and amend tests --- package.json | 2 +- spec/unit/MultivariateTestSpec.js | 22 +++++++++++----------- spec/unit/SelectionButtonSpec.js | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3086fb66..50185ce8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "grunt": "~0.4.2", "grunt-cli": "0.1.11", "grunt-contrib-clean":"~0.6.0", - "grunt-contrib-jasmine": "~0.5.2", + "grunt-contrib-jasmine": "~0.8.2", "grunt-contrib-sass": "0.7.4", "grunt-scss-lint": "0.3.4", "jquery-browser": "~1.7.2-3" diff --git a/spec/unit/MultivariateTestSpec.js b/spec/unit/MultivariateTestSpec.js index 8ad82695..ddaf4216 100644 --- a/spec/unit/MultivariateTestSpec.js +++ b/spec/unit/MultivariateTestSpec.js @@ -6,7 +6,7 @@ describe("MultivariateTest", function() { describe("#run", function() { it("should pick a random cohort on first run", function() { - GOVUK.cookie.andReturn(null); + GOVUK.cookie.and.returnValue(null); var fooSpy = jasmine.createSpy('fooSpy'); var barSpy = jasmine.createSpy('barSpy'); var test = new GOVUK.MultivariateTest({ @@ -18,9 +18,9 @@ describe("MultivariateTest", function() { } }); - expect(GOVUK.cookie.callCount).toEqual(2); - expect(GOVUK.cookie.argsForCall[1][0]).toEqual('multivariatetest_cohort_stuff'); - if (GOVUK.cookie.argsForCall[1][1] == 'foo') { + expect(GOVUK.cookie.calls.count()).toEqual(2); + expect(GOVUK.cookie.calls.argsFor(1)[0]).toEqual('multivariatetest_cohort_stuff'); + if (GOVUK.cookie.calls.argsFor(1)[1] == 'foo') { expect(fooSpy).toHaveBeenCalled(); } else { @@ -29,7 +29,7 @@ describe("MultivariateTest", function() { }); it("should use an existing cohort choice on subsequent runs", function() { - GOVUK.cookie.andReturn('foo'); + GOVUK.cookie.and.returnValue('foo'); var fooSpy = jasmine.createSpy('fooSpy'); var barSpy = jasmine.createSpy('barSpy'); var test = new GOVUK.MultivariateTest({ @@ -44,7 +44,7 @@ describe("MultivariateTest", function() { }); it("should set a custom var", function() { - GOVUK.cookie.andReturn('foo'); + GOVUK.cookie.and.returnValue('foo'); var test = new GOVUK.MultivariateTest({ name: 'stuff', cohorts: { @@ -73,7 +73,7 @@ describe("MultivariateTest", function() { }); it("should set html for a cohort", function() { - GOVUK.cookie.andReturn('foo'); + GOVUK.cookie.and.returnValue('foo'); var $el = $('
'); var test = new GOVUK.MultivariateTest({ name: 'stuff', @@ -90,7 +90,7 @@ describe("MultivariateTest", function() { it("should call the callback for a cohort", function() { var fooSpy = jasmine.createSpy('fooSpy'); var barSpy = jasmine.createSpy('barSpy'); - GOVUK.cookie.andReturn('bar'); + GOVUK.cookie.and.returnValue('bar'); var $el = $('
'); var test = new GOVUK.MultivariateTest({ name: 'stuff', @@ -105,7 +105,7 @@ describe("MultivariateTest", function() { }); it("should call the callback for a cohort if it is a string", function() { - GOVUK.cookie.andReturn('foo'); + GOVUK.cookie.and.returnValue('foo'); var test = new GOVUK.MultivariateTest({ name: 'stuff', customVarIndex: 1, @@ -123,7 +123,7 @@ describe("MultivariateTest", function() { it("should assign a new random cohort if the assigned cohort does not exist", function() { var fooSpy = jasmine.createSpy('fooSpy'); var barSpy = jasmine.createSpy('barSpy'); - GOVUK.cookie.andReturn('baz'); + GOVUK.cookie.and.returnValue('baz'); var test = new GOVUK.MultivariateTest({ name: 'stuff', customVarIndex: 1, @@ -132,7 +132,7 @@ describe("MultivariateTest", function() { bar: {callback: barSpy} } }); - if (GOVUK.cookie.argsForCall[1][1] == 'foo') { + if (GOVUK.cookie.calls.argsFor(1)[1] == 'foo') { expect(fooSpy).toHaveBeenCalled(); } else { diff --git a/spec/unit/SelectionButtonSpec.js b/spec/unit/SelectionButtonSpec.js index b8eb0fa4..b1e6b453 100644 --- a/spec/unit/SelectionButtonSpec.js +++ b/spec/unit/SelectionButtonSpec.js @@ -694,7 +694,7 @@ describe("selection-buttons", function () { clickCallbackCancelled = false, focusBlurCallbackCancelled = false; - spyOn($.fn, "on").andCallFake(function (evt, callback) { + spyOn($.fn, "on").and.callFake(function (evt, callback) { if (this === $radioButtons) { if (evt === "click") { clickCallbackBound = callback; @@ -706,7 +706,7 @@ describe("selection-buttons", function () { return this; }); - spyOn($.fn, "off").andCallFake(function (evt, callback) { + spyOn($.fn, "off").and.callFake(function (evt, callback) { if (this === $radioButtons) { if (evt === "click") { clickCallbackCancelled = callback; @@ -731,7 +731,7 @@ describe("selection-buttons", function () { clickCallbackCancelled = false, focusBlurCallbackCancelled = false; - spyOn($.fn, "on").andCallFake(function (evt, selector, callback) { + spyOn($.fn, "on").and.callFake(function (evt, selector, callback) { if ((this[0] === document) && (selector === "label.selectable input[type='checkbox']")) { if (evt === "click") { clickCallbackBound = callback; @@ -743,7 +743,7 @@ describe("selection-buttons", function () { return this; }); - spyOn($.fn, "off").andCallFake(function (evt, selector, callback) { + spyOn($.fn, "off").and.callFake(function (evt, selector, callback) { if ((this[0] === document) && (selector === "label.selectable input[type='checkbox']")) { if (evt === "click") { clickCallbackCancelled = callback; From ee2fd0de3e11855ed6828a728a2854a902b28274 Mon Sep 17 00:00:00 2001 From: Jon Wyatt Date: Mon, 9 Mar 2015 10:06:10 +0000 Subject: [PATCH 2/8] Port analytics libs from static repo Include sample init file. Remove jQuery dependency from Tracker --- Gruntfile.js | 5 +- javascripts/govuk/analytics/error-tracking.js | 22 ++++ .../google-analytics-classic-tracker.js | 111 ++++++++++++++++ .../google-analytics-universal-tracker.js | 104 +++++++++++++++ javascripts/govuk/analytics/print-intent.js | 31 +++++ javascripts/govuk/analytics/sample-init.js | 20 +++ javascripts/govuk/analytics/tracker.js | 67 ++++++++++ .../GoogleAnalyticsClassicTrackerSpec.js | 124 ++++++++++++++++++ .../GoogleAnalyticsUniversalTrackerSpec.js | 122 +++++++++++++++++ spec/unit/analytics/TrackerSpec.js | 94 +++++++++++++ 10 files changed, 699 insertions(+), 1 deletion(-) create mode 100644 javascripts/govuk/analytics/error-tracking.js create mode 100644 javascripts/govuk/analytics/google-analytics-classic-tracker.js create mode 100644 javascripts/govuk/analytics/google-analytics-universal-tracker.js create mode 100644 javascripts/govuk/analytics/print-intent.js create mode 100644 javascripts/govuk/analytics/sample-init.js create mode 100644 javascripts/govuk/analytics/tracker.js create mode 100644 spec/unit/analytics/GoogleAnalyticsClassicTrackerSpec.js create mode 100644 spec/unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js create mode 100644 spec/unit/analytics/TrackerSpec.js diff --git a/Gruntfile.js b/Gruntfile.js index 76203f99..ef050da6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,10 +30,13 @@ module.exports = function(grunt) { javascripts: { src: [ 'node_modules/jquery-browser/lib/jquery.js', + 'javascripts/govuk/analytics/google-analytics-classic-tracker.js', + 'javascripts/govuk/analytics/google-analytics-universal-tracker.js', + 'javascripts/govuk/analytics/tracker.js', 'javascripts/**/*.js' ], options: { - specs: 'spec/unit/*Spec.js', + specs: 'spec/unit/**/*Spec.js', helpers: 'spec/unit/*Helper.js' } } diff --git a/javascripts/govuk/analytics/error-tracking.js b/javascripts/govuk/analytics/error-tracking.js new file mode 100644 index 00000000..d06bf04d --- /dev/null +++ b/javascripts/govuk/analytics/error-tracking.js @@ -0,0 +1,22 @@ +// Extension to track errors using google analytics as a data store. +(function() { + + "use strict"; + var trackJavaScriptError = function (e) { + var errorSource = e.filename + ': ' + e.lineno; + GOVUK.analytics.trackEvent('JavaScript Error', e.message, { + label: errorSource, + value: 1, + nonInteraction: true + }); + }; + + if (window.addEventListener) { + window.addEventListener('error', trackJavaScriptError, false); + } else if (window.attachEvent) { + window.attachEvent('onerror', trackJavaScriptError); + } else { + window.onerror = trackJavaScriptError; + } + +}()); diff --git a/javascripts/govuk/analytics/google-analytics-classic-tracker.js b/javascripts/govuk/analytics/google-analytics-classic-tracker.js new file mode 100644 index 00000000..3d5f2e0f --- /dev/null +++ b/javascripts/govuk/analytics/google-analytics-classic-tracker.js @@ -0,0 +1,111 @@ +(function() { + "use strict"; + window.GOVUK = window.GOVUK || {}; + + var GoogleAnalyticsClassicTracker = function(id, cookieDomain) { + window._gaq = window._gaq || []; + configureProfile(id, cookieDomain); + allowCrossDomainTracking(); + anonymizeIp(); + + function configureProfile(id, cookieDomain) { + _gaq.push(['_setAccount', id]); + _gaq.push(['_setDomainName', cookieDomain]); + } + + function allowCrossDomainTracking() { + _gaq.push(['_setAllowLinker', true]); + } + + // https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApi_gat#_gat._anonymizeIp + function anonymizeIp() { + _gaq.push(['_gat._anonymizeIp']); + } + }; + + GoogleAnalyticsClassicTracker.load = function() { + var ga = document.createElement('script'), + s = document.getElementsByTagName('script')[0]; + + ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + s.parentNode.insertBefore(ga, s); + }; + + // https://developers.google.com/analytics/devguides/collection/gajs/asyncMigrationExamples#VirtualPageviews + GoogleAnalyticsClassicTracker.prototype.trackPageview = function(path) { + var pageview = ['_trackPageview']; + + if (typeof path === "string") { + pageview.push(path); + } + + _gaq.push(pageview); + }; + + // https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide + GoogleAnalyticsClassicTracker.prototype.trackEvent = function(category, action, options) { + var value, + options = options || {}, + hasLabel = false, + hasValue = false, + evt = ["_trackEvent", category, action]; + + // Label is optional + if (typeof options.label === "string") { + hasLabel = true; + evt.push(options.label); + } + + // Value is optional, but when used must be an + // integer, otherwise the event will be invalid + // and not logged + if (options.value || options.value === 0) { + value = parseInt(options.value, 10); + if (typeof value === "number" && !isNaN(value)) { + hasValue = true; + + // Push an empty label if not set for correct final argument order + if (!hasLabel) { + evt.push(''); + } + + evt.push(value); + } + } + + // Prevents an event from affecting bounce rate + // https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide#non-interaction + if (options.nonInteraction) { + + // Push empty label/value if not already set, for correct final argument order + if (!hasValue) { + if (!hasLabel) { + evt.push(''); + } + evt.push(0); + } + + evt.push(true); + } + + _gaq.push(evt); + }; + + /* + https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiSocialTracking + network – The network on which the action occurs (e.g. Facebook, Twitter) + action – The type of action that happens (e.g. Like, Send, Tweet) + target – The text value that indicates the subject of the action + */ + GoogleAnalyticsClassicTracker.prototype.trackSocial = function(network, action, target) { + _gaq.push(['_trackSocial', network, action, target]); + }; + + // https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables + GoogleAnalyticsClassicTracker.prototype.setCustomVariable = function(index, value, name, scope) { + _gaq.push(['_setCustomVar', index, name, String(value), scope]); + }; + + GOVUK.GoogleAnalyticsClassicTracker = GoogleAnalyticsClassicTracker; +})(); diff --git a/javascripts/govuk/analytics/google-analytics-universal-tracker.js b/javascripts/govuk/analytics/google-analytics-universal-tracker.js new file mode 100644 index 00000000..cdc86a35 --- /dev/null +++ b/javascripts/govuk/analytics/google-analytics-universal-tracker.js @@ -0,0 +1,104 @@ +(function() { + "use strict"; + window.GOVUK = window.GOVUK || {}; + + var GoogleAnalyticsUniversalTracker = function(id, cookieDomain) { + configureProfile(id, cookieDomain); + anonymizeIp(); + + function configureProfile(id, cookieDomain) { + sendToGa('create', id, {'cookieDomain': cookieDomain}); + } + + function anonymizeIp() { + // https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#anonymizeip + sendToGa('set', 'anonymizeIp', true); + } + }; + + GoogleAnalyticsUniversalTracker.load = function() { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + }; + + // https://developers.google.com/analytics/devguides/collection/analyticsjs/pages + GoogleAnalyticsUniversalTracker.prototype.trackPageview = function(path, title) { + if (typeof path === "string") { + var pageviewObject = { + page: path + }; + + if (typeof title === "string") { + pageviewObject.title = title; + } + sendToGa('send', 'pageview', pageviewObject); + } else { + sendToGa('send', 'pageview'); + } + }; + + // https://developers.google.com/analytics/devguides/collection/analyticsjs/events + GoogleAnalyticsUniversalTracker.prototype.trackEvent = function(category, action, options) { + var value, + options = options || {}, + evt = { + hitType: 'event', + eventCategory: category, + eventAction: action + }; + + // Label is optional + if (typeof options.label === "string") { + evt.eventLabel = options.label; + } + + // Value is optional, but when used must be an + // integer, otherwise the event will be invalid + // and not logged + if (options.value || options.value === 0) { + value = parseInt(options.value, 10); + if (typeof value === "number" && !isNaN(value)) { + evt.eventValue = value; + } + } + + // Prevents an event from affecting bounce rate + // https://developers.google.com/analytics/devguides/collection/analyticsjs/events#implementation + if (options.nonInteraction) { + evt.nonInteraction = 1; + } + + sendToGa('send', evt); + }; + + /* + https://developers.google.com/analytics/devguides/collection/analyticsjs/social-interactions + network – The network on which the action occurs (e.g. Facebook, Twitter) + action – The type of action that happens (e.g. Like, Send, Tweet) + target – Specifies the target of a social interaction. + This value is typically a URL but can be any text. + */ + GoogleAnalyticsUniversalTracker.prototype.trackSocial = function(network, action, target) { + sendToGa('send', { + 'hitType': 'social', + 'socialNetwork': network, + 'socialAction': action, + 'socialTarget': target + }); + }; + + // https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets + GoogleAnalyticsUniversalTracker.prototype.setDimension = function(index, value) { + sendToGa('set', 'dimension' + index, String(value)); + }; + + function sendToGa() { + if (typeof window.ga === "function") { + ga.apply(window, arguments); + } + } + + GOVUK.GoogleAnalyticsUniversalTracker = GoogleAnalyticsUniversalTracker; +})(); diff --git a/javascripts/govuk/analytics/print-intent.js b/javascripts/govuk/analytics/print-intent.js new file mode 100644 index 00000000..54f13f76 --- /dev/null +++ b/javascripts/govuk/analytics/print-intent.js @@ -0,0 +1,31 @@ +// Extension to monitor attempts to print pages. +(function() { + + "use strict"; + var printAttempt = (function() { + GOVUK.analytics.trackEvent('Print Intent', document.location.pathname); + }); + + // Most browsers + if (window.matchMedia) { + var mediaQueryList = window.matchMedia('print'), + mqlListenerCount = 0; + mediaQueryList.addListener(function(mql) { + if (!mql.matches && mqlListenerCount === 0) { + printAttempt(); + mqlListenerCount++; + // If we try and print again in 3 seconds, don't log it + window.setTimeout(function(){ + mqlListenerCount = 0; + // printing will be tracked again now + },1e3); + } + }); + } + + // IE < 10 + if(window.onafterprint){ + window.onafterprint = printAttempt; + } + +}()); diff --git a/javascripts/govuk/analytics/sample-init.js b/javascripts/govuk/analytics/sample-init.js new file mode 100644 index 00000000..0b427db5 --- /dev/null +++ b/javascripts/govuk/analytics/sample-init.js @@ -0,0 +1,20 @@ +(function() { + "use strict"; + + // Load Google Analytics libraries + GOVUK.Tracker.load(); + + // Use document.domain in dev, preview and staging so that tracking works + // Otherwise explicitly set the domain as www.gov.uk (and not gov.uk). + var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; + + // Configure profiles, setup custom vars, track initial pageview + var analytics = new GOVUK.Tracker({ + universalId: 'UA-XXXXXXXX-X', + classicId: 'UA-XXXXXXXX-X', + cookieDomain: cookieDomain + }); + + // Make interface public for virtual pageviews and events + GOVUK.analytics = analytics; +})(); diff --git a/javascripts/govuk/analytics/tracker.js b/javascripts/govuk/analytics/tracker.js new file mode 100644 index 00000000..3797f5cf --- /dev/null +++ b/javascripts/govuk/analytics/tracker.js @@ -0,0 +1,67 @@ +(function() { + "use strict"; + window.GOVUK = window.GOVUK || {}; + + var Tracker = function(config) { + + this.universal = new GOVUK.GoogleAnalyticsUniversalTracker(config.universalId, config.cookieDomain); + this.classic = new GOVUK.GoogleAnalyticsClassicTracker(config.classicId, config.cookieDomain); + + // Dimensions should be set before page view is tracked + + if (config.prePageviewConfiguration && typeof config.prePageviewConfiguration === 'function') { + config.prePageviewConfiguration(); + } + + this.trackPageview(); + + }; + + Tracker.load = function() { + GOVUK.GoogleAnalyticsClassicTracker.load(); + GOVUK.GoogleAnalyticsUniversalTracker.load(); + }; + + Tracker.prototype.trackPageview = function(path, title) { + this.classic.trackPageview(path); + this.universal.trackPageview(path, title); + }; + + /* + https://developers.google.com/analytics/devguides/collection/analyticsjs/events + options.label – Useful for categorizing events (eg nav buttons) + options.value – Values must be non-negative. Useful to pass counts + options.nonInteraction – Prevent event from impacting bounce rate + */ + Tracker.prototype.trackEvent = function(category, action, options) { + this.classic.trackEvent(category, action, options); + this.universal.trackEvent(category, action, options); + }; + + Tracker.prototype.trackShare = function(network) { + var target = location.pathname; + this.classic.trackSocial(network, 'share', target); + this.universal.trackSocial(network, 'share', target); + }; + + /* + Assumes that the index of the dimension is the same for both classic and universal. Check this for your app before using this + */ + Tracker.prototype.setDimension = function(index, value, name, scope) { + var PAGE_LEVEL_SCOPE = 3; + scope = scope || PAGE_LEVEL_SCOPE; + + if (typeof index !== "number") { + index = parseInt(index, 10); + } + + if (typeof scope !== "number") { + scope = parseInt(scope, 10); + } + + this.universal.setDimension(index, value); + this.classic.setCustomVariable(index, value, name, scope); + }; + + GOVUK.Tracker = Tracker; +})(); diff --git a/spec/unit/analytics/GoogleAnalyticsClassicTrackerSpec.js b/spec/unit/analytics/GoogleAnalyticsClassicTrackerSpec.js new file mode 100644 index 00000000..9673d5e5 --- /dev/null +++ b/spec/unit/analytics/GoogleAnalyticsClassicTrackerSpec.js @@ -0,0 +1,124 @@ +describe("GOVUK.GoogleAnalyticsClassicTracker", function() { + var classic; + + beforeEach(function() { + window._gaq = []; + classic = new GOVUK.GoogleAnalyticsClassicTracker('id', 'cookie-domain.com'); + }); + + it('can load the libraries needed to run classic Google Analytics', function() { + delete window._gaq; + $('[src*="google-analytics.com/ga.js"]').remove(); + + GOVUK.GoogleAnalyticsClassicTracker.load(); + expect($('script[async][src*="google-analytics.com/ga.js"]').length).toBe(1); + }); + + describe('when created', function() { + it('configures a Google tracker using the provided profile ID and cookie domain', function() { + expect(window._gaq[0]).toEqual(['_setAccount', 'id']); + expect(window._gaq[1]).toEqual(['_setDomainName', 'cookie-domain.com']); + }); + + it('allows cross site linking', function() { + expect(window._gaq[2]).toEqual(['_setAllowLinker', true]); + }); + + it('anonymises the IP', function() { + expect(window._gaq[3]).toEqual(['_gat._anonymizeIp']); + }); + }); + + describe('when pageviews are tracked', function() { + beforeEach(function() { + // reset queue after setup + window._gaq = []; + }); + + it('sends them to Google Analytics', function() { + classic.trackPageview(); + expect(window._gaq[0]).toEqual(['_trackPageview']); + }); + + it('can track a virtual pageview', function() { + classic.trackPageview('/nicholas-page'); + expect(window._gaq[0]).toEqual(['_trackPageview', '/nicholas-page']); + }); + }); + + describe('when events are tracked', function() { + beforeEach(function() { + // reset queue after setup + window._gaq = []; + }); + + it('sends them to Google Analytics', function() { + classic.trackEvent('category', 'action', {label: 'label'}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', 'label']); + }); + + it('the label is optional', function() { + classic.trackEvent('category', 'action'); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action']); + }); + + it('a value can be tracked if the label is omitted', function() { + classic.trackEvent('category', 'action', {value: 10}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', '', 10]); + }); + + it('only sends values if they are parseable as numbers', function() { + classic.trackEvent('category', 'action', {label: 'label', value: '10'}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', 'label', 10]); + + classic.trackEvent('category', 'action', {label: 'label', value: 10}); + expect(window._gaq[1]).toEqual(['_trackEvent', 'category', 'action', 'label', 10]); + + classic.trackEvent('category', 'action', {label: 'label', value: 'not a number'}); + expect(window._gaq[2]).toEqual(['_trackEvent', 'category', 'action', 'label']); + }); + + it('can mark an event as non interactive', function() { + classic.trackEvent('category', 'action', {label: 'label', value: 1, nonInteraction: true}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', 'label', 1, true]); + }); + + it('can mark an event as non interactive without a value', function() { + classic.trackEvent('category', 'action', {label: 'label', nonInteraction: true}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', 'label', 0, true]); + }); + + it('can mark an event as non interactive without a label or value', function() { + classic.trackEvent('category', 'action', {nonInteraction: true}); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action', '', 0, true]); + }); + }); + + describe('when social events are tracked', function() { + beforeEach(function() { + window._gaq = []; + }); + + it('sends them to Google Analytics', function() { + classic.trackSocial('network', 'action', 'target'); + expect(window._gaq[0]).toEqual(['_trackSocial', 'network', 'action', 'target']); + }); + }); + + describe('when setting a custom variable', function() { + beforeEach(function() { + // reset queue after setup + window._gaq = []; + }); + + it('sends the variable to Google Analytics', function() { + classic.setCustomVariable(1, 'value', 'name', 10); + expect(window._gaq[0]).toEqual(['_setCustomVar', 1, 'name', 'value', 10]); + }); + + it('coerces the value to a string', function() { + classic.setCustomVariable(1, 100, 'name', 10); + expect(window._gaq[0]).toEqual(['_setCustomVar', 1, 'name', '100', 10]); + }); + }); +}); diff --git a/spec/unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js b/spec/unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js new file mode 100644 index 00000000..286dbbca --- /dev/null +++ b/spec/unit/analytics/GoogleAnalyticsUniversalTrackerSpec.js @@ -0,0 +1,122 @@ +describe("GOVUK.GoogleAnalyticsUniversalTracker", function() { + var universal; + + beforeEach(function() { + window.ga = function() {}; + spyOn(window, 'ga'); + universal = new GOVUK.GoogleAnalyticsUniversalTracker('id', 'cookie-domain.com'); + }); + + it('can load the libraries needed to run universal Google Analytics', function() { + delete window.ga; + $('[src="//www.google-analytics.com/analytics.js"]').remove(); + GOVUK.GoogleAnalyticsUniversalTracker.load(); + expect($('script[async][src="//www.google-analytics.com/analytics.js"]').length).toBe(1); + expect(typeof window.ga).toBe('function'); + + window.ga('send message'); + expect(window.ga.q[0]).toEqual(jasmine.any(Object)); + }); + + describe('when created', function() { + var setupArguments; + + beforeEach(function() { + setupArguments = window.ga.calls.allArgs(); + }); + + it('configures a Google tracker using the provided profile ID and cookie domain', function() { + expect(setupArguments[0]).toEqual(['create', 'id', {'cookieDomain': 'cookie-domain.com'}]); + }); + + it('anonymises the IP', function() { + expect(setupArguments[1]).toEqual(['set', 'anonymizeIp', true]); + }); + }); + + describe('when pageviews are tracked', function() { + it('sends them to Google Analytics', function() { + universal.trackPageview(); + expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview']); + }); + + it('can track a virtual pageview', function() { + universal.trackPageview('/nicholas-page'); + expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', {page: '/nicholas-page'}]); + }); + + it('can track a virtual pageview with a custom title', function() { + universal.trackPageview('/nicholas-page', 'Nicholas Page'); + expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', {page: '/nicholas-page', title: 'Nicholas Page'}]); + }); + }); + + describe('when events are tracked', function() { + function eventObjectFromSpy() { + return window.ga.calls.mostRecent().args[1]; + } + + it('sends them to Google Analytics', function() { + universal.trackEvent('category', 'action', {label: 'label'}); + expect(window.ga.calls.mostRecent().args).toEqual( + ['send', {hitType: 'event', eventCategory: 'category', eventAction: 'action', eventLabel: 'label'}] + ); + }); + + it('the label is optional', function() { + universal.trackEvent('category', 'action'); + expect(window.ga.calls.mostRecent().args).toEqual( + ['send', {hitType: 'event', eventCategory: 'category', eventAction: 'action'}] + ); + }); + + it('only sends values if they are parseable as numbers', function() { + universal.trackEvent('category', 'action', {label: 'label', value: '10'}); + expect(eventObjectFromSpy()['eventValue']).toEqual(10); + + universal.trackEvent('category', 'action', {label: 'label', value: 10}); + expect(eventObjectFromSpy()['eventValue']).toEqual(10); + + universal.trackEvent('category', 'action', {label: 'label', value: 'not a number'}); + expect(eventObjectFromSpy()['eventValue']).toEqual(undefined); + }); + + it('can mark an event as non interactive', function() { + universal.trackEvent('category', 'action', {label: 'label', value: 0, nonInteraction: true}); + expect(window.ga.calls.mostRecent().args).toEqual( + ['send', { + hitType: 'event', + eventCategory: 'category', + eventAction: 'action', + eventLabel: 'label', + eventValue: 0, + nonInteraction: 1 + }] + ); + }); + }); + + describe('when social events are tracked', function() { + it('sends them to Google Analytics', function() { + universal.trackSocial('network', 'action', 'target'); + expect(window.ga.calls.mostRecent().args).toEqual(['send', { + 'hitType': 'social', + 'socialNetwork': 'network', + 'socialAction': 'action', + 'socialTarget': 'target' + }]); + }); + }); + + describe('when setting a custom dimension', function() { + it('sends the dimension to Google Analytics with the specified index and value', function() { + universal.setDimension(1, 'value'); + expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', 'value']); + }); + + it('coerces the value to a string', function() { + universal.setDimension(1, 10); + expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', '10']); + }); + }); +}); diff --git a/spec/unit/analytics/TrackerSpec.js b/spec/unit/analytics/TrackerSpec.js new file mode 100644 index 00000000..96eb10cb --- /dev/null +++ b/spec/unit/analytics/TrackerSpec.js @@ -0,0 +1,94 @@ +describe("GOVUK.Tracker", function() { + var tracker; + + beforeEach(function() { + window._gaq = []; + window.ga = function() {}; + spyOn(window, 'ga'); + this.config = { + universalId: 'universal-id', + classicId: 'classic-id', + cookieDomain: '.www.gov.uk' + }; + }); + + describe('when created', function() { + var universalSetupArguments; + + beforeEach(function () { + tracker = new GOVUK.Tracker(this.config); + universalSetupArguments = window.ga.calls.allArgs(); + }); + + it('configures classic and universal trackers', function () { + expect(window._gaq[0]).toEqual(['_setAccount', 'classic-id']); + expect(window._gaq[1]).toEqual(['_setDomainName', '.www.gov.uk']); + expect(universalSetupArguments[0]).toEqual(['create', 'universal-id', {'cookieDomain': '.www.gov.uk'}]); + }); + + it('tracks a pageview in both classic and universal', function () { + expect(window._gaq[4]).toEqual(['_trackPageview']); + expect(universalSetupArguments[2]).toEqual(['send', 'pageview']); + }); + + }); + + + describe('created with a pre-pageview callback', function() { + + it('calls a pre-pageview callback function, if supplied', function () { + var config = { + universalId: 'universal-id', + prePageviewConfiguration: jasmine.createSpy() + }; + tracker = new GOVUK.Tracker(config); + expect(config.prePageviewConfiguration).toHaveBeenCalled(); + }); + + }); + + describe('when tracking pageviews, events and custom dimensions', function() { + + beforeEach(function() { + tracker = new GOVUK.Tracker(this.config); + }); + + it('tracks in both classic and universal', function() { + window._gaq = []; + tracker.trackPageview('/path', 'Title'); + expect(window._gaq[0]).toEqual(['_trackPageview', '/path']); + expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', {page: '/path', title: 'Title'}]); + + window._gaq = []; + tracker.trackEvent('category', 'action'); + expect(window._gaq[0]).toEqual(['_trackEvent', 'category', 'action']); + expect(window.ga.calls.mostRecent().args).toEqual(['send', {hitType: 'event', eventCategory: 'category', eventAction: 'action'}]); + + window._gaq = []; + tracker.setDimension(1, 'value', 'name'); + expect(window._gaq[0]).toEqual(['_setCustomVar', 1, 'name', 'value', 3]); + expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', 'value']); + }); + }); + + describe('when tracking social media shares', function() { + + beforeEach(function() { + tracker = new GOVUK.Tracker(this.config); + }); + + it('tracks in both classic and universal', function() { + window._gaq = []; + tracker.trackShare('network'); + + expect(window._gaq[0]).toEqual(['_trackSocial', 'network', 'share', jasmine.any(String)]); + expect(window.ga.calls.mostRecent().args).toEqual(['send', { + hitType: 'social', + socialNetwork: 'network', + socialAction: 'share', + socialTarget: jasmine.any(String) + }]); + }); + }); + +}); From 3f3c719993e76d2c3455da1e60ed0cc118b387c0 Mon Sep 17 00:00:00 2001 From: Jon Wyatt Date: Mon, 9 Mar 2015 10:06:26 +0000 Subject: [PATCH 3/8] Add basic analytics instructions to README --- README.md | 10 +++++ javascripts/govuk/analytics/print-intent.js | 50 ++++++++++----------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7a15ab02..667aaf7c 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,16 @@ In production: * [Stick at top when scrolling](/docs/javascript.md#stick-at-top-when-scrolling) * [Selection buttons](/docs/javascript.md#selection-buttons) +## Analytics + +The minimum you need to use the analytics function is: +1. Include the following files from /javascripts/govuk/analytics in your project: + 1. google-analytics-classic-tracker.js + 2. google-analytics-universal-tracker.js + 3. tracker.js +2. Copy the file sample-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with 'UA-') + + ## Licence Released under the MIT Licence, a copy of which can be found in the file `LICENCE`. diff --git a/javascripts/govuk/analytics/print-intent.js b/javascripts/govuk/analytics/print-intent.js index 54f13f76..0f28654f 100644 --- a/javascripts/govuk/analytics/print-intent.js +++ b/javascripts/govuk/analytics/print-intent.js @@ -1,31 +1,31 @@ // Extension to monitor attempts to print pages. -(function() { +(function () { - "use strict"; - var printAttempt = (function() { - GOVUK.analytics.trackEvent('Print Intent', document.location.pathname); - }); + "use strict"; + var printAttempt = (function () { + GOVUK.analytics.trackEvent('Print Intent', document.location.pathname); + }); - // Most browsers - if (window.matchMedia) { - var mediaQueryList = window.matchMedia('print'), - mqlListenerCount = 0; - mediaQueryList.addListener(function(mql) { - if (!mql.matches && mqlListenerCount === 0) { - printAttempt(); - mqlListenerCount++; - // If we try and print again in 3 seconds, don't log it - window.setTimeout(function(){ - mqlListenerCount = 0; - // printing will be tracked again now - },1e3); - } - }); - } + // Most browsers + if (window.matchMedia) { + var mediaQueryList = window.matchMedia('print'), + mqlListenerCount = 0; + mediaQueryList.addListener(function (mql) { + if (!mql.matches && mqlListenerCount === 0) { + printAttempt(); + mqlListenerCount++; + // If we try and print again in 3 seconds, don't log it + window.setTimeout(function () { + mqlListenerCount = 0; + // printing will be tracked again now + }, 1e3); + } + }); + } - // IE < 10 - if(window.onafterprint){ - window.onafterprint = printAttempt; - } + // IE < 10 + if (window.onafterprint) { + window.onafterprint = printAttempt; + } }()); From 89ea88d78763936665708b6ca9003ab4cc86f8b5 Mon Sep 17 00:00:00 2001 From: Jon Wyatt Date: Mon, 9 Mar 2015 10:46:11 +0000 Subject: [PATCH 4/8] Move sample init file to own folder for clarity --- README.md | 2 +- .../analytics/{sample-init.js => example-init/example-init.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename javascripts/govuk/analytics/{sample-init.js => example-init/example-init.js} (100%) diff --git a/README.md b/README.md index 667aaf7c..ae0c5c60 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ The minimum you need to use the analytics function is: 1. google-analytics-classic-tracker.js 2. google-analytics-universal-tracker.js 3. tracker.js -2. Copy the file sample-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with 'UA-') +2. Copy the file example-init/example-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with 'UA-') ## Licence diff --git a/javascripts/govuk/analytics/sample-init.js b/javascripts/govuk/analytics/example-init/example-init.js similarity index 100% rename from javascripts/govuk/analytics/sample-init.js rename to javascripts/govuk/analytics/example-init/example-init.js From ebfa5ded23252be11d5d590aad1015373eb01a7d Mon Sep 17 00:00:00 2001 From: Paul Hayes Date: Tue, 10 Mar 2015 10:29:46 +0000 Subject: [PATCH 5/8] Remove pageview tracking on instantiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Custom variables and dimensions need to be configured before the initial page view is tracked. Rather than specifying a callback function (which suggests an asynchronous behaviour), remove the pageview tracking and let apps call it when they’re ready. * Update the example to indicate that the pageview is needed --- .../analytics/example-init/example-init.js | 7 ++++--- javascripts/govuk/analytics/tracker.js | 13 ++---------- spec/unit/analytics/TrackerSpec.js | 20 ------------------- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/javascripts/govuk/analytics/example-init/example-init.js b/javascripts/govuk/analytics/example-init/example-init.js index 0b427db5..9eaad910 100644 --- a/javascripts/govuk/analytics/example-init/example-init.js +++ b/javascripts/govuk/analytics/example-init/example-init.js @@ -9,12 +9,13 @@ var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; // Configure profiles, setup custom vars, track initial pageview - var analytics = new GOVUK.Tracker({ + // and make interface public for virtual pageviews and events + GOVUK.analytics = new GOVUK.Tracker({ universalId: 'UA-XXXXXXXX-X', classicId: 'UA-XXXXXXXX-X', cookieDomain: cookieDomain }); - // Make interface public for virtual pageviews and events - GOVUK.analytics = analytics; + // Track initial pageview + GOVUK.analytics.trackPageview(); })(); diff --git a/javascripts/govuk/analytics/tracker.js b/javascripts/govuk/analytics/tracker.js index 3797f5cf..3e4c7157 100644 --- a/javascripts/govuk/analytics/tracker.js +++ b/javascripts/govuk/analytics/tracker.js @@ -3,18 +3,8 @@ window.GOVUK = window.GOVUK || {}; var Tracker = function(config) { - this.universal = new GOVUK.GoogleAnalyticsUniversalTracker(config.universalId, config.cookieDomain); this.classic = new GOVUK.GoogleAnalyticsClassicTracker(config.classicId, config.cookieDomain); - - // Dimensions should be set before page view is tracked - - if (config.prePageviewConfiguration && typeof config.prePageviewConfiguration === 'function') { - config.prePageviewConfiguration(); - } - - this.trackPageview(); - }; Tracker.load = function() { @@ -45,7 +35,8 @@ }; /* - Assumes that the index of the dimension is the same for both classic and universal. Check this for your app before using this + Assumes that the index of the dimension is the same for both classic and universal. + Check this for your app before using this */ Tracker.prototype.setDimension = function(index, value, name, scope) { var PAGE_LEVEL_SCOPE = 3; diff --git a/spec/unit/analytics/TrackerSpec.js b/spec/unit/analytics/TrackerSpec.js index 96eb10cb..d714d15d 100644 --- a/spec/unit/analytics/TrackerSpec.js +++ b/spec/unit/analytics/TrackerSpec.js @@ -25,26 +25,6 @@ describe("GOVUK.Tracker", function() { expect(window._gaq[1]).toEqual(['_setDomainName', '.www.gov.uk']); expect(universalSetupArguments[0]).toEqual(['create', 'universal-id', {'cookieDomain': '.www.gov.uk'}]); }); - - it('tracks a pageview in both classic and universal', function () { - expect(window._gaq[4]).toEqual(['_trackPageview']); - expect(universalSetupArguments[2]).toEqual(['send', 'pageview']); - }); - - }); - - - describe('created with a pre-pageview callback', function() { - - it('calls a pre-pageview callback function, if supplied', function () { - var config = { - universalId: 'universal-id', - prePageviewConfiguration: jasmine.createSpy() - }; - tracker = new GOVUK.Tracker(config); - expect(config.prePageviewConfiguration).toHaveBeenCalled(); - }); - }); describe('when tracking pageviews, events and custom dimensions', function() { From c4054dbcd2afe97af215686c534cd29907086b8f Mon Sep 17 00:00:00 2001 From: Paul Hayes Date: Tue, 10 Mar 2015 11:24:33 +0000 Subject: [PATCH 6/8] Add analytics documentation Include details about how to create and use the analytics API * Detail caveats about custom variable and dimension indexes * Include example of when to set custom dimensions in example * Outline when to instantiate the tracker * Detail the public API --- README.md | 15 +- docs/analytics.md | 146 ++++++++++++++++++ .../analytics/example-init/example-init.js | 3 + 3 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 docs/analytics.md diff --git a/README.md b/README.md index ae0c5c60..292aaa58 100644 --- a/README.md +++ b/README.md @@ -120,16 +120,11 @@ In production: * [Primary links](/docs/javascript.md#primary-links) * [Stick at top when scrolling](/docs/javascript.md#stick-at-top-when-scrolling) * [Selection buttons](/docs/javascript.md#selection-buttons) - -## Analytics - -The minimum you need to use the analytics function is: -1. Include the following files from /javascripts/govuk/analytics in your project: - 1. google-analytics-classic-tracker.js - 2. google-analytics-universal-tracker.js - 3. tracker.js -2. Copy the file example-init/example-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with 'UA-') - +* [Analytics](/docs/analytics.md) + * [Create an analytics tracker](/docs/analytics.md#create-an-analytics-tracker) + * [Virtual pageviews](/docs/analytics.md#virtual-pageviews) + * [Custom events](/docs/analytics.md#custom-events) + * [Custom dimensions and custom variables](/docs/analytics.md#custom-dimensions-and-custom-variables) ## Licence diff --git a/docs/analytics.md b/docs/analytics.md new file mode 100644 index 00000000..5d16eaca --- /dev/null +++ b/docs/analytics.md @@ -0,0 +1,146 @@ +# Analytics + +The toolkit provides an abstraction around analytics to make tracking pageviews, events and dimensions across multiple analytics providers easier. Specifically it was created to ease the migration from Google’s Classic Analytics to Universal Analytics. It includes: + +* a Google Analytics classic tracker +* a Google Analytics universal tracker +* code to asynchronously load these libraries +* a generic Analytics tracker that combines these into a single API +* sensible defaults such as anonymising IPs +* data coercion into the format required by Google Analytics (eg a custom event value must be a number, a custom dimension’s value must be a string) + +## Create an analytics tracker + +The minimum you need to use the analytics function is: +1. Include the following files from /javascripts/govuk/analytics in your project: + 1. google-analytics-classic-tracker.js + 2. google-analytics-universal-tracker.js + 3. tracker.js +2. Copy the file example-init/example-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with `UA-`) + +Load and create the analytics tracker at the earliest opportunity so that: +* the time until the first pageview is tracked is kept small and pageviews aren’t missed +* javascript that depends on `GOVUK.analytics` runs after the tracker has been created + +An excerpt from the [`example-init.js`](/javascripts/govuk/analytics/example-init/example-init.js) file: + +```js +// Load Google Analytics libraries +GOVUK.Tracker.load(); + +// Use document.domain in dev, preview and staging so that tracking works +// Otherwise explicitly set the domain as www.gov.uk (and not gov.uk). +var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; + +// Configure profiles, setup custom vars, track initial pageview +GOVUK.analytics = new GOVUK.Tracker({ + universalId: 'UA-XXXXXXXX-X', + classicId: 'UA-XXXXXXXX-X', + cookieDomain: cookieDomain +}); + +// Set custom dimensions before tracking pageviews +// GOVUK.analytics.setDimension(…) + +// Track initial pageview +GOVUK.analytics.trackPageview(); +``` + +Once instantiated, the `GOVUK.analytics` object can be used to track virtual pageviews, custom events and custom dimensions. + +## Virtual pageviews + +> Page tracking allows you to measure the number of views you had of a particular page on your web site. + +* [Classic Analytics](https://developers.google.com/analytics/devguides/collection/gajs/asyncMigrationExamples#VirtualPageviews) +* [Universal Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/pages) + +Argument | Description +---------|------------ +`path` (optional) | Custom path, eg `/path` +`title` (optional) | Custom page title, Universal only + + +```js +// Track current page +GOVUK.analytics.trackPageview(); + +// Track a custom path +GOVUK.analytics.trackPageview('/path'); + +// Track a custom path and custom page title +GOVUK.analytics.trackPageview('/path', 'Title'); +``` + +## Custom events + +> Event tracking allows you to measure how users interact with the content of your website. For example, you might want to measure how many times a button was pressed, or how many times a particular item was used. + +* [Classic Analytics](https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide) +* [Universal Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/events) + +Argument | Description +---------|------------ +`category` (required) | Typically the object that was interacted with, eg "JavaScript error" +`action` (required) | The type of interaction, eg a Javascript error message +`options` (optional) | Optional parameters to further describe event + +Option | Description +-------|------------ +`label` | Useful for categorising events, eg Javascript error source +`value` | Values must be non-negative. Useful to pass counts, eg error happened 5 times +`nonInteraction` | Defaults to false. When set the event will not affect bounce rate + +```js +// Track a custom event with required category and action fields +GOVUK.analytics.trackEvent('category', 'action'); + +// Track a custom event with optional label, value and nonInteraction options +GOVUK.analytics.trackEvent('category', 'action', { + label: 'label', + value: 1, + nonInteraction: true // event will not affect bounce rate +}); +``` + +## Custom dimensions and custom variables + +> Custom dimensions and metrics are a powerful way to send custom data to Google Analytics. Use custom dimensions and metrics to segment and measure differences between: logged in and logged out users, authors of pages, or any other business data you have on a page. + +* [Classic Analytics](https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables) +* [Universal Analytics](https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets) + +Universal custom dimensions are configured within analytics. Classic custom variables rely on the javascript to additionally pass in the name and scope of the variable. + +### Set custom dimensions before tracking pageviews + +Many page level custom dimensions must be set before the initial pageview is tracked. Calls to `setDimension` should typically be made before the initial `trackPageview` is sent to analytics. + +### Custom dimensions and variables must have matching indexes + +When calling `setDimension`, the first argument (`index`) becomes the index of the Universal custom dimension as well as the slot of the Classic custom variable. + +Argument | Description +---------|------------ +`index` (required) | The Universal dimension’s index and and Classic variable’s slot. These must be configured to the same slot and index within analytics profiles. +`value` (required) | Value of the custom dimension/variable +`name` (required) | The name of the custom variable (classic only) +`scope` (optional) | The scope of the custom variable (classic only), defaults to a [page level scope](https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables#pagelevel) (3) if omitted + +```js +// Set a custom dimension at index 1 with value and name +GOVUK.analytics.setDimension(1, 'value', 'name'); + +// Set a custom dimension with an alternative scope +GOVUK.analytics.setDimension(1, 'value', 'name', 2); +``` + +### Create custom dimension helpers + +Because dimensions and variables rely on names and indexes being set correctly, it’s helpful to create methods that abstract away the details. For example: + +```js +function setPixelDensityDimension(pixelDensity) { + GOVUK.analytics.setDimension(1, pixelDensity, 'PixelDensity', 2); +} +``` diff --git a/javascripts/govuk/analytics/example-init/example-init.js b/javascripts/govuk/analytics/example-init/example-init.js index 9eaad910..3613d05c 100644 --- a/javascripts/govuk/analytics/example-init/example-init.js +++ b/javascripts/govuk/analytics/example-init/example-init.js @@ -16,6 +16,9 @@ cookieDomain: cookieDomain }); + // Set custom dimensions before tracking pageviews + // GOVUK.analytics.setDimension(…) + // Track initial pageview GOVUK.analytics.trackPageview(); })(); From bff50a4e86da57cc232bbe45be29b7b0902faa4d Mon Sep 17 00:00:00 2001 From: Paul Hayes Date: Tue, 10 Mar 2015 13:23:03 +0000 Subject: [PATCH 7/8] Avoid example duplication Keep a canonical example that can be copy and pasted within the analytics documentation. * Include link to docs from within tracker.js * Remove old example-init.js --- docs/analytics.md | 62 ++++++++++--------- .../analytics/example-init/example-init.js | 24 ------- javascripts/govuk/analytics/tracker.js | 3 + 3 files changed, 36 insertions(+), 53 deletions(-) delete mode 100644 javascripts/govuk/analytics/example-init/example-init.js diff --git a/docs/analytics.md b/docs/analytics.md index 5d16eaca..c1e88908 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -12,38 +12,42 @@ The toolkit provides an abstraction around analytics to make tracking pageviews, ## Create an analytics tracker The minimum you need to use the analytics function is: -1. Include the following files from /javascripts/govuk/analytics in your project: - 1. google-analytics-classic-tracker.js - 2. google-analytics-universal-tracker.js - 3. tracker.js -2. Copy the file example-init/example-init.js from the same folder to your own project and replace the dummy IDs with your own (they begin with `UA-`) - -Load and create the analytics tracker at the earliest opportunity so that: -* the time until the first pageview is tracked is kept small and pageviews aren’t missed -* javascript that depends on `GOVUK.analytics` runs after the tracker has been created -An excerpt from the [`example-init.js`](/javascripts/govuk/analytics/example-init/example-init.js) file: +1. Include the following files from /javascripts/govuk/analytics in your project: + * google-analytics-classic-tracker.js + * google-analytics-universal-tracker.js + * tracker.js +2. Copy the following `init` script into your own project and replace the dummy IDs with your own (they begin with `UA-`). + * This initialisation can occur immediately as this API has no dependencies. + * Load and create the analytics tracker at the earliest opportunity so that: + * the time until the first pageview is tracked is kept small and pageviews aren’t missed + * javascript that depends on `GOVUK.analytics` runs after the tracker has been created ```js -// Load Google Analytics libraries -GOVUK.Tracker.load(); - -// Use document.domain in dev, preview and staging so that tracking works -// Otherwise explicitly set the domain as www.gov.uk (and not gov.uk). -var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; - -// Configure profiles, setup custom vars, track initial pageview -GOVUK.analytics = new GOVUK.Tracker({ - universalId: 'UA-XXXXXXXX-X', - classicId: 'UA-XXXXXXXX-X', - cookieDomain: cookieDomain -}); - -// Set custom dimensions before tracking pageviews -// GOVUK.analytics.setDimension(…) - -// Track initial pageview -GOVUK.analytics.trackPageview(); +(function() { + "use strict"; + + // Load Google Analytics libraries + GOVUK.Tracker.load(); + + // Use document.domain in dev, preview and staging so that tracking works + // Otherwise explicitly set the domain as www.gov.uk (and not gov.uk). + var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; + + // Configure profiles and make interface public + // for custom dimensions, virtual pageviews and events + GOVUK.analytics = new GOVUK.Tracker({ + universalId: 'UA-XXXXXXXX-X', + classicId: 'UA-XXXXXXXX-X', + cookieDomain: cookieDomain + }); + + // Set custom dimensions before tracking pageviews + // GOVUK.analytics.setDimension(…) + + // Track initial pageview + GOVUK.analytics.trackPageview(); +})(); ``` Once instantiated, the `GOVUK.analytics` object can be used to track virtual pageviews, custom events and custom dimensions. diff --git a/javascripts/govuk/analytics/example-init/example-init.js b/javascripts/govuk/analytics/example-init/example-init.js deleted file mode 100644 index 3613d05c..00000000 --- a/javascripts/govuk/analytics/example-init/example-init.js +++ /dev/null @@ -1,24 +0,0 @@ -(function() { - "use strict"; - - // Load Google Analytics libraries - GOVUK.Tracker.load(); - - // Use document.domain in dev, preview and staging so that tracking works - // Otherwise explicitly set the domain as www.gov.uk (and not gov.uk). - var cookieDomain = (document.domain === 'www.gov.uk') ? '.www.gov.uk' : document.domain; - - // Configure profiles, setup custom vars, track initial pageview - // and make interface public for virtual pageviews and events - GOVUK.analytics = new GOVUK.Tracker({ - universalId: 'UA-XXXXXXXX-X', - classicId: 'UA-XXXXXXXX-X', - cookieDomain: cookieDomain - }); - - // Set custom dimensions before tracking pageviews - // GOVUK.analytics.setDimension(…) - - // Track initial pageview - GOVUK.analytics.trackPageview(); -})(); diff --git a/javascripts/govuk/analytics/tracker.js b/javascripts/govuk/analytics/tracker.js index 3e4c7157..446ad9c8 100644 --- a/javascripts/govuk/analytics/tracker.js +++ b/javascripts/govuk/analytics/tracker.js @@ -2,6 +2,9 @@ "use strict"; window.GOVUK = window.GOVUK || {}; + // For usage and initialisation see: + // https://github.com/alphagov/govuk_frontend_toolkit/blob/master/docs/analytics.md#create-an-analytics-tracker + var Tracker = function(config) { this.universal = new GOVUK.GoogleAnalyticsUniversalTracker(config.universalId, config.cookieDomain); this.classic = new GOVUK.GoogleAnalyticsClassicTracker(config.classicId, config.cookieDomain); From cca27ba3e55ffd0042193a60fbca6c8ca920e7bc Mon Sep 17 00:00:00 2001 From: Paul Hayes Date: Tue, 10 Mar 2015 13:36:04 +0000 Subject: [PATCH 8/8] Mention the print and error tracking options --- README.md | 2 ++ docs/analytics.md | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 292aaa58..ac0d67ed 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ In production: * [Virtual pageviews](/docs/analytics.md#virtual-pageviews) * [Custom events](/docs/analytics.md#custom-events) * [Custom dimensions and custom variables](/docs/analytics.md#custom-dimensions-and-custom-variables) + * [Print tracking](/docs/analytics.md#print-tracking) + * [Error tracking](/docs/analytics.md#error-tracking) ## Licence diff --git a/docs/analytics.md b/docs/analytics.md index c1e88908..09a28828 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -148,3 +148,11 @@ function setPixelDensityDimension(pixelDensity) { GOVUK.analytics.setDimension(1, pixelDensity, 'PixelDensity', 2); } ``` + +## Print tracking + +Pull `print-intent.js` into your project, after analytics has been initialised, to track when users are attempting to print content. + +## Error tracking + +Pull `error-tracking.js` into your project, after analytics has been initialised, to track JavaScript errors.