Skip to content

Commit

Permalink
Rubicon feature/s2s test module (prebid#1678)
Browse files Browse the repository at this point in the history
* added new server-to-server testing module

* fixed linting errors

* s2sTesting module fixes

* s2sTesting module fixes

* removed "both" option from s2s ab testing module

* removed leftover StorageManager reference

* s2sTesting module improvements

* s2sTesting module improvement

* Revert "Merge branch 'bugfix/pbs-adapter-storagemanager' into rubicon-feature/s2s-test-module"

This reverts commit be232c6, reversing
changes made to 4a3abd7.

* minor s2sTesting fixes
  • Loading branch information
harpere authored and dluxemburg committed Jul 17, 2018
1 parent 55a2db1 commit 9044c27
Show file tree
Hide file tree
Showing 4 changed files with 788 additions and 12 deletions.
126 changes: 126 additions & 0 deletions modules/s2sTesting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { config } from 'src/config';
import { setS2STestingModule } from 'src/adaptermanager';

var CONSTANTS = require('src/constants.json');
const AST = CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING;
export const SERVER = 'server';
export const CLIENT = 'client';

var testing = false; // whether testing is turned on
var bidSource = {}; // store bidder sources determined from s2sConfing bidderControl

// load s2sConfig
config.getConfig('s2sConfig', config => {
testing = config.s2sConfig && config.s2sConfig.testing;
addBidderSourceTargeting(config.s2sConfig)
calculateBidSources(config.s2sConfig);
});

// function to add hb_source_<bidder> adServerTargeting (AST) kvp to bidder settings
function addBidderSourceTargeting(s2sConfig = {}) {
// bail if testing is not turned on
if (!testing) {
return;
}
var bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {};
var bidderControl = s2sConfig.bidderControl || {};
// for each configured bidder
(s2sConfig.bidders || []).forEach((bidder) => {
// remove any existing kvp setting
if (bidderSettings[bidder] && bidderSettings[bidder][AST]) {
bidderSettings[bidder][AST] = bidderSettings[bidder][AST].filter((kvp) => {
return kvp.key !== `hb_source_${bidder}`;
});
}
// if includeSourceKvp === true add new kvp setting
if (bidderControl[bidder] && bidderControl[bidder].includeSourceKvp) {
bidderSettings[bidder] = bidderSettings[bidder] || {};
bidderSettings[bidder][AST] = bidderSettings[bidder][AST] || [];
bidderSettings[bidder][AST].push({
key: `hb_source_${bidder}`,
val: function (bidResponse) {
// default to client (currently only S2S sets this)
return bidResponse.source || CLIENT;
}
});
// make sure "alwaysUseBid" is true so targeting is set
bidderSettings[bidder].alwaysUseBid = true;
}
});
}

export function getSourceBidderMap(adUnits = []) {
var sourceBidders = {[SERVER]: {}, [CLIENT]: {}};

// bail if testing is not turned on
if (!testing) {
return {[SERVER]: [], [CLIENT]: []};
}

adUnits.forEach((adUnit) => {
// if any adUnit bidders specify a bidSource, include them
(adUnit.bids || []).forEach((bid) => {
// calculate the source once and store on bid object
bid.calcSource = bid.calcSource || getSource(bid.bidSource);
// if no bidSource at bid level, default to bidSource from bidder
bid.finalSource = bid.calcSource || bidSource[bid.bidder] || CLIENT; // default to client
// add bidder to sourceBidders data structure
sourceBidders[bid.finalSource][bid.bidder] = true;
});
});

// make sure all bidders in bidSource are in sourceBidders
Object.keys(bidSource).forEach((bidder) => {
sourceBidders[bidSource[bidder]][bidder] = true;
});

// return map of source => array of bidders
return {
[SERVER]: Object.keys(sourceBidders[SERVER]),
[CLIENT]: Object.keys(sourceBidders[CLIENT])
};
}

/**
* @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level
* @param s2sConfig server-to-server configuration
*/
function calculateBidSources(s2sConfig = {}) {
// bail if testing is not turned on
if (!testing) {
return;
}
bidSource = {}; // reset bid sources
// calculate bid source (server/client) for each s2s bidder
var bidderControl = s2sConfig.bidderControl || {};
(s2sConfig.bidders || []).forEach((bidder) => {
bidSource[bidder] = getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server
});
}

/**
* @function getSource() gets a random source based on the given sourceWeights (export just for testing)
* @param sourceWeights mapping of relative weights of potential sources. for example {server: 1, client: 3} should do a server request 25% of the time and a client request 75% of the time.
* @param bidSources list of possible bid sources: "server", "client". In theory could get the sources from the sourceWeights keys, but this is publisher config defined, so bidSources let's us constrain that.
* @return the chosen source ("server" or "client"), or undefined if none chosen
*/
export function getSource(sourceWeights = {}, bidSources = [SERVER, CLIENT]) {
var srcIncWeight = {}; // store incremental weights of each source
var totWeight = 0;
bidSources.forEach((source) => {
totWeight += (sourceWeights[source] || 0);
srcIncWeight[source] = totWeight;
});
if (!totWeight) return; // bail if no source weights
// choose a source randomly based on weights
var rndWeight = Math.random() * totWeight;
for (var i = 0; i < bidSources.length; i++) {
let source = bidSources[i];
// choose the first source with an incremental weight > random weight
if (rndWeight < srcIncWeight[source]) return source;
}
}

// inject the s2sTesting module into the adaptermanager rather than importing it
// importing it causes the packager to include it even when it's not explicitly included in the build
setS2STestingModule(exports);
48 changes: 39 additions & 9 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { newBidder } from './adapters/bidderFactory';
var utils = require('./utils.js');
var CONSTANTS = require('./constants.json');
var events = require('./events');
let s2sTestingModule; // store s2sTesting module if it's loaded

var _bidderRegistry = {};
exports.bidderRegistry = _bidderRegistry;
Expand Down Expand Up @@ -100,33 +101,42 @@ exports.callBids = ({adUnits, cbTimeout}) => {
s2sAdapter.queueSync({bidderCodes});
}

let clientTestAdapters = [];
let s2sTesting = false;
if (_s2sConfig.enabled) {
// if s2sConfig.bidderControl testing is turned on
s2sTesting = _s2sConfig.testing && typeof s2sTestingModule !== 'undefined';
if (s2sTesting) {
// get all adapters doing client testing
clientTestAdapters = s2sTestingModule.getSourceBidderMap(adUnits)[s2sTestingModule.CLIENT];
}

// these are called on the s2s adapter
let adaptersServerSide = _s2sConfig.bidders;

// don't call these client side
// don't call these client side (unless client request is needed for testing)
bidderCodes = bidderCodes.filter((elm) => {
return !adaptersServerSide.includes(elm);
return !adaptersServerSide.includes(elm) || clientTestAdapters.includes(elm);
});
let adUnitsCopy = utils.cloneJson(adUnits);
let adUnitsS2SCopy = utils.cloneJson(adUnits);

// filter out client side bids
adUnitsCopy.forEach((adUnit) => {
adUnitsS2SCopy.forEach((adUnit) => {
if (adUnit.sizeMapping) {
adUnit.sizes = mapSizes(adUnit);
delete adUnit.sizeMapping;
}
adUnit.sizes = transformHeightWidth(adUnit);
adUnit.bids = adUnit.bids.filter((bid) => {
return adaptersServerSide.includes(bid.bidder);
return adaptersServerSide.includes(bid.bidder) && (!s2sTesting || bid.finalSource !== s2sTestingModule.CLIENT);
}).map((bid) => {
bid.bid_id = utils.getUniqueIdentifierStr();
return bid;
});
});

// don't send empty requests
adUnitsCopy = adUnitsCopy.filter(adUnit => {
adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnit => {
return adUnit.bids.length !== 0;
});

Expand All @@ -138,7 +148,7 @@ exports.callBids = ({adUnits, cbTimeout}) => {
requestId,
bidderRequestId,
tid,
bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsCopy}),
bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsS2SCopy}),
start: new Date().getTime(),
auctionStart: auctionStart,
timeout: _s2sConfig.timeout,
Expand All @@ -149,13 +159,27 @@ exports.callBids = ({adUnits, cbTimeout}) => {
}
});

let s2sBidRequest = {tid, 'ad_units': adUnitsCopy};
let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy};
utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.join(',')}`);
if (s2sBidRequest.ad_units.length) {
s2sAdapter.callBids(s2sBidRequest);
}
}

// client side adapters
let adUnitsClientCopy = utils.cloneJson(adUnits);
// filter out s2s bids
adUnitsClientCopy.forEach((adUnit) => {
adUnit.bids = adUnit.bids.filter((bid) => {
return !s2sTesting || bid.finalSource !== s2sTestingModule.SERVER;
})
});

// don't send empty requests
adUnitsClientCopy = adUnitsClientCopy.filter(adUnit => {
return adUnit.bids.length !== 0;
});

bidderCodes.forEach(bidderCode => {
const adapter = _bidderRegistry[bidderCode];
if (adapter) {
Expand All @@ -164,7 +188,7 @@ exports.callBids = ({adUnits, cbTimeout}) => {
bidderCode,
requestId,
bidderRequestId,
bids: getBids({bidderCode, requestId, bidderRequestId, adUnits}),
bids: getBids({bidderCode, requestId, bidderRequestId, 'adUnits': adUnitsClientCopy}),
start: new Date().getTime(),
auctionStart: auctionStart,
timeout: cbTimeout
Expand Down Expand Up @@ -296,3 +320,9 @@ exports.setBidderSequence = function (order) {
exports.setS2SConfig = function (config) {
_s2sConfig = config;
};

// the s2sTesting module is injected when it's loaded rather than being imported
// importing it causes the packager to include it even when it's not explicitly included in the build
exports.setS2STestingModule = function (module) {
s2sTestingModule = module;
};
Loading

0 comments on commit 9044c27

Please sign in to comment.