Skip to content

Commit 4a6603e

Browse files
committed
ACP2E-2119: emulate advanced search for graphql products filter match
1 parent 968fb3e commit 4a6603e

File tree

3 files changed

+196
-3
lines changed

3 files changed

+196
-3
lines changed

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

Lines changed: 60 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
@@ -61,14 +63,20 @@ class SearchCriteriaBuilder
6163
*/
6264
private Config $eavConfig;
6365

66+
/**
67+
* @var SearchConfig
68+
*/
69+
private mixed $config;
70+
6471
/**
6572
* @param Builder $builder
6673
* @param ScopeConfigInterface $scopeConfig
6774
* @param FilterBuilder $filterBuilder
6875
* @param FilterGroupBuilder $filterGroupBuilder
6976
* @param Visibility $visibility
70-
* @param SortOrderBuilder $sortOrderBuilder
71-
* @param Config $eavConfig
77+
* @param SortOrderBuilder|null $sortOrderBuilder
78+
* @param Config|null $eavConfig
79+
* @param SearchConfig|null $config
7280
*/
7381
public function __construct(
7482
Builder $builder,
@@ -77,7 +85,8 @@ public function __construct(
7785
FilterGroupBuilder $filterGroupBuilder,
7886
Visibility $visibility,
7987
SortOrderBuilder $sortOrderBuilder = null,
80-
Config $eavConfig = null
88+
Config $eavConfig = null,
89+
SearchConfig $config = null
8190
) {
8291
$this->scopeConfig = $scopeConfig;
8392
$this->filterBuilder = $filterBuilder;
@@ -86,6 +95,7 @@ public function __construct(
8695
$this->visibility = $visibility;
8796
$this->sortOrderBuilder = $sortOrderBuilder ?? ObjectManager::getInstance()->get(SortOrderBuilder::class);
8897
$this->eavConfig = $eavConfig ?? ObjectManager::getInstance()->get(Config::class);
98+
$this->config = $config ?? ObjectManager::getInstance()->get(SearchConfig::class);
8999
}
90100

91101
/**
@@ -94,9 +104,11 @@ public function __construct(
94104
* @param array $args
95105
* @param bool $includeAggregation
96106
* @return SearchCriteriaInterface
107+
* @throws LocalizedException
97108
*/
98109
public function build(array $args, bool $includeAggregation): SearchCriteriaInterface
99110
{
111+
$matchTypes = $this->getMatchType($args);
100112
$searchCriteria = $this->builder->build('products', $args);
101113
$isSearch = isset($args['search']);
102114
$this->updateRangeFilters($searchCriteria);
@@ -113,6 +125,10 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte
113125
}
114126
$searchCriteria->setRequestName($requestName);
115127

128+
if (count($matchTypes)) {
129+
$this->updateMatchTypeRequestConfig($requestName, $matchTypes);
130+
}
131+
116132
if ($isSearch) {
117133
$this->addFilter($searchCriteria, 'search_term', $args['search']);
118134
}
@@ -130,6 +146,47 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte
130146
return $searchCriteria;
131147
}
132148

149+
/**
150+
* Update dynamically the search match type based on requested params
151+
*
152+
* @param string $requestName
153+
* @param array $matchType
154+
* @return void
155+
*/
156+
private function updateMatchTypeRequestConfig(string $requestName, array $matchType): void
157+
{
158+
$data = $this->config->get($requestName);
159+
foreach ($data['queries'] as $queryName => $match) {
160+
if (isset($matchType[$queryName]) && $matchType[$queryName] === 'PARTIAL') {
161+
$match['match'][0]['matchCondition'] = 'match_phrase_prefix';
162+
$data['queries'][$queryName] = $match;
163+
}
164+
}
165+
$this->config->merge([$requestName => $data]);
166+
}
167+
168+
/**
169+
* Check if and what type of match_type value was requested
170+
*
171+
* @param array $args
172+
* @return array
173+
*/
174+
private function getMatchType(array &$args): array
175+
{
176+
$matchType = [];
177+
if (isset($args['filter'])) {
178+
foreach ($args['filter'] as $fieldName => $conditions) {
179+
foreach ($conditions as $filter => $value) {
180+
if ($filter === 'match_type') {
181+
$matchType[$fieldName.'_query'] = $value;
182+
unset($args['filter'][$fieldName][$filter]);
183+
}
184+
}
185+
}
186+
}
187+
return $matchType;
188+
}
189+
133190
/**
134191
* Add filter by visibility
135192
*

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 providing results. Possible values FULL or PARTIAL.")
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)