Skip to content

Commit

Permalink
gptPreAuction: pass publisher provided signals to GPT (#11946)
Browse files Browse the repository at this point in the history
* 10997 set pps to gam display

* update

* update

* review changes

* module handling

* code sharing

* linting fixes

* Rename setPPSConfig

* Filter out adIds that have no auction

* use eql instead of JSON for deep equals

---------

Co-authored-by: Marcin Komorski <marcinkomorski@Marcins-MacBook-Pro.local>
Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
  • Loading branch information
3 people committed Jul 11, 2024
1 parent efbf6ad commit 1bd87b7
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 65 deletions.
7 changes: 7 additions & 0 deletions libraries/dfpUtils/dfpUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ export const DFP_ENDPOINT = {
host: 'securepubads.g.doubleclick.net',
pathname: '/gampad/ads'
}

export const setGdprConsent = (gdprConsent, queryParams) => {
if (!gdprConsent) { return; }
if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); }
if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; }
if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; }
}
24 changes: 23 additions & 1 deletion libraries/gptUtils/gptUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CLIENT_SECTIONS } from '../../src/fpd/oneClient.js';
import {find} from '../../src/polyfill.js';
import {compareCodeAndSlot, isGptPubadsDefined} from '../../src/utils.js';
import {compareCodeAndSlot, deepAccess, isGptPubadsDefined, uniques} from '../../src/utils.js';

/**
* Returns filter function to match adUnitCode in slot
Expand Down Expand Up @@ -35,3 +36,24 @@ export function getGptSlotInfoForAdUnitCode(adUnitCode) {
}
return {};
}

export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2'];

export function getSignals(fpd) {
const signals = Object.entries({
[taxonomies[0]]: getSegments(fpd, ['user.data'], 4),
[taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6)
}).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null)
.filter(ob => ob);

return signals;
}

export function getSegments(fpd, sections, segtax) {
return sections
.flatMap(section => deepAccess(fpd, section) || [])
.filter(datum => datum.ext?.segtax === segtax)
.flatMap(datum => datum.segment?.map(seg => seg.id))
.filter(ob => ob)
.filter(uniques)
}
48 changes: 15 additions & 33 deletions modules/dfpAdServerVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@
* This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid.
*/

import {registerVideoSupport} from '../src/adServerManager.js';
import {targeting} from '../src/targeting.js';
import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent } from '../libraries/dfpUtils/dfpUtils.js';
import { getSignals } from '../libraries/gptUtils/gptUtils.js';
import { registerVideoSupport } from '../src/adServerManager.js';
import { gdprDataHandler } from '../src/adapterManager.js';
import { getPPID } from '../src/adserver.js';
import { auctionManager } from '../src/auctionManager.js';
import { config } from '../src/config.js';
import { EVENTS } from '../src/constants.js';
import * as events from '../src/events.js';
import { getHook } from '../src/hook.js';
import { getRefererInfo } from '../src/refererDetection.js';
import { targeting } from '../src/targeting.js';
import {
buildUrl,
deepAccess,
Expand All @@ -12,19 +22,8 @@ import {
isNumber,
logError,
parseSizesInput,
parseUrl,
uniques
parseUrl
} from '../src/utils.js';
import {config} from '../src/config.js';
import {getHook} from '../src/hook.js';
import {auctionManager} from '../src/auctionManager.js';
import {gdprDataHandler} from '../src/adapterManager.js';
import * as events from '../src/events.js';
import {EVENTS} from '../src/constants.js';
import {getPPID} from '../src/adserver.js';
import {getRefererInfo} from '../src/refererDetection.js';
import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js';
import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js';
/**
* @typedef {Object} DfpVideoParams
*
Expand Down Expand Up @@ -115,11 +114,7 @@ export function buildDfpVideoUrl(options) {
const descriptionUrl = getDescriptionUrl(bid, options, 'params');
if (descriptionUrl) { queryParams.description_url = descriptionUrl; }
const gdprConsent = gdprDataHandler.getConsentData();
if (gdprConsent) {
if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); }
if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; }
if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; }
}
setGdprConsent(gdprConsent, queryParams);

if (!queryParams.ppid) {
const ppid = getPPID();
Expand Down Expand Up @@ -171,20 +166,7 @@ export function buildDfpVideoUrl(options) {
const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ??
auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global;

function getSegments(sections, segtax) {
return sections
.flatMap(section => deepAccess(fpd, section) || [])
.filter(datum => datum.ext?.segtax === segtax)
.flatMap(datum => datum.segment?.map(seg => seg.id))
.filter(ob => ob)
.filter(uniques)
}

const signals = Object.entries({
IAB_AUDIENCE_1_1: getSegments(['user.data'], 4),
IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6)
}).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null)
.filter(ob => ob);
const signals = getSignals(fpd);

if (signals.length) {
queryParams.ppsj = btoa(JSON.stringify({
Expand Down
8 changes: 2 additions & 6 deletions modules/dfpAdpod.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {submodule} from '../src/hook.js';
import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js';
import {auctionManager} from '../src/auctionManager.js';
import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js';
import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent} from '../libraries/dfpUtils/dfpUtils.js';
import {gdprDataHandler} from '../src/consentHandler.js';
import {registerVideoSupport} from '../src/adServerManager.js';

Expand Down Expand Up @@ -79,11 +79,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) {
);

const gdprConsent = gdprDataHandler.getConsentData();
if (gdprConsent) {
if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); }
if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; }
if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; }
}
setGdprConsent(gdprConsent, queryParams);

const masterTag = buildUrl({
...DFP_ENDPOINT,
Expand Down
67 changes: 63 additions & 4 deletions modules/gptPreAuction.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,68 @@
import { getSignals as getSignalsFn, getSegments as getSegmentsFn, taxonomies } from '../libraries/gptUtils/gptUtils.js';
import { auctionManager } from '../src/auctionManager.js';
import { config } from '../src/config.js';
import { TARGETING_KEYS } from '../src/constants.js';
import { getHook } from '../src/hook.js';
import { find } from '../src/polyfill.js';
import {
deepAccess,
deepSetValue,
isAdUnitCodeMatchingSlot,
isGptPubadsDefined,
logInfo,
logWarn,
pick,
deepSetValue, logWarn
uniques
} from '../src/utils.js';
import {config} from '../src/config.js';
import {getHook} from '../src/hook.js';
import {find} from '../src/polyfill.js';

const MODULE_NAME = 'GPT Pre-Auction';
export let _currentConfig = {};
let hooksAdded = false;

export function getSegments(fpd, sections, segtax) {
return getSegmentsFn(fpd, sections, segtax);
}

export function getSignals(fpd) {
return getSignalsFn(fpd);
}

export function getSignalsArrayByAuctionsIds(auctionIds, index = auctionManager.index) {
const signals = auctionIds
.map(auctionId => index.getAuction({ auctionId })?.getFPD()?.global)
.map(getSignals)
.filter(fpd => fpd);

return signals;
}

export function getSignalsIntersection(signals) {
const result = {};
taxonomies.forEach((taxonomy) => {
const allValues = signals
.flatMap(x => x)
.filter(x => x.taxonomy === taxonomy)
.map(x => x.values);
result[taxonomy] = allValues.length ? (
allValues.reduce((commonElements, subArray) => {
return commonElements.filter(element => subArray.includes(element));
})
) : []
result[taxonomy] = { values: result[taxonomy] };
})
return result;
}

export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) {
return Object.values(targeting)
.flatMap(x => Object.entries(x))
.filter((entry) => entry[0] === TARGETING_KEYS.AD_ID || entry[0].startsWith(TARGETING_KEYS.AD_ID + '_'))
.flatMap(entry => entry[1])
.map(adId => am.findBidByAdId(adId)?.auctionId)
.filter(id => id != null)
.filter(uniques);
}

export const appendGptSlots = adUnits => {
const { customGptSlotMatching } = _currentConfig;

Expand Down Expand Up @@ -153,6 +202,14 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => {
return fn.call(this, adUnits, ...args);
};

const setPpsConfigFromTargetingSet = (next, targetingSet) => {
// set gpt config
const auctionsIds = getAuctionsIdsFromTargeting(targetingSet);
const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds));
window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }});
next(targetingSet);
};

const handleSetGptConfig = moduleConfig => {
_currentConfig = pick(moduleConfig, [
'enabled', enabled => enabled !== false,
Expand All @@ -166,12 +223,14 @@ const handleSetGptConfig = moduleConfig => {
if (_currentConfig.enabled) {
if (!hooksAdded) {
getHook('makeBidRequests').before(makeBidRequestsHook);
getHook('targetingDone').after(setPpsConfigFromTargetingSet)
hooksAdded = true;
}
} else {
logInfo(`${MODULE_NAME}: Turning off module`);
_currentConfig = {};
getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove();
getHook('targetingDone').getHooks({hook: setPpsConfigFromTargetingSet}).remove();
hooksAdded = false;
}
};
Expand Down
46 changes: 26 additions & 20 deletions src/targeting.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import { auctionManager } from './auctionManager.js';
import { getTTL } from './bidTTL.js';
import { bidderSettings } from './bidderSettings.js';
import { config } from './config.js';
import {
BID_STATUS,
DEFAULT_TARGETING_KEYS,
EVENTS,
JSON_MAPPING,
NATIVE_KEYS,
STATUS,
TARGETING_KEYS
} from './constants.js';
import * as events from './events.js';
import { hook } from './hook.js';
import { ADPOD } from './mediaTypes.js';
import { NATIVE_TARGETING_KEYS } from './native.js';
import { find, includes } from './polyfill.js';
import {
deepAccess,
deepClone,
Expand All @@ -14,25 +32,7 @@ import {
timestamp,
uniques,
} from './utils.js';
import {config} from './config.js';
import {NATIVE_TARGETING_KEYS} from './native.js';
import {auctionManager} from './auctionManager.js';
import {ADPOD} from './mediaTypes.js';
import {hook} from './hook.js';
import {bidderSettings} from './bidderSettings.js';
import {find, includes} from './polyfill.js';
import {
BID_STATUS,
DEFAULT_TARGETING_KEYS,
EVENTS,
JSON_MAPPING,
NATIVE_KEYS,
STATUS,
TARGETING_KEYS
} from './constants.js';
import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js';
import {getTTL} from './bidTTL.js';
import * as events from './events.js';
import { getHighestCpm, getOldestHighestCpmBid } from './utils/reducers.js';

var pbTargetingKeys = [];

Expand Down Expand Up @@ -139,7 +139,7 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) {
* @param {Array<String>} adUnitCodes
* @param customSlotMatching
* @param getSlots
* @return {{[p: string]: any}}
* @return {Object.<string,any>}
*/
export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) {
return getSlots().reduce((auToSlots, slot) => {
Expand Down Expand Up @@ -461,10 +461,16 @@ export function newTargeting(auctionManager) {
});
});

targeting.targetingDone(targetingSet);

// emit event
events.emit(EVENTS.SET_TARGETING, targetingSet);
}, 'setTargetingForGPT');

targeting.targetingDone = hook('sync', function (targetingSet) {
return targetingSet;
}, 'targetingDone');

/**
* normlizes input to a `adUnit.code` array
* @param {(string|string[])} adUnitCode [description]
Expand Down
Loading

0 comments on commit 1bd87b7

Please sign in to comment.