Skip to content

Commit

Permalink
Merge pull request #1616 from algolia/release/3.14.2
Browse files Browse the repository at this point in the history
Release/3.14.2
  • Loading branch information
mrahman3177 authored Sep 16, 2024
2 parents b29cd68 + f15a7ec commit 499aa68
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 13 deletions.
29 changes: 29 additions & 0 deletions Api/RecommendManagementInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Algolia\AlgoliaSearch\Api;

interface RecommendManagementInterface
{
/**
* @param string $productId
* @return array
*/
public function getBoughtTogetherRecommendation(string $productId): array;

/**
* @param string $productId
* @return array
*/
public function getRelatedProductsRecommendation(string $productId): array;

/**
* @return array
*/
public function getTrendingItemsRecommendation(): array;

/**
* @param string $productId
* @return array
*/
public function getLookingSimilarRecommendation(string $productId): array;
}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGE LOG

## 3.14.2

### Updates
- Trained model check before enabling recommend

## 3.14.1

### Updates
Expand Down
18 changes: 9 additions & 9 deletions Helper/ConfigHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,28 +120,28 @@ class ConfigHelper
public const EXTRA_SETTINGS_ADDITIONAL_SECTIONS =
'algoliasearch_extra_settings/extra_settings/additional_sections_extra_settings';
public const MAGENTO_DEFAULT_CACHE_TIME = 'system/full_page_cache/ttl';
protected const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled';
protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled';
protected const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled_in_cart_page';
protected const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled_in_cart_page';
public const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled';
public const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled';
public const IS_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/frequently_bought_together/is_frequently_bought_together_enabled_in_cart_page';
public const IS_RECOMMEND_RELATED_PRODUCTS_ENABLED_ON_CART_PAGE = 'algoliasearch_recommend/recommend/related_product/is_related_products_enabled_in_cart_page';
protected const NUM_OF_RECOMMEND_FREQUENTLY_BOUGHT_TOGETHER_PRODUCTS = 'algoliasearch_recommend/recommend/frequently_bought_together/num_of_frequently_bought_together_products';
protected const NUM_OF_RECOMMEND_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/related_product/num_of_related_products';
protected const IS_REMOVE_RELATED_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/related_product/is_remove_core_related_products_block';
protected const IS_REMOVE_UPSELL_PRODUCTS_BLOCK = 'algoliasearch_recommend/recommend/frequently_bought_together/is_remove_core_upsell_products_block';
protected const IS_RECOMMEND_TRENDING_ITEMS_ENABLED = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled';
public const IS_RECOMMEND_TRENDING_ITEMS_ENABLED = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled';
protected const IS_RECOMMEND_LOOKING_SIMILAR_ENABLED = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled';
protected const NUM_OF_LOOKING_SIMILAR = 'algoliasearch_recommend/recommend/looking_similar/num_of_products';
protected const NUM_OF_TRENDING_ITEMS = 'algoliasearch_recommend/recommend/trends_item/num_of_trending_items';
protected const TREND_ITEMS_FACET_NAME = 'algoliasearch_recommend/recommend/trends_item/facet_name';
protected const TREND_ITEMS_FACET_VALUE = 'algoliasearch_recommend/recommend/trends_item/facet_value';
protected const IS_TREND_ITEMS_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_pdp';
protected const IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_cart_page';
public const IS_TREND_ITEMS_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_pdp';
public const IS_TREND_ITEMS_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/trends_item/is_trending_items_enabled_on_cart_page';
protected const IS_ADDTOCART_ENABLED_IN_FREQUENTLY_BOUGHT_TOGETHER = 'algoliasearch_recommend/recommend/frequently_bought_together/is_addtocart_enabled';
protected const IS_ADDTOCART_ENABLED_IN_RELATED_PRODUCTS = 'algoliasearch_recommend/recommend/related_product/is_addtocart_enabled';
protected const IS_ADDTOCART_ENABLED_IN_TRENDS_ITEM = 'algoliasearch_recommend/recommend/trends_item/is_addtocart_enabled';
protected const IS_ADDTOCART_ENABLED_IN_LOOKING_SIMILAR = 'algoliasearch_recommend/recommend/looking_similar/is_addtocart_enabled';
protected const IS_LOOKING_SIMILAR_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_pdp';
protected const IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_cart_page';
public const IS_LOOKING_SIMILAR_ENABLED_IN_PDP = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_pdp';
public const IS_LOOKING_SIMILAR_ENABLED_IN_SHOPPING_CART = 'algoliasearch_recommend/recommend/looking_similar/is_looking_similar_enabled_on_cart_page';
protected const LOOKING_SIMILAR_TITLE = 'algoliasearch_recommend/recommend/looking_similar/title';
public const LEGACY_USE_VIRTUAL_REPLICA_ENABLED = 'algoliasearch_instant/instant/use_virtual_replica';
protected const AUTOCOMPLETE_KEYBORAD_NAVIAGATION = 'algoliasearch_autocomplete/autocomplete/navigator';
Expand Down
108 changes: 108 additions & 0 deletions Model/RecommendManagement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);

namespace Algolia\AlgoliaSearch\Model;

use Algolia\AlgoliaSearch\Api\RecommendClient;
use Algolia\AlgoliaSearch\Api\RecommendManagementInterface;
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
use Algolia\AlgoliaSearch\Service\IndexNameFetcher;
use Magento\Framework\Exception\NoSuchEntityException;

class RecommendManagement implements RecommendManagementInterface
{
/**
* @var null|RecommendClient
*/
protected ?RecommendClient $client = null;

/**
* @param ConfigHelper $configHelper
* @param IndexNameFetcher $indexNameFetcher
*/
public function __construct(
protected readonly ConfigHelper $configHelper,
protected readonly IndexNameFetcher $indexNameFetcher
){}

/**
* @return RecommendClient
*/
protected function getClient(): RecommendClient
{
if ($this->client === null) {
$this->client = RecommendClient::create(
$this->configHelper->getApplicationID(),
$this->configHelper->getAPIKey()
);
}
return $this->client;
}

/**
* @param string $productId
* @return array
* @throws NoSuchEntityException
*/
public function getBoughtTogetherRecommendation(string $productId): array
{
return $this->getRecommendations($productId, 'bought-together');
}

/**
* @param string $productId
* @return array
* @throws NoSuchEntityException
*/
public function getRelatedProductsRecommendation(string $productId): array
{
return $this->getRecommendations($productId, 'related-products');
}

/**
* @return array
* @throws NoSuchEntityException
*/
public function getTrendingItemsRecommendation(): array
{
return $this->getRecommendations('', 'trending-items');
}

/**
* @param string $productId
* @return array
* @throws NoSuchEntityException
*/
public function getLookingSimilarRecommendation(string $productId): array
{
return $this->getRecommendations($productId, 'bought-together');
}

/**
* @param string $productId
* @param string $model
* @param float|int $threshold
* @return array
* @throws NoSuchEntityException
*/
protected function getRecommendations(string $productId, string $model, float|int $threshold = 50): array
{
$request['indexName'] = $this->indexNameFetcher->getIndexName('_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] ?? [];
}
}
195 changes: 195 additions & 0 deletions Observer/RecommendSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
declare(strict_types=1);

namespace Algolia\AlgoliaSearch\Observer;

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\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\LocalizedException;

class RecommendSettings implements ObserverInterface
{
const QUANTITY_AND_STOCK_STATUS = 'quantity_and_stock_status';
const STATUS = 'status';
const VISIBILITY = 'visibility';

/**
* @var string
*/
protected string $productId = '';

/**
* @param ConfigHelper $configHelper
* @param WriterInterface $configWriter
* @param ProductRepositoryInterface $productRepository
* @param RecommendManagementInterface $recommendManagement
* @param SearchCriteriaBuilder $searchCriteriaBuilder
*/
public function __construct(
protected readonly ConfigHelper $configHelper,
protected readonly WriterInterface $configWriter,
protected readonly ProductRepositoryInterface $productRepository,
protected readonly RecommendManagementInterface $recommendManagement,
protected readonly SearchCriteriaBuilder $searchCriteriaBuilder
){}

/**
* @param Observer $observer
* @throws LocalizedException
*/
public function execute(Observer $observer): void
{
foreach ($observer->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
{
$this->validateRecommendation($changedPath, 'getBoughtTogetherRecommendation', 'Frequently Bought Together');
}

/**
* @param string $changedPath
* @return void
* @throws LocalizedException
*/
protected function validateRelatedProducts(string $changedPath): void
{
$this->validateRecommendation($changedPath, 'getRelatedProductsRecommendation', 'Related Products');
}

/**
* @param string $changedPath
* @return void
* @throws LocalizedException
*/
protected function validateTrendingItems(string $changedPath): void
{
$this->validateRecommendation($changedPath, 'getTrendingItemsRecommendation', 'Trending Items');
}

/**
* @param string $changedPath
* @return 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->$recommendationMethod($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 %1 Recommend configuration due to the following error: %2",
$modelName,
$e->getMessage()
)
);
}
}

/**
* @return string - Product ID string for use in API calls
* @throws LocalizedException
*/
protected function getProductId(): string
{
if ($this->productId === '') {
$searchCriteria = $this->searchCriteriaBuilder
->addFilter(self::STATUS, 1)
->addFilter(self::QUANTITY_AND_STOCK_STATUS, 1)
->addFilter(
self::VISIBILITY,
[
Visibility::VISIBILITY_IN_CATALOG,
Visibility::VISIBILITY_IN_SEARCH,
Visibility::VISIBILITY_BOTH
],
'in')
->setPageSize(1)
->create();
$result = $this->productRepository->getList($searchCriteria);
$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."));
}
}

return $this->productId;
}
}
Loading

0 comments on commit 499aa68

Please sign in to comment.