Skip to content

Commit

Permalink
Merge pull request #4371 from alphagov/autocomp-track
Browse files Browse the repository at this point in the history
Add GA4 tracking for search autocomplete
  • Loading branch information
csutter authored Nov 6, 2024
2 parents f47d172 + 03834fe commit 5a92892
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
useful summary for people upgrading their application, not a replication
of the commit log.

## Unreleased
* Add GA4 tracking for search autocomplete ([PR #4371](https://github.com/alphagov/govuk_publishing_components/pull/4371))

## 45.0.0

* **BREAKING** Refactor organisation styles and upgrade to govuk-frontend v5.7.1 ([PR #4321](https://github.com/alphagov/govuk_publishing_components/pull/4321))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
percent_scrolled: this.undefined,
video_current_time: this.undefined,
length: this.undefined,
video_percent: this.undefined
video_percent: this.undefined,
autocomplete_input: this.undefined,
autocomplete_suggestions: this.undefined
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
}

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 = {
Expand All @@ -50,6 +54,19 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
text: this.searchTerm()
}

if (this.$searchInput.dataset.autocompleteTriggerInput) {
// Only set the tool_name if the autocomplete was accepted, but the other autocomplete
// attributes should be included regardless (that way we can differentiate between users
// having seen the autocomplete and not used it, and not having seen it at all)
if (this.$searchInput.dataset.autocompleteAccepted === 'true') {
data.tool_name = 'autocomplete'
}

data.length = Number(this.$searchInput.dataset.autocompleteSuggestionsCount)
data.autocomplete_input = this.$searchInput.dataset.autocompleteTriggerInput
data.autocomplete_suggestions = this.$searchInput.dataset.autocompleteSuggestions
}

window.GOVUK.analyticsGa4.core.applySchemaAndSendData(data, 'event_data')
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,31 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
url.searchParams.set('q', query)
fetch(url, { headers: { Accept: 'application/json' } })
.then(response => response.json())
.then((data) => { populateResults(data[this.sourceKey]) })
.then((data) => {
const results = data[this.sourceKey]

this.setTrackingAttributes(query, results)
populateResults(results)
})
.catch(() => { populateResults([]) })
}

// Set tracking attributes on the input field. These can be used by the containing form's
// analytics module to track the user's interaction with the autocomplete component.
setTrackingAttributes (query, results) {
const formattedResults = results.slice(0, 5).join('|')

this.$autocompleteInput.dataset.autocompleteTriggerInput = query
this.$autocompleteInput.dataset.autocompleteSuggestions = formattedResults
this.$autocompleteInput.dataset.autocompleteSuggestionsCount = results.length
this.$autocompleteInput.dataset.autocompleteAccepted = false
}

// Callback used by accessible-autocomplete to submit the containing form when a suggestion is
// confirmed by the user (e.g. by pressing Enter or clicking on it)
submitContainingForm (value) {
this.$autocompleteInput.dataset.autocompleteAccepted = true

if (this.$form) {
// The accessible-autocomplete component calls this callback _before_ it updates its
// internal state, so the value of the input field is not yet updated when this callback is
Expand Down
2 changes: 2 additions & 0 deletions docs/analytics-ga4/trackers/ga4-search-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ When the form is submitted, a `search` event with the will be tracked containing
- 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
- information about the user's interaction with autocomplete (if present), based on attributes set
by the `search_with_autocomplete` component
65 changes: 65 additions & 0 deletions spec/javascripts/components/search-with-autocomplete-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,69 @@ describe('Search with autocomplete component', () => {
expect(formData.get('q')).toEqual('updated value')
expect(submitSpy).toHaveBeenCalled()
})

describe('analytics data attributes', () => {
it('sets data attributes on the input when suggestions are returned', (done) => {
const input = fixture.querySelector('input')

stubSuccessfulFetch([
'my favourite song is red',
'my favourite song is karma',
'my favourite song is death by a thousand cuts'
])
performInput(input, 'my favourite song is', () => {
expect(input.dataset.autocompleteTriggerInput).toEqual('my favourite song is')
expect(input.dataset.autocompleteSuggestions).toEqual(
'my favourite song is red|' +
'my favourite song is karma|' +
'my favourite song is death by a thousand cuts'
)
expect(input.dataset.autocompleteSuggestionsCount).toEqual('3')
expect(input.dataset.autocompleteAccepted).toEqual('false')
done()
})
})
})

it('limits the number of suggestions included in the data to 5', (done) => {
const input = fixture.querySelector('input')

stubSuccessfulFetch([
'my favourite album is red',
'my favourite album is midnights',
'my favourite album is lover',
'my favourite album is folklore',
'my favourite album is reputation',
'my favourite album is 1989'
])
performInput(input, 'my favourite album is', () => {
expect(input.dataset.autocompleteSuggestions).toEqual(
'my favourite album is red|' +
'my favourite album is midnights|' +
'my favourite album is lover|' +
'my favourite album is folklore|' +
'my favourite album is reputation'
)
expect(input.dataset.autocompleteSuggestionsCount).toEqual('6')
done()
})
})

it('sets autocompleteAccepted when a suggestion is accepted', (done) => {
const form = fixture.querySelector('form')
const input = fixture.querySelector('input')
spyOn(form, 'requestSubmit')

stubSuccessfulFetch([
'my favourite bonus track is message in a bottle',
'my favourite bonus track is is it over now'
])
performInput(input, 'my favourite bonus track is', () => {
const secondOption = fixture.querySelectorAll('.gem-c-search-with-autocomplete__option')[1]
secondOption.click()

expect(input.dataset.autocompleteAccepted).toEqual('true')
done()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ describe('Google Analytics schemas', function () {
percent_scrolled: undefined,
video_current_time: undefined,
length: undefined,
video_percent: undefined
video_percent: undefined,
autocomplete_input: undefined,
autocomplete_suggestions: undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -76,7 +78,9 @@ describe('Google Analytics schemas', function () {
percent_scrolled: undefined,
video_current_time: undefined,
length: undefined,
video_percent: undefined
video_percent: undefined,
autocomplete_input: undefined,
autocomplete_suggestions: undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -111,7 +115,9 @@ describe('Google Analytics schemas', function () {
percent_scrolled: undefined,
video_current_time: undefined,
length: undefined,
video_percent: undefined
video_percent: undefined,
autocomplete_input: undefined,
autocomplete_suggestions: undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -147,7 +153,9 @@ describe('Google Analytics schemas', function () {
percent_scrolled: undefined,
video_current_time: undefined,
length: undefined,
video_percent: undefined
video_percent: undefined,
autocomplete_input: undefined,
autocomplete_suggestions: undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,45 @@ describe('Google Analytics search tracking', () => {

expect(sendSpy).not.toHaveBeenCalled()
})

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'

input.value = 'i want to fish'
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'
},
'event_data'
)
})

it('does not set tool_name 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'

input.value = 'i want to fish'
GOVUK.triggerEvent(form, 'submit')

expect(sendSpy.calls.mostRecent().args[0].tool_name).toBeUndefined()
})
})

describe('when the input is originally empty', () => {
Expand Down

0 comments on commit 5a92892

Please sign in to comment.