From cb4b051b330d035e676acb8239417281b35e1d11 Mon Sep 17 00:00:00 2001 From: meili-bot <74670311+meili-bot@users.noreply.github.com> Date: Mon, 13 Mar 2023 18:51:04 +0100 Subject: [PATCH 1/7] Update README.md From 3be2aaf8d9963cafade43d70adbf644a3e52a99c Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:36:07 +0200 Subject: [PATCH 2/7] Use the multiSearch api instead of multiple individual searches for Meilisearch v1.1.0 (#1038) --- .changeset/fresh-countries-move.md | 5 + README.md | 5 - .../__tests__/search-resolver.test.ts | 59 ++++++++-- packages/instant-meilisearch/package.json | 2 +- .../search-params-adapter.ts | 19 +++- .../search-request-adapter/search-resolver.ts | 52 +++++---- .../search-response-adapter/hits-adapter.ts | 22 ++-- .../pagination-adapter.ts | 2 +- .../search-response-adapter.ts | 74 +++++++++---- .../src/cache/__tests__/search-cache.test.ts | 4 +- .../src/cache/init-facets-distribution.ts | 50 ++++++--- .../src/cache/search-cache.ts | 4 +- .../src/client/config/index.ts | 72 ++++++++++++ .../src/client/instant-meilisearch-client.ts | 104 ++++++++---------- .../instant-meilisearch/src/types/types.ts | 50 +++++++-- .../instant-meilisearch/src/utils/array.ts | 14 +++ .../instant-meilisearch/src/utils/index.ts | 1 - .../instant-meilisearch/src/utils/validate.ts | 29 ----- yarn.lock | 8 +- 19 files changed, 372 insertions(+), 204 deletions(-) create mode 100644 .changeset/fresh-countries-move.md create mode 100644 packages/instant-meilisearch/src/client/config/index.ts delete mode 100644 packages/instant-meilisearch/src/utils/validate.ts diff --git a/.changeset/fresh-countries-move.md b/.changeset/fresh-countries-move.md new file mode 100644 index 00000000..29d246ac --- /dev/null +++ b/.changeset/fresh-countries-move.md @@ -0,0 +1,5 @@ +--- +"@meilisearch/instant-meilisearch": minor +--- + +Replaces search with multiSearch API. diff --git a/README.md b/README.md index f7ed40ca..039f9ec4 100644 --- a/README.md +++ b/README.md @@ -350,8 +350,6 @@ const search = instantsearch({ `Index` is the component that lets you apply widgets to a dedicated index. It’s useful if you want to build an interface that targets multiple indices. -Using this component, instant-meilisearch does an http-request for each different `Index` widget added. More http requests are made when using the [`RefinementList`](#✅-refinementlist) widget. - ### ✅ SearchBox [SearchBox references](https://www.algolia.com/doc/api-reference/widgets/search-box/js/) @@ -675,9 +673,6 @@ The `refinementList` widget is one of the most common widgets you can find in a - ✅ templates: The templates to use for the widget. - ✅ cssClasses: The CSS classes to override. -The `RefinementList` widget uses the `disjunctive facet search` principle when using the `or` operator. For each different facet category used, an additional http call is made. -For example, if I ask for `color=green` and `size=2`, three http requests are made. One for the hits, one for the `color` facet distribution, and one for the `size` facet distribution. To provide feedback on the subject, refer to [this discussion](https://github.com/meilisearch/product/issues/54). - The following example will create a UI component with the a list of genres on which you will be able to facet. ```js diff --git a/packages/instant-meilisearch/__tests__/search-resolver.test.ts b/packages/instant-meilisearch/__tests__/search-resolver.test.ts index 3db2eddb..27d2b1eb 100644 --- a/packages/instant-meilisearch/__tests__/search-resolver.test.ts +++ b/packages/instant-meilisearch/__tests__/search-resolver.test.ts @@ -3,6 +3,7 @@ import { instantMeiliSearch } from '../src' import { MeiliSearch } from 'meilisearch' import { mocked } from 'ts-jest/utils' import { PACKAGE_VERSION } from '../src/package-version' +import { MeiliSearchMultiSearchParams } from '../src/types' jest.mock('meilisearch') @@ -18,19 +19,22 @@ export const searchResponse = { // Mocking of Meilisearch package const mockedMeilisearch = mocked(MeiliSearch, true) -const mockedSearch = jest.fn(() => searchResponse) -const mockedIndex = jest.fn(() => { +const mockedMultiSearch = jest.fn((request) => { + const response = request.queries.map((req: MeiliSearchMultiSearchParams) => ({ + ...searchResponse, + indexUid: req.indexUid, + })) return { - search: mockedSearch, + results: response, } }) mockedMeilisearch.mockReturnValue({ // @ts-ignore - index: mockedIndex, + multiSearch: mockedMultiSearch, }) -describe('Pagination browser test', () => { +describe('Cached search tests', () => { afterEach(() => { jest.clearAllMocks() }) @@ -51,7 +55,7 @@ describe('Pagination browser test', () => { apiKey: '', clientAgents: [`Meilisearch instant-meilisearch (v${PACKAGE_VERSION})`], }) - expect(mockedSearch).toHaveBeenCalledTimes(2) + expect(mockedMultiSearch).toHaveBeenCalledTimes(2) }) test('two different search parameters', async () => { @@ -77,7 +81,7 @@ describe('Pagination browser test', () => { apiKey: '', clientAgents: [`Meilisearch instant-meilisearch (v${PACKAGE_VERSION})`], }) - expect(mockedSearch).toHaveBeenCalledTimes(3) + expect(mockedMultiSearch).toHaveBeenCalledTimes(3) }) test('two identical and one different search parameters', async () => { @@ -104,7 +108,7 @@ describe('Pagination browser test', () => { apiKey: '', clientAgents: [`Meilisearch instant-meilisearch (v${PACKAGE_VERSION})`], }) - expect(mockedSearch).toHaveBeenCalledTimes(3) + expect(mockedMultiSearch).toHaveBeenCalledTimes(3) }) test('two same and two different search parameter', async () => { @@ -132,6 +136,43 @@ describe('Pagination browser test', () => { apiKey: '', clientAgents: [`Meilisearch instant-meilisearch (v${PACKAGE_VERSION})`], }) - expect(mockedSearch).toHaveBeenCalledTimes(3) + expect(mockedMultiSearch).toHaveBeenCalledTimes(3) + }) + + test('Multiple search parameters on different index uids', async () => { + const searchParameters1 = [ + { + indexName: 'movies', + params: { + query: '', + }, + }, + { + indexName: 'game', + params: { + query: '', + }, + }, + ] + + const searchParameters2 = { + indexName: 'movies', + params: { + query: 'other query', + }, + } + const searchClient = instantMeiliSearch('http://localhost:7700') + await searchClient.search(searchParameters1) + await searchClient.search([searchParameters2]) + await searchClient.search(searchParameters1) + await searchClient.search([searchParameters2]) + + expect(mockedMeilisearch).toHaveBeenCalledWith({ + host: 'http://localhost:7700', + apiKey: '', + clientAgents: [`Meilisearch instant-meilisearch (v${PACKAGE_VERSION})`], + }) + + expect(mockedMultiSearch).toHaveBeenCalledTimes(3) }) }) diff --git a/packages/instant-meilisearch/package.json b/packages/instant-meilisearch/package.json index 2050e3f5..2abe7f11 100644 --- a/packages/instant-meilisearch/package.json +++ b/packages/instant-meilisearch/package.json @@ -47,7 +47,7 @@ "templates" ], "dependencies": { - "meilisearch": "^0.31.1" + "meilisearch": "0.32.0-v1.1.0-pre-release.0" }, "devDependencies": { "@babel/cli": "^7.21.0", diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts index cb3e8b2a..b0d0981c 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts @@ -1,8 +1,8 @@ import type { - MeiliSearchParams, SearchContext, Filter, PaginationState, + MeiliSearchMultiSearchParams, } from '../../types' import { @@ -72,7 +72,7 @@ function setFinitePagination( * @returns {MeiliSearchParams} */ export function MeiliParamsCreator(searchContext: SearchContext) { - const meiliSearchParams: Record = {} + const meiliSearchParams: any = {} const { facets, attributesToSnippet, @@ -89,14 +89,21 @@ export function MeiliParamsCreator(searchContext: SearchContext) { filters, numericFilters, facetFilters, + indexUid, } = searchContext const meilisearchFilters = adaptFilters(filters, numericFilters, facetFilters) return { - getParams() { + getParams(): MeiliSearchMultiSearchParams { return meiliSearchParams }, + addQuery() { + meiliSearchParams.q = query + }, + addIndexUid() { + meiliSearchParams.indexUid = indexUid + }, addFacets() { if (Array.isArray(facets)) { meiliSearchParams.facets = facets @@ -194,12 +201,14 @@ export function MeiliParamsCreator(searchContext: SearchContext) { * to search request compliant with Meilisearch * * @param {SearchContext} searchContext - * @returns {MeiliSearchParams} + * @returns {MeiliSearchMultiSearchParams} */ export function adaptSearchParams( searchContext: SearchContext -): MeiliSearchParams { +): MeiliSearchMultiSearchParams { const meilisearchParams = MeiliParamsCreator(searchContext) + meilisearchParams.addQuery() + meilisearchParams.addIndexUid() meilisearchParams.addFacets() meilisearchParams.addAttributesToHighlight() meilisearchParams.addPreTag() diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-resolver.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-resolver.ts index 84fd6b5d..7e176217 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-resolver.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-resolver.ts @@ -1,9 +1,9 @@ import { - SearchContext, MeiliSearch, - MeiliSearchResponse, SearchCacheInterface, - MeiliSearchParams, + MeiliSearchMultiSearchParams, + MeilisearchMultiSearchResult, + PaginationState, } from '../../types' /** @@ -14,37 +14,35 @@ export function SearchResolver( cache: SearchCacheInterface ) { return { - /** - * @param {SearchContext} searchContext - * @param {MeiliSearchParams} searchParams - * @param {MeiliSearch} client - * @returns {Promise} - */ - searchResponse: async function ( - searchContext: SearchContext, - searchParams: MeiliSearchParams - ): Promise>> { - // Create cache key containing a unique set of search parameters - const key = cache.formatKey([ - searchParams, - searchContext.indexUid, - searchContext.query, - searchContext.pagination, - ]) - const cachedResponse = cache.getEntry(key) + multiSearch: async function ( + searchQueries: MeiliSearchMultiSearchParams[], + instantSearchPagination: PaginationState[] + ): Promise { + const key = cache.formatKey([searchQueries]) + + const cachedResponse = cache.getEntry(key) // Check if specific request is already cached with its associated search response. if (cachedResponse) return cachedResponse - // Make search request - const searchResponse = await client - .index(searchContext.indexUid) - .search(searchContext.query, searchParams) + const searchResponses = await client.multiSearch({ + queries: searchQueries, + }) + const responseWithPagination = searchResponses.results.map( + (response, index) => ({ + ...response, + // TODO: should be removed at one point + pagination: instantSearchPagination[index] || {}, + }) + ) // Cache response - cache.setEntry(key, searchResponse) + cache.setEntry( + key, + responseWithPagination + ) - return searchResponse + return responseWithPagination }, } } diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/hits-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/hits-adapter.ts index a4c673ca..451d4fe8 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/hits-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/hits-adapter.ts @@ -1,26 +1,30 @@ -import type { SearchContext, MeiliSearchResponse } from '../../types' +import type { + PaginationState, + MeilisearchMultiSearchResult, + InstantMeiliSearchConfig, +} from '../../types' import { adaptFormattedFields } from './format-adapter' import { adaptGeoResponse } from './geo-reponse-adapter' /** - * @param {MeiliSearchResponse>} searchResponse + * @param {MeilisearchMultiSearchResult} searchResult * @param {SearchContext} searchContext * @returns {Array>} */ export function adaptHits( - searchResponse: MeiliSearchResponse>, - searchContext: SearchContext + searchResponse: MeilisearchMultiSearchResult & { + pagination: PaginationState + }, + config: InstantMeiliSearchConfig ): any { - const { primaryKey } = searchContext const { hits } = searchResponse - const { - pagination: { finite, hitsPerPage }, - } = searchContext + const { hitsPerPage } = searchResponse.pagination + const { finitePagination, primaryKey } = config // Needs: finite, hitsPerPage // if the length of the hits is bigger than the hitsPerPage // It means that there is still pages to come as we append limit by hitsPerPage + 1 // In which case we still need to remove the additional hit returned by Meilisearch - if (!finite && hits.length > hitsPerPage) { + if (!finitePagination && hits.length > hitsPerPage) { hits.splice(hits.length - 1, 1) } diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts index 5262d9de..f6f44845 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts @@ -1,7 +1,7 @@ import type { MeiliSearchResponse, - PaginationState, InstantSearchPagination, + PaginationState, } from '../../types' function adaptNbPages( diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts index 3dc72829..24304c6c 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts @@ -1,8 +1,8 @@ import type { - SearchContext, - MeiliSearchResponse, AlgoliaSearchResponse, FacetDistribution, + InstantMeiliSearchConfig, + MeilisearchMultiSearchResult, } from '../../types' import { adaptHits } from './hits-adapter' import { adaptTotalHits } from './total-hits-adapter' @@ -10,45 +10,72 @@ import { adaptPaginationParameters } from './pagination-adapter' import { adaptFacetDistribution } from './facet-distribution-adapter' /** - * Adapt search response from Meilisearch - * to search response compliant with instantsearch.js + * Adapt multiple search results from Meilisearch + * to search results compliant with instantsearch.js * - * @param {MeiliSearchResponse>} searchResponse - * @param {SearchContext} searchContext + * @param {Array>} searchResponse + * @param {Record} initialFacetDistribution + * @param {InstantMeiliSearchConfig} config * @returns {{ results: Array> }} */ -export function adaptSearchResponse( - searchResponse: MeiliSearchResponse>, - searchContext: SearchContext, - initialFacetDistribution: FacetDistribution +export function adaptSearchResults>( + meilisearchResults: MeilisearchMultiSearchResult[], + initialFacetDistribution: Record, + config: InstantMeiliSearchConfig +): { results: Array> } { + const instantSearchResult: Array> = + meilisearchResults.map((meilisearchResult) => { + return adaptSearchResult( + meilisearchResult, + initialFacetDistribution[meilisearchResult.indexUid], + config + ) + }) + + return { results: instantSearchResult } +} + +/** + * Adapt search result from Meilisearch + * to search result compliant with instantsearch.js + * + * @param {MeilisearchMultiSearchResult>} searchResponse + * @param {Record} initialFacetDistribution + * @param {InstantMeiliSearchConfig} config + * @returns {AlgoliaSearchResponse} + */ +export function adaptSearchResult( + meiliSearchResult: MeilisearchMultiSearchResult, + initialFacetDistribution: FacetDistribution, + config: InstantMeiliSearchConfig ): AlgoliaSearchResponse { - const searchResponseOptionals: Record = {} const { processingTimeMs, query, - facetDistribution: responseFacetDistribution, - } = searchResponse + indexUid, + facetDistribution: responseFacetDistribution = {}, + } = meiliSearchResult - const { keepZeroFacets, facets } = searchContext + const facets = Object.keys(responseFacetDistribution) const { hitsPerPage, page, nbPages } = adaptPaginationParameters( - searchResponse, - searchContext.pagination + meiliSearchResult, + meiliSearchResult.pagination ) - const hits = adaptHits(searchResponse, searchContext) - const nbHits = adaptTotalHits(searchResponse) + const hits = adaptHits(meiliSearchResult, config) + const nbHits = adaptTotalHits(meiliSearchResult) const facetDistribution = adaptFacetDistribution( - keepZeroFacets, + config.keepZeroFacets, facets, initialFacetDistribution, responseFacetDistribution ) - // Create response object compliant with InstantSearch - const adaptedSearchResponse = { - index: searchContext.indexUid, + // Create result object compliant with InstantSearch + const adaptedSearchResult = { + index: indexUid, hitsPerPage, page, facets: facetDistribution, @@ -59,7 +86,6 @@ export function adaptSearchResponse( hits, params: '', exhaustiveNbHits: false, - ...searchResponseOptionals, } - return adaptedSearchResponse + return adaptedSearchResult } diff --git a/packages/instant-meilisearch/src/cache/__tests__/search-cache.test.ts b/packages/instant-meilisearch/src/cache/__tests__/search-cache.test.ts index a0edad27..a87ffa36 100644 --- a/packages/instant-meilisearch/src/cache/__tests__/search-cache.test.ts +++ b/packages/instant-meilisearch/src/cache/__tests__/search-cache.test.ts @@ -13,7 +13,7 @@ describe('Entries in the cache', () => { const cache = SearchCache({ myKey: 'myValue' }) const key = cache.getEntry('myKey') - expect(key).toEqual('myValue') + expect(key).toBeUndefined() }) test('to getEntry on valid json string', () => { @@ -34,7 +34,7 @@ describe('Entries in the cache', () => { const cache = SearchCache({ myKey: '{ id: 1 }' }) const key = cache.getEntry('myKey') - expect(key).toEqual('{ id: 1 }') + expect(key).toBeUndefined() }) }) diff --git a/packages/instant-meilisearch/src/cache/init-facets-distribution.ts b/packages/instant-meilisearch/src/cache/init-facets-distribution.ts index d9d5e087..14120143 100644 --- a/packages/instant-meilisearch/src/cache/init-facets-distribution.ts +++ b/packages/instant-meilisearch/src/cache/init-facets-distribution.ts @@ -1,10 +1,15 @@ -import { FacetDistribution, SearchContext } from '../types' +import { + FacetDistribution, + SearchContext, + MeiliSearchMultiSearchParams, + MultiSearchResolver, +} from '../types' import { MeiliParamsCreator } from '../adapter' +import { removeDuplicate } from '../utils' -async function getIndexFacetDistribution( - searchResolver: any, +export function getParametersWithoutFilters( searchContext: SearchContext -): Promise { +): MeiliSearchMultiSearchParams { const defaultSearchContext = { ...searchContext, // placeholdersearch true to ensure a request is made @@ -14,27 +19,36 @@ async function getIndexFacetDistribution( } const meilisearchParams = MeiliParamsCreator(defaultSearchContext) meilisearchParams.addFacets() + meilisearchParams.addIndexUid() meilisearchParams.addPagination() - // Search response from Meilisearch - const searchResponse = await searchResolver.searchResponse( - defaultSearchContext, - meilisearchParams.getParams() - ) - return searchResponse.facetDistribution || {} + return meilisearchParams.getParams() } +// Fetch the initial facets distribution of an Index +// Used to show the facets when `placeholderSearch` is set to true +// Used to fill the missing facet values when `keepZeroFacets` is set to true export async function initFacetDistribution( - searchResolver: any, - searchContext: SearchContext, + searchResolver: MultiSearchResolver, + queries: MeiliSearchMultiSearchParams[], initialFacetDistribution: Record ): Promise> { - // Fetch the initial facets distribution of an Index - // Used to show the facets when `placeholderSearch` is set to true - // Used to fill the missing facet values when `keepZeroFacets` is set to true - if (!initialFacetDistribution[searchContext.indexUid]) { - initialFacetDistribution[searchContext.indexUid] = - await getIndexFacetDistribution(searchResolver, searchContext) + const removeIndexUidDuplicates = removeDuplicate('indexUid') + + const searchQueries = queries + .filter(removeIndexUidDuplicates) // only make one request per indexUid + .filter(({ indexUid }) => { + // avoid requesting on indexes that already have an initial facetDistribution + return !Object.keys(initialFacetDistribution).includes(indexUid) + }) + + if (searchQueries.length === 0) return initialFacetDistribution + + const results = await searchResolver.multiSearch(searchQueries, []) + + for (const searchResult of results) { + initialFacetDistribution[searchResult.indexUid] = + searchResult.facetDistribution || {} } return initialFacetDistribution diff --git a/packages/instant-meilisearch/src/cache/search-cache.ts b/packages/instant-meilisearch/src/cache/search-cache.ts index ef3a7d12..8506b5c5 100644 --- a/packages/instant-meilisearch/src/cache/search-cache.ts +++ b/packages/instant-meilisearch/src/cache/search-cache.ts @@ -9,12 +9,12 @@ export function SearchCache( ): SearchCacheInterface { let searchCache = cache return { - getEntry: function (key: string) { + getEntry: function (key: string): T | undefined { if (searchCache[key]) { try { return JSON.parse(searchCache[key]) } catch (_) { - return searchCache[key] + return undefined } } return undefined diff --git a/packages/instant-meilisearch/src/client/config/index.ts b/packages/instant-meilisearch/src/client/config/index.ts new file mode 100644 index 00000000..48198a9a --- /dev/null +++ b/packages/instant-meilisearch/src/client/config/index.ts @@ -0,0 +1,72 @@ +import { + InstantMeiliSearchOptions, + InstantMeiliSearchConfig, +} from '../../types' + +/** + * Get the configuration of instant meilisearch + * + * @param {InstantMeiliSearchOptions} option + * @returns {InstantMeiliSearchConfig} + */ + +export function getInstantMeilisearchConfig( + options: InstantMeiliSearchOptions +): InstantMeiliSearchConfig { + const defaultOptions = { + placeholderSearch: true, + keepZeroFacets: false, + clientAgents: [], + finitePagination: false, + } + + return { + ...defaultOptions, + ...options, + } +} + +/** + * Resolves apiKey if it is a function + * @param {string | apiKeyCallback} apiKey + * @returns {string} api key value + */ +export function getApiKey(apiKey: string | (() => string)): string { + // If apiKey is function, call it to get the apiKey + if (typeof apiKey === 'function') { + const apiKeyFnValue = apiKey() + if (typeof apiKeyFnValue !== 'string') { + throw new TypeError( + 'Provided apiKey function (2nd parameter) did not return a string, expected string' + ) + } + + return apiKeyFnValue + } + + return apiKey +} + +/** + * Validates host and apiKey parameters, throws if invalid + * @param hostUrl + * @param apiKey + */ +export function validateInstantMeiliSearchParams( + hostUrl: string, + apiKey: string | (() => string) +) { + // Validate host url + if (typeof hostUrl !== 'string') { + throw new TypeError( + 'Provided hostUrl value (1st parameter) is not a string, expected string' + ) + } + + // Validate api key + if (typeof apiKey !== 'string' && typeof apiKey !== 'function') { + throw new TypeError( + 'Provided apiKey value (2nd parameter) is not a string or a function, expected string or function' + ) + } +} diff --git a/packages/instant-meilisearch/src/client/instant-meilisearch-client.ts b/packages/instant-meilisearch/src/client/instant-meilisearch-client.ts index 38c2f5be..a493045c 100644 --- a/packages/instant-meilisearch/src/client/instant-meilisearch-client.ts +++ b/packages/instant-meilisearch/src/client/instant-meilisearch-client.ts @@ -6,22 +6,25 @@ import { AlgoliaMultipleQueriesQuery, SearchContext, FacetDistribution, + PaginationState, } from '../types' import { - adaptSearchResponse, + getApiKey, + getInstantMeilisearchConfig, + validateInstantMeiliSearchParams, +} from './config' +import { + adaptSearchResults, adaptSearchParams, SearchResolver, } from '../adapter' import { createSearchContext } from '../contexts' -import { SearchCache, initFacetDistribution } from '../cache/' +import { + SearchCache, + initFacetDistribution, + getParametersWithoutFilters, +} from '../cache/' import { constructClientAgents } from './agents' -import { validateInstantMeiliSearchParams } from '../utils' - -/** - * apiKey callback definition - * @callback apiKeyCallback - * @returns {string} - The apiKey to use - */ /** * Instantiate SearchClient required by instantsearch.js. @@ -57,6 +60,9 @@ export function instantMeiliSearch( let initialFacetDistribution: Record = {} + const instantMeilisearchConfig = getInstantMeilisearchConfig( + instantMeiliSearchOptions + ) return { clearCache: () => searchCache.clearCache(), /** @@ -67,44 +73,49 @@ export function instantMeiliSearch( instantSearchRequests: readonly AlgoliaMultipleQueriesQuery[] ): Promise<{ results: Array> }> { try { - const searchResponses: { results: Array> } = { - results: [], - } + const meilisearchRequests = [] + const instantSearchPagination: PaginationState[] = [] + const initialFacetDistributionsRequests = [] - const requests = instantSearchRequests - - for (const searchRequest of requests) { + for (const searchRequest of instantSearchRequests) { const searchContext: SearchContext = createSearchContext( searchRequest, instantMeiliSearchOptions ) - // Adapt search request to Meilisearch compliant search request - const adaptedSearchRequest = adaptSearchParams(searchContext) + // Adapt the search parameters provided by instantsearch to + // search parameters that are compliant with Meilisearch + const meilisearchSearchQuery = adaptSearchParams(searchContext) + meilisearchRequests.push(meilisearchSearchQuery) - initialFacetDistribution = await initFacetDistribution( - searchResolver, - searchContext, - initialFacetDistribution - ) + // Create a parameter without any filters to be able to store the default facet distribution + const defaultSearchQuery = getParametersWithoutFilters(searchContext) + initialFacetDistributionsRequests.push(defaultSearchQuery) - // Search response from Meilisearch - const searchResponse = await searchResolver.searchResponse( - searchContext, - adaptedSearchRequest - ) + // Keep information about the pagination parameters of instantsearch as + // they are needed to adapt the search response of Meilisearch + instantSearchPagination.push(searchContext.pagination) + } - // Adapt the Meilisearch response to a compliant instantsearch.js response - const adaptedSearchResponse = adaptSearchResponse( - searchResponse, - searchContext, - initialFacetDistribution[searchContext.indexUid] - ) + initialFacetDistribution = await initFacetDistribution( + searchResolver, + initialFacetDistributionsRequests, + initialFacetDistribution + ) - searchResponses.results.push(adaptedSearchResponse) - } + // Search request to Meilisearch happens here + const meilisearchResults = await searchResolver.multiSearch( + meilisearchRequests, + instantSearchPagination // Create issue on pagination + ) + + const instantSearchResponse = adaptSearchResults( + meilisearchResults, + initialFacetDistribution, + instantMeilisearchConfig + ) - return searchResponses + return instantSearchResponse } catch (e: any) { console.error(e) throw new Error(e) @@ -120,24 +131,3 @@ export function instantMeiliSearch( }, } } - -/** - * Resolves apiKey if it is a function - * @param {string | apiKeyCallback} apiKey - * @returns {string} api key value - */ -function getApiKey(apiKey: string | (() => string)): string { - // If apiKey is function, call it to get the apiKey - if (typeof apiKey === 'function') { - const apiKeyFnValue = apiKey() - if (typeof apiKeyFnValue !== 'string') { - throw new TypeError( - 'Provided apiKey function (2nd parameter) did not return a string, expected string' - ) - } - - return apiKeyFnValue - } - - return apiKey -} diff --git a/packages/instant-meilisearch/src/types/types.ts b/packages/instant-meilisearch/src/types/types.ts index 2bb14616..7ac4d77e 100644 --- a/packages/instant-meilisearch/src/types/types.ts +++ b/packages/instant-meilisearch/src/types/types.ts @@ -1,18 +1,22 @@ -import type { SearchResponse as MeiliSearchResponse } from 'meilisearch' import type { SearchClient } from 'instantsearch.js' import type { MultipleQueriesQuery as AlgoliaMultipleQueriesQuery } from '@algolia/client-search' -export type { AlgoliaMultipleQueriesQuery } -export type { SearchResponse as AlgoliaSearchResponse } from '@algolia/client-search' - -export type { - Filter, - FacetDistribution, - SearchResponse as MeiliSearchResponse, +import type { SearchParams as MeiliSearchParams, - MeiliSearch, + SearchResponse as MeiliSearchResponse, + MultiSearchResponse as MeilisearchMultiSearchResponse, } from 'meilisearch' +export type { + AlgoliaMultipleQueriesQuery, + MeilisearchMultiSearchResponse, + MeiliSearchParams, + MeiliSearchResponse, +} +export type { SearchResponse as AlgoliaSearchResponse } from '@algolia/client-search' + +export type { Filter, FacetDistribution, MeiliSearch } from 'meilisearch' + export type InstantSearchParams = AlgoliaMultipleQueriesQuery['params'] export const enum MatchingStrategies { @@ -29,8 +33,17 @@ export type InstantMeiliSearchOptions = { finitePagination?: boolean } +export type InstantMeiliSearchConfig = { + placeholderSearch: boolean + keepZeroFacets: boolean + clientAgents: string[] + finitePagination: boolean + primaryKey?: string + matchingStrategy?: MatchingStrategies +} + export type SearchCacheInterface = { - getEntry: (key: string) => MeiliSearchResponse | undefined + getEntry: (key: string) => T | undefined formatKey: (components: any[]) => string setEntry: (key: string, searchResponse: T) => void clearCache: () => void @@ -63,6 +76,16 @@ export type InstantSearchPagination = { export type Facets = string | string[] | undefined +export type MeiliSearchMultiSearchParams = MeiliSearchParams & { + indexUid: string +} + +export type MeilisearchMultiSearchResult> = + MeiliSearchResponse & { + indexUid: string + pagination: PaginationState + } + export type SearchContext = Omit & InstantSearchParams & { pagination: PaginationState @@ -79,3 +102,10 @@ export type SearchContext = Omit & export type InstantMeiliSearchInstance = SearchClient & { clearCache: () => void } + +export type MultiSearchResolver = { + multiSearch: ( + searchQueries: MeiliSearchMultiSearchParams[], + instantSearchPagination: PaginationState[] + ) => Promise +} diff --git a/packages/instant-meilisearch/src/utils/array.ts b/packages/instant-meilisearch/src/utils/array.ts index bb8dc381..e57bfe5d 100644 --- a/packages/instant-meilisearch/src/utils/array.ts +++ b/packages/instant-meilisearch/src/utils/array.ts @@ -1,3 +1,17 @@ export const removeUndefined = (arr: Array) => { return arr.filter((x) => x !== undefined) as T[] } + +export function removeDuplicate(key: string) { + const indexes: string[] = [] + + return (object: Record) => { + if (indexes.includes(object[key])) { + return false + } + + indexes.push(object[key]) + + return true + } +} diff --git a/packages/instant-meilisearch/src/utils/index.ts b/packages/instant-meilisearch/src/utils/index.ts index d05bae6f..29679e42 100644 --- a/packages/instant-meilisearch/src/utils/index.ts +++ b/packages/instant-meilisearch/src/utils/index.ts @@ -1,4 +1,3 @@ export * from './array' export * from './string' export * from './object' -export * from './validate' diff --git a/packages/instant-meilisearch/src/utils/validate.ts b/packages/instant-meilisearch/src/utils/validate.ts deleted file mode 100644 index 7783fd89..00000000 --- a/packages/instant-meilisearch/src/utils/validate.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * apiKey callback definition - * @callback apiKeyCallback - * @returns {string} - The apiKey to use - */ - -/** - * Validates host and apiKey parameters, throws if invalid - * @param hostUrl - * @param apiKey - */ -export function validateInstantMeiliSearchParams( - hostUrl: string, - apiKey: string | (() => string) -) { - // Validate host url - if (typeof hostUrl !== 'string') { - throw new TypeError( - 'Provided hostUrl value (1st parameter) is not a string, expected string' - ) - } - - // Validate api key - if (typeof apiKey !== 'string' && typeof apiKey !== 'function') { - throw new TypeError( - 'Provided apiKey value (2nd parameter) is not a string or a function, expected string or function' - ) - } -} diff --git a/yarn.lock b/yarn.lock index d829a298..baf60c78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9707,10 +9707,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -meilisearch@^0.31.1: - version "0.31.1" - resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.31.1.tgz#c526274c3cda844e8ee8d9562cbd3febbe1b9508" - integrity sha512-ajMieU0e25lLkT+05J0snX0Ycow1UofxIy5sag03flERUbjXq8ouVwkrJkW27JsKftIeDeffRRRr89LasU9+0w== +meilisearch@0.32.0-v1.1.0-pre-release.0: + version "0.32.0-v1.1.0-pre-release.0" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.32.0-v1.1.0-pre-release.0.tgz#d67a45c05625fe93bf5f7fee3d81169620b9068f" + integrity sha512-xO1k2z6KgOWJq4LeWHXa/Zy45dBxmxcIzJ6cPrhByl2otlZPbW6tXS/4cQWik81iTtbfx4pa+vS4kBpsXgVjmQ== dependencies: cross-fetch "^3.1.5" From d574c7ea617b24ad91142a6a75d413c855e4f99f Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 27 Mar 2023 18:40:40 +0200 Subject: [PATCH 3/7] Update meilisearch-js version to 0.32.0-v1.1.0-pre-release.2 --- packages/instant-meilisearch/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/instant-meilisearch/package.json b/packages/instant-meilisearch/package.json index 2abe7f11..1e5d5ed1 100644 --- a/packages/instant-meilisearch/package.json +++ b/packages/instant-meilisearch/package.json @@ -47,7 +47,7 @@ "templates" ], "dependencies": { - "meilisearch": "0.32.0-v1.1.0-pre-release.0" + "meilisearch": "0.32.0-v1.1.0-pre-release.2" }, "devDependencies": { "@babel/cli": "^7.21.0", diff --git a/yarn.lock b/yarn.lock index baf60c78..7ba28a8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9707,10 +9707,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -meilisearch@0.32.0-v1.1.0-pre-release.0: - version "0.32.0-v1.1.0-pre-release.0" - resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.32.0-v1.1.0-pre-release.0.tgz#d67a45c05625fe93bf5f7fee3d81169620b9068f" - integrity sha512-xO1k2z6KgOWJq4LeWHXa/Zy45dBxmxcIzJ6cPrhByl2otlZPbW6tXS/4cQWik81iTtbfx4pa+vS4kBpsXgVjmQ== +meilisearch@0.32.0-v1.1.0-pre-release.2: + version "0.32.0-v1.1.0-pre-release.2" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.32.0-v1.1.0-pre-release.2.tgz#f1ab76492dd23ed7d09137dafa7cc61ca25c1591" + integrity sha512-w7dyKWIEPkPUvYUv9QCm90flxgSz4W3YmTf4p67vJgON8IhUGCqhqXJ5NF39HbrppsRil0nccsYGTDu9tURlWQ== dependencies: cross-fetch "^3.1.5" From 0211825a4ce9bb12f54aff3f7a77b4c14ed4615e Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Tue, 28 Mar 2023 11:30:01 +0200 Subject: [PATCH 4/7] Add facetStats compatibility to be aware of min and max for MS v1.1.0 (#1046) --- .changeset/stupid-mirrors-itch.md | 6 ++ README.md | 46 +------------ .../__tests__/facet-stats.test.ts | 68 +++++++++++++++++++ .../adapt-facet-stats.ts | 19 ++++++ .../search-response-adapter.ts | 3 + .../instant-meilisearch/src/types/types.ts | 29 +++++++- 6 files changed, 127 insertions(+), 44 deletions(-) create mode 100644 .changeset/stupid-mirrors-itch.md create mode 100644 packages/instant-meilisearch/__tests__/facet-stats.test.ts create mode 100644 packages/instant-meilisearch/src/adapter/search-response-adapter/adapt-facet-stats.ts diff --git a/.changeset/stupid-mirrors-itch.md b/.changeset/stupid-mirrors-itch.md new file mode 100644 index 00000000..1f42b708 --- /dev/null +++ b/.changeset/stupid-mirrors-itch.md @@ -0,0 +1,6 @@ +--- +"@meilisearch/instant-meilisearch": patch +--- + +Add the facetStats of numeric facets, giving access to the min and max value of these facets. +The following widgets are now compatible with Meilisearch: `RangeSlider` and `RangeInput` diff --git a/README.md b/README.md index 039f9ec4..c9a0db7a 100644 --- a/README.md +++ b/README.md @@ -741,54 +741,14 @@ The `rangeSlider` widget provides a user-friendly way to filter the results, bas - ✅ attribute: The name of the attribute in the document. _required_. - ✅ min: The minimum value for the input. _required_ - ✅ max: The maximum value for the input. _required_ -- ❌ precision: The number of digits after the decimal point to use. Not compatible as only integers work with `rangeSlider`. +- ✅ precision: The number of digits after the decimal point to use. Not compatible as only integers work with `rangeSlider`. - ✅ step: The number of steps between each handle move. - ✅ pips: Whether to show slider pips (ruler marks). - ✅ tooltips: Whether to show tooltips. The default tooltips show the raw value. - ✅ cssClasses: The CSS classes to override. -#### ⚠️ The component is compatible but only by applying the following requirements: +To be able to use the `rangeSlider` on an attribute, the attribute must be in the[`filterableAttributes`](https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html#configuring-filters) and must contain numeric values. -#### 1. Manual Min Max - -Min and max of attributes are not returned from Meilisearch and thus **must be set manually**. - -```js - instantsearch.widgets.rangeSlider({ - // ... - min: 0, - max: 100000, - }), -``` - -#### 2. Attribute must be in `filterableAttributes` - -If the attribute is not in the [`filterableAttributes`](https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html#configuring-filters) setting list, filtering on this attribute is not possible. - -Example: -Given the attribute `id` that has not been added in `filterableAttributes`: - -```js - instantsearch.widgets.rangeSlider({ - attribute: 'id', - // ... - }), -``` - -The widget throws the following error: - -```json -{ - "message": " .. attribute `id` is not filterable, available filterable attributes are: author, price, genres", - "errorCode": "bad_request", - "errorType": "invalid_request_error", - "errorLink": "https://docs.meilisearch.com/errors#bad_request" -} -``` - -To avoid this error, the attribute must be added to the [`filterableAttributes` setting](https://docs.meilisearch.com/reference/api/filterable_attributes.html#get-filterable-attributes). - -After these steps, `rangeSlider` becomes compatible. ### ✅ Menu @@ -832,7 +792,7 @@ The `rangeInput` widget allows a user to select a numeric range using a minimum - ✅ templates: The templates to use for the widget. - ✅ cssClasses: The CSS classes to override. -⚠️ Not compatible with Meilisearch by default, needs a workaround. See workaround in [RangeSlider](#-rangeslider) section. +To be able to use the `RangeInput` on an attribute, the attribute must be in the[`filterableAttributes`](https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html#configuring-filters) and must contain numeric values. ### ✅ MenuSelect diff --git a/packages/instant-meilisearch/__tests__/facet-stats.test.ts b/packages/instant-meilisearch/__tests__/facet-stats.test.ts new file mode 100644 index 00000000..7d366d1a --- /dev/null +++ b/packages/instant-meilisearch/__tests__/facet-stats.test.ts @@ -0,0 +1,68 @@ +import { searchClient, dataset, meilisearchClient } from './assets/utils' + +describe('Facet stats tests', () => { + beforeAll(async () => { + const deleteTask = await meilisearchClient.deleteIndex('movies') + await meilisearchClient.waitForTask(deleteTask.taskUid) + await meilisearchClient + .index('movies') + .updateFilterableAttributes(['genres', 'release_date', 'id']) + const documentsTask = await meilisearchClient + .index('movies') + .addDocuments(dataset) + await meilisearchClient.index('movies').waitForTask(documentsTask.taskUid) + }) + + test('Facet stats on an empty facets array', async () => { + const response = await searchClient.search([ + { + indexName: 'movies', + params: { + query: '', + facets: [], + }, + }, + ]) + + expect(response.results[0].facets_stats?.release_date).toEqual(undefined) + }) + + test('Facet stats on a facet with no numeric values', async () => { + const response = await searchClient.search([ + { + indexName: 'movies', + params: { + query: '', + facets: ['genres'], + }, + }, + ]) + + expect(response.results[0].facets_stats?.genres).toEqual(undefined) + }) + + test('Facet stats on two facet', async () => { + const response = await searchClient.search([ + { + indexName: 'movies', + params: { + query: '', + facets: ['release_date', 'id'], + }, + }, + ]) + + expect(response.results[0].facets_stats?.release_date).toEqual({ + avg: 0, + max: 1065744000, + min: 233366400, + sum: 0, + }) + expect(response.results[0].facets_stats?.id).toEqual({ + avg: 0, + max: 30, + min: 2, + sum: 0, + }) + }) +}) diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/adapt-facet-stats.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/adapt-facet-stats.ts new file mode 100644 index 00000000..590ff816 --- /dev/null +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/adapt-facet-stats.ts @@ -0,0 +1,19 @@ +import { + AlgoliaSearchResponse, + MeiliFacetStats, + AlgoliaFacetStats, +} from '../../types' + +export function adaptFacetStats( + meiliFacetStats: MeiliFacetStats +): AlgoliaSearchResponse['facets_stats'] { + const facetStats = Object.keys(meiliFacetStats).reduce( + (stats: AlgoliaFacetStats, facet: string) => { + stats[facet] = { ...meiliFacetStats[facet], avg: 0, sum: 0 } // Set at 0 as these numbers are not provided by Meilisearch + + return stats + }, + {} as AlgoliaFacetStats + ) + return facetStats +} diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts index 24304c6c..1c097dac 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/search-response-adapter.ts @@ -8,6 +8,7 @@ import { adaptHits } from './hits-adapter' import { adaptTotalHits } from './total-hits-adapter' import { adaptPaginationParameters } from './pagination-adapter' import { adaptFacetDistribution } from './facet-distribution-adapter' +import { adaptFacetStats } from './adapt-facet-stats' /** * Adapt multiple search results from Meilisearch @@ -54,6 +55,7 @@ export function adaptSearchResult( query, indexUid, facetDistribution: responseFacetDistribution = {}, + facetStats = {}, } = meiliSearchResult const facets = Object.keys(responseFacetDistribution) @@ -86,6 +88,7 @@ export function adaptSearchResult( hits, params: '', exhaustiveNbHits: false, + facets_stats: adaptFacetStats(facetStats), } return adaptedSearchResult } diff --git a/packages/instant-meilisearch/src/types/types.ts b/packages/instant-meilisearch/src/types/types.ts index 7ac4d77e..0f13f307 100644 --- a/packages/instant-meilisearch/src/types/types.ts +++ b/packages/instant-meilisearch/src/types/types.ts @@ -15,7 +15,12 @@ export type { } export type { SearchResponse as AlgoliaSearchResponse } from '@algolia/client-search' -export type { Filter, FacetDistribution, MeiliSearch } from 'meilisearch' +export type { + Filter, + FacetDistribution, + MeiliSearch, + FacetStats as MeiliFacetStats, +} from 'meilisearch' export type InstantSearchParams = AlgoliaMultipleQueriesQuery['params'] @@ -109,3 +114,25 @@ export type MultiSearchResolver = { instantSearchPagination: PaginationState[] ) => Promise } + +export type AlgoliaFacetStats = Record< + string, + { + /** + * The minimum value in the result set. + */ + min: number + /** + * The maximum value in the result set. + */ + max: number + /** + * The average facet value in the result set. + */ + avg: number + /** + * The sum of all values in the result set. + */ + sum: number + } +> From 7f20d0b81f9c44fe5f7ead3212fad2d7cafb421d Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:13:16 +0200 Subject: [PATCH 5/7] Remove unecessary types (#1049) --- .../search-params-adapter.ts | 2 -- .../adapter/search-request-adapter/search.ts | 19 ---------------- .../__tests__/pagination-adapter.test.ts | 12 ++++++++-- .../pagination-adapter.ts | 6 ++--- .../total-hits-adapter.ts | 4 ++-- .../instant-meilisearch/src/types/types.ts | 22 +++++-------------- 6 files changed, 20 insertions(+), 45 deletions(-) delete mode 100644 packages/instant-meilisearch/src/adapter/search-request-adapter/search.ts diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts index b0d0981c..11bb7f06 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts @@ -68,8 +68,6 @@ function setFinitePagination( * to meilisearch search query parameters. * * @param {SearchContext} searchContext - * - * @returns {MeiliSearchParams} */ export function MeiliParamsCreator(searchContext: SearchContext) { const meiliSearchParams: any = {} diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/search.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/search.ts deleted file mode 100644 index 6dbc866b..00000000 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/search.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - SearchContext, - MeiliSearchParams, - MeiliSearchResponse, - MeiliSearch, -} from '../../types' -/** - * @param {MeiliSearch} client - * @param {SearchContext} context - * @param {MeiliSearchParams} searchRequest - * @returns {Promise} - */ -export async function search( - client: MeiliSearch, - context: SearchContext, - searchRequest: MeiliSearchParams -): Promise { - return client.index(context.indexUid).search(context.query, searchRequest) -} diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/__tests__/pagination-adapter.test.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/__tests__/pagination-adapter.test.ts index a5615116..f4c4b3a5 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/__tests__/pagination-adapter.test.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/__tests__/pagination-adapter.test.ts @@ -401,6 +401,7 @@ describe.each(finitePaginateHitsTestsParameters)( processingTimeMs: 0, query: '', totalPages, + indexUid: '', }, { hitsPerPage, page, finite: true } ) @@ -421,7 +422,7 @@ describe.each(lazyPaginateHitsTestsParameters)( adaptedPagination )} where limit is ${limit} in the response and where the instantsearch pagination context is page: ${page} and hitsPerPage: ${hitsPerPage}`, () => { const response = adaptPaginationParameters( - { hits, limit, offset, processingTimeMs: 0, query: '' }, + { hits, limit, offset, processingTimeMs: 0, query: '', indexUid: '' }, { hitsPerPage, page, finite: false } ) @@ -436,7 +437,14 @@ it('Should throw when hitsPerPage is negative', () => { const hitsPerPage = -1 const page = 0 adaptPaginationParameters( - { hits, page: page + 1, hitsPerPage, processingTimeMs: 0, query: '' }, + { + hits, + page: page + 1, + hitsPerPage, + processingTimeMs: 0, + query: '', + indexUid: '', + }, { hitsPerPage, page, finite: true } ) } catch (e: any) { diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts index f6f44845..e57af64e 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/pagination-adapter.ts @@ -1,11 +1,11 @@ import type { - MeiliSearchResponse, + MultiSearchResult, InstantSearchPagination, PaginationState, } from '../../types' function adaptNbPages( - searchResponse: MeiliSearchResponse>, + searchResponse: MultiSearchResult>, hitsPerPage: number ): number { if (searchResponse.totalPages != null) { @@ -23,7 +23,7 @@ function adaptNbPages( } export function adaptPaginationParameters( - searchResponse: MeiliSearchResponse>, + searchResponse: MultiSearchResult>, paginationState: PaginationState ): InstantSearchPagination & { nbPages: number } { const { hitsPerPage, page } = paginationState diff --git a/packages/instant-meilisearch/src/adapter/search-response-adapter/total-hits-adapter.ts b/packages/instant-meilisearch/src/adapter/search-response-adapter/total-hits-adapter.ts index b1a8ee2d..350a3919 100644 --- a/packages/instant-meilisearch/src/adapter/search-response-adapter/total-hits-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-response-adapter/total-hits-adapter.ts @@ -1,7 +1,7 @@ -import type { MeiliSearchResponse } from '../../types' +import type { MultiSearchResult } from '../../types' export function adaptTotalHits( - searchResponse: MeiliSearchResponse> + searchResponse: MultiSearchResult> ): number { const { hitsPerPage = 0, diff --git a/packages/instant-meilisearch/src/types/types.ts b/packages/instant-meilisearch/src/types/types.ts index 0f13f307..e5005bc5 100644 --- a/packages/instant-meilisearch/src/types/types.ts +++ b/packages/instant-meilisearch/src/types/types.ts @@ -2,17 +2,11 @@ import type { SearchClient } from 'instantsearch.js' import type { MultipleQueriesQuery as AlgoliaMultipleQueriesQuery } from '@algolia/client-search' import type { - SearchParams as MeiliSearchParams, - SearchResponse as MeiliSearchResponse, - MultiSearchResponse as MeilisearchMultiSearchResponse, + MultiSearchQuery as MeiliSearchMultiSearchParams, + MultiSearchResult, } from 'meilisearch' -export type { - AlgoliaMultipleQueriesQuery, - MeilisearchMultiSearchResponse, - MeiliSearchParams, - MeiliSearchResponse, -} +export type { AlgoliaMultipleQueriesQuery, MultiSearchResult } export type { SearchResponse as AlgoliaSearchResponse } from '@algolia/client-search' export type { @@ -20,6 +14,7 @@ export type { FacetDistribution, MeiliSearch, FacetStats as MeiliFacetStats, + MultiSearchQuery as MeiliSearchMultiSearchParams, } from 'meilisearch' export type InstantSearchParams = AlgoliaMultipleQueriesQuery['params'] @@ -79,15 +74,8 @@ export type InstantSearchPagination = { nbPages: number } -export type Facets = string | string[] | undefined - -export type MeiliSearchMultiSearchParams = MeiliSearchParams & { - indexUid: string -} - export type MeilisearchMultiSearchResult> = - MeiliSearchResponse & { - indexUid: string + MultiSearchResult & { pagination: PaginationState } From c6b3c09560ffa861195251b275442eb5b65a90a1 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:15:04 +0200 Subject: [PATCH 6/7] Use _geoBoundingBox when creating the geo search filter (#1048) * Use _geoBoundingBox to create geosearch filters * Add the _geoBoundingBox changeset --- .changeset/small-masks-tease.md | 5 + .../__tests__/geosearch.test.ts | 14 +-- .../__tests__/geo-rules.test.ts | 62 +++++----- .../__tests__/search-params.test.ts | 18 +-- .../geo-rules-adapter.ts | 116 +++++------------- .../search-params-adapter.ts | 30 +++-- .../instant-meilisearch/src/types/types.ts | 20 +-- .../src/utils/geographic.ts | 105 ---------------- 8 files changed, 115 insertions(+), 255 deletions(-) create mode 100644 .changeset/small-masks-tease.md delete mode 100644 packages/instant-meilisearch/src/utils/geographic.ts diff --git a/.changeset/small-masks-tease.md b/.changeset/small-masks-tease.md new file mode 100644 index 00000000..bbc16ca4 --- /dev/null +++ b/.changeset/small-masks-tease.md @@ -0,0 +1,5 @@ +--- +"@meilisearch/instant-meilisearch": patch +--- + +Use the `_geoBoundingBox` filter to adapt the `insideBoundingBox`parameter diff --git a/packages/instant-meilisearch/__tests__/geosearch.test.ts b/packages/instant-meilisearch/__tests__/geosearch.test.ts index 1c250dcb..76d8b233 100644 --- a/packages/instant-meilisearch/__tests__/geosearch.test.ts +++ b/packages/instant-meilisearch/__tests__/geosearch.test.ts @@ -51,7 +51,7 @@ describe('Instant Meilisearch Browser test', () => { ]) const hits = response.results[0].hits - expect(hits.length).toEqual(7) + expect(hits.length).toEqual(5) expect(hits[0].city).toEqual('Lille') }) @@ -68,8 +68,8 @@ describe('Instant Meilisearch Browser test', () => { ]) const hits = response.results[0].hits - expect(hits.length).toEqual(4) - expect(hits[0].city).toEqual('Ghent') + expect(hits.length).toEqual(2) + expect(hits[0].city).toEqual('Brussels') }) test('insideBoundingBox and aroundRadius in geo search', async () => { @@ -86,8 +86,8 @@ describe('Instant Meilisearch Browser test', () => { ]) const hits = response.results[0].hits - expect(hits.length).toEqual(4) - expect(hits[0].city).toEqual('Ghent') + expect(hits.length).toEqual(2) + expect(hits[0].city).toEqual('Brussels') }) test('insideBoundingBox and aroundLatLng in geo search', async () => { @@ -104,7 +104,7 @@ describe('Instant Meilisearch Browser test', () => { ]) const hits = response.results[0].hits - expect(hits.length).toEqual(4) - expect(hits[0].city).toEqual('Ghent') + expect(hits.length).toEqual(2) + expect(hits[0].city).toEqual('Brussels') }) }) diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/geo-rules.test.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/geo-rules.test.ts index b9a6e8de..2f0693b7 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/geo-rules.test.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/geo-rules.test.ts @@ -1,82 +1,82 @@ -import { adaptGeoPointsRules } from '../geo-rules-adapter' +import { adaptGeoSearch } from '../geo-rules-adapter' -test('Adapt geoPoints rules without a boundingBox', () => { - const rules = adaptGeoPointsRules() - expect(rules).toBeUndefined() +test('Adapt instantsearch geo parameters to meilisearch filters without a boundingBox', () => { + const filter = adaptGeoSearch({}) + expect(filter).toBeUndefined() }) -test('Adapt geoPoints rules with same 0 lat and 0 lng geo points', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with same 0 lat and 0 lng geo points', () => { + const filter = adaptGeoSearch({ insideBoundingBox: '0,0,0,0', }) - expect(rules?.filter).toBe('_geoRadius(0.00000, 0.00000, 0)') + expect(filter).toBe('_geoBoundingBox([0, 0], [0, 0])') }) -test('Adapt geoPoints rules with integer geo points', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with integer geo points', () => { + const filter = adaptGeoSearch({ insideBoundingBox: '1,2,3,4', }) - expect(rules?.filter).toBe('_geoRadius(3.17650, 3.19394, 157201.47551181243)') + expect(filter).toBe('_geoBoundingBox([1, 2], [3, 4])') }) -test('Try geoContext with only a radius', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with only a radius', () => { + const filter = adaptGeoSearch({ aroundRadius: 1, }) - expect(rules).toBeUndefined() + expect(filter).toBeUndefined() }) -test('Try geoContext with an aroundLatLng', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with only an aroundLatLng', () => { + const filter = adaptGeoSearch({ aroundLatLng: '51.1241999, 9.662499900000057', }) - expect(rules?.filter).toBeUndefined() + expect(filter).toBeUndefined() }) -test('Try geoContext with an aroundLatLng and a radius', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with an aroundLatLng and a radius', () => { + const filter = adaptGeoSearch({ aroundLatLng: '51.1241999, 9.662499900000057', aroundRadius: 1, }) - expect(rules?.filter).toBe('_geoRadius(51.12420, 9.66250, 1)') + expect(filter).toBe('_geoRadius(51.12420, 9.66250, 1)') }) -test('Try geoContext with an aroundLatLng and a 0 radius', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with an aroundLatLng and a 0 radius', () => { + const filter = adaptGeoSearch({ aroundLatLng: '51.1241999, 9.662499900000057', aroundRadius: 0, }) - expect(rules?.filter).toBe('_geoRadius(51.12420, 9.66250, 0)') + expect(filter).toBe('_geoRadius(51.12420, 9.66250, 0)') }) -test('Try geoContext with aroundLatLng, radius and insideBoundingBox', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with aroundLatLng, radius and insideBoundingBox', () => { + const filter = adaptGeoSearch({ aroundLatLng: '51.1241999, 9.662499900000057', aroundRadius: 1, insideBoundingBox: '1,2,3,4', }) - expect(rules?.filter).toBe('_geoRadius(3.17650, 3.19394, 157201.47551181243)') + expect(filter).toBe('_geoBoundingBox([1, 2], [3, 4])') }) -test('Try geoContext with a radius and insideBoundingBox', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with a radius and insideBoundingBox', () => { + const filter = adaptGeoSearch({ aroundRadius: 1, insideBoundingBox: '1,2,3,4', }) - expect(rules?.filter).toBe('_geoRadius(3.17650, 3.19394, 157201.47551181243)') + expect(filter).toBe('_geoBoundingBox([1, 2], [3, 4])') }) -test('Try geoContext with aroundLatLng and insideBoundingBox', () => { - const rules = adaptGeoPointsRules({ +test('Adapt instantsearch geo parameters to meilisearch filters with aroundLatLng and insideBoundingBox', () => { + const filter = adaptGeoSearch({ aroundLatLng: '51.1241999, 9.662499900000057', insideBoundingBox: '1,2,3,4', }) - expect(rules?.filter).toBe('_geoRadius(3.17650, 3.19394, 157201.47551181243)') + expect(filter).toBe('_geoBoundingBox([1, 2], [3, 4])') }) diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/search-params.test.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/search-params.test.ts index b042cdfa..66827d5d 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/search-params.test.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/__tests__/search-params.test.ts @@ -44,8 +44,8 @@ describe('Parameters adapter', () => { }) }) -describe('Geo rules adapter', () => { - test('adapting a searchContext with filters, sort and geo rules ', () => { +describe('Geo filter adapter', () => { + test('adapting a searchContext with filters, sort and geo filters ', () => { const searchParams = adaptSearchParams({ ...DEFAULT_CONTEXT, facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']], @@ -54,7 +54,7 @@ describe('Geo rules adapter', () => { }) expect(searchParams.filter).toStrictEqual([ - '_geoRadius(0.00000, 0.00000, 0)', + '_geoBoundingBox([0, 0], [0, 0])', ['genres="Drama"', 'genres="Thriller"'], ['title="Ariel"'], ]) @@ -63,7 +63,7 @@ describe('Geo rules adapter', () => { expect(searchParams.attributesToHighlight?.length).toBe(1) }) - test('adapting a searchContext with only facetFilters and geo rules ', () => { + test('adapting a searchContext with only facetFilters and geo filters ', () => { const searchParams = adaptSearchParams({ ...DEFAULT_CONTEXT, facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']], @@ -71,7 +71,7 @@ describe('Geo rules adapter', () => { }) expect(searchParams.filter).toEqual([ - '_geoRadius(0.00000, 0.00000, 0)', + '_geoBoundingBox([0, 0], [0, 0])', ['genres="Drama"', 'genres="Thriller"'], ['title="Ariel"'], ]) @@ -79,26 +79,26 @@ describe('Geo rules adapter', () => { expect(searchParams.attributesToHighlight?.length).toBe(1) }) - test('adapting a searchContext with only sort and geo rules ', () => { + test('adapting a searchContext with only sort and geo filters ', () => { const searchParams = adaptSearchParams({ ...DEFAULT_CONTEXT, insideBoundingBox: '0,0,0,0', sort: 'id < 1', }) - expect(searchParams.filter).toEqual(['_geoRadius(0.00000, 0.00000, 0)']) + expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])']) expect(searchParams.sort).toStrictEqual(['id < 1']) expect(searchParams.attributesToHighlight).toContain('*') expect(searchParams.attributesToHighlight?.length).toBe(1) }) - test('adapting a searchContext with no sort and no filters and geo rules ', () => { + test('adapting a searchContext with no sort and no filters and geo filters ', () => { const searchParams = adaptSearchParams({ ...DEFAULT_CONTEXT, insideBoundingBox: '0,0,0,0', }) - expect(searchParams.filter).toEqual(['_geoRadius(0.00000, 0.00000, 0)']) + expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])']) expect(searchParams.attributesToHighlight).toContain('*') expect(searchParams.attributesToHighlight?.length).toBe(1) }) diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/geo-rules-adapter.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/geo-rules-adapter.ts index 2015d90b..b8f20088 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/geo-rules-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/geo-rules-adapter.ts @@ -1,94 +1,46 @@ -import { SearchContext, GeoSearchContext } from '../../types' -import { getDistanceInMeter, middleGeoPoints } from '../../utils/geographic' - -export function adaptGeoPointsRules( - geoSearchContext?: GeoSearchContext -): { filter?: string; sort?: string } | undefined { - if (!geoSearchContext) { - return undefined - } - const { insideBoundingBox, aroundLatLng, aroundRadius, minimumAroundRadius } = - geoSearchContext - - let middlePoint - let radius +import { InstantSearchGeoParams } from '../../types' + +export function adaptGeoSearch({ + insideBoundingBox, + aroundLatLng, + aroundRadius, + minimumAroundRadius, +}: InstantSearchGeoParams): string | undefined { + let middlePoint: string[] | undefined + let radius: number | undefined + let filter: string | undefined if (aroundLatLng) { - middlePoint = aroundLatLng - } - if (aroundRadius != null || minimumAroundRadius != null) { - if (aroundRadius != null) radius = aroundRadius - else radius = minimumAroundRadius - } - - // If insideBoundingBox is provided it takes precedent over all other options - if (insideBoundingBox && typeof insideBoundingBox === 'string') { - const [lat1Raw, lng1Raw, lat2Raw, lng2Raw] = insideBoundingBox.split(',') - - const [lat1, lng1, lat2, lng2] = [ - parseFloat(lat1Raw), - parseFloat(lng1Raw), - parseFloat(lat2Raw), - parseFloat(lng2Raw), - ] - radius = getDistanceInMeter(lat1, lng1, lat2, lng2) / 2 - middlePoint = middleGeoPoints(lat1, lng1, lat2, lng2) - } - - if (middlePoint != null && radius != null) { - let [lat3, lng3] = middlePoint.split(',') - lat3 = Number.parseFloat(lat3).toFixed(5) - lng3 = Number.parseFloat(lng3).toFixed(5) - const filter = `_geoRadius(${lat3}, ${lng3}, ${radius})` + const [lat, lng] = aroundLatLng + .split(',') + .map((pt) => Number.parseFloat(pt).toFixed(5)) - return { filter } - } - return undefined -} - -export function createGeoSearchContext( - searchContext: SearchContext -): GeoSearchContext { - const geoContext: Record = {} - const { - aroundLatLng, - aroundLatLngViaIP, - aroundRadius, - aroundPrecision, - minimumAroundRadius, - insideBoundingBox, - insidePolygon, - } = searchContext - - if (aroundLatLng) { - geoContext.aroundLatLng = aroundLatLng + middlePoint = [lat, lng] } - if (aroundLatLngViaIP) { - console.warn('instant-meilisearch: `aroundLatLngViaIP` is not supported.') + if (aroundRadius != null || minimumAroundRadius != null) { + if (aroundRadius === 'all') { + console.warn( + 'instant-meilisearch is not compatible with the `all` value on the aroundRadius parameter' + ) + } else if (aroundRadius != null) { + radius = aroundRadius + } else { + radius = minimumAroundRadius + } } - if (aroundRadius) { - geoContext.aroundRadius = aroundRadius - } + if (insideBoundingBox && typeof insideBoundingBox === 'string') { + const [lat1, lng1, lat2, lng2] = insideBoundingBox + .split(',') + .map((pt) => parseFloat(pt)) - if (aroundPrecision) { - console.warn(`instant-meilisearch: \`aroundPrecision\` is not supported. - See this discussion to track its implementation https://github.com/meilisearch/product/discussions/264`) - } + filter = `_geoBoundingBox([${lat1}, ${lng1}], [${lat2}, ${lng2}])` + } else if (middlePoint != null && radius != null) { + const [lat, lng] = middlePoint - if (minimumAroundRadius) { - geoContext.minimumAroundRadius = minimumAroundRadius + filter = `_geoRadius(${lat}, ${lng}, ${radius})` } - if (insideBoundingBox) { - geoContext.insideBoundingBox = insideBoundingBox - } - // See related issue: https://github.com/meilisearch/instant-meilisearch/issues/555 - if (insidePolygon) { - console.warn( - `instant-meilisearch: \`insidePolygon\` is not implented in instant-meilisearch.` - ) - } - return geoContext + return filter } diff --git a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts index 11bb7f06..ea2d27ba 100644 --- a/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/packages/instant-meilisearch/src/adapter/search-request-adapter/search-params-adapter.ts @@ -5,10 +5,7 @@ import type { MeiliSearchMultiSearchParams, } from '../../types' -import { - adaptGeoPointsRules, - createGeoSearchContext, -} from './geo-rules-adapter' +import { adaptGeoSearch } from './geo-rules-adapter' import { adaptFilters } from './filter-adapter' function isPaginationRequired( @@ -174,15 +171,26 @@ export function MeiliParamsCreator(searchContext: SearchContext) { meiliSearchParams.sort = Array.isArray(sort) ? sort : [sort] } }, - addGeoSearchRules() { - const geoSearchContext = createGeoSearchContext(searchContext) - const geoRules = adaptGeoPointsRules(geoSearchContext) + addGeoSearchFilter() { + const { + insideBoundingBox, + aroundLatLng, + aroundRadius, + minimumAroundRadius, + } = searchContext - if (geoRules?.filter) { + const filter = adaptGeoSearch({ + insideBoundingBox, + aroundLatLng, + aroundRadius, + minimumAroundRadius, + }) + + if (filter) { if (meiliSearchParams.filter) { - meiliSearchParams.filter.unshift(geoRules.filter) + meiliSearchParams.filter.unshift(filter) } else { - meiliSearchParams.filter = [geoRules.filter] + meiliSearchParams.filter = [filter] } } }, @@ -217,7 +225,7 @@ export function adaptSearchParams( meilisearchParams.addPagination() meilisearchParams.addFilters() meilisearchParams.addSort() - meilisearchParams.addGeoSearchRules() + meilisearchParams.addGeoSearchFilter() meilisearchParams.addMatchingStrategy() return meilisearchParams.getParams() diff --git a/packages/instant-meilisearch/src/types/types.ts b/packages/instant-meilisearch/src/types/types.ts index e5005bc5..247e2004 100644 --- a/packages/instant-meilisearch/src/types/types.ts +++ b/packages/instant-meilisearch/src/types/types.ts @@ -51,16 +51,6 @@ export type SearchCacheInterface = { export type InsideBoundingBox = string | ReadonlyArray -export type GeoSearchContext = { - aroundLatLng?: string - aroundLatLngViaIP?: boolean - aroundRadius?: number | 'all' - aroundPrecision?: number - minimumAroundRadius?: number - insideBoundingBox?: InsideBoundingBox - insidePolygon?: ReadonlyArray -} - // Current state of the pagination export type PaginationState = { finite: boolean @@ -92,6 +82,16 @@ export type SearchContext = Omit & matchingStrategy?: MatchingStrategies } +export type InstantSearchGeoParams = { + aroundLatLng?: string + aroundLatLngViaIP?: boolean + aroundRadius?: number | 'all' + aroundPrecision?: number + minimumAroundRadius?: number + insideBoundingBox?: InsideBoundingBox + insidePolygon?: ReadonlyArray +} + export type InstantMeiliSearchInstance = SearchClient & { clearCache: () => void } diff --git a/packages/instant-meilisearch/src/utils/geographic.ts b/packages/instant-meilisearch/src/utils/geographic.ts deleted file mode 100644 index b27ba2df..00000000 --- a/packages/instant-meilisearch/src/utils/geographic.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @param {number} rad - * @returns {number} - */ -function rad2degr(rad: number): number { - return (rad * 180) / Math.PI -} - -/** - * @param {number} degr - * @returns {number} - */ -function degr2rad(degr: number): number { - return (degr * Math.PI) / 180 -} - -/** - * @param {number} lat1 - * @param {number} lng1 - * @param {number} lat2 - * @param {number} lng2 - * @returns {string} - */ -export function middleGeoPoints( - lat1: number, - lng1: number, - lat2: number, - lng2: number -): string { - // convert to radians - lat1 = degr2rad(lat1) - lng1 = degr2rad(lng1) - - const x1 = Math.cos(lat1) * Math.cos(lng1) - const y1 = Math.cos(lat1) * Math.sin(lng1) - const z1 = Math.sin(lat1) - - // convert to radians - lat2 = degr2rad(lat2) - lng2 = degr2rad(lng2) - - const x2 = Math.cos(lat2) * Math.cos(lng2) - const y2 = Math.cos(lat2) * Math.sin(lng2) - const z2 = Math.sin(lat2) - - const x = x1 + x2 - const y = y1 + y2 - const z = z1 + z2 - - const Hyp = Math.sqrt(x * x + y * y) - let lng3 = Math.atan2(y, x) - let lat3 = Math.atan2(z, Hyp) - - if (lng1 < lng2 || (lng1 > lng2 && lng1 > Math.PI && lng2 < -Math.PI)) { - lat3 = lat3 + Math.PI - lng3 = lng3 + Math.PI - } else { - lat3 = rad2degr(lat3) - lng3 = rad2degr(lng3) - } - - if ( - Math.abs(x) < Math.pow(10, -9) && - Math.abs(y) < Math.pow(10, -9) && - Math.abs(z) < Math.pow(10, -9) - ) { - lat3 = 0 - lng3 = 0 - } - - return `${lat3},${lng3}` -} -/** - * @param {number} lat1 - * @param {number} lng1 - * @param {number} lat2 - * @param {number} lng2 - * @returns {number} - */ -export function getDistanceInMeter( - lat1: number, - lng1: number, - lat2: number, - lng2: number -): number { - // Haversine Algorithm - const R = 6371e3 // metres - const latRad1 = (lat1 * Math.PI) / 180 - const latRad2 = (lat2 * Math.PI) / 180 - const latCenterRad = ((lat2 - lat1) * Math.PI) / 180 - const lngCenterRad = ((lng2 - lng1) * Math.PI) / 180 - - const a = - Math.sin(latCenterRad / 2) * Math.sin(latCenterRad / 2) + - Math.cos(latRad1) * - Math.cos(latRad2) * - Math.sin(lngCenterRad / 2) * - Math.sin(lngCenterRad / 2) - - const bearing = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) - - const distance = R * bearing // in metres - - return distance -} From c4abafbc4b61fd378a3e09c20e80200c1a59da18 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Mon, 3 Apr 2023 12:25:23 +0200 Subject: [PATCH 7/7] Update version of meilisearch-js to v0.32.1 --- packages/instant-meilisearch/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/instant-meilisearch/package.json b/packages/instant-meilisearch/package.json index 1e5d5ed1..5a67e5fd 100644 --- a/packages/instant-meilisearch/package.json +++ b/packages/instant-meilisearch/package.json @@ -47,7 +47,7 @@ "templates" ], "dependencies": { - "meilisearch": "0.32.0-v1.1.0-pre-release.2" + "meilisearch": "^0.32.1" }, "devDependencies": { "@babel/cli": "^7.21.0", diff --git a/yarn.lock b/yarn.lock index 7ba28a8a..8e49268a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9707,10 +9707,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -meilisearch@0.32.0-v1.1.0-pre-release.2: - version "0.32.0-v1.1.0-pre-release.2" - resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.32.0-v1.1.0-pre-release.2.tgz#f1ab76492dd23ed7d09137dafa7cc61ca25c1591" - integrity sha512-w7dyKWIEPkPUvYUv9QCm90flxgSz4W3YmTf4p67vJgON8IhUGCqhqXJ5NF39HbrppsRil0nccsYGTDu9tURlWQ== +meilisearch@^0.32.1: + version "0.32.1" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.32.1.tgz#3b09a3d2a5d70949c38f410a53c22f0136d4e8f6" + integrity sha512-ceC0TSKL7GF91b50y1Qb2lKUUF/d5uZ242Iry093cTDRO3KfL/lcSwXxNxKzluk71iBPH0A0cKAIy6v06sPriA== dependencies: cross-fetch "^3.1.5"