From 789ea04b809a516e44c3070867b9e8e6167baa68 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Laroche Date: Thu, 16 Jul 2026 17:34:29 -0400 Subject: [PATCH] Get splitTestAssignment from SDK bundle, if provided and doesn't override if splitTestAssignment is already set (in case it is set server-side) --- lib/addons/prebid/analytics.test.ts | 70 +++++++++++++++++++++++++++++ lib/addons/prebid/analytics.ts | 14 ++++++ lib/addons/prototypes/analytics.js | 13 ++++++ 3 files changed, 97 insertions(+) diff --git a/lib/addons/prebid/analytics.test.ts b/lib/addons/prebid/analytics.test.ts index c63b9a2..865758a 100644 --- a/lib/addons/prebid/analytics.test.ts +++ b/lib/addons/prebid/analytics.test.ts @@ -322,6 +322,76 @@ describe("OptablePrebidAnalytics", () => { }); }); + describe("trackAuctionEnd - split test assignment", () => { + function makeEvent(bids: any[]) { + return { + auctionId: "auction-split-test", + timeout: 3000, + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { site: { domain: "example.com" }, user: { eids: [] } }, + bids, + }, + ], + bidsReceived: [], + noBids: [], + timeoutBids: [], + }; + } + + it("does nothing when getSplitTestAssignment is not configured", async () => { + analytics = new OptablePrebidAnalytics(mockOptableInstance); + const bid = { bidId: "bid-1" }; + await analytics.trackAuctionEnd(makeEvent([bid])); + + expect(bid).not.toHaveProperty("ortb2Imp"); + }); + + it("does nothing when getSplitTestAssignment returns undefined", async () => { + analytics = new OptablePrebidAnalytics(mockOptableInstance, { + getSplitTestAssignment: () => undefined, + }); + const bid = { bidId: "bid-1" }; + await analytics.trackAuctionEnd(makeEvent([bid])); + + expect(bid).not.toHaveProperty("ortb2Imp"); + }); + + it("tags every bid's ortb2Imp.ext.optable.splitTestAssignment", async () => { + analytics = new OptablePrebidAnalytics(mockOptableInstance, { + getSplitTestAssignment: () => "all", + }); + const bidA: any = { bidId: "bid-1" }; + const bidB: any = { bidId: "bid-2" }; + await analytics.trackAuctionEnd(makeEvent([bidA, bidB])); + + expect(bidA.ortb2Imp.ext.optable.splitTestAssignment).toBe("all"); + expect(bidB.ortb2Imp.ext.optable.splitTestAssignment).toBe("all"); + }); + + it("does not override an already-set splitTestAssignment", async () => { + analytics = new OptablePrebidAnalytics(mockOptableInstance, { + getSplitTestAssignment: () => "all", + }); + const bid: any = { bidId: "bid-1", ortb2Imp: { ext: { optable: { splitTestAssignment: "control" } } } }; + await analytics.trackAuctionEnd(makeEvent([bid])); + + expect(bid.ortb2Imp.ext.optable.splitTestAssignment).toBe("control"); + }); + + it("preserves other fields already present on ortb2Imp.ext.optable", async () => { + analytics = new OptablePrebidAnalytics(mockOptableInstance, { + getSplitTestAssignment: () => "all", + }); + const bid: any = { bidId: "bid-1", ortb2Imp: { ext: { optable: { foo: "bar" } } } }; + await analytics.trackAuctionEnd(makeEvent([bid])); + + expect(bid.ortb2Imp.ext.optable).toEqual({ foo: "bar", splitTestAssignment: "all" }); + }); + }); + describe("trackBidWon", () => { beforeEach(() => { jest.useFakeTimers(); diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index f03aad3..7e6a796 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -31,6 +31,7 @@ interface OptablePrebidAnalyticsConfig { samplingSeed?: string; samplingRate?: number; samplingRateFn?: () => boolean; + getSplitTestAssignment?: () => string | undefined; } interface AuctionItem { @@ -227,6 +228,19 @@ class OptablePrebidAnalytics { this.log(`Processing auction ${auctionId} with ${bidderRequests.length} bidder requests`); + const splitTestAssignment = this.config.getSplitTestAssignment?.(); + if (splitTestAssignment) { + bidderRequests.forEach((br: any) => { + (br.bids || []).forEach((b: any) => { + if (b.ortb2Imp?.ext?.optable?.splitTestAssignment) return; + b.ortb2Imp = b.ortb2Imp || {}; + b.ortb2Imp.ext = b.ortb2Imp.ext || {}; + b.ortb2Imp.ext.optable = b.ortb2Imp.ext.optable || {}; + b.ortb2Imp.ext.optable.splitTestAssignment = splitTestAssignment; + }); + }); + } + (window as any).optable = (window as any).optable || {}; (window as any).optable.pageAuctionsCount = (Number((window as any).optable.pageAuctionsCount) || 0) + 1; diff --git a/lib/addons/prototypes/analytics.js b/lib/addons/prototypes/analytics.js index fbdf4f1..b702164 100644 --- a/lib/addons/prototypes/analytics.js +++ b/lib/addons/prototypes/analytics.js @@ -107,6 +107,19 @@ class OptablePrebidAnalytics { this.log(`Processing auction ${auctionId} with ${bidderRequests.length} bidder requests`); + const splitTestAssignment = this.config.getSplitTestAssignment?.(); + if (splitTestAssignment) { + bidderRequests.forEach((br) => { + (br.bids || []).forEach((b) => { + if (b.ortb2Imp?.ext?.optable?.splitTestAssignment) return; + b.ortb2Imp = b.ortb2Imp || {}; + b.ortb2Imp.ext = b.ortb2Imp.ext || {}; + b.ortb2Imp.ext.optable = b.ortb2Imp.ext.optable || {}; + b.ortb2Imp.ext.optable.splitTestAssignment = splitTestAssignment; + }); + }); + } + // Build auction object with bidder requests and EID flags const auction = { auctionId,