Skip to content

Commit

Permalink
Support price granularity per mediaType (prebid#2348)
Browse files Browse the repository at this point in the history
* Add config support for mediaTypePriceGranularity

* Update to set default granularity to use config default

* Formatting change, comment updated

* Formatting change

* Added test for mediaTypePriceGranularity enabled

* Added test for mediaTypePriceGranularity video type

* Changed double quotes to single in config_spec

* Linting fixes for indentation and spacing
  • Loading branch information
idettman authored and mkendall07 committed May 16, 2018
1 parent ada85ba commit 82fdbf6
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 4 deletions.
14 changes: 10 additions & 4 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,12 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) {
bidObject.renderer.setRender(adUnitRenderer.render);
}

// Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket'
const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${bid.mediaType}`);

const priceStringsObj = getPriceBucketString(
bidObject.cpm,
config.getConfig('customPriceBucket'),
(typeof mediaTypeGranularity === 'object') ? mediaTypeGranularity : config.getConfig('customPriceBucket'),
config.getConfig('currency.granularityMultiplier')
);
bidObject.pbLg = priceStringsObj.low;
Expand All @@ -330,8 +333,11 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) {
return bidObject;
}

export function getStandardBidderSettings() {
let granularity = config.getConfig('priceGranularity');
export function getStandardBidderSettings(mediaType) {
// Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity'
const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`);
const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity');

let bidder_settings = $$PREBID_GLOBAL$$.bidderSettings;
if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) {
bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = {};
Expand Down Expand Up @@ -404,7 +410,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj) {
// 1) set the keys from "standard" setting or from prebid defaults
if (bidder_settings) {
// initialize default if not set
const standardSettings = getStandardBidderSettings();
const standardSettings = getStandardBidderSettings(custBidObj.mediaType);
setKeys(keyValues, standardSettings, custBidObj);

// 2) set keys from specific bidder setting override if they exist
Expand Down
20 changes: 20 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ export function newConfig() {
return this._customPriceBucket;
},

_mediaTypePriceGranularity: {},
get mediaTypePriceGranularity() {
return this._mediaTypePriceGranularity;
},
set mediaTypePriceGranularity(val) {
this._mediaTypePriceGranularity = Object.keys(val).reduce((aggregate, item) => {
if (validatePriceGranularity(val[item])) {
if (typeof val === 'string') {
aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity;
} else if (typeof val === 'object') {
aggregate[item] = val[item];
utils.logMessage(`Using custom price granularity for ${item}`);
}
} else {
utils.logWarn(`Invalid price granularity for media type: ${item}`);
}
return aggregate;
}, {});
},

_sendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS,
get enableSendAllBids() {
return this._sendAllBids;
Expand Down
23 changes: 23 additions & 0 deletions test/spec/config_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,29 @@ describe('config API', () => {
expect(getConfig('priceGranularity')).to.be.equal('low');
});

it('set mediaTypePriceGranularity', () => {
const customPriceGranularity = {
'buckets': [{
'min': 0,
'max': 3,
'increment': 0.01,
'cap': true
}]
};
setConfig({
'mediaTypePriceGranularity': {
'banner': 'medium',
'video': customPriceGranularity,
'native': 'medium'
}
});

const configResult = getConfig('mediaTypePriceGranularity');
expect(configResult.banner).to.be.equal('medium');
expect(configResult.video).to.be.equal(customPriceGranularity);
expect(configResult.native).to.be.equal('medium');
});

it('sets priceGranularity and customPriceBucket', () => {
const goodConfig = {
'buckets': [{
Expand Down
254 changes: 254 additions & 0 deletions test/spec/unit/pbjs_api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,260 @@ describe('Unit: Prebid Module', function () {
});
});

describe('getAdserverTargeting with `mediaTypePriceGranularity` set for media type', function() {
let currentPriceBucket;
let auction;
let ajaxStub;
let response;
let cbTimeout = 3000;
let auctionManagerInstance;
let targeting;

const bannerResponse = {
'version': '0.0.1',
'tags': [{
'uuid': '4d0a6829338a07',
'tag_id': 4799418,
'auction_id': '2256922143947979797',
'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad',
'timeout_ms': 2500,
'ads': [{
'content_source': 'rtb',
'ad_type': 'banner',
'buyer_member_id': 958,
'creative_id': 33989846,
'media_type_id': 1,
'media_subtype_id': 1,
'cpm': 1.99,
'cpm_publisher_currency': 0.500000,
'publisher_currency_code': '$',
'client_initiated_ad_counting': true,
'rtb': {
'banner': {
'width': 300,
'height': 250,
'content': '<!-- Creative -->'
},
'trackers': [{
'impression_urls': ['http://lax1-ib.adnxs.com/impression']
}]
}
}]
}]
};
const videoResponse = {
'version': '0.0.1',
'tags': [{
'uuid': '4d0a6829338a07',
'tag_id': 4799418,
'auction_id': '2256922143947979797',
'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad',
'timeout_ms': 2500,
'ads': [{
'content_source': 'rtb',
'ad_type': 'video',
'buyer_member_id': 958,
'creative_id': 33989846,
'media_type_id': 1,
'media_subtype_id': 1,
'cpm': 1.99,
'cpm_publisher_currency': 0.500000,
'publisher_currency_code': '$',
'client_initiated_ad_counting': true,
'rtb': {
'video': {
'width': 300,
'height': 250,
'content': '<!-- Creative -->'
},
'trackers': [{
'impression_urls': ['http://lax1-ib.adnxs.com/impression']
}]
}
}]
}]
};

const createAdUnit = (code, mediaTypes) => {
if (!mediaTypes) {
mediaTypes = ['banner'];
} else if (typeof mediaTypes === 'string') {
mediaTypes = [mediaTypes];
}

const adUnit = {
code: code,
sizes: [[300, 250], [300, 600]],
bids: [{
bidder: 'appnexus',
params: {
placementId: '10433394'
}
}]
};

let _mediaTypes = {};
if (mediaTypes.indexOf('banner') !== -1) {
_mediaTypes['banner'] = {
'banner': {}
};
}
if (mediaTypes.indexOf('video') !== -1) {
_mediaTypes['video'] = {
'video': {
context: 'instream',
playerSize: [300, 250]
}
};
}
if (mediaTypes.indexOf('native') !== -1) {
_mediaTypes['native'] = {
'native': {}
};
}

if (Object.keys(_mediaTypes).length > 0) {
adUnit['mediaTypes'] = _mediaTypes;
// if video type, add video to every bid.param object
if (_mediaTypes.video) {
adUnit.bids.forEach(bid => {
bid.params['video'] = {
width: 300,
height: 250,
vastUrl: '',
ttl: 3600
};
});
}
}
return adUnit;
}
const initTestConfig = (data) => {
$$PREBID_GLOBAL$$.bidderSettings = {};

ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() {
return function(url, callback) {
const fakeResponse = sinon.stub();
fakeResponse.returns('headerContent');
callback.success(JSON.stringify(response), { getResponseHeader: fakeResponse });
}
});
auctionManagerInstance = newAuctionManager();
targeting = newTargeting(auctionManagerInstance)

configObj.setConfig({
'priceGranularity': {
'buckets': [
{ 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 },
{ 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05 },
{ 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 },
{ 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 }
]
},
'mediaTypePriceGranularity': {
'banner': {
'buckets': [
{ 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.25 },
{ 'precision': 2, 'min': 6, 'max': 20, 'increment': 0.5 },
{ 'precision': 2, 'min': 21, 'max': 100, 'increment': 1 }
]
},
'video': 'low',
'native': 'high'
}
});

auction = auctionManagerInstance.createAuction({
adUnits: data.adUnits,
adUnitCodes: data.adUnitCodes
});
};

before(() => {
currentPriceBucket = configObj.getConfig('priceGranularity');
sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{
'bidderCode': 'appnexus',
'auctionId': '20882439e3238c',
'bidderRequestId': '331f3cf3f1d9c8',
'bids': [
{
'bidder': 'appnexus',
'params': {
'placementId': '10433394'
},
'adUnitCode': 'div-gpt-ad-1460505748561-0',
'sizes': [
[
300,
250
],
[
300,
600
]
],
'bidId': '4d0a6829338a07',
'bidderRequestId': '331f3cf3f1d9c8',
'auctionId': '20882439e3238c'
}
],
'auctionStart': 1505250713622,
'timeout': 3000
}]));
});

after(() => {
configObj.setConfig({ priceGranularity: currentPriceBucket });
adaptermanager.makeBidRequests.restore();
})

afterEach(() => {
ajaxStub.restore();
});

it('should get correct hb_pb with cpm between 0 - 5', () => {
initTestConfig({
adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')],
adUnitCodes: ['div-gpt-ad-1460505748561-0']
});

response = bannerResponse;
response.tags[0].ads[0].cpm = 3.4288;

auction.callBids(cbTimeout);
let bidTargeting = targeting.getAllTargeting();
expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.25');
});

it('should get correct hb_pb with cpm between 21 - 100', () => {
initTestConfig({
adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')],
adUnitCodes: ['div-gpt-ad-1460505748561-0']
});

response = bannerResponse;
response.tags[0].ads[0].cpm = 43.4288;

auction.callBids(cbTimeout);
let bidTargeting = targeting.getAllTargeting();
expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('43.00');
});

it('should only apply price granularity if bid media type matches', () => {
initTestConfig({
adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ],
adUnitCodes: ['div-gpt-ad-1460505748561-0']
});

response = videoResponse;
response.tags[0].ads[0].cpm = 3.4288;

auction.callBids(cbTimeout);
let bidTargeting = targeting.getAllTargeting();
expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.00');
});
});

describe('getBidResponses', function () {
it('should return expected bid responses when not passed an adunitCode', function () {
var result = $$PREBID_GLOBAL$$.getBidResponses();
Expand Down

0 comments on commit 82fdbf6

Please sign in to comment.