Skip to content

Commit

Permalink
put server latency on bidder timeout event for NO_BIDs (#2561)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich Snapp authored and mkendall07 committed May 23, 2018
1 parent fc95a52 commit ebf0d30
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 65 deletions.
34 changes: 16 additions & 18 deletions modules/prebidServerBidAdapter.js
Expand Up @@ -362,24 +362,27 @@ const LEGACY_PROTOCOL = {
return request;
},

interpretResponse(result, bidRequests, requestedBidders) {
interpretResponse(result, bidderRequests, requestedBidders) {
const bids = [];
let responseTimes = {};

if (result.status === 'OK' || result.status === 'no_cookie') {
if (result.bidder_status) {
result.bidder_status.forEach(bidder => {
if (bidder.error) {
utils.logWarn(`Prebid Server returned error: '${bidder.error}' for ${bidder.bidder}`);
}

responseTimes[bidder.bidder] = bidder.response_time_ms;
bidderRequests.filter(bidderRequest => bidderRequest.bidderCode === bidder.bidder)
.forEach(bidderRequest =>
(bidderRequest.bids || []).forEach(bid =>
bid.serverResponseTimeMs = bidder.response_time_ms
)
)
});
}

if (result.bids) {
result.bids.forEach(bidObj => {
const bidRequest = utils.getBidRequest(bidObj.bid_id, bidRequests);
const bidRequest = utils.getBidRequest(bidObj.bid_id, bidderRequests);
const cpm = bidObj.price;
const status = cpm !== 0 ? STATUS.GOOD : STATUS.NO_BID;
let bidObject = bidfactory.createBid(status, bidRequest);
Expand All @@ -388,9 +391,6 @@ const LEGACY_PROTOCOL = {
bidObject.creative_id = bidObj.creative_id;
bidObject.bidderCode = bidObj.bidder;
bidObject.cpm = cpm;
if (responseTimes[bidObj.bidder]) {
bidObject.serverResponseTimeMs = responseTimes[bidObj.bidder];
}
if (bidObj.cache_id) {
bidObject.cache_id = bidObj.cache_id;
}
Expand Down Expand Up @@ -580,7 +580,7 @@ const OPEN_RTB_PROTOCOL = {
return request;
},

interpretResponse(response, bidRequests, requestedBidders) {
interpretResponse(response, bidderRequests, requestedBidders) {
const bids = [];

if (response.seatbid) {
Expand All @@ -589,7 +589,7 @@ const OPEN_RTB_PROTOCOL = {
(seatbid.bid || []).forEach(bid => {
const bidRequest = utils.getBidRequest(
this.bidMap[`${bid.impid}${seatbid.seat}`],
bidRequests
bidderRequests
);

const cpm = bid.price;
Expand All @@ -601,8 +601,8 @@ const OPEN_RTB_PROTOCOL = {
bidObject.cpm = cpm;

let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.'));
if (serverResponseTimeMs) {
bidObject.serverResponseTimeMs = serverResponseTimeMs;
if (bidRequest && serverResponseTimeMs) {
bidRequest.serverResponseTimeMs = serverResponseTimeMs;
}

if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) {
Expand Down Expand Up @@ -701,27 +701,25 @@ export function PrebidServer() {
};

/* Notify Prebid of bid responses so bids can get in the auction */
function handleResponse(response, requestedBidders, bidRequests, addBidResponse, done) {
function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done) {
let result;

try {
result = JSON.parse(response);

const bids = protocolAdapter().interpretResponse(
result,
bidRequests,
bidderRequests,
requestedBidders
);

bids.forEach(({adUnit, bid}) => {
if (isValid(adUnit, bid, bidRequests)) {
if (isValid(adUnit, bid, bidderRequests)) {
addBidResponse(adUnit, bid);
}
});

bidRequests.forEach((bidRequest) => {
events.emit(EVENTS.BIDDER_DONE, bidRequest);
});
bidderRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest));

if (result.status === 'no_cookie' && _s2sConfig.cookieSet && typeof _s2sConfig.cookieSetUrl === 'string') {
// cookie sync
Expand Down
12 changes: 7 additions & 5 deletions modules/rubiconAnalyticsAdapter.js
Expand Up @@ -376,9 +376,11 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
switch (args.getStatusCode()) {
case GOOD:
bid.status = 'success';
delete bid.error; // it's possible for this to be set by a previous timeout
break;
case NO_BID:
bid.status = 'no-bid';
delete bid.error;
break;
default:
bid.status = 'error';
Expand All @@ -387,14 +389,14 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
};
}
bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp;
if (typeof args.serverResponseTimeMs !== 'undefined') {
bid.serverLatencyMillis = args.serverResponseTimeMs;
}
bid.bidResponse = parseBidResponse(args);
break;
case BIDDER_DONE:
args.bids.forEach(bid => {
let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId];
let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.adId];
if (typeof bid.serverResponseTimeMs !== 'undefined') {
cachedBid.serverLatencyMillis = bid.serverResponseTimeMs;
}
if (!cachedBid.status) {
cachedBid.status = 'no-bid';
}
Expand Down Expand Up @@ -433,7 +435,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
case BID_TIMEOUT:
args.forEach(badBid => {
let auctionCache = cache.auctions[badBid.auctionId];
let bid = auctionCache.bids[badBid.bidId];
let bid = auctionCache.bids[badBid.bidId || badBid.adId];
bid.status = 'error';
bid.error = {
code: 'timeout-error'
Expand Down
12 changes: 10 additions & 2 deletions src/utils.js
Expand Up @@ -685,8 +685,16 @@ export function flatten(a, b) {
return a.concat(b);
}

export function getBidRequest(id, bidsRequested) {
return find(bidsRequested.map(bidSet => find(bidSet.bids, bid => bid.bidId === id)), bid => bid);
export function getBidRequest(id, bidderRequests) {
let bidRequest;
bidderRequests.some(bidderRequest => {
let result = find(bidderRequest.bids, bid => ['bidId', 'adId', 'bid_id'].some(type => bid[type] === id));
if (result) {
bidRequest = result;
}
return result;
});
return bidRequest;
}

export function getKeys(obj) {
Expand Down
88 changes: 49 additions & 39 deletions test/spec/modules/prebidServerBidAdapter_spec.js
Expand Up @@ -7,6 +7,8 @@ import { userSync } from 'src/userSync';
import { ajax } from 'src/ajax';
import { config } from 'src/config';
import { requestBidsHook } from 'modules/consentManagement';
import events from 'src/events';
import CONSTANTS from 'src/constants';

let CONFIG = {
accountId: '1',
Expand Down Expand Up @@ -79,34 +81,7 @@ const VIDEO_REQUEST = {
]
};

const BID_REQUESTS = [
{
'bidderCode': 'appnexus',
'auctionId': '173afb6d132ba3',
'bidderRequestId': '3d1063078dfcc8',
'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
'bids': [
{
'bidder': 'appnexus',
'params': {
'placementId': '10433394',
'member': 123
},
'bid_id': '123',
'adUnitCode': 'div-gpt-ad-1460505748561-0',
'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c',
'sizes': [300, 250],
'bidId': '259fb43aaa06c1',
'bidderRequestId': '3d1063078dfcc8',
'auctionId': '173afb6d132ba3'
}
],
'auctionStart': 1510852447530,
'timeout': 5000,
'src': 's2s',
'doneCbCallCount': 0
}
];
let BID_REQUESTS;

const RESPONSE = {
'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
Expand Down Expand Up @@ -359,7 +334,37 @@ describe('S2S Adapter', () => {
addBidResponse = sinon.spy(),
done = sinon.spy();

beforeEach(() => adapter = new Adapter());
beforeEach(() => {
adapter = new Adapter();
BID_REQUESTS = [
{
'bidderCode': 'appnexus',
'auctionId': '173afb6d132ba3',
'bidderRequestId': '3d1063078dfcc8',
'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5',
'bids': [
{
'bidder': 'appnexus',
'params': {
'placementId': '10433394',
'member': 123
},
'bid_id': '123',
'adUnitCode': 'div-gpt-ad-1460505748561-0',
'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c',
'sizes': [300, 250],
'bidId': '123',
'bidderRequestId': '3d1063078dfcc8',
'auctionId': '173afb6d132ba3'
}
],
'auctionStart': 1510852447530,
'timeout': 5000,
'src': 's2s',
'doneCbCallCount': 0
}
];
});

afterEach(() => {
addBidResponse.resetHistory();
Expand Down Expand Up @@ -558,7 +563,7 @@ describe('S2S Adapter', () => {
s2sConfig: s2sConfig,
device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' },
app: { bundle: 'com.test.app' },
}
};

config.setConfig(_config);
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
Expand Down Expand Up @@ -675,31 +680,34 @@ describe('S2S Adapter', () => {
sinon.stub(utils, 'insertUserSyncIframe');
sinon.stub(utils, 'logError');
sinon.stub(cookie, 'cookieSet');
sinon.stub(utils, 'getBidRequest').returns({
bidId: '123'
});
sinon.stub(events, 'emit');
logWarnSpy = sinon.spy(utils, 'logWarn');
});

afterEach(() => {
server.restore();
utils.getBidRequest.restore();
utils.triggerPixel.restore();
utils.insertUserSyncIframe.restore();
utils.logError.restore();
events.emit.restore();
cookie.cookieSet.restore();
logWarnSpy.restore();
});

// TODO: test dependent on pbjs_api_spec. Needs to be isolated
it('registers bids', () => {
it('registers bids and calls BIDDER_DONE', () => {
server.respondWith(JSON.stringify(RESPONSE));

config.setConfig({s2sConfig: CONFIG});
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
server.respond();
sinon.assert.calledOnce(addBidResponse);

sinon.assert.calledOnce(events.emit);
const event = events.emit.firstCall.args;
expect(event[0]).to.equal(CONSTANTS.EVENTS.BIDDER_DONE);
expect(event[1].bids[0]).to.have.property('serverResponseTimeMs', 52);

const response = addBidResponse.firstCall.args[1];
expect(response).to.have.property('statusMessage', 'Bid available');
expect(response).to.have.property('cpm', 0.5);
Expand All @@ -708,7 +716,6 @@ describe('S2S Adapter', () => {
expect(response).to.have.property('cache_id', '7654321');
expect(response).to.have.property('cache_url', 'http://www.test.com/cache?uuid=7654321');
expect(response).to.not.have.property('vastUrl');
expect(response).to.have.property('serverResponseTimeMs', 52);
});

it('registers video bids', () => {
Expand Down Expand Up @@ -880,7 +887,7 @@ describe('S2S Adapter', () => {
sinon.assert.calledOnce(cookie.cookieSet);
});

it('handles OpenRTB responses', () => {
it('handles OpenRTB responses and call BIDDER_DONE', () => {
const s2sConfig = Object.assign({}, CONFIG, {
endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'
});
Expand All @@ -890,13 +897,17 @@ describe('S2S Adapter', () => {
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
server.respond();

sinon.assert.calledOnce(events.emit);
const event = events.emit.firstCall.args;
expect(event[0]).to.equal(CONSTANTS.EVENTS.BIDDER_DONE);
expect(event[1].bids[0]).to.have.property('serverResponseTimeMs', 8);

sinon.assert.calledOnce(addBidResponse);
const response = addBidResponse.firstCall.args[1];
expect(response).to.have.property('statusMessage', 'Bid available');
expect(response).to.have.property('bidderCode', 'appnexus');
expect(response).to.have.property('adId', '123');
expect(response).to.have.property('cpm', 0.5);
expect(response).to.have.property('serverResponseTimeMs', 8);
});

it('handles OpenRTB video responses', () => {
Expand All @@ -917,7 +928,6 @@ describe('S2S Adapter', () => {
expect(response).to.have.property('bidderCode', 'appnexus');
expect(response).to.have.property('adId', '123');
expect(response).to.have.property('cpm', 10);
expect(response).to.have.property('serverResponseTimeMs', 81);
});

it('should log warning for unsupported bidder', () => {
Expand Down

0 comments on commit ebf0d30

Please sign in to comment.