Skip to content

Commit

Permalink
always reset GPT slot component auctions on setTargeting
Browse files Browse the repository at this point in the history
  • Loading branch information
dgirardi committed Jun 11, 2024
1 parent 7c272d1 commit 23392bd
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 72 deletions.
75 changes: 40 additions & 35 deletions modules/paapiForGpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,30 @@ import {getGPTSlotsForAdUnits, targeting} from '../src/targeting.js';
const MODULE = 'paapiForGpt';

let getPAAPIConfig;
let expandFilters;
let configWithTargeting = false;

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

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 setTargetingHookFactory(getPAAPI = getPAAPIConfig, setAuctionConfigs = setComponentAuctions, expand = expandFilters) {
function getAuctionConfigs(adUnitCode) {
return getPAAPI(adUnitCode == null ? undefined : {adUnitCode}, true)
}
function getResetConfig(adUnitCode) {
return Object.fromEntries((adUnitCode == null ? Object.keys(expand()) : [adUnitCode]).map(au => [au, null]))
}
return function (next, adUnit, customSlotMatching) {
const adUnitCodes = Array.isArray(adUnit) ? adUnit : [adUnit];
const auctionConfigs = Object.assign({}, ...adUnitCodes.map(configWithTargeting ? getAuctionConfigs : getResetConfig))
setAuctionConfigs(auctionConfigs, customSlotMatching);
next(adUnit, customSlotMatching);
};
}

export function slotConfigurator() {
Expand Down Expand Up @@ -64,6 +70,23 @@ export function slotConfigurator() {

const setComponentAuction = slotConfigurator();

export function setComponentAuctionsFactory(getSlots = getGPTSlotsForAdUnits, setGptConfig = setComponentAuction) {
return function(auctionConfigs, customSlotMatching) {
auctionConfigs = auctionConfigs ?? {}
let some = false;
const auToSlots = getSlots(Object.keys(auctionConfigs), customSlotMatching);

Object.entries(auctionConfigs).forEach(([au, config]) => {
if (config != null) {
some = true;
}
setGptConfig(au, auToSlots[au], config?.componentAuctions || [], true);
})
return some;
}
}
const setComponentAuctions = setComponentAuctionsFactory();

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

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

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

submodule('paapi', {
name: 'gpt',
init(params) {
getPAAPIConfig = params.getPAAPIConfig;
expandFilters = params.expandFilters;
getHook('getPAAPISize').before(getPAAPISizeHook);
targeting.setTargetingForGPT.before(setTargetingHookFactory());
}
});
112 changes: 75 additions & 37 deletions test/spec/modules/paapiForGpt_spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import {
getPAAPISizeHook,
onAuctionConfigFactory,
setPAAPIConfigFactory, setTargetingHookFactory,
setComponentAuctionsFactory,
setTargetingHookFactory,
slotConfigurator
} from 'modules/paapiForGpt.js';
import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js';
import 'modules/appnexusBidAdapter.js';
import 'modules/rubiconBidAdapter.js';
import {deepSetValue} from '../../../src/utils.js';
import {config} from 'src/config.js';

describe('paapiForGpt module', () => {
Expand Down Expand Up @@ -115,59 +113,101 @@ describe('paapiForGpt module', () => {
})
});
describe('setTargeting hook', () => {
let setPaapiConfig, setTargetingHook, next;
let getPAAPIConfig, setAuctionConfigs, expand, setTargetingHook, next;
beforeEach(() => {
setPaapiConfig = sinon.stub()
setTargetingHook = setTargetingHookFactory(setPaapiConfig);
getPAAPIConfig = sinon.stub();
setAuctionConfigs = sinon.stub();
expand = sinon.stub();
setTargetingHook = setTargetingHookFactory(getPAAPIConfig, setAuctionConfigs, expand);
next = sinon.stub();
});
function expectFilters(...filters) {
expect(setPaapiConfig.args.length).to.eql(filters.length)
filters.forEach(filter => {
sinon.assert.calledWith(setPaapiConfig, filter, 'mock-matcher')
})
}
afterEach(() => {
config.resetConfig();
})
function runHook(adUnit) {
setTargetingHook(next, adUnit, 'mock-matcher');
sinon.assert.calledWith(next, adUnit, 'mock-matcher');
}
it('should invoke with no filters when adUnit is undef', () => {
runHook();
expectFilters(undefined);
describe('when configWithTargeting = true', () => {
beforeEach(() => {
getPAAPIConfig.callsFake(({adUnitCode = 'none'} = {}) => ({[adUnitCode]: adUnitCode}))
config.setConfig({paapi: {gpt: {configWithTargeting: true}}})
});
function expectFilters(...filters) {
expect(getPAAPIConfig.args.length).to.eql(filters.length)
sinon.assert.calledWith(
setAuctionConfigs,
Object.fromEntries(filters.map(f => f == null ? 'none' : f.adUnitCode).map(au => [au, au])),
'mock-matcher'
);
filters.forEach(filter => {
sinon.assert.calledWith(getPAAPIConfig, filter, true)
})
}
it('should invoke with no filters when adUnit is undef', () => {
runHook();
expectFilters(undefined);
});
it('should invoke once when adUnit is a string', () => {
runHook('mock-au');
expectFilters({adUnitCode: 'mock-au'})
});
it('should invoke once per ad unit when an array', () => {
runHook(['au1', 'au2']);
expectFilters({adUnitCode: 'au1'}, {adUnitCode: 'au2'});
})
});
it('should invoke once when adUnit is a string', () => {
runHook('mock-au');
expectFilters({adUnitCode: 'mock-au'})
Object.entries({
'false': () => { config.setConfig({paapi: {gpt: {configWithTargeting: false}}})},
'default': () => {}
}).forEach(([t, setup]) => {
describe(`when configWithTargeting = ${t}`, () => {
setup && beforeEach(setup);
[
[
'mock-ad-unit',
{'mock-ad-unit': null}
],
[
['au1', 'au2'],
{'au1': null, 'au2': null}
]
].forEach(([adUnit, expected]) => {
it(`should reset with ${expected} when adUnit = ${adUnit}`, () => {
runHook(adUnit);
sinon.assert.calledWith(setAuctionConfigs, expected, 'mock-matcher');
sinon.assert.notCalled(expand);
});
});
it('should reset all au units when adUnit = undef', () => {
expand.returns({'au1': 'auct', 'au2': null});
runHook();
sinon.assert.calledWith(expand);
sinon.assert.calledWith(setAuctionConfigs, {'au1': null, 'au2': null}, 'mock-matcher');
});
});
});
it('should invoke once per ad unit when an array', () => {
runHook(['au1', 'au2']);
expectFilters({adUnitCode: 'au1'}, {adUnitCode: 'au2'});
})
})
describe('setPAAPIConfigForGpt', () => {
let getPAAPIConfig, setGptConfig, getSlots, setPAAPIConfigForGPT;
});
describe('setComponentAuctions', () => {
let setGptConfig, getSlots, setComponentAuctions;
beforeEach(() => {
getPAAPIConfig = sinon.stub();
setGptConfig = sinon.stub();
getSlots = sinon.stub().callsFake((codes) => Object.fromEntries(codes.map(code => [code, ['mock-slot']])))
setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig, getSlots);
setComponentAuctions = setComponentAuctionsFactory(getSlots, setGptConfig);
});

Object.entries({
missing: null,
empty: {}
}).forEach(([t, configs]) => {
}).forEach(([t, paapiConfig]) => {
it(`does not set GPT slot config when config is ${t}`, () => {
getPAAPIConfig.returns(configs);
setPAAPIConfigForGPT('mock-filters');
sinon.assert.calledWith(getPAAPIConfig, 'mock-filters');
expect(setComponentAuctions(paapiConfig)).to.be.false;
sinon.assert.notCalled(setGptConfig);
})
});

it('passes customSlotMatching to getSlots', () => {
getPAAPIConfig.returns({au1: {}});
setPAAPIConfigForGPT('mock-filters', 'mock-custom-matching');
expect(setComponentAuctions({au1: null}, 'mock-custom-matching')).to.be.false;
sinon.assert.calledWith(getSlots, ['au1'], 'mock-custom-matching');
})

Expand All @@ -181,9 +221,7 @@ describe('paapiForGpt module', () => {
},
au3: null
}
getPAAPIConfig.returns(cfg);
setPAAPIConfigForGPT('mock-filters');
sinon.assert.calledWith(getPAAPIConfig, 'mock-filters');
expect(setComponentAuctions(cfg)).to.be.true;
Object.entries(cfg).forEach(([au, config]) => {
sinon.assert.calledWith(setGptConfig, au, ['mock-slot'], config?.componentAuctions ?? [], true);
})
Expand Down

0 comments on commit 23392bd

Please sign in to comment.