Skip to content

Commit 0eddde3

Browse files
committed
Merge remote-tracking branch 'origin/MC-32278-daniel' into MC-32120
2 parents 06d52d7 + 52c56c0 commit 0eddde3

File tree

6 files changed

+188
-38
lines changed

6 files changed

+188
-38
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1717,7 +1717,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC)
17171717
// optimize if using cat index
17181718
$filters = $this->_productLimitationFilters;
17191719
if (isset($filters['category_id']) || isset($filters['visibility'])) {
1720-
$this->getSelect()->order('cat_index.position ' . $dir);
1720+
$this->getSelect()->order(['cat_index.position ' . $dir, 'e.entity_id ' . $dir]);
17211721
} else {
17221722
$this->getSelect()->order('e.entity_id ' . $dir);
17231723
}

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
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;
2021

2122
/**
2223
* Product field data provider for product search, used for GraphQL resolver processing.
@@ -48,25 +49,33 @@ class ProductSearch
4849
*/
4950
private $searchResultApplierFactory;
5051

52+
/**
53+
* @var ProductCollectionSearchCriteriaBuilder
54+
*/
55+
private $searchCriteriaBuilder;
56+
5157
/**
5258
* @param CollectionFactory $collectionFactory
5359
* @param ProductSearchResultsInterfaceFactory $searchResultsFactory
5460
* @param CollectionProcessorInterface $collectionPreProcessor
5561
* @param CollectionPostProcessor $collectionPostProcessor
5662
* @param SearchResultApplierFactory $searchResultsApplierFactory
63+
* @param ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder
5764
*/
5865
public function __construct(
5966
CollectionFactory $collectionFactory,
6067
ProductSearchResultsInterfaceFactory $searchResultsFactory,
6168
CollectionProcessorInterface $collectionPreProcessor,
6269
CollectionPostProcessor $collectionPostProcessor,
63-
SearchResultApplierFactory $searchResultsApplierFactory
70+
SearchResultApplierFactory $searchResultsApplierFactory,
71+
ProductCollectionSearchCriteriaBuilder $searchCriteriaBuilder
6472
) {
6573
$this->collectionFactory = $collectionFactory;
6674
$this->searchResultsFactory = $searchResultsFactory;
6775
$this->collectionPreProcessor = $collectionPreProcessor;
6876
$this->collectionPostProcessor = $collectionPostProcessor;
6977
$this->searchResultApplierFactory = $searchResultsApplierFactory;
78+
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
7079
}
7180

7281
/**
@@ -85,15 +94,21 @@ public function getList(
8594
/** @var Collection $collection */
8695
$collection = $this->collectionFactory->create();
8796

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

91-
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes);
106+
$this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes);
92107
$collection->load();
93108
$this->collectionPostProcessor->process($collection, $attributes);
94109

95110
$searchResults = $this->searchResultsFactory->create();
96-
$searchResults->setSearchCriteria($searchCriteria);
111+
$searchResults->setSearchCriteria($searchCriteriaForCollection);
97112
$searchResults->setItems($collection->getItems());
98113
$searchResults->setTotalCount($searchResult->getTotalCount());
99114
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 & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
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;
17-
use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory;
1817
use Magento\Search\Model\Search\PageSizeProvider;
1918

2019
/**
@@ -37,11 +36,6 @@ class Search implements ProductQueryInterface
3736
*/
3837
private $pageSizeProvider;
3938

40-
/**
41-
* @var SearchCriteriaInterfaceFactory
42-
*/
43-
private $searchCriteriaFactory;
44-
4539
/**
4640
* @var FieldSelection
4741
*/
@@ -61,7 +55,6 @@ class Search implements ProductQueryInterface
6155
* @param SearchInterface $search
6256
* @param SearchResultFactory $searchResultFactory
6357
* @param PageSizeProvider $pageSize
64-
* @param SearchCriteriaInterfaceFactory $searchCriteriaFactory
6558
* @param FieldSelection $fieldSelection
6659
* @param ProductSearch $productsProvider
6760
* @param SearchCriteriaBuilder $searchCriteriaBuilder
@@ -70,15 +63,13 @@ public function __construct(
7063
SearchInterface $search,
7164
SearchResultFactory $searchResultFactory,
7265
PageSizeProvider $pageSize,
73-
SearchCriteriaInterfaceFactory $searchCriteriaFactory,
7466
FieldSelection $fieldSelection,
7567
ProductSearch $productsProvider,
7668
SearchCriteriaBuilder $searchCriteriaBuilder
7769
) {
7870
$this->search = $search;
7971
$this->searchResultFactory = $searchResultFactory;
8072
$this->pageSizeProvider = $pageSize;
81-
$this->searchCriteriaFactory = $searchCriteriaFactory;
8273
$this->fieldSelection = $fieldSelection;
8374
$this->productsProvider = $productsProvider;
8475
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
@@ -101,28 +92,18 @@ public function getResult(
10192

10293
$realPageSize = $searchCriteria->getPageSize();
10394
$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
95+
//Because of limitations of sort and pagination on search API we will query all IDS
10596
$pageSize = $this->pageSizeProvider->getMaxPageSize();
10697
$searchCriteria->setPageSize($pageSize);
10798
$searchCriteria->setCurrentPage(0);
10899
$itemsResults = $this->search->search($searchCriteria);
109100

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-
}
101+
//Address limitations of sort and pagination on search API apply original pagination from GQL query
124102
$searchCriteria->setPageSize($realPageSize);
125103
$searchCriteria->setCurrentPage($realCurrentPage);
104+
$searchResults = $this->productsProvider->getList($searchCriteria, $itemsResults, $queryFields);
105+
106+
$totalPages = $realPageSize ? ((int)ceil($searchResults->getTotalCount() / $realPageSize)) : 0;
126107

127108
$productArray = [];
128109
/** @var \Magento\Catalog\Model\Product $product */
@@ -138,7 +119,7 @@ public function getResult(
138119
'searchAggregation' => $itemsResults->getAggregations(),
139120
'pageSize' => $realPageSize,
140121
'currentPage' => $realCurrentPage,
141-
'totalPages' => $maxPages,
122+
'totalPages' => $totalPages,
142123
]
143124
);
144125
}

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/ProductSearchTest.php

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Catalog\Api\ProductRepositoryInterface;
1414
use Magento\Catalog\Model\Category;
1515
use Magento\Catalog\Model\CategoryLinkManagement;
16+
use Magento\Catalog\Model\ResourceModel\Category\Collection;
1617
use Magento\Eav\Model\Config;
1718
use Magento\TestFramework\ObjectManager;
1819
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -461,7 +462,7 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string
461462
}
462463

463464
/**
464-
* Full text search for Products and then filter the results by custom attribute ( sort is by defaulty by relevance)
465+
* Full text search for Products and then filter the results by custom attribute (default sort is relevance)
465466
*
466467
* @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php
467468
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -1114,6 +1115,81 @@ public function testFilterWithinSpecificPriceRangeSortedByNameDesc()
11141115
$this->assertEquals(4, $response['products']['page_info']['page_size']);
11151116
}
11161117

1118+
/**
1119+
* @magentoApiDataFixture Magento/Catalog/_files/category_with_three_products.php
1120+
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
1121+
*/
1122+
public function testSortByPosition()
1123+
{
1124+
// Get category ID for filtering
1125+
/** @var Collection $categoryCollection */
1126+
$categoryCollection = Bootstrap::getObjectManager()->get(Collection::class);
1127+
$category = $categoryCollection->addFieldToFilter('name', 'Category 999')->getFirstItem();
1128+
$categoryId = $category->getId();
1129+
1130+
$queryAsc = <<<QUERY
1131+
{
1132+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {position: ASC}) {
1133+
total_count
1134+
items {
1135+
sku
1136+
name
1137+
}
1138+
}
1139+
}
1140+
QUERY;
1141+
$resultAsc = $this->graphQlQuery($queryAsc);
1142+
$this->assertArrayNotHasKey('errors', $resultAsc);
1143+
$productsAsc = array_column($resultAsc['products']['items'], 'sku');
1144+
$expectedProductsAsc = ['simple1000', 'simple1001', 'simple1002'];
1145+
$this->assertEquals($expectedProductsAsc, $productsAsc);
1146+
1147+
$queryDesc = <<<QUERY
1148+
{
1149+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {position: DESC}) {
1150+
total_count
1151+
items {
1152+
sku
1153+
name
1154+
}
1155+
}
1156+
}
1157+
QUERY;
1158+
$resultDesc = $this->graphQlQuery($queryDesc);
1159+
$this->assertArrayNotHasKey('errors', $resultDesc);
1160+
$productsDesc = array_column($resultDesc['products']['items'], 'sku');
1161+
$expectedProductsDesc = array_reverse($expectedProductsAsc);
1162+
$this->assertEquals($expectedProductsDesc, $productsDesc);
1163+
1164+
//revert position
1165+
$productPositions = $category->getProductsPosition();
1166+
$count = 3;
1167+
foreach ($productPositions as $productId => $position) {
1168+
$productPositions[$productId] = $count;
1169+
$count--;
1170+
}
1171+
1172+
$category->setPostedProducts($productPositions);
1173+
$category->save();
1174+
1175+
$queryDesc = <<<QUERY
1176+
{
1177+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {position: DESC}) {
1178+
total_count
1179+
items {
1180+
sku
1181+
name
1182+
}
1183+
}
1184+
}
1185+
QUERY;
1186+
$resultDesc = $this->graphQlQuery($queryDesc);
1187+
$this->assertArrayNotHasKey('errors', $resultDesc);
1188+
$productsDesc = array_column($resultDesc['products']['items'], 'sku');
1189+
$expectedProductsDesc = $expectedProductsAsc;
1190+
$this->assertEquals($expectedProductsDesc, $productsDesc);
1191+
}
1192+
11171193
/**
11181194
* pageSize = total_count and current page = 2
11191195
* expected - error is thrown

0 commit comments

Comments
 (0)