From cf9697cfab0680be258c4a6b162757a222481181 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 26 Jun 2024 16:18:30 +0200 Subject: [PATCH 01/10] 10997 set pps to gam display --- src/pps.js | 77 +++++++++++++++++++++++++++++++++ src/targeting.js | 35 +++++++++++---- test/spec/unit/core/pps_spec.js | 59 +++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 src/pps.js create mode 100644 test/spec/unit/core/pps_spec.js diff --git a/src/pps.js b/src/pps.js new file mode 100644 index 00000000000..337e33ac36f --- /dev/null +++ b/src/pps.js @@ -0,0 +1,77 @@ +import { CLIENT_SECTIONS } from './fpd/oneClient.js'; +import { deepAccess, uniques } from './utils.js'; + +export function getSegments(fpd, sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) +} + +export function getSignals(fpd) { + const signals = Object.entries({ + IAB_AUDIENCE_1_1: getSegments(fpd, ['user.data'], 4), + IAB_CONTENT_2_2: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + return signals; +} + +export function getFpdIntersection(fpdArray) { + if (fpdArray.length === 0) { + return {}; + } + + let common = fpdArray[0]; + + for (let i = 1; i < fpdArray.length; i++) { + common = getCommonObject(common, fpdArray[i]); + } + + return common; +} + +function getCommonObject(obj1, obj2) { + let common = {}; + + for (let key in obj1) { + if (obj1.hasOwnProperty(key) && obj2.hasOwnProperty(key)) { + if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { + let commonArray = getCommonArray(obj1[key], obj2[key]); + if (commonArray.length > 0) { + common[key] = commonArray; + } + } else if (typeof obj1[key] === 'object' && obj1[key] !== null && typeof obj2[key] === 'object' && obj2[key] !== null) { + let commonPart = getCommonObject(obj1[key], obj2[key]); + if (Object.keys(commonPart).length > 0) { + common[key] = commonPart; + } + } else if (obj1[key] === obj2[key]) { + common[key] = obj1[key]; + } + } + } + + return common; +} + +function getCommonArray(arr1, arr2) { + let commonArray = []; + let minLength = Math.min(arr1.length, arr2.length); + + for (let i = 0; i < minLength; i++) { + if (typeof arr1[i] === 'object' && arr1[i] !== null && typeof arr2[i] === 'object' && arr2[i] !== null) { + let commonElement = getCommonObject(arr1[i], arr2[i]); + if (Object.keys(commonElement).length > 0) { + commonArray.push(commonElement); + } + } else if (arr1[i] === arr2[i]) { + commonArray.push(arr1[i]); + } + } + + return commonArray; +} diff --git a/src/targeting.js b/src/targeting.js index 0c4874fc50b..da04e5e105e 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -33,6 +33,7 @@ import { import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; import {getTTL} from './bidTTL.js'; import * as events from './events.js'; +import { getFpdIntersection, getSignals } from './pps.js'; var pbTargetingKeys = []; @@ -209,7 +210,7 @@ export function newTargeting(auctionManager) { /** * Returns targeting for any bids which have deals if alwaysIncludeDeals === true */ - function getDealBids(adUnitCodes, bidsReceived) { + function getDealBids(adUnitCodes, bidsReceived, bidsAddedToTargeting) { if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); @@ -219,6 +220,7 @@ export function newTargeting(auctionManager) { // populate targeting keys for the remaining bids if they have a dealId return bids.map(bid => { if (bid.dealId && bidShouldBeAddedToTargeting(bid, adUnitCodes)) { + bidsAddedToTargeting.push(bid) return { [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( key => typeof bid.adserverTargeting[key] !== 'undefined') @@ -280,13 +282,23 @@ export function newTargeting(auctionManager) { targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) { const adUnitCodes = getAdUnitCodes(adUnitCode); + const bidsAddedToTargeting = []; + // Get targeting for the winning bid. Add targeting for any bids that have // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) - .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)) + var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) + .concat(getCustomBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting)) + .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) : getDealBids(adUnitCodes, bidsReceived, bidsAddedToTargeting)) .concat(getAdUnitTargeting(adUnitCodes)); + const fpdArray = bidsAddedToTargeting + .map(bid => auctionManager.index.getAuction(bid || {})?.getFPD()?.global) + .filter(fpd => fpd); + + const commonFpd = getFpdIntersection(fpdArray); + const signals = getSignals(commonFpd); + //@TODO: what to do with signals? + // store a reference of the targeting keys targeting.map(adUnitCode => { Object.keys(adUnitCode).map(key => { @@ -562,10 +574,12 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} winning bids targeting */ - function getWinningBidTargeting(adUnitCodes, bidsReceived) { + function getWinningBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); let standardKeys = getStandardKeys(); + bidsAddedToTargeting.push(...winners); + winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting) @@ -662,10 +676,14 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} bids with custom targeting defined in bidderSettings */ - function getCustomBidTargeting(adUnitCodes, bidsReceived) { + function getCustomBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { return bidsReceived .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .map(bid => Object.assign({}, bid)) + .map(bid => { + const newBid = Object.assign({}, bid); + bidsAddedToTargeting(newBid); + return newBid; + }) .reduce(mergeAdServerTargeting, []) .map(truncateCustomKeys) .filter(bid => bid); // removes empty elements in array; @@ -676,7 +694,7 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} all non-winning bids targeting */ - function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { + function getBidLandscapeTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); @@ -689,6 +707,7 @@ export function newTargeting(auctionManager) { // populate targeting keys for the remaining bids return bids.map(bid => { if (bidShouldBeAddedToTargeting(bid, adUnitCodes)) { + bidsAddedToTargeting.push(bid); return { [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( key => typeof bid.adserverTargeting[key] !== 'undefined' && diff --git a/test/spec/unit/core/pps_spec.js b/test/spec/unit/core/pps_spec.js new file mode 100644 index 00000000000..6b877463af1 --- /dev/null +++ b/test/spec/unit/core/pps_spec.js @@ -0,0 +1,59 @@ +import { getFpdIntersection } from '../../../../src/pps' + +describe('pps', () => { + const mockFpdArray = [ + { user: { + keywords: 'a,b', + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + }, + + }}, + { user: { + keywords: 'b,c', + data: [{ + name: 'dataprovider2.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }] + }], + ext: { + data: { + registered: false, + interests: ['cars', 'bicycles'] + } + } + }}, + ] + const common = { + user: { + data: [{ + ext: { segtax: 4 } + }], + ext: { + data: { + interests: ['cars'] + } + } + } + } + it('should properly find intersection', () => { + const result = getFpdIntersection(mockFpdArray) + expect(JSON.stringify(result)).to.equal(JSON.stringify(common)) + }) +}) From afc58a33f9a445f41597d296c7b09cd044e4ef35 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Tue, 2 Jul 2024 23:41:52 +0200 Subject: [PATCH 02/10] update --- modules/dfpAdServerVideo.js | 18 +---- src/pps.js | 74 ++++++------------- src/targeting.js | 41 +++++------ test/spec/unit/core/pps_spec.js | 125 +++++++++++++++++++------------- 4 files changed, 117 insertions(+), 141 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 8325af56b20..3cb3f5f1f60 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -25,6 +25,7 @@ import {getPPID} from '../src/adserver.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; +import { getSegments, getSignals } from '../src/pps.js'; /** * @typedef {Object} DfpVideoParams * @@ -170,21 +171,8 @@ export function buildDfpVideoUrl(options) { }); const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ?? auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global; - - function getSegments(sections, segtax) { - return sections - .flatMap(section => deepAccess(fpd, section) || []) - .filter(datum => datum.ext?.segtax === segtax) - .flatMap(datum => datum.segment?.map(seg => seg.id)) - .filter(ob => ob) - .filter(uniques) - } - - const signals = Object.entries({ - IAB_AUDIENCE_1_1: getSegments(['user.data'], 4), - IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) - }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) - .filter(ob => ob); + + const signals = getSignals(fpd); if (signals.length) { queryParams.ppsj = btoa(JSON.stringify({ diff --git a/src/pps.js b/src/pps.js index 337e33ac36f..ad2e4887e60 100644 --- a/src/pps.js +++ b/src/pps.js @@ -1,3 +1,4 @@ +import { auctionManager } from './auctionManager.js'; import { CLIENT_SECTIONS } from './fpd/oneClient.js'; import { deepAccess, uniques } from './utils.js'; @@ -20,58 +21,27 @@ export function getSignals(fpd) { return signals; } -export function getFpdIntersection(fpdArray) { - if (fpdArray.length === 0) { - return {}; - } - - let common = fpdArray[0]; - - for (let i = 1; i < fpdArray.length; i++) { - common = getCommonObject(common, fpdArray[i]); - } - - return common; +export function getSignalsArray(auctionIds) { + const signals = auctionIds + .map(auctionId => auctionManager.index.getAuction({ auctionId })?.getFPD()?.global) + .map(getSignals) + .filter(fpd => fpd); + return signals } -function getCommonObject(obj1, obj2) { - let common = {}; - - for (let key in obj1) { - if (obj1.hasOwnProperty(key) && obj2.hasOwnProperty(key)) { - if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { - let commonArray = getCommonArray(obj1[key], obj2[key]); - if (commonArray.length > 0) { - common[key] = commonArray; - } - } else if (typeof obj1[key] === 'object' && obj1[key] !== null && typeof obj2[key] === 'object' && obj2[key] !== null) { - let commonPart = getCommonObject(obj1[key], obj2[key]); - if (Object.keys(commonPart).length > 0) { - common[key] = commonPart; - } - } else if (obj1[key] === obj2[key]) { - common[key] = obj1[key]; - } - } - } - - return common; -} - -function getCommonArray(arr1, arr2) { - let commonArray = []; - let minLength = Math.min(arr1.length, arr2.length); - - for (let i = 0; i < minLength; i++) { - if (typeof arr1[i] === 'object' && arr1[i] !== null && typeof arr2[i] === 'object' && arr2[i] !== null) { - let commonElement = getCommonObject(arr1[i], arr2[i]); - if (Object.keys(commonElement).length > 0) { - commonArray.push(commonElement); - } - } else if (arr1[i] === arr2[i]) { - commonArray.push(arr1[i]); - } - } - - return commonArray; +export function getSegmentsIntersection(signals) { + const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; + const result = {}; + taxonomies.forEach((taxonomy) => { + const allValues = signals + .flatMap(x => x) + .filter(x => x.taxonomy === taxonomy) + .map(x => x.values); + result[taxonomy] = allValues.length ? ( + allValues.reduce((commonElements, subArray) => { + return commonElements.filter(element => subArray.includes(element)); + }) + ) : [] + }) + return result; } diff --git a/src/targeting.js b/src/targeting.js index da04e5e105e..e7761e972c4 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -33,7 +33,7 @@ import { import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; import {getTTL} from './bidTTL.js'; import * as events from './events.js'; -import { getFpdIntersection, getSignals } from './pps.js'; +import { getSignalsArray } from './pps.js'; var pbTargetingKeys = []; @@ -210,7 +210,7 @@ export function newTargeting(auctionManager) { /** * Returns targeting for any bids which have deals if alwaysIncludeDeals === true */ - function getDealBids(adUnitCodes, bidsReceived, bidsAddedToTargeting) { + function getDealBids(adUnitCodes, bidsReceived) { if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); @@ -220,7 +220,6 @@ export function newTargeting(auctionManager) { // populate targeting keys for the remaining bids if they have a dealId return bids.map(bid => { if (bid.dealId && bidShouldBeAddedToTargeting(bid, adUnitCodes)) { - bidsAddedToTargeting.push(bid) return { [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( key => typeof bid.adserverTargeting[key] !== 'undefined') @@ -282,23 +281,13 @@ export function newTargeting(auctionManager) { targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) { const adUnitCodes = getAdUnitCodes(adUnitCode); - const bidsAddedToTargeting = []; - // Get targeting for the winning bid. Add targeting for any bids that have // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) - .concat(getCustomBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) : getDealBids(adUnitCodes, bidsReceived, bidsAddedToTargeting)) + var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) + .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) + .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)) .concat(getAdUnitTargeting(adUnitCodes)); - const fpdArray = bidsAddedToTargeting - .map(bid => auctionManager.index.getAuction(bid || {})?.getFPD()?.global) - .filter(fpd => fpd); - - const commonFpd = getFpdIntersection(fpdArray); - const signals = getSignals(commonFpd); - //@TODO: what to do with signals? - // store a reference of the targeting keys targeting.map(adUnitCode => { Object.keys(adUnitCode).map(key => { @@ -447,6 +436,16 @@ export function newTargeting(auctionManager) { // get our ad unit codes let targetingSet = targeting.getAllTargeting(adUnit); + const auctionIds = targetingSet + .flatMap(entry => Object.values(entry)) + .flatMap(x => x) + .filter(x => Object.keys(x).some(key => key.includes(TARGETING_KEYS.AD_ID))) + .map(entry => Object.values(entry)) + .flatMap(x => x[0]) + + const signals = getSignalsArray(auctionIds) + googletag.setConfig({pps: signals}) + let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { @@ -574,12 +573,10 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} winning bids targeting */ - function getWinningBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { + function getWinningBidTargeting(adUnitCodes, bidsReceived) { let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); let standardKeys = getStandardKeys(); - bidsAddedToTargeting.push(...winners); - winners = winners.map(winner => { return { [winner.adUnitCode]: Object.keys(winner.adserverTargeting) @@ -676,12 +673,11 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} bids with custom targeting defined in bidderSettings */ - function getCustomBidTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { + function getCustomBidTargeting(adUnitCodes, bidsReceived) { return bidsReceived .filter(bid => includes(adUnitCodes, bid.adUnitCode)) .map(bid => { const newBid = Object.assign({}, bid); - bidsAddedToTargeting(newBid); return newBid; }) .reduce(mergeAdServerTargeting, []) @@ -694,7 +690,7 @@ export function newTargeting(auctionManager) { * @param {string[]} adUnitCodes code array * @return {targetingArray} all non-winning bids targeting */ - function getBidLandscapeTargeting(adUnitCodes, bidsReceived, bidsAddedToTargeting) { + function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); @@ -707,7 +703,6 @@ export function newTargeting(auctionManager) { // populate targeting keys for the remaining bids return bids.map(bid => { if (bidShouldBeAddedToTargeting(bid, adUnitCodes)) { - bidsAddedToTargeting.push(bid); return { [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( key => typeof bid.adserverTargeting[key] !== 'undefined' && diff --git a/test/spec/unit/core/pps_spec.js b/test/spec/unit/core/pps_spec.js index 6b877463af1..1f8d981260d 100644 --- a/test/spec/unit/core/pps_spec.js +++ b/test/spec/unit/core/pps_spec.js @@ -1,59 +1,82 @@ -import { getFpdIntersection } from '../../../../src/pps' +import { auctionManager } from '../../../../src/auctionManager'; +import { getFpdIntersection, getSegmentsIntersection, getSignalsArray } from '../../../../src/pps' describe('pps', () => { - const mockFpdArray = [ - { user: { - keywords: 'a,b', - data: [{ - name: 'dataprovider.com', - ext: { - segtax: 4 - }, - segment: [{ - id: '1' - }] - }], - ext: { - data: { - registered: true, - interests: ['cars'] - } - }, + let getAuctionStub; - }}, - { user: { - keywords: 'b,c', - data: [{ - name: 'dataprovider2.com', - ext: { - segtax: 4 - }, - segment: [{ - id: '2' - }] - }], - ext: { - data: { - registered: false, - interests: ['cars', 'bicycles'] + const mocksAuctions = [ + { + auctionId: '1111', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }, { + id: '2' + }] + }], + } } - } - }}, - ] - const common = { - user: { - data: [{ - ext: { segtax: 4 } - }], - ext: { - data: { - interests: ['cars'] + }) + }, + { + auctionId: '234234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }] + }] + } } - } - } - } + }), + }, { + auctionId: '234324234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }, { + id: '3' + }] + }] + } + } + }) + }, + ] + + beforeEach(function () { + getAuctionStub = sinon.stub(auctionManager.index, 'getAuction').callsFake(({ auctionId }) => { + return mocksAuctions.find(auction => auction.auctionId === auctionId) + }); + }); + + afterEach(function () { + getAuctionStub.restore(); + }); + it('should properly find intersection', () => { - const result = getFpdIntersection(mockFpdArray) - expect(JSON.stringify(result)).to.equal(JSON.stringify(common)) + const signals = getSignalsArray(['1111', '234234', '234324234']); + const expectedResult = { IAB_AUDIENCE_1_1:["2"], IAB_CONTENT_2_2:[]}; + expect(signals).to.be.equal(JSON.stringify(expectedResult)); }) }) From 9e14c1366d9cb4df974bf0fc1e568328a07673fa Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Mon, 8 Jul 2024 19:28:29 +0200 Subject: [PATCH 03/10] update --- modules/dfpAdServerVideo.js | 30 ++++++++--------- src/pps.js | 17 ++++++++-- src/targeting.js | 60 ++++++++++++++------------------- test/spec/unit/core/pps_spec.js | 54 +++++++++++++++++++++++------ 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 3cb3f5f1f60..3d1fbfa0788 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,8 +2,18 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import {registerVideoSupport} from '../src/adServerManager.js'; -import {targeting} from '../src/targeting.js'; +import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT } from '../libraries/dfpUtils/dfpUtils.js'; +import { registerVideoSupport } from '../src/adServerManager.js'; +import { gdprDataHandler } from '../src/adapterManager.js'; +import { getPPID } from '../src/adserver.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { EVENTS } from '../src/constants.js'; +import * as events from '../src/events.js'; +import { getHook } from '../src/hook.js'; +import { getSignals } from '../src/pps.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { targeting } from '../src/targeting.js'; import { buildUrl, deepAccess, @@ -12,20 +22,8 @@ import { isNumber, logError, parseSizesInput, - parseUrl, - uniques + parseUrl } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook} from '../src/hook.js'; -import {auctionManager} from '../src/auctionManager.js'; -import {gdprDataHandler} from '../src/adapterManager.js'; -import * as events from '../src/events.js'; -import {EVENTS} from '../src/constants.js'; -import {getPPID} from '../src/adserver.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; -import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; -import { getSegments, getSignals } from '../src/pps.js'; /** * @typedef {Object} DfpVideoParams * @@ -171,7 +169,7 @@ export function buildDfpVideoUrl(options) { }); const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ?? auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global; - + const signals = getSignals(fpd); if (signals.length) { diff --git a/src/pps.js b/src/pps.js index ad2e4887e60..352673b3a1a 100644 --- a/src/pps.js +++ b/src/pps.js @@ -1,4 +1,5 @@ import { auctionManager } from './auctionManager.js'; +import { TARGETING_KEYS } from './constants.js'; import { CLIENT_SECTIONS } from './fpd/oneClient.js'; import { deepAccess, uniques } from './utils.js'; @@ -21,15 +22,16 @@ export function getSignals(fpd) { return signals; } -export function getSignalsArray(auctionIds) { +export function getSignalsArrayByAuctionsIds(auctionIds) { const signals = auctionIds .map(auctionId => auctionManager.index.getAuction({ auctionId })?.getFPD()?.global) .map(getSignals) .filter(fpd => fpd); - return signals + + return signals; } -export function getSegmentsIntersection(signals) { +export function getSignalsIntersection(signals) { const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; const result = {}; taxonomies.forEach((taxonomy) => { @@ -42,6 +44,15 @@ export function getSegmentsIntersection(signals) { return commonElements.filter(element => subArray.includes(element)); }) ) : [] + result[taxonomy] = { values: result[taxonomy] }; }) return result; } + +export function getAuctionsIdsFromTargeting(targeting) { + return Object.values(targeting) + .flatMap(x => Object.entries(x)) + .filter((entry) => (entry[0] || '').includes(TARGETING_KEYS.AD_ID)) + .flatMap(entry => entry[1]) + .filter(uniques); +} diff --git a/src/targeting.js b/src/targeting.js index e7761e972c4..f88de6652f8 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,3 +1,22 @@ +import { auctionManager } from './auctionManager.js'; +import { getTTL } from './bidTTL.js'; +import { bidderSettings } from './bidderSettings.js'; +import { config } from './config.js'; +import { + BID_STATUS, + DEFAULT_TARGETING_KEYS, + EVENTS, + JSON_MAPPING, + NATIVE_KEYS, + STATUS, + TARGETING_KEYS +} from './constants.js'; +import * as events from './events.js'; +import { hook } from './hook.js'; +import { ADPOD } from './mediaTypes.js'; +import { NATIVE_TARGETING_KEYS } from './native.js'; +import { find, includes } from './polyfill.js'; +import { getAuctionsIdsFromTargeting, getSignalsArrayByAuctionsIds, getSignalsIntersection } from './pps.js'; import { deepAccess, deepClone, @@ -14,26 +33,7 @@ import { timestamp, uniques, } from './utils.js'; -import {config} from './config.js'; -import {NATIVE_TARGETING_KEYS} from './native.js'; -import {auctionManager} from './auctionManager.js'; -import {ADPOD} from './mediaTypes.js'; -import {hook} from './hook.js'; -import {bidderSettings} from './bidderSettings.js'; -import {find, includes} from './polyfill.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'; -import { getSignalsArray } from './pps.js'; +import { getHighestCpm, getOldestHighestCpmBid } from './utils/reducers.js'; var pbTargetingKeys = []; @@ -436,16 +436,6 @@ export function newTargeting(auctionManager) { // get our ad unit codes let targetingSet = targeting.getAllTargeting(adUnit); - const auctionIds = targetingSet - .flatMap(entry => Object.values(entry)) - .flatMap(x => x) - .filter(x => Object.keys(x).some(key => key.includes(TARGETING_KEYS.AD_ID))) - .map(entry => Object.values(entry)) - .flatMap(x => x[0]) - - const signals = getSignalsArray(auctionIds) - googletag.setConfig({pps: signals}) - let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { @@ -472,6 +462,11 @@ export function newTargeting(auctionManager) { }); }); + // set gpt config + const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); + const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); + window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }}); + // emit event events.emit(EVENTS.SET_TARGETING, targetingSet); }, 'setTargetingForGPT'); @@ -676,10 +671,7 @@ export function newTargeting(auctionManager) { function getCustomBidTargeting(adUnitCodes, bidsReceived) { return bidsReceived .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .map(bid => { - const newBid = Object.assign({}, bid); - return newBid; - }) + .map(bid => Object.assign({}, bid)) .reduce(mergeAdServerTargeting, []) .map(truncateCustomKeys) .filter(bid => bid); // removes empty elements in array; diff --git a/test/spec/unit/core/pps_spec.js b/test/spec/unit/core/pps_spec.js index 1f8d981260d..d71c4369d87 100644 --- a/test/spec/unit/core/pps_spec.js +++ b/test/spec/unit/core/pps_spec.js @@ -1,8 +1,9 @@ import { auctionManager } from '../../../../src/auctionManager'; -import { getFpdIntersection, getSegmentsIntersection, getSignalsArray } from '../../../../src/pps' +import { getAuctionsIdsFromTargeting, getSegments, getSignals, getSignalsArrayByAuctionsIds, getSignalsIntersection } from '../../../../src/pps'; describe('pps', () => { let getAuctionStub; + const mockTargeting = {'/123456/header-bid-tag-0': {'hb_deal_rubicon': '1234', 'hb_deal': '1234', 'hb_pb': '0.53', 'hb_adid': '148018fe5e', 'hb_bidder': 'rubicon', 'foobar': '300x250', 'hb_pb_rubicon': '0.53', 'hb_adid_rubicon': '148018fe5e', 'hb_bidder_rubicon': 'rubicon', 'hb_deal_appnexus': '4321', 'hb_pb_appnexus': '0.1', 'hb_adid_appnexus': '567891011', 'hb_bidder_appnexus': 'appnexus'}} const mocksAuctions = [ { @@ -64,19 +65,52 @@ describe('pps', () => { }, ] - beforeEach(function () { + it('should parse segments from fpd', () => { + const twoSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 4); + expect(JSON.stringify(twoSegments)).to.equal(JSON.stringify(['1', '2'])) + const zeroSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 6); + expect(zeroSegments).to.length(0); + }) + + it('should return signals from fpd', () => { + const signals = getSignals(mocksAuctions[0].getFPD().global); + const expectedSignals = [{ taxonomy: 'IAB_AUDIENCE_1_1', values: ['1', '2'] }]; + expect(JSON.stringify(signals)).to.equal(JSON.stringify(expectedSignals)) + }) + + it('should properly get auctions ids from targeting', () => { + const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting); + expect(JSON.stringify(auctionsIds)).to.equal(JSON.stringify(['148018fe5e', '567891011'])) + }) + + it('should properly return empty array of auction ids for invalid targeting', () => { + let auctionsIds = getAuctionsIdsFromTargeting({}); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + auctionsIds = getAuctionsIdsFromTargeting({'/123456/header-bid-tag-0/bg': {'invalidContent': '123'}}); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + }) + + it('should properly get signals from auctions', () => { getAuctionStub = sinon.stub(auctionManager.index, 'getAuction').callsFake(({ auctionId }) => { return mocksAuctions.find(auction => auction.auctionId === auctionId) }); - }); - - afterEach(function () { + const signals = getSignalsArrayByAuctionsIds(['1111', '234234', '234324234']); + const intersection = getSignalsIntersection(signals) + const expectedResult = { IAB_AUDIENCE_1_1: { values: ['2'] }, IAB_CONTENT_2_2: { values: [] } }; + expect(JSON.stringify(intersection)).to.be.equal(JSON.stringify(expectedResult)); getAuctionStub.restore(); - }); + }) + + it('should return empty signals array for empty auctions ids array', () => { + const signals = getSignalsArrayByAuctionsIds([]); + expect(Array.isArray(signals)).to.equal(true); + expect(signals).to.length(0); + }) - it('should properly find intersection', () => { - const signals = getSignalsArray(['1111', '234234', '234324234']); - const expectedResult = { IAB_AUDIENCE_1_1:["2"], IAB_CONTENT_2_2:[]}; - expect(signals).to.be.equal(JSON.stringify(expectedResult)); + it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { + const signals = getSignalsIntersection([]); + expect(Object.keys(signals)).to.contain.members(['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']); }) }) From 116c3ab57ccc5aae38191d08cfa7b5b0d42d9277 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Tue, 9 Jul 2024 15:12:15 +0200 Subject: [PATCH 04/10] review changes --- modules/gptPreAuction.js | 61 ++++++++++- src/pps.js | 58 ---------- test/spec/modules/gptPreAuction_spec.js | 135 +++++++++++++++++++++++- test/spec/unit/core/pps_spec.js | 116 -------------------- 4 files changed, 194 insertions(+), 176 deletions(-) delete mode 100644 src/pps.js delete mode 100644 test/spec/unit/core/pps_spec.js diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 65b1bf24eef..3c2f8c13e6d 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -4,16 +4,75 @@ import { isGptPubadsDefined, logInfo, pick, - deepSetValue, logWarn + deepSetValue, logWarn, uniques } from '../src/utils.js'; import {config} from '../src/config.js'; import {getHook} from '../src/hook.js'; import {find} from '../src/polyfill.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { CLIENT_SECTIONS } from '../src/fpd/oneClient.js'; +import { TARGETING_KEYS } from '../src/constants.js'; const MODULE_NAME = 'GPT Pre-Auction'; export let _currentConfig = {}; let hooksAdded = false; +const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; + +export function getSegments(fpd, sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) +} + +export function getSignals(fpd) { + const signals = Object.entries({ + [taxonomies[0]]: getSegments(fpd, ['user.data'], 4), + [taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + return signals; +} + +export function getSignalsArrayByAuctionsIds(auctionIds, index = auctionManager.index) { + const signals = auctionIds + .map(auctionId => index.getAuction({ auctionId })?.getFPD()?.global) + .map(getSignals) + .filter(fpd => fpd); + + return signals; +} + +export function getSignalsIntersection(signals) { + const result = {}; + taxonomies.forEach((taxonomy) => { + const allValues = signals + .flatMap(x => x) + .filter(x => x.taxonomy === taxonomy) + .map(x => x.values); + result[taxonomy] = allValues.length ? ( + allValues.reduce((commonElements, subArray) => { + return commonElements.filter(element => subArray.includes(element)); + }) + ) : [] + result[taxonomy] = { values: result[taxonomy] }; + }) + return result; +} + +export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) { + return Object.values(targeting) + .flatMap(x => Object.entries(x)) + .filter((entry) => entry[0] === TARGETING_KEYS.AD_ID || entry[0].startsWith(TARGETING_KEYS.AD_ID + '_')) + .flatMap(entry => entry[1]) + .map(adId => am.findBidByAdId(adId).auctionId) + .filter(uniques); +} + export const appendGptSlots = adUnits => { const { customGptSlotMatching } = _currentConfig; diff --git a/src/pps.js b/src/pps.js deleted file mode 100644 index 352673b3a1a..00000000000 --- a/src/pps.js +++ /dev/null @@ -1,58 +0,0 @@ -import { auctionManager } from './auctionManager.js'; -import { TARGETING_KEYS } from './constants.js'; -import { CLIENT_SECTIONS } from './fpd/oneClient.js'; -import { deepAccess, uniques } from './utils.js'; - -export function getSegments(fpd, sections, segtax) { - return sections - .flatMap(section => deepAccess(fpd, section) || []) - .filter(datum => datum.ext?.segtax === segtax) - .flatMap(datum => datum.segment?.map(seg => seg.id)) - .filter(ob => ob) - .filter(uniques) -} - -export function getSignals(fpd) { - const signals = Object.entries({ - IAB_AUDIENCE_1_1: getSegments(fpd, ['user.data'], 4), - IAB_CONTENT_2_2: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) - }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) - .filter(ob => ob); - - return signals; -} - -export function getSignalsArrayByAuctionsIds(auctionIds) { - const signals = auctionIds - .map(auctionId => auctionManager.index.getAuction({ auctionId })?.getFPD()?.global) - .map(getSignals) - .filter(fpd => fpd); - - return signals; -} - -export function getSignalsIntersection(signals) { - const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; - const result = {}; - taxonomies.forEach((taxonomy) => { - const allValues = signals - .flatMap(x => x) - .filter(x => x.taxonomy === taxonomy) - .map(x => x.values); - result[taxonomy] = allValues.length ? ( - allValues.reduce((commonElements, subArray) => { - return commonElements.filter(element => subArray.includes(element)); - }) - ) : [] - result[taxonomy] = { values: result[taxonomy] }; - }) - return result; -} - -export function getAuctionsIdsFromTargeting(targeting) { - return Object.values(targeting) - .flatMap(x => Object.entries(x)) - .filter((entry) => (entry[0] || '').includes(TARGETING_KEYS.AD_ID)) - .flatMap(entry => entry[1]) - .filter(uniques); -} diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 5caa95404dc..37485e21c90 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -2,7 +2,12 @@ import { appendGptSlots, appendPbAdSlot, _currentConfig, - makeBidRequestsHook + makeBidRequestsHook, + getAuctionsIdsFromTargeting, + getSegments, + getSignals, + getSignalsArrayByAuctionsIds, + getSignalsIntersection } from 'modules/gptPreAuction.js'; import { config } from 'src/config.js'; import { makeSlot } from '../integration/faker/googletag.js'; @@ -25,6 +30,87 @@ describe('GPT pre-auction module', () => { makeSlot({ code: 'slotCode4', divId: 'div5' }) ]; + const mockTargeting = {'/123456/header-bid-tag-0': {'hb_deal_rubicon': '1234', 'hb_deal': '1234', 'hb_pb': '0.53', 'hb_adid': '148018fe5e', 'hb_bidder': 'rubicon', 'foobar': '300x250', 'hb_pb_rubicon': '0.53', 'hb_adid_rubicon': '148018fe5e', 'hb_bidder_rubicon': 'rubicon', 'hb_deal_appnexus': '4321', 'hb_pb_appnexus': '0.1', 'hb_adid_appnexus': '567891011', 'hb_bidder_appnexus': 'appnexus'}} + + const mockAuctionManager = { + findBidByAdId(adId) { + const bidsMap = { + '148018fe5e': { + auctionId: mocksAuctions[0].auctionId + }, + '567891011': { + auctionId: mocksAuctions[1].auctionId + }, + }; + return bidsMap[adId]; + }, + index: { + getAuction({ auctionId }) { + return mocksAuctions.find(auction => auction.auctionId === auctionId); + } + } + } + + const mocksAuctions = [ + { + auctionId: '1111', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }, { + id: '2' + }] + }], + } + } + }) + }, + { + auctionId: '234234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }] + }] + } + } + }), + }, { + auctionId: '234324234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }, { + id: '3' + }] + }] + } + } + }) + }, + ] + describe('appendPbAdSlot', () => { // sets up our document body to test the pbAdSlot dom actions against document.body.innerHTML = '
test1
' + @@ -454,4 +540,51 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); }); + + describe('pps gpt config', () => { + it('should parse segments from fpd', () => { + const twoSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 4); + expect(JSON.stringify(twoSegments)).to.equal(JSON.stringify(['1', '2'])); + const zeroSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 6); + expect(zeroSegments).to.length(0); + }); + + it('should return signals from fpd', () => { + const signals = getSignals(mocksAuctions[0].getFPD().global); + const expectedSignals = [{ taxonomy: 'IAB_AUDIENCE_1_1', values: ['1', '2'] }]; + expect(JSON.stringify(signals)).to.equal(JSON.stringify(expectedSignals)); + }); + + it('should properly get auctions ids from targeting', () => { + const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting, mockAuctionManager); + expect(JSON.stringify(auctionsIds)).to.equal(JSON.stringify([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId])) + }); + + it('should properly return empty array of auction ids for invalid targeting', () => { + let auctionsIds = getAuctionsIdsFromTargeting({}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + auctionsIds = getAuctionsIdsFromTargeting({'/123456/header-bid-tag-0/bg': {'invalidContent': '123'}}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + }); + + it('should properly get signals from auctions', () => { + const signals = getSignalsArrayByAuctionsIds(['1111', '234234', '234324234'], mockAuctionManager.index); + const intersection = getSignalsIntersection(signals); + const expectedResult = { IAB_AUDIENCE_1_1: { values: ['2'] }, IAB_CONTENT_2_2: { values: [] } }; + expect(JSON.stringify(intersection)).to.be.equal(JSON.stringify(expectedResult)); + }); + + it('should return empty signals array for empty auctions ids array', () => { + const signals = getSignalsArrayByAuctionsIds([], mockAuctionManager.index); + expect(Array.isArray(signals)).to.equal(true); + expect(signals).to.length(0); + }); + + it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { + const signals = getSignalsIntersection([]); + expect(Object.keys(signals)).to.contain.members(['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']); + }); + }); }); diff --git a/test/spec/unit/core/pps_spec.js b/test/spec/unit/core/pps_spec.js deleted file mode 100644 index d71c4369d87..00000000000 --- a/test/spec/unit/core/pps_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import { auctionManager } from '../../../../src/auctionManager'; -import { getAuctionsIdsFromTargeting, getSegments, getSignals, getSignalsArrayByAuctionsIds, getSignalsIntersection } from '../../../../src/pps'; - -describe('pps', () => { - let getAuctionStub; - const mockTargeting = {'/123456/header-bid-tag-0': {'hb_deal_rubicon': '1234', 'hb_deal': '1234', 'hb_pb': '0.53', 'hb_adid': '148018fe5e', 'hb_bidder': 'rubicon', 'foobar': '300x250', 'hb_pb_rubicon': '0.53', 'hb_adid_rubicon': '148018fe5e', 'hb_bidder_rubicon': 'rubicon', 'hb_deal_appnexus': '4321', 'hb_pb_appnexus': '0.1', 'hb_adid_appnexus': '567891011', 'hb_bidder_appnexus': 'appnexus'}} - - const mocksAuctions = [ - { - auctionId: '1111', - getFPD: () => ({ - global: { - user: { - data: [{ - name: 'dataprovider.com', - ext: { - segtax: 4 - }, - segment: [{ - id: '1' - }, { - id: '2' - }] - }], - } - } - }) - }, - { - auctionId: '234234', - getFPD: () => ({ - global: { - user: { - data: [{ - name: 'dataprovider.com', - ext: { - segtax: 4 - }, - segment: [{ - id: '2' - }] - }] - } - } - }), - }, { - auctionId: '234324234', - getFPD: () => ({ - global: { - user: { - data: [{ - name: 'dataprovider.com', - ext: { - segtax: 4 - }, - segment: [{ - id: '2' - }, { - id: '3' - }] - }] - } - } - }) - }, - ] - - it('should parse segments from fpd', () => { - const twoSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 4); - expect(JSON.stringify(twoSegments)).to.equal(JSON.stringify(['1', '2'])) - const zeroSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 6); - expect(zeroSegments).to.length(0); - }) - - it('should return signals from fpd', () => { - const signals = getSignals(mocksAuctions[0].getFPD().global); - const expectedSignals = [{ taxonomy: 'IAB_AUDIENCE_1_1', values: ['1', '2'] }]; - expect(JSON.stringify(signals)).to.equal(JSON.stringify(expectedSignals)) - }) - - it('should properly get auctions ids from targeting', () => { - const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting); - expect(JSON.stringify(auctionsIds)).to.equal(JSON.stringify(['148018fe5e', '567891011'])) - }) - - it('should properly return empty array of auction ids for invalid targeting', () => { - let auctionsIds = getAuctionsIdsFromTargeting({}); - expect(Array.isArray(auctionsIds)).to.equal(true); - expect(auctionsIds).to.length(0); - auctionsIds = getAuctionsIdsFromTargeting({'/123456/header-bid-tag-0/bg': {'invalidContent': '123'}}); - expect(Array.isArray(auctionsIds)).to.equal(true); - expect(auctionsIds).to.length(0); - }) - - it('should properly get signals from auctions', () => { - getAuctionStub = sinon.stub(auctionManager.index, 'getAuction').callsFake(({ auctionId }) => { - return mocksAuctions.find(auction => auction.auctionId === auctionId) - }); - const signals = getSignalsArrayByAuctionsIds(['1111', '234234', '234324234']); - const intersection = getSignalsIntersection(signals) - const expectedResult = { IAB_AUDIENCE_1_1: { values: ['2'] }, IAB_CONTENT_2_2: { values: [] } }; - expect(JSON.stringify(intersection)).to.be.equal(JSON.stringify(expectedResult)); - getAuctionStub.restore(); - }) - - it('should return empty signals array for empty auctions ids array', () => { - const signals = getSignalsArrayByAuctionsIds([]); - expect(Array.isArray(signals)).to.equal(true); - expect(signals).to.length(0); - }) - - it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { - const signals = getSignalsIntersection([]); - expect(Object.keys(signals)).to.contain.members(['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']); - }) -}) From 23af08e52d2bd8d9a8c70fea2d0af24f4c5a1b72 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 10 Jul 2024 16:06:12 +0200 Subject: [PATCH 05/10] module handling --- modules/gptPreAuction.js | 12 +++++++++++- src/targeting.js | 10 +++++----- test/spec/modules/gptPreAuction_spec.js | 5 +++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 3c2f8c13e6d..a4d4cebccea 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -17,7 +17,7 @@ const MODULE_NAME = 'GPT Pre-Auction'; export let _currentConfig = {}; let hooksAdded = false; -const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; +export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; export function getSegments(fpd, sections, segtax) { return sections @@ -212,6 +212,14 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { return fn.call(this, adUnits, ...args); }; +const setPPSConfigForGPT = (next, targetingSet) => { + // set gpt config + const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); + const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); + window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }}); + next(targetingSet); +}; + const handleSetGptConfig = moduleConfig => { _currentConfig = pick(moduleConfig, [ 'enabled', enabled => enabled !== false, @@ -225,12 +233,14 @@ const handleSetGptConfig = moduleConfig => { if (_currentConfig.enabled) { if (!hooksAdded) { getHook('makeBidRequests').before(makeBidRequestsHook); + getHook('getReadyTargetingSetForGPT').after(setPPSConfigForGPT) hooksAdded = true; } } else { logInfo(`${MODULE_NAME}: Turning off module`); _currentConfig = {}; getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + getHook('getReadyTargetingSetForGPT').getHooks({hook: setPPSConfigForGPT}).remove(); hooksAdded = false; } }; diff --git a/src/targeting.js b/src/targeting.js index f88de6652f8..36503227a9c 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -16,7 +16,6 @@ import { hook } from './hook.js'; import { ADPOD } from './mediaTypes.js'; import { NATIVE_TARGETING_KEYS } from './native.js'; import { find, includes } from './polyfill.js'; -import { getAuctionsIdsFromTargeting, getSignalsArrayByAuctionsIds, getSignalsIntersection } from './pps.js'; import { deepAccess, deepClone, @@ -462,15 +461,16 @@ export function newTargeting(auctionManager) { }); }); - // set gpt config - const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); - const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); - window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }}); + targeting.getReadyTargetingSetForGPT(targetingSet); // emit event events.emit(EVENTS.SET_TARGETING, targetingSet); }, 'setTargetingForGPT'); + targeting.getReadyTargetingSetForGPT = hook('sync', function (targetingSet) { + return targetingSet; + }, 'getReadyTargetingSetForGPT'); + /** * normlizes input to a `adUnit.code` array * @param {(string|string[])} adUnitCode [description] diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 37485e21c90..0031248f7ef 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -11,6 +11,7 @@ import { } from 'modules/gptPreAuction.js'; import { config } from 'src/config.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import { taxonomies } from '../../../modules/gptPreAuction.js'; describe('GPT pre-auction module', () => { let sandbox; @@ -551,7 +552,7 @@ describe('GPT pre-auction module', () => { it('should return signals from fpd', () => { const signals = getSignals(mocksAuctions[0].getFPD().global); - const expectedSignals = [{ taxonomy: 'IAB_AUDIENCE_1_1', values: ['1', '2'] }]; + const expectedSignals = [{ taxonomy: taxonomies[0], values: ['1', '2'] }]; expect(JSON.stringify(signals)).to.equal(JSON.stringify(expectedSignals)); }); @@ -584,7 +585,7 @@ describe('GPT pre-auction module', () => { it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { const signals = getSignalsIntersection([]); - expect(Object.keys(signals)).to.contain.members(['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']); + expect(Object.keys(signals)).to.contain.members(taxonomies); }); }); }); From 42aefd704ec5c8b85ec7c85491225dcb5d70942b Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 10 Jul 2024 18:25:53 +0200 Subject: [PATCH 06/10] code sharing --- libraries/gptUtils/gptUtils.js | 24 ++++++++++++++- modules/dfpAdServerVideo.js | 2 +- modules/gptPreAuction.js | 39 +++++++++---------------- src/targeting.js | 6 ++-- test/spec/modules/gptPreAuction_spec.js | 2 +- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js index 950f28c618f..25c1de03538 100644 --- a/libraries/gptUtils/gptUtils.js +++ b/libraries/gptUtils/gptUtils.js @@ -1,5 +1,6 @@ +import { CLIENT_SECTIONS } from '../../src/fpd/oneClient.js'; import {find} from '../../src/polyfill.js'; -import {compareCodeAndSlot, isGptPubadsDefined} from '../../src/utils.js'; +import {compareCodeAndSlot, deepAccess, isGptPubadsDefined, uniques} from '../../src/utils.js'; /** * Returns filter function to match adUnitCode in slot @@ -35,3 +36,24 @@ export function getGptSlotInfoForAdUnitCode(adUnitCode) { } return {}; } + +export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; + +export function getSignals(fpd) { + const signals = Object.entries({ + [taxonomies[0]]: getSegments(fpd, ['user.data'], 4), + [taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + return signals; +} + +export function getSegments(fpd, sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) +} diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 3d1fbfa0788..7d0c02ba78a 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -3,6 +3,7 @@ */ import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT } from '../libraries/dfpUtils/dfpUtils.js'; +import { getSignals } from '../libraries/gptUtils/gptUtils.js'; import { registerVideoSupport } from '../src/adServerManager.js'; import { gdprDataHandler } from '../src/adapterManager.js'; import { getPPID } from '../src/adserver.js'; @@ -11,7 +12,6 @@ import { config } from '../src/config.js'; import { EVENTS } from '../src/constants.js'; import * as events from '../src/events.js'; import { getHook } from '../src/hook.js'; -import { getSignals } from '../src/pps.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { targeting } from '../src/targeting.js'; import { diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index a4d4cebccea..e7e541b2161 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -1,41 +1,30 @@ +import { getSignals as getSignalsFn, getSegments as getSegmentsFn, taxonomies } from '../libraries/gptUtils/gptUtils.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { TARGETING_KEYS } from '../src/constants.js'; +import { getHook } from '../src/hook.js'; +import { find } from '../src/polyfill.js'; import { deepAccess, + deepSetValue, isAdUnitCodeMatchingSlot, isGptPubadsDefined, logInfo, + logWarn, pick, - deepSetValue, logWarn, uniques + uniques } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook} from '../src/hook.js'; -import {find} from '../src/polyfill.js'; -import { auctionManager } from '../src/auctionManager.js'; -import { CLIENT_SECTIONS } from '../src/fpd/oneClient.js'; -import { TARGETING_KEYS } from '../src/constants.js'; const MODULE_NAME = 'GPT Pre-Auction'; export let _currentConfig = {}; let hooksAdded = false; -export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; - export function getSegments(fpd, sections, segtax) { - return sections - .flatMap(section => deepAccess(fpd, section) || []) - .filter(datum => datum.ext?.segtax === segtax) - .flatMap(datum => datum.segment?.map(seg => seg.id)) - .filter(ob => ob) - .filter(uniques) + return getSegmentsFn(fpd, sections, segtax); } export function getSignals(fpd) { - const signals = Object.entries({ - [taxonomies[0]]: getSegments(fpd, ['user.data'], 4), - [taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) - }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) - .filter(ob => ob); - - return signals; + return getSignalsFn(fpd); } export function getSignalsArrayByAuctionsIds(auctionIds, index = auctionManager.index) { @@ -212,7 +201,7 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { return fn.call(this, adUnits, ...args); }; -const setPPSConfigForGPT = (next, targetingSet) => { +const getPpsConfigFromTargetingSet = (next, targetingSet) => { // set gpt config const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); @@ -233,14 +222,14 @@ const handleSetGptConfig = moduleConfig => { if (_currentConfig.enabled) { if (!hooksAdded) { getHook('makeBidRequests').before(makeBidRequestsHook); - getHook('getReadyTargetingSetForGPT').after(setPPSConfigForGPT) + getHook('targetingDone').after(getPpsConfigFromTargetingSet) hooksAdded = true; } } else { logInfo(`${MODULE_NAME}: Turning off module`); _currentConfig = {}; getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); - getHook('getReadyTargetingSetForGPT').getHooks({hook: setPPSConfigForGPT}).remove(); + getHook('targetingDone').getHooks({hook: getPpsConfigFromTargetingSet}).remove(); hooksAdded = false; } }; diff --git a/src/targeting.js b/src/targeting.js index 36503227a9c..0d50d749bb6 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -461,15 +461,15 @@ export function newTargeting(auctionManager) { }); }); - targeting.getReadyTargetingSetForGPT(targetingSet); + targeting.targetingDone(targetingSet); // emit event events.emit(EVENTS.SET_TARGETING, targetingSet); }, 'setTargetingForGPT'); - targeting.getReadyTargetingSetForGPT = hook('sync', function (targetingSet) { + targeting.targetingDone = hook('sync', function (targetingSet) { return targetingSet; - }, 'getReadyTargetingSetForGPT'); + }, 'targetingDone'); /** * normlizes input to a `adUnit.code` array diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 0031248f7ef..5889bd3094c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -11,7 +11,7 @@ import { } from 'modules/gptPreAuction.js'; import { config } from 'src/config.js'; import { makeSlot } from '../integration/faker/googletag.js'; -import { taxonomies } from '../../../modules/gptPreAuction.js'; +import { taxonomies } from '../../../libraries/gptUtils/gptUtils.js'; describe('GPT pre-auction module', () => { let sandbox; From fb9a5a1e8fd0caedf45d04e92d29318795d544cc Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Thu, 11 Jul 2024 15:29:00 +0200 Subject: [PATCH 07/10] linting fixes --- libraries/dfpUtils/dfpUtils.js | 7 +++++++ modules/dfpAdServerVideo.js | 8 ++------ modules/dfpAdpod.js | 8 ++------ src/targeting.js | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libraries/dfpUtils/dfpUtils.js b/libraries/dfpUtils/dfpUtils.js index 0f070b15ba2..d7df13824c7 100644 --- a/libraries/dfpUtils/dfpUtils.js +++ b/libraries/dfpUtils/dfpUtils.js @@ -11,3 +11,10 @@ export const DFP_ENDPOINT = { host: 'securepubads.g.doubleclick.net', pathname: '/gampad/ads' } + +export const setGdprConsent = (gdprConsent, queryParams) => { + if (!gdprConsent) { return; } + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } +} diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 7d0c02ba78a..367520870e3 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,7 +2,7 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT } from '../libraries/dfpUtils/dfpUtils.js'; +import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent } from '../libraries/dfpUtils/dfpUtils.js'; import { getSignals } from '../libraries/gptUtils/gptUtils.js'; import { registerVideoSupport } from '../src/adServerManager.js'; import { gdprDataHandler } from '../src/adapterManager.js'; @@ -114,11 +114,7 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } + setGdprConsent(gdprConsent, queryParams); if (!queryParams.ppid) { const ppid = getPPID(); diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js index a5bd48f60e4..1675954459c 100644 --- a/modules/dfpAdpod.js +++ b/modules/dfpAdpod.js @@ -1,7 +1,7 @@ import {submodule} from '../src/hook.js'; import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; -import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent} from '../libraries/dfpUtils/dfpUtils.js'; import {gdprDataHandler} from '../src/consentHandler.js'; import {registerVideoSupport} from '../src/adServerManager.js'; @@ -79,11 +79,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { ); const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } + setGdprConsent(gdprConsent, queryParams); const masterTag = buildUrl({ ...DFP_ENDPOINT, diff --git a/src/targeting.js b/src/targeting.js index 0d50d749bb6..9a2ea5d66fa 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -139,7 +139,7 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { * @param {Array} adUnitCodes * @param customSlotMatching * @param getSlots - * @return {{[p: string]: any}} + * @return {Object.} */ export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) { return getSlots().reduce((auToSlots, slot) => { From f99dbf7b1b326297d3df72e9ea9058eee1caba80 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 09:45:18 -0700 Subject: [PATCH 08/10] Rename setPPSConfig --- modules/gptPreAuction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index e7e541b2161..369f0237aab 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -201,7 +201,7 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { return fn.call(this, adUnits, ...args); }; -const getPpsConfigFromTargetingSet = (next, targetingSet) => { +const setPpsConfigFromTargetingSet = (next, targetingSet) => { // set gpt config const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); @@ -222,14 +222,14 @@ const handleSetGptConfig = moduleConfig => { if (_currentConfig.enabled) { if (!hooksAdded) { getHook('makeBidRequests').before(makeBidRequestsHook); - getHook('targetingDone').after(getPpsConfigFromTargetingSet) + getHook('targetingDone').after(setPpsConfigFromTargetingSet) hooksAdded = true; } } else { logInfo(`${MODULE_NAME}: Turning off module`); _currentConfig = {}; getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); - getHook('targetingDone').getHooks({hook: getPpsConfigFromTargetingSet}).remove(); + getHook('targetingDone').getHooks({hook: setPpsConfigFromTargetingSet}).remove(); hooksAdded = false; } }; From 9b390f8d10383a7e24c7651659150c935c3cfaf5 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 09:51:39 -0700 Subject: [PATCH 09/10] Filter out adIds that have no auction --- modules/gptPreAuction.js | 3 ++- test/spec/modules/gptPreAuction_spec.js | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 369f0237aab..29b9257d325 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -58,7 +58,8 @@ export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) { .flatMap(x => Object.entries(x)) .filter((entry) => entry[0] === TARGETING_KEYS.AD_ID || entry[0].startsWith(TARGETING_KEYS.AD_ID + '_')) .flatMap(entry => entry[1]) - .map(adId => am.findBidByAdId(adId).auctionId) + .map(adId => am.findBidByAdId(adId)?.auctionId) + .filter(id => id != null) .filter(uniques); } diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 5889bd3094c..55de0e5cc83 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -561,6 +561,14 @@ describe('GPT pre-auction module', () => { expect(JSON.stringify(auctionsIds)).to.equal(JSON.stringify([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId])) }); + it('should filter out adIds that do not map to any auction', () => { + const auctionsIds = getAuctionsIdsFromTargeting({ + ...mockTargeting, + 'au': {'hb_adid': 'missing'}, + }, mockAuctionManager); + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]); + }) + it('should properly return empty array of auction ids for invalid targeting', () => { let auctionsIds = getAuctionsIdsFromTargeting({}, mockAuctionManager); expect(Array.isArray(auctionsIds)).to.equal(true); From 0635c611cf6b5383f8400dcae39b8ef31f478bfc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 09:53:26 -0700 Subject: [PATCH 10/10] use eql instead of JSON for deep equals --- test/spec/modules/gptPreAuction_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 55de0e5cc83..88062f2b785 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -553,12 +553,12 @@ describe('GPT pre-auction module', () => { it('should return signals from fpd', () => { const signals = getSignals(mocksAuctions[0].getFPD().global); const expectedSignals = [{ taxonomy: taxonomies[0], values: ['1', '2'] }]; - expect(JSON.stringify(signals)).to.equal(JSON.stringify(expectedSignals)); + expect(signals).to.eql(expectedSignals); }); it('should properly get auctions ids from targeting', () => { const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting, mockAuctionManager); - expect(JSON.stringify(auctionsIds)).to.equal(JSON.stringify([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId])) + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]) }); it('should filter out adIds that do not map to any auction', () => {