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

PBS Adapter: Support Bidder-Specific Schains #8594

Merged
merged 8 commits into from
Jul 25, 2022
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
37 changes: 37 additions & 0 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,43 @@ Object.assign(ORTB2.prototype, {
request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid);
}

// get reference to pbs config schain bidder names (if any exist)
const pbsSchainBidderNamesArr = request.ext.prebid?.schains ? request.ext.prebid.schains.flatMap(s => s.bidders) : [];
// create an schains object
const schains = Object.fromEntries(
(request.ext.prebid?.schains || []).map(({bidders, schain}) => [JSON.stringify(schain), {bidders: new Set(bidders), schain}])
);

// compare bidder specific schains with pbs specific schains
request.ext.prebid.schains = Object.values(
bidRequests
.map((req) => [req.bidderCode, req.bids[0].schain])
.reduce((chains, [bidder, chain]) => {
const chainKey = JSON.stringify(chain);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm assuming here that as long a bidder is associated only with one chain, it's OK to have duplicates due to different serialization of equivalent schains, e.g. [{bidders: ['A'], schain1}, {bidders: ['B'], schain2}] works for the backend even if schain1 and schain2 are identical.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I refactored things a bit to include the following logic:

if pbjs.setBidderConfig is used to create an schain with bidder A, but bidder B has the same schain object (as configured on s2sConfig.extPrebid.schains) as bidder A`.. now the output in the request will be:

ext.prebid.schains[{bidders: ['bidder A', 'bidder B'], schain: <the schain obj they were both using>}]


switch (true) {
// if pbjs bidder name is same as pbs bidder name, pbs bidder name always wins
case chainKey && pbsSchainBidderNamesArr.indexOf(bidder) !== -1:
logInfo(`bidder-specific schain for ${bidder} skipped due to existing entry`);
break;
// if a pbjs schain obj is equal to an schain obj that exists on the pbs side, add the bidder name on the pbs side
case chainKey && chains.hasOwnProperty(chainKey) && pbsSchainBidderNamesArr.indexOf(bidder) === -1:
chains[chainKey].bidders.add(bidder);
break;
// if a pbjs schain obj is not on the pbs side, add a new schain entry on the pbs side
case chainKey && !chains.hasOwnProperty(chainKey):
chains[chainKey] = {bidders: new Set(), schain: chain};
chains[chainKey].bidders.add(bidder);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe the bidders.add(bidder) needs to be detented out of if (!chains.hasOwnProperty(chainKey)); otherwise, if multiple bidders have chains that serialize to the same key, you are losing the association for every one of them except the first.

This would also be a good test case, if I understand your intent, you want two separate bid requests with identical chains to be "merged" into a single entry in ext.prebid.schains.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

adjusted the logic for this and also added a test case

break;
default:
}

return chains;
}, schains)
).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain}));
// if schains evaluates to an empty array, remove it from the prebid object
if (request.ext.prebid.schains.length === 0) delete request.ext.prebid.schains;

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know if this is in scope for this, but I noticed something while working on a separate issue: on line 918, the PBS adapter sets source.ext.schain with the first schain it finds. Does that make sense? is it just ignored server side?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hmm, looks as though source.ext.schain only gets added if an schain was configured using pbjs.setBidderConfig and the bidder the schain was configured for is the same as what is set under defaultVendor configured in the s2sConfig..

logic appears to be something like that, but I am not 100% sure on what the expected logic is supposed to be here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

let's keep that out of scope, @dgirardi did you already open another issue for this?

/**
* @type {(string[]|string|undefined)} - OpenRTB property 'cur', currencies available for bids
*/
Expand Down
157 changes: 157 additions & 0 deletions test/spec/modules/prebidServerBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,163 @@ describe('S2S Adapter', function () {
});
});

it('should have extPrebid.schains present on req object if bidder specific schains were configured with pbjs', function () {
let bidRequest = utils.deepClone(BID_REQUESTS);
bidRequest[0].bids[0].schain = {
complete: 1,
nodes: [{
asi: 'test.com',
hp: 1,
sid: '11111'
}],
ver: '1.0'
};

adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax);
let requestBid = JSON.parse(server.requests[0].requestBody);

expect(requestBid.ext.prebid.schains).to.deep.equal([
{
bidders: ['appnexus'],
schain: {
complete: 1,
nodes: [
{
asi: 'test.com',
hp: 1,
sid: '11111'
}
],
ver: '1.0'
}
}
]);
});

it('should skip over adding any bid specific schain entries that already exist on extPrebid.schains', function () {
let bidRequest = utils.deepClone(BID_REQUESTS);
bidRequest[0].bids[0].schain = {
complete: 1,
nodes: [{
asi: 'pbjs.com',
hp: 1,
sid: '22222'
}],
ver: '1.0'
};

const s2sConfig = Object.assign({}, CONFIG, {
extPrebid: {
schains: [
{
bidders: ['appnexus'],
schain: {
complete: 1,
nodes: [
{
asi: 'pbs.com',
hp: 1,
sid: '11111'
}
],
ver: '1.0'
}
}
]
}
});

const s2sBidRequest = utils.deepClone(REQUEST);
s2sBidRequest.s2sConfig = s2sConfig;

adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax);

let requestBid = JSON.parse(server.requests[0].requestBody);
expect(requestBid.ext.prebid.schains).to.deep.equal([
{
bidders: ['appnexus'],
schain: {
complete: 1,
nodes: [
{
asi: 'pbs.com',
hp: 1,
sid: '11111'
}
],
ver: '1.0'
}
}
]);
});

it('should add a bidder name to pbs schain if the schain is equal to a pbjs one but the pbjs bidder name is not in the bidder array on the pbs side', function () {
let bidRequest = utils.deepClone(BID_REQUESTS);
bidRequest[0].bids[0].schain = {
complete: 1,
nodes: [{
asi: 'test.com',
hp: 1,
sid: '11111'
}],
ver: '1.0'
};

bidRequest[0].bids[1] = {
bidder: 'rubicon',
params: {
accountId: 14062,
siteId: 70608,
zoneId: 498816
}
};

const s2sConfig = Object.assign({}, CONFIG, {
bidders: ['rubicon', 'appnexus'],
extPrebid: {
schains: [
{
bidders: ['rubicon'],
schain: {
complete: 1,
nodes: [
{
asi: 'test.com',
hp: 1,
sid: '11111'
}
],
ver: '1.0'
}
}
]
}
});

const s2sBidRequest = utils.deepClone(REQUEST);
s2sBidRequest.s2sConfig = s2sConfig;

adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax);

let requestBid = JSON.parse(server.requests[0].requestBody);
expect(requestBid.ext.prebid.schains).to.deep.equal([
{
bidders: ['rubicon', 'appnexus'],
schain: {
complete: 1,
nodes: [
{
asi: 'test.com',
hp: 1,
sid: '11111'
}
],
ver: '1.0'
}
}
]);
});

it('passes schain object in request', function () {
const bidRequests = utils.deepClone(BID_REQUESTS);
const schainObject = {
Expand Down