Skip to content

Commit 6c7758c

Browse files
MC-33952: Elasticsearch contains wrong values for attribute option in store view
1 parent 8d90d12 commit 6c7758c

File tree

5 files changed

+289
-67
lines changed

5 files changed

+289
-67
lines changed

app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ private function convertToProductData(int $productId, array $indexData, int $sto
190190
$attributeValues = [$productId => $attributeValues];
191191
}
192192
$attributeValues = $this->prepareAttributeValues($productId, $attribute, $attributeValues, $storeId);
193-
$productAttributes += $this->convertAttribute($attribute, $attributeValues);
193+
$productAttributes += $this->convertAttribute($attribute, $attributeValues, $storeId);
194194
}
195195

196196
return $productAttributes;
@@ -201,9 +201,10 @@ private function convertToProductData(int $productId, array $indexData, int $sto
201201
*
202202
* @param Attribute $attribute
203203
* @param array $attributeValues
204+
* @param int $storeId
204205
* @return array
205206
*/
206-
private function convertAttribute(Attribute $attribute, array $attributeValues): array
207+
private function convertAttribute(Attribute $attribute, array $attributeValues, int $storeId): array
207208
{
208209
$productAttributes = [];
209210

@@ -212,7 +213,7 @@ private function convertAttribute(Attribute $attribute, array $attributeValues):
212213
$productAttributes[$attribute->getAttributeCode()] = $retrievedValue;
213214

214215
if ($attribute->getIsSearchable()) {
215-
$attributeLabels = $this->getValuesLabels($attribute, $attributeValues);
216+
$attributeLabels = $this->getValuesLabels($attribute, $attributeValues, $storeId);
216217
$retrievedLabel = $this->retrieveFieldValue($attributeLabels);
217218
if ($retrievedLabel) {
218219
$productAttributes[$attribute->getAttributeCode() . '_value'] = $retrievedLabel;
@@ -299,20 +300,21 @@ private function isAttributeDate(Attribute $attribute): bool
299300
*
300301
* @param Attribute $attribute
301302
* @param array $attributeValues
303+
* @param int $storeId
302304
* @return array
303305
*/
304-
private function getValuesLabels(Attribute $attribute, array $attributeValues): array
306+
private function getValuesLabels(Attribute $attribute, array $attributeValues, int $storeId): array
305307
{
306308
$attributeLabels = [];
307309

308-
$options = $this->getAttributeOptions($attribute);
310+
$options = $this->getAttributeOptions($attribute, $storeId);
309311
if (empty($options)) {
310312
return $attributeLabels;
311313
}
312314

313-
foreach ($attributeValues as $attributeValue) {
314-
if (isset($options[$attributeValue])) {
315-
$attributeLabels[] = $options[$attributeValue]->getLabel();
315+
foreach ($options as $option) {
316+
if (\in_array($option['value'], $attributeValues)) {
317+
$attributeLabels[] = $option['label'];
316318
}
317319
}
318320

@@ -323,20 +325,23 @@ private function getValuesLabels(Attribute $attribute, array $attributeValues):
323325
* Retrieve options for attribute
324326
*
325327
* @param Attribute $attribute
328+
* @param int $storeId
326329
* @return array
327330
*/
328-
private function getAttributeOptions(Attribute $attribute): array
331+
private function getAttributeOptions(Attribute $attribute, int $storeId): array
329332
{
330-
if (!isset($this->attributeOptionsCache[$attribute->getId()])) {
331-
$options = $attribute->getOptions() ?? [];
332-
$optionsByValue = [];
333-
foreach ($options as $option) {
334-
$optionsByValue[$option->getValue()] = $option;
335-
}
336-
$this->attributeOptionsCache[$attribute->getId()] = $optionsByValue;
333+
if (!isset($this->attributeOptionsCache[$storeId][$attribute->getId()])) {
334+
$attributeStoreId = $attribute->getStoreId();
335+
/**
336+
* Load array format of options.
337+
* $attribute->getOptions() loads options into data objects which can be costly.
338+
*/
339+
$options = $attribute->usesSource() ? $attribute->setStoreId($storeId)->getSource()->getAllOptions() : [];
340+
$this->attributeOptionsCache[$storeId][$attribute->getId()] = $options;
341+
$attribute->setStoreId($attributeStoreId);
337342
}
338343

339-
return $this->attributeOptionsCache[$attribute->getId()];
344+
return $this->attributeOptionsCache[$storeId][$attribute->getId()];
340345
}
341346

342347
/**

app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
1212
use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider;
1313
use Magento\Eav\Api\Data\AttributeOptionInterface;
14+
use Magento\Eav\Model\Entity\Attribute\Source\SourceInterface;
1415
use Magento\Elasticsearch\Model\Adapter\BatchDataMapper\ProductDataMapper;
1516
use Magento\Elasticsearch\Model\Adapter\Document\Builder;
1617
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
@@ -199,11 +200,17 @@ public function testGetMapWithOptions()
199200
*/
200201
private function getAttribute(array $attributeData): MockObject
201202
{
202-
$attributeMock = $this->createMock(Attribute::class);
203-
$attributeMock->method('getAttributeCode')->willReturn($attributeData['code']);
204-
$attributeMock->method('getBackendType')->willReturn($attributeData['backendType']);
205-
$attributeMock->method('getFrontendInput')->willReturn($attributeData['frontendInput']);
206-
$attributeMock->method('getIsSearchable')->willReturn($attributeData['is_searchable']);
203+
$attributeMock = $this->createPartialMock(
204+
Attribute::class,
205+
[
206+
'getSource',
207+
'getOptions',
208+
]
209+
);
210+
211+
$sourceMock = $this->createMock(SourceInterface::class);
212+
$attributeMock->method('getSource')->willReturn($sourceMock);
213+
$sourceMock->method('getAllOptions')->willReturn($attributeData['options'] ?? []);
207214
$options = [];
208215
foreach ($attributeData['options'] as $option) {
209216
$optionMock = $this->getMockForAbstractClass(AttributeOptionInterface::class);
@@ -212,6 +219,8 @@ private function getAttribute(array $attributeData): MockObject
212219
$options[] = $optionMock;
213220
}
214221
$attributeMock->method('getOptions')->willReturn($options);
222+
unset($attributeData['options']);
223+
$attributeMock->setData($attributeData);
215224

216225
return $attributeMock;
217226
}
@@ -226,9 +235,9 @@ public static function mapProvider(): array
226235
'text' => [
227236
10,
228237
[
229-
'code' => 'description',
230-
'backendType' => 'text',
231-
'frontendInput' => 'text',
238+
'attribute_code' => 'description',
239+
'backend_type' => 'text',
240+
'frontend_input' => 'text',
232241
'is_searchable' => false,
233242
'options' => [],
234243
],
@@ -238,9 +247,9 @@ public static function mapProvider(): array
238247
'datetime' => [
239248
10,
240249
[
241-
'code' => 'created_at',
242-
'backendType' => 'datetime',
243-
'frontendInput' => 'date',
250+
'attribute_code' => 'created_at',
251+
'backend_type' => 'datetime',
252+
'frontend_input' => 'date',
244253
'is_searchable' => false,
245254
'options' => [],
246255
],
@@ -251,9 +260,9 @@ public static function mapProvider(): array
251260
'array single value' => [
252261
10,
253262
[
254-
'code' => 'attribute_array',
255-
'backendType' => 'text',
256-
'frontendInput' => 'text',
263+
'attribute_code' => 'attribute_array',
264+
'backend_type' => 'text',
265+
'frontend_input' => 'text',
257266
'is_searchable' => false,
258267
'options' => [],
259268
],
@@ -263,9 +272,9 @@ public static function mapProvider(): array
263272
'array multiple value' => [
264273
10,
265274
[
266-
'code' => 'attribute_array',
267-
'backendType' => 'text',
268-
'frontendInput' => 'text',
275+
'attribute_code' => 'attribute_array',
276+
'backend_type' => 'text',
277+
'frontend_input' => 'text',
269278
'is_searchable' => false,
270279
'options' => [],
271280
],
@@ -275,9 +284,9 @@ public static function mapProvider(): array
275284
'array multiple decimal value' => [
276285
10,
277286
[
278-
'code' => 'decimal_array',
279-
'backendType' => 'decimal',
280-
'frontendInput' => 'text',
287+
'attribute_code' => 'decimal_array',
288+
'backend_type' => 'decimal',
289+
'frontend_input' => 'text',
281290
'is_searchable' => false,
282291
'options' => [],
283292
],
@@ -287,9 +296,9 @@ public static function mapProvider(): array
287296
'array excluded from merge' => [
288297
10,
289298
[
290-
'code' => 'status',
291-
'backendType' => 'int',
292-
'frontendInput' => 'select',
299+
'attribute_code' => 'status',
300+
'backend_type' => 'int',
301+
'frontend_input' => 'select',
293302
'is_searchable' => false,
294303
'options' => [
295304
['value' => '1', 'label' => 'Enabled'],
@@ -302,9 +311,9 @@ public static function mapProvider(): array
302311
'select without options' => [
303312
10,
304313
[
305-
'code' => 'color',
306-
'backendType' => 'text',
307-
'frontendInput' => 'select',
314+
'attribute_code' => 'color',
315+
'backend_type' => 'text',
316+
'frontend_input' => 'select',
308317
'is_searchable' => false,
309318
'options' => [],
310319
],
@@ -314,9 +323,9 @@ public static function mapProvider(): array
314323
'unsearchable select with options' => [
315324
10,
316325
[
317-
'code' => 'color',
318-
'backendType' => 'text',
319-
'frontendInput' => 'select',
326+
'attribute_code' => 'color',
327+
'backend_type' => 'text',
328+
'frontend_input' => 'select',
320329
'is_searchable' => false,
321330
'options' => [
322331
['value' => '44', 'label' => 'red'],
@@ -329,9 +338,9 @@ public static function mapProvider(): array
329338
'searchable select with options' => [
330339
10,
331340
[
332-
'code' => 'color',
333-
'backendType' => 'text',
334-
'frontendInput' => 'select',
341+
'attribute_code' => 'color',
342+
'backend_type' => 'text',
343+
'frontend_input' => 'select',
335344
'is_searchable' => true,
336345
'options' => [
337346
['value' => '44', 'label' => 'red'],
@@ -344,9 +353,9 @@ public static function mapProvider(): array
344353
'composite select with options' => [
345354
10,
346355
[
347-
'code' => 'color',
348-
'backendType' => 'text',
349-
'frontendInput' => 'select',
356+
'attribute_code' => 'color',
357+
'backend_type' => 'text',
358+
'frontend_input' => 'select',
350359
'is_searchable' => true,
351360
'options' => [
352361
['value' => '44', 'label' => 'red'],
@@ -359,9 +368,9 @@ public static function mapProvider(): array
359368
'multiselect without options' => [
360369
10,
361370
[
362-
'code' => 'multicolor',
363-
'backendType' => 'text',
364-
'frontendInput' => 'multiselect',
371+
'attribute_code' => 'multicolor',
372+
'backend_type' => 'text',
373+
'frontend_input' => 'multiselect',
365374
'is_searchable' => false,
366375
'options' => [],
367376
],
@@ -371,9 +380,9 @@ public static function mapProvider(): array
371380
'unsearchable multiselect with options' => [
372381
10,
373382
[
374-
'code' => 'multicolor',
375-
'backendType' => 'text',
376-
'frontendInput' => 'multiselect',
383+
'attribute_code' => 'multicolor',
384+
'backend_type' => 'text',
385+
'frontend_input' => 'multiselect',
377386
'is_searchable' => false,
378387
'options' => [
379388
['value' => '44', 'label' => 'red'],
@@ -386,9 +395,9 @@ public static function mapProvider(): array
386395
'searchable multiselect with options' => [
387396
10,
388397
[
389-
'code' => 'multicolor',
390-
'backendType' => 'text',
391-
'frontendInput' => 'multiselect',
398+
'attribute_code' => 'multicolor',
399+
'backend_type' => 'text',
400+
'frontend_input' => 'multiselect',
392401
'is_searchable' => true,
393402
'options' => [
394403
['value' => '44', 'label' => 'red'],
@@ -401,9 +410,9 @@ public static function mapProvider(): array
401410
'composite multiselect with options' => [
402411
10,
403412
[
404-
'code' => 'multicolor',
405-
'backendType' => 'text',
406-
'frontendInput' => 'multiselect',
413+
'attribute_code' => 'multicolor',
414+
'backend_type' => 'text',
415+
'frontend_input' => 'multiselect',
407416
'is_searchable' => true,
408417
'options' => [
409418
['value' => '44', 'label' => 'red'],
@@ -417,9 +426,9 @@ public static function mapProvider(): array
417426
'excluded attribute' => [
418427
10,
419428
[
420-
'code' => 'price',
421-
'backendType' => 'int',
422-
'frontendInput' => 'int',
429+
'attribute_code' => 'price',
430+
'backend_type' => 'int',
431+
'frontend_input' => 'int',
423432
'is_searchable' => false,
424433
'options' => []
425434
],

0 commit comments

Comments
 (0)