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

Adagio Analytics Adapter: new endpoint and new code to track auctions #10426

Merged
merged 6 commits into from
Sep 12, 2023
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
328 changes: 313 additions & 15 deletions modules/adagioAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,35 @@
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
import { getWindowTop } from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { BANNER } from '../src/mediaTypes.js';
import { getWindowTop, getWindowSelf, deepAccess, logInfo, logError } from '../src/utils.js';
import { getGlobal } from '../src/prebidGlobal.js';

const emptyUrl = '';
const analyticsType = 'endpoint';
const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]);
const VERSION = '2.0.0';
const ADAGIO_GVLID = 617;
const VERSION = '3.0.0';
const PREBID_VERSION = '$prebid.version$';
const ENDPOINT = 'https://c.4dex.io/pba.gif';
const cache = {
auctions: {},
getAuction: function(auctionId, adUnitCode) {
return this.auctions[auctionId][adUnitCode];
},
updateAuction: function(auctionId, adUnitCode, values) {
this.auctions[auctionId][adUnitCode] = {
...this.auctions[auctionId][adUnitCode],
...values
};
}
};
const enc = window.encodeURIComponent;

const adagioEnqueue = function adagioEnqueue(action, data) {
getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() });
}
/**
/* BEGIN ADAGIO.JS CODE
*/

function canAccessTopWindow() {
try {
Expand All @@ -24,24 +43,302 @@ function canAccessTopWindow() {
} catch (error) {
return false;
}
}
};

function getCurrentWindow() {
return currentWindow;
};

let currentWindow;

const adagioEnqueue = function adagioEnqueue(action, data) {
getCurrentWindow().ADAGIO.queue.push({ action, data, ts: Date.now() });
};

/**
* END ADAGIO.JS CODE
*/

/**
* UTILS FUNCTIONS
*/

const guard = {
adagio: (value) => isAdagio(value),
bidTracked: (auctionId, adUnitCode) => deepAccess(cache, `auctions.${auctionId}.${adUnitCode}`, false)
};

function removeDuplicates(arr, getKey) {
const seen = {};
return arr.filter(item => {
const key = getKey(item);
return seen.hasOwnProperty(key) ? false : (seen[key] = true);
});
};

function getAdapterNameForAlias(aliasName) {
return adapterManager.aliasRegistry[aliasName] || aliasName;
};

function isAdagio(value) {
return value.toLowerCase().includes('adagio') ||
getAdapterNameForAlias(value).toLowerCase().includes('adagio');
};

function getMediaTypeAlias(mediaType) {
const mediaTypesMap = {
banner: 'ban',
outstream: 'vidout',
instream: 'vidin',
adpod: 'vidadpod',
native: 'nat'
};
return mediaTypesMap[mediaType] || mediaType;
};

/**
* sendRequest to Adagio. It filter null values and encode each query param.
* @param {Object} qp
*/
function sendRequest(qp) {
// Removing null values
qp = Object.keys(qp).reduce((acc, key) => {
if (qp[key] !== null) {
acc[key] = qp[key];
}
return acc;
}, {});

const url = `${ENDPOINT}?${Object.keys(qp).map(key => `${key}=${enc(qp[key])}`).join('&')}`;
ajax(url, null, null, {method: 'GET'});
};

/**
* Send a new beacon to Adagio. It increment the version of the beacon.
* @param {string} auctionId
* @param {string} adUnitCode
*/
function sendNewBeacon(auctionId, adUnitCode) {
cache.updateAuction(auctionId, adUnitCode, {
v: (cache.getAuction(auctionId, adUnitCode).v || 0) + 1
});
sendRequest(cache.getAuction(auctionId, adUnitCode));
};

/**
* END UTILS FUNCTIONS
*/

/**
* HANDLERS
* - handlerAuctionInit
* - handlerBidResponse
* - handlerBidWon
* - handlerAdRender
*
* Each handler is called when the event is fired.
*/

function handlerAuctionInit(event) {
const w = getCurrentWindow();

const prebidAuctionId = event.auctionId;
const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode);

// Check if Adagio is on the bid requests.
// If not, we don't need to track the auction.
const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode));
if (!adagioBidRequest) {
logInfo(`Adagio is not on the bid requests for auction '${prebidAuctionId}'`)
return;
}

cache.auctions[prebidAuctionId] = {};

adUnitCodes.forEach(adUnitCode => {
const adUnits = event.adUnits.filter(adUnit => adUnit.code === adUnitCode);

// Get all bidders configures for the ad unit.
const bidders = removeDuplicates(
adUnits.map(adUnit => adUnit.bids.map(bid => ({bidder: bid.bidder, params: bid.params}))).flat(),
bidder => bidder.bidder
);

// Check if Adagio is configured for the ad unit.
// If not, we don't need to track the ad unit.
const adagioBidder = bidders.find(bidder => isAdagio(bidder.bidder));
if (!adagioBidder) {
logInfo(`Adagio is not configured for ad unit '${adUnitCode}'`);
return;
}

// Get all media types and banner sizes configured for the ad unit.
const mediaTypes = adUnits.map(adUnit => adUnit.mediaTypes);
const mediaTypesKeys = removeDuplicates(
mediaTypes.map(mediaTypeObj => Object.keys(mediaTypeObj)).flat(),
mediaTypeKey => mediaTypeKey
).map(mediaType => getMediaTypeAlias(mediaType)).sort();
const bannerSizes = removeDuplicates(
mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER))
.map(mediaType => mediaType[BANNER].sizes.map(size => size.join('x')))
.flat(),
bannerSize => bannerSize
).sort();

// Get all Adagio bids for the ad unit from the bidRequest.
// If no bids, we don't need to track the ad unit.
const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode);
if (deepAccess(adagioAdUnitBids, 'length', 0) <= 0) {
logInfo(`Adagio is not on the bid requests for ad unit '${adUnitCode}' and auction '${prebidAuctionId}'`)
return;
}
// Get Adagio params from the first bid.
// We assume that all Adagio bids for a same adunit have the same params.
const params = adagioAdUnitBids[0].params;

// Get all media types requested for Adagio.
const adagioMediaTypes = removeDuplicates(
adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(),
mediaTypeKey => mediaTypeKey
).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort();

const qp = {
v: 0,
pbjsv: PREBID_VERSION,
org_id: params.organizationId,
site: params.site,
pv_id: params.pageviewId,
auct_id: params.adagioAuctionId,
adu_code: adUnitCode,
url_dmn: w.location.hostname,
dvc: params.environment,
pgtyp: params.pagetype,
plcmt: params.placement,
tname: params.testName || null,
tvname: params.testVariationName || null,
mts: mediaTypesKeys.join(','),
ban_szs: bannerSizes.join(','),
bdrs: bidders.map(bidder => getAdapterNameForAlias(bidder.bidder)).sort().join(','),
adg_mts: adagioMediaTypes.join(',')
};

cache.auctions[prebidAuctionId][adUnitCode] = qp;
sendNewBeacon(prebidAuctionId, adUnitCode);
});
};

/**
* handlerBidResponse allow to track the adagio bid response
* and to update the auction cache with the seat ID.
* No beacon is sent here.
*/
function handlerBidResponse(event) {
if (!guard.adagio(event.bidder)) {
return;
}

if (!guard.bidTracked(event.auctionId, event.adUnitCode)) {
return;
}

cache.updateAuction(event.auctionId, event.adUnitCode, {
adg_sid: event.seatId || null
});
};

function handlerBidWon(event) {
if (!guard.bidTracked(event.auctionId, event.adUnitCode)) {
return;
}

let adsCurRateToUSD = (event.currency === 'USD') ? 1 : null;
let ogCurRateToUSD = (event.originalCurrency === 'USD') ? 1 : null;
try {
if (typeof getGlobal().convertCurrency === 'function') {
// Currency module is loaded, we can calculate the conversion rate.

// Get the conversion rate from the original currency to USD.
ogCurRateToUSD = getGlobal().convertCurrency(1, event.originalCurrency, 'USD');
// Get the conversion rate from the ad server currency to USD.
adsCurRateToUSD = getGlobal().convertCurrency(1, event.currency, 'USD');
}
} catch (error) {
logError('Error on Adagio Analytics Adapter - handlerBidWon', error);
}

cache.updateAuction(event.auctionId, event.adUnitCode, {
win_bdr: getAdapterNameForAlias(event.bidder),
win_mt: getMediaTypeAlias(event.mediaType),
win_ban_sz: event.mediaType === BANNER ? `${event.width}x${event.height}` : null,

// ad server currency
win_cpm: event.cpm,
cur: event.currency,
cur_rate: adsCurRateToUSD,

// original currency from bidder
og_cpm: event.originalCpm,
og_cur: event.originalCurrency,
og_cur_rate: ogCurRateToUSD,
});
sendNewBeacon(event.auctionId, event.adUnitCode);
};

function handlerAdRender(event, isSuccess) {
const { auctionId, adUnitCode } = event.bid;
if (!guard.bidTracked(auctionId, adUnitCode)) {
return;
}

cache.updateAuction(auctionId, adUnitCode, {
rndr: isSuccess ? 1 : 0
});
sendNewBeacon(auctionId, adUnitCode);
};

/**
* END HANDLERS
*/

let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), {
track: function({ eventType, args }) {
if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) {
adagioEnqueue('pb-analytics-event', { eventName: eventType, args });
track: function(event) {
const { eventType, args } = event;

try {
switch (eventType) {
case CONSTANTS.EVENTS.AUCTION_INIT:
handlerAuctionInit(args);
break;
case CONSTANTS.EVENTS.BID_RESPONSE:
handlerBidResponse(args);
break;
case CONSTANTS.EVENTS.BID_WON:
handlerBidWon(args);
break;
case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED:
case CONSTANTS.EVENTS.AD_RENDER_FAILED:
handlerAdRender(args, eventType === CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED);
break;
}
} catch (error) {
logError('Error on Adagio Analytics Adapter', error);
}

try {
if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) {
adagioEnqueue('pb-analytics-event', { eventName: eventType, args });
}
} catch (error) {
logError('Error on Adagio Analytics Adapter - adagio.js', error);
}
}
});

adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics;

adagioAdapter.enableAnalytics = config => {
if (!canAccessTopWindow()) {
return;
}

const w = getWindowTop();
const w = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf();
currentWindow = w;

w.ADAGIO = w.ADAGIO || {};
w.ADAGIO.queue = w.ADAGIO.queue || [];
Expand All @@ -53,7 +350,8 @@ adagioAdapter.enableAnalytics = config => {

adapterManager.registerAnalyticsAdapter({
adapter: adagioAdapter,
code: 'adagio'
code: 'adagio',
gvlid: ADAGIO_GVLID,
});

export default adagioAdapter;
10 changes: 5 additions & 5 deletions modules/adagioAnalyticsAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Maintainer: [email protected]

Analytics adapter for Adagio

# Test Parameters
# Settings

```
{
provider: 'adagio'
}
```js
pbjs.enableAnalytics({
provider: 'adagio',
});
```
Loading