Instantsearch.js: Float values in refinement list don't sort properly.

Created on 10 Sep 2018  ยท  4Comments  ยท  Source: algolia/instantsearch.js

Describe the bug ๐Ÿ›
Float values in refinement list don't sort properly.

To Reproduce ๐Ÿ”
Steps to reproduce the behavior:

  1. Pick a test index
  2. Add size attribute and put values like 9, 9.5, 10, 10.5, 11โ€ฆ
  3. Add size as a facet filter
  4. Add refinement list to your page:
search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#brand-list',
    attributeName: 'size',
    sortBy: ['size:asc'],
  })
);

https://codesandbox.io/s/420yp9lyn0

Expected behavior ๐Ÿ’ญ
Should be sorted 9, 9.5, 10. It's 9, 10, 9.5 instead.

Screenshots ๐Ÿ–ฅ
image

Environment:

  • OS: Mac
  • Browser: Chrome
  • Version 68.0.3440.106

Additional context
Please ping me back when it's solved so I can contact the customer who reported it.

Feature

Most helpful comment

Thanks @noclat that's indeed an annoying problem and definetely unexpected. We should be able to do better.

Looking at a response from Algolia, we can notice two things:

  • the facet values are strings whatever the number they are in the engine
  • if the values are numbers, the facet stats will give us some statistics which is a strong signal about the type.
{
  "results": [
    {
      "hits": [...],
      "nbHits": 15297,
      "page": 0,
      "nbPages": 50,
      "hitsPerPage": 20,
      "processingTimeMS": 11,
      "facets": {
        "price": {
          "39.99": 731,
          "49.99": 699,
          "19.99": 680,
          "29.99": 624,
          "99.99": 590,
          "59.99": 454,
          "24.99": 404,
          "34.99": 354,
          "199.99": 342,
          "79.99": 342
        }
      },
      "facets_stats": {
        "price": {
          "min": 1,
          "max": 4999.99,
          "avg": 258.651,
          "sum": 3956580
        }
      },
      "exhaustiveFacetsCount": true,
      "exhaustiveNbHits": true,
      "query": "h",
      "params": "query=h&maxValuesPerFacet=10&page=0&facets=%5B%22price%22%5D&tagFilters=",
      "index": "instant_search"
    }
  ]
}

We should be able to leverage that information to infer the type of the facet and apply a correct sort but it implies a lot of magic behind the scene.

On the other hand, the user knows better and could easily implement a custom sort function:

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#brand-list',
    attributeName: 'price',
    sortBy: function(a, b) {
      return Number.parseFloat(a.name) - Number.parseFloat(b.name);
    },
  })
);

All 4 comments

Thanks @noclat that's indeed an annoying problem and definetely unexpected. We should be able to do better.

Looking at a response from Algolia, we can notice two things:

  • the facet values are strings whatever the number they are in the engine
  • if the values are numbers, the facet stats will give us some statistics which is a strong signal about the type.
{
  "results": [
    {
      "hits": [...],
      "nbHits": 15297,
      "page": 0,
      "nbPages": 50,
      "hitsPerPage": 20,
      "processingTimeMS": 11,
      "facets": {
        "price": {
          "39.99": 731,
          "49.99": 699,
          "19.99": 680,
          "29.99": 624,
          "99.99": 590,
          "59.99": 454,
          "24.99": 404,
          "34.99": 354,
          "199.99": 342,
          "79.99": 342
        }
      },
      "facets_stats": {
        "price": {
          "min": 1,
          "max": 4999.99,
          "avg": 258.651,
          "sum": 3956580
        }
      },
      "exhaustiveFacetsCount": true,
      "exhaustiveNbHits": true,
      "query": "h",
      "params": "query=h&maxValuesPerFacet=10&page=0&facets=%5B%22price%22%5D&tagFilters=",
      "index": "instant_search"
    }
  ]
}

We should be able to leverage that information to infer the type of the facet and apply a correct sort but it implies a lot of magic behind the scene.

On the other hand, the user knows better and could easily implement a custom sort function:

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#brand-list',
    attributeName: 'price',
    sortBy: function(a, b) {
      return Number.parseFloat(a.name) - Number.parseFloat(b.name);
    },
  })
);

@bobylito I think this issue should be addressed in the JavaScript helper since we leverage the getFacetValues() method in connectRefinementList (see corresponding line โ†’).

@bobylito I think this issue should be addressed in the JavaScript helper since we leverage the getFacetValues() method in connectRefinementList (see corresponding line โ†’).

Yes that seems to be the place to fix ๐Ÿ‘

Let's keep this issue for keeping track of the implementation at the IS.js level.

Thanks for the effort in handling this folks! I understand, super clear answers ๐Ÿ™Œ.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jvreeken picture jvreeken  ยท  3Comments

anthonyBertrant picture anthonyBertrant  ยท  3Comments

Spone picture Spone  ยท  3Comments

zackify picture zackify  ยท  4Comments

bobylito picture bobylito  ยท  3Comments