Skip to content

Commit 3be9d74

Browse files
committed
MAGE-938 Add more robust handling of unknown unused replica indices with CLI confirmation as these deletions are potentially risky
1 parent ec32818 commit 3be9d74

File tree

3 files changed

+99
-30
lines changed

3 files changed

+99
-30
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ interface ReplicaManagerInterface
2525
* @throws AlgoliaException
2626
* @throws ExceededRetriesException
2727
* @throws LocalizedException
28-
* @throws NoSuchEntityException
2928
*/
3029
public function syncReplicasToAlgolia(int $storeId, array $primaryIndexSettings): void;
3130

@@ -46,4 +45,14 @@ public function isReplicaSyncEnabled(int $storeId): bool;
4645
* @return int
4746
*/
4847
public function getMaxVirtualReplicasPerIndex() : int;
48+
49+
/**
50+
* For a given store return replicas that do not appear to be managed by Magento
51+
* @param int $storeId
52+
* @return string[]
53+
* @throws NoSuchEntityException
54+
* @throws LocalizedException
55+
* @throws AlgoliaException
56+
*/
57+
public function getUnusedReplicaIndices(int $storeId): array;
4958
}

Console/Command/ReplicaDeleteCommand.php

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
namespace Algolia\AlgoliaSearch\Console\Command;
44

55
use Algolia\AlgoliaSearch\Api\Product\ReplicaManagerInterface;
6+
use Algolia\AlgoliaSearch\Exceptions\BadRequestException;
67
use Algolia\AlgoliaSearch\Service\StoreNameFetcher;
7-
use Magento\Framework\App\Area;
8-
use Magento\Framework\App\State;
98
use Magento\Framework\Console\Cli;
109
use Magento\Store\Model\StoreManagerInterface;
1110
use Symfony\Component\Console\Command\Command;
1211
use Symfony\Component\Console\Input\InputArgument;
1312
use Symfony\Component\Console\Input\InputInterface;
1413
use Symfony\Component\Console\Input\InputOption;
1514
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Console\Question\ConfirmationQuestion;
1616

1717
class ReplicaDeleteCommand extends Command
1818
{
@@ -22,9 +22,9 @@ class ReplicaDeleteCommand extends Command
2222
protected const UNUSED_OPTION_SHORTCUT = 'u';
2323

2424
protected ?OutputInterface $output = null;
25+
protected ?InputInterface $input = null;
2526

2627
public function __construct(
27-
protected State $state,
2828
protected ReplicaManagerInterface $replicaManager,
2929
protected StoreManagerInterface $storeManager,
3030
protected StoreNameFetcher $storeNameFetcher,
@@ -71,13 +71,69 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7171
}
7272

7373
$this->output = $output;
74-
// $this->state->setAreaCode(Area::AREA_ADMINHTML);
74+
$this->input = $input;
7575

76-
$this->deleteReplicas($storeIds, $unused);
76+
if ($unused) {
77+
$unusedReplicas = $this->getUnusedReplicas($storeIds);
78+
if (!$unusedReplicas) {
79+
$output->writeln('<comment>No unused replicas found.</comment>');
80+
return Cli::RETURN_SUCCESS;
81+
}
82+
if (!$this->confirmDeleteUnused($unusedReplicas)) {
83+
return Cli::RETURN_SUCCESS;
84+
}
85+
}
86+
87+
try {
88+
$this->deleteReplicas($storeIds, $unused);
89+
} catch (BadRequestException $e) {
90+
$this->output->writeln("<error>Error encountered while attempting to delete replica: {$e->getMessage()}</error>");
91+
$this->output->writeln('<comment>It is likely that the Magento integration does not manage the index. You should review your application configuration in Algolia.</comment>');
92+
return CLI::RETURN_FAILURE;
93+
}
7794

7895
return Cli::RETURN_SUCCESS;
7996
}
8097

98+
protected function getUnusedReplicas(array $storeIds): array
99+
{
100+
return array_reduce(
101+
$storeIds,
102+
function($allUnused, $storeId) {
103+
$unused = [];
104+
try {
105+
$unused = $this->replicaManager->getUnusedReplicaIndices($storeId);
106+
} catch (\Exception $e) {
107+
$this->output->writeln("<error>Unable to retrieve unused replicas for $storeId: {$e->getMessage()}</error>");
108+
}
109+
return array_unique(array_merge($allUnused, $unused));
110+
},
111+
[]
112+
);
113+
}
114+
115+
/**
116+
* Deleting unused replica indices is potentially risky, especially if they have enabled query suggestions on their index
117+
* Verify with the end user first!
118+
*
119+
* @param array $unusedReplicas
120+
* @return bool
121+
*/
122+
protected function confirmDeleteUnused(array $unusedReplicas): bool
123+
{
124+
$this->output->writeln('<info>The following replicas appear to be unused and will be deleted:</info>');
125+
foreach ($unusedReplicas as $unusedReplica) {
126+
$this->output->writeln('<info> - ' . $unusedReplica . '</info>');
127+
}
128+
$helper = $this->getHelper('question');
129+
$question = new ConfirmationQuestion('<question>Do you want to proceed? (y/n)</question> ', false);
130+
131+
if (!$helper->ask($this->input, $this->output, $question)) {
132+
$this->output->writeln('<comment>Operation cancelled.</comment>');
133+
return false;
134+
}
135+
return true;
136+
}
81137

82138
protected function deleteReplicas(array $storeIds = [], bool $unused = false): void
83139
{

Service/Product/ReplicaManager.php

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class ReplicaManager implements ReplicaManagerInterface
5151
/** @var array<int, string[]> */
5252
protected array $_magentoReplicaPossibleConfig = [];
5353

54+
/** @var array<int, string[]> */
55+
protected array $_unusedReplicaIndices = [];
56+
5457
public function __construct(
5558
protected ConfigHelper $configHelper,
5659
protected AlgoliaHelper $algoliaHelper,
@@ -253,7 +256,7 @@ protected function setReplicasOnPrimaryIndex(int $storeId): array
253256
$setReplicasTaskId = $this->algoliaHelper->getLastTaskId();
254257
$this->algoliaHelper->waitLastTask($indexName, $setReplicasTaskId);
255258
$this->clearAlgoliaReplicaSettingCache($indexName);
256-
$this->deleteReplicaIndices($replicasToDelete);
259+
$this->deleteIndices($replicasToDelete);
257260

258261
if (self::_DEBUG) {
259262
$this->logger->log(
@@ -345,7 +348,7 @@ protected function getBareIndexNameFromReplicaSetting(string $replicaSetting): s
345348
* @return void
346349
* @throws AlgoliaException
347350
*/
348-
protected function deleteReplicaIndices(array $replicasToDelete): void
351+
protected function deleteIndices(array $replicasToDelete): void
349352
{
350353
foreach ($replicasToDelete as $deletedReplica) {
351354
$this->algoliaHelper->deleteIndex($deletedReplica);
@@ -426,16 +429,15 @@ protected function clearReplicasSettingInAlgolia(string $primaryIndexName): void
426429
*/
427430
public function deleteReplicasFromAlgolia(int $storeId, bool $unused = false): void
428431
{
429-
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
430-
431432
if ($unused) {
432-
$replicasToDelete = $this->getUnusedReplicaIndices($primaryIndexName);
433+
$replicasToDelete = $this->getUnusedReplicaIndices($storeId);
433434
} else {
435+
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
434436
$replicasToDelete = $this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName);
435437
$this->clearReplicasSettingInAlgolia($primaryIndexName);
436438
}
437439

438-
$this->deleteReplicaIndices($replicasToDelete);
440+
$this->deleteIndices($replicasToDelete);
439441
}
440442

441443
/**
@@ -447,29 +449,31 @@ protected function getMagentoReplicaIndicesFromAlgolia(string $primaryIndexName)
447449
}
448450

449451
/**
450-
* @return string[]
451-
* @throws NoSuchEntityException
452-
* @throws LocalizedException
453-
* @throws AlgoliaException
452+
* @inheritDoc
454453
*/
455-
protected function getUnusedReplicaIndices(string $primaryIndexName): array
454+
public function getUnusedReplicaIndices(int $storeId): array
456455
{
457-
$currentReplicas = $this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName);
458-
$unusedReplicas = [];
459-
$allIndices = $this->algoliaHelper->listIndexes();
460-
461-
foreach ($allIndices['items'] as $indexInfo) {
462-
$indexName = $indexInfo['name'];
463-
if ($this->isMagentoReplicaIndex($indexName, $primaryIndexName)
464-
&& !$this->indexNameFetcher->isTempIndex($indexName)
465-
&& !$this->indexNameFetcher->isQuerySuggestionsIndex($indexName)
466-
&& !in_array($indexName, $currentReplicas))
467-
{
468-
$unusedReplicas[] = $indexName;
456+
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
457+
if (!isset($this->_unusedReplicaIndices[$storeId])) {
458+
$currentReplicas = $this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName);
459+
$unusedReplicas = [];
460+
$allIndices = $this->algoliaHelper->listIndexes();
461+
462+
foreach ($allIndices['items'] as $indexInfo) {
463+
$indexName = $indexInfo['name'];
464+
if ($this->isMagentoReplicaIndex($indexName, $primaryIndexName)
465+
&& !$this->indexNameFetcher->isTempIndex($indexName)
466+
&& !$this->indexNameFetcher->isQuerySuggestionsIndex($indexName)
467+
&& !in_array($indexName, $currentReplicas))
468+
{
469+
$unusedReplicas[] = $indexName;
470+
}
469471
}
472+
$this->_unusedReplicaIndices[$storeId] = $unusedReplicas;
470473
}
471474

472-
return $unusedReplicas;
475+
476+
return $this->_unusedReplicaIndices[$storeId];
473477
}
474478

475479
/**

0 commit comments

Comments
 (0)