From bb997b2cf93f0baade26e7c4fd3bb8ebbcc8a78c Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Mon, 27 Feb 2023 11:07:40 -0700 Subject: [PATCH] Prebid Core: emit seatnonbid from prebid server (#9453) * Parse and emit seatnonbid from server * Fix testing adjustments * Use onResponse for seatNonBids * Fix linting error * Emit to auction and add unit tests * Use optional property chaining * returnallbidstatus * fix varname in spec --- modules/prebidServerBidAdapter/index.js | 18 ++++++++++++++++-- src/auction.js | 12 +++++++++++- src/constants.json | 1 + test/spec/auctionmanager_spec.js | 14 ++++++++++++++ .../modules/prebidServerBidAdapter_spec.js | 15 +++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 924748ce197..c5f082f5355 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -469,10 +469,13 @@ export function PrebidServer() { } processPBSRequest(s2sBidRequest, bidRequests, ajax, { - onResponse: function (isValid, requestedBidders) { + onResponse: function (isValid, requestedBidders, response) { if (isValid) { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } + if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { + emitNonBids(response.ext.seatnonbid, bidRequests[0].auctionId); + } done(); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, @@ -551,7 +554,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques logError('error parsing response: ', result ? result.status : 'not valid JSON'); onResponse(false, requestedBidders); } else { - onResponse(true, requestedBidders); + onResponse(true, requestedBidders, result); } }, error: function () { @@ -567,6 +570,17 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques } }, 'processPBSRequest'); +function shouldEmitNonbids(s2sConfig, response) { + return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; +} + +function emitNonBids(seatnonbid, auctionId) { + events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + seatnonbid, + auctionId + }); +} + /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included diff --git a/src/auction.js b/src/auction.js index 41e6fe3565b..9052d6fd7a8 100644 --- a/src/auction.js +++ b/src/auction.js @@ -151,11 +151,13 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _auctionEnd; let _timer; let _auctionStatus; + let _nonBids = []; function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); } function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); } function addBidRejected(bidsRejected) { _bidsRejected = _bidsRejected.concat(bidsRejected); } function addNoBid(noBid) { _noBids = _noBids.concat(noBid); } + function addNonBids(seatnonbids) { _nonBids = _nonBids.concat(seatnonbids); } function getProperties() { return { @@ -172,7 +174,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a bidsRejected: _bidsRejected, winningBids: _winningBids, timeout: _timeout, - metrics: metrics + metrics: metrics, + seatNonBids: _nonBids }; } @@ -369,6 +372,12 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); } + events.on(CONSTANTS.EVENTS.SEAT_NON_BID, (event) => { + if (event.auctionId === _auctionId) { + addNonBids(event.seatnonbid) + } + }); + return { addBidReceived, addBidRejected, @@ -387,6 +396,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getBidRequests: () => _bidderRequests, getBidsReceived: () => _bidsReceived, getNoBids: () => _noBids, + getNonBids: () => _nonBids, getFPD: () => ortb2Fragments, getMetrics: () => metrics, }; diff --git a/src/constants.json b/src/constants.json index e33d65f3fb1..ef652af1ae5 100644 --- a/src/constants.json +++ b/src/constants.json @@ -31,6 +31,7 @@ "BID_RESPONSE": "bidResponse", "BID_REJECTED": "bidRejected", "NO_BID": "noBid", + "SEAT_NON_BID": "seatNonBid", "BID_WON": "bidWon", "BIDDER_DONE": "bidderDone", "BIDDER_ERROR": "bidderError", diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e1ecf801aa3..5ddf3ebf75e 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -759,6 +759,20 @@ describe('auctionmanager.js', function () { sinon.assert.calledWith(stubMakeBidRequests, ...anyArgs.slice(0, 5).concat([sinon.match.same(ortb2Fragments)])); sinon.assert.calledWith(stubCallAdapters, ...anyArgs.slice(0, 7).concat([sinon.match.same(ortb2Fragments)])); }); + + it('correctly adds nonbids when they are emitted', () => { + const ortb2Fragments = { + global: {}, + bidder: {} + } + const auction = auctionManager.createAuction({adUnits, ortb2Fragments}); + expect(auction.getNonBids()[0]).to.equal(undefined); + events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + auctionId: auction.getAuctionId(), + seatnonbid: ['test'] + }); + expect(auction.getNonBids()[0]).to.equal('test'); + }); }); describe('addBidResponse #1', function () { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 0ce060b9904..820a57b4e83 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2928,6 +2928,21 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 60); }); + it('handles seatnonbid responses and calls SEAT_NON_BID', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const nonbidResponse = {...RESPONSE_OPENRTB, ext: {seatnonbid: [{}]}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(nonbidResponse); + Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.secondCall.args; + expect(event[0]).to.equal(CONSTANTS.EVENTS.SEAT_NON_BID); + expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30