Skip to content

Commit 333fa67

Browse files
committed
MAGE-935 Modify change state tracking to utilize opinionated prefixing and support granular virtual toggle
1 parent 7418962 commit 333fa67

File tree

4 files changed

+114
-65
lines changed

4 files changed

+114
-65
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: 98 additions & 53 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,20 +190,19 @@ 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
187207
* @return string[] Replicas added by this operation
188208
* @throws LocalizedException
@@ -192,32 +212,57 @@ public function handleReplicas(string $indexName, int $storeId, array $primaryIn
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
222+
$replicasToUpdate = array_diff($newMagentoReplicasSetting, $oldMagentoReplicasSetting);
223+
$replicasToUpdate = $this->getBareIndexNamesFromReplicaSetting($replicasToUpdate);
202224
$replicasToAdd = array_diff($newMagentoReplicaIndices, $oldMagentoReplicaIndices);
203-
$this->algoliaHelper->setSettings($indexName, ['replicas' => array_merge($newMagentoReplicas, $nonMagentoReplicas)]);
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+
return $replicasToUpdate;
209245
}
210246

211-
function getBareIndexNamesFromReplicaSetting(array $replicas): array
247+
/**
248+
* @param string[] $replicas
249+
* @return string[]
250+
*/
251+
protected function getBareIndexNamesFromReplicaSetting(array $replicas): array
212252
{
213253
return array_map(
214-
function($str) {
215-
return preg_replace('/.*\((.*)\).*/', '$1', $str);
254+
function ($str) {
255+
return $this->getBareIndexNameFromReplicaSetting($str);
216256
},
217257
$replicas
218258
);
219259
}
220260

261+
protected function getBareIndexNameFromReplicaSetting(string $replicaSetting): string
262+
{
263+
return preg_replace('/.*\((.*)\).*/', '$1', $replicaSetting);
264+
}
265+
221266
protected function deleteReplicas(array $replicasToDelete): void
222267
{
223268
foreach ($replicasToDelete as $deletedReplica) {
@@ -227,7 +272,7 @@ protected function deleteReplicas(array $replicasToDelete): void
227272

228273
/**
229274
* Apply ranking settings to the added replica indices
230-
* @param string $indexName
275+
* @param string $primaryIndexName
231276
* @param int $storeId
232277
* @param string[] $replicas
233278
* @param array<string, mixed> $primaryIndexSettings
@@ -236,9 +281,9 @@ protected function deleteReplicas(array $replicasToDelete): void
236281
* @throws LocalizedException
237282
* @throws NoSuchEntityException
238283
*/
239-
protected function configureRanking(string $indexName, int $storeId, array $replicas, array $primaryIndexSettings): void
284+
protected function configureRanking(string $primaryIndexName, int $storeId, array $replicas, array $primaryIndexSettings): void
240285
{
241-
$sortingIndices = $this->configHelper->getSortingIndices($indexName, $storeId);
286+
$sortingIndices = $this->configHelper->getSortingIndices($primaryIndexName, $storeId);
242287
$replicaDetails = array_filter(
243288
$sortingIndices,
244289
function($replica) use ($replicas) {

0 commit comments

Comments
 (0)