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

Prebid 8: improve transmitTid logic for PBS adapter #10092

Merged
merged 1 commit into from
Jun 13, 2023
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
35 changes: 33 additions & 2 deletions modules/prebidServerBidAdapter/ortbConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.j
import {SUPPORTED_MEDIA_TYPES} from '../../libraries/pbsExtensions/processors/mediaType.js';
import {IMP, REQUEST, RESPONSE} from '../../src/pbjsORTB.js';
import {beConvertCurrency} from '../../src/utils/currency.js';
import {redactor} from '../../src/activities/redactor.js';
import {s2sActivityParams} from '../../src/adapterManager.js';
import {activityParams} from '../../src/activities/activityParams.js';
import {MODULE_TYPE_BIDDER} from '../../src/activities/modules.js';
import {isActivityAllowed} from '../../src/activities/rules.js';
import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js';

const DEFAULT_S2S_TTL = 60;
const DEFAULT_S2S_CURRENCY = 'USD';
Expand Down Expand Up @@ -68,6 +74,10 @@ const PBS_CONVERTER = ortbConverter({
deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions);
}

if (!context.transmitTids) {
deepSetValue(request, 'ext.prebid.createtids', false);
}

return request;
}
},
Expand Down Expand Up @@ -165,13 +175,17 @@ const PBS_CONVERTER = ortbConverter({
deepSetValue(ortbRequest, 'ext.prebid', mergeDeep(ortbRequest.ext?.prebid || {}, context.s2sBidRequest.s2sConfig.extPrebid));
}

// for global FPD, check allowed activities against "prebid.pbsBidAdapter"...
context.getRedactor().ortb2(ortbRequest);

const fpdConfigs = Object.entries(context.s2sBidRequest.ortb2Fragments?.bidder || {}).filter(([bidder]) => {
const bidders = context.s2sBidRequest.s2sConfig.bidders;
const allowUnknownBidderCodes = context.s2sBidRequest.s2sConfig.allowUnknownBidderCodes;
return allowUnknownBidderCodes || (bidders && bidders.includes(bidder));
}).map(([bidder, ortb2]) => ({
// ... but for bidder specific FPD we can use the actual bidder
bidders: [bidder],
config: {ortb2}
config: {ortb2: context.getRedactor(bidder).ortb2(ortb2)}
}));
if (fpdConfigs.length) {
deepSetValue(ortbRequest, 'ext.prebid.bidderconfig', fpdConfigs);
Expand Down Expand Up @@ -239,10 +253,25 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste
const requestTimestamp = timestamp();
const impIds = new Set();
const proxyBidRequests = [];
const s2sParams = s2sActivityParams(s2sBidRequest.s2sConfig);

const getRedactor = (() => {
const global = redactor(s2sParams);
const bidders = {};
return (bidder) => {
if (bidder == null) return global;
if (!bidders.hasOwnProperty(bidder)) {
bidders[bidder] = redactor(activityParams(MODULE_TYPE_BIDDER, bidder));
}
return bidders[bidder]
}
})();

adUnits = adUnits.map((au) => getRedactor().bidRequest(au))

adUnits.forEach(adUnit => {
const actualBidRequests = new Map();

adUnits.bids = adUnit.bids.map(br => getRedactor(br.bidder).bidRequest(br));
adUnit.bids.forEach((bid) => {
if (bid.mediaTypes != null) {
// TODO: support labels / conditional bids
Expand Down Expand Up @@ -284,6 +313,8 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste
actualBidderRequests: bidderRequests,
eidPermissions,
nativeRequest: s2sBidRequest.s2sConfig.ortbNative,
getRedactor,
transmitTids: isActivityAllowed(ACTIVITY_TRANSMIT_TID, s2sParams),
}
});
}
Expand Down
27 changes: 16 additions & 11 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from './activities/activ
import {ACTIVITY_PARAM_ANL_CONFIG, ACTIVITY_PARAM_S2S_NAME, activityParamsBuilder} from './activities/params.js';
import {redactor} from './activities/redactor.js';

const PBS_ADAPTER_NAME = 'pbsBidAdapter';
export const PBS_ADAPTER_NAME = 'pbsBidAdapter';
export const PARTITIONS = {
CLIENT: 'client',
SERVER: 'server'
Expand All @@ -68,6 +68,12 @@ var _analyticsRegistry = {};

const activityParams = activityParamsBuilder((alias) => adapterManager.resolveAlias(alias));

export function s2sActivityParams(s2sConfig) {
return activityParams(MODULE_TYPE_PREBID, PBS_ADAPTER_NAME, {
[ACTIVITY_PARAM_S2S_NAME]: s2sConfig.configName
});
}

/**
* @typedef {object} LabelDescriptor
* @property {boolean} labelAll describes whether or not this object expects all labels to match, or any label to match
Expand Down Expand Up @@ -271,8 +277,12 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
const ortb2 = ortb2Fragments.global || {};
const bidderOrtb2 = ortb2Fragments.bidder || {};

function addOrtb2(bidderRequest) {
const redact = dep.redact(activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode));
function addOrtb2(bidderRequest, s2sActivityParams) {
const redact = dep.redact(
s2sActivityParams != null
? s2sActivityParams
: activityParams(MODULE_TYPE_BIDDER, bidderRequest.bidderCode)
);
const fpd = Object.freeze(redact.ortb2(mergeDeep({source: {tid: auctionId}}, ortb2, bidderOrtb2[bidderRequest.bidderCode])));
bidderRequest.ortb2 = fpd;
bidderRequest.bids = bidderRequest.bids.map((bid) => {
Expand All @@ -282,14 +292,9 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
return bidderRequest;
}

function isS2SAllowed(s2sConfig) {
return dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_PREBID, PBS_ADAPTER_NAME, {
[ACTIVITY_PARAM_S2S_NAME]: s2sConfig.configName
}));
}

_s2sConfigs.forEach(s2sConfig => {
if (s2sConfig && s2sConfig.enabled && isS2SAllowed(s2sConfig)) {
const s2sParams = s2sActivityParams(s2sConfig);
if (s2sConfig && s2sConfig.enabled && dep.isAllowed(ACTIVITY_FETCH_BIDS, s2sParams)) {
let {adUnits: adUnitsS2SCopy, hasModuleBids} = getAdUnitCopyForPrebidServer(adUnits, s2sConfig);

// uniquePbsTid is so we know which server to send which bids to during the callBids function
Expand All @@ -309,7 +314,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a
src: CONSTANTS.S2S.SRC,
refererInfo,
metrics,
});
}, s2sParams);
if (bidderRequest.bids.length !== 0) {
bidRequests.push(bidderRequest);
}
Expand Down
143 changes: 115 additions & 28 deletions test/spec/modules/prebidServerBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'modules/consentManagement.js';
import 'modules/consentManagementUsp.js';
import 'modules/schain.js';
import 'modules/fledgeForGpt.js';
import * as redactor from 'src/activities/redactor.js';
import {hook} from '../../../src/hook.js';
import {decorateAdUnitsWithNativeParams} from '../../../src/native.js';
import {auctionManager} from '../../../src/auctionManager.js';
Expand Down Expand Up @@ -636,11 +637,101 @@ describe('S2S Adapter', function () {
resetSyncedStatus();
});

it('should pick source.tid from FPD', () => {
config.setConfig({ s2sConfig: CONFIG });
adapter.callBids({...REQUEST, ortb2Fragments: {global: {source: {tid: 'mock-tid'}}}}, BID_REQUESTS, addBidResponse, done, ajax);
const req = JSON.parse(server.requests[0].requestBody)
expect(req.source.tid).to.eql('mock-tid');
describe('FPD redaction', () => {
let sandbox, ortb2Fragments, redactorMocks, s2sReq;

beforeEach(() => {
sandbox = sinon.sandbox.create();
redactorMocks = {};
sandbox.stub(redactor, 'redactor').callsFake((params) => {
if (!redactorMocks.hasOwnProperty(params.component)) {
redactorMocks[params.component] = {
ortb2: sinon.stub().callsFake(o => o),
bidRequest: sinon.stub().callsFake(o => o)
}
}
return redactorMocks[params.component];
})
ortb2Fragments = {
global: {
mock: 'value'
},
bidder: {
appnexus: {
mock: 'A'
}
}
}
const s2sConfig = {
...CONFIG,
};
config.setConfig({s2sConfig});
s2sReq = {
...REQUEST,
s2sConfig
};
});

afterEach(() => {
sandbox.restore();
})

function callBids() {
adapter.callBids({
...s2sReq,
ortb2Fragments
}, BID_REQUESTS, addBidResponse, done, ajax);
}

it('should be applied to ortb2Fragments', () => {
callBids();
sinon.assert.calledWithMatch(redactorMocks['prebid.pbsBidAdapter'].ortb2, ortb2Fragments.global);
Object.entries(ortb2Fragments.bidder).forEach(([bidder, ortb2]) => {
sinon.assert.calledWith(redactorMocks[`bidder.${bidder}`].ortb2, ortb2);
});
});

it('should be applied to ad units', () => {
callBids();
s2sReq.ad_units.forEach(au => {
sinon.assert.calledWith(redactorMocks['prebid.pbsBidAdapter'].bidRequest, au);
au.bids.forEach((bid) => {
sinon.assert.calledWith(redactorMocks[`bidder.${bid.bidder}`].bidRequest, bid);
})
})
})
});

describe('transaction IDs', () => {
let s2sReq;
beforeEach(() => {
s2sReq = {
...REQUEST,
ortb2Fragments: {global: {source: {tid: 'mock-tid'}}},
ad_units: REQUEST.ad_units.map(au => ({...au, ortb2Imp: {ext: {tid: 'mock-tid'}}}))
};
BID_REQUESTS[0].bids[0].ortb2Imp = {ext: {tid: 'mock-tid'}};
});

function makeRequest() {
adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax);
return JSON.parse(server.requests[0].requestBody);
}

it('should not be set when transmitTid is not allowed, with ext.prebid.createtids: false', () => {
config.setConfig({ s2sConfig: CONFIG, enableTIDs: false });
const req = makeRequest();
expect(req.source.tid).to.not.exist;
expect(req.imp[0].ext.tid).to.not.exist;
expect(req.ext.prebid.createtids).to.equal(false);
});

it('should be picked from FPD otherwise', () => {
config.setConfig({s2sConfig: CONFIG, enableTIDs: true});
const req = makeRequest();
expect(req.source.tid).to.eql('mock-tid');
expect(req.imp[0].ext.tid).to.eql('mock-tid');
})
})

it('should set tmax to s2sConfig.timeout', () => {
Expand Down Expand Up @@ -1638,19 +1729,17 @@ describe('S2S Adapter', function () {

const requestBid = JSON.parse(server.requests[0].requestBody);

expect(requestBid.ext).to.deep.equal({
prebid: {
auctiontimestamp: 1510852447530,
targeting: {
includebidderkeys: false,
includewinners: true
},
channel: {
name: 'pbjs',
version: 'v$prebid.version$'
}
sinon.assert.match(requestBid.ext.prebid, {
auctiontimestamp: 1510852447530,
targeting: {
includebidderkeys: false,
includewinners: true
},
channel: {
name: 'pbjs',
version: 'v$prebid.version$'
}
});
})
});

it('skips dynamic aliases to request when skipPbsAliasing enabled', function () {
Expand All @@ -1677,17 +1766,15 @@ describe('S2S Adapter', function () {

const requestBid = JSON.parse(server.requests[0].requestBody);

expect(requestBid.ext).to.deep.equal({
prebid: {
auctiontimestamp: 1510852447530,
targeting: {
includebidderkeys: false,
includewinners: true
},
channel: {
name: 'pbjs',
version: 'v$prebid.version$'
}
sinon.assert.match(requestBid.ext.prebid, {
auctiontimestamp: 1510852447530,
targeting: {
includebidderkeys: false,
includewinners: true
},
channel: {
name: 'pbjs',
version: 'v$prebid.version$'
}
});
});
Expand Down