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

IX Bid Adapter: add priceFloors support and an integration example #6390

Merged
merged 7 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
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>

95 changes: 80 additions & 15 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[adUnitProperty]) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could there be cases where the imp.video[adUnitProperty] value is falsey?

Like a couple in your videoAdUnitAllowlist for example look like they might be at times:

skip (boolean?) if this is false?
boxingallowed ? is this a boolean? if false do you want to pass along?
startdelay could this be set to 0 (zero) ?

hasOwnProperty is better in this case, otherwise if you do not care about passing along falsey values, then this change can stay.
I do not actually know if these are legit values on the imp.video object, but want to ask and make sure you are aware.

These two above examples would be

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added.

imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty];
}
}

_applyFloor(bid, imp, VIDEO);

return imp;
}

Expand All @@ -92,12 +99,68 @@ 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];
}
moduleFloor = bid.getFloor({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend surrounding this in a try catch. Just in case the price floors module throws up. We do not want your entire request to get thrown out!

you do not have to of course, just a suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Robert, that is a good suggestion, I will add the change

mediaType: _mediaType,
size: _size
});
}

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('No floors available, no floors applied')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a prefix to this so when looking at logs it is clear where it is coming from:

utils.logInfo('IX Bid Adapter: No floors available, no floors applied')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it

}
}

/**
Expand Down Expand Up @@ -270,7 +333,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 @@ -610,16 +673,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 +724,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 +796,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 +832,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