Skip to content

Commit dd63719

Browse files
authored
Merge pull request #5807 from magento-mpi/MC-31375
[MPI]MC-31375: Catalog graphQl - position sort doesn't work properly
2 parents 8159f43 + 20a49e3 commit dd63719

File tree

7 files changed

+211
-39
lines changed

7 files changed

+211
-39
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,10 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC)
17181718
// optimize if using cat index
17191719
$filters = $this->_productLimitationFilters;
17201720
if (isset($filters['category_id']) || isset($filters['visibility'])) {
1721-
$this->getSelect()->order('cat_index.position ' . $dir);
1721+
$this->getSelect()->order([
1722+
'cat_index.position ' . $dir,
1723+
'e.entity_id ' . \Magento\Framework\DB\Select::SQL_DESC
1724+
]);
17221725
} else {
17231726
$this->getSelect()->order('e.entity_id ' . $dir);
17241727
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77

88
namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider;
99

10+
use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
12+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
1013
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor;
14+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface;
15+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch\ProductCollectionSearchCriteriaBuilder;
1116
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
1217
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
1318
use Magento\Framework\Api\Search\SearchResultInterface;
1419
use Magento\Framework\Api\SearchCriteriaInterface;
15-
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
16-
use Magento\Catalog\Model\ResourceModel\Product\Collection;
17-
use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory;
1820
use Magento\Framework\Api\SearchResultsInterface;
19-
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface;
21+
use Magento\Framework\App\ObjectManager;
2022

2123
/**
2224
* Product field data provider for product search, used for GraphQL resolver processing.
@@ -48,25 +50,34 @@ class ProductSearch
4850
*/
4951
private $searchResultApplierFactory;
5052

53+
/**
54+
* @var ProductCollectionSearchCriteriaBuilder
55+
*/
56+
private $searchCriteriaBuilder;
57+
5158
/**
5259
* @param CollectionFactory $collectionFactory
5360
* @param ProductSearchResultsInterfaceFactory $searchResultsFactory
5461
* @param CollectionProcessorInterface $collectionPreProcessor
5562
* @param CollectionPostProcessor $collectionPostProcessor
5663
* @param SearchResultApplierFactory $searchResultsApplierFactory
64+
* @param ProductCollectionSearchCriteriaBuilder|null $searchCriteriaBuilder
5765
*/
5866
public function __construct(
5967
CollectionFactory $collectionFactory,
6068
ProductSearchResultsInterfaceFactory $searchResultsFactory,
6169
CollectionProcessorInterface $collectionPreProcessor,
6270
CollectionPostProcessor $collectionPostProcessor,
63-
SearchResultApplierFactory $searchResultsApplierFactory
71+
SearchResultApplierFactory $searchResultsApplierFactory,
72+
?ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder = null
6473
) {
6574
$this->collectionFactory = $collectionFactory;
6675
$this->searchResultsFactory = $searchResultsFactory;
6776
$this->collectionPreProcessor = $collectionPreProcessor;
6877
$this->collectionPostProcessor = $collectionPostProcessor;
6978
$this->searchResultApplierFactory = $searchResultsApplierFactory;
79+
$this->searchCriteriaBuilder = $searchCriteriaBuilder
80+
?? ObjectManager::getInstance()->get(ProductCollectionSearchCriteriaBuilder::class);
7081
}
7182

7283
/**
@@ -85,15 +96,21 @@ public function getList(
8596
/** @var Collection $collection */
8697
$collection = $this->collectionFactory->create();
8798

88-
//Join search results
89-
$this->getSearchResultsApplier($searchResult, $collection, $this->getSortOrderArray($searchCriteria))->apply();
99+
//Create a copy of search criteria without filters to preserve the results from search
100+
$searchCriteriaForCollection = $this->searchCriteriaBuilder->build($searchCriteria);
101+
//Apply CatalogSearch results from search and join table
102+
$this->getSearchResultsApplier(
103+
$searchResult,
104+
$collection,
105+
$this->getSortOrderArray($searchCriteriaForCollection)
106+
)->apply();
90107

91-
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes);
108+
$this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes);
92109
$collection->load();
93110
$this->collectionPostProcessor->process($collection, $attributes);
94111

95112
$searchResults = $this->searchResultsFactory->create();
96-
$searchResults->setSearchCriteria($searchCriteria);
113+
$searchResults->setSearchCriteria($searchCriteriaForCollection);
97114
$searchResults->setItems($collection->getItems());
98115
$searchResults->setTotalCount($searchResult->getTotalCount());
99116
return $searchResults;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch;
9+
10+
use Magento\Catalog\Model\CategoryProductLink;
11+
use Magento\Framework\Api\FilterBuilder;
12+
use Magento\Framework\Api\Search\FilterGroupBuilder;
13+
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
14+
use Magento\Framework\Api\SearchCriteriaInterface;
15+
16+
/**
17+
* Builds a search criteria intended for the product collection based on search criteria used on SearchAPI
18+
*/
19+
class ProductCollectionSearchCriteriaBuilder
20+
{
21+
/** @var SearchCriteriaInterfaceFactory */
22+
private $searchCriteriaFactory;
23+
24+
/** @var FilterBuilder */
25+
private $filterBuilder;
26+
27+
/** @var FilterGroupBuilder */
28+
private $filterGroupBuilder;
29+
30+
/**
31+
* @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
32+
* @param FilterBuilder $filterBuilder
33+
* @param FilterGroupBuilder $filterGroupBuilder
34+
*/
35+
public function __construct(
36+
SearchCriteriaInterfaceFactory $searchCriteriaFactory,
37+
FilterBuilder $filterBuilder,
38+
FilterGroupBuilder $filterGroupBuilder
39+
) {
40+
$this->searchCriteriaFactory = $searchCriteriaFactory;
41+
$this->filterBuilder = $filterBuilder;
42+
$this->filterGroupBuilder = $filterGroupBuilder;
43+
}
44+
45+
/**
46+
* Build searchCriteria from search for product collection
47+
*
48+
* @param SearchCriteriaInterface $searchCriteria
49+
* @return SearchCriteriaInterface
50+
*/
51+
public function build(SearchCriteriaInterface $searchCriteria): SearchCriteriaInterface
52+
{
53+
//Create a copy of search criteria without filters to preserve the results from search
54+
$searchCriteriaForCollection = $this->searchCriteriaFactory->create()
55+
->setSortOrders($searchCriteria->getSortOrders())
56+
->setPageSize($searchCriteria->getPageSize())
57+
->setCurrentPage($searchCriteria->getCurrentPage());
58+
59+
//Add category id to enable sorting by position
60+
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
61+
foreach ($filterGroup->getFilters() as $filter) {
62+
if ($filter->getField() == CategoryProductLink::KEY_CATEGORY_ID) {
63+
$categoryFilter = $this->filterBuilder
64+
->setField($filter->getField())
65+
->setValue($filter->getValue())
66+
->setConditionType($filter->getConditionType())
67+
->create();
68+
69+
$this->filterGroupBuilder->addFilter($categoryFilter);
70+
$categoryGroup = $this->filterGroupBuilder->create();
71+
$searchCriteriaForCollection->setFilterGroups([$categoryGroup]);
72+
}
73+
}
74+
}
75+
return $searchCriteriaForCollection;
76+
}
77+
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder;
1111
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch;
12-
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
13-
use Magento\Framework\Api\Search\SearchCriteriaInterface;
1412
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult;
1513
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory;
14+
use Magento\Framework\Api\Search\SearchCriteriaInterface;
15+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1616
use Magento\Search\Api\SearchInterface;
1717
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
1818
use Magento\Search\Model\Search\PageSizeProvider;
@@ -101,28 +101,18 @@ public function getResult(
101101

102102
$realPageSize = $searchCriteria->getPageSize();
103103
$realCurrentPage = $searchCriteria->getCurrentPage();
104-
// Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround
104+
//Because of limitations of sort and pagination on search API we will query all IDS
105105
$pageSize = $this->pageSizeProvider->getMaxPageSize();
106106
$searchCriteria->setPageSize($pageSize);
107107
$searchCriteria->setCurrentPage(0);
108108
$itemsResults = $this->search->search($searchCriteria);
109109

110-
//Create copy of search criteria without conditions (conditions will be applied by joining search result)
111-
$searchCriteriaCopy = $this->searchCriteriaFactory->create()
112-
->setSortOrders($searchCriteria->getSortOrders())
113-
->setPageSize($realPageSize)
114-
->setCurrentPage($realCurrentPage);
115-
116-
$searchResults = $this->productsProvider->getList($searchCriteriaCopy, $itemsResults, $queryFields);
117-
118-
//possible division by 0
119-
if ($realPageSize) {
120-
$maxPages = (int)ceil($searchResults->getTotalCount() / $realPageSize);
121-
} else {
122-
$maxPages = 0;
123-
}
110+
//Address limitations of sort and pagination on search API apply original pagination from GQL query
124111
$searchCriteria->setPageSize($realPageSize);
125112
$searchCriteria->setCurrentPage($realCurrentPage);
113+
$searchResults = $this->productsProvider->getList($searchCriteria, $itemsResults, $queryFields);
114+
115+
$totalPages = $realPageSize ? ((int)ceil($searchResults->getTotalCount() / $realPageSize)) : 0;
126116

127117
$productArray = [];
128118
/** @var \Magento\Catalog\Model\Product $product */
@@ -138,7 +128,7 @@ public function getResult(
138128
'searchAggregation' => $itemsResults->getAggregations(),
139129
'pageSize' => $realPageSize,
140130
'currentPage' => $realCurrentPage,
141-
'totalPages' => $maxPages,
131+
'totalPages' => $totalPages,
142132
]
143133
);
144134
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public function apply(Filter $filter, AbstractDb $collection)
6161
$category = $this->categoryFactory->create();
6262
$this->categoryResourceModel->load($category, $categoryId);
6363
$categoryProducts[$categoryId] = $category->getProductCollection()->getAllIds();
64+
$collection->addCategoryFilter($category);
6465
}
6566

6667
$categoryProductIds = array_unique(array_merge(...$categoryProducts));

dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ public function testQueryChildCategoriesWithProducts()
186186
//Check base category products
187187
$expectedBaseCategoryProducts = [
188188
['sku' => 'simple', 'name' => 'Simple Product'],
189-
['sku' => '12345', 'name' => 'Simple Product Two'],
190-
['sku' => 'simple-4', 'name' => 'Simple Product Three']
189+
['sku' => 'simple-4', 'name' => 'Simple Product Three'],
190+
['sku' => '12345', 'name' => 'Simple Product Two']
191191
];
192192
$this->assertCategoryProducts($baseCategory, $expectedBaseCategoryProducts);
193193
//Check base category children
@@ -202,8 +202,8 @@ public function testQueryChildCategoriesWithProducts()
202202
$this->assertEquals('Category 1.1', $firstChildCategory['name']);
203203
$this->assertEquals('Category 1.1 description.', $firstChildCategory['description']);
204204
$firstChildCategoryExpectedProducts = [
205-
['sku' => 'simple', 'name' => 'Simple Product'],
206205
['sku' => '12345', 'name' => 'Simple Product Two'],
206+
['sku' => 'simple', 'name' => 'Simple Product'],
207207
];
208208
$this->assertCategoryProducts($firstChildCategory, $firstChildCategoryExpectedProducts);
209209
$firstChildCategoryChildren = [['name' =>'Category 1.1.1']];
@@ -213,8 +213,8 @@ public function testQueryChildCategoriesWithProducts()
213213
$this->assertEquals('Category 1.2', $secondChildCategory['name']);
214214
$this->assertEquals('Its a description of Test Category 1.2', $secondChildCategory['description']);
215215
$firstChildCategoryExpectedProducts = [
216+
['sku' => 'simple-4', 'name' => 'Simple Product Three'],
216217
['sku' => 'simple', 'name' => 'Simple Product'],
217-
['sku' => 'simple-4', 'name' => 'Simple Product Three']
218218
];
219219
$this->assertCategoryProducts($secondChildCategory, $firstChildCategoryExpectedProducts);
220220
$firstChildCategoryChildren = [];
@@ -277,8 +277,8 @@ public function testQueryCategoryWithDisabledChildren()
277277
//Check base category products
278278
$expectedBaseCategoryProducts = [
279279
['sku' => 'simple', 'name' => 'Simple Product'],
280-
['sku' => '12345', 'name' => 'Simple Product Two'],
281-
['sku' => 'simple-4', 'name' => 'Simple Product Three']
280+
['sku' => 'simple-4', 'name' => 'Simple Product Three'],
281+
['sku' => '12345', 'name' => 'Simple Product Two']
282282
];
283283
$this->assertCategoryProducts($baseCategory, $expectedBaseCategoryProducts);
284284
//Check base category children
@@ -293,8 +293,8 @@ public function testQueryCategoryWithDisabledChildren()
293293
$this->assertEquals('Its a description of Test Category 1.2', $firstChildCategory['description']);
294294

295295
$firstChildCategoryExpectedProducts = [
296-
['sku' => 'simple', 'name' => 'Simple Product'],
297-
['sku' => 'simple-4', 'name' => 'Simple Product Three']
296+
['sku' => 'simple-4', 'name' => 'Simple Product Three'],
297+
['sku' => 'simple', 'name' => 'Simple Product']
298298
];
299299
$this->assertCategoryProducts($firstChildCategory, $firstChildCategoryExpectedProducts);
300300
$firstChildCategoryChildren = [];

0 commit comments

Comments
 (0)