Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GA4 tracking for search autocomplete #4371

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had not realised you were a swiftie 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you'll enjoy the Finder Frontend search tests 😂 I've almost crowded out @sihugh's Harry Potter references!

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
Loading