Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support to consent management protocol v2 to main #949

Merged
merged 1 commit into from
Jan 9, 2024
Merged
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
4 changes: 4 additions & 0 deletions packages/react/src/__tests__/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ Object {
"GA": [Function],
"GA4": [Function],
"GTM": [Function],
"GoogleConsentType": Object {
"Denied": "denied",
"Granted": "granted",
},
"Integration": [Function],
"Omnitracking": [Function],
"Riskified": [Function],
Expand Down
42 changes: 36 additions & 6 deletions packages/react/src/analytics/integrations/GA4/GA4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import {
PageType as analyticsPageTypes,
TrackType as analyticsTrackTypes,
type ConsentData,
type EventData,
integrations,
type LoadIntegrationEventData,
Expand All @@ -36,6 +37,7 @@ import {
OPTION_DATA_LAYER_NAME,
OPTION_DEBUG_MODE,
OPTION_ENABLE_AUTOMATIC_PAGE_VIEWS,
OPTION_GOOGLE_CONSENT_CONFIG,
OPTION_LOAD_SCRIPT_FUNCTION,
OPTION_MEASUREMENT_ID,
OPTION_NON_INTERACTION_EVENTS,
Expand All @@ -46,6 +48,7 @@ import {
OPTION_SET_CUSTOM_USER_ID_PROPERTY,
} from './constants.js';
import { each, get, merge } from 'lodash-es';
import { GoogleConsentMode } from '../shared/index.js';
import { validateFields } from './validation/optionsValidator.js';
import defaultSchemaEventsMap from '../shared/validation/eventSchemas.js';
import eventValidator from '../shared/validation/eventValidator.js';
Expand All @@ -70,6 +73,7 @@ import type {
* Google Analytics 4 Integration.
*/
class GA4 extends integrations.Integration<GA4IntegrationOptions> {
private googleConsentMode!: GoogleConsentMode;
private enableAutomaticPageViews!: boolean;
private schemaEventsMap!: Schemas;
private initializePromise!: Promise<void>;
Expand Down Expand Up @@ -100,7 +104,7 @@ class GA4 extends integrations.Integration<GA4IntegrationOptions> {
strippedDownAnalytics: StrippedDownAnalytics,
) {
super(options, loadData, strippedDownAnalytics);
this.initialize(options);
this.initialize(options, loadData);
this.onSetUser(loadData);
}

Expand All @@ -111,9 +115,13 @@ class GA4 extends integrations.Integration<GA4IntegrationOptions> {
* Initializes member variables from options and tries to initialize Google
* Analytics 4.
*
* @param options - Options passed for the GA4 integration.
* @param options - Options passed for the GA4 integration.
* @param loadData - Analytics's load event data.
*/
initialize(options: GA4IntegrationOptions) {
initialize(
options: GA4IntegrationOptions,
loadData: LoadIntegrationEventData,
) {
this.optionsValidationResultsMap = validateFields(options);

this.measurementId = options[OPTION_MEASUREMENT_ID];
Expand Down Expand Up @@ -148,9 +156,33 @@ class GA4 extends integrations.Integration<GA4IntegrationOptions> {
true,
);

this.googleConsentMode = new GoogleConsentMode(
this.getDataLayerName(),
loadData.consent,
get(options, OPTION_GOOGLE_CONSENT_CONFIG),
);

this.loadGtagScript(options);
}

getDataLayerName(): string {
return get(this.options, OPTION_DATA_LAYER_NAME, DEFAULT_DATA_LAYER_NAME);
}

/**
* Sets the consent object. This method is called by analytics whenever the consent
* changes, so there's no need to validate if it has changed or not.
*
* @param consent - Object to be written on the dataLayer.
*
* @returns This allows chaining of class methods.
*/
override setConsent(this: this, consent: ConsentData): this {
this.googleConsentMode.updateConsent(consent);

return this;
}

/**
* Send page events to GA4.
*
Expand Down Expand Up @@ -725,9 +757,7 @@ class GA4 extends integrations.Integration<GA4IntegrationOptions> {
* @param options - User configured options.
*/
internalLoadScript(options: GA4IntegrationOptions) {
const customDataLayerAttr = options[OPTION_DATA_LAYER_NAME]
? options[OPTION_DATA_LAYER_NAME]
: DEFAULT_DATA_LAYER_NAME;
const customDataLayerAttr = this.getDataLayerName();
const debugMode = options[OPTION_DEBUG_MODE] || false;
const script = document.createElement('script');

Expand Down
1 change: 1 addition & 0 deletions packages/react/src/analytics/integrations/GA4/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export const OPTION_SCHEMAS = 'schemas';
export const OPTION_DATA_LAYER_NAME = 'dataLayerName';
export const OPTION_SET_CUSTOM_USER_ID_PROPERTY = 'setCustomUserIdProperty';
export const OPTION_DEBUG_MODE = 'debugMode';
export const OPTION_GOOGLE_CONSENT_CONFIG = 'googleConsentConfig';

export const GA4_UNIQUE_EVENT_ID = 'blackoutAnalyticsEventId';
4 changes: 4 additions & 0 deletions packages/react/src/analytics/integrations/GA4/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
import type {
OPTION_DATA_LAYER_NAME,
OPTION_ENABLE_AUTOMATIC_PAGE_VIEWS,
OPTION_GOOGLE_CONSENT_CONFIG,
OPTION_LOAD_SCRIPT_FUNCTION,
OPTION_MEASUREMENT_ID,
OPTION_NON_INTERACTION_EVENTS,
Expand All @@ -21,6 +22,8 @@ import type {
} from '../constants.js';
import type { Schema } from '../../shared/types/shared.types.js';

import { type GoogleConsentModeConfig } from '../../shared/index.js';

export type Schemas = Record<string, Schema>;

export interface GA4IntegrationOptions extends IntegrationOptions {
Expand All @@ -34,6 +37,7 @@ export interface GA4IntegrationOptions extends IntegrationOptions {
[OPTION_DATA_LAYER_NAME]?: string;
[OPTION_LOAD_SCRIPT_FUNCTION]?: () => Promise<void>;
[OPTION_ON_PRE_PROCESS_COMMANDS]?: OnPreProcessCommandsHandler;
[OPTION_GOOGLE_CONSENT_CONFIG]?: GoogleConsentModeConfig;
}

export type ProductMappings = Record<string, string | string[]>;
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/analytics/integrations/GTM/GTM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
DATA_LAYER_SET_USER_EVENT,
EVENT_SCHEMAS_KEY,
EVENTS_MAPPER_KEY,
GOOGLE_CONSENT_CONFIG_KEY,
GTM_DATA_LAYER,
GTM_LABEL_PREFIX,
GTM_TYPE_ERROR_PREFIX,
INVALID_FUNCTION_ERROR_SUFFIX,
Expand All @@ -46,6 +48,7 @@ import {
} from '@farfetch/blackout-analytics';
import { get, isEqual, isPlainObject, merge } from 'lodash-es';
import { getContextParameters, getUserParameters } from './utils.js';
import { GoogleConsentMode } from '../shared/index.js';
import eventSchemas from '../shared/validation/eventSchemas.js';
import eventsMapper from './eventsMapper.js';
import eventValidator from '../shared/validation/eventValidator.js';
Expand All @@ -61,6 +64,7 @@ import type { Schemas } from '../GA/index.js';
* GTM Integration.
*/
class GTM extends integrations.Integration<GTMIntegrationOptions> {
protected googleConsentMode!: GoogleConsentMode;
protected consentKey?: string;
protected contextKey?: string;
protected setUserKey?: string;
Expand Down Expand Up @@ -132,6 +136,12 @@ class GTM extends integrations.Integration<GTMIntegrationOptions> {
options: GTMIntegrationOptions,
loadData: LoadIntegrationEventData,
) {
this.googleConsentMode = new GoogleConsentMode(
GTM_DATA_LAYER,
loadData.consent,
get(options, GOOGLE_CONSENT_CONFIG_KEY),
);

this.runGTMScript(options);
this.setConsent(loadData.consent as ConsentData);
this.setContext(loadData.context);
Expand Down Expand Up @@ -175,6 +185,7 @@ class GTM extends integrations.Integration<GTMIntegrationOptions> {
* @returns This allows chaining of class methods.
*/
override setConsent(this: this, consent: ConsentData): this {
this.googleConsentMode.updateConsent(consent);
this.write({
consent,
event: this.consentKey as string,
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/analytics/integrations/GTM/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const SET_USER_KEY = 'userKey';
export const SET_USER_FN_KEY = 'onSetUser';
export const EVENTS_MAPPER_KEY = 'eventsMapper';
export const EVENT_SCHEMAS_KEY = 'eventSchemas';
export const GOOGLE_CONSENT_CONFIG_KEY = 'googleConsentConfig';
export const GTM_DATA_LAYER = 'dataLayer';

export const GTM_LABEL_PREFIX = 'Google Tag Manager -';
export const GTM_TYPE_ERROR_PREFIX = `${GTM_LABEL_PREFIX} TypeError:`;
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/analytics/integrations/GTM/gtmTag.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable */

import { GTM_DATA_LAYER } from './constants.js';

/**
* Script from google tag manager.
*
Expand All @@ -18,4 +20,4 @@ export default (containerId: string) =>
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', containerId);
})(window, document, 'script', GTM_DATA_LAYER, containerId);
4 changes: 4 additions & 0 deletions packages/react/src/analytics/integrations/GTM/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
CONTAINER_ID_KEY,
EVENT_SCHEMAS_KEY,
EVENTS_MAPPER_KEY,
GOOGLE_CONSENT_CONFIG_KEY,
SET_CONSENT_KEY,
SET_CONTEXT_FN_KEY,
SET_CONTEXT_KEY,
Expand All @@ -18,6 +19,8 @@ import type {
import type { Schemas } from '../../GA/index.js';
import type URLParse from 'url-parse';

import { type GoogleConsentModeConfig } from '../../shared/index.js';

export type EventMappers = Record<
string,
(data: EventData<TrackTypesValues>) => unknown
Expand All @@ -32,6 +35,7 @@ export interface GTMIntegrationOptions extends IntegrationOptions {
[SET_USER_FN_KEY]?: (user: UserData) => void;
[EVENTS_MAPPER_KEY]?: EventMappers;
[EVENT_SCHEMAS_KEY]?: Schemas;
[GOOGLE_CONSENT_CONFIG_KEY]?: GoogleConsentModeConfig;
}

export type GTMEventContext = {
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/analytics/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { integrations } from '@farfetch/blackout-analytics';

const { Integration, Omnitracking } = integrations;

export {
type GoogleConsentModeConfig,
GoogleConsentType,
} from './shared/index.js';
export { Integration };
export { Omnitracking };
export { default as GA, validationSchemaBuilder } from './GA/index.js';
Expand Down
Loading
Loading