Skip to content

Commit

Permalink
Merge pull request #3535 from alphagov/ga4-video-tracking
Browse files Browse the repository at this point in the history
Add GA4 video tracking
  • Loading branch information
andysellick authored Aug 11, 2023
2 parents d2efdd3 + f71adf4 commit 3659c8c
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Add section attribute to scroll tracking ([PR #3537](https://github.com/alphagov/govuk_publishing_components/pull/3537))
* Add navigation-page-type GA4 pageview attribute ([PR #3529](https://github.com/alphagov/govuk_publishing_components/pull/3529))
* Update the documentation for loading component stylesheets individually ([PR #3543](https://github.com/alphagov/govuk_publishing_components/pull/3543))
* Add GA4 video tracking ([PR #3535](https://github.com/alphagov/govuk_publishing_components/pull/3535))

## 35.13.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
//= require ./analytics-ga4/ga4-auto-tracker
//= require ./analytics-ga4/ga4-smart-answer-results-tracker
//= require ./analytics-ga4/ga4-scroll-tracker
//= require ./analytics-ga4/ga4-video-tracker
//= require ./analytics-ga4/init-ga4
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
link_domain: this.undefined,
link_path_parts: this.undefined,
tool_name: this.undefined,
percent_scrolled: this.undefined
percent_scrolled: this.undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
window.GOVUK = window.GOVUK || {}
window.GOVUK.analyticsGa4 = window.GOVUK.analyticsGa4 || {}
window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analyticsModules || {};

(function (analyticsModules) {
'use strict'

var VideoTracker = {
init: function () {
this.handlers = {}
},

configureVideo: function (event) {
var player = event.target
var videoId = player.id
var duration = player.getDuration()
var percentages = [25, 50, 75]

for (var i = 0; i < percentages.length; i++) {
var percent = percentages[i]
var position = (duration / 100) * percent
this.handlers['video-' + videoId + '-' + percent + '-percent-begin'] = position
// interval is once a second, so end point must be at least one second beyond begin point
this.handlers['video-' + videoId + '-' + percent + '-percent-end'] = position + 2
}
},

trackVideo: function (event, state) {
var videoTracker = window.GOVUK.analyticsGa4.analyticsModules.VideoTracker
var player = event.target
var videoId = player.id
clearInterval(videoTracker.handlers['video-' + videoId])

if (state === 'VideoUnstarted') {
videoTracker.handlers['video-' + videoId] = setInterval(videoTracker.checkProgress, 1000, player)
videoTracker.sendData(player, 'start', 0) // VideoUnstarted seems to only happen the first time video is played
} else if (state === 'VideoPlaying') {
videoTracker.handlers['video-' + videoId] = setInterval(videoTracker.checkProgress, 1000, player)
} else if (state === 'VideoEnded') {
if (!videoTracker.handlers['video-' + videoId + '-100']) {
videoTracker.sendData(player, 'complete', 100)
videoTracker.handlers['video-' + videoId + '-100'] = true
}
}
},

checkProgress: function (player) {
var videoId = player.id
var videoTracker = window.GOVUK.analyticsGa4.analyticsModules.VideoTracker
var pos = player.getCurrentTime()
var percentages = [25, 50, 75]

// this looks really clunky and long hand
// but we have to do this once a second so doing the minimum before dropping out
// of an if statement is more efficient than combining all these statements into one
for (var i = 0; i < percentages.length; i++) {
if (pos >= videoTracker.handlers['video-' + videoId + '-' + percentages[i] + '-percent-begin']) {
if (pos < videoTracker.handlers['video-' + videoId + '-' + percentages[i] + '-percent-end']) {
if (!videoTracker.handlers['video-' + videoId + '-' + percentages[i]]) {
videoTracker.sendData(player, 'progress', percentages[i])
videoTracker.handlers['video-' + videoId + '-' + percentages[i]] = true
}
return
}
}
}
},

sendData: function (player, event, position) {
var data = {}
data.event_name = 'video_' + event
data.type = 'video'
data.url = player.getVideoUrl()
data.text = player.videoTitle
data.action = event
data.video_current_time = Math.round(player.getCurrentTime())
data.video_duration = Math.ceil(player.getDuration()) // number returned from the API varies, so round up
data.video_percent = position

var schemas = new window.GOVUK.analyticsGa4.Schemas()
var schema = schemas.mergeProperties(data, 'event_data')

window.GOVUK.analyticsGa4.core.sendData(schema)
}
}

analyticsModules.VideoTracker = VideoTracker
})(window.GOVUK.analyticsGa4.analyticsModules)
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@
// https://github.com/alphagov/govuk_publishing_components/pull/908#discussion_r302913995
var videoTitle = options.title
event.target.getIframe().title = videoTitle + ' (video)'
if (window.GOVUK.analyticsGa4.analyticsModules.VideoTracker) {
window.GOVUK.analyticsGa4.analyticsModules.VideoTracker.configureVideo(event)
}
},
onStateChange: function (event) {
var eventData = event.data
Expand All @@ -140,6 +143,10 @@

window.GOVUK.analytics.trackEvent(tracking.category, tracking.action, tracking.label)
}

if (window.GOVUK.analyticsGa4.analyticsModules.VideoTracker) {
window.GOVUK.analyticsGa4.analyticsModules.VideoTracker.trackVideo(event, states[eventData])
}
}
}
})
Expand Down
33 changes: 33 additions & 0 deletions docs/analytics-ga4/ga4-video-tracker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Google Analytics 4 video tracker

This script tracks interactions with videos on GOV.UK. It sends an event to the dataLayer when any of the following occur.

- a video starts playing
- a video reaches the end
- a video gets to 25%, 50%, or 75% of the way through

These events only fire once for each video on any given page. Below is an example of the data sent to the dataLayer for a specific video, on beginning of playback.

```
{
'event': 'event_data',
'event_data': {
'event_name': 'video_start',
'type': 'video',
'url': 'https://www.youtube.com/watch?v=TvHpBXB0q0Y',
'text': 'My first Self Assessment tax return',
'action': 'start',
'video_current_time': 0,
'video_duration': 152,
'video_percent': 0
}
}
```

## How it works

Assuming that consent has been given, the video tracker is launched automatically by the [Youtube link enhancement script](https://github.com/alphagov/govuk_publishing_components/blob/main/app/assets/javascripts/govuk_publishing_components/lib/govspeak/youtube-link-enhancement.js). Note that if cookie consent is not given, videos are not loaded on GOV.UK. Instead the user is shown a link to the video.

The Youtube enhancement script creates an embedded Youtube player based on a link to a video. This includes the `enablejsapi` option, which is needed for tracking. The `onStateChange` event provided by the Youtube API allows code to be executed when a user interacts with a video, for example when playing or pausing.

The function attached to this event listener calls the GA4 video tracker. Since there is no event fired during playback, in order to track progress (e.g. reaching 25%) an interval is created once a second, that checks progress using calls to the Youtube API. When it reaches one of the percentage positions it pushes information to the dataLayer and records that this event should not be fired again.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ describe('Google Analytics schemas', function () {
link_domain: undefined,
link_path_parts: undefined,
tool_name: undefined,
percent_scrolled: undefined
percent_scrolled: undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -73,7 +76,10 @@ describe('Google Analytics schemas', function () {
link_domain: undefined,
link_path_parts: undefined,
tool_name: undefined,
percent_scrolled: undefined
percent_scrolled: undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -106,7 +112,10 @@ describe('Google Analytics schemas', function () {
link_domain: undefined,
link_path_parts: undefined,
tool_name: undefined,
percent_scrolled: undefined
percent_scrolled: undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -138,7 +147,10 @@ describe('Google Analytics schemas', function () {
link_domain: undefined,
link_path_parts: undefined,
tool_name: undefined,
percent_scrolled: undefined
percent_scrolled: undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down Expand Up @@ -176,7 +188,10 @@ describe('Google Analytics schemas', function () {
not: 'defined by the schema'
},
tool_name: undefined,
percent_scrolled: undefined
percent_scrolled: undefined,
video_current_time: this.undefined,
video_duration: this.undefined,
video_percent: this.undefined
}
}
var returned = schemas.mergeProperties(data, 'example')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,12 @@ describe('GA4 scroll tracker', function () {
window.dataLayer = []
window.GOVUK.setCookie('cookies_policy', '{"essential":true,"settings":true,"usage":true,"campaigns":true}')
jasmine.clock().install()
expected = {
event: 'event_data',
event_data: {
action: 'scroll',
event_name: 'scroll',
external: undefined,
index: {
index_link: undefined,
index_section: undefined,
index_section_count: undefined
},
index_total: undefined,
link_domain: undefined,
link_path_parts: undefined,
method: undefined,
percent_scrolled: undefined,
section: undefined,
text: undefined,
tool_name: undefined,
type: undefined,
url: undefined
},
govuk_gem_version: 'gem-version'
}

expected = new GOVUK.analyticsGa4.Schemas().eventSchema()
expected.event = 'event_data'
expected.event_data.action = 'scroll'
expected.event_data.event_name = 'scroll'
expected.govuk_gem_version = 'gem-version'
spyOn(GOVUK.analyticsGa4.core, 'getGemVersion').and.returnValue('gem-version')
})

Expand Down Expand Up @@ -215,7 +197,7 @@ describe('GA4 scroll tracker', function () {
})

it('should send a tracking event on page load for positions that are already visible', function () {
setPageHeight(window.innerHeight)
setPageHeight(10)

expect(window.dataLayer.length).toEqual(5)

Expand Down
Loading

0 comments on commit 3659c8c

Please sign in to comment.