Skip to content

Commit

Permalink
Added bid pool and fixed getAllWinningBids function (#2328)
Browse files Browse the repository at this point in the history
* Created bid pool, fixed getAllWinningBids and added new api getAllPrebidWinningBids

* Updated ttl buffer to 1000

* updated function names
  • Loading branch information
jaiminpanchal27 authored Apr 10, 2018
1 parent 275e67d commit c738ab5
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 16 deletions.
6 changes: 3 additions & 3 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
let _callback = callback;
let _timer;
let _timeout = cbTimeout;
let _winningBid;
let _winningBids = [];

function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests) };
function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); }
Expand Down Expand Up @@ -202,8 +202,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
executeCallback,
callBids,
bidsBackAll,
setWinningBid: (winningBid) => { _winningBid = winningBid },
getWinningBid: () => _winningBid,
addWinningBid: (winningBid) => { _winningBids = _winningBids.concat(winningBid) },
getWinningBids: () => _winningBids,
getTimeout: () => _timeout,
getAuctionId: () => _auctionId,
getAuctionStatus: () => _auctionStatus,
Expand Down
4 changes: 2 additions & 2 deletions src/auctionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ export function newAuctionManager() {
auctionManager.addWinningBid = function(bid) {
const auction = find(_auctions, auction => auction.getAuctionId() === bid.auctionId);
if (auction) {
auction.setWinningBid(bid);
auction.addWinningBid(bid);
} else {
utils.logWarn(`Auction not found when adding winning bid`);
}
}

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

Expand Down
24 changes: 17 additions & 7 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { userSync } from 'src/userSync.js';
import { loadScript } from './adloader';
import { config } from './config';
import { auctionManager } from './auctionManager';
import { targeting } from './targeting';
import { targeting, getOldestBid, RENDERED, BID_TARGETING_SET } from './targeting';
import { createHook } from 'src/hook';
import includes from 'core-js/library/fn/array/includes';

Expand All @@ -20,8 +20,6 @@ const events = require('./events');
const { triggerUserSyncs } = userSync;

/* private variables */

const RENDERED = 'rendered';
const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED } = CONSTANTS.EVENTS;
const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON;

Expand Down Expand Up @@ -110,7 +108,8 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) {

$$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) {
utils.logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments);
return targeting.getAllTargeting(adUnitCode, auctionManager.getBidsReceived());
let bidsReceived = auctionManager.getBidsReceived();
return targeting.getAllTargeting(adUnitCode, bidsReceived);
};

/**
Expand Down Expand Up @@ -556,14 +555,24 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias) {
*/

/**
* Get all of the bids that have won their respective auctions. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html).
* @return {Array<AdapterBidResponse>} A list of bids that have won their respective auctions.
* Get all of the bids that have been rendered. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html).
* @return {Array<AdapterBidResponse>} A list of bids that have been rendered.
*/
$$PREBID_GLOBAL$$.getAllWinningBids = function () {
return auctionManager.getAllWinningBids()
.map(removeRequestId);
};

/**
* Get all of the bids that have won their respective auctions.
* @return {Array<AdapterBidResponse>} A list of bids that have won their respective auctions.
*/
$$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () {
return auctionManager.getBidsReceived()
.filter(bid => bid.status === BID_TARGETING_SET)
.map(removeRequestId);
};

/**
* Get array of highest cpm bids for all adUnits, or highest cpm bid
* object for the given adUnit
Expand All @@ -572,7 +581,8 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () {
* @return {Array} array containing highest cpm bid object(s)
*/
$$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) {
return targeting.getWinningBids(adUnitCode, auctionManager.getBidsReceived())
let bidsReceived = auctionManager.getBidsReceived().filter(getOldestBid);
return targeting.getWinningBids(adUnitCode, bidsReceived)
.map(removeRequestId);
};

Expand Down
21 changes: 19 additions & 2 deletions src/targeting.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,29 @@ var CONSTANTS = require('./constants.json');
var pbTargetingKeys = [];

export const BID_TARGETING_SET = 'targetingSet';
export const RENDERED = 'rendered';

const MAX_DFP_KEYLENGTH = 20;
const TTL_BUFFER = 1000;

// return unexpired bids
export const isBidExpired = (bid) => (timestamp() - bid.responseTimestamp) < bid.ttl * 1000;
export const isBidExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 + TTL_BUFFER) > timestamp();

// return bids whose status is not set. Winning bid can have status `targetingSet` or `rendered`.
const isUnusedBid = (bid) => bid && ((bid.status && bid.status === BID_TARGETING_SET) || !bid.status);
const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_TARGETING_SET, RENDERED], bid.status)) || !bid.status);

// If two bids are found for same adUnitCode, we will use the latest one to take part in auction
// This can happen in case of concurrent autions
export const getOldestBid = function(bid, i, arr) {
let oldestBid = true;
arr.forEach((val, j) => {
if (i === j) return;
if (bid.bidder === val.bidder && bid.adUnitCode === val.adUnitCode && bid.responseTimestamp > val.responseTimestamp) {
oldestBid = false;
}
});
return oldestBid;
}

/**
* @typedef {Object.<string,string>} targeting
Expand Down Expand Up @@ -162,6 +177,8 @@ export function newTargeting(auctionManager) {
return auctionManager.getBidsReceived()
.filter(isUnusedBid)
.filter(exports.isBidExpired)
.filter(getOldestBid)
;
}

/**
Expand Down
38 changes: 38 additions & 0 deletions test/fixtures/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -1404,3 +1404,41 @@ export function getCurrencyRates() {
}
};
}

export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl}) {
let bid = {
'bidderCode': bidder,
'width': '300',
'height': '250',
'statusMessage': 'Bid available',
'adId': adId,
'cpm': cpm,
'ad': 'markup',
'ad_id': adId,
'sizeId': '15',
'requestTimestamp': 1454535718610,
'responseTimestamp': responseTimestamp,
'auctionId': auctionId,
'timeToRespond': 123,
'pbLg': '0.50',
'pbMg': '0.50',
'pbHg': '0.53',
'adUnitCode': adUnitCode,
'bidder': bidder,
'size': '300x250',
'adserverTargeting': {
'hb_bidder': bidder,
'hb_adid': adId,
'hb_pb': cpm,
'foobar': '300x250'
},
'netRevenue': true,
'currency': 'USD',
'ttl': (!ttl) ? 300 : ttl
};

if (typeof status !== 'undefined') {
bid.status = status;
}
return bid;
}
100 changes: 99 additions & 1 deletion test/spec/unit/core/targeting_spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { expect } from 'chai';
import { targeting as targetingInstance } from 'src/targeting';
import { config } from 'src/config';
import { getAdUnits } from 'test/fixtures/fixtures';
import { getAdUnits, createBidReceived } from 'test/fixtures/fixtures';
import CONSTANTS from 'src/constants.json';
import { auctionManager } from 'src/auctionManager';
import * as targetingModule from 'src/targeting';
import * as utils from 'src/utils';

const bid1 = {
'bidderCode': 'rubicon',
Expand Down Expand Up @@ -134,4 +135,101 @@ describe('targeting tests', () => {
expect(targeting['/123456/header-bid-tag-0']['hb_pb_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0']['hb_pb']);
});
}); // end getAllTargeting tests

describe('Targeting in concurrent auctions', () => {
describe('check getOldestBid', () => {
let bidExpiryStub;
let auctionManagerStub;
beforeEach(() => {
bidExpiryStub = sinon.stub(targetingModule, 'isBidExpired').returns(true);
auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived');
});

afterEach(() => {
bidExpiryStub.restore();
auctionManagerStub.restore();
});

it('should use bids from pool to get Winning Bid', () => {
let bidsReceived = [
createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2'}),
createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4'}),
];
let adUnitCodes = ['code-0', 'code-1'];

let bids = targetingInstance.getWinningBids(adUnitCodes, bidsReceived);

expect(bids.length).to.equal(2);
expect(bids[0].adId).to.equal('adid-1');
expect(bids[1].adId).to.equal('adid-2');
});

it('should not use rendered bid to get winning bid', () => {
let bidsReceived = [
createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'rendered'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2'}),
createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4'}),
];
auctionManagerStub.returns(bidsReceived);

let adUnitCodes = ['code-0', 'code-1'];
let bids = targetingInstance.getWinningBids(adUnitCodes);

expect(bids.length).to.equal(2);
expect(bids[0].adId).to.equal('adid-2');
expect(bids[1].adId).to.equal('adid-3');
});

it('should use oldest bids from bid pool to get winning bid', () => {
// Pool is having 4 bids from 2 auctions. There are 2 bids from rubicon, #2 which is first bid will be selected to take part in auction.
let bidsReceived = [
createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1'}),
createBidReceived({bidder: 'rubicon', cpm: 9, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-0', adId: 'adid-2'}),
createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}),
createBidReceived({bidder: 'rubicon', cpm: 10, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-0', adId: 'adid-4'}),
];
auctionManagerStub.returns(bidsReceived);

let adUnitCodes = ['code-0'];
let bids = targetingInstance.getWinningBids(adUnitCodes);

expect(bids.length).to.equal(1);
expect(bids[0].adId).to.equal('adid-2');
});
});

describe('check bidExpiry', () => {
let auctionManagerStub;
let timestampStub;
beforeEach(() => {
auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived');
timestampStub = sinon.stub(utils, 'timestamp');
});

afterEach(() => {
auctionManagerStub.restore();
timestampStub.restore();
});
it('should not include expired bids in the auction', () => {
timestampStub.returns(200000);
// Pool is having 4 bids from 2 auctions. All the bids are expired and only bid #3 is passing the bidExpiry check.
let bidsReceived = [
createBidReceived({bidder: 'appnexus', cpm: 18, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', ttl: 150}),
createBidReceived({bidder: 'sampleBidder', cpm: 16, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-0', adId: 'adid-2', ttl: 100}),
createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3', ttl: 300}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-0', adId: 'adid-4', ttl: 50}),
];
auctionManagerStub.returns(bidsReceived);

let adUnitCodes = ['code-0', 'code-1'];
let bids = targetingInstance.getWinningBids(adUnitCodes);

expect(bids.length).to.equal(1);
expect(bids[0].adId).to.equal('adid-3');
});
});
});
});
28 changes: 27 additions & 1 deletion test/spec/unit/pbjs_api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
getBidResponsesFromAPI,
getTargetingKeys,
getTargetingKeysBidLandscape,
getAdUnits
getAdUnits,
createBidReceived
} from 'test/fixtures/fixtures';
import { auctionManager, newAuctionManager } from 'src/auctionManager';
import { targeting, newTargeting } from 'src/targeting';
Expand Down Expand Up @@ -1628,4 +1629,29 @@ describe('Unit: Prebid Module', function () {
assert.equal($$PREBID_GLOBAL$$.que.push, $$PREBID_GLOBAL$$.cmd.push);
});
});

describe('getAllPrebidWinningBids', () => {
let auctionManagerStub;
beforeEach(() => {
auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived');
});

afterEach(() => {
auctionManagerStub.restore();
});

it('should return prebid auction winning bids', () => {
let bidsReceived = [
createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'targetingSet'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2'}),
createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}),
createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4'}),
];
auctionManagerStub.returns(bidsReceived)
let bids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids();

expect(bids.length).to.equal(1);
expect(bids[0].adId).to.equal('adid-1');
});
});
});

0 comments on commit c738ab5

Please sign in to comment.