Skip to content

Commit f6a1c1c

Browse files
aymeric-giraudetdhayab
authored andcommitted
feat(recommend): handle multiple objectIDs for one widget (#6160)
* feat(recommend): handle multiple objectIDs for one widget * move flat to its own file with comment to remove it
1 parent 313b0ea commit f6a1c1c

File tree

9 files changed

+277
-22
lines changed

9 files changed

+277
-22
lines changed

bundlesize.config.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
"files": [
33
{
44
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.js",
5-
"maxSize": "40.5 kB"
5+
"maxSize": "41 kB"
66
},
77
{
88
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.min.js",
9-
"maxSize": "13 kB"
9+
"maxSize": "13.25 kB"
1010
},
1111
{
1212
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
@@ -30,7 +30,7 @@
3030
},
3131
{
3232
"path": "packages/vue-instantsearch/vue3/umd/index.js",
33-
"maxSize": "67 kB"
33+
"maxSize": "67.25 kB"
3434
},
3535
{
3636
"path": "packages/vue-instantsearch/vue2/cjs/index.js",

packages/algoliasearch-helper/src/RecommendParameters/index.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,8 @@ RecommendParameters.prototype = {
2323

2424
addParams: function (params) {
2525
var newParams = this.params.slice();
26-
var existingParamsIndex = this.params.findIndex(function (currentParams) {
27-
return currentParams.$$id === params.$$id;
28-
});
2926

30-
if (existingParamsIndex !== -1) {
31-
newParams.splice(existingParamsIndex, 1, params);
32-
} else {
33-
newParams.push(params);
34-
}
27+
newParams.push(params);
3528

3629
return new RecommendParameters({ params: newParams });
3730
},

packages/algoliasearch-helper/src/algoliasearch.helper.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var RecommendResults = require('./RecommendResults');
1313
var requestBuilder = require('./requestBuilder');
1414
var SearchParameters = require('./SearchParameters');
1515
var SearchResults = require('./SearchResults');
16+
var sortAndMergeRecommendations = require('./utils/sortAndMergeRecommendations');
1617
var version = require('./version');
1718

1819
/**
@@ -1740,10 +1741,25 @@ AlgoliaSearchHelper.prototype._dispatchRecommendResponse = function (
17401741

17411742
if (this._currentNbRecommendQueries === 0) this.emit('recommendQueueEmpty');
17421743

1744+
var idsMap = {};
1745+
ids.forEach(function (id, index) {
1746+
if (!idsMap[id]) idsMap[id] = [];
1747+
1748+
idsMap[id].push(index);
1749+
});
1750+
17431751
var results = {};
1744-
content.results.forEach(function (result, index) {
1745-
var id = ids[index];
1746-
results[id] = result;
1752+
Object.keys(idsMap).forEach(function (id) {
1753+
var indices = idsMap[id];
1754+
if (indices.length === 1) {
1755+
results[id] = content.results[indices[0]];
1756+
return;
1757+
}
1758+
results[id] = sortAndMergeRecommendations(
1759+
indices.map(function (idx) {
1760+
return content.results[idx].hits;
1761+
})
1762+
);
17471763
});
17481764

17491765
states.forEach(function (s) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @MAJOR: remove this function and use Array.prototype.flat
2+
module.exports = function flat(arr) {
3+
return arr.reduce(function (acc, val) {
4+
return acc.concat(val);
5+
}, []);
6+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
3+
var find = require('../functions/find');
4+
var flat = require('../functions/flat');
5+
6+
function getAverageIndices(indexTracker, nrOfObjs) {
7+
var avgIndices = [];
8+
9+
Object.keys(indexTracker).forEach(function (key) {
10+
if (indexTracker[key].count < 2) {
11+
indexTracker[key].indexSum += 100;
12+
}
13+
avgIndices.push({
14+
objectID: key,
15+
avgOfIndices: indexTracker[key].indexSum / nrOfObjs,
16+
});
17+
});
18+
19+
return avgIndices.sort(function (a, b) {
20+
return a.avgOfIndices > b.avgOfIndices ? 1 : -1;
21+
});
22+
}
23+
24+
function sortAndMergeRecommendations(results) {
25+
var indexTracker = {};
26+
27+
results.forEach(function (hits) {
28+
hits.forEach(function (hit, index) {
29+
if (!indexTracker[hit.objectID]) {
30+
indexTracker[hit.objectID] = { indexSum: index, count: 1 };
31+
} else {
32+
indexTracker[hit.objectID] = {
33+
indexSum: indexTracker[hit.objectID].indexSum + index,
34+
count: indexTracker[hit.objectID].count + 1,
35+
};
36+
}
37+
});
38+
});
39+
40+
var sortedAverageIndices = getAverageIndices(indexTracker, results.length);
41+
42+
var finalOrder = sortedAverageIndices.reduce(function (
43+
orderedHits,
44+
avgIndexRef
45+
) {
46+
var result = find(flat(results), function (hit) {
47+
return hit.objectID === avgIndexRef.objectID;
48+
});
49+
return result ? orderedHits.concat(result) : orderedHits;
50+
},
51+
[]);
52+
53+
return finalOrder;
54+
}
55+
56+
module.exports = sortAndMergeRecommendations;

packages/algoliasearch-helper/test/datasets/RecommendParameters/recommend.dataset.js

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
var RecommendParameters = require('../../../src/SearchParameters');
3+
var RecommendParameters = require('../../../src/RecommendParameters');
44

55
module.exports = getData;
66

@@ -9,7 +9,7 @@ function getData() {
99
results: [
1010
{
1111
exhaustiveNbHits: true,
12-
ExhaustiveFacetsCount: false,
12+
exhaustiveFacetsCount: false,
1313
hits: [
1414
{
1515
_highlightResult: {
@@ -99,9 +99,47 @@ function getData() {
9999
page: 0,
100100
processingTimeMS: 11,
101101
},
102+
{
103+
exhaustiveNbHits: true,
104+
exhaustiveFacetsCount: false,
105+
hits: [
106+
{
107+
_highlightResult: {
108+
brand: {
109+
matchLevel: 'none',
110+
matchedWords: [],
111+
value: 'Gabs',
112+
},
113+
name: {
114+
matchLevel: 'none',
115+
matchedWords: [],
116+
value: 'Bag “Sabrina“ medium Gabs',
117+
},
118+
},
119+
_score: 40.91,
120+
brand: 'Gabs',
121+
list_categories: ['Women', 'Bags', 'Shoulder bags'],
122+
name: 'Bag “Sabrina“ medium Gabs',
123+
objectID: 'A0E200000001WFI',
124+
parentID: 'SABRINA',
125+
price: {
126+
currency: 'EUR',
127+
discount_level: -100,
128+
discounted_value: 0,
129+
on_sales: false,
130+
value: 210,
131+
},
132+
},
133+
],
134+
hitsPerPage: 3,
135+
nbHits: 3,
136+
nbPages: 1,
137+
page: 0,
138+
processingTimeMS: 11,
139+
},
102140
{
103141
exhaustiveNbHits: false,
104-
ExhaustiveFacetsCount: false,
142+
exhaustiveFacetsCount: false,
105143
hits: [
106144
{
107145
_score: 40.89,
@@ -156,6 +194,11 @@ function getData() {
156194
objectID: 'A0E20000000279B',
157195
model: 'bought-together',
158196
},
197+
{
198+
$$id: 1,
199+
objectID: 'A0E20000000279C',
200+
model: 'bought-together',
201+
},
159202
{
160203
$$id: 2,
161204
facetName: 'brand',

packages/algoliasearch-helper/test/spec/RecommendParameters/methods.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('addParams', () => {
1818
expect(recommendParameters.params).toEqual([params1, params2]);
1919
});
2020

21-
test('replaces params for the same $$id', () => {
21+
test('can have params with the same $$id', () => {
2222
var recommendParameters = new RecommendParameters({
2323
params: [params1, params2],
2424
});
@@ -33,8 +33,12 @@ describe('addParams', () => {
3333
};
3434

3535
recommendParameters = recommendParameters.addParams(params1Updated);
36-
expect(recommendParameters.params).toHaveLength(2);
37-
expect(recommendParameters.params).toEqual([params1Updated, params2]);
36+
expect(recommendParameters.params).toHaveLength(3);
37+
expect(recommendParameters.params).toEqual([
38+
params1,
39+
params2,
40+
params1Updated,
41+
]);
3842
});
3943
});
4044

packages/algoliasearch-helper/test/spec/recommend.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ describe('recommend()', () => {
1919
$$id: 1,
2020
objectID: 'A0E20000000279B',
2121
});
22+
helper.addFrequentlyBoughtTogether({
23+
$$id: 1,
24+
objectID: 'A0E20000000279C',
25+
});
2226
helper.addTrendingFacets({ $$id: 2, facetName: 'brand' });
2327

2428
// eslint-disable-next-line no-warning-comments
@@ -28,8 +32,10 @@ describe('recommend()', () => {
2832
// As it also includes '_state' and '_rawResults'
2933
expect(Object.keys(results)).toHaveLength(4);
3034

31-
expect(results[1]).toBe(testData.response.results[0]);
32-
expect(results[2]).toBe(testData.response.results[1]);
35+
// This one should be sorted
36+
var hits = testData.response.results[0].hits;
37+
expect(results[1]).toEqual([hits[1], hits[0], hits[2]]);
38+
expect(results[2]).toBe(testData.response.results[2]);
3339

3440
var state = event.recommend.state;
3541
expect(state.params).toEqual(testData.recommendParams.params);
@@ -45,6 +51,11 @@ describe('recommend()', () => {
4551
model: 'bought-together',
4652
objectID: 'A0E20000000279B',
4753
},
54+
{
55+
indexName: 'indexName',
56+
model: 'bought-together',
57+
objectID: 'A0E20000000279C',
58+
},
4859
{ indexName: 'indexName', model: 'trending-facets', facetName: 'brand' },
4960
]);
5061
});

0 commit comments

Comments
 (0)