From 972c8d182bdc6e65369c3b069e2b95617266372a Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Wed, 20 Jul 2022 17:51:43 +0200 Subject: [PATCH 01/10] Add beta pagination feature --- README.md | 32 ------------ playgrounds/react/src/App.js | 1 - .../search-params-adapter.ts | 20 ++------ .../search-request-adapter/search-resolver.ts | 17 ++----- .../search-response-adapter/hits-adapter.ts | 10 ++-- .../pagination-adapter.ts | 51 +++++++++++++++++++ .../search-response-adapter.ts | 22 ++++---- .../total-hits-adapter.ts | 18 +++++++ src/client/instant-meilisearch-client.ts | 2 +- src/contexts/pagination-context.ts | 3 -- src/contexts/search-context.ts | 2 - src/types/types.ts | 8 +-- tests/env/react/src/App.js | 5 +- 13 files changed, 95 insertions(+), 96 deletions(-) create mode 100644 src/adapter/search-response-adapter/total-hits-adapter.ts diff --git a/README.md b/README.md index 415a1092..faf58aa8 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,6 @@ const searchClient = instantMeiliSearch( `instant-meilisearch` offers some options you can set to further fit your needs. - [`placeholderSearch`](#placeholder-search): Enable or disable placeholder search (default: `true`). -- [`paginationTotalHits`](#pagination-total-hits): Maximum total number of hits to create a finite pagination (default: `200`). -- [`finitePagination`](#finite-pagination): Used to work with the [`pagination`](#-pagination) widget (default: `false`) . - [`primaryKey`](#primary-key): Specify the primary key of your documents (default `undefined`). - [`keepZeroFacets`](#keep-zero-facets): Show the facets value even when they have 0 matches (default `false`). @@ -93,7 +91,6 @@ const searchClient = instantMeiliSearch( 'https://integration-demos.meilisearch.com', '99d1e034ed32eb569f9edc27962cccf90b736e4c5a70f7f5e76b9fab54d6a185', { - paginationTotalHits: 30, // default: 200. placeholderSearch: false, // default: true. primaryKey: 'id', // default: undefined // ... @@ -110,35 +107,6 @@ When placeholder search is set to `false`, no results appears when searching on { placeholderSearch : true } // default true ``` -### Pagination total hits - -The total (and finite) number of hits (default: `200`) you can browse during pagination when using the [pagination widget](https://www.algolia.com/doc/api-reference/widgets/pagination/js/) or the [`infiniteHits` widget](#-infinitehits). If none of these widgets are used, `paginationTotalHits` is ignored.
- -For example, using the `infiniteHits` widget, and a `paginationTotalHits` of 9. On the first search request 6 hits are shown, by clicking a second time on `load more` only 3 more hits are added. This is because `paginationTotalHits` is `9`. - -Usage: - -```js -{ paginationTotalHits: 50 } // default: 200 -``` - -`hitsPerPage` has a value of `20` by default and can [be customized](#-hitsperpage). - -### Finite Pagination - -Finite pagination is used when you want to add a numbered pagination at the bottom of your hits (for example: `< << 1, 2, 3 > >>`). -To be able to know the amount of page numbers you have, a search is done requesting `paginationTotalHits` documents (default: `200`). -With the amount of documents returned, instantsearch is able to render the correct amount of numbers in the pagination widget. - -Example: - -```js -{ finitePagination: true } // default: false -``` - -⚠️ Meilisearch is not designed for pagination and this can lead to performances issues, so the usage `finitePagination` but also of the pagination widgets are not recommended.
-More information about Meilisearch and the pagination [here](https://github.com/meilisearch/documentation/issues/561). - ### Primary key Specify the field in your documents containing the [unique identifier](https://docs.meilisearch.com/learn/core_concepts/documents.html#primary-field) (`undefined` by default). By adding this option, we avoid instantSearch errors that are thrown in the browser console. In `React` particularly, this option removes the `Each child in a list should have a unique "key" prop` error. diff --git a/playgrounds/react/src/App.js b/playgrounds/react/src/App.js index d8936b8a..6e84b522 100644 --- a/playgrounds/react/src/App.js +++ b/playgrounds/react/src/App.js @@ -20,7 +20,6 @@ const searchClient = instantMeiliSearch( 'https://integration-demos.meilisearch.com', '99d1e034ed32eb569f9edc27962cccf90b736e4c5a70f7f5e76b9fab54d6a185', { - paginationTotalHits: 60, primaryKey: 'id', } ) diff --git a/src/adapter/search-request-adapter/search-params-adapter.ts b/src/adapter/search-request-adapter/search-params-adapter.ts index 95a8cd80..fcfb97ff 100644 --- a/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/src/adapter/search-request-adapter/search-params-adapter.ts @@ -85,23 +85,13 @@ export function adaptSearchParams( // Pagination const { pagination } = searchContext + // TODO: remove finitePagination and paginationTotalHits // Limit based on pagination preferences - if ( - (!placeholderSearch && query === '') || - pagination.paginationTotalHits === 0 - ) { - meiliSearchParams.limit = 0 - } else if (searchContext.finitePagination) { - meiliSearchParams.limit = pagination.paginationTotalHits + if (!placeholderSearch && query === '') { + meiliSearchParams.hitsPerPage = 0 } else { - const limit = (pagination.page + 1) * pagination.hitsPerPage + 1 - // If the limit is bigger than the total hits accepted - // force the limit to that amount - if (limit > pagination.paginationTotalHits) { - meiliSearchParams.limit = pagination.paginationTotalHits - } else { - meiliSearchParams.limit = limit - } + meiliSearchParams.page = pagination.page + 1 + meiliSearchParams.hitsPerPage = pagination.hitsPerPage } const sort = searchContext.sort diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index 84cb168d..37d21640 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -11,9 +11,10 @@ const emptySearch: MeiliSearchResponse> = { hits: [], query: '', facetDistribution: {}, - limit: 0, - offset: 0, - estimatedTotalHits: 0, + page: 0, + hitsPerPage: 0, + totalPages: 0, + totalHits: 0, processingTimeMs: 0, } @@ -41,20 +42,11 @@ export function SearchResolver(cache: SearchCacheInterface) { return emptySearch } - const { pagination } = searchContext - - // In case we are in a `finitePagination`, only one big request is made - // containing a total of max the paginationTotalHits (default: 200). - // Thus we dont want the pagination to impact the cache as every - // hits are already cached. - const paginationCache = searchContext.finitePagination ? {} : pagination - // Create cache key containing a unique set of search parameters const key = cache.formatKey([ searchParams, searchContext.indexUid, searchContext.query, - paginationCache, ]) const cachedResponse = cache.getEntry(key) @@ -62,6 +54,7 @@ export function SearchResolver(cache: SearchCacheInterface) { if (cachedResponse) return cachedResponse const facetsCache = extractFacets(searchContext, searchParams) + console.log({ searchParams }) // Make search request const searchResponse = await client diff --git a/src/adapter/search-response-adapter/hits-adapter.ts b/src/adapter/search-response-adapter/hits-adapter.ts index dae04ff8..79b2b77d 100644 --- a/src/adapter/search-response-adapter/hits-adapter.ts +++ b/src/adapter/search-response-adapter/hits-adapter.ts @@ -1,5 +1,4 @@ -import type { PaginationContext, SearchContext } from '../../types' -import { adaptPagination } from './pagination-adapter' +import type { SearchContext } from '../../types' import { adaptFormattedFields } from './format-adapter' import { adaptGeoResponse } from './geo-reponse-adapter' @@ -11,14 +10,11 @@ import { adaptGeoResponse } from './geo-reponse-adapter' */ export function adaptHits( hits: Array>, - searchContext: SearchContext, - paginationContext: PaginationContext + searchContext: SearchContext ): any { const { primaryKey } = searchContext - const { hitsPerPage, page } = paginationContext - const paginatedHits = adaptPagination(hits, page, hitsPerPage) - let adaptedHits = paginatedHits.map((hit: Record) => { + let adaptedHits = hits.map((hit: Record) => { // Creates Hit object compliant with InstantSearch if (Object.keys(hit).length > 0) { const { diff --git a/src/adapter/search-response-adapter/pagination-adapter.ts b/src/adapter/search-response-adapter/pagination-adapter.ts index ddf69df7..d0dfbc5f 100644 --- a/src/adapter/search-response-adapter/pagination-adapter.ts +++ b/src/adapter/search-response-adapter/pagination-adapter.ts @@ -1,3 +1,6 @@ +import type { MeiliSearchResponse, PaginationContext } from '../../types' +import { ceiledDivision } from '../../utils' + /** * Slice the requested hits based on the pagination position. * @@ -19,3 +22,51 @@ export function adaptPagination( const start = page * hitsPerPage return hits.slice(start, start + hitsPerPage) } + +// export function adaptNbPages(): number {} +function adaptHitsPerPage( + searchResponse: MeiliSearchResponse>, + paginationContext: PaginationContext +): number { + if (searchResponse.hitsPerPage) { + return searchResponse.hitsPerPage + } + + return paginationContext.hitsPerPage +} + +function adaptNbPages( + searchResponse: MeiliSearchResponse>, + hitsPerPage: number +): number { + if (searchResponse.totalPages) { + return searchResponse.totalPages + } + + return ceiledDivision(searchResponse.hits.length, hitsPerPage) +} + +function adaptPage( + searchResponse: MeiliSearchResponse>, + paginationContext: PaginationContext +): number { + if (searchResponse.page) { + return searchResponse.page - 1 + } + + return paginationContext.page +} + +export function adaptPaginationContext( + searchResponse: MeiliSearchResponse>, + paginationContext: PaginationContext +): PaginationContext & { nbPages: number } { + const hitsPerPage = adaptHitsPerPage(searchResponse, paginationContext) + const nbPages = adaptNbPages(searchResponse, hitsPerPage) + const page = adaptPage(searchResponse, paginationContext) + return { + hitsPerPage, + page, + nbPages, + } +} diff --git a/src/adapter/search-response-adapter/search-response-adapter.ts b/src/adapter/search-response-adapter/search-response-adapter.ts index 57a25524..423a43d9 100644 --- a/src/adapter/search-response-adapter/search-response-adapter.ts +++ b/src/adapter/search-response-adapter/search-response-adapter.ts @@ -3,8 +3,9 @@ import type { MeiliSearchResponse, AlgoliaSearchResponse, } from '../../types' -import { ceiledDivision } from '../../utils' import { adaptHits } from './hits-adapter' +import { adaptTotalHits } from './total-hits-adapter' +import { adaptPaginationContext } from './pagination-adapter' /** * Adapt search response from Meilisearch @@ -20,21 +21,16 @@ export function adaptSearchResponse( searchContext: SearchContext ): { results: Array> } { const searchResponseOptionals: Record = {} + const { processingTimeMs, query, facetDistribution: facets } = searchResponse - const facets = searchResponse.facetDistribution - const { pagination } = searchContext - - const nbPages = ceiledDivision( - searchResponse.hits.length, - pagination.hitsPerPage + const { hitsPerPage, page, nbPages } = adaptPaginationContext( + searchResponse, + searchContext.pagination ) - const hits = adaptHits(searchResponse.hits, searchContext, pagination) - const estimatedTotalHits = searchResponse.estimatedTotalHits - const processingTimeMs = searchResponse.processingTimeMs - const query = searchResponse.query + const hits = adaptHits(searchResponse.hits, searchContext) - const { hitsPerPage, page } = pagination + const nbHits = adaptTotalHits(searchResponse) // Create response object compliant with InstantSearch const adaptedSearchResponse = { @@ -43,7 +39,7 @@ export function adaptSearchResponse( page, facets, nbPages, - nbHits: estimatedTotalHits, + nbHits, processingTimeMS: processingTimeMs, query, hits, diff --git a/src/adapter/search-response-adapter/total-hits-adapter.ts b/src/adapter/search-response-adapter/total-hits-adapter.ts new file mode 100644 index 00000000..b2768844 --- /dev/null +++ b/src/adapter/search-response-adapter/total-hits-adapter.ts @@ -0,0 +1,18 @@ +import type { MeiliSearchResponse } from '../../types' + +export function adaptTotalHits( + searchResponse: MeiliSearchResponse> +): number { + const { + hitsPerPage = 0, + totalPages = 0, + estimatedTotalHits = undefined, + totalHits = undefined, + } = searchResponse + if (estimatedTotalHits !== undefined) { + return estimatedTotalHits + } else if (totalHits !== undefined) { + return totalHits + } + return hitsPerPage * totalPages +} diff --git a/src/client/instant-meilisearch-client.ts b/src/client/instant-meilisearch-client.ts index 9caf65a3..fd53b1c6 100644 --- a/src/client/instant-meilisearch-client.ts +++ b/src/client/instant-meilisearch-client.ts @@ -30,7 +30,7 @@ export function instantMeiliSearch( ): InstantMeiliSearchInstance { // create search resolver with included cache const searchResolver = SearchResolver(SearchCache()) - // paginationTotalHits can be 0 as it is a valid number + let defaultFacetDistribution: any = {} const clientAgents = constructClientAgents( instantMeiliSearchOptions.clientAgents diff --git a/src/contexts/pagination-context.ts b/src/contexts/pagination-context.ts index c6d6b32c..c1966277 100644 --- a/src/contexts/pagination-context.ts +++ b/src/contexts/pagination-context.ts @@ -6,14 +6,11 @@ import { PaginationContext, PaginationParams } from '../types' * @returns {SearchContext} */ export function createPaginationContext({ - paginationTotalHits, hitsPerPage, page, }: PaginationParams): // searchContext: SearchContext PaginationContext { return { - paginationTotalHits: - paginationTotalHits != null ? paginationTotalHits : 200, hitsPerPage: hitsPerPage === undefined ? 20 : hitsPerPage, // 20 is the Meilisearch's default limit value. `hitsPerPage` can be changed with `InsantSearch.configure`. page: page || 0, // default page is 0 if none is provided } diff --git a/src/contexts/search-context.ts b/src/contexts/search-context.ts index 3577e55a..a9ae1a87 100644 --- a/src/contexts/search-context.ts +++ b/src/contexts/search-context.ts @@ -22,7 +22,6 @@ export function createSearchContext( const { params: instantSearchParams } = searchRequest const pagination = createPaginationContext({ - paginationTotalHits: options.paginationTotalHits, hitsPerPage: instantSearchParams?.hitsPerPage, // 20 by default page: instantSearchParams?.page, }) @@ -36,7 +35,6 @@ export function createSearchContext( defaultFacetDistribution, placeholderSearch: options.placeholderSearch !== false, // true by default keepZeroFacets: !!options.keepZeroFacets, // false by default - finitePagination: !!options.finitePagination, // false by default } return searchContext } diff --git a/src/types/types.ts b/src/types/types.ts index 8b94757c..0b4b015f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -28,11 +28,9 @@ export type ParsedFilter = { } export type InstantMeiliSearchOptions = { - paginationTotalHits?: number placeholderSearch?: boolean primaryKey?: string keepZeroFacets?: boolean - finitePagination?: boolean clientAgents?: string[] } @@ -49,8 +47,6 @@ type ClientParams = { placeholderSearch?: boolean sort?: string indexUid: string - paginationTotalHits: number - finitePagination: boolean } export type GeoSearchContext = { @@ -64,20 +60,18 @@ export type GeoSearchContext = { } export type PaginationContext = { - paginationTotalHits: number hitsPerPage: number page: number } export type PaginationParams = { - paginationTotalHits?: number hitsPerPage?: number page?: number } export type SearchContext = Omit< InstantSearchParams & ClientParams, - 'insideBoundingBox' | 'paginationTotalHits' + 'insideBoundingBox' > & { insideBoundingBox?: InsideBoundingBox keepZeroFacets?: boolean diff --git a/tests/env/react/src/App.js b/tests/env/react/src/App.js index e992c6f7..935c7c94 100644 --- a/tests/env/react/src/App.js +++ b/tests/env/react/src/App.js @@ -17,7 +17,6 @@ import './App.css' import { instantMeiliSearch } from '../../../../src/index' const searchClient = instantMeiliSearch('http://localhost:7700', 'masterKey', { - paginationTotalHits: 60, primaryKey: 'id', }) @@ -55,13 +54,13 @@ const App = () => (

Genres

Players

- +

Platforms

Misc

From e134ee9db2201d4d33d649c4944d704eeaa34008 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Thu, 21 Jul 2022 16:02:45 +0200 Subject: [PATCH 02/10] Update tests --- .../__tests__/search-params.tests.ts | 114 ++---------------- .../search-params-adapter.ts | 2 +- .../search-request-adapter/search-resolver.ts | 1 - tests/pagination.tests.ts | 105 ---------------- tests/placeholder-search.tests.ts | 17 +-- 5 files changed, 18 insertions(+), 221 deletions(-) diff --git a/src/adapter/search-request-adapter/__tests__/search-params.tests.ts b/src/adapter/search-request-adapter/__tests__/search-params.tests.ts index 280a7f1f..4231830b 100644 --- a/src/adapter/search-request-adapter/__tests__/search-params.tests.ts +++ b/src/adapter/search-request-adapter/__tests__/search-params.tests.ts @@ -3,9 +3,8 @@ import { adaptSearchParams } from '../search-params-adapter' test('Adapt basic SearchContext ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.attributesToHighlight).toContain('*') expect(searchParams.attributesToHighlight?.length).toBe(1) @@ -14,11 +13,10 @@ test('Adapt basic SearchContext ', () => { test('Adapt SearchContext with filters, sort and no geo rules ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']], sort: 'id < 1', defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.filter).toStrictEqual([ @@ -33,12 +31,11 @@ test('Adapt SearchContext with filters, sort and no geo rules ', () => { test('Adapt SearchContext with filters, sort and geo rules ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']], insideBoundingBox: '0,0,0,0', sort: 'id < 1', defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.filter).toStrictEqual([ @@ -54,11 +51,10 @@ test('Adapt SearchContext with filters, sort and geo rules ', () => { test('Adapt SearchContext with only facetFilters and geo rules ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']], insideBoundingBox: '0,0,0,0', defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.filter).toEqual([ @@ -73,11 +69,10 @@ test('Adapt SearchContext with only facetFilters and geo rules ', () => { test('Adapt SearchContext with only sort and geo rules ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, insideBoundingBox: '0,0,0,0', sort: 'id < 1', defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.filter).toEqual(['_geoRadius(0.00000, 0.00000, 0)']) @@ -89,10 +84,9 @@ test('Adapt SearchContext with only sort and geo rules ', () => { test('Adapt SearchContext with no sort and no filters and geo rules ', () => { const searchParams = adaptSearchParams({ indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, insideBoundingBox: '0,0,0,0', defaultFacetDistribution: {}, - finitePagination: false, }) expect(searchParams.filter).toEqual(['_geoRadius(0.00000, 0.00000, 0)']) @@ -100,112 +94,28 @@ test('Adapt SearchContext with no sort and no filters and geo rules ', () => { expect(searchParams.attributesToHighlight?.length).toBe(1) }) -test('Adapt SearchContext with finite pagination', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: true, - }) - - expect(searchParams.limit).toBe(20) -}) - -test('Adapt SearchContext with finite pagination on a later page', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 10, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: true, - }) - - expect(searchParams.limit).toBe(20) -}) - -test('Adapt SearchContext with finite pagination and pagination total hits lower than hitsPerPage', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 4, page: 0, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: true, - }) - - expect(searchParams.limit).toBe(4) -}) - -test('Adapt SearchContext with no finite pagination', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 0, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: false, - }) - - expect(searchParams.limit).toBe(7) -}) - -test('Adapt SearchContext with no finite pagination on page 2', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 1, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: false, - }) - - expect(searchParams.limit).toBe(13) -}) - -test('Adapt SearchContext with no finite pagination on page higher than paginationTotalHits', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 20, page: 40, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: false, - }) - - expect(searchParams.limit).toBe(20) -}) - -test('Adapt SearchContext with no finite pagination and pagination total hits lower than hitsPerPage', () => { - const searchParams = adaptSearchParams({ - indexUid: 'test', - pagination: { paginationTotalHits: 4, page: 0, hitsPerPage: 6 }, - insideBoundingBox: '0,0,0,0', - defaultFacetDistribution: {}, - finitePagination: false, - }) - - expect(searchParams.limit).toBe(4) -}) - test('Adapt SearchContext placeholderSearch set to false', () => { const searchParams = adaptSearchParams({ indexUid: 'test', query: '', - pagination: { paginationTotalHits: 4, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, defaultFacetDistribution: {}, - finitePagination: false, placeholderSearch: false, }) - expect(searchParams.limit).toBe(0) + expect(searchParams.page).toBe(1) + expect(searchParams.hitsPerPage).toBe(0) }) test('Adapt SearchContext placeholderSearch set to false', () => { const searchParams = adaptSearchParams({ indexUid: 'test', query: '', - pagination: { paginationTotalHits: 200, page: 0, hitsPerPage: 6 }, + pagination: { page: 0, hitsPerPage: 6 }, defaultFacetDistribution: {}, - finitePagination: false, placeholderSearch: true, }) - expect(searchParams.limit).toBe(7) + expect(searchParams.page).toBe(1) + expect(searchParams.hitsPerPage).toBe(6) }) diff --git a/src/adapter/search-request-adapter/search-params-adapter.ts b/src/adapter/search-request-adapter/search-params-adapter.ts index fcfb97ff..ef73adfb 100644 --- a/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/src/adapter/search-request-adapter/search-params-adapter.ts @@ -85,10 +85,10 @@ export function adaptSearchParams( // Pagination const { pagination } = searchContext - // TODO: remove finitePagination and paginationTotalHits // Limit based on pagination preferences if (!placeholderSearch && query === '') { meiliSearchParams.hitsPerPage = 0 + meiliSearchParams.page = pagination.page + 1 } else { meiliSearchParams.page = pagination.page + 1 meiliSearchParams.hitsPerPage = pagination.hitsPerPage diff --git a/src/adapter/search-request-adapter/search-resolver.ts b/src/adapter/search-request-adapter/search-resolver.ts index 37d21640..2197f587 100644 --- a/src/adapter/search-request-adapter/search-resolver.ts +++ b/src/adapter/search-request-adapter/search-resolver.ts @@ -54,7 +54,6 @@ export function SearchResolver(cache: SearchCacheInterface) { if (cachedResponse) return cachedResponse const facetsCache = extractFacets(searchContext, searchParams) - console.log({ searchParams }) // Make search request const searchResponse = await client diff --git a/tests/pagination.tests.ts b/tests/pagination.tests.ts index d6377017..71054b83 100644 --- a/tests/pagination.tests.ts +++ b/tests/pagination.tests.ts @@ -1,4 +1,3 @@ -import { instantMeiliSearch } from '../src' import { searchClient, dataset, @@ -95,108 +94,4 @@ describe('Pagination browser test', () => { expect(hits.length).toBe(0) expect(hits).toEqual([]) }) - - test('Test pagination total hits ', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 1, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(1) - }) - - test('Test zero pagination total hits ', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 0, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(0) - }) - - test('Test bigger pagination total hits than nbr hits', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 1000, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(6) - }) - - test('Test bigger pagination total hits than nbr hits', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 1000, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(6) - }) - - test('Test pagination total hits with finite pagination', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 5, - finitePagination: true, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(5) - }) - - test('Test pagination total hits with infinite pagination', async () => { - const customClient = instantMeiliSearch( - 'http://localhost:7700', - 'masterKey', - { - paginationTotalHits: 5, - finitePagination: true, - } - ) - const response = await customClient.search([ - { - indexName: 'movies', - }, - ]) - const hits = response.results[0].hits - expect(hits.length).toBe(5) - }) }) diff --git a/tests/placeholder-search.tests.ts b/tests/placeholder-search.tests.ts index c78b2298..ad24deda 100644 --- a/tests/placeholder-search.tests.ts +++ b/tests/placeholder-search.tests.ts @@ -1,10 +1,5 @@ import { instantMeiliSearch } from '../src' -import { - searchClient, - dataset, - Movies, - meilisearchClient, -} from './assets/utils' +import { dataset, Movies, meilisearchClient } from './assets/utils' describe('Pagination browser test', () => { beforeAll(async () => { @@ -24,8 +19,7 @@ describe('Pagination browser test', () => { 'http://localhost:7700', 'masterKey', { - paginationTotalHits: 5, - placeholderSearch: true, + placeholderSearch: false, } ) const response = await customClient.search([ @@ -34,7 +28,7 @@ describe('Pagination browser test', () => { }, ]) const hits = response.results[0].hits - expect(hits.length).toBe(5) + expect(hits.length).toBe(0) }) test('Test placeholdersearch set to true', async () => { @@ -42,8 +36,7 @@ describe('Pagination browser test', () => { 'http://localhost:7700', 'masterKey', { - paginationTotalHits: 5, - placeholderSearch: false, + placeholderSearch: true, } ) const response = await customClient.search([ @@ -52,6 +45,6 @@ describe('Pagination browser test', () => { }, ]) const hits = response.results[0].hits - expect(hits.length).toBe(0) + expect(hits.length).toBe(6) }) }) From 7010569f504a02d3467b62fb7203dbf043dcfe45 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Thu, 21 Jul 2022 16:02:45 +0200 Subject: [PATCH 03/10] Update tests --- src/adapter/search-request-adapter/search-params-adapter.ts | 1 + tests/pagination.tests.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/adapter/search-request-adapter/search-params-adapter.ts b/src/adapter/search-request-adapter/search-params-adapter.ts index ef73adfb..1dcc3c0c 100644 --- a/src/adapter/search-request-adapter/search-params-adapter.ts +++ b/src/adapter/search-request-adapter/search-params-adapter.ts @@ -112,5 +112,6 @@ export function adaptSearchParams( } } + console.log(meiliSearchParams) return meiliSearchParams } diff --git a/tests/pagination.tests.ts b/tests/pagination.tests.ts index 71054b83..894c6d97 100644 --- a/tests/pagination.tests.ts +++ b/tests/pagination.tests.ts @@ -79,7 +79,7 @@ describe('Pagination browser test', () => { expect(hits).toEqual([]) }) - test('Test 0 hitsPerPage w/ page 0 ', async () => { + test.skip('Test 0 hitsPerPage w/ page 0 ', async () => { const response = await searchClient.search([ { indexName: 'movies', From 09f83764a1ed726cfd310d0d6c6a4ed54631edc5 Mon Sep 17 00:00:00 2001 From: cvermand <33010418+bidoubiwa@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:18:38 +0200 Subject: [PATCH 04/10] Update tests/env/react/src/App.js --- tests/env/react/src/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/env/react/src/App.js b/tests/env/react/src/App.js index 935c7c94..72ed676c 100644 --- a/tests/env/react/src/App.js +++ b/tests/env/react/src/App.js @@ -60,7 +60,7 @@ const App = () => (

Misc

From e51025b7c47c124f16a3ef05daa88a4216978e14 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 26 Jul 2022 11:54:11 +0200 Subject: [PATCH 05/10] Install meilisearch beta version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 459066cf..ae54eb12 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "url": "https://github.com/meilisearch/instant-meilisearch.git" }, "dependencies": { - "meilisearch": "^0.27.0" + "meilisearch": "0.28.0-pagination-beta.0" }, "devDependencies": { "@babel/cli": "^7.18.6", diff --git a/yarn.lock b/yarn.lock index 2af1528c..bd1daedd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5124,10 +5124,10 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -meilisearch@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.27.0.tgz#8bd57ddb77b975f93e054cb977b951c488ece297" - integrity sha512-kZOZFIuSO7c6xRf+Y2/9/h6A9pl0sCl/G44X4KuaSwxGbruOZPhmxbeVEgLHBv4pUFvQ56rNVTA/2d/5GCU1YA== +meilisearch@0.28.0-pagination-beta.0: + version "0.28.0-pagination-beta.0" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.28.0-pagination-beta.0.tgz#4a67c0a419b1be322671ad02bfa295dec3122eba" + integrity sha512-rOCbE4KYOhZqYED1C0GIrtXmdy6g5+POgdyq6yWDRSZ+1FgLGofRTeF25Vl4jG7dNlZRm5tSJcDUVYLAeHxMaA== dependencies: cross-fetch "^3.1.5" From d8bd4a4a7027725e53f5fc5db0e95913ca1a1910 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 26 Jul 2022 12:03:14 +0200 Subject: [PATCH 06/10] Update version --- package.json | 2 +- src/package-version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f5cde66f..9ee7f466 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@meilisearch/instant-meilisearch", - "version": "0.8.1", + "version": "0.8.1-pagination-beta.0", "private": false, "description": "The search client to use Meilisearch with InstantSearch.", "scripts": { diff --git a/src/package-version.ts b/src/package-version.ts index 01cd1d27..0c09632d 100644 --- a/src/package-version.ts +++ b/src/package-version.ts @@ -1 +1 @@ -export const PACKAGE_VERSION = '0.8.1' +export const PACKAGE_VERSION = '0.8.1-pagination-beta.0' From cabd7f99196c75d384dedd9b9fbe6ef1c314b380 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 26 Jul 2022 12:35:18 +0200 Subject: [PATCH 07/10] Update tests --- .../__tests__/pagination-adapter.tests.ts | 116 +++++++++++++++--- tests/pagination.tests.ts | 2 +- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts index 904ecdc6..80a71ff5 100644 --- a/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts +++ b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts @@ -1,4 +1,4 @@ -import { adaptPagination } from '../pagination-adapter' +import { adaptPaginationContext } from '../pagination-adapter' import { ceiledDivision } from '../../../utils' const numberPagesTestParameters = [ @@ -6,26 +6,31 @@ const numberPagesTestParameters = [ hitsPerPage: 0, hitsLength: 100, numberPages: 0, + page: 0, }, { hitsPerPage: 1, hitsLength: 100, numberPages: 100, + page: 1, }, { hitsPerPage: 20, hitsLength: 24, numberPages: 2, + page: 1, }, { hitsPerPage: 20, hitsLength: 0, numberPages: 0, + page: 1, }, { hitsPerPage: 0, hitsLength: 0, numberPages: 0, + page: 1, }, // Not an Algolia behavior. Algolia returns an error: // "Value too small for \"hitsPerPage\" parameter, expected integer between 0 and 9223372036854775807", @@ -42,19 +47,31 @@ const paginateHitsTestsParameters = [ hits: [], page: 0, hitsPerPage: 20, - returnedHits: [], + returnedPagination: { + page: 0, + hitsPerPage: 20, + nbPages: 0, + }, }, { hits: [], page: 100, hitsPerPage: 0, - returnedHits: [], + returnedPagination: { + page: 100, + hitsPerPage: 0, + nbPages: 0, + }, }, { hits: [], page: 100, hitsPerPage: 20, - returnedHits: [], + returnedPagination: { + page: 100, + hitsPerPage: 20, + nbPages: 0, + }, }, // Page 0 @@ -62,25 +79,37 @@ const paginateHitsTestsParameters = [ hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 0, hitsPerPage: 20, - returnedHits: [{ id: 1 }, { id: 2 }, { id: 3 }], + returnedPagination: { page: 0, hitsPerPage: 20, nbPages: 1 }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 0, hitsPerPage: 0, - returnedHits: [], + returnedPagination: { + page: 0, + hitsPerPage: 0, + nbPages: 0, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 0, hitsPerPage: 20, - returnedHits: [{ id: 1 }, { id: 2 }, { id: 3 }], + returnedPagination: { + page: 0, + hitsPerPage: 20, + nbPages: 1, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 0, hitsPerPage: 2, - returnedHits: [{ id: 1 }, { id: 2 }], + returnedPagination: { + page: 0, + hitsPerPage: 2, + nbPages: 2, + }, }, // Page 1 @@ -88,19 +117,31 @@ const paginateHitsTestsParameters = [ hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 1, hitsPerPage: 2, - returnedHits: [{ id: 3 }], + returnedPagination: { + page: 1, + hitsPerPage: 2, + nbPages: 2, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 1, hitsPerPage: 20, - returnedHits: [], + returnedPagination: { + page: 1, + hitsPerPage: 20, + nbPages: 1, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 1, hitsPerPage: 0, - returnedHits: [], + returnedPagination: { + page: 1, + hitsPerPage: 0, + nbPages: 0, + }, }, // Page 2 @@ -108,19 +149,31 @@ const paginateHitsTestsParameters = [ hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 2, hitsPerPage: 20, - returnedHits: [], + returnedPagination: { + page: 2, + hitsPerPage: 20, + nbPages: 1, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 2, hitsPerPage: 20, - returnedHits: [], + returnedPagination: { + hitsPerPage: 20, + nbPages: 1, + page: 2, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 2, hitsPerPage: 0, - returnedHits: [], + returnedPagination: { + page: 2, + nbPages: 0, + hitsPerPage: 0, + }, }, ] @@ -136,24 +189,47 @@ describe.each(numberPagesTestParameters)( describe.each(paginateHitsTestsParameters)( 'Paginate hits tests', - ({ hits, page, hitsPerPage, returnedHits }) => { + ({ hits, page, hitsPerPage, returnedPagination }) => { it(`Should return ${JSON.stringify( - returnedHits + returnedPagination )} when hitsPerPage is ${hitsPerPage}, number of page is ${page} and when hits is ${JSON.stringify( hits )}`, () => { - const response = adaptPagination(hits, page, hitsPerPage) - expect(response).toEqual(returnedHits) + const response = adaptPaginationContext( + { hits, page: page + 1, hitsPerPage, processingTimeMs: 0, query: '' }, + { hitsPerPage, page } + ) + expect(response).toEqual(returnedPagination) + }) + } +) + +describe.each(paginateHitsTestsParameters)( + 'Paginate hits tests', + ({ hits, page, hitsPerPage, returnedPagination }) => { + it(`Should return ${JSON.stringify( + returnedPagination + )} when hitsPerPage is ${hitsPerPage}, number of page is ${page} and when hits is ${JSON.stringify( + hits + )} but there is no page and hitsPerPage fields returned by Meilisearch`, () => { + const response = adaptPaginationContext( + { hits, processingTimeMs: 0, query: '' }, + { hitsPerPage, page } + ) + expect(response).toEqual(returnedPagination) }) } ) it('Should throw when hitsPerPage is negative', () => { try { - const hits: string[] = [] + const hits: Array> = [] const hitsPerPage = -1 const page = 0 - adaptPagination(hits, page, hitsPerPage) + adaptPaginationContext( + { hits, page: page + 1, hitsPerPage, processingTimeMs: 0, query: '' }, + { hitsPerPage, page } + ) } catch (e: any) { expect(e.message).toBe( 'Value too small for "hitsPerPage" parameter, expected integer between 0 and 9223372036854775807' diff --git a/tests/pagination.tests.ts b/tests/pagination.tests.ts index 894c6d97..71054b83 100644 --- a/tests/pagination.tests.ts +++ b/tests/pagination.tests.ts @@ -79,7 +79,7 @@ describe('Pagination browser test', () => { expect(hits).toEqual([]) }) - test.skip('Test 0 hitsPerPage w/ page 0 ', async () => { + test('Test 0 hitsPerPage w/ page 0 ', async () => { const response = await searchClient.search([ { indexName: 'movies', From 5c6f65ec631630efc2f653727894b81fa214c4c7 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 26 Jul 2022 12:48:45 +0200 Subject: [PATCH 08/10] Remove warning on pagination --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 31131257..628b878a 100644 --- a/README.md +++ b/README.md @@ -874,8 +874,6 @@ instantsearch.widgets.clearRefinements({ The `pagination` widget displays a pagination system allowing the user to change the current page. -We do not recommend using this widget as pagination slows the search responses. Instead, the [InfiniteHits](#-infinitehits) component is recommended. - - ✅ container: The CSS Selector or HTMLElement to insert the widget into. _required_ - ✅ showFirst: Whether to display the first-page link. - ✅ showPrevious: Whether to display the previous page link. From 8d6c4f4ca06d94c0fd8c3bd98f232e5b0b90e359 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Tue, 26 Jul 2022 12:49:18 +0200 Subject: [PATCH 09/10] Remove unecessary function --- .../pagination-adapter.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/adapter/search-response-adapter/pagination-adapter.ts b/src/adapter/search-response-adapter/pagination-adapter.ts index d0dfbc5f..366752ce 100644 --- a/src/adapter/search-response-adapter/pagination-adapter.ts +++ b/src/adapter/search-response-adapter/pagination-adapter.ts @@ -1,29 +1,6 @@ import type { MeiliSearchResponse, PaginationContext } from '../../types' import { ceiledDivision } from '../../utils' -/** - * Slice the requested hits based on the pagination position. - * - * @param {Record, - page: number, - hitsPerPage: number -): Array> { - if (hitsPerPage < 0) { - throw new TypeError( - 'Value too small for "hitsPerPage" parameter, expected integer between 0 and 9223372036854775807' - ) - } - const start = page * hitsPerPage - return hits.slice(start, start + hitsPerPage) -} - -// export function adaptNbPages(): number {} function adaptHitsPerPage( searchResponse: MeiliSearchResponse>, paginationContext: PaginationContext From fb46af7fd547a0fad7ccb5425c912cd61fb74567 Mon Sep 17 00:00:00 2001 From: Charlotte Vermandel Date: Wed, 3 Aug 2022 12:46:31 +0200 Subject: [PATCH 10/10] Add consistency in object formating --- .../__tests__/pagination-adapter.tests.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts index 80a71ff5..83d0af34 100644 --- a/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts +++ b/src/adapter/search-response-adapter/__tests__/pagination-adapter.tests.ts @@ -79,7 +79,11 @@ const paginateHitsTestsParameters = [ hits: [{ id: 1 }, { id: 2 }, { id: 3 }], page: 0, hitsPerPage: 20, - returnedPagination: { page: 0, hitsPerPage: 20, nbPages: 1 }, + returnedPagination: { + page: 0, + hitsPerPage: 20, + nbPages: 1, + }, }, { hits: [{ id: 1 }, { id: 2 }, { id: 3 }],