diff --git a/CHANGELOG.md b/CHANGELOG.md index e118e362a..593ef708e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. This projec * Update tagging with category listing support * Add category export +### 7.2.5 +* Restore `nosto_product_sync.delete` message queue consumer to handle product deletion +>>>>>>> origin/develop + ### 7.2.4 * Add null check in order observer to allow for orders overriding @@ -17,7 +21,7 @@ All notable changes to this project will be documented in this file. This projec * Add store filter to the DefaulCategoryService to generate categories for specific store ### 7.2.0 -* Remove `nosto_product_sync` message queue consumer +* Remove `nosto_product_sync.delete` message queue consumer ### 7.1.2 * Remove unreffered system configuration for enabling 'Indexer full reindex' for Nosto indexers diff --git a/Model/Service/Sync/Delete/AsyncBulkConsumer.php b/Model/Service/Sync/Delete/AsyncBulkConsumer.php new file mode 100644 index 000000000..0b067ebb5 --- /dev/null +++ b/Model/Service/Sync/Delete/AsyncBulkConsumer.php @@ -0,0 +1,93 @@ + + * @copyright 2020 Nosto Solutions Ltd + * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * + */ + +namespace Nosto\Tagging\Model\Service\Sync\Delete; + +use Nosto\NostoException; +use Nosto\Tagging\Model\Service\Sync\AbstractBulkConsumer; +use Nosto\Tagging\Helper\Scope as NostoHelperScope; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\Json\Helper\Data as JsonHelper; +use Nosto\Tagging\Logger\Logger; +use Magento\Store\Model\App\Emulation; + +class AsyncBulkConsumer extends AbstractBulkConsumer +{ + /** @var DeleteService */ + private DeleteService $deleteService; + + /** @var NostoHelperScope */ + private NostoHelperScope $nostoHelperScope; + + /** + * AsyncBulkConsumer constructor. + * @param DeleteService $deleteService + * @param NostoHelperScope $nostoHelperScope + * @param JsonHelper $jsonHelper + * @param EntityManager $entityManager + * @param Emulation $storeEmulation + * @param Logger $logger + */ + public function __construct( + DeleteService $deleteService, + NostoHelperScope $nostoHelperScope, + JsonHelper $jsonHelper, + EntityManager $entityManager, + Emulation $storeEmulation, + Logger $logger + ) { + $this->deleteService = $deleteService; + $this->nostoHelperScope = $nostoHelperScope; + parent::__construct( + $logger, + $jsonHelper, + $entityManager, + $storeEmulation + ); + } + + /** + * @inheritDoc + * @param array $productIds + * @param string $storeId + * @throws NostoException + */ + public function doOperation(array $productIds, string $storeId) + { + $store = $this->nostoHelperScope->getStore($storeId); + $this->deleteService->delete($productIds, $store); + } +} diff --git a/Model/Service/Sync/Delete/AsyncBulkPublisher.php b/Model/Service/Sync/Delete/AsyncBulkPublisher.php new file mode 100644 index 000000000..5198406c7 --- /dev/null +++ b/Model/Service/Sync/Delete/AsyncBulkPublisher.php @@ -0,0 +1,77 @@ + + * @copyright 2020 Nosto Solutions Ltd + * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * + */ + +namespace Nosto\Tagging\Model\Service\Sync\Delete; + +use Nosto\Tagging\Model\Service\Sync\AbstractBulkPublisher; + +class AsyncBulkPublisher extends AbstractBulkPublisher +{ + public const NOSTO_DELETE_MESSAGE_QUEUE = 'nosto_product_sync.delete'; + public const BULK_SIZE = 100; + + /** + * @inheritDoc + */ + public function getTopicName(): string + { + return self::NOSTO_DELETE_MESSAGE_QUEUE; + } + + /** + * @inheritDoc + */ + public function getBulkSize(): int + { + return self::BULK_SIZE; + } + + /** + * @inheritDoc + */ + public function getBulkDescription(): string + { + return sprintf('Delete %d Nosto products', 2); + } + + /** + * @inheritDoc + */ + public function getMetaData(): string + { + return 'Delete Nosto products'; + } +} diff --git a/Model/Service/Sync/Delete/DeleteService.php b/Model/Service/Sync/Delete/DeleteService.php new file mode 100644 index 000000000..aafb1f39a --- /dev/null +++ b/Model/Service/Sync/Delete/DeleteService.php @@ -0,0 +1,134 @@ + + * @copyright 2020 Nosto Solutions Ltd + * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * + */ + +namespace Nosto\Tagging\Model\Service\Sync\Delete; + +use Exception; +use Magento\Store\Model\Store; +use Nosto\Model\Signup\Account as NostoSignupAccount; +use Nosto\NostoException; +use Nosto\Operation\DeleteProduct; +use Nosto\Tagging\Helper\Account as NostoHelperAccount; +use Nosto\Tagging\Helper\Data as NostoHelperData; +use Nosto\Tagging\Helper\Url as NostoHelperUrl; +use Nosto\Tagging\Logger\Logger as NostoLogger; +use Nosto\Tagging\Model\Service\AbstractService; +use Nosto\Tagging\Model\Service\Cache\CacheService; + +class DeleteService extends AbstractService +{ + + public const BENCHMARK_DELETE_NAME = 'nosto_product_delete'; + public const BENCHMARK_DELETE_BREAKPOINT = 1; + public const PRODUCT_DELETION_BATCH_SIZE = 100; + + /** @var CacheService */ + private CacheService $cacheService; + + /** @var NostoHelperAccount */ + private NostoHelperAccount $nostoHelperAccount; + + /** @var NostoHelperUrl */ + private NostoHelperUrl $nostoHelperUrl; + + /** @var int */ + private int $deleteBatchSize; + + /** + * DeleteService constructor. + * @param CacheService $cacheService + * @param NostoHelperAccount $nostoHelperAccount + * @param NostoHelperData $nostoHelperData + * @param NostoHelperUrl $nostoHelperUrl + * @param NostoLogger $logger + * @param $deleteBatchSize + */ + public function __construct( + CacheService $cacheService, + NostoHelperAccount $nostoHelperAccount, + NostoHelperData $nostoHelperData, + NostoHelperUrl $nostoHelperUrl, + NostoLogger $logger, + $deleteBatchSize + ) { + $this->cacheService = $cacheService; + $this->nostoHelperAccount = $nostoHelperAccount; + $this->nostoHelperUrl = $nostoHelperUrl; + $this->deleteBatchSize = $deleteBatchSize; + parent::__construct($nostoHelperData, $nostoHelperAccount, $logger); + } + + /** + * Discontinues products in Nosto and removes indexed products from Nosto product index + * + * @param array $productIds + * @param Store $store + * @throws NostoException + */ + public function delete(array $productIds, Store $store) + { + if (count($productIds) === 0) { + return; + } + $account = $this->nostoHelperAccount->findAccount($store); + if ($account instanceof NostoSignupAccount === false) { + throw new NostoException(sprintf('Store view %s does not have Nosto installed', $store->getName())); + } + $this->startBenchmark(self::BENCHMARK_DELETE_NAME, self::BENCHMARK_DELETE_BREAKPOINT); + $productIdBatches = array_chunk($productIds, $this->deleteBatchSize); + $this->logDebugWithStore( + sprintf( + 'Deleting total of %d products in batches of %d', + count($productIds), + count($productIdBatches) + ), + $store + ); + foreach ($productIdBatches as $ids) { + try { + $op = new DeleteProduct($account, $this->nostoHelperUrl->getActiveDomain($store)); + $op->setResponseTimeout(30); + $op->setProductIds($ids); + $op->delete(); // @codingStandardsIgnoreLine + $this->cacheService->removeByProductIds($store, $ids); + $this->tickBenchmark(self::BENCHMARK_DELETE_NAME); + } catch (Exception $e) { + $this->getLogger()->exception($e); + } + } + $this->logBenchmarkSummary(self::BENCHMARK_DELETE_NAME, $store); + } +} diff --git a/Model/Service/Update/ProductUpdateService.php b/Model/Service/Update/ProductUpdateService.php index 12c6a6946..665d286fa 100644 --- a/Model/Service/Update/ProductUpdateService.php +++ b/Model/Service/Update/ProductUpdateService.php @@ -61,6 +61,9 @@ class ProductUpdateService extends AbstractService /** @var BulkPublisherInterface */ private BulkPublisherInterface $upsertBulkPublisher; + /** @var BulkPublisherInterface */ + private BulkPublisherInterface $deleteBulkPublisher; + /** * ProductUpdateService constructor. * @param NostoLogger $logger @@ -68,6 +71,7 @@ class ProductUpdateService extends AbstractService * @param NostoAccountHelper $nostoAccountHelper * @param NostoProductRepository $nostoProductRepository * @param BulkPublisherInterface $upsertBulkPublisher + * @param BulkPublisherInterface $deleteBulkPublisher * @param int $batchSize */ public function __construct( @@ -76,11 +80,13 @@ public function __construct( NostoAccountHelper $nostoAccountHelper, NostoProductRepository $nostoProductRepository, BulkPublisherInterface $upsertBulkPublisher, + BulkPublisherInterface $deleteBulkPublisher, int $batchSize ) { parent::__construct($nostoDataHelper, $nostoAccountHelper, $logger); $this->nostoProductRepository = $nostoProductRepository; $this->upsertBulkPublisher = $upsertBulkPublisher; + $this->deleteBulkPublisher = $deleteBulkPublisher; $this->batchSize = $batchSize; } @@ -117,6 +123,24 @@ public function addCollectionToUpdateMessageQueue(ProductCollection $collection, } } + /** + * Sets the product ids into the delete message queue + * + * @param array $productIds + * @param Store $store + */ + public function addIdsToDeleteMessageQueue(array $productIds, Store $store) + { + if ($this->getAccountHelper()->findAccount($store) === null) { + $this->logDebugWithStore('No nosto account found for the store', $store); + return; + } + $batchedIds = array_chunk($productIds, $this->batchSize); + foreach ($batchedIds as $idBatch) { + $this->deleteBulkPublisher->execute($store->getId(), $idBatch); + } + } + /** * @param ProductCollection $collection * @return array diff --git a/Plugin/ProductUpdate.php b/Plugin/ProductUpdate.php index 895582654..c81d23792 100644 --- a/Plugin/ProductUpdate.php +++ b/Plugin/ProductUpdate.php @@ -41,9 +41,12 @@ use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Model\AbstractModel; use Nosto\Tagging\Exception\ParentProductDisabledException; +use Nosto\Tagging\Helper\Scope as NostoHelperScope; use Nosto\Tagging\Model\Indexer\ProductIndexer; use Nosto\Tagging\Model\Product\Repository as NostoProductRepository; use Nosto\Tagging\Logger\Logger as NostoLogger; +use Nosto\Tagging\Model\ResourceModel\Magento\Product\CollectionBuilder; +use Nosto\Tagging\Model\Service\Update\ProductUpdateService; /** * Plugin for product updates @@ -62,23 +65,40 @@ class ProductUpdate /** @var NostoLogger */ private NostoLogger $logger; + /** @var ProductUpdateService */ + private ProductUpdateService $productUpdateService; + + /** @var NostoHelperScope */ + private NostoHelperScope $nostoHelperScope; + + /** @var CollectionBuilder */ + private CollectionBuilder $productCollectionBuilder; + /** * ProductUpdate constructor. * @param IndexerRegistry $indexerRegistry * @param ProductIndexer $productIndexer * @param NostoProductRepository $nostoProductRepository * @param NostoLogger $logger + * @param ProductUpdateService $productUpdateService + * @param NostoHelperScope $nostoHelperScope */ public function __construct( - IndexerRegistry $indexerRegistry, - ProductIndexer $productIndexer, - NostoProductRepository $nostoProductRepository, - NostoLogger $logger + IndexerRegistry $indexerRegistry, + ProductIndexer $productIndexer, + NostoProductRepository $nostoProductRepository, + NostoLogger $logger, + ProductUpdateService $productUpdateService, + NostoHelperScope $nostoHelperScope, + CollectionBuilder $productCollectionBuilder ) { $this->indexerRegistry = $indexerRegistry; $this->productIndexer = $productIndexer; $this->nostoProductRepository = $nostoProductRepository; $this->logger = $logger; + $this->productUpdateService = $productUpdateService; + $this->nostoHelperScope = $nostoHelperScope; + $this->productCollectionBuilder = $productCollectionBuilder; } /** @@ -115,26 +135,35 @@ public function aroundDelete( Closure $proceed, AbstractModel $product ) { - $mageIndexer = $this->indexerRegistry->get(ProductIndexer::INDEXER_ID); - if (!$mageIndexer->isScheduled()) { + try { + $productIds = $this->nostoProductRepository->resolveParentProductIds($product); + } catch (ParentProductDisabledException $e) { + $this->logger->debug($e->getMessage()); + return $proceed($product); + } - try { - $productIds = $this->nostoProductRepository->resolveParentProductIds($product); - } catch (ParentProductDisabledException $e) { - $this->logger->debug($e->getMessage()); - return $proceed($product); - } - - if (empty($productIds)) { - $productResource->addCommitCallback(function () use ($product) { - $this->productIndexer->executeRow($product->getId()); - }); - } - if (is_array($productIds) && !empty($productIds)) { - $productResource->addCommitCallback(function () use ($productIds) { - $this->productIndexer->executeList($productIds); - }); - } + $storeIds = $product->getStoreIds(); + + // The current product does not have parent product + if (empty($productIds)) { + $productResource->addCommitCallback(function () use ($product, $storeIds) { + foreach ($storeIds as $storeId) { + $store = $this->nostoHelperScope->getStore($storeId); + $this->productUpdateService->addIdsToDeleteMessageQueue([$product->getId()], $store); + } + }); + } + + // Current product is child product + if (is_array($productIds) && !empty($productIds)) { + $productResource->addCommitCallback(function () use ($productIds, $storeIds) { + $productCollection = $this->productCollectionBuilder->withIds($productIds)->build(); + + foreach ($storeIds as $storeId) { + $store = $this->nostoHelperScope->getStore($storeId); + $this->productUpdateService->addCollectionToUpdateMessageQueue($productCollection, $store); + } + }); } return $proceed($product); diff --git a/composer.json b/composer.json index e50d21d1f..7673e00c9 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nosto/module-nostotagging", "description": "Increase your conversion rate and average order value by delivering your customers personalized product recommendations throughout their shopping journey.", "type": "magento2-module", - "version": "7.2.4", + "version": "7.2.5", "require-dev": { "phpmd/phpmd": "^2.5", "sebastian/phpcpd": "*", @@ -41,7 +41,7 @@ "php": ">=7.4.0", "magento/framework": ">=101.0.6|~104.0", "ext-json": "*", - "nosto/php-sdk": "dev-hotfix/fix-update-category" + "nosto/php-sdk": "^7.1" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index c08646609..ab769840f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5d5b24449493c6a2fc300fae27501844", + "content-hash": "aa6d7af5994cf533b8e395bf62d5c06d", "packages": [ { "name": "brick/math", @@ -2866,16 +2866,16 @@ }, { "name": "nosto/php-sdk", - "version": "dev-hotfix/fix-update-category", + "version": "7.1.1", "source": { "type": "git", "url": "https://github.com/Nosto/nosto-php-sdk.git", - "reference": "9a479e74ddd84df77b590d0818e008f99218d1bc" + "reference": "8f14a91acf7f60f6fb48eab1dfdd61a7f97eef4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nosto/nosto-php-sdk/zipball/9a479e74ddd84df77b590d0818e008f99218d1bc", - "reference": "9a479e74ddd84df77b590d0818e008f99218d1bc", + "url": "https://api.github.com/repos/Nosto/nosto-php-sdk/zipball/8f14a91acf7f60f6fb48eab1dfdd61a7f97eef4f", + "reference": "8f14a91acf7f60f6fb48eab1dfdd61a7f97eef4f", "shasum": "" }, "require": { @@ -2916,9 +2916,9 @@ "description": "PHP SDK for developing Nosto modules for e-commerce platforms", "support": { "issues": "https://github.com/Nosto/nosto-php-sdk/issues", - "source": "https://github.com/Nosto/nosto-php-sdk/tree/hotfix/fix-update-category" + "source": "https://github.com/Nosto/nosto-php-sdk/tree/7.1.1" }, - "time": "2023-06-15T08:38:35+00:00" + "time": "2023-06-27T11:31:54+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -12255,9 +12255,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "nosto/php-sdk": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/etc/communication.xml b/etc/communication.xml index 677b07327..bbbd74187 100644 --- a/etc/communication.xml +++ b/etc/communication.xml @@ -38,4 +38,7 @@ + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 75cd6e2cc..40ad97e97 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -3,7 +3,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> - +
diff --git a/etc/di.xml b/etc/di.xml index b44f2aab7..3f85137fd 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -202,6 +202,13 @@ + + + + Nosto\Tagging\Model\Service\Sync\Delete\AsyncBulkConsumer + + + @@ -211,11 +218,19 @@ 60 + + + 100 + + Nosto\Tagging\Model\Service\Sync\Upsert\AsyncBulkPublisher + + Nosto\Tagging\Model\Service\Sync\Delete\AsyncBulkPublisher + 500 diff --git a/etc/module.xml b/etc/module.xml index d4202ebe8..32cc0bb49 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -37,5 +37,5 @@ - + diff --git a/etc/queue.xml b/etc/queue.xml index 0c90c5028..95666bc8a 100644 --- a/etc/queue.xml +++ b/etc/queue.xml @@ -41,4 +41,10 @@ consumerInstance="Magento\Framework\MessageQueue\Consumer" handler="Nosto\Tagging\Model\Service\Sync\Upsert\AsyncBulkConsumer::process"/> + + + diff --git a/etc/queue_consumer.xml b/etc/queue_consumer.xml index 15b96bd92..d6c596e3c 100644 --- a/etc/queue_consumer.xml +++ b/etc/queue_consumer.xml @@ -41,4 +41,10 @@ maxMessages="20" consumerInstance="Magento\Framework\MessageQueue\Consumer" handler="Nosto\Tagging\Model\Service\Sync\Upsert\AsyncBulkConsumer::processOperation"/> + diff --git a/etc/queue_publisher.xml b/etc/queue_publisher.xml index 16b0c8f2e..0d044bc71 100644 --- a/etc/queue_publisher.xml +++ b/etc/queue_publisher.xml @@ -38,4 +38,7 @@ + + + diff --git a/etc/queue_topology.xml b/etc/queue_topology.xml index 6fb2b8d10..8f70a8190 100644 --- a/etc/queue_topology.xml +++ b/etc/queue_topology.xml @@ -37,5 +37,6 @@ +