Skip to content

Commit 471d76b

Browse files
authored
Merge pull request #1533 from algolia/feature/MAGE-935
Feature/mage 935 - handle toggle virtual / standard replica on individual attributes
2 parents 7418962 + 6037378 commit 471d76b

File tree

4 files changed

+116
-66
lines changed

4 files changed

+116
-66
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface ReplicaManagerInterface
1212
/**
1313
* Configure replicas in Algolia based on the sorting configuration in Magento
1414
*
15-
* @param string $indexName Could be tmp (legacy impl)
15+
* @param string $primaryIndexName Could be tmp (legacy impl)
1616
* @param int $storeId
1717
* @param array<string, mixed> $primaryIndexSettings
1818
* @return void
@@ -22,5 +22,5 @@ interface ReplicaManagerInterface
2222
* @throws LocalizedException
2323
* @throws NoSuchEntityException
2424
*/
25-
public function handleReplicas(string $indexName, int $storeId, array $primaryIndexSettings): void;
25+
public function handleReplicas(string $primaryIndexName, int $storeId, array $primaryIndexSettings): void;
2626
}

Helper/Entity/ProductHelper.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,22 +363,22 @@ public function setSettings(string $indexName, string $indexNameTmp, int $storeI
363363

364364
$this->algoliaHelper->setSettings($indexName, $indexSettings, false, true);
365365
$this->logger->log('Settings: ' . json_encode($indexSettings));
366-
if ($saveToTmpIndicesToo === true) {
366+
if ($saveToTmpIndicesToo) {
367367
$this->algoliaHelper->setSettings($indexNameTmp, $indexSettings, false, true, $indexName);
368368
$this->logger->log('Pushing the same settings to TMP index as well');
369369
}
370370

371371
$this->setFacetsQueryRules($indexName);
372-
if ($saveToTmpIndicesToo === true) {
372+
if ($saveToTmpIndicesToo) {
373373
$this->setFacetsQueryRules($indexNameTmp);
374374
}
375375

376-
/*
377-
* Handle replicas
378-
*/
379376
$this->replicaManager->handleReplicas($indexName, $storeId, $indexSettings);
377+
if ($saveToTmpIndicesToo) {
378+
$this->replicaManager->handleReplicas($indexNameTmp, $storeId, $indexSettings);
379+
}
380380

381-
if ($saveToTmpIndicesToo === true) {
381+
if ($saveToTmpIndicesToo) {
382382
try {
383383
$this->algoliaHelper->copySynonyms($indexName, $indexNameTmp);
384384
$this->algoliaHelper->waitLastTask();

Model/IndicesConfigurator.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ public function __construct(
7878
/**
7979
* @param int $storeId
8080
* @param bool $useTmpIndex
81-
*
81+
* @return void
8282
* @throws AlgoliaException
83+
* @throws \Magento\Framework\Exception\LocalizedException
84+
* @throws \Magento\Framework\Exception\NoSuchEntityException
8385
*/
84-
public function saveConfigurationToAlgolia($storeId, $useTmpIndex = false)
86+
public function saveConfigurationToAlgolia(int $storeId, bool $useTmpIndex = false): void
8587
{
8688
$logEventName = 'Save configuration to Algolia for store: ' . $this->logger->getStoreName($storeId);
8789
$this->logger->start($logEventName);
@@ -210,10 +212,12 @@ protected function setAdditionalSectionsSettings($storeId)
210212
/**
211213
* @param int $storeId
212214
* @param bool $useTmpIndex
213-
*
215+
* @return void
214216
* @throws AlgoliaException
217+
* @throws \Magento\Framework\Exception\LocalizedException
218+
* @throws \Magento\Framework\Exception\NoSuchEntityException
215219
*/
216-
protected function setProductsSettings($storeId, $useTmpIndex)
220+
protected function setProductsSettings(int $storeId, bool $useTmpIndex): void
217221
{
218222
$this->logger->start('Pushing settings for products indices.');
219223

Model/Product/ReplicaManager.php

Lines changed: 100 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException;
1010
use Algolia\AlgoliaSearch\Helper\AlgoliaHelper;
1111
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
12+
use Algolia\AlgoliaSearch\Helper\Logger;
1213
use Algolia\AlgoliaSearch\Registry\ReplicaState;
1314
use Magento\Framework\Exception\LocalizedException;
1415
use Magento\Framework\Exception\NoSuchEntityException;
@@ -34,17 +35,22 @@
3435
class ReplicaManager implements ReplicaManagerInterface
3536
{
3637
public const REPLICA_TRANSFORM_MODE_STANDARD = 1;
37-
public const REPLICA_TRANSFORM_MODE_VIRTUAL = 2;
38-
public const REPLICA_TRANSFORM_MODE_ACTUAL = 3;
38+
public const REPLICA_TRANSFORM_MODE_VIRTUAL = 2;
39+
public const REPLICA_TRANSFORM_MODE_ACTUAL = 3;
40+
41+
protected const _DEBUG = true;
3942

4043
protected array $_algoliaReplicaConfig = [];
4144
protected array $_magentoReplicaPossibleConfig = [];
4245

4346
public function __construct(
44-
protected ConfigHelper $configHelper,
47+
protected ConfigHelper $configHelper,
4548
protected AlgoliaHelper $algoliaHelper,
46-
protected ReplicaState $replicaState
47-
) {}
49+
protected ReplicaState $replicaState,
50+
protected Logger $logger
51+
)
52+
{
53+
}
4854

4955
/**
5056
* Evaluate the replica state of the index for a given store and determine
@@ -54,61 +60,76 @@ public function __construct(
5460
* @throws NoSuchEntityException
5561
* @throws LocalizedException
5662
*/
57-
protected function hasReplicaConfigurationChanged(string $indexName, int $storeId): bool {
58-
$old = $this->getMagentoReplicaConfigurationFromAlgolia($indexName, $storeId);
59-
$new = $this->transformSortingIndicesToReplicaSetting($this->configHelper->getSortingIndices($indexName, $storeId));
63+
protected function hasReplicaConfigurationChanged(string $primaryIndexName, int $storeId): bool
64+
{
65+
$old = $this->getMagentoReplicaConfigurationFromAlgolia($primaryIndexName, $storeId);
66+
$new = $this->transformSortingIndicesToReplicaSetting($this->configHelper->getSortingIndices($primaryIndexName, $storeId));
6067
sort($old);
6168
sort($new);
6269
return $old !== $new;
6370
}
6471

65-
protected function getReplicaConfigurationFromAlgolia($indexName, bool $refreshCache = false)
72+
protected function getReplicaConfigurationFromAlgolia($primaryIndexName, bool $refreshCache = false)
6673
{
67-
if ($refreshCache || !isset($this->_algoliaReplicaConfig[$indexName])) {
68-
$currentSettings = $this->algoliaHelper->getSettings($indexName);
69-
$this->_algoliaReplicaConfig[$indexName] = array_key_exists('replicas', $currentSettings)
74+
if ($refreshCache || !isset($this->_algoliaReplicaConfig[$primaryIndexName])) {
75+
$currentSettings = $this->algoliaHelper->getSettings($primaryIndexName);
76+
$this->_algoliaReplicaConfig[$primaryIndexName] = array_key_exists('replicas', $currentSettings)
7077
? $currentSettings['replicas']
7178
: [];
7279
}
73-
return $this->_algoliaReplicaConfig[$indexName];
80+
return $this->_algoliaReplicaConfig[$primaryIndexName];
7481
}
7582

76-
protected function clearAlgoliaReplicaSettingCache($indexName = null): void
83+
protected function clearAlgoliaReplicaSettingCache($primaryIndexName = null): void
7784
{
78-
if (is_null($indexName)) {
85+
if (is_null($primaryIndexName)) {
7986
$this->_algoliaReplicaConfig = [];
80-
}
81-
else
82-
{
83-
unset($this->_algoliaReplicaConfig[$indexName]);
87+
} else {
88+
unset($this->_algoliaReplicaConfig[$primaryIndexName]);
8489
}
8590
}
8691

8792
/**
8893
* Obtain the replica configuration from Algolia but only those indices that are
8994
* relevant to the Magento integration
9095
*
91-
* @param string $indexName
96+
* @param string $primaryIndexName
9297
* @param int $storeId
9398
* @return string[]
9499
* @throws LocalizedException
95100
* @throws NoSuchEntityException
96101
*/
97-
protected function getMagentoReplicaConfigurationFromAlgolia(string $indexName, int $storeId): array
102+
protected function getMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName, int $storeId): array
98103
{
99-
$algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($indexName);
100-
$magentoReplicas = $this->getPossibleMagentoReplicaSettings($indexName, $storeId);
104+
$algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName);
105+
$magentoReplicas = $this->getPossibleMagentoReplicaSettings($primaryIndexName, $algoliaReplicas);
101106
return array_values(array_intersect($magentoReplicas, $algoliaReplicas));
102107
}
103108

109+
/**
110+
* Replicas will be considered Magento managed if they are prefixed with the primary index name
111+
* @param string $baseIndexName
112+
* @param string[] $algoliaReplicas
113+
* @return string[]
114+
*/
115+
protected function getPossibleMagentoReplicaSettings(string $baseIndexName, array $algoliaReplicas): array
116+
{
117+
return array_filter(
118+
$algoliaReplicas,
119+
function ($algoliaReplicaSetting) use ($baseIndexName) {
120+
return str_starts_with($this->getBareIndexNameFromReplicaSetting($algoliaReplicaSetting), $baseIndexName);
121+
}
122+
);
123+
}
124+
104125
/**
105126
* @throws NoSuchEntityException
106127
* @throws LocalizedException
107128
*/
108-
protected function getNonMagentoReplicaConfigurationFromAlgolia(string $indexName, int $storeId): array
129+
protected function getNonMagentoReplicaConfigurationFromAlgolia(string $primaryIndexName, int $storeId): array
109130
{
110-
$algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($indexName);
111-
$magentoReplicas = $this->getPossibleMagentoReplicaSettings($indexName, $storeId);
131+
$algoliaReplicas = $this->getReplicaConfigurationFromAlgolia($primaryIndexName);
132+
$magentoReplicas = $this->getPossibleMagentoReplicaSettings($primaryIndexName, $algoliaReplicas);
112133
return array_diff($algoliaReplicas, $magentoReplicas);
113134
}
114135

@@ -119,17 +140,17 @@ protected function getNonMagentoReplicaConfigurationFromAlgolia(string $indexNam
119140
*/
120141
protected function transformSortingIndicesToReplicaSetting(
121142
array $sortingIndices,
122-
int $mode = self::REPLICA_TRANSFORM_MODE_ACTUAL
143+
int $mode = self::REPLICA_TRANSFORM_MODE_ACTUAL
123144
): array
124145
{
125146
return array_map(
126-
function($sort) use ($mode) {
147+
function ($sort) use ($mode) {
127148
$replica = $sort['name'];
128149
if (
129150
$mode === self::REPLICA_TRANSFORM_MODE_VIRTUAL
130151
|| array_key_exists('virtualReplica', $sort)
131-
&& $sort['virtualReplica']
132-
&& $mode === self::REPLICA_TRANSFORM_MODE_ACTUAL
152+
&& $sort['virtualReplica']
153+
&& $mode === self::REPLICA_TRANSFORM_MODE_ACTUAL
133154
) {
134155
$replica = "virtual($replica)";
135156
}
@@ -143,21 +164,21 @@ function($sort) use ($mode) {
143164
* In order to avoid interfering with replicas configured directly in the Algolia dashboard,
144165
* we must know which replica indices are Magento managed and which are not.
145166
*
146-
* @param string $indexName
167+
* @param string $primaryIndexName
147168
* @param int $storeId
148169
* @param bool $refreshCache
149170
* @return array
150171
* @throws LocalizedException
151172
* @throws NoSuchEntityException
152173
*/
153-
protected function getPossibleMagentoReplicaSettings(string $indexName, int $storeId, bool $refreshCache = false): array
174+
protected function getPossibleMagentoReplicaSettingsFromConfig(string $primaryIndexName, int $storeId, bool $refreshCache = false): array
154175
{
155176
if ($refreshCache || !isset($this->_magentoReplicaPossibleConfig[$storeId])) {
156177
//TODO: Determine whether it is necessary to merge the new configuration on an update when checking against Algolia
157178
$sortConfig = $this->replicaState->isStateChanged()
158179
? array_merge($this->replicaState->getOriginalSortConfiguration(), $this->replicaState->getUpdatedSortConfiguration())
159180
: null;
160-
$sortingIndices = $this->configHelper->getSortingIndices($indexName, $storeId, null, $sortConfig);
181+
$sortingIndices = $this->configHelper->getSortingIndices($primaryIndexName, $storeId, null, $sortConfig);
161182
$this->_magentoReplicaPossibleConfig[$storeId] = array_merge(
162183
$this->transformSortingIndicesToReplicaSetting($sortingIndices, self::REPLICA_TRANSFORM_MODE_STANDARD),
163184
$this->transformSortingIndicesToReplicaSetting($sortingIndices, self::REPLICA_TRANSFORM_MODE_VIRTUAL)
@@ -169,55 +190,80 @@ protected function getPossibleMagentoReplicaSettings(string $indexName, int $sto
169190
/**
170191
* @inheritDoc
171192
*/
172-
public function handleReplicas(string $indexName, int $storeId, array $primaryIndexSettings): void
193+
public function handleReplicas(string $primaryIndexName, int $storeId, array $primaryIndexSettings): void
173194
{
174195
// TODO: Determine if InstantSearch is a hard requirement (i.e. headless implementations may still need replicas)
175196
if ($this->configHelper->isInstantEnabled($storeId)
176-
&& $this->hasReplicaConfigurationChanged($indexName, $storeId))
177-
{
197+
&& $this->hasReplicaConfigurationChanged($primaryIndexName, $storeId)) {
178198
// TODO: Handle ranking adjustments when toggling virtual vs standard replicas
179-
$addedReplicas = $this->setReplicasOnPrimaryIndex($indexName, $storeId);
180-
$this->configureRanking($indexName, $storeId, $addedReplicas, $primaryIndexSettings);
199+
$addedReplicas = $this->setReplicasOnPrimaryIndex($primaryIndexName, $storeId);
200+
$this->configureRanking($primaryIndexName, $storeId, $addedReplicas, $primaryIndexSettings);
181201
}
182202
}
183203

184204
/**
185-
* @param $indexName
205+
* @param $primaryIndexName
186206
* @param int $storeId
187-
* @return string[] Replicas added by this operation
207+
* @return string[] Replicas added or modified by this operation
188208
* @throws LocalizedException
189209
* @throws NoSuchEntityException
190210
* @throws AlgoliaException
191211
*/
192212
protected function setReplicasOnPrimaryIndex($indexName, int $storeId): array
193213
{
194214
$sortingIndices = $this->configHelper->getSortingIndices($indexName, $storeId);
195-
$newMagentoReplicas = $this->transformSortingIndicesToReplicaSetting($sortingIndices);
196-
$oldMagentoReplicas = $this->getMagentoReplicaConfigurationFromAlgolia($indexName, $storeId);
197-
$nonMagentoReplicas = $this->getNonMagentoReplicaConfigurationFromAlgolia($indexName, $storeId);
198-
$oldMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($oldMagentoReplicas);
199-
$newMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($newMagentoReplicas);
215+
$newMagentoReplicasSetting = $this->transformSortingIndicesToReplicaSetting($sortingIndices);
216+
$oldMagentoReplicasSetting = $this->getMagentoReplicaConfigurationFromAlgolia($indexName, $storeId);
217+
$nonMagentoReplicasSetting = $this->getNonMagentoReplicaConfigurationFromAlgolia($indexName, $storeId);
218+
$oldMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($oldMagentoReplicasSetting);
219+
$newMagentoReplicaIndices = $this->getBareIndexNamesFromReplicaSetting($newMagentoReplicasSetting);
220+
200221
$replicasToDelete = array_diff($oldMagentoReplicaIndices, $newMagentoReplicaIndices);
201-
// TODO: Refactor for virtual / standard toggle - not just added replica indices require ranking config
202222
$replicasToAdd = array_diff($newMagentoReplicaIndices, $oldMagentoReplicaIndices);
203-
$this->algoliaHelper->setSettings($indexName, ['replicas' => array_merge($newMagentoReplicas, $nonMagentoReplicas)]);
223+
$replicasToRank = $this->getBareIndexNamesFromReplicaSetting(array_diff($newMagentoReplicasSetting, $oldMagentoReplicasSetting));
224+
$replicasToUpdate = array_diff($replicasToRank, $replicasToAdd);
225+
226+
$this->algoliaHelper->setSettings(
227+
$indexName,
228+
['replicas' => array_merge($newMagentoReplicasSetting, $nonMagentoReplicasSetting)]
229+
);
204230
$setReplicasTaskId = $this->algoliaHelper->getLastTaskId();
205231
$this->algoliaHelper->waitLastTask($indexName, $setReplicasTaskId);
206232
$this->clearAlgoliaReplicaSettingCache($indexName);
207233
$this->deleteReplicas($replicasToDelete);
208-
return $replicasToAdd;
234+
235+
if (self::_DEBUG) {
236+
$this->logger->log(
237+
"Replicas configured on $indexName for store $storeId: "
238+
. count($replicasToAdd) . ' added, '
239+
. count($replicasToUpdate) . ' updated, '
240+
. count($replicasToDelete) . ' deleted'
241+
);
242+
}
243+
244+
// include both added and updated replica indices
245+
return $replicasToRank;
209246
}
210247

211-
function getBareIndexNamesFromReplicaSetting(array $replicas): array
248+
/**
249+
* @param string[] $replicas
250+
* @return string[]
251+
*/
252+
protected function getBareIndexNamesFromReplicaSetting(array $replicas): array
212253
{
213254
return array_map(
214-
function($str) {
215-
return preg_replace('/.*\((.*)\).*/', '$1', $str);
255+
function ($str) {
256+
return $this->getBareIndexNameFromReplicaSetting($str);
216257
},
217258
$replicas
218259
);
219260
}
220261

262+
protected function getBareIndexNameFromReplicaSetting(string $replicaSetting): string
263+
{
264+
return preg_replace('/.*\((.*)\).*/', '$1', $replicaSetting);
265+
}
266+
221267
protected function deleteReplicas(array $replicasToDelete): void
222268
{
223269
foreach ($replicasToDelete as $deletedReplica) {
@@ -227,7 +273,7 @@ protected function deleteReplicas(array $replicasToDelete): void
227273

228274
/**
229275
* Apply ranking settings to the added replica indices
230-
* @param string $indexName
276+
* @param string $primaryIndexName
231277
* @param int $storeId
232278
* @param string[] $replicas
233279
* @param array<string, mixed> $primaryIndexSettings
@@ -236,9 +282,9 @@ protected function deleteReplicas(array $replicasToDelete): void
236282
* @throws LocalizedException
237283
* @throws NoSuchEntityException
238284
*/
239-
protected function configureRanking(string $indexName, int $storeId, array $replicas, array $primaryIndexSettings): void
285+
protected function configureRanking(string $primaryIndexName, int $storeId, array $replicas, array $primaryIndexSettings): void
240286
{
241-
$sortingIndices = $this->configHelper->getSortingIndices($indexName, $storeId);
287+
$sortingIndices = $this->configHelper->getSortingIndices($primaryIndexName, $storeId);
242288
$replicaDetails = array_filter(
243289
$sortingIndices,
244290
function($replica) use ($replicas) {

0 commit comments

Comments
 (0)