Skip to content

Commit

Permalink
Scaleable Analytics Adapter: Grouping Server Calls (prebid#4634)
Browse files Browse the repository at this point in the history
* Initial draft changes for adapter

* Updated bids and tests

* Fixed timeouts being sent all the time

* Added prod endpoint back

* Test Updates to be more in line with base spec file

* Sending bidder params to server with win event

* Missed a conflict with new XHR functionality

* Added local Object.entries polyfill
  • Loading branch information
cconnors1515 authored and hellsingblack committed Mar 5, 2020
1 parent 84642a9 commit 3f46fec
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 83 deletions.
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

0 comments on commit 3f46fec

Please sign in to comment.