diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js new file mode 100644 index 00000000000..2fe26488ebe --- /dev/null +++ b/modules/eplanningAnalyticsAdapter.js @@ -0,0 +1,131 @@ +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'); + +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 + eplAnalyticsAdapter.context.ci + '?d=' + encodeURIComponent(strjson)); + } catch (err) {} + } + } +} +); + +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, + ci: config.options.ci + }; + + 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..3127b523be0 --- /dev/null +++ b/modules/eplanningAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# 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/', // Host (optional) + ci: "123456" // Client ID (required) + } +} +``` diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..04a7d65e12a --- /dev/null +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -0,0 +1,162 @@ +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/', + ci: '12345' + }; + 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); + + 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'))); + + // 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); + }); + }); +});