Skip to content

Commit

Permalink
Standardizing First Party Data (#4472)
Browse files Browse the repository at this point in the history
* Add microadBidAdapter

* Remove unnecessary encodeURIComponent from microadBidAdapter

* Submit Advangelists Prebid Adapter

* Submit Advangelists Prebid Adapter 1.1

* Correct procudtion endpoint for prebid

* analytics update with wrapper name

* reverted error merge

* update changed default value of netRevenue to true

* Support user and context first party data in rubicon and prebid server adapters

* Place user and context within fpd object

* Separate global and bidder-specific first party data

* Repeat FPD for each bidder when sending to PBS

Co-authored-by: Isaac A. Dettman <[email protected]>
  • Loading branch information
msm0504 and Isaac A. Dettman authored Feb 19, 2020
1 parent 7279255 commit 3662ee3
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 16 deletions.
35 changes: 35 additions & 0 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,32 @@ function _appendSiteAppDevice(request, pageUrl) {
}
}

function addBidderFirstPartyDataToRequest(request) {
const bidderConfig = config.getBidderConfig();
const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => {
const currBidderConfig = bidderConfig[bidder];
if (currBidderConfig.fpd) {
const fpd = {};
if (currBidderConfig.fpd.context) {
fpd.site = currBidderConfig.fpd.context;
}
if (currBidderConfig.fpd.user) {
fpd.user = currBidderConfig.fpd.user;
}

acc.push({
bidders: [ bidder ],
config: { fpd }
});
}
return acc;
}, []);

if (fpdConfigs.length) {
utils.deepSetValue(request, 'ext.prebid.bidderconfig', fpdConfigs);
}
}

// https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40
let nativeDataIdMap = {
sponsoredBy: 1, // sponsored
Expand Down Expand Up @@ -661,6 +687,15 @@ const OPEN_RTB_PROTOCOL = {
utils.deepSetValue(request, 'regs.coppa', 1);
}

const commonFpd = getConfig('fpd') || {};
if (commonFpd.context) {
utils.deepSetValue(request, 'site.ext.data', commonFpd.context);
}
if (commonFpd.user) {
utils.deepSetValue(request, 'user.ext.data', commonFpd.user);
}
addBidderFirstPartyDataToRequest(request);

return request;
},

Expand Down
58 changes: 43 additions & 15 deletions modules/rubiconBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,27 @@ export const spec = {
utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain);
}

const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context'));
const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user'));
if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) {
const bidderData = {
bidders: [ bidderRequest.bidderCode ],
config: {
fpd: {}
}
};

if (!utils.isEmpty(siteData)) {
bidderData.config.fpd.site = siteData;
}

if (!utils.isEmpty(userData)) {
bidderData.config.fpd.user = userData;
}

utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData);
}

/**
* Prebid AdSlot
* @type {(string|undefined)}
Expand Down Expand Up @@ -448,7 +469,6 @@ export const spec = {
'x_source.tid': bidRequest.transactionId,
'x_source.pchain': params.pchain,
'p_screen_res': _getScreenResolution(),
'kw': Array.isArray(params.keywords) ? params.keywords.join(',') : '',
'tk_user_key': params.userId,
'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4),
'p_geo.longitude': isNaN(parseFloat(longitude)) ? undefined : parseFloat(longitude).toFixed(4),
Expand Down Expand Up @@ -487,22 +507,30 @@ export const spec = {
}

// visitor properties
if (params.visitor !== null && typeof params.visitor === 'object') {
Object.keys(params.visitor).forEach((key) => {
if (params.visitor[key] != null) {
data[`tg_v.${key}`] = params.visitor[key].toString(); // initialize array;
}
});
}
const visitorData = Object.assign({}, params.visitor, config.getConfig('fpd.user'));
Object.keys(visitorData).forEach((key) => {
if (visitorData[key] != null && key !== 'keywords') {
data[`tg_v.${key}`] = typeof visitorData[key] === 'object' && !Array.isArray(visitorData[key])
? JSON.stringify(visitorData[key])
: visitorData[key].toString(); // initialize array;
}
});

// inventory properties
if (params.inventory !== null && typeof params.inventory === 'object') {
Object.keys(params.inventory).forEach((key) => {
if (params.inventory[key] != null) {
data[`tg_i.${key}`] = params.inventory[key].toString();
}
});
}
const inventoryData = Object.assign({}, params.inventory, config.getConfig('fpd.context'));
Object.keys(inventoryData).forEach((key) => {
if (inventoryData[key] != null && key !== 'keywords') {
data[`tg_i.${key}`] = typeof inventoryData[key] === 'object' && !Array.isArray(inventoryData[key])
? JSON.stringify(inventoryData[key])
: inventoryData[key].toString();
}
});

// keywords
const keywords = (params.keywords || []).concat(
utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || [],
utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || []);
data.kw = keywords.length ? keywords.join(',') : '';

/**
* Prebid AdSlot
Expand Down
46 changes: 45 additions & 1 deletion test/spec/modules/prebidServerBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,51 @@ describe('S2S Adapter', function () {
adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax);
const parsedRequestBody = JSON.parse(server.requests[0].requestBody);
expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject);
})
});

it('passes first party data in request', () => {
const s2sBidRequest = utils.deepClone(REQUEST);
const bidRequests = utils.deepClone(BID_REQUESTS);

const commonContext = {
keywords: ['power tools'],
search: 'drill'
};
const commonUser = {
keywords: ['a', 'b'],
gender: 'M'
};

const context = {
content: { userrating: 4 },
data: {
pageType: 'article',
category: 'tools'
}
};
const user = {
yob: '1984',
geo: { country: 'ca' },
data: {
registered: true,
interests: ['cars']
}
};
const allowedBidders = [ 'rubicon', 'appnexus' ];

const expected = allowedBidders.map(bidder => ({
bidders: [ bidder ],
config: { fpd: { site: context, user } }
}));

config.setConfig({ fpd: { context: commonContext, user: commonUser } });
config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } });
adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax);
const parsedRequestBody = JSON.parse(server.requests[0].requestBody);
expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected);
expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext);
expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser);
});

describe('pbAdSlot config', function () {
it('should not send \"imp.ext.context.data.adslot\" if \"fpd.context\" is undefined', function () {
Expand Down
83 changes: 83 additions & 0 deletions test/spec/modules/rubiconBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,51 @@ describe('the rubicon adapter', function () {
expect(data[key]).to.equal(value);
});
});

it('should use first party data from getConfig over the bid params, if present', () => {
const context = {
keywords: ['e', 'f'],
rating: '4-star'
};
const user = {
keywords: ['d'],
gender: 'M',
yob: '1984',
geo: { country: 'ca' }
};

sandbox.stub(config, 'getConfig').callsFake(key => {
const config = {
fpd: {
context,
user
}
};
return utils.deepAccess(config, key);
});

const expectedQuery = {
'kw': 'a,b,c,d,e,f',
'tg_v.ucat': 'new',
'tg_v.lastsearch': 'iphone',
'tg_v.likes': 'sports,video games',
'tg_v.gender': 'M',
'tg_v.yob': '1984',
'tg_v.geo': '{"country":"ca"}',
'tg_i.rating': '4-star',
'tg_i.prodtype': 'tech,mobile',
};

// get the built request
let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
let data = parseQuery(request.data);

// make sure that tg_v, tg_i, and kw values are correct
Object.keys(expectedQuery).forEach(key => {
let value = expectedQuery[key];
expect(data[key]).to.deep.equal(value);
});
});
});

describe('singleRequest config', function () {
Expand Down Expand Up @@ -1641,6 +1686,44 @@ describe('the rubicon adapter', function () {
expect(request.data.regs.coppa).to.equal(1);
});

it('should include first party data', () => {
createVideoBidderRequest();

const context = {
keywords: ['e', 'f'],
rating: '4-star'
};
const user = {
keywords: ['d'],
gender: 'M',
yob: '1984',
geo: { country: 'ca' }
};

sandbox.stub(config, 'getConfig').callsFake(key => {
const config = {
fpd: {
context,
user
}
};
return utils.deepAccess(config, key);
});

const expected = [{
bidders: [ 'rubicon' ],
config: {
fpd: {
site: Object.assign({}, bidderRequest.bids[0].params.inventory, context),
user: Object.assign({}, bidderRequest.bids[0].params.visitor, user)
}
}
}];

const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected);
});

it('should include storedAuctionResponse in video bid request', function () {
createVideoBidderRequest();

Expand Down

0 comments on commit 3662ee3

Please sign in to comment.