Skip to content

Commit

Permalink
Permutive RTD Module: add support for new ssp standard cohorts (prebi…
Browse files Browse the repository at this point in the history
…d#9236)

* add logic to parse and pass ssp data to appnexus

* simplify shouldSetConfig condition

* Write SSP cohorts into p_standard targeting (+ bug fixes)

* fix tests

* Simplify

* add logic to parse and pass ssp data to appnexus

* simplify shouldSetConfig condition

* Write SSP cohorts into p_standard targeting (+ bug fixes)

* fix tests

* Simplify

* use new key for auction kw cohorts

* Push SSP cohorts to SSPs via ORTB2

* Add tests and fix bugs

* Update tests

* update example with _pssps

* Remove custom `appnexusAuctionKeywords` and use user.keywords in ortb2 config

* Fix linting issues

Co-authored-by: Paulius Imbrasas <paulius@permutive.com>
  • Loading branch information
2 people authored and jorgeluisrocha committed May 18, 2023
1 parent c8e0f6d commit 1e55e99
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 21 deletions.
3 changes: 2 additions & 1 deletion integrationExamples/gpt/permutiveRtdProvider_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
_papns: ['appnexus1', 'appnexus2'],
_psegs: ['1234', '1000001', '1000002'],
_ppam: ['ppam1', 'ppam2'],
_pcrprs: ['pcrprs1', 'pcrprs2']
_pcrprs: ['pcrprs1', 'pcrprs2'],
_pssps: { ssps: ['appnexus', 'some other'], cohorts: ['abcd', 'efgh', 'ijkl'] },
}

for (let key in data) {
Expand Down
61 changes: 49 additions & 12 deletions modules/permutiveRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {includes} from '../src/polyfill.js';
const MODULE_NAME = 'permutive'

export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd'
export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud'

export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME})

Expand Down Expand Up @@ -118,9 +119,21 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) {
const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || []
const segmentData = getSegments(maxSegs)

acBidders.forEach(function (bidder) {
const ssps = segmentData?.ssp?.ssps ?? []
const sspCohorts = segmentData?.ssp?.cohorts ?? []

const bidders = new Set([...acBidders, ...ssps])
bidders.forEach(function (bidder) {
const currConfig = { ortb2: bidderOrtb2[bidder] || {} }
const nextConfig = updateOrtbConfig(currConfig, segmentData.ac, transformationConfigs) // ORTB2 uses the `ac` segment IDs

const isAcBidder = acBidders.indexOf(bidder) > -1
const isSspBidder = ssps.indexOf(bidder) > -1

let cohorts = []
if (isAcBidder) cohorts = segmentData.ac
if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs)

const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs)
bidderOrtb2[bidder] = nextConfig.ortb2;
})
}
Expand All @@ -131,9 +144,10 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) {
* @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
* @return {Object} Merged ortb2 object
*/
function updateOrtbConfig (currConfig, segmentIDs, transformationConfigs) {
function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) {
const name = 'permutive.com'

const permutiveUserData = {
Expand All @@ -154,6 +168,12 @@ function updateOrtbConfig (currConfig, segmentIDs, transformationConfigs) {

deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData)

// As of writing this, only used for AppNexus/Xandr in place of appnexusAuctionKeywords in config
const currentUserKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') || ''
const keywords = sspSegmentIDs.map(segment => `${PERMUTIVE_STANDARD_AUD_KEYWORD}=${segment}`).join(',')
const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}`
deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords)

return ortbConfig
}

Expand Down Expand Up @@ -222,10 +242,19 @@ function getCustomBidderFn (moduleConfig, bidder) {
* @return {Object} Bidder function
*/
function getDefaultBidderFn (bidder) {
const isPStandardTargetingEnabled = (data, acEnabled) => {
return (acEnabled && data.ac && data.ac.length) || (data.ssp && data.ssp.cohorts.length)
}
const pStandardTargeting = (data, acEnabled) => {
const ac = (acEnabled) ? (data.ac ?? []) : []
const ssp = data?.ssp?.cohorts ?? []
return [...new Set([...ac, ...ssp])]
}
const bidderMap = {
appnexus: function (bid, data, acEnabled) {
if (acEnabled && data.ac && data.ac.length) {
deepSetValue(bid, 'params.keywords.p_standard', data.ac)
if (isPStandardTargetingEnabled(data, acEnabled)) {
const segments = pStandardTargeting(data, acEnabled)
deepSetValue(bid, 'params.keywords.p_standard', segments)
}
if (data.appnexus && data.appnexus.length) {
deepSetValue(bid, 'params.keywords.permutive', data.appnexus)
Expand All @@ -234,19 +263,20 @@ function getDefaultBidderFn (bidder) {
return bid
},
rubicon: function (bid, data, acEnabled) {
if (acEnabled && data.ac && data.ac.length) {
deepSetValue(bid, 'params.visitor.p_standard', data.ac)
if (isPStandardTargetingEnabled(data, acEnabled)) {
const segments = pStandardTargeting(data, acEnabled)
deepSetValue(bid, 'params.visitor.p_standard', segments)
}
if (data.rubicon && data.rubicon.length) {
const rubiconCohorts = deepAccess(bid, 'params.video') ? data.rubicon.map(String) : data.rubicon
deepSetValue(bid, 'params.visitor.permutive', rubiconCohorts)
deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String))
}

return bid
},
ozone: function (bid, data, acEnabled) {
if (acEnabled && data.ac && data.ac.length) {
deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac)
if (isPStandardTargetingEnabled(data, acEnabled)) {
const segments = pStandardTargeting(data, acEnabled)
deepSetValue(bid, 'params.customData.0.targeting.p_standard', segments)
}

return bid
Expand Down Expand Up @@ -290,10 +320,17 @@ export function getSegments (maxSegs) {
rubicon: readSegments('_prubicons'),
appnexus: readSegments('_papns'),
gam: readSegments('_pdfps'),
ssp: readSegments('_pssps'),
}

for (const bidder in segments) {
segments[bidder] = segments[bidder].slice(0, maxSegs)
if (bidder === 'ssp') {
if (segments[bidder].cohorts && Array.isArray(segments[bidder].cohorts)) {
segments[bidder].cohorts = segments[bidder].cohorts.slice(0, maxSegs)
}
} else {
segments[bidder] = segments[bidder].slice(0, maxSegs)
}
}

return segments
Expand Down
113 changes: 105 additions & 8 deletions test/spec/modules/permutiveRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,42 @@ describe('permutiveRtdProvider', function () {
})
})
it('should not overwrite ortb2 config', function () {
const moduleConfig = getConfig()
const acBidders = moduleConfig.params.acBidders
const sampleOrtbConfig = {
site: {
name: 'example'
},
user: {
data: [
{
name: 'www.dataprovider1.com',
ext: { taxonomyname: 'iab_audience_taxonomy' },
segment: [{ id: '687' }, { id: '123' }]
}
]
}
}

const bidderConfig = Object.fromEntries(acBidders.map(bidder => [bidder, sampleOrtbConfig]))

const transformedUserData = {
name: 'transformation',
ext: { test: true },
segment: [1, 2, 3]
}

setBidderRtb(bidderConfig, moduleConfig, {
// TODO: this argument is unused, is the test still valid / needed?
testTransformation: userData => transformedUserData
})

acBidders.forEach(bidder => {
expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name)
expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]])
})
})
it('should update user.keywords and not override existing values', function () {
const moduleConfig = getConfig()
const acBidders = moduleConfig.params.acBidders
const sampleOrtbConfig = {
Expand Down Expand Up @@ -275,9 +311,64 @@ describe('permutiveRtdProvider', function () {

acBidders.forEach(bidder => {
expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name)
expect(bidderConfig[bidder].user.keywords).to.equal(sampleOrtbConfig.user.keywords)
expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]])
expect(bidderConfig[bidder].user.keywords).to.deep.equal('a,b,p_standard_aud=123,p_standard_aud=abc')
})
})
it('should merge ortb2 correctly for ac and ssps', function () {
setLocalStorage({
'_ppam': [],
'_psegs': [],
'_pcrprs': ['abc', 'def', 'xyz'],
'_pssps': {
ssps: ['foo', 'bar'],
cohorts: ['xyz', 'uvw'],
}
})
const moduleConfig = {
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['foo', 'other'],
maxSegs: 30
}
}
const bidderConfig = {};

setBidderRtb(bidderConfig, moduleConfig)

// include both ac and ssp cohorts, as foo is both in ac bidders and ssps
const expectedFooTargetingData = [
{ id: 'abc' },
{ id: 'def' },
{ id: 'xyz' },
{ id: 'uvw' },
]
expect(bidderConfig['foo'].user.data).to.deep.include.members([{
name: 'permutive.com',
segment: expectedFooTargetingData
}])

// don't include ac targeting as it's not in ac bidders
const expectedBarTargetingData = [
{ id: 'xyz' },
{ id: 'uvw' },
]
expect(bidderConfig['bar'].user.data).to.deep.include.members([{
name: 'permutive.com',
segment: expectedBarTargetingData
}])

// only include ac targeting as this ssp is not in ssps list
const expectedOtherTargetingData = [
{ id: 'abc' },
{ id: 'def' },
{ id: 'xyz' },
]
expect(bidderConfig['other'].user.data).to.deep.include.members([{
name: 'permutive.com',
segment: expectedOtherTargetingData
}])
})
})

Expand All @@ -291,7 +382,11 @@ describe('permutiveRtdProvider', function () {
const segments = getSegments(max)

for (const key in segments) {
expect(segments[key]).to.have.length(max)
if (key === 'ssp') {
expect(segments[key].cohorts).to.have.length(max)
} else {
expect(segments[key]).to.have.length(max)
}
}
})
})
Expand All @@ -311,7 +406,7 @@ describe('permutiveRtdProvider', function () {

if (bidder === 'appnexus') {
expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus)
expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac)
expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts))
}
})
})
Expand All @@ -332,7 +427,7 @@ describe('permutiveRtdProvider', function () {

if (bidder === 'rubicon') {
expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon)
expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac)
expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts))
}
})
})
Expand Down Expand Up @@ -363,7 +458,7 @@ describe('permutiveRtdProvider', function () {
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)
expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts))
}
})
})
Expand All @@ -383,7 +478,7 @@ describe('permutiveRtdProvider', function () {
const { bidder, params } = bid

if (bidder === 'ozone') {
expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac)
expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts))
}
})
})
Expand Down Expand Up @@ -417,7 +512,7 @@ describe('permutiveRtdProvider', function () {

if (bidder === 'rubicon') {
expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam)
expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac)
expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts))
}
})
})
Expand Down Expand Up @@ -555,6 +650,7 @@ function transformedTargeting (data = getTargetingData()) {
appnexus: data._papns,
rubicon: data._prubicons,
gam: data._pdfps,
ssp: data._pssps,
}
}

Expand All @@ -565,7 +661,8 @@ function getTargetingData () {
_papns: ['appnexus1', 'appnexus2'],
_psegs: ['1234', '1000001', '1000002'],
_ppam: ['ppam1', 'ppam2'],
_pcrprs: ['pcrprs1', 'pcrprs2']
_pcrprs: ['pcrprs1', 'pcrprs2', 'dup'],
_pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] }
}
}

Expand Down

0 comments on commit 1e55e99

Please sign in to comment.