From 5bb9b24f3f907a63013398000a9236afcbacb18d Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 27 Oct 2022 08:06:35 +0200 Subject: [PATCH 01/11] add support for native --- modules/rubiconBidAdapter.js | 574 +++++++++----------- test/spec/modules/rubiconBidAdapter_spec.js | 320 +++++++++-- 2 files changed, 535 insertions(+), 359 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 254799b995d..64c6c093223 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -15,10 +15,12 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; import {Renderer} from '../src/Renderer.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; @@ -136,17 +138,143 @@ var sizeMap = { 578: '980x552', 580: '505x656' }; + +/** + * This flag enables all bids (banner, native, video) to be sent to Prebid Server. + * when rubiconf.multiformat !== MULTIFORMAT_PBS, video (and native) bids go to PBS while + * banner will go to fastlane. + */ +const MULTIFORMAT_PBS = 'pbs'; + _each(sizeMap, (item, key) => sizeMap[item] = key); +const hasBannerMediaTypeOnly = bidRequest => { + const bidRequestType = bidType(bidRequest); + return (bidRequestType.includes(BANNER) && bidRequestType.length == 1); +}; + +const hasBannerMediaType = bidRequest => { + const bidRequestType = bidType(bidRequest); + return (bidRequestType.includes(BANNER)) +} + +export const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const {bidRequests} = context; + const data = buildRequest(imps, bidderRequest, context); + data.test = config.getConfig('debug') ? 1 : 0; + deepSetValue(data, 'ext.prebid.cache', { + vastxml: { + returnCreative: rubiConf.returnVast === true + } + }); + + deepSetValue(data, 'ext.prebid.bidders', { + rubicon: { + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION + } + }); + + deepSetValue(data, 'ext.prebid.targeting.pricegranularity', getPriceGranularity(config)); + + let modules = (getGlobal()).installedModules; + if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { + deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); + } + + addOrtbFirstPartyData(data, bidRequests); + + delete data?.ext?.prebid?.storedrequest; + + // floors + if (rubiConf.disableFloors === true) { + delete data.ext.prebid.floors; + } + + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + const haveFloorDataBidRequests = bidRequests.filter(bidRequest => typeof bidRequest.floorData === 'object'); + if (haveFloorDataBidRequests.length > 0) { + data.ext.prebid.floors = { enabled: false }; + } + // multibid + const multibid = config.getConfig('multibid'); + addMultibid(data, multibid); + + const aliasedBidRequests = bidRequests + // get all bidRequests that are using an alias + .filter(bidRequest => bidRequest.bidder !== spec.code) + // transform those bidRequests in + // { + // 'alias1': rubicon, + // 'alias2': rubicon, + // } + .reduce((acc, bidRequest) => { + return { + ...acc, + [bidRequest.bidder]: spec.code + } + }, {}); + if (Object.keys(aliasedBidRequests).length > 0) { + data.ext.prebid.aliases = aliasedBidRequests; + } + + return data; + }, + imp(buildImp, bidRequest, context) { + // skip banner-only requests if multiformat === 'pbs' + if (rubiConf.multiformat !== MULTIFORMAT_PBS && hasBannerMediaTypeOnly(bidRequest)) { + return; + } + const imp = buildImp(bidRequest, context); + imp.id = bidRequest.adUnitCode; + if (rubiConf.multiformat !== MULTIFORMAT_PBS && imp.banner) { + delete imp.banner; + } + if (config.getConfig('s2sConfig.defaultTtl')) { + imp.exp = config.getConfig('s2sConfig.defaultTtl'); + }; + bidRequest.params.position === 'atf' && (imp.video.pos = 1); + bidRequest.params.position === 'btf' && (imp.video.pos = 3); + delete imp.ext?.prebid?.storedrequest; + + setBidFloors(bidRequest, imp); + + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.prebid.type'); + const bidResponse = buildBidResponse(bid, context); + bidResponse.meta.mediaType = context.mediaType; + const {bidRequest} = context; + if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = outstreamRenderer(bidResponse); + } + bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'); + bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'); + + if (deepAccess(bid, 'ext.bidder.rp.advid')) { + deepSetValue(bidResponse, 'meta.advertiserId', bid.ext.bidder.rp.advid); + } + return bidResponse; + }, + context: { + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true + ttl: 300, + currency: 'USD' + }, + processors: pbsExtensions +}); + export const spec = { code: 'rubicon', gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param {object} bid * @return boolean */ isBidRequestValid: function (bid) { + let valid = true; if (typeof bid.params !== 'object') { return false; } @@ -158,15 +286,16 @@ export const spec = { return false } } - let bidFormat = bidType(bid, true); + let bidFormats = bidType(bid, true); // bidType is undefined? Return false - if (!bidFormat) { + if (!bidFormats.length) { return false; - } else if (bidFormat === 'video') { // bidType is video, make sure it has required params - return hasValidVideoParams(bid); + } else if (bidFormats.includes(VIDEO)) { // bidType is video, make sure it has required params + valid = hasValidVideoParams(bid); } - // bidType is banner? return true - return true; + const hasBannerOrNativeMediaType = [BANNER, NATIVE].filter(mediaType => bidFormats.includes(mediaType)).length > 0; + if (!hasBannerOrNativeMediaType) return valid; + return valid && hasBannerOrNativeMediaType; }, /** * @param {BidRequest[]} bidRequests @@ -176,166 +305,33 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { // separate video bids because the requests are structured differently let requests = []; - const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video').map(bidRequest => { - bidRequest.startTime = new Date().getTime(); - - const data = { - id: bidRequest.transactionId, - test: config.getConfig('debug') ? 1 : 0, - cur: ['USD'], - source: { - tid: bidRequest.transactionId - }, - tmax: bidderRequest.timeout, - imp: [{ - exp: config.getConfig('s2sConfig.defaultTtl'), - id: bidRequest.adUnitCode, - secure: 1, - ext: { - [bidRequest.bidder]: bidRequest.params - }, - video: deepAccess(bidRequest, 'mediaTypes.video') || {} - }], - ext: { - prebid: { - channel: { - name: 'pbjs', - version: $$PREBID_GLOBAL$$.version - }, - cache: { - vastxml: { - returnCreative: rubiConf.returnVast === true - } - }, - targeting: { - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false, - pricegranularity: getPriceGranularity(config) - }, - bidders: { - rubicon: { - integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION - } - } - } - } - } - - // Add alias if it is there - if (bidRequest.bidder !== 'rubicon') { - data.ext.prebid.aliases = { - [bidRequest.bidder]: 'rubicon' - } - } - - let modules = (getGlobal()).installedModules; - if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { - deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); - } + let filteredHttpRequest = []; + let filteredRequests = bidRequests; - let bidFloor; - if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { - let floorInfo; - try { - floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'video', - size: parseSizes(bidRequest, 'video') - }); - } catch (e) { - logError('Rubicon: getFloor threw an error: ', e); - } - bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; - } else { - bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); - } - if (!isNaN(bidFloor)) { - data.imp[0].bidfloor = bidFloor; - } - - // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check - if (typeof bidRequest.floorData === 'object') { - data.ext.prebid.floors = { enabled: false }; - } - - // if value is set, will overwrite with same value - data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) - - appendSiteAppDevice(data, bidRequest, bidderRequest); - - addVideoParameters(data, bidRequest); - - if (bidderRequest.gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - - deepSetValue(data, 'regs.ext.gdpr', gdprApplies); - deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); - if (eids && eids.length) { - deepSetValue(data, 'user.ext.eids', eids); - } - - // set user.id value from config value - const configUserId = config.getConfig('user.id'); - if (configUserId) { - deepSetValue(data, 'user.id', configUserId); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(data, 'regs.coppa', 1); - } - - if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - - const multibid = config.getConfig('multibid'); - if (multibid) { - deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { - let obj = {}; - - Object.keys(i).forEach(key => { - obj[key.toLowerCase()] = i[key]; - }); - - result.push(obj); - - return result; - }, [])); - } - - applyFPD(bidRequest, VIDEO, data); - - // set ext.prebid.auctiontimestamp using auction time - deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + if (rubiConf.multiformat !== MULTIFORMAT_PBS) { + filteredRequests = bidRequests.filter(bidRequest => !hasBannerMediaTypeOnly(bidRequest)); + } - // set storedrequests to undefined so not sent to PBS - // top level and imp level both 'ext.prebid' objects are set above so no exception thrown here - data.ext.prebid.storedrequest = undefined; - data.imp[0].ext.prebid.storedrequest = undefined; + if (filteredRequests && filteredRequests.length) { + const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); - return { + filteredHttpRequest.push({ method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, + // url: `http://localhost:5500/pbs/v1/openrtb2/auction`, data, - bidRequest - } - }); + bidRequest: filteredRequests + }); + } + if (filteredHttpRequest.length && rubiConf.multiformat === MULTIFORMAT_PBS) { + return filteredHttpRequest; + } + + const bannerBidRequests = bidRequests.filter(hasBannerMediaType); if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled - requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { + requests = filteredHttpRequest.concat(bannerBidRequests.map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', @@ -350,8 +346,7 @@ export const spec = { } else { // single request requires bids to be grouped by site id into a single request // note: groupBy wasn't used because deep property access was needed - const nonVideoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner'); - const groupedBidRequests = nonVideoRequests.reduce((groupedBids, bid) => { + const groupedBidRequests = bannerBidRequests.reduce((groupedBids, bid) => { (groupedBids[bid.params['siteId']] = groupedBids[bid.params['siteId']] || []).push(bid); return groupedBids; }, {}); @@ -360,7 +355,7 @@ export const spec = { const SRA_BID_LIMIT = 10; // multiple requests are used if bids groups have more than 10 bids - requests = videoRequests.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { + requests = filteredHttpRequest.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { // for each partioned bidGroup, append a bidRequest to requests list partitionArray(groupedBidRequests[bidGroupKey], SRA_BID_LIMIT).forEach(bidsInGroup => { const combinedSlotParams = spec.combineSlotUrlParams(bidsInGroup.map(bidRequest => { @@ -613,106 +608,36 @@ export const spec = { /** * @param {*} responseObj - * @param {BidRequest|Object.} bidRequest - if request was SRA the bidRequest argument will be a keyed BidRequest array object, + * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object * @return {Bid[]} An array of bids which */ - interpretResponse: function (responseObj, {bidRequest}) { + interpretResponse: function (responseObj, request) { responseObj = responseObj.body; + const {data} = request; // check overall response if (!responseObj || typeof responseObj !== 'object') { return []; } - // video response from PBS Java openRTB + // Response from PBS Java openRTB if (responseObj.seatbid) { const responseErrors = deepAccess(responseObj, 'ext.errors.rubicon'); if (Array.isArray(responseErrors) && responseErrors.length > 0) { logWarn('Rubicon: Error in video response'); } - const bids = []; - responseObj.seatbid.forEach(seatbid => { - (seatbid.bid || []).forEach(bid => { - let bidObject = { - requestId: bidRequest.bidId, - currency: responseObj.cur || 'USD', - creativeId: bid.crid, - cpm: bid.price || 0, - bidderCode: seatbid.seat, - ttl: 300, - netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - width: bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'), - height: bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'), - }; - - if (bid.id) { - bidObject.seatBidId = bid.id; - } - - if (bid.dealid) { - bidObject.dealId = bid.dealid; - } - - if (bid.adomain) { - deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); - } - - if (deepAccess(bid, 'ext.bidder.rp.advid')) { - deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); - } - - let serverResponseTimeMs = deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); - if (bidRequest && serverResponseTimeMs) { - bidRequest.serverResponseTimeMs = serverResponseTimeMs; - } - - if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { - bidObject.mediaType = VIDEO; - deepSetValue(bidObject, 'meta.mediaType', VIDEO); - const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); - - // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' - if (extPrebidTargeting && typeof extPrebidTargeting === 'object') { - bidObject.adserverTargeting = extPrebidTargeting; - } - - // try to get cache values from 'response.ext.prebid.cache.js' - // else try 'bid.ext.prebid.targeting' as fallback - if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { - bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; - bidObject.vastUrl = bid.ext.prebid.cache.vastXml.url; - } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { - bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; - // build url using key and cache host - bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; - } - - if (bid.adm) { bidObject.vastXml = bid.adm; } - if (bid.nurl) { bidObject.vastUrl = bid.nurl; } - if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - if (videoContext.toLowerCase() === 'outstream') { - bidObject.renderer = outstreamRenderer(bidObject); - } - } else { - logWarn('Rubicon: video response received non-video media type'); - } - - bids.push(bidObject); - }); - }); - + const bids = converter.fromORTB({request: data, response: responseObj}).bids; return bids; } let ads = responseObj.ads; let lastImpId; let multibid = 0; + const {bidRequest} = request; // video ads array is wrapped in an object - if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { + if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest).includes(VIDEO) && typeof ads === 'object') { ads = ads[bidRequest.adUnitCode]; } @@ -778,7 +703,6 @@ export const spec = { } else { logError(`Rubicon: bidRequest undefined at index position:${i}`, bidRequest, responseObj); } - return bids; }, []).sort((adA, adB) => { return (adB.cpm || 0.0) - (adA.cpm || 0.0); @@ -917,7 +841,7 @@ function outstreamRenderer(rtbBid) { function parseSizes(bid, mediaType) { let params = bid.params; - if (mediaType === 'video') { + if (mediaType === VIDEO) { let size = []; if (params.video && params.video.playerWidth && params.video.playerHeight) { size = [ @@ -947,65 +871,6 @@ function parseSizes(bid, mediaType) { return masSizeOrdering(sizes); } -/** - * @param {Object} data - * @param bidRequest - * @param bidderRequest - */ -function appendSiteAppDevice(data, bidRequest, bidderRequest) { - if (!data) return; - - // ORTB specifies app OR site - if (typeof config.getConfig('app') === 'object') { - data.app = config.getConfig('app'); - } else { - data.site = { - page: _getPageUrl(bidRequest, bidderRequest) - } - } - if (typeof config.getConfig('device') === 'object') { - data.device = config.getConfig('device'); - } - // Add language to site and device objects if there - if (bidRequest.params.video.language) { - ['site', 'device'].forEach(function(param) { - if (data[param]) { - if (param === 'site') { - data[param].content = Object.assign({language: bidRequest.params.video.language}, data[param].content) - } else { - data[param] = Object.assign({language: bidRequest.params.video.language}, data[param]) - } - } - }); - } -} - -/** - * @param {Object} data - * @param {BidRequest} bidRequest - */ -function addVideoParameters(data, bidRequest) { - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skip === undefined) { - data.imp[0].video.skip = bidRequest.params.video.skip; - } - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skipafter === undefined) { - data.imp[0].video.skipafter = bidRequest.params.video.skipdelay; - } - // video.pos can already be specified by adunit.mediatypes.video.pos. - // but if not, it might be specified in the params - if (typeof data.imp[0].video === 'object' && data.imp[0].video.pos === undefined) { - if (bidRequest.params.position === 'atf') { - data.imp[0].video.pos = 1; - } else if (bidRequest.params.position === 'btf') { - data.imp[0].video.pos = 3; - } - } - - const size = parseSizes(bidRequest, 'video') - data.imp[0].video.w = size[0] - data.imp[0].video.h = size[1] -} - function applyFPD(bidRequest, mediaType, data) { const BID_FPD = { user: {ext: {data: {...bidRequest.params.visitor}}}, @@ -1131,13 +996,14 @@ export function classifiedAsVideo(bidRequest) { } /** - * Determine bidRequest mediaType + * Determine bidRequest mediaTypes. All mediaTypes must be correct. If one fails, all the others will fail too. * @param bid the bid to test - * @param log whether we should log errors/warnings for invalid bids - * @returns {string|undefined} Returns 'video' or 'banner' if resolves to a type, or undefined otherwise (invalid). + * @param log boolean. whether we should log errors/warnings for invalid bids + * @returns {string|undefined} Returns an array containing one of 'video' or 'banner' or 'native' if resolves to a type. */ function bidType(bid, log = false) { // Is it considered video ad unit by rubicon + let bidTypes = []; if (classifiedAsVideo(bid)) { // Removed legacy mediaType support. new way using mediaTypes.video object is now required // We require either context as instream or outstream @@ -1145,37 +1011,41 @@ function bidType(bid, log = false) { if (log) { logError('Rubicon: mediaTypes.video.context must be outstream or instream'); } - return; + return bidTypes; } // we require playerWidth and playerHeight to come from one of params.playerWidth/playerHeight or mediaTypes.video.playerSize or adUnit.sizes - if (parseSizes(bid, 'video').length < 2) { + if (parseSizes(bid, VIDEO).length < 2) { if (log) { logError('Rubicon: could not determine the playerSize of the video'); } - return; + return bidTypes; } if (log) { logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); } - return 'video'; - } else { - // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order - // if we cannot determine them, we reject it! - if (parseSizes(bid, 'banner').length === 0) { - if (log) { - logError('Rubicon: could not determine the sizes for banner request'); - } - return; - } + bidTypes.push(VIDEO); + } + if (typeof deepAccess(bid, `mediaTypes.${NATIVE}`) !== 'undefined') { + bidTypes.push(NATIVE); + } - // everything looks good for banner so lets do it + // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order + // if we cannot determine them, we reject it! + if (parseSizes(bid, BANNER).length === 0) { if (log) { - logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); + logError('Rubicon: could not determine the sizes for banner request'); } - return 'banner'; + return bidTypes; } + + // everything looks good for banner so lets do it + if (log) { + logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); + } + bidTypes.push(BANNER); + return bidTypes; } export const resetRubiConf = () => rubiConf = {}; @@ -1305,4 +1175,70 @@ export function resetUserSync() { hasSynced = false; } +function setBidFloors(bidRequest, imp) { + if (imp.bidfloorcur != 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + } + + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = 'USD'; + } +} + +function addMultibid(data, multibid) { + if (!multibid) { + return; + } + deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { + let obj = {}; + + Object.keys(i).forEach(key => { + obj[key.toLowerCase()] = i[key]; + }); + + result.push(obj); + + return result; + }, [])); +} + +function addOrtbFirstPartyData(data, nonBannerRequests) { + let fpd = {}; + const keywords = new Set(); + nonBannerRequests.forEach(bidRequest => { + const bidFirstPartyData = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} + }; + + // add site.content.language + const impThatHasVideoLanguage = data.imp.find(imp => imp.ext?.prebid?.bidder?.rubicon?.video?.language); + if (impThatHasVideoLanguage) { + bidFirstPartyData.site.content = { + language: impThatHasVideoLanguage.ext?.prebid?.bidder?.rubicon?.video?.language + } + } + + if (bidRequest.params.keywords) { + const keywordsArray = (!Array.isArray(bidRequest.params.keywords) ? bidRequest.params.keywords.split(',') : bidRequest.params.keywords); + keywordsArray.forEach(keyword => keywords.add(keyword)); + } + fpd = mergeDeep(fpd, bidRequest.ortb2 || {}, bidFirstPartyData); + + // add user.id from config. + // NOTE: This is DEPRECATED. user.id should come from setConfig({ortb2}). + const configUserId = config.getConfig('user.id'); + fpd.user.id = fpd.user.id || configUserId; + }); + + mergeDeep(data, fpd); + + deepSetValue(data, 'site.keywords', Array.from(keywords.values()).join(',')); + delete data?.ext?.prebid?.storedrequest; +} + registerBidder(spec); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e81ef1c805f..350a93a4565 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -5,13 +5,19 @@ import { masSizeOrdering, resetUserSync, classifiedAsVideo, - resetRubiConf + resetRubiConf, + converter } from 'modules/rubiconBidAdapter.js'; import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; +import 'modules/schain.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/userId/index.js'; +import 'modules/priceFloors.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -1553,23 +1559,21 @@ describe('the rubicon adapter', function () { expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); expect(imp.video.minduration).to.equal(15); expect(imp.video.maxduration).to.equal(30); expect(imp.video.startdelay).to.equal(0); expect(imp.video.skip).to.equal(1); expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.size_id).to.equal(201); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); // Also want it to be in post.site.content.language - expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(imp.ext.prebid.bidder.rubicon.video.skip).to.equal(1); + expect(imp.ext.prebid.bidder.rubicon.video.skipafter).to.equal(15); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); // should contain version - expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // EIDs should exist expect(post.user.ext).to.have.property('eids').that.is.an('array'); @@ -1672,8 +1676,8 @@ describe('the rubicon adapter', function () { expect( bidderRequest.bids[0].getFloor.calledWith({ currency: 'USD', - mediaType: 'video', - size: [640, 480] + mediaType: '*', + size: '*' }) ).to.be.true; @@ -1701,7 +1705,7 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].bidfloor).to.equal(1.23); }); - it('should continue with auction and log error if getFloor throws one', function () { + it('should continue with auction if getFloor throws error', function () { createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => { @@ -1713,15 +1717,13 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // log error called - expect(logErrorSpy.calledOnce).to.equal(true); - // should have an imp expect(request.data.imp).to.exist.and.to.be.a('array'); expect(request.data.imp).to.have.lengthOf(1); // should be NO bidFloor expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; }); it('should add alias name to PBS Request', function () { @@ -1736,8 +1738,8 @@ describe('the rubicon adapter', function () { expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); // should have the imp ext bidder params be under the alias name not rubicon superRubicon - expect(request.data.imp[0].ext).to.have.property('superRubicon').that.is.an('object'); - expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); + expect(request.data.imp[0].ext.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); }); it('should add floors flag correctly to PBS Request', function () { @@ -2000,7 +2002,7 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.imp[0].ext.rubicon.video.size_id).to.equal(203); + expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); }); it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { @@ -2214,21 +2216,18 @@ describe('the rubicon adapter', function () { expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); expect(imp.video.minduration).to.equal(15); expect(imp.video.maxduration).to.equal(30); expect(imp.video.startdelay).to.equal(0); expect(imp.video.skip).to.equal(1); expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // Config user.id @@ -2366,6 +2365,76 @@ describe('the rubicon adapter', function () { expect(bid.params.video).to.not.be.undefined; }); }); + + describe('when there is a native request', function () { + it('should send only one native bid to PBS endpoint', function () { + const bidReq = addNativeToBidRequest(bidderRequest); + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); + }); + + describe('that contains also a banner mediaType', function () { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].mediaTypes.banner = { + sizes: [[300, 250]] + } + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }) + describe('with another banner request', () => { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second bidRqeuest + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }) + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + + describe('with multiformat === "pbs"', () => { + it('should send just one request to PBS with 2 imps', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second bid request + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }); + config.setConfig({ + rubicon: { + multiformat: 'pbs' + } + }); + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); + expect(request.data.imp).to.have.nested.property('[1].banner'); + }); + }); + }); }); describe('interpretResponse', function () { @@ -3119,7 +3188,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'instream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3144,9 +3213,9 @@ describe('the rubicon adapter', function () { }], }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, {data: request}); expect(bids).to.be.lengthOf(1); @@ -3167,6 +3236,16 @@ describe('the rubicon adapter', function () { }); }); + describe('for native', () => { + it('should get a native bid', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids).to.have.nested.property('[0].native'); + }); + }); + describe('for outstream video', function () { const sandbox = sinon.createSandbox(); beforeEach(function () { @@ -3196,7 +3275,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'outstream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3221,9 +3300,9 @@ describe('the rubicon adapter', function () { }], }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, { data: request }); expect(bids).to.be.lengthOf(1); @@ -3256,7 +3335,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'outstream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3282,11 +3361,11 @@ describe('the rubicon adapter', function () { }], }; + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + sinon.spy(window.MagniteApex, 'renderAd'); - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + let bids = spec.interpretResponse({body: response}, {data: request}); const bid = bids[0]; bid.adUnitCode = 'outstream_video1_placement'; const adUnit = document.createElement('div'); @@ -3617,3 +3696,164 @@ describe('the rubicon adapter', function () { }); }); }); + +function addNativeToBidRequest(bidderRequest) { + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }] + }; + bidderRequest.refererInfo = { + page: 'localhost' + } + bidderRequest.bids[0] = { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + }, + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest + } + return bidderRequest; +} + +function getNativeResponse(options = {impid: 1234}) { + return { + 'id': 'd7786a80-bfb4-4541-859f-225a934e81d4', + 'seatbid': [ + { + 'bid': [ + { + 'id': '971650', + 'impid': options.impid, + 'price': 20, + 'adm': { + 'ver': '1.2', + 'assets': [ + { + 'id': 0, + 'title': { + 'text': 'This is a title' + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=0' + ] + } + }, + { + 'id': 1, + 'img': { + 'url': 'https:\\\\/\\\\/vcdn.adnxs.com\\\\/p\\\\/creative-image\\\\/94\\\\/22\\\\/cd\\\\/0f\\\\/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + 'h': 2250, + 'w': 3000 + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=1' + ] + } + }, + { + 'id': 2, + 'data': { + 'value': 'this is asset data 1 that corresponds to sponsoredBy' + } + } + ], + 'link': { + 'url': 'https://magnite.com', + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card', + 'http://localhost:5500/event?type=click2&component=card' + ] + }, + 'jstracker': '', + 'eventtrackers': [ + { + 'event': 1, + 'method': 2, + 'url': 'http://localhost:5500/event?type=1&method=2' + }, + { + 'event': 2, + 'method': 1, + 'url': 'http://localhost:5500/event?type=v50&component=card' + } + ] + }, + 'adid': '392180', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://lax1-ib.adnxs.com/cr?id=97494403', + 'cid': '9325', + 'crid': '97494403', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'rubicon', + 'hb_cache_host': 'prebid.lax1.adnxs-simple.com', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_pb': '20.00' + }, + 'type': 'native', + 'video': { + 'duration': 0, + 'primary_category': '' + } + }, + 'rubicon': { + 'auction_id': 642778043863823100, + 'bid_ad_type': 3, + 'bidder_id': 2, + 'brand_id': 555545 + } + } + } + ], + 'seat': 'rubicon' + } + ], + 'cur': 'USD' + }; +} From f08b78f20d1c7ecba56c4e764d4a00074ad2df5c Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 27 Oct 2022 08:15:33 +0200 Subject: [PATCH 02/11] wrap native tests around FEATURES.NATIVE --- test/spec/modules/rubiconBidAdapter_spec.js | 142 ++++++++++---------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 350a93a4565..a6756974758 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2366,75 +2366,77 @@ describe('the rubicon adapter', function () { }); }); - describe('when there is a native request', function () { - it('should send only one native bid to PBS endpoint', function () { - const bidReq = addNativeToBidRequest(bidderRequest); - let [request] = spec.buildRequests(bidReq.bids, bidReq); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request.data.imp).to.have.nested.property('[0].native'); - }); - - describe('that contains also a banner mediaType', function () { - it('should send the native bid to PBS and the banner to fastlane', function() { + if (FEATURES.NATIVE) { + describe('when there is a native request', function () { + it('should send only one native bid to PBS endpoint', function () { const bidReq = addNativeToBidRequest(bidderRequest); - bidReq.bids[0].mediaTypes.banner = { - sizes: [[300, 250]] - } - let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); - expect(request1.method).to.equal('POST'); - expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request1.data.imp).to.have.nested.property('[0].native'); - expect(request2.method).to.equal('GET'); - expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); }); - }) - describe('with another banner request', () => { - it('should send the native bid to PBS and the banner to fastlane', function() { - const bidReq = addNativeToBidRequest(bidderRequest); - // add second bidRqeuest - bidReq.bids.push({ - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: bidReq.bids[0].params - }) - let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); - expect(request1.method).to.equal('POST'); - expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request1.data.imp).to.have.nested.property('[0].native'); - expect(request2.method).to.equal('GET'); - expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + + describe('that contains also a banner mediaType', function () { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].mediaTypes.banner = { + sizes: [[300, 250]] + } + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }) + describe('with another banner request', () => { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second bidRqeuest + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }) + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); }); - }); - describe('with multiformat === "pbs"', () => { - it('should send just one request to PBS with 2 imps', () => { - const bidReq = addNativeToBidRequest(bidderRequest); - // add second bid request - bidReq.bids.push({ - mediaTypes: { - banner: { - sizes: [[300, 250]] + describe('with multiformat === "pbs"', () => { + it('should send just one request to PBS with 2 imps', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second bid request + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }); + config.setConfig({ + rubicon: { + multiformat: 'pbs' } - }, - params: bidReq.bids[0].params - }); - config.setConfig({ - rubicon: { - multiformat: 'pbs' - } + }); + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); + expect(request.data.imp).to.have.nested.property('[1].banner'); }); - let [request] = spec.buildRequests(bidReq.bids, bidReq); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request.data.imp).to.have.nested.property('[0].native'); - expect(request.data.imp).to.have.nested.property('[1].banner'); }); }); - }); + } }); describe('interpretResponse', function () { @@ -3236,15 +3238,17 @@ describe('the rubicon adapter', function () { }); }); - describe('for native', () => { - it('should get a native bid', () => { - const nativeBidderRequest = addNativeToBidRequest(bidderRequest); - const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); - let response = getNativeResponse({impid: request.imp[0].id}); - let bids = spec.interpretResponse({body: response}, {data: request}); - expect(bids).to.have.nested.property('[0].native'); + if (FEATURES.NATIVE) { + describe('for native', () => { + it('should get a native bid', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids).to.have.nested.property('[0].native'); + }); }); - }); + } describe('for outstream video', function () { const sandbox = sinon.createSandbox(); From 5069d6b47b8a6f013f678404df1dfee6a6a28e2f Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Mon, 31 Oct 2022 13:23:25 +0100 Subject: [PATCH 03/11] remove commented out code --- modules/rubiconBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 64c6c093223..df5e17ab9ab 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -318,7 +318,6 @@ export const spec = { filteredHttpRequest.push({ method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, - // url: `http://localhost:5500/pbs/v1/openrtb2/auction`, data, bidRequest: filteredRequests }); From f550bd1b19c3b23bf445221480448a85acb474bd Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Fri, 25 Nov 2022 18:00:39 +0100 Subject: [PATCH 04/11] HB-16092 support multiformat parameter --- modules/rubiconBidAdapter.js | 28 ++-- test/spec/modules/rubiconBidAdapter_spec.js | 152 +++++++++++++------- 2 files changed, 110 insertions(+), 70 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index df5e17ab9ab..b4714080229 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -27,7 +27,9 @@ const DEFAULT_PBS_INTEGRATION = 'pbjs'; const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; // renderer code at https://github.com/rubicon-project/apex2 -let rubiConf = {}; +let rubiConf = { + multiformat: false +}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { @@ -139,13 +141,6 @@ var sizeMap = { 580: '505x656' }; -/** - * This flag enables all bids (banner, native, video) to be sent to Prebid Server. - * when rubiconf.multiformat !== MULTIFORMAT_PBS, video (and native) bids go to PBS while - * banner will go to fastlane. - */ -const MULTIFORMAT_PBS = 'pbs'; - _each(sizeMap, (item, key) => sizeMap[item] = key); const hasBannerMediaTypeOnly = bidRequest => { @@ -171,7 +166,8 @@ export const converter = ortbConverter({ deepSetValue(data, 'ext.prebid.bidders', { rubicon: { - integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION, + multiformat: rubiConf.multiformat } }); @@ -221,13 +217,13 @@ export const converter = ortbConverter({ return data; }, imp(buildImp, bidRequest, context) { - // skip banner-only requests if multiformat === 'pbs' - if (rubiConf.multiformat !== MULTIFORMAT_PBS && hasBannerMediaTypeOnly(bidRequest)) { + // skip banner-only requests if multiformat === false + if (rubiConf.multiformat === true && hasBannerMediaTypeOnly(bidRequest)) { return; } const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; - if (rubiConf.multiformat !== MULTIFORMAT_PBS && imp.banner) { + if (rubiConf.multiformat === true && imp.banner) { delete imp.banner; } if (config.getConfig('s2sConfig.defaultTtl')) { @@ -308,8 +304,10 @@ export const spec = { let filteredHttpRequest = []; let filteredRequests = bidRequests; - if (rubiConf.multiformat !== MULTIFORMAT_PBS) { + if (rubiConf.multiformat === true) { filteredRequests = bidRequests.filter(bidRequest => !hasBannerMediaTypeOnly(bidRequest)); + } else { + filteredRequests = bidRequests.filter(bidRequest => bidType(bidRequest).some(mediaType => [VIDEO, NATIVE].includes(mediaType)) && bidRequest?.params?.video); } if (filteredRequests && filteredRequests.length) { @@ -323,10 +321,6 @@ export const spec = { }); } - if (filteredHttpRequest.length && rubiConf.multiformat === MULTIFORMAT_PBS) { - return filteredHttpRequest; - } - const bannerBidRequests = bidRequests.filter(hasBannerMediaType); if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index a6756974758..cd4998ac8bc 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2368,71 +2368,117 @@ describe('the rubicon adapter', function () { if (FEATURES.NATIVE) { describe('when there is a native request', function () { - it('should send only one native bid to PBS endpoint', function () { - const bidReq = addNativeToBidRequest(bidderRequest); - let [request] = spec.buildRequests(bidReq.bids, bidReq); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request.data.imp).to.have.nested.property('[0].native'); - }); - - describe('that contains also a banner mediaType', function () { - it('should send the native bid to PBS and the banner to fastlane', function() { + describe('and multiformat = undefined (false)', () => { + it('should send only one native bid to PBS endpoint', function () { const bidReq = addNativeToBidRequest(bidderRequest); - bidReq.bids[0].mediaTypes.banner = { - sizes: [[300, 250]] + bidReq.bids[0].params = { + video: {} } - let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); - expect(request1.method).to.equal('POST'); - expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request1.data.imp).to.have.nested.property('[0].native'); - expect(request2.method).to.equal('GET'); - expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); }); - }) - describe('with another banner request', () => { - it('should send the native bid to PBS and the banner to fastlane', function() { - const bidReq = addNativeToBidRequest(bidderRequest); - // add second bidRqeuest - bidReq.bids.push({ - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: bidReq.bids[0].params - }) - let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); - expect(request1.method).to.equal('POST'); - expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request1.data.imp).to.have.nested.property('[0].native'); - expect(request2.method).to.equal('GET'); - expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + + describe('that contains also a banner mediaType', function () { + it('should send the banner to fastlane BUT NOT the native bid because missing params.video', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].mediaTypes.banner = { + sizes: [[300, 250]] + } + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('GET'); + expect(request.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with another banner request', () => { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].params = { video: {} }; + // add second bidRqeuest + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }) + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); }); }); - describe('with multiformat === "pbs"', () => { - it('should send just one request to PBS with 2 imps', () => { + describe('with multiformat === true', () => { + it('should send two requests, to PBS with 2 imps', () => { const bidReq = addNativeToBidRequest(bidderRequest); - // add second bid request - bidReq.bids.push({ - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: bidReq.bids[0].params + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + config.setConfig({ + rubicon: { + multiformat: true + } }); + let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); + expect(pbsRequest.method).to.equal('POST'); + expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with multiformat === false', () => { + it('should send only banner request because there\'s no params.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; config.setConfig({ rubicon: { - multiformat: 'pbs' + // when missing, multiformat is false by default } }); - let [request] = spec.buildRequests(bidReq.bids, bidReq); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(request.data.imp).to.have.nested.property('[0].native'); - expect(request.data.imp).to.have.nested.property('[1].banner'); + let [fastlanteRequest, ...others] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(others).to.be.empty; + }); + + it('should send both banner and video because there\'s param.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + // by adding this, when multiformat is false, the native request will be sent to pbs + bidReq.bids[0].params = { + video: {} + } + config.setConfig({ + rubicon: { + // when missing, multiformat is false by default + } + }); + let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); + expect(pbsRequest.method).to.equal('POST'); + expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); }); }); From 6b760904618bfe9ec8d713c821a2d087fc28691d Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 1 Dec 2022 15:02:38 +0100 Subject: [PATCH 05/11] do not generate imp if has only banner media type --- modules/rubiconBidAdapter.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index b4714080229..63f9f17c17d 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -217,12 +217,12 @@ export const converter = ortbConverter({ return data; }, imp(buildImp, bidRequest, context) { - // skip banner-only requests if multiformat === false - if (rubiConf.multiformat === true && hasBannerMediaTypeOnly(bidRequest)) { - return; - } + // skip banner-only requests + if (hasBannerMediaTypeOnly(bidRequest)) return; + const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; + if (rubiConf.multiformat === true && imp.banner) { delete imp.banner; } @@ -302,7 +302,7 @@ export const spec = { // separate video bids because the requests are structured differently let requests = []; let filteredHttpRequest = []; - let filteredRequests = bidRequests; + let filteredRequests; if (rubiConf.multiformat === true) { filteredRequests = bidRequests.filter(bidRequest => !hasBannerMediaTypeOnly(bidRequest)); From 3468ecd9ed4051819b91f66fc68a5826eb0d8e4f Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Mon, 5 Dec 2022 12:53:29 +0100 Subject: [PATCH 06/11] check banner bid type only if mediaTypes.banner --- modules/rubiconBidAdapter.js | 24 +++++++++++---------- test/spec/modules/rubiconBidAdapter_spec.js | 6 ++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 63f9f17c17d..7272443af9e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1024,20 +1024,22 @@ function bidType(bid, log = false) { bidTypes.push(NATIVE); } - // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order - // if we cannot determine them, we reject it! - if (parseSizes(bid, BANNER).length === 0) { - if (log) { - logError('Rubicon: could not determine the sizes for banner request'); + if (typeof deepAccess(bid, `mediaTypes.${BANNER}`) !== 'undefined') { + // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order + // if we cannot determine them, we reject it! + if (parseSizes(bid, BANNER).length === 0) { + if (log) { + logError('Rubicon: could not determine the sizes for banner request'); + } + return bidTypes; } - return bidTypes; - } - // everything looks good for banner so lets do it - if (log) { - logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); + // everything looks good for banner so lets do it + if (log) { + logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); + } + bidTypes.push(BANNER); } - bidTypes.push(BANNER); return bidTypes; } diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index cd4998ac8bc..cd1718f23cc 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -108,6 +108,9 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], @@ -329,6 +332,9 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], From 464b056a9f0ab07c4cfcf165479c717c29972c1e Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Tue, 6 Dec 2022 14:45:34 +0100 Subject: [PATCH 07/11] new multiformat logic --- modules/rubiconBidAdapter.js | 31 +++++++++++++-------- test/spec/modules/rubiconBidAdapter_spec.js | 23 ++++----------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 7272443af9e..b4ecb8d0736 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -27,9 +27,7 @@ const DEFAULT_PBS_INTEGRATION = 'pbjs'; const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; // renderer code at https://github.com/rubicon-project/apex2 -let rubiConf = { - multiformat: false -}; +let rubiConf = {}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { @@ -167,7 +165,6 @@ export const converter = ortbConverter({ deepSetValue(data, 'ext.prebid.bidders', { rubicon: { integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION, - multiformat: rubiConf.multiformat } }); @@ -223,7 +220,7 @@ export const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; - if (rubiConf.multiformat === true && imp.banner) { + if (bidRequest.params?.multiformat === true && imp.banner) { delete imp.banner; } if (config.getConfig('s2sConfig.defaultTtl')) { @@ -304,11 +301,17 @@ export const spec = { let filteredHttpRequest = []; let filteredRequests; - if (rubiConf.multiformat === true) { - filteredRequests = bidRequests.filter(bidRequest => !hasBannerMediaTypeOnly(bidRequest)); - } else { - filteredRequests = bidRequests.filter(bidRequest => bidType(bidRequest).some(mediaType => [VIDEO, NATIVE].includes(mediaType)) && bidRequest?.params?.video); - } + filteredRequests = bidRequests.filter(req => { + const mediaTypes = bidType(req); + return ( + // if there's no banner in volved, add to PBS + !mediaTypes.includes(BANNER) || + // if multiformat is true, and it doesn't contain only banner, add to PBS + (Boolean(req?.params?.multiformat) && !hasBannerMediaTypeOnly(req)) || + // if multiformat is false, there's params.video, and there's not banner only, send to PBS + (!req?.params?.multiformat && req?.params?.video && !hasBannerMediaTypeOnly(req)) + ); + }); if (filteredRequests && filteredRequests.length) { const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); @@ -321,7 +324,13 @@ export const spec = { }); } - const bannerBidRequests = bidRequests.filter(hasBannerMediaType); + const bannerBidRequests = bidRequests.filter((req) => { + return ( + hasBannerMediaTypeOnly(req) || + (!req.params?.multiformat && !req.params?.video && hasBannerMediaType(req)) || + (req.params?.multiformat && hasBannerMediaType(req)) + ); + }); if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled requests = filteredHttpRequest.concat(bannerBidRequests.map(bidRequest => { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index cd1718f23cc..fbabe2fbe72 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2430,11 +2430,7 @@ describe('the rubicon adapter', function () { sizes: [[300, 250]] } }; - config.setConfig({ - rubicon: { - multiformat: true - } - }); + bidReq.bids[0].params.multiformat = true; let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); expect(pbsRequest.method).to.equal('POST'); expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); @@ -2452,17 +2448,13 @@ describe('the rubicon adapter', function () { sizes: [[300, 250]] } }; - config.setConfig({ - rubicon: { - // when missing, multiformat is false by default - } - }); + let [fastlanteRequest, ...others] = spec.buildRequests(bidReq.bids, bidReq); expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); expect(others).to.be.empty; }); - it('should send both banner and video because there\'s param.video', () => { + it('should send only video because there\'s param.video', () => { const bidReq = addNativeToBidRequest(bidderRequest); // add second mediaType bidReq.bids[0].mediaTypes = { @@ -2475,16 +2467,11 @@ describe('the rubicon adapter', function () { bidReq.bids[0].params = { video: {} } - config.setConfig({ - rubicon: { - // when missing, multiformat is false by default - } - }); - let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); + let [pbsRequest, ...other] = spec.buildRequests(bidReq.bids, bidReq); expect(pbsRequest.method).to.equal('POST'); expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); - expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(other[0]).to.be.null; }); }); }); From b2a0e55549c6ec8d2f165edba7e200c53bb16aa4 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Tue, 13 Dec 2022 16:48:26 +0100 Subject: [PATCH 08/11] bidonmultiformat --- modules/rubiconBidAdapter.js | 59 ++++++++++++--------- test/spec/modules/rubiconBidAdapter_spec.js | 21 ++++---- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index b4ecb8d0736..cbe5218b70e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -141,16 +141,6 @@ var sizeMap = { _each(sizeMap, (item, key) => sizeMap[item] = key); -const hasBannerMediaTypeOnly = bidRequest => { - const bidRequestType = bidType(bidRequest); - return (bidRequestType.includes(BANNER) && bidRequestType.length == 1); -}; - -const hasBannerMediaType = bidRequest => { - const bidRequestType = bidType(bidRequest); - return (bidRequestType.includes(BANNER)) -} - export const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const {bidRequests} = context; @@ -215,14 +205,12 @@ export const converter = ortbConverter({ }, imp(buildImp, bidRequest, context) { // skip banner-only requests - if (hasBannerMediaTypeOnly(bidRequest)) return; + const bidRequestType = bidType(bidRequest); + if (bidRequestType.includes(BANNER) && bidRequestType.length == 1) return; const imp = buildImp(bidRequest, context); imp.id = bidRequest.adUnitCode; - - if (bidRequest.params?.multiformat === true && imp.banner) { - delete imp.banner; - } + delete imp.banner; if (config.getConfig('s2sConfig.defaultTtl')) { imp.exp = config.getConfig('s2sConfig.defaultTtl'); }; @@ -302,15 +290,20 @@ export const spec = { let filteredRequests; filteredRequests = bidRequests.filter(req => { - const mediaTypes = bidType(req); + const mediaTypes = bidType(req) || []; + const { length } = mediaTypes; + const { bidonmultiformat, video } = req.params || {}; + return ( - // if there's no banner in volved, add to PBS - !mediaTypes.includes(BANNER) || - // if multiformat is true, and it doesn't contain only banner, add to PBS - (Boolean(req?.params?.multiformat) && !hasBannerMediaTypeOnly(req)) || - // if multiformat is false, there's params.video, and there's not banner only, send to PBS - (!req?.params?.multiformat && req?.params?.video && !hasBannerMediaTypeOnly(req)) - ); + // if there's just one mediaType and it's video or native, just send it! + (length === 1 && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) || + // if it's two mediaTypes, and they don't contain banner, send to PBS both native & video + (length === 2 && !mediaTypes.includes(BANNER)) || + // if it contains the video param and the Video mediaType, send Video to PBS (not native!) + (video && mediaTypes.includes(VIDEO)) || + // if bidonmultiformat is on, send everything to PBS + (bidonmultiformat && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) + ) }); if (filteredRequests && filteredRequests.length) { @@ -325,10 +318,20 @@ export const spec = { } const bannerBidRequests = bidRequests.filter((req) => { + const mediaTypes = bidType(req) || []; + const {bidonmultiformat, video} = req.params || {}; return ( - hasBannerMediaTypeOnly(req) || - (!req.params?.multiformat && !req.params?.video && hasBannerMediaType(req)) || - (req.params?.multiformat && hasBannerMediaType(req)) + // Send to fastlane if: it must include BANNER and... + mediaTypes.includes(BANNER) && ( + // if it's just banner + (mediaTypes.length === 1) || + // if bidonmultiformat is true + bidonmultiformat || + // if bidonmultiformat is false and there's no video parameter + (!bidonmultiformat && !video) || + // if there's video parameter, but there's no video mediatype + (!bidonmultiformat && video && !mediaTypes.includes(VIDEO)) + ) ); }); if (config.getConfig('rubicon.singleRequest') !== true) { @@ -983,11 +986,15 @@ function mapSizes(sizes) { export function classifiedAsVideo(bidRequest) { let isVideo = typeof deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'; let isBanner = typeof deepAccess(bidRequest, `mediaTypes.${BANNER}`) !== 'undefined'; + let isBidOnMultiformat = typeof deepAccess(bidRequest, `params.bidonmultiformat`) !== 'undefined'; let isMissingVideoParams = typeof deepAccess(bidRequest, 'params.video') !== 'object'; // If an ad has both video and banner types, a legacy implementation allows choosing video over banner // based on whether or not there is a video object defined in the params // Given this legacy implementation, other code depends on params.video being defined + // if it's bidonmultiformat, we don't care of the video object + if (isVideo && isBidOnMultiformat) return true; + if (isBanner && isMissingVideoParams) { isVideo = false; } diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index fbabe2fbe72..4555f890d88 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2374,7 +2374,7 @@ describe('the rubicon adapter', function () { if (FEATURES.NATIVE) { describe('when there is a native request', function () { - describe('and multiformat = undefined (false)', () => { + describe('and bidonmultiformat = undefined (false)', () => { it('should send only one native bid to PBS endpoint', function () { const bidReq = addNativeToBidRequest(bidderRequest); bidReq.bids[0].params = { @@ -2420,7 +2420,7 @@ describe('the rubicon adapter', function () { }); }); - describe('with multiformat === true', () => { + describe('with bidonmultiformat === true', () => { it('should send two requests, to PBS with 2 imps', () => { const bidReq = addNativeToBidRequest(bidderRequest); // add second mediaType @@ -2430,7 +2430,7 @@ describe('the rubicon adapter', function () { sizes: [[300, 250]] } }; - bidReq.bids[0].params.multiformat = true; + bidReq.bids[0].params.bidonmultiformat = true; let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); expect(pbsRequest.method).to.equal('POST'); expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); @@ -2438,7 +2438,7 @@ describe('the rubicon adapter', function () { expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); }); - describe('with multiformat === false', () => { + describe('with bidonmultiformat === false', () => { it('should send only banner request because there\'s no params.video', () => { const bidReq = addNativeToBidRequest(bidderRequest); // add second mediaType @@ -2454,7 +2454,7 @@ describe('the rubicon adapter', function () { expect(others).to.be.empty; }); - it('should send only video because there\'s param.video', () => { + it('should not send native to PBS even if there\'s param.video', () => { const bidReq = addNativeToBidRequest(bidderRequest); // add second mediaType bidReq.bids[0].mediaTypes = { @@ -2463,15 +2463,14 @@ describe('the rubicon adapter', function () { sizes: [[300, 250]] } }; - // by adding this, when multiformat is false, the native request will be sent to pbs + // by adding this, when bidonmultiformat is false, the native request will be sent to pbs bidReq.bids[0].params = { video: {} } - let [pbsRequest, ...other] = spec.buildRequests(bidReq.bids, bidReq); - expect(pbsRequest.method).to.equal('POST'); - expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); - expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); - expect(other[0]).to.be.null; + let [fastlaneRequest, ...other] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlaneRequest.method).to.equal('GET'); + expect(fastlaneRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(other).to.be.empty; }); }); }); From beb6348bb301a1a1a7f6c700e728d0788405678f Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Mon, 6 Feb 2023 16:40:49 +0100 Subject: [PATCH 09/11] fixes: do not set empty keywords; better behavior for floors. --- modules/rubiconBidAdapter.js | 58 +++++++++------------ test/spec/modules/rubiconBidAdapter_spec.js | 3 +- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index cbe5218b70e..f65a2637831 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,5 +1,12 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { find } from '../src/polyfill.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { Renderer } from '../src/Renderer.js'; import { - _each, convertTypes, deepAccess, deepSetValue, @@ -11,16 +18,8 @@ import { logMessage, logWarn, mergeDeep, - parseSizesInput + parseSizesInput, _each } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import {find} from '../src/polyfill.js'; -import {Renderer} from '../src/Renderer.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; @@ -183,24 +182,6 @@ export const converter = ortbConverter({ const multibid = config.getConfig('multibid'); addMultibid(data, multibid); - const aliasedBidRequests = bidRequests - // get all bidRequests that are using an alias - .filter(bidRequest => bidRequest.bidder !== spec.code) - // transform those bidRequests in - // { - // 'alias1': rubicon, - // 'alias2': rubicon, - // } - .reduce((acc, bidRequest) => { - return { - ...acc, - [bidRequest.bidder]: spec.code - } - }, {}); - if (Object.keys(aliasedBidRequests).length > 0) { - data.ext.prebid.aliases = aliasedBidRequests; - } - return data; }, imp(buildImp, bidRequest, context) { @@ -1186,17 +1167,26 @@ export function resetUserSync() { hasSynced = false; } +/** + * Sets the floor on the bidRequest. imp.bidfloor and imp.bidfloorcur + * should be already set by the conversion library. if they're not, + * or invalid, try to read from params.floor. + * @param {*} bidRequest + * @param {*} imp + */ function setBidFloors(bidRequest, imp) { if (imp.bidfloorcur != 'USD') { delete imp.bidfloor; delete imp.bidfloorcur; } - let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + if (!imp.bidfloor) { + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); - if (!isNaN(bidFloor)) { - imp.bidfloor = bidFloor; - imp.bidfloorcur = 'USD'; + if (!isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = 'USD'; + } } } @@ -1248,7 +1238,9 @@ function addOrtbFirstPartyData(data, nonBannerRequests) { mergeDeep(data, fpd); - deepSetValue(data, 'site.keywords', Array.from(keywords.values()).join(',')); + if (keywords && keywords.size) { + deepSetValue(data, 'site.keywords', Array.from(keywords.values()).join(',')); + } delete data?.ext?.prebid?.storedrequest; } diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 4555f890d88..b0b73f44fe0 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -18,6 +18,7 @@ import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/userId/index.js'; import 'modules/priceFloors.js'; +import adapterManager from 'src/adapterManager.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -1734,7 +1735,7 @@ describe('the rubicon adapter', function () { it('should add alias name to PBS Request', function () { createVideoBidderRequest(); - + adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; bidderRequest.bidderCode = 'superRubicon'; bidderRequest.bids[0].bidder = 'superRubicon'; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); From 05c16ed582e342d61baecd1becd5f8fc351442c9 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 9 Feb 2023 15:45:26 +0100 Subject: [PATCH 10/11] currency is always added --- modules/rubiconBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index f65a2637831..bc4cb60557a 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -144,6 +144,7 @@ export const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const {bidRequests} = context; const data = buildRequest(imps, bidderRequest, context); + data.cur = ['USD']; data.test = config.getConfig('debug') ? 1 : 0; deepSetValue(data, 'ext.prebid.cache', { vastxml: { @@ -222,7 +223,6 @@ export const converter = ortbConverter({ context: { netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true ttl: 300, - currency: 'USD' }, processors: pbsExtensions }); From 93d582354b431ebb727edf6d6c5bc34585723f41 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 23 Feb 2023 17:09:04 +0100 Subject: [PATCH 11/11] remove prorperties that are already set by ortb --- modules/rubiconBidAdapter.js | 24 +-------------------- test/spec/modules/rubiconBidAdapter_spec.js | 1 + 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index bc4cb60557a..3b9aedf4cf2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -179,10 +179,6 @@ export const converter = ortbConverter({ if (haveFloorDataBidRequests.length > 0) { data.ext.prebid.floors = { enabled: false }; } - // multibid - const multibid = config.getConfig('multibid'); - addMultibid(data, multibid); - return data; }, imp(buildImp, bidRequest, context) { @@ -205,9 +201,8 @@ export const converter = ortbConverter({ return imp; }, bidResponse(buildBidResponse, bid, context) { - context.mediaType = deepAccess(bid, 'ext.prebid.type'); const bidResponse = buildBidResponse(bid, context); - bidResponse.meta.mediaType = context.mediaType; + bidResponse.meta.mediaType = deepAccess(bid, 'ext.prebid.type'); const {bidRequest} = context; if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = outstreamRenderer(bidResponse); @@ -1190,23 +1185,6 @@ function setBidFloors(bidRequest, imp) { } } -function addMultibid(data, multibid) { - if (!multibid) { - return; - } - deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { - let obj = {}; - - Object.keys(i).forEach(key => { - obj[key.toLowerCase()] = i[key]; - }); - - result.push(obj); - - return result; - }, [])); -} - function addOrtbFirstPartyData(data, nonBannerRequests) { let fpd = {}; const keywords = new Set(); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b0b73f44fe0..d33a9350359 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -18,6 +18,7 @@ import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/userId/index.js'; import 'modules/priceFloors.js'; +import 'modules/multibid/index.js'; import adapterManager from 'src/adapterManager.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid