From 23a22072baaeff885725b4df1da156a5323baafd Mon Sep 17 00:00:00 2001 From: Shrikant Patwari Date: Fri, 13 Apr 2018 02:48:52 +0530 Subject: [PATCH 1/3] Analytic Adaptor by YuktaMedia --- modules/yuktamediaAnalyticsAdapter.js | 146 ++++++++++++++ modules/yuktamediaAnalyticsAdapter.md | 22 +++ .../yuktamediaAnalyticsAdaptor_spec.js | 179 ++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 modules/yuktamediaAnalyticsAdapter.js create mode 100644 modules/yuktamediaAnalyticsAdapter.md create mode 100644 test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js new file mode 100644 index 00000000000..2de328ae771 --- /dev/null +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -0,0 +1,146 @@ +import { ajax } from 'src/ajax'; +import adapter from 'src/AnalyticsAdapter'; +import adaptermanager from 'src/adaptermanager'; +import CONSTANTS from 'src/constants.json'; +import * as url from 'src/url'; +import * as utils from 'src/utils'; + +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const yuktamediaAnalyticsVersion = 'v1.0.0'; + +let initOptions; +let auctionTimestamp; +let events = { + bids: [] +}; + +var yuktamediaAnalyticsAdapter = Object.assign(adapter( + { + emptyUrl, + analyticsType + }), { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + args.forEach(item => { mapBidResponse(item, 'timeout'); }); + } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + events.auctionInit = args; + auctionTimestamp = args.timestamp; + } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + mapBidRequests(args).forEach(item => { events.bids.push(item) }); + } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + mapBidResponse(args, 'response'); + } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + send({ + bidWon: mapBidResponse(args, 'win') + }, 'won'); + } + } + + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + send(events, 'auctionEnd'); + } + } +}); + +function mapBidRequests(params) { + let arr = []; + if (typeof params.bids !== 'undefined' && params.bids.length) { + params.bids.forEach(function (bid) { + arr.push({ + bidderCode: bid.bidder, + bidId: bid.bidId, + adUnitCode: bid.adUnitCode, + requestId: bid.bidderRequestId, + auctionId: bid.auctionId, + pubcid: bid.crumbs.pubcid, + placementId: bid.params.placementId, + transactionId: bid.transactionId, + sizes: utils.parseSizesInput(bid.sizes).toString(), + renderStatus: 1, + requestTimestamp: params.auctionStart + }); + }); + } + return arr; +} + +function mapBidResponse(bidResponse, status) { + if (status !== 'win') { + let bid = events.bids.filter(o => o.bidId == bidResponse.bidId || o.bidId == bidResponse.requestId)[0]; + Object.assign(bid, { + bidderCode: bidResponse.bidder, + bidId: status == 'timeout' ? bidResponse.bidId : bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + status: bidResponse.status, + renderStatus: status == 'timeout' ? 3 : 2, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp + }); + } else if (status == 'win') { + return { + bidderCode: bidResponse.bidder, + bidId: bidResponse.requestId, + adUnitCode: bidResponse.adUnitCode, + auctionId: bidResponse.auctionId, + creativeId: bidResponse.creativeId, + transactionId: bidResponse.transactionId, + currency: bidResponse.currency, + cpm: bidResponse.cpm, + netRevenue: bidResponse.netRevenue, + renderedSize: bidResponse.size, + mediaType: bidResponse.mediaType, + statusMessage: bidResponse.statusMessage, + status: bidResponse.status, + renderStatus: 4, + timeToRespond: bidResponse.timeToRespond, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp + } + } +} + +function send(data, status) { + let location = utils.getTopWindowLocation(); + let secure = location.protocol == 'https:'; + if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { + data.auctionInit = Object.assign({ host: location.host, path: location.pathname, hash: location.hash, search: location.search }, data.auctionInit); + } + data.initOptions = initOptions; + + let yuktamediaAnalyticsRequestUrl = url.format({ + protocol: secure ? 'https' : 'http', + hostname: 'analytics-prebid.yuktamedia.com', + pathname: status == 'auctionEnd' ? '/api/bids' : '/api/bid/won', + search: { + auctionTimestamp: auctionTimestamp, + yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, + prebidVersion: $$PREBID_GLOBAL$$.version + } + }); + + ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'application/json' }); +} + +yuktamediaAnalyticsAdapter.originEnableAnalytics = yuktamediaAnalyticsAdapter.enableAnalytics; +yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + yuktamediaAnalyticsAdapter.originEnableAnalytics(config); +}; + +adaptermanager.registerAnalyticsAdapter({ + adapter: yuktamediaAnalyticsAdapter, + code: 'yuktamedia' +}); + +export default yuktamediaAnalyticsAdapter; diff --git a/modules/yuktamediaAnalyticsAdapter.md b/modules/yuktamediaAnalyticsAdapter.md new file mode 100644 index 00000000000..a21675b6b1d --- /dev/null +++ b/modules/yuktamediaAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: YuktaMedia Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: info@yuktamedia.com + +# Description + +Analytics adapter for prebid provided by YuktaMedia. Contact info@yuktamedia.com for information. + +# Test Parameters + +``` +{ + provider: 'yuktamedia', + options : { + pubId : 50357 //id provided by YuktaMedia LLP + pubKey: 'xxx' //key provided by YuktaMedia LLP + } +} +``` diff --git a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js new file mode 100644 index 00000000000..9764a6d96c2 --- /dev/null +++ b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js @@ -0,0 +1,179 @@ +import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter'; +import { expect } from 'chai'; +let adaptermanager = require('src/adaptermanager'); +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('YuktaMedia 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', () => { + let initOptions = { + pubId: '1', + pubKey: 'ZXlKaGJHY2lPaUpJVXpJMU5pSjkuT==' + }; + + adaptermanager.registerAnalyticsAdapter({ + code: 'yuktamedia', + adapter: yuktamediaAnalyticsAdapter + }); + + beforeEach(() => { + adaptermanager.enableAnalytics({ + provider: 'yuktamedia', + options: initOptions + }); + }); + + afterEach(() => { + yuktamediaAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', () => { + let auctionTimestamp = 1496510254313; + let bidRequest = { + 'bidderCode': 'appnexus', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'bidderRequestId': '173209942f8bdd', + 'bids': [{ + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'crumbs': { + 'pubcid': '9a2a4e71-f39b-405f-aecc-19efc22b618d' + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '2eddfdc0c791dc', + 'bidderRequestId': '173209942f8bdd', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7' + } + ], + 'auctionStart': 1522265863591, + 'timeout': 3000, + 'start': 1522265863600, + 'doneCbCallCount': 1 + }; + let bidResponse = { + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '2eddfdc0c791dc', + 'mediaType': 'banner', + 'source': 'client', + 'requestId': '2eddfdc0c791dc', + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'responseTimestamp': 1522265866110, + 'requestTimestamp': 1522265863600, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'timeToRespond': 2510, + 'size': '300x250' + }; + let bidTimeoutArgsV1 = [ + { + bidId: '2baa51527bd015', + bidder: 'bidderOne', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + }, + { + bidId: '6fe3b4c2c23092', + bidder: 'bidderTwo', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + } + ]; + let bid = { + 'bidderCode': 'appnexus', + 'bidId': '2eddfdc0c791dc', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'requestId': '173209942f8bdd', + 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', + 'pubcid': '9a2a4e71-f39b-405f-aecc-19efc22b618d', + 'placementId': '10433394', + 'renderStatus': 2, + 'cpm': 0.5, + 'creativeId': 29681110, + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'requestTimestamp': 1522265863600, + 'responseTimestamp': 1522265866110, + 'sizes': '300x250,300x600', + 'statusMessage': 'Bid available', + 'timeToRespond': 2510 + } + + // Step 1: Send auction init event + events.emit(constants.EVENTS.AUCTION_INIT, { + 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, bidTimeoutArgsV1); + + // Step 5: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}, 'auctionEnd'); + + expect(requests.length).to.equal(1); + + let auctionEventData = JSON.parse(requests[0].requestBody); + + expect(auctionEventData.bids.length).to.equal(1); + expect(auctionEventData.bids[0]).to.deep.equal(bid); + + expect(auctionEventData.initOptions).to.deep.equal(initOptions); + + // Step 6: Send auction bid won event + events.emit(constants.EVENTS.BID_WON, { + 'bidderCode': 'appnexus', + 'statusMessage': 'Bid available', + 'adId': '108abedd106b669', + 'auctionId': '6355d610-7cdc-4009-a866-f91997fd24bb', + 'responseTimestamp': 1522144433058, + 'requestTimestamp': 1522144432923, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'timeToRespond': 135, + 'size': '300x250', + 'status': 'rendered' + }, 'won'); + + expect(requests.length).to.equal(2); + + let winEventData = JSON.parse(requests[1].requestBody); + + expect(winEventData.bidWon.status).to.equal('rendered'); + expect(winEventData.initOptions).to.deep.equal(initOptions); + }); + }); +}); From 904523e0a45f271c683526a1a23126331c001dc9 Mon Sep 17 00:00:00 2001 From: Shrikant Patwari Date: Wed, 18 Apr 2018 13:16:12 +0530 Subject: [PATCH 2/3] removed optional bid request params --- modules/yuktamediaAnalyticsAdapter.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 2de328ae771..2801ec3afb8 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -54,8 +54,6 @@ function mapBidRequests(params) { adUnitCode: bid.adUnitCode, requestId: bid.bidderRequestId, auctionId: bid.auctionId, - pubcid: bid.crumbs.pubcid, - placementId: bid.params.placementId, transactionId: bid.transactionId, sizes: utils.parseSizesInput(bid.sizes).toString(), renderStatus: 1, From 16bde782f859754f92605cfe66ab6cf4c6e13dd2 Mon Sep 17 00:00:00 2001 From: Shrikant Patwari Date: Wed, 18 Apr 2018 14:15:59 +0530 Subject: [PATCH 3/3] test case modified --- test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js index 9764a6d96c2..29d23d66bd2 100644 --- a/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js +++ b/test/spec/modules/yuktamediaAnalyticsAdaptor_spec.js @@ -112,8 +112,6 @@ describe('YuktaMedia analytics adapter', () => { 'adUnitCode': 'div-gpt-ad-1438287399331-0', 'requestId': '173209942f8bdd', 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7', - 'pubcid': '9a2a4e71-f39b-405f-aecc-19efc22b618d', - 'placementId': '10433394', 'renderStatus': 2, 'cpm': 0.5, 'creativeId': 29681110,