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);