From a4f24641783da108f71bc6b4654808447cc43708 Mon Sep 17 00:00:00 2001 From: Matias Martinho Date: Fri, 2 Mar 2018 12:42:25 -0300 Subject: [PATCH 1/4] Add e-planning analytics adapter --- modules/eplanningAnalyticsAdapter.js | 124 ++++++++++++++ modules/eplanningAnalyticsAdapter.md | 22 +++ .../modules/eplanningAnalyticsAdapter_spec.js | 158 ++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 modules/eplanningAnalyticsAdapter.js create mode 100644 modules/eplanningAnalyticsAdapter.md create mode 100644 test/spec/modules/eplanningAnalyticsAdapter_spec.js diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js new file mode 100644 index 00000000000..9e94ddf3ade --- /dev/null +++ b/modules/eplanningAnalyticsAdapter.js @@ -0,0 +1,124 @@ +import {ajax} from 'src/ajax'; +import adapter from 'src/AnalyticsAdapter'; +import adaptermanager from 'src/adaptermanager'; + +const CONSTANTS = require('src/constants.json'); + +const analyticsType = 'endpoint'; +const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; + +function auctionEndHandler(args) { + return {auctionId: args.auctionId}; +} + +function auctionInitHandler(args) { + return { + auctionId: args.auctionId, + time: args.timestamp + }; +} + +function bidRequestedHandler(args) { + return { + auctionId: args.auctionId, + time: args.start, + bidder: args.bidderCode, + bids: args.bids.map(function(bid) { + return { + time: bid.startTime, + bidder: bid.bidder, + placementCode: bid.placementCode, + auctionId: bid.auctionId, + sizes: bid.sizes + }; + }), + }; +} + +function bidResponseHandler(args) { + return { + bidder: args.bidder, + size: args.size, + auctionId: args.auctionId, + cpm: args.cpm, + time: args.responseTimestamp, + }; +} + +function bidWonHandler(args) { + return { + auctionId: args.auctionId, + size: args.width + 'x' + args.height, + }; +} + +function bidTimeoutHandler(args) { + return args.map(function(bid) { + return { + bidder: bid.bidder, + auctionId: bid.auctionId + }; + }) +} + +function callHandler(evtype, args) { + let handler = null; + + if (evtype === CONSTANTS.EVENTS.AUCTION_INIT) { + handler = auctionInitHandler; + eplAnalyticsAdapter.context.events = []; + } else if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + handler = auctionEndHandler; + } else if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + handler = bidRequestedHandler; + } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + handler = bidResponseHandler + } else if (evtype === CONSTANTS.EVENTS.BID_TIMEOUT) { + handler = bidTimeoutHandler; + } else if (evtype === CONSTANTS.EVENTS.BID_WON) { + handler = bidWonHandler; + } + + if (handler) { + eplAnalyticsAdapter.context.events.push({ec: evtype, p: handler(args)}); + } +} + +var eplAnalyticsAdapter = Object.assign(adapter( + { + EPL_HOST, + analyticsType + }), +{ + track({eventType, args}) { + if (typeof args !== 'undefined') { + callHandler(eventType, args); + } + + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + try { + let strjson = JSON.stringify(eplAnalyticsAdapter.context.events); + ajax(eplAnalyticsAdapter.context.host + '?d=' + encodeURIComponent(strjson)); + } catch (err) {} + } + } +} +); + +eplAnalyticsAdapter.originEnableAnalytics = eplAnalyticsAdapter.enableAnalytics; + +eplAnalyticsAdapter.enableAnalytics = function (config) { + eplAnalyticsAdapter.context = { + events: [], + host: config.options.host || EPL_HOST + }; + + eplAnalyticsAdapter.originEnableAnalytics(config); +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: eplAnalyticsAdapter, + code: 'eplanning' +}); + +export default eplAnalyticsAdapter; diff --git a/modules/eplanningAnalyticsAdapter.md b/modules/eplanningAnalyticsAdapter.md new file mode 100644 index 00000000000..89c386e1058 --- /dev/null +++ b/modules/eplanningAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview + +``` +Module Name: E-Planning Analytics Adapter +Module Type: Analytics Adapter +Maintainer: mmartinho@e-planning.net +``` + +# Description + +Analytics adapter for E-Planning. + +# Test Parameters + +``` +{ + provider: 'eplanning', + options : { + host: 'https://ads.us.e-planning.net/hba/1/' //optional host + } +} +``` diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..525c2094d91 --- /dev/null +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -0,0 +1,158 @@ +import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter'; +import { expect } from 'chai'; +let adaptermanager = require('src/adaptermanager'); +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('eplanning analytics adapter', () => { + let xhr; + let requests; + + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => { requests.push(request) }; + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(() => { + xhr.restore(); + events.getEvents.restore(); + }); + + describe('track', () => { + it('builds and sends auction data', () => { + sinon.spy(eplAnalyticsAdapter, 'track'); + + let auctionTimestamp = 1496510254313; + let pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let initOptions = { + host: 'https://ads.ar.e-planning.net/hba/1/' + }; + let pbidderCode = 'adapter'; + + const bidRequest = { + bidderCode: pbidderCode, + auctionId: pauctionId, + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: pbidderCode, + placementCode: 'container-1', + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: pauctionId, + startTime: 1509369418389, + sizes: [[300, 250]], + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + const bidResponse = { + bidderCode: pbidderCode, + adId: '208750227436c1', + cpm: 0.015, + auctionId: pauctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: pbidderCode, + timeToRespond: 443, + size: '300x250', + width: 300, + height: 250, + }; + + let bidTimeout = [ + { + bidId: '208750227436c1', + bidder: pbidderCode, + auctionId: pauctionId + } + ]; + + adaptermanager.registerAnalyticsAdapter({ + code: 'eplanning', + adapter: eplAnalyticsAdapter + }); + + adaptermanager.enableAnalytics({ + provider: 'eplanning', + options: initOptions + }); + + // Emit the events with the "real" arguments + + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: pauctionId, + timestamp: auctionTimestamp + }); + + // Step 2: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 3: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 4: Send bid time out event + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + + // Step 5: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, { + adId: 'adIdData', + ad: 'adContent', + auctionId: pauctionId, + width: 300, + height: 250 + }); + + // Step 6: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {auctionId: pauctionId}); + + // Step 7: Find the request data sent (filtering other hosts) + requests = requests.filter(req => req.url.includes(initOptions.host)); + + expect(requests.length).to.equal(1); + + let info = requests[0].url; + let purl = new URL(info); + let eplData = JSON.parse(decodeURIComponent(purl.searchParams.get('d'))); + + // Step 8 check that 6 events were sent + expect(eplData.length).to.equal(6); + + // Step 9 verify that we only receive the parameters we need + let expectedEventValues = [ + // AUCTION INIT + {ec: constants.EVENTS.AUCTION_INIT, + p: {auctionId: pauctionId, time: auctionTimestamp}}, + // BID REQ + {ec: constants.EVENTS.BID_REQUESTED, + p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{ time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, + // BID RESP + {ec: constants.EVENTS.BID_RESPONSE, + p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, + // BID T.O. + {ec: constants.EVENTS.BID_TIMEOUT, + p: [{auctionId: pauctionId, bidder: pbidderCode}]}, + // BID WON + {ec: constants.EVENTS.BID_WON, + p: {auctionId: pauctionId, size: '300x250'}}, + // AUCTION END + {ec: constants.EVENTS.AUCTION_END, + p: {auctionId: pauctionId}} + ]; + + for (let evid = 0; evid < eplData.length; evid++) { + expect(eplData[evid]).to.deep.equal(expectedEventValues[evid]); + } + + // Step 10 check that the host to send the ajax request is configurable via options + expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); + + // Step 11 verify that we received 6 events + sinon.assert.callCount(eplAnalyticsAdapter.track, 6); + }); + }); +}); From 81fef715a5f3371c585232b91c36d00ac9d3eab7 Mon Sep 17 00:00:00 2001 From: Matias Martinho Date: Fri, 2 Mar 2018 15:59:21 -0300 Subject: [PATCH 2/4] fix irregular spacing in braces --- test/spec/modules/eplanningAnalyticsAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 525c2094d91..c01c4dda914 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -129,7 +129,7 @@ describe('eplanning analytics adapter', () => { p: {auctionId: pauctionId, time: auctionTimestamp}}, // BID REQ {ec: constants.EVENTS.BID_REQUESTED, - p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{ time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, + p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, // BID RESP {ec: constants.EVENTS.BID_RESPONSE, p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, From 17aa40f700327bd57e25d8b509cae33d37e112bc Mon Sep 17 00:00:00 2001 From: Matias Martinho Date: Mon, 12 Mar 2018 11:53:10 -0300 Subject: [PATCH 3/4] Added new ci config option --- modules/eplanningAnalyticsAdapter.js | 11 +++++++++-- modules/eplanningAnalyticsAdapter.md | 3 ++- test/spec/modules/eplanningAnalyticsAdapter_spec.js | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js index 9e94ddf3ade..2fe26488ebe 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -1,6 +1,7 @@ import {ajax} from 'src/ajax'; import adapter from 'src/AnalyticsAdapter'; import adaptermanager from 'src/adaptermanager'; +import * as utils from 'src/utils'; const CONSTANTS = require('src/constants.json'); @@ -98,7 +99,7 @@ var eplAnalyticsAdapter = Object.assign(adapter( if (eventType === CONSTANTS.EVENTS.AUCTION_END) { try { let strjson = JSON.stringify(eplAnalyticsAdapter.context.events); - ajax(eplAnalyticsAdapter.context.host + '?d=' + encodeURIComponent(strjson)); + ajax(eplAnalyticsAdapter.context.host + eplAnalyticsAdapter.context.ci + '?d=' + encodeURIComponent(strjson)); } catch (err) {} } } @@ -108,9 +109,15 @@ var eplAnalyticsAdapter = Object.assign(adapter( eplAnalyticsAdapter.originEnableAnalytics = eplAnalyticsAdapter.enableAnalytics; eplAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.ci) { + utils.logError('Client ID (ci) option is not defined. Analytics won\'t work'); + return; + } + eplAnalyticsAdapter.context = { events: [], - host: config.options.host || EPL_HOST + host: config.options.host || EPL_HOST, + ci: config.options.ci }; eplAnalyticsAdapter.originEnableAnalytics(config); diff --git a/modules/eplanningAnalyticsAdapter.md b/modules/eplanningAnalyticsAdapter.md index 89c386e1058..3127b523be0 100644 --- a/modules/eplanningAnalyticsAdapter.md +++ b/modules/eplanningAnalyticsAdapter.md @@ -16,7 +16,8 @@ Analytics adapter for E-Planning. { provider: 'eplanning', options : { - host: 'https://ads.us.e-planning.net/hba/1/' //optional host + host: 'https://ads.us.e-planning.net/hba/1/', // Host (optional) + ci: "123456" // Client ID (required) } } ``` diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index c01c4dda914..b930781fd46 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -27,7 +27,8 @@ describe('eplanning analytics adapter', () => { let auctionTimestamp = 1496510254313; let pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; let initOptions = { - host: 'https://ads.ar.e-planning.net/hba/1/' + host: 'https://ads.ar.e-planning.net/hba/1/', + ci: '12345' }; let pbidderCode = 'adapter'; @@ -115,6 +116,9 @@ describe('eplanning analytics adapter', () => { expect(requests.length).to.equal(1); + expect(requests[0].url.includes(initOptions.host+initOptions.ci)); + expect(requests[0].url.includes("https://ads.ar.e-planning.net/hba/1/12345?d=")); + let info = requests[0].url; let purl = new URL(info); let eplData = JSON.parse(decodeURIComponent(purl.searchParams.get('d'))); From b6ed173d993eb84729e8c265b9b235444c5219df Mon Sep 17 00:00:00 2001 From: Matias Martinho Date: Mon, 12 Mar 2018 12:20:42 -0300 Subject: [PATCH 4/4] Fix linting --- test/spec/modules/eplanningAnalyticsAdapter_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index b930781fd46..04a7d65e12a 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -116,8 +116,8 @@ describe('eplanning analytics adapter', () => { expect(requests.length).to.equal(1); - expect(requests[0].url.includes(initOptions.host+initOptions.ci)); - expect(requests[0].url.includes("https://ads.ar.e-planning.net/hba/1/12345?d=")); + expect(requests[0].url.includes(initOptions.host + initOptions.ci)); + expect(requests[0].url.includes('https://ads.ar.e-planning.net/hba/1/12345?d=')); let info = requests[0].url; let purl = new URL(info);