Skip to content

Commit

Permalink
OpenX analytics adapter (prebid#3285)
Browse files Browse the repository at this point in the history
  • Loading branch information
haohany authored and AdSpacesDevelopers committed Jan 30, 2019
1 parent 6cfb33f commit 3d32e25
Show file tree
Hide file tree
Showing 2 changed files with 698 additions and 0 deletions.
253 changes: 253 additions & 0 deletions modules/openxAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import adapter from 'src/AnalyticsAdapter';
import CONSTANTS from 'src/constants.json';
import adaptermanager from 'src/adaptermanager';
import { config } from 'src/config';
import { ajax } from 'src/ajax';
import * as utils from 'src/utils';

const {
EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON }
} = CONSTANTS;

const SLOT_LOADED = 'slotOnload';

const ENDPOINT = '//ads.openx.net/w/1.0/pban';

let initOptions;

let auctionMap = {};

function onAuctionInit({ auctionId }) {
auctionMap[auctionId] = {
adUnitMap: {}
};
}

function onBidRequested({ auctionId, auctionStart, bids, start }) {
const adUnitMap = auctionMap[auctionId]['adUnitMap'];

bids.forEach(bid => {
const { adUnitCode, bidId, bidder, params, transactionId } = bid;

adUnitMap[adUnitCode] = adUnitMap[adUnitCode] || {
auctionId,
auctionStart,
transactionId,
bidMap: {}
};

adUnitMap[adUnitCode]['bidMap'][bidId] = {
bidder,
params,
requestTimestamp: start
};
});
}

function onBidResponse({
auctionId,
adUnitCode,
adId: bidId,
cpm,
creativeId,
responseTimestamp,
ts
}) {
const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode];
const bid = adUnit['bidMap'][bidId];
bid.cpm = cpm;
bid.creativeId = creativeId;
bid.responseTimestamp = responseTimestamp;
bid.ts = ts;
}

function onBidTimeout(args) {
utils
._map(args, value => value)
.forEach(({ auctionId, adUnitCode, bidId }) => {
const bid =
auctionMap[auctionId]['adUnitMap'][adUnitCode]['bidMap'][bidId];
bid.timedOut = true;
});
}

function onBidWon({ auctionId, adUnitCode, adId: bidId }) {
const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode];
const bid = adUnit['bidMap'][bidId];
bid.won = true;
}

function onSlotLoaded({ slot }) {
const targeting = slot.getTargetingKeys().reduce((targeting, key) => {
targeting[key] = slot.getTargeting(key);
return targeting;
}, {});
utils.logMessage(
'GPT slot is loaded. Current targeting set on slot:',
targeting
);

const bidId = slot.getTargeting('hb_adid')[0];
const adUnit = getAdUnitByBidId(bidId);

if (!adUnit) {
return;
}

const adUnitData = getAdUnitData(adUnit);
const performanceData = getPerformanceData(adUnit.auctionStart);
const commonFields = {
'hb.asiid': slot.getAdUnitPath(),
'hb.cur': config.getConfig('currency.adServerCurrency'),
'hb.pubid': initOptions.publisherId
};

const data = Object.assign({}, adUnitData, performanceData, commonFields);
sendEvent(data);
}

function getAdUnitByBidId(bidId) {
let result;

utils._map(auctionMap, value => value).forEach(auction => {
utils._map(auction.adUnitMap, value => value).forEach(adUnit => {
if (bidId in adUnit.bidMap) {
result = adUnit;
}
});
});

return result;
}

function getAdUnitData(adUnit) {
const bids = utils._map(adUnit.bidMap, value => value);
const bidders = bids.map(bid => bid.bidder);
const requestTimes = bids.map(
bid => bid.requestTimestamp && bid.requestTimestamp - adUnit.auctionStart
);
const responseTimes = bids.map(
bid => bid.responseTimestamp && bid.responseTimestamp - adUnit.auctionStart
);
const bidValues = bids.map(bid => bid.cpm || 0);
const timeouts = bids.map(bid => !!bid.timedOut);
const creativeIds = bids.map(bid => bid.creativeId);
const winningBid = bids.filter(bid => bid.won)[0];
const winningExchangeIndex = bids.indexOf(winningBid);
const openxBid = bids.filter(bid => bid.bidder === 'openx')[0];

return {
'hb.ct': adUnit.auctionStart,
'hb.rid': adUnit.auctionId,
'hb.exn': bidders.join(','),
'hb.sts': requestTimes.join(','),
'hb.ets': responseTimes.join(','),
'hb.bv': bidValues.join(','),
'hb.to': timeouts.join(','),
'hb.crid': creativeIds.join(','),
'hb.we': winningExchangeIndex,
'hb.g1': winningExchangeIndex === -1,
dddid: adUnit.transactionId,
ts: openxBid && openxBid.ts,
auid: openxBid && openxBid.params && openxBid.params.unit
};
}

function getPerformanceData(auctionStart) {
let timing;
try {
timing = window.top.performance.timing;
} catch (e) {}

if (!timing) {
return;
}

const { fetchStart, domContentLoadedEventEnd, loadEventEnd } = timing;
const domContentLoadTime = domContentLoadedEventEnd - fetchStart;
const pageLoadTime = loadEventEnd - fetchStart;
const timeToAuction = auctionStart - fetchStart;
const timeToRender = Date.now() - fetchStart;

return {
'hb.dcl': domContentLoadTime,
'hb.dl': pageLoadTime,
'hb.tta': timeToAuction,
'hb.ttr': timeToRender
};
}

function sendEvent(data) {
utils._map(data, (value, key) => [key, value]).forEach(([key, value]) => {
if (
value === undefined ||
value === null ||
(typeof value === 'number' && isNaN(value))
) {
delete data[key];
}
});
ajax(ENDPOINT, null, data, { method: 'GET' });
}

let googletag = window.googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().addEventListener(SLOT_LOADED, args => {
openxAdapter.track({ eventType: SLOT_LOADED, args });
});
});

const openxAdapter = Object.assign(
adapter({ url: ENDPOINT, analyticsType: 'endpoint' }),
{
track({ eventType, args }) {
utils.logMessage(eventType, Object.assign({}, args));
switch (eventType) {
case AUCTION_INIT:
onAuctionInit(args);
break;
case BID_REQUESTED:
onBidRequested(args);
break;
case BID_RESPONSE:
onBidResponse(args);
break;
case BID_TIMEOUT:
onBidTimeout(args);
break;
case BID_WON:
onBidWon(args);
break;
case SLOT_LOADED:
onSlotLoaded(args);
break;
}
}
}
);

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

// override enableAnalytics so we can get access to the config passed in from the page
openxAdapter.enableAnalytics = function(config) {
if (!config || !config.options || !config.options.publisherId) {
utils.logError('OpenX analytics adapter: publisherId is required.');
return;
}
initOptions = config.options;
openxAdapter.originEnableAnalytics(config); // call the base class function
};

// reset the cache for unit tests
openxAdapter.reset = function() {
auctionMap = {};
};

adaptermanager.registerAnalyticsAdapter({
adapter: openxAdapter,
code: 'openx'
});

export default openxAdapter;
Loading

0 comments on commit 3d32e25

Please sign in to comment.