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

Support price granularity per mediaType #2348

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') {
Copy link
Member

Choose a reason for hiding this comment

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

looks like this code was copied from the function above around line ~90. Can we combine it/make more generic?

Copy link
Contributor Author

@idettman idettman May 15, 2018

Choose a reason for hiding this comment

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

I initially had the logic for both extracted into a generic function, but the added complexity specific for mediaTypesGranularity resulted in more code than inlining with the additional conditionals

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