Skip to content

Commit b7dcdf9

Browse files
authored
Rubicon Bid Adapter FPD Update (#6122)
* Update to consolidate applying FPD to both banner and video requests. FPD will be merged using global defined FPD, ad unit FPD, and rubicon bidder param FPD. Validation logic with warning logs added * Refectored last push to: 1) Correct keywords bug 2) Revise error which looked for FPD in (user/context).ext.data as opposed to (user/context).data 3) General code cleanup * Consolidated other FPD data logic into new function * 1. Update to move pbadslot and adserver data into imp[] as opposed to parent. 2. Update to convert keywords passed through RP params to string if array found * Removed unnecessary conditional * Changed conditional to check for undefined type * Update to consolidate several lines of duplicate code into one location
1 parent 86516ab commit b7dcdf9

File tree

2 files changed

+117
-106
lines changed

2 files changed

+117
-106
lines changed

modules/rubiconBidAdapter.js

+77-83
Original file line numberDiff line numberDiff line change
@@ -254,46 +254,7 @@ export const spec = {
254254
utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain);
255255
}
256256

257-
const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context'));
258-
const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user'));
259-
if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) {
260-
const bidderData = {
261-
bidders: [ bidderRequest.bidderCode ],
262-
config: {
263-
fpd: {}
264-
}
265-
};
266-
267-
if (!utils.isEmpty(siteData)) {
268-
bidderData.config.fpd.site = siteData;
269-
}
270-
271-
if (!utils.isEmpty(userData)) {
272-
bidderData.config.fpd.user = userData;
273-
}
274-
275-
utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData);
276-
}
277-
278-
/**
279-
* Prebid AdSlot
280-
* @type {(string|undefined)}
281-
*/
282-
const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot');
283-
if (typeof pbAdSlot === 'string' && pbAdSlot) {
284-
utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot);
285-
}
286-
287-
/**
288-
* Copy GAM AdUnit and Name to imp
289-
*/
290-
['name', 'adSlot'].forEach(name => {
291-
/** @type {(string|undefined)} */
292-
const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`);
293-
if (typeof value === 'string' && value) {
294-
utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value);
295-
}
296-
});
257+
applyFPD(bidRequest, VIDEO, data);
297258

298259
// if storedAuctionResponse has been set, pass SRID
299260
if (bidRequest.storedAuctionResponse) {
@@ -547,49 +508,7 @@ export const spec = {
547508
data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent);
548509
}
549510

550-
// visitor properties
551-
const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user'));
552-
Object.keys(visitorData).forEach((key) => {
553-
if (visitorData[key] != null && key !== 'keywords') {
554-
data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key])
555-
? JSON.stringify(visitorData[key])
556-
: visitorData[key].toString(); // initialize array;
557-
}
558-
});
559-
560-
// inventory properties
561-
const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context'));
562-
Object.keys(inventoryData).forEach((key) => {
563-
if (inventoryData[key] != null && key !== 'keywords') {
564-
data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key])
565-
? JSON.stringify(inventoryData[key])
566-
: inventoryData[key].toString();
567-
}
568-
});
569-
570-
// keywords
571-
const keywords = (params.keywords || []).concat(
572-
utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [],
573-
utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []);
574-
data.kw = Array.isArray(keywords) && keywords.length ? keywords.join(',') : '';
575-
576-
/**
577-
* Prebid AdSlot
578-
* @type {(string|undefined)}
579-
*/
580-
const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot');
581-
if (typeof pbAdSlot === 'string' && pbAdSlot) {
582-
data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, '');
583-
}
584-
585-
/**
586-
* GAM Ad Unit
587-
* @type {(string|undefined)}
588-
*/
589-
const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot');
590-
if (typeof gamAdUnit === 'string' && gamAdUnit) {
591-
data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, '');
592-
}
511+
applyFPD(bidRequest, BANNER, data);
593512

594513
if (config.getConfig('coppa') === true) {
595514
data['coppa'] = 1;
@@ -949,6 +868,81 @@ function addVideoParameters(data, bidRequest) {
949868
data.imp[0].video.h = size[1]
950869
}
951870

871+
function applyFPD(bidRequest, mediaType, data) {
872+
const bidFpd = {
873+
user: {...bidRequest.params.visitor},
874+
context: {...bidRequest.params.inventory}
875+
};
876+
877+
if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords;
878+
879+
let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd);
880+
881+
const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'};
882+
let obj = {};
883+
let impData = {};
884+
let keywords = [];
885+
const validate = function(prop, key) {
886+
if (typeof prop === 'object' && !Array.isArray(prop)) {
887+
utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints');
888+
} else if (typeof prop !== 'undefined') {
889+
return (Array.isArray(prop)) ? prop.filter(value => {
890+
if (typeof value !== 'object' && typeof value !== 'undefined') return value.toString();
891+
892+
utils.logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints');
893+
}).toString() : prop.toString();
894+
}
895+
};
896+
897+
Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => {
898+
obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => {
899+
if (key === 'keywords') {
900+
if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]]
901+
902+
result[key] = fpd[type][key];
903+
904+
if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]);
905+
} else if (key === 'data') {
906+
utils.mergeDeep(result, {ext: {data: fpd[type][key]}});
907+
} else if (key === 'adServer' || key === 'pbAdSlot') {
908+
(key === 'adServer') ? ['name', 'adSlot'].forEach(name => {
909+
const value = validate(fpd[type][key][name]);
910+
if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, ''))
911+
}) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '')
912+
} else {
913+
utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}});
914+
}
915+
916+
return result;
917+
}, {});
918+
919+
if (mediaType === BANNER) {
920+
let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {};
921+
922+
Object.keys(duplicate).forEach((key) => {
923+
const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key);
924+
925+
if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val;
926+
});
927+
}
928+
});
929+
930+
Object.keys(impData).forEach((key) => {
931+
if (mediaType === BANNER) {
932+
(map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key];
933+
} else {
934+
utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}});
935+
}
936+
});
937+
938+
if (mediaType === BANNER) {
939+
let kw = validate(keywords, 'keywords');
940+
if (kw) data.kw = kw;
941+
} else {
942+
utils.mergeDeep(data, obj);
943+
}
944+
}
945+
952946
/**
953947
* @param sizes
954948
* @returns {*}

test/spec/modules/rubiconBidAdapter_spec.js

+40-23
Original file line numberDiff line numberDiff line change
@@ -826,16 +826,22 @@ describe('the rubicon adapter', function () {
826826
});
827827
});
828828

829-
it('should use first party data from getConfig over the bid params, if present', () => {
829+
it('should merge first party data from getConfig with the bid params, if present', () => {
830830
const context = {
831-
keywords: ['e', 'f'],
832-
rating: '4-star'
831+
keywords: 'e,f',
832+
rating: '4-star',
833+
data: {
834+
page: 'home'
835+
}
833836
};
834837
const user = {
835-
keywords: ['d'],
836838
gender: 'M',
837839
yob: '1984',
838-
geo: {country: 'ca'}
840+
geo: {country: 'ca'},
841+
keywords: 'd',
842+
data: {
843+
age: 40
844+
}
839845
};
840846

841847
sandbox.stub(config, 'getConfig').callsFake(key => {
@@ -849,14 +855,15 @@ describe('the rubicon adapter', function () {
849855
});
850856

851857
const expectedQuery = {
852-
'kw': 'a,b,c,d,e,f',
858+
'kw': 'a,b,c,d',
853859
'tg_v.ucat': 'new',
854860
'tg_v.lastsearch': 'iphone',
855861
'tg_v.likes': 'sports,video games',
856862
'tg_v.gender': 'M',
863+
'tg_v.age': '40',
857864
'tg_v.yob': '1984',
858-
'tg_v.geo': '{"country":"ca"}',
859-
'tg_i.rating': '4-star',
865+
'tg_i.rating': '5-star',
866+
'tg_i.page': 'home',
860867
'tg_i.prodtype': 'tech,mobile',
861868
};
862869

@@ -1865,11 +1872,17 @@ describe('the rubicon adapter', function () {
18651872
createVideoBidderRequest();
18661873

18671874
const context = {
1868-
keywords: ['e', 'f'],
1875+
data: {
1876+
page: 'home'
1877+
},
1878+
keywords: 'e,f',
18691879
rating: '4-star'
18701880
};
18711881
const user = {
1872-
keywords: ['d'],
1882+
data: {
1883+
age: 31
1884+
},
1885+
keywords: 'd',
18731886
gender: 'M',
18741887
yob: '1984',
18751888
geo: {country: 'ca'}
@@ -1885,18 +1898,22 @@ describe('the rubicon adapter', function () {
18851898
return utils.deepAccess(config, key);
18861899
});
18871900

1888-
const expected = [{
1889-
bidders: ['rubicon'],
1890-
config: {
1891-
fpd: {
1892-
site: Object.assign({}, bidderRequest.bids[0].params.inventory, context),
1893-
user: Object.assign({}, bidderRequest.bids[0].params.visitor, user)
1894-
}
1895-
}
1896-
}];
1897-
18981901
const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
1899-
expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected);
1902+
1903+
const expected = {
1904+
site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory),
1905+
user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor)
1906+
};
1907+
1908+
delete expected.site.data;
1909+
delete expected.user.data;
1910+
delete expected.site.keywords;
1911+
delete expected.user.keywords;
1912+
1913+
expect(request.data.site.keywords).to.deep.equal('a,b,c');
1914+
expect(request.data.user.keywords).to.deep.equal('d');
1915+
expect(request.data.site.ext.data).to.deep.equal(expected.site);
1916+
expect(request.data.user.ext.data).to.deep.equal(expected.user);
19001917
});
19011918

19021919
it('should include storedAuctionResponse in video bid request', function () {
@@ -1935,7 +1952,7 @@ describe('the rubicon adapter', function () {
19351952
createVideoBidderRequest();
19361953
bidderRequest.bids[0].fpd = {
19371954
context: {
1938-
adserver: {
1955+
adServer: {
19391956
adSlot: '1234567890',
19401957
name: 'adServerName1'
19411958
}
@@ -2079,7 +2096,7 @@ describe('the rubicon adapter', function () {
20792096
it('should not fail if keywords param is not an array', function () {
20802097
bidderRequest.bids[0].params.keywords = 'a,b,c';
20812098
const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest);
2082-
expect(slotParams.kw).to.equal('');
2099+
expect(slotParams.kw).to.equal('a,b,c');
20832100
});
20842101
});
20852102

0 commit comments

Comments
 (0)