Vue-storefront: Category sort = 'position:asc' does not reflect the order set within magento categories

Created on 15 Jun 2019  路  18Comments  路  Source: DivanteLtd/vue-storefront

Current behavior

Currently it does not seem possible to display products within categories using the order specified in the Magento admin.

I am able to see the position number in the elastic data using Kibana. When using the new magento2-vsbridge-indexer you can see this in the categories attribute against a product.
Screenshot 2019-06-15 at 17 30 09

When using the mage2vuestorefront to import data, the position number is set on extension_attributes.category_links
Screenshot 2019-06-15 at 17 34 20

In either case, setting sort = 'position:asc' or 'position:desc' makes no difference to the sorting of products.

Expected behavior

When sort = 'position:asc' products within categories should be ordered as per the order field within magento admin
Screenshot 2019-06-15 at 17 40 16

Steps to reproduce the issue

Either using postman to hit the api directly or by configuring the storefront to sort products by position:asc products will not sort by the position number. And position:desc returns products in same position as position:asc.

Can you handle fixing this bug by yourself?

  • [ ] YES
  • [x] NO

Which [Release Cycle]

  • [ ] This is a bug report for test version on https://test.storefrontcloud.io - In this case Developer should create branch from develop branch and create Pull Request 2. Feature / Improvement back to develop.
  • [ ] This is a bug report for current Release Candidate version on https://next.storefrontcloud.io - In this case Developer should create branch from release branch and create Pull Request 3. Stabilisation fix back to release.
  • [x] This is a bug report for current Stable version on https://demo.storefrontcloud.io and should be placed in next stable version hotfix - In this case Developer should create branch from hotfix or master branch and create Pull Request 4. Hotfix back to hotfix.
Nice to have feature request vs-hackathon

All 18 comments

Thanks! However, it may look like a bug it's a feature request :-) This field is not being used for sorting as for now.

Shouldn't be that much of work to implement it. I belive we need to start with the vue-storefront/config/*.product.schema.json adding the product.category.position as a integer to the schema. Then we can just add the sorting field of category.position to vue-storefront/config/default.json:

      "defaultSortBy": {
        "attribute": "updated_at",
        "order": "desc"
      },
      "sortByAttributes": {
        "Latest": "updated_at",
        "Price: Low to high":"final_price",
        "Price: High to low":"final_price:desc"
      },

I think this might be a little more challenging unfortunately. The problem is ES doesn't know how to handle the sorting because these are nested values, inside extension_attributes.category_links. I believe using ElasticSearch's Nested Sorting ability could work here, but looking at the schema these don't appear to be declared as type nested so I don't know if it will work. A quick test on my side didn't seem to work. I don't know if adding the type: nested to the schema will fix this or not though.

For reference, I'm using the ES Docs for nested sorting here.

@rain2o I've tried your suggestion and it looks like it does try to order by extension_attributes.category_links but I think the issue now is that it does not know to use the specific category id order field.

For example below is the category_links array for the first product returned

"category_links": [
{
"category_id": "28",
"position": 0
},
{
"category_id": "32",
"position": 0
},
{
"category_id": "30",
"position": 4
},
{
"category_id": "6",
"position": 33
},
{
"category_id": "3",
"position": 13
},
{
"category_id": "48",
"position": 68
},
{
"category_id": "7",
"position": 20
}
]

The actual category I was viewing though was "category_id": "6" so this product shouldn't have been first in the list. How do we specify to sort by the position attribute associated with "category_id": "6"

@stevenoddy This is the same challenge we came up against. Our solution was to add the products attribute to each category in ES which comes from Magento's catalog_category_product table so that it lists each product in the category with its sort order. We use that for the looping order and have to have a separate query for all products and just grab each one from the product_id. For example my category now has this:

    "products": [
            {
              "category_id": 19,
              "product_id": 20,
              "position": 2
            },
            {
              "category_id": 19,
              "product_id": 21,
              "position": 1
            },
            {
              "category_id": 19,
              "product_id": 22,
              "position": 3
            }
    ]

In order to do this with mage2vs we had to add adapter.mode = 2; in importProductcategoriesPromise. There's a built-in MODE setting that can tell it to save this data, but it isn't used anymore so we had to manually use it. In order to add this using the vsbridge Magento module we just created a plugin for \Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Category\AttributeData using afterAddData to add the additional data to the category.

Might not be the best solution, but it is what we were able to come up with for our needs and time limit.

@rain2o this is great; can you please create a (not sure what:)) - PR, docs whatever? to keep this rolling? or just add this solution to the forum please?

@pkarw I can add this information to the forum for now, is there already a post I could add this to as a comment?

I'll try to implement our changes into core and submit a PR for both m2vs and vsbridge as soon as I can.

@rain2o as far as I know there is not - please feel free to create one :)

Thank you @rain2o this sounds great, it would be great if you could include the query / logic used on categories

@stevenoddy I posted some information here. Hope it helps https://forum.vuestorefront.io/t/sorting-products-by-category-position-magento/470/2

Hi @rain2o
Category position is already export to ES using https://github.com/DivanteLtd/magento2-vsbridge-indexer, it's there from the beginning.

    "category": [
      {
        "category_id": 3,
        "name": "Gear",
        "position": 0
      },
      {
        "category_id": 4,
        "name": "Bags",
        "position": 1
      },
      {
        "category_id": 7,
        "name": "Collections",
        "position": 0
      }
    ],

mapping:

          "category": {
            "type": "nested",
            "properties": {
              "category_id": {
                "type": "long"
              },
              "name": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "position": {
                "type": "long"
              }
            }
          },

product.category is a nested type indeed to allow us sorting by position.
It's enough to build nested sorting - I tried it some time ago, and it worked.

@afirlejczyk Thanks for the info. It could be that I'm just not familiar enough with how to query in ES, but I couldn't figure out a good way to sort products in a particular category by this nested value based on the category ID. So in your example if I'm on Category 4, how would I sort all products in Category 4 by the position? I would need to first get all the products, then somehow filter the product.category array to find the one where category_id matches my current category, then use the position field to sort all products. This proved to be rather difficult and a lot of extra processing. If you have a good solution to accomplish it though I'd be interested in seeing it.

It was far easier for me to list the Products in the Category entity with their ID/SKU and their position within the category. So if I'm on Category 1, then I can just loop category.products sorted by position.

Hi @rain2o - we can add conditions to sort!

  • so we really need ID of current category,
  • and add condition to sort

You can use sth like this in elasticsearchQuery.js to make it work

  if (sort === 'relevance') {
    const categories = searchQuery.getAppliedFilters().find(element => element.attribute === 'category_ids')
    let mainCategoryId = null
    if (categories && categories.value) {
      mainCategoryId = categories.value.in[0]
      if (mainCategoryId) {
        query.sort('category.position', {
          'order': 'asc',
          'nested_path': 'category',
          'nested_filter': {
            'terms': {
              'category.category_id': [mainCategoryId],
            },
          },
        })
      }
    }
  }

It's not fully working solution, but you can use to build query for ES.

Hi everyone,

we had the same issues and we were able to solving as per @afirlejczyk suggestion by hacking a little bit elasticsearchQuery.js.

First, we passed the whole Request to the function, not only Request.searchQuery and created a fallback const searchQuery to preserve functionality. We needed to change also any caller to the function (we only found one btw).

export async function prepareElasticsearchQueryBody (Request) {
  const searchQuery = Request.searchQuery

Then, since the sorting order is defined in the request, before query.build() we wrote

  if (Request.type === 'product') {
    const sortType = Request.sort.split(':',2)
    if (sortType[0] === 'position') {
      const categories = searchQuery.getAppliedFilters().find(element => element.attribute === 'category_ids')
      let mainCategoryId = null
      if (categories && categories.value) {
        mainCategoryId = categories.value.in[0]
        if (mainCategoryId) {
          query.sort('category.position', {
            'order': sortType[1] || 'asc',
            'nested_path': 'category',
            'nested_filter': {
              'terms': {
                'category.category_id': [mainCategoryId]
              }
            }
          })
        }
      }
    }
  }

For us, it works. To be clear, we use the Magento indexer, so we have the position info.

Hope this helps!

I think the solution would be the combination nested sort by and filter by query.
Below blog explains the concept very well : https://qbox.io/blog/sorting-nested-fields-in-elasticsearch

Hi everyone,

we had the same issues and we were able to solving as per @afirlejczyk suggestion by hacking a little bit elasticsearchQuery.js.

First, we passed the whole Request to the function, not only Request.searchQuery and created a fallback const searchQuery to preserve functionality. We needed to change also any caller to the function (we only found one btw).

export async function prepareElasticsearchQueryBody (Request) {
  const searchQuery = Request.searchQuery

Then, since the sorting order is defined in the request, before query.build() we wrote

  if (Request.type === 'product') {
    const sortType = Request.sort.split(':',2)
    if (sortType[0] === 'position') {
      const categories = searchQuery.getAppliedFilters().find(element => element.attribute === 'category_ids')
      let mainCategoryId = null
      if (categories && categories.value) {
        mainCategoryId = categories.value.in[0]
        if (mainCategoryId) {
          query.sort('category.position', {
            'order': sortType[1] || 'asc',
            'nested_path': 'category',
            'nested_filter': {
              'terms': {
                'category.category_id': [mainCategoryId]
              }
            }
          })
        }
      }
    }
  }

For us, it works. To be clear, we use the Magento indexer, so we have the position info.

Hope this helps!

My code is :
"sort": [
{
"extension_attributes.category_links.position": {
"order": "desc",
"unmapped_type" : "keyword",
"nested_filter": {
"terms": {
"extension_attributes.category_links.category_id": [
"17"
]
}
}
}
}
]
But it doesn't work! no sorting ^cry^

vsf 1.12.1 How to solve this problem ths

we are currently struggling with this exact problem - any solution yet? We are using VSF 1.12.2...

Looks like one is still able to get it working using the suggestion above with some slight tweaking as it's now using a different file and possibly different syntax depending on your ES version.

Was this page helpful?
0 / 5 - 0 ratings