Skip to content

Commit

Permalink
IX Bid Adapter: add priceFloors support and an integration example (#…
Browse files Browse the repository at this point in the history
…6390)

* Adding priceFloors Support to IX bid adapter and some refactoring

* refactoring the getfloor logic, and removing dead code

* newline at end of integration example

* spacing issues lint fix

* try catch around getFloor, in case the priceFloors module throws an exception

* minor rename allU -> allu

* falsey values fix, log prefix added
  • Loading branch information
punkiller authored Mar 24, 2021
1 parent b0c20ee commit d2a0360
Show file tree
Hide file tree
Showing 3 changed files with 404 additions and 22 deletions.
112 changes: 112 additions & 0 deletions integrationExamples/gpt/adUnitFloors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<!--
This page calls a single bidder for a single ad slot.
It is a specialized example for adding floors to bids using the priceFloors Module
It also makes a good test page for new adapter PR submissions. Simply set your server's Bid Params object in the
bids array inside the adUnits, and it will use your adapter to load an ad.
NOTE that many ad servers won't send back an ad if the URL is localhost... so you might need to
set an alias in your /etc/hosts file so that you can load this page from a different domain.
-->

<html>

<head>
<script async src="../../build/dist/prebid.js"></script>
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
<script>
var FAILSAFE_TIMEOUT = 3300;
var PREBID_TIMEOUT = 1000;
var adUnits = [{
code: 'div-gpt-ad-51545-0',
sizes: [[300, 250], [600, 500]],
mediaTypes: {
banner: {
sizes: [[300, 250], [600, 500]]
}
},
// Replace this object to test a new Adapter!
bids: [{
bidder: 'ix',
params: {
siteId: '300',
size: [300, 250]
}
}]
}];
var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
</script>
<script>
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function () {
googletag.pubads().disableInitialLoad();
});

pbjs.que.push(function () {
pbjs.addAdUnits(adUnits);
pbjs.setConfig({
floors: {
enforcement: {
floorDeals: false, //default to false
bidAdjustment: true
},
data: { // default if endpoint doesn't return in time
currency: 'USD',
skipRate: 5,
modelVersion: 'BlackBerryZap',
schema: {
fields: ['gptSlot', 'mediaType', 'size']
},
values: {
'*|banner|600x500': 6.5,
'*|banner|300x250': 3.25,
'*|video': 3.5
}
}
}
});
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
timeout: PREBID_TIMEOUT
});
});

function sendAdserverRequest() {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
googletag.cmd.push(function () {
pbjs.que.push(function () {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}

setTimeout(function () {
sendAdserverRequest();
}, FAILSAFE_TIMEOUT);

</script>

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-51545-0').addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h2>Prebid.js Test</h2>
<h5>Div-1</h5>
<div id='div-gpt-ad-51545-0'>
<script type='text/javascript'>
googletag.cmd.push(function () { googletag.display('div-gpt-ad-51545-0'); });
</script>
</div>
</body>

</html>

104 changes: 87 additions & 17 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ const CENT_TO_DOLLAR_FACTOR = 100;
const BANNER_TIME_TO_LIVE = 300;
const VIDEO_TIME_TO_LIVE = 3600; // 1hr
const NET_REVENUE = true;

const PRICE_TO_DOLLAR_FACTOR = {
JPY: 1
};
const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html';

const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' };

/**
* Transform valid bid request config object to banner impression object that will be sent to ad server.
*
Expand All @@ -33,6 +36,8 @@ function bidToBannerImp(bid) {
imp.banner.h = bid.params.size[1];
imp.banner.topframe = utils.inIframe() ? 0 : 1;

_applyFloor(bid, imp, BANNER);

return imp;
}

Expand All @@ -46,7 +51,7 @@ function bidToVideoImp(bid) {
const imp = bidToImp(bid);
const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video');
const context = utils.deepAccess(bid, 'mediaTypes.video.context');
const videoAdUnitWhitelist = [
const videoAdUnitAllowlist = [
'mimes', 'minduration', 'maxduration', 'protocols', 'protocol',
'startdelay', 'placement', 'linearity', 'skip', 'skipmin',
'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate',
Expand All @@ -68,12 +73,14 @@ function bidToVideoImp(bid) {
}
}

for (let adUnitProperty in videoAdUnitRef) {
if (videoAdUnitWhitelist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) {
for (const adUnitProperty in videoAdUnitRef) {
if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) {
imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty];
}
}

_applyFloor(bid, imp, VIDEO);

return imp;
}

Expand All @@ -92,12 +99,73 @@ function bidToImp(bid) {
imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`;
}

if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) {
imp.bidfloor = bid.params.bidFloor;
imp.bidfloorcur = bid.params.bidFloorCur;
return imp;
}

/**
* Gets priceFloors floors and IX adapter floors,
* Validates and sets the higher one on the impression
* @param {object} bid bid object
* @param {object} imp impression object
* @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES
*/
function _applyFloor(bid, imp, mediaType) {
let adapterFloor = null;
let moduleFloor = null;

if (bid.params.bidFloor && bid.params.bidFloorCur) {
adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur };
}

return imp;
if (utils.isFn(bid.getFloor)) {
let _mediaType = '*';
let _size = '*';

if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) {
const { w: width, h: height } = imp[mediaType];
_mediaType = mediaType;
_size = [width, height];
}
try {
moduleFloor = bid.getFloor({
mediaType: _mediaType,
size: _size
});
} catch (err) {
// continue with no module floors
utils.logWarn('priceFloors module call getFloor failed, error : ', err);
}
}

if (adapterFloor && moduleFloor) {
if (adapterFloor.currency !== moduleFloor.currency) {
utils.logWarn('The bid floor currency mismatch between IX params and priceFloors module config');
return;
}

if (adapterFloor.floor > moduleFloor.floor) {
imp.bidfloor = adapterFloor.floor;
imp.bidfloorcur = adapterFloor.currency;
imp.ext.fl = FLOOR_SOURCE.IX;
} else {
imp.bidfloor = moduleFloor.floor;
imp.bidfloorcur = moduleFloor.currency;
imp.ext.fl = FLOOR_SOURCE.PBJS;
}
return;
}

if (moduleFloor) {
imp.bidfloor = moduleFloor.floor;
imp.bidfloorcur = moduleFloor.currency;
imp.ext.fl = FLOOR_SOURCE.PBJS;
} else if (adapterFloor) {
imp.bidfloor = adapterFloor.floor;
imp.bidfloorcur = adapterFloor.currency;
imp.ext.fl = FLOOR_SOURCE.IX;
} else {
utils.logInfo('IX Bid Adapter: No floors available, no floors applied');
}
}

/**
Expand Down Expand Up @@ -270,7 +338,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) {
if (identityInfo.hasOwnProperty(partnerName)) {
let response = identityInfo[partnerName];
if (!response.responsePending && response.data && typeof response.data === 'object' &&
Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) {
Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) {
userEids.push(response.data);
}
}
Expand Down Expand Up @@ -498,7 +566,7 @@ function buildIXDiag(validBidRequests) {
iu: 0,
nu: 0,
ou: 0,
allU: 0,
allu: 0,
ren: false,
version: '$prebid.version$'
};
Expand Down Expand Up @@ -534,7 +602,7 @@ function buildIXDiag(validBidRequests) {
ixdiag.iu++;
}

ixdiag.allU++;
ixdiag.allu++;
}
}

Expand Down Expand Up @@ -610,16 +678,19 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) {
}

/**
*
* @param {object} bid ValidBidRequest object, used to adjust floor
* @param {object} imp Impression object to be modified
* @param {array} newSize The new size to be applied
* @return {object} newImp Updated impression object
*/
function createMissingBannerImp(imp, newSize) {
function createMissingBannerImp(bid, imp, newSize) {
const newImp = utils.deepClone(imp);
newImp.ext.sid = `${newSize[0]}x${newSize[1]}`;
newImp.banner.w = newSize[0];
newImp.banner.h = newSize[1];

_applyFloor(bid, newImp, BANNER);

return newImp;
}

Expand Down Expand Up @@ -658,7 +729,7 @@ export const spec = {
}

if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) ||
(mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) {
(mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) {
utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.');
return false;
}
Expand Down Expand Up @@ -730,13 +801,12 @@ export const spec = {
if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) {
videoImps[validBidRequest.transactionId].ixImps = [];
}

videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest));
}
}
if (validBidRequest.mediaType === BANNER ||
(utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) ||
(!validBidRequest.mediaType && !validBidRequest.mediaTypes)) {
(utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) ||
(!validBidRequest.mediaType && !validBidRequest.mediaTypes)) {
let imp = bidToBannerImp(validBidRequest);

if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) {
Expand Down Expand Up @@ -767,7 +837,7 @@ export const spec = {

let origImp = missingBannerSizes[transactionId].impression;
for (let i = 0; i < missingSizes.length; i++) {
let newImp = createMissingBannerImp(origImp, missingSizes[i]);
let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]);
bannerImps[transactionId].missingImps.push(newImp);
bannerImps[transactionId].missingCount++;
}
Expand Down
Loading

0 comments on commit d2a0360

Please sign in to comment.