Skip to content

Commit

Permalink
Restore return format of bidsBackHandler callback (#371)
Browse files Browse the repository at this point in the history
* a `groupByPlacement` reduce function transforms the bids received array to a map

* prevent concurrent bid requests

If an auction is running don't accept a new request for bids..
This adds a `clearAuction` function to set `auctionRunning` false and to clear `_bidsRequested` and `_bidsReceived`. This is a stopgap measure util #353

* clear data structures on additional bid request

* make auctionRunning private and allow passing adUnits to requestBids

* pass adUnitCodes on refresh bids

* use adUnitCodes if passed in to requestBids, otherwise `getBidderCodes()`, disambiguate `bidmanager.getBidderCode`

* restore use of adUnitCodes to filter adUnits

* cherrypick reset targeting merged

* clear targeting fixes and test mock

* Add GptPubadsDefined check
  • Loading branch information
Nate Guisinger authored and Matt Kendall committed May 31, 2016
1 parent 19c60ee commit bf06c0b
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build
# Test Files
test/app
gpt.html
gpt-each-bidder3.html

# Dev File

Expand Down
10 changes: 5 additions & 5 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { BaseAdapter } from './adapters/baseAdapter';
var _bidderRegistry = {};
exports.bidderRegistry = _bidderRegistry;

function getBids({ bidderCode, requestId, bidderRequestId }) {
return pbjs.adUnits.map(adUnit => {
function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) {
return adUnits.map(adUnit => {
return adUnit.bids.filter(bid => bid.bidder === bidderCode)
.map(bid => Object.assign(bid, {
placementCode: adUnit.code,
Expand All @@ -23,18 +23,18 @@ function getBids({ bidderCode, requestId, bidderRequestId }) {
}).reduce(flatten, []);
}

exports.callBids = () => {
exports.callBids = ({ adUnits }) => {
const requestId = utils.getUniqueIdentifierStr();

getBidderCodes().forEach(bidderCode => {
getBidderCodes(adUnits).forEach(bidderCode => {
const adapter = _bidderRegistry[bidderCode];
if (adapter) {
const bidderRequestId = utils.getUniqueIdentifierStr();
const bidderRequest = {
bidderCode,
requestId,
bidderRequestId,
bids: getBids({ bidderCode, requestId, bidderRequestId }),
bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }),
start: new Date().getTime()
};
utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`);
Expand Down
33 changes: 30 additions & 3 deletions src/bidmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const _hgPriceCap = 20.00;
*/
exports.getTimedOutBidders = function () {
return pbjs._bidsRequested
.map(getBidderCodes)
.map(getBidderCode)
.filter(uniques)
.filter(bidder => pbjs._bidsReceived
.map(getBidders)
Expand All @@ -32,7 +32,7 @@ exports.getTimedOutBidders = function () {

function timestamp() { return new Date().getTime(); }

function getBidderCodes(bidSet) {
function getBidderCode(bidSet) {
return bidSet.bidderCode;
}

Expand Down Expand Up @@ -236,6 +236,8 @@ exports.executeCallback = function () {
processCallbacks([externalOneTimeCallback]);
externalOneTimeCallback = null;
}

pbjs.clearAuction();
};

function triggerAdUnitCallbacks(adUnitCode) {
Expand All @@ -249,11 +251,36 @@ function processCallbacks(callbackQueue) {
if (utils.isArray(callbackQueue)) {
for (i = 0; i < callbackQueue.length; i++) {
var func = callbackQueue[i];
func.call(pbjs, pbjs._bidsReceived);
func.call(pbjs, pbjs._bidsReceived.reduce(groupByPlacement, {}));
}
}
}

/**
* groupByPlacement is a reduce function that converts an array of Bid objects
* to an object with placement codes as keys, with each key representing an object
* with an array of `Bid` objects for that placement
* @param prev previous value as accumulator object
* @param item current array item
* @param idx current index
* @param arr the array being reduced
* @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } }
*/
function groupByPlacement(prev, item, idx, arr) {
// this uses a standard "array to map" operation that could be abstracted further
if (item.adUnitCode in Object.keys(prev)) {
// if the adUnitCode key is present in the accumulator object, continue
return prev;
} else {
// otherwise add the adUnitCode key to the accumulator object and set to an object with an
// array of Bids for that adUnitCode
prev[item.adUnitCode] = {
bids: arr.filter(bid => bid.adUnitCode === item.adUnitCode)
};
return prev;
}
}

/**
* Add a one time callback, that is discarded after it is called
* @param {Function} callback [description]
Expand Down
92 changes: 65 additions & 27 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT;

var pb_bidsTimedOut = false;
var pb_sendAllBids = false;
var auctionRunning = false;
var presetTargeting = [];

var eventValidators = {
bidWon: checkDefinedPlacement
Expand All @@ -35,6 +37,7 @@ var eventValidators = {

pbjs._bidsRequested = [];
pbjs._bidsReceived = [];
pbjs._adsReceived = [];

//default timeout for all bids
pbjs.bidderTimeout = pbjs.bidderTimeout || 2000;
Expand Down Expand Up @@ -101,11 +104,35 @@ function checkDefinedPlacement(id) {
return true;
}

function resetPresetTargeting() {
if (isGptPubadsDefined()) {
window.googletag.pubads().getSlots().forEach(slot => {
slot.clearTargeting();
});

setTargeting(presetTargeting);
}
}

function setTargeting(targetingConfig) {
window.googletag.pubads().getSlots().forEach(slot => {
targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() ||
Object.keys(targeting)[0] === slot.getSlotElementId())
.forEach(targeting => targeting[Object.keys(targeting)[0]]
.forEach(key => {
key[Object.keys(key)[0]]
.map((value) => {
utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`);
return value;
})
.forEach(value => slot.setTargeting(Object.keys(key)[0], value));
}));
});
}

function getWinningBidTargeting() {
let presets;
if (isGptPubadsDefined()) {
presets = (function getPresetTargeting() {
presetTargeting = (function getPresetTargeting() {
return window.googletag.pubads().getSlots().map(slot => {
return {
[slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => {
Expand All @@ -125,7 +152,7 @@ function getWinningBidTargeting() {
adUnitCode: adUnitCode,
cpm: 0,
adserverTargeting: {},
timeToRespond : 0
timeToRespond: 0
}));

winners = winners.map(winner => {
Expand All @@ -137,8 +164,8 @@ function getWinningBidTargeting() {
};
});

if (presets) {
winners.concat(presets);
if (presetTargeting) {
winners.concat(presetTargeting);
}

return winners;
Expand Down Expand Up @@ -189,12 +216,10 @@ pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) {
};

/**
* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent.
* @param {string} [adunitCode] adUnitCode to get the bid responses for
* @alias module:pbjs.getAdserverTargetingForAdUnitCode
* @return {object} returnObj return bids
* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent.
* @param adUnitCode {string} adUnitCode to get the bid responses for
* @returns {object} returnObj return bids
*/

pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) {
utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments);

Expand Down Expand Up @@ -287,20 +312,7 @@ pbjs.setTargetingForGPTAsync = function () {
return;
}

window.googletag.pubads().getSlots().forEach(slot => {
getAllTargeting()
.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() ||
Object.keys(targeting)[0] === slot.getSlotElementId())
.forEach(targeting => targeting[Object.keys(targeting)[0]]
.forEach(key => {
key[Object.keys(key)[0]]
.map((value, index, array) => {
utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`);
return value;
})
.forEach(value => slot.setTargeting(Object.keys(key)[0], value));
}));
});
setTargeting(getAllTargeting());
};

/**
Expand Down Expand Up @@ -387,29 +399,55 @@ pbjs.removeAdUnit = function (adUnitCode) {
}
};

pbjs.clearAuction = function() {
auctionRunning = false;
utils.logMessage('Prebid auction cleared');
};

/**
*
* @param bidsBackHandler
* @param timeout
* @param adUnits
* @param adUnitCodes
*/
pbjs.requestBids = function ({ bidsBackHandler, timeout }) {
pbjs.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) {
if (auctionRunning) {
utils.logError('Prebid Error: `pbjs.requestBids` was called while a previous auction was' +
' still running. Resubmit this request.');
return;
} else {
auctionRunning = true;
pbjs._bidsRequested = [];
pbjs._bidsReceived = [];
resetPresetTargeting();
}

const cbTimeout = timeout || pbjs.bidderTimeout;

// use adUnits provided or from pbjs global
adUnits = adUnits || pbjs.adUnits;

// if specific adUnitCodes filter adUnits for those codes
if (adUnitCodes && adUnitCodes.length) {
adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code));
}

if (typeof bidsBackHandler === objectType_function) {
bidmanager.addOneTimeCallback(bidsBackHandler);
}

utils.logInfo('Invoking pbjs.requestBids', arguments);

if (!pbjs.adUnits || pbjs.adUnits.length === 0) {
if (!adUnits || adUnits.length === 0) {
utils.logMessage('No adUnits configured. No bids requested.');
return;
}

//set timeout for all bids
setTimeout(bidmanager.executeCallback, cbTimeout);

adaptermanager.callBids();
adaptermanager.callBids({ adUnits, adUnitCodes });
};

/**
Expand Down
14 changes: 14 additions & 0 deletions test/spec/unit/pbjs_api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ pbjs = pbjs || {};
pbjs._bidsRequested = getBidRequests();
pbjs._bidsReceived = getBidResponses();

function resetAuction() {
pbjs.clearAuction();
pbjs._bidsRequested = getBidRequests();
pbjs._bidsReceived = getBidResponses();
}

var Slot = function Slot(elementId, pathId) {
var slot = {
getSlotElementId: function getSlotElementId() {
Expand All @@ -38,6 +44,10 @@ var Slot = function Slot(elementId, pathId) {

getTargetingKeys: function getTargetingKeys() {
return ['testKey'];
},

clearTargeting: function clearTargeting() {
return googletag.pubads().getSlots();
}
};
slot.spySetTargeting = sinon.spy(slot, 'setTargeting');
Expand Down Expand Up @@ -282,6 +292,7 @@ describe('Unit: Prebid Module', function () {
assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler),
'called bidmanager.addOneTimeCallback');
bidmanager.addOneTimeCallback.restore();
resetAuction();
});

it('should log message when adUnits not configured', () => {
Expand All @@ -294,6 +305,7 @@ describe('Unit: Prebid Module', function () {
assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged');
utils.logMessage.restore();
pbjs.adUnits = adUnitsBackup;
resetAuction();
});

it('should execute callback after timeout', () => {
Expand All @@ -314,13 +326,15 @@ describe('Unit: Prebid Module', function () {

bidmanager.executeCallback.restore();
clock.restore();
resetAuction();
});

it('should call callBids function on adaptermanager', () => {
var spyCallBids = sinon.spy(adaptermanager, 'callBids');
pbjs.requestBids({});
assert.ok(spyCallBids.called, 'called adaptermanager.callBids');
adaptermanager.callBids.restore();
resetAuction();
});
});

Expand Down

0 comments on commit bf06c0b

Please sign in to comment.