From a4341461aad6453e125cdf95c47ff343049234ae Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 24 Feb 2020 20:11:02 -0800 Subject: [PATCH 01/32] OpenX Analytics Adapter: initial commit --- modules/openxAnalyticsAdapter.js | 617 +++++++++++++++++++++---------- 1 file changed, 412 insertions(+), 205 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 7addfe68bc6..8c54f9a60f0 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,257 +1,464 @@ -import adapter from '../src/AnalyticsAdapter.js'; +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.js'; -import { config } from '../src/config.js'; -import { ajax } from '../src/ajax.js'; -import * as utils from '../src/utils.js'; - -const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } -} = CONSTANTS; - -const SLOT_LOADED = 'slotOnload'; - -const ENDPOINT = 'https://ads.openx.net/w/1.0/pban'; - -let initOptions; +import adapterManager from '../src/adapterManager'; +import { type } from 'os'; + +const zlib = require('zlib'); +const utils = require('../src/utils'); + +const urlParam = ''; +const analyticsType = 'endpoint'; + +const MAX_RETRIES = 2; +const MAX_TIMEOUT = 10000; +const AUCTION_END_WAIT_TIME = 2000; + +const auctionInitConst = CONSTANTS.EVENTS.AUCTION_INIT; +const auctionEndConst = CONSTANTS.EVENTS.AUCTION_END; +const bidWonConst = CONSTANTS.EVENTS.BID_WON; +const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; +const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; +const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; +const bidTimeoutConst = CONSTANTS.EVENTS.BID_TIMEOUT; + +let initOptions = { + publisherPlatformId: '', + publisherAccountId: -1, + utmTagData: [], + adUnits: [] +}; +let bidWon = { options: {}, events: [] }; +let eventStack = { options: {}, events: [] }; + +let auctionStatus = 'not_started'; + +let localStoragePrefix = 'openx_analytics_'; +let utmTags = [ + 'utm_source', + 'utm_medium', + 'utm_campaign', + 'utm_term', + 'utm_content' +]; +let utmTimeoutKey = 'utm_timeout'; +let utmTimeout = 60 * 60 * 1000; +let sessionTimeout = 60 * 60 * 1000; +let sessionIdStorageKey = 'session_id'; +let sessionTimeoutKey = 'session_timeout'; + +function getParameterByName(param) { + let vars = {}; + window.location.href + .replace(location.hash, '') + .replace(/[?&]+([^=&]+)=?([^&]*)?/gi, function(m, key, value) { + vars[key] = value !== undefined ? value : ''; + }); -let auctionMap = {}; + return vars[param] ? vars[param] : ''; +} -function onAuctionInit({ auctionId }) { - auctionMap[auctionId] = { - adUnitMap: {} - }; +function buildSessionIdLocalStorageKey() { + return localStoragePrefix.concat(sessionIdStorageKey); } -function onBidRequested({ auctionId, auctionStart, bids, start }) { - const adUnitMap = auctionMap[auctionId]['adUnitMap']; +function buildSessionIdTimeoutLocalStorageKey() { + return localStoragePrefix.concat(sessionTimeoutKey); +} - bids.forEach(bid => { - const { adUnitCode, bidId, bidder, params, transactionId } = bid; +function updateSessionId() { + if (isSessionIdTimeoutExpired()) { + let newSessionId = utils.generateUUID(); + localStorage.setItem(buildSessionIdLocalStorageKey(), newSessionId); + } + initOptions.sessionId = getSessionId(); + updateSessionIdTimeout(); +} - adUnitMap[adUnitCode] = adUnitMap[adUnitCode] || { - auctionId, - auctionStart, - transactionId, - bidMap: {} - }; +function updateSessionIdTimeout() { + localStorage.setItem(buildSessionIdTimeoutLocalStorageKey(), Date.now()); +} - adUnitMap[adUnitCode]['bidMap'][bidId] = { - bidder, - params, - requestTimestamp: start - }; - }); +function isSessionIdTimeoutExpired() { + let cpmSessionTimestamp = localStorage.getItem( + buildSessionIdTimeoutLocalStorageKey() + ); + return Date.now() - cpmSessionTimestamp > sessionTimeout; } -function onBidResponse({ - auctionId, - adUnitCode, - requestId: bidId, - cpm, - creativeId, - responseTimestamp, - ts, - adId -}) { - const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode]; - const bid = adUnit['bidMap'][bidId]; - bid.cpm = cpm; - bid.creativeId = creativeId; - bid.responseTimestamp = responseTimestamp; - bid.ts = ts; - bid.adId = adId; +function getSessionId() { + return localStorage.getItem(buildSessionIdLocalStorageKey()) + ? localStorage.getItem(buildSessionIdLocalStorageKey()) + : ''; } -function onBidTimeout(args) { - utils - ._map(args, value => value) - .forEach(({ auctionId, adUnitCode, bidId }) => { - const bid = - auctionMap[auctionId]['adUnitMap'][adUnitCode]['bidMap'][bidId]; - bid.timedOut = true; - }); +function updateUtmTimeout() { + localStorage.setItem(buildUtmLocalStorageTimeoutKey(), Date.now()); } -function onBidWon({ auctionId, adUnitCode, requestId: bidId }) { - const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode]; - const bid = adUnit['bidMap'][bidId]; - bid.won = true; +function isUtmTimeoutExpired() { + let utmTimestamp = localStorage.getItem(buildUtmLocalStorageTimeoutKey()); + return Date.now() - utmTimestamp > utmTimeout; } -function onSlotLoaded({ slot }) { - const targeting = slot.getTargetingKeys().reduce((targeting, key) => { - targeting[key] = slot.getTargeting(key); - return targeting; - }, {}); - utils.logMessage( - 'GPT slot is loaded. Current targeting set on slot:', - targeting - ); +function buildUtmLocalStorageTimeoutKey() { + return localStoragePrefix.concat(utmTimeoutKey); +} - const adId = slot.getTargeting('hb_adid')[0]; - if (!adId) { - return; +function buildUtmLocalStorageKey(utmMarkKey) { + return localStoragePrefix.concat(utmMarkKey); +} +function checkPublisherPlatformId() { + if (initOptions.publisherPlatformId !== undefined) { + if (typeof initOptions.publisherPlatformId === 'string') { + if (initOptions.publisherPlatformId !== '') { + return initOptions.publisherPlatformId; + } else { + utils.logError('SOX: Invalid PublisherPlatformId'); + return null; + } + } else { + utils.logError('SOX: Invalid datatype for PublisherPlatformId'); + return null; + } + } else { + utils.logError('SOX : PublisherPlatformId not defined'); + return null; } - - const adUnit = getAdUnitByAdId(adId); - if (!adUnit) { - return; +} +function checkPublisherAccountId() { + if (initOptions.publisherAccountId !== undefined) { + if (typeof initOptions.publisherAccountId === 'number') { + if (initOptions.publisherAccountId > -1) { + return initOptions.publisherAccountId; + } else { + utils.logError('SOX: Invalid PublisherAccountId'); + return null; + } + } else { + utils.logError('SOX: Invalid datatype for PublisherAccountId'); + return null; + } + } else { + utils.logError('SOX : PublisherAccountId not defined'); + return null; } +} - const adUnitData = getAdUnitData(adUnit); - const performanceData = getPerformanceData(adUnit.auctionStart); - const commonFields = { - 'hb.asiid': slot.getAdUnitPath(), - 'hb.cur': config.getConfig('currency.adServerCurrency'), - 'hb.pubid': initOptions.publisherId - }; +function checkInitOptions() { + let publisherPlatformId = checkPublisherPlatformId(); + let publisherAccountId = checkPublisherAccountId(); + if (publisherPlatformId && publisherAccountId) { + return true; + } + return false; +} - const data = Object.assign({}, adUnitData, performanceData, commonFields); - sendEvent(data); +function checkAdUnitConfig() { + if (typeof initOptions.adUnits === 'undefined') { + return false; + } + return initOptions.adUnits.length > 0; } -function getAdUnitByAdId(adId) { - let result; +function buildEventStack() { + eventStack.options = initOptions; +} - utils._map(auctionMap, value => value).forEach(auction => { - utils._map(auction.adUnitMap, value => value).forEach(adUnit => { - utils._map(adUnit.bidMap, value => value).forEach(bid => { - if (adId === bid.adId) { - result = adUnit; - } - }) - }); +function filterBidsByAdUnit(bids) { + var filteredBids = []; + bids.forEach(function(bid) { + if (includes(initOptions.adUnits, bid.placementCode)) { + filteredBids.push(bid); + } }); - - return result; + return filteredBids; } -function getAdUnitData(adUnit) { - const bids = utils._map(adUnit.bidMap, value => value); - const bidders = bids.map(bid => bid.bidder); - const requestTimes = bids.map( - bid => bid.requestTimestamp && bid.requestTimestamp - adUnit.auctionStart - ); - const responseTimes = bids.map( - bid => bid.responseTimestamp && bid.responseTimestamp - adUnit.auctionStart - ); - const bidValues = bids.map(bid => bid.cpm || 0); - const timeouts = bids.map(bid => !!bid.timedOut); - const creativeIds = bids.map(bid => bid.creativeId); - const winningBid = bids.filter(bid => bid.won)[0]; - const winningExchangeIndex = bids.indexOf(winningBid); - const openxBid = bids.filter(bid => bid.bidder === 'openx')[0]; - - return { - 'hb.ct': adUnit.auctionStart, - 'hb.rid': adUnit.auctionId, - 'hb.exn': bidders.join(','), - 'hb.sts': requestTimes.join(','), - 'hb.ets': responseTimes.join(','), - 'hb.bv': bidValues.join(','), - 'hb.to': timeouts.join(','), - 'hb.crid': creativeIds.join(','), - 'hb.we': winningExchangeIndex, - 'hb.g1': winningExchangeIndex === -1, - dddid: adUnit.transactionId, - ts: openxBid && openxBid.ts, - auid: openxBid && openxBid.params && openxBid.params.unit - }; +function isValidEvent(eventType, adUnitCode) { + if (checkAdUnitConfig()) { + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; + if ( + !includes(initOptions.adUnits, adUnitCode) && + includes(validationEvents, eventType) + ) { + return false; + } + } + return true; } -function getPerformanceData(auctionStart) { - let timing; - try { - timing = window.top.performance.timing; - } catch (e) {} +function isValidEventStack() { + if (eventStack.events.length > 0) { + return eventStack.events.some(function(event) { + return ( + bidRequestConst === event.eventType || bidWonConst === event.eventType + ); + }); + } + return false; +} - if (!timing) { - return; +function removeads(info) { + if (info && info.bidsReceived) { + let newInfo = JSON.parse(JSON.stringify(info)); + let bidsReceivedArray = newInfo.bidsReceived; + for (var index = 0; index < bidsReceivedArray.length; index++) { + if (bidsReceivedArray[index].ad !== undefined) { + bidsReceivedArray[index].ad = ''; + } + } + newInfo.bidsReceived = bidsReceivedArray; + return newInfo; + } else { + return info; } +} - const { fetchStart, domContentLoadedEventEnd, loadEventEnd } = timing; - const domContentLoadTime = domContentLoadedEventEnd - fetchStart; - const pageLoadTime = loadEventEnd - fetchStart; - const timeToAuction = auctionStart - fetchStart; - const timeToRender = Date.now() - fetchStart; +let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { + track({ eventType, args }) { + if (!checkInitOptions()) { + send(eventType, {}, null, null); + return; + } - return { - 'hb.dcl': domContentLoadTime, - 'hb.dl': pageLoadTime, - 'hb.tta': timeToAuction, - 'hb.ttr': timeToRender - }; -} + let info = Object.assign({}, args); -function sendEvent(data) { - utils._map(data, (value, key) => [key, value]).forEach(([key, value]) => { - if ( - value === undefined || - value === null || - (typeof value === 'number' && isNaN(value)) - ) { - delete data[key]; + if (info && info.ad) { + info.ad = ''; } - }); - ajax(ENDPOINT, null, data, { method: 'GET' }); -} -let googletag = window.googletag || {}; -googletag.cmd = googletag.cmd || []; -googletag.cmd.push(function() { - googletag.pubads().addEventListener(SLOT_LOADED, args => { - openxAdapter.track({ eventType: SLOT_LOADED, args }); - }); -}); + if (eventType === auctionInitConst) { + auctionStatus = 'started'; + } -const openxAdapter = Object.assign( - adapter({ url: ENDPOINT, analyticsType: 'endpoint' }), - { - track({ eventType, args }) { - utils.logMessage(eventType, Object.assign({}, args)); - switch (eventType) { - case AUCTION_INIT: - onAuctionInit(args); - break; - case BID_REQUESTED: - onBidRequested(args); - break; - case BID_RESPONSE: - onBidResponse(args); - break; - case BID_TIMEOUT: - onBidTimeout(args); - break; - case BID_WON: - onBidWon(args); - break; - case SLOT_LOADED: - onSlotLoaded(args); - break; + if (eventType === bidWonConst && auctionStatus === 'not_started') { + pushEvent(eventType, info); + utils.logInfo('SOX:Bid won called... '); + return; + } + + if (eventType === auctionEndConst) { + pushEvent(eventType, removeads(info)); + utils.logInfo('SOX:Auction end called... '); + updateSessionId(); + buildEventStack(); + if (isValidEventStack()) { + auctionStatus = 'not_started'; + setTimeout(function() { + let publisherPlatformId = eventStack.options.publisherPlatformId; + let publisherAccountId = eventStack.options.publisherAccountId; + send(eventType, eventStack, publisherPlatformId, publisherAccountId); + }, AUCTION_END_WAIT_TIME); } + } else if (eventType === bidRequestConst || eventType === bidTimeoutConst) { + pushEvent(eventType, info); } } -); +}); -// save the base class function openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; -// override enableAnalytics so we can get access to the config passed in from the page openxAdapter.enableAnalytics = function(config) { - if (!config || !config.options || !config.options.publisherId) { - utils.logError('OpenX analytics adapter: publisherId is required.'); - return; - } initOptions = config.options; - openxAdapter.originEnableAnalytics(config); // call the base class function + initOptions.utmTagData = this.buildUtmTagData(); + utils.logInfo('OpenX Analytics enabled with config', initOptions); + openxAdapter.originEnableAnalytics(config); }; -// reset the cache for unit tests -openxAdapter.reset = function() { - auctionMap = {}; +openxAdapter.buildUtmTagData = function() { + let utmTagData = {}; + let utmTagsDetected = false; + utmTags.forEach(function(utmTagKey) { + let utmTagValue = getParameterByName(utmTagKey); + if (utmTagValue !== '') { + utmTagsDetected = true; + } + utmTagData[utmTagKey] = utmTagValue; + }); + utmTags.forEach(function(utmTagKey) { + if (utmTagsDetected) { + localStorage.setItem( + buildUtmLocalStorageKey(utmTagKey), + utmTagData[utmTagKey] + ); + updateUtmTimeout(); + } else { + if (!isUtmTimeoutExpired()) { + utmTagData[utmTagKey] = localStorage.getItem( + buildUtmLocalStorageKey(utmTagKey) + ) + ? localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) + : ''; + updateUtmTimeout(); + } + } + }); + return utmTagData; }; +function buildPayload( + data, + eventType, + publisherPlatformId, + publisherAccountId, + sourceUrl +) { + return { + data: data, + eventType: eventType, + publisherPlatformId: publisherPlatformId, + publisherAccountId: publisherAccountId, + sourceUrl: sourceUrl + }; +} +function apiCall(url, MAX_RETRIES, payload) { + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState !== 4) return; + if (xhr.status >= 200 && xhr.status < 300) { + utils.logInfo('SOX: Data sent for event :', payload.eventType); + } else { + if (MAX_RETRIES == 0) { + utils.logError('SOX:Retries Exhausted, Data could not be Sent!!'); + return; + } + utils.logInfo('SOX:Retrying.....', MAX_RETRIES); + url = getRandomUrl(url); + apiCall(url, MAX_RETRIES - 1, payload); + } + }; + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-Type', 'application/gzip'); + if (payload.publisherPlatformId) { + xhr.setRequestHeader('PublisherPlatformId', payload.publisherPlatformId); + } + if (payload.publisherAccountId) { + xhr.setRequestHeader('PublisherAccountId', payload.publisherAccountId); + } + xhr.setRequestHeader('Source-Url', payload.sourceUrl); + xhr.timeout = MAX_TIMEOUT; + xhr.send(payload.data); +} + +function getRandomUrl(failedUrl) { + let urlHead = 'http://'; + let urlTail = '.sigmoid.io/publish/'; + let urlList = [ + 'sox-prebid', + 'sox-prebid-1', + 'sox-prebid-2', + 'sox-prebid-3', + 'sox-prebid-4' + ]; + let randomIndex = Math.floor(Math.random() * urlList.length); + let randomUrl = urlHead + urlList[randomIndex] + urlTail; + if (failedUrl) { + if (failedUrl === randomUrl) { + return getRandomUrl(failedUrl); + } + return randomUrl; + } + return randomUrl; +} + +function detectMob() { + if ( + navigator.userAgent.match(/Android/i) || + navigator.userAgent.match(/webOS/i) || + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/iPod/i) || + navigator.userAgent.match(/BlackBerry/i) || + navigator.userAgent.match(/Windows Phone/i) + ) { + return true; + } else { + return false; + } +} + +function detectOS() { + if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; + if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; + if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; + if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; + if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; + if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; + return 'Others'; +} + +function detectBrowser() { + var isChrome = + /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); + var isCriOS = navigator.userAgent.match('CriOS'); + var isSafari = + /Safari/.test(navigator.userAgent) && + /Apple Computer/.test(navigator.vendor); + var isFirefox = /Firefox/.test(navigator.userAgent); + var isIE = + /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent); + var isEdge = /Edge/.test(navigator.userAgent); + if (isIE) return 'Internet Explorer'; + if (isEdge) return 'Microsoft Edge'; + if (isCriOS) return 'Chrome'; + if (isSafari) return 'Safari'; + if (isFirefox) return 'Firefox'; + if (isChrome) return 'Chrome'; + return 'Others'; +} + +function send(eventType, data, publisherPlatformId, publisherAccountId) { + var ua = window.navigator.userAgent; + var sourceUrl = window.location.href; + var sourceBrowser = detectBrowser(); + var sourceOs = detectOS(); + data['user_agent'] = ua; + data['source_url'] = sourceUrl; + data['source_browser'] = sourceBrowser; + data['source_os'] = sourceOs; + if (detectMob()) { + data['deviceType'] = 'Mobile'; + } else { + data['deviceType'] = 'Desktop'; + } + if (typeof data === 'object') { + const stringData = JSON.stringify(data); + console.log(stringData); + if (typeof stringData === 'string') { + const compressedData = zlib.gzipSync(stringData); + let urlGenerated = getRandomUrl(null); + let payload = buildPayload( + compressedData, + eventType, + publisherPlatformId, + publisherAccountId, + sourceUrl + ); + apiCall(urlGenerated, MAX_RETRIES, payload); + } else { + utils.logError('SOX:Invalid data format'); + return; + } + } else { + utils.logError('SOX:Invalid data format'); + return; + } +} +function pushEvent(eventType, args) { + if (eventType === bidRequestConst) { + if (checkAdUnitConfig()) { + args.bids = filterBidsByAdUnit(args.bids); + } + if (args.bids.length > 0) { + eventStack.events.push({ eventType: eventType }); + } + } else { + if (isValidEvent(eventType, args.adUnitCode)) { + eventStack.events.push({ eventType: eventType, args: args }); + } + } +} adapterManager.registerAnalyticsAdapter({ adapter: openxAdapter, code: 'openx' From d701939e8e622c30c64767f755c79b37cf1a99b4 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 24 Feb 2020 20:30:00 -0800 Subject: [PATCH 02/32] OpenX Analytics Adapter: switch to OX domain --- modules/openxAnalyticsAdapter.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 8c54f9a60f0..ea71988ecee 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -2,7 +2,6 @@ import includes from 'core-js/library/fn/array/includes'; import adapter from '../src/AnalyticsAdapter'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager'; -import { type } from 'os'; const zlib = require('zlib'); const utils = require('../src/utils'); @@ -342,14 +341,11 @@ function apiCall(url, MAX_RETRIES, payload) { } function getRandomUrl(failedUrl) { - let urlHead = 'http://'; - let urlTail = '.sigmoid.io/publish/'; + let urlHead = 'https://'; + let urlTail = '.prebid.openx.net/publish/'; let urlList = [ - 'sox-prebid', - 'sox-prebid-1', - 'sox-prebid-2', - 'sox-prebid-3', - 'sox-prebid-4' + 'analytics', + 'analytics-2' ]; let randomIndex = Math.floor(Math.random() * urlList.length); let randomUrl = urlHead + urlList[randomIndex] + urlTail; From 9a6f45aa5ae603c32a2ecf64a371841c98e60ba2 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Wed, 26 Feb 2020 06:35:12 +0530 Subject: [PATCH 03/32] Added testCode, bidTimeout events, send individual auctions payloads --- modules/openxAnalyticsAdapter.js | 143 ++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index ea71988ecee..2af2f5a11a3 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -24,13 +24,11 @@ const bidTimeoutConst = CONSTANTS.EVENTS.BID_TIMEOUT; let initOptions = { publisherPlatformId: '', publisherAccountId: -1, + testCode: 'default', utmTagData: [], adUnits: [] }; -let bidWon = { options: {}, events: [] }; -let eventStack = { options: {}, events: [] }; - -let auctionStatus = 'not_started'; +let eventStack = {}; let localStoragePrefix = 'openx_analytics_'; let utmTags = [ @@ -107,47 +105,64 @@ function buildUtmLocalStorageTimeoutKey() { function buildUtmLocalStorageKey(utmMarkKey) { return localStoragePrefix.concat(utmMarkKey); } + function checkPublisherPlatformId() { if (initOptions.publisherPlatformId !== undefined) { if (typeof initOptions.publisherPlatformId === 'string') { if (initOptions.publisherPlatformId !== '') { return initOptions.publisherPlatformId; } else { - utils.logError('SOX: Invalid PublisherPlatformId'); + utils.logError('OX: Invalid PublisherPlatformId'); return null; } } else { - utils.logError('SOX: Invalid datatype for PublisherPlatformId'); + utils.logError('OX: Invalid datatype for PublisherPlatformId'); return null; } } else { - utils.logError('SOX : PublisherPlatformId not defined'); + utils.logError('OX: PublisherPlatformId not defined'); return null; } } + function checkPublisherAccountId() { if (initOptions.publisherAccountId !== undefined) { if (typeof initOptions.publisherAccountId === 'number') { if (initOptions.publisherAccountId > -1) { return initOptions.publisherAccountId; } else { - utils.logError('SOX: Invalid PublisherAccountId'); + utils.logError('OX: Invalid PublisherAccountId'); return null; } } else { - utils.logError('SOX: Invalid datatype for PublisherAccountId'); + utils.logError('OX: Invalid datatype for PublisherAccountId'); return null; } } else { - utils.logError('SOX : PublisherAccountId not defined'); + utils.logError('OX: PublisherAccountId not defined'); return null; } } +function checkTestCode() { + if (initOptions.testCode !== undefined) { + if (typeof initOptions.testCode === 'string') { + return initOptions.testCode; + } else { + utils.logError('OX: Invalid datatype for testCode'); + return null; + } + } else { + utils.logInfo('OX: testCode not defined'); + return 'default'; + } +} + function checkInitOptions() { let publisherPlatformId = checkPublisherPlatformId(); let publisherAccountId = checkPublisherAccountId(); - if (publisherPlatformId && publisherAccountId) { + let testCode = checkTestCode(); + if (publisherPlatformId && publisherAccountId && testCode) { return true; } return false; @@ -160,8 +175,9 @@ function checkAdUnitConfig() { return initOptions.adUnits.length > 0; } -function buildEventStack() { - eventStack.options = initOptions; +function buildEventStack(auctionId) { + eventStack[auctionId].options = initOptions; + utils.logInfo('OX: Options Initialized', eventStack); } function filterBidsByAdUnit(bids) { @@ -176,7 +192,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -187,11 +203,13 @@ function isValidEvent(eventType, adUnitCode) { return true; } -function isValidEventStack() { - if (eventStack.events.length > 0) { - return eventStack.events.some(function(event) { +function isValidEventStack(auctionId) { + utils.logInfo('OX: Validating eventStack for', auctionId) + if (eventStack[auctionId].events.length > 0) { + return eventStack[auctionId].events.some(function(event) { + // utils.logInfo('OX: EventType of event ', event.eventType) return ( - bidRequestConst === event.eventType || bidWonConst === event.eventType + bidRequestConst === event.eventType || bidResponseConst === event.eventType || bidAdjustmentConst === event.eventType || auctionEndConst === event.eventType || bidTimeoutConst === event.eventType ); }); } @@ -216,8 +234,9 @@ function removeads(info) { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { + if (!checkInitOptions()) { - send(eventType, {}, null, null); + send(eventType, {}, null); return; } @@ -227,31 +246,44 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { info.ad = ''; } - if (eventType === auctionInitConst) { - auctionStatus = 'started'; - } + let auctionId = info.auctionId + // utils.logInfo('OX: Got auctionId', auctionId); - if (eventType === bidWonConst && auctionStatus === 'not_started') { - pushEvent(eventType, info); - utils.logInfo('SOX:Bid won called... '); + if (eventType === auctionInitConst) { + eventStack[auctionId] = { options: {}, events: [] }; + // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); + } + else if (eventType === bidWonConst) { // && auctionStatus[auctionId] !== 'started' + pushEvent(eventType, info, auctionId); + // utils.logInfo('OX: Bid won called for', auctionId); return; } - - if (eventType === auctionEndConst) { - pushEvent(eventType, removeads(info)); - utils.logInfo('SOX:Auction end called... '); + else if (eventType === auctionEndConst) { + pushEvent(eventType, removeads(info), auctionId); + // utils.logInfo('OX: Auction end called for', auctionId); updateSessionId(); - buildEventStack(); - if (isValidEventStack()) { - auctionStatus = 'not_started'; + buildEventStack(auctionId); + if (isValidEventStack(auctionId)) { setTimeout(function() { - let publisherPlatformId = eventStack.options.publisherPlatformId; - let publisherAccountId = eventStack.options.publisherAccountId; - send(eventType, eventStack, publisherPlatformId, publisherAccountId); + // utils.logInfo('OX: Sending data', eventStack); + send( + eventType, + eventStack, + auctionId + ); + delete eventStack[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, AUCTION_END_WAIT_TIME); + } else { + setTimeout(function() { + delete eventStack[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } - } else if (eventType === bidRequestConst || eventType === bidTimeoutConst) { - pushEvent(eventType, info); + } + else if (eventType === bidTimeoutConst) { + // utils.logInfo('SA: Bid Timedout for', auctionId); + pushEvent(eventType, info, auctionId); } } }); @@ -260,6 +292,7 @@ openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; openxAdapter.enableAnalytics = function(config) { initOptions = config.options; + initOptions.testCode = checkTestCode(); initOptions.utmTagData = this.buildUtmTagData(); utils.logInfo('OpenX Analytics enabled with config', initOptions); openxAdapter.originEnableAnalytics(config); @@ -301,6 +334,7 @@ function buildPayload( eventType, publisherPlatformId, publisherAccountId, + testCode, sourceUrl ) { return { @@ -308,21 +342,23 @@ function buildPayload( eventType: eventType, publisherPlatformId: publisherPlatformId, publisherAccountId: publisherAccountId, + testCode: testCode, sourceUrl: sourceUrl }; } -function apiCall(url, MAX_RETRIES, payload) { + +function apiCall(url, MAX_RETRIES, payload, auctionId) { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status >= 200 && xhr.status < 300) { - utils.logInfo('SOX: Data sent for event :', payload.eventType); + utils.logInfo('OX: Data sent for event:', payload.eventType); } else { if (MAX_RETRIES == 0) { - utils.logError('SOX:Retries Exhausted, Data could not be Sent!!'); + utils.logError('OX: Retries Exhausted, Data could not be Sent!!'); return; } - utils.logInfo('SOX:Retrying.....', MAX_RETRIES); + utils.logInfo('OX: Retrying ...', MAX_RETRIES); url = getRandomUrl(url); apiCall(url, MAX_RETRIES - 1, payload); } @@ -404,11 +440,16 @@ function detectBrowser() { return 'Others'; } -function send(eventType, data, publisherPlatformId, publisherAccountId) { +function send(eventType, eventStack, auctionId) { var ua = window.navigator.userAgent; var sourceUrl = window.location.href; var sourceBrowser = detectBrowser(); var sourceOs = detectOS(); + // utils.logInfo('OX: AuctionId', auctionId); + var data = eventStack[auctionId]; + var publisherPlatformId = eventStack[auctionId].options.publisherPlatformId; + var publisherAccountId = eventStack[auctionId].options.publisherPlatformId; + var testCode = eventStack[auctionId].options.testCode; data['user_agent'] = ua; data['source_url'] = sourceUrl; data['source_browser'] = sourceBrowser; @@ -420,7 +461,6 @@ function send(eventType, data, publisherPlatformId, publisherAccountId) { } if (typeof data === 'object') { const stringData = JSON.stringify(data); - console.log(stringData); if (typeof stringData === 'string') { const compressedData = zlib.gzipSync(stringData); let urlGenerated = getRandomUrl(null); @@ -429,29 +469,34 @@ function send(eventType, data, publisherPlatformId, publisherAccountId) { eventType, publisherPlatformId, publisherAccountId, + testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload); + apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); } else { - utils.logError('SOX:Invalid data format'); + utils.logError('OX: Invalid data format'); + delete eventStack[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); return; } } else { - utils.logError('SOX:Invalid data format'); + utils.logError('OX: Invalid data format'); + delete eventStack[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); return; } } -function pushEvent(eventType, args) { +function pushEvent(eventType, args, auctionId) { if (eventType === bidRequestConst) { if (checkAdUnitConfig()) { args.bids = filterBidsByAdUnit(args.bids); } if (args.bids.length > 0) { - eventStack.events.push({ eventType: eventType }); + eventStack[auctionId].events.push({ eventType: eventType }); } } else { if (isValidEvent(eventType, args.adUnitCode)) { - eventStack.events.push({ eventType: eventType, args: args }); + eventStack[auctionId].events.push({ eventType: eventType, args: args }); } } } From 3baeb2fd669bf7a76d7d531477ade4c66ed92b70 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 26 Feb 2020 16:57:34 -0800 Subject: [PATCH 04/32] fixed publisherAccountId Updated imports with .js filetype to pass linter. --- modules/openxAnalyticsAdapter.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 2af2f5a11a3..a3bab17dc02 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,10 +1,10 @@ -import includes from 'core-js/library/fn/array/includes'; -import adapter from '../src/AnalyticsAdapter'; +import includes from 'core-js/library/fn/array/includes.js'; +import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager'; +import adapterManager from '../src/adapterManager.js'; const zlib = require('zlib'); -const utils = require('../src/utils'); +const utils = require('../src/utils.js'); const urlParam = ''; const analyticsType = 'endpoint'; @@ -252,7 +252,7 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { if (eventType === auctionInitConst) { eventStack[auctionId] = { options: {}, events: [] }; // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); - } + } else if (eventType === bidWonConst) { // && auctionStatus[auctionId] !== 'started' pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); @@ -280,7 +280,7 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } - } + } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); @@ -448,7 +448,7 @@ function send(eventType, eventStack, auctionId) { // utils.logInfo('OX: AuctionId', auctionId); var data = eventStack[auctionId]; var publisherPlatformId = eventStack[auctionId].options.publisherPlatformId; - var publisherAccountId = eventStack[auctionId].options.publisherPlatformId; + var publisherAccountId = eventStack[auctionId].options.publisherAccountId; var testCode = eventStack[auctionId].options.testCode; data['user_agent'] = ua; data['source_url'] = sourceUrl; From 376e30662ed132e24c901a7b619b290f72c374ef Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:30:18 +0530 Subject: [PATCH 05/32] Added AuctionId to headers testCode function renamed --- modules/openxAnalyticsAdapter.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index a3bab17dc02..c627bba095e 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -106,7 +106,7 @@ function buildUtmLocalStorageKey(utmMarkKey) { return localStoragePrefix.concat(utmMarkKey); } -function checkPublisherPlatformId() { +function getPublisherPlatformId() { if (initOptions.publisherPlatformId !== undefined) { if (typeof initOptions.publisherPlatformId === 'string') { if (initOptions.publisherPlatformId !== '') { @@ -125,7 +125,7 @@ function checkPublisherPlatformId() { } } -function checkPublisherAccountId() { +function getPublisherAccountId() { if (initOptions.publisherAccountId !== undefined) { if (typeof initOptions.publisherAccountId === 'number') { if (initOptions.publisherAccountId > -1) { @@ -144,7 +144,7 @@ function checkPublisherAccountId() { } } -function checkTestCode() { +function getTestCode() { if (initOptions.testCode !== undefined) { if (typeof initOptions.testCode === 'string') { return initOptions.testCode; @@ -159,9 +159,9 @@ function checkTestCode() { } function checkInitOptions() { - let publisherPlatformId = checkPublisherPlatformId(); - let publisherAccountId = checkPublisherAccountId(); - let testCode = checkTestCode(); + let publisherPlatformId = getPublisherPlatformId(); + let publisherAccountId = getPublisherAccountId(); + let testCode = getTestCode(); if (publisherPlatformId && publisherAccountId && testCode) { return true; } @@ -292,7 +292,7 @@ openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; openxAdapter.enableAnalytics = function(config) { initOptions = config.options; - initOptions.testCode = checkTestCode(); + initOptions.testCode = getTestCode(); initOptions.utmTagData = this.buildUtmTagData(); utils.logInfo('OpenX Analytics enabled with config', initOptions); openxAdapter.originEnableAnalytics(config); @@ -334,6 +334,7 @@ function buildPayload( eventType, publisherPlatformId, publisherAccountId, + auctionId, testCode, sourceUrl ) { @@ -342,12 +343,13 @@ function buildPayload( eventType: eventType, publisherPlatformId: publisherPlatformId, publisherAccountId: publisherAccountId, + auctionId: auctionId, testCode: testCode, sourceUrl: sourceUrl }; } -function apiCall(url, MAX_RETRIES, payload, auctionId) { +function apiCall(url, MAX_RETRIES, payload) { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; @@ -371,6 +373,9 @@ function apiCall(url, MAX_RETRIES, payload, auctionId) { if (payload.publisherAccountId) { xhr.setRequestHeader('PublisherAccountId', payload.publisherAccountId); } + if (payload.publisherAccountId) { + xhr.setRequestHeader('AuctionId', payload.auctionId); + } xhr.setRequestHeader('Source-Url', payload.sourceUrl); xhr.timeout = MAX_TIMEOUT; xhr.send(payload.data); @@ -378,7 +383,7 @@ function apiCall(url, MAX_RETRIES, payload, auctionId) { function getRandomUrl(failedUrl) { let urlHead = 'https://'; - let urlTail = '.prebid.openx.net/publish/'; + let urlTail = 'https://analytics.prebid.openx.net/publish/'; let urlList = [ 'analytics', 'analytics-2' @@ -469,10 +474,11 @@ function send(eventType, eventStack, auctionId) { eventType, publisherPlatformId, publisherAccountId, + auctionId, testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); + apiCall(urlGenerated, MAX_RETRIES, payload); } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; From 73057477131e3eaf3e924c76d79a9dc0096187f0 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:38:08 +0530 Subject: [PATCH 06/32] Bugfix --- modules/openxAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index c627bba095e..d5aaf579988 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -383,7 +383,7 @@ function apiCall(url, MAX_RETRIES, payload) { function getRandomUrl(failedUrl) { let urlHead = 'https://'; - let urlTail = 'https://analytics.prebid.openx.net/publish/'; + let urlTail = '.prebid.openx.net/publish/'; let urlList = [ 'analytics', 'analytics-2' From 8d42674c2db66342979ad63758853ee42178e2e4 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 9 Mar 2020 13:04:04 -0700 Subject: [PATCH 07/32] added default sampling/throttle rate to 5% updated analytics endpoint added bidTimeoutConst to Swapped out deletes for nulls --- modules/openxAnalyticsAdapter.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index d5aaf579988..39c083ed614 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -192,7 +192,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -234,7 +234,6 @@ function removeads(info) { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { - if (!checkInitOptions()) { send(eventType, {}, null); return; @@ -246,7 +245,7 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { info.ad = ''; } - let auctionId = info.auctionId + let auctionId = info.auctionId; // utils.logInfo('OX: Got auctionId', auctionId); if (eventType === auctionInitConst) { @@ -271,12 +270,12 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { eventStack, auctionId ); - delete eventStack[auctionId]; + eventStack[auctionId] = null; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } else { setTimeout(function() { - delete eventStack[auctionId]; + eventStack[auctionId] = null; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } @@ -295,6 +294,9 @@ openxAdapter.enableAnalytics = function(config) { initOptions.testCode = getTestCode(); initOptions.utmTagData = this.buildUtmTagData(); utils.logInfo('OpenX Analytics enabled with config', initOptions); + + // set default sampling rate to 5% + config.options.sampling = config.options.sampling || 0.05; openxAdapter.originEnableAnalytics(config); }; @@ -383,10 +385,10 @@ function apiCall(url, MAX_RETRIES, payload) { function getRandomUrl(failedUrl) { let urlHead = 'https://'; - let urlTail = '.prebid.openx.net/publish/'; + let urlTail = '.openx.net/publish/'; let urlList = [ - 'analytics', - 'analytics-2' + 'prebid-analytics', + 'prebid-analytics-2' ]; let randomIndex = Math.floor(Math.random() * urlList.length); let randomUrl = urlHead + urlList[randomIndex] + urlTail; From b1c7f9a0bd08b640d91f66b7038482d2a9d2e399 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Mon, 16 Mar 2020 23:35:38 +0530 Subject: [PATCH 08/32] Added BidTimeout Added BidTimeout + BugFix --- modules/openxAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 39c083ed614..635691f8e98 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -375,7 +375,7 @@ function apiCall(url, MAX_RETRIES, payload) { if (payload.publisherAccountId) { xhr.setRequestHeader('PublisherAccountId', payload.publisherAccountId); } - if (payload.publisherAccountId) { + if (payload.auctionId) { xhr.setRequestHeader('AuctionId', payload.auctionId); } xhr.setRequestHeader('Source-Url', payload.sourceUrl); From 25b128b5fb1a53744510711b26962bbfd7c704a8 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Wed, 26 Feb 2020 06:35:12 +0530 Subject: [PATCH 09/32] Added testCode, bidTimeout events, send individual auctions payloads --- modules/openxAnalyticsAdapter.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 635691f8e98..d285b91467e 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -162,6 +162,7 @@ function checkInitOptions() { let publisherPlatformId = getPublisherPlatformId(); let publisherAccountId = getPublisherAccountId(); let testCode = getTestCode(); + let testCode = checkTestCode(); if (publisherPlatformId && publisherAccountId && testCode) { return true; } @@ -192,7 +193,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -234,6 +235,7 @@ function removeads(info) { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { + if (!checkInitOptions()) { send(eventType, {}, null); return; @@ -351,7 +353,7 @@ function buildPayload( }; } -function apiCall(url, MAX_RETRIES, payload) { +function apiCall(url, MAX_RETRIES, payload, auctionId) { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; @@ -480,7 +482,7 @@ function send(eventType, eventStack, auctionId) { testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload); + apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; From a44cb3e221b210a44f08d00a109754fe1ba824af Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:30:18 +0530 Subject: [PATCH 10/32] Added AuctionId to headers testCode function renamed --- modules/openxAnalyticsAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index d285b91467e..e05b3c66ac5 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -353,7 +353,7 @@ function buildPayload( }; } -function apiCall(url, MAX_RETRIES, payload, auctionId) { +function apiCall(url, MAX_RETRIES, payload) { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; @@ -482,7 +482,7 @@ function send(eventType, eventStack, auctionId) { testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); + apiCall(urlGenerated, MAX_RETRIES, payload); } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; From a29512bb01ad7e5bc015dacb5e950c3326715889 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 24 Feb 2020 20:11:02 -0800 Subject: [PATCH 11/32] OpenX Analytics Adapter: initial commit --- modules/openxAnalyticsAdapter.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index e05b3c66ac5..0d8d6e2e7ac 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -3,7 +3,9 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -const zlib = require('zlib'); +// temp dependency on zlib to minimize payload +const zlib = require('zlib'); // eslint-disable-line + const utils = require('../src/utils.js'); const urlParam = ''; @@ -162,7 +164,6 @@ function checkInitOptions() { let publisherPlatformId = getPublisherPlatformId(); let publisherAccountId = getPublisherAccountId(); let testCode = getTestCode(); - let testCode = checkTestCode(); if (publisherPlatformId && publisherAccountId && testCode) { return true; } @@ -193,7 +194,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -247,19 +248,18 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { info.ad = ''; } - let auctionId = info.auctionId; - // utils.logInfo('OX: Got auctionId', auctionId); + // on bid timeout events, the info is an array of bids + let auctionId = eventType === CONSTANTS.EVENTS.BID_TIMEOUT + ? info[0].auctionId + : info.auctionId; if (eventType === auctionInitConst) { eventStack[auctionId] = { options: {}, events: [] }; // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); - } - else if (eventType === bidWonConst) { // && auctionStatus[auctionId] !== 'started' + } else if (eventType === bidWonConst) { pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); - return; - } - else if (eventType === auctionEndConst) { + } else if (eventType === auctionEndConst) { pushEvent(eventType, removeads(info), auctionId); // utils.logInfo('OX: Auction end called for', auctionId); updateSessionId(); @@ -281,8 +281,7 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } - } - else if (eventType === bidTimeoutConst) { + } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); } @@ -487,13 +486,11 @@ function send(eventType, eventStack, auctionId) { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - return; } } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - return; } } function pushEvent(eventType, args, auctionId) { @@ -506,7 +503,7 @@ function pushEvent(eventType, args, auctionId) { } } else { if (isValidEvent(eventType, args.adUnitCode)) { - eventStack[auctionId].events.push({ eventType: eventType, args: args }); + eventStack[auctionId].events.push({ eventType: eventType, args: args }); } } } From cf7329e5e1265f23be95fadc9d41a7cd7d21a18a Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 24 Feb 2020 20:30:00 -0800 Subject: [PATCH 12/32] OpenX Analytics Adapter: switch to OX domain --- modules/openxAnalyticsAdapter.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 0d8d6e2e7ac..e05b3c66ac5 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -3,9 +3,7 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -// temp dependency on zlib to minimize payload -const zlib = require('zlib'); // eslint-disable-line - +const zlib = require('zlib'); const utils = require('../src/utils.js'); const urlParam = ''; @@ -164,6 +162,7 @@ function checkInitOptions() { let publisherPlatformId = getPublisherPlatformId(); let publisherAccountId = getPublisherAccountId(); let testCode = getTestCode(); + let testCode = checkTestCode(); if (publisherPlatformId && publisherAccountId && testCode) { return true; } @@ -194,7 +193,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -248,18 +247,19 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { info.ad = ''; } - // on bid timeout events, the info is an array of bids - let auctionId = eventType === CONSTANTS.EVENTS.BID_TIMEOUT - ? info[0].auctionId - : info.auctionId; + let auctionId = info.auctionId; + // utils.logInfo('OX: Got auctionId', auctionId); if (eventType === auctionInitConst) { eventStack[auctionId] = { options: {}, events: [] }; // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); - } else if (eventType === bidWonConst) { + } + else if (eventType === bidWonConst) { // && auctionStatus[auctionId] !== 'started' pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); - } else if (eventType === auctionEndConst) { + return; + } + else if (eventType === auctionEndConst) { pushEvent(eventType, removeads(info), auctionId); // utils.logInfo('OX: Auction end called for', auctionId); updateSessionId(); @@ -281,7 +281,8 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } - } else if (eventType === bidTimeoutConst) { + } + else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); } @@ -486,11 +487,13 @@ function send(eventType, eventStack, auctionId) { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + return; } } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + return; } } function pushEvent(eventType, args, auctionId) { @@ -503,7 +506,7 @@ function pushEvent(eventType, args, auctionId) { } } else { if (isValidEvent(eventType, args.adUnitCode)) { - eventStack[auctionId].events.push({ eventType: eventType, args: args }); + eventStack[auctionId].events.push({ eventType: eventType, args: args }); } } } From 6f12fcb3b070220ec067a701e61c56229f40433b Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Mon, 16 Mar 2020 23:35:38 +0530 Subject: [PATCH 13/32] Added BidTimeout Added BidTimeout + BugFix --- modules/openxAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index e05b3c66ac5..d903e72d4c3 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -193,7 +193,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) From a847474c3e0a8d6ae8b4572c453fe06639de95b5 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 9 Mar 2020 13:04:04 -0700 Subject: [PATCH 14/32] added default sampling/throttle rate to 5% updated analytics endpoint added bidTimeoutConst to Swapped out deletes for nulls --- modules/openxAnalyticsAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index d903e72d4c3..bde87970ffe 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -235,7 +235,6 @@ function removeads(info) { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { - if (!checkInitOptions()) { send(eventType, {}, null); return; From 16f89f0fee6785c7be2b09024fd752a35fee812a Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Wed, 26 Feb 2020 06:35:12 +0530 Subject: [PATCH 15/32] Added testCode, bidTimeout events, send individual auctions payloads --- modules/openxAnalyticsAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index bde87970ffe..98ff5faf48e 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -193,7 +193,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -235,6 +235,7 @@ function removeads(info) { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { + if (!checkInitOptions()) { send(eventType, {}, null); return; @@ -481,7 +482,7 @@ function send(eventType, eventStack, auctionId) { testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload); + apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; From aee5c504e9b1b5fe19ad0bcc1ec52b7b8150e2a5 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy <50863120+nuthanreddyox@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:30:18 +0530 Subject: [PATCH 16/32] Added AuctionId to headers testCode function renamed --- modules/openxAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 98ff5faf48e..e05b3c66ac5 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -482,7 +482,7 @@ function send(eventType, eventStack, auctionId) { testCode, sourceUrl ); - apiCall(urlGenerated, MAX_RETRIES, payload, auctionId); + apiCall(urlGenerated, MAX_RETRIES, payload); } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; From a02ba2abdef854595310b20dd5d54302dd203209 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 25 Mar 2020 14:45:01 -0700 Subject: [PATCH 17/32] OpenX Analytics Adapter: Adding bid timeout tracking logic. --- modules/openxAnalyticsAdapter.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index e05b3c66ac5..e11b4b76b2d 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -193,7 +193,7 @@ function filterBidsByAdUnit(bids) { function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; + let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; if ( !includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType) @@ -247,19 +247,18 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { info.ad = ''; } - let auctionId = info.auctionId; - // utils.logInfo('OX: Got auctionId', auctionId); + // on bid timeout events, the info is an array of bids + let auctionId = eventType === CONSTANTS.EVENTS.BID_TIMEOUT + ? info[0].auctionId + : info.auctionId; if (eventType === auctionInitConst) { eventStack[auctionId] = { options: {}, events: [] }; // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); - } - else if (eventType === bidWonConst) { // && auctionStatus[auctionId] !== 'started' + } else if (eventType === bidWonConst) { pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); - return; - } - else if (eventType === auctionEndConst) { + } else if (eventType === auctionEndConst) { pushEvent(eventType, removeads(info), auctionId); // utils.logInfo('OX: Auction end called for', auctionId); updateSessionId(); @@ -281,8 +280,7 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); }, AUCTION_END_WAIT_TIME); } - } - else if (eventType === bidTimeoutConst) { + } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); } @@ -487,13 +485,11 @@ function send(eventType, eventStack, auctionId) { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - return; } } else { utils.logError('OX: Invalid data format'); delete eventStack[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - return; } } function pushEvent(eventType, args, auctionId) { @@ -506,7 +502,13 @@ function pushEvent(eventType, args, auctionId) { } } else { if (isValidEvent(eventType, args.adUnitCode)) { - eventStack[auctionId].events.push({ eventType: eventType, args: args }); + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + // convert object to array + let timedOutBids = utils._map(args, timedOutBid => timedOutBid); + eventStack[auctionId].events.push({ eventType: eventType, args: timedOutBids }); + } else { + eventStack[auctionId].events.push({ eventType: eventType, args: args }); + } } } } From 74460c54d1dbda60961429dd4c0175ac41d7d115 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 25 Mar 2020 22:07:42 -0700 Subject: [PATCH 18/32] Revert conversion to array for bid timeouts. --- modules/openxAnalyticsAdapter.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index e11b4b76b2d..0d8d6e2e7ac 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -3,7 +3,9 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -const zlib = require('zlib'); +// temp dependency on zlib to minimize payload +const zlib = require('zlib'); // eslint-disable-line + const utils = require('../src/utils.js'); const urlParam = ''; @@ -162,7 +164,6 @@ function checkInitOptions() { let publisherPlatformId = getPublisherPlatformId(); let publisherAccountId = getPublisherAccountId(); let testCode = getTestCode(); - let testCode = checkTestCode(); if (publisherPlatformId && publisherAccountId && testCode) { return true; } @@ -502,13 +503,7 @@ function pushEvent(eventType, args, auctionId) { } } else { if (isValidEvent(eventType, args.adUnitCode)) { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { - // convert object to array - let timedOutBids = utils._map(args, timedOutBid => timedOutBid); - eventStack[auctionId].events.push({ eventType: eventType, args: timedOutBids }); - } else { eventStack[auctionId].events.push({ eventType: eventType, args: args }); - } } } } From 7fbf588dc26bc3ad26fe5513283bb93883eafca6 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Tue, 7 Apr 2020 16:42:05 -0700 Subject: [PATCH 19/32] Added module and schema version for better tracking --- modules/openxAnalyticsAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 0d8d6e2e7ac..e4e0a2af005 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -11,6 +11,9 @@ const utils = require('../src/utils.js'); const urlParam = ''; const analyticsType = 'endpoint'; +const ADAPTER_VERSION = '0.1'; +const SCHEMA_VERSION = '0.1'; + const MAX_RETRIES = 2; const MAX_TIMEOUT = 10000; const AUCTION_END_WAIT_TIME = 2000; @@ -342,6 +345,8 @@ function buildPayload( sourceUrl ) { return { + adapterVersion: ADAPTER_VERSION, + schemaVersion: SCHEMA_VERSION, data: data, eventType: eventType, publisherPlatformId: publisherPlatformId, From 4cea9d2cdd972fd194ada2fc3b9c64c2e39d7300 Mon Sep 17 00:00:00 2001 From: suribabu95 Date: Thu, 9 Apr 2020 23:35:00 +0530 Subject: [PATCH 20/32] added slotOnLoad event listener to publish data before auction end timeout --- modules/openxAnalyticsAdapter.js | 160 ++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 23 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index e4e0a2af005..3bc4e659dc8 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -17,6 +17,7 @@ const SCHEMA_VERSION = '0.1'; const MAX_RETRIES = 2; const MAX_TIMEOUT = 10000; const AUCTION_END_WAIT_TIME = 2000; +const DEFAULT_SLOT_LOAD_BUFFER_TIME = 100; const auctionInitConst = CONSTANTS.EVENTS.AUCTION_INIT; const auctionEndConst = CONSTANTS.EVENTS.AUCTION_END; @@ -25,15 +26,21 @@ const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; const bidTimeoutConst = CONSTANTS.EVENTS.BID_TIMEOUT; +const SLOT_LOADED = "slotOnload" + +let googletag = window.googletag || {}; +googletag.cmd = googletag.cmd || []; let initOptions = { publisherPlatformId: '', publisherAccountId: -1, testCode: 'default', utmTagData: [], - adUnits: [] + adUnits: [], + slotLoadWaitTime: 0 }; let eventStack = {}; +let loadedAdSlots = {}; let localStoragePrefix = 'openx_analytics_'; let utmTags = [ @@ -237,6 +244,106 @@ function removeads(info) { } } +function getAuctionIdByAdId(adId) { + + let auctionId; + utils._map(eventStack, value => value).forEach( function(auctionInfo) { + if(auctionInfo && auctionInfo.events){ + let bidWonEvent; + bidWonEvent = auctionInfo.events.filter(function(eventsInfo) { + return eventsInfo.eventType === "bidWon"; + }); + + if(bidWonEvent.length > 0) { + bidWonEvent.forEach(function(bidWon) { + if(bidWon.args && bidWon.args.adId && bidWon.args.adId === adId) { + auctionId = bidWon.args.auctionId; + } + }); + } + } + }); + return auctionId; +} + +function getAllAdUnitCodesByAuctionId(auctionId) { + + let adUnitCodes; + if(eventStack[auctionId] && eventStack[auctionId].events) { + + eventStack[auctionId].events.forEach(function(eventsInfo) { + if(eventsInfo.eventType === "auctionEnd") { + adUnitCodes = eventsInfo.args.adUnitCodes; + } + }) + } + return adUnitCodes; +} + +function getAuctionIdByAdUnitCode(adUnitCode) { + let auctionId; + utils._map(eventStack, value => value).forEach( function(auctionInfo) { + if(auctionId === undefined) { + if(auctionInfo && auctionInfo.events) { + auctionInfo.events.forEach(function(eventsInfo){ + if(eventsInfo.eventType === auctionEndConst) { + if(eventsInfo.args && eventsInfo.args.adUnitCodes) { + if(eventsInfo.args.adUnitCodes.includes(adUnitCode)){ + auctionId = eventsInfo.args.auctionId; + } + } + } + }) + } + } + }); + return auctionId; +} + +function onSlotLoaded({ slot }) { + + const adId = slot.getTargeting('hb_adid')[0]; + const slotElementId = slot.getSlotElementId(); + const adUnitPath = slot.getAdUnitPath(); + + let auctionId = getAuctionIdByAdId(adId); + if(!auctionId) { + auctionId = getAuctionIdByAdUnitCode(slotElementId); + if(!auctionId) { + auctionId = getAuctionIdByAdUnitCode(adUnitPath); + } + } + + let allSlotsLoaded = false; + if(auctionId) { + if(!loadedAdSlots[auctionId]) { + loadedAdSlots[auctionId] = [] + } + loadedAdSlots[auctionId].push(slotElementId); + let allAdUnitCodes = getAllAdUnitCodesByAuctionId(auctionId); + if(loadedAdSlots[auctionId].length === allAdUnitCodes.length) { + allSlotsLoaded = true; + } + } + + if(auctionId && eventStack[auctionId] && allSlotsLoaded) { + setTimeout(function(){ + if(eventStack[auctionId]) { + send(SLOT_LOADED, eventStack, auctionId); + eventStack[auctionId] = null; + } + delete loadedAdSlots[auctionId]; + }, initOptions.slotLoadWaitTime); + } +} + +googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, function(args) { + utils.logInfo("OX: SlotOnLoad event triggered"); + onSlotLoaded(args); + }); +}); + let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { @@ -263,31 +370,34 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); } else if (eventType === auctionEndConst) { - pushEvent(eventType, removeads(info), auctionId); - // utils.logInfo('OX: Auction end called for', auctionId); - updateSessionId(); - buildEventStack(auctionId); - if (isValidEventStack(auctionId)) { - setTimeout(function() { - // utils.logInfo('OX: Sending data', eventStack); - send( - eventType, - eventStack, - auctionId - ); - eventStack[auctionId] = null; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); - } else { - setTimeout(function() { - eventStack[auctionId] = null; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); - } + pushEvent(eventType, removeads(info), auctionId); + // utils.logInfo('OX: Auction end called for', auctionId); + updateSessionId(); + buildEventStack(auctionId); + if (isValidEventStack(auctionId)) { + setTimeout(function() { + // utils.logInfo('OX: Sending data', eventStack); + if(eventStack[auctionId]) { + send( + eventType, + eventStack, + auctionId + ); + eventStack[auctionId] = null; + } + delete loadedAdSlots[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, AUCTION_END_WAIT_TIME); + } else { + setTimeout(function() { + eventStack[auctionId] = null; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, AUCTION_END_WAIT_TIME); + } } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); - } + } } }); @@ -297,6 +407,10 @@ openxAdapter.enableAnalytics = function(config) { initOptions = config.options; initOptions.testCode = getTestCode(); initOptions.utmTagData = this.buildUtmTagData(); + + if(!initOptions.slotLoadWaitTime) { + initOptions.slotLoadWaitTime = DEFAULT_SLOT_LOAD_BUFFER_TIME + } utils.logInfo('OpenX Analytics enabled with config', initOptions); // set default sampling rate to 5% From 34ad818180048abdf6224b4fb6d945db2103cdbb Mon Sep 17 00:00:00 2001 From: suribabu95 Date: Mon, 13 Apr 2020 16:38:07 +0530 Subject: [PATCH 21/32] added unit test cases for container, DFP winner cases, disabled other openx adapter test cases --- modules/openxAnalyticsAdapter.js | 110 ++++----- .../modules/openxAnalyticsAdapter_spec.js | 221 ++++++++++++++---- 2 files changed, 236 insertions(+), 95 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 3bc4e659dc8..cfbe59662cb 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -245,21 +245,20 @@ function removeads(info) { } function getAuctionIdByAdId(adId) { - let auctionId; - utils._map(eventStack, value => value).forEach( function(auctionInfo) { - if(auctionInfo && auctionInfo.events){ + utils._map(eventStack, value => value).forEach(function(auctionInfo) { + if (auctionInfo && auctionInfo.events) { let bidWonEvent; bidWonEvent = auctionInfo.events.filter(function(eventsInfo) { return eventsInfo.eventType === "bidWon"; }); - if(bidWonEvent.length > 0) { + if (bidWonEvent.length > 0) { bidWonEvent.forEach(function(bidWon) { - if(bidWon.args && bidWon.args.adId && bidWon.args.adId === adId) { + if (bidWon.args && bidWon.args.adId && bidWon.args.adId === adId) { auctionId = bidWon.args.auctionId; } - }); + }); } } }); @@ -267,10 +266,8 @@ function getAuctionIdByAdId(adId) { } function getAllAdUnitCodesByAuctionId(auctionId) { - let adUnitCodes; - if(eventStack[auctionId] && eventStack[auctionId].events) { - + if (eventStack[auctionId] && eventStack[auctionId].events) { eventStack[auctionId].events.forEach(function(eventsInfo) { if(eventsInfo.eventType === "auctionEnd") { adUnitCodes = eventsInfo.args.adUnitCodes; @@ -282,13 +279,13 @@ function getAllAdUnitCodesByAuctionId(auctionId) { function getAuctionIdByAdUnitCode(adUnitCode) { let auctionId; - utils._map(eventStack, value => value).forEach( function(auctionInfo) { - if(auctionId === undefined) { - if(auctionInfo && auctionInfo.events) { - auctionInfo.events.forEach(function(eventsInfo){ - if(eventsInfo.eventType === auctionEndConst) { - if(eventsInfo.args && eventsInfo.args.adUnitCodes) { - if(eventsInfo.args.adUnitCodes.includes(adUnitCode)){ + utils._map(eventStack, value => value).forEach(function(auctionInfo) { + if (auctionId === undefined) { + if (auctionInfo && auctionInfo.events) { + auctionInfo.events.forEach(function(eventsInfo) { + if (eventsInfo.eventType === auctionEndConst) { + if (eventsInfo.args && eventsInfo.args.adUnitCodes) { + if (eventsInfo.args.adUnitCodes.includes(adUnitCode)) { auctionId = eventsInfo.args.auctionId; } } @@ -301,34 +298,33 @@ function getAuctionIdByAdUnitCode(adUnitCode) { } function onSlotLoaded({ slot }) { - const adId = slot.getTargeting('hb_adid')[0]; const slotElementId = slot.getSlotElementId(); const adUnitPath = slot.getAdUnitPath(); let auctionId = getAuctionIdByAdId(adId); - if(!auctionId) { + if (!auctionId) { auctionId = getAuctionIdByAdUnitCode(slotElementId); - if(!auctionId) { + if (!auctionId) { auctionId = getAuctionIdByAdUnitCode(adUnitPath); } } let allSlotsLoaded = false; - if(auctionId) { - if(!loadedAdSlots[auctionId]) { + if (auctionId) { + if (!loadedAdSlots[auctionId]) { loadedAdSlots[auctionId] = [] } loadedAdSlots[auctionId].push(slotElementId); let allAdUnitCodes = getAllAdUnitCodesByAuctionId(auctionId); - if(loadedAdSlots[auctionId].length === allAdUnitCodes.length) { + if (loadedAdSlots[auctionId].length === allAdUnitCodes.length) { allSlotsLoaded = true; } } - if(auctionId && eventStack[auctionId] && allSlotsLoaded) { - setTimeout(function(){ - if(eventStack[auctionId]) { + if (auctionId && eventStack[auctionId] && allSlotsLoaded) { + setTimeout(function() { + if (eventStack[auctionId]) { send(SLOT_LOADED, eventStack, auctionId); eventStack[auctionId] = null; } @@ -346,7 +342,6 @@ googletag.cmd.push(function() { let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { - if (!checkInitOptions()) { send(eventType, {}, null); return; @@ -370,34 +365,34 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { pushEvent(eventType, info, auctionId); // utils.logInfo('OX: Bid won called for', auctionId); } else if (eventType === auctionEndConst) { - pushEvent(eventType, removeads(info), auctionId); - // utils.logInfo('OX: Auction end called for', auctionId); - updateSessionId(); - buildEventStack(auctionId); - if (isValidEventStack(auctionId)) { - setTimeout(function() { - // utils.logInfo('OX: Sending data', eventStack); - if(eventStack[auctionId]) { - send( - eventType, - eventStack, - auctionId - ); - eventStack[auctionId] = null; - } - delete loadedAdSlots[auctionId]; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); - } else { - setTimeout(function() { + pushEvent(eventType, removeads(info), auctionId); + // utils.logInfo('OX: Auction end called for', auctionId); + updateSessionId(); + buildEventStack(auctionId); + if (isValidEventStack(auctionId)) { + setTimeout(function() { + // utils.logInfo('OX: Sending data', eventStack); + if (eventStack[auctionId]) { + send( + eventType, + eventStack, + auctionId + ); eventStack[auctionId] = null; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); - } + } + delete loadedAdSlots[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, AUCTION_END_WAIT_TIME); + } else { + setTimeout(function() { + eventStack[auctionId] = null; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, AUCTION_END_WAIT_TIME); + } } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); pushEvent(eventType, info, auctionId); - } + } } }); @@ -407,8 +402,8 @@ openxAdapter.enableAnalytics = function(config) { initOptions = config.options; initOptions.testCode = getTestCode(); initOptions.utmTagData = this.buildUtmTagData(); - - if(!initOptions.slotLoadWaitTime) { + + if (!initOptions.slotLoadWaitTime) { initOptions.slotLoadWaitTime = DEFAULT_SLOT_LOAD_BUFFER_TIME } utils.logInfo('OpenX Analytics enabled with config', initOptions); @@ -622,10 +617,19 @@ function pushEvent(eventType, args, auctionId) { } } else { if (isValidEvent(eventType, args.adUnitCode)) { - eventStack[auctionId].events.push({ eventType: eventType, args: args }); + eventStack[auctionId].events.push({ eventType: eventType, args: args }); } } } + +openxAdapter.reset = function() { + eventStack = {}; + loadedAdSlots = {}; +}; + +openxAdapter.slotOnLoad = onSlotLoaded; +openxAdapter.AUCTION_END_WAIT_TIME = AUCTION_END_WAIT_TIME; + adapterManager.registerAnalyticsAdapter({ adapter: openxAdapter, code: 'openx' diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index 805435abf80..9afe22872ef 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -7,13 +7,14 @@ import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON, AUCTION_END } } = CONSTANTS; - const SLOT_LOADED = 'slotOnload'; +const zlib = require('zlib'); + describe('openx analytics adapter', function() { - it('should require publisher id', function() { + xit('should require publisher id', function() { sinon.spy(utils, 'logError'); openxAdapter.enableAnalytics(); @@ -29,6 +30,27 @@ describe('openx analytics adapter', function() { describe('sending analytics event', function() { const auctionInit = { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' }; + const auctionId = 'add5eb0f-587d-441d-86ec-bbb722c70f79'; + const OPENX_ADID = '33dddbb61d359a'; + const ADUNITCODE1 = 'div-1'; + const AUCTION_END_WAIT_TIME = openxAdapter.AUCTION_END_WAIT_TIME; + const SLOT_LOAD_WAIT_TIME = 200; + + let clock; + before(function () { + clock = sinon.useFakeTimers(); + }); + after(function () { + clock.restore(); + }); + + const openxAdUnitInfo = {'code': 'div-1', + 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, + 'bids': [{'bidder': 'openx', + 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}], + 'sizes': [[320, 50]], + 'transactionId': 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873'}; + const bidRequestedOpenX = { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', auctionStart: 1540944528017, @@ -111,6 +133,55 @@ describe('openx analytics adapter', function() { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' }; + const auctionEnd = { + 'auctionId': 'add5eb0f-587d-441d-86ec-bbb722c70f79', + 'timestamp': 1540944528017, + 'auctionEnd': 1540944528117, + 'auctionStatus': 'completed', + 'adUnits': openxAdUnitInfo, + 'adUnitCodes': [ + 'div-1' + ], + 'bidderRequests': [bidRequestedOpenX], + 'noBids': [], + 'bidsReceived': [bidResponseOpenX], + 'winningBids': [], + 'timeout': 300 + }; + + const auctionEndDFPWon = { + 'auctionId': 'add5eb0f-587d-441d-86ec-bbb722c70f79', + 'timestamp': 1540944528017, + 'auctionEnd': 1540944528117, + 'auctionStatus': 'completed', + 'adUnits': openxAdUnitInfo, + 'adUnitCodes': [ + 'div-1' + ], + 'bidderRequests': [bidRequestedOpenX], + 'noBids': [], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 300 + }; + + const slotLoadDFPWin = { + slot: { + getAdUnitPath: () => { + return '/90577858/test_ad_unit'; + }, + getSlotElementId: function () { + return 'div-1'; + }, + getTargetingKeys: () => { + return []; + }, + getTargeting: () => { + return []; // sinon.stub().withArgs('hb_adid').returns(highestBid ? [highestBid.adId] : []) + } + } + }; + function simulateAuction(events) { let highestBid; @@ -123,31 +194,16 @@ describe('openx analytics adapter', function() { highestBid = args; } } - }); - - openxAdapter.track({ - eventType: SLOT_LOADED, - args: { - slot: { - getAdUnitPath: () => { - return '/90577858/test_ad_unit'; - }, - getTargetingKeys: () => { - return []; - }, - getTargeting: sinon - .stub() - .withArgs('hb_adid') - .returns(highestBid ? [highestBid.adId] : []) - } + if (eventType === SLOT_LOADED) { + openxAdapter.slotOnLoad(args); } }); } function getQueryData(url) { - const queryArgs = url.split('?')[1].split('&'); + const queryArgs = url.splxit('?')[1].splxit('&'); return queryArgs.reduce((data, arg) => { - const [key, val] = arg.split('='); + const [key, val] = arg.splxit('='); data[key] = val; return data; }, {}); @@ -156,8 +212,13 @@ describe('openx analytics adapter', function() { before(function() { sinon.stub(events, 'getEvents').returns([]); openxAdapter.enableAnalytics({ + provider: 'openx', options: { - publisherId: 'test123' + publisherPlatformId: 'a3aece0c-9e80-4316-8deb-faf804779bd1', + publisherAccountId: 537143056, + sampling: 1.0, + testCode: 'test-code-1', + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME } }); }); @@ -173,7 +234,7 @@ describe('openx analytics adapter', function() { afterEach(function() {}); - it('should not send request if no bid response', function() { + xit('should not send request if no bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX] @@ -182,7 +243,7 @@ describe('openx analytics adapter', function() { expect(server.requests.length).to.equal(0); }); - it('should send 1 request to the right endpoint', function() { + xit('should send 1 request to the right endpoint', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -191,13 +252,13 @@ describe('openx analytics adapter', function() { expect(server.requests.length).to.equal(1); - const endpoint = server.requests[0].url.split('?')[0]; + const endpoint = server.requests[0].url.splxit('?')[0]; // note IE11 returns the default secure port, so we look for this alternate value as well in these tests expect(endpoint).to.be.oneOf(['https://ads.openx.net/w/1.0/pban', 'https://ads.openx.net:443/w/1.0/pban']); }); describe('hb.ct, hb.rid, dddid, hb.asiid, hb.pubid', function() { - it('should always be in the query string', function() { + xit('should always be in the query string', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -216,7 +277,7 @@ describe('openx analytics adapter', function() { }); describe('hb.cur', function() { - it('should be in the query string if currency is set', function() { + xit('should be in the query string if currency is set', function() { sinon .stub(config, 'getConfig') .withArgs('currency.adServerCurrency') @@ -236,7 +297,7 @@ describe('openx analytics adapter', function() { }); }); - it('should not be in the query string if currency is not set', function() { + xit('should not be in the query string if currency is not set', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -249,7 +310,7 @@ describe('openx analytics adapter', function() { }); describe('hb.dcl, hb.dl, hb.tta, hb.ttr', function() { - it('should be in the query string if browser supports performance API', function() { + xit('should be in the query string if browser supports performance API', function() { const timing = { fetchStart: 1540944528000, domContentLoadedEventEnd: 1540944528010, @@ -279,7 +340,7 @@ describe('openx analytics adapter', function() { }); }); - it('should not be in the query string if browser does not support performance API', function() { + xit('should not be in the query string if browser does not support performance API', function() { const originalPerf = window.top.performance; window.top.performance = undefined; @@ -302,7 +363,7 @@ describe('openx analytics adapter', function() { }); describe('ts, auid', function() { - it('OpenX is in auction and has a bid response', function() { + xit('OpenX is in auction and has a bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -318,7 +379,7 @@ describe('openx analytics adapter', function() { }); }); - it('OpenX is in auction but no bid response', function() { + xit('OpenX is in auction but no bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -333,7 +394,7 @@ describe('openx analytics adapter', function() { expect(queryData).to.not.have.key('ts'); }); - it('OpenX is not in auction', function() { + xit('OpenX is not in auction', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedCloseX], @@ -346,7 +407,7 @@ describe('openx analytics adapter', function() { }); describe('hb.exn, hb.sts, hb.ets, hb.bv, hb.crid, hb.to', function() { - it('2 bidders in auction', function() { + xit('2 bidders in auction', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -379,7 +440,7 @@ describe('openx analytics adapter', function() { }); }); - it('OpenX timed out', function() { + xit('OpenX timed out', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -416,28 +477,104 @@ describe('openx analytics adapter', function() { [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], [BID_RESPONSE, bidResponseOpenX], - [BID_WON, bidWonOpenX] + [BID_WON, bidWonOpenX], + [AUCTION_END, auctionEnd] ]); - const queryData = getQueryData(server.requests[0].url); + // Handle timeouts + clock.tick(AUCTION_END_WAIT_TIME + 10); + + let compressedPayload = server.requests[0].requestBody; + let payloadBuffer = new Buffer(compressedPayload); + let unCompressedPayload = zlib.gunzipSync(payloadBuffer).toString(); + let auctionData = JSON.parse(unCompressedPayload); + + let biddersRequests = []; + let biddersResponded = []; + auctionData.events.forEach(function (event) { + if (event.eventType === AUCTION_END) { + event.args.bidderRequests.forEach(function(bidRequestInfo) { + if (bidRequestInfo.bids.length > 0) { + biddersRequests.push(bidRequestInfo.bids[0].bidder); + } + }); + event.args.bidsReceived.forEach(function(bidsInfo) { + biddersResponded.push(bidsInfo); + }); + } + }); + + expect(biddersRequests.length).to.equal(1); + expect(biddersRequests[0]).to.equal('openx'); + expect(biddersResponded.length).to.equal(1); + expect(biddersResponded[0]).to.include({ + creativeId: 'openx-crid', + cpm: 0.5 + }); + + let bidWonEventInfoList = auctionData.events.filter(function (event) { + return event.eventType === BID_WON && event.args.auctionId === auctionId; + }); + + expect(bidWonEventInfoList.length).to.equal(1); + expect(bidWonEventInfoList[0].args).to.include({ + 'adId': OPENX_ADID, + 'adUnitCode': ADUNITCODE1 + }); + + /* const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.we': '0', 'hb.g1': 'false' - }); + }); */ }); it('DFP won', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], - [BID_RESPONSE, bidResponseOpenX] + [AUCTION_END, auctionEndDFPWon], + [SLOT_LOADED, slotLoadDFPWin] ]); - const queryData = getQueryData(server.requests[0].url); + // Handle timeouts + clock.tick(SLOT_LOAD_WAIT_TIME + 10); + + let compressedPayload = server.requests[0].requestBody; + let payloadBuffer = new Buffer(compressedPayload); + let unCompressedPayload = zlib.gunzipSync(payloadBuffer).toString(); + let auctionData = JSON.parse(unCompressedPayload); + + let biddersRequests = []; + let biddersResponded = []; + auctionData.events.forEach(function (event) { + if (event.eventType === AUCTION_END) { + event.args.bidderRequests.forEach(function(bidRequestInfo) { + if (bidRequestInfo.bids.length > 0) { + biddersRequests.push(bidRequestInfo.bids[0].bidder); + } + }); + event.args.bidsReceived.forEach(function(bidsInfo) { + biddersResponded.push(bidsInfo); + }); + } + }); + + expect(biddersRequests.length).to.equal(1); + expect(biddersRequests[0]).to.equal('openx'); + expect(biddersResponded.length).to.equal(0); + + let bidWonEventInfoList = auctionData.events.filter(function (event) { + return event.eventType === BID_WON && event.args.auctionId === auctionId; + }); + + expect(bidWonEventInfoList.length).to.equal(0); + + /* const queryData = getQueryData(server.requests[0].url); expect(queryData).to.include({ 'hb.we': '-1', 'hb.g1': 'true' - }); + }); */ }); }); }); From 319ea017175f30a9c571b410566c7f43946534b8 Mon Sep 17 00:00:00 2001 From: suribabu95 Date: Tue, 14 Apr 2020 11:33:26 +0530 Subject: [PATCH 22/32] code review changes: not attaching constants to openx adapter while exporting, type fix --- modules/openxAnalyticsAdapter.js | 14 +++-- .../modules/openxAnalyticsAdapter_spec.js | 63 ++++++++----------- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index cfbe59662cb..83f9f59cb5d 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -26,7 +26,7 @@ const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; const bidTimeoutConst = CONSTANTS.EVENTS.BID_TIMEOUT; -const SLOT_LOADED = "slotOnload" +const SLOT_LOADED = 'slotOnload' let googletag = window.googletag || {}; googletag.cmd = googletag.cmd || []; @@ -250,7 +250,7 @@ function getAuctionIdByAdId(adId) { if (auctionInfo && auctionInfo.events) { let bidWonEvent; bidWonEvent = auctionInfo.events.filter(function(eventsInfo) { - return eventsInfo.eventType === "bidWon"; + return eventsInfo.eventType === 'bidWon'; }); if (bidWonEvent.length > 0) { @@ -269,7 +269,7 @@ function getAllAdUnitCodesByAuctionId(auctionId) { let adUnitCodes; if (eventStack[auctionId] && eventStack[auctionId].events) { eventStack[auctionId].events.forEach(function(eventsInfo) { - if(eventsInfo.eventType === "auctionEnd") { + if (eventsInfo.eventType === 'auctionEnd') { adUnitCodes = eventsInfo.args.adUnitCodes; } }) @@ -335,7 +335,7 @@ function onSlotLoaded({ slot }) { googletag.cmd.push(function() { googletag.pubads().addEventListener(SLOT_LOADED, function(args) { - utils.logInfo("OX: SlotOnLoad event triggered"); + utils.logInfo('OX: SlotOnLoad event triggered'); onSlotLoaded(args); }); }); @@ -628,11 +628,13 @@ openxAdapter.reset = function() { }; openxAdapter.slotOnLoad = onSlotLoaded; -openxAdapter.AUCTION_END_WAIT_TIME = AUCTION_END_WAIT_TIME; adapterManager.registerAnalyticsAdapter({ adapter: openxAdapter, code: 'openx' }); -export default openxAdapter; +export default Object.assign({ + adapter: openxAdapter, + auctionEndWaitTime: AUCTION_END_WAIT_TIME +}); diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index 9afe22872ef..dee7a287a6e 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import openxAdapter from 'modules/openxAnalyticsAdapter.js'; +import openxAdapterParams from 'modules/openxAnalyticsAdapter.js'; import { config } from 'src/config.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; @@ -12,9 +12,10 @@ const { const SLOT_LOADED = 'slotOnload'; const zlib = require('zlib'); +const openxAdapter = openxAdapterParams.adapter; describe('openx analytics adapter', function() { - xit('should require publisher id', function() { + it.skip('should require publisher id', function() { sinon.spy(utils, 'logError'); openxAdapter.enableAnalytics(); @@ -33,7 +34,7 @@ describe('openx analytics adapter', function() { const auctionId = 'add5eb0f-587d-441d-86ec-bbb722c70f79'; const OPENX_ADID = '33dddbb61d359a'; const ADUNITCODE1 = 'div-1'; - const AUCTION_END_WAIT_TIME = openxAdapter.AUCTION_END_WAIT_TIME; + const AUCTION_END_WAIT_TIME = openxAdapterParams.auctionEndWaitTime; const SLOT_LOAD_WAIT_TIME = 200; let clock; @@ -103,6 +104,8 @@ describe('openx analytics adapter', function() { ts: 'hu1QWo6iD3MHs6NG_AQAcFtyNqsj9y4S0YRbX7Kb06IrGns0BABb' }; + const emptyBidResponses = {}; + const bidTimeoutOpenX = { 0: { adUnitCode: 'div-1', @@ -149,22 +152,6 @@ describe('openx analytics adapter', function() { 'timeout': 300 }; - const auctionEndDFPWon = { - 'auctionId': 'add5eb0f-587d-441d-86ec-bbb722c70f79', - 'timestamp': 1540944528017, - 'auctionEnd': 1540944528117, - 'auctionStatus': 'completed', - 'adUnits': openxAdUnitInfo, - 'adUnitCodes': [ - 'div-1' - ], - 'bidderRequests': [bidRequestedOpenX], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 300 - }; - const slotLoadDFPWin = { slot: { getAdUnitPath: () => { @@ -193,17 +180,16 @@ describe('openx analytics adapter', function() { if (highestBid.cpm < args.cpm) { highestBid = args; } - } - if (eventType === SLOT_LOADED) { + } else if (eventType === SLOT_LOADED) { openxAdapter.slotOnLoad(args); } }); } function getQueryData(url) { - const queryArgs = url.splxit('?')[1].splxit('&'); + const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { - const [key, val] = arg.splxit('='); + const [key, val] = arg.split('='); data[key] = val; return data; }, {}); @@ -234,7 +220,7 @@ describe('openx analytics adapter', function() { afterEach(function() {}); - xit('should not send request if no bid response', function() { + it.skip('should not send request if no bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX] @@ -243,7 +229,7 @@ describe('openx analytics adapter', function() { expect(server.requests.length).to.equal(0); }); - xit('should send 1 request to the right endpoint', function() { + it.skip('should send 1 request to the right endpoint', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -252,13 +238,13 @@ describe('openx analytics adapter', function() { expect(server.requests.length).to.equal(1); - const endpoint = server.requests[0].url.splxit('?')[0]; + const endpoint = server.requests[0].url.split('?')[0]; // note IE11 returns the default secure port, so we look for this alternate value as well in these tests expect(endpoint).to.be.oneOf(['https://ads.openx.net/w/1.0/pban', 'https://ads.openx.net:443/w/1.0/pban']); }); describe('hb.ct, hb.rid, dddid, hb.asiid, hb.pubid', function() { - xit('should always be in the query string', function() { + it.skip('should always be in the query string', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -277,7 +263,7 @@ describe('openx analytics adapter', function() { }); describe('hb.cur', function() { - xit('should be in the query string if currency is set', function() { + it.skip('should be in the query string if currency is set', function() { sinon .stub(config, 'getConfig') .withArgs('currency.adServerCurrency') @@ -297,7 +283,7 @@ describe('openx analytics adapter', function() { }); }); - xit('should not be in the query string if currency is not set', function() { + it.skip('should not be in the query string if currency is not set', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -310,7 +296,7 @@ describe('openx analytics adapter', function() { }); describe('hb.dcl, hb.dl, hb.tta, hb.ttr', function() { - xit('should be in the query string if browser supports performance API', function() { + it.skip('should be in the query string if browser supports performance API', function() { const timing = { fetchStart: 1540944528000, domContentLoadedEventEnd: 1540944528010, @@ -340,7 +326,7 @@ describe('openx analytics adapter', function() { }); }); - xit('should not be in the query string if browser does not support performance API', function() { + it.skip('should not be in the query string if browser does not support performance API', function() { const originalPerf = window.top.performance; window.top.performance = undefined; @@ -363,7 +349,7 @@ describe('openx analytics adapter', function() { }); describe('ts, auid', function() { - xit('OpenX is in auction and has a bid response', function() { + it.skip('OpenX is in auction and has a bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -379,7 +365,7 @@ describe('openx analytics adapter', function() { }); }); - xit('OpenX is in auction but no bid response', function() { + it.skip('OpenX is in auction but no bid response', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -394,7 +380,7 @@ describe('openx analytics adapter', function() { expect(queryData).to.not.have.key('ts'); }); - xit('OpenX is not in auction', function() { + it.skip('OpenX is not in auction', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedCloseX], @@ -407,7 +393,7 @@ describe('openx analytics adapter', function() { }); describe('hb.exn, hb.sts, hb.ets, hb.bv, hb.crid, hb.to', function() { - xit('2 bidders in auction', function() { + it.skip('2 bidders in auction', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -440,7 +426,7 @@ describe('openx analytics adapter', function() { }); }); - xit('OpenX timed out', function() { + it.skip('OpenX timed out', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], @@ -533,7 +519,8 @@ describe('openx analytics adapter', function() { simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], - [AUCTION_END, auctionEndDFPWon], + [BID_RESPONSE, bidResponseOpenX], + [AUCTION_END, auctionEnd], [SLOT_LOADED, slotLoadDFPWin] ]); @@ -562,7 +549,7 @@ describe('openx analytics adapter', function() { expect(biddersRequests.length).to.equal(1); expect(biddersRequests[0]).to.equal('openx'); - expect(biddersResponded.length).to.equal(0); + expect(biddersResponded.length).to.equal(1); let bidWonEventInfoList = auctionData.events.filter(function (event) { return event.eventType === BID_WON && event.args.auctionId === auctionId; From a7d1e1fc4ed30048c3dbafd95579b9c5ace3530f Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Tue, 14 Apr 2020 22:47:10 -0700 Subject: [PATCH 23/32] Added functionality for Analytics Auction Schema. Added a defaults for config fields renamed initOptions to analyticsConfig googletag event tracking switches between different handlers --- modules/openxAnalyticsAdapter.js | 523 ++++++++++++++++-- .../modules/openxAnalyticsAdapter_spec.js | 461 +++++++++++++++ 2 files changed, 938 insertions(+), 46 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 83f9f59cb5d..db302b22d0b 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -3,6 +3,9 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; +//* ******* V2 Code +import { ajax } from '../src/ajax.js'; + // temp dependency on zlib to minimize payload const zlib = require('zlib'); // eslint-disable-line @@ -26,19 +29,43 @@ const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; const bidTimeoutConst = CONSTANTS.EVENTS.BID_TIMEOUT; -const SLOT_LOADED = 'slotOnload' +const SLOT_LOADED = 'slotOnload'; -let googletag = window.googletag || {}; -googletag.cmd = googletag.cmd || []; +/** + * @typedef {Object} AnalyticsConfig + * @property {string} publisherPlatformId + * @property {number} publisherAccountId + * @property {number} sampling + * @property {boolean} testPipeline + * @property {number} slotLoadWaitTime + * @property {Object} utmTagData + * @property {string} adIdKey + * @property {number} payloadSendDelayTime + * @property {Array}adUnits + */ -let initOptions = { - publisherPlatformId: '', - publisherAccountId: -1, - testCode: 'default', - utmTagData: [], +/** + * @type {AnalyticsConfig} + */ +const DEFAULT_ANALYTICS_CONFIG = { + publisherPlatformId: void (0), + publisherAccountId: void (0), + sampling: 0.05, // default sampling rate of 5% + testPipeline: false, + adIdKey: 'hb_adid', + utmTagData: {}, adUnits: [], - slotLoadWaitTime: 0 + slotLoadWaitTime: DEFAULT_SLOT_LOAD_BUFFER_TIME, + payloadSendDelayTime: AUCTION_END_WAIT_TIME, }; + +let googletag = window.googletag || {}; +googletag.cmd = googletag.cmd || []; + +/** + * @type {AnalyticsConfig} + */ +let analyticsConfig; let eventStack = {}; let loadedAdSlots = {}; @@ -80,7 +107,7 @@ function updateSessionId() { let newSessionId = utils.generateUUID(); localStorage.setItem(buildSessionIdLocalStorageKey(), newSessionId); } - initOptions.sessionId = getSessionId(); + analyticsConfig.sessionId = getSessionId(); updateSessionIdTimeout(); } @@ -119,10 +146,10 @@ function buildUtmLocalStorageKey(utmMarkKey) { } function getPublisherPlatformId() { - if (initOptions.publisherPlatformId !== undefined) { - if (typeof initOptions.publisherPlatformId === 'string') { - if (initOptions.publisherPlatformId !== '') { - return initOptions.publisherPlatformId; + if (analyticsConfig.publisherPlatformId !== undefined) { + if (typeof analyticsConfig.publisherPlatformId === 'string') { + if (analyticsConfig.publisherPlatformId !== '') { + return analyticsConfig.publisherPlatformId; } else { utils.logError('OX: Invalid PublisherPlatformId'); return null; @@ -138,10 +165,10 @@ function getPublisherPlatformId() { } function getPublisherAccountId() { - if (initOptions.publisherAccountId !== undefined) { - if (typeof initOptions.publisherAccountId === 'number') { - if (initOptions.publisherAccountId > -1) { - return initOptions.publisherAccountId; + if (analyticsConfig.publisherAccountId !== undefined) { + if (typeof analyticsConfig.publisherAccountId === 'number') { + if (analyticsConfig.publisherAccountId > -1) { + return analyticsConfig.publisherAccountId; } else { utils.logError('OX: Invalid PublisherAccountId'); return null; @@ -157,9 +184,9 @@ function getPublisherAccountId() { } function getTestCode() { - if (initOptions.testCode !== undefined) { - if (typeof initOptions.testCode === 'string') { - return initOptions.testCode; + if (analyticsConfig.testCode !== undefined) { + if (typeof analyticsConfig.testCode === 'string') { + return analyticsConfig.testCode; } else { utils.logError('OX: Invalid datatype for testCode'); return null; @@ -181,21 +208,21 @@ function checkInitOptions() { } function checkAdUnitConfig() { - if (typeof initOptions.adUnits === 'undefined') { + if (typeof analyticsConfig.adUnits === 'undefined') { return false; } - return initOptions.adUnits.length > 0; + return analyticsConfig.adUnits.length > 0; } function buildEventStack(auctionId) { - eventStack[auctionId].options = initOptions; + eventStack[auctionId].options = analyticsConfig; utils.logInfo('OX: Options Initialized', eventStack); } function filterBidsByAdUnit(bids) { var filteredBids = []; bids.forEach(function(bid) { - if (includes(initOptions.adUnits, bid.placementCode)) { + if (includes(analyticsConfig.adUnits, bid.placementCode)) { filteredBids.push(bid); } }); @@ -206,7 +233,7 @@ function isValidEvent(eventType, adUnitCode) { if (checkAdUnitConfig()) { let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst, bidTimeoutConst]; if ( - !includes(initOptions.adUnits, adUnitCode) && + !includes(analyticsConfig.adUnits, adUnitCode) && includes(validationEvents, eventType) ) { return false; @@ -329,17 +356,10 @@ function onSlotLoaded({ slot }) { eventStack[auctionId] = null; } delete loadedAdSlots[auctionId]; - }, initOptions.slotLoadWaitTime); + }, analyticsConfig.slotLoadWaitTime); } } -googletag.cmd.push(function() { - googletag.pubads().addEventListener(SLOT_LOADED, function(args) { - utils.logInfo('OX: SlotOnLoad event triggered'); - onSlotLoaded(args); - }); -}); - let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { track({ eventType, args }) { if (!checkInitOptions()) { @@ -382,12 +402,12 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { } delete loadedAdSlots[auctionId]; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); + }, analyticsConfig.payloadSendDelayTime); } else { setTimeout(function() { eventStack[auctionId] = null; // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, AUCTION_END_WAIT_TIME); + }, analyticsConfig.payloadSendDelayTime); } } else if (eventType === bidTimeoutConst) { // utils.logInfo('SA: Bid Timedout for', auctionId); @@ -398,19 +418,32 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; -openxAdapter.enableAnalytics = function(config) { - initOptions = config.options; - initOptions.testCode = getTestCode(); - initOptions.utmTagData = this.buildUtmTagData(); +openxAdapter.enableAnalytics = function(adapterConfig) { + analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; + analyticsConfig.testCode = getTestCode(); + analyticsConfig.utmTagData = this.buildUtmTagData(); + utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); + + if (analyticsConfig.testPipeline) { + // override track method with v2 handlers + openxAdapter.track = prebidAnalyticsEventHandler; - if (!initOptions.slotLoadWaitTime) { - initOptions.slotLoadWaitTime = DEFAULT_SLOT_LOAD_BUFFER_TIME + googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, args => { + openxAdapter.track({ eventType: SLOT_LOADED, args }); + utils.logInfo('OX: SlotOnLoad event triggered'); + }); + }); + } else { + googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, function(args) { + utils.logInfo('OX: SlotOnLoad event triggered'); + onSlotLoaded(args); + }); + }); } - utils.logInfo('OpenX Analytics enabled with config', initOptions); - // set default sampling rate to 5% - config.options.sampling = config.options.sampling || 0.05; - openxAdapter.originEnableAnalytics(config); + openxAdapter.originEnableAnalytics(adapterConfig); }; openxAdapter.buildUtmTagData = function() { @@ -634,7 +667,405 @@ adapterManager.registerAnalyticsAdapter({ code: 'openx' }); +const { + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } +} = CONSTANTS; + +const ENDPOINT = 'https://prebid.openx.net/ox/analytics'; +let auctionMap = {}; +let auctionOrder = 1; // tracks the number of auctions ran on the page + +function prebidAnalyticsEventHandler({eventType, args}) { + utils.logMessage(eventType, Object.assign({}, args)); + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args); + break; + case BID_REQUESTED: + onBidRequested(args); + break; + case BID_RESPONSE: + onBidResponse(args); + break; + case BID_TIMEOUT: + onBidTimeout(args); + break; + case BID_WON: + onBidWon(args); + break; + case SLOT_LOADED: + onSlotLoadedV2(args); + break; + } +} + +/* +TODO: type Auction +auctionId: "526ce090-e42e-4444-996f-ea78cde2244d" +timestamp: 1586675964364 +auctionEnd: undefined +auctionStatus: "inProgress" +adUnits: [{…}] +adUnitCodes: ["video1"] +labels: undefined +bidderRequests: (2) [{…}, {…}] +noBids: [] +bidsReceived: [] +winningBids: [] +timeout: 3000 +config: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, testPipeline: true} + */ +function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { + auctionMap[auctionId] = { + id: auctionId, + startTime, + timeout, + auctionOrder, + adUnitCodesCount: adUnitCodes.length, + adunitCodesRenderedCount: 0, + auctionCompleted: false, + auctionSendDelayTimer: void (0), + }; + + // setup adunit properties in map + auctionMap[auctionId].adUnitCodeToBidderRequestMap = adUnitCodes.reduce((obj, adunitCode) => { + obj[adunitCode] = {}; + return obj; + }, {}); + + auctionOrder++; +} + +// TODO: type BidRequest +function onBidRequested(bidRequest) { + const {auctionId, auctionStart, refererInfo, bids: bidderRequests, start} = bidRequest; + const auction = auctionMap[auctionId]; + const adUnitCodeToBidderRequestMap = auction.adUnitCodeToBidderRequestMap; + + bidderRequests.forEach(bidderRequest => { + const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src } = bidderRequest; + + adUnitCodeToBidderRequestMap[adUnitCode][requestId] = { + bidder, + params, + mediaTypes, + source: src, + startTime: start, + timedOut: false, + bids: {} + }; + }); +} + +/** + * + * @param {BidResponse} bidResponse + */ +function onBidResponse(bidResponse) { + let { + auctionId, + adUnitCode, + requestId, + cpm, + creativeId, + requestTimestamp, + responseTimestamp, + ts, + mediaType, + dealId, + ttl, + netRevenue, + currency, + originalCpm, + originalCurrency, + width, + height, + timeToRespond: latency, + adId, + meta + } = bidResponse; + + auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].bids[adId] = { + cpm, + creativeId, + requestTimestamp, + responseTimestamp, + ts, + adId, + meta, + mediaType, + dealId, + ttl, + netRevenue, + currency, + originalCpm, + originalCurrency, + width, + height, + latency, + winner: false, + rendered: false, + renderTime: 0, + }; +} + +function onBidTimeout(args) { + utils + ._map(args, value => value) + .forEach(({ auctionId, adUnitCode, bidId: requestId }) => { + auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].timedOut = true; + }); +} + +/** + * + * @param {BidResponse} bidResponse + */ +function onBidWon(bidResponse) { + const { auctionId, adUnitCode, requestId, adId } = bidResponse; + auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].bids[adId].winner = true; +} + +/** + * + * @param {GoogleTagSlot} slot + * @param {string} serviceName + */ +function onSlotLoadedV2({ slot }) { + const renderTime = Date.now(); + const auction = getAuctionByGoogleTagSLot(slot); + + if (!auction) { + return; // slot is not participating in a prebid auction + } + + // reset the delay timer to send the auction data + if (auction.auctionSendDelayTimer) { + clearTimeout(auction.auctionSendDelayTimer); + auction.auctionSendDelayTimer = void (0); + } + + // track that an adunit code has completed within an auction + auction.adunitCodesRenderedCount++; + + // mark adunit as rendered + const adId = slot.getTargeting('hb_adid')[0]; + const adUnit = getAdUnitByAuctionAndAdId(auction, adId); + + if (adUnit) { + adUnit.rendered = true; + adUnit.renderTime = renderTime; + } + + // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked + // add additional padding when not all slots are rendered + const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount + ? analyticsConfig.slotLoadWaitTime + : analyticsConfig.slotLoadWaitTime + 500; + + auction.auctionSendDelayTimer = setTimeout(() => { + let payload = JSON.stringify([buildAuctionPayload(auction)]); + ajax(ENDPOINT, deleteAuctionMap, payload, { contentType: 'application/json' }); + + function deleteAuctionMap() { + delete auctionMap[auction.id]; + } + }, delayTime); + + auction.auctionCompleted = true; +} + +function getAuctionByGoogleTagSLot(slot) { + let slotAdunitCodes = [slot.getSlotElementId(), slot.getAdUnitPath()]; + let slotAuction; + + utils._each(auctionMap, auction => { + utils._each(auction.adUnitCodeToBidderRequestMap, (bidderRequestIdMap, adUnitCode) => { + if (slotAdunitCodes.includes(adUnitCode)) { + slotAuction = auction; + } + }); + }); + + return slotAuction; +} + +function buildAuctionPayload(auction) { + let {startTime, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction; + + return { + publisherPlatformId: analyticsConfig.publisherPlatformId, + publisherAccountId: analyticsConfig.publisherAccountId, + startTime, + timeLimit: timeout, + auctionOrder, + deviceType: detectMob() ? 'Mobile' : 'Desktop', + deviceOSType: detectOS(), + browser: detectBrowser(), + testCode: analyticsConfig.testCode, + bidRequests: buildBidRequestsPayload(adUnitCodeToBidderRequestMap), + }; + + function buildBidRequestsPayload(adUnitCodeToBidderRequestMap) { + return utils._map(adUnitCodeToBidderRequestMap, (bidderRequestMap, adUnitCode) => { + return utils._map(bidderRequestMap, (bidderRequest) => { + let {bidder, source, bids, mediaTypes, timedOut} = bidderRequest; + return { + adUnitCode, + bidder, + source, + hasBidderResponded: Object.keys(bids).length > 0, + availableAdSizes: getMediaTypeSizes(mediaTypes), + availableMediaTypes: getMediaTypes(mediaTypes), + timedOut, + bidResponses: utils._map(bidderRequest.bids, (bidderBidResponse) => { + let { + cpm, + creativeId, + responseTimestamp, + ts, + adId, + meta, + mediaType, + dealId, + ttl, + netRevenue, + currency, + originalCpm, + originalCurrency, + width, + height, + latency, + winner, + rendered, + renderTime, + } = bidderBidResponse; + + return { + microCPM: cpm * 1000, + netRevenue, + currency, + mediaType, + height, + width, + size: `${width}x${height}`, + dealId, + latency, + ttl, + winner, + creativeId, + ts, + rendered, + renderTime, + meta, + } + }) + } + }) + }).flat(); + } + + function getMediaTypeSizes(mediaTypes) { + return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => { + return utils.parseSizesInput(mediaTypeConfig.sizes) + .map(size => `${mediaType}_${size}`); + }).flat(); + } + + function getMediaTypes(mediaTypes) { + return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType); + } +} + +function getAdUnitByAuctionAndAdId(auction, adId) { + let adunit; + + utils._each(auction.adUnitCodeToBidderRequestMap, (bidderRequestIdMap) => { + utils._each(bidderRequestIdMap, bidderRequest => { + utils._each(bidderRequest.bids, (bid, bidId) => { + if (bidId === adId) { + adunit = bid; + } + }); + }); + }); + + return adunit; +} + export default Object.assign({ adapter: openxAdapter, auctionEndWaitTime: AUCTION_END_WAIT_TIME }); + +/** + * Test Helper Functions + */ + +// reset the cache for unit tests +openxAdapter.reset = function() { + auctionMap = {}; + auctionOrder = 1; +}; + +/** + * Type Definitions + */ + +/** + * @typedef {Object} BidResponse + * @property {string} auctionId - Auction ID of the request this bid responded to + * @property {string} bidderCode - The bidder code. Used by ad server’s line items to identify bidders + * @property {string} adId - The unique identifier of a bid creative. It’s used by the line item’s creative as in this example. + * @property {number} width - The width of the returned creative size. + * @property {number} height - The height of the returned creative size. + * @property {string} size - The width x height of the returned creative size. + * @property {number} originalCpm - The original bid price from the bidder prior to bid adjustments + * @property {number} cpm - The exact bid price from the bidder + * @property {string} originalCurrency - Original currency of the bid prior to bid adjustments + * @property {string} currency - 3-letter ISO 4217 code defining the currency of the bid. + * @property {Boolean} netRevenue - True if bid is Net, False if Gross + * @property {number} requestTimestamp - The time stamp when the bid request is sent out in milliseconds + * @property {number} responseTimestamp - The time stamp when the bid response is received in milliseconds + * @property {number} timeToRespond - The amount of time for the bidder to respond with the bid + * @property {string} adUnitCode - adUnitCode to get the bid responses for + * @property {number} creativeId - Bidder-specific creative ID + * @property {string} mediaType - One of: banner, native, video banner + * @property {string} [dealId] - (Optional) If the bid is associated with a Deal, this field contains the deal ID. + * @property {Object} adserverTargeting - Contains all the adserver targeting parameters + * @property {string} [ad] - Contains the ad payload for banner ads. + * @property {string} [vastUrl] - URL where the VAST document can be retrieved when ready for display. + * @property {string} [vastImpUrl] - Optional; only usable with vastUrl and requires prebid cache to be enabled. + * An impression tracking URL to serve with video Ad + * @property {string} [vastXml] - XML for VAST document to be cached for later retrieval. + * @property {Object} [native] - Contains native key value pairs. + * @property {string} status - Status of the bid. Possible values: targetingSet, rendered "targetingSet" + * @property {string} statusMessage - The bid’s status message “Bid returned empty or error response” or “Bid available” + * @property {number} ttl - How long (in seconds) this bid is considered valid. See this FAQ entry for more info. 300 + * @property {string} requestId - Used to tie this bid back to the request + * @property {string} mediaType - Specifies the type of media type. One of: banner, video, native + * @property {string} source - Whether this bid response came from a client-side or server side request. One of: client, server. + * @property {string} pbLg - CPM quantized to a granularity: Low (pbLg) + * @property {string} pbMg - CPM quantized to a granularity: Medium (pbMg) + * @property {string} pbHg - CPM quantized to a granularity: High (pbHg) + * @property {string} pbAg - CPM quantized to a granularity: Auto (pbAg) + * @property {string} pbDg - CPM quantized to a granularity: Dense (pbDg) + * @property {BidResponseMeta} [meta] - Object containing metadata about the bid + * }} + */ + +/** + * @typedef {Object} BidResponseMeta + * @property {number} [networkId] Bidder-specific Network/DSP Id + * @property {string} [networkName] - Network/DSP Name. example: "NetworkN" + * @property {number} [agencyId] - Bidder-specific Agency ID. example: 2222 + * @property {string} [agencyName] - Agency Name. example: "Agency, Inc." + * @property {number} [advertiserId] - Bidder-specific Advertiser ID. example: 3333 + * @property {string} [advertiserName] - Advertiser Name. example: "AdvertiserA" + * @property {Array} [advertiserDomains] - Array of Advertiser Domains for the landing page(s). This is an array + * to align with the OpenRTB ‘adomain’ field.. example: ["advertisera.com"] + * @property {number} [brandId] - Bidder-specific Brand ID (some advertisers may have many brands). example: 4444 + * @property {string} [brandName] - Brand Name. example: "BrandB" + * @property {string} [primaryCatId] - Primary IAB category ID. example: "IAB-111" + * @property {Array} [secondaryCatIds] - Array of secondary IAB category IDs. example: ["IAB-222","IAB-333"] + */ diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index dee7a287a6e..f41d6a7c96b 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -565,4 +565,465 @@ describe('openx analytics adapter', function() { }); }); }); + + describe('when test pipeline is enabled', function () { + const AD_UNIT_CODE = 'test-div-1'; + + const auctionInit = { + auctionId: 'test-auction-id', + timestamp: 1586000000000, + timeout: 3000, + adUnitCodes: [AD_UNIT_CODE], + }; + + const bidRequestedOpenX = { + auctionId: 'test-auction-id', + auctionStart: 1586000000000, + bids: [ + { + adUnitCode: AD_UNIT_CODE, + bidId: 'test-openx-request-id', + bidder: 'openx', + params: { unit: 'test-openx-ad-unit-id' }, + } + ], + start: 1586000000010 + }; + + const bidRequestedCloseX = { + auctionId: 'test-auction-id', + auctionStart: 1586000000000, + bids: [ + { + adUnitCode: AD_UNIT_CODE, + bidId: 'test-closex-request-id', + bidder: 'closex', + params: { unit: 'test-closex-ad-unit-id' }, + } + ], + start: 1586000000020 + }; + + const bidResponseOpenX = { + adUnitCode: AD_UNIT_CODE, + cpm: 0.5, + netRevenue: true, + requestId: 'test-openx-request-id', + mediaType: 'banner', + width: 300, + height: 250, + adId: 'test-openx-ad-id', + auctionId: 'test-auction-id', + creativeId: 'openx-crid', + currency: 'USD', + timeToRespond: 100, + responseTimestamp: 1586000000030, + ts: 'test-openx-ts' + }; + + const bidResponseCloseX = { + adUnitCode: AD_UNIT_CODE, + cpm: 0.3, + netRevenue: true, + requestId: 'test-closex-request-id', + mediaType: 'video', + width: 300, + height: 250, + adId: 'test-closex-ad-id', + auctionId: 'test-auction-id', + creativeId: 'closex-crid', + currency: 'USD', + timeToRespond: 200, + dealId: 'test-closex-deal-id', + responseTimestamp: 1586000000040, + ts: 'test-closex-ts' + }; + + const bidTimeoutOpenX = [{ + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-openx-request-id' + }]; + + const bidTimeoutCloseX = [{ + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-closex-request-id' + }]; + + const bidWonOpenX = { + requestId: 'test-openx-request-id', + adId: 'test-openx-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' + }; + + const bidWonCloseX = { + requestId: 'test-closex-request-id', + adId: 'test-closex-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' + }; + + function simulateAuction(events) { + let highestBid; + + events.forEach(event => { + const [eventType, args] = event; + openxAdapter.track({ eventType, args }); + if (eventType === BID_RESPONSE) { + highestBid = highestBid || args; + if (highestBid.cpm < args.cpm) { + highestBid = args; + } + } + }); + + openxAdapter.track({ + eventType: SLOT_LOADED, + args: { + slot: { + getAdUnitPath: () => { + return '/12345678/test_ad_unit'; + }, + getSlotElementId: () => { + return AD_UNIT_CODE; + }, + getTargeting: sinon + .stub() + .withArgs('hb_adid') + .returns(highestBid ? [highestBid.adId] : []) + } + } + }); + } + + const SLOT_LOAD_WAIT_TIME = 10; + let clock; + + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(); + }); + + afterEach(function() { + events.getEvents.restore(); + clock.restore(); + }); + + describe('when there is an auction', function () { + let auction; + let auction2; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + ]); + + simulateAuction([ + [AUCTION_INIT, {auctionId: 'second-auction-id', ...auctionInit} ], + ]); + + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + auction2 = JSON.parse(server.requests[1].requestBody)[0]; + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track auction start time', function () { + expect(auction.startTime).to.equal(auctionInit.timestamp); + }); + + it('should track auction time limit', function () { + expect(auction.timeLimit).to.equal(auctionInit.timeout); + }); + + it('should track the \'default\' test code', function () { + expect(auction.testCode).to.equal('default'); + }); + + it('should track auction count', function () { + expect(auction.auctionOrder).to.equal(1); + expect(auction2.auctionOrder).to.equal(2); + }); + }); + + describe('when there is a custom test code', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + testCode: 'test-code', + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track the custom test code', function () { + expect(auction.testCode).to.equal('test-code'); + }); + }); + + describe('when there are bid requests', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], + [BID_REQUESTED, bidRequestedOpenX], + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track the bidder', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + + expect(openxBidder.bidder).to.equal('openx'); + expect(closexBidder.bidder).to.equal('closex'); + }); + + it('should track the adunit code', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + + expect(openxBidder.adUnitCode).to.equal(AD_UNIT_CODE); + expect(closexBidder.adUnitCode).to.equal(AD_UNIT_CODE); + }); + + it('should not have responded', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + + expect(openxBidder.hasBidderResponded).to.equal(false); + expect(closexBidder.hasBidderResponded).to.equal(false); + }); + }); + + describe('when there are request timeouts', function () { + let auction; + let openxBidRequest; + let closexBidRequest; + + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], + [BID_REQUESTED, bidRequestedOpenX], + [BID_TIMEOUT, bidTimeoutCloseX], + [BID_TIMEOUT, bidTimeoutOpenX], + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + openxBidRequest = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + closexBidRequest = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track the timeout', function () { + expect(openxBidRequest.timedOut).to.equal(true); + expect(closexBidRequest.timedOut).to.equal(true); + }); + }); + + describe('when there are bid responses', function () { + let auction; + let openxBidResponse; + let closexBidResponse; + + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX] + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + openxBidResponse = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx').bidResponses[0]; + closexBidResponse = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex').bidResponses[0]; + }); + + afterEach(function () { + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track the cpm in microCPM', function () { + expect(openxBidResponse.microCPM).to.equal(bidResponseOpenX.cpm * 1000); + expect(closexBidResponse.microCPM).to.equal(bidResponseCloseX.cpm * 1000); + }); + + it('should track if the bid is in net revenue', function () { + expect(openxBidResponse.netRevenue).to.equal(bidResponseOpenX.netRevenue); + expect(closexBidResponse.netRevenue).to.equal(bidResponseCloseX.netRevenue); + }); + + it('should track the mediaType', function () { + expect(openxBidResponse.mediaType).to.equal(bidResponseOpenX.mediaType); + expect(closexBidResponse.mediaType).to.equal(bidResponseCloseX.mediaType); + }); + + it('should track the currency', function () { + expect(openxBidResponse.currency).to.equal(bidResponseOpenX.currency); + expect(closexBidResponse.currency).to.equal(bidResponseCloseX.currency); + }); + + it('should track the ad width and height', function () { + expect(openxBidResponse.width).to.equal(bidResponseOpenX.width); + expect(openxBidResponse.height).to.equal(bidResponseOpenX.height); + + expect(closexBidResponse.width).to.equal(bidResponseCloseX.width); + expect(closexBidResponse.height).to.equal(bidResponseCloseX.height); + }); + + it('should track the bid dealId', function () { + expect(openxBidResponse.dealId).to.equal(bidResponseOpenX.dealId); // no deal id defined + expect(closexBidResponse.dealId).to.equal(bidResponseCloseX.dealId); // deal id defined + }); + + it('should track the bid\'s latency', function () { + expect(openxBidResponse.latency).to.equal(bidResponseOpenX.timeToRespond); + expect(closexBidResponse.latency).to.equal(bidResponseCloseX.timeToRespond); + }); + + it('should not have any bid winners', function () { + expect(openxBidResponse.winner).to.equal(false); + expect(closexBidResponse.winner).to.equal(false); + }); + + it('should track the bid currency', function () { + expect(openxBidResponse.currency).to.equal(bidResponseOpenX.currency); + expect(closexBidResponse.currency).to.equal(bidResponseCloseX.currency); + }); + }); + + describe('when are bidder wins', function () { + const CURRENT_TIME = 1586000000000; + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 'test-publisher-account-id', + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + // set current time + clock = sinon.useFakeTimers(CURRENT_TIME); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX], + [BID_WON, bidWonOpenX] + ]); + + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + }); + + afterEach(function () { + clock.restore(); + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should track that bidder as the winner', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({winner: true}); + }); + + it('should track that bidder as the losers', function () { + let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + expect(closexBidder.bidResponses[0]).to.contain({winner: false}); + }); + + it('should track that winning bid rendered', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({rendered: true}); + }); + + it('should track that winning bid render time', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({renderTime: CURRENT_TIME}); + }); + }); + }); }); From a78433877ad347a83c2cb1df4e69c2d324860721 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 15 Apr 2020 17:08:23 -0700 Subject: [PATCH 24/32] added line to designate the beginning of the majority of v2 code. --- modules/openxAnalyticsAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index db302b22d0b..6519d5e4dfd 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -667,6 +667,7 @@ adapterManager.registerAnalyticsAdapter({ code: 'openx' }); +//* ******* V2 Code ******* const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } } = CONSTANTS; From a1bdb2a8e11eaf05911e01bb3f7dcb4bbf52309b Mon Sep 17 00:00:00 2001 From: suribabu95 Date: Fri, 17 Apr 2020 20:50:25 +0530 Subject: [PATCH 25/32] tracking and publishing adposition info (in ad unit info) --- modules/openxAnalyticsAdapter.js | 127 +++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 6519d5e4dfd..9f7dfc9bb44 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -272,24 +272,23 @@ function removeads(info) { } function getAuctionIdByAdId(adId) { - let auctionId; - utils._map(eventStack, value => value).forEach(function(auctionInfo) { + let auctionId, adUnitCode; + utils._each(eventStack, function(auctionInfo) { if (auctionInfo && auctionInfo.events) { - let bidWonEvent; - bidWonEvent = auctionInfo.events.filter(function(eventsInfo) { - return eventsInfo.eventType === 'bidWon'; - }); - - if (bidWonEvent.length > 0) { - bidWonEvent.forEach(function(bidWon) { - if (bidWon.args && bidWon.args.adId && bidWon.args.adId === adId) { - auctionId = bidWon.args.auctionId; + auctionInfo.events.forEach(function(eventsInfo) { + if(eventsInfo.eventType === bidWonConst) { + if (eventsInfo.args && eventsInfo.args.adId && eventsInfo.args.adId === adId) { + auctionId = eventsInfo.args.auctionId; + adUnitCode = eventsInfo.args.adUnitCode; } - }); - } + } + }); } }); - return auctionId; + return { + auctionId: auctionId, + adUnitCode: adUnitCode + }; } function getAllAdUnitCodesByAuctionId(auctionId) { @@ -329,22 +328,31 @@ function onSlotLoaded({ slot }) { const slotElementId = slot.getSlotElementId(); const adUnitPath = slot.getAdUnitPath(); - let auctionId = getAuctionIdByAdId(adId); - if (!auctionId) { - auctionId = getAuctionIdByAdUnitCode(slotElementId); + // AdId will be present in `eventStack` only if winner is through prebid auction. + // Assuming either `adUnitPath` or `slotElementId` to be adUnitCode because there is no other way - + // to know for which ad unit the slot is rendered + + let auctionId, adUnitCode; + let adUnitInfo = getAuctionIdByAdId(adId); + if(adUnitInfo && adUnitInfo.auctionId && adUnitInfo.adUnitCode) { + auctionId = adUnitInfo.auctionId; + adUnitCode = adUnitInfo.adUnitCode; + } else { + adUnitCode = slotElementId; + auctionId = getAuctionIdByAdUnitCode(adUnitCode); if (!auctionId) { + adUnitCode = adUnitPath; auctionId = getAuctionIdByAdUnitCode(adUnitPath); } } let allSlotsLoaded = false; if (auctionId) { - if (!loadedAdSlots[auctionId]) { - loadedAdSlots[auctionId] = [] - } - loadedAdSlots[auctionId].push(slotElementId); + let adPosition = getAdPositionByElementId(slotElementId); + updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition); + let loadedAdUnitCodes = getLoadedAdUnitCodes(auctionId); let allAdUnitCodes = getAllAdUnitCodesByAuctionId(auctionId); - if (loadedAdSlots[auctionId].length === allAdUnitCodes.length) { + if (loadedAdUnitCodes.length === allAdUnitCodes.length) { allSlotsLoaded = true; } } @@ -601,6 +609,7 @@ function send(eventType, eventStack, auctionId) { var sourceBrowser = detectBrowser(); var sourceOs = detectOS(); // utils.logInfo('OX: AuctionId', auctionId); + pushAdPositionData(auctionId); var data = eventStack[auctionId]; var publisherPlatformId = eventStack[auctionId].options.publisherPlatformId; var publisherAccountId = eventStack[auctionId].options.publisherAccountId; @@ -655,6 +664,80 @@ function pushEvent(eventType, args, auctionId) { } } +function updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition) { + + if(auctionId && adUnitCode) { + if(!loadedAdSlots[auctionId]){ + loadedAdSlots[auctionId] = {}; + } + loadedAdSlots[auctionId][adUnitCode] = {}; + if(adPosition) { + loadedAdSlots[auctionId][adUnitCode] = { adPosition: adPosition }; + } + } else { + utils.logWarn("OX: Couldn't update loadedAdSlots information."); + } +} + +function getLoadedAdUnitCodes(auctionId) { + + return (!auctionId || !loadedAdSlots[auctionId] || typeof loadedAdSlots[auctionId] !== 'object') + ? [] : Object.keys(loadedAdSlots[auctionId]); +} + +function pushAdPositionData(auctionId) { + + if(auctionId && eventStack[auctionId] && eventStack[auctionId].events) { + + let adUnitPositionMap = loadedAdSlots[auctionId]; + if(adUnitPositionMap && JSON.stringify(adUnitPositionMap) !== "{}") { + + eventStack[auctionId].events.filter(function(event) { + return event.eventType === auctionEndConst; + }).forEach(function (auctionEndEvent) { + + if(auctionEndEvent.args && auctionEndEvent.args.adUnits) { + auctionEndEvent.args.adUnits.forEach(function (adUnitInfo) { + if(adUnitPositionMap[adUnitInfo.code] && adUnitPositionMap[adUnitInfo.code]["adPosition"]) { + adUnitInfo["adPosition"] = adUnitPositionMap[adUnitInfo.code]["adPosition"]; + } else { + adUnitInfo["adPosition"] = ""; + } + }) + } + }); + } + } +} + +function getAdPositionByElementId(elementId) { + + let elem = document.querySelector("#" + elementId); + let adPosition; + if (elem) { + let bounding = elem.getBoundingClientRect(); + if (bounding) { + let windowWidth = (window.innerWidth || document.documentElement.clientWidth); + let windowHeight = (window.innerHeight || document.documentElement.clientHeight); + + // intersection coordinates + let left = Math.max(0, bounding.left); + let right = Math.min(windowWidth, bounding.right); + let bottom = Math.min(windowHeight, bounding.bottom); + let top = Math.max(0, bounding.top); + + let intersectionArea = (right - left) * (bottom - top); + let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top); + + // Atleast 50% of intersection in window + adPosition = (intersectionArea * 2 >= adSlotArea) ? "ATF" : "BTF"; + } + } else { + utils.logWarn("OX: DOM element not for id " + elementId); + } + return adPosition; +} + openxAdapter.reset = function() { eventStack = {}; loadedAdSlots = {}; From c8273ef58834e04a7e71df2897cb1d18a8baa451 Mon Sep 17 00:00:00 2001 From: suribabu95 Date: Tue, 21 Apr 2020 19:53:16 +0530 Subject: [PATCH 26/32] Fixed edge cases for finding ad position using intersection area --- modules/openxAnalyticsAdapter.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 9f7dfc9bb44..05be60b482d 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -726,11 +726,16 @@ function getAdPositionByElementId(elementId) { let bottom = Math.min(windowHeight, bounding.bottom); let top = Math.max(0, bounding.top); - let intersectionArea = (right - left) * (bottom - top); + let intersectionWidth = right - left; + let intersectionHeight = bottom - top; + + let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0; let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top); - // Atleast 50% of intersection in window - adPosition = (intersectionArea * 2 >= adSlotArea) ? "ATF" : "BTF"; + if(adSlotArea > 0) { + // Atleast 50% of intersection in window + adPosition = (intersectionArea * 2 >= adSlotArea) ? "ATF" : "BTF"; + } } } else { utils.logWarn("OX: DOM element not for id " + elementId); From b1d1ef25d0ccc711bae4c5b56eda6c336c5b0469 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Thu, 23 Apr 2020 22:44:47 -0700 Subject: [PATCH 27/32] feat(openxAnalyticsAdaptor): PBID-350: Added flag to enable both endpoints * payloadWaitTime is now the default config to replace slotLoadWaitTime * added analytics config validation * updated unit tests after config validation broke everything --- modules/openxAnalyticsAdapter.js | 243 ++++++++----- .../modules/openxAnalyticsAdapter_spec.js | 338 ++++++++++++++++-- 2 files changed, 453 insertions(+), 128 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 05be60b482d..11481f0703e 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -19,7 +19,7 @@ const SCHEMA_VERSION = '0.1'; const MAX_RETRIES = 2; const MAX_TIMEOUT = 10000; -const AUCTION_END_WAIT_TIME = 2000; +const AUCTION_END_WAIT_TIME = 1000; const DEFAULT_SLOT_LOAD_BUFFER_TIME = 100; const auctionInitConst = CONSTANTS.EVENTS.AUCTION_INIT; @@ -36,11 +36,11 @@ const SLOT_LOADED = 'slotOnload'; * @property {string} publisherPlatformId * @property {number} publisherAccountId * @property {number} sampling + * @property {boolean} enableV2 * @property {boolean} testPipeline - * @property {number} slotLoadWaitTime * @property {Object} utmTagData * @property {string} adIdKey - * @property {number} payloadSendDelayTime + * @property {number} payloadWaitTime * @property {Array}adUnits */ @@ -51,12 +51,13 @@ const DEFAULT_ANALYTICS_CONFIG = { publisherPlatformId: void (0), publisherAccountId: void (0), sampling: 0.05, // default sampling rate of 5% + testCode: 'default', + enableV2: false, testPipeline: false, adIdKey: 'hb_adid', utmTagData: {}, adUnits: [], - slotLoadWaitTime: DEFAULT_SLOT_LOAD_BUFFER_TIME, - payloadSendDelayTime: AUCTION_END_WAIT_TIME, + payloadWaitTime: AUCTION_END_WAIT_TIME, }; let googletag = window.googletag || {}; @@ -66,6 +67,7 @@ googletag.cmd = googletag.cmd || []; * @type {AnalyticsConfig} */ let analyticsConfig; + let eventStack = {}; let loadedAdSlots = {}; @@ -364,94 +366,93 @@ function onSlotLoaded({ slot }) { eventStack[auctionId] = null; } delete loadedAdSlots[auctionId]; - }, analyticsConfig.slotLoadWaitTime); + }, analyticsConfig.payloadWaitTime); } } -let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }), { - track({ eventType, args }) { - if (!checkInitOptions()) { - send(eventType, {}, null); - return; - } - - let info = Object.assign({}, args); +let openxAdapter = Object.assign(adapter({ urlParam, analyticsType })); - if (info && info.ad) { - info.ad = ''; - } +openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; - // on bid timeout events, the info is an array of bids - let auctionId = eventType === CONSTANTS.EVENTS.BID_TIMEOUT - ? info[0].auctionId - : info.auctionId; - - if (eventType === auctionInitConst) { - eventStack[auctionId] = { options: {}, events: [] }; - // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); - } else if (eventType === bidWonConst) { - pushEvent(eventType, info, auctionId); - // utils.logInfo('OX: Bid won called for', auctionId); - } else if (eventType === auctionEndConst) { - pushEvent(eventType, removeads(info), auctionId); - // utils.logInfo('OX: Auction end called for', auctionId); - updateSessionId(); - buildEventStack(auctionId); - if (isValidEventStack(auctionId)) { - setTimeout(function() { - // utils.logInfo('OX: Sending data', eventStack); - if (eventStack[auctionId]) { - send( - eventType, - eventStack, - auctionId - ); - eventStack[auctionId] = null; - } - delete loadedAdSlots[auctionId]; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, analyticsConfig.payloadSendDelayTime); - } else { - setTimeout(function() { - eventStack[auctionId] = null; - // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); - }, analyticsConfig.payloadSendDelayTime); - } - } else if (eventType === bidTimeoutConst) { - // utils.logInfo('SA: Bid Timedout for', auctionId); - pushEvent(eventType, info, auctionId); - } +openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) { + // Backwards compatibility for external documentation + if(adapterConfig.options.slotLoadWaitTime){ + adapterConfig.options.payloadWaitTime = adapterConfig.options.slotLoadWaitTime; } -}); - -openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; -openxAdapter.enableAnalytics = function(adapterConfig) { - analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; - analyticsConfig.testCode = getTestCode(); - analyticsConfig.utmTagData = this.buildUtmTagData(); - utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); + if(isValidConfig(adapterConfig)){ + analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; + analyticsConfig.utmTagData = this.buildUtmTagData(); + utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); + + if (analyticsConfig.testPipeline) { + openxAdapter.track = (args) => { + prebidAnalyticsEventHandlerV1(args); + prebidAnalyticsEventHandlerV2(args); + }; + + googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, args => { + utils.logInfo('OX: SlotOnLoad event triggered'); + onSlotLoaded(args); + onSlotLoadedV2(args); + }); + }); - if (analyticsConfig.testPipeline) { - // override track method with v2 handlers - openxAdapter.track = prebidAnalyticsEventHandler; + } else if (analyticsConfig.enableV2) { + // override track method with v2 handlers + openxAdapter.track = prebidAnalyticsEventHandlerV2; - googletag.cmd.push(function() { - googletag.pubads().addEventListener(SLOT_LOADED, args => { - openxAdapter.track({ eventType: SLOT_LOADED, args }); - utils.logInfo('OX: SlotOnLoad event triggered'); + googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, args => { + openxAdapter.track({ eventType: SLOT_LOADED, args }); + utils.logInfo('OX: SlotOnLoad event triggered'); + }); }); - }); - } else { - googletag.cmd.push(function() { - googletag.pubads().addEventListener(SLOT_LOADED, function(args) { - utils.logInfo('OX: SlotOnLoad event triggered'); - onSlotLoaded(args); + } else { + openxAdapter.track = prebidAnalyticsEventHandlerV1; + googletag.cmd.push(function() { + googletag.pubads().addEventListener(SLOT_LOADED, function(args) { + utils.logInfo('OX: SlotOnLoad event triggered'); + onSlotLoaded(args); + }); }); - }); + } + + openxAdapter.originEnableAnalytics(adapterConfig); } - openxAdapter.originEnableAnalytics(adapterConfig); + function isValidConfig({options: analyticsOptions}){ + const fieldValidations = [ + // tuple of property, type, required + ['publisherPlatformId', 'string', true], + ['publisherAccountId', 'number', true], + ['sampling', 'number', false], + ['enableV2', 'boolean', false], + ['testPipeline', 'boolean', false], + ['adIdKey', 'string', false], + ['payloadWaitTime', 'number', false], + ]; + + let failedValidation = fieldValidations.find(([property, type, required]) => { + // if required, the property has to exist + // if property exists, type check value + return (required && !analyticsOptions.hasOwnProperty(property)) || + (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type); + }); + + if(failedValidation) { + let [property, type, required] = failedValidation; + + if(required){ + utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); + } else { + utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); + } + } + + return !failedValidation; + } }; openxAdapter.buildUtmTagData = function() { @@ -743,11 +744,6 @@ function getAdPositionByElementId(elementId) { return adPosition; } -openxAdapter.reset = function() { - eventStack = {}; - loadedAdSlots = {}; -}; - openxAdapter.slotOnLoad = onSlotLoaded; adapterManager.registerAnalyticsAdapter({ @@ -755,6 +751,60 @@ adapterManager.registerAnalyticsAdapter({ code: 'openx' }); +function prebidAnalyticsEventHandlerV1({eventType, args}){ + if (!checkInitOptions()) { + send(eventType, {}, null); + return; + } + + let info = Object.assign({}, args); + + if (info && info.ad) { + info.ad = ''; + } + + // on bid timeout events, the info is an array of bids + let auctionId = eventType === CONSTANTS.EVENTS.BID_TIMEOUT + ? info[0].auctionId + : info.auctionId; + + if (eventType === auctionInitConst) { + eventStack[auctionId] = { options: {}, events: [] }; + // utils.logInfo('OX: Event Stack updated after AuctionInit', eventStack); + } else if (eventType === bidWonConst) { + pushEvent(eventType, info, auctionId); + // utils.logInfo('OX: Bid won called for', auctionId); + } else if (eventType === auctionEndConst) { + pushEvent(eventType, removeads(info), auctionId); + // utils.logInfo('OX: Auction end called for', auctionId); + updateSessionId(); + buildEventStack(auctionId); + if (isValidEventStack(auctionId)) { + setTimeout(function() { + // utils.logInfo('OX: Sending data', eventStack); + if (eventStack[auctionId]) { + send( + eventType, + eventStack, + auctionId + ); + eventStack[auctionId] = null; + } + delete loadedAdSlots[auctionId]; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, analyticsConfig.payloadWaitTime); + } else { + setTimeout(function() { + eventStack[auctionId] = null; + // utils.logInfo('OX: Deleted Auction Info for auctionId', auctionId); + }, analyticsConfig.payloadWaitTime); + } + } else if (eventType === bidTimeoutConst) { + // utils.logInfo('SA: Bid Timedout for', auctionId); + pushEvent(eventType, info, auctionId); + } +} + //* ******* V2 Code ******* const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } @@ -764,7 +814,7 @@ const ENDPOINT = 'https://prebid.openx.net/ox/analytics'; let auctionMap = {}; let auctionOrder = 1; // tracks the number of auctions ran on the page -function prebidAnalyticsEventHandler({eventType, args}) { +function prebidAnalyticsEventHandlerV2({eventType, args}) { utils.logMessage(eventType, Object.assign({}, args)); switch (eventType) { case AUCTION_INIT: @@ -802,7 +852,7 @@ noBids: [] bidsReceived: [] winningBids: [] timeout: 3000 -config: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, testPipeline: true} +config: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true} */ function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { auctionMap[auctionId] = { @@ -949,8 +999,8 @@ function onSlotLoadedV2({ slot }) { // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked // add additional padding when not all slots are rendered const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount - ? analyticsConfig.slotLoadWaitTime - : analyticsConfig.slotLoadWaitTime + 500; + ? analyticsConfig.payloadWaitTime + : analyticsConfig.payloadWaitTime + 500; auction.auctionSendDelayTimer = setTimeout(() => { let payload = JSON.stringify([buildAuctionPayload(auction)]); @@ -1031,7 +1081,7 @@ function buildAuctionPayload(auction) { } = bidderBidResponse; return { - microCPM: cpm * 1000, + microCpm: cpm * 1000, netRevenue, currency, mediaType, @@ -1093,6 +1143,11 @@ export default Object.assign({ // reset the cache for unit tests openxAdapter.reset = function() { + // V1 data + eventStack = {}; + loadedAdSlots = {}; + + // V2 data auctionMap = {}; auctionOrder = 1; }; @@ -1145,15 +1200,15 @@ openxAdapter.reset = function() { /** * @typedef {Object} BidResponseMeta - * @property {number} [networkId] Bidder-specific Network/DSP Id + * @property {string} [networkId] Bidder-specific Network/DSP Id * @property {string} [networkName] - Network/DSP Name. example: "NetworkN" - * @property {number} [agencyId] - Bidder-specific Agency ID. example: 2222 + * @property {string} [agencyId] - Bidder-specific Agency ID. example: 2222 * @property {string} [agencyName] - Agency Name. example: "Agency, Inc." - * @property {number} [advertiserId] - Bidder-specific Advertiser ID. example: 3333 + * @property {string} [advertiserId] - Bidder-specific Advertiser ID. example: 3333 * @property {string} [advertiserName] - Advertiser Name. example: "AdvertiserA" * @property {Array} [advertiserDomains] - Array of Advertiser Domains for the landing page(s). This is an array * to align with the OpenRTB ‘adomain’ field.. example: ["advertisera.com"] - * @property {number} [brandId] - Bidder-specific Brand ID (some advertisers may have many brands). example: 4444 + * @property {string} [brandId] - Bidder-specific Brand ID (some advertisers may have many brands). example: 4444 * @property {string} [brandName] - Brand Name. example: "BrandB" * @property {string} [primaryCatId] - Primary IAB category ID. example: "IAB-111" * @property {Array} [secondaryCatIds] - Array of secondary IAB category IDs. example: ["IAB-222","IAB-333"] diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index f41d6a7c96b..e64248de53d 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -15,17 +15,35 @@ const zlib = require('zlib'); const openxAdapter = openxAdapterParams.adapter; describe('openx analytics adapter', function() { - it.skip('should require publisher id', function() { - sinon.spy(utils, 'logError'); + describe('when validating the configuration', function () { + let spy; + beforeEach(function () { + spy = sinon.spy(utils, 'logError'); + }); + + afterEach(function(){ + utils.logError.restore(); + }); - openxAdapter.enableAnalytics(); - expect( - utils.logError.calledWith( - 'OpenX analytics adapter: publisherId is required.' - ) - ).to.be.true; + it('should require publisher id when no configuration is passed', function() { + openxAdapter.enableAnalytics(); + expect(spy.firstCall.args[0]).to.match(/publisherPlatformId/); + expect(spy.firstCall.args[0]).to.match(/to exist/); + }); + + it('should validate types', function() { + openxAdapter.enableAnalytics({ + provider: 'openx', + options: { + publisherPlatformId: 'test platformId', + publisherAccountId: 123, + sampling: 'invalid-float' + } + }); - utils.logError.restore(); + expect(spy.firstCall.args[0]).to.match(/sampling/); + expect(spy.firstCall.args[0]).to.match(/type 'number'/); + }); }); describe('sending analytics event', function() { @@ -45,12 +63,12 @@ describe('openx analytics adapter', function() { clock.restore(); }); - const openxAdUnitInfo = {'code': 'div-1', + const openxAdUnitInfo = [{'code': 'div-1', 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, 'bids': [{'bidder': 'openx', 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}], 'sizes': [[320, 50]], - 'transactionId': 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873'}; + 'transactionId': 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873'}]; const bidRequestedOpenX = { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', @@ -204,7 +222,7 @@ describe('openx analytics adapter', function() { publisherAccountId: 537143056, sampling: 1.0, testCode: 'test-code-1', - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); }); @@ -463,8 +481,8 @@ describe('openx analytics adapter', function() { [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedOpenX], [BID_RESPONSE, bidResponseOpenX], + [AUCTION_END, auctionEnd], [BID_WON, bidWonOpenX], - [AUCTION_END, auctionEnd] ]); // Handle timeouts @@ -566,7 +584,7 @@ describe('openx analytics adapter', function() { }); }); - describe('when test pipeline is enabled', function () { + describe('when version 2 is enabled', function () { const AD_UNIT_CODE = 'test-div-1'; const auctionInit = { @@ -717,11 +735,11 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', + publisherAccountId: 123, publisherPlatformId: 'test-platform-id', sample: 1.0, - testPipeline: true, - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -766,12 +784,12 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', + publisherAccountId: 123, publisherPlatformId: 'test-platform-id', sample: 1.0, - testPipeline: true, + enableV2: true, testCode: 'test-code', - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -797,11 +815,11 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', + publisherAccountId: 123, publisherPlatformId: 'test-platform-id', sample: 1.0, - testPipeline: true, - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -852,11 +870,11 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', + publisherAccountId: 123, publisherPlatformId: 'test-platform-id', sample: 1.0, - testPipeline: true, - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -893,11 +911,11 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', + publisherAccountId: 123, publisherPlatformId: 'test-platform-id', sample: 1.0, - testPipeline: true, - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -921,8 +939,8 @@ describe('openx analytics adapter', function() { }); it('should track the cpm in microCPM', function () { - expect(openxBidResponse.microCPM).to.equal(bidResponseOpenX.cpm * 1000); - expect(closexBidResponse.microCPM).to.equal(bidResponseCloseX.cpm * 1000); + expect(openxBidResponse.microCpm).to.equal(bidResponseOpenX.cpm * 1000); + expect(closexBidResponse.microCpm).to.equal(bidResponseCloseX.cpm * 1000); }); it('should track if the bid is in net revenue', function () { @@ -975,11 +993,11 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 'test-publisher-account-id', publisherPlatformId: 'test-platform-id', + publisherAccountId: 123, sample: 1.0, - testPipeline: true, - slotLoadWaitTime: SLOT_LOAD_WAIT_TIME + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME } }); @@ -1026,4 +1044,256 @@ describe('openx analytics adapter', function() { }); }); }); + + describe('when we\'re testing the pipeline', function () { + const AD_UNIT_CODE = 'test-div-1'; + + const auctionInit = { + auctionId: 'test-auction-id', + timestamp: 1586000000000, + timeout: 3000, + adUnitCodes: [AD_UNIT_CODE], + }; + + const bidRequestedOpenX = { + auctionId: 'test-auction-id', + auctionStart: 1586000000000, + bids: [ + { + adUnitCode: AD_UNIT_CODE, + bidId: 'test-openx-request-id', + bidder: 'openx', + params: { unit: 'test-openx-ad-unit-id' }, + } + ], + start: 1586000000010 + }; + + const bidRequestedCloseX = { + auctionId: 'test-auction-id', + auctionStart: 1586000000000, + bids: [ + { + adUnitCode: AD_UNIT_CODE, + bidId: 'test-closex-request-id', + bidder: 'closex', + params: { unit: 'test-closex-ad-unit-id' }, + } + ], + start: 1586000000020 + }; + + const bidResponseOpenX = { + adUnitCode: AD_UNIT_CODE, + cpm: 0.5, + netRevenue: true, + requestId: 'test-openx-request-id', + mediaType: 'banner', + width: 300, + height: 250, + adId: 'test-openx-ad-id', + auctionId: 'test-auction-id', + creativeId: 'openx-crid', + currency: 'USD', + timeToRespond: 100, + responseTimestamp: 1586000000030, + ts: 'test-openx-ts' + }; + + const bidResponseCloseX = { + adUnitCode: AD_UNIT_CODE, + cpm: 0.3, + netRevenue: true, + requestId: 'test-closex-request-id', + mediaType: 'video', + width: 300, + height: 250, + adId: 'test-closex-ad-id', + auctionId: 'test-auction-id', + creativeId: 'closex-crid', + currency: 'USD', + timeToRespond: 200, + dealId: 'test-closex-deal-id', + responseTimestamp: 1586000000040, + ts: 'test-closex-ts' + }; + + const bidTimeoutOpenX = [{ + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-openx-request-id' + }]; + + const bidTimeoutCloseX = [{ + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-closex-request-id' + }]; + + const openxAdUnitInfo = [{'code': 'test-div-1', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bids': [{'bidder': 'openx', + 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}, + {'bidder': 'closex', + 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}], + 'sizes': [[300, 250]], + 'transactionId': 'test-transaction-id'}]; + + const auctionEnd = { + 'auctionId': 'test-auction-id', + 'timestamp': 1586000000000, + 'auctionEnd': 1586000000100, + 'auctionStatus': 'completed', + 'adUnits': openxAdUnitInfo, + 'adUnitCodes': [ + 'test-div-1' + ], + 'bidderRequests': [bidRequestedOpenX, bidRequestedCloseX], + 'noBids': [], + 'bidsReceived': [bidResponseOpenX, bidResponseCloseX], + 'winningBids': [bidResponseOpenX], + 'timeout': 300 + }; + + const bidWonOpenX = { + requestId: 'test-openx-request-id', + adId: 'test-openx-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' + }; + + const bidWonCloseX = { + requestId: 'test-closex-request-id', + adId: 'test-closex-ad-id', + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id' + }; + + let highestBid; + const onSlotLoadEvent = { + eventType: SLOT_LOADED, + args: { + slot: { + getAdUnitPath: () => { + return '/12345678/test_ad_unit'; + }, + getSlotElementId: () => { + return AD_UNIT_CODE; + }, + getTargeting: () => { + return highestBid ? [highestBid.adId] : [] + } + } + } + }; + function simulateAuction(events) { + events.forEach(event => { + const [eventType, args] = event; + openxAdapter.track({eventType, args}); + if (eventType === BID_RESPONSE) { + highestBid = highestBid || args; + if (highestBid.cpm < args.cpm) { + highestBid = args; + } + } + }); + openxAdapter.slotOnLoad(onSlotLoadEvent.args); + openxAdapter.track(onSlotLoadEvent); + } + + const SLOT_LOAD_WAIT_TIME = 10; + let clock; + + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(); + }); + + afterEach(function() { + events.getEvents.restore(); + clock.restore(); + }); + + describe('when are bidder wins', function () { + const CURRENT_TIME = 1586000000000; + let v1Auction; + let v2Auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherAccountId: 123123, + publisherPlatformId: 'test-platform-id', + sample: 1.0, + testPipeline: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME + } + }); + + // set current time + clock = sinon.useFakeTimers(CURRENT_TIME); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd], + [BID_WON, bidWonOpenX] + ]); + + clock.tick(SLOT_LOAD_WAIT_TIME + 1); + + // ******* + let compressedPayload = server.requests[0].requestBody; + let payloadBuffer = new Buffer(compressedPayload); + let unCompressedPayload = zlib.gunzipSync(payloadBuffer).toString(); + v1Auction = JSON.parse(unCompressedPayload); + + v2Auction = JSON.parse(server.requests[1].requestBody)[0]; + }); + + afterEach(function () { + clock.restore(); + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); + + it('should send out both payloads', function(){ + expect(server.requests.length).to.equal(2); + }); + + it('should track the bidder as the winner in both requests', function () { + let biddersRequests = []; + let biddersResponded = []; + v1Auction.events.forEach(function (event) { + if (event.eventType === AUCTION_END) { + event.args.bidderRequests.forEach(function(bidRequestInfo) { + if (bidRequestInfo.bids.length > 0) { + biddersRequests.push(bidRequestInfo.bids[0].bidder); + } + }); + event.args.bidsReceived.forEach(function(bidsInfo) { + biddersResponded.push(bidsInfo); + }); + } + }); + + expect(biddersRequests.length).to.equal(2); + expect(biddersRequests[0]).to.equal('openx'); + expect(biddersRequests[1]).to.equal('closex'); + expect(biddersResponded.length).to.equal(2); + + let bidWonEventInfoList = v1Auction.events.filter(function (event) { + return event.eventType === BID_WON && event.args.auctionId === bidWonOpenX.auctionId; + }); + + expect(bidWonEventInfoList.length).to.equal(1); + + + let openxBidder = v2Auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + expect(openxBidder.bidResponses[0]).to.contain({winner: true}); + }); + }); + }); }); From c80735966d4a63aae005ea66f48a51f09fea6141 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Mon, 27 Apr 2020 22:22:11 -0700 Subject: [PATCH 28/32] fix(openxAnalyticsAdaptor): fixed bug where slotloaded event does not always fire * added payloadWaitTimePadding * added auction state * added auction endTime --- modules/openxAnalyticsAdapter.js | 141 ++++++++++------- .../modules/openxAnalyticsAdapter_spec.js | 144 +++++++++++++----- 2 files changed, 196 insertions(+), 89 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 11481f0703e..529d55b684a 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -41,6 +41,7 @@ const SLOT_LOADED = 'slotOnload'; * @property {Object} utmTagData * @property {string} adIdKey * @property {number} payloadWaitTime + * @property {number} payloadWaitTimePadding * @property {Array}adUnits */ @@ -58,6 +59,7 @@ const DEFAULT_ANALYTICS_CONFIG = { utmTagData: {}, adUnits: [], payloadWaitTime: AUCTION_END_WAIT_TIME, + payloadWaitTimePadding: 100 }; let googletag = window.googletag || {}; @@ -278,7 +280,7 @@ function getAuctionIdByAdId(adId) { utils._each(eventStack, function(auctionInfo) { if (auctionInfo && auctionInfo.events) { auctionInfo.events.forEach(function(eventsInfo) { - if(eventsInfo.eventType === bidWonConst) { + if (eventsInfo.eventType === bidWonConst) { if (eventsInfo.args && eventsInfo.args.adId && eventsInfo.args.adId === adId) { auctionId = eventsInfo.args.auctionId; adUnitCode = eventsInfo.args.adUnitCode; @@ -336,7 +338,7 @@ function onSlotLoaded({ slot }) { let auctionId, adUnitCode; let adUnitInfo = getAuctionIdByAdId(adId); - if(adUnitInfo && adUnitInfo.auctionId && adUnitInfo.adUnitCode) { + if (adUnitInfo && adUnitInfo.auctionId && adUnitInfo.adUnitCode) { auctionId = adUnitInfo.auctionId; adUnitCode = adUnitInfo.adUnitCode; } else { @@ -374,13 +376,13 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType })); openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; -openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) { +openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { // Backwards compatibility for external documentation - if(adapterConfig.options.slotLoadWaitTime){ + if (adapterConfig.options.slotLoadWaitTime) { adapterConfig.options.payloadWaitTime = adapterConfig.options.slotLoadWaitTime; } - if(isValidConfig(adapterConfig)){ + if (isValidConfig(adapterConfig)) { analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; analyticsConfig.utmTagData = this.buildUtmTagData(); utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); @@ -398,7 +400,6 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) { onSlotLoadedV2(args); }); }); - } else if (analyticsConfig.enableV2) { // override track method with v2 handlers openxAdapter.track = prebidAnalyticsEventHandlerV2; @@ -422,7 +423,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) { openxAdapter.originEnableAnalytics(adapterConfig); } - function isValidConfig({options: analyticsOptions}){ + function isValidConfig({options: analyticsOptions}) { const fieldValidations = [ // tuple of property, type, required ['publisherPlatformId', 'string', true], @@ -441,10 +442,10 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) { (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type); }); - if(failedValidation) { + if (failedValidation) { let [property, type, required] = failedValidation; - if(required){ + if (required) { utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); } else { utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); @@ -666,13 +667,12 @@ function pushEvent(eventType, args, auctionId) { } function updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition) { - - if(auctionId && adUnitCode) { - if(!loadedAdSlots[auctionId]){ + if (auctionId && adUnitCode) { + if (!loadedAdSlots[auctionId]) { loadedAdSlots[auctionId] = {}; } loadedAdSlots[auctionId][adUnitCode] = {}; - if(adPosition) { + if (adPosition) { loadedAdSlots[auctionId][adUnitCode] = { adPosition: adPosition }; } } else { @@ -681,28 +681,23 @@ function updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition) { } function getLoadedAdUnitCodes(auctionId) { - return (!auctionId || !loadedAdSlots[auctionId] || typeof loadedAdSlots[auctionId] !== 'object') ? [] : Object.keys(loadedAdSlots[auctionId]); } function pushAdPositionData(auctionId) { - - if(auctionId && eventStack[auctionId] && eventStack[auctionId].events) { - + if (auctionId && eventStack?.[auctionId]?.events) { let adUnitPositionMap = loadedAdSlots[auctionId]; - if(adUnitPositionMap && JSON.stringify(adUnitPositionMap) !== "{}") { - + if (adUnitPositionMap && JSON.stringify(adUnitPositionMap) !== '{}') { eventStack[auctionId].events.filter(function(event) { return event.eventType === auctionEndConst; }).forEach(function (auctionEndEvent) { - - if(auctionEndEvent.args && auctionEndEvent.args.adUnits) { + if (auctionEndEvent.args && auctionEndEvent.args.adUnits) { auctionEndEvent.args.adUnits.forEach(function (adUnitInfo) { - if(adUnitPositionMap[adUnitInfo.code] && adUnitPositionMap[adUnitInfo.code]["adPosition"]) { - adUnitInfo["adPosition"] = adUnitPositionMap[adUnitInfo.code]["adPosition"]; + if (adUnitPositionMap[adUnitInfo.code] && adUnitPositionMap[adUnitInfo.code]['adPosition']) { + adUnitInfo['adPosition'] = adUnitPositionMap[adUnitInfo.code]['adPosition']; } else { - adUnitInfo["adPosition"] = ""; + adUnitInfo['adPosition'] = ''; } }) } @@ -712,8 +707,7 @@ function pushAdPositionData(auctionId) { } function getAdPositionByElementId(elementId) { - - let elem = document.querySelector("#" + elementId); + let elem = document.querySelector('#' + elementId); let adPosition; if (elem) { let bounding = elem.getBoundingClientRect(); @@ -733,13 +727,13 @@ function getAdPositionByElementId(elementId) { let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0; let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top); - if(adSlotArea > 0) { + if (adSlotArea > 0) { // Atleast 50% of intersection in window - adPosition = (intersectionArea * 2 >= adSlotArea) ? "ATF" : "BTF"; + adPosition = (intersectionArea * 2 >= adSlotArea) ? 'ATF' : 'BTF'; } } } else { - utils.logWarn("OX: DOM element not for id " + elementId); + utils.logWarn('OX: DOM element not for id ' + elementId); } return adPosition; } @@ -751,7 +745,7 @@ adapterManager.registerAnalyticsAdapter({ code: 'openx' }); -function prebidAnalyticsEventHandlerV1({eventType, args}){ +function prebidAnalyticsEventHandlerV1({eventType, args}) { if (!checkInitOptions()) { send(eventType, {}, null); return; @@ -807,9 +801,15 @@ function prebidAnalyticsEventHandlerV1({eventType, args}){ //* ******* V2 Code ******* const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, AUCTION_END, BID_WON } } = CONSTANTS; +export const AUCTION_STATES = { + INIT: 'initialized', // auction has initialized + ENDED: 'ended', // all auction requests have been accounted for + COMPLETED: 'completed' // all slots have rendered +}; + const ENDPOINT = 'https://prebid.openx.net/ox/analytics'; let auctionMap = {}; let auctionOrder = 1; // tracks the number of auctions ran on the page @@ -829,6 +829,9 @@ function prebidAnalyticsEventHandlerV2({eventType, args}) { case BID_TIMEOUT: onBidTimeout(args); break; + case AUCTION_END: + onAuctionEnd(args); + break; case BID_WON: onBidWon(args); break; @@ -838,31 +841,33 @@ function prebidAnalyticsEventHandlerV2({eventType, args}) { } } -/* -TODO: type Auction -auctionId: "526ce090-e42e-4444-996f-ea78cde2244d" -timestamp: 1586675964364 -auctionEnd: undefined -auctionStatus: "inProgress" -adUnits: [{…}] -adUnitCodes: ["video1"] -labels: undefined -bidderRequests: (2) [{…}, {…}] -noBids: [] -bidsReceived: [] -winningBids: [] -timeout: 3000 -config: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true} +/** + * @typedef {Object} PbAuction + * @property {string} auctionId - Auction ID of the request this bid responded to + * @property {number} timestamp //: 1586675964364 + * @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364 + * @property {string} auctionStatus //: "inProgress" + * @property {Array} adUnits //: [{…}] + * @property {string} adUnitCodes //: ["video1"] + * @property {string} labels //: undefined + * @property {Array} bidderRequests //: (2) [{…}, {…}] + * @property {Array} noBids //: [] + * @property {Array} bidsReceived //: [] + * @property {Array} winningBids //: [] + * @property {number} timeout //: 3000 + * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true}/* */ + function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { auctionMap[auctionId] = { id: auctionId, startTime, + endTime: void(0), timeout, auctionOrder, adUnitCodesCount: adUnitCodes.length, adunitCodesRenderedCount: 0, - auctionCompleted: false, + state: AUCTION_STATES.INIT, auctionSendDelayTimer: void (0), }; @@ -956,6 +961,23 @@ function onBidTimeout(args) { }); } +/** + * + * @param {PbAuction} endedAuction + */ +function onAuctionEnd(endedAuction) { + let auction = auctionMap[endedAuction.auctionId]; + + if (!auction) { + return; + } + + clearAuctionTimer(auction); + auction.endTime = endedAuction.auctionEnd; + auction.state = AUCTION_STATES.ENDED; + delayedSend(auction); +} + /** * * @param {BidResponse} bidResponse @@ -978,11 +1000,7 @@ function onSlotLoadedV2({ slot }) { return; // slot is not participating in a prebid auction } - // reset the delay timer to send the auction data - if (auction.auctionSendDelayTimer) { - clearTimeout(auction.auctionSendDelayTimer); - auction.auctionSendDelayTimer = void (0); - } + clearAuctionTimer(auction); // track that an adunit code has completed within an auction auction.adunitCodesRenderedCount++; @@ -998,9 +1016,14 @@ function onSlotLoadedV2({ slot }) { // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked // add additional padding when not all slots are rendered + delayedSend(auction); + auction.state = AUCTION_STATES.COMPLETED; +} + +function delayedSend(auction) { const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount ? analyticsConfig.payloadWaitTime - : analyticsConfig.payloadWaitTime + 500; + : analyticsConfig.payloadWaitTime + analyticsConfig.payloadWaitTimePadding; auction.auctionSendDelayTimer = setTimeout(() => { let payload = JSON.stringify([buildAuctionPayload(auction)]); @@ -1010,8 +1033,14 @@ function onSlotLoadedV2({ slot }) { delete auctionMap[auction.id]; } }, delayTime); +} - auction.auctionCompleted = true; +function clearAuctionTimer(auction) { + // reset the delay timer to send the auction data + if (auction.auctionSendDelayTimer) { + clearTimeout(auction.auctionSendDelayTimer); + auction.auctionSendDelayTimer = void (0); + } } function getAuctionByGoogleTagSLot(slot) { @@ -1030,12 +1059,14 @@ function getAuctionByGoogleTagSLot(slot) { } function buildAuctionPayload(auction) { - let {startTime, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction; + let {startTime, endTime, state, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction; return { publisherPlatformId: analyticsConfig.publisherPlatformId, publisherAccountId: analyticsConfig.publisherAccountId, + state, startTime, + endTime, timeLimit: timeout, auctionOrder, deviceType: detectMob() ? 'Mobile' : 'Desktop', diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index e64248de53d..08659cc7f6a 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import openxAdapterParams from 'modules/openxAnalyticsAdapter.js'; +import openxAdapterParams, {AUCTION_STATES} from 'modules/openxAnalyticsAdapter.js'; import { config } from 'src/config.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; @@ -21,7 +21,7 @@ describe('openx analytics adapter', function() { spy = sinon.spy(utils, 'logError'); }); - afterEach(function(){ + afterEach(function() { utils.logError.restore(); }); @@ -676,6 +676,14 @@ describe('openx analytics adapter', function() { auctionId: 'test-auction-id' }; + const auctionEnd = { + auctionId: 'test-auction-id', + timestamp: 1586000000000, + auctionEnd: 1586000000100, + timeout: 3000, + adUnitCodes: [AD_UNIT_CODE], + }; + const bidWonCloseX = { requestId: 'test-closex-request-id', adId: 'test-closex-ad-id', @@ -683,35 +691,40 @@ describe('openx analytics adapter', function() { auctionId: 'test-auction-id' }; + function simulateAuction(events) { let highestBid; events.forEach(event => { const [eventType, args] = event; - openxAdapter.track({ eventType, args }); if (eventType === BID_RESPONSE) { highestBid = highestBid || args; if (highestBid.cpm < args.cpm) { highestBid = args; } } - }); - openxAdapter.track({ - eventType: SLOT_LOADED, - args: { - slot: { - getAdUnitPath: () => { - return '/12345678/test_ad_unit'; - }, - getSlotElementId: () => { - return AD_UNIT_CODE; - }, - getTargeting: sinon - .stub() - .withArgs('hb_adid') - .returns(highestBid ? [highestBid.adId] : []) - } + if (eventType === SLOT_LOADED) { + const slotLoaded = { + slot: { + getAdUnitPath: () => { + return '/12345678/test_ad_unit'; + }, + getSlotElementId: () => { + return AD_UNIT_CODE; + }, + getTargeting: (key) => { + if (key === 'hb_adid') { + return highestBid ? [highestBid.adId] : []; + } else { + return []; + } + } + } + }; + openxAdapter.track({ eventType, args: slotLoaded }); + } else { + openxAdapter.track({ eventType, args }); } }); } @@ -745,10 +758,12 @@ describe('openx analytics adapter', function() { simulateAuction([ [AUCTION_INIT, auctionInit], + [SLOT_LOADED] ]); simulateAuction([ - [AUCTION_INIT, {auctionId: 'second-auction-id', ...auctionInit} ], + [AUCTION_INIT, {...auctionInit, auctionId: 'second-auction-id'} ], + [SLOT_LOADED] ]); clock.tick(SLOT_LOAD_WAIT_TIME); @@ -795,6 +810,7 @@ describe('openx analytics adapter', function() { simulateAuction([ [AUCTION_INIT, auctionInit], + [SLOT_LOADED], ]); clock.tick(SLOT_LOAD_WAIT_TIME); auction = JSON.parse(server.requests[0].requestBody)[0]; @@ -819,7 +835,8 @@ describe('openx analytics adapter', function() { publisherPlatformId: 'test-platform-id', sample: 1.0, enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME, } }); @@ -827,8 +844,9 @@ describe('openx analytics adapter', function() { [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedCloseX], [BID_REQUESTED, bidRequestedOpenX], + [SLOT_LOADED], ]); - clock.tick(SLOT_LOAD_WAIT_TIME); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); auction = JSON.parse(server.requests[0].requestBody)[0]; }); @@ -874,7 +892,8 @@ describe('openx analytics adapter', function() { publisherPlatformId: 'test-platform-id', sample: 1.0, enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME, } }); @@ -884,8 +903,9 @@ describe('openx analytics adapter', function() { [BID_REQUESTED, bidRequestedOpenX], [BID_TIMEOUT, bidTimeoutCloseX], [BID_TIMEOUT, bidTimeoutOpenX], + [AUCTION_END, auctionEnd] ]); - clock.tick(SLOT_LOAD_WAIT_TIME); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); auction = JSON.parse(server.requests[0].requestBody)[0]; openxBidRequest = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); @@ -915,7 +935,8 @@ describe('openx analytics adapter', function() { publisherPlatformId: 'test-platform-id', sample: 1.0, enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME } }); @@ -924,9 +945,11 @@ describe('openx analytics adapter', function() { [BID_REQUESTED, bidRequestedCloseX], [BID_REQUESTED, bidRequestedOpenX], [BID_RESPONSE, bidResponseOpenX], - [BID_RESPONSE, bidResponseCloseX] + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd] ]); - clock.tick(SLOT_LOAD_WAIT_TIME); + + clock.tick(SLOT_LOAD_WAIT_TIME * 2); auction = JSON.parse(server.requests[0].requestBody)[0]; openxBidResponse = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx').bidResponses[0]; @@ -985,9 +1008,17 @@ describe('openx analytics adapter', function() { expect(openxBidResponse.currency).to.equal(bidResponseOpenX.currency); expect(closexBidResponse.currency).to.equal(bidResponseCloseX.currency); }); + + it('should track the auction end time', function () { + expect(auction.endTime).to.equal(auctionEnd.auctionEnd); + }); + + it('should track that the auction ended', function () { + expect(auction.state).to.equal(AUCTION_STATES.ENDED); + }); }); - describe('when are bidder wins', function () { + describe('when there are bidder wins', function () { const CURRENT_TIME = 1586000000000; let auction; beforeEach(function () { @@ -997,7 +1028,8 @@ describe('openx analytics adapter', function() { publisherAccountId: 123, sample: 1.0, enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME } }); @@ -1010,10 +1042,11 @@ describe('openx analytics adapter', function() { [BID_REQUESTED, bidRequestedCloseX], [BID_RESPONSE, bidResponseOpenX], [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd], [BID_WON, bidWonOpenX] ]); - clock.tick(SLOT_LOAD_WAIT_TIME); + clock.tick(SLOT_LOAD_WAIT_TIME * 2); auction = JSON.parse(server.requests[0].requestBody)[0]; }); @@ -1032,6 +1065,46 @@ describe('openx analytics adapter', function() { let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); expect(closexBidder.bidResponses[0]).to.contain({winner: false}); }); + }); + + describe('when a winning bid renders', function () { + const CURRENT_TIME = 1586000000000; + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({ + options: { + publisherPlatformId: 'test-platform-id', + publisherAccountId: 123, + sample: 1.0, + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME + } + }); + + // set current time + clock = sinon.useFakeTimers(CURRENT_TIME); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX], + [AUCTION_END, auctionEnd], + [BID_WON, bidWonOpenX], + [SLOT_LOADED] + ]); + + clock.tick(SLOT_LOAD_WAIT_TIME * 2); + auction = JSON.parse(server.requests[0].requestBody)[0]; + }); + + afterEach(function () { + clock.restore(); + openxAdapter.reset(); + openxAdapter.disableAnalytics(); + }); it('should track that winning bid rendered', function () { let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); @@ -1042,6 +1115,10 @@ describe('openx analytics adapter', function() { let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); expect(openxBidder.bidResponses[0]).to.contain({renderTime: CURRENT_TIME}); }); + + it('should track that the auction completed', function () { + expect(auction.state).to.equal(AUCTION_STATES.COMPLETED); + }); }); }); @@ -1134,8 +1211,8 @@ describe('openx analytics adapter', function() { 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [{'bidder': 'openx', 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}, - {'bidder': 'closex', - 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}], + {'bidder': 'closex', + 'params': {'unit': '540249866', 'delDomain': 'sademo-d.openx.net'}}], 'sizes': [[300, 250]], 'transactionId': 'test-transaction-id'}]; @@ -1259,7 +1336,7 @@ describe('openx analytics adapter', function() { openxAdapter.disableAnalytics(); }); - it('should send out both payloads', function(){ + it('should send out both payloads', function() { expect(server.requests.length).to.equal(2); }); @@ -1290,7 +1367,6 @@ describe('openx analytics adapter', function() { expect(bidWonEventInfoList.length).to.equal(1); - let openxBidder = v2Auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); expect(openxBidder.bidResponses[0]).to.contain({winner: true}); }); From 3ed4d12c583cdb7d1cf5cef1760603eedc3a4a2f Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 29 Apr 2020 22:38:34 -0700 Subject: [PATCH 29/32] feat(openxAnalyticsAdaptor): Added userId tracking for v2 pipeline. --- modules/openxAnalyticsAdapter.js | 41 ++++++++++++--- .../modules/openxAnalyticsAdapter_spec.js | 51 +++++++++---------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 529d55b684a..c90a81db3e4 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -433,6 +433,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { ['testPipeline', 'boolean', false], ['adIdKey', 'string', false], ['payloadWaitTime', 'number', false], + ['payloadWaitTimePadding', 'number', false], ]; let failedValidation = fieldValidations.find(([property, type, required]) => { @@ -887,13 +888,14 @@ function onBidRequested(bidRequest) { const adUnitCodeToBidderRequestMap = auction.adUnitCodeToBidderRequestMap; bidderRequests.forEach(bidderRequest => { - const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src } = bidderRequest; + const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src, userId } = bidderRequest; adUnitCodeToBidderRequestMap[adUnitCode][requestId] = { bidder, params, mediaTypes, source: src, + userId, startTime: start, timedOut: false, bids: {} @@ -954,10 +956,13 @@ function onBidResponse(bidResponse) { } function onBidTimeout(args) { - utils - ._map(args, value => value) - .forEach(({ auctionId, adUnitCode, bidId: requestId }) => { - auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].timedOut = true; + utils._each(args, ({auctionId, adUnitCode, bidId: requestId}) => { + try{ + auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].timedOut = true; + } catch (e) { + utils.logError(`ERROR: oxAnalyticsAdapter: unable to track timeout for + auction:${auctionId}, adUnitCode:${adUnitCode}, requestId:${requestId}`) + } }); } @@ -1079,11 +1084,18 @@ function buildAuctionPayload(auction) { function buildBidRequestsPayload(adUnitCodeToBidderRequestMap) { return utils._map(adUnitCodeToBidderRequestMap, (bidderRequestMap, adUnitCode) => { return utils._map(bidderRequestMap, (bidderRequest) => { - let {bidder, source, bids, mediaTypes, timedOut} = bidderRequest; + let {bidder, source, bids, mediaTypes, timedOut, userId} = bidderRequest; return { adUnitCode, bidder, source, + // return an array of objects containing the module name and id + userIds: utils._map(userId, (id, module) => { + return { + module: module, + id: getUserId(module, id)}; + }) + .filter(({id}) => id), hasBidderResponded: Object.keys(bids).length > 0, availableAdSizes: getMediaTypeSizes(mediaTypes), availableMediaTypes: getMediaTypes(mediaTypes), @@ -1135,6 +1147,23 @@ function buildAuctionPayload(auction) { }).flat(); } + function getUserId(module, idOrIdObject) { + let normalizedId; + + switch (module) { + case 'digitrustid': + normalizedId = utils.deepAccess(idOrIdObject, 'data.id'); + break; + case 'lipb': + normalizedId = idOrIdObject.lipbid; + break; + default: + normalizedId = idOrIdObject; + } + + return normalizedId; + } + function getMediaTypeSizes(mediaTypes) { return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => { return utils.parseSizesInput(mediaTypeConfig.sizes) diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index 08659cc7f6a..a105b8df132 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -603,6 +603,9 @@ describe('openx analytics adapter', function() { bidId: 'test-openx-request-id', bidder: 'openx', params: { unit: 'test-openx-ad-unit-id' }, + userId: { + tdid: 'test-tradedesk-id' + } } ], start: 1586000000010 @@ -617,6 +620,9 @@ describe('openx analytics adapter', function() { bidId: 'test-closex-request-id', bidder: 'closex', params: { unit: 'test-closex-ad-unit-id' }, + userId: { + tdid: 'test-tradedesk-id' + } } ], start: 1586000000020 @@ -657,17 +663,20 @@ describe('openx analytics adapter', function() { ts: 'test-closex-ts' }; - const bidTimeoutOpenX = [{ + const bidTimeoutOpenX = { + 0: { adUnitCode: AD_UNIT_CODE, auctionId: 'test-auction-id', bidId: 'test-openx-request-id' - }]; + }}; - const bidTimeoutCloseX = [{ - adUnitCode: AD_UNIT_CODE, - auctionId: 'test-auction-id', - bidId: 'test-closex-request-id' - }]; + const bidTimeoutCloseX = { + 0: { + adUnitCode: AD_UNIT_CODE, + auctionId: 'test-auction-id', + bidId: 'test-closex-request-id' + } + }; const bidWonOpenX = { requestId: 'test-openx-request-id', @@ -691,7 +700,6 @@ describe('openx analytics adapter', function() { auctionId: 'test-auction-id' }; - function simulateAuction(events) { let highestBid; @@ -871,6 +879,14 @@ describe('openx analytics adapter', function() { expect(closexBidder.adUnitCode).to.equal(AD_UNIT_CODE); }); + it('should track the user ids', function () { + let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); + let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); + + expect(openxBidder.userIds).to.deep.include({module: 'tdid', id: bidRequestedOpenX.bids[0].userId.tdid}); + expect(closexBidder.userIds).to.deep.include({module: 'tdid', id: bidRequestedCloseX.bids[0].userId.tdid}); + }); + it('should not have responded', function () { let openxBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'openx'); let closexBidder = auction.bidRequests.find(bidderRequest => bidderRequest.bidder === 'closex'); @@ -1195,18 +1211,6 @@ describe('openx analytics adapter', function() { ts: 'test-closex-ts' }; - const bidTimeoutOpenX = [{ - adUnitCode: AD_UNIT_CODE, - auctionId: 'test-auction-id', - bidId: 'test-openx-request-id' - }]; - - const bidTimeoutCloseX = [{ - adUnitCode: AD_UNIT_CODE, - auctionId: 'test-auction-id', - bidId: 'test-closex-request-id' - }]; - const openxAdUnitInfo = [{'code': 'test-div-1', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [{'bidder': 'openx', @@ -1239,13 +1243,6 @@ describe('openx analytics adapter', function() { auctionId: 'test-auction-id' }; - const bidWonCloseX = { - requestId: 'test-closex-request-id', - adId: 'test-closex-ad-id', - adUnitCode: AD_UNIT_CODE, - auctionId: 'test-auction-id' - }; - let highestBid; const onSlotLoadEvent = { eventType: SLOT_LOADED, From 688dbb447808c876c17ac4d658b7236177d86f10 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Tue, 5 May 2020 23:03:14 -0700 Subject: [PATCH 30/32] feat(openxAnalyticsAdaptor): Added campaign support from utm and configuration properties. * fixed bug where auction state was pre-emptively set to completed --- modules/openxAnalyticsAdapter.js | 85 +++++----- .../modules/openxAnalyticsAdapter_spec.js | 148 ++++++++++-------- 2 files changed, 129 insertions(+), 104 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 529d55b684a..c4fa453de40 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -5,6 +5,7 @@ import adapterManager from '../src/adapterManager.js'; //* ******* V2 Code import { ajax } from '../src/ajax.js'; +import {getWindowLocation, parseQS} from '../src/utils'; // temp dependency on zlib to minimize payload const zlib = require('zlib'); // eslint-disable-line @@ -38,11 +39,11 @@ const SLOT_LOADED = 'slotOnload'; * @property {number} sampling * @property {boolean} enableV2 * @property {boolean} testPipeline - * @property {Object} utmTagData + * @property {Object} campaign * @property {string} adIdKey * @property {number} payloadWaitTime * @property {number} payloadWaitTimePadding - * @property {Array}adUnits + * @property {Array} adUnits */ /** @@ -56,10 +57,10 @@ const DEFAULT_ANALYTICS_CONFIG = { enableV2: false, testPipeline: false, adIdKey: 'hb_adid', - utmTagData: {}, + campaign: {}, adUnits: [], payloadWaitTime: AUCTION_END_WAIT_TIME, - payloadWaitTimePadding: 100 + payloadWaitTimePadding: 2000 }; let googletag = window.googletag || {}; @@ -75,12 +76,20 @@ let loadedAdSlots = {}; let localStoragePrefix = 'openx_analytics_'; let utmTags = [ + 'utm_campaign', 'utm_source', 'utm_medium', - 'utm_campaign', 'utm_term', 'utm_content' ]; + +const UTM_TO_CAMPAIGN_PROPERTIES = { + 'utm_campaign': 'name', + 'utm_source': 'source', + 'utm_medium': 'medium', + 'utm_term': 'term', + 'utm_content': 'content' +}; let utmTimeoutKey = 'utm_timeout'; let utmTimeout = 60 * 60 * 1000; let sessionTimeout = 60 * 60 * 1000; @@ -384,7 +393,10 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { if (isValidConfig(adapterConfig)) { analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; - analyticsConfig.utmTagData = this.buildUtmTagData(); + + // campaign properties defined by config will override utm query parameters + analyticsConfig.campaign = {...buildCampaignFromUtmCodes(), ...analyticsConfig.campaign}; + utils.logInfo('OpenX Analytics enabled with config', analyticsConfig); if (analyticsConfig.testPipeline) { @@ -433,6 +445,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { ['testPipeline', 'boolean', false], ['adIdKey', 'string', false], ['payloadWaitTime', 'number', false], + ['payloadWaitTimePadding', 'number', false], ]; let failedValidation = fieldValidations.find(([property, type, required]) => { @@ -441,7 +454,6 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { return (required && !analyticsOptions.hasOwnProperty(property)) || (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type); }); - if (failedValidation) { let [property, type, required] = failedValidation; @@ -456,36 +468,19 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { } }; -openxAdapter.buildUtmTagData = function() { - let utmTagData = {}; - let utmTagsDetected = false; - utmTags.forEach(function(utmTagKey) { - let utmTagValue = getParameterByName(utmTagKey); - if (utmTagValue !== '') { - utmTagsDetected = true; - } - utmTagData[utmTagKey] = utmTagValue; - }); - utmTags.forEach(function(utmTagKey) { - if (utmTagsDetected) { - localStorage.setItem( - buildUtmLocalStorageKey(utmTagKey), - utmTagData[utmTagKey] - ); - updateUtmTimeout(); - } else { - if (!isUtmTimeoutExpired()) { - utmTagData[utmTagKey] = localStorage.getItem( - buildUtmLocalStorageKey(utmTagKey) - ) - ? localStorage.getItem(buildUtmLocalStorageKey(utmTagKey)) - : ''; - updateUtmTimeout(); - } +function buildCampaignFromUtmCodes() { + let campaign = {}; + let queryParams = utils.parseQS(utils.getWindowLocation() && utils.getWindowLocation().search); + + utmTags.forEach(function(utmKey) { + let utmValue = queryParams[utmKey]; + if(utmValue){ + let key = UTM_TO_CAMPAIGN_PROPERTIES[utmKey]; + campaign[key] = utmValue; } }); - return utmTagData; -}; + return campaign; +} function buildPayload( data, @@ -494,7 +489,8 @@ function buildPayload( publisherAccountId, auctionId, testCode, - sourceUrl + sourceUrl, + campaign ) { return { adapterVersion: ADAPTER_VERSION, @@ -505,7 +501,8 @@ function buildPayload( publisherAccountId: publisherAccountId, auctionId: auctionId, testCode: testCode, - sourceUrl: sourceUrl + sourceUrl: sourceUrl, + campaign }; } @@ -637,7 +634,8 @@ function send(eventType, eventStack, auctionId) { publisherAccountId, auctionId, testCode, - sourceUrl + sourceUrl, + analyticsConfig.campaign ); apiCall(urlGenerated, MAX_RETRIES, payload); } else { @@ -1014,10 +1012,13 @@ function onSlotLoadedV2({ slot }) { adUnit.renderTime = renderTime; } + if (auction.adunitCodesRenderedCount === auction.adUnitCodesCount) { + auction.state = AUCTION_STATES.COMPLETED; + } + // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked // add additional padding when not all slots are rendered delayedSend(auction); - auction.state = AUCTION_STATES.COMPLETED; } function delayedSend(auction) { @@ -1060,10 +1061,12 @@ function getAuctionByGoogleTagSLot(slot) { function buildAuctionPayload(auction) { let {startTime, endTime, state, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction; + let {publisherPlatformId, publisherAccountId, campaign} = analyticsConfig; return { - publisherPlatformId: analyticsConfig.publisherPlatformId, - publisherAccountId: analyticsConfig.publisherAccountId, + publisherPlatformId, + publisherAccountId, + campaign, state, startTime, endTime, diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index 08659cc7f6a..02ce113d296 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -586,6 +586,16 @@ describe('openx analytics adapter', function() { describe('when version 2 is enabled', function () { const AD_UNIT_CODE = 'test-div-1'; + const SLOT_LOAD_WAIT_TIME = 10; + + const DEFAULT_V2_ANALYTICS_CONFIG = { + publisherAccountId: 123, + publisherPlatformId: 'test-platform-id', + sample: 1.0, + enableV2: true, + payloadWaitTime: SLOT_LOAD_WAIT_TIME, + payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME + }; const auctionInit = { auctionId: 'test-auction-id', @@ -729,7 +739,6 @@ describe('openx analytics adapter', function() { }); } - const SLOT_LOAD_WAIT_TIME = 10; let clock; beforeEach(function() { @@ -746,15 +755,7 @@ describe('openx analytics adapter', function() { let auction; let auction2; beforeEach(function () { - openxAdapter.enableAnalytics({ - options: { - publisherAccountId: 123, - publisherPlatformId: 'test-platform-id', - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME - } - }); + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], @@ -799,12 +800,8 @@ describe('openx analytics adapter', function() { beforeEach(function () { openxAdapter.enableAnalytics({ options: { - publisherAccountId: 123, - publisherPlatformId: 'test-platform-id', - sample: 1.0, - enableV2: true, - testCode: 'test-code', - payloadWaitTime: SLOT_LOAD_WAIT_TIME + ...DEFAULT_V2_ANALYTICS_CONFIG, + testCode: 'test-code' } }); @@ -826,20 +823,81 @@ describe('openx analytics adapter', function() { }); }); - describe('when there are bid requests', function () { + describe('when there is campaign (utm) data', function () { let auction; beforeEach(function () { + + }); + + afterEach(function () { + openxAdapter.reset(); + utils.getWindowLocation.restore(); + openxAdapter.disableAnalytics(); + }); + + it('should track values from query params when they exist', function () { + sinon.stub(utils, 'getWindowLocation').returns({search: '?' + + 'utm_campaign=test-campaign-name&' + + 'utm_source=test-source&' + + 'utm_medium=test-medium&' + }); + + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [SLOT_LOADED], + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + expect(auction.campaign.name).to.equal('test-campaign-name'); + expect(auction.campaign.source).to.equal('test-source'); + expect(auction.campaign.medium).to.equal('test-medium'); + expect(auction.campaign.content).to.be.undefined; + expect(auction.campaign.term).to.be.undefined; + }); + + it('should override query params if configuration parameters exist', function () { + sinon.stub(utils, 'getWindowLocation').returns({search: '?' + + 'utm_campaign=test-campaign-name&' + + 'utm_source=test-source&' + + 'utm_medium=test-medium&' + + 'utm_content=test-content&' + + 'utm_term=test-term' + }); + openxAdapter.enableAnalytics({ options: { - publisherAccountId: 123, - publisherPlatformId: 'test-platform-id', - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME, - payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME, + ...DEFAULT_V2_ANALYTICS_CONFIG, + campaign: { + name: 'test-config-name', + source: 'test-config-source', + medium: 'test-config-medium' + } } }); + simulateAuction([ + [AUCTION_INIT, auctionInit], + [SLOT_LOADED], + ]); + clock.tick(SLOT_LOAD_WAIT_TIME); + auction = JSON.parse(server.requests[0].requestBody)[0]; + + expect(auction.campaign.name).to.equal('test-config-name'); + expect(auction.campaign.source).to.equal('test-config-source'); + expect(auction.campaign.medium).to.equal('test-config-medium'); + expect(auction.campaign.content).to.equal('test-content'); + expect(auction.campaign.term).to.equal('test-term'); + }); + }); + + describe('when there are bid requests', function () { + let auction; + beforeEach(function () { + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); + simulateAuction([ [AUCTION_INIT, auctionInit], [BID_REQUESTED, bidRequestedCloseX], @@ -886,16 +944,7 @@ describe('openx analytics adapter', function() { let closexBidRequest; beforeEach(function () { - openxAdapter.enableAnalytics({ - options: { - publisherAccountId: 123, - publisherPlatformId: 'test-platform-id', - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME, - payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME, - } - }); + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], @@ -929,16 +978,7 @@ describe('openx analytics adapter', function() { let closexBidResponse; beforeEach(function () { - openxAdapter.enableAnalytics({ - options: { - publisherAccountId: 123, - publisherPlatformId: 'test-platform-id', - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME, - payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME - } - }); + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); simulateAuction([ [AUCTION_INIT, auctionInit], @@ -1022,16 +1062,7 @@ describe('openx analytics adapter', function() { const CURRENT_TIME = 1586000000000; let auction; beforeEach(function () { - openxAdapter.enableAnalytics({ - options: { - publisherPlatformId: 'test-platform-id', - publisherAccountId: 123, - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME, - payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME - } - }); + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); // set current time clock = sinon.useFakeTimers(CURRENT_TIME); @@ -1071,16 +1102,7 @@ describe('openx analytics adapter', function() { const CURRENT_TIME = 1586000000000; let auction; beforeEach(function () { - openxAdapter.enableAnalytics({ - options: { - publisherPlatformId: 'test-platform-id', - publisherAccountId: 123, - sample: 1.0, - enableV2: true, - payloadWaitTime: SLOT_LOAD_WAIT_TIME, - payloadWaitTimePadding: SLOT_LOAD_WAIT_TIME - } - }); + openxAdapter.enableAnalytics({options: DEFAULT_V2_ANALYTICS_CONFIG}); // set current time clock = sinon.useFakeTimers(CURRENT_TIME); From f35b46a3bb095169fd6cd2272edba22f64c48b73 Mon Sep 17 00:00:00 2001 From: Jimmy Tu Date: Wed, 6 May 2020 21:37:50 -0700 Subject: [PATCH 31/32] clean(openxBidderAdaptor): Switch try catch to checking for request prior to assigning value. --- modules/openxAnalyticsAdapter.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index c90a81db3e4..a7202295326 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -957,15 +957,15 @@ function onBidResponse(bidResponse) { function onBidTimeout(args) { utils._each(args, ({auctionId, adUnitCode, bidId: requestId}) => { - try{ - auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].timedOut = true; - } catch (e) { - utils.logError(`ERROR: oxAnalyticsAdapter: unable to track timeout for - auction:${auctionId}, adUnitCode:${adUnitCode}, requestId:${requestId}`) - } - }); + if (auctionMap[auctionId] + && auctionMap[auctionId].adUnitCodeToBidderRequestMap + && auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode] + && auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId] + ) { + auctionMap[auctionId].adUnitCodeToBidderRequestMap[adUnitCode][requestId].timedOut = true; + } + }); } - /** * * @param {PbAuction} endedAuction From 05056273d4a7b54ee96b3052f3deb341575a8522 Mon Sep 17 00:00:00 2001 From: Nuthan Reddy Date: Tue, 19 May 2020 09:55:23 +0530 Subject: [PATCH 32/32] log when testCode is empty --- modules/openxAnalyticsAdapter.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index c7b367a8687..f0c76f1f584 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -199,7 +199,12 @@ function getPublisherAccountId() { function getTestCode() { if (analyticsConfig.testCode !== undefined) { if (typeof analyticsConfig.testCode === 'string') { - return analyticsConfig.testCode; + if (analyticsConfig.testCode !== '') { + return analyticsConfig.testCode; + } else { + utils.logError('OX: Invalid testCode. testCode can\'t be empty'); + return null; + } } else { utils.logError('OX: Invalid datatype for testCode'); return null; @@ -745,6 +750,7 @@ adapterManager.registerAnalyticsAdapter({ function prebidAnalyticsEventHandlerV1({eventType, args}) { if (!checkInitOptions()) { + utils.logError('OX: Incorrect adapter options') send(eventType, {}, null); return; }