diff --git a/packages/autocomplete-plugin-query-suggestions/src/__tests__/createQuerySuggestionsPlugin.test.ts b/packages/autocomplete-plugin-query-suggestions/src/__tests__/createQuerySuggestionsPlugin.test.ts index 1b2cbfb96..046b979bf 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/__tests__/createQuerySuggestionsPlugin.test.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/__tests__/createQuerySuggestionsPlugin.test.ts @@ -59,6 +59,67 @@ const hits: Hit = [ }, }, ]; +const multiIndexHits: Hit = [ + { + index_1: { + exact_nb_hits: 100, + facets: { + exact_matches: { + data_origin: [ + { + value: 'Index 1', + count: 100, + }, + ], + categories: [ + { + value: 'Appliances', + count: 252, + }, + { + value: 'Ranges, Cooktops & Ovens', + count: 229, + }, + ], + }, + }, + }, + index_2: { + exact_nb_hits: 200, + facets: { + exact_matches: { + data_origin: [ + { + value: 'Index 2', + count: 200, + }, + ], + genre: [ + { + value: 'Poetry', + count: 340, + }, + { + value: 'Fiction', + count: 140, + }, + ], + }, + }, + }, + nb_words: 1, + popularity: 1230, + query: 'cooktop', + objectID: 'cooktop', + _highlightResult: { + query: { + value: 'cooktop', + matchLevel: 'none', + matchedWords: [], + }, + }, + }, +]; /* eslint-enable @typescript-eslint/camelcase */ const searchClient = createSearchClient({ @@ -512,6 +573,83 @@ describe('createQuerySuggestionsPlugin', () => { }); }); + test('accumulates suggestion categories from multiple indexes and attributes', async () => { + castToJestMock(searchClient.search).mockReturnValueOnce( + Promise.resolve( + createMultiSearchResponse({ + hits: multiIndexHits, + }) + ) + ); + + const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'indexName', + categoryAttribute: [ + [ + 'index_1', + 'facets', + 'exact_matches', + 'data_origin', + ], + [ + 'index_2', + 'facets', + 'exact_matches', + 'data_origin', + ], + [ + 'index_1', + 'facets', + 'exact_matches', + 'categories', + ], + [ + 'index_2', + 'facets', + 'exact_matches', + 'genre', + ] + ], + categoriesPerItem: 6, + }); + + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + autocomplete({ + container, + panelContainer, + plugins: [querySuggestionsPlugin], + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="querySuggestionsPlugin"]' + ) + ) + .getAllByRole('option') + .map((option) => option.textContent) + ).toEqual([ + 'cooktop', // Query Suggestions item + 'in Poetry', // Category item + 'in Appliances', // Category item + 'in Ranges, Cooktops & Ovens', // Category item + 'in Index 2', // Category item + 'in Fiction', // Category item + 'in Index 1', // Category item + ]); + }); + }); + test('fills the input with the query item key followed by a space on tap ahead', async () => { const querySuggestionsPlugin = createQuerySuggestionsPlugin({ searchClient, diff --git a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts index d6927be16..87efcc23b 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts @@ -9,7 +9,11 @@ import { SearchOptions } from '@algolia/client-search'; import { SearchClient } from 'algoliasearch/lite'; import { getTemplates } from './getTemplates'; -import { AutocompleteQuerySuggestionsHit, QuerySuggestionsHit } from './types'; +import { + AutocompleteQuerySuggestionsHit, + QuerySuggestionsHit, + QuerySuggestionsFacetValue, +} from './types'; export type CreateQuerySuggestionsPluginParams< TItem extends QuerySuggestionsHit @@ -43,13 +47,21 @@ export type CreateQuerySuggestionsPluginParams< onTapAhead(item: TItem): void; }): AutocompleteSource; /** - * The attribute or attribute path to display categories for. + * The attribute, attribute path, or array of paths to display categories for. * * @example ["instant_search", "facets", "exact_matches", "categories"] * @example ["instant_search", "facets", "exact_matches", "hierarchicalCategories.lvl0"] + * @example [ + * ["index_1", "facets", "exact_matches", "data_origin"], + * ["index_2", "facets", "exact_matches", "data_origin"], + * ] + * @example [ + * ["index_1", "facets", "exact_matches", "attr_1"], + * ["index_2", "facets", "exact_matches", "attr_2"], + * ] * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions/createQuerySuggestionsPlugin/#param-categoryattribute */ - categoryAttribute?: string | string[]; + categoryAttribute?: string | string[] | string[][]; /** * How many items to display categories for. * @@ -118,6 +130,7 @@ export function createQuerySuggestionsPlugin< } let itemsWithCategoriesAdded = 0; + return querySuggestionsHits.reduce< Array> >((acc, current) => { @@ -126,14 +139,29 @@ export function createQuerySuggestionsPlugin< > = [current]; if (itemsWithCategoriesAdded < itemsWithCategories) { - const categories = ( - getAttributeValueByPath( - current, - Array.isArray(categoryAttribute) - ? categoryAttribute - : [categoryAttribute] - ) || [] - ) + let paths = (Array.isArray(categoryAttribute[0]) + ? categoryAttribute + : [categoryAttribute]) as string[][]; + + if (typeof categoryAttribute === 'string') { + paths = [[categoryAttribute]]; + } + + const categoriesValues = paths.reduce< + QuerySuggestionsFacetValue[] + >((totalCategories, path) => { + const attrVal = getAttributeValueByPath(current, path); + + return attrVal + ? totalCategories.concat(attrVal) + : totalCategories; + }, []); + + if (paths.length > 1) { + categoriesValues.sort((a, b) => b.count - a.count); + } + + const categories = categoriesValues .map((x) => x.value) .slice(0, categoriesPerItem); diff --git a/packages/autocomplete-plugin-query-suggestions/src/types/QuerySuggestionsHit.ts b/packages/autocomplete-plugin-query-suggestions/src/types/QuerySuggestionsHit.ts index 0f263be88..9b5cc5140 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/types/QuerySuggestionsHit.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/types/QuerySuggestionsHit.ts @@ -1,6 +1,6 @@ import { Hit } from '@algolia/client-search'; -type QuerySuggestionsFacetValue = { value: string; count: number }; +export type QuerySuggestionsFacetValue = { value: string; count: number }; type QuerySuggestionsIndexMatch = Record< TKey,