Skip to content

Commit 563a8a5

Browse files
committed
Merge branch 'ACP2E-2119' of https://github.com/magento-l3/magento2ce into L3-PR-2023-09-13
2 parents 3262448 + dc70a9f commit 563a8a5

File tree

3 files changed

+217
-3
lines changed

3 files changed

+217
-3
lines changed

app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use Magento\Framework\Api\SortOrderBuilder;
1919
use Magento\Framework\App\Config\ScopeConfigInterface;
2020
use Magento\Framework\App\ObjectManager;
21+
use Magento\Framework\Exception\LocalizedException;
2122
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder;
23+
use Magento\Framework\Search\Request\Config as SearchConfig;
2224

2325
/**
2426
* Build search criteria
@@ -46,6 +48,7 @@ class SearchCriteriaBuilder
4648
* @var Builder
4749
*/
4850
private $builder;
51+
4952
/**
5053
* @var Visibility
5154
*/
@@ -61,14 +64,20 @@ class SearchCriteriaBuilder
6164
*/
6265
private Config $eavConfig;
6366

67+
/**
68+
* @var SearchConfig
69+
*/
70+
private SearchConfig $searchConfig;
71+
6472
/**
6573
* @param Builder $builder
6674
* @param ScopeConfigInterface $scopeConfig
6775
* @param FilterBuilder $filterBuilder
6876
* @param FilterGroupBuilder $filterGroupBuilder
6977
* @param Visibility $visibility
70-
* @param SortOrderBuilder $sortOrderBuilder
71-
* @param Config $eavConfig
78+
* @param SortOrderBuilder|null $sortOrderBuilder
79+
* @param Config|null $eavConfig
80+
* @param SearchConfig|null $searchConfig
7281
*/
7382
public function __construct(
7483
Builder $builder,
@@ -77,7 +86,8 @@ public function __construct(
7786
FilterGroupBuilder $filterGroupBuilder,
7887
Visibility $visibility,
7988
SortOrderBuilder $sortOrderBuilder = null,
80-
Config $eavConfig = null
89+
Config $eavConfig = null,
90+
SearchConfig $searchConfig = null
8191
) {
8292
$this->scopeConfig = $scopeConfig;
8393
$this->filterBuilder = $filterBuilder;
@@ -86,6 +96,7 @@ public function __construct(
8696
$this->visibility = $visibility;
8797
$this->sortOrderBuilder = $sortOrderBuilder ?? ObjectManager::getInstance()->get(SortOrderBuilder::class);
8898
$this->eavConfig = $eavConfig ?? ObjectManager::getInstance()->get(Config::class);
99+
$this->searchConfig = $searchConfig ?? ObjectManager::getInstance()->get(SearchConfig::class);
89100
}
90101

91102
/**
@@ -94,9 +105,15 @@ public function __construct(
94105
* @param array $args
95106
* @param bool $includeAggregation
96107
* @return SearchCriteriaInterface
108+
* @throws LocalizedException
97109
*/
98110
public function build(array $args, bool $includeAggregation): SearchCriteriaInterface
99111
{
112+
$partialMatchFilters = [];
113+
if (isset($args['filter'])) {
114+
$partialMatchFilters = $this->getPartialMatchFilters($args);
115+
$args = $this->removeMatchTypeFromArguments($args);
116+
}
100117
$searchCriteria = $this->builder->build('products', $args);
101118
$isSearch = isset($args['search']);
102119
$this->updateRangeFilters($searchCriteria);
@@ -113,6 +130,10 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte
113130
}
114131
$searchCriteria->setRequestName($requestName);
115132

133+
if (count($partialMatchFilters)) {
134+
$this->updateMatchTypeRequestConfig($requestName, $partialMatchFilters);
135+
}
136+
116137
if ($isSearch) {
117138
$this->addFilter($searchCriteria, 'search_term', $args['search']);
118139
}
@@ -130,6 +151,63 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte
130151
return $searchCriteria;
131152
}
132153

154+
/**
155+
* Update dynamically the search match type based on requested params
156+
*
157+
* @param string $requestName
158+
* @param array $partialMatchFilters
159+
*
160+
* @return void
161+
*/
162+
private function updateMatchTypeRequestConfig(string $requestName, array $partialMatchFilters): void
163+
{
164+
$data = $this->searchConfig->get($requestName);
165+
foreach ($data['queries'] as $queryName => $query) {
166+
foreach ($query['match'] ?? [] as $index => $matchItem) {
167+
if (in_array($matchItem['field'] ?? null, $partialMatchFilters, true)) {
168+
$data['queries'][$queryName]['match'][$index]['matchCondition'] = 'match_phrase_prefix';
169+
}
170+
}
171+
}
172+
$this->searchConfig->merge([$requestName => $data]);
173+
}
174+
175+
/**
176+
* Check if and what type of match_type value was requested
177+
*
178+
* @param array $args
179+
*
180+
* @return array
181+
*/
182+
private function getPartialMatchFilters(array $args): array
183+
{
184+
$partialMatchFilters = [];
185+
foreach ($args['filter'] as $fieldName => $conditions) {
186+
if (isset($conditions['match_type']) && $conditions['match_type'] === 'PARTIAL') {
187+
$partialMatchFilters[] = $fieldName;
188+
}
189+
}
190+
return $partialMatchFilters;
191+
}
192+
193+
/**
194+
* Remove the match_type to avoid search criteria containing it
195+
*
196+
* @param array $args
197+
*
198+
* @return array
199+
*/
200+
private function removeMatchTypeFromArguments(array $args): array
201+
{
202+
foreach ($args['filter'] as &$conditions) {
203+
if (isset($conditions['match_type'])) {
204+
unset($conditions['match_type']);
205+
}
206+
}
207+
208+
return $args;
209+
}
210+
133211
/**
134212
* Add filter by visibility
135213
*

app/code/Magento/GraphQl/etc/schema.graphqls

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ input FilterRangeTypeInput @doc(description: "Defines a filter that matches a ra
7777

7878
input FilterMatchTypeInput @doc(description: "Defines a filter that performs a fuzzy search.") {
7979
match: String @doc(description: "Use this attribute to fuzzy match the specified string. For example, to filter on a specific SKU, specify a value such as `24-MB01`.")
80+
match_type: FilterMatchTypeEnum @doc(description: "Filter match type for fine-tuned results. Possible values FULL or PARTIAL. If match_type is not provided, returned results will default to FULL match.")
81+
}
82+
83+
enum FilterMatchTypeEnum {
84+
FULL
85+
PARTIAL
8086
}
8187

8288
input FilterStringTypeInput @doc(description: "Defines a filter for an input string.") {

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,134 @@ private function getSearchQueryWithSuggestions(): string
241241
}
242242
QUERY;
243243
}
244+
245+
#[
246+
DataFixture(CategoryFixture::class, as: 'category'),
247+
DataFixture(
248+
ProductFixture::class,
249+
[
250+
'name' => 'Lifelong 1',
251+
'sku' => 'lifelong1',
252+
'description' => 'Life product 1',
253+
'category_ids' => ['$category.id$'],
254+
],
255+
'product1'
256+
),
257+
DataFixture(
258+
ProductFixture::class,
259+
[
260+
'name' => 'Life 2',
261+
'sku' => 'life2',
262+
'description' => 'Lifelong product 2',
263+
'category_ids' => ['$category.id$'],
264+
],
265+
'product2'
266+
),
267+
DataFixture(
268+
ProductFixture::class,
269+
[
270+
'name' => 'Life 3',
271+
'sku' => 'life3',
272+
'description' => 'Life product 3',
273+
'category_ids' => ['$category.id$'],
274+
],
275+
'product3'
276+
),
277+
DataFixture(
278+
ProductFixture::class,
279+
[
280+
'name' => 'Lifelong 4',
281+
'sku' => 'lifelong4',
282+
'description' => 'Lifelong product 4',
283+
'category_ids' => ['$category.id$'],
284+
],
285+
'product4'
286+
),
287+
]
288+
public function testSearchProductsWithFilterAndMatchTypeInQuery(): void
289+
{
290+
$response1 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, false, '', ''));
291+
$response2 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, false, 'FULL', ''));
292+
$response3 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, false, 'PARTIAL', ''));
293+
294+
$response4 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(false, true, '', ''));
295+
$response5 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(false, true, '', 'FULL'));
296+
$response6 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(false, true, '', 'PARTIAL'));
297+
298+
$response7 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, true, '', ''));
299+
$response8 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, true, 'FULL', 'FULL'));
300+
$response9 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, true, 'PARTIAL', 'PARTIAL'));
301+
$response10 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, true, 'FULL', 'PARTIAL'));
302+
$response11 = $this->graphQlQuery($this->getProductSearchQueryWithMatchType(true, true, 'PARTIAL', 'FULL'));
303+
304+
$this->assertEquals($response1, $response2);
305+
$this->assertNotEquals($response2, $response3);
306+
$this->assertEquals(2, $response1['products']['total_count']); // product 2, product 3
307+
$this->assertEquals(2, $response2['products']['total_count']); // product 2, product 3
308+
$this->assertEquals(4, $response3['products']['total_count']); // all
309+
$this->assertEquals('life2', $response1['products']['items'][1]['sku']);
310+
$this->assertEquals('life2', $response2['products']['items'][1]['sku']);
311+
$this->assertEquals('lifelong4', $response3['products']['items'][0]['sku']);
312+
313+
$this->assertEquals($response4, $response5);
314+
$this->assertNotEquals($response5, $response6);
315+
$this->assertEquals(2, $response4['products']['total_count']); // product 1, product 3
316+
$this->assertEquals(2, $response5['products']['total_count']); // product 1, product 3
317+
$this->assertEquals(4, $response6['products']['total_count']); // all
318+
$this->assertEquals('lifelong1', $response4['products']['items'][1]['sku']);
319+
$this->assertEquals('lifelong1', $response5['products']['items'][1]['sku']);
320+
$this->assertEquals('lifelong4', $response6['products']['items'][0]['sku']);
321+
322+
$this->assertEquals($response7, $response8);
323+
$this->assertNotEquals($response8, $response9);
324+
$this->assertEquals(1, $response7['products']['total_count']); // product 3
325+
$this->assertEquals(1, $response8['products']['total_count']); // product 3
326+
$this->assertEquals(4, $response9['products']['total_count']); // all
327+
$this->assertEquals(2, $response10['products']['total_count']); // product 2, product 3
328+
$this->assertEquals(2, $response11['products']['total_count']); // product 1, product 3
329+
$this->assertEquals('life3', $response7['products']['items'][0]['sku']);
330+
$this->assertEquals('life3', $response8['products']['items'][0]['sku']);
331+
$this->assertEquals('lifelong4', $response9['products']['items'][0]['sku']);
332+
$this->assertEquals('life2', $response10['products']['items'][1]['sku']);
333+
$this->assertEquals('lifelong1', $response11['products']['items'][1]['sku']);
334+
}
335+
336+
/**
337+
* Get a combinations of queries which contain different match_type
338+
*
339+
* @param bool $filterByName
340+
* @param bool $filterByDescription
341+
* @param string $matchTypeName
342+
* @param string $matchTypeDescription
343+
* @return string
344+
*/
345+
private function getProductSearchQueryWithMatchType(
346+
bool $filterByName,
347+
bool $filterByDescription,
348+
string $matchTypeName = '',
349+
string $matchTypeDescription = ''
350+
): string {
351+
$matchTypeName = $matchTypeName ? 'match_type:' . $matchTypeName : '';
352+
$matchTypeDescription = $matchTypeDescription ? 'match_type:' . $matchTypeDescription : '';
353+
$name = $filterByName ? 'name : { match : "Life", '.$matchTypeName.'}' : '';
354+
$description = $filterByDescription ? 'description: { match: "Life", '.$matchTypeDescription.'}' : '';
355+
return <<<QUERY
356+
{
357+
products(filter :
358+
{
359+
$name
360+
$description
361+
}){
362+
total_count
363+
items {
364+
name
365+
sku
366+
description {
367+
html
368+
}
369+
}
370+
}
371+
}
372+
QUERY;
373+
}
244374
}

0 commit comments

Comments
 (0)