Skip to content

IntentIq ID & Analytics Modules: GAM reporting #12785

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

Merged
merged 5 commits into from
Feb 20, 2025
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
2 changes: 1 addition & 1 deletion libraries/intentIqConstants/intentIqConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export const OPT_OUT = 'O';
export const BLACK_LIST = 'L';
export const CLIENT_HINTS_KEY = '_iiq_ch';
export const EMPTY = 'EMPTY'
export const VERSION = 0.25
export const VERSION = 0.26
2 changes: 1 addition & 1 deletion libraries/intentIqUtils/detectBrowserUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function detectBrowserFromUserAgent(userAgent) {

/**
* Detects the browser from the NavigatorUAData object
* @param {NavigatorUAData} userAgentData - The user agent data object from the browser
* @param {Object} userAgentData - The user agent data object from the browser
Copy link
Collaborator

Choose a reason for hiding this comment

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

ty

* @return {string} The name of the detected browser or 'unknown' if unable to detect
*/
export function detectBrowserFromUserAgentData(userAgentData) {
Expand Down
24 changes: 23 additions & 1 deletion modules/intentIqIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @requires module:modules/userId
*/

import {logError, logInfo} from '../src/utils.js';
import {logError, logInfo, isPlainObject} from '../src/utils.js';
import {ajax} from '../src/ajax.js';
import {submodule} from '../src/hook.js'
import {getStorageManager} from '../src/storageManager.js';
Expand Down Expand Up @@ -159,6 +159,23 @@ function tryParse(data) {
}
}

/**
* Configures and updates A/B testing group in Google Ad Manager (GAM).
*
* @param {object} gamObjectReference - Reference to the GAM object, expected to have a `cmd` queue and `pubads()` API.
* @param {string} gamParameterName - The name of the GAM targeting parameter where the group value will be stored.
* @param {string} userGroup - The A/B testing group assigned to the user (e.g., 'A', 'B', or a custom value).
*/
export function setGamReporting(gamObjectReference, gamParameterName, userGroup) {
if (isPlainObject(gamObjectReference) && Array.isArray(gamObjectReference.cmd)) {
gamObjectReference.cmd.push(() => {
gamObjectReference
.pubads()
.setTargeting(gamParameterName, userGroup || NOT_YET_DEFINED);
});
}
}

/**
* Processes raw client hints data into a structured format.
* @param {object} clientHints - Raw client hints data
Expand Down Expand Up @@ -218,11 +235,14 @@ export const intentIqIdSubmodule = {
let decryptedData, callbackTimeoutID;
let callbackFired = false;
let runtimeEids = { eids: [] };
let gamObjectReference = isPlainObject(configParams.gamObjectReference) ? configParams.gamObjectReference : undefined;
let gamParameterName = configParams.gamParameterName ? configParams.gamParameterName : 'intent_iq_group';

const allowedStorage = defineStorageType(config.enabledStorageTypes);

let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage));
const isGroupB = firstPartyData?.group === WITHOUT_IIQ;
setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group)

const firePartnerCallback = () => {
if (configParams.callback && !callbackFired) {
Expand Down Expand Up @@ -403,9 +423,11 @@ export const intentIqIdSubmodule = {
firstPartyData.group = WITHOUT_IIQ;
storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage);
defineEmptyDataAndFireCallback();
if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group);
return
} else {
firstPartyData.group = WITH_IIQ;
if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group);
}
}
if ('isOptedOut' in respJson) {
Expand Down
2 changes: 2 additions & 0 deletions modules/intentIqIdSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Please find below list of paramters that could be used in configuring Intent IQ
| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` |
| params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`|
| params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` |
| params.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured. | `googletag` |
| params.gamParameterName | Optional | String | The name of the targeting parameter that will be used to pass the group. If not specified, the default value is `intent_iq_group`. | `"intent_iq_group"` |

### Configuration example

Expand Down
84 changes: 82 additions & 2 deletions test/spec/modules/intentIqIdSystem_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { expect } from 'chai';
import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js';
import * as utils from 'src/utils.js';
import { server } from 'test/mocks/xhr.js';
import { decryptData, handleClientHints, readData } from '../../../modules/intentIqIdSystem';
import { decryptData, handleClientHints, readData, setGamReporting } from '../../../modules/intentIqIdSystem';
import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js';
import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler';
import { clearAllCookies } from '../../helpers/cookies';
import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils';
import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js';
import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, NOT_YET_DEFINED, WITH_IIQ} from '../../../libraries/intentIqConstants/intentIqConstants.js';

const partner = 10;
const pai = '11';
Expand Down Expand Up @@ -48,6 +48,24 @@ export const testClientHints = {
wow64: false
};

const mockGAM = () => {
const targetingObject = {};
return {
cmd: [],
pubads: () => ({
setTargeting: (key, value) => {
targetingObject[key] = value;
},
getTargeting: (key) => {
return [targetingObject[key]];
},
getTargetingKeys: () => {
return Object.keys(targetingObject);
}
})
};
};

describe('IntentIQ tests', function () {
let logErrorStub;
let testLSValue = {
Expand Down Expand Up @@ -199,6 +217,68 @@ describe('IntentIQ tests', function () {
expect(callBackSpy.calledOnce).to.be.true;
});

it('should set GAM targeting to U initially and update to A after server response', function () {
let callBackSpy = sinon.spy();
let mockGamObject = mockGAM();
let expectedGamParameterName = 'intent_iq_group';

const originalPubads = mockGamObject.pubads;
let setTargetingSpy = sinon.spy();
mockGamObject.pubads = function () {
const obj = { ...originalPubads.apply(this, arguments) };
const originalSetTargeting = obj.setTargeting;
obj.setTargeting = function (...args) {
setTargetingSpy(...args);
return originalSetTargeting.apply(this, args);
};
return obj;
};

defaultConfigParams.params.gamObjectReference = mockGamObject;

let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;

submoduleCallback(callBackSpy);
let request = server.requests[0];

mockGamObject.cmd.forEach(cb => cb());
mockGamObject.cmd = []

let groupBeforeResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName);

request.respond(
200,
responseHeader,
JSON.stringify({ group: 'A', tc: 20 })
);

mockGamObject.cmd.forEach(item => item());

let groupAfterResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName);

expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39');
expect(groupBeforeResponse).to.deep.equal([NOT_YET_DEFINED]);
expect(groupAfterResponse).to.deep.equal([WITH_IIQ]);

expect(setTargetingSpy.calledTwice).to.be.true;
});

it('should use the provided gamParameterName from configParams', function () {
let callBackSpy = sinon.spy();
let mockGamObject = mockGAM();
let customParamName = 'custom_gam_param';

defaultConfigParams.params.gamObjectReference = mockGamObject;
defaultConfigParams.params.gamParameterName = customParamName;

let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
submoduleCallback(callBackSpy);
mockGamObject.cmd.forEach(cb => cb());
let targetingKeys = mockGamObject.pubads().getTargetingKeys();

expect(targetingKeys).to.include(customParamName);
});

it('should not throw Uncaught TypeError when IntentIQ endpoint returns empty response', function () {
let callBackSpy = sinon.spy();
let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
Expand Down