From 9faca73c03fa76fb8de9299cc1145f90fb19b8c0 Mon Sep 17 00:00:00 2001 From: Chris Connors Date: Tue, 21 May 2019 22:02:54 -0400 Subject: [PATCH 1/2] Adding Scaleable Analytics Adapter --- modules/scaleableAnalyticsAdapter.js | 153 ++++++++++++++++++ modules/scaleableAnalyticsAdapter.md | 20 +++ .../modules/scaleableAnalyticsAdapter_spec.js | 139 ++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 modules/scaleableAnalyticsAdapter.js create mode 100644 modules/scaleableAnalyticsAdapter.md create mode 100644 test/spec/modules/scaleableAnalyticsAdapter_spec.js diff --git a/modules/scaleableAnalyticsAdapter.js b/modules/scaleableAnalyticsAdapter.js new file mode 100644 index 00000000000..c875dab7e18 --- /dev/null +++ b/modules/scaleableAnalyticsAdapter.js @@ -0,0 +1,153 @@ +/* COPYRIGHT SCALEABLE LLC 2019 */ + +import { ajax } from '../src/ajax'; +import CONSTANTS from '../src/constants.json'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; + +const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; +const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; +const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; +const BID_WON = CONSTANTS.EVENTS.BID_WON; +const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; + +const URL = 'https://auction.scaleable.ai/'; +const ANALYTICS_TYPE = 'endpoint'; + +let auctionData = {}; + +let scaleableAnalytics = Object.assign({}, + adapter({ + URL, + ANALYTICS_TYPE + }), + { + // Override AnalyticsAdapter functions by supplying custom methods + track({ eventType, args }) { + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args); + break; + case AUCTION_END: + onAuctionEnd(args); + break; + case BID_WON: + onBidWon(args); + break; + case BID_RESPONSE: + onBidResponse(args); + break; + case BID_TIMEOUT: + onBidTimeout(args); + break; + default: + break; + } + } + } +); + +scaleableAnalytics.config = {}; +scaleableAnalytics.originEnableAnalytics = scaleableAnalytics.enableAnalytics; +scaleableAnalytics.enableAnalytics = config => { + scaleableAnalytics.config = config; + + scaleableAnalytics.originEnableAnalytics(config); + + scaleableAnalytics.enableAnalytics = function _enable() { + return utils.logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); + }; +} + +scaleableAnalytics.getAuctionData = () => { + return auctionData; +}; + +const sendDataToServer = data => ajax(URL, () => {}, JSON.stringify(data)); + +// Track auction initiated +const onAuctionInit = args => { + const config = scaleableAnalytics.config || {options: {}}; + + for (let idx = args.adUnitCodes.length; idx--;) { + const data = { + event: 'request', + site: config.options.site, + adunit: args.adUnitCodes[idx] + }; + + sendDataToServer(data); + } +} + +// Handle all events besides requests and wins +const onAuctionEnd = args => { + for (let adunit in auctionData) { + sendDataToServer(auctionData[adunit]); + } +} + +// Bid Win Events occur after auction end +const onBidWon = args => { + const config = scaleableAnalytics.config || {options: {}}; + + const data = { + event: 'win', + site: config.options.site, + adunit: args.adUnitCode, + code: args.bidderCode, + cpm: args.cpm, + ttr: args.timeToRespond + }; + + sendDataToServer(data); +} + +const onBidResponse = args => { + const config = scaleableAnalytics.config || {options: {}}; + + if (!auctionData[args.adUnitCode]) { + auctionData[args.adUnitCode] = { + event: 'bids', + bids: [], + adunit: args.adUnitCode, + site: config.options.site + }; + } + + const currBidData = { + code: args.bidderCode, + cpm: args.cpm, + ttr: args.timeToRespond + }; + + auctionData[args.adUnitCode].bids.push(currBidData); +} + +const onBidTimeout = args => { + const config = scaleableAnalytics.config || {options: {}}; + + for (let i = args.length; i--;) { + let currObj = args[i]; + + if (!auctionData[currObj.adUnitCode]) { + auctionData[currObj.adUnitCode] = { + event: 'bids', + bids: [], + timeouts: [], + adunit: currObj.adUnitCode, + site: config.options.site + }; + } + + auctionData[currObj.adUnitCode].timeouts.push(currObj.bidder); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: scaleableAnalytics, + code: 'scaleable' +}) + +export default scaleableAnalytics; diff --git a/modules/scaleableAnalyticsAdapter.md b/modules/scaleableAnalyticsAdapter.md new file mode 100644 index 00000000000..0f6fbada55a --- /dev/null +++ b/modules/scaleableAnalyticsAdapter.md @@ -0,0 +1,20 @@ +# Overview + +Module Name: Scaleable Analytics Adapter +Module Type: Analytics Adapter +Maintainer: chris@scaleable.ai + +# Description + +Analytics adapter for scaleable.ai. Contact team@scaleable.ai for more information or to sign up for analytics. + +# Implementation Code + +``` +pbjs.enableAnalytics({ + provider: 'scaleable', + options: { + site: '' // Contact Scaleable to receive your unique site id + } +}); +``` diff --git a/test/spec/modules/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..99fe8f941ec --- /dev/null +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -0,0 +1,139 @@ +import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter'; +import { expect } from 'chai'; +import events from 'src/events'; +import CONSTANTS from 'src/constants.json'; +import adapterManager from 'src/adapterManager'; + +const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; +const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; +const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; +const BID_WON = CONSTANTS.EVENTS.BID_WON; +const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; + +describe('Scaleable Analytics Adapter', function() { + const MOCK_DATA = { + adUnitCode: '12345', + site: '5c4fab7a829e955d6c265e72', + bidResponse: { + adUnitCode: '12345', + bidderCode: 'test-code', + cpm: 3.14, + timeToRespond: 285 + }, + bidTimeout: [ + { + adUnitCode: '67890', + bidder: 'test-code' + } + ] + }; + + MOCK_DATA.expectedBidResponse = { + event: 'bids', + bids: [{ + code: MOCK_DATA.bidResponse.bidderCode, + cpm: MOCK_DATA.bidResponse.cpm, + ttr: MOCK_DATA.bidResponse.timeToRespond + }], + adunit: MOCK_DATA.adUnitCode, + site: MOCK_DATA.site + }; + + MOCK_DATA.expectedBidTimeout = { + event: 'bids', + bids: [], + timeouts: [MOCK_DATA.bidTimeout[0].bidder], + adunit: MOCK_DATA.bidTimeout[0].adUnitCode, + site: MOCK_DATA.site + }; + + let xhr; + let requests; + + before(function() { + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + }); + + after(function() { + xhr.restore(); + }); + + describe('Event Handling', function() { + beforeEach(function() { + requests = []; + sinon.stub(events, 'getEvents').returns([]); + + scaleableAnalytics.enableAnalytics({ + provider: 'scaleable', + options: { + site: MOCK_DATA.site + } + }); + }); + + afterEach(function() { + events.getEvents.restore(); + scaleableAnalytics.disableAnalytics(); + }); + + // after(function() { + // }); + + it('should handle the auction init event', function(done) { + events.emit(AUCTION_INIT, { + adUnitCodes: [MOCK_DATA.adUnitCode] + }); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal({ + event: 'request', + site: MOCK_DATA.site, + adunit: MOCK_DATA.adUnitCode + }); + + done(); + }); + + it('should handle the bid response event', function() { + events.emit(BID_RESPONSE, MOCK_DATA.bidResponse); + + const actual = scaleableAnalytics.getAuctionData(); + + expect(actual[MOCK_DATA.adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidResponse); + }); + + it('should handle the bid timeout event', function() { + events.emit(BID_TIMEOUT, MOCK_DATA.bidTimeout); + + const actual = scaleableAnalytics.getAuctionData(); + + expect(actual[MOCK_DATA.bidTimeout[0].adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidTimeout); + }); + + it('should handle the bid won event', function(done) { + events.emit(BID_WON, MOCK_DATA.bidResponse); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal({ + adunit: MOCK_DATA.adUnitCode, + code: MOCK_DATA.bidResponse.bidderCode, + cpm: MOCK_DATA.bidResponse.cpm, + ttr: MOCK_DATA.bidResponse.timeToRespond, + event: 'win', + site: MOCK_DATA.site + }); + + done(); + }); + + it('should handle the auction end event', function(done) { + events.emit(AUCTION_END, {}); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal(MOCK_DATA.expectedBidResponse); + + done(); + }); + }); +}); From 9a191ea8ee23543ed36526d7c0cef8c8feb123b9 Mon Sep 17 00:00:00 2001 From: Chris Connors Date: Thu, 13 Jun 2019 13:40:47 -0400 Subject: [PATCH 2/2] Removed commented out code --- test/spec/modules/scaleableAnalyticsAdapter_spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/spec/modules/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js index 99fe8f941ec..300f72751a4 100644 --- a/test/spec/modules/scaleableAnalyticsAdapter_spec.js +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -77,9 +77,6 @@ describe('Scaleable Analytics Adapter', function() { scaleableAnalytics.disableAnalytics(); }); - // after(function() { - // }); - it('should handle the auction init event', function(done) { events.emit(AUCTION_INIT, { adUnitCodes: [MOCK_DATA.adUnitCode]