Skip to content

Commit 210bd41

Browse files
authored
analytics: Check ext.eids alongside eids for optable resolved events in `auctionEnd (#271)
1 parent b18b4b6 commit 210bd41

2 files changed

Lines changed: 245 additions & 2 deletions

File tree

lib/addons/prebid/analytics.test.ts

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,245 @@ describe("OptablePrebidAnalytics", () => {
11071107
});
11081108
});
11091109

1110+
describe("EID deduplication", () => {
1111+
beforeEach(() => {
1112+
analytics = new OptablePrebidAnalytics(mockOptableInstance, {
1113+
tenant: "test-tenant",
1114+
});
1115+
});
1116+
1117+
it("should deduplicate EIDs by source when both ext.eids and eids contain same source", async () => {
1118+
const auctionEndEvent = {
1119+
auctionId: "auction-dedup",
1120+
bidderRequests: [
1121+
{
1122+
bidderCode: "bidder1",
1123+
bidderRequestId: "req-1",
1124+
ortb2: {
1125+
site: { domain: "example.com" },
1126+
user: {
1127+
ext: {
1128+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "optable.co" }],
1129+
},
1130+
eids: [{ inserter: "optable.co", matcher: "matcher2", source: "optable.co" }],
1131+
},
1132+
},
1133+
bids: [],
1134+
},
1135+
],
1136+
bidsReceived: [],
1137+
noBids: [],
1138+
timeoutBids: [],
1139+
};
1140+
1141+
const result = await analytics.toWitness(auctionEndEvent, null);
1142+
1143+
// Should only have one source since they have the same source value
1144+
expect(result.optableSources).toEqual(["optable.co"]);
1145+
// The last occurrence (from eids, not ext.eids) should win due to deduplication
1146+
expect(result.optableMatchers).toEqual(["matcher2"]);
1147+
});
1148+
1149+
it("should preserve EIDs with different sources from ext.eids and eids", async () => {
1150+
const auctionEndEvent = {
1151+
auctionId: "auction-no-dedup",
1152+
bidderRequests: [
1153+
{
1154+
bidderCode: "bidder1",
1155+
bidderRequestId: "req-1",
1156+
ortb2: {
1157+
site: { domain: "example.com" },
1158+
user: {
1159+
ext: {
1160+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1.optable.co" }],
1161+
},
1162+
eids: [{ inserter: "optable.co", matcher: "matcher2", source: "source2.optable.co" }],
1163+
},
1164+
},
1165+
bids: [],
1166+
},
1167+
],
1168+
bidsReceived: [],
1169+
noBids: [],
1170+
timeoutBids: [],
1171+
};
1172+
1173+
const result = await analytics.toWitness(auctionEndEvent, null);
1174+
1175+
// Should have both sources since they're different
1176+
expect(result.optableSources).toHaveLength(2);
1177+
expect(result.optableSources).toContain("source1.optable.co");
1178+
expect(result.optableSources).toContain("source2.optable.co");
1179+
expect(result.optableMatchers).toHaveLength(2);
1180+
expect(result.optableMatchers).toContain("matcher1");
1181+
expect(result.optableMatchers).toContain("matcher2");
1182+
});
1183+
1184+
it("should handle empty ext.eids and only use eids", async () => {
1185+
const auctionEndEvent = {
1186+
auctionId: "auction-only-eids",
1187+
bidderRequests: [
1188+
{
1189+
bidderCode: "bidder1",
1190+
bidderRequestId: "req-1",
1191+
ortb2: {
1192+
site: { domain: "example.com" },
1193+
user: {
1194+
ext: {
1195+
eids: [],
1196+
},
1197+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1" }],
1198+
},
1199+
},
1200+
bids: [],
1201+
},
1202+
],
1203+
bidsReceived: [],
1204+
noBids: [],
1205+
timeoutBids: [],
1206+
};
1207+
1208+
const result = await analytics.toWitness(auctionEndEvent, null);
1209+
1210+
expect(result.optableSources).toEqual(["source1"]);
1211+
expect(result.optableMatchers).toEqual(["matcher1"]);
1212+
});
1213+
1214+
it("should handle missing ext.eids and only use eids", async () => {
1215+
const auctionEndEvent = {
1216+
auctionId: "auction-no-ext-eids",
1217+
bidderRequests: [
1218+
{
1219+
bidderCode: "bidder1",
1220+
bidderRequestId: "req-1",
1221+
ortb2: {
1222+
site: { domain: "example.com" },
1223+
user: {
1224+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1" }],
1225+
},
1226+
},
1227+
bids: [],
1228+
},
1229+
],
1230+
bidsReceived: [],
1231+
noBids: [],
1232+
timeoutBids: [],
1233+
};
1234+
1235+
const result = await analytics.toWitness(auctionEndEvent, null);
1236+
1237+
expect(result.optableSources).toEqual(["source1"]);
1238+
expect(result.optableMatchers).toEqual(["matcher1"]);
1239+
});
1240+
1241+
it("should handle empty eids and only use ext.eids", async () => {
1242+
const auctionEndEvent = {
1243+
auctionId: "auction-only-ext-eids",
1244+
bidderRequests: [
1245+
{
1246+
bidderCode: "bidder1",
1247+
bidderRequestId: "req-1",
1248+
ortb2: {
1249+
site: { domain: "example.com" },
1250+
user: {
1251+
ext: {
1252+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1" }],
1253+
},
1254+
eids: [],
1255+
},
1256+
},
1257+
bids: [],
1258+
},
1259+
],
1260+
bidsReceived: [],
1261+
noBids: [],
1262+
timeoutBids: [],
1263+
};
1264+
1265+
const result = await analytics.toWitness(auctionEndEvent, null);
1266+
1267+
expect(result.optableSources).toEqual(["source1"]);
1268+
expect(result.optableMatchers).toEqual(["matcher1"]);
1269+
});
1270+
1271+
it("should deduplicate multiple EIDs with same source across different locations", async () => {
1272+
const auctionEndEvent = {
1273+
auctionId: "auction-multi-dedup",
1274+
bidderRequests: [
1275+
{
1276+
bidderCode: "bidder1",
1277+
bidderRequestId: "req-1",
1278+
ortb2: {
1279+
site: { domain: "example.com" },
1280+
user: {
1281+
ext: {
1282+
eids: [
1283+
{ inserter: "optable.co", matcher: "matcher1", source: "optable.co" },
1284+
{ inserter: "other.co", matcher: "other1", source: "other.co" },
1285+
],
1286+
},
1287+
eids: [
1288+
{ inserter: "optable.co", matcher: "matcher2", source: "optable.co" },
1289+
{ inserter: "other.co", matcher: "other2", source: "other.co" },
1290+
],
1291+
},
1292+
},
1293+
bids: [],
1294+
},
1295+
],
1296+
bidsReceived: [],
1297+
noBids: [],
1298+
timeoutBids: [],
1299+
};
1300+
1301+
const result = await analytics.toWitness(auctionEndEvent, null);
1302+
1303+
// Should only have one optable source and matcher due to deduplication
1304+
expect(result.optableSources).toEqual(["optable.co"]);
1305+
expect(result.optableMatchers).toEqual(["matcher2"]);
1306+
// Non-optable EIDs should be filtered out
1307+
expect(result.optableMatchers).not.toContain("other1");
1308+
expect(result.optableMatchers).not.toContain("other2");
1309+
});
1310+
1311+
it("should handle trackAuctionEnd with duplicate EIDs in ext.eids and eids", async () => {
1312+
const event = {
1313+
auctionId: "auction-track-dedup",
1314+
timeout: 3000,
1315+
bidderRequests: [
1316+
{
1317+
bidderCode: "bidder1",
1318+
bidderRequestId: "req-1",
1319+
ortb2: {
1320+
site: { domain: "example.com" },
1321+
user: {
1322+
ext: {
1323+
eids: [{ inserter: "optable.co", matcher: "matcher1", source: "optable.co" }],
1324+
},
1325+
eids: [{ inserter: "optable.co", matcher: "matcher2", source: "optable.co" }],
1326+
},
1327+
},
1328+
bids: [],
1329+
},
1330+
],
1331+
bidsReceived: [],
1332+
noBids: [],
1333+
timeoutBids: [],
1334+
};
1335+
1336+
await analytics.trackAuctionEnd(event);
1337+
1338+
const storedAuction = analytics["auctions"].get("auction-track-dedup");
1339+
expect(storedAuction).toBeDefined();
1340+
1341+
const payload = await analytics.toWitness(storedAuction.auctionEnd, null);
1342+
// Should only have one source after deduplication
1343+
expect(payload.optableSources).toEqual(["optable.co"]);
1344+
// The last matcher should win
1345+
expect(payload.optableMatchers).toEqual(["matcher2"]);
1346+
});
1347+
});
1348+
11101349
describe("trackAuctionEnd - edge cases", () => {
11111350
beforeEach(() => {
11121351
analytics = new OptablePrebidAnalytics(mockOptableInstance);

lib/addons/prebid/analytics.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ class OptablePrebidAnalytics {
229229
bidderRequests: bidderRequests.map((br: any) => {
230230
const { bidderCode, bidderRequestId, bids = [] } = br;
231231
const domain = br.ortb2.site?.domain ?? "unknown";
232-
const eids = br.ortb2.user?.eids ?? [];
232+
const allEids = [...(br.ortb2.user?.ext?.eids ?? []), ...(br.ortb2.user?.eids ?? [])];
233+
// Deduplicate EIDs by source
234+
const eids = Array.from(new Map(allEids.map((eid: any) => [eid.source, eid])).values());
233235

234236
// Optable EIDs
235237
const optableEIDS = eids.filter((e: { inserter: string }) => e.inserter === "optable.co");
@@ -449,7 +451,9 @@ class OptablePrebidAnalytics {
449451
const requests = bidderRequests.map((br: any) => {
450452
const { bidderCode, bidderRequestId, bids = [] } = br;
451453
const domain = br.ortb2.site?.domain ?? "unknown";
452-
const eids = br.ortb2.user?.eids ?? [];
454+
const allEids = [...(br.ortb2.user?.ext?.eids ?? []), ...(br.ortb2.user?.eids ?? [])];
455+
// Deduplicate EIDs by source
456+
const eids = Array.from(new Map(allEids.map((eid: any) => [eid.source, eid])).values());
453457

454458
// Optable EIDs
455459
const optableEIDS = eids.filter((e: { inserter: string }) => e.inserter === "optable.co");

0 commit comments

Comments
 (0)