From 5e27370b550451cb07cd75304a9e7af157312c5f Mon Sep 17 00:00:00 2001 From: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Date: Wed, 24 Oct 2018 10:46:39 -0400 Subject: [PATCH 01/62] Submitting EMX Digital Adapter (#3173) * Submitting EMX Digital Prebid Adapter Submitting EMX Digital Prebid Adapter code * fixing lint errors. updating our md * updating to const/let variables. adding test spec. * fixed linting on test spec js --- modules/emx_digitalBidAdapter.js | 108 ++++++ modules/emx_digitalBidAdapter.md | 38 ++ .../modules/emx_digitalBidAdapter_spec.js | 338 ++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 modules/emx_digitalBidAdapter.js create mode 100644 modules/emx_digitalBidAdapter.md create mode 100644 test/spec/modules/emx_digitalBidAdapter_spec.js diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js new file mode 100644 index 00000000000..c25c20f2eda --- /dev/null +++ b/modules/emx_digitalBidAdapter.js @@ -0,0 +1,108 @@ +import * as utils from 'src/utils'; +import { + registerBidder +} from 'src/adapters/bidderFactory'; +import { + BANNER +} from 'src/mediaTypes'; +import { + config +} from 'src/config'; + +const BIDDER_CODE = 'emx_digital'; +const ENDPOINT = 'hb.emxdgt.com'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.params.tagid); + }, + buildRequests: function (validBidRequests, bidRequests) { + const {host, href, protocol} = utils.getTopWindowLocation(); + let emxData = {}; + let emxImps = []; + const auctionId = bidRequests.auctionId; + const timeout = config.getConfig('bidderTimeout'); + const timestamp = Date.now(); + const url = location.protocol + '//' + ENDPOINT + ('?t=' + timeout + '&ts=' + timestamp); + + utils._each(validBidRequests, function (bid) { + let tagId = String(utils.getBidIdParameter('tagid', bid.params)); + let bidFloor = utils.getBidIdParameter('bidfloor', bid.params) || 0; + let emxBid = { + id: bid.bidId, + tid: bid.transactionId, + tagid: tagId, + secure: protocol === 'https:' ? 1 : 0, + banner: { + format: bid.sizes.map(function (size) { + return { + w: size[0], + h: size[1] + }; + }), + w: bid.sizes[0][0], + h: bid.sizes[0][1] + } + } + if (bidFloor > 0) { + emxBid.bidfloor = bidFloor + } + emxImps.push(emxBid); + }); + emxData = { + id: auctionId, + imp: emxImps, + site: { + domain: host, + page: href + } + }; + if (bidRequests.gdprConsent) { + emxData.regs = { + ext: { + gdpr: bidRequests.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (bidRequests.gdprConsent && bidRequests.gdprConsent.gdprApplies) { + emxData.user = { + ext: { + consent: bidRequests.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: url, + data: JSON.stringify(emxData), + options: { + withCredentials: true + } + }; + }, + interpretResponse: function (serverResponse) { + let emxBidResponses = []; + let response = serverResponse.body || {}; + if (response.seatbid && response.seatbid.length > 0 && response.seatbid[0].bid) { + response.seatbid.forEach(function (emxBid) { + emxBid = emxBid.bid[0]; + emxBidResponses.push({ + requestId: emxBid.id, + cpm: emxBid.price, + width: emxBid.w, + height: emxBid.h, + creativeId: emxBid.crid || emxBid.id, + dealId: emxBid.dealid || null, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: decodeURIComponent(emxBid.adm), + ttl: emxBid.ttl + }); + }); + } + return emxBidResponses; + } +}; +registerBidder(spec); diff --git a/modules/emx_digitalBidAdapter.md b/modules/emx_digitalBidAdapter.md new file mode 100644 index 00000000000..c9435e2f1d1 --- /dev/null +++ b/modules/emx_digitalBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: EMX Digital Adapter +Module Type: Bidder Adapter +Maintainer: git@emxdigital.com +``` + +# Description + +The EMX Digital adapter provides publishers with access to the EMX Marketplace. The adapter is GDPR compliant. Please note that the adapter supports Banner media type only. + +Note: The EMX Digital adapter requires approval and implementation guidelines from the EMX team, including existing publishers that work with EMX Digital. Please reach out to your account manager or prebid@emxdigital.com for more information. + +The bidder code should be ```emx_digital``` +The params used by the bidder are : +```tagid``` - string (mandatory) +```bidfloor``` - string (optional) + +# Test Parameters +``` +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600] + } + }, + bids: [ + { + bidder: 'emx_digital', + params: { + tagid: '25251', + } + }] +}]; +``` diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js new file mode 100644 index 00000000000..dd34582e22d --- /dev/null +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -0,0 +1,338 @@ +import { expect } from 'chai'; +import { spec } from 'modules/emx_digitalBidAdapter'; +import * as utils from 'src/utils'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('emx_digital Adapter', function () { + const adapter = newBidder(spec); + + describe('required function', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should contain tagid param', function () { + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + tagid: '' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + tagid: '123' + } + })).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec' + }, { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec' + }]; + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + }; + bidderRequest.bids = bidRequests + + let request = spec.buildRequests(bidRequests, bidderRequest); + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('contains the correct options', function () { + expect(request.options.withCredentials).to.equal(true); + }); + + it('sends contains a properly formatted endpoint url', function () { + const url = request.url.split('?'); + const queryParams = url[1].split('&'); + expect(queryParams[0]).to.match(new RegExp('^t=\d*', 'g')); + expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); + }); + + it('builds request properly', function () { + const data = JSON.parse(request.data); + + expect(Array.isArray(data.imp)).to.equal(true); + expect(data.id).to.equal(bidderRequest.auctionId); + expect(data.imp.length).to.equal(2); + expect(data.imp[0].id).to.equal('30b31c2501de1e'); + expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ec'); + expect(data.imp[0].tagid).to.equal('25251'); + expect(data.imp[0].secure).to.equal(0); + }); + + it('builds with bid floor', function() { + const bidRequestWithBidFloor = utils.deepClone(bidRequests); + bidRequestWithBidFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + const data = JSON.parse(requestWithFloor.data); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + }) + + it('properly sends site information and protocol', function () { + let mock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => { + return { + protocol: 'https:', + host: 'example.com', + href: 'https://example.com/index.html' + }; + }); + + let request; + + request = spec.buildRequests(bidRequests, bidderRequest); + request = JSON.parse(request.data); + expect(request.site.domain).to.equal('example.com'); + expect(request.site.page).to.equal('https://example.com/index.html'); + expect(request.imp[0].secure).to.equal(1); + + mock.restore(); + }) + + it('builds correctly formatted request banner object', function () { + let request; + + let bidRequestWithBanner = utils.deepClone(bidRequests); + + request = spec.buildRequests(bidRequestWithBanner, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[0].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.format[0].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[1].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][0]); + expect(data.imp[0].banner.format[1].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][1]); + }) + + it('shouldn\'t contain a user obj without GDPR information', function () { + let request = spec.buildRequests(bidRequests, bidderRequest) + request = JSON.parse(request.data) + expect(request).to.not.have.property('user'); + }); + + it('should have the right gdpr info when enabled', function () { + let consentString = 'OIJSZsOAFsABAB8EMXZZZZZ+A=='; + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + }; + bidderRequest.bids = bidRequests + let request = spec.buildRequests(bidRequests, bidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 1); + expect(request.user.ext).to.have.property('consent', 'OIJSZsOAFsABAB8EMXZZZZZ+A=='); + }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'gdprConsent': { + 'gdprApplies': false + } + }; + bidderRequest.bids = bidRequests + let request = spec.buildRequests(bidRequests, bidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 0); + expect(request).to.not.have.property('user'); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + 'id': '12819a18-56e1-4256-b836-b69a10202668', + 'seatbid': [{ + 'bid': [{ + 'adid': '123456abcde', + 'adm': '', + 'crid': '3434abab34', + 'h': 250, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }], + 'seat': '1356' + }, { + 'bid': [{ + 'adid': '123456abcdf', + 'adm': '', + 'crid': '3434abab35', + 'h': 600, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }] + }] + }; + + const expectedResponse = [{ + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'creativeId': '3434abab34', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }, { + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.7, + 'width': 300, + 'height': 600, + 'creativeId': '3434abab35', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }]; + + it('should properly format bid response', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Object.keys(result[0]).length).to.equal(Object.keys(expectedResponse[0]).length); + expect(Object.keys(result[0]).requestId).to.equal(Object.keys(expectedResponse[0]).requestId); + expect(Object.keys(result[0]).bidderCode).to.equal(Object.keys(expectedResponse[0]).bidderCode); + expect(Object.keys(result[0]).cpm).to.equal(Object.keys(expectedResponse[0]).cpm); + expect(Object.keys(result[0]).creativeId).to.equal(Object.keys(expectedResponse[0]).creativeId); + expect(Object.keys(result[0]).width).to.equal(Object.keys(expectedResponse[0]).width); + expect(Object.keys(result[0]).height).to.equal(Object.keys(expectedResponse[0]).height); + expect(Object.keys(result[0]).ttl).to.equal(Object.keys(expectedResponse[0]).ttl); + expect(Object.keys(result[0]).adId).to.equal(Object.keys(expectedResponse[0]).adId); + expect(Object.keys(result[0]).currency).to.equal(Object.keys(expectedResponse[0]).currency); + expect(Object.keys(result[0]).netRevenue).to.equal(Object.keys(expectedResponse[0]).netRevenue); + expect(Object.keys(result[0]).ad).to.equal(Object.keys(expectedResponse[0]).ad); + }); + + it('should return multiple bids', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Array.isArray(result.seatbid)) + + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.ad).to.equal(serverResponse.seatbid[0].bid[0].adm); + expect(ad0.cpm).to.equal(serverResponse.seatbid[0].bid[0].price); + expect(ad0.creativeId).to.equal(serverResponse.seatbid[0].bid[0].crid); + expect(ad0.currency).to.equal('USD'); + expect(ad0.height).to.equal(serverResponse.seatbid[0].bid[0].h); + expect(ad0.mediaType).to.equal('banner'); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.requestId).to.equal(serverResponse.seatbid[0].bid[0].id); + expect(ad0.ttl).to.equal(300); + expect(ad0.width).to.equal(serverResponse.seatbid[0].bid[0].w); + + expect(ad1.ad).to.equal(serverResponse.seatbid[1].bid[0].adm); + expect(ad1.cpm).to.equal(serverResponse.seatbid[1].bid[0].price); + expect(ad1.creativeId).to.equal(serverResponse.seatbid[1].bid[0].crid); + expect(ad1.currency).to.equal('USD'); + expect(ad1.height).to.equal(serverResponse.seatbid[1].bid[0].h); + expect(ad1.mediaType).to.equal('banner'); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.requestId).to.equal(serverResponse.seatbid[1].bid[0].id); + expect(ad1.ttl).to.equal(300); + expect(ad1.width).to.equal(serverResponse.seatbid[1].bid[0].w); + }); + + it('handles nobid responses', function () { + let serverResponse = { + 'bids': [] + }; + + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(result.length).to.equal(0); + }); + }); +}); From 2863df1090a79d570fdddd12974a7da62fa98b3b Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Thu, 25 Oct 2018 23:03:19 +0300 Subject: [PATCH 02/62] TheMediaGrid Bid Adapter (#3204) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter --- modules/gridBidAdapter.js | 151 ++++++++++++ modules/gridBidAdapter.md | 40 +++ test/spec/modules/gridBidAdapter_spec.js | 299 +++++++++++++++++++++++ 3 files changed, 490 insertions(+) create mode 100644 modules/gridBidAdapter.js create mode 100755 modules/gridBidAdapter.md create mode 100644 test/spec/modules/gridBidAdapter_spec.js diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js new file mode 100644 index 00000000000..660b5c66a78 --- /dev/null +++ b/modules/gridBidAdapter.js @@ -0,0 +1,151 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +const BIDDER_CODE = 'grid'; +const ENDPOINT_URL = '//grid.bidswitch.net/hb'; +const TIME_TO_LIVE = 360; +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; +export const spec = { + code: BIDDER_CODE, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!bid.params.uid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const auids = []; + const bidsMap = {}; + const bids = validBidRequests || []; + let reqId; + + bids.forEach(bid => { + reqId = bid.bidderRequestId; + if (!bidsMap[bid.params.uid]) { + bidsMap[bid.params.uid] = [bid]; + auids.push(bid.params.uid); + } else { + bidsMap[bid.params.uid].push(bid); + } + }); + + const payload = { + u: utils.getTopWindowUrl(), + auids: auids.join(','), + r: reqId + }; + + if (bidderRequest) { + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bidsMap: bidsMap, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; + const bidsMap = bidRequest.bidsMap; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidsMap, bidResponses); + }); + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + } +} + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function _addBidResponse(serverBid, bidsMap, bidResponses) { + if (!serverBid) return; + let errorMessage; + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else { + const awaitingBids = bidsMap[serverBid.auid]; + if (awaitingBids) { + awaitingBids.forEach(bid => { + const bidResponse = { + requestId: bid.bidId, // bid.bidderRequestId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: false, + ttl: TIME_TO_LIVE, + ad: serverBid.adm, + dealId: serverBid.dealid + }; + bidResponses.push(bidResponse); + }); + } else { + errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; + } + } + if (errorMessage) { + utils.logError(errorMessage); + } +} + +registerBidder(spec); diff --git a/modules/gridBidAdapter.md b/modules/gridBidAdapter.md new file mode 100755 index 00000000000..9b7b0e0515e --- /dev/null +++ b/modules/gridBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: The Grid Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "grid", + params: { + uid: '1', + priceType: 'gross' // by default is 'net' + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "grid", + params: { + uid: 2, + priceType: 'gross' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js new file mode 100644 index 00000000000..f4401dfe677 --- /dev/null +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -0,0 +1,299 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gridBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TheMediaGrid Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + let bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]]); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('auids', '1'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', function () { + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('auids', '1,2'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
test content 4
', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 2, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
test content 2
', + 'bidderCode': 'grid', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '3' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); From 50a7cffcdcfdecd45d7c9ec02395df9313e189aa Mon Sep 17 00:00:00 2001 From: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Date: Thu, 25 Oct 2018 16:24:32 -0400 Subject: [PATCH 03/62] Adding user sync method for IFRAME and Pixel (#3232) * Submitting EMX Digital Prebid Adapter Submitting EMX Digital Prebid Adapter code * fixing lint errors. updating our md * updating to const/let variables. adding test spec. * fixed linting on test spec js * adding emx usersync methods --- modules/emx_digitalBidAdapter.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index c25c20f2eda..042251ea035 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -103,6 +103,22 @@ export const spec = { }); } return emxBidResponses; + }, + getUserSyncs: function (syncOptions) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//biddr.brealtime.com/check.html' + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: '//edba.brealtime.com/' + }); + } + return syncs; } }; registerBidder(spec); From 5743e2bf2e086fec09f87f6337319d507e81efc5 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Fri, 26 Oct 2018 09:04:44 -0400 Subject: [PATCH 04/62] updates (#3162) --- src/utils.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 77dfe10c918..93b19485dfe 100644 --- a/src/utils.js +++ b/src/utils.js @@ -66,10 +66,22 @@ exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; */ exports.generateUUID = function generateUUID(placeholder) { return placeholder - ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + ? (placeholder ^ _getRandomData() >> placeholder / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, generateUUID); }; +/** + * Returns random data using the Crypto API if available and Math.random if not + * Method is from https://gist.github.com/jed/982883 like generateUUID, direct link https://gist.github.com/jed/982883#gistcomment-45104 + */ +function _getRandomData() { + if (window && window.crypto && window.crypto.getRandomValues) { + return crypto.getRandomValues(new Uint8Array(1))[0] % 16; + } else { + return Math.random() * 16; + } +} + exports.getBidIdParameter = function (key, paramsObj) { if (paramsObj && paramsObj[key]) { return paramsObj[key]; From 6363197be50c1cad6a5c4a17ba71d85582d45e0d Mon Sep 17 00:00:00 2001 From: Kenan Shifflett Date: Fri, 26 Oct 2018 11:15:06 -0400 Subject: [PATCH 05/62] Only set native targeting if value exists. (#3225) --- src/native.js | 4 ++-- test/spec/native_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/native.js b/src/native.js index b4d2d959b70..c9d274ddccd 100644 --- a/src/native.js +++ b/src/native.js @@ -147,7 +147,7 @@ export function fireNativeTrackers(message, adObject) { } /** - * Gets native targeting key-value paris + * Gets native targeting key-value pairs * @param {Object} bid * @return {Object} targeting */ @@ -163,7 +163,7 @@ export function getNativeTargeting(bid) { value = value.url; } - if (key) { + if (key && value) { keyValues[key] = value; } }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 91b96cac281..68653808c06 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -16,6 +16,19 @@ const bid = { } }; +const bidWithUndefinedFields = { + native: { + title: 'Native Creative', + body: undefined, + cta: undefined, + sponsoredBy: 'AppNexus', + clickUrl: 'https://www.link.example', + clickTrackers: ['https://tracker.example'], + impressionTrackers: ['https://impression.example'], + javascriptTrackers: '' + } +}; + describe('native.js', function () { let triggerPixelStub; let insertHtmlIntoIframeStub; @@ -37,6 +50,16 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); }); + it('should only include native targeting keys with values', function () { + const targeting = getNativeTargeting(bidWithUndefinedFields); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.sponsoredBy, + CONSTANTS.NATIVE_KEYS.clickUrl + ]); + }); + it('fires impression trackers', function () { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); From e1f55ceb2f9385a080c80fb7d751d286b99356a6 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Fri, 26 Oct 2018 11:36:37 -0600 Subject: [PATCH 06/62] add nolint command line option, similar to notest (#3234) --- gulpfile.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index ced29b266a7..03a0a4a7559 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -76,7 +76,10 @@ function escapePostbidConfig() { }; escapePostbidConfig.displayName = 'escape-postbid-config'; -function lint() { +function lint(done) { + if (argv.nolint) { + return done(); + } return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js']) .pipe(eslint()) .pipe(eslint.format('stylish')) From fda63ec22a96907333651f37be5ee08b370b8023 Mon Sep 17 00:00:00 2001 From: Micha Niskin Date: Fri, 26 Oct 2018 15:50:56 -0400 Subject: [PATCH 07/62] add inskin iab vendor id: enables consent via string (#3235) --- modules/inskinBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 0e7e28b9b6b..27d01e677ef 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -59,6 +59,7 @@ export const spec = { if (bidderRequest && bidderRequest.gdprConsent) { data.consent = { + gdprVendorId: 150, gdprConsentString: bidderRequest.gdprConsent.consentString, // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true gdprConsentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true From 67b24c9edff4a5845cafd7a03342deb397493c82 Mon Sep 17 00:00:00 2001 From: Omer Koren Date: Mon, 29 Oct 2018 20:15:23 +0200 Subject: [PATCH 08/62] Added user sync support for undertone bid adapter (#3172) * Added user sync support for undertone bid adapter (new pull request) * Added user sync support for undertone bid adapter * fix indentation * Changed utils.getWindowTop() with the newer prebid utilities --- modules/undertoneBidAdapter.js | 77 +++++++++++++++---- test/spec/modules/undertoneBidAdapter_spec.js | 37 ++++++++- 2 files changed, 97 insertions(+), 17 deletions(-) diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index dd99df1fc7e..6d4ced88ba1 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,11 +2,44 @@ * Adapter to send bids to Undertone */ -import * as utils from 'src/utils'; +import * as urlUtils from 'src/url'; import { registerBidder } from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'undertone'; const URL = '//hb.undertone.com/hb'; +const FRAME_USER_SYNC = '//cdn.undertone.com/js/usersync.html'; +const PIXEL_USER_SYNC_1 = '//usr.undertone.com/userPixel/syncOne?id=1&of=2'; +const PIXEL_USER_SYNC_2 = '//usr.undertone.com/userPixel/syncOne?id=2&of=2'; + +function getCanonicalUrl() { + try { + let doc = window.top.document; + let element = doc.querySelector("link[rel='canonical']"); + if (element !== null) { + return element.href; + } + } catch (e) { + } + return null; +} + +function extractDomainFromHost(pageHost) { + let domain = null; + try { + let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); + if (domains != null && domains.length > 0) { + domain = domains[0]; + for (let i = 1; i < domains.length; i++) { + if (domains[i].length > domain.length) { + domain = domains[i]; + } + } + } + } catch (e) { + domain = null; + } + return domain; +} export const spec = { code: BIDDER_CODE, @@ -16,21 +49,14 @@ export const spec = { return true; } }, - buildRequests: function(validBidRequests) { + buildRequests: function(validBidRequests, bidderRequest) { const payload = { 'x-ut-hb-params': [] }; - const location = utils.getTopWindowLocation(); - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(location.host); - let domain = null; - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } + const referer = bidderRequest.refererInfo.referer; + const hostname = urlUtils.parse(referer).hostname; + let domain = extractDomainFromHost(hostname); + const pageUrl = getCanonicalUrl() || referer; const pubid = validBidRequests[0].params.publisherId; const REQ_URL = `${URL}?pid=${pubid}&domain=${domain}`; @@ -39,7 +65,7 @@ export const spec = { const bid = { bidRequestId: bidReq.bidId, hbadaptor: 'prebid', - url: location.href, + url: pageUrl, domain: domain, placementId: bidReq.params.placementId != undefined ? bidReq.params.placementId : null, publisherId: bidReq.params.publisherId, @@ -78,6 +104,29 @@ export const spec = { }); } return bids; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const syncs = []; + if (gdprConsent && gdprConsent.gdprApplies === true) { + return syncs; + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: FRAME_USER_SYNC + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: PIXEL_USER_SYNC_1 + }, + { + type: 'image', + url: PIXEL_USER_SYNC_2 + }); + } + return syncs; } }; registerBidder(spec); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 4b816d851d9..400e86567ea 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -44,6 +44,12 @@ const bidReq = [{ auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874' }]; +const bidderReq = { + refererInfo: { + referer: 'http://prebid.org/dev-docs/bidder-adaptor.html' + } +}; + const validBidRes = { ad: '
Hello
', publisherId: 12345, @@ -98,14 +104,16 @@ describe('Undertone Adapter', function () { }); describe('build request', function () { it('should send request to correct url via POST', function () { - const request = spec.buildRequests(bidReq); - const domain = null; + const request = spec.buildRequests(bidReq, bidderReq); + const domainStart = bidderReq.refererInfo.referer.indexOf('//'); + const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.referer.substring(domainStart + 2, domainEnd); const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}`; expect(request.url).to.equal(REQ_URL); expect(request.method).to.equal('POST'); }); it('should have all relevant fields', function () { - const request = spec.buildRequests(bidReq); + const request = spec.buildRequests(bidReq, bidderReq); const bid1 = JSON.parse(request.data)['x-ut-hb-params'][0]; expect(bid1.bidRequestId).to.equal('263be71e91dd9d'); expect(bid1.sizes.length).to.equal(2); @@ -150,4 +158,27 @@ describe('Undertone Adapter', function () { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); }); + + describe('getUserSyncs', () => { + it('verifies gdpr consent checked', () => { + const options = ({ iframeEnabled: true, pixelEnabled: true }); + expect(spec.getUserSyncs(options, {}, { gdprApplies: true }).length).to.equal(0); + }); + + it('Verifies sync iframe option', function () { + const result = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }); + expect(result).to.have.lengthOf(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('//cdn.undertone.com/js/usersync.html'); + }); + + it('Verifies sync image option', function () { + const result = spec.getUserSyncs({ pixelEnabled: true }); + expect(result).to.have.lengthOf(2); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=1&of=2'); + expect(result[1].type).to.equal('image'); + expect(result[1].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=2&of=2'); + }); + }); }); From 4c085b8d0f430c6b476a58a19fc29976f638c56a Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 30 Oct 2018 06:52:03 -0700 Subject: [PATCH 09/62] Updating Auction Init Pick for timestamp + Test update (#3223) * Updating Auction Init Pick for timestamp + Test update * Updating Auction Init to include once again + Rubicon Analytics update accordingly * Removing from auction init events in favor of old --- src/auction.js | 2 +- .../modules/rubiconAnalyticsAdapter_spec.js | 54 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/auction.js b/src/auction.js index 61c403e7ff5..221cd519815 100644 --- a/src/auction.js +++ b/src/auction.js @@ -110,7 +110,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) function getProperties() { return { auctionId: _auctionId, - auctionStart: _auctionStart, + timestamp: _auctionStart, auctionEnd: _auctionEnd, auctionStatus: _auctionStatus, adUnits: _adUnits, diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index fa64513730a..97737675325 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -106,9 +106,59 @@ const MOCK = { [BID2.adUnitCode]: BID2.adserverTargeting }, AUCTION_INIT: { - 'timestamp': 1519767010567, 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timeout': 3000 + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag1'], + 'bidderRequests': [ { + 'bidderCode': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'auctionStart': 1519767010567, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000, + 'config': { + 'accountId': 1001, 'endpoint': '//localhost:9999/event' + } }, BID_REQUESTED: { 'bidder': 'rubicon', From 50d509764e6b1d81903c9f00a8ac0524131a397b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E9=9B=A8=E8=BB=92=20=D0=9F=D0=B5=D1=82=D1=80?= Date: Tue, 30 Oct 2018 15:47:21 +0100 Subject: [PATCH 10/62] Add code, test, and doc for Adikteev adapter (#3229) * Add code, test, and doc for Adikteev adapter * Reflect comments on other PR http://prebid.org/dev-docs/bidder-adaptor.html#referrers https://github.com/prebid/Prebid.js/pull/3230#discussion_r228319752 * 'currency' isn't a bidder-specific param Update PR following this remark on another one: https://github.com/prebid/Prebid.js/pull/3228#discussion_r228317072 * Add integration example, fix bid requestId --- .../gpt/hello_world_adikteev.html | 93 +++++++ integrationExamples/gpt/pbjs_example_gpt.html | 11 +- modules/adikteevBidAdapter.js | 94 +++++++ modules/adikteevBidAdapter.md | 35 +++ test/spec/modules/adikteevBidAdapter_spec.js | 235 ++++++++++++++++++ 5 files changed, 466 insertions(+), 2 deletions(-) create mode 100644 integrationExamples/gpt/hello_world_adikteev.html create mode 100644 modules/adikteevBidAdapter.js create mode 100644 modules/adikteevBidAdapter.md create mode 100644 test/spec/modules/adikteevBidAdapter_spec.js diff --git a/integrationExamples/gpt/hello_world_adikteev.html b/integrationExamples/gpt/hello_world_adikteev.html new file mode 100644 index 00000000000..7372ff12d7f --- /dev/null +++ b/integrationExamples/gpt/hello_world_adikteev.html @@ -0,0 +1,93 @@ + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ + + diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 88d4839d984..6852b9f680a 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -312,8 +312,15 @@ width: '300', height: '250', } - } - + }, + { + bidder: 'adikteev', + params: { + placementId: 12345, + currency: 'EUR', + bidFloorPrice: 0.1, + } + }, ] }, { code: 'div-gpt-ad-12345678-1', diff --git a/modules/adikteevBidAdapter.js b/modules/adikteevBidAdapter.js new file mode 100644 index 00000000000..12d502de94a --- /dev/null +++ b/modules/adikteevBidAdapter.js @@ -0,0 +1,94 @@ +import {registerBidder} from 'src/adapters/bidderFactory'; +import {BANNER} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {config} from 'src/config'; + +export const BIDDER_CODE = 'adikteev'; +export const ENDPOINT_URL = 'https://serve-adserver.adikteev.com/api/prebid/bid'; +export const ENDPOINT_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/bid'; +export const USER_SYNC_IFRAME_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IFRAME_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-iframe'; +export const USER_SYNC_IMAGE_URL = 'https://serve-adserver.adikteev.com/api/prebid/sync-image'; +export const USER_SYNC_IMAGE_URL_STAGING = 'https://serve-adserver-staging.adikteev.com/api/prebid/sync-image'; + +export let stagingEnvironmentSwitch = false; // Don't use it. Allow us to make tests on staging + +export function setstagingEnvironmentSwitch(value) { + stagingEnvironmentSwitch = value; +} + +function validateSizes(sizes) { + if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { + return false; + } + return sizes.every(size => utils.isArray(size) && size.length === 2); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + setstagingEnvironmentSwitch(stagingEnvironmentSwitch || !!bid.params.stagingEnvironment); + return !!( + bid && + bid.params && + bid.params.bidFloorPrice && + bid.params.placementId && + bid.bidder === BIDDER_CODE && + validateSizes(bid.mediaTypes.banner.sizes) + ); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const payload = { + validBidRequests, + bidderRequest, + refererInfo: bidderRequest.refererInfo, + currency: config.getConfig('currency'), + userAgent: navigator.userAgent, + screen: { + width: window.screen.width, + height: window.screen.height + }, + language: navigator.language, + cookies: document.cookie.split(';'), + prebidUpdateVersion: '1.29.0', + }; + return { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (serverResponse, bidRequests) => { + const returnedBids = []; + const validBidRequests = JSON.parse(bidRequests.data).validBidRequests; + serverResponse.body.forEach((value, index) => { + const overrides = { + requestId: validBidRequests[index].bidId, + }; + returnedBids.push(Object.assign({}, value, overrides)); + }); + return returnedBids; + }, + + getUserSyncs: (syncOptions, serverResponses) => { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: stagingEnvironmentSwitch ? USER_SYNC_IFRAME_URL_STAGING : USER_SYNC_IFRAME_URL, + }); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'image', + url: stagingEnvironmentSwitch ? USER_SYNC_IMAGE_URL_STAGING : USER_SYNC_IMAGE_URL, + }); + } + return syncs; + }, +}; +registerBidder(spec); diff --git a/modules/adikteevBidAdapter.md b/modules/adikteevBidAdapter.md new file mode 100644 index 00000000000..d5008f61b03 --- /dev/null +++ b/modules/adikteevBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Adikteev Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adnetwork@adikteev.com +``` + +# Description + +Module that connects to Adikteev's demand sources + +# Test Parameters + +``` javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[750, 200]], // a display size + } + }, + bids: [ + { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/adikteevBidAdapter_spec.js b/test/spec/modules/adikteevBidAdapter_spec.js new file mode 100644 index 00000000000..243cbe2a9c5 --- /dev/null +++ b/test/spec/modules/adikteevBidAdapter_spec.js @@ -0,0 +1,235 @@ +import {expect} from 'chai'; +import { + ENDPOINT_URL, + ENDPOINT_URL_STAGING, + setstagingEnvironmentSwitch, + spec, + stagingEnvironmentSwitch, + USER_SYNC_IFRAME_URL, + USER_SYNC_IFRAME_URL_STAGING, + USER_SYNC_IMAGE_URL, + USER_SYNC_IMAGE_URL_STAGING, +} from 'modules/adikteevBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../../../src/utils'; + +describe('adikteevBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', () => { + expect(setstagingEnvironmentSwitch).to.exist.and.to.be.a('function'); + }); + it('exists and is correctly set', () => { + expect(stagingEnvironmentSwitch).to.exist.and.to.equal(false); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const validBid = { + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + }; + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should mutate stagingEnvironmentSwitch when required params found', () => { + const withstagingEnvironmentSwitch = { + params: { + stagingEnvironment: true, + }, + }; + spec.isBidRequestValid(withstagingEnvironmentSwitch); + expect(stagingEnvironmentSwitch).to.equal(true); + setstagingEnvironmentSwitch(false); + }); + + it('should return false when required params are invalid', () => { + expect(spec.isBidRequestValid({ + bidder: '', // invalid bidder + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: '', // invalid placementId + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'adikteev', + params: { + placementId: 12345, + bidFloorPrice: 0.1, + }, + mediaTypes: { + banner: { + sizes: [[750]] // invalid size + } + }, + })).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const validBidRequests = []; + const bidderRequest = {}; + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + it('creates a request object with correct method, url and data', () => { + expect(serverRequest).to.exist.and.have.all.keys( + 'method', + 'url', + 'data', + ); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(ENDPOINT_URL); + + let requestData = JSON.parse(serverRequest.data); + expect(requestData).to.exist.and.have.all.keys( + 'validBidRequests', + 'bidderRequest', + 'userAgent', + 'screen', + 'language', + 'cookies', + // 'refererInfo', + // 'currency', + 'prebidUpdateVersion', + ); + expect(requestData.validBidRequests).to.deep.equal(validBidRequests); + expect(requestData.bidderRequest).to.deep.equal(bidderRequest); + expect(requestData.userAgent).to.deep.equal(navigator.userAgent); + expect(requestData.screen.width).to.deep.equal(window.screen.width); + expect(requestData.screen.height).to.deep.equal(window.screen.height); + expect(requestData.language).to.deep.equal(navigator.language); + expect(requestData.prebidUpdateVersion).to.deep.equal('1.29.0'); + }); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + const serverRequest = spec.buildRequests(validBidRequests, bidderRequest); + expect(serverRequest.url).to.equal(ENDPOINT_URL_STAGING); + setstagingEnvironmentSwitch(false); + }); + }); + + describe('interpretResponse', () => { + it('bid objects from response', () => { + const serverResponse = + { + body: [ + { + cpm: 1, + width: 300, + height: 250, + ad: '
', + ttl: 360, + creativeId: 123, + netRevenue: false, + currency: 'EUR', + } + ] + }; + const payload = { + validBidRequests: [{ + bidId: '2ef7bb021ac847' + }], + }; + const bidRequests = { + method: 'POST', + url: stagingEnvironmentSwitch ? ENDPOINT_URL_STAGING : ENDPOINT_URL, + data: JSON.stringify(payload), + }; + const bidResponses = spec.interpretResponse(serverResponse, bidRequests); + expect(bidResponses).to.be.an('array').that.is.not.empty; // yes, syntax is correct + expect(bidResponses[0]).to.have.all.keys( + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + ); + + expect(bidResponses[0].requestId).to.equal(payload.validBidRequests[0].bidId); + expect(bidResponses[0].cpm).to.equal(serverResponse.body[0].cpm); + expect(bidResponses[0].width).to.equal(serverResponse.body[0].width); + expect(bidResponses[0].height).to.equal(serverResponse.body[0].height); + expect(bidResponses[0].ad).to.equal(serverResponse.body[0].ad); + expect(bidResponses[0].ttl).to.equal(serverResponse.body[0].ttl); + expect(bidResponses[0].creativeId).to.equal(serverResponse.body[0].creativeId); + expect(bidResponses[0].netRevenue).to.equal(serverResponse.body[0].netRevenue); + expect(bidResponses[0].currency).to.equal(serverResponse.body[0].currency); + }); + }); + + describe('getUserSyncs', () => { + expect(spec.getUserSyncs({ + iframeEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }]); + + expect(spec.getUserSyncs({ + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL + }]); + + describe('staging environment', () => { + setstagingEnvironmentSwitch(true); + expect(spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL_STAGING + }, { + type: 'image', + url: USER_SYNC_IMAGE_URL_STAGING + }]); + setstagingEnvironmentSwitch(false); + }); + }); +}); From 9ddb20b689f65c4305de7129163a7d2308bfc868 Mon Sep 17 00:00:00 2001 From: jacekburys-quantcast <44467819+jacekburys-quantcast@users.noreply.github.com> Date: Tue, 30 Oct 2018 14:49:27 +0000 Subject: [PATCH 11/62] Quantcast adapter onTimeout (#3239) * onTimeout WIP * test for onTimeout * some renaming * cleanup * cleanup * trying to fix the test * using ajax instead of fetch --- modules/quantcastBidAdapter.js | 7 ++++++- test/spec/modules/quantcastBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index e6f4d27bdbb..79128a834a4 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from 'src/utils'; +import { ajax } from 'src/ajax'; import { registerBidder } from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'quantcast'; @@ -69,7 +70,7 @@ export const spec = { }); }); - const gdprConsent = bidderRequest ? bidderRequest.gdprConsent : {}; + const gdprConsent = (bidderRequest && bidderRequest.gdprConsent) ? bidderRequest.gdprConsent : {}; // Request Data Format can be found at https://wiki.corp.qc/display/adinf/QCX const requestData = { @@ -157,6 +158,10 @@ export const spec = { }); return bidResponsesList; + }, + onTimeout(timeoutData) { + const url = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; + ajax(url, null, null); } }; diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index f5a7602c7ab..e64294e87e2 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -1,5 +1,6 @@ import * as utils from 'src/utils'; import { expect } from 'chai'; +import { stub, sandbox } from 'sinon'; import { QUANTCAST_DOMAIN, QUANTCAST_TEST_DOMAIN, @@ -12,6 +13,7 @@ import { } from '../../../modules/quantcastBidAdapter'; import { newBidder } from '../../../src/adapters/bidderFactory'; import { parse } from 'src/url'; +import * as ajax from 'src/ajax'; describe('Quantcast adapter', function () { const quantcastAdapter = newBidder(qcSpec); @@ -211,4 +213,19 @@ describe('Quantcast adapter', function () { expect(interpretedResponse.length).to.equal(0); }); }); + + describe('`onTimeout`', function() { + it('makes a request to the notify endpoint', function() { + const sinonSandbox = sandbox.create(); + const ajaxStub = sinonSandbox.stub(ajax, 'ajax').callsFake(function() {}); + const timeoutData = { + bidder: 'quantcast' + }; + qcSpec.onTimeout(timeoutData); + const expectedUrl = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; + ajaxStub.withArgs(expectedUrl, null, null).calledOnce.should.be.true; + ajaxStub.restore(); + sinonSandbox.restore(); + }); + }); }); From 903743a20fb721428851413491e75ff271261e3a Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Tue, 30 Oct 2018 09:30:48 -0600 Subject: [PATCH 12/62] Test cleanup (#3238) * stub pixel call in justpremium tests * properly stub geolocation services to prevent prompts * stub img creation as well to prevent call in justpremium --- modules/justpremiumBidAdapter.js | 16 ++-- .../modules/justpremiumBidAdapter_spec.js | 20 ++++- test/spec/modules/uolBidAdapter_spec.js | 75 +++++++++---------- 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 48b6805c0e1..c31f485020e 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -122,6 +122,16 @@ export const spec = { } +export let pixel = { + fire(url) { + let img = document.createElement('img') + img.src = url + img.id = 'jp-pixel-track' + img.style.cssText = 'display:none !important;' + document.body.appendChild(img) + } +}; + function track (data, payload, type) { let pubUrl = '' @@ -147,11 +157,7 @@ ru=${encodeURIComponent(pubUrl)}&tt=&siw=&sh=${payload.sh}&sw=${payload.sw}&wh=$ sd=&_c=&et=&aid=&said=&ei=&fc=&sp=&at=bidder&cid=&ist=&mg=&dl=&dlt=&ev=&vt=&zid=${payload.id}&dr=${duration}&di=&pr=& cw=&ch=&nt=&st=&jp=${encodeURIComponent(JSON.stringify(jp))}&ty=${type}` - let img = document.createElement('img') - img.src = pixelUrl - img.id = 'jp-pixel-track' - img.style.cssText = 'display:none !important;' - document.body.appendChild(img) + pixel.fire(pixelUrl); } function findBid (params, bids) { diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index 3c1048143d2..8167c29e5c2 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,7 +1,19 @@ import { expect } from 'chai' -import { spec } from 'modules/justpremiumBidAdapter' +import { spec, pixel } from 'modules/justpremiumBidAdapter' describe('justpremium adapter', function () { + let sandbox; + let pixelStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + pixelStub = sandbox.stub(pixel, 'fire'); + }); + + afterEach(function() { + sandbox.restore(); + }); + let adUnits = [ { adUnitCode: 'div-gpt-ad-1471513102552-1', @@ -132,7 +144,7 @@ describe('justpremium adapter', function () { }) describe('onTimeout', function () { - it('onTimeout', (done) => { + it('onTimeout', function(done) { spec.onTimeout([{ 'bidId': '25cd3ec3fd6ed7', 'bidder': 'justpremium', @@ -153,7 +165,9 @@ describe('justpremium adapter', function () { 'zone': 21521 }], 'timeout': 1 - }]) + }]); + + expect(pixelStub.calledOnce).to.equal(true); done() }) diff --git a/test/spec/modules/uolBidAdapter_spec.js b/test/spec/modules/uolBidAdapter_spec.js index 1733afc91f9..e9341772e7d 100644 --- a/test/spec/modules/uolBidAdapter_spec.js +++ b/test/spec/modules/uolBidAdapter_spec.js @@ -1,11 +1,20 @@ import { expect } from 'chai'; import { spec } from 'modules/uolBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; const ENDPOINT = 'https://prebid.adilligo.com/v1/prebid.json'; describe('UOL Bid Adapter', function () { - const adapter = newBidder(spec); + let sandbox; + let queryStub; + let getCurrentPositionStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { @@ -88,31 +97,6 @@ describe('UOL Bid Adapter', function () { }); describe('buildRequests', function () { - let queryPermission; - let cleanup = function() { - navigator.permissions.query = queryPermission; - }; - let grantTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'granted'}); - }); - } - }; - let denyTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'prompt'}); - }); - } - }; - let removeQuerySupport = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = undefined; - } - let bidRequests = [ { 'bidder': 'uol', @@ -173,31 +157,42 @@ describe('UOL Bid Adapter', function () { describe('buildRequest geolocation param', function () { // shall only be tested if browser engine supports geolocation and permissions API. let geolocation = { lat: 4, long: 3, timestamp: 123121451 }; - it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function () { + beforeEach(function() { + getCurrentPositionStub = sandbox.stub(navigator.geolocation, 'getCurrentPosition'); + queryStub = sandbox.stub(navigator.permissions, 'query'); + }); + + it('should not contain user coordinates if browser doesnt support permission query', function () { localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - grantTriangulation(); + navigator.permissions.query = undefined; const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.exist.and.not.be.empty; - cleanup(); + expect(payload.geolocation).to.not.exist; }) - it('should not contain user coordinates if localStorage is empty', function () { - localStorage.removeItem('uolLocationTracker'); - denyTriangulation(); + it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function (done) { + localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'granted'}); + }); + }); + getCurrentPositionStub.callsFake(() => done()); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.not.exist; - cleanup(); + expect(payload.geolocation).to.exist.and.not.be.empty; }) - it('should not contain user coordinates if browser doesnt support permission query', function () { - localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - removeQuerySupport(); + it('should not contain user coordinates if localStorage is empty', function () { + localStorage.removeItem('uolLocationTracker'); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'prompt'}); + }); + }); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); expect(payload.geolocation).to.not.exist; - cleanup(); }) }) } From 0de2478f8f94ebfa5fcc802673b65b4d33989ba5 Mon Sep 17 00:00:00 2001 From: Jaimin Panchal Date: Tue, 30 Oct 2018 11:32:27 -0400 Subject: [PATCH 13/62] Appnexus adapter: Added dealPriority and dealCode to bidResponse (#3201) * Added dealPriority and dealCode to appnexus adapter * update failed test * added namespace and did deep merge * keep all properties together --- modules/appnexusBidAdapter.js | 4 +++- test/spec/modules/appnexusBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index aaec207dc1e..19aa5e7cf73 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -233,7 +233,9 @@ function newBid(serverBid, rtbBid, bidderRequest) { netRevenue: true, ttl: 300, appnexus: { - buyerMemberId: rtbBid.buyer_member_id + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code } }; diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9be87ac8628..5e41c1c9544 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -561,5 +561,15 @@ describe('AppNexusAdapter', function () { bidderRequest.bids[0].renderer.options ); }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + + let bidderRequest; + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + }); }); }); From 5481af902483506628302dbf0659bf1ccd7f1f21 Mon Sep 17 00:00:00 2001 From: HolzAndrew Date: Tue, 30 Oct 2018 13:32:25 -0400 Subject: [PATCH 14/62] use unit id being sent instead of hard coded auid (#3236) * use unit id being sent instead of hard coded auid * make multiple requests * removes commented out code. adds aus param back --- modules/openxoutstreamBidAdapter.js | 47 +++++++--------- modules/openxoutstreamBidAdapter.md | 3 +- .../modules/openxoutstreamBidAdapter_spec.js | 54 +++++++++---------- 3 files changed, 48 insertions(+), 56 deletions(-) diff --git a/modules/openxoutstreamBidAdapter.js b/modules/openxoutstreamBidAdapter.js index dde101f25d5..aee260e0a11 100644 --- a/modules/openxoutstreamBidAdapter.js +++ b/modules/openxoutstreamBidAdapter.js @@ -20,28 +20,21 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function(bidRequest) { - if (bidRequest.params.delDomain) { - return !!bidRequest.params.unit || utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } - return false; + return !!(bidRequest.params.delDomain && bidRequest.params.unit) }, buildRequests: function(bidRequests, bidderRequest) { if (bidRequests.length === 0) { return []; } let requests = []; - requests.push(buildOXBannerRequest(bidRequests, bidderRequest)); + bidRequests.forEach(bid => { + requests.push(buildOXBannerRequest(bid, bidderRequest)); + }) return requests; }, - interpretResponse: function(serverResponse, serverRequest) { - return handleVastResponse(serverResponse, serverRequest.payload) + interpretResponse: function(bid, serverResponse) { + return handleVastResponse(bid, serverResponse.payload) }, - - transformBidParams: function(params, isOpenRtb) { - return utils.convertTypes({ - 'unit': 'string', - }, params); - } }; function getViewportDimensions(isIfr) { @@ -70,7 +63,7 @@ function getViewportDimensions(isIfr) { return `${width}x${height}`; } -function buildCommonQueryParamsFromBids(bids, bidderRequest) { +function buildCommonQueryParamsFromBids(bid, bidderRequest) { const isInIframe = utils.inIframe(); let defaultParams; defaultParams = { @@ -82,13 +75,13 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { tz: new Date().getTimezoneOffset(), tws: getViewportDimensions(isInIframe), be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - auid: '540141567', - dddid: utils._map(bids, bid => bid.transactionId).join(','), + bc: bid.params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, + auid: bid.params.unit, + dddid: bid.transactionId, openrtb: '%7B%22mimes%22%3A%5B%22video%2Fmp4%22%5D%7D', nocache: new Date().getTime(), - vht: bids[0].params.height || bids[0].sizes[0][1], - vwd: bids[0].params.width || bids[0].sizes[0][0] + vht: bid.params.height || bid.sizes[0][1], + vwd: bid.params.width || bid.sizes[0][0] }; if (utils.deepAccess(bidderRequest, 'gdprConsent')) { @@ -110,24 +103,24 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { return defaultParams; } -function buildOXBannerRequest(bids, bidderRequest) { - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.sizes).join(',')).join('|'); +function buildOXBannerRequest(bid, bidderRequest) { + let queryParams = buildCommonQueryParamsFromBids(bid, bidderRequest); + queryParams.aus = utils.parseSizesInput(bid.sizes).join(','); - if (bids.some(bid => bid.params.doNotTrack)) { + if (bid.params.doNotTrack) { queryParams.ns = 1; } - if (bids.some(bid => bid.params.coppa)) { + if (bid.params.coppa) { queryParams.tfcd = 1; } - let url = `https://${bids[0].params.delDomain}/v/1.0/avjp` + let url = `https://${bid.params.delDomain}/v/1.0/avjp` return { method: 'GET', url: url, data: queryParams, - payload: {'bids': bids} + payload: {'bid': bid} }; } @@ -146,7 +139,7 @@ function handleVastResponse(response, serverResponse) { const ymAdsScript = ''; let bidResponse = {}; - bidResponse.requestId = serverResponse.bids[0].bidId; + bidResponse.requestId = serverResponse.bid.bidId; bidResponse.bidderCode = BIDDER_CODE; bidResponse.netRevenue = NET_REVENUE; bidResponse.currency = CURRENCY; diff --git a/modules/openxoutstreamBidAdapter.md b/modules/openxoutstreamBidAdapter.md index a77b4560f97..16d66b92409 100644 --- a/modules/openxoutstreamBidAdapter.md +++ b/modules/openxoutstreamBidAdapter.md @@ -24,9 +24,8 @@ var adUnits = [ { bidder: 'openxoutstream', params: { - unit: '53943996499', + unit: '540141567', delDomain: 'se-demo-d.openx.net', - publisher_page_url: 'yieldmo.com', width: '300', height: '250', } diff --git a/test/spec/modules/openxoutstreamBidAdapter_spec.js b/test/spec/modules/openxoutstreamBidAdapter_spec.js index 59d56bee74b..9b189856e1a 100644 --- a/test/spec/modules/openxoutstreamBidAdapter_spec.js +++ b/test/spec/modules/openxoutstreamBidAdapter_spec.js @@ -44,28 +44,23 @@ describe('OpenXOutstreamAdapter', function () { bannerBid.params = {delDomain: 'test-delivery-domain'} }); - it('should return false when there is no ad unit id and size', function () { + it('should return false if there is no adunit id and sizes are defined', function () { + bannerBid.mediaTypes.banner.sizes = [720, 90]; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); }); - it('should return true if there is an adunit id ', function () { + it('should return true if there is delivery domain and unit', function () { bannerBid.params.unit = '12345678'; expect(spec.isBidRequestValid(bannerBid)).to.equal(true); }); - - it('should return true if there is no adunit id and sizes are defined', function () { - bannerBid.mediaTypes.banner.sizes = [720, 90]; - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - }); - - it('should return false if no sizes are defined ', function () { + it('should return false if there is unit but no delivery domain', function () { + bannerBid.params = {unit: '12345678'}; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); }); - - it('should return false if sizes empty ', function () { - bannerBid.mediaTypes.banner.sizes = []; + it('shoud return false if there is no delivery domain and no unit', function () { + bannerBid.params = {}; expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); + }) }); }); }); @@ -92,40 +87,45 @@ describe('OpenXOutstreamAdapter', function () { expect(request[0].method).to.equal('GET'); }); - it('should send ad unit ids when any are defined', function () { + it('should send ad unit ids, height, and width when any are defined', function () { const bidRequestsWithUnitIds = [{ 'bidder': BIDDER, 'params': { + 'unit': '540141567', + 'height': '300', + 'width': '250', 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', - 'sizes': [300, 250], + sizes: [300, 250], mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [[728, 90]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + 'bidId': 'test-bid-id-2', + 'bidderRequestId': 'test-bid-request-2', + 'auctionId': 'test-auction-2' }, { 'bidder': BIDDER, 'params': { - 'unit': '540141567', 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', + 'sizes': [300, 250], mediaTypes: { banner: { - sizes: [[728, 90]] + sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' }]; const request = spec.buildRequests(bidRequestsWithUnitIds); - expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[1].params.unit}`); + expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[0].params.unit}`); + expect(request[0].data.vht).to.equal(`${bidRequestsWithUnitIds[0].params.height}`); + expect(request[0].data.vwd).to.equal(`${bidRequestsWithUnitIds[0].params.width}`); }); describe('interpretResponse', function () { @@ -147,9 +147,9 @@ describe('OpenXOutstreamAdapter', function () { }; serverRequest = { payload: { - bids: [{ + bid: { bidId: '2d36ac90d654af', - }], + }, } }; }); From 6649ef96ec224cb861e23a2fe8891a9073fd49e8 Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Tue, 30 Oct 2018 14:34:12 -0400 Subject: [PATCH 15/62] Prebid 1.30.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a53085fa062..542c14f3525 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.30.0-pre", + "version": "1.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ab703accc628d2dcf888125c818bf925ffa77ab2 Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Tue, 30 Oct 2018 14:50:06 -0400 Subject: [PATCH 16/62] increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 542c14f3525..288d970f842 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "1.30.0", + "version": "1.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2c5685cb271c364478113546cef471c11499ec06 Mon Sep 17 00:00:00 2001 From: Rich Snapp Date: Wed, 31 Oct 2018 07:52:28 -0600 Subject: [PATCH 17/62] fix deal targeting for cpm 0 (#3233) --- src/targeting.js | 13 +---- test/spec/unit/core/targeting_spec.js | 78 ++++++++++++++++----------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index dcd59f362c6..30407994b8e 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -35,20 +35,11 @@ export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback) { // filter top bid for each bucket by bidder Object.keys(buckets).forEach(bucketKey => { let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(highestCpmCallback, getEmptyBid()))); + Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(highestCpmCallback))); }); return bids; } -function getEmptyBid(adUnitCode) { - return { - adUnitCode: adUnitCode, - cpm: 0, - adserverTargeting: {}, - timeToRespond: 0 - }; -} - /** * @typedef {Object.} targeting * @property {string} targeting_key @@ -222,7 +213,7 @@ export function newTargeting(auctionManager) { .filter(uniques) .map(adUnitCode => bidsReceived .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm, getEmptyBid(adUnitCode))); + .reduce(getHighestCpm)); }; /** diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 9910645be09..427ceeca74c 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -99,42 +99,68 @@ const bid3 = { }; describe('targeting tests', function () { + let sandbox; + let enableSendAllBids = false; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'enableSendAllBids') { + return enableSendAllBids; + } + return origGetConfig.apply(config, arguments); + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('getAllTargeting', function () { let amBidsReceivedStub; let amGetAdUnitsStub; let bidExpiryStub; + let bidsReceived; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { - return [bid1, bid2, bid3]; + bidsReceived = [bid1, bid2, bid3]; + + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { + return bidsReceived; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - }); - - afterEach(function () { - auctionManager.getBidsReceived.restore(); - auctionManager.getAdUnitCodes.restore(); - targetingModule.isBidNotExpired.restore(); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); }); describe('when hb_deal is present in bid.adserverTargeting', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0; + enableSendAllBids = true; + + bidsReceived.push(bid4); + }); + it('returns targeting with both hb_deal and hb_deal_{bidder_code}', function () { const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // We should add both keys rather than one or the other - expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', `hb_deal_${bid1.bidderCode}`); + expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', `hb_deal_${bid1.bidderCode}`, `hb_deal_${bid4.bidderCode}`); // We should assign both keys the same value expect(targeting['/123456/header-bid-tag-0']['hb_deal']).to.deep.equal(targeting['/123456/header-bid-tag-0'][`hb_deal_${bid1.bidderCode}`]); }); }); - it('selects the top bid when _sendAllBids true', function () { - config.setConfig({ enableSendAllBids: true }); + it('selects the top bid when enableSendAllBids true', function () { + enableSendAllBids = true; let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // we should only get the targeting data for the one requested adunit @@ -155,14 +181,13 @@ describe('targeting tests', function () { let bidExpiryStub; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { return []; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); }); afterEach(function () { @@ -184,13 +209,8 @@ describe('targeting tests', function () { let bidExpiryStub; let auctionManagerStub; beforeEach(function () { - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - afterEach(function () { - bidExpiryStub.restore(); - auctionManagerStub.restore(); + bidExpiryStub = sandbox.stub(targetingModule, 'isBidNotExpired').returns(true); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); }); it('should use bids from pool to get Winning Bid', function () { @@ -248,14 +268,10 @@ describe('targeting tests', function () { let auctionManagerStub; let timestampStub; beforeEach(function () { - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - timestampStub = sinon.stub(utils, 'timestamp'); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); + timestampStub = sandbox.stub(utils, 'timestamp'); }); - afterEach(function () { - auctionManagerStub.restore(); - timestampStub.restore(); - }); it('should not include expired bids in the auction', function () { 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. From d25f57187b8eeae25a7aa5f3a26ffa76ea4e865b Mon Sep 17 00:00:00 2001 From: kusapan Date: Thu, 1 Nov 2018 23:23:40 +0900 Subject: [PATCH 18/62] YIELDONE adapter - support Video (#3227) * added UserSync * added UserSync Unit Test * support for multi sizes * register the adapter as supporting video * supporting video * change requestId acquisition method * fix the parameter name of dealID * update test parameters * support instream video * add test for bidRequest * add test for interpretResponse * add test params * add note to documentaion * clarifying the multi-format support message --- modules/yieldoneBidAdapter.js | 36 ++++-- modules/yieldoneBidAdapter.md | 50 ++++++-- test/spec/modules/yieldoneBidAdapter_spec.js | 117 ++++++++++++++----- 3 files changed, 154 insertions(+), 49 deletions(-) diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index cdcab0c705a..5843ce9d339 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from 'src/utils'; import {config} from 'src/config'; import {registerBidder} from 'src/adapters/bidderFactory'; +import { BANNER, VIDEO } from 'src/mediaTypes'; const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = '//y.one.impact-ad.jp/h_bid'; @@ -9,15 +10,13 @@ const USER_SYNC_URL = '//y.one.impact-ad.jp/push_sync'; export const spec = { code: BIDDER_CODE, aliases: ['y1'], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { return !!(bid.params.placementId); }, buildRequests: function(validBidRequests) { return validBidRequests.map(bidRequest => { const params = bidRequest.params; - const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; - const width = sizes.split('x')[0]; - const height = sizes.split('x')[1]; const placementId = params.placementId; const cb = Math.floor(Math.random() * 99999999999); const referrer = encodeURIComponent(utils.getTopWindowUrl()); @@ -25,13 +24,24 @@ export const spec = { const payload = { v: 'hb1', p: placementId, - w: width, - h: height, cb: cb, r: referrer, uid: bidId, t: 'i' }; + + const videoMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video'); + if (bidRequest.mediaType === VIDEO || videoMediaType) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; + const size = utils.parseSizesInput(sizes)[0]; + payload.w = size.split('x')[0]; + payload.h = size.split('x')[1]; + } else if ((utils.isEmpty(bidRequest.mediaType) && utils.isEmpty(bidRequest.mediaTypes)) || + (bidRequest.mediaType === BANNER || (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER]))) { + const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; + payload.sz = utils.parseSizesInput(sizes).join(','); + } + return { method: 'GET', url: ENDPOINT_URL, @@ -47,12 +57,12 @@ export const spec = { const height = response.height || 0; const cpm = response.cpm * 1000 || 0; if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; + const dealId = response.dealId || ''; const currency = response.currency || 'JPY'; const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; const referrer = utils.getTopWindowUrl(); const bidResponse = { - requestId: bidRequest.data.uid, + requestId: response.uid, cpm: cpm, width: response.width, height: response.height, @@ -61,9 +71,17 @@ export const spec = { currency: currency, netRevenue: netRevenue, ttl: config.getConfig('_bidderTimeout'), - referrer: referrer, - ad: response.adTag + referrer: referrer }; + + if (response.adTag) { + bidResponse.mediaType = BANNER; + bidResponse.ad = response.adTag; + } else if (response.adm) { + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = response.adm; + } + bidResponses.push(bidResponse); } return bidResponses; diff --git a/modules/yieldoneBidAdapter.md b/modules/yieldoneBidAdapter.md index b154a2ee781..1414d4e464f 100644 --- a/modules/yieldoneBidAdapter.md +++ b/modules/yieldoneBidAdapter.md @@ -10,20 +10,46 @@ Maintainer: y1dev@platform-one.co.jp Connect to YIELDONE for bids. -THE YIELDONE adapter requires setup and approval from the YIELDONE team. Please reach out to your account team or y1s@platform-one.co.jp for more information. +THE YIELDONE adapter requires setup and approval from the YIELDONE team. +Please reach out to your account team or y1s@platform-one.co.jp for more information. + +Note: THE YIELDONE adapter do not support "multi-format" scenario... if both +banner and video are specified as mediatypes, YIELDONE will treat it as a video unit. # Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'yieldone', - params: { - placementId: '44082' - } - }] - }]; +```javascript +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]] + } + }, + bids: [{ + bidder: 'yieldone', + params: { + placementId: '36891' + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'yieldone', + params: { + placementId: '41993' + } + }] + } ``` ### Configuration diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index b717ef52709..19756b86bc1 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/yieldoneBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; const ENDPOINT = '//y.one.impact-ad.jp/h_bid'; +const USER_SYNC_URL = '//y.one.impact-ad.jp/push_sync'; describe('yieldoneBidAdapter', function() { const adapter = newBidder(spec); @@ -11,12 +12,10 @@ describe('yieldoneBidAdapter', function() { let bid = { 'bidder': 'yieldone', 'params': { - placementId: '44082' + placementId: '36891' }, 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250], [336, 280]], 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -43,12 +42,10 @@ describe('yieldoneBidAdapter', function() { { 'bidder': 'yieldone', 'params': { - placementId: '44082' + placementId: '36891' }, 'adUnitCode': 'adunit-code1', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250], [336, 280]], 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -56,12 +53,10 @@ describe('yieldoneBidAdapter', function() { { 'bidder': 'yieldone', 'params': { - placementId: '44337' + placementId: '47919' }, 'adUnitCode': 'adunit-code2', - 'sizes': [ - [300, 250] - ], + 'sizes': [[300, 250]], 'bidId': '382091349b149f"', 'bidderRequestId': '"1f9c98192de251"', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', @@ -79,18 +74,31 @@ describe('yieldoneBidAdapter', function() { expect(request[0].url).to.equal(ENDPOINT); expect(request[1].url).to.equal(ENDPOINT); }); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('300x250'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + const request = spec.buildRequests([bidRequest]); + expect(request[0].data.w).to.equal('300'); + expect(request[0].data.h).to.equal('250'); + }); }); describe('interpretResponse', function () { - let bidRequest = [ + let bidRequestBanner = [ { 'method': 'GET', 'url': '//y.one.impact-ad.jp/h_bid', 'data': { 'v': 'hb1', - 'p': '44082', - 'w': '300', - 'h': '250', + 'p': '36891', + 'sz': '300x250,336x280', 'cb': 12892917383, 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', 'uid': '23beaa6af6cdde', @@ -99,34 +107,89 @@ describe('yieldoneBidAdapter', function() { } ]; - let serverResponse = { + let serverResponseBanner = { body: { 'adTag': '', + 'uid': '23beaa6af6cdde', + 'height': 250, + 'width': 300, 'cpm': 0.0536616, 'crid': '2494768', + 'currency': 'JPY', 'statusMessage': 'Bid available', - 'uid': '23beaa6af6cdde', - 'width': 300, - 'height': 250 + 'dealId': 'P1-FIX-7800-DSP-MON' } }; - it('should get the correct bid response', function () { + it('should get the correct bid response for banner', function () { let expectedResponse = [{ 'requestId': '23beaa6af6cdde', 'cpm': 53.6616, 'width': 300, 'height': 250, 'creativeId': '2494768', - 'dealId': '', + 'dealId': 'P1-FIX-7800-DSP-MON', 'currency': 'JPY', 'netRevenue': true, 'ttl': 3000, 'referrer': '', + 'mediaType': 'banner', 'ad': '' }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + let result = spec.interpretResponse(serverResponseBanner, bidRequestBanner[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); + }); + + let serverResponseVideo = { + body: { + 'uid': '23beaa6af6cdde', + 'height': 360, + 'width': 640, + 'cpm': 0.0536616, + 'dealId': 'P1-FIX-766-DSP-MON', + 'crid': '2494768', + 'currency': 'JPY', + 'statusMessage': 'Bid available', + 'adm': '' + } + }; + + let bidRequestVideo = [ + { + 'method': 'GET', + 'url': '//y.one.impact-ad.jp/h_bid', + 'data': { + 'v': 'hb1', + 'p': '41993', + 'w': '640', + 'h': '360', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i' + } + } + ]; + + it('should get the correct bid response for video', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 53.6616, + 'width': 640, + 'height': 360, + 'creativeId': '2494768', + 'dealId': 'P1-FIX-7800-DSP-MON', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'mediaType': 'video', + 'vastXml': '' + }]; + let result = spec.interpretResponse(serverResponseVideo, bidRequestVideo[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0].mediaType).to.equal(expectedResponse[0].mediaType); }); it('handles empty bid response', function () { @@ -140,14 +203,12 @@ describe('yieldoneBidAdapter', function() { 'cpm': 0 } }; - let result = spec.interpretResponse(response, bidRequest[0]); + let result = spec.interpretResponse(response, bidRequestBanner[0]); expect(result.length).to.equal(0); }); }); describe('getUserSyncs', function () { - const userSyncUrl = '//y.one.impact-ad.jp/push_sync'; - it('handles empty sync options', function () { expect(spec.getUserSyncs({})).to.be.empty; }); @@ -156,7 +217,7 @@ describe('yieldoneBidAdapter', function() { expect(spec.getUserSyncs({ 'iframeEnabled': true })).to.deep.equal([{ - type: 'iframe', url: userSyncUrl + type: 'iframe', url: USER_SYNC_URL }]); }); }); From d854db39c0d6b70f4369a634089129b6baecdf17 Mon Sep 17 00:00:00 2001 From: Kamoris Date: Thu, 1 Nov 2018 15:29:51 +0100 Subject: [PATCH 19/62] rtbhouseBidAdapter changes (#3241) * Add transactionId support * Change site getting method * Add bidfloor param --- modules/rtbhouseBidAdapter.js | 21 +++++-- modules/rtbhouseBidAdapter.md | 6 +- test/spec/modules/rtbhouseBidAdapter_spec.js | 63 +++++++++++++++----- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index e4a30782dbe..0be3887637d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -43,9 +43,12 @@ export const spec = { const request = { id: validBidRequests[0].auctionId, imp: validBidRequests.map(slot => mapImpression(slot)), - site: mapSite(validBidRequests), + site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, - test: validBidRequests[0].params.test || 0 + test: validBidRequests[0].params.test || 0, + source: { + tid: validBidRequests[0].transactionId + } }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) @@ -89,12 +92,19 @@ registerBidder(spec); * @returns {object} Imp by OpenRTB 2.5 §3.2.4 */ function mapImpression(slot) { - return { + const imp = { id: slot.bidId, banner: mapBanner(slot), native: mapNative(slot), tagid: slot.adUnitCode.toString() }; + + const bidfloor = parseFloat(slot.params.bidfloor); + if (bidfloor) { + imp.bidfloor = bidfloor + } + + return imp; } /** @@ -118,9 +128,10 @@ function mapBanner(slot) { /** * @param {object} slot Ad Unit Params by Prebid + * @param {object} bidderRequest by Prebid * @returns {object} Site by OpenRTB 2.5 §3.2.13 */ -function mapSite(slot) { +function mapSite(slot, bidderRequest) { const pubId = slot && slot.length > 0 ? slot[0].params.publisherId : 'unknown'; @@ -128,7 +139,7 @@ function mapSite(slot) { publisher: { id: pubId.toString(), }, - page: utils.getTopWindowUrl(), + page: bidderRequest.refererInfo.referer, name: utils.getOrigin() } } diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index a2d7e2aedda..c847e688941 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -7,7 +7,7 @@ Maintainer: prebid@rtbhouse.com # Description Connects to RTB House unique demand. -Banner formats are supported. +Banner and native formats are supported. Unique publisherId is required. Please reach out to pmp@rtbhouse.com to receive your own @@ -23,7 +23,8 @@ Please reach out to pmp@rtbhouse.com to receive your own bidder: "rtbhouse", params: { region: 'prebid-eu', - publisherId: 'PREBID_TEST_ID' + publisherId: 'PREBID_TEST_ID', + bidfloor: 0.01 // optional } } ] @@ -50,6 +51,7 @@ Please reach out to pmp@rtbhouse.com to receive your own params: { region: 'prebid-eu', publisherId: 'PREBID_TEST_ID' + bidfloor: 0.01 // optional } } ] diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 3c1d81a86c9..bd341465ab9 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -52,17 +52,26 @@ describe('RTBHouseAdapter', () => { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + 'auctionId': '1d1a030790a475', + 'transactionId': 'example-transaction-id', } ]; + const bidderRequest = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } + }; it('should build test param into the request', () => { - let builtTestRequest = spec.buildRequests(bidRequests).data; + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; expect(JSON.parse(builtTestRequest).test).to.equal(1); }); it('should build valid OpenRTB banner object', () => { - const request = JSON.parse(spec.buildRequests((bidRequests)).data); + const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); const imp = request.imp[0]; expect(imp.banner).to.deep.equal({ w: 300, @@ -80,7 +89,7 @@ describe('RTBHouseAdapter', () => { it('sends bid request to ENDPOINT via POST', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebid/bids'); expect(request.method).to.equal('POST'); }); @@ -88,7 +97,7 @@ describe('RTBHouseAdapter', () => { it('should not populate GDPR if for non-EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); let data = JSON.parse(request.data); expect(data).to.not.have.property('regs'); expect(data).to.not.have.property('user'); @@ -97,12 +106,15 @@ describe('RTBHouseAdapter', () => { it('should populate GDPR and consent string if available for EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, { - gdprConsent: { - gdprApplies: true, - consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' - } - }); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ-A'); @@ -111,7 +123,14 @@ describe('RTBHouseAdapter', () => { it('should populate GDPR and empty consent string if available for EEA users without consent string but with consent', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, {gdprConsent: {gdprApplies: true}}); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal(''); @@ -119,11 +138,26 @@ describe('RTBHouseAdapter', () => { it('should include banner imp in request', () => { const bidRequest = Object.assign([], bidRequests); - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].banner).to.not.be.empty; }); + it('should include source.tid in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.tid).to.equal('example-transaction-id'); + }); + + it('should include bidfloor in request if available', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.bidfloor = 0.01; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].bidfloor).to.equal(0.01) + }); + describe('native imp', () => { function basicRequest(extension) { return Object.assign({ @@ -139,7 +173,8 @@ describe('RTBHouseAdapter', () => { } function buildImp(request) { - return JSON.parse(spec.buildRequests([request]).data).imp[0]; + const resultRequest = spec.buildRequests([request], bidderRequest); + return JSON.parse(resultRequest.data).imp[0]; } it('should extract native params when single mediaType', () => { From ec023bae07fef725c8a69f8dc111f227138d0c88 Mon Sep 17 00:00:00 2001 From: ankur-modi <38654685+ankur-modi@users.noreply.github.com> Date: Thu, 1 Nov 2018 22:56:02 +0530 Subject: [PATCH 20/62] correct user agent value population (#3248) --- modules/oneVideoBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index 5b1fd999ee6..bd341dfd79f 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -136,7 +136,6 @@ function isConsentRequired(consentData) { function getRequestData(bid, consentData) { let loc = utils.getTopWindowLocation(); - let global = (window.top) ? window.top : window; let page = (bid.params.site && bid.params.site.page) ? (bid.params.site.page) : (loc.href); let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : utils.getTopWindowReferrer(); let bidData = { @@ -160,7 +159,7 @@ function getRequestData(bid, consentData) { ref: ref }, device: { - ua: global.navigator.userAgent + ua: navigator.userAgent }, tmax: 200 }; From ae42588244550b2c44848d5d65d7fa5a89355c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Mon, 14 May 2018 10:20:27 +0000 Subject: [PATCH 21/62] RVR-1124 Setup initial skeleton analytics adapter that can send something. Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 188 ++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 modules/rivrAnalyticsAdapter.js diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js new file mode 100644 index 00000000000..37d00938c18 --- /dev/null +++ b/modules/rivrAnalyticsAdapter.js @@ -0,0 +1,188 @@ +import {ajax} from 'src/ajax'; +import adapter from 'src/AnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; +import adaptermanager from 'src/adaptermanager'; +import { logInfo } from 'src/utils'; + +const analyticsType = 'endpoint'; +const DEFAULT_HOST = 'integrations.rivr.simplaex.net/prebid/auctions'; +const DEFAULT_QUEUE_TIMEOUT = 4000; + +const RIVR_HB_EVENTS = { + AUCTION_INIT: 'auctionInit', + BID_REQUEST: 'bidRequested', + BID_RESPONSE: 'bidResponse', + BID_WON: 'bidWon', + AUCTION_END: 'auctionEnd', + TIMEOUT: 'adapterTimedOut' +}; + +let rivrAnalytics = Object.assign(adapter({analyticsType}), { + track({ eventType, args }) { + if (!rivrAnalytics.context) { + return; + } + logInfo(`ARGUMENTS FOR TYPE: ============= ${eventType}`, args); + let handler = null; + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + if (rivrAnalytics.context.queue) { + rivrAnalytics.context.queue.init(); + } + handler = trackAuctionInit; + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + handler = trackBidRequest; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + handler = trackBidResponse; + break; + case CONSTANTS.EVENTS.BID_WON: + handler = trackBidWon; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + handler = trackBidTimeout; + break; + case CONSTANTS.EVENTS.AUCTION_END: + handler = trackAuctionEnd; + break; + } + if (handler) { + let events = handler(args); + if (rivrAnalytics.context.queue) { + rivrAnalytics.context.queue.push(events); + } + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + sendAll(); + } + } + } +}); + +function sendAll() { + let events = rivrAnalytics.context.queue.popAll(); + if (events.length !== 0) { + let req = Object.assign({}, {hb_ev: events}); + logInfo('sending request to analytics => ', req); + ajax(`http://${rivrAnalytics.context.host}`, () => { + }, JSON.stringify(req)); + } +} + +function trackAuctionInit() { + rivrAnalytics.context.auctionTimeStart = Date.now(); + const event = createHbEvent(undefined, RIVR_HB_EVENTS.AUCTION_INIT); + return [event]; +} + +function trackBidRequest(args) { + return args.bids.map(bid => + createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_REQUEST, bid.adUnitCode)); +} + +function trackBidResponse(args) { + const event = createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_RESPONSE, + args.adUnitCode, args.cpm, args.timeToRespond / 1000); + return [event]; +} + +function trackBidWon(args) { + const event = createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_WON, args.adUnitCode, args.cpm); + return [event]; +} + +function trackAuctionEnd(args) { + const event = createHbEvent(undefined, RIVR_HB_EVENTS.AUCTION_END, undefined, + undefined, (Date.now() - rivrAnalytics.context.auctionTimeStart) / 1000); + return [event]; +} + +function trackBidTimeout(args) { + return args.map(bidderName => createHbEvent(bidderName, RIVR_HB_EVENTS.TIMEOUT)); +} + +function createHbEvent(adapter, event, tagid = undefined, value = 0, time = 0) { + let ev = { event: event }; + if (adapter) { + ev.adapter = adapter + } + if (tagid) { + ev.tagid = tagid; + } + if (value) { + ev.val = value; + } + if (time) { + ev.time = time; + } + return ev; +} + +/** + * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. + * @param callback + * @param ttl + * @constructor + */ +export function ExpiringQueue(callback, ttl) { + let queue = []; + let timeoutId; + + this.push = (event) => { + if (event instanceof Array) { + queue.push.apply(queue, event); + } else { + queue.push(event); + } + reset(); + }; + + this.popAll = () => { + let result = queue; + queue = []; + reset(); + return result; + }; + + /** + * For test/debug purposes only + * @return {Array} + */ + this.peekAll = () => { + return queue; + }; + + this.init = reset; + + function reset() { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + if (queue.length) { + callback(); + } + }, ttl); + } +} + +// save the base class function +rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +rivrAnalytics.enableAnalytics = (config) => { + rivrAnalytics.context = { + host: config.options.host || DEFAULT_HOST, + pubId: config.options.pubId, + queue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) + }; + logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); + rivrAnalytics.originEnableAnalytics(config); +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: rivrAnalytics, + code: 'rivr' +}); + +export default rivrAnalytics From 4aa0799f3f0f515423055e6f56cc927160ffcca6 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Mon, 14 May 2018 14:17:24 +0200 Subject: [PATCH 22/62] Formatted auction/events data to fit needed schema. --- modules/rivrAnalyticsAdapter.js | 208 ++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 52 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 37d00938c18..7a5dc266646 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -5,18 +5,9 @@ import adaptermanager from 'src/adaptermanager'; import { logInfo } from 'src/utils'; const analyticsType = 'endpoint'; -const DEFAULT_HOST = 'integrations.rivr.simplaex.net/prebid/auctions'; +const DEFAULT_HOST = 'integrations.rivr.simplaex.net/prebid'; const DEFAULT_QUEUE_TIMEOUT = 4000; -const RIVR_HB_EVENTS = { - AUCTION_INIT: 'auctionInit', - BID_REQUEST: 'bidRequested', - BID_RESPONSE: 'bidResponse', - BID_WON: 'bidWon', - AUCTION_END: 'auctionEnd', - TIMEOUT: 'adapterTimedOut' -}; - let rivrAnalytics = Object.assign(adapter({analyticsType}), { track({ eventType, args }) { if (!rivrAnalytics.context) { @@ -26,8 +17,8 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { let handler = null; switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: - if (rivrAnalytics.context.queue) { - rivrAnalytics.context.queue.init(); + if (rivrAnalytics.context.impressionsQueue) { + rivrAnalytics.context.impressionsQueue.init(); } handler = trackAuctionInit; break; @@ -48,83 +39,196 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { break; } if (handler) { - let events = handler(args); - if (rivrAnalytics.context.queue) { - rivrAnalytics.context.queue.push(events); - } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - sendAll(); + if (handler == trackBidWon) { + let impressions = handler(args); + if (rivrAnalytics.context.impressionsQueue) { + rivrAnalytics.context.impressionsQueue.push(impressions); + } + } else { + handler(args) } } } }); function sendAll() { - let events = rivrAnalytics.context.queue.popAll(); - if (events.length !== 0) { - let req = Object.assign({}, {hb_ev: events}); - logInfo('sending request to analytics => ', req); - ajax(`http://${rivrAnalytics.context.host}`, () => { - }, JSON.stringify(req)); + let impressions = rivrAnalytics.context.impressionsQueue.popAll(); + let auctionObject = rivrAnalytics.context.auctionObject + let req = Object.assign({}, {Auction: auctionObject}); + auctionObject = createAuctionObject(); + logInfo('sending request to analytics => ', req); + ajax(`http://${rivrAnalytics.context.host}/auctions`, () => { + }, JSON.stringify(req)); + + if (impressions.length !== 0) { + let impressionsReq = Object.assign({}, {impressions}); + logInfo('sending impressions request to analytics => ', impressionsReq); + ajax(`http://${rivrAnalytics.context.host}/impressions`, () => { + }, JSON.stringify(impressionsReq)); } } -function trackAuctionInit() { +function trackAuctionInit(args) { rivrAnalytics.context.auctionTimeStart = Date.now(); - const event = createHbEvent(undefined, RIVR_HB_EVENTS.AUCTION_INIT); - return [event]; + rivrAnalytics.context.auctionObject.id = args.auctionId } function trackBidRequest(args) { - return args.bids.map(bid => - createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_REQUEST, bid.adUnitCode)); + setCurrentUserId(args); } function trackBidResponse(args) { - const event = createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_RESPONSE, - args.adUnitCode, args.cpm, args.timeToRespond / 1000); - return [event]; + let bidResponse = createBidResponse(args); + rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse) } function trackBidWon(args) { - const event = createHbEvent(args.bidderCode, RIVR_HB_EVENTS.BID_WON, args.adUnitCode, args.cpm); - return [event]; + let auctionObject = rivrAnalytics.context.auctionObject; + let bidResponse = createBidResponse(args) + let impression = createImpression(args) + let imp = createImp(args); + auctionObject.bidResponses.push(bidResponse) + auctionObject.imp.push(imp) + + return [impression]; } function trackAuctionEnd(args) { - const event = createHbEvent(undefined, RIVR_HB_EVENTS.AUCTION_END, undefined, - undefined, (Date.now() - rivrAnalytics.context.auctionTimeStart) / 1000); - return [event]; + rivrAnalytics.context.auctionTimeEnd = Date.now(); } function trackBidTimeout(args) { - return args.map(bidderName => createHbEvent(bidderName, RIVR_HB_EVENTS.TIMEOUT)); + return [args] } -function createHbEvent(adapter, event, tagid = undefined, value = 0, time = 0) { - let ev = { event: event }; - if (adapter) { - ev.adapter = adapter +function setCurrentUserId(bidRequested) { + let user = rivrAnalytics.context.auctionObject.user + if (!user.id) { + rivrAnalytics.context.pubId ? user.id = rivrAnalytics.context.pubId : user.id = bidRequested.bids[0].crumbs.pubcid } - if (tagid) { - ev.tagid = tagid; +} + +function createBidResponse(bidResponseEvent) { + return { + timestamp: bidResponseEvent.responseTimestamp, + status: bidResponseEvent.getStatusCode(), + total_duration: bidResponseEvent.timeToRespond, + bidderId: null, + bidder_name: bidResponseEvent.bidder, + cur: bidResponseEvent.currency, + seatid: [ + { + seat: null, + bid: [ + { + status: bidResponseEvent.getStatusCode(), + clear_price: bidResponseEvent.cpm, + attr: [], + crid: bidResponseEvent.creativeId, + cid: null, + id: null, + adid: bidResponseEvent.adId, + adomain: [], + iurl: null + } + ] + } + ] } - if (value) { - ev.val = value; +} + +function createImpression(bidWonEvent) { + return { + timestamp: bidWonEvent.responseTimestamp, + requestId: bidWonEvent.auctionId, + chargePrice: bidWonEvent.adserverTargeting.hb_pb, + publisherRevenue: bidWonEvent.cpm } - if (time) { - ev.time = time; +} + +function createImp(bidWonEvent) { + return { + tagid: bidWonEvent.adUnitCode, + displaymanager: null, + displaymanagerver: null, + secure: null, + bidfloor: null, + banner: { + w: bidWonEvent.width, + h: bidWonEvent.height, + pos: null, + expandable: [], + api: [] + } } - return ev; } +function createAuctionObject() { + return { + id: null, + timestamp: null, + at: null, + bcat: [], + imp: [], + app: { + id: null, + name: null, + domain: null, + bundle: null, + cat: [], + publisher: { + id: null, + name: null + } + }, + site: { + id: null, + name: null, + domain: null, + cat: [], + publisher: { + id: null, + name: null + } + }, + device: { + geo: { + city: null, + country: null, + region: null, + zip: null, + type: null, + metro: null + }, + connectiontype: null, + devicetype: null, + osv: null, + os: null, + model: null, + make: null, + carrier: null, + ip: null, + didsha1: null, + dpidmd5: null, + ext: { + uid: null + } + }, + user: { + id: null, + yob: null, + gender: null, + }, + bidResponses: [] + } +} /** * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. * @param callback * @param ttl * @constructor */ -export function ExpiringQueue(callback, ttl) { +export function ExpiringQueue(callback, ttl, log) { let queue = []; let timeoutId; @@ -143,7 +247,6 @@ export function ExpiringQueue(callback, ttl) { reset(); return result; }; - /** * For test/debug purposes only * @return {Array} @@ -174,7 +277,8 @@ rivrAnalytics.enableAnalytics = (config) => { rivrAnalytics.context = { host: config.options.host || DEFAULT_HOST, pubId: config.options.pubId, - queue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) + auctionObject: createAuctionObject(), + impressionsQueue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); From 08a438fdf4ec6d0d03d2ce852d98272c9c9ea002 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 15:40:33 +0200 Subject: [PATCH 23/62] Applied feedback. --- modules/rivrAnalyticsAdapter.js | 56 +++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 7a5dc266646..d8a12c53707 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -66,47 +66,55 @@ function sendAll() { ajax(`http://${rivrAnalytics.context.host}/impressions`, () => { }, JSON.stringify(impressionsReq)); } -} +}; function trackAuctionInit(args) { rivrAnalytics.context.auctionTimeStart = Date.now(); - rivrAnalytics.context.auctionObject.id = args.auctionId -} + rivrAnalytics.context.auctionObject.id = args.auctionId; +}; function trackBidRequest(args) { - setCurrentUserId(args); -} + setCurrentPublisherId(args); +}; function trackBidResponse(args) { let bidResponse = createBidResponse(args); - rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse) -} + rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); +}; function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; - let bidResponse = createBidResponse(args) - let impression = createImpression(args) + let bidResponse = createBidResponse(args); + let impression = createImpression(args); let imp = createImp(args); - auctionObject.bidResponses.push(bidResponse) - auctionObject.imp.push(imp) + auctionObject.bidResponses.push(bidResponse); + auctionObject.imp.push(imp); return [impression]; } function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); -} +}; function trackBidTimeout(args) { - return [args] -} + return [args]; +}; -function setCurrentUserId(bidRequested) { - let user = rivrAnalytics.context.auctionObject.user - if (!user.id) { - rivrAnalytics.context.pubId ? user.id = rivrAnalytics.context.pubId : user.id = bidRequested.bids[0].crumbs.pubcid +function setCurrentPublisherId(bidRequested) { + let site = rivrAnalytics.context.auctionObject.site; + let app = rivrAnalytics.context.auctionObject.app; + let pubId = rivrAnalytics.context.pubId; + if (!site.publisher.id || app.publisher.id) { + if (pubId) { + site.publisher.id = pubId; + app.publisher.id = pubId; + } else { + site.publisher.id = bidRequested.bids[0].crumbs.pubcid; + app.publisher.id = bidRequested.bids[0].crumbs.pubcid; + } } -} +}; function createBidResponse(bidResponseEvent) { return { @@ -135,7 +143,7 @@ function createBidResponse(bidResponseEvent) { } ] } -} +}; function createImpression(bidWonEvent) { return { @@ -144,7 +152,7 @@ function createImpression(bidWonEvent) { chargePrice: bidWonEvent.adserverTargeting.hb_pb, publisherRevenue: bidWonEvent.cpm } -} +}; function createImp(bidWonEvent) { return { @@ -161,7 +169,7 @@ function createImp(bidWonEvent) { api: [] } } -} +}; function createAuctionObject() { return { @@ -221,7 +229,7 @@ function createAuctionObject() { }, bidResponses: [] } -} +}; /** * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. * @param callback @@ -267,7 +275,7 @@ export function ExpiringQueue(callback, ttl, log) { } }, ttl); } -} +}; // save the base class function rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; From 8bea7bc1bffe01885cd61dc2e3643f9dd2240c6b Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 16 May 2018 13:35:28 +0200 Subject: [PATCH 24/62] RVR-1135 fetched device data. --- modules/rivrAnalyticsAdapter.js | 37 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index d8a12c53707..b6157a3d758 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -20,6 +20,10 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { if (rivrAnalytics.context.impressionsQueue) { rivrAnalytics.context.impressionsQueue.init(); } + if (rivrAnalytics.context.auctionObject) { + rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + fetchLocalization(); + } handler = trackAuctionInit; break; case CONSTANTS.EVENTS.BID_REQUESTED: @@ -55,7 +59,7 @@ function sendAll() { let impressions = rivrAnalytics.context.impressionsQueue.popAll(); let auctionObject = rivrAnalytics.context.auctionObject let req = Object.assign({}, {Auction: auctionObject}); - auctionObject = createAuctionObject(); + auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); ajax(`http://${rivrAnalytics.context.host}/auctions`, () => { }, JSON.stringify(req)); @@ -116,6 +120,27 @@ function setCurrentPublisherId(bidRequested) { } }; +function fetchLocalization() { + ajax(`https://ipapi.co/json`, (rawLocalization) => { + let deviceLocation = rivrAnalytics.context.auctionObject.device.geo + let location = JSON.parse(rawLocalization) + deviceLocation.city = location.city; + deviceLocation.country = location.country + deviceLocation.region = location.region + deviceLocation.zip = location.postal + }); +}; + +function getPlatformType() { + if (navigator.userAgent.match(/mobile/i)) { + return 'Mobile'; + } else if (navigator.userAgent.match(/iPad|Android|Touch/i)) { + return 'Tablet'; + } else { + return 'Desktop'; + } +} + function createBidResponse(bidResponseEvent) { return { timestamp: bidResponseEvent.responseTimestamp, @@ -171,7 +196,7 @@ function createImp(bidWonEvent) { } }; -function createAuctionObject() { +function fulfillAuctionObject() { return { id: null, timestamp: null, @@ -192,7 +217,7 @@ function createAuctionObject() { site: { id: null, name: null, - domain: null, + domain: window.location.hostname, cat: [], publisher: { id: null, @@ -208,8 +233,8 @@ function createAuctionObject() { type: null, metro: null }, - connectiontype: null, - devicetype: null, + connectiontype: navigator.connection.effectiveType, + devicetype: getPlatformType(), osv: null, os: null, model: null, @@ -285,7 +310,7 @@ rivrAnalytics.enableAnalytics = (config) => { rivrAnalytics.context = { host: config.options.host || DEFAULT_HOST, pubId: config.options.pubId, - auctionObject: createAuctionObject(), + auctionObject: {}, impressionsQueue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); From 316aaea96ea3eae0a3d6037391db918db3ede71e Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 16:17:01 +0200 Subject: [PATCH 25/62] Applied feedback. --- modules/rivrAnalyticsAdapter.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index b6157a3d758..34eeeed745d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -57,7 +57,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { function sendAll() { let impressions = rivrAnalytics.context.impressionsQueue.popAll(); - let auctionObject = rivrAnalytics.context.auctionObject + let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); @@ -121,13 +121,10 @@ function setCurrentPublisherId(bidRequested) { }; function fetchLocalization() { - ajax(`https://ipapi.co/json`, (rawLocalization) => { - let deviceLocation = rivrAnalytics.context.auctionObject.device.geo - let location = JSON.parse(rawLocalization) - deviceLocation.city = location.city; - deviceLocation.country = location.country - deviceLocation.region = location.region - deviceLocation.zip = location.postal + navigator.geolocation.getCurrentPosition((position) => { + let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; + deviceLocation.lat = position.coords.latitude; + deviceLocation.long = position.coords.longitude; }); }; From 5ad40bdf7bb5c1a0eba325e9eb8fcfafa25ad8e5 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 21:01:54 +0200 Subject: [PATCH 26/62] Fetched core. --- test/spec/unit/pbjs_api_spec.js | 254 ++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index a03339c76b3..144cbb656db 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -763,6 +763,260 @@ describe('Unit: Prebid Module', function () { }); }); + describe('getAdserverTargeting with `mediaTypePriceGranularity` set for media type', function() { + let currentPriceBucket; + let auction; + let ajaxStub; + let response; + let cbTimeout = 3000; + let auctionManagerInstance; + let targeting; + + const bannerResponse = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '4d0a6829338a07', + 'tag_id': 4799418, + 'auction_id': '2256922143947979797', + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 2500, + 'ads': [{ + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 33989846, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 1.99, + 'cpm_publisher_currency': 0.500000, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'width': 300, + 'height': 250, + 'content': '' + }, + 'trackers': [{ + 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] + }] + } + }] + }] + }; + const videoResponse = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '4d0a6829338a07', + 'tag_id': 4799418, + 'auction_id': '2256922143947979797', + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 2500, + 'ads': [{ + 'content_source': 'rtb', + 'ad_type': 'video', + 'buyer_member_id': 958, + 'creative_id': 33989846, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 1.99, + 'cpm_publisher_currency': 0.500000, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'rtb': { + 'video': { + 'width': 300, + 'height': 250, + 'content': '' + }, + 'trackers': [{ + 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] + }] + } + }] + }] + }; + + const createAdUnit = (code, mediaTypes) => { + if (!mediaTypes) { + mediaTypes = ['banner']; + } else if (typeof mediaTypes === 'string') { + mediaTypes = [mediaTypes]; + } + + const adUnit = { + code: code, + sizes: [[300, 250], [300, 600]], + bids: [{ + bidder: 'appnexus', + params: { + placementId: '10433394' + } + }] + }; + + let _mediaTypes = {}; + if (mediaTypes.indexOf('banner') !== -1) { + _mediaTypes['banner'] = { + 'banner': {} + }; + } + if (mediaTypes.indexOf('video') !== -1) { + _mediaTypes['video'] = { + 'video': { + context: 'instream', + playerSize: [300, 250] + } + }; + } + if (mediaTypes.indexOf('native') !== -1) { + _mediaTypes['native'] = { + 'native': {} + }; + } + + if (Object.keys(_mediaTypes).length > 0) { + adUnit['mediaTypes'] = _mediaTypes; + // if video type, add video to every bid.param object + if (_mediaTypes.video) { + adUnit.bids.forEach(bid => { + bid.params['video'] = { + width: 300, + height: 250, + vastUrl: '', + ttl: 3600 + }; + }); + } + } + return adUnit; + } + const initTestConfig = (data) => { + $$PREBID_GLOBAL$$.bidderSettings = {}; + + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { + return function(url, callback) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callback.success(JSON.stringify(response), { getResponseHeader: fakeResponse }); + } + }); + auctionManagerInstance = newAuctionManager(); + targeting = newTargeting(auctionManagerInstance) + + configObj.setConfig({ + 'priceGranularity': { + 'buckets': [ + { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 }, + { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05 }, + { 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 }, + { 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 } + ] + }, + 'mediaTypePriceGranularity': { + 'banner': { + 'buckets': [ + { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.25 }, + { 'precision': 2, 'min': 6, 'max': 20, 'increment': 0.5 }, + { 'precision': 2, 'min': 21, 'max': 100, 'increment': 1 } + ] + }, + 'video': 'low', + 'native': 'high' + } + }); + + auction = auctionManagerInstance.createAuction({ + adUnits: data.adUnits, + adUnitCodes: data.adUnitCodes + }); + }; + + before(() => { + currentPriceBucket = configObj.getConfig('priceGranularity'); + sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{ + 'bidderCode': 'appnexus', + 'auctionId': '20882439e3238c', + 'bidderRequestId': '331f3cf3f1d9c8', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4d0a6829338a07', + 'bidderRequestId': '331f3cf3f1d9c8', + 'auctionId': '20882439e3238c' + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }])); + }); + + after(() => { + configObj.setConfig({ priceGranularity: currentPriceBucket }); + adaptermanager.makeBidRequests.restore(); + }) + + afterEach(() => { + ajaxStub.restore(); + }); + + it('should get correct hb_pb with cpm between 0 - 5', () => { + initTestConfig({ + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = bannerResponse; + response.tags[0].ads[0].cpm = 3.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.25'); + }); + + it('should get correct hb_pb with cpm between 21 - 100', () => { + initTestConfig({ + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = bannerResponse; + response.tags[0].ads[0].cpm = 43.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('43.00'); + }); + + it('should only apply price granularity if bid media type matches', () => { + initTestConfig({ + adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = videoResponse; + response.tags[0].ads[0].cpm = 3.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.00'); + }); + }); + describe('getBidResponses', function () { it('should return expected bid responses when not passed an adunitCode', function () { var result = $$PREBID_GLOBAL$$.getBidResponses(); From 8314ce218ab2eb60ba98f350596019842d7db14a Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Fri, 18 May 2018 12:01:49 +0200 Subject: [PATCH 27/62] Added click handler for reporting banners click events. --- modules/rivrAnalyticsAdapter.js | 53 ++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 34eeeed745d..6aa61f296bf 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -2,10 +2,10 @@ import {ajax} from 'src/ajax'; import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; -import { logInfo } from 'src/utils'; +import { logInfo, generateUUID } from 'src/utils'; const analyticsType = 'endpoint'; -const DEFAULT_HOST = 'integrations.rivr.simplaex.net/prebid'; +const DEFAULT_HOST = 'integrations.rivr.simplaex.net'; const DEFAULT_QUEUE_TIMEOUT = 4000; let rivrAnalytics = Object.assign(adapter({analyticsType}), { @@ -89,13 +89,13 @@ function trackBidResponse(args) { function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; let bidResponse = createBidResponse(args); - let impression = createImpression(args); - let imp = createImp(args); + let standaloneImpression = createStandaloneImpression(args); + let auctionImpression = createAuctionImpression(args); auctionObject.bidResponses.push(bidResponse); - auctionObject.imp.push(imp); + auctionObject.imp.push(auctionImpression); - return [impression]; -} + return [standaloneImpression]; +}; function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); @@ -136,7 +136,7 @@ function getPlatformType() { } else { return 'Desktop'; } -} +}; function createBidResponse(bidResponseEvent) { return { @@ -167,7 +167,7 @@ function createBidResponse(bidResponseEvent) { } }; -function createImpression(bidWonEvent) { +function createStandaloneImpression(bidWonEvent) { return { timestamp: bidWonEvent.responseTimestamp, requestId: bidWonEvent.auctionId, @@ -176,7 +176,7 @@ function createImpression(bidWonEvent) { } }; -function createImp(bidWonEvent) { +function createAuctionImpression(bidWonEvent) { return { tagid: bidWonEvent.adUnitCode, displaymanager: null, @@ -193,6 +193,38 @@ function createImp(bidWonEvent) { } }; +function reportClickEvent(event) { + let link = event.currentTarget.getElementsByTagName('a')[0]; + let clickUrl; + if (link) { + clickUrl = link.getAttribute('href'); + } + let timestamp = new Date().toISOString(); + let requestId = generateUUID(); + let req = { + timestamp, + 'request_id': requestId, + 'click_url': clickUrl + }; + logInfo('Sending click events with parameters: ', req); + ajax(`http://${rivrAnalytics.context.host}/${window.location.href}/clicks`, () => { + }, JSON.stringify(req)); +}; + +function clickHandler(bannersIds) { + setTimeout(function () { + bannersIds.forEach(function (bannerId) { + var doc = document.getElementById(bannerId); + if (doc) { + var iframe = doc.getElementsByTagName('iframe')[0]; + if (iframe) { + iframe.contentDocument.addEventListener('click', reportClickEvent); + } + } + }); + }, 1500); +}; + function fulfillAuctionObject() { return { id: null, @@ -310,6 +342,7 @@ rivrAnalytics.enableAnalytics = (config) => { auctionObject: {}, impressionsQueue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; + clickHandler(config.options.bannersIds); logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); }; From cf19b684b57766fb2df96cb6e6e4e38233590924 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Fri, 18 May 2018 14:06:08 +0200 Subject: [PATCH 28/62] Applied analyzer for reporting displayed impressions. --- modules/rivrAnalyticsAdapter.js | 78 +++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 6aa61f296bf..574c0077c10 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -17,8 +17,8 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { let handler = null; switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: - if (rivrAnalytics.context.impressionsQueue) { - rivrAnalytics.context.impressionsQueue.init(); + if (rivrAnalytics.context.queue) { + rivrAnalytics.context.queue.init(); } if (rivrAnalytics.context.auctionObject) { rivrAnalytics.context.auctionObject = fulfillAuctionObject(); @@ -43,27 +43,22 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { break; } if (handler) { - if (handler == trackBidWon) { - let impressions = handler(args); - if (rivrAnalytics.context.impressionsQueue) { - rivrAnalytics.context.impressionsQueue.push(impressions); - } - } else { - handler(args) - } + handler(args) } } }); -function sendAll() { - let impressions = rivrAnalytics.context.impressionsQueue.popAll(); +function sendAuction() { let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); - ajax(`http://${rivrAnalytics.context.host}/auctions`, () => { + ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientURL}/auctions`, () => { }, JSON.stringify(req)); +}; +function sendImpressions() { + let impressions = rivrAnalytics.context.queue.popAll(); if (impressions.length !== 0) { let impressionsReq = Object.assign({}, {impressions}); logInfo('sending impressions request to analytics => ', impressionsReq); @@ -89,12 +84,9 @@ function trackBidResponse(args) { function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; let bidResponse = createBidResponse(args); - let standaloneImpression = createStandaloneImpression(args); let auctionImpression = createAuctionImpression(args); auctionObject.bidResponses.push(bidResponse); auctionObject.imp.push(auctionImpression); - - return [standaloneImpression]; }; function trackAuctionEnd(args) { @@ -167,15 +159,6 @@ function createBidResponse(bidResponseEvent) { } }; -function createStandaloneImpression(bidWonEvent) { - return { - timestamp: bidWonEvent.responseTimestamp, - requestId: bidWonEvent.auctionId, - chargePrice: bidWonEvent.adserverTargeting.hb_pb, - publisherRevenue: bidWonEvent.cpm - } -}; - function createAuctionImpression(bidWonEvent) { return { tagid: bidWonEvent.adUnitCode, @@ -207,16 +190,16 @@ function reportClickEvent(event) { 'click_url': clickUrl }; logInfo('Sending click events with parameters: ', req); - ajax(`http://${rivrAnalytics.context.host}/${window.location.href}/clicks`, () => { + ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientURL}/clicks`, () => { }, JSON.stringify(req)); }; function clickHandler(bannersIds) { setTimeout(function () { bannersIds.forEach(function (bannerId) { - var doc = document.getElementById(bannerId); + let doc = document.getElementById(bannerId); if (doc) { - var iframe = doc.getElementsByTagName('iframe')[0]; + let iframe = doc.getElementsByTagName('iframe')[0]; if (iframe) { iframe.contentDocument.addEventListener('click', reportClickEvent); } @@ -225,6 +208,32 @@ function clickHandler(bannersIds) { }, 1500); }; +function displayedImpressionHandler(bannersIds) { + setTimeout(function () { + bannersIds.forEach((bannerId) => { + let doc = document.getElementById(bannerId); + if (doc) { + let iframe = doc.getElementsByTagName('iframe')[0]; + if (iframe) { + let displayedImpressionImg = iframe.contentDocument.getElementsByTagName('img').length > 0; + if (displayedImpressionImg) { + let timestamp = new Date().toISOString(); + let requestId = generateUUID(); + let impression = { + timestamp, + 'request_id': requestId, + }; + if (rivrAnalytics.context.queue) { + rivrAnalytics.context.queue.push(impression); + } + } + } + } + }); + sendImpressions(); + }, 3000); +}; + function fulfillAuctionObject() { return { id: null, @@ -235,7 +244,7 @@ function fulfillAuctionObject() { app: { id: null, name: null, - domain: null, + domain: rivrAnalytics.context.clientURL, bundle: null, cat: [], publisher: { @@ -246,7 +255,7 @@ function fulfillAuctionObject() { site: { id: null, name: null, - domain: window.location.hostname, + domain: rivrAnalytics.context.clientURL, cat: [], publisher: { id: null, @@ -290,7 +299,7 @@ function fulfillAuctionObject() { * @param ttl * @constructor */ -export function ExpiringQueue(callback, ttl, log) { +export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { let queue = []; let timeoutId; @@ -324,8 +333,9 @@ export function ExpiringQueue(callback, ttl, log) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { + sendAuction(); if (queue.length) { - callback(); + sendImpressions(); } }, ttl); } @@ -340,9 +350,11 @@ rivrAnalytics.enableAnalytics = (config) => { host: config.options.host || DEFAULT_HOST, pubId: config.options.pubId, auctionObject: {}, - impressionsQueue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) + clientURL: window.location.href, + queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; clickHandler(config.options.bannersIds); + displayedImpressionHandler(config.options.bannersIds); logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); }; From 3abcf5347b46ac157d3fa9b9f318155a7d8b6b8b Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Tue, 22 May 2018 21:05:28 +0200 Subject: [PATCH 29/62] Applied feedback. --- modules/rivrAnalyticsAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 574c0077c10..c1cfec7c1c9 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -194,7 +194,7 @@ function reportClickEvent(event) { }, JSON.stringify(req)); }; -function clickHandler(bannersIds) { +function addClickHandlers(bannersIds) { setTimeout(function () { bannersIds.forEach(function (bannerId) { let doc = document.getElementById(bannerId); @@ -353,7 +353,7 @@ rivrAnalytics.enableAnalytics = (config) => { clientURL: window.location.href, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; - clickHandler(config.options.bannersIds); + addClickHandlers(config.options.bannersIds); displayedImpressionHandler(config.options.bannersIds); logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); From 9d655d2a14631b3e667885bf67d5087f5d4b326f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Thu, 24 May 2018 08:24:30 +0000 Subject: [PATCH 30/62] Merged in RVR-1214-invoke-handlers-on-rendering (pull request #7) RVR-1214 Invoke handlers on rendering * RVR-1214 Invoked handlers right after ad is displayed. * Applied feedback. Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 93 ++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c1cfec7c1c9..04648e3e3cb 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -194,44 +194,64 @@ function reportClickEvent(event) { }, JSON.stringify(req)); }; -function addClickHandlers(bannersIds) { - setTimeout(function () { - bannersIds.forEach(function (bannerId) { - let doc = document.getElementById(bannerId); - if (doc) { - let iframe = doc.getElementsByTagName('iframe')[0]; - if (iframe) { - iframe.contentDocument.addEventListener('click', reportClickEvent); - } - } - }); - }, 1500); +function addClickHandler(bannerId) { + pinHandlerToHTMLElement(bannerId, dataLoaderForHandler, addClickListener); }; -function displayedImpressionHandler(bannersIds) { - setTimeout(function () { - bannersIds.forEach((bannerId) => { - let doc = document.getElementById(bannerId); - if (doc) { - let iframe = doc.getElementsByTagName('iframe')[0]; - if (iframe) { - let displayedImpressionImg = iframe.contentDocument.getElementsByTagName('img').length > 0; - if (displayedImpressionImg) { - let timestamp = new Date().toISOString(); - let requestId = generateUUID(); - let impression = { - timestamp, - 'request_id': requestId, - }; - if (rivrAnalytics.context.queue) { - rivrAnalytics.context.queue.push(impression); - } - } - } +function addDisplayedImpHandler(bannerId) { + pinHandlerToHTMLElement(bannerId, dataLoaderForHandler, impHandler); +}; + +function pinHandlerToHTMLElement(elementId, dataLoaderForHandler, specializedHandler) { + function waitForElement() { + let element = document.getElementById(elementId); + if (!element) { + window.requestAnimationFrame(waitForElement); + } else { + dataLoaderForHandler(element, specializedHandler); + } + } + waitForElement(); +} + +function dataLoaderForHandler(element, specializedHandler) { + function waitForElement() { + let iframe = element.getElementsByTagName('iframe')[0]; + if (!iframe) { + window.requestAnimationFrame(waitForElement); + } else { + let displayedImpression = iframe.contentDocument.getElementsByTagName('img').length > 0; + if (!displayedImpression) { + window.requestAnimationFrame(waitForElement); + } else { + specializedHandler(iframe); } - }); - sendImpressions(); - }, 3000); + } + } + waitForElement(); +}; + +function addClickListener(iframe) { + iframe.contentDocument.addEventListener('click', reportClickEvent); +} + +function impHandler(iframe) { + let timestamp = new Date().toISOString(); + let requestId = generateUUID(); + let impression = { + timestamp, + 'request_id': requestId, + }; + if (rivrAnalytics.context.queue) { + rivrAnalytics.context.queue.push(impression); + } +} + +function addHandlers(bannersIds) { + bannersIds.forEach((bannerId) => { + addClickHandler(bannerId); + addDisplayedImpHandler(bannerId); + }) }; function fulfillAuctionObject() { @@ -353,8 +373,7 @@ rivrAnalytics.enableAnalytics = (config) => { clientURL: window.location.href, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; - addClickHandlers(config.options.bannersIds); - displayedImpressionHandler(config.options.bannersIds); + addHandlers(config.options.bannersIds); logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); }; From 9d2e6c6fcff6a8c2ddea709e29ec89f53dbed8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Thu, 24 May 2018 09:28:39 +0000 Subject: [PATCH 31/62] Merged in RVR-1192-configuration-global-parameters (pull request #8) RVR-1192 Configuration/Global parameters Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 04648e3e3cb..35a14c588bc 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -53,7 +53,7 @@ function sendAuction() { let req = Object.assign({}, {Auction: auctionObject}); auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); - ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientURL}/auctions`, () => { + ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => { }, JSON.stringify(req)); }; @@ -190,7 +190,7 @@ function reportClickEvent(event) { 'click_url': clickUrl }; logInfo('Sending click events with parameters: ', req); - ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientURL}/clicks`, () => { + ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/clicks`, () => { }, JSON.stringify(req)); }; @@ -370,7 +370,7 @@ rivrAnalytics.enableAnalytics = (config) => { host: config.options.host || DEFAULT_HOST, pubId: config.options.pubId, auctionObject: {}, - clientURL: window.location.href, + clientID: config.options.clientID, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; addHandlers(config.options.bannersIds); From 531fedfe38be02e6ffff298db4c751b964aed5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Fri, 25 May 2018 09:12:04 +0000 Subject: [PATCH 32/62] Merged in RVR-1181-Prebid-js-unit-tests-setup (pull request #6) RVR-1181 Prebid.js Unit tests setup Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 7 +- .../spec/modules/rivrAnalyticsAdapter_spec.js | 221 ++++++++++++++++++ 2 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 test/spec/modules/rivrAnalyticsAdapter_spec.js diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 35a14c588bc..ea13d233d3e 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -51,7 +51,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { function sendAuction() { let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); - auctionObject = fulfillAuctionObject(); + rivrAnalytics.context.auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => { }, JSON.stringify(req)); @@ -83,9 +83,7 @@ function trackBidResponse(args) { function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; - let bidResponse = createBidResponse(args); let auctionImpression = createAuctionImpression(args); - auctionObject.bidResponses.push(bidResponse); auctionObject.imp.push(auctionImpression); }; @@ -255,7 +253,7 @@ function addHandlers(bannersIds) { }; function fulfillAuctionObject() { - return { + let newAuction = { id: null, timestamp: null, at: null, @@ -312,6 +310,7 @@ function fulfillAuctionObject() { }, bidResponses: [] } + return newAuction; }; /** * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b0d3b2fd72f --- /dev/null +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -0,0 +1,221 @@ +import analyticsAdapter, {ExpiringQueue} from 'modules/rivrAnalyticsAdapter'; +import {expect} from 'chai'; +import adaptermanager from 'src/adaptermanager'; +import * as ajax from 'src/ajax'; +import CONSTANTS from 'src/constants.json'; + +const events = require('../../../src/events'); + +describe('', () => { + let sandbox; + + before(() => { + sandbox = sinon.sandbox.create(); + }); + + after(() => { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('ExpiringQueue', () => { + let timer; + before(() => { + timer = sandbox.useFakeTimers(0); + }); + after(() => { + timer.restore(); + }); + + it('should notify after timeout period', (done) => { + let queue = new ExpiringQueue(() => { + let elements = queue.popAll(); + expect(elements).to.be.eql([1, 2, 3, 4]); + elements = queue.popAll(); + expect(elements).to.have.lengthOf(0); + expect(Date.now()).to.be.equal(200); + done(); + }, () => {}, 100); + + queue.push(1); + setTimeout(() => { + queue.push([2, 3]); + timer.tick(50); + }, 50); + setTimeout(() => { + queue.push([4]); + timer.tick(100); + }, 100); + timer.tick(50); + }); + }); + + const REQUEST = { + bidderCode: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + const RESPONSE = { + bidderCode: 'adapter', + width: 300, + height: 250, + statusMessage: 'Bid available', + getStatusCode: () => 1, + adId: '208750227436c1', + mediaType: 'banner', + cpm: 0.015, + creativeId: 999, + ad: '', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: 'adapter', + adUnitCode: 'container-1', + timeToRespond: 443, + currency: 'EU', + size: '300x250' + }; + + describe('Analytics adapter', () => { + let ajaxStub; + let timer; + + before(() => { + ajaxStub = sandbox.stub(ajax, 'ajax'); + timer = sandbox.useFakeTimers(0); + }); + + beforeEach(() => { + sandbox.stub(events, 'getEvents').callsFake(() => { + return [] + }); + }); + + afterEach(() => { + events.getEvents.restore(); + }); + + it('should be configurable', () => { + adaptermanager.registerAnalyticsAdapter({ + code: 'rivr', + adapter: analyticsAdapter + }); + + adaptermanager.enableAnalytics({ + provider: 'rivr', + options: { + pubId: 777, + } + }); + + expect(analyticsAdapter.context).to.have.property('host', 'integrations.rivr.simplaex.net'); + expect(analyticsAdapter.context).to.have.property('pubId', 777); + }); + + it('should handle auction init event', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 1, config: {}, timeout: 3000}); + const auctionId = analyticsAdapter.context.auctionObject.id; + const auctionStart = analyticsAdapter.context.auctionTimeStart; + expect(auctionId).to.be.eql(1); + }); + + it('should handle bid request event', () => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; + const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; + expect(sitePubcid).to.be.eql(777); + expect(sitePubcid).to.be.eql(appPubcid); + }); + + it('should handle bid response event', () => { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); + const ev = analyticsAdapter.context.auctionObject.bidResponses; + expect(ev).to.have.length(1); + expect(ev[0]).to.be.eql({ + timestamp: 1509369418832, + status: 1, + 'total_duration': 443, + bidderId: null, + 'bidder_name': 'adapter', + cur: 'EU', + seatid: [ + { + seat: null, + bid: [ + { + status: 1, + 'clear_price': 0.015, + attr: [], + crid: 999, + cid: null, + id: null, + adid: '208750227436c1', + adomain: [], + iurl: null + } + ] + } + ] + }); + }); + + it('should handle auction end event', () => { + timer.tick(447); + events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + const endTime = analyticsAdapter.context.auctionTimeEnd; + expect(endTime).to.be.eql(447); + }); + + it('should handle winning bid', () => { + events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + const ev = analyticsAdapter.context.auctionObject.imp; + expect(ev.length).to.be.eql(1); + expect(ev[0]).to.be.eql({ + tagid: 'container-1', + displaymanager: null, + displaymanagerver: null, + secure: null, + bidfloor: null, + banner: { + w: 300, + h: 250, + pos: null, + expandable: [], + api: [] + } + }); + }); + + it('sends request after timeout', () => { + let impressions = analyticsAdapter.context.auctionObject.imp; + let responses = analyticsAdapter.context.auctionObject.bidResponses; + + expect(impressions.length).to.be.eql(1); + expect(responses.length).to.be.eql(1); + expect(ajaxStub.calledOnce).to.be.equal(false); + + timer.tick(4500); + + let impressionss = analyticsAdapter.context.auctionObject.imp; + let responsess = analyticsAdapter.context.auctionObject.bidResponses; + + expect(ajaxStub.calledOnce).to.be.equal(true); + expect(impressionss.length).to.be.eql(0); + expect(responsess.length).to.be.eql(0); + }); + }); +}); From f70992fb0971a517938aada4371ea22717c3f887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Fri, 25 May 2018 09:12:42 +0000 Subject: [PATCH 33/62] Merged in RVR-1247-additional-data-to-impression-records (pull request #9) RVR-1247 Additional data to impression records Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index ea13d233d3e..3ef6d325ad3 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -236,9 +236,11 @@ function addClickListener(iframe) { function impHandler(iframe) { let timestamp = new Date().toISOString(); let requestId = generateUUID(); + let adContainerId = iframe.parentElement.parentElement.id; let impression = { timestamp, 'request_id': requestId, + 'tag_id': adContainerId }; if (rivrAnalytics.context.queue) { rivrAnalytics.context.queue.push(impression); From ddbd01803a08b0273582e4ff93fdea035c31efc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Fri, 25 May 2018 09:13:27 +0000 Subject: [PATCH 34/62] Merged in RVR-1249-add-requestedbids-to-auction (pull request #10) RVR-1249 Add requested bids to auction object request. Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 3ef6d325ad3..b9222bdc0f1 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -74,6 +74,8 @@ function trackAuctionInit(args) { function trackBidRequest(args) { setCurrentPublisherId(args); + let bidRequest = args; + rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); }; function trackBidResponse(args) { @@ -310,7 +312,8 @@ function fulfillAuctionObject() { yob: null, gender: null, }, - bidResponses: [] + bidResponses: [], + bidRequests: [] } return newAuction; }; From e7e28acfa3b89a300258c89ed47440ed7a97dce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Wed, 30 May 2018 09:42:04 +0000 Subject: [PATCH 35/62] Merged in RVR-1261-fix-tests (pull request #11) RVR-1261 fix tests * RVR-1261 Secured adapter from no containers configuration. And changed fetching URL. * RVR-1261 Added event check for request and changed some names. * Applied feedback. Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 12 +++-- .../spec/modules/rivrAnalyticsAdapter_spec.js | 44 ++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index b9222bdc0f1..575bf996bd3 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -266,7 +266,7 @@ function fulfillAuctionObject() { app: { id: null, name: null, - domain: rivrAnalytics.context.clientURL, + domain: window.location.href, bundle: null, cat: [], publisher: { @@ -277,7 +277,7 @@ function fulfillAuctionObject() { site: { id: null, name: null, - domain: rivrAnalytics.context.clientURL, + domain: window.location.href, cat: [], publisher: { id: null, @@ -293,7 +293,6 @@ function fulfillAuctionObject() { type: null, metro: null }, - connectiontype: navigator.connection.effectiveType, devicetype: getPlatformType(), osv: null, os: null, @@ -377,7 +376,12 @@ rivrAnalytics.enableAnalytics = (config) => { clientID: config.options.clientID, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; - addHandlers(config.options.bannersIds); + let bannersIds = config.options.bannersIds + if (bannersIds) { + if (bannersIds.length > 0) { + addHandlers(config.options.bannersIds); + } + } logInfo('Rivr Analytics enabled with config', rivrAnalytics.context); rivrAnalytics.originEnableAnalytics(config); }; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index b0d3b2fd72f..7c2bb55e155 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -137,15 +137,35 @@ describe('', () => { events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; + const requestEvent = analyticsAdapter.context.auctionObject.bidRequests; expect(sitePubcid).to.be.eql(777); expect(sitePubcid).to.be.eql(appPubcid); + expect(requestEvent).to.have.length(1); + expect(requestEvent[0]).to.be.eql({ + bidderCode: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }); }); it('should handle bid response event', () => { events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); - const ev = analyticsAdapter.context.auctionObject.bidResponses; - expect(ev).to.have.length(1); - expect(ev[0]).to.be.eql({ + const responseEvent = analyticsAdapter.context.auctionObject.bidResponses; + expect(responseEvent).to.have.length(1); + expect(responseEvent[0]).to.be.eql({ timestamp: 1509369418832, status: 1, 'total_duration': 443, @@ -182,9 +202,9 @@ describe('', () => { it('should handle winning bid', () => { events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); - const ev = analyticsAdapter.context.auctionObject.imp; - expect(ev.length).to.be.eql(1); - expect(ev[0]).to.be.eql({ + const wonEvent = analyticsAdapter.context.auctionObject.imp; + expect(wonEvent.length).to.be.eql(1); + expect(wonEvent[0]).to.be.eql({ tagid: 'container-1', displaymanager: null, displaymanagerver: null, @@ -203,19 +223,23 @@ describe('', () => { it('sends request after timeout', () => { let impressions = analyticsAdapter.context.auctionObject.imp; let responses = analyticsAdapter.context.auctionObject.bidResponses; + let requests = analyticsAdapter.context.auctionObject.bidRequests; expect(impressions.length).to.be.eql(1); expect(responses.length).to.be.eql(1); + expect(requests.length).to.be.eql(1); expect(ajaxStub.calledOnce).to.be.equal(false); timer.tick(4500); - let impressionss = analyticsAdapter.context.auctionObject.imp; - let responsess = analyticsAdapter.context.auctionObject.bidResponses; + let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; + let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; + let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; expect(ajaxStub.calledOnce).to.be.equal(true); - expect(impressionss.length).to.be.eql(0); - expect(responsess.length).to.be.eql(0); + expect(impressionsAfterSend.length).to.be.eql(0); + expect(responsesAfterSend.length).to.be.eql(0); + expect(requestsAfterSend.length).to.be.eql(0); }); }); }); From 3b2d283436c75e77cab801a45556456f2adb10db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Wed, 13 Jun 2018 08:55:11 +0000 Subject: [PATCH 36/62] RVR-1352 analytics adapter bugs Approved-by: Alessandro Di Giovanni --- modules/rivrAnalyticsAdapter.js | 53 +++++++++++- .../spec/modules/rivrAnalyticsAdapter_spec.js | 82 ++++++++++++++++++- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 575bf996bd3..854ee311aab 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -87,10 +87,12 @@ function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; let auctionImpression = createAuctionImpression(args); auctionObject.imp.push(auctionImpression); + assignBidWonStatusToResponse(args); }; function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); + createEmptyBidResponses(); }; function trackBidTimeout(args) { @@ -138,12 +140,12 @@ function createBidResponse(bidResponseEvent) { bidderId: null, bidder_name: bidResponseEvent.bidder, cur: bidResponseEvent.currency, - seatid: [ + seatbid: [ { seat: null, bid: [ { - status: bidResponseEvent.getStatusCode(), + status: 2, clear_price: bidResponseEvent.cpm, attr: [], crid: bidResponseEvent.creativeId, @@ -159,6 +161,18 @@ function createBidResponse(bidResponseEvent) { } }; +function createSingleEmptyBidResponse(bidResponse) { + return { + timestamp: bidResponse.start, + total_duration: 'noResponseDuration', + bidderId: null, + bidder_name: bidResponse.bidder, + cur: null, + response: 'noBid', + seatbid: [] + } +}; + function createAuctionImpression(bidWonEvent) { return { tagid: bidWonEvent.adUnitCode, @@ -364,6 +378,41 @@ export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { } }; +function assignBidWonStatusToResponse(wonBid) { + let wonBidId = wonBid.adId; + rivrAnalytics.context.auctionObject.bidResponses.forEach((response) => { + if (response.seatbid.length > 0) { + let bidObjectResponse = response.seatbid[0].bid[0]; + if (wonBidId === bidObjectResponse.adid) { + bidObjectResponse.status = 1 + } + } + }); +}; + +function createEmptyBidResponses() { + let unRespondedBidRequests = findAllUnrespondedBidRequests(); + unRespondedBidRequests.forEach((bid) => { + let emptyBidResponse = createSingleEmptyBidResponse(bid); + rivrAnalytics.context.auctionObject.bidResponses.push(emptyBidResponse); + }); +}; + +function findAllUnrespondedBidRequests() { + let respondedBidIds = getAllRespondedBidIds(); + let bidRequests = rivrAnalytics.context.auctionObject.bidRequests; + let allNotRespondedBidRequests = bidRequests.reduce((cache, requestBidder) => { + let notRespondedBids = requestBidder.bids.filter((bid) => !respondedBidIds.includes(bid.bidId)); + notRespondedBids.forEach((bid) => bid.start = requestBidder.start); + return cache.concat(notRespondedBids); + }, []); + return allNotRespondedBidRequests; +}; + +function getAllRespondedBidIds() { + return rivrAnalytics.context.auctionObject.bidResponses.map((response) => response.seatbid[0].bid[0].adid); +}; + // save the base class function rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 7c2bb55e155..d4bf1c344e8 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -69,6 +69,44 @@ describe('', () => { start: 1509369418389 }; + const REQUEST2 = { + bidderCode: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: 'request2id', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + const REQUEST3 = { + bidderCode: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: 'request3id', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + const RESPONSE = { bidderCode: 'adapter', width: 300, @@ -172,12 +210,12 @@ describe('', () => { bidderId: null, 'bidder_name': 'adapter', cur: 'EU', - seatid: [ + seatbid: [ { seat: null, bid: [ { - status: 1, + status: 2, 'clear_price': 0.015, attr: [], crid: 999, @@ -200,8 +238,17 @@ describe('', () => { expect(endTime).to.be.eql(447); }); + it('should map unresponded requests to empty responded on auction end', () => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST2); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST3); + events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + const responses = analyticsAdapter.context.auctionObject.bidResponses; + expect(responses.length).to.be.eql(3); + }) + it('should handle winning bid', () => { events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; const wonEvent = analyticsAdapter.context.auctionObject.imp; expect(wonEvent.length).to.be.eql(1); expect(wonEvent[0]).to.be.eql({ @@ -218,6 +265,33 @@ describe('', () => { api: [] } }); + + expect(responseWhichIsWonAlso).to.be.eql({ + timestamp: 1509369418832, + status: 1, + 'total_duration': 443, + bidderId: null, + 'bidder_name': 'adapter', + cur: 'EU', + seatbid: [ + { + seat: null, + bid: [ + { + status: 1, + 'clear_price': 0.015, + attr: [], + crid: 999, + cid: null, + id: null, + adid: '208750227436c1', + adomain: [], + iurl: null + } + ] + } + ] + }); }); it('sends request after timeout', () => { @@ -226,8 +300,8 @@ describe('', () => { let requests = analyticsAdapter.context.auctionObject.bidRequests; expect(impressions.length).to.be.eql(1); - expect(responses.length).to.be.eql(1); - expect(requests.length).to.be.eql(1); + expect(responses.length).to.be.eql(3); + expect(requests.length).to.be.eql(3); expect(ajaxStub.calledOnce).to.be.equal(false); timer.tick(4500); From 5f692b21dbaf36a9f29fc13b070efd60b28b0443 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:27:17 +0200 Subject: [PATCH 37/62] Fixed bug with geolocation notification. --- modules/rivrAnalyticsAdapter.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 854ee311aab..55552420278 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -115,12 +115,15 @@ function setCurrentPublisherId(bidRequested) { }; function fetchLocalization() { - navigator.geolocation.getCurrentPosition((position) => { - let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; - deviceLocation.lat = position.coords.latitude; - deviceLocation.long = position.coords.longitude; + navigator.permissions.query({ name: 'geolocation' }).then(function (permission) { + if (permission.status === 'granted') { + navigator.geolocation.getCurrentPosition((position) => { + let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; + deviceLocation.lat = position.coords.latitude; + deviceLocation.long = position.coords.longitude; + }); + } }); -}; function getPlatformType() { if (navigator.userAgent.match(/mobile/i)) { From 9c98d85618a960da8f00d98a29283b919e9d4bae Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:35:46 +0200 Subject: [PATCH 38/62] fixed missing bracket. --- modules/rivrAnalyticsAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 55552420278..005e4c25a77 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -115,7 +115,7 @@ function setCurrentPublisherId(bidRequested) { }; function fetchLocalization() { - navigator.permissions.query({ name: 'geolocation' }).then(function (permission) { + navigator.permissions.query({ name: 'geolocation' }).then((permission) => { if (permission.status === 'granted') { navigator.geolocation.getCurrentPosition((position) => { let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; @@ -124,6 +124,7 @@ function fetchLocalization() { }); } }); +} function getPlatformType() { if (navigator.userAgent.match(/mobile/i)) { From eb6bba3f495d8749aaf4f9040c01981397061fda Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:46:43 +0200 Subject: [PATCH 39/62] one more fix. --- modules/rivrAnalyticsAdapter.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 005e4c25a77..b3a4c0f66ca 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -115,15 +115,17 @@ function setCurrentPublisherId(bidRequested) { }; function fetchLocalization() { - navigator.permissions.query({ name: 'geolocation' }).then((permission) => { - if (permission.status === 'granted') { - navigator.geolocation.getCurrentPosition((position) => { - let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; - deviceLocation.lat = position.coords.latitude; - deviceLocation.long = position.coords.longitude; - }); - } - }); + if (navigator.permissions) { + navigator.permissions.query({ name: 'geolocation' }).then((permission) => { + if (permission.status === 'granted') { + navigator.geolocation.getCurrentPosition((position) => { + let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; + deviceLocation.lat = position.coords.latitude; + deviceLocation.long = position.coords.longitude; + }); + } + }); + } } function getPlatformType() { From f654565a7b0fe9a4d1026aab0128f50d78336307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Wo=C5=BAniak?= Date: Tue, 26 Jun 2018 11:58:38 +0000 Subject: [PATCH 40/62] RVR-1357 Different optimisation responses & tracking into auction event --- modules/rivrAnalyticsAdapter.js | 61 ++++++++++++++++++- .../spec/modules/rivrAnalyticsAdapter_spec.js | 20 ++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index b3a4c0f66ca..708bc957a9c 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -22,6 +22,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { } if (rivrAnalytics.context.auctionObject) { rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + saveUnoptimisedParams(); fetchLocalization(); } handler = trackAuctionInit; @@ -332,10 +333,62 @@ function fulfillAuctionObject() { gender: null, }, bidResponses: [], - bidRequests: [] + bidRequests: [], + 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', + modelVersion: localStorage.getItem('rivr_model_version') || null, + 'ext.rivr.originalvalues': [] } return newAuction; }; + +function saveUnoptimisedParams() { + let units = rivrAnalytics.context.adUnits; + if (units) { + if (units.length > 0) { + let allUnits = connectAllUnits(units); + allUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + let configForAd = fetchConfigForBidder(bid.bidder); + if (configForAd) { + let unOptimisedParamsField = createUnOptimisedParamsField(bid, configForAd) + rivrAnalytics.context.auctionObject['ext.rivr.originalvalues'].push(unOptimisedParamsField); + } + }) + }); + } + } +}; + +function connectAllUnits(units) { + return units.reduce((acc, units) => { + units.forEach((unit) => acc.push(unit)) + return acc + }, []); +} + +function createUnOptimisedParamsField(unit, config) { + let floorPriceLabel = config['floorPriceLabel']; + let currencyLabel = config['currencyLabel']; + let pmpLabel = config['pmpLabel']; + return { + 'ext.rivr.demand_source_original': unit.bidder, + 'ext.rivr.bidfloor_original': unit.params[floorPriceLabel], + 'ext.rivr.currency_original': unit.params[currencyLabel], + 'ext.rivr.pmp_original': unit.params[pmpLabel], + } +} + +function fetchConfigForBidder(bidderName) { + let config = localStorage.getItem('rivr_config_string'); + if (config) { + let parsed = JSON.parse(config); + return parsed.demand.map((bidderConfig) => { + if (bidderName === bidderConfig.partner) { + return bidderConfig + }; + })[0]; + } +} /** * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. * @param callback @@ -424,10 +477,16 @@ rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page rivrAnalytics.enableAnalytics = (config) => { + let copiedUnits; + if (config.options.adUnits) { + let stringifiedAdUnits = JSON.stringify(config.options.adUnits); + copiedUnits = JSON.parse(stringifiedAdUnits); + } rivrAnalytics.context = { host: config.options.host || DEFAULT_HOST, pubId: config.options.pubId, auctionObject: {}, + adUnits: copiedUnits, clientID: config.options.clientID, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index d4bf1c344e8..00bcfbc5357 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -171,6 +171,26 @@ describe('', () => { expect(auctionId).to.be.eql(1); }); + it('should map proper response params on auction init', () => { + localStorage.setItem('rivr_should_optimise', 'optimise') + localStorage.setItem('rivr_model_version', 'some model version'); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 2, config: {}, timeout: 3000}); + let auctionObject2 = analyticsAdapter.context.auctionObject; + + expect(auctionObject2['ext.rivr.optimiser']).to.be.eql('optimise'); + expect(auctionObject2['modelVersion']).to.be.eql('some model version'); + + localStorage.removeItem('rivr_should_optimise'); + localStorage.removeItem('rivr_model_version'); + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 3, config: {}, timeout: 3000}); + + let auctionObject3 = analyticsAdapter.context.auctionObject; + + expect(auctionObject3['ext.rivr.optimiser']).to.be.eql('unoptimised'); + expect(auctionObject3['modelVersion']).to.be.eql(null); + }) + it('should handle bid request event', () => { events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; From 2a3923a2bd79fb2633a5d90dbf383c28afdac93e Mon Sep 17 00:00:00 2001 From: adg Date: Thu, 30 Aug 2018 17:50:34 +0200 Subject: [PATCH 41/62] RVR-1852 - Add content type and hardcoded auth headers (cherry picked from commit 4def881) --- modules/rivrAnalyticsAdapter.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 708bc957a9c..91b01d2362f 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -54,8 +54,17 @@ function sendAuction() { let req = Object.assign({}, {Auction: auctionObject}); rivrAnalytics.context.auctionObject = fulfillAuctionObject(); logInfo('sending request to analytics => ', req); - ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => { - }, JSON.stringify(req)); + ajax( + `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, + () => {}, + JSON.stringify(req), + { + customHeaders: { + 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=', + 'Content-type': 'application/json' + } + } + ); }; function sendImpressions() { @@ -63,8 +72,17 @@ function sendImpressions() { if (impressions.length !== 0) { let impressionsReq = Object.assign({}, {impressions}); logInfo('sending impressions request to analytics => ', impressionsReq); - ajax(`http://${rivrAnalytics.context.host}/impressions`, () => { - }, JSON.stringify(impressionsReq)); + ajax( + `http://${rivrAnalytics.context.host}/impressions`, + () => {}, + JSON.stringify(impressionsReq), + { + customHeaders: { + 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=', + 'Content-type': 'application/json' + } + } + ); } }; From ebf8f54cdb2ae137f23232f644f238327618b908 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 31 Aug 2018 14:09:20 +0200 Subject: [PATCH 42/62] RVR-1852 - Change tracker host --- modules/rivrAnalyticsAdapter.js | 2 +- test/spec/modules/rivrAnalyticsAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 91b01d2362f..1d13ffe944d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -5,7 +5,7 @@ import adaptermanager from 'src/adaptermanager'; import { logInfo, generateUUID } from 'src/utils'; const analyticsType = 'endpoint'; -const DEFAULT_HOST = 'integrations.rivr.simplaex.net'; +const DEFAULT_HOST = 'tracker.rivr.simplaex.com'; const DEFAULT_QUEUE_TIMEOUT = 4000; let rivrAnalytics = Object.assign(adapter({analyticsType}), { diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 00bcfbc5357..c52711f6ee9 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -160,7 +160,7 @@ describe('', () => { } }); - expect(analyticsAdapter.context).to.have.property('host', 'integrations.rivr.simplaex.net'); + expect(analyticsAdapter.context).to.have.property('host', 'tracker.rivr.simplaex.com'); expect(analyticsAdapter.context).to.have.property('pubId', 777); }); From 07bc09f25f07173865e3b11b4dc8af8db89bde63 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 31 Aug 2018 14:25:06 +0200 Subject: [PATCH 43/62] RVR-1852 - Override content type instead of adding header --- modules/rivrAnalyticsAdapter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 1d13ffe944d..f3afde6ba95 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -59,9 +59,9 @@ function sendAuction() { () => {}, JSON.stringify(req), { + contentType: 'application/json', customHeaders: { - 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=', - 'Content-type': 'application/json' + 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=' } } ); @@ -77,9 +77,9 @@ function sendImpressions() { () => {}, JSON.stringify(impressionsReq), { + contentType: 'application/json', customHeaders: { - 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=', - 'Content-type': 'application/json' + 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=' } } ); From 53d2493f51d280c3deb712f830a4311727fc7b4f Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Thu, 13 Sep 2018 15:14:40 +0000 Subject: [PATCH 44/62] RVR-1914 Consistent data types in events Also removes undefined and null properties in audience events --- modules/rivrAnalyticsAdapter.js | 24 ++++++++++++++----- .../spec/modules/rivrAnalyticsAdapter_spec.js | 21 +++++++++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index f3afde6ba95..ca64d4710bf 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -49,7 +49,9 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { } }); -function sendAuction() { +export function sendAuction() { + console.log('Function called: ============= sendAuction'); + removeEmptyProperties(rivrAnalytics.context.auctionObject) let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); rivrAnalytics.context.auctionObject = fulfillAuctionObject(); @@ -68,6 +70,7 @@ function sendAuction() { }; function sendImpressions() { + console.log('Function called: ============= sendImpressions'); let impressions = rivrAnalytics.context.queue.popAll(); if (impressions.length !== 0) { let impressionsReq = Object.assign({}, {impressions}); @@ -111,7 +114,7 @@ function trackBidWon(args) { function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); - createEmptyBidResponses(); + fillBidResponsesOfUnrespondedBidRequests(); }; function trackBidTimeout(args) { @@ -189,7 +192,7 @@ function createBidResponse(bidResponseEvent) { function createSingleEmptyBidResponse(bidResponse) { return { timestamp: bidResponse.start, - total_duration: 'noResponseDuration', + total_duration: null, bidderId: null, bidder_name: bidResponse.bidder, cur: null, @@ -443,10 +446,12 @@ export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { this.init = reset; function reset() { + console.log('Function called: ============= reset'); if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { + console.log('Function called: ============= reset -> timeout expired'); sendAuction(); if (queue.length) { sendImpressions(); @@ -467,15 +472,15 @@ function assignBidWonStatusToResponse(wonBid) { }); }; -function createEmptyBidResponses() { - let unRespondedBidRequests = findAllUnrespondedBidRequests(); +function fillBidResponsesOfUnrespondedBidRequests() { + let unRespondedBidRequests = getAllUnrespondedBidRequests(); unRespondedBidRequests.forEach((bid) => { let emptyBidResponse = createSingleEmptyBidResponse(bid); rivrAnalytics.context.auctionObject.bidResponses.push(emptyBidResponse); }); }; -function findAllUnrespondedBidRequests() { +function getAllUnrespondedBidRequests() { let respondedBidIds = getAllRespondedBidIds(); let bidRequests = rivrAnalytics.context.auctionObject.bidRequests; let allNotRespondedBidRequests = bidRequests.reduce((cache, requestBidder) => { @@ -490,6 +495,13 @@ function getAllRespondedBidIds() { return rivrAnalytics.context.auctionObject.bidResponses.map((response) => response.seatbid[0].bid[0].adid); }; +function removeEmptyProperties(obj) { + Object.keys(obj).forEach(function(key) { + if (obj[key] && typeof obj[key] === 'object') removeEmptyProperties(obj[key]) + else if (obj[key] == null) delete obj[key] + }); +}; + // save the base class function rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index c52711f6ee9..0a462563d02 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import analyticsAdapter, {ExpiringQueue} from 'modules/rivrAnalyticsAdapter'; +import analyticsAdapter, {ExpiringQueue, sendAuction} from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; import * as ajax from 'src/ajax'; @@ -264,6 +264,7 @@ describe('', () => { events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); const responses = analyticsAdapter.context.auctionObject.bidResponses; expect(responses.length).to.be.eql(3); + expect(responses[2].total_duration).to.be.eql(null); }) it('should handle winning bid', () => { @@ -335,5 +336,23 @@ describe('', () => { expect(responsesAfterSend.length).to.be.eql(0); expect(requestsAfterSend.length).to.be.eql(0); }); + + describe('sendAuction', () => { + it('clears empty payload properties', () => { + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + sendAuction(); + + // sendAuction is called automatically. This is the reason why we are testing the second call here. + // Understand how to avoid it and isolate the test. + expect(ajaxStub.getCall(1).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/(\w+)\/auctions/); + + const payload = JSON.parse(ajaxStub.getCall(1).args[2]); + + expect(payload.Auction.notNullProperty).to.be.equal('aValue'); + expect(payload.nullProperty).to.be.equal(undefined); + }); + }); }); }); From 849897f9ab5aba82d371efc80fc3af106d394e64 Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Mon, 17 Sep 2018 14:08:56 +0000 Subject: [PATCH 45/62] Merged in RVR-1883-Add-Basic-Access-Authentication (pull request #17) RVR-1883 Add Basic Access Authentication * RVR-1914 - Rename functions * RVR-1914 - Set default total_duration to null in bid response * RVR-1883 - Use RIVR_CLIENT_AUTH_TOKEN global variable for Auth token * RVR-1883 - Restore stub after every test not just at the end * RVR-1883 - Remove commented code --- modules/rivrAnalyticsAdapter.js | 60 ++++++++------- .../spec/modules/rivrAnalyticsAdapter_spec.js | 77 ++++++++++++------- 2 files changed, 83 insertions(+), 54 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index ca64d4710bf..38e857f472a 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -51,44 +51,49 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { export function sendAuction() { console.log('Function called: ============= sendAuction'); - removeEmptyProperties(rivrAnalytics.context.auctionObject) - let auctionObject = rivrAnalytics.context.auctionObject; - let req = Object.assign({}, {Auction: auctionObject}); - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); - logInfo('sending request to analytics => ', req); - ajax( - `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, - () => {}, - JSON.stringify(req), - { - contentType: 'application/json', - customHeaders: { - 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=' - } - } - ); -}; - -function sendImpressions() { - console.log('Function called: ============= sendImpressions'); - let impressions = rivrAnalytics.context.queue.popAll(); - if (impressions.length !== 0) { - let impressionsReq = Object.assign({}, {impressions}); - logInfo('sending impressions request to analytics => ', impressionsReq); + console.log('Function called: ============= sendAuction rivrAnalytics.context.authToken', rivrAnalytics.context.authToken); + if (rivrAnalytics.context.authToken) { + removeEmptyProperties(rivrAnalytics.context.auctionObject) + let auctionObject = rivrAnalytics.context.auctionObject; + let req = Object.assign({}, {Auction: auctionObject}); + rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + logInfo('sending request to analytics => ', req); ajax( - `http://${rivrAnalytics.context.host}/impressions`, + `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => {}, - JSON.stringify(impressionsReq), + JSON.stringify(req), { contentType: 'application/json', customHeaders: { - 'Authorization': 'Basic b3V0ZXJwYXNzaXZlOkQ3OVZ5YXI1eVZXUEVBaHI=' + 'Authorization': 'Basic ' + rivrAnalytics.context.authToken } } ); } }; +function sendImpressions() { + console.log('Function called: ============= sendImpressions'); + if (rivrAnalytics.context.authToken) { + let impressions = rivrAnalytics.context.queue.popAll(); + if (impressions.length !== 0) { + let impressionsReq = Object.assign({}, {impressions}); + logInfo('sending impressions request to analytics => ', impressionsReq); + ajax( + `http://${rivrAnalytics.context.host}/impressions`, + () => {}, + JSON.stringify(impressionsReq), + { + contentType: 'application/json', + customHeaders: { + 'Authorization': 'Basic ' + rivrAnalytics.context.authToken + } + } + ); + } + } +}; + function trackAuctionInit(args) { rivrAnalytics.context.auctionTimeStart = Date.now(); rivrAnalytics.context.auctionObject.id = args.auctionId; @@ -518,6 +523,7 @@ rivrAnalytics.enableAnalytics = (config) => { auctionObject: {}, adUnits: copiedUnits, clientID: config.options.clientID, + authToken: config.options.authToken, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; let bannersIds = config.options.bannersIds diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 0a462563d02..98505c51228 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -133,11 +133,11 @@ describe('', () => { let timer; before(() => { - ajaxStub = sandbox.stub(ajax, 'ajax'); timer = sandbox.useFakeTimers(0); }); beforeEach(() => { + ajaxStub = sandbox.stub(ajax, 'ajax'); sandbox.stub(events, 'getEvents').callsFake(() => { return [] }); @@ -145,6 +145,7 @@ describe('', () => { afterEach(() => { events.getEvents.restore(); + ajaxStub.restore(); }); it('should be configurable', () => { @@ -315,43 +316,65 @@ describe('', () => { }); }); - it('sends request after timeout', () => { - let impressions = analyticsAdapter.context.auctionObject.imp; - let responses = analyticsAdapter.context.auctionObject.bidResponses; - let requests = analyticsAdapter.context.auctionObject.bidRequests; + describe('when authToken is defined', () => { + it('sends request after timeout', () => { + analyticsAdapter.context.authToken = 'anAuthToken'; + let impressions = analyticsAdapter.context.auctionObject.imp; + let responses = analyticsAdapter.context.auctionObject.bidResponses; + let requests = analyticsAdapter.context.auctionObject.bidRequests; - expect(impressions.length).to.be.eql(1); - expect(responses.length).to.be.eql(3); - expect(requests.length).to.be.eql(3); - expect(ajaxStub.calledOnce).to.be.equal(false); + expect(impressions.length).to.be.eql(1); + expect(responses.length).to.be.eql(3); + expect(requests.length).to.be.eql(3); + expect(ajaxStub.notCalled).to.be.equal(true); - timer.tick(4500); + timer.tick(4500); - let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; - let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; - let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; + let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; + let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; + let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; - expect(ajaxStub.calledOnce).to.be.equal(true); - expect(impressionsAfterSend.length).to.be.eql(0); - expect(responsesAfterSend.length).to.be.eql(0); - expect(requestsAfterSend.length).to.be.eql(0); + expect(ajaxStub.calledOnce).to.be.equal(true); + expect(impressionsAfterSend.length).to.be.eql(0); + expect(responsesAfterSend.length).to.be.eql(0); + expect(requestsAfterSend.length).to.be.eql(0); + + analyticsAdapter.context.authToken = undefined; + }); }); describe('sendAuction', () => { - it('clears empty payload properties', () => { - analyticsAdapter.context.auctionObject.nullProperty = null; - analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + describe('when authToken is defined', () => { + it('fires call clearing empty payload properties', () => { + analyticsAdapter.context.authToken = 'anAuthToken'; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + sendAuction(); + + expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/(\w+)\/auctions/); + + const payload = JSON.parse(ajaxStub.getCall(0).args[2]); + + expect(payload.Auction.notNullProperty).to.be.equal('aValue'); + expect(payload.nullProperty).to.be.equal(undefined); + + analyticsAdapter.context.authToken = undefined; + }); + }); - sendAuction(); + describe('when authToken is not defined', () => { + it('does not fire call', () => { + analyticsAdapter.context.authToken = undefined; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; - // sendAuction is called automatically. This is the reason why we are testing the second call here. - // Understand how to avoid it and isolate the test. - expect(ajaxStub.getCall(1).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/(\w+)\/auctions/); + expect(ajaxStub.callCount).to.be.equal(0); - const payload = JSON.parse(ajaxStub.getCall(1).args[2]); + sendAuction(); - expect(payload.Auction.notNullProperty).to.be.equal('aValue'); - expect(payload.nullProperty).to.be.equal(undefined); + expect(ajaxStub.callCount).to.be.equal(0); + }); }); }); }); From 77e39d5ef425debee8e65b23c23b2354da1d4125 Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Mon, 1 Oct 2018 15:14:50 +0000 Subject: [PATCH 46/62] Increase code coverage --- modules/rivrAnalyticsAdapter.js | 51 +- .../spec/modules/rivrAnalyticsAdapter_spec.js | 872 ++++++++++++------ 2 files changed, 621 insertions(+), 302 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 38e857f472a..846c8bd3250 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -17,6 +17,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { let handler = null; switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: + logInfo(`CONSTANTS.EVENTS.AUCTION_INIT rivrAnalytics.context.auctionObject`, rivrAnalytics.context.auctionObject); if (rivrAnalytics.context.queue) { rivrAnalytics.context.queue.init(); } @@ -50,10 +51,8 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { }); export function sendAuction() { - console.log('Function called: ============= sendAuction'); - console.log('Function called: ============= sendAuction rivrAnalytics.context.authToken', rivrAnalytics.context.authToken); if (rivrAnalytics.context.authToken) { - removeEmptyProperties(rivrAnalytics.context.auctionObject) + removeEmptyProperties(rivrAnalytics.context.auctionObject); let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); rivrAnalytics.context.auctionObject = fulfillAuctionObject(); @@ -62,6 +61,7 @@ export function sendAuction() { `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => {}, JSON.stringify(req), + // TODO extract this object to variable { contentType: 'application/json', customHeaders: { @@ -72,8 +72,7 @@ export function sendAuction() { } }; -function sendImpressions() { - console.log('Function called: ============= sendImpressions'); +export function sendImpressions() { if (rivrAnalytics.context.authToken) { let impressions = rivrAnalytics.context.queue.popAll(); if (impressions.length !== 0) { @@ -141,20 +140,23 @@ function setCurrentPublisherId(bidRequested) { } }; -function fetchLocalization() { +export function fetchLocalization() { if (navigator.permissions) { navigator.permissions.query({ name: 'geolocation' }).then((permission) => { if (permission.status === 'granted') { navigator.geolocation.getCurrentPosition((position) => { - let deviceLocation = rivrAnalytics.context.auctionObject.device.geo; - deviceLocation.lat = position.coords.latitude; - deviceLocation.long = position.coords.longitude; + setAuctionAbjectPosition(position); }); } }); } } +export function setAuctionAbjectPosition(position) { + rivrAnalytics.context.auctionObject.device.geo.lat = position.coords.latitude; + rivrAnalytics.context.auctionObject.device.geo.long = position.coords.longitude; +} + function getPlatformType() { if (navigator.userAgent.match(/mobile/i)) { return 'Mobile'; @@ -223,7 +225,7 @@ function createAuctionImpression(bidWonEvent) { } }; -function reportClickEvent(event) { +export function reportClickEvent(event) { let link = event.currentTarget.getElementsByTagName('a')[0]; let clickUrl; if (link) { @@ -237,6 +239,8 @@ function reportClickEvent(event) { 'click_url': clickUrl }; logInfo('Sending click events with parameters: ', req); + + // TODO add Authentication header ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/clicks`, () => { }, JSON.stringify(req)); }; @@ -249,7 +253,7 @@ function addDisplayedImpHandler(bannerId) { pinHandlerToHTMLElement(bannerId, dataLoaderForHandler, impHandler); }; -function pinHandlerToHTMLElement(elementId, dataLoaderForHandler, specializedHandler) { +export function pinHandlerToHTMLElement(elementId, dataLoaderForHandler, specializedHandler) { function waitForElement() { let element = document.getElementById(elementId); if (!element) { @@ -261,7 +265,7 @@ function pinHandlerToHTMLElement(elementId, dataLoaderForHandler, specializedHan waitForElement(); } -function dataLoaderForHandler(element, specializedHandler) { +export function dataLoaderForHandler(element, specializedHandler) { function waitForElement() { let iframe = element.getElementsByTagName('iframe')[0]; if (!iframe) { @@ -363,7 +367,7 @@ function fulfillAuctionObject() { 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', modelVersion: localStorage.getItem('rivr_model_version') || null, 'ext.rivr.originalvalues': [] - } + }; return newAuction; }; @@ -374,9 +378,9 @@ function saveUnoptimisedParams() { let allUnits = connectAllUnits(units); allUnits.forEach((adUnit) => { adUnit.bids.forEach((bid) => { - let configForAd = fetchConfigForBidder(bid.bidder); - if (configForAd) { - let unOptimisedParamsField = createUnOptimisedParamsField(bid, configForAd) + let configForBidder = fetchConfigForBidder(bid.bidder); + if (configForBidder) { + let unOptimisedParamsField = createUnOptimisedParamsField(bid, configForBidder); rivrAnalytics.context.auctionObject['ext.rivr.originalvalues'].push(unOptimisedParamsField); } }) @@ -392,15 +396,15 @@ function connectAllUnits(units) { }, []); } -function createUnOptimisedParamsField(unit, config) { +export function createUnOptimisedParamsField(bid, config) { let floorPriceLabel = config['floorPriceLabel']; let currencyLabel = config['currencyLabel']; let pmpLabel = config['pmpLabel']; return { - 'ext.rivr.demand_source_original': unit.bidder, - 'ext.rivr.bidfloor_original': unit.params[floorPriceLabel], - 'ext.rivr.currency_original': unit.params[currencyLabel], - 'ext.rivr.pmp_original': unit.params[pmpLabel], + 'ext.rivr.demand_source_original': bid.bidder, + 'ext.rivr.bidfloor_original': bid.params[floorPriceLabel], + 'ext.rivr.currency_original': bid.params[currencyLabel], + 'ext.rivr.pmp_original': bid.params[pmpLabel], } } @@ -451,12 +455,10 @@ export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { this.init = reset; function reset() { - console.log('Function called: ============= reset'); if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { - console.log('Function called: ============= reset -> timeout expired'); sendAuction(); if (queue.length) { sendImpressions(); @@ -526,7 +528,8 @@ rivrAnalytics.enableAnalytics = (config) => { authToken: config.options.authToken, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) }; - let bannersIds = config.options.bannersIds + + let bannersIds = config.options.bannersIds; if (bannersIds) { if (bannersIds.length > 0) { addHandlers(config.options.bannersIds); diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 98505c51228..dca63bf935e 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -1,4 +1,15 @@ -import analyticsAdapter, {ExpiringQueue, sendAuction} from 'modules/rivrAnalyticsAdapter'; +import * as utils from 'src/utils'; +import analyticsAdapter from 'modules/rivrAnalyticsAdapter'; +import { + ExpiringQueue, + sendAuction, + sendImpressions, + reportClickEvent, + createUnOptimisedParamsField, + dataLoaderForHandler, + pinHandlerToHTMLElement, + setAuctionAbjectPosition, +} from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; import * as ajax from 'src/ajax'; @@ -6,50 +17,557 @@ import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); -describe('', () => { +describe('RIVR Analytics adapter', () => { + const EXPIRING_QUEUE_TIMEOUT = 4000; + const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; + const PUBLISHER_ID_MOCK = 777; + const EMITTED_AUCTION_ID = 1; + const TRACKER_BASE_URL_MOCK = 'tracker.rivr.simplaex.com'; let sandbox; + let ajaxStub; + let timer; before(() => { sandbox = sinon.sandbox.create(); }); + beforeEach(() => { + timer = sandbox.useFakeTimers(0); + ajaxStub = sandbox.stub(ajax, 'ajax'); + sinon.stub(events, 'getEvents').returns([]); + + adaptermanager.registerAnalyticsAdapter({ + code: 'rivr', + adapter: analyticsAdapter + }); + adaptermanager.enableAnalytics({ + provider: 'rivr', + options: { + pubId: PUBLISHER_ID_MOCK, + adUnits: [utils.deepClone(AD_UNITS_MOCK)] + } + }); + }); + + afterEach(() => { + analyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + ajaxStub.restore(); + timer.restore(); + }); + after(() => { sandbox.restore(); - analyticsAdapter.disableAnalytics(); }); - describe('ExpiringQueue', () => { - let timer; - before(() => { - timer = sandbox.useFakeTimers(0); + it('ExpiringQueue should call sendImpression callback after expiring queue timeout is elapsed', (done) => { + const sendImpressionMock = () => { + let elements = queue.popAll(); + expect(elements).to.be.eql([1, 2, 3, 4]); + elements = queue.popAll(); + expect(elements).to.have.lengthOf(0); + expect(Date.now()).to.be.equal(200); + done(); + }; + const sendAuctionMock = () => {}; + + let queue = new ExpiringQueue( + sendImpressionMock, + sendAuctionMock, + EXPIRING_QUEUE_TIMEOUT_MOCK); + + queue.push(1); + + setTimeout(() => { + queue.push([2, 3]); + timer.tick(50); + }, 50); + setTimeout(() => { + queue.push([4]); + timer.tick(100); + }, 100); + timer.tick(50); + }); + + it('enableAnalytics - should configure host and pubId in adapter context', () => { + // adaptermanager.enableAnalytics() is called in beforeEach. If only called here it doesn't seem to work. + + expect(analyticsAdapter.context).to.have.property('host', TRACKER_BASE_URL_MOCK); + expect(analyticsAdapter.context).to.have.property('pubId', PUBLISHER_ID_MOCK); + }); + + it('Firing AUCTION_INIT should set auction id of context when AUCTION_INIT event is fired', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + const auctionId = analyticsAdapter.context.auctionObject.id; + expect(auctionId).to.be.eql(EMITTED_AUCTION_ID); + }); + + it('Firing AUCTION_INIT when rivr_should_optimise and rivr_model_version are in local storage, sets ext.rivr.optimiser and modelVersion of in auction context', () => { + const RIVR_SHOULD_OPTIMISE_VALUE_MOCK = 'optimise'; + const RIVR_MODEL_VERSION_VALUE_MOCK = 'some model version'; + + localStorage.setItem('rivr_should_optimise', RIVR_SHOULD_OPTIMISE_VALUE_MOCK); + localStorage.setItem('rivr_model_version', RIVR_MODEL_VERSION_VALUE_MOCK); + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 2, config: {}, timeout: 3000}); + + let auctionObject2 = analyticsAdapter.context.auctionObject; + + expect(auctionObject2['ext.rivr.optimiser']).to.be.eql(RIVR_SHOULD_OPTIMISE_VALUE_MOCK); + expect(auctionObject2['modelVersion']).to.be.eql(RIVR_MODEL_VERSION_VALUE_MOCK); + + localStorage.removeItem('rivr_should_optimise'); + localStorage.removeItem('rivr_model_version'); + }); + + it('Firing AUCTION_INIT , when auction object is already there and rivr_config_string is not in local storage, it does not save unoptimized params in rivr original values', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 3, config: {}, timeout: 3000}); + + expect(analyticsAdapter.context.auctionObject['ext.rivr.originalvalues']).to.be.eql([]); + }); + + it('Firing AUCTION_INIT when rivr_should_optimise and rivr_model_version are NOT in local storage, does not set ext.rivr.optimiser and modelVersion of in auction context', () => { + localStorage.removeItem('rivr_should_optimise'); + localStorage.removeItem('rivr_model_version'); + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 3, config: {}, timeout: 3000}); + + let auctionObject3 = analyticsAdapter.context.auctionObject; + + expect(auctionObject3['ext.rivr.optimiser']).to.be.eql('unoptimised'); + expect(auctionObject3['modelVersion']).to.be.eql(null); + }); + + it('Firing BID_REQUESTED it sets app and site publisher id in auction object', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + + const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; + const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; + expect(sitePubcid).to.be.eql(PUBLISHER_ID_MOCK); + expect(appPubcid).to.be.eql(PUBLISHER_ID_MOCK); + }); + + it('Firing BID_REQUESTED it adds bid request in bid requests array', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + + const requestEvent = analyticsAdapter.context.auctionObject.bidRequests; + expect(requestEvent).to.have.length(1); + expect(requestEvent[0]).to.be.eql({ + bidderCode: 'adapter', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'adapter', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 }); - after(() => { - timer.restore(); + }); + + it('Firing BID_RESPONSE it inserts bid response object in auctionObject', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); + const bidResponses = analyticsAdapter.context.auctionObject.bidResponses; + + expect(bidResponses).to.have.length(1); + expect(bidResponses[0]).to.be.eql({ + timestamp: 1509369418832, + status: 1, + 'total_duration': 443, + bidderId: null, + 'bidder_name': 'adapter', + cur: 'EU', + seatbid: [ + { + seat: null, + bid: [ + { + status: 2, + 'clear_price': 0.015, + attr: [], + crid: 999, + cid: null, + id: null, + adid: '208750227436c1', + adomain: [], + iurl: null + } + ] + } + ] }); + }); - it('should notify after timeout period', (done) => { - let queue = new ExpiringQueue(() => { - let elements = queue.popAll(); - expect(elements).to.be.eql([1, 2, 3, 4]); - elements = queue.popAll(); - expect(elements).to.have.lengthOf(0); - expect(Date.now()).to.be.equal(200); - done(); - }, () => {}, 100); - - queue.push(1); - setTimeout(() => { - queue.push([2, 3]); - timer.tick(50); - }, 50); - setTimeout(() => { - queue.push([4]); - timer.tick(100); - }, 100); - timer.tick(50); + it('Firing AUCTION_END it sets auction time end to current time', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + const MILLIS_FROM_EPOCH_TO_NOW_MOCK = 477; + timer.tick(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + + events.emit(CONSTANTS.EVENTS.AUCTION_END, BID_RESPONSE_MOCK); + + const endTime = analyticsAdapter.context.auctionTimeEnd; + expect(endTime).to.be.eql(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + }); + + it('Firing AUCTION_END when there are unresponded bid requests should insert then to bidResponses in auctionObject with null duration', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST2); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST3); + events.emit(CONSTANTS.EVENTS.AUCTION_END, BID_RESPONSE_MOCK); + + const responses = analyticsAdapter.context.auctionObject.bidResponses; + expect(responses.length).to.be.eql(2); + expect(responses[0].total_duration).to.be.eql(null); + expect(responses[1].total_duration).to.be.eql(null); + }); + + it('Firing BID_WON when it happens after BID_RESPONSE should add won event as auction impression to imp array', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + + const wonEvent = analyticsAdapter.context.auctionObject.imp; + + expect(wonEvent.length).to.be.eql(1); + expect(wonEvent[0]).to.be.eql({ + tagid: 'container-1', + displaymanager: null, + displaymanagerver: null, + secure: null, + bidfloor: null, + banner: { + w: 300, + h: 250, + pos: null, + expandable: [], + api: [] + } }); }); + it('Firing BID_WON when it happens after BID_RESPONSE should change the status of winning bidResponse to 1', () => { + const BID_STATUS_WON = 1; + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + + const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; + + expect(responseWhichIsWonAlso.seatbid[0].bid[0].status).to.be.eql(BID_STATUS_WON); + }); + + it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it sends the auction', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + analyticsAdapter.context.authToken = 'anAuthToken'; + + expect(ajaxStub.notCalled).to.be.equal(true); + + timer.tick(EXPIRING_QUEUE_TIMEOUT + 500); + + expect(ajaxStub.calledOnce).to.be.equal(true); + }); + + it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it clears imp, bidResponses and bidRequests', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + + analyticsAdapter.context.authToken = 'anAuthToken'; + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + + let impressions = analyticsAdapter.context.auctionObject.imp; + let responses = analyticsAdapter.context.auctionObject.bidResponses; + let requests = analyticsAdapter.context.auctionObject.bidRequests; + + expect(impressions.length).to.be.eql(1); + expect(responses.length).to.be.eql(1); + expect(requests.length).to.be.eql(1); + + timer.tick(EXPIRING_QUEUE_TIMEOUT + 500); + + let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; + let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; + let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; + + expect(impressionsAfterSend.length).to.be.eql(0); + expect(responsesAfterSend.length).to.be.eql(0); + expect(requestsAfterSend.length).to.be.eql(0); + }); + + it('sendAuction(), when authToken is defined, it fires call clearing empty payload properties', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + analyticsAdapter.context.authToken = 'anAuthToken'; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + sendAuction(); + + expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/(\w+)\/auctions/); + + const payload = JSON.parse(ajaxStub.getCall(0).args[2]); + + expect(payload.Auction.notNullProperty).to.be.equal('aValue'); + expect(payload.nullProperty).to.be.equal(undefined); + + analyticsAdapter.context.authToken = undefined; + }); + + it('sendAuction(), when authToken is not defined, it does not fire call', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + analyticsAdapter.context.authToken = undefined; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + expect(ajaxStub.callCount).to.be.equal(0); + + sendAuction(); + + expect(ajaxStub.callCount).to.be.equal(0); + }); + + it('sendImpressions(), when authToken is not defined, it does not fire call', () => { + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + analyticsAdapter.context.authToken = undefined; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + expect(ajaxStub.callCount).to.be.equal(0); + + sendImpressions(); + + expect(ajaxStub.callCount).to.be.equal(0); + }); + + it('sendImpressions(), when authToken is defined and there are impressions, it sends impressions to the tracker', () => { + const aMockString = 'anImpressionPropertyValue'; + const IMPRESSION_MOCK = { anImpressionProperty: aMockString }; + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + analyticsAdapter.context.authToken = 'anAuthToken'; + analyticsAdapter.context.queue = new ExpiringQueue( + () => {}, + () => {}, + EXPIRING_QUEUE_TIMEOUT_MOCK + ); + + analyticsAdapter.context.queue.push(IMPRESSION_MOCK); + + expect(ajaxStub.callCount).to.be.equal(0); + + sendImpressions(); + + const payload = JSON.parse(ajaxStub.getCall(0).args[2]); + + expect(ajaxStub.callCount).to.be.equal(1); + expect(payload.impressions.length).to.be.equal(1); + expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/impressions/); + expect(payload.impressions[0].anImpressionProperty).to.be.equal(aMockString); + }); + + it('reportClickEvent(), when authToken is not defined, it calls endpoint', () => { + const CLIENT_ID_MOCK = 'aClientId'; + const CLICK_URL_MOCK = 'clickURLMock'; + const EVENT_MOCK = { + currentTarget: { + getElementsByTagName: () => { + return [ + { + getAttribute: (attributeName) => { + return CLICK_URL_MOCK; + } + } + ] + } + } + }; + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + analyticsAdapter.context.authToken = undefined; + analyticsAdapter.context.clientID = CLIENT_ID_MOCK; + analyticsAdapter.context.auctionObject.nullProperty = null; + analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; + + expect(ajaxStub.callCount).to.be.equal(0); + + reportClickEvent(EVENT_MOCK); + + const payload = JSON.parse(ajaxStub.getCall(0).args[2]); + + expect(ajaxStub.callCount).to.be.equal(1); + expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/aClientId\/clicks/); + expect(payload.timestamp).to.be.equal('1970-01-01T00:00:00.000Z'); + expect(payload.request_id).to.be.a('string'); + expect(payload.click_url).to.be.equal(CLICK_URL_MOCK); + }); + + it('createUnOptimisedParamsField(), creates object with unoptimized properties', () => { + const CONFIG_FOR_BIDDER_MOCK = { + floorPriceLabel: 'floorPriceLabelForTestBidder', + currencyLabel: 'currencyLabelForTestBidder', + pmpLabel: 'pmpLabelForTestBidder', + }; + const BID_MOCK = { + bidder: 'aBidder', + params: { + floorPriceLabelForTestBidder: 'theOriginalBidFloor', + currencyLabelForTestBidder: 'theOriginalCurrency', + pmpLabelForTestBidder: 'theOriginalPmp', + }, + }; + + const result = createUnOptimisedParamsField(BID_MOCK, CONFIG_FOR_BIDDER_MOCK); + + expect(result['ext.rivr.demand_source_original']).to.be.equal('aBidder'); + expect(result['ext.rivr.bidfloor_original']).to.be.equal('theOriginalBidFloor'); + expect(result['ext.rivr.currency_original']).to.be.equal('theOriginalCurrency'); + expect(result['ext.rivr.pmp_original']).to.be.equal('theOriginalPmp'); + }); + + it('dataLoaderForHandler(), when iframe and the ad image contained in it are there, it calls the specialized handler', () => { + const MOCK_ELEMENT = { + getElementsByTagName: () => { + return [ + { + contentDocument: { + getElementsByTagName: () => { + return ['displayedImpressionMock'] + } + }, + aDummyProperty: 'aDummyPropertyValue' + } + ] + } + }; + + var specializedHandlerSpy = sinon.spy(); + + expect(specializedHandlerSpy.callCount).to.be.equal(0); + + dataLoaderForHandler(MOCK_ELEMENT, specializedHandlerSpy); + + expect(specializedHandlerSpy.callCount).to.be.equal(1); + expect(specializedHandlerSpy.firstCall.args[0].aDummyProperty).to.be.equal('aDummyPropertyValue'); + expect(specializedHandlerSpy.firstCall.args[0].contentDocument.getElementsByTagName()[0]).to.be.equal('displayedImpressionMock'); + }); + + it('dataLoaderForHandler(), when iframe is not there, it requests animation frame', () => { + const MOCK_ELEMENT = { + getElementsByTagName: () => { + return [ + { + contentDocument: { + getElementsByTagName: () => { + return [] + } + }, + } + ] + } + }; + + const specializedHandlerSpy = sinon.spy(); + const requestAnimationFrameStub = sinon.stub(window, 'requestAnimationFrame'); + expect(requestAnimationFrameStub.callCount).to.be.equal(0); + + dataLoaderForHandler(MOCK_ELEMENT, specializedHandlerSpy); + + expect(requestAnimationFrameStub.callCount).to.be.equal(1); + + requestAnimationFrameStub.restore(); + }); + + it('pinHandlerToHTMLElement(), when element is there, it calls dataLoaderForHandler', () => { + const ELEMENT_MOCK = { + anElementProperty: 'aValue' + } + const dataLoaderForHandlerSpy = sinon.spy(); + sinon.stub(window, 'requestAnimationFrame'); + + sinon.stub(document, 'getElementById').returns(ELEMENT_MOCK); + + expect(dataLoaderForHandlerSpy.callCount).to.be.equal(0); + + pinHandlerToHTMLElement('', dataLoaderForHandlerSpy, () => {}); + + expect(dataLoaderForHandlerSpy.callCount).to.be.equal(1); + expect(dataLoaderForHandlerSpy.firstCall.args[0].anElementProperty).to.be.equal('aValue'); + + window.requestAnimationFrame.restore(); + document.getElementById.restore(); + }); + + it('pinHandlerToHTMLElement(), when element is not there, it requests animation frame', () => { + const dataLoaderForHandlerSpy = sinon.spy(); + const requestAnimationFrameStub = sinon.stub(window, 'requestAnimationFrame'); + + sinon.stub(document, 'getElementById').returns(undefined); + + expect(requestAnimationFrameStub.callCount).to.be.equal(0); + + pinHandlerToHTMLElement('', dataLoaderForHandlerSpy, () => {}); + + expect(dataLoaderForHandlerSpy.callCount).to.be.equal(0); + expect(requestAnimationFrameStub.callCount).to.be.equal(1); + + requestAnimationFrameStub.restore(); + document.getElementById.restore(); + }); + + it('setAuctionAbjectPosition(), it sets latitude and longitude in auction object', () => { + const POSITION_MOCK = { + coords: { + latitude: 'aLatitude', + longitude: 'aLongitude', + } + } + analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + + setAuctionAbjectPosition(POSITION_MOCK); + + expect(analyticsAdapter.context.auctionObject.device.geo.lat).to.be.equal('aLatitude'); + }); + + const AD_UNITS_MOCK = [ + { + code: 'banner-container1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200], [300, 600]] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '10433394', + reserve: 0.5 + } + }, + { + bidder: 'huddledmasses', + params: { + placement_id: 0 + } + }, + ] + } + ]; + const REQUEST = { bidderCode: 'adapter', auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', @@ -107,7 +625,7 @@ describe('', () => { start: 1509369418389 }; - const RESPONSE = { + const BID_RESPONSE_MOCK = { bidderCode: 'adapter', width: 300, height: 250, @@ -128,254 +646,52 @@ describe('', () => { size: '300x250' }; - describe('Analytics adapter', () => { - let ajaxStub; - let timer; - - before(() => { - timer = sandbox.useFakeTimers(0); - }); - - beforeEach(() => { - ajaxStub = sandbox.stub(ajax, 'ajax'); - sandbox.stub(events, 'getEvents').callsFake(() => { - return [] - }); - }); - - afterEach(() => { - events.getEvents.restore(); - ajaxStub.restore(); - }); - - it('should be configurable', () => { - adaptermanager.registerAnalyticsAdapter({ - code: 'rivr', - adapter: analyticsAdapter - }); - - adaptermanager.enableAnalytics({ - provider: 'rivr', - options: { - pubId: 777, + const CONTEXT_AFTER_AUCTION_INIT = { + host: TRACKER_BASE_URL_MOCK, + pubId: PUBLISHER_ID_MOCK, + queue: { + mockProp: 'mockValue' + }, + auctionObject: { + id: null, + timestamp: null, + at: null, + bcat: [], + imp: [], + app: { + id: null, + name: null, + domain: window.location.href, + bundle: null, + cat: [], + publisher: { + id: null, + name: null } - }); - - expect(analyticsAdapter.context).to.have.property('host', 'tracker.rivr.simplaex.com'); - expect(analyticsAdapter.context).to.have.property('pubId', 777); - }); - - it('should handle auction init event', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 1, config: {}, timeout: 3000}); - const auctionId = analyticsAdapter.context.auctionObject.id; - const auctionStart = analyticsAdapter.context.auctionTimeStart; - expect(auctionId).to.be.eql(1); - }); - - it('should map proper response params on auction init', () => { - localStorage.setItem('rivr_should_optimise', 'optimise') - localStorage.setItem('rivr_model_version', 'some model version'); - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 2, config: {}, timeout: 3000}); - let auctionObject2 = analyticsAdapter.context.auctionObject; - - expect(auctionObject2['ext.rivr.optimiser']).to.be.eql('optimise'); - expect(auctionObject2['modelVersion']).to.be.eql('some model version'); - - localStorage.removeItem('rivr_should_optimise'); - localStorage.removeItem('rivr_model_version'); - - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 3, config: {}, timeout: 3000}); - - let auctionObject3 = analyticsAdapter.context.auctionObject; - - expect(auctionObject3['ext.rivr.optimiser']).to.be.eql('unoptimised'); - expect(auctionObject3['modelVersion']).to.be.eql(null); - }) - - it('should handle bid request event', () => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; - const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; - const requestEvent = analyticsAdapter.context.auctionObject.bidRequests; - expect(sitePubcid).to.be.eql(777); - expect(sitePubcid).to.be.eql(appPubcid); - expect(requestEvent).to.have.length(1); - expect(requestEvent[0]).to.be.eql({ - bidderCode: 'adapter', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: 'adapter', - params: {}, - adUnitCode: 'container-1', - transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [[300, 250]], - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }); - }); - - it('should handle bid response event', () => { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); - const responseEvent = analyticsAdapter.context.auctionObject.bidResponses; - expect(responseEvent).to.have.length(1); - expect(responseEvent[0]).to.be.eql({ - timestamp: 1509369418832, - status: 1, - 'total_duration': 443, - bidderId: null, - 'bidder_name': 'adapter', - cur: 'EU', - seatbid: [ - { - seat: null, - bid: [ - { - status: 2, - 'clear_price': 0.015, - attr: [], - crid: 999, - cid: null, - id: null, - adid: '208750227436c1', - adomain: [], - iurl: null - } - ] - } - ] - }); - }); - - it('should handle auction end event', () => { - timer.tick(447); - events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); - const endTime = analyticsAdapter.context.auctionTimeEnd; - expect(endTime).to.be.eql(447); - }); - - it('should map unresponded requests to empty responded on auction end', () => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST2); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST3); - events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); - const responses = analyticsAdapter.context.auctionObject.bidResponses; - expect(responses.length).to.be.eql(3); - expect(responses[2].total_duration).to.be.eql(null); - }) - - it('should handle winning bid', () => { - events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); - const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; - const wonEvent = analyticsAdapter.context.auctionObject.imp; - expect(wonEvent.length).to.be.eql(1); - expect(wonEvent[0]).to.be.eql({ - tagid: 'container-1', - displaymanager: null, - displaymanagerver: null, - secure: null, - bidfloor: null, - banner: { - w: 300, - h: 250, - pos: null, - expandable: [], - api: [] + }, + site: { + id: null, + name: null, + domain: window.location.href, + cat: [], + publisher: { + id: null, + name: null } - }); - - expect(responseWhichIsWonAlso).to.be.eql({ - timestamp: 1509369418832, - status: 1, - 'total_duration': 443, - bidderId: null, - 'bidder_name': 'adapter', - cur: 'EU', - seatbid: [ - { - seat: null, - bid: [ - { - status: 1, - 'clear_price': 0.015, - attr: [], - crid: 999, - cid: null, - id: null, - adid: '208750227436c1', - adomain: [], - iurl: null - } - ] - } - ] - }); - }); - - describe('when authToken is defined', () => { - it('sends request after timeout', () => { - analyticsAdapter.context.authToken = 'anAuthToken'; - let impressions = analyticsAdapter.context.auctionObject.imp; - let responses = analyticsAdapter.context.auctionObject.bidResponses; - let requests = analyticsAdapter.context.auctionObject.bidRequests; - - expect(impressions.length).to.be.eql(1); - expect(responses.length).to.be.eql(3); - expect(requests.length).to.be.eql(3); - expect(ajaxStub.notCalled).to.be.equal(true); - - timer.tick(4500); - - let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; - let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; - let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; - - expect(ajaxStub.calledOnce).to.be.equal(true); - expect(impressionsAfterSend.length).to.be.eql(0); - expect(responsesAfterSend.length).to.be.eql(0); - expect(requestsAfterSend.length).to.be.eql(0); - - analyticsAdapter.context.authToken = undefined; - }); - }); - - describe('sendAuction', () => { - describe('when authToken is defined', () => { - it('fires call clearing empty payload properties', () => { - analyticsAdapter.context.authToken = 'anAuthToken'; - analyticsAdapter.context.auctionObject.nullProperty = null; - analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; - - sendAuction(); - - expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/(\w+)\/auctions/); - - const payload = JSON.parse(ajaxStub.getCall(0).args[2]); - - expect(payload.Auction.notNullProperty).to.be.equal('aValue'); - expect(payload.nullProperty).to.be.equal(undefined); - - analyticsAdapter.context.authToken = undefined; - }); - }); - - describe('when authToken is not defined', () => { - it('does not fire call', () => { - analyticsAdapter.context.authToken = undefined; - analyticsAdapter.context.auctionObject.nullProperty = null; - analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; - - expect(ajaxStub.callCount).to.be.equal(0); - - sendAuction(); - - expect(ajaxStub.callCount).to.be.equal(0); - }); - }); - }); - }); + }, + device: { + geo: {} + }, + user: { + id: null, + yob: null, + gender: null, + }, + bidResponses: [], + bidRequests: [], + 'ext.rivr.optimiser': 'unoptimised', + modelVersion: null, + 'ext.rivr.originalvalues': [] + } + }; }); From 4ca7c57f337659aa94d0922a64ede1486b8e7aa1 Mon Sep 17 00:00:00 2001 From: adg Date: Wed, 17 Oct 2018 16:39:29 +0200 Subject: [PATCH 47/62] Fix for IE 11.0.0 and Safari 8.0.8 - includes() Use core-js includes function for array --- modules/rivrAnalyticsAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 846c8bd3250..6dfe8f6b2c2 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,4 +1,5 @@ import {ajax} from 'src/ajax'; +import includes from 'core-js/library/fn/array/includes'; import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; @@ -491,7 +492,7 @@ function getAllUnrespondedBidRequests() { let respondedBidIds = getAllRespondedBidIds(); let bidRequests = rivrAnalytics.context.auctionObject.bidRequests; let allNotRespondedBidRequests = bidRequests.reduce((cache, requestBidder) => { - let notRespondedBids = requestBidder.bids.filter((bid) => !respondedBidIds.includes(bid.bidId)); + let notRespondedBids = requestBidder.bids.filter((bid) => !includes(respondedBidIds, bid.bidId)); notRespondedBids.forEach((bid) => bid.start = requestBidder.start); return cache.concat(notRespondedBids); }, []); From 099fe28b5494addcf800f33fd948aa0d013ce3b0 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 19 Oct 2018 14:25:31 +0200 Subject: [PATCH 48/62] Restore pbjs_api_spec.js --- test/spec/unit/pbjs_api_spec.js | 254 -------------------------------- 1 file changed, 254 deletions(-) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 144cbb656db..a03339c76b3 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -763,260 +763,6 @@ describe('Unit: Prebid Module', function () { }); }); - describe('getAdserverTargeting with `mediaTypePriceGranularity` set for media type', function() { - let currentPriceBucket; - let auction; - let ajaxStub; - let response; - let cbTimeout = 3000; - let auctionManagerInstance; - let targeting; - - const bannerResponse = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '4d0a6829338a07', - 'tag_id': 4799418, - 'auction_id': '2256922143947979797', - 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 2500, - 'ads': [{ - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 33989846, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 1.99, - 'cpm_publisher_currency': 0.500000, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'rtb': { - 'banner': { - 'width': 300, - 'height': 250, - 'content': '' - }, - 'trackers': [{ - 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] - }] - } - }] - }] - }; - const videoResponse = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '4d0a6829338a07', - 'tag_id': 4799418, - 'auction_id': '2256922143947979797', - 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 2500, - 'ads': [{ - 'content_source': 'rtb', - 'ad_type': 'video', - 'buyer_member_id': 958, - 'creative_id': 33989846, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 1.99, - 'cpm_publisher_currency': 0.500000, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'rtb': { - 'video': { - 'width': 300, - 'height': 250, - 'content': '' - }, - 'trackers': [{ - 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] - }] - } - }] - }] - }; - - const createAdUnit = (code, mediaTypes) => { - if (!mediaTypes) { - mediaTypes = ['banner']; - } else if (typeof mediaTypes === 'string') { - mediaTypes = [mediaTypes]; - } - - const adUnit = { - code: code, - sizes: [[300, 250], [300, 600]], - bids: [{ - bidder: 'appnexus', - params: { - placementId: '10433394' - } - }] - }; - - let _mediaTypes = {}; - if (mediaTypes.indexOf('banner') !== -1) { - _mediaTypes['banner'] = { - 'banner': {} - }; - } - if (mediaTypes.indexOf('video') !== -1) { - _mediaTypes['video'] = { - 'video': { - context: 'instream', - playerSize: [300, 250] - } - }; - } - if (mediaTypes.indexOf('native') !== -1) { - _mediaTypes['native'] = { - 'native': {} - }; - } - - if (Object.keys(_mediaTypes).length > 0) { - adUnit['mediaTypes'] = _mediaTypes; - // if video type, add video to every bid.param object - if (_mediaTypes.video) { - adUnit.bids.forEach(bid => { - bid.params['video'] = { - width: 300, - height: 250, - vastUrl: '', - ttl: 3600 - }; - }); - } - } - return adUnit; - } - const initTestConfig = (data) => { - $$PREBID_GLOBAL$$.bidderSettings = {}; - - ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { - return function(url, callback) { - const fakeResponse = sinon.stub(); - fakeResponse.returns('headerContent'); - callback.success(JSON.stringify(response), { getResponseHeader: fakeResponse }); - } - }); - auctionManagerInstance = newAuctionManager(); - targeting = newTargeting(auctionManagerInstance) - - configObj.setConfig({ - 'priceGranularity': { - 'buckets': [ - { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.01 }, - { 'precision': 2, 'min': 5, 'max': 8, 'increment': 0.05 }, - { 'precision': 2, 'min': 8, 'max': 20, 'increment': 0.5 }, - { 'precision': 2, 'min': 20, 'max': 25, 'increment': 1 } - ] - }, - 'mediaTypePriceGranularity': { - 'banner': { - 'buckets': [ - { 'precision': 2, 'min': 0, 'max': 5, 'increment': 0.25 }, - { 'precision': 2, 'min': 6, 'max': 20, 'increment': 0.5 }, - { 'precision': 2, 'min': 21, 'max': 100, 'increment': 1 } - ] - }, - 'video': 'low', - 'native': 'high' - } - }); - - auction = auctionManagerInstance.createAuction({ - adUnits: data.adUnits, - adUnitCodes: data.adUnitCodes - }); - }; - - before(() => { - currentPriceBucket = configObj.getConfig('priceGranularity'); - sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{ - 'bidderCode': 'appnexus', - 'auctionId': '20882439e3238c', - 'bidderRequestId': '331f3cf3f1d9c8', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '4d0a6829338a07', - 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c' - } - ], - 'auctionStart': 1505250713622, - 'timeout': 3000 - }])); - }); - - after(() => { - configObj.setConfig({ priceGranularity: currentPriceBucket }); - adaptermanager.makeBidRequests.restore(); - }) - - afterEach(() => { - ajaxStub.restore(); - }); - - it('should get correct hb_pb with cpm between 0 - 5', () => { - initTestConfig({ - adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], - adUnitCodes: ['div-gpt-ad-1460505748561-0'] - }); - - response = bannerResponse; - response.tags[0].ads[0].cpm = 3.4288; - - auction.callBids(cbTimeout); - let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.25'); - }); - - it('should get correct hb_pb with cpm between 21 - 100', () => { - initTestConfig({ - adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], - adUnitCodes: ['div-gpt-ad-1460505748561-0'] - }); - - response = bannerResponse; - response.tags[0].ads[0].cpm = 43.4288; - - auction.callBids(cbTimeout); - let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('43.00'); - }); - - it('should only apply price granularity if bid media type matches', () => { - initTestConfig({ - adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ], - adUnitCodes: ['div-gpt-ad-1460505748561-0'] - }); - - response = videoResponse; - response.tags[0].ads[0].cpm = 3.4288; - - auction.callBids(cbTimeout); - let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.00'); - }); - }); - describe('getBidResponses', function () { it('should return expected bid responses when not passed an adunitCode', function () { var result = $$PREBID_GLOBAL$$.getBidResponses(); From e489aed7422d91e05f3f3e711ceea85ce5ca4df7 Mon Sep 17 00:00:00 2001 From: adg Date: Tue, 23 Oct 2018 18:31:07 +0200 Subject: [PATCH 49/62] Fix API calls for rivr analytics impressions and clicks --- modules/rivrAnalyticsAdapter.js | 18 ++++++++++++------ test/spec/modules/rivrAnalyticsAdapter_spec.js | 11 ++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 6dfe8f6b2c2..c8616894826 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -62,7 +62,6 @@ export function sendAuction() { `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, () => {}, JSON.stringify(req), - // TODO extract this object to variable { contentType: 'application/json', customHeaders: { @@ -80,7 +79,7 @@ export function sendImpressions() { let impressionsReq = Object.assign({}, {impressions}); logInfo('sending impressions request to analytics => ', impressionsReq); ajax( - `http://${rivrAnalytics.context.host}/impressions`, + `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/impressions`, () => {}, JSON.stringify(impressionsReq), { @@ -240,10 +239,17 @@ export function reportClickEvent(event) { 'click_url': clickUrl }; logInfo('Sending click events with parameters: ', req); - - // TODO add Authentication header - ajax(`http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/clicks`, () => { - }, JSON.stringify(req)); + ajax( + `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/clicks`, + () => {}, + JSON.stringify(req), + { + contentType: 'application/json', + customHeaders: { + 'Authorization': 'Basic ' + rivrAnalytics.context.authToken + } + } + ); }; function addClickHandler(bannerId) { diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index dca63bf935e..f903f6e9c2d 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -358,8 +358,10 @@ describe('RIVR Analytics adapter', () => { it('sendImpressions(), when authToken is defined and there are impressions, it sends impressions to the tracker', () => { const aMockString = 'anImpressionPropertyValue'; const IMPRESSION_MOCK = { anImpressionProperty: aMockString }; + const CLIENT_ID_MOCK = 'aClientID'; analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); analyticsAdapter.context.authToken = 'anAuthToken'; + analyticsAdapter.context.clientID = CLIENT_ID_MOCK; analyticsAdapter.context.queue = new ExpiringQueue( () => {}, () => {}, @@ -376,12 +378,13 @@ describe('RIVR Analytics adapter', () => { expect(ajaxStub.callCount).to.be.equal(1); expect(payload.impressions.length).to.be.equal(1); - expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/impressions/); + expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/aClientID\/impressions/); expect(payload.impressions[0].anImpressionProperty).to.be.equal(aMockString); }); - it('reportClickEvent(), when authToken is not defined, it calls endpoint', () => { + it('reportClickEvent() calls endpoint', () => { const CLIENT_ID_MOCK = 'aClientId'; + const AUTH_TOKEN_MOCK = 'aToken'; const CLICK_URL_MOCK = 'clickURLMock'; const EVENT_MOCK = { currentTarget: { @@ -397,7 +400,7 @@ describe('RIVR Analytics adapter', () => { } }; analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - analyticsAdapter.context.authToken = undefined; + analyticsAdapter.context.authToken = AUTH_TOKEN_MOCK; analyticsAdapter.context.clientID = CLIENT_ID_MOCK; analyticsAdapter.context.auctionObject.nullProperty = null; analyticsAdapter.context.auctionObject.notNullProperty = 'aValue'; @@ -407,9 +410,11 @@ describe('RIVR Analytics adapter', () => { reportClickEvent(EVENT_MOCK); const payload = JSON.parse(ajaxStub.getCall(0).args[2]); + const options = ajaxStub.getCall(0).args[3]; expect(ajaxStub.callCount).to.be.equal(1); expect(ajaxStub.getCall(0).args[0]).to.match(/http:\/\/tracker.rivr.simplaex.com\/aClientId\/clicks/); + expect(options.customHeaders.Authorization).to.equal('Basic aToken'); expect(payload.timestamp).to.be.equal('1970-01-01T00:00:00.000Z'); expect(payload.request_id).to.be.a('string'); expect(payload.click_url).to.be.equal(CLICK_URL_MOCK); From 37fabf1fdcebb1bd6912e24180f3483025a764f7 Mon Sep 17 00:00:00 2001 From: adg Date: Wed, 31 Oct 2018 12:45:26 +0100 Subject: [PATCH 50/62] RVR-2005 - Change auction object model --- modules/rivrAnalyticsAdapter.js | 185 +++++++++++------- .../spec/modules/rivrAnalyticsAdapter_spec.js | 17 ++ 2 files changed, 135 insertions(+), 67 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c8616894826..40d00608afa 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -3,7 +3,7 @@ import includes from 'core-js/library/fn/array/includes'; import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; -import { logInfo, generateUUID } from 'src/utils'; +import { logInfo, generateUUID, timestamp } from 'src/utils'; const analyticsType = 'endpoint'; const DEFAULT_HOST = 'tracker.rivr.simplaex.com'; @@ -23,7 +23,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { rivrAnalytics.context.queue.init(); } if (rivrAnalytics.context.auctionObject) { - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + rivrAnalytics.context.auctionObject = createNewAuctionObject(); saveUnoptimisedParams(); fetchLocalization(); } @@ -56,7 +56,7 @@ export function sendAuction() { removeEmptyProperties(rivrAnalytics.context.auctionObject); let auctionObject = rivrAnalytics.context.auctionObject; let req = Object.assign({}, {Auction: auctionObject}); - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); + rivrAnalytics.context.auctionObject = createNewAuctionObject(); logInfo('sending request to analytics => ', req); ajax( `http://${rivrAnalytics.context.host}/${rivrAnalytics.context.clientID}/auctions`, @@ -158,12 +158,10 @@ export function setAuctionAbjectPosition(position) { } function getPlatformType() { - if (navigator.userAgent.match(/mobile/i)) { - return 'Mobile'; - } else if (navigator.userAgent.match(/iPad|Android|Touch/i)) { - return 'Tablet'; + if (navigator.userAgent.match(/mobile/i) || navigator.userAgent.match(/iPad|Android|Touch/i)) { + return 1; } else { - return 'Desktop'; + return 2; } }; @@ -314,68 +312,122 @@ function addHandlers(bannersIds) { }) }; -function fulfillAuctionObject() { - let newAuction = { - id: null, - timestamp: null, - at: null, - bcat: [], - imp: [], - app: { - id: null, - name: null, - domain: window.location.href, - bundle: null, - cat: [], - publisher: { - id: null, - name: null - } +export function createNewAuctionObject() { + const auction = { + id: '', + publisher: rivrAnalytics.context.clientID, + blockedCategories: [''], + timestamp: timestamp(), + user: { + id: '' }, site: { - id: null, - name: null, - domain: window.location.href, - cat: [], - publisher: { - id: null, - name: null - } + domain: window.location.host, + page: window.location.pathname, + categories: [''] }, - device: { - geo: { - city: null, - country: null, - region: null, - zip: null, - type: null, - metro: null - }, - devicetype: getPlatformType(), - osv: null, - os: null, - model: null, - make: null, - carrier: null, - ip: null, - didsha1: null, - dpidmd5: null, - ext: { - uid: null + impressions: [ + { + id: '', + adSpotId: '', + adType: '', + bidFloor: 0.0, + acceptedSizes: [ + { + h: 0, + w: 0 + } + ], + banner: { + h: 0, + w: 0, + pos: '' + }, } - }, - user: { - id: null, - yob: null, - gender: null, - }, - bidResponses: [], - bidRequests: [], - 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', - modelVersion: localStorage.getItem('rivr_model_version') || null, - 'ext.rivr.originalvalues': [] - }; - return newAuction; + ], + bidders: [ + { + id: '', + bids: [ + { + adomain: [''], + clearPrice: 0.0, + impId: '', + price: 0.0, + status: 0 + } + ] + } + ], + device: { + userAgent: navigator.userAgent, + browser: '', + deviceType: getPlatformType() + } + } + + return auction; + + // let newAuction = { + // id: null, + // timestamp: null, + // at: null, + // bcat: [], + // imp: [], + // app: { + // id: null, + // name: null, + // domain: window.location.href, + // bundle: null, + // cat: [], + // publisher: { + // id: null, + // name: null + // } + // }, + // site: { + // id: null, + // name: null, + // domain: window.location.href, + // cat: [], + // publisher: { + // id: null, + // name: null + // } + // }, + // device: { + // geo: { + // city: null, + // country: null, + // region: null, + // zip: null, + // type: null, + // metro: null + // }, + // devicetype: getPlatformType(), + // osv: null, + // os: null, + // model: null, + // make: null, + // carrier: null, + // ip: null, + // didsha1: null, + // dpidmd5: null, + // ext: { + // uid: null + // } + // }, + // user: { + // id: null, + // yob: null, + // gender: null, + // }, + // bidResponses: [], + // bidRequests: [], + // 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', + // modelVersion: localStorage.getItem('rivr_model_version') || null, + // 'ext.rivr.originalvalues': [] + // }; }; function saveUnoptimisedParams() { @@ -528,7 +580,6 @@ rivrAnalytics.enableAnalytics = (config) => { } rivrAnalytics.context = { host: config.options.host || DEFAULT_HOST, - pubId: config.options.pubId, auctionObject: {}, adUnits: copiedUnits, clientID: config.options.clientID, diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index f903f6e9c2d..965c38a11ab 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -9,6 +9,7 @@ import { dataLoaderForHandler, pinHandlerToHTMLElement, setAuctionAbjectPosition, + createNewAuctionObject, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; @@ -21,6 +22,7 @@ describe('RIVR Analytics adapter', () => { const EXPIRING_QUEUE_TIMEOUT = 4000; const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; const PUBLISHER_ID_MOCK = 777; + const RVR_CLIENT_ID_MOCK = 'aCliendId'; const EMITTED_AUCTION_ID = 1; const TRACKER_BASE_URL_MOCK = 'tracker.rivr.simplaex.com'; let sandbox; @@ -43,6 +45,7 @@ describe('RIVR Analytics adapter', () => { adaptermanager.enableAnalytics({ provider: 'rivr', options: { + clientID: RVR_CLIENT_ID_MOCK, pubId: PUBLISHER_ID_MOCK, adUnits: [utils.deepClone(AD_UNITS_MOCK)] } @@ -547,6 +550,20 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.device.geo.lat).to.be.equal('aLatitude'); }); + it.only('createNewAuctionObject(), it creates a new auction object', () => { + const MILLIS_FROM_EPOCH_TO_NOW_MOCK = 123456; + timer.tick(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + + const result = createNewAuctionObject(); + + expect(result.device.deviceType).to.be.equal(2); + expect(result.publisher).to.be.equal(RVR_CLIENT_ID_MOCK); + expect(result.device.userAgent).to.be.equal(navigator.userAgent); + expect(result.timestamp).to.be.equal(MILLIS_FROM_EPOCH_TO_NOW_MOCK); + expect(result.site.domain.substring(0, 9)).to.be.equal('localhost'); + expect(result.site.page).to.be.equal('/context.html'); + }); + const AD_UNITS_MOCK = [ { code: 'banner-container1', From d4c53788ac1684324237ec718089a1a097215e0c Mon Sep 17 00:00:00 2001 From: adg Date: Thu, 1 Nov 2018 12:22:52 +0100 Subject: [PATCH 51/62] RVR-2005 - Set rvr_usr_id cookie --- modules/rivrAnalyticsAdapter.js | 26 +++++++--- .../spec/modules/rivrAnalyticsAdapter_spec.js | 52 ++++++++++++++++++- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 40d00608afa..9c2c32f643d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -6,6 +6,7 @@ import adaptermanager from 'src/adaptermanager'; import { logInfo, generateUUID, timestamp } from 'src/utils'; const analyticsType = 'endpoint'; +const rivrUsrIdCookieKey = 'rvr_usr_id'; const DEFAULT_HOST = 'tracker.rivr.simplaex.com'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -24,7 +25,7 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { } if (rivrAnalytics.context.auctionObject) { rivrAnalytics.context.auctionObject = createNewAuctionObject(); - saveUnoptimisedParams(); + saveUnoptimisedAdUnits(); fetchLocalization(); } handler = trackAuctionInit; @@ -319,12 +320,12 @@ export function createNewAuctionObject() { blockedCategories: [''], timestamp: timestamp(), user: { - id: '' + id: rivrAnalytics.context.userId }, site: { domain: window.location.host, page: window.location.pathname, - categories: [''] + categories: rivrAnalytics.context.siteCategories }, impressions: [ { @@ -363,7 +364,8 @@ export function createNewAuctionObject() { userAgent: navigator.userAgent, browser: '', deviceType: getPlatformType() - } + }, + 'ext.rivr.originalvalues': [] } return auction; @@ -430,7 +432,7 @@ export function createNewAuctionObject() { // }; }; -function saveUnoptimisedParams() { +export function saveUnoptimisedAdUnits() { let units = rivrAnalytics.context.adUnits; if (units) { if (units.length > 0) { @@ -448,7 +450,7 @@ function saveUnoptimisedParams() { } }; -function connectAllUnits(units) { +export function connectAllUnits(units) { return units.reduce((acc, units) => { units.forEach((unit) => acc.push(unit)) return acc @@ -568,6 +570,16 @@ function removeEmptyProperties(obj) { }); }; +function getCookie(name) { + var value = '; ' + document.cookie; + var parts = value.split('; ' + name + '='); + if (parts.length == 2) return parts.pop().split(';').shift(); +} + +function storeAndReturnRivrUsrIdCookie() { + return document.cookie = 'rvr_usr_id=' + generateUUID(); +} + // save the base class function rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; @@ -579,9 +591,11 @@ rivrAnalytics.enableAnalytics = (config) => { copiedUnits = JSON.parse(stringifiedAdUnits); } rivrAnalytics.context = { + userId: getCookie(rivrUsrIdCookieKey) || storeAndReturnRivrUsrIdCookie(), host: config.options.host || DEFAULT_HOST, auctionObject: {}, adUnits: copiedUnits, + siteCategories: config.options.siteCategories || [], clientID: config.options.clientID, authToken: config.options.authToken, queue: new ExpiringQueue(sendImpressions, sendAuction, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 965c38a11ab..3951c5ab3ae 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -10,6 +10,7 @@ import { pinHandlerToHTMLElement, setAuctionAbjectPosition, createNewAuctionObject, + connectAllUnits, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; @@ -23,8 +24,10 @@ describe('RIVR Analytics adapter', () => { const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; const PUBLISHER_ID_MOCK = 777; const RVR_CLIENT_ID_MOCK = 'aCliendId'; + const SITE_CATEGORIES_MOCK = ['cat1', 'cat2']; const EMITTED_AUCTION_ID = 1; const TRACKER_BASE_URL_MOCK = 'tracker.rivr.simplaex.com'; + const UUID_REG_EXP = new RegExp('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', 'i'); let sandbox; let ajaxStub; let timer; @@ -47,7 +50,8 @@ describe('RIVR Analytics adapter', () => { options: { clientID: RVR_CLIENT_ID_MOCK, pubId: PUBLISHER_ID_MOCK, - adUnits: [utils.deepClone(AD_UNITS_MOCK)] + adUnits: [utils.deepClone(BANNER_AD_UNITS_MOCK)], + siteCategories: SITE_CATEGORIES_MOCK, } }); }); @@ -99,6 +103,10 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context).to.have.property('pubId', PUBLISHER_ID_MOCK); }); + it.only('enableAnalytics - should set a cookie containing a user id', () => { + expect(UUID_REG_EXP.test(analyticsAdapter.context.userId)).to.equal(true); + }); + it('Firing AUCTION_INIT should set auction id of context when AUCTION_INIT event is fired', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); const auctionId = analyticsAdapter.context.auctionObject.id; @@ -562,9 +570,20 @@ describe('RIVR Analytics adapter', () => { expect(result.timestamp).to.be.equal(MILLIS_FROM_EPOCH_TO_NOW_MOCK); expect(result.site.domain.substring(0, 9)).to.be.equal('localhost'); expect(result.site.page).to.be.equal('/context.html'); + expect(result.site.categories).to.be.equal(SITE_CATEGORIES_MOCK); + }); + + it('connectAllUnits(), returns a flattened array with all banner and video adunits', () => { + const allAdUnits = [BANNER_AD_UNITS_MOCK, VIDEO_AD_UNITS_MOCK]; + + const result = connectAllUnits(allAdUnits); + + expect(result.length).to.be.eql(2); + expect(result[0].code).to.be.eql('banner-container1'); + expect(result[1].code).to.be.eql('video'); }); - const AD_UNITS_MOCK = [ + const BANNER_AD_UNITS_MOCK = [ { code: 'banner-container1', mediaTypes: { @@ -590,6 +609,35 @@ describe('RIVR Analytics adapter', () => { } ]; + const VIDEO_AD_UNITS_MOCK = [ + { + code: 'video', + mediaTypes: { + video: { + context: 'outstream', + sizes: [[640, 360], [640, 480]] + } + }, + bids: [ + { + bidder: "vuble", + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: "http://www.vuble.tv/", // optional + floorPrice: 5.00 // optional + } + }, + { + bidder: 'vertamedia', + params: { + aid: 331133 + } + } + ] + }]; + const REQUEST = { bidderCode: 'adapter', auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', From 47126c40812b29111a9a142bd70901ce68832fdc Mon Sep 17 00:00:00 2001 From: adg Date: Thu, 1 Nov 2018 14:02:39 +0100 Subject: [PATCH 52/62] RVR-2005 - Remove BID_REQUESTED and BID_RESPONSE handlers We have the same infos all collected in AUCTION_END --- modules/rivrAnalyticsAdapter.js | 40 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 9c2c32f643d..cb4c9b088ea 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -30,12 +30,14 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { } handler = trackAuctionInit; break; - case CONSTANTS.EVENTS.BID_REQUESTED: - handler = trackBidRequest; - break; - case CONSTANTS.EVENTS.BID_RESPONSE: - handler = trackBidResponse; - break; + // TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] + // case CONSTANTS.EVENTS.BID_REQUESTED: + // handler = trackBidRequest; + // break; + // TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] + // case CONSTANTS.EVENTS.BID_RESPONSE: + // handler = trackBidResponse; + // break; case CONSTANTS.EVENTS.BID_WON: handler = trackBidWon; break; @@ -100,14 +102,15 @@ function trackAuctionInit(args) { }; function trackBidRequest(args) { - setCurrentPublisherId(args); - let bidRequest = args; - rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); + // TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] + // let bidRequest = args; + // rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); }; function trackBidResponse(args) { - let bidResponse = createBidResponse(args); - rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); + // TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] + // let bidResponse = createBidResponse(args); + // rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); }; function trackBidWon(args) { @@ -126,21 +129,6 @@ function trackBidTimeout(args) { return [args]; }; -function setCurrentPublisherId(bidRequested) { - let site = rivrAnalytics.context.auctionObject.site; - let app = rivrAnalytics.context.auctionObject.app; - let pubId = rivrAnalytics.context.pubId; - if (!site.publisher.id || app.publisher.id) { - if (pubId) { - site.publisher.id = pubId; - app.publisher.id = pubId; - } else { - site.publisher.id = bidRequested.bids[0].crumbs.pubcid; - app.publisher.id = bidRequested.bids[0].crumbs.pubcid; - } - } -}; - export function fetchLocalization() { if (navigator.permissions) { navigator.permissions.query({ name: 'geolocation' }).then((permission) => { From 7f9be8fde4c54bc61f6aa81cf58d80d4cceb25fb Mon Sep 17 00:00:00 2001 From: adg Date: Thu, 1 Nov 2018 17:49:57 +0100 Subject: [PATCH 53/62] RVR-2005 - build Bidders Array From Auction End --- modules/rivrAnalyticsAdapter.js | 138 ++++++-------- .../spec/modules/rivrAnalyticsAdapter_spec.js | 179 +++++++++++++++++- 2 files changed, 237 insertions(+), 80 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index cb4c9b088ea..643a8b6a664 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,5 +1,4 @@ import {ajax} from 'src/ajax'; -import includes from 'core-js/library/fn/array/includes'; import adapter from 'src/AnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; @@ -101,17 +100,17 @@ function trackAuctionInit(args) { rivrAnalytics.context.auctionObject.id = args.auctionId; }; -function trackBidRequest(args) { - // TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] - // let bidRequest = args; - // rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); -}; +// TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] +// function trackBidRequest(args) { +// let bidRequest = args; +// rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); +// }; -function trackBidResponse(args) { - // TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] - // let bidResponse = createBidResponse(args); - // rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); -}; +// TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] +// function trackBidResponse(args) { +// let bidResponse = createBidResponse(args); +// rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); +// }; function trackBidWon(args) { let auctionObject = rivrAnalytics.context.auctionObject; @@ -120,11 +119,33 @@ function trackBidWon(args) { assignBidWonStatusToResponse(args); }; -function trackAuctionEnd(args) { +export function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); - fillBidResponsesOfUnrespondedBidRequests(); + rivrAnalytics.context.auctionObject.bidders = buildBiddersArrayFromAuctionEnd(args); }; +function buildBiddersArrayFromAuctionEnd(args) { + return args.bidderRequests.map((bidderRequest) => { + const bidder = {}; + bidder.id = bidderRequest.bidderCode; + bidder.bids = bidderRequest.bids.map((bid) => { + const bidReceivedForThisRequest = args.bidsReceived.find((bidReceived) => { + return bidderRequest.bidderCode === bidReceived.bidderCode && + bid.bidId === bidReceived.adId && + bid.adUnitCode === bidReceived.adUnitCode; + }); + return { + adomain: [''], + clearPrice: 0.0, + impId: bid.adUnitCode, + price: bidReceivedForThisRequest ? bidReceivedForThisRequest.cpm : 0.0, + status: 0 + }; + }); + return bidder; + }); +} + function trackBidTimeout(args) { return [args]; }; @@ -154,46 +175,34 @@ function getPlatformType() { } }; -function createBidResponse(bidResponseEvent) { - return { - timestamp: bidResponseEvent.responseTimestamp, - status: bidResponseEvent.getStatusCode(), - total_duration: bidResponseEvent.timeToRespond, - bidderId: null, - bidder_name: bidResponseEvent.bidder, - cur: bidResponseEvent.currency, - seatbid: [ - { - seat: null, - bid: [ - { - status: 2, - clear_price: bidResponseEvent.cpm, - attr: [], - crid: bidResponseEvent.creativeId, - cid: null, - id: null, - adid: bidResponseEvent.adId, - adomain: [], - iurl: null - } - ] - } - ] - } -}; - -function createSingleEmptyBidResponse(bidResponse) { - return { - timestamp: bidResponse.start, - total_duration: null, - bidderId: null, - bidder_name: bidResponse.bidder, - cur: null, - response: 'noBid', - seatbid: [] - } -}; +// function createBidResponse(bidResponseEvent) { +// return { +// timestamp: bidResponseEvent.responseTimestamp, +// status: bidResponseEvent.getStatusCode(), +// total_duration: bidResponseEvent.timeToRespond, +// bidderId: null, +// bidder_name: bidResponseEvent.bidder, +// cur: bidResponseEvent.currency, +// seatbid: [ +// { +// seat: null, +// bid: [ +// { +// status: 2, +// clear_price: bidResponseEvent.cpm, +// attr: [], +// crid: bidResponseEvent.creativeId, +// cid: null, +// id: null, +// adid: bidResponseEvent.adId, +// adomain: [], +// iurl: null +// } +// ] +// } +// ] +// } +// }; function createAuctionImpression(bidWonEvent) { return { @@ -528,29 +537,6 @@ function assignBidWonStatusToResponse(wonBid) { }); }; -function fillBidResponsesOfUnrespondedBidRequests() { - let unRespondedBidRequests = getAllUnrespondedBidRequests(); - unRespondedBidRequests.forEach((bid) => { - let emptyBidResponse = createSingleEmptyBidResponse(bid); - rivrAnalytics.context.auctionObject.bidResponses.push(emptyBidResponse); - }); -}; - -function getAllUnrespondedBidRequests() { - let respondedBidIds = getAllRespondedBidIds(); - let bidRequests = rivrAnalytics.context.auctionObject.bidRequests; - let allNotRespondedBidRequests = bidRequests.reduce((cache, requestBidder) => { - let notRespondedBids = requestBidder.bids.filter((bid) => !includes(respondedBidIds, bid.bidId)); - notRespondedBids.forEach((bid) => bid.start = requestBidder.start); - return cache.concat(notRespondedBids); - }, []); - return allNotRespondedBidRequests; -}; - -function getAllRespondedBidIds() { - return rivrAnalytics.context.auctionObject.bidResponses.map((response) => response.seatbid[0].bid[0].adid); -}; - function removeEmptyProperties(obj) { Object.keys(obj).forEach(function(key) { if (obj[key] && typeof obj[key] === 'object') removeEmptyProperties(obj[key]) diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 3951c5ab3ae..96b16bf29f5 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -11,6 +11,7 @@ import { setAuctionAbjectPosition, createNewAuctionObject, connectAllUnits, + trackAuctionEnd, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; @@ -103,7 +104,7 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context).to.have.property('pubId', PUBLISHER_ID_MOCK); }); - it.only('enableAnalytics - should set a cookie containing a user id', () => { + it('enableAnalytics - should set a cookie containing a user id', () => { expect(UUID_REG_EXP.test(analyticsAdapter.context.userId)).to.equal(true); }); @@ -558,7 +559,7 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.device.geo.lat).to.be.equal('aLatitude'); }); - it.only('createNewAuctionObject(), it creates a new auction object', () => { + it('createNewAuctionObject(), it creates a new auction object', () => { const MILLIS_FROM_EPOCH_TO_NOW_MOCK = 123456; timer.tick(MILLIS_FROM_EPOCH_TO_NOW_MOCK); @@ -583,6 +584,26 @@ describe('RIVR Analytics adapter', () => { expect(result[1].code).to.be.eql('video'); }); + it.only('trackAuctionEnd(), populates the bidders array from bidderRequests and bidsReceived', () => { + trackAuctionEnd(AUCTION_END_EVENT_FOR_BIDDER_ARRAY_MOCK); + + const result = analyticsAdapter.context.auctionObject.bidders; + + expect(result.length).to.be.eql(3); + + expect(result[0].id).to.be.eql('vuble'); + expect(result[0].bids[0].price).to.be.eql(0); + + expect(result[1].id).to.be.eql('vertamedia'); + expect(result[1].bids[0].price).to.be.eql(0); + + expect(result[2].id).to.be.eql('appnexus'); + expect(result[2].bids[0].price).to.be.eql(0.5); + expect(result[2].bids[0].impId).to.be.eql('/19968336/header-bid-tag-0'); + expect(result[2].bids[1].price).to.be.eql(0.7); + expect(result[2].bids[1].impId).to.be.eql('/19968336/header-bid-tag-1'); + }); + const BANNER_AD_UNITS_MOCK = [ { code: 'banner-container1', @@ -620,12 +641,12 @@ describe('RIVR Analytics adapter', () => { }, bids: [ { - bidder: "vuble", + bidder: 'vuble', params: { env: 'net', pubId: '18', zoneId: '12345', - referrer: "http://www.vuble.tv/", // optional + referrer: 'http://www.vuble.tv/', // optional floorPrice: 5.00 // optional } }, @@ -764,4 +785,154 @@ describe('RIVR Analytics adapter', () => { 'ext.rivr.originalvalues': [] } }; + + const AUCTION_END_EVENT_FOR_BIDDER_ARRAY_MOCK = { + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart: 1540560217395, + auctionEnd: 1540560217703, + auctionStatus: 'completed', + adUnitCodes: [ + '/19968336/header-bid-tag-0', + '/19968336/header-bid-tag-1', + 'video' + ], + bidderRequests: [ + { + bidderCode: 'vuble', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '1bb11e055665bc', + bids: [ + { + bidder: 'vuble', + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: 'video', + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d', + bidId: '2859b890da7418', + bidderRequestId: '1bb11e055665bc', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + refererInfo: { + referer: 'http: //localhost: 8080/', + reachedTop: true, + numIframes: 0, + stack: [ + 'http://localhost:8080/' + ] + }, + start: 1540560217401, + doneCbCallCount: 0 + }, + { + bidderCode: 'vertamedia', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '3c2cbf7f1466cb', + bids: [ + { + bidder: 'vertamedia', + params: { + aid: 331133 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: 'video', + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d', + bidId: '45b3ad5c2dc794', + bidderRequestId: '3c2cbf7f1466cb', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + start: 1540560217401 + }, + { + bidderCode: 'appnexus', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidderRequestId: '5312eef4418cd7', + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'aee9bf8d-6d8f-425b-a42a-52c875371ebc', + bidId: '6de82e80757293', + bidderRequestId: '5312eef4418cd7', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + }, + { + bidder: 'appnexus', + params: { + placementId: 13144370 + }, + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + }, + adUnitCode: '/19968336/header-bid-tag-1', + transactionId: '3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', + bidId: '7e1a45d85bd57c', + bidderRequestId: '5312eef4418cd7', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + src: 'client', + bidRequestsCount: 1 + } + ], + auctionStart: 1540560217395, + timeout: 3000, + start: 1540560217403, + doneCbCallCount: 0 + } + ], + bidsReceived: [ + { + bidderCode: 'appnexus', + adId: '6de82e80757293', + mediaType: 'banner', + source: 'client', + requestId: '6de82e80757293', + cpm: 0.5, + creativeId: 96846035, + appnexus: { + buyerMemberId: 9325 + }, + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-0', + }, + { + bidderCode: 'appnexus', + adId: '7e1a45d85bd57c', + mediaType: 'banner', + source: 'client', + requestId: '7e1a45d85bd57c', + cpm: 0.7, + creativeId: 96846035, + appnexus: { + buyerMemberId: 9325 + }, + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-1', + } + ], + winningBids: [ + + ], + timeout: 3000 + }; }); From 11bd67361104d4c6dbfa2222e99442f306da1a4a Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 11:19:43 +0100 Subject: [PATCH 54/62] RVR-2005 - build impressions Array From Auction End --- modules/rivrAnalyticsAdapter.js | 44 +++- .../spec/modules/rivrAnalyticsAdapter_spec.js | 209 +++++++++++++++++- 2 files changed, 247 insertions(+), 6 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 643a8b6a664..7f3873ad9b7 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -122,14 +122,52 @@ function trackBidWon(args) { export function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); rivrAnalytics.context.auctionObject.bidders = buildBiddersArrayFromAuctionEnd(args); + rivrAnalytics.context.auctionObject.impressions = buildImpressionsArrayFromAuctionEnd(args); }; -function buildBiddersArrayFromAuctionEnd(args) { - return args.bidderRequests.map((bidderRequest) => { +function buildImpressionsArrayFromAuctionEnd(auctionEndEvent) { + return auctionEndEvent.adUnits.map((adUnit) => { + const impression = {}; + impression.id = adUnit.code; + impression.adType = 'unknown'; + impression.acceptedSizes = []; + const bidReceivedForThisAdUnit = auctionEndEvent.bidsReceived.find((bidReceived) => { + return adUnit.code === bidReceived.adUnitCode; + }); + if (adUnit.mediaTypes) { + let acceptedSizes = []; + if (adUnit.mediaTypes.banner) { + buildAdTypeDependentFieldsForImpression(impression, 'banner', adUnit, bidReceivedForThisAdUnit); + } else if (adUnit.mediaTypes.video) { + buildAdTypeDependentFieldsForImpression(impression, 'video', adUnit, bidReceivedForThisAdUnit); + } + } + return impression; + }); +} + +function buildAdTypeDependentFieldsForImpression(impression, adType, adUnit, bidReceivedForThisAdUnit) { + impression.adType = adType; + impression.acceptedSizes = adUnit.mediaTypes[adType].sizes.map((acceptedSize) => { + return { + w: acceptedSize[0], + h: acceptedSize[1] + }; + }); + if (bidReceivedForThisAdUnit) { + impression[adType] = { + w: bidReceivedForThisAdUnit.width, + h: bidReceivedForThisAdUnit.height + }; + } +} + +function buildBiddersArrayFromAuctionEnd(auctionEndEvent) { + return auctionEndEvent.bidderRequests.map((bidderRequest) => { const bidder = {}; bidder.id = bidderRequest.bidderCode; bidder.bids = bidderRequest.bids.map((bid) => { - const bidReceivedForThisRequest = args.bidsReceived.find((bidReceived) => { + const bidReceivedForThisRequest = auctionEndEvent.bidsReceived.find((bidReceived) => { return bidderRequest.bidderCode === bidReceived.bidderCode && bid.bidId === bidReceived.adId && bid.adUnitCode === bidReceived.adUnitCode; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 96b16bf29f5..ab710f44ebb 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -584,8 +584,8 @@ describe('RIVR Analytics adapter', () => { expect(result[1].code).to.be.eql('video'); }); - it.only('trackAuctionEnd(), populates the bidders array from bidderRequests and bidsReceived', () => { - trackAuctionEnd(AUCTION_END_EVENT_FOR_BIDDER_ARRAY_MOCK); + it('trackAuctionEnd(), populates the bidders array from bidderRequests and bidsReceived', () => { + trackAuctionEnd(AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK); const result = analyticsAdapter.context.auctionObject.bidders; @@ -604,6 +604,26 @@ describe('RIVR Analytics adapter', () => { expect(result[2].bids[1].impId).to.be.eql('/19968336/header-bid-tag-1'); }); + it.only('trackAuctionEnd(), populates the impressions array from adUnits', () => { + trackAuctionEnd(AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); + + const result = analyticsAdapter.context.auctionObject.impressions; + + expect(result.length).to.be.eql(3); + + expect(result[0].id).to.be.eql('/19968336/header-bid-tag-0'); + expect(result[0].adType).to.be.eql('banner'); + + expect(result[1].id).to.be.eql('/19968336/header-bid-tag-1'); + expect(result[1].adType).to.be.eql('banner'); + expect(result[1].acceptedSizes).to.be.eql([{w: 728, h: 90}, {w: 970, h: 250}]); + expect(result[1].banner).to.be.eql({w: 300, h: 250}); + + // expect(result[2].id).to.be.eql('video'); + // expect(result[2].adType).to.be.eql('video'); + // expect(result[2].acceptedSizes).to.be.eql([{w: 640, h: 360}, {w: 640, h: 480}]); + }); + const BANNER_AD_UNITS_MOCK = [ { code: 'banner-container1', @@ -786,11 +806,194 @@ describe('RIVR Analytics adapter', () => { } }; - const AUCTION_END_EVENT_FOR_BIDDER_ARRAY_MOCK = { + const AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK = { + auctionId:'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart:1540560217395, + auctionEnd:1540560217703, + auctionStatus:'completed', + adUnits:[ + { + code:'/19968336/header-bid-tag-0', + mediaTypes:{ + banner:{ + sizes:[ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + bids:[ + { + bidder:'appnexus', + params:{ + placementId:13144370 + }, + crumbs:{ + pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId:'aee9bf8d-6d8f-425b-a42a-52c875371ebc', + sizes:[ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + }, + { + code:'/19968336/header-bid-tag-1', + mediaTypes:{ + banner:{ + sizes:[ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + } + }, + bids:[ + { + bidder:'appnexus', + params:{ + placementId:13144370 + }, + crumbs:{ + pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId:'3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', + sizes:[ + [ + 728, + 90 + ], + [ + 970, + 250 + ] + ] + }, + { + code:'video', + mediaTypes:{ + video:{ + context:'outstream', + sizes:[ + [ + 640, + 360 + ], + [ + 640, + 480 + ] + ] + } + }, + bids:[ + { + bidder:'vuble', + params:{ + env:'net', + pubId:'18', + zoneId:'12345', + referrer:'http://www.vuble.tv/', + floorPrice:5 + }, + crumbs:{ + pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + }, + { + bidder:'vertamedia', + params:{ + aid:331133 + }, + crumbs:{ + pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + } + } + ], + transactionId:'df11a105-4eef-4ceb-bbc3-a49224f7c49d' + } + ], + adUnitCodes:[ + '/19968336/header-bid-tag-0', + '/19968336/header-bid-tag-1', + 'video' + ], + bidderRequests: [], + bidsReceived:[ + { + bidderCode:'appnexus', + width:300, + height:250, + statusMessage:'Bid available', + adId:'6de82e80757293', + mediaType:'banner', + source:'client', + requestId:'6de82e80757293', + cpm:0.5, + creativeId:96846035, + currency:'USD', + netRevenue:true, + ttl:300, + appnexus:{ + buyerMemberId:9325 + }, + ad:'...HTML CONTAINING THE AD...', + auctionId:'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + responseTimestamp:1540560217636, + requestTimestamp:1540560217403, + bidder:'appnexus', + adUnitCode:'/19968336/header-bid-tag-1', + timeToRespond:233, + pbLg:'0.50', + pbMg:'0.50', + pbHg:'0.50', + pbAg:'0.50', + pbDg:'0.50', + pbCg:'', + size:'728x90', + adserverTargeting:{ + hb_bidder:'appnexus', + hb_adid:'7e1a45d85bd57c', + hb_pb:'0.50', + hb_size:'728x90', + hb_source:'client', + hb_format:'banner' + } + } + ], + winningBids:[ + + ], + timeout:3000 + }; + + const AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK = { auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', auctionStart: 1540560217395, auctionEnd: 1540560217703, auctionStatus: 'completed', + bidders: [], adUnitCodes: [ '/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', From f21dd1243941e450405558cef507d8f96c503dcd Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 12:39:19 +0100 Subject: [PATCH 55/62] RVR-2005 - set status of winning bid --- modules/rivrAnalyticsAdapter.js | 76 ++-- .../spec/modules/rivrAnalyticsAdapter_spec.js | 330 +++++++++++------- 2 files changed, 240 insertions(+), 166 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 7f3873ad9b7..cba0d62fc36 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -113,10 +113,22 @@ function trackAuctionInit(args) { // }; function trackBidWon(args) { + setWinningBidStatus(args); +}; + +function setWinningBidStatus(args) { let auctionObject = rivrAnalytics.context.auctionObject; - let auctionImpression = createAuctionImpression(args); - auctionObject.imp.push(auctionImpression); - assignBidWonStatusToResponse(args); + const bidderObjectForThisWonBid = auctionObject.bidders.find((bidder) => { + return bidder.id === args.bidderCode; + }); + if (bidderObjectForThisWonBid) { + const bidObjectForThisWonBid = bidderObjectForThisWonBid.bids.find((bid) => { + return bid.impId === args.adUnitCode; + }); + if (bidObjectForThisWonBid) { + bidObjectForThisWonBid.status = 1; + } + } }; export function trackAuctionEnd(args) { @@ -127,23 +139,22 @@ export function trackAuctionEnd(args) { function buildImpressionsArrayFromAuctionEnd(auctionEndEvent) { return auctionEndEvent.adUnits.map((adUnit) => { - const impression = {}; - impression.id = adUnit.code; - impression.adType = 'unknown'; - impression.acceptedSizes = []; - const bidReceivedForThisAdUnit = auctionEndEvent.bidsReceived.find((bidReceived) => { - return adUnit.code === bidReceived.adUnitCode; - }); + const impression = {}; + impression.id = adUnit.code; + impression.adType = 'unknown'; + impression.acceptedSizes = []; + const bidReceivedForThisAdUnit = auctionEndEvent.bidsReceived.find((bidReceived) => { + return adUnit.code === bidReceived.adUnitCode; + }); if (adUnit.mediaTypes) { - let acceptedSizes = []; - if (adUnit.mediaTypes.banner) { - buildAdTypeDependentFieldsForImpression(impression, 'banner', adUnit, bidReceivedForThisAdUnit); - } else if (adUnit.mediaTypes.video) { - buildAdTypeDependentFieldsForImpression(impression, 'video', adUnit, bidReceivedForThisAdUnit); - } + if (adUnit.mediaTypes.banner) { + buildAdTypeDependentFieldsForImpression(impression, 'banner', adUnit, bidReceivedForThisAdUnit); + } else if (adUnit.mediaTypes.video) { + buildAdTypeDependentFieldsForImpression(impression, 'video', adUnit, bidReceivedForThisAdUnit); } - return impression; - }); + } + return impression; + }); } function buildAdTypeDependentFieldsForImpression(impression, adType, adUnit, bidReceivedForThisAdUnit) { @@ -242,23 +253,6 @@ function getPlatformType() { // } // }; -function createAuctionImpression(bidWonEvent) { - return { - tagid: bidWonEvent.adUnitCode, - displaymanager: null, - displaymanagerver: null, - secure: null, - bidfloor: null, - banner: { - w: bidWonEvent.width, - h: bidWonEvent.height, - pos: null, - expandable: [], - api: [] - } - } -}; - export function reportClickEvent(event) { let link = event.currentTarget.getElementsByTagName('a')[0]; let clickUrl; @@ -563,18 +557,6 @@ export function ExpiringQueue(sendImpressions, sendAuction, ttl, log) { } }; -function assignBidWonStatusToResponse(wonBid) { - let wonBidId = wonBid.adId; - rivrAnalytics.context.auctionObject.bidResponses.forEach((response) => { - if (response.seatbid.length > 0) { - let bidObjectResponse = response.seatbid[0].bid[0]; - if (wonBidId === bidObjectResponse.adid) { - bidObjectResponse.status = 1 - } - } - }); -}; - function removeEmptyProperties(obj) { Object.keys(obj).forEach(function(key) { if (obj[key] && typeof obj[key] === 'object') removeEmptyProperties(obj[key]) diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index ab710f44ebb..1fa206815b1 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -248,29 +248,21 @@ describe('RIVR Analytics adapter', () => { expect(responses[1].total_duration).to.be.eql(null); }); - it('Firing BID_WON when it happens after BID_RESPONSE should add won event as auction impression to imp array', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); + it.only('Firing BID_WON should set to 1 the status of the corresponding bid', () => { + analyticsAdapter.context.auctionObject = utils.deepClone(AUCTION_OBJECT_AFTER_AUCTION_END_MOCK); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_WON_MOCK); - const wonEvent = analyticsAdapter.context.auctionObject.imp; - - expect(wonEvent.length).to.be.eql(1); - expect(wonEvent[0]).to.be.eql({ - tagid: 'container-1', - displaymanager: null, - displaymanagerver: null, - secure: null, - bidfloor: null, - banner: { - w: 300, - h: 250, - pos: null, - expandable: [], - api: [] - } - }); + expect(analyticsAdapter.context.auctionObject.bidders.length).to.be.equal(3); + + console.log('analyticsAdapter.context.auctionObject.bidders[0]', analyticsAdapter.context.auctionObject.bidders[0]); + + expect(analyticsAdapter.context.auctionObject.bidders[0].bids[0].status).to.be.equal(0); + + expect(analyticsAdapter.context.auctionObject.bidders[1].bids[0].status).to.be.equal(0); + + expect(analyticsAdapter.context.auctionObject.bidders[2].bids[0].status).to.be.equal(1); + expect(analyticsAdapter.context.auctionObject.bidders[2].bids[1].status).to.be.equal(0); }); it('Firing BID_WON when it happens after BID_RESPONSE should change the status of winning bidResponse to 1', () => { @@ -604,7 +596,7 @@ describe('RIVR Analytics adapter', () => { expect(result[2].bids[1].impId).to.be.eql('/19968336/header-bid-tag-1'); }); - it.only('trackAuctionEnd(), populates the impressions array from adUnits', () => { + it('trackAuctionEnd(), populates the impressions array from adUnits', () => { trackAuctionEnd(AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); const result = analyticsAdapter.context.auctionObject.impressions; @@ -619,9 +611,9 @@ describe('RIVR Analytics adapter', () => { expect(result[1].acceptedSizes).to.be.eql([{w: 728, h: 90}, {w: 970, h: 250}]); expect(result[1].banner).to.be.eql({w: 300, h: 250}); - // expect(result[2].id).to.be.eql('video'); - // expect(result[2].adType).to.be.eql('video'); - // expect(result[2].acceptedSizes).to.be.eql([{w: 640, h: 360}, {w: 640, h: 480}]); + expect(result[2].id).to.be.eql('video'); + expect(result[2].adType).to.be.eql('video'); + expect(result[2].acceptedSizes).to.be.eql([{w: 640, h: 360}, {w: 640, h: 480}]); }); const BANNER_AD_UNITS_MOCK = [ @@ -757,6 +749,53 @@ describe('RIVR Analytics adapter', () => { size: '300x250' }; + const BID_WON_MOCK = { + bidderCode: 'appnexus', + width: 300, + height: 600, + statusMessage: 'Bid available', + adId: '63301dc59deb3b', + mediaType: 'banner', + source: 'client', + requestId: '63301dc59deb3b', + cpm: 0.5, + creativeId: 98493581, + currency: 'USD', + netRevenue: true, + ttl: 300, + appnexus: { + buyerMemberId: 9325 + }, + ad: '...HTML CONTAINING THE AD...', + auctionId: '1825871c-b4c2-401a-b219-64549d412495', + responseTimestamp: 1540560447955, + requestTimestamp: 1540560447622, + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-0', + timeToRespond: 333, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '', + size: '300x600', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '63301dc59deb3b', + hb_pb: '0.50', + hb_size: '300x600', + hb_source: 'client', + hb_format: 'banner' + }, + status: 'rendered', + params: [ + { + placementId: 13144370 + } + ] + }; + const CONTEXT_AFTER_AUCTION_INIT = { host: TRACKER_BASE_URL_MOCK, pubId: PUBLISHER_ID_MOCK, @@ -807,16 +846,16 @@ describe('RIVR Analytics adapter', () => { }; const AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK = { - auctionId:'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', - auctionStart:1540560217395, - auctionEnd:1540560217703, - auctionStatus:'completed', - adUnits:[ + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart: 1540560217395, + auctionEnd: 1540560217703, + auctionStatus: 'completed', + adUnits: [ { - code:'/19968336/header-bid-tag-0', - mediaTypes:{ - banner:{ - sizes:[ + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: [ [ 300, 250 @@ -828,19 +867,19 @@ describe('RIVR Analytics adapter', () => { ] } }, - bids:[ + bids: [ { - bidder:'appnexus', - params:{ - placementId:13144370 + bidder: 'appnexus', + params: { + placementId: 13144370 }, - crumbs:{ - pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' } } ], - transactionId:'aee9bf8d-6d8f-425b-a42a-52c875371ebc', - sizes:[ + transactionId: 'aee9bf8d-6d8f-425b-a42a-52c875371ebc', + sizes: [ [ 300, 250 @@ -852,10 +891,10 @@ describe('RIVR Analytics adapter', () => { ] }, { - code:'/19968336/header-bid-tag-1', - mediaTypes:{ - banner:{ - sizes:[ + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [ [ 728, 90 @@ -867,19 +906,19 @@ describe('RIVR Analytics adapter', () => { ] } }, - bids:[ + bids: [ { - bidder:'appnexus', - params:{ - placementId:13144370 + bidder: 'appnexus', + params: { + placementId: 13144370 }, - crumbs:{ - pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' } } ], - transactionId:'3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', - sizes:[ + transactionId: '3d5f0f89-e9cd-4714-b314-3f0fb7fcf8e3', + sizes: [ [ 728, 90 @@ -891,11 +930,11 @@ describe('RIVR Analytics adapter', () => { ] }, { - code:'video', - mediaTypes:{ - video:{ - context:'outstream', - sizes:[ + code: 'video', + mediaTypes: { + video: { + context: 'outstream', + sizes: [ [ 640, 360 @@ -907,85 +946,140 @@ describe('RIVR Analytics adapter', () => { ] } }, - bids:[ + bids: [ { - bidder:'vuble', - params:{ - env:'net', - pubId:'18', - zoneId:'12345', - referrer:'http://www.vuble.tv/', - floorPrice:5 + bidder: 'vuble', + params: { + env: 'net', + pubId: '18', + zoneId: '12345', + referrer: 'http: //www.vuble.tv/', + floorPrice: 5 }, - crumbs:{ - pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' } }, { - bidder:'vertamedia', - params:{ - aid:331133 + bidder: 'vertamedia', + params: { + aid: 331133 }, - crumbs:{ - pubcid:'87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' + crumbs: { + pubcid: '87eb6b0e-e1a8-42a9-b58d-e93a382e2d9b' } } ], - transactionId:'df11a105-4eef-4ceb-bbc3-a49224f7c49d' + transactionId: 'df11a105-4eef-4ceb-bbc3-a49224f7c49d' } ], - adUnitCodes:[ + adUnitCodes: [ '/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', 'video' ], bidderRequests: [], - bidsReceived:[ + bidsReceived: [ { - bidderCode:'appnexus', - width:300, - height:250, - statusMessage:'Bid available', - adId:'6de82e80757293', - mediaType:'banner', - source:'client', - requestId:'6de82e80757293', - cpm:0.5, - creativeId:96846035, - currency:'USD', - netRevenue:true, - ttl:300, - appnexus:{ - buyerMemberId:9325 + bidderCode: 'appnexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '6de82e80757293', + mediaType: 'banner', + source: 'client', + requestId: '6de82e80757293', + cpm: 0.5, + creativeId: 96846035, + currency: 'USD', + netRevenue: true, + ttl: 300, + appnexus: { + buyerMemberId: 9325 }, - ad:'...HTML CONTAINING THE AD...', - auctionId:'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', - responseTimestamp:1540560217636, - requestTimestamp:1540560217403, - bidder:'appnexus', - adUnitCode:'/19968336/header-bid-tag-1', - timeToRespond:233, - pbLg:'0.50', - pbMg:'0.50', - pbHg:'0.50', - pbAg:'0.50', - pbDg:'0.50', - pbCg:'', - size:'728x90', - adserverTargeting:{ - hb_bidder:'appnexus', - hb_adid:'7e1a45d85bd57c', - hb_pb:'0.50', - hb_size:'728x90', - hb_source:'client', - hb_format:'banner' + ad: '...HTML CONTAINING THE AD...', + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + responseTimestamp: 1540560217636, + requestTimestamp: 1540560217403, + bidder: 'appnexus', + adUnitCode: '/19968336/header-bid-tag-1', + timeToRespond: 233, + pbLg: '0.50', + pbMg: '0.50', + pbHg: '0.50', + pbAg: '0.50', + pbDg: '0.50', + pbCg: '', + size: '728x90', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '7e1a45d85bd57c', + hb_pb: '0.50', + hb_size: '728x90', + hb_source: 'client', + hb_format: 'banner' } } ], - winningBids:[ + winningBids: [], + timeout: 3000 + }; + const AUCTION_OBJECT_AFTER_AUCTION_END_MOCK = { + bidders: [ + { + id: 'vuble', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: 'video', + price: 0, + status: 0 + } + ] + }, + { + id: 'vertamedia', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: 'video', + price: 0, + status: 0 + } + ] + }, + { + id: 'appnexus', + bids: [ + { + adomain: [ + '' + ], + clearPrice: 0, + impId: '/19968336/header-bid-tag-0', + price: 0.5, + status: 0 + }, + { + adomain: [ + '' + ], + clearPrice: 0, + impId: '/19968336/header-bid-tag-1', + price: 0.7, + status: 0 + } + ] + } ], - timeout:3000 + impressions: [] }; const AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK = { @@ -993,7 +1087,7 @@ describe('RIVR Analytics adapter', () => { auctionStart: 1540560217395, auctionEnd: 1540560217703, auctionStatus: 'completed', - bidders: [], + adUnits: [], adUnitCodes: [ '/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', @@ -1133,9 +1227,7 @@ describe('RIVR Analytics adapter', () => { adUnitCode: '/19968336/header-bid-tag-1', } ], - winningBids: [ - - ], + winningBids: [], timeout: 3000 }; }); From 924d6835cd4d5ffc775ac20e402f3db7b0b4afe2 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 12:41:04 +0100 Subject: [PATCH 56/62] RVR-2005 - cleanup --- modules/rivrAnalyticsAdapter.js | 110 -------------------------------- 1 file changed, 110 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index cba0d62fc36..c4683c83f57 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -29,14 +29,6 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { } handler = trackAuctionInit; break; - // TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] - // case CONSTANTS.EVENTS.BID_REQUESTED: - // handler = trackBidRequest; - // break; - // TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] - // case CONSTANTS.EVENTS.BID_RESPONSE: - // handler = trackBidResponse; - // break; case CONSTANTS.EVENTS.BID_WON: handler = trackBidWon; break; @@ -100,18 +92,6 @@ function trackAuctionInit(args) { rivrAnalytics.context.auctionObject.id = args.auctionId; }; -// TODO remove this. args object will be also found in AUCTION_END event -> args.bidderRequests[i] -// function trackBidRequest(args) { -// let bidRequest = args; -// rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); -// }; - -// TODO remove this. args object will be also found in AUCTION_END event -> args.bidsReceived[i] -// function trackBidResponse(args) { -// let bidResponse = createBidResponse(args); -// rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); -// }; - function trackBidWon(args) { setWinningBidStatus(args); }; @@ -224,35 +204,6 @@ function getPlatformType() { } }; -// function createBidResponse(bidResponseEvent) { -// return { -// timestamp: bidResponseEvent.responseTimestamp, -// status: bidResponseEvent.getStatusCode(), -// total_duration: bidResponseEvent.timeToRespond, -// bidderId: null, -// bidder_name: bidResponseEvent.bidder, -// cur: bidResponseEvent.currency, -// seatbid: [ -// { -// seat: null, -// bid: [ -// { -// status: 2, -// clear_price: bidResponseEvent.cpm, -// attr: [], -// crid: bidResponseEvent.creativeId, -// cid: null, -// id: null, -// adid: bidResponseEvent.adId, -// adomain: [], -// iurl: null -// } -// ] -// } -// ] -// } -// }; - export function reportClickEvent(event) { let link = event.currentTarget.getElementsByTagName('a')[0]; let clickUrl; @@ -398,67 +349,6 @@ export function createNewAuctionObject() { } return auction; - - // let newAuction = { - // id: null, - // timestamp: null, - // at: null, - // bcat: [], - // imp: [], - // app: { - // id: null, - // name: null, - // domain: window.location.href, - // bundle: null, - // cat: [], - // publisher: { - // id: null, - // name: null - // } - // }, - // site: { - // id: null, - // name: null, - // domain: window.location.href, - // cat: [], - // publisher: { - // id: null, - // name: null - // } - // }, - // device: { - // geo: { - // city: null, - // country: null, - // region: null, - // zip: null, - // type: null, - // metro: null - // }, - // devicetype: getPlatformType(), - // osv: null, - // os: null, - // model: null, - // make: null, - // carrier: null, - // ip: null, - // didsha1: null, - // dpidmd5: null, - // ext: { - // uid: null - // } - // }, - // user: { - // id: null, - // yob: null, - // gender: null, - // }, - // bidResponses: [], - // bidRequests: [], - // 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', - // modelVersion: localStorage.getItem('rivr_model_version') || null, - // 'ext.rivr.originalvalues': [] - // }; }; export function saveUnoptimisedAdUnits() { From 6252ac2c2c88a6366c2c779d7f12b70df81e863f Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 12:46:48 +0100 Subject: [PATCH 57/62] RVR-2005 - adapt enableAnalytics() test --- .../spec/modules/rivrAnalyticsAdapter_spec.js | 83 +------------------ 1 file changed, 4 insertions(+), 79 deletions(-) diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 1fa206815b1..ac12a9c5a2b 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -23,7 +23,6 @@ const events = require('../../../src/events'); describe('RIVR Analytics adapter', () => { const EXPIRING_QUEUE_TIMEOUT = 4000; const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; - const PUBLISHER_ID_MOCK = 777; const RVR_CLIENT_ID_MOCK = 'aCliendId'; const SITE_CATEGORIES_MOCK = ['cat1', 'cat2']; const EMITTED_AUCTION_ID = 1; @@ -50,7 +49,6 @@ describe('RIVR Analytics adapter', () => { provider: 'rivr', options: { clientID: RVR_CLIENT_ID_MOCK, - pubId: PUBLISHER_ID_MOCK, adUnits: [utils.deepClone(BANNER_AD_UNITS_MOCK)], siteCategories: SITE_CATEGORIES_MOCK, } @@ -97,11 +95,11 @@ describe('RIVR Analytics adapter', () => { timer.tick(50); }); - it('enableAnalytics - should configure host and pubId in adapter context', () => { + it.only('enableAnalytics - should configure host and clientID in adapter context', () => { // adaptermanager.enableAnalytics() is called in beforeEach. If only called here it doesn't seem to work. expect(analyticsAdapter.context).to.have.property('host', TRACKER_BASE_URL_MOCK); - expect(analyticsAdapter.context).to.have.property('pubId', PUBLISHER_ID_MOCK); + expect(analyticsAdapter.context).to.have.property('clientID', RVR_CLIENT_ID_MOCK); }); it('enableAnalytics - should set a cookie containing a user id', () => { @@ -150,79 +148,6 @@ describe('RIVR Analytics adapter', () => { expect(auctionObject3['modelVersion']).to.be.eql(null); }); - it('Firing BID_REQUESTED it sets app and site publisher id in auction object', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - - const sitePubcid = analyticsAdapter.context.auctionObject.site.publisher.id; - const appPubcid = analyticsAdapter.context.auctionObject.app.publisher.id; - expect(sitePubcid).to.be.eql(PUBLISHER_ID_MOCK); - expect(appPubcid).to.be.eql(PUBLISHER_ID_MOCK); - }); - - it('Firing BID_REQUESTED it adds bid request in bid requests array', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - - const requestEvent = analyticsAdapter.context.auctionObject.bidRequests; - expect(requestEvent).to.have.length(1); - expect(requestEvent[0]).to.be.eql({ - bidderCode: 'adapter', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: 'adapter', - params: {}, - adUnitCode: 'container-1', - transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [[300, 250]], - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }); - }); - - it('Firing BID_RESPONSE it inserts bid response object in auctionObject', () => { - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - const bidResponses = analyticsAdapter.context.auctionObject.bidResponses; - - expect(bidResponses).to.have.length(1); - expect(bidResponses[0]).to.be.eql({ - timestamp: 1509369418832, - status: 1, - 'total_duration': 443, - bidderId: null, - 'bidder_name': 'adapter', - cur: 'EU', - seatbid: [ - { - seat: null, - bid: [ - { - status: 2, - 'clear_price': 0.015, - attr: [], - crid: 999, - cid: null, - id: null, - adid: '208750227436c1', - adomain: [], - iurl: null - } - ] - } - ] - }); - }); - it('Firing AUCTION_END it sets auction time end to current time', () => { analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); @@ -248,7 +173,7 @@ describe('RIVR Analytics adapter', () => { expect(responses[1].total_duration).to.be.eql(null); }); - it.only('Firing BID_WON should set to 1 the status of the corresponding bid', () => { + it('Firing BID_WON should set to 1 the status of the corresponding bid', () => { analyticsAdapter.context.auctionObject = utils.deepClone(AUCTION_OBJECT_AFTER_AUCTION_END_MOCK); events.emit(CONSTANTS.EVENTS.BID_WON, BID_WON_MOCK); @@ -798,7 +723,7 @@ describe('RIVR Analytics adapter', () => { const CONTEXT_AFTER_AUCTION_INIT = { host: TRACKER_BASE_URL_MOCK, - pubId: PUBLISHER_ID_MOCK, + clientID: RVR_CLIENT_ID_MOCK, queue: { mockProp: 'mockValue' }, From c60d1240a6e8627ad081d100462ddc49f6d51d92 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 13:05:10 +0100 Subject: [PATCH 58/62] RVR-2005 - adapt all tests --- modules/rivrAnalyticsAdapter.js | 39 ++------------- .../spec/modules/rivrAnalyticsAdapter_spec.js | 50 +++++-------------- 2 files changed, 17 insertions(+), 72 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c4683c83f57..8fdf817a37d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -307,45 +307,16 @@ export function createNewAuctionObject() { page: window.location.pathname, categories: rivrAnalytics.context.siteCategories }, - impressions: [ - { - id: '', - adSpotId: '', - adType: '', - bidFloor: 0.0, - acceptedSizes: [ - { - h: 0, - w: 0 - } - ], - banner: { - h: 0, - w: 0, - pos: '' - }, - } - ], - bidders: [ - { - id: '', - bids: [ - { - adomain: [''], - clearPrice: 0.0, - impId: '', - price: 0.0, - status: 0 - } - ] - } - ], + impressions: [], + bidders: [], device: { userAgent: navigator.userAgent, browser: '', deviceType: getPlatformType() }, - 'ext.rivr.originalvalues': [] + 'ext.rivr.originalvalues': [], + 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', + modelVersion: localStorage.getItem('rivr_model_version') || null, } return auction; diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index ac12a9c5a2b..63ef6176870 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -95,7 +95,7 @@ describe('RIVR Analytics adapter', () => { timer.tick(50); }); - it.only('enableAnalytics - should configure host and clientID in adapter context', () => { + it('enableAnalytics - should configure host and clientID in adapter context', () => { // adaptermanager.enableAnalytics() is called in beforeEach. If only called here it doesn't seem to work. expect(analyticsAdapter.context).to.have.property('host', TRACKER_BASE_URL_MOCK); @@ -160,17 +160,13 @@ describe('RIVR Analytics adapter', () => { expect(endTime).to.be.eql(MILLIS_FROM_EPOCH_TO_NOW_MOCK); }); - it('Firing AUCTION_END when there are unresponded bid requests should insert then to bidResponses in auctionObject with null duration', () => { + it('Firing AUCTION_END populates impressions arrai in auction object', () => { analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST2); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST3); - events.emit(CONSTANTS.EVENTS.AUCTION_END, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); - const responses = analyticsAdapter.context.auctionObject.bidResponses; - expect(responses.length).to.be.eql(2); - expect(responses[0].total_duration).to.be.eql(null); - expect(responses[1].total_duration).to.be.eql(null); + const impressions = analyticsAdapter.context.auctionObject.impressions; + expect(impressions.length).to.be.eql(3); }); it('Firing BID_WON should set to 1 the status of the corresponding bid', () => { @@ -180,8 +176,6 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.bidders.length).to.be.equal(3); - console.log('analyticsAdapter.context.auctionObject.bidders[0]', analyticsAdapter.context.auctionObject.bidders[0]); - expect(analyticsAdapter.context.auctionObject.bidders[0].bids[0].status).to.be.equal(0); expect(analyticsAdapter.context.auctionObject.bidders[1].bids[0].status).to.be.equal(0); @@ -190,18 +184,6 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.bidders[2].bids[1].status).to.be.equal(0); }); - it('Firing BID_WON when it happens after BID_RESPONSE should change the status of winning bidResponse to 1', () => { - const BID_STATUS_WON = 1; - analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); - - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); - - const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; - - expect(responseWhichIsWonAlso.seatbid[0].bid[0].status).to.be.eql(BID_STATUS_WON); - }); - it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it sends the auction', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); analyticsAdapter.context.authToken = 'anAuthToken'; @@ -213,31 +195,23 @@ describe('RIVR Analytics adapter', () => { expect(ajaxStub.calledOnce).to.be.equal(true); }); - it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it clears imp, bidResponses and bidRequests', () => { + it('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it resets auctionObject', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); analyticsAdapter.context.authToken = 'anAuthToken'; - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); - let impressions = analyticsAdapter.context.auctionObject.imp; - let responses = analyticsAdapter.context.auctionObject.bidResponses; - let requests = analyticsAdapter.context.auctionObject.bidRequests; + let impressions = analyticsAdapter.context.auctionObject.impressions; - expect(impressions.length).to.be.eql(1); - expect(responses.length).to.be.eql(1); - expect(requests.length).to.be.eql(1); + expect(impressions.length).to.be.eql(3); timer.tick(EXPIRING_QUEUE_TIMEOUT + 500); - let impressionsAfterSend = analyticsAdapter.context.auctionObject.imp; - let responsesAfterSend = analyticsAdapter.context.auctionObject.bidResponses; - let requestsAfterSend = analyticsAdapter.context.auctionObject.bidRequests; + let impressionsAfterSend = analyticsAdapter.context.auctionObject.impressions; + let biddersAfterSend = analyticsAdapter.context.auctionObject.bidders; expect(impressionsAfterSend.length).to.be.eql(0); - expect(responsesAfterSend.length).to.be.eql(0); - expect(requestsAfterSend.length).to.be.eql(0); + expect(biddersAfterSend.length).to.be.eql(0); }); it('sendAuction(), when authToken is defined, it fires call clearing empty payload properties', () => { From cdbd41306aea490e98b6781549310006b0d55cf0 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 13:47:14 +0100 Subject: [PATCH 59/62] RVR-2005 - Add Rivr Analytics adapter md file --- modules/rivrAnalyticsAdapter.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 modules/rivrAnalyticsAdapter.md diff --git a/modules/rivrAnalyticsAdapter.md b/modules/rivrAnalyticsAdapter.md new file mode 100644 index 00000000000..787034e362e --- /dev/null +++ b/modules/rivrAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Rivr Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: rnd@simplaex.com + +# Description + +Analytics adapter for www.rivr.ai. + +Contact support@simplaex.com for information and support. From f38fb3bbde7e28e1ca5f32d7590d596d53561212 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 13:57:00 +0100 Subject: [PATCH 60/62] RVR-2005 - rewrite connectAllUnits --- modules/rivrAnalyticsAdapter.js | 9 +++------ test/spec/modules/rivrAnalyticsAdapter_spec.js | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 8fdf817a37d..c99c81d7f76 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -326,7 +326,7 @@ export function saveUnoptimisedAdUnits() { let units = rivrAnalytics.context.adUnits; if (units) { if (units.length > 0) { - let allUnits = connectAllUnits(units); + let allUnits = concatAllUnits(units); allUnits.forEach((adUnit) => { adUnit.bids.forEach((bid) => { let configForBidder = fetchConfigForBidder(bid.bidder); @@ -340,11 +340,8 @@ export function saveUnoptimisedAdUnits() { } }; -export function connectAllUnits(units) { - return units.reduce((acc, units) => { - units.forEach((unit) => acc.push(unit)) - return acc - }, []); +export function concatAllUnits(units) { + return Array.prototype.concat.apply([], units); } export function createUnOptimisedParamsField(bid, config) { diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 63ef6176870..770fc3709a3 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -10,7 +10,7 @@ import { pinHandlerToHTMLElement, setAuctionAbjectPosition, createNewAuctionObject, - connectAllUnits, + concatAllUnits, trackAuctionEnd, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; @@ -465,10 +465,10 @@ describe('RIVR Analytics adapter', () => { expect(result.site.categories).to.be.equal(SITE_CATEGORIES_MOCK); }); - it('connectAllUnits(), returns a flattened array with all banner and video adunits', () => { + it('concatAllUnits(), returns a flattened array with all banner and video adunits', () => { const allAdUnits = [BANNER_AD_UNITS_MOCK, VIDEO_AD_UNITS_MOCK]; - const result = connectAllUnits(allAdUnits); + const result = concatAllUnits(allAdUnits); expect(result.length).to.be.eql(2); expect(result[0].code).to.be.eql('banner-container1'); From 41430e611aac85ffd00309f897b9b98d273c9466 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 14:14:57 +0100 Subject: [PATCH 61/62] RVR-2005 - correct typo --- test/spec/modules/rivrAnalyticsAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 770fc3709a3..0fc20171e0a 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -160,7 +160,7 @@ describe('RIVR Analytics adapter', () => { expect(endTime).to.be.eql(MILLIS_FROM_EPOCH_TO_NOW_MOCK); }); - it('Firing AUCTION_END populates impressions arrai in auction object', () => { + it('Firing AUCTION_END populates impressions array in auction object', () => { analyticsAdapter.context = utils.deepClone(CONTEXT_AFTER_AUCTION_INIT); events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT_WITH_AD_UNITS_AND_BID_RESPONSES_MOCK); From 88d81213ea1667d94d5ecc6973f18fe158a7d010 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 2 Nov 2018 14:20:02 +0100 Subject: [PATCH 62/62] RVR-2005 - use IE compatible find() --- modules/rivrAnalyticsAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c99c81d7f76..14143f5f21d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,5 +1,6 @@ import {ajax} from 'src/ajax'; import adapter from 'src/AnalyticsAdapter'; +import find from 'core-js/library/fn/array/find'; import CONSTANTS from 'src/constants.json'; import adaptermanager from 'src/adaptermanager'; import { logInfo, generateUUID, timestamp } from 'src/utils'; @@ -98,11 +99,11 @@ function trackBidWon(args) { function setWinningBidStatus(args) { let auctionObject = rivrAnalytics.context.auctionObject; - const bidderObjectForThisWonBid = auctionObject.bidders.find((bidder) => { + const bidderObjectForThisWonBid = find(auctionObject.bidders, (bidder) => { return bidder.id === args.bidderCode; }); if (bidderObjectForThisWonBid) { - const bidObjectForThisWonBid = bidderObjectForThisWonBid.bids.find((bid) => { + const bidObjectForThisWonBid = find(bidderObjectForThisWonBid.bids, (bid) => { return bid.impId === args.adUnitCode; }); if (bidObjectForThisWonBid) { @@ -123,7 +124,7 @@ function buildImpressionsArrayFromAuctionEnd(auctionEndEvent) { impression.id = adUnit.code; impression.adType = 'unknown'; impression.acceptedSizes = []; - const bidReceivedForThisAdUnit = auctionEndEvent.bidsReceived.find((bidReceived) => { + const bidReceivedForThisAdUnit = find(auctionEndEvent.bidsReceived, (bidReceived) => { return adUnit.code === bidReceived.adUnitCode; }); if (adUnit.mediaTypes) { @@ -158,7 +159,7 @@ function buildBiddersArrayFromAuctionEnd(auctionEndEvent) { const bidder = {}; bidder.id = bidderRequest.bidderCode; bidder.bids = bidderRequest.bids.map((bid) => { - const bidReceivedForThisRequest = auctionEndEvent.bidsReceived.find((bidReceived) => { + const bidReceivedForThisRequest = find(auctionEndEvent.bidsReceived, (bidReceived) => { return bidderRequest.bidderCode === bidReceived.bidderCode && bid.bidId === bidReceived.adId && bid.adUnitCode === bidReceived.adUnitCode;