From 6459aa5ab7c8e129d09d9adc07ff88940415efe9 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 01/30] 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 0e28aca1d5daa499ac31c4e2b47ad0103f616e83 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Mon, 14 May 2018 14:17:24 +0200 Subject: [PATCH 02/30] 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 1b6e48700b835f397955fc8d24c5d7e2c3f8275e Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 15:40:33 +0200 Subject: [PATCH 03/30] 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 e8aa2b8d2c63bdd896b1e47383915c10d549631a Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 16 May 2018 13:35:28 +0200 Subject: [PATCH 04/30] 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 0f0d7c7857fd8ba14d685441a1c084e50397ccd3 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 16:17:01 +0200 Subject: [PATCH 05/30] 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 cc95453cae678818fa03ab15dc03ddf54942b64f Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Thu, 17 May 2018 21:01:54 +0200 Subject: [PATCH 06/30] 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 4701afea09591ebd9154b8c55814190ef297d507 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Fri, 18 May 2018 12:01:49 +0200 Subject: [PATCH 07/30] 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 a6843a734b34eca4c6d6eb354843187c05838417 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Fri, 18 May 2018 14:06:08 +0200 Subject: [PATCH 08/30] 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 38197d8b9c22078f4dcca314b285b1f5ad65507f Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Tue, 22 May 2018 21:05:28 +0200 Subject: [PATCH 09/30] 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 25f87f9372b315a4ad67bd1632fac4061d0ae5db 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 10/30] 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 337d9981bda76db78e96607d379b7c8785a837b2 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 11/30] 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 9c53d297c4a670bb3cb8ca6ddfb6e84f4e1d697f 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 12/30] 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 71c81e141caae0f9f4874befabf6953af6e7bf84 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 13/30] 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 0d787846ffa2975af78a1054b9cd7b858f2b1363 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 14/30] 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 0b8c96f61667fe754b31e64e36589287900b2cd5 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 15/30] 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 ccc1aaaddbd0b34937b72034ff0c76dfa4aba5ee 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 16/30] 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 817c0e862e0a15b55443bc9829bf6a5d5ada764b Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:27:17 +0200 Subject: [PATCH 17/30] 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 cf02bbce93c40a377297980c82772be4d8f279b9 Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:35:46 +0200 Subject: [PATCH 18/30] 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 710c1a4a1f6894993ee6368cf1f7d336de6ebfee Mon Sep 17 00:00:00 2001 From: Wozniaxos Date: Wed, 13 Jun 2018 14:46:43 +0200 Subject: [PATCH 19/30] 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 4d5f827903d665bd4824ec6034930af1dc796750 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 20/30] 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 40d27ec2ec57323016b9fd027a6b14ba02c4637f Mon Sep 17 00:00:00 2001 From: adg Date: Thu, 30 Aug 2018 17:50:34 +0200 Subject: [PATCH 21/30] 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 4f48bdd39f40a76032792dceb92dece1db9f3b0b Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 31 Aug 2018 14:09:20 +0200 Subject: [PATCH 22/30] 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 4e2eef7805e45a4ab8aa1b93e97f483a423647bc Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 31 Aug 2018 14:25:06 +0200 Subject: [PATCH 23/30] 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 6cdfbef2ea5069df0760637b901baf76096c524a Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Thu, 13 Sep 2018 15:14:40 +0000 Subject: [PATCH 24/30] 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 7e2f1369b6eefa20ebf26c0d987133cf97e51a5b Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Mon, 17 Sep 2018 14:08:56 +0000 Subject: [PATCH 25/30] 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 2260902e29c369281cefbfd0640ac363495c32ca Mon Sep 17 00:00:00 2001 From: Alessandro Di Giovanni Date: Mon, 1 Oct 2018 15:14:50 +0000 Subject: [PATCH 26/30] 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 215bc0f62b10eccaf995256560b24c30df16f5dc Mon Sep 17 00:00:00 2001 From: adg Date: Wed, 17 Oct 2018 16:39:29 +0200 Subject: [PATCH 27/30] 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 144e2121d9f4ddeb6fae7c2af50297c060e87ec9 Mon Sep 17 00:00:00 2001 From: adg Date: Fri, 19 Oct 2018 14:25:31 +0200 Subject: [PATCH 28/30] 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 3fff4176fb29d76299936810b5cfa08867431a08 Mon Sep 17 00:00:00 2001 From: adg Date: Tue, 23 Oct 2018 18:31:07 +0200 Subject: [PATCH 29/30] 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 ba19ef6c11718d31968728d7d67279beba903444 Mon Sep 17 00:00:00 2001 From: AlessandroDG Date: Fri, 2 Nov 2018 14:34:08 +0100 Subject: [PATCH 30/30] Rvr 2005 rvr analytics adapter (#4) * 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 * TheMediaGrid Bid Adapter (#3204) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * 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 * updates (#3162) * Only set native targeting if value exists. (#3225) * add nolint command line option, similar to notest (#3234) * add inskin iab vendor id: enables consent via string (#3235) * 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 * 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 * 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 * Quantcast adapter onTimeout (#3239) * onTimeout WIP * test for onTimeout * some renaming * cleanup * cleanup * trying to fix the test * using ajax instead of fetch * 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 * 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 * 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 * Prebid 1.30.0 Release * increment pre version * fix deal targeting for cpm 0 (#3233) * 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 * rtbhouseBidAdapter changes (#3241) * Add transactionId support * Change site getting method * Add bidfloor param * correct user agent value population (#3248) * RVR-1124 Setup initial skeleton analytics adapter that can send something. Approved-by: Alessandro Di Giovanni * Formatted auction/events data to fit needed schema. * RVR-1135 fetched device data. * Applied feedback. * Applied feedback. * Fetched core. * Added click handler for reporting banners click events. * Applied analyzer for reporting displayed impressions. * Applied feedback. * 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 * Merged in RVR-1192-configuration-global-parameters (pull request #8) RVR-1192 Configuration/Global parameters Approved-by: Alessandro Di Giovanni * Merged in RVR-1181-Prebid-js-unit-tests-setup (pull request #6) RVR-1181 Prebid.js Unit tests setup Approved-by: Alessandro Di Giovanni * Merged in RVR-1247-additional-data-to-impression-records (pull request #9) RVR-1247 Additional data to impression records Approved-by: Alessandro Di Giovanni * 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 * 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 * RVR-1352 analytics adapter bugs Approved-by: Alessandro Di Giovanni * Fixed bug with geolocation notification. * fixed missing bracket. * one more fix. * RVR-1357 Different optimisation responses & tracking into auction event * RVR-1852 - Add content type and hardcoded auth headers (cherry picked from commit 4def881) * RVR-1852 - Change tracker host * RVR-1852 - Override content type instead of adding header * RVR-1914 Consistent data types in events Also removes undefined and null properties in audience events * 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 * Increase code coverage * Fix for IE 11.0.0 and Safari 8.0.8 - includes() Use core-js includes function for array * Restore pbjs_api_spec.js * Fix API calls for rivr analytics impressions and clicks * RVR-2005 - Change auction object model * RVR-2005 - Set rvr_usr_id cookie * RVR-2005 - Remove BID_REQUESTED and BID_RESPONSE handlers We have the same infos all collected in AUCTION_END * RVR-2005 - build Bidders Array From Auction End * RVR-2005 - build impressions Array From Auction End * RVR-2005 - set status of winning bid * RVR-2005 - cleanup * RVR-2005 - adapt enableAnalytics() test * RVR-2005 - adapt all tests * RVR-2005 - Add Rivr Analytics adapter md file * RVR-2005 - rewrite connectAllUnits * RVR-2005 - correct typo * RVR-2005 - use IE compatible find() --- modules/rivrAnalyticsAdapter.js | 322 +++----- modules/rivrAnalyticsAdapter.md | 13 + .../spec/modules/rivrAnalyticsAdapter_spec.js | 696 ++++++++++++++---- 3 files changed, 696 insertions(+), 335 deletions(-) create mode 100644 modules/rivrAnalyticsAdapter.md diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index c8616894826..14143f5f21d 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,11 +1,12 @@ import {ajax} from 'src/ajax'; -import includes from 'core-js/library/fn/array/includes'; 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 } from 'src/utils'; +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; @@ -23,18 +24,12 @@ let rivrAnalytics = Object.assign(adapter({analyticsType}), { rivrAnalytics.context.queue.init(); } if (rivrAnalytics.context.auctionObject) { - rivrAnalytics.context.auctionObject = fulfillAuctionObject(); - saveUnoptimisedParams(); + rivrAnalytics.context.auctionObject = createNewAuctionObject(); + saveUnoptimisedAdUnits(); fetchLocalization(); } 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; @@ -56,7 +51,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`, @@ -98,46 +93,91 @@ function trackAuctionInit(args) { rivrAnalytics.context.auctionObject.id = args.auctionId; }; -function trackBidRequest(args) { - setCurrentPublisherId(args); - let bidRequest = args; - rivrAnalytics.context.auctionObject.bidRequests.push(bidRequest); -}; - -function trackBidResponse(args) { - let bidResponse = createBidResponse(args); - rivrAnalytics.context.auctionObject.bidResponses.push(bidResponse); +function trackBidWon(args) { + setWinningBidStatus(args); }; -function trackBidWon(args) { +function setWinningBidStatus(args) { let auctionObject = rivrAnalytics.context.auctionObject; - let auctionImpression = createAuctionImpression(args); - auctionObject.imp.push(auctionImpression); - assignBidWonStatusToResponse(args); + const bidderObjectForThisWonBid = find(auctionObject.bidders, (bidder) => { + return bidder.id === args.bidderCode; + }); + if (bidderObjectForThisWonBid) { + const bidObjectForThisWonBid = find(bidderObjectForThisWonBid.bids, (bid) => { + return bid.impId === args.adUnitCode; + }); + if (bidObjectForThisWonBid) { + bidObjectForThisWonBid.status = 1; + } + } }; -function trackAuctionEnd(args) { +export function trackAuctionEnd(args) { rivrAnalytics.context.auctionTimeEnd = Date.now(); - fillBidResponsesOfUnrespondedBidRequests(); -}; - -function trackBidTimeout(args) { - return [args]; + rivrAnalytics.context.auctionObject.bidders = buildBiddersArrayFromAuctionEnd(args); + rivrAnalytics.context.auctionObject.impressions = buildImpressionsArrayFromAuctionEnd(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; +function buildImpressionsArrayFromAuctionEnd(auctionEndEvent) { + return auctionEndEvent.adUnits.map((adUnit) => { + const impression = {}; + impression.id = adUnit.code; + impression.adType = 'unknown'; + impression.acceptedSizes = []; + const bidReceivedForThisAdUnit = find(auctionEndEvent.bidsReceived, (bidReceived) => { + return adUnit.code === bidReceived.adUnitCode; + }); + if (adUnit.mediaTypes) { + 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 = find(auctionEndEvent.bidsReceived, (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]; }; export function fetchLocalization() { @@ -158,70 +198,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'; - } -}; - -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 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: [] - } + return 2; } }; @@ -314,75 +294,40 @@ 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: rivrAnalytics.context.userId }, site: { - id: null, - name: null, - domain: window.location.href, - cat: [], - publisher: { - id: null, - name: null - } + domain: window.location.host, + page: window.location.pathname, + categories: rivrAnalytics.context.siteCategories }, + impressions: [], + bidders: [], 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, + userAgent: navigator.userAgent, + browser: '', + deviceType: getPlatformType() }, - bidResponses: [], - bidRequests: [], + 'ext.rivr.originalvalues': [], 'ext.rivr.optimiser': localStorage.getItem('rivr_should_optimise') || 'unoptimised', modelVersion: localStorage.getItem('rivr_model_version') || null, - 'ext.rivr.originalvalues': [] - }; - return newAuction; + } + + return auction; }; -function saveUnoptimisedParams() { +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); @@ -396,11 +341,8 @@ function saveUnoptimisedParams() { } }; -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) { @@ -474,41 +416,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 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]) @@ -516,6 +423,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; @@ -527,10 +444,11 @@ rivrAnalytics.enableAnalytics = (config) => { copiedUnits = JSON.parse(stringifiedAdUnits); } rivrAnalytics.context = { + userId: getCookie(rivrUsrIdCookieKey) || storeAndReturnRivrUsrIdCookie(), host: config.options.host || DEFAULT_HOST, - pubId: config.options.pubId, 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/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. diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index f903f6e9c2d..0fc20171e0a 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -9,6 +9,9 @@ import { dataLoaderForHandler, pinHandlerToHTMLElement, setAuctionAbjectPosition, + createNewAuctionObject, + concatAllUnits, + trackAuctionEnd, } from 'modules/rivrAnalyticsAdapter'; import {expect} from 'chai'; import adaptermanager from 'src/adaptermanager'; @@ -20,9 +23,11 @@ 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; 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; @@ -43,8 +48,9 @@ describe('RIVR Analytics adapter', () => { adaptermanager.enableAnalytics({ provider: 'rivr', options: { - pubId: PUBLISHER_ID_MOCK, - adUnits: [utils.deepClone(AD_UNITS_MOCK)] + clientID: RVR_CLIENT_ID_MOCK, + adUnits: [utils.deepClone(BANNER_AD_UNITS_MOCK)], + siteCategories: SITE_CATEGORIES_MOCK, } }); }); @@ -89,11 +95,15 @@ describe('RIVR Analytics adapter', () => { timer.tick(50); }); - it('enableAnalytics - should configure host and pubId 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); - 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', () => { + 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', () => { @@ -138,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); @@ -223,54 +160,28 @@ 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 array 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 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('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); - - 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: [] - } - }); - }); + events.emit(CONSTANTS.EVENTS.BID_WON, BID_WON_MOCK); - 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); + expect(analyticsAdapter.context.auctionObject.bidders.length).to.be.equal(3); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, BID_RESPONSE_MOCK); - events.emit(CONSTANTS.EVENTS.BID_WON, BID_RESPONSE_MOCK); + expect(analyticsAdapter.context.auctionObject.bidders[0].bids[0].status).to.be.equal(0); - const responseWhichIsWonAlso = analyticsAdapter.context.auctionObject.bidResponses[0]; + expect(analyticsAdapter.context.auctionObject.bidders[1].bids[0].status).to.be.equal(0); - expect(responseWhichIsWonAlso.seatbid[0].bid[0].status).to.be.eql(BID_STATUS_WON); + 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('when auction is initialized and authToken is defined and ExpiringQueue ttl expires, it sends the auction', () => { @@ -284,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', () => { @@ -547,7 +450,72 @@ describe('RIVR Analytics adapter', () => { expect(analyticsAdapter.context.auctionObject.device.geo.lat).to.be.equal('aLatitude'); }); - const AD_UNITS_MOCK = [ + it('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'); + expect(result.site.categories).to.be.equal(SITE_CATEGORIES_MOCK); + }); + + it('concatAllUnits(), returns a flattened array with all banner and video adunits', () => { + const allAdUnits = [BANNER_AD_UNITS_MOCK, VIDEO_AD_UNITS_MOCK]; + + const result = concatAllUnits(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'); + }); + + 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; + + 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'); + }); + + 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; + + 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', mediaTypes: { @@ -573,6 +541,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', @@ -651,9 +648,56 @@ 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, + clientID: RVR_CLIENT_ID_MOCK, queue: { mockProp: 'mockValue' }, @@ -699,4 +743,390 @@ describe('RIVR Analytics adapter', () => { 'ext.rivr.originalvalues': [] } }; + + 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_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 + } + ] + } + ], + impressions: [] + }; + + const AUCTION_END_EVENT_WITH_BID_REQUESTS_AND_BID_RESPONSES_MOCK = { + auctionId: 'f6c1d093-14a3-4ade-bc7d-1de37e7cbdb2', + auctionStart: 1540560217395, + auctionEnd: 1540560217703, + auctionStatus: 'completed', + adUnits: [], + 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 + }; });