Skip to content

Commit

Permalink
updated sizeMapping to use sizeConfig and support labels (prebid#1772)
Browse files Browse the repository at this point in the history
* updated sizeMapping to use sizeConfig and support labels

* added new tests for labels and sizes w/ sizeConfig when making auction

* made some names clearer and added type to labels for sizeMapping

* make error message more descriptive in adaptermanager

* remove extra line in adpatermanager
  • Loading branch information
snapwich authored and Matt Kendall committed Nov 9, 2017
1 parent f14f4ee commit 6e552de
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 229 deletions.
151 changes: 88 additions & 63 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @module adaptermanger */

import { flatten, getBidderCodes, getDefinedParams, shuffle } from './utils';
import { mapSizes } from './sizeMapping';
import { resolveStatus } from './sizeMapping';
import { processNativeAdUnitParams, nativeAdapters } from './native';
import { newBidder } from './adapters/bidderFactory';
import { ajaxBuilder } from 'src/ajax';
Expand All @@ -19,53 +19,74 @@ let _s2sConfig = config.getConfig('s2sConfig');

var _analyticsRegistry = {};

function getBids({bidderCode, auctionId, bidderRequestId, adUnits}) {
return adUnits.map(adUnit => {
return adUnit.bids.filter(bid => bid.bidder === bidderCode)
.map(bid => {
let sizes = adUnit.sizes;
if (adUnit.sizeMapping) {
let sizeMapping = mapSizes(adUnit);
if (sizeMapping === '') {
return '';
}
sizes = sizeMapping;
}
/**
* @typedef {object} LabelDescriptor
* @property {boolean} labelAll describes whether or not this object expects all labels to match, or any label to match
* @property {Array<string>} labels the labels listed on the bidder or adUnit
* @property {Array<string>} activeLabels the labels specified as being active by requestBids
*/

/**
* Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids
* @param bidOrAdUnit the bidder or adUnit to get label info on
* @param activeLabels the labels passed to requestBids
* @returns {LabelDescriptor}
*/
function getLabels(bidOrAdUnit, activeLabels) {
if (bidOrAdUnit.labelAll) {
return {labelAll: true, labels: bidOrAdUnit.labelAll, activeLabels};
}
return {labelAll: false, labels: bidOrAdUnit.labelAny, activeLabels};
}

if (adUnit.mediaTypes) {
if (utils.isValidMediaTypes(adUnit.mediaTypes)) {
bid = Object.assign({}, bid, { mediaTypes: adUnit.mediaTypes });
} else {
utils.logError(
`mediaTypes is not correctly configured for adunit ${adUnit.code}`
);
function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}) {
return adUnits.reduce((result, adUnit) => {
let {active, sizes: filteredAdUnitSizes} = resolveStatus(getLabels(adUnit, labels), adUnit.sizes);

if (active) {
result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode)
.reduce((bids, bid) => {
if (adUnit.mediaTypes) {
if (utils.isValidMediaTypes(adUnit.mediaTypes)) {
bid = Object.assign({}, bid, {mediaTypes: adUnit.mediaTypes});
} else {
utils.logError(
`mediaTypes is not correctly configured for adunit ${adUnit.code}`
);
}
}
}

const nativeParams =
adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native');
if (nativeParams) {
bid = Object.assign({}, bid, {
nativeParams: processNativeAdUnitParams(nativeParams),
});
}
const nativeParams =
adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native');
if (nativeParams) {
bid = Object.assign({}, bid, {
nativeParams: processNativeAdUnitParams(nativeParams),
});
}

bid = Object.assign({}, bid, getDefinedParams(adUnit, [
'mediaType',
'renderer'
]));

return Object.assign({}, bid, {
adUnitCode: adUnit.code,
transactionId: adUnit.transactionId,
sizes: sizes,
bidId: bid.bid_id || utils.getUniqueIdentifierStr(),
bidderRequestId,
auctionId
});
}
bid = Object.assign({}, bid, getDefinedParams(adUnit, [
'mediaType',
'renderer'
]));

let {active, sizes} = resolveStatus(getLabels(bid, labels), filteredAdUnitSizes);

if (active) {
bids.push(Object.assign({}, bid, {
adUnitCode: adUnit.code,
transactionId: adUnit.transactionId,
sizes: sizes,
bidId: bid.bid_id || utils.getUniqueIdentifierStr(),
bidderRequestId,
auctionId
}));
}
return bids;
}, [])
);
}).reduce(flatten, []).filter(val => val !== '');
}
return result;
}, []).reduce(flatten, []).filter(val => val !== '');
}

function getAdUnitCopyForPrebidServer(adUnits) {
Expand Down Expand Up @@ -94,7 +115,7 @@ function getAdUnitCopyForPrebidServer(adUnits) {
return adUnitsCopy;
}

exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout) {
exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) {
let bidRequests = [];
let bidderCodes = getBidderCodes(adUnits);
if (config.getConfig('bidderSequence') === RANDOM) {
Expand Down Expand Up @@ -134,7 +155,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout)
auctionId,
bidderRequestId,
tid,
bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy}),
bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy, labels}),
auctionStart: auctionStart,
timeout: _s2sConfig.timeout,
src: CONSTANTS.S2S.SRC
Expand Down Expand Up @@ -165,7 +186,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout)
bidderCode,
auctionId,
bidderRequestId,
bids: getBids({bidderCode, auctionId, bidderRequestId, adUnits}),
bids: getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}),
auctionStart: auctionStart,
timeout: cbTimeout
};
Expand All @@ -174,7 +195,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout)
}
});
return bidRequests;
}
};

exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => {
let serverBidRequests = bidRequests.filter(bidRequest => {
Expand All @@ -194,22 +215,26 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => {
}
}
}
let ajax = ajaxBuilder(bidRequests[0].timeout);
bidRequests.forEach(bidRequest => {
bidRequest.start = new Date().getTime();
// TODO : Do we check for bid in pool from here and skip calling adapter again ?
const adapter = _bidderRegistry[bidRequest.bidderCode];
if (adapter) {
utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
bidRequest.doneCbCallCount = 0;
let done = doneCb(bidRequest.bidderRequestId);
adapter.callBids(bidRequest, addBidResponse, done, ajax);
} else {
utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`);
}
});
}
if (bidRequests.length) {
let ajax = ajaxBuilder(bidRequests[0].timeout);
bidRequests.forEach(bidRequest => {
bidRequest.start = new Date().getTime();
// TODO : Do we check for bid in pool from here and skip calling adapter again ?
const adapter = _bidderRegistry[bidRequest.bidderCode];
if (adapter) {
utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
bidRequest.doneCbCallCount = 0;
let done = doneCb(bidRequest.bidderRequestId);
adapter.callBids(bidRequest, addBidResponse, done, ajax);
} else {
utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`);
}
});
} else {
utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?');
}
};

function transformHeightWidth(adUnit) {
let sizesObj = [];
Expand Down
5 changes: 3 additions & 2 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) {
*
* @returns {Auction} auction instance
*/
export function newAuction({adUnits, adUnitCodes, callback, cbTimeout}) {
export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) {
let _adUnits = adUnits;
let _labels = labels;
let _adUnitCodes = adUnitCodes;
let _bidderRequests = [];
let _bidsReceived = [];
Expand Down Expand Up @@ -181,7 +182,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout}) {
};
events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit);

let bidRequests = adaptermanager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout);
let bidRequests = adaptermanager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels);
utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests);
bidRequests.forEach(bidRequest => {
addBidRequests(bidRequest);
Expand Down
4 changes: 2 additions & 2 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export function newAuctionManager() {
.filter(uniques);
};

_public.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout }) {
const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout })
_public.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout, labels }) {
const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels });
_addAuction(auction);
return auction;
};
Expand Down
5 changes: 3 additions & 2 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,10 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) {
* @param {number} requestOptions.timeout
* @param {Array} requestOptions.adUnits
* @param {Array} requestOptions.adUnitCodes
* @param {Array} requestOptions.labels
* @alias module:pbjs.requestBids
*/
$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes } = {}) {
$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) {
events.emit('requestBids');
const cbTimeout = timeout || config.getConfig('bidderTimeout');
adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits;
Expand Down Expand Up @@ -346,7 +347,7 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a
return;
}

const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout});
const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels});
auction.callBids();
};

Expand Down
125 changes: 74 additions & 51 deletions src/sizeMapping.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,84 @@
import { config } from 'src/config';

let sizeConfig = [];

/**
* @module sizeMapping
* @typedef {object} SizeConfig
*
* @property {string} [mediaQuery] A CSS media query string that will to be interpreted by window.matchMedia. If the
* media query matches then the this config will be active and sizesSupported will filter bid and adUnit sizes. If
* this property is not present then this SizeConfig will only be active if triggered manually by a call to
* pbjs.setConfig({labels:['label']) specifying one of the labels present on this SizeConfig.
* @property {Array<Array>} sizesSupported The sizes to be accepted if this SizeConfig is enabled.
* @property {Array<string>} labels The active labels to match this SizeConfig to an adUnits and/or bidders.
*/
import * as utils from './utils';
let _win;

function mapSizes(adUnit) {
if (!isSizeMappingValid(adUnit.sizeMapping)) {
return adUnit.sizes;
}
const width = getScreenWidth();
if (!width) {
// size not detected - get largest value set for desktop
const mapping = adUnit.sizeMapping.reduce((prev, curr) => {
return prev.minWidth < curr.minWidth ? curr : prev;
});
if (mapping.sizes && mapping.sizes.length) {
return mapping.sizes;
}
return adUnit.sizes;
}
let sizes = '';
const mapping = adUnit.sizeMapping.find(sizeMapping => {
return width >= sizeMapping.minWidth;
});
if (mapping && mapping.sizes && mapping.sizes.length) {
sizes = mapping.sizes;
utils.logMessage(`AdUnit : ${adUnit.code} resized based on device width to : ${sizes}`);
} else {
utils.logMessage(`AdUnit : ${adUnit.code} not mapped to any sizes for device width. This request will be suppressed.`);
}
return sizes;
}

function isSizeMappingValid(sizeMapping) {
if (utils.isArray(sizeMapping) && sizeMapping.length > 0) {
return true;
}
utils.logInfo('No size mapping defined');
return false;
/**
*
* @param {Array<SizeConfig>} config
*/
export function setSizeConfig(config) {
sizeConfig = config;
}
config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig));

function getScreenWidth(win) {
var w = win || _win || window;
var d = w.document;
/**
* Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match
* @param {Array<string>} labels Labels specified on adUnit or bidder
* @param {boolean} labelAll if true, all labels must match to be enabled
* @param {Array<string>} activeLabels Labels passed in through requestBids
* @param {Array<Array<number>>} sizes Sizes specified on adUnit
* @param {Array<SizeConfig>} configs
* @returns {{labels: Array<string>, sizes: Array<Array<number>>}}
*/
export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, sizes = [], configs = sizeConfig) {
let maps = evaluateSizeConfig(configs);

if (w.innerWidth) {
return w.innerWidth;
} else if (d.body.clientWidth) {
return d.body.clientWidth;
} else if (d.documentElement.clientWidth) {
return d.documentElement.clientWidth;
let filteredSizes;
if (maps.shouldFilter) {
filteredSizes = sizes.filter(size => maps.sizesSupported[size]);
} else {
filteredSizes = sizes;
}
return 0;
}

function setWindow(win) {
_win = win;
return {
active: filteredSizes.length > 0 && (
labels.length === 0 || (
(!labelAll && (
labels.some(label => maps.labels[label]) ||
labels.some(label => activeLabels.includes(label))
)) ||
(labelAll && (
labels.reduce((result, label) => !result ? result : (
maps.labels[label] || activeLabels.includes(label)
), true)
))
)
),
sizes: filteredSizes
};
}

export { mapSizes, getScreenWidth, setWindow };
function evaluateSizeConfig(configs) {
return configs.reduce((results, config) => {
if (
typeof config === 'object' &&
typeof config.mediaQuery === 'string' &&
matchMedia(config.mediaQuery).matches
) {
if (Array.isArray(config.sizesSupported)) {
results.shouldFilter = true;
}
['labels', 'sizesSupported'].forEach(
type => (config[type] || []).forEach(
thing => results[type][thing] = true
)
);
}
return results;
}, {
labels: {},
sizesSupported: {},
shouldFilter: false
});
}
Loading

0 comments on commit 6e552de

Please sign in to comment.