Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prebid core & PBS adapter: better support for server-side stored impressions #8154

Merged
merged 7 commits into from
Mar 24, 2022
17 changes: 12 additions & 5 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ let eidPermissions;
* @type {S2SDefaultConfig}
*/
const s2sDefaultConfig = {
bidders: Object.freeze([]),
timeout: 1000,
syncTimeout: 1000,
maxBids: 1,
Expand Down Expand Up @@ -143,7 +144,7 @@ function updateConfigDefaultVendor(option) {
*/
function validateConfigRequiredProps(option) {
const keys = Object.keys(option);
if (['accountId', 'bidders', 'endpoint'].filter(key => {
if (['accountId', 'endpoint'].filter(key => {
if (!includes(keys, key)) {
logError(key + ' missing in server to server config');
return true;
Expand Down Expand Up @@ -706,6 +707,7 @@ Object.assign(ORTB2.prototype, {
// get bidder params in form { <bidder code>: {...params} }
// initialize reduce function with the user defined `ext` properties on the ad unit
const ext = adUnit.bids.reduce((acc, bid) => {
if (bid.bidder == null) return acc;
const adapter = adapterManager.bidderRegistry[bid.bidder];
if (adapter && adapter.getSpec().transformBidParams) {
bid.params = adapter.getSpec().transformBidParams(bid.params, true, adUnit, bidRequests);
Expand Down Expand Up @@ -914,10 +916,15 @@ Object.assign(ORTB2.prototype, {
// a seatbid object contains a `bid` array and a `seat` string
response.seatbid.forEach(seatbid => {
(seatbid.bid || []).forEach(bid => {
const bidRequest = this.getBidRequest(bid.impid, seatbid.seat);
if (bidRequest == null && !s2sConfig.allowUnknownBidderCodes) {
logWarn(`PBS adapter received bid from unknown bidder (${seatbid.seat}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`);
return;
let bidRequest = this.getBidRequest(bid.impid, seatbid.seat);
if (bidRequest == null) {
if (!s2sConfig.allowUnknownBidderCodes) {
logWarn(`PBS adapter received bid from unknown bidder (${seatbid.seat}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`);
return;
}
// for stored impression, a request was made with bidder code `null`. Pick it up here so that NO_BID, BID_WON, etc events
// can work as expected (otherwise, the original request will always result in NO_BID).
bidRequest = this.getBidRequest(bid.impid, null);
}

const cpm = bid.price;
Expand Down
77 changes: 63 additions & 14 deletions modules/s2sTesting.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { setS2STestingModule } from '../src/adapterManager.js';
import {PARTITIONS, partitionBidders, filterBidsForAdUnit, getS2SBidderSet} from '../src/adapterManager.js';
import {find} from '../src/polyfill.js';
import {getBidderCodes, logWarn} from '../src/utils.js';

let s2sTesting = {};

const SERVER = 'server';
const CLIENT = 'client';

s2sTesting.SERVER = SERVER;
s2sTesting.CLIENT = CLIENT;
const {CLIENT, SERVER} = PARTITIONS;
export const s2sTesting = {
...PARTITIONS,
clientTestBidders: new Set()
};

s2sTesting.bidSource = {}; // store bidder sources determined from s2sConfig bidderControl
s2sTesting.globalRand = Math.random(); // if 10% of bidderA and 10% of bidderB should be server-side, make it the same 10%
Expand Down Expand Up @@ -40,7 +40,7 @@ s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) {
[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
Expand All @@ -53,7 +53,7 @@ s2sTesting.calculateBidSources = function(s2sConfig = {}) {
(s2sConfig.bidders || []).forEach((bidder) => {
s2sTesting.bidSource[bidder] = s2sTesting.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)
Expand All @@ -76,10 +76,59 @@ s2sTesting.getSource = function(sourceWeights = {}, bidSources = [SERVER, CLIENT
// choose the first source with an incremental weight > random weight
if (rndWeight < srcIncWeight[source]) return source;
}
};
}

function doingS2STesting(s2sConfig) {
return s2sConfig && s2sConfig.enabled && s2sConfig.testing;
}

// 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(s2sTesting);
function isTestingServerOnly(s2sConfig) {
return Boolean(doingS2STesting(s2sConfig) && s2sConfig.testServerOnly);
}

const adUnitsContainServerRequests = (adUnits, s2sConfig) => Boolean(
find(adUnits, adUnit => find(adUnit.bids, bid => (
bid.bidSource ||
(s2sConfig.bidderControl && s2sConfig.bidderControl[bid.bidder])
) && bid.finalSource === SERVER))
);

partitionBidders.before(function (next, adUnits, s2sConfigs) {
const serverBidders = getS2SBidderSet(s2sConfigs);
let serverOnly = false;

s2sConfigs.forEach((s2sConfig) => {
if (doingS2STesting(s2sConfig)) {
s2sTesting.calculateBidSources(s2sConfig);
const bidderMap = s2sTesting.getSourceBidderMap(adUnits, [...serverBidders]);
// get all adapters doing client testing
bidderMap[CLIENT].forEach((bidder) => s2sTesting.clientTestBidders.add(bidder))
}
if (isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig)) {
logWarn('testServerOnly: True. All client requests will be suppressed.');
serverOnly = true;
}
});

next.bail(getBidderCodes(adUnits).reduce((memo, bidder) => {
if (serverBidders.has(bidder)) {
memo[SERVER].push(bidder);
}
if (!serverOnly && (!serverBidders.has(bidder) || s2sTesting.clientTestBidders.has(bidder))) {
memo[CLIENT].push(bidder);
}
return memo;
}, {[CLIENT]: [], [SERVER]: []}));
});

filterBidsForAdUnit.before(function(next, bids, s2sConfig) {
if (s2sConfig == null) {
next.bail(bids.filter((bid) => !s2sTesting.clientTestBidders.size || bid.finalSource !== SERVER));
} else {
const serverBidders = getS2SBidderSet(s2sConfig);
next.bail(bids.filter((bid) => serverBidders.has(bid.bidder) &&
(!doingS2STesting(s2sConfig) || bid.finalSource !== CLIENT)));
}
});

export default s2sTesting;
13 changes: 3 additions & 10 deletions modules/sizeMappingV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,12 @@ export function checkAdUnitSetupHook(adUnits) {
}
const validatedAdUnits = [];
adUnits.forEach(adUnit => {
const bids = adUnit.bids;
adUnit = adUnitSetupChecks.validateAdUnit(adUnit);
if (adUnit == null) return;

const mediaTypes = adUnit.mediaTypes;
let validatedBanner, validatedVideo, validatedNative;

if (!bids || !isArray(bids)) {
logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`);
return;
}

if (!mediaTypes || Object.keys(mediaTypes).length === 0) {
logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`);
return;
}
if (mediaTypes.banner) {
if (mediaTypes.banner.sizes) {
// Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks!
Expand Down
Loading