From 1f92dd4f0fdbe719b3e365cf871688b399f98848 Mon Sep 17 00:00:00 2001 From: mosherBT Date: Fri, 20 Feb 2026 12:56:37 -0500 Subject: [PATCH 1/3] SplitTestVariant in analytics module --- lib/addons/prebid/analytics.test.ts | 113 ++++++++++++++++++++++++++++ lib/addons/prebid/analytics.ts | 27 ++++++- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/lib/addons/prebid/analytics.test.ts b/lib/addons/prebid/analytics.test.ts index 284207c..a28749e 100644 --- a/lib/addons/prebid/analytics.test.ts +++ b/lib/addons/prebid/analytics.test.ts @@ -684,6 +684,119 @@ describe("OptablePrebidAnalytics", () => { expect(storedAuction).toBeDefined(); }); + it("should extract splitTestAssignment from bidsReceived", async () => { + const event = { + auctionId: "auction-split-test", + timeout: 3000, + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { + site: { domain: "example.com" }, + user: { + eids: [ + { + inserter: "optable.co", + matcher: "matcher1", + source: "source1", + }, + ], + }, + }, + bids: [ + { + bidId: "bid-1", + adUnitCode: "ad-unit-1", + adUnitId: "ad-id-1", + transactionId: "trans-1", + src: "client", + }, + ], + }, + ], + bidsReceived: [ + { + requestId: "bid-1", + cpm: 1.5, + width: 300, + height: 250, + currency: "USD", + adUnitCode: "ad-unit-1", + ortb2Imp: { + ext: { + optable: { + splitTestAssignment: "treatment", + }, + }, + }, + }, + ], + noBids: [], + timeoutBids: [], + }; + + await analytics.trackAuctionEnd(event); + + // Get the stored auction and check the bid has splitTestAssignment + const storedAuction = analytics["auctions"].get("auction-split-test"); + expect(storedAuction).toBeDefined(); + + // The splitTestAssignment should be extracted and merged in toWitness + const payload = await analytics.toWitness(storedAuction.auctionEnd, null); + expect(payload.bidderRequests[0].bids[0].splitTestAssignment).toBe("treatment"); + }); + + it("should handle missing splitTestAssignment gracefully", async () => { + const event = { + auctionId: "auction-no-split-test", + timeout: 3000, + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { + site: { domain: "example.com" }, + user: { + eids: [], + }, + }, + bids: [ + { + bidId: "bid-1", + adUnitCode: "ad-unit-1", + adUnitId: "ad-id-1", + transactionId: "trans-1", + src: "client", + }, + ], + }, + ], + bidsReceived: [ + { + requestId: "bid-1", + cpm: 1.5, + width: 300, + height: 250, + currency: "USD", + adUnitCode: "ad-unit-1", + // No ortb2Imp field + }, + ], + noBids: [], + timeoutBids: [], + }; + + await analytics.trackAuctionEnd(event); + + // Should not throw error and splitTestAssignment should be undefined + const storedAuction = analytics["auctions"].get("auction-no-split-test"); + expect(storedAuction).toBeDefined(); + + const payload = await analytics.toWitness(storedAuction.auctionEnd, null); + expect(payload.bidderRequests[0].bids[0].splitTestAssignment).toBeUndefined(); + }); + it("should handle noBids and update status", async () => { const event = { auctionId: "auction-no-bids", diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index 22a8d2c..f358579 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -244,6 +244,7 @@ class OptablePrebidAnalytics { transactionId: string; src: string; floorData?: { floorMin: number }; + ortb2Imp?: { ext?: { optable?: { splitTestAssignment?: string } } }; }) => ({ bidId: b.bidId, bidderRequestId, @@ -252,6 +253,7 @@ class OptablePrebidAnalytics { transactionId: b.transactionId, src: b.src, floorMin: b.floorData?.floorMin, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, status: STATUS.REQUESTED, }) ), @@ -291,6 +293,7 @@ class OptablePrebidAnalytics { cpm: b.cpm, size: `${b.width}x${b.height}`, currency: b.currency, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, }); } else { // Create new bid object for this response @@ -305,6 +308,7 @@ class OptablePrebidAnalytics { size: `${b.width}x${b.height}`, currency: b.currency, status: STATUS.RECEIVED, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, }; br.bids.push(bidObj); bidIndex[bidId] = bidObj; @@ -349,7 +353,7 @@ class OptablePrebidAnalytics { this.sendToWitnessAPI("optable.prebid.auction", payload); }, this.config.bidWinTimeout); - // Store the processed auction + // Store the auction data this.auctions.set(auctionId, { auctionEnd: event, createdAt, missed, auctionEndTimeoutId }); // Clean up old auctions @@ -433,6 +437,12 @@ class OptablePrebidAnalytics { let totalBids = 0; let device = null; + // Extract device from first bidder request if available + if (bidderRequests.length > 0) { + device = bidderRequests[0].ortb2?.device; + } + + // Process bidder requests const requests = bidderRequests.map((br: any) => { const { bidderCode, bidderRequestId, bids = [] } = br; const domain = br.ortb2.site?.domain ?? "unknown"; @@ -443,8 +453,6 @@ class OptablePrebidAnalytics { const optableMatchers = [...new Set(optableEIDS.map((e: any) => e.matcher).filter(Boolean))]; const optableSources = [...new Set(optableEIDS.map((e: any) => e.source).filter(Boolean))]; - device = br.ortb2.device; - return { bidderCode, bidderRequestId, @@ -461,6 +469,7 @@ class OptablePrebidAnalytics { transactionId: string; src: string; floorData?: { floorMin: number }; + ortb2Imp?: { ext?: { optable?: { splitTestAssignment?: string } } }; }) => ({ bidId: b.bidId, bidderRequestId, @@ -469,12 +478,24 @@ class OptablePrebidAnalytics { transactionId: b.transactionId, src: b.src, floorMin: b.floorData?.floorMin, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, status: STATUS.REQUESTED, }) ), }; }); + // Merge splitTestAssignment from bidsReceived into the requests + const bidsReceivedMap = new Map(bidsReceived.map((b: any) => [b.requestId, b])); + requests.forEach((request: any) => { + request.bids.forEach((bid: any) => { + const bidReceived = bidsReceivedMap.get(bid.bidId); + if (bidReceived?.ortb2Imp?.ext?.optable?.splitTestAssignment) { + bid.splitTestAssignment = bidReceived.ortb2Imp.ext.optable.splitTestAssignment; + } + }); + }); + const witnessData: WitnessProperties = { bidderRequests: requests.map((br: any) => { br.optableMatchers.forEach((m: unknown) => oMatchersSet.add(m)); From cd3bf54dfde2422093e4a64d40ada5b61bc79ccb Mon Sep 17 00:00:00 2001 From: mosherBT Date: Mon, 2 Mar 2026 11:24:13 -0500 Subject: [PATCH 2/3] revert --- lib/addons/prebid/analytics.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index f358579..af9ac09 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -437,11 +437,6 @@ class OptablePrebidAnalytics { let totalBids = 0; let device = null; - // Extract device from first bidder request if available - if (bidderRequests.length > 0) { - device = bidderRequests[0].ortb2?.device; - } - // Process bidder requests const requests = bidderRequests.map((br: any) => { const { bidderCode, bidderRequestId, bids = [] } = br; @@ -453,6 +448,8 @@ class OptablePrebidAnalytics { const optableMatchers = [...new Set(optableEIDS.map((e: any) => e.matcher).filter(Boolean))]; const optableSources = [...new Set(optableEIDS.map((e: any) => e.source).filter(Boolean))]; + device = br.ortb2.device; + return { bidderCode, bidderRequestId, From 1e5004a2afee398c9c29c2f6b45ebea627e3a452 Mon Sep 17 00:00:00 2001 From: mosherBT Date: Mon, 2 Mar 2026 11:28:16 -0500 Subject: [PATCH 3/3] other analytics file --- lib/addons/prebid/analytics.ts | 2 +- lib/addons/prototypes/analytics.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index af9ac09..cb9e6c5 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -483,7 +483,7 @@ class OptablePrebidAnalytics { }); // Merge splitTestAssignment from bidsReceived into the requests - const bidsReceivedMap = new Map(bidsReceived.map((b: any) => [b.requestId, b])); + const bidsReceivedMap = new Map(bidsReceived.map((b: any) => [b.requestId, b])); requests.forEach((request: any) => { request.bids.forEach((bid: any) => { const bidReceived = bidsReceivedMap.get(bid.bidId); diff --git a/lib/addons/prototypes/analytics.js b/lib/addons/prototypes/analytics.js index 91d02dd..e4cfb5e 100644 --- a/lib/addons/prototypes/analytics.js +++ b/lib/addons/prototypes/analytics.js @@ -141,6 +141,7 @@ class OptablePrebidAnalytics { transactionId: b.transactionId, src: b.src, floorMin: b.floorData?.floorMin, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, status: STATUS.REQUESTED, })), }; @@ -178,6 +179,7 @@ class OptablePrebidAnalytics { cpm: b.cpm, size: `${b.width}x${b.height}`, currency: b.currency, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, }); } else { // Create new bid object for this response @@ -192,6 +194,7 @@ class OptablePrebidAnalytics { size: `${b.width}x${b.height}`, currency: b.currency, status: STATUS.RECEIVED, + splitTestAssignment: b.ortb2Imp?.ext?.optable?.splitTestAssignment, }; br.bids.push(bidObj); bidIndex[bidId] = bidObj; @@ -255,6 +258,7 @@ class OptablePrebidAnalytics { cpm: b.cpm, size: b.size, bidId: b.bidId, + splitTestAssignment: b.splitTestAssignment, }; }), };