Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions modules/permutiveRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export function getModuleConfig(customModuleConfig) {
acBidders: [],
overwrites: {},
enforceVendorConsent: false,
bidders: {
msft: {
customCohorts: { source: 'ls', key: '_papns' }
},
},
},
},
permutiveModuleConfig,
Expand All @@ -108,13 +113,15 @@ 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 biddersConfig = deepAccess(moduleConfig, 'params.bidders') || {}

const ssps = segmentData?.ssp?.ssps ?? []
const sspCohorts = segmentData?.ssp?.cohorts ?? []
const topics = segmentData?.topics ?? {}

const bidders = new Set([...acBidders, ...ssps])
bidders.forEach(function (bidder) {
const bidderConfig = biddersConfig[bidder] || {};
const currConfig = { ortb2: bidderOrtb2[bidder] || {} }

let cohorts = []
Expand All @@ -129,11 +136,29 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) {
cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs)
}

const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, topics, transformationConfigs, segmentData)
const customCohortsData = getCustomCohortsData(bidderConfig, bidder, segmentData, maxSegs)

const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, topics, transformationConfigs, customCohortsData)
bidderOrtb2[bidder] = nextConfig.ortb2
})
}

/**
* Resolves custom cohorts data for a bidder, reading from localStorage if configured.
* @param {Object} bidderCfg - Bidder-specific configuration from params.bidders
* @param {string} bidder - The bidder identifier
* @param {Object} segmentData - Segment data grouped by bidder or type
* @param {number} maxSegs - Maximum number of segments
* @return {string[]} Custom cohort IDs
*/
function getCustomCohortsData (bidderCfg, bidder, segmentData, maxSegs) {
const customCohorts = bidderCfg?.customCohorts
if (customCohorts?.source === 'ls' && customCohorts?.key) {
return makeSafe(() => readSegments(customCohorts.key, []).map(String).slice(0, maxSegs)) || []
}
return deepAccess(segmentData, bidder) || []
}

/**
* Updates `user.data` object in existing bidder config with Permutive segments
* @param {string} bidder - The bidder identifier
Expand All @@ -143,14 +168,12 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) {
* @param {Object} topics - Privacy Sandbox Topics, keyed by IAB taxonomy version (600, 601, etc.)
* @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 {Object} segmentData - The segments available for targeting
* @param {string[]} customCohortsData - Custom cohort IDs for this bidder
* @return {Object} Merged ortb2 object
*/
function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, transformationConfigs, segmentData) {
function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, transformationConfigs, customCohortsData) {
logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig })

const customCohortsData = deepAccess(segmentData, bidder) || []

const name = 'permutive.com'

const permutiveUserData = {
Expand Down
7 changes: 7 additions & 0 deletions modules/permutiveRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd
| params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` |
| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` |
| params.enforceVendorConsent | Boolean | When `true`, require TCF vendor consent for Permutive (vendor 361). See note below. | `false` |
| params.bidders | Object | Per-bidder configuration for custom cohort sources. Keys are bidder codes. | `{ msft: { customCohorts: { source: 'ls', key: '_papns' } } }` |
| params.bidders.\<bidder\>.customCohorts | Object | Custom cohorts source configuration for a specific bidder. | - |
| params.bidders.\<bidder\>.customCohorts.source | String | Storage type to read custom cohorts from. Currently only `'ls'` (localStorage) is supported. | - |
| params.bidders.\<bidder\>.customCohorts.key | String | The localStorage key to read custom cohorts from. | - |

#### Consent

Expand Down Expand Up @@ -81,11 +85,14 @@ Currently, bidders with known support for custom cohort targeting are:

- Xandr
- Magnite
- Microsoft (msft)

When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request.
There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard.
Permutive cohorts will be sent in the permutive key-value.

**Note:** Publishers migrating from the `appnexus` bidder to `msft` should add `msft` to the `acBidders` array (or via the Permutive platform). The module automatically reads custom cohorts for `msft` from the same localStorage key used by `appnexus`.


### _Enabling Advertiser Cohorts_

Expand Down
65 changes: 65 additions & 0 deletions test/spec/modules/permutiveCombined_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ describe('permutiveRtdProvider', function () {
acBidders: [],
overwrites: {},
enforceVendorConsent: false,
bidders: {
msft: {
customCohorts: { source: 'ls', key: '_papns' }
},
},
},
})

Expand Down Expand Up @@ -689,6 +694,66 @@ describe('permutiveRtdProvider', function () {
})
})
})

describe('bidders config with customCohorts', function () {
it('should read custom cohorts from localStorage for msft bidder using customCohorts config', function () {
const segmentsData = transformedTargeting()
const expectedAppnexusCohorts = segmentsData.appnexus

const moduleConfig = {
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['msft'],
maxSegs: 500,
bidders: {
msft: {
customCohorts: { source: 'ls', key: '_papns' }
}
}
}
}
const bidderConfig = {}

setBidderRtb(bidderConfig, moduleConfig, segmentsData)

expect(bidderConfig['msft'].user.data).to.deep.include.members([
{
name: 'permutive',
segment: expectedAppnexusCohorts.map(id => ({ id })),
},
])

expectedAppnexusCohorts.forEach(id => {
expect(bidderConfig['msft'].user.keywords).to.include(`permutive=${id}`)
})
})

it('should fall back to segmentData lookup when customCohorts is not configured', function () {
const segmentsData = transformedTargeting()

const moduleConfig = {
name: 'permutive',
waitForIt: true,
params: {
acBidders: ['appnexus'],
maxSegs: 500,
bidders: {}
}
}
const bidderConfig = {}

setBidderRtb(bidderConfig, moduleConfig, segmentsData)

const expectedAppnexusCohorts = segmentsData.appnexus
expect(bidderConfig['appnexus'].user.data).to.deep.include.members([
{
name: 'permutive',
segment: expectedAppnexusCohorts.map(id => ({ id })),
},
])
})
})
})

describe('Getting segments', function () {
Expand Down
Loading