Skip to content

Commit

Permalink
Merge pull request #3032 from alphagov/fix_search_referrer_bug
Browse files Browse the repository at this point in the history
Fix search referrer bug
  • Loading branch information
JamesCGDS authored Oct 27, 2022
2 parents d6b9810 + 72d7400 commit d4be674
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 34 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
## Unreleased

* Fix issue with blue action link arrow svg ([PR #3039](https://github.com/alphagov/govuk_publishing_components/pull/3039))

* **BREAKING:** Fix referrer bug ([PR #3032](https://github.com/alphagov/govuk_publishing_components/pull/3032))

## 31.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
PIIRemover: new GOVUK.analyticsGa4.PIIRemover(),
DEFAULT_LIST_TITLE: 'Site search results',

init: function (isNewPageLoad) {
init: function (referrer) {
if (window.dataLayer) {
/* The referrer parameter is only passed to the init() function as a result of an AJAX request
(in live_search.js in the finder-frontend repository). Otherwise it will not be available and this indicates a fresh page load.
This is needed to trigger a fresh pageViewTracker push to the dataLayer on dynamic page updates and to prevent multiple
click listeners from being applied on this.searchResultsBlocks elements. */
var isNewPageLoad = !referrer

/* The data-ga4-ecommerce attribute may be present on several DOM elements e.g. search results and spelling
suggestions, hence why document.querySelectorAll is required */
this.searchResultsBlocks = document.querySelectorAll('[data-ga4-ecommerce]')
this.isNewPageLoad = isNewPageLoad

if (!this.searchResultsBlocks.length === 0) {
return
Expand All @@ -23,18 +28,18 @@
/* If the results are updated by JS, the URL of the page will change and this needs to be visible to PA's,
hence the pageView object push to the dataLayer. We do not need to send a pageView object on page load as
this is handled elsewhere. */
if (!this.isNewPageLoad) {
if (referrer) {
var pageViewTracker = window.GOVUK.analyticsGa4.analyticsModules.PageViewTracker

if (pageViewTracker) {
pageViewTracker.init()
pageViewTracker.init(referrer)
}
}

for (var i = 0; i < this.searchResultsBlocks.length; i++) {
this.trackSearchResults(this.searchResultsBlocks[i])

if (this.isNewPageLoad) {
if (isNewPageLoad) {
this.searchResultsBlocks[i].addEventListener('click', this.handleClick.bind(this))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
PIIRemover: new window.GOVUK.analyticsGa4.PIIRemover(), // imported in analytics-ga4.js
nullValue: undefined,

init: function () {
init: function (referrer) {
if (window.dataLayer) {
var data = {
event: 'page_view',
page_view: {
location: this.getLocation(),
referrer: this.getReferrer(),
/* If the init() function receives a referrer parameter, this indicates that it has been called as a part of an AJAX request and
this.getReferrer() will not return the correct value. Therefore we need to rely on the referrer parameter. */
referrer: referrer || this.getReferrer(),
title: this.getTitle(),
status_code: this.getStatusCode(),

Expand All @@ -42,7 +44,12 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
political_status: this.getMetaContent('political-status'),
primary_publishing_organisation: this.getMetaContent('primary-publishing-organisation'),
organisations: this.getMetaContent('analytics:organisations'),
world_locations: this.getMetaContent('analytics:world-locations')
world_locations: this.getMetaContent('analytics:world-locations'),

/* The existence of a referrer parameter indicates that the page has been dynamically updated via an AJAX request
and therefore we can use it to set the dynamic property appropriately. This value is used by PA's to differentiate
between fresh page loads and dynamic page updates. */
dynamic: referrer ? 'true' : 'false'
}
}
window.GOVUK.analyticsGa4.core.sendData(data)
Expand Down
22 changes: 16 additions & 6 deletions docs/analytics-ga4/ga4-ecommerce-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The ecommerce tracker script tracks search results in finder applications (e.g. search) and sends them to the `dataLayer`. There are 3 scenarios that result in a push to the `dataLayer`:

- on page load
- the application of filters and/or sorting
- the application of filters and/or sorting (i.e. a dynamic page update)
- clicking a search result

## Basic use
Expand All @@ -17,22 +17,32 @@ In `analytics-ga4.js`:
In `live_search.js` in the `finder-frontend` repository (https://github.com/alphagov/finder-frontend/blob/main/app/assets/javascripts/live_search.js#L129):

```JavaScript
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(isNewPage) // isNewPage is a boolean and is used to detect whether a new page has been loaded or not
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(referrer)
```

The init() function takes a string (`referrer`) as an optional parameter to detect whether dynamic updates have been made to the page via AJAX requests. The presence of the `referrer` parameter is used to determine what is pushed to the dataLayer. If a parameter is not passed, this indicates a fresh page load (e.g. by refreshing the page or using the pagination) and that the page has not been dynamically updated via JS.

Unlike the other GA4 tracking scripts in this repository, ecommerce tracking is not initialised by `init-ga4.js`. Instead, it is initialised by the JS that runs on finder pages (specifically, the `LiveSearch.prototype.Ga4EcommerceTracking()` function). It is called:

On page load - https://github.com/alphagov/finder-frontend/blob/main/app/assets/javascripts/live_search.js#L55
On page load (where no parameters are passed) e.g.

```
this.Ga4EcommerceTracking()
```

And whenever search results are updated - https://github.com/alphagov/finder-frontend/blob/main/app/assets/javascripts/live_search.js#L121
And whenever search results are updated (where a string parameter is passed) e.g.

```
this.Ga4EcommerceTracking(this.previousSearchUrl)
```

Note: ecommerce tracking will only run if there is an element which has the `data-ga4-ecommerce` attribute. If the script can't find an element with this attribute, it will stop running.

## Ecommerce tracking

### On page load

Upon initialisation, the ecommerce tracker script will first detect whether a new page has been loaded (this is determined by the boolean passed when `Ga4EcommerceTracker.init()` is called). If `true`, this indicates that a new page has been loaded (i.e. the user has conducted a new search or has navigated to a new page using the pagination) and the initial set of search results that is loaded is pushed to the `dataLayer`.
Upon initialisation, the ecommerce tracker script will first detect whether a new page has been loaded (this is determined by the presence of the `referrer` parameter that `LiveSearch.prototype.Ga4EcommerceTracking()` passes to `Ga4EcommerceTracker.init()`). If `Ga4EcommerceTracker.init()` does *not* receive a parameter, this indicates that a new page has been loaded (i.e. the user has conducted a new search or has navigated to a new page using the pagination) and the initial set of search results that is loaded is pushed to the `dataLayer`.

For example, if a user searches for "tax", the following object will be pushed to the `dataLayer` on page load:

Expand Down Expand Up @@ -80,7 +90,7 @@ Then if the user applies the "Corporate information" topic filter and sorts by n

`https://www.gov.uk/search/all?keywords=tax&level_one_taxon=a544d48b-1e9e-47fb-b427-7a987c658c14&order=updated-newest`

To track these changes to the URL, a new `pageView` object is sent to the `dataLayer` along with the updated search results so that the user journey can be tracked.
To track these changes to the URL, a new `page_view` object is sent to the `dataLayer` along with the updated search results so that the user journey can be tracked. In addition to this, PA's also need to be able to differentiate between a dynamic page update and a fresh page load; this is why the `referrer` parameter is passed on dynamic page updates and allows the `page_view.referrer` and `page_view.dynamic` to be set appropriately.

Note: currently, filters that have been applied are not tracked; only the `search_results.ecommerce.items` and `search_result.sort` properties are updated.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,51 +91,51 @@ describe('Google Analytics ecommerce tracking', function () {

describe('on page load', function () {
it('should push a nullified ecommerce object to the dataLayer', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[0].search_results.ecommerce).toBe(null)
})

it('should get the search query', function () {
onPageLoadExpected.search_results.term = 'coronavirus'
searchResultsParentEl.setAttribute('data-search-query', 'coronavirus')
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[1].search_results.term).toBe(onPageLoadExpected.search_results.term)
})

it('should remove PII from search query', function () {
onPageLoadExpected.search_results.term = '[email]'
searchResultsParentEl.setAttribute('data-search-query', '[email protected]')
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[1].search_results.term).toBe(onPageLoadExpected.search_results.term)
})

it('should get the variant', function () {
onPageLoadExpected.search_results.sort = 'Relevance'
searchResultsParentEl.setAttribute('data-ecommerce-variant', 'Relevance')
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[1].search_results.sort).toBe(onPageLoadExpected.search_results.sort)
})

it('should set the variant to undefined when the data-ecommerce-variant does not exist', function () {
onPageLoadExpected.search_results.sort = undefined
searchResultsParentEl.removeAttribute('data-ecommerce-variant')
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[1].search_results.sort).toBe(onPageLoadExpected.search_results.sort)
})

it('should get the number of search results i.e. 12345 search results in this test case', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[1].search_results.results).toBe(onPageLoadExpected.search_results.results)
})

it('should get the item id for each search result', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

var searchResults = window.dataLayer[1].search_results.ecommerce.items
for (var i = 0; i < searchResults.length; i++) {
Expand All @@ -144,7 +144,7 @@ describe('Google Analytics ecommerce tracking', function () {
})

it('should get the item list name for each search result', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

var searchResults = window.dataLayer[1].search_results.ecommerce.items
for (var i = 0; i < searchResults.length; i++) {
Expand All @@ -158,7 +158,7 @@ describe('Google Analytics ecommerce tracking', function () {
}
searchResultsParentEl.removeAttribute('data-list-title')

GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

var searchResults = window.dataLayer[1].search_results.ecommerce.items
for (var j = 0; j < searchResults.length; j++) {
Expand All @@ -167,7 +167,7 @@ describe('Google Analytics ecommerce tracking', function () {
})

it('should get the index for each search result using the data-ecommerce-index attribute', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

var searchResults = window.dataLayer[1].search_results.ecommerce.items

Expand All @@ -182,7 +182,7 @@ describe('Google Analytics ecommerce tracking', function () {
ecommerceRows[i].removeAttribute('data-ecommerce-index')
}

GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

var searchResults = window.dataLayer[1].search_results.ecommerce.items
for (var j = 0; j < searchResults.length; j++) {
Expand All @@ -193,7 +193,7 @@ describe('Google Analytics ecommerce tracking', function () {

describe('when a new page isn\'t loaded e.g. when the user selects a filter', function () {
it('should push a nullified ecommerce object to the dataLayer', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(false)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init('test')

expect(window.dataLayer[1].search_results.ecommerce).toBe(null)
})
Expand All @@ -215,7 +215,7 @@ describe('Google Analytics ecommerce tracking', function () {
}
}

GOVUK.analyticsGa4.Ga4EcommerceTracker.init(false)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init('https://gov.uk/')

expect(window.dataLayer[0].event).toBe(pageViewExpected.event)
expect(window.dataLayer[0].page_view.location).toBe(pageViewExpected.page_view.location)
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('Google Analytics ecommerce tracking', function () {
})

it('should push a nullified ecommerce object to the dataLayer', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

expect(window.dataLayer[0].search_results.ecommerce).toBe(null)
})
Expand All @@ -263,29 +263,29 @@ describe('Google Analytics ecommerce tracking', function () {
var tracker = GOVUK.analyticsGa4.Ga4EcommerceTracker

spyOn(tracker, 'handleClick')
tracker.init(true)
tracker.init()

resultToBeClicked.click()
expect(tracker.handleClick).toHaveBeenCalled()
})

it('should push 1 search result to the dataLayer (i.e. the clicked search result)', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

resultToBeClicked.click()
expect(window.dataLayer[3].search_results.ecommerce.items.length).toBe(1)
})

it('should add the event_data property to the object and set it appropriately', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

resultToBeClicked.click()
expect(window.dataLayer[3].event_data).not.toBe(null)
expect(window.dataLayer[3].event_data.external).toBe(onSearchResultClickExpected.event_data.external)
})

it('should set the remaining properties appropriately', function () {
GOVUK.analyticsGa4.Ga4EcommerceTracker.init(true)
GOVUK.analyticsGa4.Ga4EcommerceTracker.init()

resultToBeClicked.click()
expect(window.dataLayer[3].event).toBe(onSearchResultClickExpected.event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ describe('Google Tag Manager page view tracking', function () {
political_status: undefined,
primary_publishing_organisation: undefined,
organisations: undefined,
world_locations: undefined
world_locations: undefined,

dynamic: 'false'
}
}
window.dataLayer = []
Expand Down Expand Up @@ -310,4 +312,21 @@ describe('Google Tag Manager page view tracking', function () {

expect(window.dataLayer[0]).toEqual(expected)
})

describe('correctly sets the referrer and dynamic properties', function () {
it('when not passed a parameter', function () {
GOVUK.analyticsGa4.analyticsModules.PageViewTracker.init()

expect(window.dataLayer[0]).toEqual(expected)
})

it('when passed a parameter for the referrer', function () {
expected.page_view.referrer = 'https://gov.uk/referrer'
expected.page_view.dynamic = 'true'

GOVUK.analyticsGa4.analyticsModules.PageViewTracker.init('https://gov.uk/referrer')

expect(window.dataLayer[0]).toEqual(expected)
})
})
})

0 comments on commit d4be674

Please sign in to comment.