Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions lib/addons/prototypes/ab-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable no-param-reassign */

// Fills missing trafficPercentage values by distributing remaining % evenly across
// tests that don't specify one.
export function calculateTrafficPercentages(abTests) {
const allocated = abTests
.filter((t) => t.trafficPercentage !== undefined)
.reduce((sum, t) => sum + t.trafficPercentage, 0);
const without = abTests.filter((t) => t.trafficPercentage === undefined);
if (without.length > 0) {
const even = (100 - allocated) / without.length;
without.forEach((t) => {
t.trafficPercentage = even;
});
}
return abTests;
}

// Weighted random bucket selection across tests with assigned trafficPercentage values.
// Mirrors determineABTest in lib/edge/targeting.ts.
export function determineABTest(abTests) {
if (!abTests || !abTests.length) return null;
const total = abTests.reduce((s, t) => s + t.trafficPercentage, 0);
if (total > 100) return null;
const bucket = Math.floor(Math.random() * 100);
let cumulative = 0;
for (const test of abTests) {
cumulative += test.trafficPercentage;
if (bucket < cumulative) return test;
}
return null;
}

// Select an A/B test, applying sessionStorage forced-include/exclude overrides before
// falling back to weighted random selection. Persists the result to OPTABLE_SPLIT_TEST.
//
// Override keys (value "1" or "true" to activate):
// optableInclude<ID> — force this test
// optableResolve<ID> — force this test (alias)
// optableExclude<ID> — exclude this test from random selection
// optableInclude<ID>=false/"0" — also excludes this test
export function selectABTest(abTests) {
if (!abTests || !abTests.length) return null;

const tests = calculateTrafficPercentages([...abTests]);
const trueVals = ["1", "true"];
const falseVals = ["0", "false"];

// Forced inclusion wins — first matching test is selected immediately.
for (const test of tests) {
const key = test.id.toUpperCase();
const forced =
trueVals.includes(sessionStorage.getItem(`optableInclude${key}`)) ||
trueVals.includes(sessionStorage.getItem(`optableResolve${key}`));
if (forced) {
const selected = { ...test, trafficPercentage: 100 };
sessionStorage.setItem("OPTABLE_SPLIT_TEST", JSON.stringify(selected));
return selected;
}
}

// Filter out excluded tests before random selection.
const eligible = tests.filter((test) => {
const key = test.id.toUpperCase();
return (
!trueVals.includes(sessionStorage.getItem(`optableExclude${key}`)) &&
!falseVals.includes(sessionStorage.getItem(`optableInclude${key}`))
);
});

const selected = determineABTest(eligible.length ? eligible : tests);
if (selected) {
const pinned = { ...selected, trafficPercentage: 100 };
sessionStorage.setItem("OPTABLE_SPLIT_TEST", JSON.stringify(pinned));
return pinned;
}
return null;
}
6 changes: 6 additions & 0 deletions lib/addons/prototypes/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class OptablePrebidAnalytics {
this.optableInstance = optableInstance;
this.isInitialized = true;

this.selectedTest = config.selectedTest ?? null;

// Store auction data
this.auctions = {};
this.maxAuctionDataSize = 20;
Expand Down Expand Up @@ -57,6 +59,8 @@ class OptablePrebidAnalytics {
}

setHooks(pbjs) {
this.optableProvider = pbjs?.getConfig?.()?.realTimeData?.dataProviders?.find((p) => p.name === "optable") ?? null;

this.log("Processing missed auctionEnd");
pbjs.getEvents().forEach((event) => {
if (event.eventType === "auctionEnd") {
Expand Down Expand Up @@ -279,6 +283,8 @@ class OptablePrebidAnalytics {
tenant: this.config.tenant,
optableWrapperVersion: SDK_WRAPPER_VERSION, // eslint-disable-line no-undef
prebidjsVersion: this.prebidInstance?.version || "unknown",
optableProvider: this.optableProvider ?? null,
selectedABTest: this.selectedTest?.id ?? null,
sessionDepth: sessionStorage?.optableSessionDepth || 1,
pageAuctionsCount: window.optable?.pageAuctionsCount || 1,
};
Expand Down
Loading