diff --git a/www/apps/book/providers/search.tsx b/www/apps/book/providers/search.tsx index 7561600632..2836ae0c9a 100644 --- a/www/apps/book/providers/search.tsx +++ b/www/apps/book/providers/search.tsx @@ -21,8 +21,8 @@ const SearchProvider = ({ children }: SearchProviderProps) => { mainIndexName: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", indices: [ - process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", + process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", ], }} searchProps={{ diff --git a/www/apps/resources/providers/search.tsx b/www/apps/resources/providers/search.tsx index da7390c22f..a936ef6f11 100644 --- a/www/apps/resources/providers/search.tsx +++ b/www/apps/resources/providers/search.tsx @@ -21,8 +21,8 @@ const SearchProvider = ({ children }: SearchProviderProps) => { mainIndexName: process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", indices: [ - process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp", + process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp", ], }} searchProps={{ diff --git a/www/packages/docs-ui/src/components/Search/Hits/index.tsx b/www/packages/docs-ui/src/components/Search/Hits/index.tsx index a7134c5cb8..c5bed2bbb4 100644 --- a/www/packages/docs-ui/src/components/Search/Hits/index.tsx +++ b/www/packages/docs-ui/src/components/Search/Hits/index.tsx @@ -97,7 +97,7 @@ export const SearchHits = ({ setNoResults, checkInternalPattern, }: SearchHitsProps) => { - const { hits } = useHits() + const { items: hits } = useHits() const { status } = useInstantSearch() const { setIsOpen } = useSearch() diff --git a/www/packages/docs-ui/src/providers/Search/index.tsx b/www/packages/docs-ui/src/providers/Search/index.tsx index de69022c34..2b9430f570 100644 --- a/www/packages/docs-ui/src/providers/Search/index.tsx +++ b/www/packages/docs-ui/src/providers/Search/index.tsx @@ -13,6 +13,9 @@ import { checkArraySameElms } from "../../utils" import { liteClient as algoliasearch, LiteClient as SearchClient, + type SearchResponses, + type SearchHits, + SearchResponse, } from "algoliasearch/lite" import clsx from "clsx" import { CSSTransition, SwitchTransition } from "react-transition-group" @@ -78,11 +81,37 @@ export const SearchProvider = ({ async search(searchParams) { const requests = "requests" in searchParams ? searchParams.requests : searchParams - const noQueries = requests.every( + // always send this request, which is the main request with no filters + const mainRequest = requests[0] + + // retrieve only requests that have filters + // this is to ensure that we show no result if no filter is selected + const requestsWithFilters = requests.filter((item) => { + if ( + !item.params || + typeof item.params !== "object" || + !("tagFilters" in item.params) + ) { + return false + } + + const tagFilters = item.params.tagFilters as string[] + + // if no tag filters are specified, there's still one item, + // which is an empty array + return tagFilters.length >= 1 && tagFilters[0].length > 0 + }) + + // check whether a query is entered in the search box + const noQueries = requestsWithFilters.every( (item) => - ("facetQuery" in item && !item.facetQuery) || - ("query" in item && !item.query) + !item.facetQuery && + (!item.params || + typeof item.params !== "object" || + !("query" in item.params) || + !item.params.query) ) + if (noQueries) { return Promise.resolve({ results: requests.map(() => ({ @@ -99,7 +128,73 @@ export const SearchProvider = ({ }) } - return algoliaClient.search(searchParams) + // split requests per tags + const newRequests: typeof requestsWithFilters = [mainRequest] + for (const request of requestsWithFilters) { + const params = request.params as Record + const tagFilters = (params.tagFilters as string[][])[0] + + // if only one tag is selected, keep the request as-is + if (tagFilters.length === 1) { + newRequests.push(request) + + continue + } + + // if multiple tags are selected, split the tags + // to retrieve a small subset of results per each tag. + newRequests.push( + ...tagFilters.map((tag) => ({ + ...request, + params: { + ...params, + tagFilters: [tag], + }, + hitsPerPage: 4, + })) + ) + } + + return algoliaClient + .search(newRequests) + .then((response) => { + // combine results of the same index and return the results + const resultsByIndex: { + [indexName: string]: SearchResponse + } = {} + // extract the response of the main request + const mainResult = response.results[0] + + response.results.forEach((result, indexNum) => { + if (indexNum === 0) { + // ignore the main request's result + return + } + const resultIndex = "index" in result ? result.index : undefined + const resultHits = "hits" in result ? result.hits : [] + + if (!resultIndex) { + return + } + + resultsByIndex[resultIndex] = { + ...result, + ...(resultsByIndex[resultIndex] || {}), + hits: [ + ...(resultsByIndex[resultIndex]?.hits || []), + ...resultHits, + ], + nbHits: + (resultsByIndex[resultIndex]?.nbHits || 0) + + resultHits.length, + } + }) + + return { + // append the results with the main request's results + results: [mainResult, ...Object.values(resultsByIndex)], + } as SearchResponses + }) }, } }, [algolia.appId, algolia.apiKey])