From be1cee7003ef6e804a22a62415f0711ec9f583a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 22 Apr 2021 20:02:45 +0200 Subject: [PATCH] feat(core): introduce Requester API (#540) Co-authored-by: Sarah Dayan --- .eslintignore | 1 + README.md | 2 +- bundlesize.config.json | 6 +- cypress/test-apps/js/app.tsx | 4 +- cypress/test-apps/js/categoriesPlugin.tsx | 9 +- cypress/test-apps/js/package.json | 4 +- .../categoriesPlugin.tsx | 7 +- .../package.json | 2 +- examples/playground/app.tsx | 29 +- examples/playground/categoriesPlugin.tsx | 9 +- examples/playground/package.json | 4 +- examples/playground/types/ProductHit.ts | 2 +- .../package.json | 2 +- examples/query-suggestions-with-hits/app.tsx | 4 +- .../query-suggestions-with-hits/package.json | 4 +- .../package.json | 2 +- .../package.json | 4 +- .../package.json | 2 +- examples/query-suggestions/package.json | 2 +- examples/react-renderer/package.json | 4 +- examples/react-renderer/src/Autocomplete.tsx | 4 +- examples/recently-viewed-items/app.tsx | 4 +- examples/recently-viewed-items/package.json | 4 +- examples/starter-algolia/app.tsx | 4 +- examples/starter-algolia/package.json | 4 +- examples/voice-search/app.tsx | 4 +- examples/voice-search/package.json | 4 +- package.json | 4 +- packages/autocomplete-core/package.json | 5 + .../src/__tests__/getSources.test.ts | 4 +- .../src/getAutocompleteSetters.ts | 2 +- packages/autocomplete-core/src/onInput.ts | 19 +- packages/autocomplete-core/src/resolve.ts | 185 ++++++ .../src/types/AutocompleteSource.ts | 7 +- .../__tests__/getNormalizedSources.test.ts | 32 + .../__tests__/mapToAlgoliaResponse.test.ts | 84 +++ .../src/utils/getNormalizedSources.ts | 12 + packages/autocomplete-core/src/utils/index.ts | 1 + .../src/utils/mapToAlgoliaResponse.ts | 30 + packages/autocomplete-js/package.json | 2 +- .../src/__tests__/getAlgoliaFacetHits.test.ts | 29 - .../src/__tests__/getAlgoliaHits.test.ts | 29 - .../src/__tests__/getAlgoliaResults.test.ts | 29 - .../src/__tests__/panelPlacement.test.ts | 2 +- .../src/__tests__/requester.test.ts | 574 ++++++++++++++++++ .../src/getAlgoliaFacetHits.ts | 17 - .../autocomplete-js/src/getAlgoliaHits.ts | 17 - .../autocomplete-js/src/getAlgoliaResults.ts | 17 - .../src/getPanelPlacementStyle.ts | 2 +- packages/autocomplete-js/src/index.ts | 4 +- .../__tests__/getAlgoliaFacets.test.ts | 91 +++ .../__tests__/getAlgoliaResults.test.ts | 82 +++ .../src/requesters/createAlgoliaRequester.ts | 13 + .../src/requesters/getAlgoliaFacets.ts | 23 + .../src/requesters/getAlgoliaResults.ts | 8 + .../autocomplete-js/src/requesters/index.ts | 2 + .../package.json | 8 +- .../src/createQuerySuggestionsPlugin.ts | 65 +- .../package.json | 2 +- .../autocomplete-preset-algolia/package.json | 8 +- .../src/highlight/index.ts | 6 + .../autocomplete-preset-algolia/src/index.ts | 14 +- .../__tests__/createRequester.test.ts | 57 ++ .../__tests__/getAlgoliaFacets.test.ts | 91 +++ .../__tests__/getAlgoliaResults.test.ts | 82 +++ .../src/requester/createAlgoliaRequester.ts | 5 + .../src/requester/createRequester.ts | 117 ++++ .../src/requester/getAlgoliaFacets.ts | 23 + .../src/requester/getAlgoliaResults.ts | 8 + .../src/requester/index.ts | 3 + .../src/search/UserAgent.ts | 1 - .../__tests__/fetchAlgoliaResults.test.ts | 157 +++++ .../src/search/__tests__/search.test.ts | 433 ------------- .../src/search/fetchAlgoliaResults.ts | 67 ++ .../src/search/getAlgoliaFacetHits.ts | 38 -- .../src/search/getAlgoliaHits.ts | 25 - .../src/search/getAlgoliaResults.ts | 15 - .../src/search/index.ts | 1 + .../src/search/search.ts | 47 -- .../src/search/searchForFacetValues.ts | 52 -- packages/website/docs/api.md | 2 +- packages/website/docs/autocomplete-js.md | 4 +- .../docs/changing-behavior-based-on-query.mdx | 4 +- packages/website/docs/context.md | 18 +- packages/website/docs/createAutocomplete.md | 6 +- packages/website/docs/creating-a-renderer.md | 6 +- .../website/docs/getAlgoliaFacetHits-js.mdx | 40 -- packages/website/docs/getAlgoliaFacetHits.mdx | 133 ---- packages/website/docs/getAlgoliaFacets-js.mdx | 57 ++ packages/website/docs/getAlgoliaFacets.mdx | 160 +++++ packages/website/docs/getAlgoliaHits-js.mdx | 41 -- packages/website/docs/getAlgoliaHits.mdx | 130 ---- .../website/docs/getAlgoliaResults-js.mdx | 43 +- packages/website/docs/getAlgoliaResults.mdx | 93 +-- packages/website/docs/getting-started.mdx | 14 +- packages/website/docs/introduction.mdx | 2 +- .../getAlgoliaFacetHits/intro.md | 3 - .../preset-algolia/getAlgoliaFacets/intro.md | 3 + .../preset-algolia/getAlgoliaHits/intro.md | 3 - .../docs/sending-algolia-insights-events.md | 8 +- packages/website/docs/sources.md | 10 +- packages/website/docs/using-react.md | 50 +- packages/website/docs/using-vue.md | 12 +- packages/website/sidebars.js | 6 +- .../website/src/components/productsPlugin.tsx | 4 +- yarn.lock | 342 ++++------- 106 files changed, 2373 insertions(+), 1603 deletions(-) create mode 100644 packages/autocomplete-core/src/resolve.ts create mode 100644 packages/autocomplete-core/src/utils/__tests__/mapToAlgoliaResponse.test.ts create mode 100644 packages/autocomplete-core/src/utils/mapToAlgoliaResponse.ts delete mode 100644 packages/autocomplete-js/src/__tests__/getAlgoliaFacetHits.test.ts delete mode 100644 packages/autocomplete-js/src/__tests__/getAlgoliaHits.test.ts delete mode 100644 packages/autocomplete-js/src/__tests__/getAlgoliaResults.test.ts create mode 100644 packages/autocomplete-js/src/__tests__/requester.test.ts delete mode 100644 packages/autocomplete-js/src/getAlgoliaFacetHits.ts delete mode 100644 packages/autocomplete-js/src/getAlgoliaHits.ts delete mode 100644 packages/autocomplete-js/src/getAlgoliaResults.ts create mode 100644 packages/autocomplete-js/src/requesters/__tests__/getAlgoliaFacets.test.ts create mode 100644 packages/autocomplete-js/src/requesters/__tests__/getAlgoliaResults.test.ts create mode 100644 packages/autocomplete-js/src/requesters/createAlgoliaRequester.ts create mode 100644 packages/autocomplete-js/src/requesters/getAlgoliaFacets.ts create mode 100644 packages/autocomplete-js/src/requesters/getAlgoliaResults.ts create mode 100644 packages/autocomplete-js/src/requesters/index.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/__tests__/createRequester.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaFacets.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaResults.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/createAlgoliaRequester.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/createRequester.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/getAlgoliaFacets.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/getAlgoliaResults.ts create mode 100644 packages/autocomplete-preset-algolia/src/requester/index.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/UserAgent.ts create mode 100644 packages/autocomplete-preset-algolia/src/search/__tests__/fetchAlgoliaResults.test.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts create mode 100644 packages/autocomplete-preset-algolia/src/search/fetchAlgoliaResults.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/getAlgoliaFacetHits.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts create mode 100644 packages/autocomplete-preset-algolia/src/search/index.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/search.ts delete mode 100644 packages/autocomplete-preset-algolia/src/search/searchForFacetValues.ts delete mode 100644 packages/website/docs/getAlgoliaFacetHits-js.mdx delete mode 100644 packages/website/docs/getAlgoliaFacetHits.mdx create mode 100644 packages/website/docs/getAlgoliaFacets-js.mdx create mode 100644 packages/website/docs/getAlgoliaFacets.mdx delete mode 100644 packages/website/docs/getAlgoliaHits-js.mdx delete mode 100644 packages/website/docs/getAlgoliaHits.mdx delete mode 100644 packages/website/docs/partials/preset-algolia/getAlgoliaFacetHits/intro.md create mode 100644 packages/website/docs/partials/preset-algolia/getAlgoliaFacets/intro.md delete mode 100644 packages/website/docs/partials/preset-algolia/getAlgoliaHits/intro.md diff --git a/.eslintignore b/.eslintignore index 88d3e6378..6f072025a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ build coverage dist node_modules +.parcel-cache diff --git a/README.md b/README.md index c04449011..245097a2f 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ You can find more on the [documentation](https://autocomplete.algolia.com). | [`autocomplete-plugin-recent-searches`](packages/autocomplete-plugin-recent-searches) | A plugin to add recent searches to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createLocalStorageRecentSearchesPlugin) | | [`autocomplete-plugin-query-suggestions`](packages/autocomplete-plugin-query-suggestions) | A plugin to add query suggestions to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createQuerySuggestionsPlugin) | | [`autocomplete-plugin-algolia-insights`](packages/autocomplete-plugin-algolia-insights) | A plugin to add Algolia Insights to Algolia Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/createAlgoliaInsightsPlugin) | -| [`autocomplete-preset-algolia`](packages/autocomplete-preset-algolia) | Presets to use Algolia features with Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/getAlgoliaHits) | +| [`autocomplete-preset-algolia`](packages/autocomplete-preset-algolia) | Presets to use Algolia features with Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/getAlgoliaResults) | | [`autocomplete-theme-classic`](packages/autocomplete-theme-classic) | Classic theme for Autocomplete | [Documentation](https://autocomplete.algolia.com/docs/autocomplete-theme-classic) | ## Showcase diff --git a/bundlesize.config.json b/bundlesize.config.json index be73143ad..684c570cd 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,15 +2,15 @@ "files": [ { "path": "packages/autocomplete-core/dist/umd/index.production.js", - "maxSize": "4.75 kB" + "maxSize": "5 kB" }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", - "maxSize": "14.75 kB" + "maxSize": "15.25 kB" }, { "path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js", - "maxSize": "2 kB" + "maxSize": "2.25 kB" }, { "path": "packages/autocomplete-plugin-algolia-insights/dist/umd/index.production.js", diff --git a/cypress/test-apps/js/app.tsx b/cypress/test-apps/js/app.tsx index f43476ec3..2610c26b6 100644 --- a/cypress/test-apps/js/app.tsx +++ b/cypress/test-apps/js/app.tsx @@ -2,7 +2,7 @@ import { autocomplete, AutocompleteComponents, - getAlgoliaHits, + getAlgoliaResults, } from '@algolia/autocomplete-js'; import { AutocompleteInsightsApi, @@ -78,7 +78,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/cypress/test-apps/js/categoriesPlugin.tsx b/cypress/test-apps/js/categoriesPlugin.tsx index 01d071dd6..d6ec7e8c6 100644 --- a/cypress/test-apps/js/categoriesPlugin.tsx +++ b/cypress/test-apps/js/categoriesPlugin.tsx @@ -1,8 +1,5 @@ /** @jsx h */ -import { - AutocompletePlugin, - getAlgoliaFacetHits, -} from '@algolia/autocomplete-js'; +import { AutocompletePlugin, getAlgoliaFacets } from '@algolia/autocomplete-js'; import { SearchClient } from 'algoliasearch/lite'; import { h, Fragment } from 'preact'; @@ -24,13 +21,13 @@ export function createCategoriesPlugin({ { sourceId: 'categoriesPlugin', getItems() { - return getAlgoliaFacetHits({ + return getAlgoliaFacets({ searchClient, queries: [ { indexName: 'instant_search', + facet: 'categories', params: { - facetName: 'categories', facetQuery: query, maxFacetHits: query ? 3 : 10, }, diff --git a/cypress/test-apps/js/package.json b/cypress/test-apps/js/package.json index 261f63f74..cbb889ea3 100644 --- a/cypress/test-apps/js/package.json +++ b/cypress/test-apps/js/package.json @@ -16,8 +16,8 @@ "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.44", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.44", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.44", - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.6", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "preact": "10.5.13", "search-insights": "1.7.1" }, diff --git a/examples/multiple-datasets-with-headers/categoriesPlugin.tsx b/examples/multiple-datasets-with-headers/categoriesPlugin.tsx index adf88ca7f..43cfb8275 100644 --- a/examples/multiple-datasets-with-headers/categoriesPlugin.tsx +++ b/examples/multiple-datasets-with-headers/categoriesPlugin.tsx @@ -1,8 +1,5 @@ /** @jsx h */ -import { - AutocompletePlugin, - getAlgoliaFacetHits, -} from '@algolia/autocomplete-js'; +import { AutocompletePlugin, getAlgoliaFacets } from '@algolia/autocomplete-js'; import { SearchClient } from 'algoliasearch/lite'; import { h, Fragment } from 'preact'; @@ -24,7 +21,7 @@ export function createCategoriesPlugin({ { sourceId: 'categoriesPlugin', getItems() { - return getAlgoliaFacetHits({ + return getAlgoliaFacets({ searchClient, queries: [ { diff --git a/examples/multiple-datasets-with-headers/package.json b/examples/multiple-datasets-with-headers/package.json index f9309839e..2c621745a 100644 --- a/examples/multiple-datasets-with-headers/package.json +++ b/examples/multiple-datasets-with-headers/package.json @@ -13,7 +13,7 @@ "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/playground/app.tsx b/examples/playground/app.tsx index a621ac4da..e3dc93bce 100644 --- a/examples/playground/app.tsx +++ b/examples/playground/app.tsx @@ -2,7 +2,7 @@ import { autocomplete, AutocompleteComponents, - getAlgoliaHits, + getAlgoliaResults, } from '@algolia/autocomplete-js'; import { AutocompleteInsightsApi, @@ -18,7 +18,7 @@ import '@algolia/autocomplete-theme-classic'; import { createCategoriesPlugin } from './categoriesPlugin'; import { shortcutsPlugin } from './shortcutsPlugin'; -import { ProductHit } from './types'; +import { ProductRecord, ProductHit } from './types'; const appId = 'latency'; const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; @@ -72,7 +72,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -85,16 +85,19 @@ autocomplete({ }, }, ], - }).then(([hits]) => { - return hits.map((hit) => ({ - ...hit, - comments: hit.popularity % 100, - sale: hit.free_shipping, - // eslint-disable-next-line @typescript-eslint/camelcase - sale_price: hit.free_shipping - ? (hit.price - hit.price / 10).toFixed(2) - : hit.price.toString(), - })); + transformResponse({ hits }) { + const [bestBuyHits] = hits; + + return bestBuyHits.map((hit) => ({ + ...hit, + comments: hit.popularity % 100, + sale: hit.free_shipping, + // eslint-disable-next-line @typescript-eslint/camelcase + sale_price: hit.free_shipping + ? (hit.price - hit.price / 10).toFixed(2) + : hit.price.toString(), + })); + }, }); }, templates: { diff --git a/examples/playground/categoriesPlugin.tsx b/examples/playground/categoriesPlugin.tsx index 913f5061c..46ca0f025 100644 --- a/examples/playground/categoriesPlugin.tsx +++ b/examples/playground/categoriesPlugin.tsx @@ -1,8 +1,5 @@ /** @jsx h */ -import { - AutocompletePlugin, - getAlgoliaFacetHits, -} from '@algolia/autocomplete-js'; +import { AutocompletePlugin, getAlgoliaFacets } from '@algolia/autocomplete-js'; import { SearchClient } from 'algoliasearch/lite'; import { h, Fragment } from 'preact'; @@ -24,13 +21,13 @@ export function createCategoriesPlugin({ { sourceId: 'categoriesPlugin', getItems() { - return getAlgoliaFacetHits({ + return getAlgoliaFacets({ searchClient, queries: [ { indexName: 'instant_search', + facet: 'categories', params: { - facetName: 'categories', facetQuery: query, maxFacetHits: query ? 3 : 5, }, diff --git a/examples/playground/package.json b/examples/playground/package.json index 25be6f5c9..a61ca6404 100644 --- a/examples/playground/package.json +++ b/examples/playground/package.json @@ -15,8 +15,8 @@ "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.6", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "preact": "10.5.13", "search-insights": "1.7.1" }, diff --git a/examples/playground/types/ProductHit.ts b/examples/playground/types/ProductHit.ts index a4aaa79a4..74d7bd40f 100644 --- a/examples/playground/types/ProductHit.ts +++ b/examples/playground/types/ProductHit.ts @@ -1,6 +1,6 @@ import { Hit } from '@algolia/client-search'; -type ProductRecord = { +export type ProductRecord = { brand: string; categories: string[]; comments: number; diff --git a/examples/query-suggestions-with-categories/package.json b/examples/query-suggestions-with-categories/package.json index e966a5857..dd33c37e3 100644 --- a/examples/query-suggestions-with-categories/package.json +++ b/examples/query-suggestions-with-categories/package.json @@ -12,7 +12,7 @@ "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/query-suggestions-with-hits/app.tsx b/examples/query-suggestions-with-hits/app.tsx index 6f47883bc..445de1ed2 100644 --- a/examples/query-suggestions-with-hits/app.tsx +++ b/examples/query-suggestions-with-hits/app.tsx @@ -2,7 +2,7 @@ import { autocomplete, AutocompleteComponents, - getAlgoliaHits, + getAlgoliaResults, } from '@algolia/autocomplete-js'; import { AutocompleteInsightsApi, @@ -50,7 +50,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/examples/query-suggestions-with-hits/package.json b/examples/query-suggestions-with-hits/package.json index c3ccd33e2..60a93a12a 100644 --- a/examples/query-suggestions-with-hits/package.json +++ b/examples/query-suggestions-with-hits/package.json @@ -15,8 +15,8 @@ "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.6", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "preact": "10.5.13", "search-insights": "1.7.1" }, diff --git a/examples/query-suggestions-with-inline-categories/package.json b/examples/query-suggestions-with-inline-categories/package.json index d0b18481a..395be0388 100644 --- a/examples/query-suggestions-with-inline-categories/package.json +++ b/examples/query-suggestions-with-inline-categories/package.json @@ -12,7 +12,7 @@ "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/query-suggestions-with-recent-searches-and-categories/package.json b/examples/query-suggestions-with-recent-searches-and-categories/package.json index 176552fa0..74875e060 100644 --- a/examples/query-suggestions-with-recent-searches-and-categories/package.json +++ b/examples/query-suggestions-with-recent-searches-and-categories/package.json @@ -13,8 +13,8 @@ "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.6", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/query-suggestions-with-recent-searches/package.json b/examples/query-suggestions-with-recent-searches/package.json index 755bcc275..bb0da050d 100644 --- a/examples/query-suggestions-with-recent-searches/package.json +++ b/examples/query-suggestions-with-recent-searches/package.json @@ -13,7 +13,7 @@ "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/query-suggestions/package.json b/examples/query-suggestions/package.json index e8b11057a..fd85b6c30 100644 --- a/examples/query-suggestions/package.json +++ b/examples/query-suggestions/package.json @@ -12,7 +12,7 @@ "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/react-renderer/package.json b/examples/react-renderer/package.json index ef3699955..422271953 100644 --- a/examples/react-renderer/package.json +++ b/examples/react-renderer/package.json @@ -7,8 +7,8 @@ "@algolia/autocomplete-core": "1.0.0-alpha.45", "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "@algolia/client-search": "4.8.4", - "algoliasearch": "4.8.4", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "react": "17.0.1", "react-dom": "17.0.1", "react-scripts": "4.0.1" diff --git a/examples/react-renderer/src/Autocomplete.tsx b/examples/react-renderer/src/Autocomplete.tsx index 1965f9b98..2280f137c 100644 --- a/examples/react-renderer/src/Autocomplete.tsx +++ b/examples/react-renderer/src/Autocomplete.tsx @@ -3,7 +3,7 @@ import { AutocompleteState, createAutocomplete, } from '@algolia/autocomplete-core'; -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; import { Hit } from '@algolia/client-search'; import algoliasearch from 'algoliasearch/lite'; import React from 'react'; @@ -54,7 +54,7 @@ export function Autocomplete( { sourceId: 'products', getItems({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/examples/recently-viewed-items/app.tsx b/examples/recently-viewed-items/app.tsx index 860bf913d..2f41edc69 100644 --- a/examples/recently-viewed-items/app.tsx +++ b/examples/recently-viewed-items/app.tsx @@ -2,7 +2,7 @@ import { autocomplete, AutocompleteComponents, - getAlgoliaHits, + getAlgoliaResults, } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch'; import { h, Fragment } from 'preact'; @@ -35,7 +35,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/examples/recently-viewed-items/package.json b/examples/recently-viewed-items/package.json index 3e2304156..01444ae86 100644 --- a/examples/recently-viewed-items/package.json +++ b/examples/recently-viewed-items/package.json @@ -12,8 +12,8 @@ "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.6", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { diff --git a/examples/starter-algolia/app.tsx b/examples/starter-algolia/app.tsx index e47d43d6c..3baef57ff 100644 --- a/examples/starter-algolia/app.tsx +++ b/examples/starter-algolia/app.tsx @@ -1,5 +1,5 @@ /** @jsx h */ -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import { Hit } from '@algolia/client-search'; import algoliasearch from 'algoliasearch'; import { h } from 'preact'; @@ -30,7 +30,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/examples/starter-algolia/package.json b/examples/starter-algolia/package.json index 884575cdf..2c92c6d61 100644 --- a/examples/starter-algolia/package.json +++ b/examples/starter-algolia/package.json @@ -11,11 +11,11 @@ "dependencies": { "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { - "@algolia/client-search": "4.8.6", + "@algolia/client-search": "4.9.0", "parcel": "2.0.0-beta.2" }, "keywords": [ diff --git a/examples/voice-search/app.tsx b/examples/voice-search/app.tsx index 4505ac0b4..75bb33f95 100644 --- a/examples/voice-search/app.tsx +++ b/examples/voice-search/app.tsx @@ -1,5 +1,5 @@ /** @jsx h */ -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import { Hit } from '@algolia/client-search'; import algoliasearch from 'algoliasearch'; import { h } from 'preact'; @@ -35,7 +35,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/examples/voice-search/package.json b/examples/voice-search/package.json index 51cbbcfee..1b524b778 100644 --- a/examples/voice-search/package.json +++ b/examples/voice-search/package.json @@ -11,11 +11,11 @@ "dependencies": { "@algolia/autocomplete-js": "1.0.0-alpha.45", "@algolia/autocomplete-theme-classic": "1.0.0-alpha.45", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "preact": "10.5.13" }, "devDependencies": { - "@algolia/client-search": "4.8.6", + "@algolia/client-search": "4.9.0", "parcel": "2.0.0-beta.2" }, "keywords": [ diff --git a/package.json b/package.json index 7eb0dc399..bef37e511 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@types/react-dom": "^17.0.0", "@typescript-eslint/eslint-plugin": "2.19.0", "@typescript-eslint/parser": "2.19.0", - "algoliasearch": "4.8.6", + "algoliasearch": "4.9.0", "autoprefixer": "10.0.4", "babel-eslint": "10.1.0", "babel-loader": "8.2.2", @@ -103,7 +103,7 @@ "stylelint-no-unsupported-browser-features": "4.1.4", "stylelint-order": "4.1.0", "stylelint-prettier": "1.1.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "watch": "1.0.2" } } diff --git a/packages/autocomplete-core/package.json b/packages/autocomplete-core/package.json index 272ba0671..b1558fdac 100644 --- a/packages/autocomplete-core/package.json +++ b/packages/autocomplete-core/package.json @@ -32,5 +32,10 @@ }, "dependencies": { "@algolia/autocomplete-shared": "1.0.0-alpha.45" + }, + "devDependencies": { + "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.45", + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0" } } diff --git a/packages/autocomplete-core/src/__tests__/getSources.test.ts b/packages/autocomplete-core/src/__tests__/getSources.test.ts index d3e3da4b1..686bc03d0 100644 --- a/packages/autocomplete-core/src/__tests__/getSources.test.ts +++ b/packages/autocomplete-core/src/__tests__/getSources.test.ts @@ -92,13 +92,13 @@ describe('getSources', () => { const onStateChange = jest.fn(); const plugin = { getSources: () => { - return [createSource()]; + return [createSource({ sourceId: 'source1' })]; }, }; const { inputElement } = createPlayground(createAutocomplete, { onStateChange, getSources: () => { - return [createSource()]; + return [createSource({ sourceId: 'source2' })]; }, plugins: [plugin], }); diff --git a/packages/autocomplete-core/src/getAutocompleteSetters.ts b/packages/autocomplete-core/src/getAutocompleteSetters.ts index 582fd456a..4a698129a 100644 --- a/packages/autocomplete-core/src/getAutocompleteSetters.ts +++ b/packages/autocomplete-core/src/getAutocompleteSetters.ts @@ -29,7 +29,7 @@ export function getAutocompleteSetters({ let baseItemId = 0; const value = rawValue.map>((collection) => ({ ...collection, - // We flatten the stored items to support calling `getAlgoliaHits` + // We flatten the stored items to support calling `getAlgoliaResults` // from the source itself. items: flatten(collection.items as any).map((item: any) => ({ ...item, diff --git a/packages/autocomplete-core/src/onInput.ts b/packages/autocomplete-core/src/onInput.ts index 575f356d9..7cdce3e70 100644 --- a/packages/autocomplete-core/src/onInput.ts +++ b/packages/autocomplete-core/src/onInput.ts @@ -1,5 +1,4 @@ -import { invariant } from '@algolia/autocomplete-shared'; - +import { preResolve, resolve, postResolve } from './resolve'; import { AutocompleteScopeApi, AutocompleteState, @@ -82,7 +81,6 @@ export function onInput({ .then((sources) => { setStatus('loading'); - // @TODO: convert `Promise.all` to fetching strategy. return Promise.all( sources.map((source) => { return Promise.resolve( @@ -92,18 +90,13 @@ export function onInput({ state: store.getState(), ...setters, }) - ).then((items) => { - invariant( - Array.isArray(items), - `The \`getItems\` function must return an array of items but returned type ${JSON.stringify( - typeof items - )}:\n\n${JSON.stringify(items, null, 2)}` - ); - - return { source, items }; - }); + ).then((itemsOrDescription) => + preResolve(itemsOrDescription, source.sourceId) + ); }) ) + .then(resolve) + .then((responses) => postResolve(responses, sources)) .then((collections) => { setStatus('idle'); setCollections(collections as any); diff --git a/packages/autocomplete-core/src/resolve.ts b/packages/autocomplete-core/src/resolve.ts new file mode 100644 index 000000000..1505f77cd --- /dev/null +++ b/packages/autocomplete-core/src/resolve.ts @@ -0,0 +1,185 @@ +import type { + Execute, + ExecuteResponse, + RequesterDescription, + TransformResponse, +} from '@algolia/autocomplete-preset-algolia'; +import { invariant } from '@algolia/autocomplete-shared'; +import { + MultipleQueriesQuery, + SearchForFacetValuesResponse, + SearchResponse, +} from '@algolia/client-search'; +import type { SearchClient } from 'algoliasearch/lite'; + +import { BaseItem, InternalAutocompleteSource } from './types'; +import { flatten, mapToAlgoliaResponse } from './utils'; + +function isDescription( + item: + | RequestDescriptionPreResolved + | RequestDescriptionPreResolvedCustom + | PackedDescription +): item is RequestDescriptionPreResolved { + return Boolean((item as RequestDescriptionPreResolved).execute); +} + +function isRequesterDescription( + description: TItem[] | TItem[][] | RequesterDescription +): description is RequesterDescription { + return Boolean((description as RequesterDescription).execute); +} + +type PackedDescription = { + searchClient: SearchClient; + execute: Execute; + items: RequestDescriptionPreResolved['requests']; +}; + +type RequestDescriptionPreResolved = Pick< + RequesterDescription, + 'execute' | 'searchClient' | 'transformResponse' +> & { + requests: Array<{ + query: MultipleQueriesQuery; + sourceId: string; + transformResponse: TransformResponse; + }>; +}; + +type RequestDescriptionPreResolvedCustom = { + items: TItem[] | TItem[][]; + sourceId: string; + transformResponse?: undefined; +}; + +export function preResolve( + itemsOrDescription: TItem[] | TItem[][] | RequesterDescription, + sourceId: string +): + | RequestDescriptionPreResolved + | RequestDescriptionPreResolvedCustom { + if (isRequesterDescription(itemsOrDescription)) { + return { + ...itemsOrDescription, + requests: itemsOrDescription.queries.map((query) => ({ + query, + sourceId, + transformResponse: itemsOrDescription.transformResponse, + })), + }; + } + + return { + items: itemsOrDescription, + sourceId, + }; +} + +export function resolve( + items: Array< + | RequestDescriptionPreResolved + | RequestDescriptionPreResolvedCustom + > +) { + const packed = items.reduce< + Array | PackedDescription> + >((acc, current) => { + if (!isDescription(current)) { + acc.push(current); + return acc; + } + + const { searchClient, execute, requests } = current; + + const container = acc.find>( + (item): item is PackedDescription => { + return ( + isDescription(current) && + isDescription(item) && + item.searchClient === searchClient && + item.execute === execute + ); + } + ); + + if (container) { + container.items.push(...requests); + } else { + const request: PackedDescription = { + execute, + items: requests, + searchClient, + }; + acc.push(request); + } + + return acc; + }, []); + + const values = packed.map< + | Promise> + | ReturnType> + >((maybeDescription) => { + if (!isDescription(maybeDescription)) { + return Promise.resolve( + maybeDescription as RequestDescriptionPreResolvedCustom + ); + } + + const { + execute, + items, + searchClient, + } = maybeDescription as PackedDescription; + + return execute({ + searchClient, + requests: items, + }); + }); + + return Promise.all< + RequestDescriptionPreResolvedCustom | ExecuteResponse + >(values).then((responses) => + flatten< + RequestDescriptionPreResolvedCustom | ExecuteResponse[0] + >(responses) + ); +} + +export function postResolve( + responses: Array< + RequestDescriptionPreResolvedCustom | ExecuteResponse[0] + >, + sources: Array> +) { + return sources.map((source) => { + const matches = responses.filter( + (response) => response.sourceId === source.sourceId + ); + const results = matches.map(({ items }) => items); + const transform = matches[0].transformResponse; + const items = transform + ? transform( + mapToAlgoliaResponse( + results as Array< + SearchForFacetValuesResponse | SearchResponse + > + ) + ) + : results; + + invariant( + Array.isArray(items), + `The \`getItems\` function must return an array of items but returned type ${JSON.stringify( + typeof items + )}:\n\n${JSON.stringify(items, null, 2)}` + ); + + return { + source, + items, + }; + }); +} diff --git a/packages/autocomplete-core/src/types/AutocompleteSource.ts b/packages/autocomplete-core/src/types/AutocompleteSource.ts index 9857faba7..9bd9c74d0 100644 --- a/packages/autocomplete-core/src/types/AutocompleteSource.ts +++ b/packages/autocomplete-core/src/types/AutocompleteSource.ts @@ -1,4 +1,5 @@ -import { MaybePromise } from '@algolia/autocomplete-shared'; +import type { RequesterDescription } from '@algolia/autocomplete-preset-algolia'; +import type { MaybePromise } from '@algolia/autocomplete-shared'; import { AutocompleteScopeApi, BaseItem } from './AutocompleteApi'; import { GetSourcesParams } from './AutocompleteOptions'; @@ -52,7 +53,9 @@ export interface AutocompleteSource { * * You can use this function to filter the items based on the query. */ - getItems(params: GetSourcesParams): MaybePromise; + getItems( + params: GetSourcesParams + ): MaybePromise>; /** * The function called whenever an item is selected. */ diff --git a/packages/autocomplete-core/src/utils/__tests__/getNormalizedSources.test.ts b/packages/autocomplete-core/src/utils/__tests__/getNormalizedSources.test.ts index ced2b301f..6b141e0c1 100644 --- a/packages/autocomplete-core/src/utils/__tests__/getNormalizedSources.test.ts +++ b/packages/autocomplete-core/src/utils/__tests__/getNormalizedSources.test.ts @@ -112,6 +112,38 @@ describe('getNormalizedSources', () => { ); }); + test('with duplicate `sourceId`s throws', async () => { + const getSources = () => [ + { + sourceId: 'testSource', + getItems() { + return []; + }, + templates: { + item() {}, + }, + }, + { + sourceId: 'testSource', + getItems() { + return []; + }, + templates: { + item() {}, + }, + }, + ]; + const params = { + query: '', + state: createState({}), + ...createScopeApi(), + }; + + await expect(getNormalizedSources(getSources, params)).rejects.toEqual( + new Error('[Autocomplete] The `sourceId` "testSource" is not unique.') + ); + }); + test('provides a default implementation for getItemInputValue which returns the query', async () => { const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }]; const params = { diff --git a/packages/autocomplete-core/src/utils/__tests__/mapToAlgoliaResponse.test.ts b/packages/autocomplete-core/src/utils/__tests__/mapToAlgoliaResponse.test.ts new file mode 100644 index 000000000..a638703ec --- /dev/null +++ b/packages/autocomplete-core/src/utils/__tests__/mapToAlgoliaResponse.test.ts @@ -0,0 +1,84 @@ +import { + createSingleSearchResponse, + createSFFVResponse, +} from '../../../../../test/utils'; +import { mapToAlgoliaResponse } from '../mapToAlgoliaResponse'; + +describe('mapToAlgoliaResponse', () => { + test('pre-maps the hits and facet hits from the results', () => { + const searchResponse = createSingleSearchResponse(); + const sffvResponse = createSFFVResponse(); + const response = mapToAlgoliaResponse([ + searchResponse, + searchResponse, + sffvResponse, + ]); + + expect(response).toEqual({ + results: expect.arrayContaining([ + expect.objectContaining({ + hits: expect.any(Array), + }), + expect.objectContaining({ + hits: expect.any(Array), + }), + ]), + hits: [[], []], + facetHits: [[]], + }); + }); + + test('returns an empty array when there are no hits', () => { + const { hits } = mapToAlgoliaResponse([createSFFVResponse()]); + + expect(hits).toEqual([]); + }); + + test('returns an empty array when there are no facet hits', () => { + const { facetHits } = mapToAlgoliaResponse([createSingleSearchResponse()]); + + expect(facetHits).toEqual([]); + }); + + test('returns formatted facet hits', () => { + const { facetHits } = mapToAlgoliaResponse([ + createSFFVResponse({ + facetHits: [ + { + count: 1, + value: 'Label 1', + highlighted: 'Label 1', + }, + { + count: 2, + value: 'Label 2', + highlighted: 'Label 2', + }, + ], + }), + ]); + + expect(facetHits).toEqual([ + [ + { + count: 1, + label: 'Label 1', + _highlightResult: { + label: { + value: 'Label 1', + }, + }, + }, + { + count: 2, + label: 'Label 2', + _highlightResult: { + label: { + value: 'Label 2', + }, + }, + }, + ], + ]); + }); +}); diff --git a/packages/autocomplete-core/src/utils/getNormalizedSources.ts b/packages/autocomplete-core/src/utils/getNormalizedSources.ts index fb560c50b..19a07047c 100644 --- a/packages/autocomplete-core/src/utils/getNormalizedSources.ts +++ b/packages/autocomplete-core/src/utils/getNormalizedSources.ts @@ -15,6 +15,8 @@ export function getNormalizedSources( getSources: GetSources, params: GetSourcesParams ): ReturnType> { + const seenSourceIds: string[] = []; + return Promise.resolve(getSources(params)).then((sources) => { invariant( Array.isArray(sources), @@ -37,6 +39,16 @@ export function getNormalizedSources( 'A source must provide a `sourceId` string.' ); + if (seenSourceIds.includes(source.sourceId)) { + throw new Error( + `[Autocomplete] The \`sourceId\` ${JSON.stringify( + source.sourceId + )} is not unique.` + ); + } + + seenSourceIds.push(source.sourceId); + const normalizedSource: InternalAutocompleteSource = { getItemInputValue({ state }) { return state.query; diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index 4a077c480..d0998991d 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -4,4 +4,5 @@ export * from './getNextActiveItemId'; export * from './getNormalizedSources'; export * from './getActiveItem'; export * from './isOrContainsNode'; +export * from './mapToAlgoliaResponse'; export * from './noop'; diff --git a/packages/autocomplete-core/src/utils/mapToAlgoliaResponse.ts b/packages/autocomplete-core/src/utils/mapToAlgoliaResponse.ts new file mode 100644 index 000000000..fc5ad6817 --- /dev/null +++ b/packages/autocomplete-core/src/utils/mapToAlgoliaResponse.ts @@ -0,0 +1,30 @@ +import type { + SearchForFacetValuesResponse, + SearchResponse, +} from '@algolia/client-search'; + +export function mapToAlgoliaResponse( + results: Array | SearchForFacetValuesResponse> +) { + return { + results, + hits: results + .map((result) => (result as SearchResponse).hits) + .filter(Boolean), + facetHits: results + .map((result) => + (result as SearchForFacetValuesResponse).facetHits?.map((facetHit) => { + return { + label: facetHit.value, + count: facetHit.count, + _highlightResult: { + label: { + value: facetHit.highlighted, + }, + }, + }; + }) + ) + .filter(Boolean), + }; +} diff --git a/packages/autocomplete-js/package.json b/packages/autocomplete-js/package.json index cc7972ba1..f2c9710ee 100644 --- a/packages/autocomplete-js/package.json +++ b/packages/autocomplete-js/package.json @@ -37,7 +37,7 @@ "preact": "^10.0.0" }, "devDependencies": { - "@algolia/client-search": "4.8.3" + "@algolia/client-search": "4.9.0" }, "peerDependencies": { "@algolia/client-search": "^4.5.1" diff --git a/packages/autocomplete-js/src/__tests__/getAlgoliaFacetHits.test.ts b/packages/autocomplete-js/src/__tests__/getAlgoliaFacetHits.test.ts deleted file mode 100644 index 5bd655d28..000000000 --- a/packages/autocomplete-js/src/__tests__/getAlgoliaFacetHits.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import algoliaPreset from '@algolia/autocomplete-preset-algolia'; - -import { createSearchClient } from '../../../../test/utils'; -import { getAlgoliaFacetHits } from '../getAlgoliaFacetHits'; -import { version } from '../version'; - -jest.mock('@algolia/autocomplete-preset-algolia', () => { - const module = jest.requireActual('@algolia/autocomplete-preset-algolia'); - - return { - ...module, - getAlgoliaFacetHits: jest.fn(), - }; -}); - -describe('getAlgoliaFacetHits', () => { - test('forwards params to the preset function', () => { - const searchClient = createSearchClient(); - - getAlgoliaFacetHits({ searchClient, queries: [] }); - - expect(algoliaPreset.getAlgoliaFacetHits).toHaveBeenCalledTimes(1); - expect(algoliaPreset.getAlgoliaFacetHits).toHaveBeenCalledWith({ - searchClient, - queries: [], - userAgents: [{ segment: 'autocomplete-js', version }], - }); - }); -}); diff --git a/packages/autocomplete-js/src/__tests__/getAlgoliaHits.test.ts b/packages/autocomplete-js/src/__tests__/getAlgoliaHits.test.ts deleted file mode 100644 index 68a35b277..000000000 --- a/packages/autocomplete-js/src/__tests__/getAlgoliaHits.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import algoliaPreset from '@algolia/autocomplete-preset-algolia'; - -import { createSearchClient } from '../../../../test/utils'; -import { getAlgoliaHits } from '../getAlgoliaHits'; -import { version } from '../version'; - -jest.mock('@algolia/autocomplete-preset-algolia', () => { - const module = jest.requireActual('@algolia/autocomplete-preset-algolia'); - - return { - ...module, - getAlgoliaHits: jest.fn(), - }; -}); - -describe('getAlgoliaHits', () => { - test('forwards params to the preset function', () => { - const searchClient = createSearchClient(); - - getAlgoliaHits({ searchClient, queries: [] }); - - expect(algoliaPreset.getAlgoliaHits).toHaveBeenCalledTimes(1); - expect(algoliaPreset.getAlgoliaHits).toHaveBeenCalledWith({ - searchClient, - queries: [], - userAgents: [{ segment: 'autocomplete-js', version }], - }); - }); -}); diff --git a/packages/autocomplete-js/src/__tests__/getAlgoliaResults.test.ts b/packages/autocomplete-js/src/__tests__/getAlgoliaResults.test.ts deleted file mode 100644 index eea7a2303..000000000 --- a/packages/autocomplete-js/src/__tests__/getAlgoliaResults.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import algoliaPreset from '@algolia/autocomplete-preset-algolia'; - -import { createSearchClient } from '../../../../test/utils'; -import { getAlgoliaResults } from '../getAlgoliaResults'; -import { version } from '../version'; - -jest.mock('@algolia/autocomplete-preset-algolia', () => { - const module = jest.requireActual('@algolia/autocomplete-preset-algolia'); - - return { - ...module, - getAlgoliaResults: jest.fn(), - }; -}); - -describe('getAlgoliaResults', () => { - test('forwards params to the preset function', () => { - const searchClient = createSearchClient(); - - getAlgoliaResults({ searchClient, queries: [] }); - - expect(algoliaPreset.getAlgoliaResults).toHaveBeenCalledTimes(1); - expect(algoliaPreset.getAlgoliaResults).toHaveBeenCalledWith({ - searchClient, - queries: [], - userAgents: [{ segment: 'autocomplete-js', version }], - }); - }); -}); diff --git a/packages/autocomplete-js/src/__tests__/panelPlacement.test.ts b/packages/autocomplete-js/src/__tests__/panelPlacement.test.ts index 826dd2f5d..11795da54 100644 --- a/packages/autocomplete-js/src/__tests__/panelPlacement.test.ts +++ b/packages/autocomplete-js/src/__tests__/panelPlacement.test.ts @@ -203,7 +203,7 @@ describe('panelPlacement', () => { (error) => { error.preventDefault(); expect(error.message).toEqual( - 'The `panelPlacement` value "invalid" is not valid.' + '[Autocomplete] The `panelPlacement` value "invalid" is not valid.' ); done(); }, diff --git a/packages/autocomplete-js/src/__tests__/requester.test.ts b/packages/autocomplete-js/src/__tests__/requester.test.ts new file mode 100644 index 000000000..3b6060596 --- /dev/null +++ b/packages/autocomplete-js/src/__tests__/requester.test.ts @@ -0,0 +1,574 @@ +import { fireEvent, waitFor, within } from '@testing-library/dom'; + +import { + createMultiSearchResponse, + createSearchClient, +} from '../../../../test/utils'; +import { autocomplete } from '../autocomplete'; +import { getAlgoliaResults, getAlgoliaFacets } from '../requesters'; + +describe('requester', () => { + beforeEach(() => { + document.body.innerHTML = ''; + }); + + test('batches calls when possible and re-dispatches results to the right sources', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const customFetch = jest.fn(() => + Promise.resolve([ + { + label: 'Label 1', + }, + { + label: 'Label 2', + }, + ]) + ); + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { hits: [{ objectID: '1', label: 'Hit 1' }] }, + { facetHits: [{ count: 2, value: 'Hit 2' }] }, + { hits: [{ objectID: '3', label: 'Hit 3' }] }, + { hits: [{ objectID: '4', label: 'Hit 4' }] }, + { hits: [{ objectID: '5', label: 'Hit 5' }] }, + { facetHits: [{ count: 6, value: 'Hit 6' }] } + ) + ) + ), + }); + const searchClient2 = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>({ + hits: [{ objectID: '7', label: 'Hit 7' }], + }) + ) + ), + }); + + autocomplete({ + container, + panelContainer, + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query, + }, + ], + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'facets', + getItems() { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: query, + }, + }, + ], + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'custom', + getItems() { + return customFetch(); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'differentClient', + getItems() { + return getAlgoliaResults({ + searchClient: searchClient2, + queries: [ + { + indexName: 'indexName', + query, + }, + ], + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'multi', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query, + }, + { + indexName: 'indexName2', + query, + }, + ], + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'multiTypes', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query, + }, + { + indexName: 'indexName', + type: 'facet', + facet: 'categories', + params: { + facetQuery: query, + }, + }, + ], + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'static', + getItems() { + return [ + { + label: 'Static label 1', + }, + { + label: 'Static label 2', + }, + ]; + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + ]; + }, + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + panelContainer.querySelector('.aa-Panel') + ).toBeInTheDocument(); + + expect(customFetch).toHaveBeenCalledTimes(1); + + expect(searchClient.search).toHaveBeenNthCalledWith(1, [ + expect.objectContaining({ + indexName: 'indexName', + query: 'a', + }), + expect.objectContaining({ + indexName: 'indexName', + type: 'facet', + facet: 'categories', + params: expect.objectContaining({ + facetQuery: 'a', + }), + }), + expect.objectContaining({ + indexName: 'indexName', + query: 'a', + }), + expect.objectContaining({ + indexName: 'indexName2', + query: 'a', + }), + expect.objectContaining({ + indexName: 'indexName', + query: 'a', + }), + expect.objectContaining({ + indexName: 'indexName', + type: 'facet', + facet: 'categories', + params: expect.objectContaining({ + facetQuery: 'a', + }), + }), + ]); + + expect(searchClient2.search).toHaveBeenNthCalledWith(1, [ + expect.objectContaining({ + indexName: 'indexName', + query: 'a', + }), + ]); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="hits"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"objectID\\":\\"1\\",\\"label\\":\\"Hit 1\\",\\"__autocomplete_id\\":0}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="facets"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"label\\":\\"Hit 2\\",\\"count\\":2,\\"_highlightResult\\":{\\"label\\":{}},\\"__autocomplete_id\\":1}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="custom"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"label\\":\\"Label 1\\",\\"__autocomplete_id\\":2}", + "{\\"label\\":\\"Label 2\\",\\"__autocomplete_id\\":3}", + ] + `); + + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="differentClient"]' + ) + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"objectID\\":\\"7\\",\\"label\\":\\"Hit 7\\",\\"__autocomplete_id\\":4}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="multi"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"objectID\\":\\"3\\",\\"label\\":\\"Hit 3\\",\\"__autocomplete_id\\":5}", + "{\\"objectID\\":\\"4\\",\\"label\\":\\"Hit 4\\",\\"__autocomplete_id\\":6}", + ] + `); + + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="multiTypes"]' + ) + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"objectID\\":\\"5\\",\\"label\\":\\"Hit 5\\",\\"__autocomplete_id\\":7}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="static"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"label\\":\\"Static label 1\\",\\"__autocomplete_id\\":8}", + "{\\"label\\":\\"Static label 2\\",\\"__autocomplete_id\\":9}", + ] + `); + }); + }); + + test('transforms the response before forwarding it to the state', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { + hits: [{ objectID: '1', label: 'Hit 1' }], + }, + { facetHits: [{ count: 2, value: 'Hit 2' }] } + ) + ) + ), + }); + + autocomplete({ + container, + panelContainer, + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query, + }, + ], + transformResponse({ results, hits }) { + return hits.map((hit) => ({ + ...hit, + hitsPerPage: results[0].hitsPerPage, + })); + }, + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'facets', + getItems() { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: query, + }, + }, + ], + transformResponse({ results, facetHits }) { + return facetHits.map((hit) => ({ + ...hit, + hitsPerPage: results[0].hitsPerPage, + })); + }, + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + ]; + }, + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="hits"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"0\\":{\\"objectID\\":\\"1\\",\\"label\\":\\"Hit 1\\"},\\"hitsPerPage\\":20,\\"__autocomplete_id\\":0}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="facets"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"0\\":{\\"label\\":\\"Hit 2\\",\\"count\\":2,\\"_highlightResult\\":{\\"label\\":{}}},\\"hitsPerPage\\":20,\\"__autocomplete_id\\":1}", + ] + `); + }); + }); + + test('properly maps response based on the expected Algolia data type', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { + hits: [{ objectID: '1', label: 'Hit 1' }], + }, + { facetHits: [{ count: 2, value: 'Hit 2' }] } + ) + ) + ), + }); + + autocomplete({ + container, + panelContainer, + getSources({ query }) { + return [ + { + sourceId: 'hits', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query, + }, + ], + transformResponse({ results, hits, facetHits }) { + return hits.map((hit) => ({ + ...hit, + results, + facetHits, + })); + }, + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + { + sourceId: 'facets', + getItems() { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: query, + }, + }, + ], + transformResponse({ results, facetHits }) { + return facetHits.map((hit) => ({ + ...hit, + results, + facetHits, + })); + }, + }); + }, + templates: { + item({ item }) { + return JSON.stringify(item); + }, + }, + }, + ]; + }, + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="hits"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"0\\":{\\"objectID\\":\\"1\\",\\"label\\":\\"Hit 1\\"},\\"results\\":[{\\"page\\":0,\\"hitsPerPage\\":20,\\"nbHits\\":1,\\"nbPages\\":1,\\"processingTimeMS\\":0,\\"hits\\":[{\\"objectID\\":\\"1\\",\\"label\\":\\"Hit 1\\"}],\\"query\\":\\"\\",\\"params\\":\\"\\",\\"exhaustiveNbHits\\":true,\\"exhaustiveFacetsCount\\":true}],\\"facetHits\\":[],\\"__autocomplete_id\\":0}", + ] + `); + + expect( + within( + panelContainer.querySelector('[data-autocomplete-source-id="facets"]') + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toMatchInlineSnapshot(` + Array [ + "{\\"0\\":{\\"label\\":\\"Hit 2\\",\\"count\\":2,\\"_highlightResult\\":{\\"label\\":{}}},\\"results\\":[{\\"page\\":0,\\"hitsPerPage\\":20,\\"nbHits\\":0,\\"nbPages\\":0,\\"processingTimeMS\\":0,\\"hits\\":[],\\"query\\":\\"\\",\\"params\\":\\"\\",\\"exhaustiveNbHits\\":true,\\"exhaustiveFacetsCount\\":true,\\"facetHits\\":[{\\"count\\":2,\\"value\\":\\"Hit 2\\"}]}],\\"facetHits\\":[[{\\"label\\":\\"Hit 2\\",\\"count\\":2,\\"_highlightResult\\":{\\"label\\":{}}}]],\\"__autocomplete_id\\":1}", + ] + `); + }); + }); +}); diff --git a/packages/autocomplete-js/src/getAlgoliaFacetHits.ts b/packages/autocomplete-js/src/getAlgoliaFacetHits.ts deleted file mode 100644 index fcd835663..000000000 --- a/packages/autocomplete-js/src/getAlgoliaFacetHits.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - getAlgoliaFacetHits as getAlgoliaFacetHitsOriginal, - SearchForFacetValuesParams, -} from '@algolia/autocomplete-preset-algolia'; - -import { version } from './version'; - -export function getAlgoliaFacetHits({ - searchClient, - queries, -}: Pick) { - return getAlgoliaFacetHitsOriginal({ - searchClient, - queries, - userAgents: [{ segment: 'autocomplete-js', version }], - }); -} diff --git a/packages/autocomplete-js/src/getAlgoliaHits.ts b/packages/autocomplete-js/src/getAlgoliaHits.ts deleted file mode 100644 index 4ce104c76..000000000 --- a/packages/autocomplete-js/src/getAlgoliaHits.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - getAlgoliaHits as getAlgoliaHitsOriginal, - SearchParams, -} from '@algolia/autocomplete-preset-algolia'; - -import { version } from './version'; - -export function getAlgoliaHits({ - searchClient, - queries, -}: Pick) { - return getAlgoliaHitsOriginal({ - searchClient, - queries, - userAgents: [{ segment: 'autocomplete-js', version }], - }); -} diff --git a/packages/autocomplete-js/src/getAlgoliaResults.ts b/packages/autocomplete-js/src/getAlgoliaResults.ts deleted file mode 100644 index 22a908a0c..000000000 --- a/packages/autocomplete-js/src/getAlgoliaResults.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - getAlgoliaResults as getAlgoliaResultsOriginal, - SearchParams, -} from '@algolia/autocomplete-preset-algolia'; - -import { version } from './version'; - -export function getAlgoliaResults({ - searchClient, - queries, -}: Pick) { - return getAlgoliaResultsOriginal({ - searchClient, - queries, - userAgents: [{ segment: 'autocomplete-js', version }], - }); -} diff --git a/packages/autocomplete-js/src/getPanelPlacementStyle.ts b/packages/autocomplete-js/src/getPanelPlacementStyle.ts index f2e20c0f6..fad8bc9f9 100644 --- a/packages/autocomplete-js/src/getPanelPlacementStyle.ts +++ b/packages/autocomplete-js/src/getPanelPlacementStyle.ts @@ -60,7 +60,7 @@ export function getPanelPlacementStyle({ default: { throw new Error( - `The \`panelPlacement\` value ${JSON.stringify( + `[Autocomplete] The \`panelPlacement\` value ${JSON.stringify( panelPlacement )} is not valid.` ); diff --git a/packages/autocomplete-js/src/index.ts b/packages/autocomplete-js/src/index.ts index 32dfb3a61..05dd16bbb 100644 --- a/packages/autocomplete-js/src/index.ts +++ b/packages/autocomplete-js/src/index.ts @@ -1,5 +1,3 @@ export * from './autocomplete'; -export * from './getAlgoliaFacetHits'; -export * from './getAlgoliaHits'; -export * from './getAlgoliaResults'; +export * from './requesters'; export * from './types'; diff --git a/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaFacets.test.ts b/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaFacets.test.ts new file mode 100644 index 000000000..ed32e6d56 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaFacets.test.ts @@ -0,0 +1,91 @@ +import { createSearchClient } from '../../../../../test/utils'; +import { getAlgoliaFacets } from '../getAlgoliaFacets'; + +describe('getAlgoliaFacets', () => { + test('returns the description', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + + expect(description).toEqual({ + execute: expect.any(Function), + transformResponse: expect.any(Function), + searchClient, + queries: [ + { + indexName: 'indexName', + type: 'facet', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + }); + + test('defaults transformItems to retrieve facetHits', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + + const transformedResponse = description.transformResponse({ + results: [], + hits: [], + facetHits: [ + [ + { + count: 1, + label: 'Label', + _highlightResult: { + label: { + value: 'Label', + }, + }, + }, + ], + ], + }); + + expect(transformedResponse).toEqual([ + [ + { + count: 1, + label: 'Label', + _highlightResult: { + label: { + value: 'Label', + }, + }, + }, + ], + ]); + }); +}); diff --git a/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaResults.test.ts b/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaResults.test.ts new file mode 100644 index 000000000..2d17b4588 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/__tests__/getAlgoliaResults.test.ts @@ -0,0 +1,82 @@ +import { createSearchClient } from '../../../../../test/utils'; +import { getAlgoliaResults } from '../getAlgoliaResults'; + +describe('getAlgoliaResults', () => { + test('returns the description', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + + expect(description).toEqual({ + execute: expect.any(Function), + transformResponse: expect.any(Function), + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + }); + + test('defaults transformItems to retrieve hits', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaResults<{ label: string }>({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + ], + }); + + const transformedResponse = description.transformResponse({ + results: [], + hits: [ + [ + { + objectID: '1', + label: 'Label', + _highlightResult: { + label: { value: 'Label', matchLevel: 'none', matchedWords: [] }, + }, + }, + ], + ], + facetHits: [], + }); + + expect(transformedResponse).toEqual([ + [ + { + objectID: '1', + label: 'Label', + _highlightResult: { + label: { value: 'Label', matchLevel: 'none', matchedWords: [] }, + }, + }, + ], + ]); + }); +}); diff --git a/packages/autocomplete-js/src/requesters/createAlgoliaRequester.ts b/packages/autocomplete-js/src/requesters/createAlgoliaRequester.ts new file mode 100644 index 000000000..e9df08668 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/createAlgoliaRequester.ts @@ -0,0 +1,13 @@ +import { + createRequester, + fetchAlgoliaResults, +} from '@algolia/autocomplete-preset-algolia'; + +import { version } from '../version'; + +export const createAlgoliaRequester = createRequester((params) => + fetchAlgoliaResults({ + ...params, + userAgents: [{ segment: 'autocomplete-js', version }], + }) +); diff --git a/packages/autocomplete-js/src/requesters/getAlgoliaFacets.ts b/packages/autocomplete-js/src/requesters/getAlgoliaFacets.ts new file mode 100644 index 000000000..0bc0f5523 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/getAlgoliaFacets.ts @@ -0,0 +1,23 @@ +import { RequestParams } from '@algolia/autocomplete-preset-algolia'; +import { MultipleQueriesQuery } from '@algolia/client-search'; + +import { createAlgoliaRequester } from './createAlgoliaRequester'; + +/** + * Retrieves Algolia facet hits from multiple indices. + */ +export function getAlgoliaFacets(requestParams: RequestParams) { + const requester = createAlgoliaRequester({ + transformResponse: (response) => response.facetHits, + }); + + const queries = requestParams.queries.map((query) => ({ + ...query, + type: 'facet', + })) as MultipleQueriesQuery[]; + + return requester({ + ...requestParams, + queries, + }); +} diff --git a/packages/autocomplete-js/src/requesters/getAlgoliaResults.ts b/packages/autocomplete-js/src/requesters/getAlgoliaResults.ts new file mode 100644 index 000000000..7bda56e50 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/getAlgoliaResults.ts @@ -0,0 +1,8 @@ +import { createAlgoliaRequester } from './createAlgoliaRequester'; + +/** + * Retrieves Algolia results from multiple indices. + */ +export const getAlgoliaResults = createAlgoliaRequester({ + transformResponse: (response) => response.hits, +}); diff --git a/packages/autocomplete-js/src/requesters/index.ts b/packages/autocomplete-js/src/requesters/index.ts new file mode 100644 index 000000000..347d31288 --- /dev/null +++ b/packages/autocomplete-js/src/requesters/index.ts @@ -0,0 +1,2 @@ +export * from './getAlgoliaFacets'; +export * from './getAlgoliaResults'; diff --git a/packages/autocomplete-plugin-query-suggestions/package.json b/packages/autocomplete-plugin-query-suggestions/package.json index 9a59c1720..efc27ef6c 100644 --- a/packages/autocomplete-plugin-query-suggestions/package.json +++ b/packages/autocomplete-plugin-query-suggestions/package.json @@ -37,11 +37,11 @@ "@algolia/autocomplete-shared": "1.0.0-alpha.45" }, "devDependencies": { - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.3" + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0" }, "peerDependencies": { - "@algolia/client-search": "^4.5.1", - "algoliasearch": "^4.5.1" + "@algolia/client-search": "^4.9.0", + "algoliasearch": "^4.9.0" } } diff --git a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts index 2b02b8dac..e4ed91fed 100644 --- a/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts +++ b/packages/autocomplete-plugin-query-suggestions/src/createQuerySuggestionsPlugin.ts @@ -2,7 +2,7 @@ import { AutocompleteState, AutocompleteSource, AutocompletePlugin, - getAlgoliaHits, + getAlgoliaResults, } from '@algolia/autocomplete-js'; import { getAttributeValueByPath } from '@algolia/autocomplete-shared'; import { SearchOptions } from '@algolia/client-search'; @@ -95,7 +95,7 @@ export function createQuerySuggestionsPlugin< return item.query; }, getItems() { - return getAlgoliaHits>({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -106,40 +106,45 @@ export function createQuerySuggestionsPlugin< }), }, ], - }).then(([hits]) => { - if (!query || !categoryAttribute) { - return hits as any; - } - - return hits.reduce< - Array> - >((acc, current, i) => { - const items: Array< + transformResponse({ hits }) { + const querySuggestionsHits: Array< AutocompleteQuerySuggestionsHit - > = [current]; + > = hits[0]; + + if (!query || !categoryAttribute) { + return querySuggestionsHits as any; + } - if (i <= itemsWithCategories - 1) { - const categories = getAttributeValueByPath( - current, - Array.isArray(categoryAttribute) - ? categoryAttribute - : [categoryAttribute] - ) - .map((x) => x.value) - .slice(0, categoriesPerItem); + return querySuggestionsHits.reduce< + Array> + >((acc, current, i) => { + const items: Array< + AutocompleteQuerySuggestionsHit + > = [current]; - for (const category of categories) { - items.push({ - __autocomplete_qsCategory: category, - ...current, - } as any); + if (i <= itemsWithCategories - 1) { + const categories = getAttributeValueByPath( + current, + Array.isArray(categoryAttribute) + ? categoryAttribute + : [categoryAttribute] + ) + .map((x) => x.value) + .slice(0, categoriesPerItem); + + for (const category of categories) { + items.push({ + __autocomplete_qsCategory: category, + ...current, + } as any); + } } - } - acc.push(...items); + acc.push(...items); - return acc; - }, []); + return acc; + }, []); + }, }); }, templates: getTemplates({ onTapAhead }), diff --git a/packages/autocomplete-plugin-recent-searches/package.json b/packages/autocomplete-plugin-recent-searches/package.json index cc1e13437..28123b901 100644 --- a/packages/autocomplete-plugin-recent-searches/package.json +++ b/packages/autocomplete-plugin-recent-searches/package.json @@ -37,7 +37,7 @@ "@algolia/autocomplete-shared": "1.0.0-alpha.45" }, "devDependencies": { - "@algolia/client-search": "4.8.3" + "@algolia/client-search": "4.9.0" }, "peerDependencies": { "@algolia/client-search": "^4.5.1" diff --git a/packages/autocomplete-preset-algolia/package.json b/packages/autocomplete-preset-algolia/package.json index 93ea49433..c6f44be78 100644 --- a/packages/autocomplete-preset-algolia/package.json +++ b/packages/autocomplete-preset-algolia/package.json @@ -34,11 +34,11 @@ "@algolia/autocomplete-shared": "1.0.0-alpha.45" }, "devDependencies": { - "@algolia/client-search": "4.8.6", - "algoliasearch": "4.8.3" + "@algolia/client-search": "4.9.0", + "algoliasearch": "4.9.0" }, "peerDependencies": { - "@algolia/client-search": "^4.5.1", - "algoliasearch": "^4.5.1" + "@algolia/client-search": "^4.9.0", + "algoliasearch": "^4.9.0" } } diff --git a/packages/autocomplete-preset-algolia/src/highlight/index.ts b/packages/autocomplete-preset-algolia/src/highlight/index.ts index e69de29bb..fdc8b211a 100644 --- a/packages/autocomplete-preset-algolia/src/highlight/index.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/index.ts @@ -0,0 +1,6 @@ +export * from './HighlightedHit'; +export * from './parseAlgoliaHitHighlight'; +export * from './parseAlgoliaHitReverseHighlight'; +export * from './parseAlgoliaHitReverseSnippet'; +export * from './parseAlgoliaHitSnippet'; +export * from './SnippetedHit'; diff --git a/packages/autocomplete-preset-algolia/src/index.ts b/packages/autocomplete-preset-algolia/src/index.ts index 2749a2af4..bac6a84da 100644 --- a/packages/autocomplete-preset-algolia/src/index.ts +++ b/packages/autocomplete-preset-algolia/src/index.ts @@ -1,11 +1,3 @@ -export * from './highlight/HighlightedHit'; -export * from './highlight/parseAlgoliaHitHighlight'; -export * from './highlight/parseAlgoliaHitReverseHighlight'; -export * from './highlight/parseAlgoliaHitReverseSnippet'; -export * from './highlight/parseAlgoliaHitSnippet'; -export * from './highlight/SnippetedHit'; -export * from './search/getAlgoliaFacetHits'; -export * from './search/getAlgoliaHits'; -export * from './search/getAlgoliaResults'; -export type { SearchForFacetValuesParams } from './search/searchForFacetValues'; -export type { SearchParams } from './search/search'; +export * from './highlight'; +export * from './requester'; +export * from './search'; diff --git a/packages/autocomplete-preset-algolia/src/requester/__tests__/createRequester.test.ts b/packages/autocomplete-preset-algolia/src/requester/__tests__/createRequester.test.ts new file mode 100644 index 000000000..b8265f37e --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/__tests__/createRequester.test.ts @@ -0,0 +1,57 @@ +import { fetchAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; + +import { + createMultiSearchResponse, + createSearchClient, +} from '../../../../../test/utils'; +import { createRequester } from '../createRequester'; + +describe('createRequester', () => { + test('returns a description', async () => { + const searchClient = createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { hits: [{ objectID: '1', label: 'Hit 1' }] }, + { hits: [{ objectID: '2', label: 'Hit 2' }] } + ) + ) + ), + }); + + const transformResponse = ({ hits }) => hits; + + const createAlgoliaRequester = createRequester(fetchAlgoliaResults); + const getAlgoliaResults = createAlgoliaRequester({ transformResponse }); + + const description = await getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + + expect(description).toEqual({ + execute: expect.any(Function), + transformResponse, + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaFacets.test.ts b/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaFacets.test.ts new file mode 100644 index 000000000..ed32e6d56 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaFacets.test.ts @@ -0,0 +1,91 @@ +import { createSearchClient } from '../../../../../test/utils'; +import { getAlgoliaFacets } from '../getAlgoliaFacets'; + +describe('getAlgoliaFacets', () => { + test('returns the description', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + + expect(description).toEqual({ + execute: expect.any(Function), + transformResponse: expect.any(Function), + searchClient, + queries: [ + { + indexName: 'indexName', + type: 'facet', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + }); + + test('defaults transformItems to retrieve facetHits', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'indexName', + facet: 'categories', + params: { + facetQuery: 'query', + maxFacetHits: 10, + }, + }, + ], + }); + + const transformedResponse = description.transformResponse({ + results: [], + hits: [], + facetHits: [ + [ + { + count: 1, + label: 'Label', + _highlightResult: { + label: { + value: 'Label', + }, + }, + }, + ], + ], + }); + + expect(transformedResponse).toEqual([ + [ + { + count: 1, + label: 'Label', + _highlightResult: { + label: { + value: 'Label', + }, + }, + }, + ], + ]); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaResults.test.ts b/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaResults.test.ts new file mode 100644 index 000000000..2d17b4588 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/__tests__/getAlgoliaResults.test.ts @@ -0,0 +1,82 @@ +import { createSearchClient } from '../../../../../test/utils'; +import { getAlgoliaResults } from '../getAlgoliaResults'; + +describe('getAlgoliaResults', () => { + test('returns the description', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + + expect(description).toEqual({ + execute: expect.any(Function), + transformResponse: expect.any(Function), + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + { + indexName: 'indexName2', + query: 'query', + }, + ], + }); + }); + + test('defaults transformItems to retrieve hits', () => { + const searchClient = createSearchClient({ + search: jest.fn(), + }); + const description = getAlgoliaResults<{ label: string }>({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + ], + }); + + const transformedResponse = description.transformResponse({ + results: [], + hits: [ + [ + { + objectID: '1', + label: 'Label', + _highlightResult: { + label: { value: 'Label', matchLevel: 'none', matchedWords: [] }, + }, + }, + ], + ], + facetHits: [], + }); + + expect(transformedResponse).toEqual([ + [ + { + objectID: '1', + label: 'Label', + _highlightResult: { + label: { value: 'Label', matchLevel: 'none', matchedWords: [] }, + }, + }, + ], + ]); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/requester/createAlgoliaRequester.ts b/packages/autocomplete-preset-algolia/src/requester/createAlgoliaRequester.ts new file mode 100644 index 000000000..b354ccc11 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/createAlgoliaRequester.ts @@ -0,0 +1,5 @@ +import { fetchAlgoliaResults } from '../search'; + +import { createRequester } from './createRequester'; + +export const createAlgoliaRequester = createRequester(fetchAlgoliaResults); diff --git a/packages/autocomplete-preset-algolia/src/requester/createRequester.ts b/packages/autocomplete-preset-algolia/src/requester/createRequester.ts new file mode 100644 index 000000000..6e07dfbcf --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/createRequester.ts @@ -0,0 +1,117 @@ +import { + MultipleQueriesQuery, + SearchForFacetValuesResponse, + SearchResponse, +} from '@algolia/client-search'; +import { SearchClient } from 'algoliasearch/lite'; + +import { fetchAlgoliaResults } from '../search'; + +type Fetcher = typeof fetchAlgoliaResults; + +type FacetHit = { + label: string; + count: number; + _highlightResult: { + label: { + value: string; + }; + }; +}; + +export type FetcherParams = Pick< + Parameters[0], + 'searchClient' | 'queries' +>; + +export type RequesterParams = { + transformResponse( + response: TransformResponseParams + ): TransformedRequesterResponse; +}; + +type TransformResponseParams = { + results: Array | SearchForFacetValuesResponse>; + hits: Array['hits']>; + facetHits: FacetHit[][]; +}; + +export type TransformedRequesterResponse = + | Array['hits']> + | SearchResponse['hits'] + | FacetHit[][] + | FacetHit[]; + +export type TransformResponse = ( + response: TransformResponseParams +) => TransformedRequesterResponse; + +type FetcherParamsQuery = { + query: MultipleQueriesQuery; + sourceId: string; + transformResponse: TransformResponse; +}; + +type ExecuteParams = { + searchClient: SearchClient; + requests: Array>; +}; + +export type Execute = ( + params: ExecuteParams +) => Promise>; + +export type ExecuteResponse = Array<{ + items: SearchResponse | SearchForFacetValuesResponse; + sourceId: string; + transformResponse: TransformResponse; +}>; + +export type RequestParams = FetcherParams & { + /** + * The function to transform the Algolia response before passing it to the Autocomplete state. You have access to the full Algolia results, as well as the pre-computed hits and facet hits. + * + * This is useful to manipulate the hits, or store data from the results in the [context](https://autocomplete.algolia.com/docs/context). + */ + transformResponse?: TransformResponse; +}; + +export type RequesterDescription = { + searchClient: SearchClient; + queries: MultipleQueriesQuery[]; + transformResponse: TransformResponse; + execute: Execute; +}; + +export function createRequester(fetcher: Fetcher) { + function execute(fetcherParams: ExecuteParams) { + return fetcher({ + searchClient: fetcherParams.searchClient, + queries: fetcherParams.requests.map((x) => x.query), + }).then((responses) => + responses.map((response, index) => { + const { sourceId, transformResponse } = fetcherParams.requests[index]; + + return { + items: response, + sourceId, + transformResponse, + }; + }) + ); + } + + return function createSpecifiedRequester( + requesterParams: RequesterParams + ) { + return function requester( + requestParams: RequestParams + ): RequesterDescription { + return { + execute, + ...requesterParams, + ...requestParams, + }; + }; + }; +} diff --git a/packages/autocomplete-preset-algolia/src/requester/getAlgoliaFacets.ts b/packages/autocomplete-preset-algolia/src/requester/getAlgoliaFacets.ts new file mode 100644 index 000000000..6ac7b261d --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/getAlgoliaFacets.ts @@ -0,0 +1,23 @@ +import { MultipleQueriesQuery } from '@algolia/client-search'; + +import { createAlgoliaRequester } from './createAlgoliaRequester'; +import { RequestParams } from './createRequester'; + +/** + * Retrieves Algolia facet hits from multiple indices. + */ +export function getAlgoliaFacets(requestParams: RequestParams) { + const requester = createAlgoliaRequester({ + transformResponse: (response) => response.facetHits, + }); + + const queries = requestParams.queries.map((query) => ({ + ...query, + type: 'facet', + })) as MultipleQueriesQuery[]; + + return requester({ + ...requestParams, + queries, + }); +} diff --git a/packages/autocomplete-preset-algolia/src/requester/getAlgoliaResults.ts b/packages/autocomplete-preset-algolia/src/requester/getAlgoliaResults.ts new file mode 100644 index 000000000..7bda56e50 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/getAlgoliaResults.ts @@ -0,0 +1,8 @@ +import { createAlgoliaRequester } from './createAlgoliaRequester'; + +/** + * Retrieves Algolia results from multiple indices. + */ +export const getAlgoliaResults = createAlgoliaRequester({ + transformResponse: (response) => response.hits, +}); diff --git a/packages/autocomplete-preset-algolia/src/requester/index.ts b/packages/autocomplete-preset-algolia/src/requester/index.ts new file mode 100644 index 000000000..b6123a4f7 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/requester/index.ts @@ -0,0 +1,3 @@ +export * from './createRequester'; +export * from './getAlgoliaFacets'; +export * from './getAlgoliaResults'; diff --git a/packages/autocomplete-preset-algolia/src/search/UserAgent.ts b/packages/autocomplete-preset-algolia/src/search/UserAgent.ts deleted file mode 100644 index 56a849c37..000000000 --- a/packages/autocomplete-preset-algolia/src/search/UserAgent.ts +++ /dev/null @@ -1 +0,0 @@ -export type UserAgent = { segment: string; version?: string }; diff --git a/packages/autocomplete-preset-algolia/src/search/__tests__/fetchAlgoliaResults.test.ts b/packages/autocomplete-preset-algolia/src/search/__tests__/fetchAlgoliaResults.test.ts new file mode 100644 index 000000000..b26c8b558 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/__tests__/fetchAlgoliaResults.test.ts @@ -0,0 +1,157 @@ +import { + createSFFVResponse, + createMultiSearchResponse, + createSearchClient, +} from '../../../../../test/utils'; +import { version } from '../../version'; +import { fetchAlgoliaResults } from '../fetchAlgoliaResults'; + +function createTestSearchClient() { + return createSearchClient({ + search: jest.fn(() => + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { hits: [{ objectID: '1', label: 'Hit 1' }] }, + { hits: [{ objectID: '2', label: 'Hit 2' }] } + ) + ) + ), + searchForFacetValues: jest.fn(() => + Promise.resolve([ + createSFFVResponse({ + facetHits: [ + { + count: 507, + value: 'Mobile phones', + highlighted: 'Mobile phones', + }, + { + count: 63, + value: 'Phone cases', + highlighted: 'Phone cases', + }, + ], + }), + ]) + ), + }); +} + +describe('fetchAlgoliaResults', () => { + test('with default options', async () => { + const searchClient = createTestSearchClient(); + + const results = await fetchAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + }, + ], + }); + + expect(searchClient.search).toHaveBeenCalledTimes(1); + expect(searchClient.search).toHaveBeenCalledWith([ + { + indexName: 'indexName', + query: 'query', + params: { + hitsPerPage: 5, + highlightPreTag: '__aa-highlight__', + highlightPostTag: '__/aa-highlight__', + }, + }, + ]); + expect(results).toEqual([ + expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), + expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), + ]); + }); + + test('with custom search parameters', async () => { + const searchClient = createTestSearchClient(); + + const results = await fetchAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + params: { + hitsPerPage: 10, + highlightPreTag: '', + highlightPostTag: '', + page: 2, + }, + }, + ], + }); + + expect(searchClient.search).toHaveBeenCalledTimes(1); + expect(searchClient.search).toHaveBeenCalledWith([ + { + indexName: 'indexName', + query: 'query', + params: { + hitsPerPage: 10, + highlightPreTag: '', + highlightPostTag: '', + page: 2, + }, + }, + ]); + expect(results).toEqual([ + expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), + expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), + ]); + }); + + test('attaches default Algolia agent', async () => { + const searchClient = createTestSearchClient(); + + await fetchAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'indexName', + query: 'query', + params: { + hitsPerPage: 10, + highlightPreTag: '', + highlightPostTag: '', + page: 2, + }, + }, + ], + }); + + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( + 'autocomplete-core', + version + ); + }); + + test('allows custom user agents', async () => { + const searchClient = createTestSearchClient(); + + await fetchAlgoliaResults({ + searchClient, + queries: [], + userAgents: [{ segment: 'custom-ua', version: '1.0.0' }], + }); + + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(2); + expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( + 1, + 'autocomplete-core', + version + ); + expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( + 2, + 'custom-ua', + '1.0.0' + ); + }); +}); diff --git a/packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts b/packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts deleted file mode 100644 index e794c20a6..000000000 --- a/packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { - createSFFVResponse, - createMultiSearchResponse, - createSearchClient, -} from '../../../../../test/utils'; -import { version } from '../../version'; -import { getAlgoliaFacetHits } from '../getAlgoliaFacetHits'; -import { getAlgoliaHits } from '../getAlgoliaHits'; -import { getAlgoliaResults } from '../getAlgoliaResults'; - -function createTestSearchClient() { - return createSearchClient({ - search: jest.fn(() => - Promise.resolve( - createMultiSearchResponse<{ label: string }>( - { hits: [{ objectID: '1', label: 'Hit 1' }] }, - { hits: [{ objectID: '2', label: 'Hit 2' }] } - ) - ) - ), - searchForFacetValues: jest.fn(() => - Promise.resolve([ - createSFFVResponse({ - facetHits: [ - { - count: 507, - value: 'Mobile phones', - highlighted: 'Mobile phones', - }, - { - count: 63, - value: 'Phone cases', - highlighted: 'Phone cases', - }, - ], - }), - ]) - ), - }); -} - -describe('getAlgoliaResults', () => { - test('with default options', async () => { - const searchClient = createTestSearchClient(); - - const results = await getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - }, - ], - }); - - expect(searchClient.search).toHaveBeenCalledTimes(1); - expect(searchClient.search).toHaveBeenCalledWith([ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 5, - highlightPreTag: '__aa-highlight__', - highlightPostTag: '__/aa-highlight__', - }, - }, - ]); - expect(results).toEqual([ - expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), - expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), - ]); - }); - - test('with custom search parameters', async () => { - const searchClient = createTestSearchClient(); - - const results = await getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ], - }); - - expect(searchClient.search).toHaveBeenCalledTimes(1); - expect(searchClient.search).toHaveBeenCalledWith([ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ]); - expect(results).toEqual([ - expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), - expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), - ]); - }); - - test('attaches default Algolia agent', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( - 'autocomplete-core', - version - ); - }); - - test('allows custom user agents', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaResults({ - searchClient, - queries: [], - userAgents: [{ segment: 'custom-ua', version: '1.0.0' }], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(2); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 1, - 'autocomplete-core', - version - ); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 2, - 'custom-ua', - '1.0.0' - ); - }); -}); - -describe('getAlgoliaHits', () => { - test('with default options', async () => { - const searchClient = createTestSearchClient(); - - const hits = await getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - }, - ], - }); - - expect(searchClient.search).toHaveBeenCalledTimes(1); - expect(searchClient.search).toHaveBeenCalledWith([ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 5, - highlightPreTag: '__aa-highlight__', - highlightPostTag: '__/aa-highlight__', - }, - }, - ]); - expect(hits).toEqual([ - [{ objectID: '1', label: 'Hit 1' }], - [{ objectID: '2', label: 'Hit 2' }], - ]); - }); - - test('with custom search parameters', async () => { - const searchClient = createTestSearchClient(); - - const hits = await getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ], - }); - - expect(searchClient.search).toHaveBeenCalledTimes(1); - expect(searchClient.search).toHaveBeenCalledWith([ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ]); - expect(hits).toEqual([ - [{ objectID: '1', label: 'Hit 1' }], - [{ objectID: '2', label: 'Hit 2' }], - ]); - }); - - test('attaches Algolia agent', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - query: 'query', - params: { - hitsPerPage: 10, - highlightPreTag: '', - highlightPostTag: '', - page: 2, - }, - }, - ], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( - 'autocomplete-core', - version - ); - }); - - test('allows custom user agents', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaHits({ - searchClient, - queries: [], - userAgents: [{ segment: 'custom-ua', version: '1.0.0' }], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(2); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 1, - 'autocomplete-core', - version - ); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 2, - 'custom-ua', - '1.0.0' - ); - }); -}); - -describe('getAlgoliaFacetHits', () => { - test('with default options', async () => { - const searchClient = createTestSearchClient(); - - const facetHits = await getAlgoliaFacetHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - params: { - facetName: 'facetName', - facetQuery: 'facetQuery', - }, - }, - ], - }); - - expect(searchClient.searchForFacetValues).toHaveBeenCalledTimes(1); - expect(searchClient.searchForFacetValues).toHaveBeenCalledWith([ - { - indexName: 'indexName', - params: { - facetName: 'facetName', - facetQuery: 'facetQuery', - highlightPreTag: '__aa-highlight__', - highlightPostTag: '__/aa-highlight__', - }, - }, - ]); - expect(facetHits).toEqual([ - [ - { - count: 507, - label: 'Mobile phones', - _highlightResult: { - label: { - value: 'Mobile phones', - }, - }, - }, - { - count: 63, - label: 'Phone cases', - _highlightResult: { - label: { - value: 'Phone cases', - }, - }, - }, - ], - ]); - }); - - test('with custom search parameters', async () => { - const searchClient = createTestSearchClient(); - - const facetHits = await getAlgoliaFacetHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - params: { - facetName: 'facetName', - facetQuery: 'facetQuery', - highlightPreTag: '', - highlightPostTag: '', - maxFacetHits: 10, - }, - }, - ], - }); - - expect(searchClient.searchForFacetValues).toHaveBeenCalledTimes(1); - expect(searchClient.searchForFacetValues).toHaveBeenCalledWith([ - { - indexName: 'indexName', - params: { - facetName: 'facetName', - facetQuery: 'facetQuery', - highlightPreTag: '', - highlightPostTag: '', - maxFacetHits: 10, - }, - }, - ]); - expect(facetHits).toEqual([ - [ - { - count: 507, - label: 'Mobile phones', - _highlightResult: { - label: { - value: 'Mobile phones', - }, - }, - }, - { - count: 63, - label: 'Phone cases', - _highlightResult: { - label: { - value: 'Phone cases', - }, - }, - }, - ], - ]); - }); - - test('attaches Algolia agent', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaFacetHits({ - searchClient, - queries: [ - { - indexName: 'indexName', - params: { - facetName: 'facetName', - facetQuery: 'facetQuery', - }, - }, - ], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( - 'autocomplete-core', - version - ); - }); - - test('allows custom user agents', async () => { - const searchClient = createTestSearchClient(); - - await getAlgoliaFacetHits({ - searchClient, - queries: [], - userAgents: [{ segment: 'custom-ua', version: '1.0.0' }], - }); - - expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(2); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 1, - 'autocomplete-core', - version - ); - expect(searchClient.addAlgoliaAgent).toHaveBeenNthCalledWith( - 2, - 'custom-ua', - '1.0.0' - ); - }); -}); diff --git a/packages/autocomplete-preset-algolia/src/search/fetchAlgoliaResults.ts b/packages/autocomplete-preset-algolia/src/search/fetchAlgoliaResults.ts new file mode 100644 index 000000000..4f07595fb --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/fetchAlgoliaResults.ts @@ -0,0 +1,67 @@ +import { + MultipleQueriesQuery, + SearchForFacetValuesResponse, + SearchResponse, +} from '@algolia/client-search'; +import type { SearchClient } from 'algoliasearch/lite'; + +import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; +import { version } from '../version'; + +type UserAgent = { segment: string; version?: string }; + +export interface SearchParams { + /** + * The initialized Algolia search client. + */ + searchClient: SearchClient; + /** + * A list of queries to execute. + */ + queries: MultipleQueriesQuery[]; + /** + * A list of user agents to add to the search client. + * + * This is useful to track usage of an integration. + */ + userAgents?: UserAgent[]; +} + +export function fetchAlgoliaResults({ + searchClient, + queries, + userAgents = [], +}: SearchParams): Promise< + Array | SearchForFacetValuesResponse> +> { + if (typeof searchClient.addAlgoliaAgent === 'function') { + const algoliaAgents: UserAgent[] = [ + { segment: 'autocomplete-core', version }, + ...userAgents, + ]; + + algoliaAgents.forEach(({ segment, version }) => { + searchClient.addAlgoliaAgent(segment, version); + }); + } + + return searchClient + .search( + queries.map((searchParameters) => { + const { params, ...headers } = searchParameters; + + return { + ...headers, + params: { + hitsPerPage: 5, + highlightPreTag: HIGHLIGHT_PRE_TAG, + highlightPostTag: HIGHLIGHT_POST_TAG, + ...params, + }, + }; + }) + ) + .then((response) => { + return response.results; + }); +} diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaFacetHits.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaFacetHits.ts deleted file mode 100644 index f5d53c0e1..000000000 --- a/packages/autocomplete-preset-algolia/src/search/getAlgoliaFacetHits.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - searchForFacetValues, - SearchForFacetValuesParams, -} from './searchForFacetValues'; - -type FacetHit = { - label: string; - count: number; - _highlightResult: { - label: { - value: string; - }; - }; -}; - -export function getAlgoliaFacetHits({ - searchClient, - queries, - userAgents, -}: SearchForFacetValuesParams): Promise { - return searchForFacetValues({ searchClient, queries, userAgents }).then( - (response) => { - return response.map((result) => - result.facetHits.map((facetHit) => { - return { - label: facetHit.value, - count: facetHit.count, - _highlightResult: { - label: { - value: facetHit.highlighted, - }, - }, - }; - }) - ); - } - ); -} diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts deleted file mode 100644 index d845895b5..000000000 --- a/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Hit } from '@algolia/client-search'; - -import { search, SearchParams } from './search'; - -export function getAlgoliaHits({ - searchClient, - queries, - userAgents, -}: SearchParams): Promise>>> { - return search({ searchClient, queries, userAgents }).then( - (response) => { - const results = response.results; - - return results.map((result) => - result.hits.map((hit) => { - return { - ...hit, - __autocomplete_indexName: result.index, - __autocomplete_queryID: result.queryID, - }; - }) - ); - } - ); -} diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts deleted file mode 100644 index cc4f1e2e7..000000000 --- a/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SearchResponse } from '@algolia/client-search'; - -import { search, SearchParams } from './search'; - -export function getAlgoliaResults({ - searchClient, - queries, - userAgents, -}: SearchParams): Promise>> { - return search({ searchClient, queries, userAgents }).then( - (response) => { - return response.results; - } - ); -} diff --git a/packages/autocomplete-preset-algolia/src/search/index.ts b/packages/autocomplete-preset-algolia/src/search/index.ts new file mode 100644 index 000000000..f8277f2bd --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/index.ts @@ -0,0 +1 @@ +export * from './fetchAlgoliaResults'; diff --git a/packages/autocomplete-preset-algolia/src/search/search.ts b/packages/autocomplete-preset-algolia/src/search/search.ts deleted file mode 100644 index 0d54cdff3..000000000 --- a/packages/autocomplete-preset-algolia/src/search/search.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { MultipleQueriesQuery } from '@algolia/client-search'; -import { SearchClient } from 'algoliasearch/lite'; - -import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; -import { version } from '../version'; - -import { UserAgent } from './UserAgent'; - -export interface SearchParams { - searchClient: SearchClient; - queries: MultipleQueriesQuery[]; - userAgents?: UserAgent[]; -} - -export function search({ - searchClient, - queries, - userAgents = [], -}: SearchParams) { - if (typeof searchClient.addAlgoliaAgent === 'function') { - const algoliaAgents: UserAgent[] = [ - { segment: 'autocomplete-core', version }, - ...userAgents, - ]; - - algoliaAgents.forEach(({ segment, version }) => { - searchClient.addAlgoliaAgent(segment, version); - }); - } - - return searchClient.search( - queries.map((searchParameters) => { - const { indexName, query, params } = searchParameters; - - return { - indexName, - query, - params: { - hitsPerPage: 5, - highlightPreTag: HIGHLIGHT_PRE_TAG, - highlightPostTag: HIGHLIGHT_POST_TAG, - ...params, - }, - }; - }) - ); -} diff --git a/packages/autocomplete-preset-algolia/src/search/searchForFacetValues.ts b/packages/autocomplete-preset-algolia/src/search/searchForFacetValues.ts deleted file mode 100644 index 2bb0d506a..000000000 --- a/packages/autocomplete-preset-algolia/src/search/searchForFacetValues.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - SearchForFacetValuesQueryParams, - SearchOptions, -} from '@algolia/client-search'; -import { SearchClient } from 'algoliasearch/lite'; - -import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; -import { version } from '../version'; - -import { UserAgent } from './UserAgent'; - -type FacetQuery = { - indexName: string; - params: SearchForFacetValuesQueryParams & SearchOptions; -}; -export interface SearchForFacetValuesParams { - searchClient: SearchClient; - queries: FacetQuery[]; - userAgents?: UserAgent[]; -} - -export function searchForFacetValues({ - searchClient, - queries, - userAgents = [], -}: SearchForFacetValuesParams) { - if (typeof searchClient.addAlgoliaAgent === 'function') { - const algoliaAgents: UserAgent[] = [ - { segment: 'autocomplete-core', version }, - ...userAgents, - ]; - - algoliaAgents.forEach(({ segment, version }) => { - searchClient.addAlgoliaAgent(segment, version); - }); - } - - return searchClient.searchForFacetValues( - queries.map((searchParameters) => { - const { indexName, params } = searchParameters; - - return { - indexName, - params: { - highlightPreTag: HIGHLIGHT_PRE_TAG, - highlightPostTag: HIGHLIGHT_POST_TAG, - ...params, - }, - }; - }) - ); -} diff --git a/packages/website/docs/api.md b/packages/website/docs/api.md index f970fa176..fa4c00b0f 100644 --- a/packages/website/docs/api.md +++ b/packages/website/docs/api.md @@ -39,7 +39,7 @@ Presets provide utilities to use in Autocomplete experiences. They facilitate in We currently provide a single preset: -- [`autocomplete-preset-algolia`](getAlgoliaHits) provides fetching and highlighting utilities for usage with Algolia. +- [`autocomplete-preset-algolia`](getAlgoliaResults) provides fetching and highlighting utilities for usage with Algolia. ## Themes diff --git a/packages/website/docs/autocomplete-js.md b/packages/website/docs/autocomplete-js.md index 501d51772..f79165f4a 100644 --- a/packages/website/docs/autocomplete-js.md +++ b/packages/website/docs/autocomplete-js.md @@ -44,7 +44,7 @@ This example uses Autocomplete with an Algolia index, along with the [`algoliase ```jsx title="JavaScript" import algoliasearch from 'algoliasearch/lite'; -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; const searchClient = algoliasearch( 'latency', @@ -59,7 +59,7 @@ const autocompleteSearch = autocomplete({ sourceId: 'querySuggestions', getItemInputValue: ({ item }) => item.query, getItems({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/packages/website/docs/changing-behavior-based-on-query.mdx b/packages/website/docs/changing-behavior-based-on-query.mdx index 70909ed70..67d85a72c 100644 --- a/packages/website/docs/changing-behavior-based-on-query.mdx +++ b/packages/website/docs/changing-behavior-based-on-query.mdx @@ -46,7 +46,7 @@ This boilerplate assumes you want to insert the autocomplete into a DOM element ```js title="index.js" import algoliasearch from 'algoliasearch/lite'; -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; const searchClient = algoliasearch( 'latency', @@ -83,7 +83,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/packages/website/docs/context.md b/packages/website/docs/context.md index 0305d479b..ddcdda661 100644 --- a/packages/website/docs/context.md +++ b/packages/website/docs/context.md @@ -27,17 +27,16 @@ autocomplete({ query, }, ], - }).then(([products]) => { - setContext({ - nbProducts: products.nbHits, - }); + transformResponse({ results, hits }) { + setContext({ + nbProducts: results[0].nbHits, + }); - // You can now use `state.context.nbProducts` - // anywhere where you have access to `state`. + // You can now use `state.context.nbProducts` + // anywhere where you have access to `state`. - return [ - // ... - ]; + return hits; + }, }); }, }); @@ -65,6 +64,7 @@ function createAutocompletePlugin() { The `setContext` function is accessible on your `autocomplete` instance. It's also provided in: + - [`getSources`](createAutocomplete#getsources) - [`onInput`](createAutocomplete#oninput) - [`onSubmit`](createAutocomplete#onsubmit) diff --git a/packages/website/docs/createAutocomplete.md b/packages/website/docs/createAutocomplete.md index 9200e34be..596945850 100644 --- a/packages/website/docs/createAutocomplete.md +++ b/packages/website/docs/createAutocomplete.md @@ -39,12 +39,12 @@ If you don't use a package manager, you can use the HTML `script` element: ## Example -This example uses the package along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client and [`getAlgoliaHits`](getAlgoliaHits) function from the Autocomplete Algolia preset. It returns [a set of functions](#returns) to build an autocomplete experience. +This example uses the package along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client and [`getAlgoliaResults`](getAlgoliaResults) function from the Autocomplete Algolia preset. It returns [a set of functions](#returns) to build an autocomplete experience. ```js import algoliasearch from 'algoliasearch/lite'; import { createAutocomplete } from '@algolia/autocomplete-core'; -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( 'latency', @@ -58,7 +58,7 @@ const autocomplete = createAutocomplete({ sourceId: 'querySuggestions', getItemInputValue: ({ item }) => item.query, getItems({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/packages/website/docs/creating-a-renderer.md b/packages/website/docs/creating-a-renderer.md index deb242807..5017ce445 100644 --- a/packages/website/docs/creating-a-renderer.md +++ b/packages/website/docs/creating-a-renderer.md @@ -17,12 +17,12 @@ Building a custom renderer is an advanced pattern that leverages the [`autocompl ## Importing the package -Begin by importing [`createAutocomplete`](createAutocomplete) from the [core package](createAutocomplete) and [`getAlgoliaHits`](getAlgoliaHits) from the [Algolia preset](getAlgoliaHits). The preset—[`autocomplete-preset-algolia`](autocomplete-js)—is a utility function to retrieve items from an Algolia index. +Begin by importing [`createAutocomplete`](createAutocomplete) from the [core package](createAutocomplete) and [`getAlgoliaResults`](getAlgoliaResults) from the [Algolia preset](getAlgoliaResults). The preset—[`autocomplete-preset-algolia`](autocomplete-js)—is a utility function to retrieve items from an Algolia index. ```js title="Autocomplete.jsx" import algoliasearch from 'algoliasearch/lite'; import { createAutocomplete } from '@algolia/autocomplete-core'; -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; // ... ``` @@ -56,7 +56,7 @@ function Autocomplete() { return item.query; }, getItems({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/packages/website/docs/getAlgoliaFacetHits-js.mdx b/packages/website/docs/getAlgoliaFacetHits-js.mdx deleted file mode 100644 index a7b3341b2..000000000 --- a/packages/website/docs/getAlgoliaFacetHits-js.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: getAlgoliaFacetHits-js -title: getAlgoliaFacetHits ---- - -import GetAlgoliaFacetHitsIntro from './partials/preset-algolia/getAlgoliaFacetHits/intro.md'; - - - -## Example - -```js -import { getAlgoliaFacetHits } from '@algolia/autocomplete-js'; -import algoliasearch from 'algoliasearch/lite'; - -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); - -getAlgoliaFacetHits({ - searchClient, - queries: [ - { - indexName: 'instant_search', - params: { - facetName: 'categories', - facetQuery: query, - maxFacetHits: 10, - }, - }, - ], -}).then((facetHits) => { - console.log(facetHits); -}); -``` - -## Parameters - -See [`autocomplete-preset-algolia#getAlgoliaFacetHits`](getAlgoliaFacetHits#parameters). diff --git a/packages/website/docs/getAlgoliaFacetHits.mdx b/packages/website/docs/getAlgoliaFacetHits.mdx deleted file mode 100644 index ed5b45149..000000000 --- a/packages/website/docs/getAlgoliaFacetHits.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -id: getAlgoliaFacetHits ---- - -import GetAlgoliaFacetHitsIntro from './partials/preset-algolia/getAlgoliaFacetHits/intro.md'; -import PresetAlgoliaNote from './partials/preset-algolia/note.md'; - - - - - -## Installation - -First, you need to install the preset. - -```bash -yarn add @algolia/autocomplete-preset-algolia@alpha -# or -npm install @algolia/autocomplete-preset-algolia@alpha -``` - -Then import it in your project: - -```js -import { getAlgoliaFacetHits } from '@algolia/autocomplete-preset-algolia'; -``` - -If you don't use a package manager, you can use the HTML `script` element: - -```html - - -``` - -## Example - -```js -import { getAlgoliaFacetHits } from '@algolia/autocomplete-preset-algolia'; -import algoliasearch from 'algoliasearch/lite'; - -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); - -getAlgoliaFacetHits({ - searchClient, - queries: [ - { - indexName: 'instant_search', - params: { - facetName: 'categories', - facetQuery: query, - maxFacetHits: 10, - }, - }, - ], -}).then((facetHits) => { - console.log(facetHits); -}); -``` - -## Parameters - -### `searchClient` - -> `SearchClient` | required - -The initialized Algolia search client. - -### `queries` - -> `FacetQuery[]` | required - -The queries to search for, with the following parameters: - -#### `indexName` - -> `string` | required - -The index name. - -#### `params` - -> [`SearchForFacetValuesQueryParams` & `SearchOptions`](https://www.algolia.com/doc/api-reference/api-methods/search-for-facet-values/#parameters) - -Algolia search for facet values parameters. - -These are the default parameters. You can leave them as is and specify other parameters, or override them. - -:::info - -If you override `highlightPreTag` and `highlightPostTag`, you won't be able to use the built-in highlighting components such as `Highlight`. - -::: - -```json -{ - "highlightPreTag": "__aa-highlight__", - "highlightPostTag": "__/aa-highlight__" -} -``` - -## Returns - -The function returns a promise that resolves to a response with the following schema: - -```json -[ - { - "count": 507, - "label": "Mobile phones", - "_highlightResult": { - "label": { - "value": "Mobile phones" - } - } - }, - { - "count": 63, - "label": "Phone cases", - "_highlightResult": { - "label": { - "value": "Phone cases" - } - } - } -] -``` diff --git a/packages/website/docs/getAlgoliaFacets-js.mdx b/packages/website/docs/getAlgoliaFacets-js.mdx new file mode 100644 index 000000000..07b9d8775 --- /dev/null +++ b/packages/website/docs/getAlgoliaFacets-js.mdx @@ -0,0 +1,57 @@ +--- +id: getAlgoliaFacets-js +title: getAlgoliaFacets +--- + +import GetAlgoliaFacetsIntro from './partials/preset-algolia/getAlgoliaFacets/intro.md'; + + + +## Example + +```js +import algoliasearch from 'algoliasearch/lite'; +import { autocomplete, getAlgoliaFacets } from '@algolia/autocomplete-js'; + +const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' +); + +autocomplete({ + // ... + getSources({ query }) { + return [ + { + sourceId: 'products', + getItems({ query }) { + getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'categories', + params: { + facetQuery: query, + maxFacetHits: 10, + }, + }, + ], + }); + }, + // ... + }, + ]; + }, +}); +``` + +:::info + +When using `getAlgoliaFacets` and [`getAlgoliaResults`](getAlgoliaResults-js) with the same search client in different sources or plugins, Autocomplete batches all queries into a single network call to Algolia. If you're using the same search client for different sources or plugins, **make sure to use the same instance to leverage the internal cache and batching mechanism.** + +::: + +## Parameters + +See [`autocomplete-preset-algolia#getAlgoliaFacets`](getAlgoliaFacets#parameters). diff --git a/packages/website/docs/getAlgoliaFacets.mdx b/packages/website/docs/getAlgoliaFacets.mdx new file mode 100644 index 000000000..bd6475e8b --- /dev/null +++ b/packages/website/docs/getAlgoliaFacets.mdx @@ -0,0 +1,160 @@ +--- +id: getAlgoliaFacets +--- + +import GetAlgoliaFacetsIntro from './partials/preset-algolia/getAlgoliaFacets/intro.md'; +import PresetAlgoliaNote from './partials/preset-algolia/note.md'; + + + + + +## Installation + +First, you need to install the preset. + +```bash +yarn add @algolia/autocomplete-preset-algolia@alpha +# or +npm install @algolia/autocomplete-preset-algolia@alpha +``` + +Then import it in your project: + +```js +import { getAlgoliaFacets } from '@algolia/autocomplete-preset-algolia'; +``` + +If you don't use a package manager, you can use the HTML `script` element: + +```html + + +``` + +## Example + +```js +import algoliasearch from 'algoliasearch/lite'; +import { createAutocomplete } from '@algolia/autocomplete-core'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; + +const searchClient = algoliasearch( + 'latency', + '6be0576ff61c053d5f9a3225e2a90f76' +); + +const autocomplete = createAutocomplete({ + // ... + getSources() { + return [ + { + sourceId: 'categories', + getItems({ query }) { + getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'categories', + params: { + facetQuery: query, + maxFacetHits: 10, + }, + }, + ], + }); + }, + // ... + }, + ]; + }, +}); +``` + +:::info + +When using `getAlgoliaFacets` and [`getAlgoliaResults`](getAlgoliaResults) with the same search client in different sources or plugins, Autocomplete batches all queries into a single network call to Algolia. If you're using the same search client for different sources or plugins, **make sure to use the same instance to leverage the internal cache and batching mechanism.** + +::: + +## Parameters + +### `searchClient` + +> `SearchClient` | required + +The initialized Algolia search client. + +### `queries` + +> `MultipleQueriesQuery[]` | required + +The queries to search for, with the following parameters: + +#### `indexName` + +> `string` | required + +The index name. + +#### `facet` + +> `string` | required + +The attribute name to search facet values into. + +Note that for this to work, it must be declared in the [`attributesForFaceting`](https://www.algolia.com/doc/api-reference/api-parameters/attributesForFaceting/) index setting with the `searchable()` modifier. + +#### `params` + +> [`SearchOptions`](https://www.algolia.com/doc/api-reference/search-api-parameters/) + +Algolia search for facet values parameters. + +These are the default parameters. You can leave them as is and specify other parameters, or override them. + +:::info + +If you override `highlightPreTag` and `highlightPostTag`, you won't be able to use the built-in highlighting components such as `Highlight`. + +::: + +```json +{ + "highlightPreTag": "__aa-highlight__", + "highlightPostTag": "__/aa-highlight__" +} +``` + +### `transformResponse` + +> `(response: { results: Array | SearchForFacetValuesResponse>, hits: MaybeArray[]>, facetHits: MaybeArray }) => MaybeArray[] | FacetHit[]>` + +The function to transform the Algolia response before passing it to the Autocomplete state. You have access to the full Algolia results, as well as the pre-computed hits and facet hits. This is useful to manipulate the hits, or store data from the results in the [context](context). + +This is the default implementation: + +```js +getAlgoliaFacets({ + // ... + transformResponse({ facetHits }) { + return facetHits; + }, +}); +``` + +## Returns + +The function returns a description with the following interface: + +```ts +{ + searchClient: SearchClient; + queries: MultipleQueriesQuery[]; + transformResponse: TransformResponse; + execute: Execute; +} +``` diff --git a/packages/website/docs/getAlgoliaHits-js.mdx b/packages/website/docs/getAlgoliaHits-js.mdx deleted file mode 100644 index 25f7d14ef..000000000 --- a/packages/website/docs/getAlgoliaHits-js.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: getAlgoliaHits-js -title: getAlgoliaHits ---- - -import GetAlgoliaHitsIntro from './partials/preset-algolia/getAlgoliaHits/intro.md'; - - - -## Example - -This example uses the function along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client. - -```js -import { getAlgoliaHits } from '@algolia/autocomplete-js'; -import algoliasearch from 'algoliasearch/lite'; - -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); - -getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'instant_search', - query, - params: { - hitsPerPage: 3, - }, - }, - ], -}).then((hits) => { - console.log(hits); -}); -``` - -## Parameters - -See [`autocomplete-preset-algolia#getAlgoliaHits`](getAlgoliaHits#parameters). diff --git a/packages/website/docs/getAlgoliaHits.mdx b/packages/website/docs/getAlgoliaHits.mdx deleted file mode 100644 index 4f86950b6..000000000 --- a/packages/website/docs/getAlgoliaHits.mdx +++ /dev/null @@ -1,130 +0,0 @@ ---- -id: getAlgoliaHits ---- - -import GetAlgoliaHitsIntro from './partials/preset-algolia/getAlgoliaHits/intro.md'; -import PresetAlgoliaNote from './partials/preset-algolia/note.md'; - - - - - -## Installation - -First, you need to install the preset. - -```bash -yarn add @algolia/autocomplete-preset-algolia@alpha -# or -npm install @algolia/autocomplete-preset-algolia@alpha -``` - -Then import it in your project: - -```js -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; -``` - -If you don't use a package manager, you can use the HTML `script` element: - -```html - - -``` - -## Example - -This example uses the function along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client. - -```js -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; -import algoliasearch from 'algoliasearch/lite'; - -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); - -getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'instant_search', - query, - params: { - hitsPerPage: 3, - }, - }, - ], -}).then((hits) => { - console.log(hits); -}); -``` - -## Parameters - -### `searchClient` - -> `SearchClient` | required - -The initialized Algolia search client. - -### `queries` - -> `MultipleQueriesQuery[]` | required - -The queries to perform, with the following parameters: - -#### `indexName` - -> `string` | required - -The index name to search into. - -#### `query` - -> `string` - -The query to search for. - -#### `params` - -> [`SearchParameters`](https://www.algolia.com/doc/api-reference/search-api-parameters/) - -Algolia search parameters. - -These are the default search parameters. You can leave them as is and specify other parameters, or override them. - -```json -{ - "hitsPerPage": 5, - "highlightPreTag": "__aa-highlight__", - "highlightPostTag": "__/aa-highlight__" -} -``` - -## Returns - -The function returns a promise that resolves to a response with the following schema: - -```json -[ - { - "objectID": "433", - "firstname": "Jimmie", - "lastname": "Barninger", - "_highlightResult": { - "firstname": { - "value": "<em>Jimmie</em>", - "matchLevel": "partial" - }, - "lastname": { - "value": "Barninger", - "matchLevel": "none" - } - } - } -] -``` diff --git a/packages/website/docs/getAlgoliaResults-js.mdx b/packages/website/docs/getAlgoliaResults-js.mdx index e9492c09d..da3d3e5cf 100644 --- a/packages/website/docs/getAlgoliaResults-js.mdx +++ b/packages/website/docs/getAlgoliaResults-js.mdx @@ -12,30 +12,47 @@ import GetAlgoliaResultsIntro from './partials/preset-algolia/getAlgoliaResults/ This example uses the function along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client. ```js -import { getAlgoliaResults } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch/lite'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; const searchClient = algoliasearch( 'latency', '6be0576ff61c053d5f9a3225e2a90f76' ); -getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'instant_search', - query, - params: { - hitsPerPage: 3, +autocomplete({ + // ... + getSources({ query }) { + return [ + { + sourceId: 'products', + getItems({ query }) { + getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + hitsPerPage: 5, + }, + }, + ], + }); + }, + // ... }, - }, - ], -}).then((results) => { - console.log(results); + ]; + }, }); ``` +:::info + +When using `getAlgoliaResults` and [`getAlgoliaFacets`](getAlgoliaFacets-js) with the same search client in different sources or plugins, Autocomplete batches all queries into a single network call to Algolia. If you're using the same search client for different sources or plugins, **make sure to use the same instance to leverage the internal cache and batching mechanism.** + +::: + ## Parameters See [`autocomplete-preset-algolia#getAlgoliaResults`](getAlgoliaResults#parameters). diff --git a/packages/website/docs/getAlgoliaResults.mdx b/packages/website/docs/getAlgoliaResults.mdx index a97a69c7d..c7e22d58e 100644 --- a/packages/website/docs/getAlgoliaResults.mdx +++ b/packages/website/docs/getAlgoliaResults.mdx @@ -39,30 +39,48 @@ If you don't use a package manager, you can use the HTML `script` element: This example uses the function along with the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) API client. ```js -import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; import algoliasearch from 'algoliasearch/lite'; +import { createAutocomplete } from '@algolia/autocomplete-core'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( 'latency', '6be0576ff61c053d5f9a3225e2a90f76' ); -getAlgoliaResults({ - searchClient, - queries: [ - { - indexName: 'instant_search', - query, - params: { - hitsPerPage: 3, +const autocomplete = createAutocomplete({ + // ... + getSources() { + return [ + { + sourceId: 'products', + getItems({ query }) { + getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + hitsPerPage: 5, + }, + }, + ], + }); + }, + // ... }, - }, - ], -}).then((results) => { - console.log(results); + ]; + }, }); ``` +:::info + +When using `getAlgoliaResults` and [`getAlgoliaFacets`](getAlgoliaFacets) with the same search client in different sources or plugins, Autocomplete batches all queries into a single network call to Algolia. If you're using the same search client for different sources or plugins, **make sure to use the same instance to leverage the internal cache and batching mechanism.** + +::: + ## Parameters ### `searchClient` @@ -105,35 +123,32 @@ These are the default search parameters. You can leave them as is and specify ot } ``` +### `transformResponse` + +> `(response: { results: Array | SearchForFacetValuesResponse>, hits: MaybeArray[]>, facetHits: MaybeArray }) => MaybeArray[] | FacetHit[]>` + +The function to transform the Algolia response before passing it to the Autocomplete state. You have access to the full Algolia results, as well as the pre-compiled hits and facet hits. This is useful to manipulate the hits, or store data from the results in the [context](context). + +This is the default implementation: + +```js +getAlgoliaResults({ + // ... + transformResponse({ hits }) { + return hits; + }, +}); +``` + ## Returns -The function returns a promise that resolves to a response with the following schema: +The function returns a description with the following interface: -```json +```ts { - "hits": [ - { - "objectID": "433", - "firstname": "Jimmie", - "lastname": "Barninger", - "_highlightResult": { - "firstname": { - "value": "<em>Jimmie</em>", - "matchLevel": "partial" - }, - "lastname": { - "value": "Barninger", - "matchLevel": "none" - } - } - } - ], - "page": 0, - "nbHits": 1, - "nbPages": 1, - "hitsPerPage": 20, - "processingTimeMS": 1, - "query": "jimmie paint", - "params": "query=jimmie+paint&attributesToRetrieve=firstname,lastname&hitsPerPage=20" + searchClient: SearchClient; + queries: MultipleQueriesQuery[]; + transformResponse: TransformResponse; + execute: Execute; } ``` diff --git a/packages/website/docs/getting-started.mdx b/packages/website/docs/getting-started.mdx index 454064200..822fb6277 100644 --- a/packages/website/docs/getting-started.mdx +++ b/packages/website/docs/getting-started.mdx @@ -103,11 +103,11 @@ Autocomplete is now plugged in. But you won't see anything appear until you defi Each source object needs to include a [`sourceId`](sources/#sourceid) and a [`getItems`](sources#getitems) function that returns the items to display. Sources can be static or dynamic. -This example uses an [Algolia index](https://www.algolia.com/doc/faq/basics/what-is-an-index/) of [e-commerce products](https://github.com/algolia/datasets/tree/master/ecommerce) as a source. The [`autocomplete-js`](autocomplete-js) package provides a built-in [`getAlgoliaHits`](getAlgoliaHits-js) function for just this purpose. +This example uses an [Algolia index](https://www.algolia.com/doc/faq/basics/what-is-an-index/) of [e-commerce products](https://github.com/algolia/datasets/tree/master/ecommerce) as a source. The [`autocomplete-js`](autocomplete-js) package provides a built-in [`getAlgoliaResults`](getAlgoliaResults-js) function for just this purpose. ```js title="app.js" import algoliasearch from 'algoliasearch/lite'; -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import '@algolia/autocomplete-theme-classic'; @@ -124,7 +124,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -144,7 +144,7 @@ autocomplete({ }); ``` -The [`getAlgoliaHits`](getAlgoliaHits-js) function requires an [Algolia search client](https://www.algolia.com/doc/api-client/getting-started/install/javascript/) initialized with an [Algolia application ID and API key](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api/#application-id). It lets you search into your Algolia index using an array of `queries`, which defines one or more queries to send to the index. +The [`getAlgoliaResults`](getAlgoliaResults-js) function requires an [Algolia search client](https://www.algolia.com/doc/api-client/getting-started/install/javascript/) initialized with an [Algolia application ID and API key](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api/#application-id). It lets you search into your Algolia index using an array of `queries`, which defines one or more queries to send to the index. This example makes just one query to the "autocomplete" index using the `query` from [`getSources`](sources#getsources). For now, it passes one additional parameter, [`hitsPerPage`](https://www.algolia.com/doc/api-reference/api-parameters/hitsPerPage/) to define how many items to display, but you could pass any other [Algolia query parameters](https://www.algolia.com/doc/api-reference/api-parameters/). @@ -158,7 +158,7 @@ The given `classNames` correspond to the [classic theme](autocomplete-theme-clas ```jsx title="app.jsx" /** @jsx h */ -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch'; import { h, Fragment } from 'preact'; @@ -177,7 +177,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -267,7 +267,7 @@ This is all you need for a basic implementation. To go further, you can use the ```jsx title="app.jsx" /** @jsx h */ -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch'; import { h } from 'preact'; diff --git a/packages/website/docs/introduction.mdx b/packages/website/docs/introduction.mdx index 5a3f07dbd..b049bb903 100644 --- a/packages/website/docs/introduction.mdx +++ b/packages/website/docs/introduction.mdx @@ -91,6 +91,6 @@ You can also display different data types (such as suggested search terms, produ Unlike [Algolia InstantSearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/), Autocomplete doesn't provide a library of ready-made UI widgets. You're in control of the full rendering of your autocomplete experience, and the library provides everything you need to make it functional and accessible. -You're also in charge of providing the collection of items to display. You can easily plug Algolia results using [`getAlgoliaHits`](getAlgoliaHits-js) if you want, but you're free to use Autocomplete with any data sources you want. +You're also in charge of providing the collection of items to display. You can easily plug Algolia results using [`getAlgoliaResults`](getAlgoliaResults-js) if you want, but you're free to use Autocomplete with any data sources you want. Ready to learn more? Move on to [Getting Started](getting-started) to see a basic example in action. diff --git a/packages/website/docs/partials/preset-algolia/getAlgoliaFacetHits/intro.md b/packages/website/docs/partials/preset-algolia/getAlgoliaFacetHits/intro.md deleted file mode 100644 index 1fd28b276..000000000 --- a/packages/website/docs/partials/preset-algolia/getAlgoliaFacetHits/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -Retrieves Algolia facet hits from multiple indices. - -The `getAlgoliaFacetHits` function lets you query facet hits several Algolia indices at once. diff --git a/packages/website/docs/partials/preset-algolia/getAlgoliaFacets/intro.md b/packages/website/docs/partials/preset-algolia/getAlgoliaFacets/intro.md new file mode 100644 index 000000000..c1c3410c7 --- /dev/null +++ b/packages/website/docs/partials/preset-algolia/getAlgoliaFacets/intro.md @@ -0,0 +1,3 @@ +Retrieves Algolia facet hits from multiple indices. + +The `getAlgoliaFacets` function lets you query facet hits from several Algolia indices at once. diff --git a/packages/website/docs/partials/preset-algolia/getAlgoliaHits/intro.md b/packages/website/docs/partials/preset-algolia/getAlgoliaHits/intro.md deleted file mode 100644 index 9a63d58d0..000000000 --- a/packages/website/docs/partials/preset-algolia/getAlgoliaHits/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -Retrieves Algolia hits from multiple indices as arrays of records. - -The `getAlgoliaHits` function lets you query several Algolia indices at once and returns the hits as an array of records. diff --git a/packages/website/docs/sending-algolia-insights-events.md b/packages/website/docs/sending-algolia-insights-events.md index ebc7fcc13..0ed681ae6 100644 --- a/packages/website/docs/sending-algolia-insights-events.md +++ b/packages/website/docs/sending-algolia-insights-events.md @@ -35,7 +35,7 @@ If you haven't implemented an autocomplete using Algolia as a source yet, follow First, begin with some boilerplate for the autocomplete implementation. Create a file called `index.js` in your `src` directory, and add the boilerplate below: ```js title="index.js" -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch'; import { h, Fragment } from 'preact'; @@ -52,7 +52,7 @@ autocomplete({ { sourceId: 'products', getItems() { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -107,7 +107,7 @@ function ProductItem({ hit, components }) { This boilerplate assumes you want to insert the autocomplete into a DOM element with `autocomplete` as an [`id`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). You should change the [`container`](autocomplete-js/#container) to [match your markup](basic-options). Setting [`openOnFocus`](autocomplete-js/#openonfocus) to `true` ensures that the dropdown appears as soon as a user focuses the input. -The autocomplete searches into an [Algolia index](https://www.algolia.com/doc/faq/basics/what-is-an-index/) of [e-commerce products](https://github.com/algolia/datasets/tree/master/ecommerce) using the [`getAlgoliaHits`](getAlgoliaHits) function. Refer to the example in the [Getting Started guide](getting-started) for more information. +The autocomplete searches into an [Algolia index](https://www.algolia.com/doc/faq/basics/what-is-an-index/) of [e-commerce products](https://github.com/algolia/datasets/tree/master/ecommerce) using the [`getAlgoliaResults`](getAlgoliaResults) function. Refer to the example in the [Getting Started guide](getting-started) for more information. :::note @@ -124,7 +124,7 @@ The [`autocomplete-plugin-algolia-insights`](createAlgoliaInsightsPlugin) packag It requires an [Algolia Insights client](https://www.algolia.com/doc/guides/getting-insights-and-analytics/search-analytics/click-and-conversion-analytics/how-to/sending-events-with-api-client/#initializing-the-insights-client) initialized with an [Algolia application ID and Search API key](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api/#application-id). ```js title="index.js" -import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'; import algoliasearch from 'algoliasearch'; import { h, Fragment } from 'preact'; import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index 1e02aac4f..1489e6fc2 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -129,12 +129,12 @@ autocomplete({ Static sources can be useful, especially [when the user hasn't typed anything yet](changing-behavior-based-on-query). However, you might want more robust search capabilities beyond exact matches in strings. -In this case, you could search into one or more Algolia indices using the built-in [`getAlgoliaHits`](getAlgoliaHits) function from the `autocomplete-preset-algolia` preset. +In this case, you could search into one or more Algolia indices using the built-in [`getAlgoliaResults`](getAlgoliaResults) function from the `autocomplete-preset-algolia` preset. ```js import algoliasearch from 'algoliasearch/lite'; import { autocomplete } from '@algolia/autocomplete-js'; -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( 'latency', @@ -148,7 +148,7 @@ autocomplete({ { sourceId: 'products', getItems({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { @@ -210,7 +210,7 @@ For example, you may want to display Algolia search results and [Query Suggestio ```js import algoliasearch from 'algoliasearch/lite'; import { autocomplete } from '@algolia/autocomplete-js'; -import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; +import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( 'latency', @@ -220,7 +220,7 @@ const searchClient = algoliasearch( autocomplete({ // ... getSources({ query }) { - return getAlgoliaHits({ + return getAlgoliaResults({ searchClient, queries: [ { diff --git a/packages/website/docs/using-react.md b/packages/website/docs/using-react.md index 2b1dee671..250777f32 100644 --- a/packages/website/docs/using-react.md +++ b/packages/website/docs/using-react.md @@ -86,13 +86,13 @@ The usage below sets [`openOnFocus`](autocomplete-js#openonfocus) and [sources]( ```jsx title=App.jsx" import React, { createElement } from 'react'; -import { getAlgoliaHits } from '@algolia/autocomplete-js'; -import algoliasearch from "algoliasearch"; +import { getAlgoliaResults } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch'; import { Autocomplete } from './components/Autocomplete'; import { ProductItem } from './components/ProductItem'; -const appId = "latency"; -const apiKey = "6be0576ff61c053d5f9a3225e2a90f76"; +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; const searchClient = algoliasearch(appId, apiKey); function App() { @@ -101,29 +101,27 @@ function App() {

React Application

- [ - { - sourceId: 'products', - getItems() { - return getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: "instant_search", - query, - } - ] - }); + getSources={({ query }) => [ + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + }, + ], + }); + }, + templates: { + item({ item, components }) { + return ; }, - templates: { - item({ item, components }) { - return ; - } - } - } - ] - } + }, + }, + ]} /> ); diff --git a/packages/website/docs/using-vue.md b/packages/website/docs/using-vue.md index 6637bb780..d8431a28d 100644 --- a/packages/website/docs/using-vue.md +++ b/packages/website/docs/using-vue.md @@ -35,7 +35,7 @@ Begin by adding a container for your autocomplete menu. This example adds a `div ``` -Then, import the necessary packages for a basic implementation. Since the example queries an Algolia index, it imports the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) package, [`autocomplete`](autocomplete-js) and [`getAlgoliaHits`](getAlgoliaHits-js) from the [`autocomplete-js`](autocomplete-js) package. Finally, it imports [`autocomplete-theme-classic`](autocomplete-theme-classic) package for some out of the box styling. +Then, import the necessary packages for a basic implementation. Since the example queries an Algolia index, it imports the [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) package, [`autocomplete`](autocomplete-js) and [`getAlgoliaResults`](getAlgoliaResults-js) from the [`autocomplete-js`](autocomplete-js) package. Finally, it imports [`autocomplete-theme-classic`](autocomplete-theme-classic) package for some out of the box styling. Depending on your desired [sources](sources), you may need to import other packages including [plugins](plugins). @@ -52,7 +52,7 @@ Include some boilerplate to insert the autocomplete into: