Skip to content

Commit

Permalink
Prebid 9: paapiForGpt: add support for customSlotMatching, remove `au…
Browse files Browse the repository at this point in the history
…toconfig` (#11714)

* paapiForGpt: support customSlotMatching

* paapiForGpt: replace autoconfig with configWithTargeting

* flip default to true for configWithTargeting
  • Loading branch information
dgirardi authored Jun 13, 2024
1 parent b6dded0 commit a254fba
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 180 deletions.
60 changes: 33 additions & 27 deletions modules/paapiForGpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,39 @@
*/
import {getHook, submodule} from '../src/hook.js';
import {deepAccess, logInfo, logWarn, sizeTupleToSizeString} from '../src/utils.js';
import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
import {config} from '../src/config.js';
import {getGlobal} from '../src/prebidGlobal.js';

import {keyCompare} from '../src/utils/reducers.js';
import {getGPTSlotsForAdUnits, targeting} from '../src/targeting.js';

const MODULE = 'paapiForGpt';

let getPAAPIConfig;

let autoconfig = false;

config.getConfig('paapi', (cfg) => {
autoconfig = deepAccess(cfg, 'paapi.gpt.autoconfig', false);
if (deepAccess(cfg, 'paapi.gpt.configWithTargeting', true)) {
logInfo(MODULE, 'enabling PAAPI configuration with setTargetingForGPTAsync')
targeting.setTargetingForGPT.before(setTargetingHook);
} else {
targeting.setTargetingForGPT.getHooks({hook: setTargetingHook}).remove();
}
});

export function setTargetingHookFactory(setPaapiConfig = getGlobal().setPAAPIConfigForGPT) {
return function(next, adUnit, customSlotMatching) {
const adUnitCodes = Array.isArray(adUnit) ? adUnit : [adUnit]
adUnitCodes
.map(adUnitCode => adUnitCode == null ? undefined : {adUnitCode})
.forEach(filters => setPaapiConfig(filters, customSlotMatching))
next(adUnit, customSlotMatching);
}
}

export function slotConfigurator() {
const PREVIOUSLY_SET = {};
return function setComponentAuction(adUnitCode, auctionConfigs, reset = true) {
const gptSlot = getGptSlotForAdUnitCode(adUnitCode);
if (gptSlot && gptSlot.setConfig) {
return function setComponentAuction(adUnitCode, gptSlots, auctionConfigs, reset = true) {
if (gptSlots.length > 0) {
let previous = PREVIOUSLY_SET[adUnitCode] ?? {};
let configsBySeller = Object.fromEntries(auctionConfigs.map(cfg => [cfg.seller, cfg]));
const sellers = Object.keys(configsBySeller);
Expand All @@ -38,8 +51,10 @@ export function slotConfigurator() {
const componentAuction = Object.entries(configsBySeller)
.map(([configKey, auctionConfig]) => ({configKey, auctionConfig}));
if (componentAuction.length > 0) {
gptSlot.setConfig({componentAuction});
logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs);
gptSlots.forEach(gptSlot => {
gptSlot.setConfig({componentAuction});
logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs);
});
}
} else if (auctionConfigs.length > 0) {
logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs);
Expand All @@ -49,17 +64,6 @@ export function slotConfigurator() {

const setComponentAuction = slotConfigurator();

export function onAuctionConfigFactory(setGptConfig = setComponentAuction) {
return function onAuctionConfig(auctionId, configsByAdUnit, markAsUsed) {
if (autoconfig) {
Object.entries(configsByAdUnit).forEach(([adUnitCode, cfg]) => {
setGptConfig(adUnitCode, cfg?.componentAuctions ?? []);
markAsUsed(adUnitCode);
});
}
}
}

export const getPAAPISizeHook = (() => {
/*
https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq
Expand Down Expand Up @@ -124,20 +128,22 @@ export const getPAAPISizeHook = (() => {

export function setPAAPIConfigFactory(
getConfig = (filters) => getPAAPIConfig(filters, true),
setGptConfig = setComponentAuction) {
setGptConfig = setComponentAuction,
getSlots = getGPTSlotsForAdUnits) {
/**
* Configure GPT slots with PAAPI auction configs.
* `filters` are the same filters accepted by `pbjs.getPAAPIConfig`;
*/
return function(filters = {}) {
return function(filters = {}, customSlotMatching) {
let some = false;
Object.entries(
getConfig(filters) || {}
).forEach(([au, config]) => {
const cfg = getConfig(filters) || {};
const auToSlots = getSlots(Object.keys(cfg), customSlotMatching);

Object.entries(cfg).forEach(([au, config]) => {
if (config != null) {
some = true;
}
setGptConfig(au, config?.componentAuctions || [], true);
setGptConfig(au, auToSlots[au], config?.componentAuctions || [], true);
})
if (!some) {
logInfo(`${MODULE}: No component auctions available to set`);
Expand All @@ -148,10 +154,10 @@ export function setPAAPIConfigFactory(
* Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`.
*/
getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory();
const setTargetingHook = setTargetingHookFactory();

submodule('paapi', {
name: 'gpt',
onAuctionConfig: onAuctionConfigFactory(),
init(params) {
getPAAPIConfig = params.getPAAPIConfig;
getHook('getPAAPISize').before(getPAAPISizeHook);
Expand Down
21 changes: 1 addition & 20 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,26 +423,7 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) {
logError('window.googletag is not defined on the page');
return;
}

// get our ad unit codes
let targetingSet = targeting.getAllTargeting(adUnit);

// first reset any old targeting
targeting.resetPresetTargeting(adUnit, customSlotMatching);

// now set new targeting keys
targeting.setTargetingForGPT(targetingSet, customSlotMatching);

Object.keys(targetingSet).forEach((adUnitCode) => {
Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => {
if (targetingKey === 'hb_adid') {
auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET);
}
});
});

// emit event
events.emit(SET_TARGETING, targetingSet);
targeting.setTargetingForGPT(adUnit, customSlotMatching);
};

/**
Expand Down
94 changes: 61 additions & 33 deletions src/targeting.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,18 @@ import {ADPOD} from './mediaTypes.js';
import {hook} from './hook.js';
import {bidderSettings} from './bidderSettings.js';
import {find, includes} from './polyfill.js';
import { BID_STATUS, JSON_MAPPING, DEFAULT_TARGETING_KEYS, TARGETING_KEYS, NATIVE_KEYS, STATUS } from './constants.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';

var pbTargetingKeys = [];

Expand Down Expand Up @@ -124,6 +133,22 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) {
}
}

/**
* Return a map where each code in `adUnitCodes` maps to a list of GPT slots that match it.
*
* @param {Array<String>} adUnitCodes
* @param customSlotMatching
* @param getSlots
* @return {{[p: string]: any}}
*/
export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) {
return getSlots().reduce((auToSlots, slot) => {
const customMatch = isFn(customSlotMatching) && customSlotMatching(slot);
Object.keys(auToSlots).filter(isFn(customMatch) ? customMatch : isAdUnitCodeMatchingSlot(slot)).forEach(au => auToSlots[au].push(slot));
return auToSlots;
}, Object.fromEntries(adUnitCodes.map(au => [au, []])));
}

/**
* @typedef {Object.<string,string>} targeting
* @property {string} targeting_key
Expand All @@ -144,22 +169,13 @@ export function newTargeting(auctionManager) {
targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) {
if (isGptPubadsDefined()) {
const adUnitCodes = getAdUnitCodes(adUnitCode);
const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code));
let unsetKeys = pbTargetingKeys.reduce((reducer, key) => {
reducer[key] = null;
return reducer;
}, {});
window.googletag.pubads().getSlots().forEach(slot => {
let customSlotMatchingFunc = isFn(customSlotMatching) && customSlotMatching(slot);
// reset only registered adunits
adUnits.forEach(unit => {
if (unit.code === slot.getAdUnitPath() ||
unit.code === slot.getSlotElementId() ||
(isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) {
slot.updateTargetingFromMap(unsetKeys);
}
});
});
Object.values(getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching)).forEach((slots) => {
slots.forEach(slot => slot.updateTargetingFromMap(unsetKeys))
})
}
};

Expand Down Expand Up @@ -415,27 +431,39 @@ export function newTargeting(auctionManager) {
return targetingObj;
}

/**
* Sets targeting for DFP
* @param {Object.<string,Object.<string,string>>} targetingConfig
*/
targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) {
window.googletag.pubads().getSlots().forEach(slot => {
Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot))
.forEach(targetId => {
Object.keys(targetingConfig[targetId]).forEach(key => {
let value = targetingConfig[targetId][key];
if (typeof value === 'string' && value.indexOf(',') !== -1) {
// due to the check the array will be formed only if string has ',' else plain string will be assigned as value
value = value.split(',');
}
targetingConfig[targetId][key] = value;
});
logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingConfig[targetId]);
slot.updateTargetingFromMap(targetingConfig[targetId])
})
targeting.setTargetingForGPT = hook('sync', function (adUnit, customSlotMatching) {
// get our ad unit codes
let targetingSet = targeting.getAllTargeting(adUnit);

let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null]));

Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => {
slots.forEach(slot => {
// now set new targeting keys
Object.keys(targetingSet[targetId]).forEach(key => {
let value = targetingSet[targetId][key];
if (typeof value === 'string' && value.indexOf(',') !== -1) {
// due to the check the array will be formed only if string has ',' else plain string will be assigned as value
value = value.split(',');
}
targetingSet[targetId][key] = value;
});
logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingSet[targetId]);
slot.updateTargetingFromMap(Object.assign({}, resetMap, targetingSet[targetId]))
})
})
};

Object.keys(targetingSet).forEach((adUnitCode) => {
Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => {
if (targetingKey === 'hb_adid') {
auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET);
}
});
});

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

/**
* normlizes input to a `adUnit.code` array
Expand Down
Loading

0 comments on commit a254fba

Please sign in to comment.