From 7680ffcbd2fa5c3ee028499c90c608a6beb4f292 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Fri, 11 Dec 2020 13:48:56 -0500 Subject: [PATCH 1/7] 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 --- modules/rubiconBidAdapter.js | 91 +++++++++++---------- test/spec/modules/rubiconBidAdapter_spec.js | 63 +++++++++----- 2 files changed, 92 insertions(+), 62 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index b44ae108b38..5d04bf2dde1 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -254,26 +254,12 @@ export const spec = { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); - if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { - const bidderData = { - bidders: [ bidderRequest.bidderCode ], - config: { - fpd: {} - } - }; - - if (!utils.isEmpty(siteData)) { - bidderData.config.fpd.site = siteData; - } - - if (!utils.isEmpty(userData)) { - bidderData.config.fpd.user = userData; - } + const bidFpd = { + user: bidRequest.params.visitor || {}, + context: utils.mergeDeep({}, bidRequest.params.inventory || {}, {keywords: (bidRequest.params.keywords || '')}) + }; - utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); - } + validateFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); /** * Prebid AdSlot @@ -547,31 +533,12 @@ export const spec = { data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); } - // visitor properties - const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user')); - Object.keys(visitorData).forEach((key) => { - if (visitorData[key] != null && key !== 'keywords') { - data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key]) - ? JSON.stringify(visitorData[key]) - : visitorData[key].toString(); // initialize array; - } - }); - - // inventory properties - const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context')); - Object.keys(inventoryData).forEach((key) => { - if (inventoryData[key] != null && key !== 'keywords') { - data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key]) - ? JSON.stringify(inventoryData[key]) - : inventoryData[key].toString(); - } - }); + const bidFpd = { + user: bidRequest.params.visitor || {}, + context: utils.mergeDeep({}, bidRequest.params.inventory || {}, {keywords: (bidRequest.params.keywords || '')}) + }; - // keywords - const keywords = (params.keywords || []).concat( - utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [], - utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []); - data.kw = Array.isArray(keywords) && keywords.length ? keywords.join(',') : ''; + validateFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); /** * Prebid AdSlot @@ -949,6 +916,44 @@ function addVideoParameters(data, bidRequest) { data.imp[0].video.h = size[1] } +function validateFPD(fpd, mediaType, data) { + const map = {user: {banner: 'tg_v.', video: 'user'}, context: {banner: 'tg_i.', video: 'site'}}; + + Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { + if (utils.deepAccess(fpd, `${type}.ext.data`) && mediaType === BANNER) { + utils.mergeDeep(fpd, {[type]: utils.deepAccess(fpd, `${type}.ext.data`)}); + } + + if (mediaType === BANNER && utils.deepAccess(fpd, 'user.keywords')) { + utils.mergeDeep(fpd, {context: {keywords: utils.deepAccess(fpd, 'user.keywords')}}); + delete fpd.user.keywords; + } + + Object.keys(fpd[type]).filter(value => fpd[type][value] && value !== 'ext').forEach((key) => { + if (mediaType === BANNER) { + if (typeof fpd[type][key] === 'object' && !Array.isArray(fpd[type][key])) { + utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); + } else { + data[(key === 'keywords' && type === 'context' ? 'kw' : `${map[type][mediaType]}${key}`)] = (Array.isArray(fpd[type][key])) ? fpd[type][key].filter(value => { + if (typeof value !== 'object') return value; + + utils.logWarn('Rubicon: Filtered data value: ', value, ': Expected value to be string, integer, or an array of strings/ints'); + }).toString() : fpd[type][key].toString(); + } + } else { + if (key !== 'keywords') { + utils.mergeDeep(fpd[type], {ext: {data: {[key]: fpd[type][key]}}}); + delete fpd[type][key]; + } + } + }); + + if (mediaType === VIDEO) utils.mergeDeep(data, {[map[type][mediaType]]: fpd[type]}); + }); + + return data; +} + /** * @param sizes * @returns {*} diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6659c281c33..e0df3932926 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -824,16 +824,26 @@ describe('the rubicon adapter', function () { }); }); - it('should use first party data from getConfig over the bid params, if present', () => { + it('should merge first party data from getConfig with the bid params, if present', () => { const context = { keywords: ['e', 'f'], - rating: '4-star' + rating: '4-star', + ext: { + data: { + page: 'home' + } + } }; const user = { - keywords: ['d'], gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + keywords: ['d'], + ext: { + data: { + age: 40 + } + } }; sandbox.stub(config, 'getConfig').callsFake(key => { @@ -847,14 +857,15 @@ describe('the rubicon adapter', function () { }); const expectedQuery = { - 'kw': 'a,b,c,d,e,f', + 'kw': 'e,f,a,b,c,d', 'tg_v.ucat': 'new', 'tg_v.lastsearch': 'iphone', 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', + 'tg_v.age': '40', 'tg_v.yob': '1984', - 'tg_v.geo': '{"country":"ca"}', - 'tg_i.rating': '4-star', + 'tg_i.rating': '5-star', + 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1827,10 +1838,20 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); const context = { + ext: { + data: { + page: 'home' + } + }, keywords: ['e', 'f'], rating: '4-star' }; const user = { + ext: { + data: { + age: 31 + } + }, keywords: ['d'], gender: 'M', yob: '1984', @@ -1847,18 +1868,22 @@ describe('the rubicon adapter', function () { return utils.deepAccess(config, key); }); - const expected = [{ - bidders: ['rubicon'], - config: { - fpd: { - site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), - user: Object.assign({}, bidderRequest.bids[0].params.visitor, user) - } - } - }]; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected); + + const expected = { + site: Object.assign({}, context, context.ext.data, bidderRequest.bids[0].params.inventory), + user: Object.assign({}, user, user.ext.data, bidderRequest.bids[0].params.visitor) + }; + + delete expected.site.ext; + delete expected.user.ext; + delete expected.site.keywords; + delete expected.user.keywords; + + expect(request.data.site.keywords).to.deep.equal(['e', 'f', 'a', 'b', 'c']); + expect(request.data.user.keywords).to.deep.equal(['d']); + expect(request.data.site.ext.data).to.deep.equal(expected.site); + expect(request.data.user.ext.data).to.deep.equal(expected.user); }); it('should include storedAuctionResponse in video bid request', function () { @@ -2042,7 +2067,7 @@ describe('the rubicon adapter', function () { it('should not fail if keywords param is not an array', function () { bidderRequest.bids[0].params.keywords = 'a,b,c'; const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); - expect(slotParams.kw).to.equal(''); + expect(slotParams.kw).to.equal('a,b,c'); }); }); From 924fc817f910fbff997e053fc4e73d40136b955e Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Wed, 16 Dec 2020 10:29:45 -0500 Subject: [PATCH 2/7] 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 --- modules/rubiconBidAdapter.js | 79 +++++++++++++-------- test/spec/modules/rubiconBidAdapter_spec.js | 32 ++++----- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 5d04bf2dde1..d18acbcf1d9 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -256,10 +256,12 @@ export const spec = { const bidFpd = { user: bidRequest.params.visitor || {}, - context: utils.mergeDeep({}, bidRequest.params.inventory || {}, {keywords: (bidRequest.params.keywords || '')}) + context: bidRequest.params.inventory || {} }; - validateFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); + if (bidRequest.params.keywords) bidFpd.context.keywords = bidRequest.params.keywords; + + applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); /** * Prebid AdSlot @@ -535,10 +537,12 @@ export const spec = { const bidFpd = { user: bidRequest.params.visitor || {}, - context: utils.mergeDeep({}, bidRequest.params.inventory || {}, {keywords: (bidRequest.params.keywords || '')}) + context: bidRequest.params.inventory || {} }; - validateFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); + if (bidRequest.params.keywords) bidFpd.context.keywords = bidRequest.params.keywords; + + applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); /** * Prebid AdSlot @@ -916,42 +920,55 @@ function addVideoParameters(data, bidRequest) { data.imp[0].video.h = size[1] } -function validateFPD(fpd, mediaType, data) { - const map = {user: {banner: 'tg_v.', video: 'user'}, context: {banner: 'tg_i.', video: 'site'}}; - - Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - if (utils.deepAccess(fpd, `${type}.ext.data`) && mediaType === BANNER) { - utils.mergeDeep(fpd, {[type]: utils.deepAccess(fpd, `${type}.ext.data`)}); +function applyFPD(fpd, mediaType, data) { + const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}}; + let obj = {}; + let keywords = []; + const validate = function(e, t) { + if (typeof e === 'object' && !Array.isArray(e)) { + utils.logWarn('Rubicon: Filtered FPD key: ', t, ': Expected value to be string, integer, or an array of strings/ints'); + } else if (e) { + return (Array.isArray(e)) ? e.filter(value => { + if (typeof value !== 'object' && value) return value; + + utils.logWarn('Rubicon: Filtered value: ', value, 'for key', t, ': Expected value to be string, integer, or an array of strings/ints'); + }).toString() : e.toString(); } + }; - if (mediaType === BANNER && utils.deepAccess(fpd, 'user.keywords')) { - utils.mergeDeep(fpd, {context: {keywords: utils.deepAccess(fpd, 'user.keywords')}}); - delete fpd.user.keywords; - } + Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { + obj[map[type].code] = Object.keys(fpd[type]).filter(value => fpd[type][value]).reduce((result, key) => { + if (key === 'keywords') { + if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] - Object.keys(fpd[type]).filter(value => fpd[type][value] && value !== 'ext').forEach((key) => { - if (mediaType === BANNER) { - if (typeof fpd[type][key] === 'object' && !Array.isArray(fpd[type][key])) { - utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); - } else { - data[(key === 'keywords' && type === 'context' ? 'kw' : `${map[type][mediaType]}${key}`)] = (Array.isArray(fpd[type][key])) ? fpd[type][key].filter(value => { - if (typeof value !== 'object') return value; + result[key] = fpd[type][key]; - utils.logWarn('Rubicon: Filtered data value: ', value, ': Expected value to be string, integer, or an array of strings/ints'); - }).toString() : fpd[type][key].toString(); - } + if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); + } else if (key === 'data') { + utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); } else { - if (key !== 'keywords') { - utils.mergeDeep(fpd[type], {ext: {data: {[key]: fpd[type][key]}}}); - delete fpd[type][key]; - } + utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); } - }); - if (mediaType === VIDEO) utils.mergeDeep(data, {[map[type][mediaType]]: fpd[type]}); + return result; + }, {}); + + if (mediaType === BANNER) { + let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; + Object.keys(duplicate).forEach((key) => { + const val = validate(duplicate[key], key); + + if (val) data[`${map[type][BANNER]}${key}`] = val; + }); + } }); - return data; + if (mediaType === BANNER) { + let kw = validate(keywords, 'keywords'); + if (kw) data.kw = kw; + } else { + utils.mergeDeep(data, obj); + } } /** diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e0df3932926..2c2b8707e35 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -828,10 +828,8 @@ describe('the rubicon adapter', function () { const context = { keywords: ['e', 'f'], rating: '4-star', - ext: { - data: { - page: 'home' - } + data: { + page: 'home' } }; const user = { @@ -839,10 +837,8 @@ describe('the rubicon adapter', function () { yob: '1984', geo: {country: 'ca'}, keywords: ['d'], - ext: { - data: { - age: 40 - } + data: { + age: 40 } }; @@ -1838,19 +1834,15 @@ describe('the rubicon adapter', function () { createVideoBidderRequest(); const context = { - ext: { - data: { - page: 'home' - } + data: { + page: 'home' }, keywords: ['e', 'f'], rating: '4-star' }; const user = { - ext: { - data: { - age: 31 - } + data: { + age: 31 }, keywords: ['d'], gender: 'M', @@ -1871,12 +1863,12 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const expected = { - site: Object.assign({}, context, context.ext.data, bidderRequest.bids[0].params.inventory), - user: Object.assign({}, user, user.ext.data, bidderRequest.bids[0].params.visitor) + site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), + user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) }; - delete expected.site.ext; - delete expected.user.ext; + delete expected.site.data; + delete expected.user.data; delete expected.site.keywords; delete expected.user.keywords; From 5780fd4ee87feaf8d5d2c6ce35a410cf27bbf81f Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Wed, 16 Dec 2020 15:39:07 -0500 Subject: [PATCH 3/7] Consolidated other FPD data logic into new function --- modules/rubiconBidAdapter.js | 50 ++++----------------- test/spec/modules/rubiconBidAdapter_spec.js | 8 ++-- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index d18acbcf1d9..283a514ebeb 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -263,26 +263,6 @@ export const spec = { applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value); - } - }); - // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { utils.deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString()); @@ -544,24 +524,6 @@ export const spec = { applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, ''); - } - - /** - * GAM Ad Unit - * @type {(string|undefined)} - */ - const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot'); - if (typeof gamAdUnit === 'string' && gamAdUnit) { - data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, ''); - } - if (config.getConfig('coppa') === true) { data['coppa'] = 1; } @@ -921,7 +883,7 @@ function addVideoParameters(data, bidRequest) { } function applyFPD(fpd, mediaType, data) { - const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}}; + const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; let obj = {}; let keywords = []; const validate = function(e, t) { @@ -946,6 +908,12 @@ function applyFPD(fpd, mediaType, data) { if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); } else if (key === 'data') { utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); + } else if (key === 'adServer' || key === 'pbAdSlot') { + (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { + const value = validate(fpd[type][key][name]); + + if (value) utils.mergeDeep(result, {ext: {data: {adserver: {[name.toLowerCase()]: value.replace(/^\/+/, '')}}}}); + }) : utils.mergeDeep(result, {ext: {data: {[key.toLowerCase()]: fpd[type][key].replace(/^\/+/, '')}}}); } else { utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); } @@ -956,9 +924,9 @@ function applyFPD(fpd, mediaType, data) { if (mediaType === BANNER) { let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; Object.keys(duplicate).forEach((key) => { - const val = validate(duplicate[key], key); + const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); - if (val) data[`${map[type][BANNER]}${key}`] = val; + if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val; }); } }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 2c2b8707e35..4182322a148 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1907,14 +1907,14 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.site.ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); bidderRequest.bids[0].fpd = { context: { - adserver: { + adServer: { adSlot: '1234567890', name: 'adServerName1' } @@ -1926,8 +1926,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + expect(request.data.site.ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.site.ext.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { From 030da98f10ff8ef8fbbe89afa8ec9c838a8fde00 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Thu, 21 Jan 2021 07:58:56 -0500 Subject: [PATCH 4/7] 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 --- modules/rubiconBidAdapter.js | 47 ++++++++++++--------- test/spec/modules/rubiconBidAdapter_spec.js | 20 ++++----- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 80a31f4a811..4532d6a560b 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -255,11 +255,11 @@ export const spec = { } const bidFpd = { - user: bidRequest.params.visitor || {}, - context: bidRequest.params.inventory || {} + user: {...bidRequest.params.visitor} || {}, + context: {...bidRequest.params.inventory} || {} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = bidRequest.params.keywords; + if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); @@ -516,11 +516,11 @@ export const spec = { } const bidFpd = { - user: bidRequest.params.visitor || {}, - context: bidRequest.params.inventory || {} + user: {...bidRequest.params.visitor} || {}, + context: {...bidRequest.params.inventory} || {} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = bidRequest.params.keywords; + if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); @@ -885,21 +885,22 @@ function addVideoParameters(data, bidRequest) { function applyFPD(fpd, mediaType, data) { const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; let obj = {}; + let impData = {}; let keywords = []; - const validate = function(e, t) { - if (typeof e === 'object' && !Array.isArray(e)) { - utils.logWarn('Rubicon: Filtered FPD key: ', t, ': Expected value to be string, integer, or an array of strings/ints'); - } else if (e) { - return (Array.isArray(e)) ? e.filter(value => { - if (typeof value !== 'object' && value) return value; - - utils.logWarn('Rubicon: Filtered value: ', value, 'for key', t, ': Expected value to be string, integer, or an array of strings/ints'); - }).toString() : e.toString(); + const validate = function(prop, key) { + if (typeof prop === 'object' && !Array.isArray(prop)) { + utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); + } else if (utils.isNumber(prop) || prop) { + return (Array.isArray(prop)) ? prop.filter(value => { + if (typeof value !== 'object' && (utils.isNumber(value) || value)) return value.toString(); + + utils.logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints'); + }).toString() : prop.toString(); } }; Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => fpd[type][value]).reduce((result, key) => { + obj[map[type].code] = Object.keys(fpd[type]).filter(value => utils.isNumber(fpd[type][value]) || fpd[type][value]).reduce((result, key) => { if (key === 'keywords') { if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] @@ -911,9 +912,8 @@ function applyFPD(fpd, mediaType, data) { } else if (key === 'adServer' || key === 'pbAdSlot') { (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { const value = validate(fpd[type][key][name]); - - if (value) utils.mergeDeep(result, {ext: {data: {adserver: {[name.toLowerCase()]: value.replace(/^\/+/, '')}}}}); - }) : utils.mergeDeep(result, {ext: {data: {[key.toLowerCase()]: fpd[type][key].replace(/^\/+/, '')}}}); + if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, '')) + }) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '') } else { utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); } @@ -923,6 +923,7 @@ function applyFPD(fpd, mediaType, data) { if (mediaType === BANNER) { let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; + Object.keys(duplicate).forEach((key) => { const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); @@ -931,6 +932,14 @@ function applyFPD(fpd, mediaType, data) { } }); + Object.keys(impData).forEach((key) => { + if (mediaType === BANNER) { + (map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key]; + } else { + utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}}); + } + }); + if (mediaType === BANNER) { let kw = validate(keywords, 'keywords'); if (kw) data.kw = kw; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 31e77ee3b28..8c25d97dada 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -828,7 +828,7 @@ describe('the rubicon adapter', function () { it('should merge first party data from getConfig with the bid params, if present', () => { const context = { - keywords: ['e', 'f'], + keywords: 'e,f', rating: '4-star', data: { page: 'home' @@ -838,7 +838,7 @@ describe('the rubicon adapter', function () { gender: 'M', yob: '1984', geo: {country: 'ca'}, - keywords: ['d'], + keywords: 'd', data: { age: 40 } @@ -855,7 +855,7 @@ describe('the rubicon adapter', function () { }); const expectedQuery = { - 'kw': 'e,f,a,b,c,d', + 'kw': 'a,b,c,d', 'tg_v.ucat': 'new', 'tg_v.lastsearch': 'iphone', 'tg_v.likes': 'sports,video games', @@ -1875,14 +1875,14 @@ describe('the rubicon adapter', function () { data: { page: 'home' }, - keywords: ['e', 'f'], + keywords: 'e,f', rating: '4-star' }; const user = { data: { age: 31 }, - keywords: ['d'], + keywords: 'd', gender: 'M', yob: '1984', geo: {country: 'ca'} @@ -1910,8 +1910,8 @@ describe('the rubicon adapter', function () { delete expected.site.keywords; delete expected.user.keywords; - expect(request.data.site.keywords).to.deep.equal(['e', 'f', 'a', 'b', 'c']); - expect(request.data.user.keywords).to.deep.equal(['d']); + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); expect(request.data.site.ext.data).to.deep.equal(expected.site); expect(request.data.user.ext.data).to.deep.equal(expected.user); }); @@ -1945,7 +1945,7 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.site.ext.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { @@ -1964,8 +1964,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.site.ext.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.site.ext.data.adserver.name).to.equal('adServerName1'); + expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { From 330dbe35214ec67ff5739278c48a0960ec40d678 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Thu, 21 Jan 2021 10:13:56 -0500 Subject: [PATCH 5/7] Removed unnecessary conditional --- modules/rubiconBidAdapter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 4532d6a560b..0f126be32fc 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -255,8 +255,8 @@ export const spec = { } const bidFpd = { - user: {...bidRequest.params.visitor} || {}, - context: {...bidRequest.params.inventory} || {} + user: {...bidRequest.params.visitor}, + context: {...bidRequest.params.inventory} }; if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; @@ -516,8 +516,8 @@ export const spec = { } const bidFpd = { - user: {...bidRequest.params.visitor} || {}, - context: {...bidRequest.params.inventory} || {} + user: {...bidRequest.params.visitor}, + context: {...bidRequest.params.inventory} }; if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; From 151b55bd831db9d1e9b637d67f34670677229d9a Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Thu, 21 Jan 2021 10:23:14 -0500 Subject: [PATCH 6/7] Changed conditional to check for undefined type --- modules/rubiconBidAdapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 0f126be32fc..c740b3a63b2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -890,9 +890,9 @@ function applyFPD(fpd, mediaType, data) { const validate = function(prop, key) { if (typeof prop === 'object' && !Array.isArray(prop)) { utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); - } else if (utils.isNumber(prop) || prop) { + } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { - if (typeof value !== 'object' && (utils.isNumber(value) || value)) return value.toString(); + if (typeof value !== 'object' && typeof value !== 'undefined') return value.toString(); utils.logWarn('Rubicon: Filtered value: ', value, 'for key', key, ': Expected value to be string, integer, or an array of strings/ints'); }).toString() : prop.toString(); @@ -900,7 +900,7 @@ function applyFPD(fpd, mediaType, data) { }; Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => utils.isNumber(fpd[type][value]) || fpd[type][value]).reduce((result, key) => { + obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => { if (key === 'keywords') { if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] From 21a2471225926035999d969c8daa1b778e44aa00 Mon Sep 17 00:00:00 2001 From: Michael Moschovas Date: Wed, 3 Feb 2021 15:33:10 -0500 Subject: [PATCH 7/7] Update to consolidate several lines of duplicate code into one location --- modules/rubiconBidAdapter.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c740b3a63b2..395b7a693b2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -254,14 +254,7 @@ export const spec = { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} - }; - - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - - applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), VIDEO, data); + applyFPD(bidRequest, VIDEO, data); // if storedAuctionResponse has been set, pass SRID if (bidRequest.storedAuctionResponse) { @@ -515,14 +508,7 @@ export const spec = { data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); } - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} - }; - - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - - applyFPD(utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd), BANNER, data); + applyFPD(bidRequest, BANNER, data); if (config.getConfig('coppa') === true) { data['coppa'] = 1; @@ -882,7 +868,16 @@ function addVideoParameters(data, bidRequest) { data.imp[0].video.h = size[1] } -function applyFPD(fpd, mediaType, data) { +function applyFPD(bidRequest, mediaType, data) { + const bidFpd = { + user: {...bidRequest.params.visitor}, + context: {...bidRequest.params.inventory} + }; + + if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + + let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd); + const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; let obj = {}; let impData = {};