Skip to content

Commit

Permalink
Merge pull request #3882 from alphagov/update-site-search-event
Browse files Browse the repository at this point in the history
Update the site-search event structure
  • Loading branch information
patrickpatrickpatrick authored Jul 5, 2024
2 parents 278d6fc + 85da490 commit 60807d4
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 69 deletions.
81 changes: 43 additions & 38 deletions __tests__/search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,16 @@ describe('Site search', () => {
expect(GoogleTagManagerDataLayer).toEqual(
expect.arrayContaining([
expect.objectContaining({
ecommerce: {
impressions: []
},
event: 'site_search',
eventDetails: {
event_data: {
action: 'no result',
category: 'site search',
label: 'lorem ipsum'
text: 'lorem ipsum'
}
}),
expect.objectContaining({
event: 'view_item_list',
ecommerce: {
items: []
}
})
])
Expand All @@ -204,29 +206,31 @@ describe('Site search', () => {
)

// Find layer that has the impressions to test.
const impressions = GoogleTagManagerDataLayer.filter(
const items = GoogleTagManagerDataLayer.filter(
(layer) => layer.ecommerce
).map((layer) => layer.ecommerce.impressions)[0]
).map((layer) => layer.ecommerce.items)[0]

expect(impressions.length).toEqual($searchOptions.length)
expect(items.length).toEqual($searchOptions.length)
expect(GoogleTagManagerDataLayer).toEqual(
expect.arrayContaining([
expect.objectContaining({
event: 'site_search',
event_data: {
action: 'results',
text: 'g'
}
}),
expect.objectContaining({
event: 'view_item_list',
ecommerce: {
impressions: expect.arrayContaining([
items: expect.arrayContaining([
expect.objectContaining({
name: expect.any(String),
category: expect.any(String),
list: 'g',
position: expect.any(Number)
})
])
},
event: 'site_search',
eventDetails: {
action: 'results',
category: 'site search',
label: 'g'
}
})
])
Expand Down Expand Up @@ -257,27 +261,28 @@ describe('Site search', () => {
expect(GoogleTagManagerDataLayer).toEqual(
expect.arrayContaining([
expect.objectContaining({
ecommerce: {
click: {
actionField: {
list: 'g'
},
products: expect.arrayContaining([
expect.objectContaining({
name: expect.any(String),
category: expect.any(String),
list: 'g',
position: 2
})
])
}
},
event: 'site_search',
eventDetails: {
event_data: {
action: 'click',
category: 'site search',
label: expect.stringContaining('g |')
text: expect.stringContaining('g'),
section: expect.any(String)
}
}),
expect.objectContaining({
ecommerce: null
}),
expect.objectContaining({
ecommerce: {
items: expect.arrayContaining([
expect.objectContaining({
name: expect.any(String),
category: expect.any(String),
list: 'g',
position: 2
})
])
},
event: 'select_item'
})
])
)
Expand All @@ -298,8 +303,8 @@ describe('Site search', () => {
expect(GoogleTagManagerDataLayer).toEqual(
expect.arrayContaining([
expect.objectContaining({
eventDetails: expect.objectContaining({
label: '[REDACTED EMAIL]'
event_data: expect.objectContaining({
text: '[REDACTED EMAIL]'
})
})
])
Expand All @@ -321,8 +326,8 @@ describe('Site search', () => {
expect(GoogleTagManagerDataLayer).toEqual(
expect.arrayContaining([
expect.objectContaining({
eventDetails: expect.objectContaining({
label: '[REDACTED NUMBER]'
event_data: expect.objectContaining({
text: '[REDACTED NUMBER]'
})
})
])
Expand Down
20 changes: 20 additions & 0 deletions src/javascripts/components/analytics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,23 @@ export function stripPossiblePII(string) {
string = string.replace(/[0-9]+/g, '[REDACTED NUMBER]')
return string
}

/**
* Translate list of search results to
* format compatiable with GA4 ecommerce
* `items` attribute
*
* @param {Array} searchResults - Array of search results
* @param {string} searchTerm - Search string entered by user
* @returns {Array} items - Array of `items`
*/
export function translateToItems(searchResults, searchTerm) {
const items = searchResults.map((result, key) => ({
name: result.title,
category: result.section,
list: searchTerm, // Used to match an searchTerm with results
position: key + 1
}))

return items
}
69 changes: 38 additions & 31 deletions src/javascripts/components/search.tracking.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { addToDataLayer, stripPossiblePII } from './analytics.mjs'
import {
addToDataLayer,
stripPossiblePII,
translateToItems
} from './analytics.mjs'

/**
* Track confirmed autocomplete result
Expand All @@ -13,28 +17,30 @@ export function trackConfirm(searchQuery, searchResults, result) {
}

const searchTerm = stripPossiblePII(searchQuery)
const products = searchResults
.map((result, key) => ({
name: result.title,
category: result.section,
list: searchTerm, // Used to match an searchTerm with results
position: key + 1
}))
// Only return the product that matches what was clicked
.filter((product) => product.name === result.title)

// Only return the product that matches what was clicked
const items = translateToItems(searchResults, searchTerm).filter(
(product) => product.name === result.title
)

addToDataLayer({
event: 'site_search',
eventDetails: {
category: 'site search',
event_data: {
action: 'click',
label: `${searchTerm} | ${result.title}`
},
text: searchTerm,
section: result.title
}
})

// Each time the ecommerce object is pushed to the dataLayer,
// it needs to be nullified first. Nullifying the ecommerce
// object clears it and prevents multiple ecommerce events on a
// page from affecting each other.
addToDataLayer({ ecommerce: null })
addToDataLayer({
event: 'select_item',
ecommerce: {
click: {
actionField: { list: searchTerm },
products
}
items
}
})
}
Expand All @@ -51,25 +57,26 @@ export function trackSearchResults(searchQuery, searchResults) {
}

const searchTerm = stripPossiblePII(searchQuery)

const hasResults = searchResults.length > 0
// Impressions is Google Analytics lingo for what people have seen.
const impressions = searchResults.map((result, key) => ({
name: result.title,
category: result.section,
list: searchTerm, // Used to match an searchTerm with results
position: key + 1
}))
const items = translateToItems(searchResults, searchTerm)

addToDataLayer({
event: 'site_search',
eventDetails: {
category: 'site search',
event_data: {
action: hasResults ? 'results' : 'no result',
label: searchTerm
},
text: searchTerm
}
})

// Each time the ecommerce object is pushed to the dataLayer,
// it needs to be nullified first. Nullifying the ecommerce
// object clears it and prevents multiple ecommerce events on a
// page from affecting each other.
addToDataLayer({ ecommerce: null })
addToDataLayer({
event: 'view_item_list',
ecommerce: {
impressions
items
}
})
}

0 comments on commit 60807d4

Please sign in to comment.