Skip to content

Commit 76b0247

Browse files
fix(insights): deduplicate view events (#6414)
* fix(insights): deduplicate view events * fix tests and bundlesize
1 parent f82ebab commit 76b0247

File tree

5 files changed

+162
-11
lines changed

5 files changed

+162
-11
lines changed

bundlesize.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
{
2828
"path": "packages/vue-instantsearch/vue2/umd/index.js",
29-
"maxSize": "68.5 kB"
29+
"maxSize": "68.75 kB"
3030
},
3131
{
3232
"path": "packages/vue-instantsearch/vue3/umd/index.js",

packages/instantsearch.js/src/middlewares/__tests__/createInsightsMiddleware.ts

+129
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,135 @@ describe('insights', () => {
13261326
}).toWarnDev();
13271327
expect(insightsClient).toHaveBeenCalledTimes(numberOfCalls); // still the same
13281328
});
1329+
1330+
it('does not send view events that were previously sent for the current query', () => {
1331+
const { insightsClient, instantSearchInstance } = createTestEnvironment();
1332+
1333+
instantSearchInstance.use(
1334+
createInsightsMiddleware({
1335+
insightsClient,
1336+
})
1337+
);
1338+
1339+
insightsClient('setUserToken', 'token');
1340+
1341+
instantSearchInstance.sendEventToInsights({
1342+
insightsMethod: 'viewedObjectIDs',
1343+
widgetType: 'ais.customWidget',
1344+
eventType: 'view',
1345+
payload: {
1346+
index: 'my-index',
1347+
eventName: 'My Hits Viewed',
1348+
objectIDs: ['obj1'],
1349+
},
1350+
});
1351+
1352+
instantSearchInstance.sendEventToInsights({
1353+
insightsMethod: 'viewedObjectIDs',
1354+
widgetType: 'ais.customWidget',
1355+
eventType: 'view',
1356+
payload: {
1357+
index: 'my-index',
1358+
eventName: 'My Hits Viewed',
1359+
objectIDs: ['obj1'],
1360+
},
1361+
});
1362+
1363+
expect(
1364+
insightsClient.mock.calls.filter(
1365+
(call) => call[0] === 'viewedObjectIDs'
1366+
)
1367+
).toHaveLength(1);
1368+
});
1369+
1370+
it('clears saved view events when the query changes', () => {
1371+
const { insightsClient, instantSearchInstance } = createTestEnvironment();
1372+
1373+
instantSearchInstance.use(
1374+
createInsightsMiddleware({
1375+
insightsClient,
1376+
})
1377+
);
1378+
1379+
insightsClient('setUserToken', 'token');
1380+
1381+
instantSearchInstance.sendEventToInsights({
1382+
insightsMethod: 'viewedObjectIDs',
1383+
widgetType: 'ais.customWidget',
1384+
eventType: 'view',
1385+
payload: {
1386+
index: 'my-index',
1387+
eventName: 'My Hits Viewed',
1388+
objectIDs: ['obj1'],
1389+
},
1390+
});
1391+
1392+
instantSearchInstance.mainHelper!.derivedHelpers[0].emit('result', {
1393+
results: { queryId: '2' },
1394+
});
1395+
1396+
instantSearchInstance.sendEventToInsights({
1397+
insightsMethod: 'viewedObjectIDs',
1398+
widgetType: 'ais.customWidget',
1399+
eventType: 'view',
1400+
payload: {
1401+
index: 'my-index',
1402+
eventName: 'My Hits Viewed',
1403+
objectIDs: ['obj1'],
1404+
},
1405+
});
1406+
1407+
expect(
1408+
insightsClient.mock.calls.filter(
1409+
(call) => call[0] === 'viewedObjectIDs'
1410+
)
1411+
).toHaveLength(2);
1412+
});
1413+
1414+
it("only sends view events that haven't been sent yet for current query", () => {
1415+
const { insightsClient, instantSearchInstance } = createTestEnvironment();
1416+
1417+
instantSearchInstance.use(
1418+
createInsightsMiddleware({
1419+
insightsClient,
1420+
})
1421+
);
1422+
1423+
insightsClient('setUserToken', 'token');
1424+
1425+
instantSearchInstance.sendEventToInsights({
1426+
insightsMethod: 'viewedObjectIDs',
1427+
widgetType: 'ais.customWidget',
1428+
eventType: 'view',
1429+
payload: {
1430+
index: 'my-index',
1431+
eventName: 'My Hits Viewed',
1432+
objectIDs: ['obj1'],
1433+
},
1434+
});
1435+
1436+
instantSearchInstance.sendEventToInsights({
1437+
insightsMethod: 'viewedObjectIDs',
1438+
widgetType: 'ais.customWidget',
1439+
eventType: 'view',
1440+
payload: {
1441+
index: 'my-index',
1442+
eventName: 'My Hits Viewed',
1443+
objectIDs: ['obj1', 'obj2'],
1444+
},
1445+
});
1446+
1447+
expect(
1448+
insightsClient.mock.calls.filter(
1449+
(call) => call[0] === 'viewedObjectIDs'
1450+
)
1451+
).toHaveLength(2);
1452+
expect(insightsClient).toHaveBeenLastCalledWith(
1453+
'viewedObjectIDs',
1454+
expect.objectContaining({ objectIDs: ['obj2'] }),
1455+
expect.any(Object)
1456+
);
1457+
});
13291458
});
13301459

13311460
test("does not write to the URL on load when there's an existing anonymous cookie", async () => {

packages/instantsearch.js/src/middlewares/createInsightsMiddleware.ts

+26
Original file line numberDiff line numberDiff line change
@@ -438,13 +438,39 @@ export function createInsightsMiddleware<
438438
};
439439
}
440440

441+
const viewedObjectIDs = new Set<string>();
442+
let lastQueryId: string | undefined;
443+
instantSearchInstance.mainHelper!.derivedHelpers[0].on(
444+
'result',
445+
({ results }) => {
446+
if (!results.queryID || results.queryID !== lastQueryId) {
447+
lastQueryId = results.queryID;
448+
viewedObjectIDs.clear();
449+
}
450+
}
451+
);
452+
441453
instantSearchInstance.sendEventToInsights = (event: InsightsEvent) => {
442454
if (onEvent) {
443455
onEvent(
444456
event,
445457
insightsClientWithLocalCredentials as TInsightsClient
446458
);
447459
} else if (event.insightsMethod) {
460+
if (event.insightsMethod === 'viewedObjectIDs') {
461+
const payload = event.payload as {
462+
objectIDs: string[];
463+
};
464+
const difference = payload.objectIDs.filter(
465+
(objectID) => !viewedObjectIDs.has(objectID)
466+
);
467+
if (difference.length === 0) {
468+
return;
469+
}
470+
difference.forEach((objectID) => viewedObjectIDs.add(objectID));
471+
payload.objectIDs = difference;
472+
}
473+
448474
// Source is used to differentiate events sent by instantsearch from those sent manually.
449475
(event.payload as any).algoliaSource = ['instantsearch'];
450476
if ($$automatic) {

tests/common/widgets/hits/insights.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export function createInsightsTests(
8181

8282
// View event called for each index once
8383
{
84-
expect(window.aa).toHaveBeenCalledTimes(3);
84+
expect(window.aa).toHaveBeenCalledTimes(2);
8585
expect(window.aa).toHaveBeenCalledWith(
8686
'viewedObjectIDs',
8787
{
@@ -130,9 +130,7 @@ export function createInsightsTests(
130130
await wait(0);
131131
});
132132

133-
// @TODO: This is a bug, we should not send a view event when the results are the same.
134-
// see: https://github.com/algolia/instantsearch/issues/5442
135-
expect(window.aa).toHaveBeenCalledTimes(3);
133+
expect(window.aa).toHaveBeenCalledTimes(2);
136134
}
137135
});
138136

@@ -195,7 +193,7 @@ export function createInsightsTests(
195193

196194
// View event called for each index, batched in chunks of 20
197195
{
198-
expect(window.aa).toHaveBeenCalledTimes(6);
196+
expect(window.aa).toHaveBeenCalledTimes(4);
199197
expect(window.aa).toHaveBeenCalledWith(
200198
'viewedObjectIDs',
201199
{

tests/common/widgets/infinite-hits/insights.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export function createInsightsTests(
8181

8282
// View event called for each index once
8383
{
84-
expect(window.aa).toHaveBeenCalledTimes(3);
84+
expect(window.aa).toHaveBeenCalledTimes(2);
8585
expect(window.aa).toHaveBeenCalledWith(
8686
'viewedObjectIDs',
8787
{
@@ -130,9 +130,7 @@ export function createInsightsTests(
130130
await wait(0);
131131
});
132132

133-
// @TODO: This is a bug, we should not send a view event when the results are the same.
134-
// see: https://github.com/algolia/instantsearch/issues/5442
135-
expect(window.aa).toHaveBeenCalledTimes(3);
133+
expect(window.aa).toHaveBeenCalledTimes(2);
136134
}
137135
});
138136

@@ -195,7 +193,7 @@ export function createInsightsTests(
195193

196194
// View event called for each index, batched in chunks of 20
197195
{
198-
expect(window.aa).toHaveBeenCalledTimes(6);
196+
expect(window.aa).toHaveBeenCalledTimes(4);
199197
expect(window.aa).toHaveBeenCalledWith(
200198
'viewedObjectIDs',
201199
{

0 commit comments

Comments
 (0)