Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaleable Analytics Adapter: Grouping Server Calls #4634

Merged
148 changes: 99 additions & 49 deletions modules/scaleableAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@ import adapter from '../src/AnalyticsAdapter';
import adapterManager from '../src/adapterManager';
import * as utils from '../src/utils';

// Object.entries polyfill
const entries = Object.entries || function(obj) {
const ownProps = Object.keys(obj);
let i = ownProps.length;
let resArray = new Array(i); // preallocate the Array
while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; }

return resArray;
};

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;

Expand All @@ -35,9 +44,6 @@ let scaleableAnalytics = Object.assign({},
case BID_WON:
onBidWon(args);
break;
case BID_RESPONSE:
onBidResponse(args);
break;
case BID_TIMEOUT:
onBidTimeout(args);
break;
Expand Down Expand Up @@ -70,22 +76,93 @@ const sendDataToServer = data => ajax(URL, () => {}, JSON.stringify(data));
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]
};
let adunitObj = {};
let adunits = [];

// Loop through adunit codes first
args.adUnitCodes.forEach((code) => {
adunitObj[code] = [{
bidder: 'scaleable_adunit_request'
}]
});

// Loop through bidder requests and bids
args.bidderRequests.forEach((bidderObj) => {
bidderObj.bids.forEach((bidObj) => {
adunitObj[bidObj.adUnitCode].push({
bidder: bidObj.bidder,
params: bidObj.params
})
});
});

entries(adunitObj).forEach(([adunitCode, bidRequests]) => {
adunits.push({
code: adunitCode,
bidRequests: bidRequests
});
});

sendDataToServer(data);
const data = {
event: 'request',
site: config.options.site,
adunits: adunits
}

sendDataToServer(data);
}

// Handle all events besides requests and wins
const onAuctionEnd = args => {
for (let adunit in auctionData) {
sendDataToServer(auctionData[adunit]);
const config = scaleableAnalytics.config || {options: {}};

let adunitObj = {};
let adunits = [];

// Add Bids Received
args.bidsReceived.forEach((bidObj) => {
if (!adunitObj[bidObj.adUnitCode]) { adunitObj[bidObj.adUnitCode] = []; }

adunitObj[bidObj.adUnitCode].push({
bidder: bidObj.bidderCode || bidObj.bidder,
cpm: bidObj.cpm,
currency: bidObj.currency,
dealId: bidObj.dealId,
type: bidObj.mediaType,
ttr: bidObj.timeToRespond,
size: bidObj.size
});
});

// Add in other data (timeouts) as we push to adunits
entries(adunitObj).forEach(([adunitCode, bidsReceived]) => {
const bidData = bidsReceived.concat(auctionData[adunitCode] || []);
adunits.push({
code: adunitCode,
bidData: bidData
});

delete auctionData[adunitCode];
});

// Add in any missed auction data
entries(auctionData).forEach(([adunitCode, bidData]) => {
adunits.push({
code: adunitCode,
bidData: bidData
})
});

const data = {
event: 'bids',
site: config.options.site,
adunits: adunits
}

if (adunits.length) { sendDataToServer(data); }

// Reset auctionData
auctionData = {}
}

// Bid Win Events occur after auction end
Expand All @@ -98,51 +175,24 @@ const onBidWon = args => {
adunit: args.adUnitCode,
code: args.bidderCode,
cpm: args.cpm,
ttr: args.timeToRespond
ttr: args.timeToRespond,
params: args.params
};

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

args.forEach(currObj => {
if (!auctionData[currObj.adUnitCode]) {
auctionData[currObj.adUnitCode] = {
event: 'bids',
bids: [],
timeouts: [],
adunit: currObj.adUnitCode,
site: config.options.site
};
auctionData[currObj.adUnitCode] = []
}

auctionData[currObj.adUnitCode].timeouts.push(currObj.bidder);
}
auctionData[currObj.adUnitCode].push({
timeouts: 1,
bidder: currObj.bidder
});
});
}

adapterManager.registerAnalyticsAdapter({
Expand Down
110 changes: 76 additions & 34 deletions test/spec/modules/scaleableAnalyticsAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,44 @@ import { server } from 'test/mocks/xhr';

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 bidsReceivedObj = {
adUnitCode: '12345',
bidderCode: 'test-code',
cpm: 3.14,
currency: 'USD',
dealId: null,
mediaType: 'banner',
timeToRespond: 285,
size: '300x250'
};

const MOCK_DATA = {
adUnitCode: '12345',
auctionEnd: {
bidsReceived: [bidsReceivedObj]
},
bidderRequests: [{
bids: [{
adUnitCode: '12345',
bidder: 'test-code',
params: {
test: 'value'
}
}]
}],
site: '5c4fab7a829e955d6c265e72',
bidResponse: {
adUnitCode: '12345',
bidderCode: 'test-code',
cpm: 3.14,
timeToRespond: 285
timeToRespond: 285,
params: [{
test: 'value'
}]
},
bidTimeout: [
{
Expand All @@ -28,25 +53,52 @@ describe('Scaleable Analytics Adapter', function() {
]
};

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
};
const bidObj = MOCK_DATA.bidderRequests[0].bids[0];

const expectedBidRequests = [{bidder: 'scaleable_adunit_request'}].concat([
{
bidder: bidObj.bidder,
params: bidObj.params
}
]);

MOCK_DATA.expectedRequestResponse = {
event: 'request',
site: MOCK_DATA.site,
adunits: [{
code: bidObj.adUnitCode,
bidRequests: expectedBidRequests
}]
}

MOCK_DATA.expectedBidTimeout = {
event: 'bids',
bids: [],
timeouts: [MOCK_DATA.bidTimeout[0].bidder],
adunit: MOCK_DATA.bidTimeout[0].adUnitCode,
site: MOCK_DATA.site
[MOCK_DATA.bidTimeout[0].adUnitCode]: [{
timeouts: 1,
bidder: MOCK_DATA.bidTimeout[0].bidder
}]
};

MOCK_DATA.expectedAuctionEndResponse = {
event: 'bids',
site: MOCK_DATA.site,
adunits: [{
code: MOCK_DATA.auctionEnd.bidsReceived[0].adUnitCode,
bidData: [{
bidder: bidsReceivedObj.bidderCode,
cpm: bidsReceivedObj.cpm,
currency: bidsReceivedObj.currency,
dealId: bidsReceivedObj.dealId,
type: bidsReceivedObj.mediaType,
ttr: bidsReceivedObj.timeToRespond,
size: bidsReceivedObj.size
}]
},
{
bidData: MOCK_DATA.expectedBidTimeout[MOCK_DATA.bidTimeout[0].adUnitCode],
code: MOCK_DATA.bidTimeout[0].adUnitCode
}]
}

describe('Event Handling', function() {
beforeEach(function() {
sinon.stub(events, 'getEvents').returns([]);
Expand All @@ -66,33 +118,22 @@ describe('Scaleable Analytics Adapter', function() {

it('should handle the auction init event', function(done) {
events.emit(AUCTION_INIT, {
adUnitCodes: [MOCK_DATA.adUnitCode]
adUnitCodes: [MOCK_DATA.adUnitCode],
bidderRequests: MOCK_DATA.bidderRequests
});

const result = JSON.parse(server.requests[0].requestBody);
expect(result).to.deep.equal({
event: 'request',
site: MOCK_DATA.site,
adunit: MOCK_DATA.adUnitCode
});
expect(result).to.deep.equal(MOCK_DATA.expectedRequestResponse);

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);
expect(actual).to.deep.equal(MOCK_DATA.expectedBidTimeout);
});

it('should handle the bid won event', function(done) {
Expand All @@ -104,6 +145,7 @@ describe('Scaleable Analytics Adapter', function() {
code: MOCK_DATA.bidResponse.bidderCode,
cpm: MOCK_DATA.bidResponse.cpm,
ttr: MOCK_DATA.bidResponse.timeToRespond,
params: MOCK_DATA.bidResponse.params,
event: 'win',
site: MOCK_DATA.site
});
Expand All @@ -112,10 +154,10 @@ describe('Scaleable Analytics Adapter', function() {
});

it('should handle the auction end event', function(done) {
events.emit(AUCTION_END, {});
events.emit(AUCTION_END, MOCK_DATA.auctionEnd);

const result = JSON.parse(server.requests[0].requestBody);
expect(result).to.deep.equal(MOCK_DATA.expectedBidResponse);
expect(result).to.deep.equal(MOCK_DATA.expectedAuctionEndResponse);

done();
});
Expand Down