Skip to content

Commit

Permalink
Merge pull request #1848 from SUI-Components/ga4-campaign-data
Browse files Browse the repository at this point in the history
feat(segment-wrapper): map GA4 campaign data
  • Loading branch information
kikoruiz authored Oct 22, 2024
2 parents 7651fec + 32ac5f2 commit 0723026
Show file tree
Hide file tree
Showing 6 changed files with 2,884 additions and 437 deletions.
3,180 changes: 2,746 additions & 434 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions packages/sui-segment-wrapper/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import './utils/patchAnalytics.js'

import {campaignContext} from './middlewares/source/campaignContext.js'
import {defaultContextProperties} from './middlewares/source/defaultContextProperties.js'
import {pageReferrer} from './middlewares/source/pageReferrer.js'
import {userScreenInfo} from './middlewares/source/userScreenInfo.js'
Expand All @@ -9,7 +10,7 @@ import {getConfig, isClient} from './config.js'
import analytics from './segmentWrapper.js'
import initTcfTracking from './tcf.js'
import {getUserDataAndNotify} from './universalId.js'
import {loadGoogleAnalytics} from './repositories/googleRepository.js'
import {loadGoogleAnalytics, getCampaignDetails} from './repositories/googleRepository.js'

const DEFAULT_GA_INIT_EVENT = 'sui'

Expand All @@ -27,6 +28,7 @@ try {
const addMiddlewares = () => {
window.analytics.addSourceMiddleware(userTraits)
window.analytics.addSourceMiddleware(defaultContextProperties)
window.analytics.addSourceMiddleware(campaignContext)
window.analytics.addSourceMiddleware(userScreenInfo)
window.analytics.addSourceMiddleware(pageReferrer)
}
Expand All @@ -37,6 +39,8 @@ if (isClient && window.analytics) {
const googleAnalyticsInitEvent = getConfig('googleAnalyticsInitEvent') ?? DEFAULT_GA_INIT_EVENT

if (googleAnalyticsMeasurementId) {
const googleAnalyticsConfig = getConfig('googleAnalyticsConfig')

window.dataLayer = window.dataLayer || []
window.gtag =
window.gtag ||
Expand All @@ -46,11 +50,16 @@ if (isClient && window.analytics) {

window.gtag('js', new Date())
window.gtag('config', googleAnalyticsMeasurementId, {
send_page_view: false
cookie_prefix: 'segment',
send_page_view: false,
...googleAnalyticsConfig,
...getCampaignDetails()
})
window.gtag('event', googleAnalyticsInitEvent)

loadGoogleAnalytics()
loadGoogleAnalytics().catch(error => {
console.log(error)
})
}

window.analytics.ready(checkAnonymousId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {getConfig} from '../../config.js'
import {getCampaignDetails} from '../../repositories/googleRepository.js'

export const campaignContext = ({payload, next}) => {
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')

if (googleAnalyticsMeasurementId) {
const campaignDetails = getCampaignDetails({needsTransformation: false})

payload.obj.context = {
...payload.obj.context,
...campaignDetails
}
}

next(payload)
}
42 changes: 42 additions & 0 deletions packages/sui-segment-wrapper/src/repositories/googleRepository.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import {utils} from '../middlewares/source/pageReferrer.js'
import {getConfig} from '../config.js'

const FIELDS = {
clientId: 'client_id',
sessionId: 'session_id'
}

const STC = {
QUERY: 'stc',
SPLIT_SYMBOL: '-',
NAME_SPLIT_SYMBOL: ':'
}

const STC_MEDIUM_TRANSFORMATIONS = {
aff: 'affiliate',
dis: 'display',
em: 'email',
met: 'paid-metasearch',
sem: 'paid-search',
rt: 'display',
sm: 'social-media',
sp: 'paid-social',
pn: 'push-notification',
cs: 'cross-sites'
}

const cachedData = {
[FIELDS.clientId]: null,
[FIELDS.sessionId]: null
Expand Down Expand Up @@ -50,5 +70,27 @@ const getGoogleField = async field => {
})
}

export const getCampaignDetails = ({needsTransformation = true} = {}) => {
const search = utils.getActualQueryString()
const searchParams = new URLSearchParams(search)

if (!searchParams.has(STC.QUERY)) return null

const stc = searchParams.get(STC.QUERY)
const [medium, source, originalName, term, content] = stc.split(STC.SPLIT_SYMBOL)
const [id, name] = originalName.split(STC.NAME_SPLIT_SYMBOL)

return {
campaign: {
medium: (needsTransformation && STC_MEDIUM_TRANSFORMATIONS[medium]) || medium,
...(typeof name !== 'undefined' && {id}),
name: name ?? id,
source,
term,
...(typeof content !== 'undefined' && {content})
}
}
}

export const getGoogleClientID = () => getGoogleField(FIELDS.clientId)
export const getGoogleSessionID = () => getGoogleField(FIELDS.sessionId)
36 changes: 36 additions & 0 deletions packages/sui-segment-wrapper/test/assertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {expect} from 'chai'
import sinon from 'sinon'

import {setConfig} from '../src/config.js'
import {getCampaignDetails} from '../src/repositories/googleRepository.js'
import suiAnalytics from '../src/index.js'
import {stubActualQueryString} from './stubs.js'
import {simulateUserAcceptConsents} from './tcf.js'

export const assertCampaignDetails = async ({queryString, expectation}) => {
const queryStringStub = stubActualQueryString(queryString)
const spy = sinon.stub()

setConfig('googleAnalyticsMeasurementId', 123)
await simulateUserAcceptConsents()
await suiAnalytics.track(
'fakeEvent',
{},
{
integrations: {fakeIntegrationKey: 'fakeIntegrationValue'}
},
spy
)

const {context} = spy.firstCall.firstArg.obj
const {campaign: campaignContext} = context
const campaignDetails = getCampaignDetails()
const campaignDetailsWithOriginalMedium = getCampaignDetails({
needsTransformation: false
})

expect(campaignDetails).to.deep.equal(expectation)
expect(campaignContext).to.deep.equal(campaignDetailsWithOriginalMedium.campaign)

queryStringStub.restore()
}
31 changes: 31 additions & 0 deletions packages/sui-segment-wrapper/test/segmentWrapperSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {getAdobeVisitorData} from '../src/repositories/adobeRepository.js'
import {setConfig} from '../src/config.js'
import suiAnalytics from '../src/index.js'
import {defaultContextProperties} from '../src/middlewares/source/defaultContextProperties.js'
import {campaignContext} from '../src/middlewares/source/campaignContext.js'
import {pageReferrer} from '../src/middlewares/source/pageReferrer.js'
import {userScreenInfo} from '../src/middlewares/source/userScreenInfo.js'
import {userTraits} from '../src/middlewares/source/userTraits.js'
import {INTEGRATIONS_WHEN_NO_CONSENTS} from '../src/segmentWrapper.js'
import initTcfTracking, {getGdprPrivacyValue, USER_GDPR} from '../src/tcf.js'
import {assertCampaignDetails} from './assertions.js'
import {
cleanWindowStubs,
resetReferrerState,
Expand Down Expand Up @@ -46,6 +48,7 @@ describe('Segment Wrapper', function () {

window.analytics.addSourceMiddleware(userTraits)
window.analytics.addSourceMiddleware(defaultContextProperties)
window.analytics.addSourceMiddleware(campaignContext)
window.analytics.addSourceMiddleware(userScreenInfo)
window.analytics.addSourceMiddleware(pageReferrer)
})
Expand Down Expand Up @@ -262,6 +265,34 @@ describe('Segment Wrapper', function () {
}
})
})

it('should send mapped campaign details', async () => {
await assertCampaignDetails({
queryString: '?stc=em-mail-winter%20promo-honda',
expectation: {
campaign: {
medium: 'email',
name: 'winter promo',
source: 'mail',
term: 'honda'
}
}
})

await assertCampaignDetails({
queryString: '?stc=sm-google-1234%3Aspring%20sale-aprilia-logolink',
expectation: {
campaign: {
medium: 'social-media',
id: '1234',
name: 'spring sale',
source: 'google',
term: 'aprilia',
content: 'logolink'
}
}
})
})
})

it('should add always the platform as web and the language', async () => {
Expand Down

0 comments on commit 0723026

Please sign in to comment.