Skip to content

Commit

Permalink
Adding Google analytics to next/third-parties (#58418)
Browse files Browse the repository at this point in the history
Co-authored-by: Jiachi Liu <[email protected]>
  • Loading branch information
janicklas-ralph and huozhi authored Dec 11, 2023
1 parent c2ab5f7 commit ce92cea
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 0 deletions.
70 changes: 70 additions & 0 deletions packages/third-parties/src/google/ga.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client'
// TODO: Evaluate import 'client only'
import React, { useEffect } from 'react'
import Script from 'next/script'

import type { GAParams } from '../types/google'

declare global {
interface Window {
dataLayer?: Object[]
}
}

let currDataLayerName: string | undefined = undefined

export function GoogleAnalytics(props: GAParams) {
const { gaId, dataLayerName = 'dataLayer' } = props

if (currDataLayerName === undefined) {
currDataLayerName = dataLayerName
}

useEffect(() => {
// performance.mark is being used as a feature use signal. While it is traditionally used for performance
// benchmarking it is low overhead and thus considered safe to use in production and it is a widely available
// existing API.
// The performance measurement will be handled by Chrome Aurora

performance.mark('mark_feature_usage', {
detail: {
feature: 'next-third-parties-ga',
},
})
}, [])

return (
<>
<Script
id="_next-ga-init"
dangerouslySetInnerHTML={{
__html: `
window['${dataLayerName}'] = window['${dataLayerName}'] || [];
function gtag(){window['${dataLayerName}'].push(arguments);}
gtag('js', new Date());
gtag('config', '${gaId}');`,
}}
/>
<Script
id="_next-ga"
src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}
/>
</>
)
}

export const sendGAEvent = (...args: Object[]) => {
if (currDataLayerName === undefined) {
console.warn(`@next/third-parties: GA has not been initialized`)
return
}

if (window[currDataLayerName]) {
window[currDataLayerName].push(...args)
} else {
console.warn(
`@next/third-parties: GA dataLayer ${currDataLayerName} does not exist`
)
}
}
1 change: 1 addition & 0 deletions packages/third-parties/src/google/gtm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function GoogleTagManager(props: GTMParams) {
// performance.mark is being used as a feature use signal. While it is traditionally used for performance
// benchmarking it is low overhead and thus considered safe to use in production and it is a widely available
// existing API.
// The performance measurement will be handled by Chrome Aurora

performance.mark('mark_feature_usage', {
detail: {
Expand Down
1 change: 1 addition & 0 deletions packages/third-parties/src/google/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as GoogleMapsEmbed } from './google-maps-embed'
export { default as YouTubeEmbed } from './youtube-embed'
export { GoogleTagManager, sendGTMEvent } from './gtm'
export { GoogleAnalytics, sendGAEvent } from './ga'
5 changes: 5 additions & 0 deletions packages/third-parties/src/types/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export type GTMParams = {
preview?: string
}

export type GAParams = {
gaId: string
dataLayerName?: string
}

export type GoogleMapsEmbed = {
height?: number
width?: number
Expand Down
23 changes: 23 additions & 0 deletions test/e2e/app-dir/third-parties/app/ga/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

import React from 'react'
import { GoogleAnalytics, sendGAEvent } from '@next/third-parties/google'

const Page = () => {
const onClick = () => {
sendGAEvent({ event: 'buttonClicked', value: 'xyz' })
}

return (
<div class="container">
<GoogleAnalytics gaId="GA-XYZ" />
<h1>GA</h1>
<button id="ga-send" onClick={onClick}>
Click
</button>
<GoogleAnalytics gaId="GA-XYZ" />
</div>
)
}

export default Page
23 changes: 23 additions & 0 deletions test/e2e/app-dir/third-parties/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,28 @@ createNextDescribe(
const dataLayer2 = await browser.eval('window.dataLayer')
expect(dataLayer2.length).toBe(2)
})

it('renders GA', async () => {
const browser = await next.browser('/ga')

await browser.waitForElementByCss('#_next-ga')
await waitFor(1000)

const gaInlineScript = await browser.elementsByCss('#_next-ga-init')
expect(gaInlineScript.length).toBe(1)

const gaScript = await browser.elementsByCss(
'[src^="https://www.googletagmanager.com/gtag/js?id=GA-XYZ"]'
)

expect(gaScript.length).toBe(1)
const dataLayer = await browser.eval('window.dataLayer')
expect(dataLayer.length).toBe(4)

await browser.elementByCss('#ga-send').click()

const dataLayer2 = await browser.eval('window.dataLayer')
expect(dataLayer2.length).toBe(5)
})
}
)
23 changes: 23 additions & 0 deletions test/e2e/third-parties/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,28 @@ createNextDescribe(
const dataLayer2 = await browser.eval('window.dataLayer')
expect(dataLayer2.length).toBe(2)
})

it('renders GA', async () => {
const browser = await next.browser('/ga')

await browser.waitForElementByCss('#_next-ga')
await waitFor(1000)

const gaInlineScript = await browser.elementsByCss('#_next-ga-init')
expect(gaInlineScript.length).toBe(1)

const gaScript = await browser.elementsByCss(
'[src^="https://www.googletagmanager.com/gtag/js?id=GA-XYZ"]'
)

expect(gaScript.length).toBe(1)
const dataLayer = await browser.eval('window.dataLayer')
expect(dataLayer.length).toBe(4)

await browser.elementByCss('#ga-send').click()

const dataLayer2 = await browser.eval('window.dataLayer')
expect(dataLayer2.length).toBe(5)
})
}
)
21 changes: 21 additions & 0 deletions test/e2e/third-parties/pages/ga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { GoogleAnalytics, sendGAEvent } from '@next/third-parties/google'

const Page = () => {
const onClick = () => {
sendGAEvent({ event: 'buttonClicked', value: 'xyz' })
}

return (
<div class="container">
<GoogleAnalytics gaId="GA-XYZ" />
<h1>GA</h1>
<button id="ga-send" onClick={onClick}>
Click
</button>
<GoogleAnalytics gaId="GA-XYZ" />
</div>
)
}

export default Page

0 comments on commit ce92cea

Please sign in to comment.