From 423c3cd0c11fa5697731329ff33ef1d93de5931a Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Tue, 20 Aug 2024 06:03:21 -0400 Subject: [PATCH 01/15] MAGE-940: validation put in place before enable recommendations --- Api/RecommendManagementInterface.php | 27 ++++ Helper/ConfigHelper.php | 20 +-- Model/Observer/RecommendSettings.php | 216 +++++++++++++++++++++++++++ Model/RecommendManagement.php | 137 +++++++++++++++++ etc/adminhtml/events.xml | 3 + etc/di.xml | 1 + 6 files changed, 394 insertions(+), 10 deletions(-) create mode 100644 Api/RecommendManagementInterface.php create mode 100644 Model/Observer/RecommendSettings.php create mode 100644 Model/RecommendManagement.php diff --git a/Api/RecommendManagementInterface.php b/Api/RecommendManagementInterface.php new file mode 100644 index 000000000..f32705d66 --- /dev/null +++ b/Api/RecommendManagementInterface.php @@ -0,0 +1,27 @@ +getData('changed_paths') as $changedPath) { + // Validate before enable FBT on PDP + if ( + $changedPath == $this->configHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED + && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabled() + ) { + $this->validateFrequentlyBroughtTogether($changedPath); + } + + // Validate before enable FBT on cart page + if ( + $changedPath == $this->configHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE + && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabledOnCartPage() + ) { + $this->validateFrequentlyBroughtTogether($changedPath); + } + + // Validate before enable related products on PDP + if ( + $changedPath == $this->configHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED + && $this->configHelper->isRecommendRelatedProductsEnabled() + ) { + $this->validateRelatedProducts($changedPath); + } + + // Validate before enable related products on cart page + if ( + $changedPath == $this->configHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE + && $this->configHelper->isRecommendRelatedProductsEnabledOnCartPage() + ) { + $this->validateRelatedProducts($changedPath); + } + + // Validate before enable trending items on PDP + if ( + $changedPath == $this->configHelper::IS_TREND_ITEMS_ENABLED_IN_PDP + && $this->configHelper->isTrendItemsEnabledInPDP() + ) { + $this->validateTrendingItems($changedPath); + } + + // Validate before enable trending items on cart page + if ( + $changedPath == $this->configHelper::IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART + && $this->configHelper->isTrendItemsEnabledInShoppingCart() + ) { + $this->validateTrendingItems($changedPath); + } + + // Validate before enable looking similar on PDP + if ( + $changedPath == $this->configHelper::IS_LOOKING_SIMILAR_ENABLED_IN_PDP + && $this->configHelper->isLookingSimilarEnabledInPDP() + ) { + $this->validateLookingSimilar($changedPath); + } + + // Validate before enable looking similar on cart page + if ( + $changedPath == $this->configHelper::IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART + && $this->configHelper->isLookingSimilarEnabledInShoppingCart() + ) { + $this->validateLookingSimilar($changedPath); + } + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateFrequentlyBroughtTogether(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getBoughtTogetherRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__( + "It appears that there is no trained model available for Frequently Bought Together recommendation for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__($e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateRelatedProducts(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getRelatedProductsRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__( + "It appears that there is no trained model available for Related Products recommendation for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__($e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateTrendingItems(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getTrendingItemsRecommendation(); + // When no recommendations suggested, most likely trained model is missing + if (empty($recommendations['renderingContent'])) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__( + "It appears that there is no trained model available for Trending Items recommendation for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__($e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateLookingSimilar(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getLookingSimilarRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__( + "It appears that there is no trained model available for Looking Similar Recommendation for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__($e->getMessage())); + } + } + + /** + * @return string + */ + private function getProductId(): string + { + if ($this->productId === '') { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('status', 1) + ->create(); + $result = $this->productRepository->getList($searchCriteria); + $products = array_reverse($result->getItems()); + $firstProduct = array_pop($products); + $this->productId = (string)$firstProduct->getId(); + } + + return $this->productId; + } +} diff --git a/Model/RecommendManagement.php b/Model/RecommendManagement.php new file mode 100644 index 000000000..85c542e90 --- /dev/null +++ b/Model/RecommendManagement.php @@ -0,0 +1,137 @@ +client === null) { + $this->client = RecommendClient::create( + $this->configHelper->getApplicationID(), + $this->configHelper->getAPIKey() + ); + } + return $this->client; + } + + /** + * @param string $name + * @return string + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + private function getFullIndexName(string $name): string + { + $prefix = $this->configHelper->getIndexPrefix(); + $storeCode = $this->storeManager->getStore()->getCode(); + + foreach ($this->algoliaHelper->listIndexes()['items'] as $index) { + if ($index['name'] == $prefix . $storeCode . '_' . $name) { + return $index['name']; + } + } + + return ''; + } + + /** + * @param string $productId + * @return array + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + public function getBoughtTogetherRecommendation(string $productId): array + { + return $this->getRecommendations($productId, 'bought-together', 50); + } + + /** + * @param string $productId + * @return array + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + public function getRelatedProductsRecommendation($productId): array + { + return $this->getRecommendations($productId, 'related-products', 50); + } + + /** + * @return array + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + public function getTrendingItemsRecommendation(): array + { + return $this->getRecommendations('', 'trending-items', 50); + } + + /** + * @param string $productId + * @return array + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + public function getLookingSimilarRecommendation($productId): array + { + return $this->getRecommendations($productId, 'bought-together', 50); + } + + /** + * @param string $productId + * @param string $model + * @param float|int $threshold + * @return array + * @throws AlgoliaException + * @throws NoSuchEntityException + */ + private function getRecommendations(string $productId, string $model, float|int $threshold = 42): array + { + $request['indexName'] = $this->getFullIndexName('products'); + $request['model'] = $model; + $request['threshold'] = $threshold; + if (!empty($productId)) { + $request['objectID'] = $productId; + } + + $client = $this->getClient(); + $recommendations = $client->getRecommendations( + [ + 'requests' => [ + $request + ], + ], + ); + + return $recommendations['results'][0] ?? []; + } +} diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index cdb5f09df..dbc80ad1a 100755 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -30,6 +30,9 @@ + + + diff --git a/etc/di.xml b/etc/di.xml index a09c94a63..26c93ea57 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -26,6 +26,7 @@ + From b5cb32294232b8bc5067e1b6c5469ebc6ff796ec Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Tue, 20 Aug 2024 06:20:55 -0400 Subject: [PATCH 02/15] MAGE-940: code cleanned up --- Api/RecommendManagementInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Api/RecommendManagementInterface.php b/Api/RecommendManagementInterface.php index f32705d66..83d444a83 100644 --- a/Api/RecommendManagementInterface.php +++ b/Api/RecommendManagementInterface.php @@ -1,5 +1,7 @@ Date: Wed, 21 Aug 2024 15:45:22 -0400 Subject: [PATCH 03/15] MAGE-940: code updated as per code review suggestions. --- Model/RecommendManagement.php | 53 ++------- Observer/RecommendSettings.php | 197 +++++++++++++++++++++++++++++++++ etc/adminhtml/events.xml | 2 +- 3 files changed, 210 insertions(+), 42 deletions(-) create mode 100644 Observer/RecommendSettings.php diff --git a/Model/RecommendManagement.php b/Model/RecommendManagement.php index 85c542e90..64c28af81 100644 --- a/Model/RecommendManagement.php +++ b/Model/RecommendManagement.php @@ -5,28 +5,24 @@ use Algolia\AlgoliaSearch\Api\RecommendClient; use Algolia\AlgoliaSearch\Api\RecommendManagementInterface; -use Algolia\AlgoliaSearch\Exceptions\AlgoliaException; -use Algolia\AlgoliaSearch\Helper\AlgoliaHelper; use Algolia\AlgoliaSearch\Helper\ConfigHelper; +use Algolia\AlgoliaSearch\Service\IndexNameFetcher; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Store\Model\StoreManagerInterface; class RecommendManagement implements RecommendManagementInterface { /** * @var null|RecommendClient */ - private $client = null; + private ?RecommendClient $client = null; /** - * @param AlgoliaHelper $algoliaHelper * @param ConfigHelper $configHelper - * @param StoreManagerInterface $storeManager + * @param IndexNameFetcher $indexNameFetcher */ public function __construct( - private readonly AlgoliaHelper $algoliaHelper, private readonly ConfigHelper $configHelper, - private readonly StoreManagerInterface $storeManager + private readonly IndexNameFetcher $indexNameFetcher ){} /** @@ -43,67 +39,43 @@ private function getClient(): RecommendClient return $this->client; } - /** - * @param string $name - * @return string - * @throws AlgoliaException - * @throws NoSuchEntityException - */ - private function getFullIndexName(string $name): string - { - $prefix = $this->configHelper->getIndexPrefix(); - $storeCode = $this->storeManager->getStore()->getCode(); - - foreach ($this->algoliaHelper->listIndexes()['items'] as $index) { - if ($index['name'] == $prefix . $storeCode . '_' . $name) { - return $index['name']; - } - } - - return ''; - } - /** * @param string $productId * @return array - * @throws AlgoliaException * @throws NoSuchEntityException */ public function getBoughtTogetherRecommendation(string $productId): array { - return $this->getRecommendations($productId, 'bought-together', 50); + return $this->getRecommendations($productId, 'bought-together'); } /** * @param string $productId * @return array - * @throws AlgoliaException * @throws NoSuchEntityException */ - public function getRelatedProductsRecommendation($productId): array + public function getRelatedProductsRecommendation(string $productId): array { - return $this->getRecommendations($productId, 'related-products', 50); + return $this->getRecommendations($productId, 'related-products'); } /** * @return array - * @throws AlgoliaException * @throws NoSuchEntityException */ public function getTrendingItemsRecommendation(): array { - return $this->getRecommendations('', 'trending-items', 50); + return $this->getRecommendations('', 'trending-items'); } /** * @param string $productId * @return array - * @throws AlgoliaException * @throws NoSuchEntityException */ - public function getLookingSimilarRecommendation($productId): array + public function getLookingSimilarRecommendation(string $productId): array { - return $this->getRecommendations($productId, 'bought-together', 50); + return $this->getRecommendations($productId, 'bought-together'); } /** @@ -111,12 +83,11 @@ public function getLookingSimilarRecommendation($productId): array * @param string $model * @param float|int $threshold * @return array - * @throws AlgoliaException * @throws NoSuchEntityException */ - private function getRecommendations(string $productId, string $model, float|int $threshold = 42): array + private function getRecommendations(string $productId, string $model, float|int $threshold = 50): array { - $request['indexName'] = $this->getFullIndexName('products'); + $request['indexName'] = $this->indexNameFetcher->getIndexName('_products'); $request['model'] = $model; $request['threshold'] = $threshold; if (!empty($productId)) { diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php new file mode 100644 index 000000000..f4e936987 --- /dev/null +++ b/Observer/RecommendSettings.php @@ -0,0 +1,197 @@ +getData('changed_paths') as $changedPath) { + // Validate before enable FBT on PDP or on cart page + if (( + $changedPath == ConfigHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED + && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabled() + ) || ( + $changedPath == ConfigHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE + && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabledOnCartPage() + )) { + $this->validateFrequentlyBroughtTogether($changedPath); + } + + // Validate before enable related products on PDP or on cart page + if (( + $changedPath == ConfigHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED + && $this->configHelper->isRecommendRelatedProductsEnabled() + ) || ( + $changedPath == ConfigHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE + && $this->configHelper->isRecommendRelatedProductsEnabledOnCartPage() + )) { + $this->validateRelatedProducts($changedPath); + } + + // Validate before enable trending items on PDP or on cart page + if (( + $changedPath == ConfigHelper::IS_TREND_ITEMS_ENABLED_IN_PDP + && $this->configHelper->isTrendItemsEnabledInPDP() + ) || ( + $changedPath == ConfigHelper::IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART + && $this->configHelper->isTrendItemsEnabledInShoppingCart() + )) { + $this->validateTrendingItems($changedPath); + } + + // Validate before enable looking similar on PDP or on cart page + if (( + $changedPath == ConfigHelper::IS_LOOKING_SIMILAR_ENABLED_IN_PDP + && $this->configHelper->isLookingSimilarEnabledInPDP() + ) || ( + $changedPath == ConfigHelper::IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART + && $this->configHelper->isLookingSimilarEnabledInShoppingCart() + )) { + $this->validateLookingSimilar($changedPath); + } + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateFrequentlyBroughtTogether(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getBoughtTogetherRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + throw new LocalizedException(__( + "It appears that there is no trained model available for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__("Unable to save FBT Recommend configuration due to the following error: " . $e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateRelatedProducts(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getRelatedProductsRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + throw new LocalizedException(__( + "It appears that there is no trained model available for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__("Unable to save Related Products Recommend configuration due to the following error: ". $e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateTrendingItems(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getTrendingItemsRecommendation(); + // When no recommendations suggested, most likely trained model is missing + if (empty($recommendations['renderingContent'])) { + throw new LocalizedException(__( + "It appears that there is no trained model available for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__("Unable to save Trending Items Recommend configuration due to the following error: ". $e->getMessage())); + } + } + + /** + * @param string $changedPath + * @return void + * @throws LocalizedException + */ + protected function validateLookingSimilar(string $changedPath): void + { + try { + $recommendations = $this->recommendManagement->getLookingSimilarRecommendation($this->getProductId()); + if (empty($recommendations['renderingContent'])) { + throw new LocalizedException(__( + "It appears that there is no trained model available for the AppID: %1.", + $this->configHelper->getApplicationID() + )); + } + } catch (\Exception $e) { + $this->configWriter->save($changedPath, 0); + throw new LocalizedException(__("Unable to save Looking Similar Recommend configuration due to the following error: ". $e->getMessage())); + } + } + + /** + * @return string + */ + private function getProductId(): string + { + if ($this->productId === '') { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('status', 1) + ->addFilter('quantity_and_stock_status', 1) + ->addFilter('visibility', [2, 3, 4], 'in') + ->setPageSize(10) + ->create(); + $result = $this->productRepository->getList($searchCriteria); + if ($result->getTotalCount()) { + $products = array_reverse($result->getItems()); + $firstProduct = array_pop($products); + $this->productId = (string)$firstProduct->getId(); + } + } + + return $this->productId; + } +} diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index dbc80ad1a..180028370 100755 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -31,7 +31,7 @@ - + From f697fd232b67d8506488754b4392c9f84f87b77d Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Wed, 21 Aug 2024 15:55:04 -0400 Subject: [PATCH 04/15] MAGE-940: code updated as per code review suggestions. --- Model/Observer/RecommendSettings.php | 216 --------------------------- 1 file changed, 216 deletions(-) delete mode 100644 Model/Observer/RecommendSettings.php diff --git a/Model/Observer/RecommendSettings.php b/Model/Observer/RecommendSettings.php deleted file mode 100644 index 8fe82d773..000000000 --- a/Model/Observer/RecommendSettings.php +++ /dev/null @@ -1,216 +0,0 @@ -getData('changed_paths') as $changedPath) { - // Validate before enable FBT on PDP - if ( - $changedPath == $this->configHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED - && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabled() - ) { - $this->validateFrequentlyBroughtTogether($changedPath); - } - - // Validate before enable FBT on cart page - if ( - $changedPath == $this->configHelper::IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE - && $this->configHelper->isRecommendFrequentlyBroughtTogetherEnabledOnCartPage() - ) { - $this->validateFrequentlyBroughtTogether($changedPath); - } - - // Validate before enable related products on PDP - if ( - $changedPath == $this->configHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED - && $this->configHelper->isRecommendRelatedProductsEnabled() - ) { - $this->validateRelatedProducts($changedPath); - } - - // Validate before enable related products on cart page - if ( - $changedPath == $this->configHelper::IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE - && $this->configHelper->isRecommendRelatedProductsEnabledOnCartPage() - ) { - $this->validateRelatedProducts($changedPath); - } - - // Validate before enable trending items on PDP - if ( - $changedPath == $this->configHelper::IS_TREND_ITEMS_ENABLED_IN_PDP - && $this->configHelper->isTrendItemsEnabledInPDP() - ) { - $this->validateTrendingItems($changedPath); - } - - // Validate before enable trending items on cart page - if ( - $changedPath == $this->configHelper::IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART - && $this->configHelper->isTrendItemsEnabledInShoppingCart() - ) { - $this->validateTrendingItems($changedPath); - } - - // Validate before enable looking similar on PDP - if ( - $changedPath == $this->configHelper::IS_LOOKING_SIMILAR_ENABLED_IN_PDP - && $this->configHelper->isLookingSimilarEnabledInPDP() - ) { - $this->validateLookingSimilar($changedPath); - } - - // Validate before enable looking similar on cart page - if ( - $changedPath == $this->configHelper::IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART - && $this->configHelper->isLookingSimilarEnabledInShoppingCart() - ) { - $this->validateLookingSimilar($changedPath); - } - } - } - - /** - * @param string $changedPath - * @return void - * @throws LocalizedException - */ - protected function validateFrequentlyBroughtTogether(string $changedPath): void - { - try { - $recommendations = $this->recommendManagement->getBoughtTogetherRecommendation($this->getProductId()); - if (empty($recommendations['renderingContent'])) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__( - "It appears that there is no trained model available for Frequently Bought Together recommendation for the AppID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__($e->getMessage())); - } - } - - /** - * @param string $changedPath - * @return void - * @throws LocalizedException - */ - protected function validateRelatedProducts(string $changedPath): void - { - try { - $recommendations = $this->recommendManagement->getRelatedProductsRecommendation($this->getProductId()); - if (empty($recommendations['renderingContent'])) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__( - "It appears that there is no trained model available for Related Products recommendation for the AppID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__($e->getMessage())); - } - } - - /** - * @param string $changedPath - * @return void - * @throws LocalizedException - */ - protected function validateTrendingItems(string $changedPath): void - { - try { - $recommendations = $this->recommendManagement->getTrendingItemsRecommendation(); - // When no recommendations suggested, most likely trained model is missing - if (empty($recommendations['renderingContent'])) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__( - "It appears that there is no trained model available for Trending Items recommendation for the AppID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__($e->getMessage())); - } - } - - /** - * @param string $changedPath - * @return void - * @throws LocalizedException - */ - protected function validateLookingSimilar(string $changedPath): void - { - try { - $recommendations = $this->recommendManagement->getLookingSimilarRecommendation($this->getProductId()); - if (empty($recommendations['renderingContent'])) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__( - "It appears that there is no trained model available for Looking Similar Recommendation for the AppID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__($e->getMessage())); - } - } - - /** - * @return string - */ - private function getProductId(): string - { - if ($this->productId === '') { - $searchCriteria = $this->searchCriteriaBuilder - ->addFilter('status', 1) - ->create(); - $result = $this->productRepository->getList($searchCriteria); - $products = array_reverse($result->getItems()); - $firstProduct = array_pop($products); - $this->productId = (string)$firstProduct->getId(); - } - - return $this->productId; - } -} From 28484f1d9e4adf9317593a3b6f11102813593985 Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Thu, 22 Aug 2024 16:31:51 -0400 Subject: [PATCH 05/15] MAGE-940: code updated as per code review suggestions --- Model/RecommendManagement.php | 10 +++++----- Observer/RecommendSettings.php | 30 +++++++++++++++++------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Model/RecommendManagement.php b/Model/RecommendManagement.php index 64c28af81..3ea570511 100644 --- a/Model/RecommendManagement.php +++ b/Model/RecommendManagement.php @@ -14,21 +14,21 @@ class RecommendManagement implements RecommendManagementInterface /** * @var null|RecommendClient */ - private ?RecommendClient $client = null; + protected ?RecommendClient $client = null; /** * @param ConfigHelper $configHelper * @param IndexNameFetcher $indexNameFetcher */ public function __construct( - private readonly ConfigHelper $configHelper, - private readonly IndexNameFetcher $indexNameFetcher + protected readonly ConfigHelper $configHelper, + protected readonly IndexNameFetcher $indexNameFetcher ){} /** * @return RecommendClient */ - private function getClient(): RecommendClient + protected function getClient(): RecommendClient { if ($this->client === null) { $this->client = RecommendClient::create( @@ -85,7 +85,7 @@ public function getLookingSimilarRecommendation(string $productId): array * @return array * @throws NoSuchEntityException */ - private function getRecommendations(string $productId, string $model, float|int $threshold = 50): array + protected function getRecommendations(string $productId, string $model, float|int $threshold = 50): array { $request['indexName'] = $this->indexNameFetcher->getIndexName('_products'); $request['model'] = $model; diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index f4e936987..ea1dcea72 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -14,10 +14,14 @@ class RecommendSettings implements ObserverInterface { + const QUANTITY_AND_STOCK_STATUS = 'quantity_and_stock_status'; + const STATUS = 'status'; + const VISIBILITY = 'visibility'; + /** * @var string */ - private $productId = ''; + protected $productId = ''; /** * @param ConfigHelper $configHelper @@ -27,11 +31,11 @@ class RecommendSettings implements ObserverInterface * @param SearchCriteriaBuilder $searchCriteriaBuilder */ public function __construct( - private readonly ConfigHelper $configHelper, - private readonly WriterInterface $configWriter, - private readonly ProductRepositoryInterface $productRepository, - private readonly RecommendManagementInterface $recommendManagement, - private readonly SearchCriteriaBuilder $searchCriteriaBuilder + protected readonly ConfigHelper $configHelper, + protected readonly WriterInterface $configWriter, + protected readonly ProductRepositoryInterface $productRepository, + protected readonly RecommendManagementInterface $recommendManagement, + protected readonly SearchCriteriaBuilder $searchCriteriaBuilder ){} /** @@ -119,7 +123,7 @@ protected function validateRelatedProducts(string $changedPath): void $recommendations = $this->recommendManagement->getRelatedProductsRecommendation($this->getProductId()); if (empty($recommendations['renderingContent'])) { throw new LocalizedException(__( - "It appears that there is no trained model available for the AppID: %1.", + "It appears that there is no trained model available for Algolia application ID: %1.", $this->configHelper->getApplicationID() )); } @@ -141,7 +145,7 @@ protected function validateTrendingItems(string $changedPath): void // When no recommendations suggested, most likely trained model is missing if (empty($recommendations['renderingContent'])) { throw new LocalizedException(__( - "It appears that there is no trained model available for the AppID: %1.", + "It appears that there is no trained model available for Algolia application ID: %1.", $this->configHelper->getApplicationID() )); } @@ -162,7 +166,7 @@ protected function validateLookingSimilar(string $changedPath): void $recommendations = $this->recommendManagement->getLookingSimilarRecommendation($this->getProductId()); if (empty($recommendations['renderingContent'])) { throw new LocalizedException(__( - "It appears that there is no trained model available for the AppID: %1.", + "It appears that there is no trained model available for Algolia application ID: %1.", $this->configHelper->getApplicationID() )); } @@ -175,13 +179,13 @@ protected function validateLookingSimilar(string $changedPath): void /** * @return string */ - private function getProductId(): string + protected function getProductId(): string { if ($this->productId === '') { $searchCriteria = $this->searchCriteriaBuilder - ->addFilter('status', 1) - ->addFilter('quantity_and_stock_status', 1) - ->addFilter('visibility', [2, 3, 4], 'in') + ->addFilter(self::STATUS, 1) + ->addFilter(self::QUANTITY_AND_STOCK_STATUS, 1) + ->addFilter(self::VISIBILITY, [2, 3, 4], 'in') ->setPageSize(10) ->create(); $result = $this->productRepository->getList($searchCriteria); From 2165c93d64f2dc6cd585be07890a1f7af75c4935 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 08:27:52 -0400 Subject: [PATCH 06/15] Update Observer/RecommendSettings.php --- Observer/RecommendSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index ea1dcea72..49a80f18d 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -102,7 +102,7 @@ protected function validateFrequentlyBroughtTogether(string $changedPath): void $recommendations = $this->recommendManagement->getBoughtTogetherRecommendation($this->getProductId()); if (empty($recommendations['renderingContent'])) { throw new LocalizedException(__( - "It appears that there is no trained model available for the AppID: %1.", + "It appears that there is no trained model available for Algolia application ID %1.", $this->configHelper->getApplicationID() )); } From b6314979b77cc78f9c773652cd0c566c56434f6f Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 08:59:14 -0400 Subject: [PATCH 07/15] MAGE-940 Implement DRY to centralize error handling --- Observer/RecommendSettings.php | 63 +++++++++++++--------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index 49a80f18d..ff6615652 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -98,18 +98,7 @@ public function execute(Observer $observer) */ protected function validateFrequentlyBroughtTogether(string $changedPath): void { - try { - $recommendations = $this->recommendManagement->getBoughtTogetherRecommendation($this->getProductId()); - if (empty($recommendations['renderingContent'])) { - throw new LocalizedException(__( - "It appears that there is no trained model available for Algolia application ID %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__("Unable to save FBT Recommend configuration due to the following error: " . $e->getMessage())); - } + $this->validateRecommendation($changedPath, 'getBoughtTogetherRecommendation', 'Frequently Bought Together'); } /** @@ -119,18 +108,7 @@ protected function validateFrequentlyBroughtTogether(string $changedPath): void */ protected function validateRelatedProducts(string $changedPath): void { - try { - $recommendations = $this->recommendManagement->getRelatedProductsRecommendation($this->getProductId()); - if (empty($recommendations['renderingContent'])) { - throw new LocalizedException(__( - "It appears that there is no trained model available for Algolia application ID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__("Unable to save Related Products Recommend configuration due to the following error: ". $e->getMessage())); - } + $this->validateRecommendation($changedPath, 'getRelatedProductsRecommendation', 'Related Products'); } /** @@ -140,19 +118,7 @@ protected function validateRelatedProducts(string $changedPath): void */ protected function validateTrendingItems(string $changedPath): void { - try { - $recommendations = $this->recommendManagement->getTrendingItemsRecommendation(); - // When no recommendations suggested, most likely trained model is missing - if (empty($recommendations['renderingContent'])) { - throw new LocalizedException(__( - "It appears that there is no trained model available for Algolia application ID: %1.", - $this->configHelper->getApplicationID() - )); - } - } catch (\Exception $e) { - $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__("Unable to save Trending Items Recommend configuration due to the following error: ". $e->getMessage())); - } + $this->validateRecommendation($changedPath, 'getTrendingItemsRecommendation', 'Trending Items'); } /** @@ -161,18 +127,35 @@ protected function validateTrendingItems(string $changedPath): void * @throws LocalizedException */ protected function validateLookingSimilar(string $changedPath): void + { + $this->validateRecommendation($changedPath, 'getLookingSimilarRecommendation', 'Looking Similar'); + } + + /** + * @param string $changedPath - config path to be reverted if validation failed + * @param string $recommendationMethod - name of method to call to retrieve method from RecommendManagementInterface + * @param string $modelName - user friendly name to refer to model in error messaging + * @return void + * @throws LocalizedException + */ + protected function validateRecommendation(string $changedPath, string $recommendationMethod, string $modelName): void { try { - $recommendations = $this->recommendManagement->getLookingSimilarRecommendation($this->getProductId()); + $recommendations = $this->recommendManagement->$recommendationMethod($this->getProductId()); if (empty($recommendations['renderingContent'])) { throw new LocalizedException(__( - "It appears that there is no trained model available for Algolia application ID: %1.", + "It appears that there is no trained model available for Algolia application ID %1.", $this->configHelper->getApplicationID() )); } } catch (\Exception $e) { $this->configWriter->save($changedPath, 0); - throw new LocalizedException(__("Unable to save Looking Similar Recommend configuration due to the following error: ". $e->getMessage())); + throw new LocalizedException(__( + "Unable to save %1 Recommend configuration due to the following error: %2", + $modelName, + $e->getMessage() + ) + ); } } From 34a08b495b51f31dff40017c8afc7d5aeb69c5e5 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 09:04:52 -0400 Subject: [PATCH 08/15] MAGE-940 Add constants for clarity of visibility values --- Observer/RecommendSettings.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index ff6615652..65a9518ce 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -6,10 +6,11 @@ use Algolia\AlgoliaSearch\Api\RecommendManagementInterface; use Algolia\AlgoliaSearch\Helper\ConfigHelper; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Visibility; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Config\Storage\WriterInterface; -use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\LocalizedException; class RecommendSettings implements ObserverInterface @@ -168,7 +169,14 @@ protected function getProductId(): string $searchCriteria = $this->searchCriteriaBuilder ->addFilter(self::STATUS, 1) ->addFilter(self::QUANTITY_AND_STOCK_STATUS, 1) - ->addFilter(self::VISIBILITY, [2, 3, 4], 'in') + ->addFilter( + self::VISIBILITY, + [ + Visibility::VISIBILITY_IN_CATALOG, + Visibility::VISIBILITY_IN_SEARCH, + Visibility::VISIBILITY_BOTH + ], + 'in') ->setPageSize(10) ->create(); $result = $this->productRepository->getList($searchCriteria); From d0404e9f7024e01487ac05abc99f01b49979c2a6 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 09:21:53 -0400 Subject: [PATCH 09/15] MAGE-940 Limit test product retrieval query to one product only --- Observer/RecommendSettings.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index 65a9518ce..57ce4673f 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -177,13 +177,16 @@ protected function getProductId(): string Visibility::VISIBILITY_BOTH ], 'in') - ->setPageSize(10) + ->setPageSize(1) + ->setCurrentPage(1) ->create(); $result = $this->productRepository->getList($searchCriteria); - if ($result->getTotalCount()) { - $products = array_reverse($result->getItems()); - $firstProduct = array_pop($products); - $this->productId = (string)$firstProduct->getId(); + $items = $result->getItems(); + $firstProduct = reset($items); + if ($firstProduct) { + $this->productId = (string) $firstProduct->getId(); + } else { + throw new LocalizedException(__("Unable to locate product to validate Recommend model.")); } } From 517bf50e5cb2f58cb87c231330e43b4d7543d2f5 Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 09:23:22 -0400 Subject: [PATCH 10/15] MAGE-940 add types and document string cast --- Observer/RecommendSettings.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index 57ce4673f..0821d9671 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -22,7 +22,7 @@ class RecommendSettings implements ObserverInterface /** * @var string */ - protected $productId = ''; + protected string $productId = ''; /** * @param ConfigHelper $configHelper @@ -43,7 +43,7 @@ public function __construct( * @param Observer $observer * @throws LocalizedException */ - public function execute(Observer $observer) + public function execute(Observer $observer): void { foreach ($observer->getData('changed_paths') as $changedPath) { // Validate before enable FBT on PDP or on cart page @@ -161,7 +161,8 @@ protected function validateRecommendation(string $changedPath, string $recommend } /** - * @return string + * @return string - Product ID string for use in API calls + * @throws LocalizedException */ protected function getProductId(): string { From 3fafe51ebd992db6c0797ee8eabd084ab539ee1c Mon Sep 17 00:00:00 2001 From: Eric Wright Date: Fri, 23 Aug 2024 11:05:42 -0400 Subject: [PATCH 11/15] MAGE-940 remove unnecessary current page op --- Observer/RecommendSettings.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Observer/RecommendSettings.php b/Observer/RecommendSettings.php index 0821d9671..a74462447 100644 --- a/Observer/RecommendSettings.php +++ b/Observer/RecommendSettings.php @@ -179,7 +179,6 @@ protected function getProductId(): string ], 'in') ->setPageSize(1) - ->setCurrentPage(1) ->create(); $result = $this->productRepository->getList($searchCriteria); $items = $result->getItems(); From 49cff3f2da665ccefd4cdbbed000cfaf53a90899 Mon Sep 17 00:00:00 2001 From: Mohammad Rahman Date: Fri, 30 Aug 2024 08:54:28 -0400 Subject: [PATCH 12/15] MAGE-1017: recommend location reference issue addressed. --- view/frontend/templates/recommend/products.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/frontend/templates/recommend/products.phtml b/view/frontend/templates/recommend/products.phtml index 9b56b5f25..842fe491a 100644 --- a/view/frontend/templates/recommend/products.phtml +++ b/view/frontend/templates/recommend/products.phtml @@ -23,7 +23,7 @@ if (!empty($recommendConfig['enabledFBT'])