From ec0a3867d52ddb91abf7cfd516472ce41158242e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 15 Feb 2022 10:50:11 -0700 Subject: [PATCH 1/3] Convert sizeMappingV1 to work on adUnits (and not bid requests) --- src/adapterManager.js | 120 ++++++++++++++++++------------------------ src/sizeMapping.js | 47 ++++++++++++++++- src/utils.js | 2 +- 3 files changed, 98 insertions(+), 71 deletions(-) diff --git a/src/adapterManager.js b/src/adapterManager.js index 297b8d81536..3799400c705 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -21,7 +21,7 @@ import { shuffle, timestamp } from './utils.js'; -import { getLabels, resolveStatus } from './sizeMapping.js'; +import {processAdUnitsForLabels} from './sizeMapping.js'; import { decorateAdUnitsWithNativeParams, nativeAdapters } from './native.js'; import { newBidder } from './adapters/bidderFactory.js'; import { ajaxBuilder } from './ajax.js'; @@ -57,75 +57,45 @@ var _analyticsRegistry = {}; * @property {Array} activeLabels the labels specified as being active by requestBids */ -function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) { +function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src}) { return adUnits.reduce((result, adUnit) => { - let { - active, - mediaTypes: filteredMediaTypes, - filterResults - } = resolveStatus( - getLabels(adUnit, labels), - adUnit.mediaTypes, - adUnit.sizes - ); - - if (!active) { - logInfo(`Size mapping disabled adUnit "${adUnit.code}"`); - } else if (filterResults) { - logInfo(`Size mapping filtered adUnit "${adUnit.code}" banner sizes from `, filterResults.before, 'to ', filterResults.after); - } - - if (active) { - result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) - .reduce((bids, bid) => { - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'nativeParams', - 'ortb2Imp', - 'mediaType', - 'renderer', - 'storedAuctionResponse' - ])); - - let { - active, - mediaTypes, - filterResults - } = resolveStatus(getLabels(bid, labels), filteredMediaTypes); - - if (!active) { - logInfo(`Size mapping deactivated adUnit "${adUnit.code}" bidder "${bid.bidder}"`); - } else if (filterResults) { - logInfo(`Size mapping filtered adUnit "${adUnit.code}" bidder "${bid.bidder}" banner sizes from `, filterResults.before, 'to ', filterResults.after); - } - - if (isValidMediaTypes(mediaTypes)) { - bid = Object.assign({}, bid, { - mediaTypes - }); - } else { - logError( - `mediaTypes is not correctly configured for adunit ${adUnit.code}` - ); - } + result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) + .reduce((bids, bid) => { + bid = Object.assign({}, bid, getDefinedParams(adUnit, [ + 'nativeParams', + 'ortb2Imp', + 'mediaType', + 'renderer', + 'storedAuctionResponse' + ])); + + const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes + + if (isValidMediaTypes(mediaTypes)) { + bid = Object.assign({}, bid, { + mediaTypes + }); + } else { + logError( + `mediaTypes is not correctly configured for adunit ${adUnit.code}` + ); + } - if (active) { - bids.push(Object.assign({}, bid, { - adUnitCode: adUnit.code, - transactionId: adUnit.transactionId, - sizes: deepAccess(mediaTypes, 'banner.sizes') || deepAccess(mediaTypes, 'video.playerSize') || [], - bidId: bid.bid_id || getUniqueIdentifierStr(), - bidderRequestId, - auctionId, - src, - bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), - bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), - bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder), - })); - } - return bids; - }, []) - ); - } + bids.push(Object.assign({}, bid, { + adUnitCode: adUnit.code, + transactionId: adUnit.transactionId, + sizes: deepAccess(mediaTypes, 'banner.sizes') || deepAccess(mediaTypes, 'video.playerSize') || [], + bidId: bid.bid_id || getUniqueIdentifierStr(), + bidderRequestId, + auctionId, + src, + bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), + bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), + bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder), + })); + return bids; + }, []) + ); return result; }, []).reduce(flatten, []).filter(val => val !== ''); } @@ -212,6 +182,17 @@ export function getAllS2SBidders() { }) } +/** + * Filter and/or modify media types for ad units based on the given labels. + * + * This should return adUnits that are active for the given labels, modified to have their `mediaTypes` + * conform to size mapping configuration. If different bids for the same adUnit should use different `mediaTypes`, + * they should be exposed under `adUnit.bids[].mediaTypes`. + */ +export const setupAdUnitMediaTypes = hook('sync', (adUnits, labels) => { + return processAdUnitsForLabels(adUnits, labels); +}, 'setupAdUnitMediaTypes') + adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { /** * emit and pass adunits for external modification @@ -219,6 +200,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); decorateAdUnitsWithNativeParams(adUnits); + adUnits = setupAdUnitMediaTypes(adUnits, labels); let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { @@ -282,7 +264,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a auctionId, bidderRequestId, uniquePbsTid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), src: CONSTANTS.S2S.SRC}), auctionStart: auctionStart, timeout: s2sConfig.timeout, src: CONSTANTS.S2S.SRC, diff --git a/src/sizeMapping.js b/src/sizeMapping.js index cd5f1190069..ee98b1e5aed 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,5 +1,5 @@ import { config } from './config.js'; -import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils.js'; +import {logWarn, isPlainObject, deepAccess, deepClone, getWindowTop, logInfo} from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; let sizeConfig = []; @@ -154,3 +154,48 @@ function evaluateSizeConfig(configs) { shouldFilter: false }); } + +export function processAdUnitsForLabels(adUnits, activeLabels) { + return adUnits.reduce((adUnits, adUnit) => { + let { + active, + mediaTypes, + filterResults + } = resolveStatus( + getLabels(adUnit, activeLabels), + adUnit.mediaTypes, + adUnit.sizes + ); + + if (!active) { + logInfo(`Size mapping disabled adUnit "${adUnit.code}"`); + } else { + if (filterResults) { + logInfo(`Size mapping filtered adUnit "${adUnit.code}" banner sizes from `, filterResults.before, 'to ', filterResults.after); + } + + adUnit.mediaTypes = mediaTypes; + + adUnit.bids = adUnit.bids.reduce((bids, bid) => { + let { + active, + mediaTypes, + filterResults + } = resolveStatus(getLabels(bid, activeLabels), adUnit.mediaTypes); + + if (!active) { + logInfo(`Size mapping deactivated adUnit "${adUnit.code}" bidder "${bid.bidder}"`); + } else { + if (filterResults) { + logInfo(`Size mapping filtered adUnit "${adUnit.code}" bidder "${bid.bidder}" banner sizes from `, filterResults.before, 'to ', filterResults.after); + bid.mediaTypes = mediaTypes; + } + bids.push(bid); + } + return bids; + }, []); + adUnits.push(adUnit); + } + return adUnits; + }, []); +} diff --git a/src/utils.js b/src/utils.js index 5f08fbf1113..de2df4d4c1c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -693,7 +693,7 @@ export function getKeyByValue(obj, value) { export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) - .reduce(flatten, [])).reduce(flatten).filter(uniques); + .reduce(flatten, [])).reduce(flatten, []).filter(uniques); } export function isGptPubadsDefined() { From 35a2f7d8ce8c1e1706a4c27369465b986e55f6d7 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 17 Feb 2022 14:08:54 -0700 Subject: [PATCH 2/3] Convert sizemappingV2 to work on adUnits (and not bidRequests) --- modules/sizeMappingV2.js | 245 ++++------ test/spec/modules/sizeMappingV2_spec.js | 574 +++++------------------- 2 files changed, 200 insertions(+), 619 deletions(-) diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index b3a6d32b2d8..e9ceff65c81 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -4,8 +4,16 @@ * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ -import { isArray, logError, isArrayOfNums, deepClone, logWarn, getWindowTop, deepEqual, logInfo, isValidMediaTypes, deepAccess, getDefinedParams, getUniqueIdentifierStr, flatten } from '../src/utils.js'; -import { adunitCounter } from '../src/adUnits.js'; +import { + isArray, + logError, + isArrayOfNums, + deepClone, + logWarn, + getWindowTop, + logInfo, + isValidMediaTypes +} from '../src/utils.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getHook } from '../src/hook.js'; import { adUnitSetupChecks } from '../src/prebid.js'; @@ -20,61 +28,33 @@ export const internal = { isLabelActivated }; -/* - 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), - and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by - the 'getBids' function. -*/ - -export const sizeMappingInternalStore = createSizeMappingInternalStore(); - -function createSizeMappingInternalStore() { - const sizeMappingInternalStore = {}; - - return { - initializeStore: function (auctionId, isUsingSizeMappingBool) { - sizeMappingInternalStore[auctionId] = { - usingSizeMappingV2: isUsingSizeMappingBool, - adUnits: [] - }; - }, - getAuctionDetail: function (auctionId) { - return sizeMappingInternalStore[auctionId]; - }, - setAuctionDetail: function (auctionId, adUnitDetail) { - sizeMappingInternalStore[auctionId].adUnits.push(adUnitDetail); - } - } -} +const V2_ADUNITS = new WeakMap(); /* Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, otherwise, returns "false." */ export function isUsingNewSizeMapping(adUnits) { - let isUsingSizeMappingBool = false; - adUnits.forEach(adUnit => { + return !!adUnits.find(adUnit => { + if (V2_ADUNITS.has(adUnit)) return V2_ADUNITS.get(adUnit); if (adUnit.mediaTypes) { // checks for the presence of sizeConfig property at the adUnit.mediaTypes object - Object.keys(adUnit.mediaTypes).forEach(mediaType => { + for (let mediaType of Object.keys(adUnit.mediaTypes)) { if (adUnit.mediaTypes[mediaType].sizeConfig) { - if (isUsingSizeMappingBool === false) { - isUsingSizeMappingBool = true; - } + V2_ADUNITS.set(adUnit, true); + return true; } - }); - - // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids && isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { - if (bidder.sizeConfig) { - if (isUsingSizeMappingBool === false) { - isUsingSizeMappingBool = true; - } + } + for (let bid of adUnit.bids && isArray(adUnit.bids) ? adUnit.bids : []) { + if (bid.sizeConfig) { + V2_ADUNITS.set(adUnit, true); + return true; } - }); + } + V2_ADUNITS.set(adUnit, false); + return false; } }); - return isUsingSizeMappingBool; } /** @@ -289,23 +269,11 @@ export function checkBidderSizeConfigFormat(sizeConfig) { return didCheckPass; } -getHook('getBids').before(function (fn, bidderInfo) { - // check if the adUnit is using sizeMappingV2 specs and store the result in _sizeMappingUsageMap. - if (typeof sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId) === 'undefined') { - const isUsingSizeMappingBool = isUsingNewSizeMapping(bidderInfo.adUnits); - - // initialize sizeMappingInternalStore for the first time for a particular auction - sizeMappingInternalStore.initializeStore(bidderInfo.auctionId, isUsingSizeMappingBool); - } - if (sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId).usingSizeMappingV2) { - // if adUnit is found using sizeMappingV2 specs, run the getBids function which processes the sizeConfig object - // and returns the bids array for a particular bidder. - - const bids = getBids(bidderInfo); - return fn.bail(bids); +getHook('setupAdUnitsForLabels').before(function (fn, adUnits, labels) { + if (isUsingNewSizeMapping(adUnits)) { + return fn.bail(setupAdUnitsForLabels(adUnits, labels)); } else { - // if not using sizeMappingV2, default back to the getBids function defined in adapterManager. - return fn.call(this, bidderInfo); + return fn.call(adUnits, labels); } }); @@ -423,8 +391,8 @@ export function getFilteredMediaTypes(mediaTypes) { return sizeBucketToSizeMap; }, {}); - return { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes }; -}; + return { sizeBucketToSizeMap, activeViewport, transformedMediaTypes }; +} /** * Evaluates the given sizeConfig object and checks for various properties to determine if the sizeConfig is active or not. For example, @@ -475,120 +443,87 @@ export function getActiveSizeBucket(sizeConfig, activeViewport) { } export function getRelevantMediaTypesForBidder(sizeConfig, activeViewport) { + const mediaTypes = new Set(); if (internal.checkBidderSizeConfigFormat(sizeConfig)) { const activeSizeBucket = internal.getActiveSizeBucket(sizeConfig, activeViewport); - return sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes']; + sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes'].forEach((mt) => mediaTypes.add(mt)); } - return []; + return mediaTypes; } -// sets sizeMappingInternalStore for a given auctionId with relevant adUnit information returned from the call to 'getFilteredMediaTypes' function -// returns adUnit details object. -export function getAdUnitDetail(auctionId, adUnit, labels) { - // fetch all adUnits for an auction from the sizeMappingInternalStore - const adUnitsForAuction = sizeMappingInternalStore.getAuctionDetail(auctionId).adUnits; - - // check if the adUnit exists already in the sizeMappingInterStore (check for equivalence of 'code' && 'mediaTypes' properties) - const adUnitDetail = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code && deepEqual(adUnitDetail.mediaTypes, adUnit.mediaTypes)); - - if (adUnitDetail.length > 0) { - adUnitDetail[0].cacheHits++; - return adUnitDetail[0]; - } else { - const identicalAdUnit = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code); - const adUnitInstance = identicalAdUnit.length > 0 && typeof identicalAdUnit[0].instance === 'number' ? identicalAdUnit[identicalAdUnit.length - 1].instance + 1 : 1; - const isLabelActivated = internal.isLabelActivated(adUnit, labels, adUnit.code, adUnitInstance); - const { mediaTypes = adUnit.mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = isLabelActivated && internal.getFilteredMediaTypes(adUnit.mediaTypes); - - const adUnitDetail = { - adUnitCode: adUnit.code, - mediaTypes, - sizeBucketToSizeMap, - activeViewport, - transformedMediaTypes, - instance: adUnitInstance, - isLabelActivated, - cacheHits: 0 - }; - - // set adUnitDetail in sizeMappingInternalStore against the correct 'auctionId'. - sizeMappingInternalStore.setAuctionDetail(auctionId, adUnitDetail); - isLabelActivated && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); - - return adUnitDetail; - } +export function getAdUnitDetail(adUnit, labels, adUnitInstance) { + const isLabelActivated = internal.isLabelActivated(adUnit, labels, adUnit.code, adUnitInstance); + const { sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = isLabelActivated && internal.getFilteredMediaTypes(adUnit.mediaTypes); + isLabelActivated && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); + return { + activeViewport, + transformedMediaTypes, + isLabelActivated, + }; } -export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src }) { +export function setupAdUnitsForLabels(adUnits, labels) { + const duplCounter = {}; return adUnits.reduce((result, adUnit) => { + const instance = (() => { + if (!duplCounter.hasOwnProperty(adUnit.code)) { + duplCounter[adUnit.code] = 1; + } + return duplCounter[adUnit.code]++; + })(); if (adUnit.mediaTypes && isValidMediaTypes(adUnit.mediaTypes)) { - const { activeViewport, transformedMediaTypes, instance: adUnitInstance, isLabelActivated, cacheHits } = internal.getAdUnitDetail(auctionId, adUnit, labels); + const { activeViewport, transformedMediaTypes, isLabelActivated } = internal.getAdUnitDetail(adUnit, labels, instance); if (isLabelActivated) { - // check if adUnit has any active media types remaining, if not drop the adUnit from auction, - // else proceed to evaluate the bids object. if (Object.keys(transformedMediaTypes).length === 0) { - cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); - return result; - } - result - .push(adUnit.bids.filter(bid => bid.bidder === bidderCode) - .reduce((bids, bid) => { - if (internal.isLabelActivated(bid, labels, adUnit.code, adUnitInstance)) { - // handle native params - - bid = Object.assign({}, bid, getDefinedParams(adUnit, ['mediaType', 'renderer', 'nativeParams'])); - - if (bid.sizeConfig) { - const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); - if (relevantMediaTypes.length === 0) { - logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bidderCode} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); - bid = Object.assign({}, bid); - } else if (relevantMediaTypes[0] !== 'none') { - const bidderMediaTypes = Object - .keys(transformedMediaTypes) - .filter(mt => relevantMediaTypes.indexOf(mt) > -1) - .reduce((mediaTypes, mediaType) => { - mediaTypes[mediaType] = transformedMediaTypes[mediaType]; - return mediaTypes; - }, {}); - - if (Object.keys(bidderMediaTypes).length > 0) { - bid = Object.assign({}, bid, { mediaTypes: bidderMediaTypes }); - } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); - return bids; + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); + } else { + adUnit.mediaTypes = transformedMediaTypes; + adUnit.bids = adUnit.bids.reduce((bids, bid) => { + if (internal.isLabelActivated(bid, labels, adUnit.code, instance)) { + if (bid.sizeConfig) { + const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); + if (relevantMediaTypes.size === 0) { + logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remain active.`); + bids.push(bid); + } else if (!relevantMediaTypes.has('none')) { + let modified = false; + const bidderMediaTypes = Object.fromEntries( + Object.entries(transformedMediaTypes) + .filter(([key, val]) => { + if (!relevantMediaTypes.has(key)) { + modified = true; + return false; + } + return true; + }) + ); + if (Object.keys(bidderMediaTypes).length > 0) { + if (modified) { + bid.mediaTypes = bidderMediaTypes; } + bids.push(bid); } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); - return bids; + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); } + } else { + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); } - bids.push(Object.assign({}, bid, { - adUnitCode: adUnit.code, - transactionId: adUnit.transactionId, - sizes: deepAccess(transformedMediaTypes, 'banner.sizes') || deepAccess(transformedMediaTypes, 'video.playerSize') || [], - mediaTypes: bid.mediaTypes || transformedMediaTypes, - bidId: bid.bid_id || getUniqueIdentifierStr(), - bidderRequestId, - auctionId, - src, - bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), - bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), - bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder) - })); - return bids; } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); - return bids; + bids.push(bid); } - }, [])); + } else { + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); + } + return bids; + }, []); + result.push(adUnit); + } } else { - cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit is disabled due to failing label check.`); + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}) => Ad unit is disabled due to failing label check.`); } } else { logWarn(`Size Mapping V2:: Ad Unit: ${adUnit.code} => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); - return result; } return result; - }, []).reduce(flatten, []).filter(val => val !== ''); + }, []) } diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index ab06ddc8f51..453edf6ebaf 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -13,10 +13,11 @@ import { getAdUnitDetail, getFilteredMediaTypes, getBids, - internal + internal, setupAdUnitsForLabels } from '../../../modules/sizeMappingV2.js'; import { adUnitSetupChecks } from '../../../src/prebid.js'; +import {deepClone} from '../../../src/utils.js'; const AD_UNITS = [{ code: 'div-gpt-ad-1460505748561-0', @@ -1106,14 +1107,14 @@ describe('sizeMappingV2', function () { internal.checkBidderSizeConfigFormat.restore(); internal.getActiveSizeBucket.restore(); }); - it('should return an empty array if the bidder sizeConfig object is not formatted correctly', function () { + it('should return an empty set if the bidder sizeConfig object is not formatted correctly', function () { const sizeConfig = [ { minViewPort: [], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] } ]; const activeViewport = [720, 600]; const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeViewport); - expect(relevantMediaTypes).to.deep.equal([]); + expect(relevantMediaTypes.size).to.equal(0) }); it('should call function checkBidderSizeConfigFormat() once', function () { @@ -1140,220 +1141,51 @@ describe('sizeMappingV2', function () { sinon.assert.calledWith(internal.getActiveSizeBucket, sizeConfig, activeViewport); }); - it('should return the array contained in "relevantMediaTypes" property whose sizeBucket matches with the current viewport', function () { + it('should return the types contained in "relevantMediaTypes" property whose sizeBucket matches with the current viewport', function () { const sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] } ]; const activeVewport = [720, 600]; const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeVewport); - expect(relevantMediaTypes).to.deep.equal(['banner', 'video']); + expect([...relevantMediaTypes]).to.deep.equal(['banner', 'video']); }); }); - describe('getAdUnitDetail(auctionId, adUnit, labels)', function () { + describe('getAdUnitDetail', function () { const adUnitDetailFixture_1 = { - adUnitCode: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, // remove if < 750px - { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px - { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px - { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px - ] - }, - video: { - context: 'instream', - sizeConfig: [ - { minViewPort: [0, 0], playerSize: [] }, - { minViewPort: [800, 0], playerSize: [[640, 400]] }, - { minViewPort: [1200, 0], playerSize: [] } - ] - }, - native: { - image: { - required: true, - sizes: [150, 50] - }, - title: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true - }, - privacyLink: { - required: false - }, - body: { - required: true - }, - icon: { - required: true, - sizes: [50, 50] - }, - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [600, 0], active: true }, - { minViewPort: [1000, 0], active: false } - ] - } - }, sizeBucketToSizeMap: {}, activeViewport: {}, - transformedMediaTypes: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, - }; - const adUnitDetailFixture_2 = { - adUnitCode: 'div-gpt-ad-1460505748561-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'instream', - playerSize: [300, 460] - } - }, - sizeBucketToSizeMap: {}, - activeViewport: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, transformedMediaTypes: { banner: {}, video: {} } } - // adunit with same code at adUnitDetailFixture_1 but differnet mediaTypes object - const adUnitDetailFixture_3 = { - adUnitCode: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] } - ] - } - }, - sizeBucketToSizeMap: {}, - activeViewport: {}, - transformedMediaTypes: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, - } const labels = ['mobile']; beforeEach(function () { - sinon - .stub(sizeMappingInternalStore, 'getAuctionDetail') - .withArgs('a1b2c3') - .returns({ - usingSizeMappingV2: true, - adUnits: [adUnitDetailFixture_1] - }); - - sinon - .stub(sizeMappingInternalStore, 'setAuctionDetail') - .withArgs('a1b2c3', adUnitDetailFixture_2); - const getFilteredMediaTypesStub = sinon.stub(internal, 'getFilteredMediaTypes'); getFilteredMediaTypesStub .withArgs(AD_UNITS[1].mediaTypes) - .returns(adUnitDetailFixture_2); - - getFilteredMediaTypesStub - .withArgs(adUnitDetailFixture_3.mediaTypes) - .returns(adUnitDetailFixture_3); - + .returns(adUnitDetailFixture_1); sinon.spy(utils, 'logInfo'); sinon.spy(utils, 'deepEqual'); }); afterEach(function () { - sizeMappingInternalStore.getAuctionDetail.restore(); - sizeMappingInternalStore.setAuctionDetail.restore(); internal.getFilteredMediaTypes.restore(); utils.logInfo.restore(); utils.deepEqual.restore(); }); - it('should return adUnit detail object from "sizeMappingInternalStore" if adUnit is already present in the store', function () { - const [adUnit] = utils.deepClone(AD_UNITS); - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.getAuctionDetail, 1); - sinon.assert.callCount(utils.deepEqual, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 0); - expect(adUnitDetail.cacheHits).to.equal(1); - expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_1); - }); - - it('should NOT return adunit detail object from "sizeMappingInternalStore" if adUnit with the SAME CODE BUT DIFFERENT MEDIATYPES OBJECT is present in the store', function () { - const [adUnit] = utils.deepClone(AD_UNITS); - adUnit.mediaTypes = { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] } - ] - } - }; - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.getAuctionDetail, 1); - sinon.assert.callCount(utils.deepEqual, 1); - expect(adUnitDetail).to.not.deep.equal(adUnitDetailFixture_1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - }); - - it('should store value in "sizeMappingInterStore" object if adUnit is NOT preset in this object', function () { - const [, adUnit] = utils.deepClone(AD_UNITS); - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.setAuctionDetail, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_2); - }); - it('should log info message to show the details for activeSizeBucket', function () { const [, adUnit] = utils.deepClone(AD_UNITS); - getAdUnitDetail('a1b2c3', adUnit, labels); + getAdUnitDetail(adUnit, labels, 1); sinon.assert.callCount(utils.logInfo, 1); - sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_2.sizeBucketToSizeMap); - }); - - it('should increment "instance" count if presence of "Identical ad units" is detected', function () { - const adUnit = { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [{ minViewPort: [0, 0], sizes: [[300, 300]] }] - } - }, - bids: [{ - bidder: 'appnexus', - params: 12 - }] - }; - - internal.getFilteredMediaTypes.restore(); - - sinon.stub(internal, 'getFilteredMediaTypes') - .withArgs(adUnit.mediaTypes) - .returns({ mediaTypes: {}, sizeBucketToSizeMap: {}, activeViewPort: [], transformedMediaTypes: {} }); - - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.setAuctionDetail, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - expect(adUnitDetail.instance).to.equal(2); + sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_1.sizeBucketToSizeMap); }); it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function () { const [adUnit] = utils.deepClone(AD_UNITS); adUnit.labelAny = ['tablet']; - getAdUnitDetail('a1b2c3', adUnit, labels); + getAdUnitDetail(adUnit, labels, 1); // assertions sinon.assert.callCount(internal.getFilteredMediaTypes, 0); @@ -1376,57 +1208,8 @@ describe('sizeMappingV2', function () { utils.getWindowTop.restore(); utils.logWarn.restore(); }); - it('should return filteredMediaTypes object with all four properties (mediaTypes, transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () { + it('should return filteredMediaTypes object with all properties (transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () { const [adUnit] = utils.deepClone(AD_UNITS); - const expectedMediaTypes = { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, // remove if < 750px - { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px - { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px - { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px - ] - }, - video: { - context: 'instream', - sizeConfig: [ - { minViewPort: [0, 0], playerSize: [] }, - { minViewPort: [800, 0], playerSize: [[640, 400]] }, - { minViewPort: [1200, 0], playerSize: [] } - ] - }, - native: { - image: { - required: true, - sizes: [150, 50] - }, - title: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true - }, - privacyLink: { - required: false - }, - body: { - required: true - }, - icon: { - required: true, - sizes: [50, 50] - }, - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [600, 0], active: true }, - { minViewPort: [1000, 0], active: false } - ] - } - }; const expectedSizeBucketToSizeMap = { banner: { activeSizeBucket: [1600, 0], @@ -1459,8 +1242,7 @@ describe('sizeMappingV2', function () { ] } }; - const { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = getFilteredMediaTypes(adUnit.mediaTypes); - expect(mediaTypes).to.deep.equal(expectedMediaTypes); + const { sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = getFilteredMediaTypes(adUnit.mediaTypes); expect(activeViewport).to.deep.equal(expectedActiveViewport); expect(sizeBucketToSizeMap).to.deep.equal(expectedSizeBucketToSizeMap); expect(transformedMediaTypes).to.deep.equal(expectedTransformedMediaTypes); @@ -1478,7 +1260,8 @@ describe('sizeMappingV2', function () { }); }); - describe('getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src })', function () { + describe('setupAdUnitsForLabels', function () { + let adUnits, adUnitDetail; const basic_AdUnit = [{ code: 'adUnit1', mediaTypes: { @@ -1508,46 +1291,31 @@ describe('sizeMappingV2', function () { }], transactionId: '123456' }]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - transactionId: '123456', - sizes: [[300, 200], [400, 600]], - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [[500, 0]], - activeSizeDimensions: [[300, 200], [400, 600]] - } - }, - activeViewport: [560, 260], - transformedMediaTypes: { - banner: { - filteredSizeConfig: [ - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizeConfig: [ - { minViewPort: [0, 0], sizes: [[]] }, - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizes: [[300, 200], [400, 600]] - } - }, - isLabelActivated: true, - instance: 1, - cacheHits: 0 - }; + + const bidderMap = (adUnit) => Object.fromEntries(adUnit.bids.map((bid) => [bid.bidder, bid])); + beforeEach(function () { + adUnits = deepClone(basic_AdUnit); + adUnitDetail = { + activeViewport: [560, 260], + transformedMediaTypes: { + banner: { + filteredSizeConfig: [ + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizeConfig: [ + { minViewPort: [0, 0], sizes: [[]] }, + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizes: [[300, 200], [400, 600]] + } + }, + isLabelActivated: true, + }; sinon .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', basic_AdUnit[0], []) - .returns(adUnitDetailFixture); + .withArgs(adUnits[0], []) + .callsFake(() => adUnitDetail); sinon.spy(internal, 'getRelevantMediaTypesForBidder'); @@ -1564,153 +1332,82 @@ describe('sizeMappingV2', function () { utils.logWarn.restore(); }); - it('should return an array of bids specific to the bidder', function () { - const expectedMediaTypes = { - banner: { - filteredSizeConfig: [ - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizeConfig: [ - { minViewPort: [0, 0], sizes: [[]] }, - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizes: [[300, 200], [400, 600]] - } + it('should update adUnit mediaTypes', function () { + adUnitDetail = { + activeViewport: [560, 260], + transformedMediaTypes: { + banner: { + filteredSizeConfig: [ + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizeConfig: [ + { minViewPort: [0, 0], sizes: [[]] }, + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizes: [[300, 200], [400, 600]] + } + }, + isLabelActivated: true, }; - const bidRequests_1 = getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - expect(bidRequests_1[0].mediaTypes).to.deep.equal(expectedMediaTypes); - expect(bidRequests_1[0].bidder).to.equal('appnexus'); - - const bidRequests_2 = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0aa', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - expect(bidRequests_2[0]).to.be.undefined; + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + + expect(actual.mediaTypes).to.deep.equal(adUnitDetail.transformedMediaTypes); + const bids = bidderMap(actual); + expect(bids.appnexus).to.not.be.undefined; + expect(bids.appnexus.mediaTypes).to.be.undefined; + expect(bids.rubicon).to.be.undefined; sinon.assert.callCount(internal.getRelevantMediaTypesForBidder, 1); }); it('should log an error message if ad unit is disabled because there are no active media types left after size config filtration', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].mediaTypes.banner.sizeConfig = [ + adUnits[0].mediaTypes.banner.sizeConfig = [ { minViewPort: [0, 0], sizes: [] }, { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } ]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [0, 0], - activeSizeDimensions: [[]] - } - }, + adUnitDetail = { activeViewport: [560, 260], transformedMediaTypes: {}, isLabelActivated: true, - instance: 1, - cacheHits: 0 }; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.be.undefined; + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + expect(actual).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); }); it('should throw an error if bidder level sizeConfig is not configured properly', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].bids[1].sizeConfig = [ + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner'] } ]; - - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - - expect(bidRequests[0]).to.not.be.undefined; + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + expect(actual).to.not.be.undefined; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.be.undefined; sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); + sinon.assert.calledWith(utils.logError, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remain active.`); }); - it('should ensure bidder relevantMediaTypes is a subset of active media types at the ad unit level', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].bids[1].sizeConfig = [ + it('should ensure only relevant sizes are in adUnit.mediaTypes', function () { + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [400, 0], relevantMediaTypes: ['banner'] } ]; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.not.be.undefined; - expect(bidRequests[0].mediaTypes.banner).to.not.be.undefined; - expect(bidRequests[0].mediaTypes.banner.sizes).to.deep.equal([[300, 200], [400, 600]]); + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + expect(actual).to.not.be.undefined; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.be.undefined; + expect(bids.appnexus.mediaTypes).to.be.undefined; + expect(actual.mediaTypes.banner).to.not.be.undefined; + expect(actual.mediaTypes.banner.sizes).to.deep.equal([[300, 200], [400, 600]]); }); - it('should logInfo if bidder relevantMediaTypes contains media type that is not active at the ad unit level', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].mediaTypes = { + it('should remove bidder if its relevantMediaTypes contains media type that is not active at the ad unit level', function () { + adUnits[0].mediaTypes = { banner: { sizeConfig: [ { minViewPort: [0, 0], sizes: [] }, @@ -1725,60 +1422,22 @@ describe('sizeMappingV2', function () { } }; - adUnit[0].bids[1].sizeConfig = [ + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [200, 0], relevantMediaTypes: ['banner'] } ]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [700, 0], sizes: [[300, 200], [400, 600]] } - ] - }, - native: { - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [400, 0], active: true } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [0, 0], - activeSizeDimensions: [[]] - }, - native: { - activeSizeBucket: [400, 0], - activeSizeDimensions: 'NA' - } - }, + adUnitDetail = { activeViewport: [560, 260], transformedMediaTypes: { native: {} }, isLabelActivated: true, - instance: 1, - cacheHits: 0 }; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0], []) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.be.undefined; + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const bids = bidderMap(actual); + expect(bids.rubicon).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); }); @@ -1788,38 +1447,19 @@ describe('sizeMappingV2', function () { .stub(utils, 'isValidMediaTypes') .returns(false); - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); + try { + setupAdUnitsForLabels(adUnits, []); + } finally { + utils.isValidMediaTypes.restore(); + } + sinon.assert.callCount(utils.logWarn, 1); sinon.assert.calledWith(utils.logWarn, `Size Mapping V2:: Ad Unit: adUnit1 => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); - - utils.isValidMediaTypes.restore(); }); it('should log a message if ad unit is disabled due to a failing label check', function () { - internal.getAdUnitDetail.restore(); - const adUnitDetail = Object.assign({}, adUnitDetailFixture); adUnitDetail.isLabelActivated = false; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', basic_AdUnit[0], []) - .returns(adUnitDetail); - - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - + setupAdUnitsForLabels(adUnits, []); sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit is disabled due to failing label check.`); }); @@ -1827,19 +1467,25 @@ describe('sizeMappingV2', function () { it('should log a message if bidder is disabled due to a failing label check', function () { const stub = sinon.stub(internal, 'isLabelActivated').returns(false); - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); + try { + setupAdUnitsForLabels(adUnits, []); + } finally { + stub.restore(); + } - sinon.assert.callCount(utils.logInfo, 1); + sinon.assert.callCount(utils.logInfo, 2); // called once for each bidder sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: appnexus => Label check for this bidder has failed. This bidder is disabled.`); + }); - internal.isLabelActivated.restore(); - }) + it('should set adUnit.bids[].mediaTypes if the bid mediaTypes should differ from the adUnit', () => { + adUnits[0].mediaTypes.native = {}; + adUnits[0].bids[1].sizeConfig = [ + { minViewPort: [0, 0], relevantMediaTypes: ['banner'] } + ]; + adUnitDetail.transformedMediaTypes.native = {}; + const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.deep.equal({banner: adUnitDetail.transformedMediaTypes.banner}); + }); }); }); From 23b5f2d2aa8dff0757aa7d9cda362c2ad0e9e767 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 17 Feb 2022 14:21:08 -0700 Subject: [PATCH 3/3] Warn about unsupported bidder-level sizeMapping configuration in PBS adapter --- modules/prebidServerBidAdapter/index.js | 8 ++++++++ modules/sizeMappingV2.js | 8 ++++---- test/spec/modules/sizeMappingV2_spec.js | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a5cd0c4c221..06ca2c9be5f 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -516,6 +516,14 @@ Object.assign(ORTB2.prototype, { // transform ad unit into array of OpenRTB impression objects let impIds = new Set(); adUnits.forEach(adUnit => { + // TODO: support labels / conditional bids + // for now, just warn about them + adUnit.bids.forEach((bid) => { + if (bid.mediaTypes != null) { + logWarn(`Prebid Server adapter does not (yet) support bidder-specific mediaTypes for the same adUnit. Size mapping configuration will be ignored for adUnit: ${adUnit.code}, bidder: ${bid.bidder}`); + } + }) + // in case there is a duplicate imp.id, add '-2' suffix to the second imp.id. // e.g. if there are 2 adUnits (case of twin adUnit codes) with code 'test', // first imp will have id 'test' and second imp will have id 'test-2' diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index e9ceff65c81..de9d0fbcd53 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -269,11 +269,11 @@ export function checkBidderSizeConfigFormat(sizeConfig) { return didCheckPass; } -getHook('setupAdUnitsForLabels').before(function (fn, adUnits, labels) { +getHook('setupAdUnitMediaTypes').before(function (fn, adUnits, labels) { if (isUsingNewSizeMapping(adUnits)) { - return fn.bail(setupAdUnitsForLabels(adUnits, labels)); + return fn.bail(setupAdUnitMediaTypes(adUnits, labels)); } else { - return fn.call(adUnits, labels); + return fn.call(this, adUnits, labels); } }); @@ -462,7 +462,7 @@ export function getAdUnitDetail(adUnit, labels, adUnitInstance) { }; } -export function setupAdUnitsForLabels(adUnits, labels) { +export function setupAdUnitMediaTypes(adUnits, labels) { const duplCounter = {}; return adUnits.reduce((result, adUnit) => { const instance = (() => { diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 453edf6ebaf..c8002b4bacc 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -13,7 +13,7 @@ import { getAdUnitDetail, getFilteredMediaTypes, getBids, - internal, setupAdUnitsForLabels + internal, setupAdUnitMediaTypes } from '../../../modules/sizeMappingV2.js'; import { adUnitSetupChecks } from '../../../src/prebid.js'; @@ -1350,7 +1350,7 @@ describe('sizeMappingV2', function () { isLabelActivated: true, }; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; expect(actual.mediaTypes).to.deep.equal(adUnitDetail.transformedMediaTypes); const bids = bidderMap(actual); @@ -1372,7 +1372,7 @@ describe('sizeMappingV2', function () { isLabelActivated: true, }; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; expect(actual).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); @@ -1383,7 +1383,7 @@ describe('sizeMappingV2', function () { { minViewPort: [], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner'] } ]; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; expect(actual).to.not.be.undefined; const bids = bidderMap(actual); expect(bids.rubicon.mediaTypes).to.be.undefined; @@ -1397,7 +1397,7 @@ describe('sizeMappingV2', function () { { minViewPort: [400, 0], relevantMediaTypes: ['banner'] } ]; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; expect(actual).to.not.be.undefined; const bids = bidderMap(actual); expect(bids.rubicon.mediaTypes).to.be.undefined; @@ -1435,7 +1435,7 @@ describe('sizeMappingV2', function () { isLabelActivated: true, }; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; const bids = bidderMap(actual); expect(bids.rubicon).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); @@ -1448,7 +1448,7 @@ describe('sizeMappingV2', function () { .returns(false); try { - setupAdUnitsForLabels(adUnits, []); + setupAdUnitMediaTypes(adUnits, []); } finally { utils.isValidMediaTypes.restore(); } @@ -1459,7 +1459,7 @@ describe('sizeMappingV2', function () { it('should log a message if ad unit is disabled due to a failing label check', function () { adUnitDetail.isLabelActivated = false; - setupAdUnitsForLabels(adUnits, []); + setupAdUnitMediaTypes(adUnits, []); sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit is disabled due to failing label check.`); }); @@ -1468,7 +1468,7 @@ describe('sizeMappingV2', function () { const stub = sinon.stub(internal, 'isLabelActivated').returns(false); try { - setupAdUnitsForLabels(adUnits, []); + setupAdUnitMediaTypes(adUnits, []); } finally { stub.restore(); } @@ -1483,7 +1483,7 @@ describe('sizeMappingV2', function () { { minViewPort: [0, 0], relevantMediaTypes: ['banner'] } ]; adUnitDetail.transformedMediaTypes.native = {}; - const actual = setupAdUnitsForLabels(adUnits, [])[0]; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; const bids = bidderMap(actual); expect(bids.rubicon.mediaTypes).to.deep.equal({banner: adUnitDetail.transformedMediaTypes.banner}); });