Skip to content

Commit

Permalink
LiveIntent Analytics Adapter: initial release (#8960)
Browse files Browse the repository at this point in the history
* CM-552 Liveintent Analytics Adapter (#4)

* start work

* send analytics event

* Add first test and get winning bids from auctionManager

* Add event test data and fix bugs

* Remove duplicate userIds

* add bidWonTimeout in configOptions

* add sampling and adjust test

* Add server test

* Compare expected request body in the test

* refactoring

* update description

* remove comment

* comments

* make sure we map defined data

* refactoring

* some refactoring

* comments

Co-authored-by: wiem <welabidine@liveintent.com>

* fix typo

* Use getRefererInfo to get url and ?? operator for default values

Co-authored-by: Leonel Cuevas Valeriano <leonel.cvs@gmail.com>
Co-authored-by: leonelcuevas <lcuevas@liveintent.com>
  • Loading branch information
3 people committed Sep 12, 2022
1 parent ceb96ab commit cac0902
Show file tree
Hide file tree
Showing 3 changed files with 467 additions and 0 deletions.
148 changes: 148 additions & 0 deletions modules/liveIntentAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {ajax} from '../src/ajax.js';
import { generateUUID, logInfo, logWarn } from '../src/utils.js';
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import CONSTANTS from '../src/constants.json';
import adapterManager from '../src/adapterManager.js';
import { auctionManager } from '../src/auctionManager.js';
import { getRefererInfo } from '../src/refererDetection.js';

const ANALYTICS_TYPE = 'endpoint';
const URL = 'https://wba.liadm.com/analytic-events';
const GVL_ID = 148;
const ADAPTER_CODE = 'liveintent';
const DEFAULT_SAMPLING = 0.1;
const DEFAULT_BID_WON_TIMEOUT = 2000;
const { EVENTS: { AUCTION_END } } = CONSTANTS;
let initOptions = {};
let isSampled;
let bidWonTimeout;

function handleAuctionEnd(args) {
setTimeout(() => {
const auction = auctionManager.index.getAuction(args.auctionId);
const winningBids = (auction) ? auction.getWinningBids() : [];
const data = createAnalyticsEvent(args, winningBids);
sendAnalyticsEvent(data);
}, bidWonTimeout);
}

function getAnalyticsEventBids(bidsReceived) {
return bidsReceived.map(bid => {
return {
adUnitCode: bid.adUnitCode,
timeToRespond: bid.timeToRespond,
cpm: bid.cpm,
currency: bid.currency,
ttl: bid.ttl,
bidder: bid.bidder
};
});
}

function getBannerSizes(banner) {
if (banner && banner.sizes) {
return banner.sizes.map(size => {
const [width, height] = size;
return {w: width, h: height};
});
} else return [];
}

function getUniqueBy(arr, key) {
return [...new Map(arr.map(item => [item[key], item])).values()]
}

function createAnalyticsEvent(args, winningBids) {
let payload = {
instanceId: generateUUID(),
url: getRefererInfo().page,
bidsReceived: getAnalyticsEventBids(args.bidsReceived),
auctionStart: args.timestamp,
auctionEnd: args.auctionEnd,
adUnits: [],
userIds: [],
bidders: []
}
let allUserIds = [];

if (args.adUnits) {
args.adUnits.forEach(unit => {
if (unit.mediaTypes && unit.mediaTypes.banner) {
payload['adUnits'].push({
code: unit.code,
mediaType: 'banner',
sizes: getBannerSizes(unit.mediaTypes.banner),
ortb2Imp: unit.ortb2Imp
});
}
if (unit.bids) {
let userIds = unit.bids.flatMap(getAnalyticsEventUserIds);
allUserIds.push(...userIds);
let bidders = unit.bids.map(({bidder, params}) => {
return { bidder, params }
});

payload['bidders'].push(...bidders);
}
})
let uniqueUserIds = getUniqueBy(allUserIds, 'source');
payload['userIds'] = uniqueUserIds;
}
payload['winningBids'] = getAnalyticsEventBids(winningBids);
payload['auctionId'] = args.auctionId;
return payload;
}

function getAnalyticsEventUserIds(bid) {
if (bid && bid.userIdAsEids) {
return bid.userIdAsEids.map(({source, uids, ext}) => {
let analyticsEventUserId = {source, uids, ext};
return ignoreUndefined(analyticsEventUserId)
});
} else { return []; }
}

function sendAnalyticsEvent(data) {
ajax(URL, {
success: function () {
logInfo('LiveIntent Prebid Analytics: send data success');
},
error: function (e) {
logWarn('LiveIntent Prebid Analytics: send data error' + e);
}
}, JSON.stringify(data), {
contentType: 'application/json',
method: 'POST'
})
}

function ignoreUndefined(data) {
const filteredData = Object.entries(data).filter(([key, value]) => value)
return Object.fromEntries(filteredData)
}

let liAnalytics = Object.assign(adapter({URL, ANALYTICS_TYPE}), {
track({ eventType, args }) {
if (eventType == AUCTION_END && args && isSampled) { handleAuctionEnd(args); }
}
});

// save the base class function
liAnalytics.originEnableAnalytics = liAnalytics.enableAnalytics;

// override enableAnalytics so we can get access to the config passed in from the page
liAnalytics.enableAnalytics = function (config) {
initOptions = config.options;
const sampling = (initOptions && initOptions.sampling) ?? DEFAULT_SAMPLING;
isSampled = Math.random() < parseFloat(sampling);
bidWonTimeout = (initOptions && initOptions.bidWonTimeout) ?? DEFAULT_BID_WON_TIMEOUT;
liAnalytics.originEnableAnalytics(config); // call the base class function
};

adapterManager.registerAnalyticsAdapter({
adapter: liAnalytics,
code: ADAPTER_CODE,
gvlid: GVL_ID
});

export default liAnalytics;
22 changes: 22 additions & 0 deletions modules/liveIntentAnalyticsAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Overview
Module Name: LiveIntent Analytics Adapter

Module Type: Analytics Adapter

Maintainer: product@liveintent.com

# Description

Analytics adapter for [LiveIntent](https://www.liveintent.com/). Contact product@liveintent.com for information.

# Test Parameters

```
{
provider: 'liveintent',
options: {
bidWonTimeout: 2000,
sampling: 0.5 // the tracked event percentage, a number between 0 to 1
}
}
```
Loading

0 comments on commit cac0902

Please sign in to comment.