From 5d485be2333b51c82f8cf5fb6c2073294f72c434 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Tue, 8 Nov 2022 11:27:17 +0000 Subject: [PATCH] Share some code - reduce some code repetition by moving a few functions in ga4-core --- .../analytics-ga4/ga4-core.js | 49 +++++++++++++++++++ .../analytics-ga4/ga4-event-tracker.js | 12 +---- .../analytics-ga4/ga4-link-tracker.js | 12 +---- .../ga4-specialist-link-tracker.js | 46 ++--------------- .../analytics-ga4/ga4-link-tracker.spec.js | 37 +++++++++++++- 5 files changed, 93 insertions(+), 63 deletions(-) diff --git a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js index d7cce95250..00d57512be 100644 --- a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js +++ b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-core.js @@ -1,3 +1,4 @@ +//= require ../vendor/polyfills/closest.js window.GOVUK = window.GOVUK || {} window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}; @@ -47,6 +48,54 @@ window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}; getGemVersion: function () { return window.GOVUK.analyticsGa4.vars.gem_version || 'not found' + }, + + trackFunctions: { + findTrackingAttributes: function (clicked, trackingTrigger) { + if (clicked.hasAttribute('[' + trackingTrigger + ']')) { + return clicked + } else { + return clicked.closest('[' + trackingTrigger + ']') + } + }, + + // create an object to split up long URLs and get around the 100 character limit on GTM data + // this gets reassembled in GA4 + populateLinkPathParts: function (href) { + var path = '' + if (this.hrefIsRelative(href) || this.isMailToLink(href)) { + path = href + } else { + // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk + path = href.replace(/^(http:||https:)?(\/\/)([^\/]*)/, '') // eslint-disable-line no-useless-escape + } + + if (path === '/' || path.length === 0) { + return + } + + /* + This will create an object with 5 keys that are indexes ("1", "2", etc.) + The values will be each part of the link path split every 100 characters, or undefined. + For example: {"1": "/hello/world/etc...", "2": "/more/path/text...", "3": undefined, "4": undefined, "5": undefined} + Undefined values are needed to override the persistent object in GTM so that any values from old pushes are overwritten. + */ + var parts = path.match(/.{1,100}/g) + var obj = {} + for (var i = 0; i < 5; i++) { + obj[(i + 1).toString()] = parts[i] + } + return obj + }, + + hrefIsRelative: function (href) { + // Checks that a link is relative, but is not a protocol relative url + return href[0] === '/' && href[1] !== '/' + }, + + isMailToLink: function (href) { + return href.substring(0, 7) === 'mailto:' + } } } diff --git a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js index 49f803f2b7..4a32232aa9 100644 --- a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js +++ b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-event-tracker.js @@ -1,4 +1,4 @@ -// = require govuk/vendor/polyfills/Element/prototype/closest.js +//= require ../vendor/polyfills/closest.js window.GOVUK = window.GOVUK || {} window.GOVUK.Modules = window.GOVUK.Modules || {}; @@ -29,7 +29,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; } Ga4EventTracker.prototype.trackClick = function (event) { - var target = this.findTrackingAttributes(event.target) + var target = window.GOVUK.analyticsGa4.core.trackFunctions.findTrackingAttributes(event.target, this.trackingTrigger) if (target) { var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema() @@ -88,14 +88,6 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; } } - Ga4EventTracker.prototype.findTrackingAttributes = function (clicked) { - if (clicked.hasAttribute('[' + this.trackingTrigger + ']')) { - return clicked - } else { - return clicked.closest('[' + this.trackingTrigger + ']') - } - } - // check if an attribute exists or contains the attribute Ga4EventTracker.prototype.getClosestAttribute = function (clicked, attribute) { var isAttributeOnElement = clicked.getAttribute(attribute) diff --git a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js index 40446421ee..44b1078ef7 100644 --- a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js +++ b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.js @@ -42,7 +42,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; } Ga4LinkTracker.prototype.trackClick = function (event) { - var target = this.findTrackingAttributes(event.target) + var target = window.GOVUK.analyticsGa4.core.trackFunctions.findTrackingAttributes(event.target, this.trackingTrigger) if (target) { var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema() @@ -58,6 +58,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; schema.event = 'event_data' data.text = event.target.textContent data.url = this.findLink(event.target).getAttribute('href') + data.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(data.url) // get attributes from the data attribute to send to GA // only allow it if it already exists in the schema for (var property in data) { @@ -78,14 +79,5 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; } } - // FIXME duplicated from the event tracker - move to somewhere shared - Ga4LinkTracker.prototype.findTrackingAttributes = function (clicked) { - if (clicked.hasAttribute('[' + this.trackingTrigger + ']')) { - return clicked - } else { - return clicked.closest('[' + this.trackingTrigger + ']') - } - } - Modules.Ga4LinkTracker = Ga4LinkTracker })(window.GOVUK.Modules) diff --git a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js index e14da762b4..92d14e062f 100644 --- a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js +++ b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-specialist-link-tracker.js @@ -71,7 +71,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics if (clickData.index_total) { clickData.index_total = parseInt(clickData.index_total) } - } else if (this.isMailToLink(href)) { + } else if (window.GOVUK.analyticsGa4.core.trackFunctions.isMailToLink(href)) { clickData.event_name = 'navigation' clickData.type = 'email' clickData.external = 'true' @@ -98,7 +98,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics if (clickData.url) { clickData.url = this.removeCrossDomainParams(clickData.url) clickData.link_domain = this.populateLinkDomain(clickData.url) - clickData.link_path_parts = this.populateLinkPathParts(clickData.url) + clickData.link_path_parts = window.GOVUK.analyticsGa4.core.trackFunctions.populateLinkPathParts(clickData.url) } var schema = new window.GOVUK.analyticsGa4.Schemas().eventSchema() @@ -116,40 +116,13 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics } }, - populateLinkPathParts: function (href) { - var path = '' - if (this.hrefIsRelative(href) || this.isMailToLink(href)) { - path = href - } else { - // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk - path = href.replace(/^(http:||https:)?(\/\/)([^\/]*)/, '') // eslint-disable-line no-useless-escape - } - - if (path === '/' || path.length === 0) { - return - } - - /* - This will create an object with 5 keys that are indexes ("1", "2", etc.) - The values will be each part of the link path split every 100 characters, or undefined. - For example: {"1": "/hello/world/etc...", "2": "/more/path/text...", "3": undefined, "4": undefined, "5": undefined} - Undefined values are needed to override the persistent object in GTM so that any values from old pushes are overwritten. - */ - var parts = path.match(/.{1,100}/g) - var obj = {} - for (var i = 0; i < 5; i++) { - obj[(i + 1).toString()] = parts[i] - } - return obj - }, - populateLinkDomain: function (href) { // We always want mailto links to have an undefined link_domain - if (this.isMailToLink(href)) { + if (window.GOVUK.analyticsGa4.core.trackFunctions.isMailToLink(href)) { return undefined } - if (this.hrefIsRelative(href)) { + if (window.GOVUK.analyticsGa4.core.trackFunctions.hrefIsRelative(href)) { return this.getProtocol() + '//' + this.getHostname() } else { // This regex matches a protocol and domain name at the start of a string such as https://www.gov.uk, http://gov.uk, //gov.uk @@ -203,10 +176,6 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics } }, - isMailToLink: function (href) { - return href.substring(0, 7) === 'mailto:' - }, - isDownloadLink: function (href) { if (this.isInternalLink(href) && this.hrefPointsToDownloadPath(href)) { return true @@ -223,7 +192,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics }, isInternalLink: function (href) { - if (this.hrefIsRelative(href) || this.hrefIsAnchor(href)) { + if (window.GOVUK.analyticsGa4.core.trackFunctions.hrefIsRelative(href) || this.hrefIsAnchor(href)) { return true } var result = false @@ -294,11 +263,6 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics return string.substring(string.length - stringToFind.length, string.length) === stringToFind }, - hrefIsRelative: function (href) { - // Checks that a link is relative, but is not a protocol relative url - return href[0] === '/' && href[1] !== '/' - }, - hrefIsAnchor: function (href) { return href[0] === '#' }, diff --git a/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.spec.js b/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.spec.js index 8d58e61898..e023ec9576 100644 --- a/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.spec.js +++ b/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-link-tracker.spec.js @@ -90,6 +90,13 @@ describe('GA4 click tracker', function () { expected.event_data.event_name = 'navigation' expected.event_data.type = 'a link' expected.govuk_gem_version = 'aVersion' + expected.event_data.link_path_parts = { + 1: undefined, + 2: undefined, + 3: undefined, + 4: undefined, + 5: undefined, + } }) it('does not track anything without a data-ga4 attribute', function () { @@ -111,15 +118,17 @@ describe('GA4 click tracker', function () { element.textContent = linkText expected.event_data.text = linkText expected.event_data.url = link + expected.event_data.link_path_parts["1"] = link initModule(element, true) expect(window.dataLayer[0]).toEqual(expected) }) it('tracks all links with data-ga4 attributes within a container', function () { + var longLink = '#link1-that-is-deliberately-really-long-to-test-the-url-being-split-into-parts-is-this-a-hundred-characters-yet' element = document.createElement('div') element.innerHTML = - 'Link 1' + + 'Link 1' + 'Link 2' + 'Link 3' + '' @@ -129,12 +138,16 @@ describe('GA4 click tracker', function () { initModule(element, false) expected.event_data.text = 'Link 1' - expected.event_data.url = '#link1' + expected.event_data.url = longLink + expected.event_data.link_path_parts["1"] = '#link1-that-is-deliberately-really-long-to-test-the-url-being-split-into-parts-is-this-a-hundred-cha' + expected.event_data.link_path_parts["2"] = 'racters-yet' element.querySelector('.first').click() expect(window.dataLayer[0]).toEqual(expected) expected.event_data.text = 'Link 2' expected.event_data.url = '#link2' + expected.event_data.link_path_parts["1"] = '#link2' + expected.event_data.link_path_parts["2"] = undefined element.querySelector('.second').click() expect(window.dataLayer[1]).toEqual(expected) @@ -157,6 +170,13 @@ describe('GA4 click tracker', function () { expected.event_data.event_name = 'navigation' expected.event_data.type = 'a link' expected.govuk_gem_version = 'aVersion' + expected.event_data.link_path_parts = { + 1: undefined, + 2: undefined, + 3: undefined, + 4: undefined, + 5: undefined, + } }) it('tracks only links within a container', function () { @@ -173,16 +193,19 @@ describe('GA4 click tracker', function () { expected.event_data.text = 'Link 1' expected.event_data.url = '#link1' + expected.event_data.link_path_parts["1"] = '#link1' element.querySelector('.first').click() expect(window.dataLayer[0]).toEqual(expected) expected.event_data.text = 'Link 2' expected.event_data.url = '#link2' + expected.event_data.link_path_parts["1"] = '#link2' element.querySelector('.second').click() expect(window.dataLayer[1]).toEqual(expected) expected.event_data.text = 'Link 3' expected.event_data.url = '#link3' + expected.event_data.link_path_parts["1"] = '#link3' element.querySelector('.third').click() expect(window.dataLayer[2]).toEqual(expected) @@ -202,6 +225,13 @@ describe('GA4 click tracker', function () { expected.event_data.event_name = 'navigation' expected.event_data.type = 'a link' expected.govuk_gem_version = 'aVersion' + expected.event_data.link_path_parts = { + 1: undefined, + 2: undefined, + 3: undefined, + 4: undefined, + 5: undefined, + } }) it('tracks only links within the given class', function () { @@ -221,16 +251,19 @@ describe('GA4 click tracker', function () { expected.event_data.text = 'Link 1' expected.event_data.url = '#link1' + expected.event_data.link_path_parts["1"] = '#link1' element.querySelector('.first').click() expect(window.dataLayer[0]).toEqual(undefined) expected.event_data.text = 'Link 2' expected.event_data.url = '#link2' + expected.event_data.link_path_parts["1"] = '#link2' element.querySelector('.second').click() expect(window.dataLayer[0]).toEqual(expected) expected.event_data.text = 'Link 3' expected.event_data.url = '#link3' + expected.event_data.link_path_parts["1"] = '#link3' element.querySelector('.third').click() expect(window.dataLayer[1]).toEqual(expected)