Skip to content

Commit 21e709b

Browse files
committed
Merge branch 'MC-42799' of https://github.com/magento-l3/magento2ce into L3-PR-20210830
2 parents 5054c14 + bab2696 commit 21e709b

File tree

13 files changed

+280
-55
lines changed

13 files changed

+280
-55
lines changed

app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ protected function _getItemsData()
112112
$facets = $productCollection->getFacetedData($attribute->getAttributeCode());
113113

114114
$data = [];
115+
$lastFacet = array_key_last($facets);
115116
foreach ($facets as $key => $aggregation) {
116117
$count = $aggregation['count'];
117118
if (!$this->isOptionReducesResults($count, $productSize)) {
@@ -124,7 +125,7 @@ protected function _getItemsData()
124125
if ($to == '*') {
125126
$to = null;
126127
}
127-
$label = $this->renderRangeLabel(empty($from) ? 0 : $from, $to);
128+
$label = $this->renderRangeLabel(empty($from) ? 0 : $from, $lastFacet === $key ? null : $to);
128129
$value = $from . '-' . $to;
129130

130131
$data[] = [

app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
*/
1717
class Decimal implements GeneratorInterface
1818
{
19+
/**
20+
* Price attribute aggregation algorithm
21+
*/
22+
private const AGGREGATION_ALGORITHM_VARIABLE = 'price_dynamic_algorithm';
23+
24+
/**
25+
* Default decimal attribute aggregation algorithm
26+
*/
27+
private const DEFAULT_AGGREGATION_ALGORITHM = 'manual';
28+
1929
/**
2030
* @inheritdoc
2131
*/
@@ -39,7 +49,9 @@ public function getAggregationData(Attribute $attribute, $bucketName)
3949
'type' => BucketInterface::TYPE_DYNAMIC,
4050
'name' => $bucketName,
4151
'field' => $attribute->getAttributeCode(),
42-
'method' => 'manual',
52+
'method' => $attribute->getFrontendInput() === 'price'
53+
? '$' . self::AGGREGATION_ALGORITHM_VARIABLE . '$'
54+
: self::DEFAULT_AGGREGATION_ALGORITHM,
4355
'metric' => [['type' => 'count']],
4456
];
4557
}

app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/DecimalTest.php

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection;
1717
use Magento\Eav\Model\Entity\Attribute;
1818
use Magento\Framework\App\RequestInterface;
19+
use Magento\Framework\Pricing\PriceCurrencyInterface;
1920
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
2021
use PHPUnit\Framework\MockObject\MockObject;
2122
use PHPUnit\Framework\TestCase;
@@ -92,7 +93,15 @@ protected function setUp(): void
9293
->method($this->anything())->willReturnSelf();
9394
$this->filterItemFactory->expects($this->any())
9495
->method('create')
95-
->willReturn($this->filterItem);
96+
->willReturnCallback(
97+
function (array $data) {
98+
return new Item(
99+
$this->createMock(\Magento\Framework\UrlInterface::class),
100+
$this->createMock(\Magento\Theme\Block\Html\Pager::class),
101+
$data
102+
);
103+
}
104+
);
96105

97106
$this->fulltextCollection = $this->getMockBuilder(
98107
Collection::class
@@ -132,12 +141,20 @@ protected function setUp(): void
132141
->willReturn($this->state);
133142

134143
$objectManagerHelper = new ObjectManagerHelper($this);
144+
$priceFormatter = $this->createMock(PriceCurrencyInterface::class);
145+
$priceFormatter->method('format')
146+
->willReturnCallback(
147+
function ($number) {
148+
return sprintf('$%01.2f', $number);
149+
}
150+
);
135151
$this->target = $objectManagerHelper->getObject(
136152
Decimal::class,
137153
[
138154
'filterItemFactory' => $this->filterItemFactory,
139155
'layer' => $this->layer,
140-
'filterDecimalFactory' => $filterDecimalFactory
156+
'filterDecimalFactory' => $filterDecimalFactory,
157+
'priceCurrency' => $priceFormatter,
141158
]
142159
);
143160

@@ -218,25 +235,54 @@ function ($field) use ($requestVar, $filter) {
218235
}
219236

220237
/**
221-
* @return void
222-
*/
223-
public function testItemData(): void
238+
* @param array $facets
239+
* @param array $expected
240+
* @dataProvider itemDataDataProvider
241+
* @return void
242+
*/
243+
public function testItemData(array $facets, array $expected): void
224244
{
225245
$this->fulltextCollection->expects($this->any())
226246
->method('getSize')
227247
->willReturn(5);
228248

229249
$this->fulltextCollection->expects($this->any())
230250
->method('getFacetedData')
231-
->willReturn([
232-
'2_10' => ['count' => 5],
233-
'*_*' => ['count' => 2]
234-
]);
235-
$this->assertEquals(
251+
->willReturn($facets);
252+
$actual = [];
253+
foreach ($this->target->getItems() as $item) {
254+
$actual[] = ['label' => $item->getLabel(), 'value' => $item->getValue(), 'count' => $item->getCount()];
255+
}
256+
$this->assertEquals($expected, $actual);
257+
}
258+
259+
/**
260+
* @return array
261+
*/
262+
public function itemDataDataProvider(): array
263+
{
264+
return [
236265
[
237-
$this->filterItem
266+
[
267+
'0_10' => ['count' => 5],
268+
'10_20' => ['count' => 2],
269+
'30_' => ['count' => 1]
270+
],
271+
[
272+
['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => '2'],
273+
['label' => '$30.00 and above', 'value' => '30-', 'count' => '1'],
274+
]
238275
],
239-
$this->target->getItems()
240-
);
276+
[
277+
[
278+
'*_100' => ['count' => 3],
279+
'200_*' => ['count' => 1],
280+
],
281+
[
282+
['label' => '$0.00 - $99.99', 'value' => '-100', 'count' => '3'],
283+
['label' => '$200.00 and above', 'value' => '200-', 'count' => '1'],
284+
]
285+
]
286+
];
241287
}
242288
}

app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ public function build(RequestInterface $request, array $queryResult)
7979
$aggregations = [];
8080
$buckets = $request->getAggregation();
8181

82-
$dataProvider = $this->dataProviderFactory->create(
83-
$this->dataProviderContainer[$request->getIndex()],
84-
$this->query
85-
);
8682
foreach ($buckets as $bucket) {
83+
$dataProvider = $this->dataProviderFactory->create(
84+
$this->dataProviderContainer[$request->getIndex()],
85+
$this->query,
86+
$bucket->getField()
87+
);
8788
$bucketAggregationBuilder = $this->aggregationContainer[$bucket->getType()];
8889
$aggregations[$bucket->getName()] = $bucketAggregationBuilder->build(
8990
$bucket,

app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/DataProviderFactory.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ public function __construct(ObjectManagerInterface $objectManager)
4848
* which will be recreated with its default configuration.
4949
*
5050
* @param DataProviderInterface $dataProvider
51-
* @param QueryContainer $query
51+
* @param QueryContainer|null $query
52+
* @param string|null $aggregationFieldName
5253
* @return DataProviderInterface
5354
* @throws \LogicException when the query is missing but it required according to the QueryAwareInterface
5455
*/
55-
public function create(DataProviderInterface $dataProvider, QueryContainer $query = null)
56-
{
56+
public function create(
57+
DataProviderInterface $dataProvider,
58+
QueryContainer $query = null,
59+
?string $aggregationFieldName = null
60+
) {
5761
$result = $dataProvider;
5862
if ($dataProvider instanceof QueryAwareInterface) {
5963
if (null === $query) {
@@ -64,7 +68,13 @@ public function create(DataProviderInterface $dataProvider, QueryContainer $quer
6468
}
6569

6670
$className = get_class($dataProvider);
67-
$result = $this->objectManager->create($className, ['queryContainer' => $query]);
71+
$result = $this->objectManager->create(
72+
$className,
73+
[
74+
'queryContainer' => $query,
75+
'aggregationFieldName' => $aggregationFieldName
76+
]
77+
);
6878
}
6979

7080
return $result;

app/code/Magento/Elasticsearch/SearchAdapter/Dynamic/DataProvider.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Magento\Elasticsearch\SearchAdapter\QueryAwareInterface;
99
use Magento\Elasticsearch\SearchAdapter\QueryContainer;
1010
use Magento\Framework\App\ObjectManager;
11+
use Magento\Framework\Search\Dynamic\EntityStorage;
1112
use Psr\Log\LoggerInterface;
1213

1314
/**
@@ -18,6 +19,11 @@
1819
*/
1920
class DataProvider implements \Magento\Framework\Search\Dynamic\DataProviderInterface, QueryAwareInterface
2021
{
22+
/**
23+
* Default field name used to aggregate data
24+
*/
25+
private const DEFAULT_AGGREGATION_FIELD = 'price';
26+
2127
/**
2228
* @var \Magento\Elasticsearch\SearchAdapter\ConnectionManager
2329
* @since 100.1.0
@@ -90,6 +96,11 @@ class DataProvider implements \Magento\Framework\Search\Dynamic\DataProviderInte
9096
*/
9197
private $logger;
9298

99+
/**
100+
* @var string
101+
*/
102+
private $aggregationFieldName;
103+
93104
/**
94105
* @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager
95106
* @param \Magento\Elasticsearch\Model\Adapter\FieldMapperInterface $fieldMapper
@@ -102,6 +113,7 @@ class DataProvider implements \Magento\Framework\Search\Dynamic\DataProviderInte
102113
* @param \Magento\Framework\App\ScopeResolverInterface $scopeResolver
103114
* @param QueryContainer|null $queryContainer
104115
* @param LoggerInterface|null $logger
116+
* @param string|null $aggregationFieldName
105117
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
106118
*/
107119
public function __construct(
@@ -115,7 +127,8 @@ public function __construct(
115127
$indexerId,
116128
\Magento\Framework\App\ScopeResolverInterface $scopeResolver,
117129
QueryContainer $queryContainer = null,
118-
LoggerInterface $logger = null
130+
LoggerInterface $logger = null,
131+
?string $aggregationFieldName = null
119132
) {
120133
$this->connectionManager = $connectionManager;
121134
$this->fieldMapper = $fieldMapper;
@@ -128,6 +141,7 @@ public function __construct(
128141
$this->scopeResolver = $scopeResolver;
129142
$this->queryContainer = $queryContainer;
130143
$this->logger = $logger ?? ObjectManager::getInstance()->get(LoggerInterface::class);
144+
$this->aggregationFieldName = $aggregationFieldName ?? self::DEFAULT_AGGREGATION_FIELD;
131145
}
132146

133147
/**
@@ -143,7 +157,7 @@ public function getRange()
143157
* @inheritdoc
144158
* @since 100.1.0
145159
*/
146-
public function getAggregations(\Magento\Framework\Search\Dynamic\EntityStorage $entityStorage)
160+
public function getAggregations(EntityStorage $entityStorage)
147161
{
148162
$aggregations = [
149163
'count' => 0,
@@ -154,7 +168,7 @@ public function getAggregations(\Magento\Framework\Search\Dynamic\EntityStorage
154168

155169
$query = $this->getBasicSearchQuery($entityStorage);
156170

157-
$fieldName = $this->fieldMapper->getFieldName('price');
171+
$fieldName = $this->fieldMapper->getFieldName($this->aggregationFieldName);
158172
$query['body']['aggregations'] = [
159173
'prices' => [
160174
'extended_stats' => [
@@ -188,10 +202,10 @@ public function getAggregations(\Magento\Framework\Search\Dynamic\EntityStorage
188202
public function getInterval(
189203
\Magento\Framework\Search\Request\BucketInterface $bucket,
190204
array $dimensions,
191-
\Magento\Framework\Search\Dynamic\EntityStorage $entityStorage
205+
EntityStorage $entityStorage
192206
) {
193207
$entityIds = $entityStorage->getSource();
194-
$fieldName = $this->fieldMapper->getFieldName('price');
208+
$fieldName = $this->fieldMapper->getFieldName($this->aggregationFieldName);
195209
$dimension = current($dimensions);
196210
$storeId = $this->scopeResolver->getScope($dimension->getValue())->getId();
197211

@@ -212,7 +226,7 @@ public function getAggregation(
212226
\Magento\Framework\Search\Request\BucketInterface $bucket,
213227
array $dimensions,
214228
$range,
215-
\Magento\Framework\Search\Dynamic\EntityStorage $entityStorage
229+
EntityStorage $entityStorage
216230
) {
217231
$query = $this->getBasicSearchQuery($entityStorage);
218232

@@ -277,12 +291,12 @@ public function prepareData($range, array $dbRanges)
277291
* but for now it's a question of backward compatibility as this class may be used somewhere else
278292
* by extension developers and we can't guarantee that they'll pass a query into constructor.
279293
*
280-
* @param \Magento\Framework\Search\Dynamic\EntityStorage $entityStorage
294+
* @param EntityStorage $entityStorage
281295
* @param array $dimensions
282296
* @return array
283297
*/
284298
private function getBasicSearchQuery(
285-
\Magento\Framework\Search\Dynamic\EntityStorage $entityStorage,
299+
EntityStorage $entityStorage,
286300
array $dimensions = []
287301
) {
288302
if (null !== $this->queryContainer) {

app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Aggregation/DataProviderFactoryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function testCreateQueryAwareDataProvider()
9191
->getMock();
9292
$this->objectManager->expects($this->once())
9393
->method('create')
94-
->with($this->isType('string'), ['queryContainer' => $queryContainer])
94+
->with($this->isType('string'), ['queryContainer' => $queryContainer, 'aggregationFieldName' => null])
9595
->willReturn($recreatedDataProvider);
9696
$result = $this->factory->create($dataProvider, $queryContainer);
9797
$this->assertNotSame($dataProvider, $result);

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,35 @@ function ($a) {
111111
$this->assertEquals($expectedOptions, $priceAggregation['options']);
112112
}
113113

114+
/**
115+
* @magentoApiDataFixture Magento/Catalog/_files/products_for_search_with_custom_price_attribute.php
116+
*/
117+
public function testAggregationCustomPriceAttribute()
118+
{
119+
$query = $this->getGraphQlQuery(
120+
'"search_product_1", "search_product_2", "search_product_3", "search_product_4" ,"search_product_5"'
121+
);
122+
$result = $this->graphQlQuery($query);
123+
124+
$this->assertArrayNotHasKey('errors', $result);
125+
$this->assertArrayHasKey('aggregations', $result['products']);
126+
127+
$priceAggregation = array_filter(
128+
$result['products']['aggregations'],
129+
function ($a) {
130+
return $a['attribute_code'] == 'product_price_attribute_bucket';
131+
}
132+
);
133+
$this->assertNotEmpty($priceAggregation);
134+
$priceAggregation = reset($priceAggregation);
135+
$this->assertEquals(2, $priceAggregation['count']);
136+
$expectedOptions = [
137+
['label' => '0_1000', 'value'=> '0_1000', 'count' => '3'],
138+
['label' => '1000_2000', 'value'=> '1000_2000', 'count' => '2']
139+
];
140+
$this->assertEquals($expectedOptions, $priceAggregation['options']);
141+
}
142+
114143
private function getGraphQlQuery(string $skus)
115144
{
116145
return <<<QUERY

0 commit comments

Comments
 (0)