Skip to content

Commit c7fa10d

Browse files
JulianRSLJulian
authored andcommitted
IntentIQ Analytics Module : initial release (prebid#9322)
* Adding IIQ analytical adapter * IIQ Analytical adapter MD * Test fix * Test fix * Modules Documentation * Test change --------- Co-authored-by: Julian <[email protected]>
1 parent 6dd609d commit c7fa10d

7 files changed

+573
-21
lines changed

modules/intentIqAnalyticsAdapter.js

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { logInfo, logError } from '../src/utils.js';
2+
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
3+
import adapterManager from '../src/adapterManager.js';
4+
import CONSTANTS from '../src/constants.json';
5+
import { ajax } from '../src/ajax.js';
6+
import { getStorageManager } from '../src/storageManager.js';
7+
import { config } from '../src/config.js';
8+
9+
const MODULE_NAME = 'iiqAnalytics'
10+
const analyticsType = 'endpoint';
11+
const defaultUrl = 'https://reports.intentiq.com/report';
12+
const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME });
13+
const prebidVersion = '$prebid.version$';
14+
const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000);
15+
16+
const FIRST_PARTY_KEY = '_iiq_fdata';
17+
const FIRST_PARTY_DATA_KEY = '_iiq_fdata';
18+
const GROUP_LS_KEY = '_iiq_group';
19+
const PRECENT_LS_KEY = '_iiq_precent';
20+
const JSVERSION = 5.3
21+
22+
const PARAMS_NAMES = {
23+
abPercentage: 'abPercentage',
24+
abTestGroup: 'abGroup',
25+
pbPauseUntill: 'pbPauseUntil',
26+
pbMonitoringEnabled: 'pbMonitoringEnabled',
27+
isInTestGroup: 'isInTestGroup',
28+
enhanceRequests: 'enhanceRequests',
29+
wasSubscribedForPrebid: 'wasSubscribedForPrebid',
30+
hadEids: 'hadEids',
31+
userActualPercentage: 'userPercentage',
32+
ABTestingConfigurationSource: 'ABTestingConfigurationSource',
33+
lateConfiguration: 'lateConfiguration',
34+
jsverion: 'jsversion',
35+
eidsNames: 'eidsNames',
36+
requestRtt: 'rtt',
37+
clientType: 'clientType',
38+
adserverDeviceType: 'AdserverDeviceType',
39+
terminationCause: 'terminationCause',
40+
callCount: 'callCount',
41+
manualCallCount: 'mcc',
42+
pubprovidedidsFailedToregister: 'ppcc',
43+
noDataCount: 'noDataCount',
44+
profile: 'profile',
45+
isProfileDeterministic: 'pidDeterministic',
46+
siteId: 'sid',
47+
hadEidsInLocalStorage: 'idls',
48+
auctionStartTime: 'ast',
49+
eidsReadTime: 'eidt',
50+
agentId: 'aid',
51+
auctionEidsLegth: 'aeidln',
52+
wasServerCalled: 'wsrvcll',
53+
refferer: 'vrref',
54+
isInBrowserBlacklist: 'inbbl',
55+
prebidVersion: 'pbjsver',
56+
partnerId: 'partnerId'
57+
};
58+
59+
var initOptions = {
60+
lsValueInitialized: false
61+
}
62+
63+
// Events needed
64+
const {
65+
EVENTS: {
66+
BID_WON
67+
}
68+
} = CONSTANTS;
69+
70+
let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), {
71+
track({ eventType, args }) {
72+
switch (eventType) {
73+
case BID_WON:
74+
bidWon(args);
75+
break;
76+
default:
77+
break;
78+
}
79+
}
80+
});
81+
82+
function readData(key) {
83+
try {
84+
if (storage.hasLocalStorage()) {
85+
return storage.getDataFromLocalStorage(key);
86+
}
87+
if (storage.cookiesAreEnabled()) {
88+
return storage.getCookie(key);
89+
}
90+
} catch (error) {
91+
logError(error);
92+
}
93+
}
94+
95+
function initLsValues() {
96+
if (initOptions.lsValueInitialized) return;
97+
initOptions.fpid = readData(FIRST_PARTY_KEY);
98+
let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId');
99+
if (iiqArr && iiqArr.length > 0) initOptions.lsValueInitialized = true;
100+
if (!iiqArr) iiqArr = [];
101+
if (iiqArr.length == 0) {
102+
iiqArr.push({
103+
'params': {
104+
'partner': -1,
105+
'group': 'U',
106+
'percentage': -1
107+
}
108+
})
109+
}
110+
if (iiqArr && iiqArr.length > 0) {
111+
if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) {
112+
initOptions.partner = iiqArr[0].params.partner;
113+
initOptions.userGroup = iiqArr[0].params.group || 'U';
114+
initOptions.userPercentage = iiqArr[0].params.percentage || '-1';
115+
116+
initOptions.currentGroup = readData(GROUP_LS_KEY + '_' + initOptions.partner)
117+
initOptions.currentPercentage = readData(PRECENT_LS_KEY + '_' + initOptions.partner)
118+
}
119+
}
120+
}
121+
122+
function initReadLsIds() {
123+
if (isNaN(initOptions.partner) || initOptions.partner == -1) return;
124+
try {
125+
initOptions.dataInLs = null;
126+
let iData = readData(FIRST_PARTY_DATA_KEY + '_' + initOptions.partner)
127+
if (iData) {
128+
initOptions.lsIdsInitialized = true;
129+
let pData = JSON.parse(iData);
130+
initOptions.dataInLs = pData.data;
131+
initOptions.eidl = pData.eidl || -1;
132+
}
133+
} catch (e) {
134+
logError(e)
135+
}
136+
}
137+
138+
function bidWon(args) {
139+
if (!initOptions.lsValueInitialized) { initLsValues(); }
140+
if (initOptions.lsValueInitialized && !initOptions.lsIdsInitialized) { initReadLsIds(); }
141+
if (!initOptions.manualReport) { ajax(constructFulllUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); }
142+
143+
logInfo('IIQ ANALYTICS -> BID WON')
144+
}
145+
146+
function getRandom(start, end) {
147+
return Math.floor((Math.random() * (end - start + 1)) + start);
148+
}
149+
150+
function preparePayload(data) {
151+
let result = getDefaultDataObject();
152+
153+
result[PARAMS_NAMES.partnerId] = initOptions.partner;
154+
result[PARAMS_NAMES.prebidVersion] = prebidVersion;
155+
result[PARAMS_NAMES.refferer] = getRefferer();
156+
result[PARAMS_NAMES.userActualPercentage] = initOptions.userPercentage;
157+
158+
if (initOptions.userGroup && initOptions.userGroup != '') { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'group'; } else if (initOptions.userPercentage && !isNaN(initOptions.userPercentage)) { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'percentage'; }
159+
160+
result[PARAMS_NAMES.abPercentage] = initOptions.currentPercentage;
161+
result[PARAMS_NAMES.abTestGroup] = initOptions.currentGroup;
162+
163+
result[PARAMS_NAMES.isInTestGroup] = initOptions.currentGroup == 'A';
164+
165+
result[PARAMS_NAMES.agentId] = REPORTER_ID;
166+
167+
fillPrebidEventData(data, result);
168+
169+
fillEidsData(result);
170+
171+
return result;
172+
}
173+
174+
function fillEidsData(result) {
175+
if (initOptions.lsIdsInitialized) {
176+
result[PARAMS_NAMES.hadEidsInLocalStorage] = initOptions.eidl && initOptions.eidl > 0;
177+
result[PARAMS_NAMES.auctionEidsLegth] = initOptions.eidl || -1;
178+
}
179+
}
180+
181+
function fillPrebidEventData(eventData, result) {
182+
if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; }
183+
if (eventData.cpm) { result.cpm = eventData.cpm; }
184+
if (eventData.currency) { result.currency = eventData.currency; }
185+
if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; }
186+
if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; }
187+
if (eventData.status) { result.status = eventData.status; }
188+
if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; }
189+
190+
result.biddingPlatformId = 1;
191+
result.partnerAuctionId = 'BW';
192+
}
193+
194+
function getDefaultDataObject() {
195+
return {
196+
'inbbl': false,
197+
'pbjsver': prebidVersion,
198+
'partnerAuctionId': 'BW',
199+
'reportSource': 'pbjs',
200+
'abPercentage': -1,
201+
'abGroup': 'U',
202+
'userPercentage': -1,
203+
'jsversion': JSVERSION,
204+
'partnerId': -1,
205+
'biddingPlatformId': 1,
206+
'idls': false,
207+
'ast': -1,
208+
'aeidln': -1
209+
}
210+
}
211+
212+
function constructFulllUrl(data) {
213+
let report = []
214+
data = btoa(JSON.stringify(data))
215+
report.push(data)
216+
return defaultUrl + '?pid=' + initOptions.partner +
217+
'&mct=1' +
218+
((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid)
219+
? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') +
220+
'&agid=' + REPORTER_ID +
221+
'&jsver=' + JSVERSION +
222+
'&source=pbjs' +
223+
'&payload=' + JSON.stringify(report)
224+
}
225+
226+
function getRefferer() {
227+
return document.referrer;
228+
}
229+
230+
iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics;
231+
232+
iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) {
233+
iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function
234+
};
235+
236+
adapterManager.registerAnalyticsAdapter({
237+
adapter: iiqAnalyticsAnalyticsAdapter,
238+
code: MODULE_NAME
239+
});
240+
241+
export default iiqAnalyticsAnalyticsAdapter;

modules/intentIqAnalyticsAdapter.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Overview
2+
3+
Module Name: iiqAnalytics
4+
Module Type: Analytics Adapter
5+
Maintainer: [email protected]
6+
7+
# Description
8+
9+
By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains.
10+
11+
## Intent IQ Universal ID Registration
12+
13+
No registration for this module is required.
14+
15+
## Intent IQ Universal IDConfiguration
16+
17+
<B>IMPORTANT</B>: requires Intent IQ Universal ID module be installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html)
18+
19+
No aaditional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module.

modules/intentIqIdSystem.js

+47-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ const PCID_EXPIRY = 365;
1515
const MODULE_NAME = 'intentIqId';
1616
export const FIRST_PARTY_KEY = '_iiq_fdata';
1717
export var FIRST_PARTY_DATA_KEY = '_iiq_fdata';
18+
export var GROUP_LS_KEY = '_iiq_group';
19+
export var WITH_IIQ = 'A';
20+
export var WITHOUT_IIQ = 'B';
21+
export var PRECENT_LS_KEY = '_iiq_precent';
22+
export var DEFAULT_PERCENTAGE = 100;
1823

1924
export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME });
2025

2126
const INVALID_ID = 'INVALID_ID';
2227

28+
function getRandom(start, end) {
29+
return Math.floor(Math.random() * (end - start + 1) + start);
30+
}
31+
2332
/**
2433
* Generate standard UUID string
2534
* @return {string}
@@ -44,6 +53,9 @@ export function readData(key) {
4453
if (storage.hasLocalStorage()) {
4554
return storage.getDataFromLocalStorage(key);
4655
}
56+
if (localStorage) {
57+
return localStorage.getItem(key)
58+
}
4759
if (storage.cookiesAreEnabled()) {
4860
return storage.getCookie(key);
4961
}
@@ -65,6 +77,9 @@ function storeData(key, value) {
6577
if (value) {
6678
if (storage.hasLocalStorage()) {
6779
storage.setDataInLocalStorage(key, value);
80+
} else
81+
if (localStorage) {
82+
localStorage.setItem(key, value)
6883
}
6984
const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString();
7085
if (storage.cookiesAreEnabled()) {
@@ -103,7 +118,7 @@ export const intentIqIdSubmodule = {
103118
* @param {{string}} value
104119
* @returns {{intentIqId: {string}}|undefined}
105120
*/
106-
decode(value) {
121+
decode(value, config) {
107122
return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined;
108123
},
109124
/**
@@ -118,6 +133,32 @@ export const intentIqIdSubmodule = {
118133
logError('User ID - intentIqId submodule requires a valid partner to be defined');
119134
return;
120135
}
136+
137+
if (isNaN(configParams.percentage)) {
138+
logInfo(MODULE_NAME + ' AB Testing percentage is not defined. Setting default value = ' + DEFAULT_PERCENTAGE);
139+
configParams.percentage = DEFAULT_PERCENTAGE;
140+
}
141+
142+
if (isNaN(configParams.percentage) || configParams.percentage < 0 || configParams.percentage > 100) {
143+
logError(MODULE_NAME + 'Percentage - intentIqId submodule requires a valid percentage value');
144+
return false;
145+
}
146+
147+
configParams.group = readData(GROUP_LS_KEY + '_' + configParams.partner);
148+
let percentage = readData(PRECENT_LS_KEY + '_' + configParams.partner);
149+
150+
if (!configParams.group || !percentage || isNaN(percentage) || percentage != configParams.percentage) {
151+
logInfo(MODULE_NAME + 'Generating new Group. Current test group: ' + configParams.group + ', current percentage: ' + percentage + ' , configured percentage: ' + configParams.percentage);
152+
if (configParams.percentage > getRandom(1, 100)) { configParams.group = WITH_IIQ; } else configParams.group = WITHOUT_IIQ;
153+
storeData(GROUP_LS_KEY + '_' + configParams.partner, configParams.group)
154+
storeData(PRECENT_LS_KEY + '_' + configParams.partner, configParams.percentage + '')
155+
logInfo(MODULE_NAME + 'New group: ' + configParams.group)
156+
}
157+
if (configParams.group == WITHOUT_IIQ) {
158+
logInfo(MODULE_NAME + 'Group "B". Passive Mode ON.');
159+
return true;
160+
}
161+
121162
if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; }
122163
let rrttStrtTime = 0;
123164

@@ -157,6 +198,11 @@ export const intentIqIdSubmodule = {
157198
partnerData.cttl = respJson.cttl;
158199
shouldUpdateLs = true;
159200
}
201+
202+
if ('eidl' in respJson) {
203+
partnerData.eidl = respJson.eidl;
204+
}
205+
160206
// If should save and data is empty, means we should save as INVALID_ID
161207
if (respJson.data == '') {
162208
respJson.data = INVALID_ID;

0 commit comments

Comments
 (0)