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

fixed #1246 and add unit test #1427

Merged
merged 7 commits into from
Aug 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions src/targeting.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter } from './utils';
import { uniques, isGptPubadsDefined, getHighestCpm, adUnitsFilter, groupBy } from './utils';
import { config } from './config';
import { NATIVE_TARGETING_KEYS } from './native';
const bidmanager = require('./bidmanager');
Expand Down Expand Up @@ -96,13 +96,7 @@ targeting.getWinningBids = function(adUnitCode) {
.filter(uniques)
.map(adUnitCode => $$PREBID_GLOBAL$$._bidsReceived
.filter(bid => bid.adUnitCode === adUnitCode ? bid : null)
.reduce(getHighestCpm,
{
adUnitCode: adUnitCode,
cpm: 0,
adserverTargeting: {},
timeToRespond: 0
}));
.reduce(getHighestCpm, getEmptyBid(adUnitCode)));
};

targeting.setTargetingForAst = function() {
Expand Down Expand Up @@ -173,19 +167,24 @@ function getAlwaysUseBidTargeting(adUnitCodes) {

function getBidLandscapeTargeting(adUnitCodes) {
const standardKeys = CONSTANTS.TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS);

return $$PREBID_GLOBAL$$._bidsReceived
.filter(adUnitsFilter.bind(this, adUnitCodes))
.map(bid => {
if (bid.adserverTargeting) {
return {
[bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter(
key => typeof bid.adserverTargeting[key] !== 'undefined') // mainly for possibly
// unset hb_deal
)
};
}
}).filter(bid => bid); // removes empty elements in array
const bids = [];
// bucket by adUnitcode
let buckets = groupBy($$PREBID_GLOBAL$$._bidsReceived, 'adUnitCode');
// filter top bid for each bucket by bidder
Object.keys(buckets).forEach(bucketKey => {
let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode');
Object.keys(bidsByBidder).forEach(key => bids.push(bidsByBidder[key].reduce(getHighestCpm, getEmptyBid())));
});
// populate targeting keys for the remaining bids
return bids.map(bid => {
if (bid.adserverTargeting) {
return {
[bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter(
key => typeof bid.adserverTargeting[key] !== 'undefined')
)
};
}
}).filter(bid => bid); // removes empty elements in array
}

function getTargetingMap(bid, keys) {
Expand All @@ -201,3 +200,12 @@ targeting.isApntagDefined = function() {
return true;
}
};

function getEmptyBid(adUnitCode) {
return {
adUnitCode: adUnitCode,
cpm: 0,
adserverTargeting: {},
timeToRespond: 0
};
}
15 changes: 15 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,3 +666,18 @@ export function getBidderRequest(bidder, adUnitCode) {
.filter(bid => bid.bidder === bidder && bid.placementCode === adUnitCode).length > 0;
}) || { start: null, requestId: null };
}

/**
*
* https://stackoverflow.com/a/34890276/428704
* @export
* @param {array} xs
* @param {string} key
* @returns {${key_value}: ${groupByArray}, key_value: {groupByArray}}
*/
export function groupBy(xs, key) {
Copy link
Contributor

@dbemiller dbemiller Jul 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hooray! Nice general code.

This is a bit subjective... but if you want to stretch it even more, you could make this function much more powerful (and only a little bit more complicated) if you accept an (element) => key function here, rather than a raw "key".

Your call would replace groupBy($$PREBID_GLOBAL$$._bidsReceived, 'adUnitCode')
with groupBy($$PREBID_GLOBAL$$._bidsReceived, bid => bid.adUnitCode).

In exchange, this function would work with arrays of anything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, but I don't understand how this is useful to me?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, it's not. Sorry... should have been clear: I didn't actually expect a change here. More of a fun FYI.

Many general-purpose utils libraries have this function, including lodash, async, the scala stdlib, etc. I was just pointing out how close this comes to "the bleeding edge" of general-purpose code.

return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
}
99 changes: 99 additions & 0 deletions test/spec/unit/core/targeting_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { expect } from 'chai';
import Targeting from 'src/targeting';
import { config } from 'src/config';
import { getAdUnits } from 'test/fixtures/fixtures';
import CONSTANTS from 'src/constants.json';

const bid1 = {
'bidderCode': 'rubicon',
'width': '300',
'height': '250',
'statusMessage': 'Bid available',
'adId': '148018fe5e',
'cpm': 0.537234,
'ad': 'markup',
'ad_id': '3163950',
'sizeId': '15',
'requestTimestamp': 1454535718610,
'responseTimestamp': 1454535724863,
'timeToRespond': 123,
'pbLg': '0.50',
'pbMg': '0.50',
'pbHg': '0.53',
'adUnitCode': '/123456/header-bid-tag-0',
'bidder': 'rubicon',
'size': '300x250',
'adserverTargeting': {
'hb_bidder': 'rubicon',
'hb_adid': '148018fe5e',
'hb_pb': '0.53',
'foobar': '300x250'
}
};

const bid2 = {
'bidderCode': 'rubicon',
'width': '300',
'height': '250',
'statusMessage': 'Bid available',
'adId': '5454545',
'cpm': 0.25,
'ad': 'markup',
'ad_id': '3163950',
'sizeId': '15',
'requestTimestamp': 1454535718610,
'responseTimestamp': 1454535724863,
'timeToRespond': 123,
'pbLg': '0.25',
'pbMg': '0.25',
'pbHg': '0.25',
'adUnitCode': '/123456/header-bid-tag-0',
'bidder': 'rubicon',
'size': '300x250',
'adserverTargeting': {
'hb_bidder': 'rubicon',
'hb_adid': '5454545',
'hb_pb': '0.25',
'foobar': '300x250'
}
};

describe('targeting tests', () => {
describe('getAllTargeting', () => {
beforeEach(() => {
$$PREBID_GLOBAL$$._sendAllBids = false;
$$PREBID_GLOBAL$$._bidsReceived = [];
$$PREBID_GLOBAL$$._adUnitCodes = [];
$$PREBID_GLOBAL$$.adUnits = [];
});

it('selects the top bid when _sendAllBids true', () => {
$$PREBID_GLOBAL$$.adUnits = [{
code: '/123456/header-bid-tag-0',
sizes: [300, 250],
bids: [
{
'bidder': 'rubicon',
'params': {
'accountId': 10617,
'siteId': 23635,
'zoneId': 453908
}
}
]
}];
config.setConfig({ enableSendAllBids: true });
$$PREBID_GLOBAL$$._bidsReceived.push(bid1, bid2);
$$PREBID_GLOBAL$$._adUnitCodes = ['/123456/header-bid-tag-0'];
let targeting = Targeting.getAllTargeting(['/123456/header-bid-tag-0']);
let flattened = [];
targeting.filter(obj => obj['/123456/header-bid-tag-0'] !== undefined).forEach(item => flattened = flattened.concat(item['/123456/header-bid-tag-0']));
let sendAllBidCpm = flattened.filter(obj => obj.hb_pb_rubicon !== undefined);
let winningBidCpm = flattened.filter(obj => obj.hb_pb !== undefined);
// we shouldn't get more than 1 key for hb_pb_${bidder}
expect(sendAllBidCpm.length).to.equal(1);
// expect the winning CPM to be equal to the sendAllBidCPM
expect(sendAllBidCpm[0]['hb_pb_rubicon']).to.deep.equal(winningBidCpm[0]['hb_pb']);
});
}); // end getAllTargeting tests
});