From 1ee4d0aa2868b167c63209c331da93d887cd62c5 Mon Sep 17 00:00:00 2001 From: Mike Menetto Date: Fri, 31 Jul 2020 13:38:15 -0700 Subject: [PATCH 1/2] initial check-in: add ability to selectively allow default keys into GAM KV targeting. --- src/targeting.js | 47 +++++++++++++++++++++++++++ test/spec/unit/core/targeting_spec.js | 40 +++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/targeting.js b/src/targeting.js index 5873eeeb559..76c56808887 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -181,6 +181,48 @@ export function newTargeting(auctionManager) { return []; }; + /** + * Returns filtered ad server targeting for custom and allowed keys. + * @param {targetingArray} targeting + * @param {string[]} allowedKeys + * @return {targetingArray} filtered targeting + */ + function getAllowedTargetingKeyValues(targeting, allowedKeys) { + logInfo(`Allowed default targeting keys [ ${allowedKeys.join(', ')}]`); + const defaultKeyring = Object.assign({}, CONSTANTS.TARGETING_KEYS, CONSTANTS.NATIVE_KEYS); + const defaultKeys = Object.keys(defaultKeyring); + targeting.map(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + const keys = keyring.filter(kvPair => { + const key = Object.keys(kvPair)[0]; + // check if key is in default keys, if not, it's custom, we won't remove it. + const isCustom = defaultKeys.filter(defaultKey => key.indexOf(defaultKeyring[defaultKey]) === 0).length === 0; + // check if key explicitly allowed, if not, we'll remove it. + const found = isCustom || allowedKeys.find(allowedKey => { + const allowedKeyName = defaultKeyring[allowedKey]; + // we're looking to see if the key exactly starts with one of our default keys. + // (which hopefully means it's not custom) + const found = key.indexOf(allowedKeyName) === 0; + return found; + }); + const keyDisposition = found ? 'Keeping' : 'Removing'; + const keyType = isCustom ? 'custom' : 'default'; + logInfo(`${keyDisposition} ${keyType} targeting key ${key} for ${adUnitCode} adserverTargeting object`); + return found; + }); + adUnit[adUnitCode] = keys; + }); + const filteredTargeting = targeting.filter(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + const targetingObjectDisposition = keyring.length > 0 ? 'Keeping populated' : 'Removing empty'; + logInfo(`${targetingObjectDisposition} ${adUnitCode} adserverTargeting object`); + return keyring.length > 0; + }); + return filteredTargeting + } + /** * Returns all ad server targeting for all ad units. * @param {string=} adUnitCode @@ -206,6 +248,11 @@ export function newTargeting(auctionManager) { }); }); + const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys'); + if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { + targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); + } + targeting = flattenTargeting(targeting); const auctionKeysThreshold = config.getConfig('targetingControls.auctionKeyMaxChars'); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 3aae6e3c33a..0fd876fc048 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -416,6 +416,46 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.allowTargetingKeys', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting = { + hb_deal: '4321', + hb_pb: '0.1', + hb_adid: '567891011', + hb_bidder: 'appnexus', + }; + bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0.1; // losing bid so not included if enableSendAllBids === false + bid4.dealId = '4321'; + enableSendAllBids = true; + config.setConfig({ + targetingControls: { + allowTargetingKeys: ['BIDDER', 'AD_ID', 'PRICE_BUCKET'] + } + }); + bidsReceived.push(bid4); + }); + + it('targeting should include custom keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('foobar'); + }); + + it('targeting should include explicily listed default targeting keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_rubicon', 'hb_adid_rubicon', 'hb_pb_rubicon'); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); + }); + + it('targeting should not include unlisted default targeting keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.not.have.all.keys(['hb_deal_appnexus', 'hb_deal_rubicon']); + }); + }); + describe('targetingControls.alwaysIncludeDeals', function () { let bid4; From ea2acbf6b3ab3fd5a5f8d93c92195d20de0b4ea8 Mon Sep 17 00:00:00 2001 From: Mike Menetto Date: Mon, 28 Sep 2020 11:39:18 -0700 Subject: [PATCH 2/2] add more descriptive test documentation to explain that the default targeting keys is checking against the key prefix to accomodate bid landscape. collate and remove targeting surrounding the key removal process. --- src/targeting.js | 12 ++++++------ test/spec/unit/core/targeting_spec.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index 76c56808887..d440ad49b26 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -188,9 +188,10 @@ export function newTargeting(auctionManager) { * @return {targetingArray} filtered targeting */ function getAllowedTargetingKeyValues(targeting, allowedKeys) { - logInfo(`Allowed default targeting keys [ ${allowedKeys.join(', ')}]`); const defaultKeyring = Object.assign({}, CONSTANTS.TARGETING_KEYS, CONSTANTS.NATIVE_KEYS); const defaultKeys = Object.keys(defaultKeyring); + const keyDispositions = {}; + logInfo(`allowTargetingKeys - allowed keys [ ${allowedKeys.map(k => defaultKeyring[k]).join(', ')} ]`); targeting.map(adUnit => { const adUnitCode = Object.keys(adUnit)[0]; const keyring = adUnit[adUnitCode]; @@ -206,18 +207,17 @@ export function newTargeting(auctionManager) { const found = key.indexOf(allowedKeyName) === 0; return found; }); - const keyDisposition = found ? 'Keeping' : 'Removing'; - const keyType = isCustom ? 'custom' : 'default'; - logInfo(`${keyDisposition} ${keyType} targeting key ${key} for ${adUnitCode} adserverTargeting object`); + keyDispositions[key] = !found; return found; }); adUnit[adUnitCode] = keys; }); + const removedKeys = Object.keys(keyDispositions).filter(d => keyDispositions[d]); + logInfo(`allowTargetingKeys - removed keys [ ${removedKeys.join(', ')} ]`); + // remove any empty targeting objects, as they're unnecessary. const filteredTargeting = targeting.filter(adUnit => { const adUnitCode = Object.keys(adUnit)[0]; const keyring = adUnit[adUnitCode]; - const targetingObjectDisposition = keyring.length > 0 ? 'Keeping populated' : 'Removing empty'; - logInfo(`${targetingObjectDisposition} ${adUnitCode} adserverTargeting object`); return keyring.length > 0; }); return filteredTargeting diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 0fd876fc048..5d43ed48266 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -444,13 +444,13 @@ describe('targeting tests', function () { expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('foobar'); }); - it('targeting should include explicily listed default targeting keys', function () { + it('targeting should include keys prefixed by allowed default targeting keys', function () { const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_rubicon', 'hb_adid_rubicon', 'hb_pb_rubicon'); expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); }); - it('targeting should not include unlisted default targeting keys', function () { + it('targeting should not include keys prefixed by disallowed default targeting keys', function () { const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); expect(targeting['/123456/header-bid-tag-0']).to.not.have.all.keys(['hb_deal_appnexus', 'hb_deal_rubicon']); });