-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-focus-loss-tracker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
window.GOVUK = window.GOVUK || {} | ||
window.GOVUK.Modules = window.GOVUK.Modules || {}; | ||
|
||
(function (Modules) { | ||
'use strict' | ||
|
||
function Ga4FocusLossTracker (module) { | ||
this.module = module | ||
this.trackingTrigger = 'data-ga4-focus-loss' // elements with this attribute get tracked | ||
} | ||
|
||
Ga4FocusLossTracker.prototype.init = function () { | ||
var consentCookie = window.GOVUK.getConsentCookie() | ||
|
||
if (consentCookie && consentCookie.usage) { | ||
this.startModule() | ||
} else { | ||
this.start = this.startModule.bind(this) | ||
window.addEventListener('cookie-consent', this.start) | ||
} | ||
} | ||
|
||
// triggered by cookie-consent event, which happens when users consent to cookies | ||
Ga4FocusLossTracker.prototype.startModule = function () { | ||
if (window.dataLayer) { | ||
window.removeEventListener('cookie-consent', this.start) | ||
this.module.addEventListener('blur', this.trackFocusLoss.bind(this)) | ||
this.module.piiRemover = new window.GOVUK.analyticsGa4.PIIRemover() | ||
} | ||
} | ||
|
||
Ga4FocusLossTracker.prototype.trackFocusLoss = function (event) { | ||
var data = event.target.getAttribute(this.trackingTrigger) | ||
if (!data) { | ||
return | ||
} | ||
|
||
try { | ||
data = JSON.parse(data) | ||
} catch (e) { | ||
// if there's a problem with the config, don't start the tracker | ||
console.warn('GA4 configuration error: ' + e.message, window.location) | ||
return | ||
} | ||
|
||
var tagName = event.target.tagName | ||
var inputType = event.target.getAttribute('type') | ||
|
||
if (data.text) { | ||
data.text = this.module.piiRemover.stripPIIWithOverride(data.text, true, true) | ||
} else { | ||
if (tagName === 'INPUT' && (inputType === 'search' || inputType === 'text')) { | ||
data.text = window.GOVUK.analyticsGa4.core.trackFunctions.standardiseSearchTerm(this.module.value) | ||
} | ||
} | ||
|
||
window.GOVUK.analyticsGa4.core.applySchemaAndSendData(data, 'event_data') | ||
} | ||
|
||
Modules.Ga4FocusLossTracker = Ga4FocusLossTracker | ||
})(window.GOVUK.Modules) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Google Analytics 4 focus loss tracker | ||
|
||
This script is intended for adding GA4 tracking to any element that needs to be tracked when focus is removed from it. | ||
|
||
## Basic use | ||
|
||
```html | ||
<div | ||
data-module="ga4-focus-loss-tracker" | ||
data-ga4-focus-loss='{ "event_name": "filter", "type": "filter" }'> | ||
</div> | ||
``` | ||
|
||
If the tracker is initialised on an input that has the type `text` or `search`, the tracker will automatically grab the input value and set it as the `text` value in the GA4 JSON. This can be overridden by simply adding your own text value in `data-ga4-focus-loss`. | ||
|
||
This module was created to record what users were searching for in a client side DOM search filter. We did not want to track each keypress the user made as that would spam our analytics data. Therefore by tracking when focus is lost on the search input, we can see what keyword was leading to a user navigating off of the search box and onto the result they wanted. |
233 changes: 233 additions & 0 deletions
233
spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-focus-loss-tracker.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
/* eslint-env jasmine */ | ||
|
||
describe('GA4 focus loss tracker', function () { | ||
var GOVUK = window.GOVUK | ||
var element | ||
var expected | ||
|
||
function agreeToCookies () { | ||
GOVUK.setCookie('cookies_policy', '{"essential":true,"settings":true,"usage":true,"campaigns":true}') | ||
} | ||
|
||
function denyCookies () { | ||
GOVUK.setCookie('cookies_policy', '{"essential":false,"settings":false,"usage":false,"campaigns":false}') | ||
} | ||
|
||
beforeAll(function () { | ||
window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {} | ||
window.GOVUK.analyticsGa4.vars = window.GOVUK.analyticsGa4.vars || {} | ||
window.GOVUK.analyticsGa4.vars.gem_version = 'aVersion' | ||
}) | ||
|
||
beforeEach(function () { | ||
window.dataLayer = [] | ||
element = document.createElement('div') | ||
document.body.appendChild(element) | ||
agreeToCookies() | ||
spyOn(GOVUK.analyticsGa4.core, 'getTimestamp').and.returnValue('123456') | ||
}) | ||
|
||
afterEach(function () { | ||
document.body.removeChild(element) | ||
}) | ||
|
||
afterAll(function () { | ||
GOVUK.setCookie('cookies_policy', null) | ||
window.dataLayer = [] | ||
}) | ||
|
||
describe('when the user has a cookie consent choice', function () { | ||
it('starts the module if consent has already been given', function () { | ||
agreeToCookies() | ||
var tracker = new GOVUK.Modules.Ga4FocusLossTracker(element) | ||
spyOn(tracker, 'startModule').and.callThrough() | ||
tracker.init() | ||
|
||
expect(tracker.startModule).toHaveBeenCalled() | ||
}) | ||
|
||
it('starts the module on the same page as cookie consent is given', function () { | ||
denyCookies() | ||
var tracker = new GOVUK.Modules.Ga4FocusLossTracker(element) | ||
spyOn(tracker, 'startModule').and.callThrough() | ||
tracker.init() | ||
expect(tracker.startModule).not.toHaveBeenCalled() | ||
|
||
// page has not been reloaded, user consents to cookies | ||
window.GOVUK.triggerEvent(window, 'cookie-consent') | ||
expect(tracker.startModule).toHaveBeenCalled() | ||
|
||
// consent listener should be removed after triggering | ||
tracker.startModule.calls.reset() | ||
window.GOVUK.triggerEvent(window, 'cookie-consent') | ||
expect(tracker.startModule).not.toHaveBeenCalled() | ||
}) | ||
|
||
it('does not do anything if consent is not given', function () { | ||
denyCookies() | ||
var tracker = new GOVUK.Modules.Ga4FocusLossTracker(element) | ||
spyOn(tracker, 'startModule') | ||
tracker.init() | ||
|
||
expect(tracker.startModule).not.toHaveBeenCalled() | ||
}) | ||
}) | ||
|
||
describe('configuring tracking without any data', function () { | ||
beforeEach(function () { | ||
element.setAttribute('data-ga4-focus-loss', '') | ||
new GOVUK.Modules.Ga4FocusLossTracker(element).init() | ||
}) | ||
|
||
it('does not cause an error or fire an event', function () { | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
}) | ||
}) | ||
|
||
describe('configuring tracking with incorrect data', function () { | ||
beforeEach(function () { | ||
element.setAttribute('data-ga4-focus-loss', 'invalid json') | ||
new GOVUK.Modules.Ga4FocusLossTracker(element).init() | ||
}) | ||
|
||
it('does not cause an error', function () { | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
}) | ||
}) | ||
|
||
describe('tracking on focus loss', function () { | ||
beforeEach(function () { | ||
expected = new GOVUK.analyticsGa4.Schemas().eventSchema() | ||
expected.event = 'event_data' | ||
expected.event_data.event_name = 'filter' | ||
expected.event_data.type = 'filter' | ||
expected.govuk_gem_version = 'aVersion' | ||
expected.timestamp = '123456' | ||
|
||
var attributes = { | ||
event_name: 'filter', | ||
type: 'filter', | ||
not_a_schema_attribute: 'something' | ||
} | ||
element.setAttribute('data-ga4-focus-loss', JSON.stringify(attributes)) | ||
new GOVUK.Modules.Ga4FocusLossTracker(element).init() | ||
}) | ||
|
||
it('pushes ga4 attributes to the dataLayer when the element is focussed on, and then focus changes', function () { | ||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
expect(window.dataLayer[0].not_a_schema_attribute).toEqual(undefined) | ||
}) | ||
|
||
it('doesnt do anything when focus is moved around within the element', function () { | ||
var child1 = document.createElement('a') | ||
var child2 = document.createElement('a') | ||
element.appendChild(child1) | ||
element.appendChild(child2) | ||
|
||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(child1, 'focus') | ||
window.GOVUK.triggerEvent(child1, 'blur') | ||
window.GOVUK.triggerEvent(child2, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
}) | ||
}) | ||
|
||
describe('automatically grabs text or search input type values', function () { | ||
beforeEach(function () { | ||
window.dataLayer = [] | ||
expected = new GOVUK.analyticsGa4.Schemas().eventSchema() | ||
expected.event = 'event_data' | ||
expected.event_data.event_name = 'filter' | ||
expected.event_data.type = 'filter' | ||
expected.govuk_gem_version = 'aVersion' | ||
expected.timestamp = '123456' | ||
|
||
var attributes = { | ||
event_name: 'filter', | ||
type: 'filter', | ||
not_a_schema_attribute: 'something' | ||
} | ||
document.body.removeChild(element) | ||
element = document.createElement('input') | ||
element.setAttribute('data-ga4-focus-loss', JSON.stringify(attributes)) | ||
document.body.appendChild(element) | ||
|
||
new GOVUK.Modules.Ga4FocusLossTracker(element).init() | ||
}) | ||
|
||
it('pushes the current text input to the dataLayer', function () { | ||
element.setAttribute('type', 'text') | ||
element.value = 'green tea' | ||
expected.event_data.text = 'green tea' | ||
|
||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
}) | ||
|
||
it('pushes the current search input to the dataLayer', function () { | ||
element.setAttribute('type', 'search') | ||
element.value = 'black tea' | ||
expected.event_data.text = 'black tea' | ||
|
||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
}) | ||
|
||
it('should remove extra spaces from the input', function () { | ||
element.setAttribute('type', 'search') | ||
element.value = ' black tea ' | ||
expected.event_data.text = 'black tea' | ||
|
||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
}) | ||
|
||
it('should set the input text to lowercase', function () { | ||
element.setAttribute('type', 'search') | ||
element.value = 'BLACK TEA' | ||
expected.event_data.text = 'black tea' | ||
|
||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
}) | ||
}) | ||
|
||
describe('PII removal', function () { | ||
beforeEach(function () { | ||
expected = new GOVUK.analyticsGa4.Schemas().eventSchema() | ||
expected.event = 'event_data' | ||
expected.event_data.event_name = 'filter' | ||
expected.event_data.type = 'filter' | ||
expected.event_data.text = '/[date]/[postcode]/[email]' | ||
expected.govuk_gem_version = 'aVersion' | ||
expected.timestamp = '123456' | ||
|
||
var attributes = { | ||
event_name: 'filter', | ||
type: 'filter', | ||
text: '/2022-02-02/SW10AA/[email protected]' | ||
} | ||
element.setAttribute('data-ga4-focus-loss', JSON.stringify(attributes)) | ||
new GOVUK.Modules.Ga4FocusLossTracker(element).init() | ||
}) | ||
|
||
it('redacts dates, postcodes and emails from text', function () { | ||
window.GOVUK.triggerEvent(element, 'focus') | ||
expect(window.dataLayer[0]).toEqual(undefined) | ||
window.GOVUK.triggerEvent(element, 'blur') | ||
expect(window.dataLayer[0]).toEqual(expected) | ||
}) | ||
}) | ||
}) |