diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 9d2f45159e9..a19c6f095ad 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -4,6 +4,7 @@ import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; @@ -124,7 +125,19 @@ function parseBidResponse(bid) { if (typeof bid.getCpmInNewCurrency === 'function') { return window.parseFloat(Number(bid.getCpmInNewCurrency(CURRENCY_USD)).toFixed(BID_PRECISION)); } - utils.logWarn(LOG_PRE_FIX + 'Could not determine the bidPriceUSD of the bid ', bid); + utils.logWarn(LOG_PRE_FIX + 'Could not determine the Net cpm in USD for the bid thus using bid.cpm', bid); + return bid.cpm + }, + 'bidGrossCpmUSD', () => { + if (typeof bid.originalCurrency === 'string' && bid.originalCurrency.toUpperCase() === CURRENCY_USD) { + return window.parseFloat(Number(bid.originalCpm).toFixed(BID_PRECISION)); + } + // use currency conversion function if present + if (typeof getGlobal().convertCurrency === 'function') { + return window.parseFloat(Number(getGlobal().convertCurrency(bid.originalCpm, bid.originalCurrency, CURRENCY_USD)).toFixed(BID_PRECISION)); + } + utils.logWarn(LOG_PRE_FIX + 'Could not determine the Gross cpm in USD for the bid, thus using bid.originalCpm', bid); + return bid.originalCpm }, 'dealId', 'currency', @@ -152,20 +165,8 @@ function getDomainFromUrl(url) { return a.hostname; } -function getHighestBidForAdUnit(adUnit) { - return Object.keys(adUnit.bids).reduce(function(currentHighestBid, bidId) { - // todo: later we will need to consider grossECPM and netECPM - let bid = adUnit.bids[bidId]; - if (bid.bidResponse && bid.bidResponse.bidPriceUSD > currentHighestBid.bidPriceUSD) { - currentHighestBid.bidPriceUSD = bid.bidResponse.bidPriceUSD; - currentHighestBid.bidId = bidId; - } - return currentHighestBid; - }, {bidId: '', bidPriceUSD: 0}); -} - -function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId) { - const highestsBid = getHighestBidForAdUnit(adUnit); +function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { + highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { let bid = adUnit.bids[bidId]; partnerBids.push({ @@ -175,16 +176,15 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId) { 'kgpv': bid.params.kgpv ? bid.params.kgpv : adUnitId, 'kgpsv': bid.params.kgpv ? bid.params.kgpv : adUnitId, 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', - 'eg': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, // todo: later we will need to consider grossECPM and netECPM, precision - 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, // todo: later we will need to consider grossECPM and netECPM, precision + 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, + 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, 'di': bid.bidResponse ? (bid.bidResponse.dealId || EMPTY_STRING) : EMPTY_STRING, 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, 'l2': 0, - // 'ss': (bid.source === 'server' ? 1 : 0), // todo: is there any special handling required as per OW? 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, - 'wb': highestsBid.bidId === bid.bidId ? 1 : 0, + 'wb': (highestBid && highestBid.requestId === bid.bidId ? 1 : 0), 'mi': bid.bidResponse ? (bid.bidResponse.mi || undefined) : undefined, 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, @@ -194,7 +194,8 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId) { }, []) } -function executeBidsLoggerCall(auctionId) { +function executeBidsLoggerCall(e, highestCpmBids) { + let auctionId = e.auctionId; let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; let auctionCache = cache.auctions[auctionId]; let outputObj = { s: [] }; @@ -230,7 +231,7 @@ function executeBidsLoggerCall(auctionId) { let slotObject = { 'sn': adUnitId, 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), - 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId) + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)) }; slotsArray.push(slotObject); return slotsArray; @@ -263,8 +264,8 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pdvid=' + enc(profileVersionId); pixelURL += '&slot=' + enc(adUnitId); pixelURL += '&pn=' + enc(winningBid.bidder); - pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); // todo: later we will need to consider grossECPM and netECPM - pixelURL += '&eg=' + enc(winningBid.bidResponse.bidPriceUSD); // todo: later we will need to consider grossECPM and netECPM + pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); + pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); pixelURL += '&kgpv=' + enc(winningBid.params.kgpv || adUnitId); ajax( pixelURL, @@ -345,8 +346,9 @@ function bidWonHandler(args) { function auctionEndHandler(args) { // if for the given auction bidderDonePendingCount == 0 then execute logger call sooners + let highestCpmBids = getGlobal().getHighestCpmBids() || []; setTimeout(() => { - executeBidsLoggerCall.call(this, args.auctionId); + executeBidsLoggerCall.call(this, args, highestCpmBids); }, (cache.auctions[args.auctionId].bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); } diff --git a/modules/pubmaticAnalyticsAdapter.md b/modules/pubmaticAnalyticsAdapter.md index 9685f758ea2..097f0b3d792 100644 --- a/modules/pubmaticAnalyticsAdapter.md +++ b/modules/pubmaticAnalyticsAdapter.md @@ -20,5 +20,4 @@ pbjs.enableAnalytics({ - Supports only Banner and Video media-type - Does not supports Native media type - Does not supports instream-video creative-render tracker -- BidCpmAdjustment: Bid CPM only after BidCpmAdjustment is logged -- If a currency module is NOT included and a bidder responds in a non-USD currency then PubMatic analytics bidder will not be able to log the bid CPM \ No newline at end of file +- If a currency module is NOT included and a bidder responds in a non-USD currency then PubMatic analytics bidder will log values in original bid currency otherwise always logged in USD \ No newline at end of file diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index e9d23692d23..bb9aa20f1b4 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -293,6 +293,10 @@ describe('pubmatic analytics adapter', function () { }); it('Logger: best case + win tracker', function() { + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); @@ -387,6 +391,122 @@ describe('pubmatic analytics adapter', function () { expect(data.en).to.equal('1.23'); }); + it('bidCpmAdjustment: USD: Logger: best case + win tracker', function() { + const bidCopy = utils.deepClone(BID); + bidCopy.cpm = bidCopy.originalCpm * 2; // bidCpmAdjustment => bidCpm * 2 + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [bidCopy, MOCK.BID_RESPONSE[1]] + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].eg).to.equal(1.23); + expect(data.s[0].ps[0].en).to.equal(2.46); + expect(data.s[0].ps[0].wb).to.equal(1); + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(1.23); + expect(data.s[0].ps[0].ocry).to.equal('USD'); + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.eg).to.equal('1.23'); + expect(data.en).to.equal('2.46'); + }); + + it('bidCpmAdjustment: JPY: Logger: best case + win tracker', function() { + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + const bidCopy = utils.deepClone(BID); + bidCopy.originalCpm = 100; + bidCopy.originalCurrency = 'JPY'; + bidCopy.currency = 'JPY'; + bidCopy.cpm = bidCopy.originalCpm * 2; // bidCpmAdjustment => bidCpm * 2 + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + // events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, bidCopy); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + // slot 1 + expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('pubmatic'); + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps[0].eg).to.equal(1); + expect(data.s[0].ps[0].en).to.equal(200); + expect(data.s[0].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[0].ps[0].af).to.equal('video'); + expect(data.s[0].ps[0].ocpm).to.equal(100); + expect(data.s[0].ps[0].ocry).to.equal('JPY'); + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(data.tst).to.equal('1519767014'); + expect(data.iid).to.equal('25c6d7f5-699a-4bfc-87c9-996f915341fa'); + expect(data.eg).to.equal('1'); + expect(data.en).to.equal('200'); // bidPriceUSD is not getting set as currency module is not added + }); + it('Logger: when bid is not submitted, default bid status 1 check: pubmatic set as s2s', function() { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -463,6 +583,11 @@ describe('pubmatic analytics adapter', function () { it('Logger: post-timeout check with bid response', function() { // db = 1 and t = 1 means bidder did NOT respond with a bid but we got a timeout notification + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[1]] + }); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); @@ -492,7 +617,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(1); - expect(data.s[1].ps[0].wb).to.equal(1); + expect(data.s[1].ps[0].wb).to.equal(1); // todo expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); @@ -538,8 +663,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].psz).to.equal('728x90'); - expect(data.s[1].ps[0].eg).to.equal(undefined); // bidPriceUSD is not getting set as currency module is not added - expect(data.s[1].ps[0].en).to.equal(undefined); // bidPriceUSD is not getting set as currency module is not added + expect(data.s[1].ps[0].eg).to.equal(1); + expect(data.s[1].ps[0].en).to.equal(100); expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); @@ -552,5 +677,5 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(100); expect(data.s[1].ps[0].ocry).to.equal('JPY'); }); - }) + }); });