From 6822cee66383b1bc8de2c77e22adc564c386b468 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Wed, 15 Feb 2023 17:10:30 +0000 Subject: [PATCH 1/8] feat(permutiveRtd): migrate rubicon targeting to ortb2 --- modules/permutiveRtdProvider.js | 56 +++--- .../spec/modules/permutiveRtdProvider_spec.js | 170 +++++++++--------- 2 files changed, 120 insertions(+), 106 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 305e175bf2c..6cfa6bff1ce 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,12 +8,19 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, logMessage} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' +const PREFIX = MODULE_NAME + 'RTD' + +const logger = { + message: (...a) => logMessage(PREFIX, ...a), +} export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' +export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' +export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) @@ -109,7 +116,7 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders - * @param {Object} bidderOrtb2 + * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders * @param {Object} customModuleConfig - Publisher config for module */ export function setBidderRtb (bidderOrtb2, customModuleConfig) { @@ -126,28 +133,35 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { bidders.forEach(function (bidder) { const currConfig = { ortb2: bidderOrtb2[bidder] || {} } + let cohorts = [] + const isAcBidder = acBidders.indexOf(bidder) > -1 - const isSspBidder = ssps.indexOf(bidder) > -1 + if (isAcBidder) { + cohorts = segmentData.ac + } - let cohorts = [] - if (isAcBidder) cohorts = segmentData.ac - if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + const isSspBidder = ssps.indexOf(bidder) > -1 + if (isSspBidder) { + cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + } - const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) - bidderOrtb2[bidder] = nextConfig.ortb2; + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) + bidderOrtb2[bidder] = nextConfig.ortb2 }) } /** * Updates `user.data` object in existing bidder config with Permutive segments + * @param string bidder - The bidder * @param {Object} currConfig - Current bidder config * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs + * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ -function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { const name = 'permutive.com' const permutiveUserData = { @@ -174,6 +188,19 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + // Set bidder specific extensions + if (bidder === 'rubicon') { + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_STANDARD_KEYWORD, segmentIDs) + } + + if (segmentData?.rubicon?.length > 0) { + deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, segmentData.rubicon.map(String)) + } + + logger.message(`Extend rubicon 'ortb2.user.ext.data'`, deepAccess(ortbConfig, 'ortb2.user.ext.data')) + } + return ortbConfig } @@ -262,17 +289,6 @@ function getDefaultBidderFn (bidder) { return bid }, - rubicon: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.visitor.p_standard', segments) - } - if (data.rubicon && data.rubicon.length) { - deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) - } - - return bid - }, ozone: function (bid, data, acEnabled) { if (isPStandardTargetingEnabled(data, acEnabled)) { const segments = pStandardTargeting(data, acEnabled) diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..cba79f2c287 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -370,6 +370,90 @@ describe('permutiveRtdProvider', function () { segment: expectedOtherTargetingData }]) }) + + describe('ortb2 rubicon extensions', function () { + it('should add standard and custom cohorts', function () { + const moduleConfig = getConfig() + expect(moduleConfig.params.acBidders).to.include('rubicon') + + const bidderConfig = {} + + const targetingData = transformedTargeting() + + setBidderRtb(bidderConfig, moduleConfig) + + moduleConfig.params.acBidders.forEach(bidder => { + if (bidder === 'rubicon') { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ + // Default targeting + p_standard: targetingData.ac, + // Custom cohort targetings + permutive: targetingData.rubicon, + }) + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } + }) + }) + + it('should add standard cohorts ONLY', function () { + const moduleConfig = getConfig() + expect(moduleConfig.params.acBidders).to.include('rubicon') + + const bidderConfig = {} + + const targetingData = transformedTargeting() + // Remove rubicon custom cohorts + storage.removeDataFromLocalStorage('_prubicons') + + setBidderRtb(bidderConfig, moduleConfig) + + moduleConfig.params.acBidders.forEach(bidder => { + if (bidder === 'rubicon') { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ + // Default targeting + p_standard: targetingData.ac, + }) + expect(bidderConfig[bidder].user.ext.data).to.not.have + .property('permutive') + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } + }) + }) + + it('should add custom cohorts ONLY', function () { + const moduleConfig = getConfig() + expect(moduleConfig.params.acBidders).to.include('rubicon') + + const bidderConfig = {} + + const targetingData = transformedTargeting() + // Remove standard cohorts (e.g targetingData.ac) + storage.removeDataFromLocalStorage('_psegs') + storage.removeDataFromLocalStorage('_ppam') + storage.removeDataFromLocalStorage('_pcrprs') + storage.removeDataFromLocalStorage('_pssps') + + setBidderRtb(bidderConfig, moduleConfig) + + moduleConfig.params.acBidders.forEach(bidder => { + if (bidder === 'rubicon') { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ + // custom cohort targeting + permutive: targetingData.rubicon, + }) + expect(bidderConfig[bidder].user.ext.data).to.not.have + .property('p_standard') + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } + }) + }) + }) }) describe('Getting segments', function () { @@ -413,58 +497,6 @@ describe('permutiveRtdProvider', function () { } }) - it('sets segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) - - it('sets segment targeting for Magnite video', function () { - const targetingData = getTargetingData() - targetingData._prubicons.push(321) - - setLocalStorage(targetingData) - - const data = transformedTargeting(targetingData) - const config = getConfig() - - const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) - expect(adUnits).to.have.lengthOf(1) - - initSegments({ adUnits }, callback, config) - - function callback() { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect( - deepAccess(params, 'visitor.permutive'), - 'Should map all targeting values to a string', - ).to.eql(data.rubicon.map(String)) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) - it('sets segment targeting for Ozone', function () { const data = transformedTargeting() const adUnits = getAdUnits() @@ -486,40 +518,6 @@ describe('permutiveRtdProvider', function () { }) }) - describe('Custom segment targeting', function () { - it('sets custom segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - config.params.overwrites = { - rubicon: function (bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } - } - } - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) - }) - describe('Existing key-value targeting', function () { it('doesn\'t overwrite existing key-values for Xandr', function () { const adUnits = getAdUnits() From 177c2c01833635a166cb165e59836bdddf15866c Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 12:32:06 +0000 Subject: [PATCH 2/8] perf(permutiveRtd): prevent redundant cohort reads and updates --- modules/permutiveRtdProvider.js | 86 +++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 6cfa6bff1ce..3c2301f01ca 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,15 +8,13 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, logMessage} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' const PREFIX = MODULE_NAME + 'RTD' -const logger = { - message: (...a) => logMessage(PREFIX, ...a), -} +const logger = prefixLog(PREFIX) export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' @@ -31,30 +29,6 @@ function init(moduleConfig, userConsent) { return true } -/** - * Set segment targeting from cache and then try to wait for Permutive - * to initialise to get realtime segment targeting - * @param {Object} reqBidsConfigObj - * @param {function} callback - Called when submodule is done - * @param {customModuleConfig} reqBidsConfigObj - Publisher config for module - */ -export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { - const permutiveOnPage = isPermutiveOnPage() - const moduleConfig = getModuleConfig(customModuleConfig) - const segmentData = getSegments(moduleConfig.params.maxSegs) - - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - - if (moduleConfig.waitForIt && permutiveOnPage) { - window.permutive.ready(function () { - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - callback() - }, 'realtime') - } else { - callback() - } -} - function liftIntoParams(params) { return isPlainObject(params) ? { params } : {} } @@ -119,12 +93,10 @@ export function getModuleConfig(customModuleConfig) { * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders * @param {Object} customModuleConfig - Publisher config for module */ -export function setBidderRtb (bidderOrtb2, customModuleConfig) { - const moduleConfig = getModuleConfig(customModuleConfig) +export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] - const segmentData = getSegments(maxSegs) const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] @@ -399,17 +371,57 @@ function iabSegmentId(permutiveSegmentId, iabIds) { return iabIds[permutiveSegmentId] || unknownIabSegmentId } +/** + * Pull the latest configuration and cohort information and update accordingly. + * + * @param reqBidsConfigObj - Bidder provided config for request + * @param customModuleConfig - Publisher provide config + */ +function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { + const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) + + makeSafe(function () { + // Legacy route with custom parameters + // ACK policy violation, in process of removing + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + }); + + makeSafe(function () { + // Route for bidders supporting ORTB2 + setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, moduleConfig, segmentData) + }) +} + +let permutiveReadyCallbackRegistered = false + /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + const completeBidRequestData = () => { + logger.logInfo(`Request data updated`) + callback() + } + + const moduleConfig = getModuleConfig(customModuleConfig) + + readAndSetCohorts(reqBidsConfigObj, moduleConfig) + makeSafe(function () { - // Legacy route with custom parameters - initSegments(reqBidsConfigObj, callback, customModuleConfig) - }); - makeSafe(function () { - // Route for bidders supporting ORTB2 - setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) + if (!permutiveReadyCallbackRegistered && moduleConfig.waitForIt && isPermutiveOnPage()) { + // Prevent multiple calls to set cohorts + permutiveReadyCallbackRegistered = true + + window.permutive.ready(function () { + logger.logInfo(`SDK is realtime, updating cohorts`) + readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) + completeBidRequestData() + }, 'realtime') + + logger.logInfo(`Registered cohort update when SDK is realtime`) + } else { + completeBidRequestData() + } }) }, init: init From 87f0170a5028d6596f2ee853027a3de783eb4ec0 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 13:02:36 +0000 Subject: [PATCH 3/8] fix(permutiveRtd): enable debugger logs for ortb2 updates --- modules/permutiveRtdProvider.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 3c2301f01ca..703870e5a94 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -12,9 +12,8 @@ import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safe import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' -const PREFIX = MODULE_NAME + 'RTD' -const logger = prefixLog(PREFIX) +const logger = prefixLog('[PermutiveRTD]') export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' @@ -170,9 +169,11 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, segmentData.rubicon.map(String)) } - logger.message(`Extend rubicon 'ortb2.user.ext.data'`, deepAccess(ortbConfig, 'ortb2.user.ext.data')) + logger.logInfo(`Extending ortb2.user.ext.data for ${bidder}`, deepAccess(ortbConfig, 'ortb2.user.ext.data')) } + logger.logInfo(`Updating ortb2 config for ${bidder}`, ortbConfig) + return ortbConfig } From 5880b11df83b4310833d6ccd40312dc71f3a5af4 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 14:46:28 +0000 Subject: [PATCH 4/8] fix(permutiveRtd): provide identity bidder fn fallback --- modules/permutiveRtdProvider.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 703870e5a94..1c463931308 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -272,7 +272,12 @@ function getDefaultBidderFn (bidder) { } } - return bidderMap[bidder] + // On no default bidder just return the same bid as passed in + function bidIdentity(bid) { + return bid + } + + return bidderMap[bidder] || bidIdentity } /** From f65b3ac7b3ff3dba11727e9ec105b923fc2b165d Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 15:54:10 +0000 Subject: [PATCH 5/8] test(permutiveRtd): update params to follow refactor --- modules/permutiveRtdProvider.js | 2 +- .../spec/modules/permutiveRtdProvider_spec.js | 175 ++++++++---------- 2 files changed, 83 insertions(+), 94 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 1c463931308..813afa28688 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -383,7 +383,7 @@ function iabSegmentId(permutiveSegmentId, iabIds) { * @param reqBidsConfigObj - Bidder provided config for request * @param customModuleConfig - Publisher provide config */ -function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { +export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) makeSafe(function () { diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index cba79f2c287..c6d33304f03 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -2,12 +2,12 @@ import { permutiveSubmodule, storage, getSegments, - initSegments, isAcEnabled, isPermutiveOnPage, setBidderRtb, getModuleConfig, PERMUTIVE_SUBMODULE_CONFIG_KEY, + readAndSetCohorts, } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' @@ -188,11 +188,12 @@ describe('permutiveRtdProvider', function () { const moduleConfig = getConfig() const bidderConfig = {}; const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].user.data).to.deep.include.members([{ @@ -205,7 +206,8 @@ describe('permutiveRtdProvider', function () { const moduleConfig = getConfig() const bidderConfig = {} const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) @@ -225,7 +227,7 @@ describe('permutiveRtdProvider', function () { } ) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].user.data).to.deep.include.members([ @@ -244,6 +246,8 @@ describe('permutiveRtdProvider', function () { it('should not overwrite ortb2 config', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -267,10 +271,7 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) @@ -280,6 +281,8 @@ describe('permutiveRtdProvider', function () { it('should update user.keywords and not override existing values', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -304,10 +307,7 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) @@ -316,7 +316,8 @@ describe('permutiveRtdProvider', function () { }) }) it('should merge ortb2 correctly for ac and ssps', function () { - setLocalStorage({ + const customTargetingData = { + ...getTargetingData(), '_ppam': [], '_psegs': [], '_pcrprs': ['abc', 'def', 'xyz'], @@ -324,7 +325,10 @@ describe('permutiveRtdProvider', function () { ssps: ['foo', 'bar'], cohorts: ['xyz', 'uvw'], } - }) + } + const segmentsData = transformedTargeting(customTargetingData) + setLocalStorage(customTargetingData) + const moduleConfig = { name: 'permutive', waitForIt: true, @@ -335,7 +339,7 @@ describe('permutiveRtdProvider', function () { } const bidderConfig = {}; - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) // include both ac and ssp cohorts, as foo is both in ac bidders and ssps const expectedFooTargetingData = [ @@ -378,18 +382,18 @@ describe('permutiveRtdProvider', function () { const bidderConfig = {} - const targetingData = transformedTargeting() + const segmentsData = transformedTargeting() - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) moduleConfig.params.acBidders.forEach(bidder => { if (bidder === 'rubicon') { expect(bidderConfig[bidder].user.ext.data).to.deep .eq({ // Default targeting - p_standard: targetingData.ac, + p_standard: segmentsData.ac, // Custom cohort targetings - permutive: targetingData.rubicon, + permutive: segmentsData.rubicon, }) } else { expect(bidderConfig[bidder].user).to.not.have.property('ext') @@ -403,18 +407,18 @@ describe('permutiveRtdProvider', function () { const bidderConfig = {} - const targetingData = transformedTargeting() + const segmentsData = transformedTargeting() // Remove rubicon custom cohorts - storage.removeDataFromLocalStorage('_prubicons') + delete segmentsData['rubicon'] - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) moduleConfig.params.acBidders.forEach(bidder => { if (bidder === 'rubicon') { expect(bidderConfig[bidder].user.ext.data).to.deep .eq({ // Default targeting - p_standard: targetingData.ac, + p_standard: segmentsData.ac, }) expect(bidderConfig[bidder].user.ext.data).to.not.have .property('permutive') @@ -430,21 +434,18 @@ describe('permutiveRtdProvider', function () { const bidderConfig = {} - const targetingData = transformedTargeting() - // Remove standard cohorts (e.g targetingData.ac) - storage.removeDataFromLocalStorage('_psegs') - storage.removeDataFromLocalStorage('_ppam') - storage.removeDataFromLocalStorage('_pcrprs') - storage.removeDataFromLocalStorage('_pssps') + const segmentsData = transformedTargeting() + // Empty the AC cohorts + segmentsData['ac'] = [] - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) moduleConfig.params.acBidders.forEach(bidder => { if (bidder === 'rubicon') { expect(bidderConfig[bidder].user.ext.data).to.deep .eq({ // custom cohort targeting - permutive: targetingData.rubicon, + permutive: segmentsData.rubicon, }) expect(bidderConfig[bidder].user.ext.data).to.not.have .property('p_standard') @@ -481,20 +482,18 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } }) - } + }) }) it('sets segment targeting for Ozone', function () { @@ -502,19 +501,17 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } }) - } + }) }) }) @@ -523,73 +520,65 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Magnite', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Ozone', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for TrustX', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'trustx') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) }) From a9c609f2fe1fc64fa987d1337b741a1907629d1f Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 16:27:31 +0000 Subject: [PATCH 6/8] fix(permutiveRtd): prevent multiple targeting updates once in realtime --- modules/permutiveRtdProvider.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 813afa28688..aad0faa8c36 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -398,7 +398,7 @@ export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { }) } -let permutiveReadyCallbackRegistered = false +let permutiveSDKInRealTime = false /** @type {RtdSubmodule} */ export const permutiveSubmodule = { @@ -414,20 +414,18 @@ export const permutiveSubmodule = { readAndSetCohorts(reqBidsConfigObj, moduleConfig) makeSafe(function () { - if (!permutiveReadyCallbackRegistered && moduleConfig.waitForIt && isPermutiveOnPage()) { - // Prevent multiple calls to set cohorts - permutiveReadyCallbackRegistered = true - - window.permutive.ready(function () { - logger.logInfo(`SDK is realtime, updating cohorts`) - readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) - completeBidRequestData() - }, 'realtime') - - logger.logInfo(`Registered cohort update when SDK is realtime`) - } else { - completeBidRequestData() + if (permutiveSDKInRealTime || !moduleConfig.waitForIt || !isPermutiveOnPage()) { + return completeBidRequestData() } + + window.permutive.ready(function () { + logger.logInfo(`SDK is realtime, updating cohorts`) + permutiveSDKInRealTime = true + readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) + completeBidRequestData() + }, 'realtime') + + logger.logInfo(`Registered cohort update when SDK is realtime`) }) }, init: init From 983f8070a536b66b9834668048f9ce493778593f Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Thu, 16 Feb 2023 16:32:48 +0000 Subject: [PATCH 7/8] fix(permutiveRtd): require `waitForIt` and permutive to be false to complete immediately --- modules/permutiveRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index aad0faa8c36..22644110453 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -414,7 +414,7 @@ export const permutiveSubmodule = { readAndSetCohorts(reqBidsConfigObj, moduleConfig) makeSafe(function () { - if (permutiveSDKInRealTime || !moduleConfig.waitForIt || !isPermutiveOnPage()) { + if (permutiveSDKInRealTime || !(moduleConfig.waitForIt && isPermutiveOnPage())) { return completeBidRequestData() } From 7aa5fa2092f69d9dd991527988397d64344134a3 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro Date: Wed, 8 Mar 2023 16:09:22 +0000 Subject: [PATCH 8/8] fix(permutiveRtd): remove bidder specific logic --- modules/permutiveRtdProvider.js | 20 ++--- .../spec/modules/permutiveRtdProvider_spec.js | 84 +++++++++++-------- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 22644110453..8af963f37dc 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -133,6 +133,8 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { * @return {Object} Merged ortb2 object */ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { + const customCohortsData = deepAccess(segmentData, bidder) || [] + const name = 'permutive.com' const permutiveUserData = { @@ -159,17 +161,15 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) - // Set bidder specific extensions - if (bidder === 'rubicon') { - if (segmentIDs.length > 0) { - deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_STANDARD_KEYWORD, segmentIDs) - } - - if (segmentData?.rubicon?.length > 0) { - deepSetValue(ortbConfig, 'ortb2.user.ext.data.' + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, segmentData.rubicon.map(String)) - } + // Set user extensions + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_STANDARD_KEYWORD}`, segmentIDs) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_STANDARD_KEYWORD}"`, segmentIDs) + } - logger.logInfo(`Extending ortb2.user.ext.data for ${bidder}`, deepAccess(ortbConfig, 'ortb2.user.ext.data')) + if (customCohortsData.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}`, customCohortsData.map(String)) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) } logger.logInfo(`Updating ortb2 config for ${bidder}`, ortbConfig) diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index c6d33304f03..207771731a7 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -375,10 +375,33 @@ describe('permutiveRtdProvider', function () { }]) }) - describe('ortb2 rubicon extensions', function () { + describe('ortb2.user.ext tests', function () { + it('should add nothing if there are no cohorts data', function () { + // Empty module config means we default + const moduleConfig = getConfig() + + const bidderConfig = {} + + // Passing empty values means there is no segment data + const segmentsData = transformedTargeting({ + _pdfps: [], + _prubicons: [], + _papns: [], + _psegs: [], + _ppam: [], + _pcrprs: [], + _pssps: { ssps: [], cohorts: [] } + }) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + }) + }) + it('should add standard and custom cohorts', function () { const moduleConfig = getConfig() - expect(moduleConfig.params.acBidders).to.include('rubicon') const bidderConfig = {} @@ -387,50 +410,43 @@ describe('permutiveRtdProvider', function () { setBidderRtb(bidderConfig, moduleConfig, segmentsData) moduleConfig.params.acBidders.forEach(bidder => { - if (bidder === 'rubicon') { - expect(bidderConfig[bidder].user.ext.data).to.deep - .eq({ - // Default targeting - p_standard: segmentsData.ac, - // Custom cohort targetings - permutive: segmentsData.rubicon, - }) - } else { - expect(bidderConfig[bidder].user).to.not.have.property('ext') + const userExtData = { + // Default targeting + p_standard: segmentsData.ac, + } + + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + deepSetValue(userExtData, 'permutive', customCohorts) } + + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq(userExtData) }) }) - it('should add standard cohorts ONLY', function () { + it('should add ac cohorts ONLY', function () { const moduleConfig = getConfig() - expect(moduleConfig.params.acBidders).to.include('rubicon') const bidderConfig = {} const segmentsData = transformedTargeting() - // Remove rubicon custom cohorts - delete segmentsData['rubicon'] + moduleConfig.params.acBidders.forEach((bidder) => { + // Remove custom cohorts + delete segmentsData[bidder] + }) setBidderRtb(bidderConfig, moduleConfig, segmentsData) - moduleConfig.params.acBidders.forEach(bidder => { - if (bidder === 'rubicon') { - expect(bidderConfig[bidder].user.ext.data).to.deep - .eq({ - // Default targeting - p_standard: segmentsData.ac, - }) - expect(bidderConfig[bidder].user.ext.data).to.not.have - .property('permutive') - } else { - expect(bidderConfig[bidder].user).to.not.have.property('ext') - } + moduleConfig.params.acBidders.forEach((bidder) => { + expect(bidderConfig[bidder].user.ext.data).to.deep.equal({ + p_standard: segmentsData.ac + }) }) }) it('should add custom cohorts ONLY', function () { const moduleConfig = getConfig() - expect(moduleConfig.params.acBidders).to.include('rubicon') const bidderConfig = {} @@ -441,14 +457,10 @@ describe('permutiveRtdProvider', function () { setBidderRtb(bidderConfig, moduleConfig, segmentsData) moduleConfig.params.acBidders.forEach(bidder => { - if (bidder === 'rubicon') { + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { expect(bidderConfig[bidder].user.ext.data).to.deep - .eq({ - // custom cohort targeting - permutive: segmentsData.rubicon, - }) - expect(bidderConfig[bidder].user.ext.data).to.not.have - .property('p_standard') + .eq({ permutive: customCohorts }) } else { expect(bidderConfig[bidder].user).to.not.have.property('ext') }