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
18 changes: 18 additions & 0 deletions lib/addons/abTestAssignment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ describe("setupAB - override via flags", () => {
expect(result.variant.id).toBe("production");
expect(result.isControl).toBe(false);
});

it("forces treatment when optableDebug flag is set", () => {
sessionStorage.setItem("optableDebug", "1");
resetFlags();
jest.spyOn(Math, "random").mockReturnValue(0.97); // would normally land in control
const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] });
expect(result.variant.id).toBe("production");
expect(result.isControl).toBe(false);
});

it("optableControlGroup=1 takes priority over optableDebug", () => {
sessionStorage.setItem("optableDebug", "1");
sessionStorage.setItem("optableControlGroup", "1");
resetFlags();
const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] });
expect(result.variant.id).toBe("test");
expect(result.isControl).toBe(true);
});
});

describe("setupAB - custom variant ids", () => {
Expand Down
5 changes: 3 additions & 2 deletions lib/addons/abTestAssignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ export function setupAB(config: SetupABConfig): ABTestSetupResult {
// Priority 1 — QA/debug override via URL param or sessionStorage flag.
// ?optableControlGroup=1 forces the control variant; =0 forces treatment.
// This lets QA verify both branches without clearing localStorage.
const controlGroupFlag = getFlags().optableControlGroup;
const flags = getFlags();
const controlGroupFlag = flags.optableControlGroup;
if (controlGroupFlag === "1") {
selected = filled.find((v) => v.id === controlId) ?? { id: controlId, trafficPercentage: 0 };
} else if (controlGroupFlag === "0") {
} else if (controlGroupFlag === "0" || flags.optableDebug) {
selected = filled.find((v) => v.id === treatmentId) ?? { id: treatmentId, trafficPercentage: 0 };
}

Expand Down
10 changes: 10 additions & 0 deletions lib/addons/prebid/analytics.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import OptablePrebidAnalytics, { initPrebidAnalytics } from "./analytics";
import type OptableSDK from "../../sdk";
import { resetFlags } from "../../core/flags";

// Mock the SDK_WRAPPER_VERSION global
declare global {
Expand Down Expand Up @@ -31,6 +32,8 @@ describe("OptablePrebidAnalytics", () => {
document.removeEventListener("visibilitychange", (analytics as any).handleVisibilityChange);
}
jest.clearAllMocks();
sessionStorage.clear();
resetFlags();
});

describe("Class instantiation", () => {
Expand Down Expand Up @@ -147,6 +150,13 @@ describe("OptablePrebidAnalytics", () => {

mockRandom.mockRestore();
});

it("should return true when optableDebug flag is set, even with samplingRate 0", () => {
sessionStorage.setItem("optableDebug", "1");
resetFlags();
analytics = new OptablePrebidAnalytics(mockOptableInstance, { samplingRate: 0 });
expect(analytics.shouldSample()).toBe(true);
});
});

describe("toWitness", () => {
Expand Down
2 changes: 2 additions & 0 deletions lib/addons/prebid/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { WitnessProperties } from "../../edge/witness";
import type OptableSDK from "../../sdk";
import { buildRequest } from "../../core/network";
import { getFlags } from "../../core/flags";

import * as Bowser from "bowser";

Expand Down Expand Up @@ -137,6 +138,7 @@ class OptablePrebidAnalytics {
* @returns true if the event should be sampled and analytics calls may proceed.
*/
shouldSample(): boolean {
if (getFlags().optableDebug) return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd bind this to optableControlGroup instead of optableDebug.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has both controlGroup and the sample rate tied to debug with the assumption that when you are debugging you don't want activity hidden by sampling.

Most of the time the first question is did x happen. If id does not happen because of some sampling issue, debugging becomes non-intuitive until you realize sampling is the issue, and then you have to figure out how to turn sampling off.

Is there a use case where logging + sampling + control + turning off bot detection this would present a problem?

If so we could use a different parameter than optableDebug.

if (this.config.samplingRate! <= 0) return false;
if (this.config.samplingRate! >= 1) return true;

Expand Down
2 changes: 2 additions & 0 deletions lib/edge/targeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { determineABTest } from "./abTest";
import { fetch } from "../core/network";
import { LocalStorage } from "../core/storage";
import { isBot } from "../addons/botDetection";
import { getFlags } from "../core/flags";
import * as ortb2 from "iab-openrtb/v26";
import * as adcom from "iab-adcom";
import { sendTargetingUpdateEvent } from "../core/events/cache-refresh";
Expand Down Expand Up @@ -102,6 +103,7 @@ function TargetingClearCache(config: ResolvedConfig) {
* Returns whether the request was identified as a bot.
*/
export function SkipTargetingForBots(): boolean {
if (getFlags().optableDebug) return false;
try {
if (typeof isBot === "function" && isBot()) {
sessionStorage.setItem(TARGETING_DONE_KEY, "1");
Expand Down
Loading