Skip to content

Commit

Permalink
new no bid event and no bids available from auction (prebid#3286)
Browse files Browse the repository at this point in the history
  • Loading branch information
snapwich authored and Pedro López Jiménez committed Mar 18, 2019
1 parent 81b8937 commit ff6f62a
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 62 deletions.
2 changes: 2 additions & 0 deletions src/AnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
BID_REQUESTED,
BID_TIMEOUT,
BID_RESPONSE,
NO_BID,
BID_WON,
BID_ADJUSTMENT,
BIDDER_DONE,
Expand Down Expand Up @@ -102,6 +103,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler }
_handlers = {
[BID_REQUESTED]: args => this.enqueue({ eventType: BID_REQUESTED, args }),
[BID_RESPONSE]: args => this.enqueue({ eventType: BID_RESPONSE, args }),
[NO_BID]: args => this.enqueue({ eventType: NO_BID, args }),
[BID_TIMEOUT]: args => this.enqueue({ eventType: BID_TIMEOUT, args }),
[BID_WON]: args => this.enqueue({ eventType: BID_WON, args }),
[BID_ADJUSTMENT]: args => this.enqueue({ eventType: BID_ADJUSTMENT, args }),
Expand Down
14 changes: 9 additions & 5 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @module adaptermanger */

import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp } from './utils';
import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest } from './utils';
import { getLabels, resolveStatus } from './sizeMapping';
import { processNativeAdUnitParams, nativeAdapters } from './native';
import { newBidder } from './adapters/bidderFactory';
Expand Down Expand Up @@ -340,7 +340,7 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbac
if (s2sBidRequest.ad_units.length) {
let doneCbs = serverBidRequests.map(bidRequest => {
bidRequest.start = timestamp();
return doneCb;
return doneCb.bind(bidRequest);
});

// only log adapters that actually have adUnit bids
Expand All @@ -360,7 +360,12 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbac
s2sAdapter.callBids(
s2sBidRequest,
serverBidRequests,
addBidResponse,
function(adUnitCode, bid) {
let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode);
if (bidderRequest) {
addBidResponse.call(bidderRequest, adUnitCode, bid)
}
},
() => doneCbs.forEach(done => done()),
s2sAjax
);
Expand All @@ -375,12 +380,11 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbac
const adapter = _bidderRegistry[bidRequest.bidderCode];
utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`);
events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest);
bidRequest.doneCbCallCount = 0;
let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? {
request: requestCallbacks.request.bind(null, bidRequest.bidderCode),
done: requestCallbacks.done
} : undefined);
adapter.callBids(bidRequest, addBidResponse, doneCb, ajax);
adapter.callBids(bidRequest, addBidResponse.bind(bidRequest), doneCb.bind(bidRequest), ajax);
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export function newBidder(spec) {
// After all the responses have come back, call done() and
// register any required usersync pixels.
const responses = [];
function afterAllResponses(bids) {
function afterAllResponses() {
done();
events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest);
registerSyncs(responses, bidderRequest.gdprConsent);
Expand Down
57 changes: 38 additions & 19 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
* @property {function(): void} callBids - sends requests to all adapters for bids
*/

import { uniques, flatten, timestamp, adUnitsFilter, getBidderRequest, deepAccess, delayExecution, getBidRequest } from './utils';
import { uniques, flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest } from './utils';
import { getPriceBucketString } from './cpmBucketManager';
import { getNativeTargeting } from './native';
import { getCacheUrl, store } from './videoCache';
Expand Down Expand Up @@ -95,6 +95,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
let _adUnitCodes = adUnitCodes;
let _bidderRequests = [];
let _bidsReceived = [];
let _noBids = [];
let _auctionStart;
let _auctionEnd;
let _auctionId = utils.generateUUID();
Expand All @@ -106,6 +107,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})

function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests) };
function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); }
function addNoBid(noBid) { _noBids = _noBids.concat(noBid); }

function getProperties() {
return {
Expand All @@ -117,6 +119,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
adUnitCodes: _adUnitCodes,
labels: _labels,
bidderRequests: _bidderRequests,
noBids: _noBids,
bidsReceived: _bidsReceived,
winningBids: _winningBids,
timeout: _timeout
Expand Down Expand Up @@ -180,7 +183,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
}
}

function auctionDone(bidderCount) {
function auctionDone() {
// when all bidders have called done callback atleast once it means auction is complete
utils.logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived);
_auctionStatus = AUCTION_COMPLETED;
Expand Down Expand Up @@ -213,10 +216,12 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties());

let callbacks = auctionCallbacks(auctionDone, this);
let boundObj = {
auctionAddBidResponse: callbacks.addBidResponse
};
adaptermanager.callBids(_adUnits, bidRequests, addBidResponse.bind(boundObj), callbacks.adapterDone, {
adaptermanager.callBids(_adUnits, bidRequests, function(...args) {
addBidResponse.apply({
dispatch: callbacks.addBidResponse,
bidderRequest: this
}, args)
}, callbacks.adapterDone, {
request(source, origin) {
increment(outstandingRequests, origin);
increment(requests, source);
Expand Down Expand Up @@ -301,6 +306,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})

return {
addBidReceived,
addNoBid,
executeCallback,
callBids,
addWinningBid,
Expand All @@ -313,20 +319,19 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
getAdUnitCodes: () => _adUnitCodes,
getBidRequests: () => _bidderRequests,
getBidsReceived: () => _bidsReceived,
getNoBids: () => _noBids
}
}

export const addBidResponse = createHook('asyncSeries', function(adUnitCode, bid) {
this.auctionAddBidResponse(adUnitCode, bid);
this.dispatch.call(this.bidderRequest, adUnitCode, bid);
}, 'addBidResponse');

export function auctionCallbacks(auctionDone, auctionInstance) {
let outstandingBidsAdded = 0;
let allAdapterCalledDone = false;

let onAllAdapterDone = delayExecution(() => {
allAdapterCalledDone = true;
}, auctionInstance.getBidRequests().length);
let bidderRequestsDone = new Set();
let bidResponseMap = {};

function afterBidAdded() {
outstandingBidsAdded--;
Expand All @@ -336,23 +341,37 @@ export function auctionCallbacks(auctionDone, auctionInstance) {
}

function addBidResponse(adUnitCode, bid) {
let bidderRequest = this;

bidResponseMap[bid.requestId] = true;

outstandingBidsAdded++;
let bidRequests = auctionInstance.getBidRequests();
let auctionId = auctionInstance.getAuctionId();

let bidRequest = getBidderRequest(bidRequests, bid.bidderCode, adUnitCode);
let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId});
let bidResponse = getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId});

if (bidResponse.mediaType === 'video') {
tryAddVideoBid(auctionInstance, bidResponse, bidRequest, afterBidAdded);
tryAddVideoBid(auctionInstance, bidResponse, bidderRequest, afterBidAdded);
} else {
addBidToAuction(auctionInstance, bidResponse);
afterBidAdded();
}
}

function adapterDone() {
onAllAdapterDone();
let bidderRequest = this;

bidderRequestsDone.add(bidderRequest);
allAdapterCalledDone = auctionInstance.getBidRequests()
.every(bidderRequest => bidderRequestsDone.has(bidderRequest));

bidderRequest.bids.forEach(bid => {
if (!bidResponseMap[bid.bidId]) {
auctionInstance.addNoBid(bid);
events.emit(CONSTANTS.EVENTS.NO_BID, bid);
}
});

if (allAdapterCalledDone && outstandingBidsAdded === 0) {
auctionDone();
}
Expand Down Expand Up @@ -417,8 +436,8 @@ function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded

// Postprocess the bids so that all the universal properties exist, no matter which bidder they came from.
// This should be called before addBidToAuction().
function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) {
const start = bidRequest.start;
function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) {
const start = bidderRequest.start;

let bidObject = Object.assign({}, bid, {
auctionId,
Expand All @@ -438,7 +457,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidRequest, auctionId}) {
events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject);

// a publisher-defined renderer can be used to render bids
const bidReq = bidRequest.bids && find(bidRequest.bids, bid => bid.adUnitCode == adUnitCode);
const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode);
const adUnitRenderer = bidReq && bidReq.renderer;

if (adUnitRenderer && adUnitRenderer.url) {
Expand Down
9 changes: 7 additions & 2 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ export function newAuctionManager() {
} else {
utils.logWarn(`Auction not found when adding winning bid`);
}
}
};

auctionManager.getAllWinningBids = function() {
return _auctions.map(auction => auction.getWinningBids())
.reduce(flatten, []);
}
};

auctionManager.getBidsRequested = function() {
return _auctions.map(auction => auction.getBidRequests())
.reduce(flatten, []);
};

auctionManager.getNoBids = function() {
return _auctions.map(auction => auction.getNoBids())
.reduce(flatten, []);
};

auctionManager.getBidsReceived = function() {
// As of now, an old bid which is not used in auction 1 can be used in auction n.
// To prevent this, bid.ttl (time to live) will be added to this logic and bid pool will also be added
Expand Down
1 change: 1 addition & 0 deletions src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"BID_TIMEOUT": "bidTimeout",
"BID_REQUESTED": "bidRequested",
"BID_RESPONSE": "bidResponse",
"NO_BID": "noBid",
"BID_WON": "bidWon",
"BIDDER_DONE": "bidderDone",
"SET_TARGETING": "setTargeting",
Expand Down
33 changes: 24 additions & 9 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,8 @@ $$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) {
return targeting.getAllTargeting(adUnitCode);
};

/**
* This function returns the bid responses at the given moment.
* @alias module:pbjs.getBidResponses
* @return {Object} map | object that contains the bidResponses
*/

$$PREBID_GLOBAL$$.getBidResponses = function () {
utils.logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments);
const responses = auctionManager.getBidsReceived()
function getBids(type) {
const responses = auctionManager[type]()
.filter(adUnitsFilter.bind(this, auctionManager.getAdUnitCodes()));

// find the last auction id to get responses for most recent auction only
Expand All @@ -139,6 +132,28 @@ $$PREBID_GLOBAL$$.getBidResponses = function () {
};
})
.reduce((a, b) => Object.assign(a, b), {});
}

/**
* This function returns the bids requests involved in an auction but not bid on
* @alias module:pbjs.getNoBids
* @return {Object} map | object that contains the bidRequests
*/

$$PREBID_GLOBAL$$.getNoBids = function () {
utils.logInfo('Invoking $$PREBID_GLOBAL$$.getNoBids', arguments);
return getBids('getNoBids');
};

/**
* This function returns the bid responses at the given moment.
* @alias module:pbjs.getBidResponses
* @return {Object} map | object that contains the bidResponses
*/

$$PREBID_GLOBAL$$.getBidResponses = function () {
utils.logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments);
return getBids('getBidsReceived');
};

/**
Expand Down
4 changes: 4 additions & 0 deletions test/spec/api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ describe('Publisher API', function () {
assert.isFunction($$PREBID_GLOBAL$$.getBidResponses);
});

it('should have function $$PREBID_GLOBAL$$.getBidResponses', function () {
assert.isFunction($$PREBID_GLOBAL$$.getNoBids);
});

it('should have function $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode', function () {
assert.isFunction($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode);
});
Expand Down
Loading

0 comments on commit ff6f62a

Please sign in to comment.