-
-
Save Shoora/43cbd409915b2f4605e3727905b729b2 to your computer and use it in GitHub Desktop.
GA4 Implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import VideoEvents from '@src/metrics/video_events'; | |
import logger from '@src/shared/logger'; | |
/** | |
* Custom Dimensions Map that is required for UA events with gtag | |
* This mapping is not required for GA4 | |
*/ | |
const concertCustomDimensionMap = { | |
'dimension1': 'video_id', | |
'dimension2': 'video_length', | |
'dimension3': 'ad_format', | |
'dimension4': 'test_variant', | |
'dimension11': 'site', | |
'dimension18': 'advertiser', | |
'dimension19': 'campaign', | |
'dimension21': 'line_item_id', | |
'dimension22': 'creative_id', | |
'dimension23': 'concert_request_id', | |
'dimension24': 'concert_creative_control' | |
}; | |
/** | |
* Setting up data layer | |
*/ | |
window.dataLayer = window.dataLayer || []; | |
class GoogleAnalytics { | |
constructor(adConfig, gaId, video = null) { | |
this.adData = adConfig; | |
this.GAConfig = {}; | |
this.video = video; | |
this.videoEvents = new VideoEvents(video); | |
this.log = logger.init('metrics/providers/google_analytics.js'); | |
this.setConfiguration(gaId); | |
this.customDimensionParams = {}; | |
this.updateCustomDimensionParams(); | |
this.loadGA(); | |
if(video) { | |
this.trackVideo(); | |
} | |
} | |
/** | |
* Log event to google analytics | |
* @param {string} category | |
* @param {string} action | |
* @param {Object} options | |
*/ | |
track(category, action, options = {}) { | |
if (!this.video || this.videoEvents.videoReadyToBeTracked()) { | |
this.trackUA(category, action, options); | |
this.trackGA4(category, action, options); | |
} else { | |
this.log(`Queueing video event for ga: ${category}, ${action}, ${JSON.stringify(options)}`); | |
this.videoEvents.queue(category, action, options); | |
} | |
} | |
/** | |
* For UA we are using the ad action as the event name per this example (https://support.google.com/analytics/answer/11091026?hl=en#gtag-dual-tagging&zippy=%2Cin-this-article) | |
* and sending the | |
* event_action, event_category, event_label, and already mapped custom dimensions as parameters | |
* @param {string} category | |
* @param {string} action | |
* @param {object} options | |
*/ | |
trackUA(category, action, options = {}) { | |
gtag('event', `ad: + ${action}`, { | |
send_to: this.GAConfig.googleAnalyticsID, | |
event_action: 'ad:' + action, | |
event_category: category, | |
event_label: this.generateLabel(), | |
value: (typeof options.value === 'number') ? parseInt(options.value) : undefined, | |
non_interaction: !!options.nonInteraction, | |
...this.customDimensionParams, | |
...options | |
}); | |
this.log(`Logging to GA(UA): category:${category}, action:${action}, options:${JSON.stringify(options)}`); | |
} | |
/** | |
* For GA4 we are using the category as the event name and sending the | |
* action, ad_name, and custom dimensions as parameters. | |
* GA4 does not require custom dimensions to be mapped. They will be sent | |
* as parameter names. | |
* @param {string} category | |
* @param {string} action | |
* @param {object} options | |
*/ | |
trackGA4(category, action, options = {}) { | |
gtag('event', category, { | |
send_to: this.GAConfig.ga4ID, | |
action: 'ad:' + action, | |
ad_name: this.generateLabel(), | |
event_value: (typeof options.value === 'number') ? parseInt(options.value) : undefined, // do we want this | |
non_interaction: !!options.nonInteraction, | |
...this.customDimensionParams, | |
...options | |
}); | |
this.log(`Logging to GA4: category:${category}, action:${action}, options:${JSON.stringify(options)}`); | |
} | |
/** | |
* Set up ga config values from ad data | |
* @private | |
* @param {string} gaId | |
*/ | |
setConfiguration(gaId) { | |
this.GAConfig = { | |
adId: this.adData.id, | |
adName: this.adData.adName, | |
variantId: this.adData.variantID, | |
adSlug: this.adData.slug, | |
clientName: this.adData.brand, | |
campaignName: this.adData.campaignName, | |
adFormat: this.adData.designTemplate, | |
googleAnalyticsID: gaId || 'UA-96398693-1', | |
ga4ID: 'G-5MBLG9L0C6', | |
lineItemId: this.adData.dfpConfig.lineItemId, | |
creativeId: this.adData.dfpConfig.creativeId, | |
siteName: this.adData.dfpConfig.network, | |
concertId: this.adData.dfpConfig.concertId, | |
concertControl: this.adData.dfpConfig.concertControl | |
}; | |
if (this.video) { | |
this.GAConfig.videoID = this.video.getVideoId(); | |
} | |
} | |
/** | |
* Combines ad name and id to generate label for event name | |
* @private | |
*/ | |
generateLabel() { | |
const adName = this.GAConfig.adName; | |
const adId = this.GAConfig.adId; | |
return [adName, adId].filter((l) => l.length > 0).join(' | '); | |
} | |
/** | |
* Record standard video events | |
* @private | |
*/ | |
trackVideo() { | |
let currentSecondsElapsedIndex = 0; | |
const secondsElapsed = [3, 6, 10, 15, 20, 25, 30]; | |
const category = 'video'; | |
if (this.video && typeof(this.video.onPlay) === 'function' && !this.videoEvents.videoAlreadyTracked(this.video)) { | |
this.video.onInitialPlay( () => this.track(category, 'start') ); | |
this.video.onPlay( () => this.track(category, 'play') ); | |
this.video.onQuartile( (quartile) => this.track(category, `quartile-${quartile}` ) ); | |
this.video.onPause( () => this.track(category, 'pause') ); | |
this.video.onMute( () => this.track(category, 'mute') ); | |
this.video.onUnmute( () => this.track(category, 'unmute') ); | |
this.video.onMaxAutoPlay( () => this.track(category, 'max-auto-play') ); | |
this.video.onSetSource((videoID) => { | |
this.updateCustomDimensionParams({ video_id: videoID }); | |
// Reset the time on new video | |
currentSecondsElapsedIndex = 0; | |
}); | |
this.video.onTimeUpdate( (time) => { | |
if (time >= secondsElapsed[currentSecondsElapsedIndex]) { | |
const ev = secondsElapsed[currentSecondsElapsedIndex]; | |
this.track(category, `time-${ev}sec`); | |
currentSecondsElapsedIndex++; | |
} | |
}); | |
this.video.onDurationChange(() => { | |
const duration = this.video.getDuration(); | |
if (!isNaN(duration)) { | |
this.updateCustomDimensionParams({ video_length: Math.floor(duration).toString() }); | |
} | |
this.processQueuedEvents(); | |
}); | |
this.video.onComplete( () => this.track(category, 'end') ); | |
} | |
} | |
/** | |
* Process queued video events by sending them to Google Analytics | |
* @private | |
*/ | |
processQueuedEvents() { | |
this.videoEvents.trackingEnabled = true; | |
this.videoEvents.events().forEach(event => { | |
this.track(...event); | |
}); | |
this.videoEvents.clearQueue(); | |
} | |
/** | |
* Loads GA script on page | |
* @private | |
*/ | |
loadGA() { | |
if (!window.hymnalGALoaded) { | |
this.log('Loading GA script'); | |
const script = document.createElement('script'); | |
script.async = true; | |
script.src = `https://www.googletagmanager.com/gtag/js?id=${this.GAConfig.googleAnalyticsID}`; | |
document.head.appendChild(script); | |
gtag('js', new Date()); | |
this.loadUA(); | |
this.loadGA4(); | |
window.hymnalGALoaded = true; | |
} | |
} | |
loadUA() { | |
gtag('config', this.GAConfig.googleAnalyticsID, { | |
send_page_view: false, | |
custom_map: concertCustomDimensionMap, | |
page_location: this.getDocumentLocation() // check that this works | |
}); | |
} | |
loadGA4() { | |
gtag('config', this.GAConfig.ga4ID, { | |
send_page_view: false, | |
page_location: this.getDocumentLocation() // check that this works | |
}); | |
} | |
/** | |
* Fetch the document location, based on availability. | |
* @private | |
*/ | |
getDocumentLocation() { | |
const SAFEFRAME_URL_REGEX = /^https:\/\/tpc\.googlesyndication\.com\/safeframe\//; | |
try { | |
if (SAFEFRAME_URL_REGEX.test(window.location.href)) { | |
return document.referrer; | |
} else { | |
return window.location.href; | |
} | |
} catch (e) { | |
return document.referrer; | |
} | |
} | |
/** | |
* Creates the custom dimension params that get sent with each event | |
* @param {object} updatedParams | |
*/ | |
updateCustomDimensionParams(updatedParams) { | |
const data = { | |
ad_format: this.GAConfig.adFormat, | |
advertiser: this.GAConfig.clientName, | |
campaign: this.GAConfig.campaignName, | |
site: this.GAConfig.siteName, | |
line_item_id: this.GAConfig.lineItemId, | |
creative_id: this.GAConfig.creativeId, | |
concert_request_id: this.GAConfig.concertId, | |
concert_creative_control: this.GAConfig.concertControl, | |
test_variant: this.GAConfig.variantId | |
}; | |
if (this.video) { | |
data.video_id = this.GAConfig.videoID; | |
} | |
for (const key in updatedParams) { | |
data[key] = updatedParams[key]; | |
} | |
this.customDimensionParams = data; | |
} | |
} | |
export default GoogleAnalytics; | |
function gtag() { | |
window.dataLayer.push(arguments); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment