From d5d5249be71f7144be9285c80156d7bf331cf8cf Mon Sep 17 00:00:00 2001 From: Joshua Poritz Date: Thu, 19 Oct 2023 18:29:57 -0400 Subject: [PATCH 1/2] Log a warning on ad slot ID mismatch instead of throwing an unhandled exception --- modules/33acrossAnalyticsAdapter.js | 7 +++++ .../modules/33acrossAnalyticsAdapter_spec.js | 29 ++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index f22a604ce55..8f0b00cb01e 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -289,6 +289,12 @@ function subscribeToGamSlots() { window.googletag.pubads().addEventListener('slotRenderEnded', event => { setTimeout(() => { const { transactionId, auctionId } = getAdUnitMetadata(event.slot.getAdUnitPath()); + if (!transactionId || !auctionId) { + const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; + log.warn('Could not find configured ad unit matching GAM render of slot:', { slotName }); + return; + } + locals.transactionManagers[auctionId] && locals.transactionManagers[auctionId].complete(transactionId); }, POST_GAM_TIMEOUT); @@ -300,6 +306,7 @@ function getAdUnitMetadata(adUnitCode) { if (adUnitMeta && adUnitMeta.length > 0) { return adUnitMeta[adUnitMeta.length - 1]; } + return {}; } /** necessary for testing */ diff --git a/test/spec/modules/33acrossAnalyticsAdapter_spec.js b/test/spec/modules/33acrossAnalyticsAdapter_spec.js index d1aa9ed48f9..716010bf77e 100644 --- a/test/spec/modules/33acrossAnalyticsAdapter_spec.js +++ b/test/spec/modules/33acrossAnalyticsAdapter_spec.js @@ -476,7 +476,7 @@ describe('33acrossAnalyticsAdapter:', function () { assert.strictEqual(navigator.sendBeacon.callCount, 1); }); - it('does NOT sent a report if not all `slotRenderEnded` events have timed out', function () { + it('does NOT send a report if not all `slotRenderEnded` events have timed out', function () { const timeout = POST_GAM_TIMEOUT + 2000; this.enableAnalytics({ timeout }); @@ -487,6 +487,26 @@ describe('33acrossAnalyticsAdapter:', function () { }); }); + context('and the `slotRenderEnded` event has fired for an unknown slot code', function () { + it('logs a warning message', function () { + this.enableAnalytics(); + + const { prebid: [auction], gam } = getMockEvents(); + auction.AUCTION_INIT.adUnits[0].code = 'INVALID_AD_UNIT_CODE'; + + const slotRenderEnded = gam.slotRenderEnded[0]; + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + mockGpt.emitEvent('slotRenderEnded', slotRenderEnded); + + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + + assert.calledWithExactly(log.warn, + 'Could not find configured ad unit matching GAM render of slot:', + { slotName: `${adUnitCodes[0]} - ${adSlotElementIds[0]}` }); + }); + }); + context('and the incomplete report has been sent successfully', function () { it('sends a report string with any bids with rendered status set to hasWon: 1', function () { navigator.sendBeacon.returns(true); @@ -757,6 +777,7 @@ function getLocalAssert() { }; const adUnitCodes = ['/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', '/17118521/header-bid-tag-2']; +const adSlotElementIds = ['ad-slot-div-0', 'ad-slot-div-1', 'ad-slot-div-2']; function createReportWithThreeBidWonEvents() { return { pid: 'test-pid', @@ -847,7 +868,7 @@ function getMockEvents() { slotRenderEnded: [ { serviceName: 'publisher_ads', - slot: mockGpt.makeSlot({ code: adUnitCodes[0] }), + slot: mockGpt.makeSlot({ code: adUnitCodes[0], divId: adSlotElementIds[0] }), isEmpty: true, slotContentChanged: true, size: null, @@ -861,7 +882,7 @@ function getMockEvents() { }, { serviceName: 'publisher_ads', - slot: mockGpt.makeSlot({ code: adUnitCodes[1] }), + slot: mockGpt.makeSlot({ code: adUnitCodes[1], divId: adSlotElementIds[1] }), isEmpty: false, slotContentChanged: true, size: [1, 1], @@ -877,7 +898,7 @@ function getMockEvents() { }, { serviceName: 'publisher_ads', - slot: mockGpt.makeSlot({ code: adUnitCodes[2] }), + slot: mockGpt.makeSlot({ code: adUnitCodes[2], divId: adSlotElementIds[2] }), isEmpty: false, slotContentChanged: true, size: [728, 90], From bc351988c759fc1b4e49393bc8da0745fe51b44c Mon Sep 17 00:00:00 2001 From: Joshua Poritz Date: Fri, 20 Oct 2023 14:53:07 -0400 Subject: [PATCH 2/2] 33Across Analytics: Allow slots to be by configured by element ID instead of GAM ad unit code. Resolves IDG-1163. --- modules/33acrossAnalyticsAdapter.js | 7 ++-- .../modules/33acrossAnalyticsAdapter_spec.js | 39 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index 8f0b00cb01e..e3539906b13 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -288,7 +288,8 @@ function calculateTransactionTimeout(configTimeout = DEFAULT_TRANSACTION_TIMEOUT function subscribeToGamSlots() { window.googletag.pubads().addEventListener('slotRenderEnded', event => { setTimeout(() => { - const { transactionId, auctionId } = getAdUnitMetadata(event.slot.getAdUnitPath()); + const { transactionId, auctionId } = + getAdUnitMetadata(event.slot.getAdUnitPath(), event.slot.getSlotElementId()); if (!transactionId || !auctionId) { const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; log.warn('Could not find configured ad unit matching GAM render of slot:', { slotName }); @@ -301,8 +302,8 @@ function subscribeToGamSlots() { }); } -function getAdUnitMetadata(adUnitCode) { - const adUnitMeta = locals.adUnitMap[adUnitCode]; +function getAdUnitMetadata(adUnitPath, adSlotElementId) { + const adUnitMeta = locals.adUnitMap[adUnitPath] || locals.adUnitMap[adSlotElementId]; if (adUnitMeta && adUnitMeta.length > 0) { return adUnitMeta[adUnitMeta.length - 1]; } diff --git a/test/spec/modules/33acrossAnalyticsAdapter_spec.js b/test/spec/modules/33acrossAnalyticsAdapter_spec.js index 716010bf77e..731fdc989f6 100644 --- a/test/spec/modules/33acrossAnalyticsAdapter_spec.js +++ b/test/spec/modules/33acrossAnalyticsAdapter_spec.js @@ -466,14 +466,28 @@ describe('33acrossAnalyticsAdapter:', function () { }); context('and the `slotRenderEnded` event fired for all bids, but not all bids have won', function () { - it('sends a report after the all `slotRenderEnded` events have fired and timed out', function () { - const timeout = POST_GAM_TIMEOUT + 2000; - this.enableAnalytics({ timeout }); + context('and the GAM slot IDs are configured as the ad unit codes', function () { + it('sends a report after the all `slotRenderEnded` events have fired and timed out', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({ timeout }); - performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); - sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); - assert.strictEqual(navigator.sendBeacon.callCount, 1); + assert.strictEqual(navigator.sendBeacon.callCount, 1); + }); + }); + + context('and the slot element IDs are configured as the ad unit codes', function () { + it('sends a report after the all `slotRenderEnded` events have fired and timed out', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({ timeout }); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd'], useSlotElementIds: true}); + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + + assert.strictEqual(navigator.sendBeacon.callCount, 1); + }); }); it('does NOT send a report if not all `slotRenderEnded` events have timed out', function () { @@ -627,12 +641,21 @@ describe('33acrossAnalyticsAdapter:', function () { }); }); -function performStandardAuction({ exclude = [] } = {}) { +const adUnitCodes = ['/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', '/17118521/header-bid-tag-2']; +const adSlotElementIds = ['ad-slot-div-0', 'ad-slot-div-1', 'ad-slot-div-2']; + +function performStandardAuction({ exclude = [], useSlotElementIds = false } = {}) { const mockEvents = getMockEvents(); const { prebid, gam } = mockEvents; const [auction] = prebid; if (!exclude.includes(EVENTS.AUCTION_INIT)) { + if (useSlotElementIds) { + // With this option, identify the ad units by slot element IDs instead of GAM paths + auction.AUCTION_INIT.adUnits.forEach((adUnit, i) => { + adUnit.code = adSlotElementIds[i]; + }); + } events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); } @@ -776,8 +799,6 @@ function getLocalAssert() { } }; -const adUnitCodes = ['/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', '/17118521/header-bid-tag-2']; -const adSlotElementIds = ['ad-slot-div-0', 'ad-slot-div-1', 'ad-slot-div-2']; function createReportWithThreeBidWonEvents() { return { pid: 'test-pid',