diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b666a4f94..235990d29b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@
* Use component wrapper on contextual footer ([PR #4562](https://github.com/alphagov/govuk_publishing_components/pull/4562))
* Update Govspeak "Warning Text" component styles ([PR #4487](https://github.com/alphagov/govuk_publishing_components/pull/4487))
* Make "Add another" component styles more specific ([PR #4579](https://github.com/alphagov/govuk_publishing_components/pull/4579))
+* Improve GA4 search tracking ([PR #4582](https://github.com/alphagov/govuk_publishing_components/pull/4582))
## 49.1.0
diff --git a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js
index 034965d27a..72014ec599 100644
--- a/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js
+++ b/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js
@@ -7,13 +7,16 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
class Ga4SearchTracker {
constructor ($module) {
this.$module = $module
- this.$searchInput = this.$module.querySelector('input[type="search"]')
+ this.$searchInput = $module.querySelector('input[type="search"]')
this.type = this.$module.dataset.ga4SearchType
this.url = this.$module.dataset.ga4SearchUrl
this.section = this.$module.dataset.ga4SearchSection
this.indexSection = this.$module.dataset.ga4SearchIndexSection
this.indexSectionCount = this.$module.dataset.ga4SearchIndexSectionCount
+
+ // At the beginning, the user has not yet interacted with any form fields
+ this.triggeredAction = 'unchanged'
}
init () {
@@ -22,7 +25,11 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
return
}
- this.initialKeywords = this.$searchInput.value
+ // These event handlers do not send any tracking data, they only set internal state. They are
+ // added here so if the user hasn't given consent yet but does so later, we do not end up with
+ // inconsistent module state.
+ this.$module.addEventListener('change', event => this.setTriggeredAction(event))
+ this.$module.addEventListener('input', event => this.setTriggeredAction(event))
if (window.GOVUK.getConsentCookie() && window.GOVUK.getConsentCookie().usage) {
this.startModule()
@@ -35,16 +42,24 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
this.$module.addEventListener('submit', event => this.trackSearch(event))
}
+ setTriggeredAction (event) {
+ if (event.target.type === 'search') {
+ this.triggeredAction = 'search'
+ } else if (this.triggeredAction !== 'search') {
+ // The 'search' action always takes precedence over the 'filter' action, so only set the
+ // action to 'filter' if it is not already 'search'.
+ this.triggeredAction = 'filter'
+ }
+ }
+
trackSearch () {
// The original search input may have been removed from the DOM by the autocomplete component
// if it is used, so make sure we are tracking the correct input
this.$searchInput = this.$module.querySelector('input[type="search"]')
- if (this.skipTracking()) return
-
const data = {
event_name: 'search',
- action: 'search',
+ action: this.triggeredAction,
type: this.type,
section: this.section,
@@ -80,12 +95,6 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
window.GOVUK.analyticsGa4.core.applySchemaAndSendData(data, 'event_data')
}
- skipTracking () {
- // Skip tracking for those events that we do not want to track: where the search term is
- // present, but has not changed from its initial value
- return this.searchTerm() !== '' && this.searchTerm() === this.initialKeywords
- }
-
searchTerm () {
const { standardiseSearchTerm } = window.GOVUK.analyticsGa4.core.trackFunctions
diff --git a/docs/analytics-ga4/trackers/ga4-search-tracker.md b/docs/analytics-ga4/trackers/ga4-search-tracker.md
index 59e9305b19..bc5b7f3b22 100644
--- a/docs/analytics-ga4/trackers/ga4-search-tracker.md
+++ b/docs/analytics-ga4/trackers/ga4-search-tracker.md
@@ -25,7 +25,12 @@ fields:
>
```
-When the form is submitted, a `search` event with the will be tracked containing:
+When the form is submitted, a `search` event will be tracked containing:
+- an action according to the user's interaction with the form:
+ - `search` if they have interacted with the search term field
+ - `filter` if they have interacted with any other field in the form (but not the search term
+ field)
+ - `unchanged` otherwise, for example if they have just (re-)submitted the form
- the type, URL, section, index section, and index section count fields based on the data attributes
outlined above
- the state (text) of the search field contained within
diff --git a/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.spec.js b/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.spec.js
index 50182c5998..a185d8e075 100644
--- a/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.spec.js
+++ b/spec/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.spec.js
@@ -3,7 +3,7 @@
describe('Google Analytics search tracking', () => {
'use strict'
- let fixture, form, input, sendSpy, setItemSpy, ga4SearchTracker
+ let fixture, form, searchInput, filterInput, sendSpy, setItemSpy, ga4SearchTracker
const GOVUK = window.GOVUK
const html = `
@@ -16,10 +16,19 @@ describe('Google Analytics search tracking', () => {
data-ga4-search-index-section-count="89"
>
+
`
+ const commonEventProps = {
+ type: 'site search',
+ section: 'section',
+ url: '/search',
+ index_section: '19',
+ index_section_count: '89'
+ }
+
beforeAll(() => {
GOVUK.analyticsGa4 = GOVUK.analyticsGa4 || {}
GOVUK.analyticsGa4.vars = GOVUK.analyticsGa4.vars || {}
@@ -36,7 +45,8 @@ describe('Google Analytics search tracking', () => {
fixture.innerHTML = html
form = fixture.querySelector('form')
- input = form.querySelector('input')
+ searchInput = form.querySelector('input[name="keyword"]')
+ filterInput = form.querySelector('input[name="foo"]')
sendSpy = spyOn(GOVUK.analyticsGa4.core, 'applySchemaAndSendData')
setItemSpy = spyOn(window.sessionStorage, 'setItem')
@@ -52,7 +62,7 @@ describe('Google Analytics search tracking', () => {
beforeEach(() => {
GOVUK.setConsentCookie({ usage: true })
- form.removeChild(input)
+ form.removeChild(searchInput)
ga4SearchTracker = new GOVUK.Modules.Ga4SearchTracker(form)
ga4SearchTracker.init()
})
@@ -83,103 +93,96 @@ describe('Google Analytics search tracking', () => {
ga4SearchTracker.init()
})
- it('tracks search events when the input changes', () => {
- input.value = 'new value'
+ it('tracks search events as `search` action when the keyword input changes', () => {
+ searchInput.value = 'new value'
+ GOVUK.triggerEvent(searchInput, 'input', { target: searchInput })
GOVUK.triggerEvent(form, 'submit')
expect(sendSpy).toHaveBeenCalledWith(
{
event_name: 'search',
action: 'search',
- type: 'site search',
- section: 'section',
- url: '/search',
- index_section: '19',
- index_section_count: '89',
- text: 'new value'
+ text: 'new value',
+ ...commonEventProps
},
'event_data'
)
})
- it('does not track search events when the input does not change', () => {
+ it('tracks search events as `filter` action when non-keyword input changes', () => {
+ filterInput.value = 'new value'
+ GOVUK.triggerEvent(filterInput, 'input', { target: filterInput })
GOVUK.triggerEvent(form, 'submit')
- expect(sendSpy).not.toHaveBeenCalled()
+ expect(sendSpy).toHaveBeenCalledWith(
+ {
+ event_name: 'search',
+ action: 'filter',
+ text: 'initial value',
+ ...commonEventProps
+ },
+ 'event_data'
+ )
+ })
+
+ it('tracks search events as `unchanged` action when nothing changes', () => {
+ GOVUK.triggerEvent(form, 'submit')
+
+ expect(sendSpy).toHaveBeenCalledWith(
+ {
+ event_name: 'search',
+ action: 'unchanged',
+ text: 'initial value',
+ ...commonEventProps
+ },
+ 'event_data'
+ )
})
it('includes autocomplete information if present', () => {
- input.dataset.autocompleteTriggerInput = 'i want to'
- input.dataset.autocompleteSuggestions = 'i want to fish|i want to dance|i want to sleep'
- input.dataset.autocompleteSuggestionsCount = '3'
- input.dataset.autocompleteAccepted = 'true'
+ searchInput.dataset.autocompleteTriggerInput = 'i want to'
+ searchInput.dataset.autocompleteSuggestions = 'i want to fish|i want to dance|i want to sleep'
+ searchInput.dataset.autocompleteSuggestionsCount = '3'
+ searchInput.dataset.autocompleteAccepted = 'true'
- input.value = 'i want to fish'
+ searchInput.value = 'i want to fish'
+ GOVUK.triggerEvent(searchInput, 'input', { target: searchInput })
GOVUK.triggerEvent(form, 'submit')
expect(sendSpy).toHaveBeenCalledWith(
{
event_name: 'search',
action: 'search',
- type: 'site search',
- section: 'section',
- url: '/search',
- index_section: '19',
- index_section_count: '89',
text: 'i want to fish',
tool_name: 'autocomplete',
length: 3,
autocomplete_input: 'i want to',
- autocomplete_suggestions: 'i want to fish|i want to dance|i want to sleep'
+ autocomplete_suggestions: 'i want to fish|i want to dance|i want to sleep',
+ ...commonEventProps
},
'event_data'
)
})
it('persists usage of autocomplete so that the next page knows it was used', () => {
- input.dataset.autocompleteTriggerInput = 'i want to remember'
- input.dataset.autocompleteAccepted = 'true'
- input.value = 'i want to remember'
+ searchInput.dataset.autocompleteTriggerInput = 'i want to remember'
+ searchInput.dataset.autocompleteAccepted = 'true'
+ searchInput.value = 'i want to remember'
GOVUK.triggerEvent(form, 'submit')
expect(setItemSpy).toHaveBeenCalledWith('searchAutocompleteAccepted', 'true')
})
it('sets tool_name to null if the user has not accepted a suggestion', () => {
- input.dataset.autocompleteTriggerInput = 'i want to'
- input.dataset.autocompleteSuggestions = 'i want to fish|i want to dance|i want to sleep'
- input.dataset.autocompleteSuggestionsCount = '3'
+ searchInput.dataset.autocompleteTriggerInput = 'i want to'
+ searchInput.dataset.autocompleteSuggestions = 'i want to fish|i want to dance|i want to sleep'
+ searchInput.dataset.autocompleteSuggestionsCount = '3'
- input.value = 'i want to fish'
+ searchInput.value = 'i want to fish'
+ GOVUK.triggerEvent(searchInput, 'input', { target: searchInput })
GOVUK.triggerEvent(form, 'submit')
expect(sendSpy.calls.mostRecent().args[0].tool_name).toBeNull()
})
})
-
- describe('when the input is originally empty', () => {
- beforeEach(() => {
- GOVUK.setConsentCookie({ usage: true })
- input.value = ''
- ga4SearchTracker.init()
- })
-
- it('tracks search events even if the (empty) input is unchanged', () => {
- GOVUK.triggerEvent(form, 'submit')
-
- expect(sendSpy).toHaveBeenCalledWith(
- {
- event_name: 'search',
- action: 'search',
- type: 'site search',
- section: 'section',
- url: '/search',
- index_section: '19',
- index_section_count: '89',
- text: ''
- },
- 'event_data'
- )
- })
- })
})