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']); - }) -})