Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 103 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ This package only guarantees the compatibility with the [version v4 of InstantSe

**Supported MeiliSearch versions**:

This package only guarantees the compatibility with the [version v0.21.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.21.0).
This package only guarantees the compatibility with the [version v0.22.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.22.0).

**Node / NPM versions**:

Expand All @@ -189,6 +189,47 @@ This package only guarantees the compatibility with the [version v0.21.0 of Meil

List of all the components that are available in [instantSearch](https://github.com/algolia/instantsearch.js) and their compatibilty with [MeiliSearch](https://github.com/meilisearch/meilisearch/).

### Table Of Widgets

- ✅ [InstantSearch](#-instantsearch)
- ❌[index](#-index)
- ✅ [SearchBox](#-searchbox)
- ✅ [Configure](#-configure)
- ❌[ConfigureRelatedItems](#-configure-related-items)
- ❌[Autocomplete](#-autocomplete)
- ✅ [Voice Search](#-voice-search)
- ✅ [Insight](#-insight)
- ✅ [Middleware](#-middleware)
- ✅ [RenderState](#-renderstate)
- ✅ [Hits](#-hits)
- ✅ [InfiniteHits](#-infinitehits)
- ✅ [Highlight](#-highlight)
- ✅ [Snippet](#-snippet)
- ❌[Geo Search](#-geo-search)
- ❌[Answers](#-answers)
- ✅ [RefinementList](#-refinementlist)
- ❌[HierarchicalMenu](#-hierarchicalmenu)
- ✅ [RangeSlider](#-rangeslider)
- ✅ [Menu](#-menu)
- ✅ [currentRefinements](#-currentrefinements)
- ✅ [RangeInput](#-rangeinput)
- ✅ [MenuSelect](#-menuselect)
- ✅ [ToggleRefinement](#-togglerefinement)
- ✅ [NumericMenu](#-numericmenu)
- ❌[RatingMenu](#-ratingmenu)
- ✅ [ClearRefinements](#-clearrefinements)
- ✅ [Pagination](#-pagination)
- ✅ [HitsPerPage](#-hitsperpage)
- ❌[Breadcrumb](#-breadcrumb)
- ✅ [Stats](#-stats)
- ❌[Analytics](#-analytics)
- ❌[QueryRuleCustomData](#-queryrulecustomdata)
- ❌[QueryRuleContext](#-queryrulecontext)
- ✅ [SortBy](#-sortby)
- ❌[RelevantSort](#-relevantsort)
- ✅ [Routing](#-routing)


### ✅ InstantSearch

[instantSearch references](https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/)
Expand Down Expand Up @@ -521,7 +562,7 @@ Min and max of attributes are not returned from MeiliSearch and thus **must be s

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:
Example:
Given the attribute `id` that has not been added in `filterableAttributes`:

```js
Expand Down Expand Up @@ -738,15 +779,71 @@ The queryRuleContext widget lets you apply ruleContexts based on filters to trig

No compatibility because MeiliSearch does not support Rules.

### SortBy
### SortBy

[Sort by references](https://www.algolia.com/doc/api-reference/widgets/sort-by/js/)

The sortBy widget displays a list of indices, allowing a user to change the way hits are sorted (with replica indices). Another common use case is to let the user switch between different indices.
The `SortBy` widget is used to create multiple sort formulas. Allowing a user to change the way hits are sorted.

No compatibility because MeiliSearch does not support hierarchical facets.
- ✅ container: The CSS Selector or HTMLElement to insert the widget into. _required_
- ✅ items: The list of different sorting possibilities. _required_
- ✅ cssClasses: The CSS classes to override.
- ✅ transformItems: function receiving the items, called before displaying them.

The usage of the `SortBy` widget differs from the one found in Algolia's documentation. In instant-meilisearch the following is possible:

- Sort using different indexes.
- Different `sort` rules on the same index.

The items list is composed of objects containing every sort possibility you want to provide to your user. Each object must contain two fields:
- `label`: What is showcased on the user interface ex: `Sort by Ascending Price`
- `value`: The sort formula.

#### Sort formula

A sort formula is expressed like this: `index:attribute:order`.

`index` is mandatory, and when adding `attribute:order`, they must always be added together.

If you'd like to get the "SortBy" feature, please vote for it in the [roadmap]https://roadmap.meilisearch.com/c/32-sort-by?utm_medium=social&utm_source=portal_share).
When sorting on an attribute, the attribute has to be added to the [`sortableAttributes`](https://docs.meilisearch.com/reference/api/sortable_attributes.html) setting on your index.

Example:
```js
[
{ label: 'Sort By Price', value: 'clothes:price:asc' }
]
```

In this scenario, in the `clothes` index, we want the price to be sorted in an ascending way. For this formula to be valid, `price` must be added to the `sortableAttributes` settings of the `clothes` index.

#### Relevancy

The impact sorting has on the returned hits is determined by the [`ranking-rules`](https://docs.meilisearch.com/learn/core_concepts/relevancy.html#ranking-rules) ordered list of each index. The `sort` ranking-rule position in the list makes sorting documents more or less important than other rules. If you want to change the sort impact on the relevancy, it is possible to change it in the [ranking-rule setting](https://docs.meilisearch.com/learn/core_concepts/relevancy.html#relevancy). For example, to favor exhaustivity over relevancy.

See [relevancy guide](https://docs.meilisearch.com/learn/core_concepts/relevancy.html#relevancy).

#### Example

```js
instantsearch.widgets.sortBy({
container: '#sort-by',
items: [
{ value: 'clothes', label: 'Relevant' }, // default index
{
value: 'clothes:price:asc', // Sort on descending price
label: 'Ascending price using query time sort',
},
{
value: 'clothes:price:asc', // Sort on ascending price
label: 'Descending price using query time sort',
},
{
value: 'clothes-sorted', // different index with different ranking rules.
label: 'Custom sort using a different index',
},
],
}),
```

### ❌ RelevantSort

Expand Down
14 changes: 14 additions & 0 deletions cypress/integration/search-ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ describe(`${playground} playground test`, () => {
cy.get(HIT_ITEM_CLASS).eq(0).contains('9.99 $')
})

it('Sort by recommendationCound ascending', () => {
const select = `.ais-SortBy-select`
cy.get(select).select('steam-video-games:recommendationCount:asc')
cy.wait(1000)
cy.get(HIT_ITEM_CLASS).eq(0).contains('Rag Doll Kung Fu')
})

it('Sort by default relevancy', () => {
const select = `.ais-SortBy-select`
cy.get(select).select('steam-video-games')
cy.wait(1000)
cy.get(HIT_ITEM_CLASS).eq(0).contains('Counter-Strike')
})

it('click on facets', () => {
const checkbox = `.ais-RefinementList-list .ais-RefinementList-checkbox`
cy.get(checkbox).eq(1).click()
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test:e2e:all": "sh scripts/e2e.sh",
"test:e2e:watch": "concurrently --kill-others -s first \"NODE_ENV=test yarn playground:javascript\" \"cypress open --env playground=javascript\"",
"test:all": "yarn test:e2e:all && yarn test && test:build",
"cy:open": "cypress open",
"playground:vue": "yarn --cwd ./playgrounds/vue && yarn --cwd ./playgrounds/vue serve",
"playground:react": "yarn --cwd ./playgrounds/react && yarn --cwd ./playgrounds/react start",
"playground:javascript": "yarn --cwd ./playgrounds/javascript && yarn --cwd ./playgrounds/javascript start",
Expand Down Expand Up @@ -52,7 +53,7 @@
"url": "https://github.com/meilisearch/instant-meilisearch.git"
},
"dependencies": {
"meilisearch": "^0.20.0"
"meilisearch": "^0.20.1"
},
"devDependencies": {
"@babel/cli": "^7.14.8",
Expand Down
18 changes: 16 additions & 2 deletions playgrounds/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ <h1 class="header-title">MeiliSearch + Angular InstantSearch</h1>
<div class="search-panel">
<div class="search-panel__filters">
<ais-clear-refinements></ais-clear-refinements>
<ais-sort-by
[items]="[
{ value: 'steam-video-games', label: 'Relevant' },
{
value: 'steam-video-games:recommendationCount:desc',
label: 'Most Recommended'
},
{
value: 'steam-video-games:recommendationCount:asc',
label: 'Least Recommended'
}
]"
></ais-sort-by>
<ais-configure [searchParameters]="{ hitsPerPage: 6 }"></ais-configure>
<h2>Genres</h2>
<ais-refinement-list attribute="genres" ></ais-refinement-list>
Expand Down Expand Up @@ -40,8 +53,9 @@ <h2>Misc</h2>
<div class="hit-description">
<ais-highlight attribute="description" [hit]="hit"></ais-highlight>
</div>
<div class="hit-info">${{hit.price}}</div>
<div class="hit-info">{{hit.releaseDate}}</div>
<div class="hit-info">price: ${{hit.price}}</div>
<div class="hit-info">Release date: {{hit.releaseDate}}</div>
<div class="hit-info">Recommendation: {{hit.recommendationCount}}</div>
</li>
</ol>
</ng-template>
Expand Down
4 changes: 2 additions & 2 deletions playgrounds/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Component } from '@angular/core'
import { instantMeiliSearch } from '../../../../src'

const searchClient = instantMeiliSearch(
'https://ms-9060336c1f95-106.saas.meili.dev',
'5d7e1929728417466fd5a82da5a28beb540d3e5bbaf4e01f742e1fb5fd72bb66'
'https://demo-steam.meilisearch.com/',
'90b03f9c47d0f321afae5ae4c4e4f184f53372a2953ab77bca679ff447ecc15c'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should think about hiding this key?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the search key! We want to provide it to the users to have a direct functioning playground. I understand this might be a security issue but we prefer providing development comfort I think. @curquiza

@alallema As this is not related to the PR, could you approve the pr if it is oke for you?

)

@Component({
Expand Down
6 changes: 3 additions & 3 deletions playgrounds/html/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
const search = instantsearch({
indexName: "steam-video-games",
searchClient: instantMeiliSearch(
'https://ms-9060336c1f95-106.saas.meili.dev',
'5d7e1929728417466fd5a82da5a28beb540d3e5bbaf4e01f742e1fb5fd72bb66',
'https://demo-steam.meilisearch.com',
'90b03f9c47d0f321afae5ae4c4e4f184f53372a2953ab77bca679ff447ecc15c',
)
});
});
search.addWidgets([
instantsearch.widgets.searchBox({
container: "#searchbox",
Expand Down
2 changes: 1 addition & 1 deletion playgrounds/javascript/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h2>Search in Steam video games 🎮</h2>

<div class="left-panel">
<div id="clear-refinements"></div>

<div id="sort-by"></div>
<h2>Genres</h2>
<div id="genres-list"></div>
<h2>Players</h2>
Expand Down
20 changes: 18 additions & 2 deletions playgrounds/javascript/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@ import { instantMeiliSearch } from '../../../src/index'
const search = instantsearch({
indexName: 'steam-video-games',
searchClient: instantMeiliSearch(
'https://ms-9060336c1f95-106.saas.meili.dev',
'5d7e1929728417466fd5a82da5a28beb540d3e5bbaf4e01f742e1fb5fd72bb66',
'https://demo-steam.meilisearch.com',
'90b03f9c47d0f321afae5ae4c4e4f184f53372a2953ab77bca679ff447ecc15c',
{
limitPerRequest: 30,
}
),
})

search.addWidgets([
instantsearch.widgets.sortBy({
container: '#sort-by',
items: [
{ value: 'steam-video-games', label: 'Relevant' },
{
value: 'steam-video-games:recommendationCount:desc',
label: 'Most Recommended',
},
{
value: 'steam-video-games:recommendationCount:asc',
label: 'Least Recommended',
},
],
}),
instantsearch.widgets.searchBox({
container: '#searchbox',
}),
Expand All @@ -32,6 +46,7 @@ search.addWidgets([
}),
instantsearch.widgets.configure({
hitsPerPage: 6,
attributesToSnippet: ['description:150'],
}),
instantsearch.widgets.refinementList({
container: '#misc-list',
Expand All @@ -51,6 +66,7 @@ search.addWidgets([
</div>
<div class="hit-info">price: {{price}}</div>
<div class="hit-info">release date: {{releaseDate}}</div>
<div class="hit-info">Recommendation: {{recommendationCount}}</div>
</div>
`,
},
Expand Down
60 changes: 45 additions & 15 deletions playgrounds/react/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import {
ClearRefinements,
RefinementList,
Configure,
SortBy,
Snippet,
} from 'react-instantsearch-dom'

import './App.css'
import { instantMeiliSearch } from '../../../src/index'

const searchClient = instantMeiliSearch(
'https://ms-9060336c1f95-106.saas.meili.dev',
'5d7e1929728417466fd5a82da5a28beb540d3e5bbaf4e01f742e1fb5fd72bb66',
'https://demo-steam.meilisearch.com/',
'90b03f9c47d0f321afae5ae4c4e4f184f53372a2953ab77bca679ff447ecc15c',
{
paginationTotalHits: 60,
primaryKey: 'id',
Expand All @@ -39,6 +42,20 @@ const App = () => (
<Stats />
<div className="left-panel">
<ClearRefinements />
<SortBy
defaultRefinement="steam-video-games"
items={[
{ value: 'steam-video-games', label: 'Relevant' },
{
value: 'steam-video-games:recommendationCount:desc',
label: 'Most Recommended',
},
{
value: 'steam-video-games:recommendationCount:asc',
label: 'Least Recommended',
},
]}
/>
<h2>Genres</h2>
<RefinementList attribute="genres" />
<h2>Players</h2>
Expand All @@ -47,7 +64,11 @@ const App = () => (
<RefinementList attribute="platforms" />
<h2>Misc</h2>
<RefinementList attribute="misc" />
<Configure hitsPerPage={6} />
<Configure
hitsPerPage={6}
attributesToSnippet={['description:50']}
snippetEllipsisText={'...'}
/>
</div>
<div className="right-panel">
<SearchBox />
Expand All @@ -57,18 +78,27 @@ const App = () => (
</div>
)

const Hit = ({ hit }) => (
<div key={hit.id}>
<div className="hit-name">
<Highlight attribute="name" hit={hit} />
</div>
<img src={hit.image} align="left" alt={hit.name} />
<div className="hit-name">
<Highlight attribute="description" hit={hit} />
const Hit = ({ hit }) => {
return (
<div key={hit.id}>
<div className="hit-name">
<Highlight attribute="name" hit={hit} />
</div>
<img src={hit.image} align="left" alt={hit.name} />
<div className="hit-name">
<Snippet attribute="description" hit={hit} />
</div>
<div className="hit-info">
<b>price:</b> {hit.price}
</div>
<div className="hit-info">
<b>release date:</b> {hit.releaseDate}
</div>
<div className="hit-info">
<b>Recommended:</b> {hit.recommendationCount}
</div>
</div>
<div className="hit-info">price: {hit.price}</div>
<div className="hit-info">release date: {hit.releaseDate}</div>
</div>
)
)
}

export default App
Loading