Skip to content

Commit be2aaae

Browse files
committed
MC-31304: [ElasticSearch] Exception on catalog search result page
1 parent c36db34 commit be2aaae

File tree

18 files changed

+774
-20
lines changed

18 files changed

+774
-20
lines changed

app/code/Magento/CatalogSearch/etc/di.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<plugin name="catalogsearchFulltextIndexerStoreGroup" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store\Group"/>
4848
</type>
4949
<type name="Magento\Catalog\Model\ResourceModel\Attribute">
50-
<plugin name="catalogsearchFulltextIndexerAttribute" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Attribute"/>
50+
<plugin name="catalogsearchFulltextIndexerAttribute" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Attribute" sortOrder="10"/>
5151
<plugin name="catalogsearchAttributeSearchWeightCache" type="Magento\CatalogSearch\Model\Attribute\SearchWeight"/>
5252
</type>
5353
<type name="Magento\Framework\Search\EntityMetadata" />

app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,23 @@ public function createIndex($index, $settings)
166166
);
167167
}
168168

169+
/**
170+
* Add/update an Elasticsearch index settings.
171+
*
172+
* @param string $index
173+
* @param array $settings
174+
* @return void
175+
*/
176+
public function putIndexSettings(string $index, array $settings): void
177+
{
178+
$this->getClient()->indices()->putSettings(
179+
[
180+
'index' => $index,
181+
'body' => $settings,
182+
]
183+
);
184+
}
185+
169186
/**
170187
* Delete an Elasticsearch index.
171188
*
@@ -348,6 +365,17 @@ private function prepareFieldInfo($fieldInfo)
348365
return $fieldInfo;
349366
}
350367

368+
/**
369+
* Get mapping from Elasticsearch index.
370+
*
371+
* @param array $params
372+
* @return array
373+
*/
374+
public function getMapping(array $params): array
375+
{
376+
return $this->getClient()->indices()->getMapping($params);
377+
}
378+
351379
/**
352380
* Delete mapping in Elasticsearch index
353381
*

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
namespace Magento\Elasticsearch\Model\Adapter;
88

9+
use Magento\Framework\App\ObjectManager;
10+
use Magento\Framework\Stdlib\ArrayManager;
11+
912
/**
1013
* Elasticsearch adapter
1114
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -69,6 +72,11 @@ class Elasticsearch
6972
*/
7073
private $batchDocumentDataMapper;
7174

75+
/**
76+
* @var ArrayManager
77+
*/
78+
private $arrayManager;
79+
7280
/**
7381
* @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager
7482
* @param FieldMapperInterface $fieldMapper
@@ -78,6 +86,7 @@ class Elasticsearch
7886
* @param Index\IndexNameResolver $indexNameResolver
7987
* @param BatchDataMapperInterface $batchDocumentDataMapper
8088
* @param array $options
89+
* @param ArrayManager|null $arrayManager
8190
* @throws \Magento\Framework\Exception\LocalizedException
8291
*/
8392
public function __construct(
@@ -88,7 +97,8 @@ public function __construct(
8897
\Psr\Log\LoggerInterface $logger,
8998
\Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver,
9099
BatchDataMapperInterface $batchDocumentDataMapper,
91-
$options = []
100+
$options = [],
101+
ArrayManager $arrayManager = null
92102
) {
93103
$this->connectionManager = $connectionManager;
94104
$this->fieldMapper = $fieldMapper;
@@ -97,6 +107,8 @@ public function __construct(
97107
$this->logger = $logger;
98108
$this->indexNameResolver = $indexNameResolver;
99109
$this->batchDocumentDataMapper = $batchDocumentDataMapper;
110+
$this->arrayManager = $arrayManager ?:
111+
ObjectManager::getInstance()->get(ArrayManager::class);
100112

101113
try {
102114
$this->client = $this->connectionManager->getConnection($options);
@@ -327,6 +339,44 @@ public function updateAlias($storeId, $mappedIndexerId)
327339
return $this;
328340
}
329341

342+
/**
343+
* Update Elasticsearch mapping for index.
344+
*
345+
* @param int $storeId
346+
* @param string $mappedIndexerId
347+
*
348+
* @return $this
349+
*/
350+
public function updateIndexMapping(int $storeId, string $mappedIndexerId): self
351+
{
352+
$indexName = $this->indexNameResolver->getIndexFromAlias($storeId, $mappedIndexerId);
353+
if (empty($indexName)) {
354+
return $this;
355+
}
356+
357+
$allAttributeTypes = $this->fieldMapper->getAllAttributesTypes([
358+
'entityType' => $mappedIndexerId,
359+
'websiteId' => $storeId,
360+
]);
361+
362+
$mappedAttributes = $this->client->getMapping(['index' => $indexName]);
363+
$pathField = $this->arrayManager->findPath('properties', $mappedAttributes);
364+
$mappedAttributes = $this->arrayManager->get($pathField, $mappedAttributes, $allAttributeTypes);
365+
366+
$settings['index']['mapping']['total_fields']['limit'] = $this->getMappingTotalFieldsLimit($allAttributeTypes);
367+
$this->client->putIndexSettings($indexName, ['settings' => $settings]);
368+
$attrToUpdate = array_diff_key($allAttributeTypes, $mappedAttributes);
369+
if (!empty($attrToUpdate)) {
370+
$this->client->addFieldsMapping(
371+
$attrToUpdate,
372+
$indexName,
373+
$this->clientConfig->getEntityType()
374+
);
375+
}
376+
377+
return $this;
378+
}
379+
330380
/**
331381
* Create new index with mapping.
332382
*

app/code/Magento/Elasticsearch/Model/Config.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Config implements ClientOptionsInterface
4545
protected $scopeConfig;
4646

4747
/**
48-
* @var string
48+
* @var string|null
4949
*/
5050
private $prefix;
5151

@@ -83,7 +83,7 @@ public function __construct(
8383
$this->scopeConfig = $scopeConfig;
8484
$this->clientResolver = $clientResolver;
8585
$this->engineResolver = $engineResolver;
86-
$this->prefix = $prefix ?: $this->clientResolver->getCurrentEngine();
86+
$this->prefix = $prefix;
8787
$this->engineList = $engineList;
8888
}
8989

@@ -101,7 +101,7 @@ public function prepareClientOptions($options = [])
101101
'enableAuth' => $this->getElasticsearchConfigData('enable_auth'),
102102
'username' => $this->getElasticsearchConfigData('username'),
103103
'password' => $this->getElasticsearchConfigData('password'),
104-
'timeout' => $this->getElasticsearchConfigData('server_timeout') ? : self::ELASTICSEARCH_DEFAULT_TIMEOUT,
104+
'timeout' => $this->getElasticsearchConfigData('server_timeout') ?: self::ELASTICSEARCH_DEFAULT_TIMEOUT,
105105
];
106106
$options = array_merge($defaultOptions, $options);
107107
$allowedOptions = array_merge(array_keys($defaultOptions), ['engine']);
@@ -125,7 +125,9 @@ function (string $key) use ($allowedOptions) {
125125
*/
126126
public function getElasticsearchConfigData($field, $storeId = null)
127127
{
128-
return $this->getSearchConfigData($this->prefix . '_' . $field, $storeId);
128+
$searchEngine = $this->prefix ?: $this->clientResolver->getCurrentEngine();
129+
130+
return $this->getSearchConfigData($searchEngine . '_' . $field, $storeId);
129131
}
130132

131133
/**
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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\Elasticsearch\Model\Indexer\Fulltext\Plugin\Category\Product;
9+
10+
use Magento\Catalog\Model\ResourceModel\Attribute as AttributeResourceModel;
11+
use Magento\CatalogSearch\Model\Indexer\Fulltext\Processor;
12+
use Magento\Elasticsearch\Model\Config;
13+
use Magento\Elasticsearch\Model\Indexer\IndexerHandler as ElasticsearchIndexerHandler;
14+
use Magento\Framework\Indexer\DimensionProviderInterface;
15+
use Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory;
16+
use Magento\Framework\Exception\LocalizedException;
17+
18+
/**
19+
* Catalog search indexer plugin for catalog attribute.
20+
*/
21+
class Attribute
22+
{
23+
/**
24+
* @var Config
25+
*/
26+
private $config;
27+
28+
/**
29+
* @var Processor
30+
*/
31+
private $indexerProcessor;
32+
33+
/**
34+
* @var DimensionProviderInterface
35+
*/
36+
private $dimensionProvider;
37+
38+
/**
39+
* @var IndexerHandlerFactory
40+
*/
41+
private $indexerHandlerFactory;
42+
43+
/**
44+
* @param Config $config
45+
* @param Processor $indexerProcessor
46+
* @param DimensionProviderInterface $dimensionProvider
47+
* @param IndexerHandlerFactory $indexerHandlerFactory
48+
*/
49+
public function __construct(
50+
Config $config,
51+
Processor $indexerProcessor,
52+
DimensionProviderInterface $dimensionProvider,
53+
IndexerHandlerFactory $indexerHandlerFactory
54+
) {
55+
$this->config = $config;
56+
$this->indexerProcessor = $indexerProcessor;
57+
$this->dimensionProvider = $dimensionProvider;
58+
$this->indexerHandlerFactory = $indexerHandlerFactory;
59+
}
60+
61+
/**
62+
* Update catalog search indexer mapping if third party search engine is used.
63+
*
64+
* @param AttributeResourceModel $subject
65+
* @param AttributeResourceModel $result
66+
* @return AttributeResourceModel
67+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
68+
* @throws LocalizedException
69+
*/
70+
public function afterSave(
71+
AttributeResourceModel $subject,
72+
AttributeResourceModel $result
73+
): AttributeResourceModel {
74+
$indexer = $this->indexerProcessor->getIndexer();
75+
if ($indexer->isInvalid()
76+
&& !$indexer->isScheduled()
77+
&& $this->config->isElasticsearchEnabled()
78+
) {
79+
$indexerHandler = $this->indexerHandlerFactory->create(['data' => $indexer->getData()]);
80+
if (!$indexerHandler instanceof ElasticsearchIndexerHandler) {
81+
throw new LocalizedException(
82+
__('Created indexer handler must be instance of %1.', ElasticsearchIndexerHandler::class)
83+
);
84+
}
85+
foreach ($this->dimensionProvider->getIterator() as $dimension) {
86+
$indexerHandler->updateIndex($dimension);
87+
}
88+
}
89+
90+
return $result;
91+
}
92+
}

app/code/Magento/Elasticsearch/Model/Indexer/IndexerHandler.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
*/
66
namespace Magento\Elasticsearch\Model\Indexer;
77

8-
use Magento\Framework\Indexer\SaveHandler\IndexerInterface;
9-
use Magento\Framework\Indexer\SaveHandler\Batch;
10-
use Magento\Framework\Indexer\IndexStructureInterface;
118
use Magento\Elasticsearch\Model\Adapter\Elasticsearch as ElasticsearchAdapter;
129
use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver;
1310
use Magento\Framework\App\ScopeResolverInterface;
11+
use Magento\Framework\Indexer\IndexStructureInterface;
12+
use Magento\Framework\Indexer\SaveHandler\Batch;
13+
use Magento\Framework\Indexer\SaveHandler\IndexerInterface;
14+
use Magento\Framework\Search\Request\Dimension;
1415

1516
/**
1617
* Indexer Handler for Elasticsearch engine.
1718
*/
1819
class IndexerHandler implements IndexerInterface
1920
{
2021
/**
21-
* Default batch size
22+
* Size of default batch
2223
*/
2324
const DEFAULT_BATCH_SIZE = 500;
2425

@@ -132,6 +133,21 @@ public function isAvailable($dimensions = [])
132133
return $this->adapter->ping();
133134
}
134135

136+
/**
137+
* Update mapping data for index.
138+
*
139+
* @param Dimension[] $dimensions
140+
* @return IndexerInterface
141+
*/
142+
public function updateIndex(array $dimensions): IndexerInterface
143+
{
144+
$dimension = current($dimensions);
145+
$scopeId = $this->scopeResolver->getScope($dimension->getValue())->getId();
146+
$this->adapter->updateIndexMapping($scopeId, $this->getIndexerId());
147+
148+
return $this;
149+
}
150+
135151
/**
136152
* Returns indexer id.
137153
*

app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ protected function setUp(): void
6969
'create',
7070
'delete',
7171
'putMapping',
72+
'getMapping',
7273
'deleteMapping',
7374
'stats',
7475
'updateAliases',
@@ -533,6 +534,22 @@ public function testDeleteMappingFailure()
533534
);
534535
}
535536

537+
/**
538+
* Test get Elasticsearch mapping process.
539+
*
540+
* @return void
541+
*/
542+
public function testGetMapping(): void
543+
{
544+
$params = ['index' => 'indexName'];
545+
$this->indicesMock->expects($this->once())
546+
->method('getMapping')
547+
->with($params)
548+
->willReturn([]);
549+
550+
$this->model->getMapping($params);
551+
}
552+
536553
/**
537554
* Test query() method
538555
* @return void

0 commit comments

Comments
 (0)