Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions packages/sui-segment-wrapper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ This package adds an abstraction layer on top of [segment.com](https://segment.c

**Google Analytics 🔍**

- [x] Load GA4 if `googleAnalyticsMeasurementId` is provided.
- [x] Retrieve `clientId` and `sessionId` automatically from GA4 and put in Segment tracks.

**Adobe Marketing Cloud Visitor Id ☁️**
Expand Down Expand Up @@ -149,9 +148,6 @@ Configure both values correctly before running the application to ensure proper

You could put a special config in a the `window.__mpi` to change some behaviour of the wrapper. This config MUST somewhere before using the Segment Wrapper.

- `googleAnalyticsMeasurementId`: _(optional)_ If set, this value will be used for the Google Analytics Measurement API. It will load `gtag` to get the client id.
- `googleAnalyticsConfig`: _(optional)_ If set, this config will be passed when initializing the Google Analytics Measurement API.
- `googleAnalyticsInitEvent`: _(optional)_ If set, an event will be sent in order to initialize all the Google Analytics data.
- `defaultContext`: _(optional)_ If set, properties will be merged and sent with every `track` and `page` in the **context object**. It's the ideal place to put the `site` and `vertical` info to make sure that static info will be sent along with all the tracking.
- `defaultProperties`: _(optional)_ If set, properties will be merged and sent with every `track` and `page`.
- `getCustomAdobeVisitorId`: _(optional)_ If set, the output of this function will be used as `marketingCloudVisitorId` in Adobe Analytics' integration. It must return a promise.
Expand All @@ -168,7 +164,6 @@ Example:
```js
window.__mpi = {
segmentWrapper: {
googleAnalyticsMeasurementId: 'GA-123456789',
universalId: '7ab9ddf3281d5d5458a29e8b3ae2864',
defaultContext: {
site: 'comprocasa',
Expand Down
3 changes: 0 additions & 3 deletions packages/sui-segment-wrapper/src/events.js

This file was deleted.

36 changes: 2 additions & 34 deletions packages/sui-segment-wrapper/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import {defaultContextProperties} from './middlewares/source/defaultContextPrope
import {pageReferrer} from './middlewares/source/pageReferrer.js'
import {userScreenInfo} from './middlewares/source/userScreenInfo.js'
import {userTraits} from './middlewares/source/userTraits.js'
import {
DEFAULT_DATA_LAYER_NAME,
getCampaignDetails,
loadGoogleAnalytics,
sendGoogleConsents
} from './repositories/googleRepository.js'
import {initDataLayer} from './repositories/googleRepository.js'
import {checkAnonymousId} from './utils/checkAnonymousId.js'
import {getConfig, isClient} from './config.js'
import analytics from './segmentWrapper.js'
Expand Down Expand Up @@ -44,33 +39,7 @@ const addMiddlewares = () => {
}

if (isClient && window.analytics) {
// Initialize Google Analtyics if needed
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
const dataLayerName = getConfig('googleAnalyticsDataLayer') || DEFAULT_DATA_LAYER_NAME
const needsConsentManagement = getConfig('googleAnalyticsConsentManagement')

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

window[dataLayerName] = window[dataLayerName] || []
window.gtag =
window.gtag ||
function gtag() {
window[dataLayerName].push(arguments)
}

window.gtag('js', new Date())
if (needsConsentManagement) sendGoogleConsents()
window.gtag('config', googleAnalyticsMeasurementId, {
cookie_prefix: 'segment',
send_page_view: false,
...googleAnalyticsConfig,
...getCampaignDetails()
})
loadGoogleAnalytics().catch(error => {
console.error(error)
})
}
initDataLayer()

window.analytics.ready(checkAnonymousId)
window.analytics.addSourceMiddleware ? addMiddlewares() : window.analytics.ready(addMiddlewares)
Expand All @@ -79,4 +48,3 @@ if (isClient && window.analytics) {
export default analytics
export {getAdobeVisitorData, getAdobeMCVisitorID} from './repositories/adobeRepository.js'
export {getUniversalId} from './universalId.js'
export {EVENTS} from './events.js'
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import {getConfig} from '../../config.js'
import {getCampaignDetails} from '../../repositories/googleRepository.js'

export const campaignContext = ({payload, next}) => {
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
const campaignDetails = getCampaignDetails({needsTransformation: false})

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

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

next(payload)
Expand Down
137 changes: 25 additions & 112 deletions packages/sui-segment-wrapper/src/repositories/googleRepository.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import {dispatchEvent} from '@s-ui/js/lib/events'

import {getConfig} from '../config.js'
import {EVENTS} from '../events.js'
import {utils} from '../middlewares/source/pageReferrer.js'

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

export const DEFAULT_DATA_LAYER_NAME = 'dataLayer'

export const CONSENT_STATES = {
granted: 'granted',
denied: 'denied'
Expand Down Expand Up @@ -45,70 +35,26 @@ const STC_MEDIUM_TRANSFORMATIONS = {
cs: 'cross-sites'
}
const STC_INVALID_CONTENT = 'na'
const DEFAULT_GA_INIT_EVENT = 'sui'

const EMPTY_STC = {medium: null, source: null, campaign: null}

const loadScript = async src =>
new Promise(function (resolve, reject) {
const script = document.createElement('script')

script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})

export const loadGoogleAnalytics = async () => {
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
const dataLayerName = getConfig('googleAnalyticsDataLayer') || DEFAULT_DATA_LAYER_NAME

// Check we have the needed config to load the script
if (!googleAnalyticsMeasurementId) return Promise.resolve(false)
// Create the `gtag` script
const gtagScript = `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsMeasurementId}&l=${dataLayerName}`
// Load it and retrieve the `clientId` from Google
return loadScript(gtagScript)
}

// Trigger GA init event just once per session.
const triggerGoogleAnalyticsInitEvent = sessionId => {
const eventName = getConfig('googleAnalyticsInitEvent') ?? DEFAULT_GA_INIT_EVENT
const eventPrefix = `ga_event_${eventName}_`
const eventKey = `${eventPrefix}${sessionId}`

if (typeof window.gtag === 'undefined') return

// Check if the event has already been sent in this session.
if (!localStorage.getItem(eventKey)) {
// If not, send it.
window.gtag('event', eventName)

// eslint-disable-next-line no-console
console.log(`Sending GA4 event "${eventName}" for the session "${sessionId}"`)

// And then save a new GA session hit in local storage.
localStorage.setItem(eventKey, 'true')
dispatchEvent({eventName: EVENTS.GA4_INIT_EVENT_SENT, detail: {eventName, sessionId}})
}
export const waitForGAData = () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’re changing the strategy to obtain the GA4 IDs. With this pattern, we give the only source of truth to GTM.

return new Promise(resolve => {
if (window.__GA4_DATA) {
console.log('[segment-wrapper] GA4 data already available!')
resolve(window.__GA4_DATA)

// Clean old GA sessions hits from the storage.
Object.keys(localStorage).forEach(key => {
if (key.startsWith(eventPrefix) && key !== eventKey) {
localStorage.removeItem(key)
return
}
})
}

const getGoogleField = async field => {
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
console.log('[segment-wrapper] Waiting for GTM...')

// If `googleAnalyticsMeasurementId` is not present, don't load anything.
if (!googleAnalyticsMeasurementId) return Promise.resolve()

return new Promise(resolve => {
// If it is, get it from `gtag`.
window.gtag?.('get', googleAnalyticsMeasurementId, field, resolve)
/**
* @param {{clientId: any, sessionId: any}} data
*/
window.resolveGAData = data => {
console.log('[segment-wrapper] GTM has delivered the data.')
resolve(data)
}
})
}

Expand All @@ -129,14 +75,12 @@ export const getCampaignDetails = ({needsTransformation = true} = {}) => {
const needsContent = typeof content !== 'undefined' && content !== STC_INVALID_CONTENT

return {
campaign: {
medium: (needsTransformation && STC_MEDIUM_TRANSFORMATIONS[medium]) || medium,
...(typeof name !== 'undefined' && {id}),
name: name ?? id,
source,
...(needsContent && {content}),
...(typeof term !== 'undefined' && {term})
}
campaign_medium: (needsTransformation && STC_MEDIUM_TRANSFORMATIONS[medium]) || medium,
...(typeof name !== 'undefined' && {campaign_id: id}),
campaign_name: name ?? id,
campaign_source: source,
...(needsContent && {campaign_content: content}),
...(typeof term !== 'undefined' && {campaign_term: term})
}
}

Expand Down Expand Up @@ -183,15 +127,6 @@ function readFromUtm(searchParams) {
}
}

export const getGoogleClientId = async () => getGoogleField(FIELDS.clientId)
export const getGoogleSessionId = async () => {
const sessionId = await getGoogleField(FIELDS.sessionId)

triggerGoogleAnalyticsInitEvent(sessionId)

return sessionId
}

// Unified consent state getter.
// Returns GRANTED, DENIED or undefined (default / unknown / unavailable).
export function getGoogleConsentValue(consentType = 'analytics_storage') {
Expand All @@ -212,35 +147,13 @@ export function getGoogleConsentValue(consentType = 'analytics_storage') {
export const getConsentState = () => getGoogleConsentValue() ?? CONSENT_STATES.denied

export const setGoogleUserId = userId => {
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')

if (!googleAnalyticsMeasurementId || !userId) return
if (!userId) return

window.gtag?.('set', 'user_id', userId)
}

/**
* Send consents to Google Consent Mode.
*
* @param {'default' | 'update'} mode Mode for the consent update
* @param {object} consents Consents object to be sent to Google Consent Mode.
* Defaults used when not provided:
* {
* analytics_storage: 'denied',
* ad_user_data: 'denied',
* ad_personalization: 'denied',
* ad_storage: 'denied'
* }
*/
export const sendGoogleConsents = (mode = 'default', consents) => {
window.gtag?.(
'consent',
mode,
consents || {
analytics_storage: CONSENT_STATES.denied,
ad_user_data: CONSENT_STATES.denied,
ad_personalization: CONSENT_STATES.denied,
ad_storage: CONSENT_STATES.denied
}
)
export function initDataLayer() {
window.dataLayer = window.dataLayer || []

window.dataLayer.push({...getCampaignDetails()})
}
18 changes: 4 additions & 14 deletions packages/sui-segment-wrapper/src/segmentWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import {
CONSENT_STATES,
getConsentState,
getGoogleConsentValue,
getGoogleClientId,
getGoogleSessionId,
setGoogleUserId,
sendGoogleConsents
waitForGAData
} from './repositories/googleRepository.js'
import {getXandrId} from './repositories/xandrRepository.js'
import {getConfig} from './config.js'
import {USER_GDPR, CMP_TRACK_EVENT, checkAnalyticsGdprIsAccepted, getGdprPrivacyValue} from './tcf.js'
import {USER_GDPR, checkAnalyticsGdprIsAccepted, getGdprPrivacyValue} from './tcf.js'

/* Default properties to be sent on all trackings */
const DEFAULT_PROPERTIES = {platform: 'web'}
Expand Down Expand Up @@ -57,15 +55,13 @@ export const getDefaultProperties = () => ({
const getTrackIntegrations = async ({gdprPrivacyValue, event}) => {
const isGdprAccepted = checkAnalyticsGdprIsAccepted(gdprPrivacyValue)
let marketingCloudVisitorId
let sessionId
let clientId
let sessionId, clientId

try {
if (isGdprAccepted) {
marketingCloudVisitorId = await getAdobeMCVisitorID()
}
sessionId = await getGoogleSessionId()
clientId = await getGoogleClientId()
;({sessionId, clientId} = await waitForGAData())
} catch (error) {
console.error(error)
}
Expand Down Expand Up @@ -223,12 +219,6 @@ const track = (event, properties, context = {}, callback) =>
}
}

const needsConsentManagement = getConfig('googleAnalyticsConsentManagement')

if (needsConsentManagement && event === CMP_TRACK_EVENT) {
sendGoogleConsents('update', newContext.google_consents)
}

window.analytics.track(
event,
newProperties,
Expand Down
2 changes: 0 additions & 2 deletions packages/sui-segment-wrapper/test/assertions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {expect} from 'chai'
import sinon from 'sinon'

import {setConfig} from '../src/config.js'
import suiAnalytics from '../src/index.js'
import {getCampaignDetails} from '../src/repositories/googleRepository.js'
import {stubActualQueryString} from './stubs.js'
Expand All @@ -12,7 +11,6 @@ export const assertCampaignDetails = async ({queryString, expectation}) => {
try {
const spy = sinon.stub()

setConfig('googleAnalyticsMeasurementId', 123)
await simulateUserAcceptConsents()
await suiAnalytics.track(
'fakeEvent',
Expand Down
Loading
Loading