Skip to content

Commit edf3af0

Browse files
authored
feat: support facet distribution for federated search (#1743)
1 parent 4662933 commit edf3af0

File tree

2 files changed

+187
-2
lines changed

2 files changed

+187
-2
lines changed

src/types/types.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,17 @@ export type SearchRequestGET = Pagination &
153153
locales?: Locale[];
154154
};
155155

156+
export type MergeFacets = {
157+
maxValuesPerFacet?: number | null;
158+
};
159+
156160
export type FederationOptions = { weight: number };
157-
export type MultiSearchFederation = { limit?: number; offset?: number };
161+
export type MultiSearchFederation = {
162+
limit?: number;
163+
offset?: number;
164+
facetsByIndex?: Record<string, string[]>;
165+
mergeFacets?: MergeFacets | null;
166+
};
158167

159168
export type MultiSearchQuery = SearchParams & { indexUid: string };
160169
export type MultiSearchQueryWithFederation = MultiSearchQuery & {
@@ -229,6 +238,14 @@ export type Hits<T = Record<string, any>> = Array<Hit<T>>;
229238
export type FacetStat = { min: number; max: number };
230239
export type FacetStats = Record<string, FacetStat>;
231240

241+
export type FacetsByIndex = Record<
242+
string,
243+
{
244+
distribution: FacetDistribution;
245+
stats: FacetStats;
246+
}
247+
>;
248+
232249
export type SearchResponse<
233250
T = Record<string, any>,
234251
S extends SearchParams | undefined = undefined,
@@ -238,6 +255,7 @@ export type SearchResponse<
238255
query: string;
239256
facetDistribution?: FacetDistribution;
240257
facetStats?: FacetStats;
258+
facetsByIndex?: FacetsByIndex;
241259
} & (undefined extends S
242260
? Partial<FinitePagination & InfinitePagination>
243261
: true extends IsFinitePagination<NonNullable<S>>

tests/search.test.ts

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if (typeof fetch === "undefined") {
2525
}
2626

2727
const index = {
28-
uid: "movies_test",
28+
uid: "books",
2929
};
3030
const emptyIndex = {
3131
uid: "empty_test",
@@ -80,6 +80,26 @@ const dataset = [
8080
{ id: 42, title: "The Hitchhiker's Guide to the Galaxy", genre: "fantasy" },
8181
];
8282

83+
type Movies = {
84+
id: number;
85+
title: string;
86+
};
87+
88+
const movies = [
89+
{
90+
id: 1,
91+
title: "Pride and Prejudice",
92+
},
93+
{
94+
id: 2,
95+
title: "The Hobbit: An Unexpected Journey",
96+
},
97+
{
98+
id: 3,
99+
title: "Harry Potter and the Half-Blood Prince",
100+
},
101+
];
102+
83103
describe.each([
84104
{ permission: "Master" },
85105
{ permission: "Admin" },
@@ -194,6 +214,153 @@ describe.each([
194214
expect(response2.hits[0].id).toEqual(1344);
195215
});
196216

217+
test(`${permission} key: Multi search with facetsByIndex`, async () => {
218+
const client = await getClient(permission);
219+
const masterClient = await getClient("Master");
220+
221+
// Setup to have a new "movies" index
222+
await masterClient.createIndex("movies");
223+
const newFilterableAttributes = ["title", "id"];
224+
const { taskUid: task1 }: EnqueuedTask = await masterClient
225+
.index("movies")
226+
.updateSettings({
227+
filterableAttributes: newFilterableAttributes,
228+
sortableAttributes: ["id"],
229+
});
230+
await masterClient.waitForTask(task1);
231+
const { taskUid: task2 } = await masterClient
232+
.index("movies")
233+
.addDocuments(movies);
234+
await masterClient.waitForTask(task2);
235+
236+
// Make a multi search on both indexes with facetsByIndex
237+
const response = await client.multiSearch<Books | Movies>({
238+
federation: {
239+
limit: 20,
240+
offset: 0,
241+
facetsByIndex: {
242+
movies: ["title", "id"],
243+
books: ["title"],
244+
},
245+
},
246+
queries: [
247+
{
248+
q: "Hobbit",
249+
indexUid: "movies",
250+
},
251+
{
252+
q: "Hobbit",
253+
indexUid: "books",
254+
},
255+
],
256+
});
257+
258+
expect(response).toHaveProperty("hits");
259+
expect(Array.isArray(response.hits)).toBe(true);
260+
expect(response.hits.length).toEqual(2);
261+
262+
expect(response).toHaveProperty("facetsByIndex");
263+
expect(response.facetsByIndex).toHaveProperty("movies");
264+
expect(response.facetsByIndex).toHaveProperty("books");
265+
266+
// Test search response on "movies" index
267+
expect(response.facetsByIndex?.movies).toEqual({
268+
distribution: {
269+
title: {
270+
"The Hobbit: An Unexpected Journey": 1,
271+
},
272+
id: {
273+
"2": 1,
274+
},
275+
},
276+
stats: {
277+
id: {
278+
min: 2,
279+
max: 2,
280+
},
281+
},
282+
});
283+
284+
// Test search response on "books" index
285+
expect(response.facetsByIndex?.books).toEqual({
286+
distribution: {
287+
title: {
288+
"The Hobbit": 1,
289+
},
290+
},
291+
stats: {},
292+
});
293+
});
294+
295+
test(`${permission} key: Multi search with mergeFacets`, async () => {
296+
const client = await getClient(permission);
297+
const masterClient = await getClient("Master");
298+
299+
// Setup to have a new "movies" index
300+
await masterClient.createIndex("movies");
301+
const newFilterableAttributes = ["title", "id"];
302+
const { taskUid: task1 }: EnqueuedTask = await masterClient
303+
.index("movies")
304+
.updateSettings({
305+
filterableAttributes: newFilterableAttributes,
306+
sortableAttributes: ["id"],
307+
});
308+
await masterClient.waitForTask(task1);
309+
const { taskUid: task2 } = await masterClient
310+
.index("movies")
311+
.addDocuments(movies);
312+
await masterClient.waitForTask(task2);
313+
314+
// Make a multi search on both indexes with mergeFacets
315+
const response = await client.multiSearch<Books | Movies>({
316+
federation: {
317+
limit: 20,
318+
offset: 0,
319+
facetsByIndex: {
320+
movies: ["title", "id"],
321+
books: ["title"],
322+
},
323+
mergeFacets: {
324+
maxValuesPerFacet: 10,
325+
},
326+
},
327+
queries: [
328+
{
329+
q: "Hobbit",
330+
indexUid: "movies",
331+
},
332+
{
333+
q: "Hobbit",
334+
indexUid: "books",
335+
},
336+
],
337+
});
338+
339+
expect(response).toHaveProperty("hits");
340+
expect(Array.isArray(response.hits)).toBe(true);
341+
expect(response.hits.length).toEqual(2);
342+
343+
expect(response).toHaveProperty("facetDistribution");
344+
expect(response).toHaveProperty("facetStats");
345+
346+
expect(response.facetDistribution).toEqual({
347+
title: {
348+
"The Hobbit": 1,
349+
"The Hobbit: An Unexpected Journey": 1,
350+
},
351+
id: {
352+
"2": 1,
353+
},
354+
});
355+
356+
expect(response.facetStats).toEqual({
357+
id: {
358+
min: 2,
359+
max: 2,
360+
},
361+
});
362+
});
363+
197364
test(`${permission} key: Basic search`, async () => {
198365
const client = await getClient(permission);
199366
const response = await client.index(index.uid).search("prince", {});

0 commit comments

Comments
 (0)