From 0409e70152ac3d6f54a931026edc04b9131b0ed2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Laroche Date: Thu, 15 Oct 2026 10:24:45 -0400 Subject: [PATCH] analytics: report Optable RTD module presence in prebid analytics Add rtdInstalled (optableRtdProvider in pbjs.installedModules) and rtdConfigured (an "optable" provider in realTimeData.dataProviders) to both the V1 and V2 auction payloads, so missing-from-build and built-but-not-configured can be told apart. --- lib/addons/prebid/analytics.md | 10 ++++++-- lib/addons/prebid/analytics.test.ts | 38 +++++++++++++++++++++++++++++ lib/addons/prebid/analytics.ts | 6 +++++ lib/addons/prototypes/analytics.js | 4 +++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/addons/prebid/analytics.md b/lib/addons/prebid/analytics.md index 4023faa8..3c42baf9 100644 --- a/lib/addons/prebid/analytics.md +++ b/lib/addons/prebid/analytics.md @@ -130,5 +130,11 @@ Each sampled auction sends an `optable.prebid.auction` event with, among others: `bidderRequests` (with per-bid `status`, `cpm`, `size`, `splitTestAssignment`), `optableMatchers`, `optableSources`, `optableTargetingDone`, `liveintentEIDs` (with per-bidder `hasLiEids` and `liSources`), `bidWon`, `missed`, -`url`, `tenant`, `prebidjsVersion`, `sessionDepth`, `pageAuctionsCount`, -`originSlug`, and the parsed `userAgent`/`device`. +`url`, `tenant`, `prebidjsVersion`, `rtdInstalled`, `rtdConfigured`, +`sessionDepth`, `pageAuctionsCount`, `originSlug`, and the parsed +`userAgent`/`device`. + +`rtdInstalled` is `true` when `optableRtdProvider` is in `pbjs.installedModules` +(compiled into the Prebid build). `rtdConfigured` is `true` when +`realTimeData.dataProviders` contains a provider named `optable`. Together they +separate "module missing from the build" from "built in but not configured". diff --git a/lib/addons/prebid/analytics.test.ts b/lib/addons/prebid/analytics.test.ts index 64bba2c6..b17c81ee 100644 --- a/lib/addons/prebid/analytics.test.ts +++ b/lib/addons/prebid/analytics.test.ts @@ -431,6 +431,44 @@ describe("OptablePrebidAnalytics", () => { expect(result.optableSources).toEqual(["uidapi.com", "id5-sync.com"]); expect(result.liveintentEIDs).toEqual(["uidapi.com"]); }); + + describe("RTD presence", () => { + const emptyAuction = { auctionId: "rtd-auction", bidderRequests: [] }; + + it("reports the RTD module as installed and configured", async () => { + (analytics as any).prebidInstance = { + installedModules: ["rtdModule", "optableRtdProvider"], + getConfig: jest.fn().mockReturnValue({ dataProviders: [{ name: "other" }, { name: "optable" }] }), + }; + + const result = await analytics.toWitness(emptyAuction, []); + + expect(result.rtdInstalled).toBe(true); + expect(result.rtdConfigured).toBe(true); + expect((analytics as any).prebidInstance.getConfig).toHaveBeenCalledWith("realTimeData"); + }); + + it("reports installed but not configured when no optable data provider is set", async () => { + (analytics as any).prebidInstance = { + installedModules: ["optableRtdProvider"], + getConfig: jest.fn().mockReturnValue(undefined), + }; + + const result = await analytics.toWitness(emptyAuction, []); + + expect(result.rtdInstalled).toBe(true); + expect(result.rtdConfigured).toBe(false); + }); + + it("reports false for both when Prebid exposes neither installedModules nor getConfig", async () => { + (analytics as any).prebidInstance = {}; + + const result = await analytics.toWitness(emptyAuction, []); + + expect(result.rtdInstalled).toBe(false); + expect(result.rtdConfigured).toBe(false); + }); + }); }); describe("trackAuctionEnd", () => { diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index 490e3bd1..9f0b202c 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -500,6 +500,12 @@ class OptablePrebidAnalytics { userAgent: Bowser.parse(window.navigator.userAgent) as unknown as Record, device, prebidjsVersion: this.prebidInstance?.version || "unknown", + // rtdInstalled: optableRtdProvider is compiled into the Prebid build. + // rtdConfigured: the "optable" provider is listed in realTimeData.dataProviders. + rtdInstalled: (this.prebidInstance?.installedModules ?? []).includes("optableRtdProvider"), + rtdConfigured: (this.prebidInstance?.getConfig?.("realTimeData")?.dataProviders ?? []).some( + (p: { name?: string }) => p?.name === "optable" + ), sessionDepth: sessionStorage?.optableSessionDepth || 1, pageAuctionsCount: (window as any).optable?.pageAuctionsCount || 1, originSlug: this.optableInstance?.dcn?.site || (window as any).optable?.site || "unknown", diff --git a/lib/addons/prototypes/analytics.js b/lib/addons/prototypes/analytics.js index fbdf4f1e..9f7191f4 100644 --- a/lib/addons/prototypes/analytics.js +++ b/lib/addons/prototypes/analytics.js @@ -279,6 +279,10 @@ class OptablePrebidAnalytics { tenant: this.config.tenant, optableWrapperVersion: SDK_WRAPPER_VERSION, // eslint-disable-line no-undef prebidjsVersion: this.prebidInstance?.version || "unknown", + rtdInstalled: (this.prebidInstance?.installedModules ?? []).includes("optableRtdProvider"), + rtdConfigured: (this.prebidInstance?.getConfig?.("realTimeData")?.dataProviders ?? []).some( + (p) => p?.name === "optable" + ), sessionDepth: sessionStorage?.optableSessionDepthIndex || 1, pageAuctionsCount: window.optable?.pageAuctionIndex || 1, originSlug: window.optable?.site || this.optableInstance?.dcn?.site || "unknown", // optable.site first since dcn is analytics always