Skip to content

Commit 55280a6

Browse files
committed
MC-32278: Position sort does not work in GraphQl.
- refactor & fix test
1 parent 94bb238 commit 55280a6

File tree

4 files changed

+137
-65
lines changed

4 files changed

+137
-65
lines changed

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
1313
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor;
1414
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface;
15+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch\ProductCollectionSearchCriteriaBuilder;
1516
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
1617
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
1718
use Magento\Framework\Api\Search\SearchResultInterface;
1819
use Magento\Framework\Api\SearchCriteriaInterface;
1920
use Magento\Framework\Api\SearchResultsInterface;
21+
use Magento\Framework\App\ObjectManager;
2022

2123
/**
2224
* Product field data provider for product search, used for GraphQL resolver processing.
@@ -48,25 +50,35 @@ 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 $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+
$this->searchCriteriaBuilder = $searchCriteriaBuilder
81+
?: ObjectManager::getInstance()->get(ProductCollectionSearchCriteriaBuilder::class);
7082
}
7183

7284
/**
@@ -85,15 +97,21 @@ public function getList(
8597
/** @var Collection $collection */
8698
$collection = $this->collectionFactory->create();
8799

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

91-
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes);
109+
$this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes);
92110
$collection->load();
93111
$this->collectionPostProcessor->process($collection, $attributes);
94112

95113
$searchResults = $this->searchResultsFactory->create();
96-
$searchResults->setSearchCriteria($searchCriteria);
114+
$searchResults->setSearchCriteria($searchCriteriaForCollection);
97115
$searchResults->setItems($collection->getItems());
98116
$searchResults->setTotalCount($searchResult->getTotalCount());
99117
return $searchResults;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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\Catalog\Model\ResourceModel\Product\CollectionFactory;
12+
use Magento\Framework\Api\FilterBuilder;
13+
use Magento\Framework\Api\Search\FilterGroupBuilder;
14+
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
15+
use Magento\Framework\Api\SearchCriteriaInterface;
16+
17+
class ProductCollectionSearchCriteriaBuilder
18+
{
19+
/**
20+
* @var SearchCriteriaInterfaceFactory
21+
*/
22+
private $searchCriteriaFactory;
23+
24+
/** @var FilterBuilder */
25+
private $filterBuilder;
26+
27+
/** @var FilterGroupBuilder */
28+
private $filterGroupBuilder;
29+
30+
/**
31+
* @param CollectionFactory $collectionFactory
32+
* @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
33+
* @param FilterBuilder $filterBuilder
34+
* @param FilterGroupBuilder $filterGroupBuilder
35+
*/
36+
public function __construct(
37+
CollectionFactory $collectionFactory,
38+
SearchCriteriaInterfaceFactory $searchCriteriaFactory,
39+
FilterBuilder $filterBuilder,
40+
FilterGroupBuilder $filterGroupBuilder
41+
) {
42+
$this->collectionFactory = $collectionFactory;
43+
$this->searchCriteriaFactory = $searchCriteriaFactory;
44+
$this->filterBuilder = $filterBuilder;
45+
$this->filterGroupBuilder = $filterGroupBuilder;
46+
}
47+
48+
/**
49+
* Build searchCriteria from search for product collection
50+
*
51+
* @param SearchCriteriaInterface $searchCriteria
52+
*/
53+
public function build(SearchCriteriaInterface $searchCriteria): SearchCriteriaInterface
54+
{
55+
//Create a copy of search criteria without filters to preserve the results from search
56+
$searchCriteriaForCollection = $this->searchCriteriaFactory->create()
57+
->setSortOrders($searchCriteria->getSortOrders())
58+
->setPageSize($searchCriteria->getPageSize())
59+
->setCurrentPage($searchCriteria->getCurrentPage());
60+
61+
//Add category id to enable sorting by position
62+
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
63+
foreach ($filterGroup->getFilters() as $filter) {
64+
if ($filter->getField() == CategoryProductLink::KEY_CATEGORY_ID) {
65+
$categoryFilter = $this->filterBuilder
66+
->setField($filter->getField())
67+
->setValue($filter->getValue())
68+
->setConditionType($filter->getConditionType())
69+
->create();
70+
71+
$this->filterGroupBuilder->addFilter($categoryFilter);
72+
$categoryGroup = $this->filterGroupBuilder->create();
73+
$searchCriteriaForCollection->setFilterGroups([$categoryGroup]);
74+
}
75+
}
76+
}
77+
return $searchCriteriaForCollection;
78+
}
79+
}

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

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@
1212
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult;
1313
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory;
1414
use Magento\Framework\Api\Search\SearchCriteriaInterface;
15-
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
1615
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1716
use Magento\Search\Api\SearchInterface;
1817
use Magento\Search\Model\Search\PageSizeProvider;
19-
use Magento\Framework\Api\FilterBuilder;
20-
use Magento\Framework\Api\Search\FilterGroupBuilder;
2118

2219
/**
2320
* Full text search for catalog using given search criteria.
@@ -39,11 +36,6 @@ class Search implements ProductQueryInterface
3936
*/
4037
private $pageSizeProvider;
4138

42-
/**
43-
* @var SearchCriteriaInterfaceFactory
44-
*/
45-
private $searchCriteriaFactory;
46-
4739
/**
4840
* @var FieldSelection
4941
*/
@@ -59,43 +51,28 @@ class Search implements ProductQueryInterface
5951
*/
6052
private $searchCriteriaBuilder;
6153

62-
/** @var FilterBuilder */
63-
private $filterBuilder;
64-
65-
/** @var FilterGroupBuilder */
66-
private $filterGroupBuilder;
67-
6854
/**
6955
* @param SearchInterface $search
7056
* @param SearchResultFactory $searchResultFactory
7157
* @param PageSizeProvider $pageSize
72-
* @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
7358
* @param FieldSelection $fieldSelection
7459
* @param ProductSearch $productsProvider
7560
* @param SearchCriteriaBuilder $searchCriteriaBuilder
76-
* @param FilterBuilder $filterBuilder
77-
* @param FilterGroupBuilder $filterGroupBuilder
7861
*/
7962
public function __construct(
8063
SearchInterface $search,
8164
SearchResultFactory $searchResultFactory,
8265
PageSizeProvider $pageSize,
83-
SearchCriteriaInterfaceFactory $searchCriteriaFactory,
8466
FieldSelection $fieldSelection,
8567
ProductSearch $productsProvider,
86-
SearchCriteriaBuilder $searchCriteriaBuilder,
87-
FilterBuilder $filterBuilder,
88-
FilterGroupBuilder $filterGroupBuilder
68+
SearchCriteriaBuilder $searchCriteriaBuilder
8969
) {
9070
$this->search = $search;
9171
$this->searchResultFactory = $searchResultFactory;
9272
$this->pageSizeProvider = $pageSize;
93-
$this->searchCriteriaFactory = $searchCriteriaFactory;
9473
$this->fieldSelection = $fieldSelection;
9574
$this->productsProvider = $productsProvider;
9675
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
97-
$this->filterBuilder = $filterBuilder;
98-
$this->filterGroupBuilder = $filterGroupBuilder;
9976
}
10077

10178
/**
@@ -115,48 +92,18 @@ public function getResult(
11592

11693
$realPageSize = $searchCriteria->getPageSize();
11794
$realCurrentPage = $searchCriteria->getCurrentPage();
118-
// Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround
95+
//Because of limitations of sort and pagination on search API we will query all IDS
11996
$pageSize = $this->pageSizeProvider->getMaxPageSize();
12097
$searchCriteria->setPageSize($pageSize);
12198
$searchCriteria->setCurrentPage(0);
12299
$itemsResults = $this->search->search($searchCriteria);
123100

124-
//Create copy of search criteria without conditions (conditions will be applied by joining search result)
125-
$searchCriteriaCopy = $this->searchCriteriaFactory->create()
126-
->setSortOrders($searchCriteria->getSortOrders())
127-
->setPageSize($realPageSize)
128-
->setCurrentPage($realCurrentPage);
129-
130-
$categoryGroup = null;
131-
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
132-
foreach ($filterGroup->getFilters() as $filter) {
133-
if ($filter->getField() == 'category_id') {
134-
$categoryFilter = $this->filterBuilder
135-
->setField($filter->getField())
136-
->setValue($filter->getValue())
137-
->setConditionType($filter->getConditionType())
138-
->create();
139-
140-
$this->filterGroupBuilder->addFilter($categoryFilter);
141-
$categoryGroup = $this->filterGroupBuilder->create();
142-
}
143-
}
144-
}
145-
//add root category or all root category children if category_id is not defined
146-
if ($categoryGroup) {
147-
$searchCriteriaCopy->setFilterGroups([$categoryGroup]);
148-
}
149-
150-
$searchResults = $this->productsProvider->getList($searchCriteriaCopy, $itemsResults, $queryFields);
151-
152-
//possible division by 0
153-
if ($realPageSize) {
154-
$maxPages = (int)ceil($searchResults->getTotalCount() / $realPageSize);
155-
} else {
156-
$maxPages = 0;
157-
}
101+
//Address limitations of sort and pagination on search API apply original pagination from GQL query
158102
$searchCriteria->setPageSize($realPageSize);
159103
$searchCriteria->setCurrentPage($realCurrentPage);
104+
$searchResults = $this->productsProvider->getList($searchCriteria, $itemsResults, $queryFields);
105+
106+
$totalPages = $realPageSize ? ((int)ceil($searchResults->getTotalCount() / $realPageSize)) : 0;
160107

161108
$productArray = [];
162109
/** @var \Magento\Catalog\Model\Product $product */
@@ -172,7 +119,7 @@ public function getResult(
172119
'searchAggregation' => $itemsResults->getAggregations(),
173120
'pageSize' => $realPageSize,
174121
'currentPage' => $realCurrentPage,
175-
'totalPages' => $maxPages,
122+
'totalPages' => $totalPages,
176123
]
177124
);
178125
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,34 @@ public function testSortByPosition()
11631163
$productsDesc = array_column($resultDesc['products']['items'], 'sku');
11641164
$expectedProductsDesc = array_reverse($expectedProductsAsc);
11651165
$this->assertEquals($expectedProductsDesc, $productsDesc);
1166+
1167+
//revert position
1168+
$productPositions = $category->getProductsPosition();
1169+
$count = 3;
1170+
foreach ($productPositions as $productId => $position) {
1171+
$productPositions[$productId] = $count;
1172+
$count--;
1173+
}
1174+
1175+
$category->setPostedProducts($productPositions);
1176+
$category->save();
1177+
1178+
$queryDesc = <<<QUERY
1179+
{
1180+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {position: DESC}) {
1181+
total_count
1182+
items {
1183+
sku
1184+
name
1185+
}
1186+
}
1187+
}
1188+
QUERY;
1189+
$resultDesc = $this->graphQlQuery($queryDesc);
1190+
$this->assertArrayNotHasKey('errors', $resultDesc);
1191+
$productsDesc = array_column($resultDesc['products']['items'], 'sku');
1192+
$expectedProductsDesc = $expectedProductsAsc;
1193+
$this->assertEquals($expectedProductsDesc, $productsDesc);
11661194
}
11671195

11681196
/**

0 commit comments

Comments
 (0)