diff --git a/src/module-elasticsuite-core/Api/Healthcheck/CheckInterface.php b/src/module-elasticsuite-core/Api/Healthcheck/CheckInterface.php new file mode 100644 index 000000000..b1194c760 --- /dev/null +++ b/src/module-elasticsuite-core/Api/Healthcheck/CheckInterface.php @@ -0,0 +1,51 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Api\Healthcheck; + +/** + * Health CheckInterface. + */ +interface CheckInterface +{ + /** + * Retrieve the unique identifier for the health check. + * + * @return string + */ + public function getIdentifier(): string; + + /** + * Retrieve the description of the health check. + * + * @return string + */ + public function getDescription(): string; + + /** + * Retrieve the status of the health check. + * Expected values: 'success', 'warning'. + * + * @return string + */ + public function getStatus(): string; + + /** + * Retrieve the sort order for the health check, which determines + * the display order of checks in the admin panel. + * + * @return int + */ + public function getSortOrder(): int; +} diff --git a/src/module-elasticsuite-core/Block/Adminhtml/Healthcheck/Healthcheck.php b/src/module-elasticsuite-core/Block/Adminhtml/Healthcheck/Healthcheck.php new file mode 100644 index 000000000..cb3c5f951 --- /dev/null +++ b/src/module-elasticsuite-core/Block/Adminhtml/Healthcheck/Healthcheck.php @@ -0,0 +1,61 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Block\Adminhtml\Healthcheck; + +use Magento\Backend\Block\Template; +use Magento\Backend\Block\Template\Context; +use Smile\ElasticsuiteCore\Api\Healthcheck\CheckInterface; +use Smile\ElasticsuiteCore\Model\Healthcheck\HealthcheckList; + +/** + * Class Healthcheck. + * + * Block class for displaying Elasticsuite health checks in the Magento Admin panel. + */ +class Healthcheck extends Template +{ + /** + * HealthcheckList instance to manage and retrieve health checks. + * + * @var HealthcheckList + */ + private $healthcheckList; + + /** + * Constructor. + * + * @param Context $context Magento context object for backend blocks. + * @param HealthcheckList $healthcheckList The health check list object providing health check data. + * @param array $data Additional block data. + */ + public function __construct(Context $context, HealthcheckList $healthcheckList, array $data = []) + { + parent::__construct($context, $data); + $this->healthcheckList = $healthcheckList; + } + + /** + * Retrieve all health checks. + * + * Provides an array of health check instances, each implementing the CheckInterface, + * sorted by their specified order. + * + * @return CheckInterface[] + */ + public function getHealthchecks(): array + { + return $this->healthcheckList->getChecks(); + } +} diff --git a/src/module-elasticsuite-core/Controller/Adminhtml/Healthcheck/Index.php b/src/module-elasticsuite-core/Controller/Adminhtml/Healthcheck/Index.php new file mode 100644 index 000000000..9fffa54a9 --- /dev/null +++ b/src/module-elasticsuite-core/Controller/Adminhtml/Healthcheck/Index.php @@ -0,0 +1,42 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Controller\Adminhtml\Healthcheck; + +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\View\Result\Page; +use Smile\ElasticsuiteIndices\Controller\Adminhtml\AbstractAction; + +/** + * Class Index. + */ +class Index extends AbstractAction implements HttpGetActionInterface +{ + /** + * @inheritdoc + * + * @return Page + */ + public function execute(): Page + { + $breadMain = __('Healthcheck'); + + /** @var Page $resultPage */ + $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->getConfig()->getTitle()->prepend($breadMain); + + return $resultPage; + } +} diff --git a/src/module-elasticsuite-core/Model/Healthcheck/GhostIndicesCheck.php b/src/module-elasticsuite-core/Model/Healthcheck/GhostIndicesCheck.php new file mode 100644 index 000000000..deea5db57 --- /dev/null +++ b/src/module-elasticsuite-core/Model/Healthcheck/GhostIndicesCheck.php @@ -0,0 +1,164 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\Healthcheck; + +use Exception; +use Magento\Framework\UrlInterface; +use Smile\ElasticsuiteCore\Api\Healthcheck\CheckInterface; +use Smile\ElasticsuiteIndices\Model\IndexStatsProvider; + +/** + * Class GhostIndicesCheck. + * + * Health check to identify any ghost indices in the Elasticsearch cluster. + */ +class GhostIndicesCheck implements CheckInterface +{ + /** + * Route to Elasticsuite -> Indices page. + */ + private const ROUTE_ELASTICSUITE_INDICES = 'smile_elasticsuite_indices'; + + public const GHOST_STATUS = 'ghost'; + + /** + * @var IndexStatsProvider + */ + private $indexStatsProvider; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * Constructor. + * + * @param IndexStatsProvider $indexStatsProvider Index stats provider. + * @param UrlInterface $urlBuilder URL builder. + */ + public function __construct( + IndexStatsProvider $indexStatsProvider, + UrlInterface $urlBuilder + ) { + $this->indexStatsProvider = $indexStatsProvider; + $this->urlBuilder = $urlBuilder; + } + + /** + * Retrieve the unique identifier for this health check. + * + * @return string + */ + public function getIdentifier(): string + { + return 'ghost_indices_check'; + } + + /** + * Retrieve a brief description of this health check. + * + * @return string + * @throws Exception + */ + public function getDescription(): string + { + $ghostCount = $this->getNumberOfGhostIndices(); + + if ($ghostCount > 0) { + // Description when ghost indices are found. + // @codingStandardsIgnoreStart + return __( + 'You have %1 ghost indices. Ghost indices have a footprint on your Elasticsearch cluster health. ' + . 'You should consider removing them.' + . 'Click here to go to the Elasticsuite Indices page to take appropriate actions.', + $ghostCount, + $this->getElasticsuiteIndicesUrl() + ); + // @codingStandardsIgnoreEnd + } + + // Description when no ghost indices are found. + return __('There are no ghost indexes in your Elasticsearch cluster. No action is required at this time.'); + } + + /** + * Retrieve the status of this health check. + * Returns 'warning' if ghost indices are found, otherwise 'success'. + * + * @return string + * @throws Exception + */ + public function getStatus(): string + { + return $this->hasGhostIndices() ? 'warning' : 'success'; + } + + /** + * Retrieve the sort order for this health check. + * + * @return int + */ + public function getSortOrder(): int + { + return 10; // Adjust as necessary. + } + + /** + * Checks if there are any ghost indices. + * + * @return bool + * @throws Exception + */ + private function hasGhostIndices(): bool + { + return $this->getNumberOfGhostIndices() > 0; + } + + /** + * Get number of ghost Elasticsuite indices. + * + * @return int + * @throws Exception + */ + private function getNumberOfGhostIndices(): int + { + $ghostIndices = 0; + $elasticsuiteIndices = $this->indexStatsProvider->getElasticSuiteIndices(); + + if ($elasticsuiteIndices !== null) { + foreach ($elasticsuiteIndices as $indexName => $indexAlias) { + $indexData = $this->indexStatsProvider->indexStats($indexName, $indexAlias); + + if (array_key_exists('index_status', $indexData) + && $indexData['index_status'] === self::GHOST_STATUS) { + $ghostIndices++; + } + } + } + + return $ghostIndices; + } + + /** + * Retrieve a URL to the Elasticsuite Indices page for more information. + * + * @return string + */ + private function getElasticsuiteIndicesUrl(): string + { + return $this->urlBuilder->getUrl(self::ROUTE_ELASTICSUITE_INDICES); + } +} diff --git a/src/module-elasticsuite-core/Model/Healthcheck/HealthcheckList.php b/src/module-elasticsuite-core/Model/Healthcheck/HealthcheckList.php new file mode 100644 index 000000000..66db18651 --- /dev/null +++ b/src/module-elasticsuite-core/Model/Healthcheck/HealthcheckList.php @@ -0,0 +1,59 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\Healthcheck; + +use Smile\ElasticsuiteCore\Api\Healthcheck\CheckInterface; + +/** + * Class HealthcheckList. + * + * Manages a list of health checks for the Elasticsuite module. + */ +class HealthcheckList +{ + /** + * Array of health checks implementing the CheckInterface. + * + * @var CheckInterface[] + */ + private $checks; + + /** + * Constructor. + * + * @param CheckInterface[] $checks Array of health checks to be managed by this list. + */ + public function __construct(array $checks = []) + { + $this->checks = $checks; + } + + /** + * Retrieve all health checks, sorted by their sort order. + * + * Sorts the checks based on the value returned by each check's `getSortOrder` method. + * + * @return CheckInterface[] Array of health checks sorted by order. + * @SuppressWarnings(PHPMD.ShortVariable) + */ + public function getChecks(): array + { + usort($this->checks, function (CheckInterface $a, CheckInterface $b) { + return $a->getSortOrder() <=> $b->getSortOrder(); + }); + + return $this->checks; + } +} diff --git a/src/module-elasticsuite-core/Model/Healthcheck/ReplicasConfigCheck.php b/src/module-elasticsuite-core/Model/Healthcheck/ReplicasConfigCheck.php new file mode 100644 index 000000000..383c04d32 --- /dev/null +++ b/src/module-elasticsuite-core/Model/Healthcheck/ReplicasConfigCheck.php @@ -0,0 +1,167 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\Healthcheck; + +use Magento\Framework\UrlInterface; +use Smile\ElasticsuiteCore\Api\Healthcheck\CheckInterface; +use Smile\ElasticsuiteCore\Helper\IndexSettings as IndexSettingsHelper; +use Smile\ElasticsuiteCore\Client\Client; + +/** + * Class ReplicasConfigCheck. + * + * Health check for replicas misconfiguration in Elasticsuite. + */ +class ReplicasConfigCheck implements CheckInterface +{ + /** + * Route to Stores -> Configuration section. + */ + private const ROUTE_SYSTEM_CONFIG = 'adminhtml/system_config/edit'; + + /** + * Anchor for Stores -> Configuration -> ELASTICSUITE -> Base Settings -> Indices Settings. + */ + private const ANCHOR_ES_INDICES_SETTINGS_PATH = 'smile_elasticsuite_core_base_settings_indices_settings-link'; + + /** + * URL for Elasticsuite Indices Settings Wiki page. + */ + private const ES_INDICES_SETTINGS_WIKI_PAGE = 'https://github.com/Smile-SA/elasticsuite/wiki/ModuleInstall#indices-settings'; + + public const WARNING_STATUS = 'warning'; + + /** + * @var IndexSettingsHelper + */ + protected $helper; + + /** + * @var Client + */ + protected $client; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * Constructor. + * + * @param IndexSettingsHelper $indexSettingHelper Index settings helper. + * @param Client $client Elasticsearch client. + * @param UrlInterface $urlBuilder URL builder. + */ + public function __construct( + IndexSettingsHelper $indexSettingHelper, + Client $client, + UrlInterface $urlBuilder + ) { + $this->helper = $indexSettingHelper; + $this->client = $client; + $this->urlBuilder = $urlBuilder; + } + + /** + * Retrieve the unique identifier for this health check. + * + * @return string + */ + public function getIdentifier(): string + { + return 'replicas_config_check'; + } + + /** + * Retrieve a dynamic description for this health check based on its status. + * + * @return string + */ + public function getDescription(): string + { + $status = $this->getStatus(); + + if ($status === self::WARNING_STATUS) { + // Description when the replicas configuration is incorrect. + // @codingStandardsIgnoreStart + return __( + 'The number of replicas configured for Elasticsuite is incorrect. ' + . 'You can\'t use %1 replicas since there is only %2 nodes in your Elasticsearch cluster.' + . 'Click here to go to the Elasticsuite Basic Settings page and change your Number of Replicas per Index parameter according to our Wiki page.', + $this->helper->getNumberOfReplicas(), + $this->getNumberOfNodes(), + $this->getElasticsuiteConfigUrl(), + self::ES_INDICES_SETTINGS_WIKI_PAGE + ); + // @codingStandardsIgnoreEnd + } + + // Description when the replicas configuration is optimized. + return __('The number of replicas is properly configured for the Elasticsearch cluster. No action is required at this time.'); + } + + /** + * Retrieve the status of this health check. + * + * @return string + */ + public function getStatus(): string + { + $numberOfReplicas = $this->helper->getNumberOfReplicas(); + $numberOfNodes = $this->getNumberOfNodes(); + + if ($numberOfReplicas > 0 && $numberOfReplicas > ($numberOfNodes - 1)) { + return 'warning'; + } + + return 'success'; + } + + /** + * Retrieve the sort order for this health check. + * + * @return int + */ + public function getSortOrder(): int + { + return 20; // Adjust as necessary. + } + + /** + * Get the number of nodes in the Elasticsearch cluster. + * + * @return int + */ + private function getNumberOfNodes(): int + { + $nodeInfo = $this->client->nodes()->info()['_nodes'] ?? []; + + return $nodeInfo['total'] ?? 0; + } + + /** + * Get URL to the admin Elasticsuite Configuration page. + * + * @return string + */ + private function getElasticsuiteConfigUrl(): string + { + return $this->urlBuilder->getUrl( + self::ROUTE_SYSTEM_CONFIG, + ['section' => 'smile_elasticsuite_core_base_settings', '_fragment' => self::ANCHOR_ES_INDICES_SETTINGS_PATH] + ); + } +} diff --git a/src/module-elasticsuite-core/Model/Healthcheck/ShardsConfigCheck.php b/src/module-elasticsuite-core/Model/Healthcheck/ShardsConfigCheck.php new file mode 100644 index 000000000..56954a76d --- /dev/null +++ b/src/module-elasticsuite-core/Model/Healthcheck/ShardsConfigCheck.php @@ -0,0 +1,192 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\Healthcheck; + +use Exception; +use Magento\Framework\UrlInterface; +use Smile\ElasticsuiteCore\Api\Healthcheck\CheckInterface; +use Smile\ElasticsuiteCore\Helper\IndexSettings as IndexSettingsHelper; +use Smile\ElasticsuiteIndices\Model\IndexStatsProvider; + +/** + * Class ShardsConfigCheck. + * + * Checks for shard misconfigurations in the Elasticsearch cluster. + */ +class ShardsConfigCheck implements CheckInterface +{ + /** + * Route to Stores -> Configuration section. + */ + private const ROUTE_SYSTEM_CONFIG = 'adminhtml/system_config/edit'; + + /** + * Anchor for Stores -> Configuration -> ELASTICSUITE -> Base Settings -> Indices Settings. + */ + private const ANCHOR_ES_INDICES_SETTINGS_PATH = 'smile_elasticsuite_core_base_settings_indices_settings-link'; + + /** + * URL for Elasticsuite Indices Settings Wiki page. + */ + private const ES_INDICES_SETTINGS_WIKI_PAGE = 'https://github.com/Smile-SA/elasticsuite/wiki/ModuleInstall#indices-settings'; + + public const WARNING_STATUS = 'warning'; + + public const UNDEFINED_SIZE = 'undefined'; + + /** + * @var IndexSettingsHelper + */ + private $indexSettingsHelper; + + /** + * @var IndexStatsProvider + */ + private $indexStatsProvider; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * Constructor. + * + * @param IndexSettingsHelper $indexSettingsHelper Index settings helper. + * @param IndexStatsProvider $indexStatsProvider Index stats provider. + * @param UrlInterface $urlBuilder URL builder. + */ + public function __construct( + IndexSettingsHelper $indexSettingsHelper, + IndexStatsProvider $indexStatsProvider, + UrlInterface $urlBuilder + ) { + $this->indexSettingsHelper = $indexSettingsHelper; + $this->indexStatsProvider = $indexStatsProvider; + $this->urlBuilder = $urlBuilder; + } + + /** + * Retrieve the unique identifier for this health check. + * + * @return string + */ + public function getIdentifier(): string + { + return 'shards_config_check'; + } + + /** + * Retrieve a dynamic description for this health check based on its status. + * + * @return string + * @throws Exception + */ + public function getDescription(): string + { + $numberOfShards = $this->indexSettingsHelper->getNumberOfShards(); + $maxIndexSize = $this->getMaxIndexSize(); + $status = $this->getStatus(); + + if ($status === self::WARNING_STATUS) { + // Description when the shard's configuration is incorrect. + // @codingStandardsIgnoreStart + return __( + 'The number of shards configured for Elasticsuite is incorrect. ' + . 'You don\'t need to use %1 shards since your biggest Elasticsuite index is only %2.' + . 'Click here to go to the Elasticsuite Basic Settings page and change your Number of Shards per Index parameter according to our Wiki page.', + $numberOfShards, + $maxIndexSize['human_size'], + $this->getElasticsuiteConfigUrl(), + self::ES_INDICES_SETTINGS_WIKI_PAGE + ); + // @codingStandardsIgnoreEnd + } + + // Description when the shard's configuration is optimized. + return __('The number of shards is properly configured for the Elasticsearch cluster. No action is required at this time.'); + } + + /** + * Retrieve the status of this health check. + * + * @return string + * @throws Exception + */ + public function getStatus(): string + { + $numberOfShards = $this->indexSettingsHelper->getNumberOfShards(); + $maxIndexSize = $this->getMaxIndexSize(); + + if ($numberOfShards > 1 && $maxIndexSize && $maxIndexSize['size_in_bytes'] < 10737418240) { + return 'warning'; + } + + return 'success'; + } + + /** + * Retrieve the sort order for this health check. + * + * @return int + */ + public function getSortOrder(): int + { + return 30; // Adjust as necessary. + } + + /** + * Get size of the largest Elasticsuite index. + * + * @return array|false + * @throws Exception + */ + private function getMaxIndexSize() + { + $elasticsuiteIndices = $this->indexStatsProvider->getElasticSuiteIndices(); + $indexSizes = []; + + foreach ($elasticsuiteIndices as $indexName => $indexAlias) { + $indexData = $this->indexStatsProvider->indexStats($indexName, $indexAlias); + + if (array_key_exists('size', $indexData) && array_key_exists('size_in_bytes', $indexData) + && $indexData['size_in_bytes'] !== self::UNDEFINED_SIZE) { + $indexSizes[] = ['human_size' => $indexData['size'], 'size_in_bytes' => $indexData['size_in_bytes']]; + } + } + + if (!empty($indexSizes)) { + $indexSizesInBytes = array_column($indexSizes, "size_in_bytes"); + array_multisort($indexSizesInBytes, SORT_DESC, $indexSizes); + + return current($indexSizes); + } + + return false; + } + + /** + * Get URL to the Elasticsuite Configuration page. + * + * @return string + */ + private function getElasticsuiteConfigUrl(): string + { + return $this->urlBuilder->getUrl( + self::ROUTE_SYSTEM_CONFIG, + ['section' => 'smile_elasticsuite_core_base_settings', '_fragment' => self::ANCHOR_ES_INDICES_SETTINGS_PATH] + ); + } +} diff --git a/src/module-elasticsuite-core/Model/System/Message/GenericHealthcheckWarning.php b/src/module-elasticsuite-core/Model/System/Message/GenericHealthcheckWarning.php new file mode 100644 index 000000000..a807adf06 --- /dev/null +++ b/src/module-elasticsuite-core/Model/System/Message/GenericHealthcheckWarning.php @@ -0,0 +1,125 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Model\System\Message; + +use Magento\Framework\UrlInterface; +use Magento\Framework\Notification\MessageInterface; +use Smile\ElasticsuiteCore\Model\Healthcheck\HealthcheckList; + +/** + * Class GenericWarningAboutClusterMisconfig + */ +class GenericHealthcheckWarning implements MessageInterface +{ + /** + * Route to Elasticsuite -> Healthcheck page. + */ + private const ROUTE_ELASTICSUITE_HEALTHCHECK = 'smile_elasticsuite/healthcheck/index'; + + /** + * @var HealthcheckList + */ + private $healthcheckList; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + public const WARNING_STATUS = 'warning'; + + /** + * Constructor. + * + * @param HealthcheckList $healthcheckList Health check list object. + * @param UrlInterface $urlBuilder URL builder. + */ + public function __construct( + HealthcheckList $healthcheckList, + UrlInterface $urlBuilder + ) { + $this->healthcheckList = $healthcheckList; + $this->urlBuilder = $urlBuilder; + } + + /** + * {@inheritdoc} + */ + public function isDisplayed() + { + return $this->getIssueCount() > 0; + } + + /** + * {@inheritdoc} + */ + public function getIdentity() + { + return hash('sha256', 'ELASTICSUITE_GENERIC_WARNING'); + } + + /** + * {@inheritdoc} + */ + public function getSeverity() + { + return self::SEVERITY_MAJOR; + } + + /** + * {@inheritdoc} + */ + public function getText() + { + $issuesCount = $this->getIssueCount(); + + // @codingStandardsIgnoreStart + return __( + 'You have %1 health checks in a warning state. ' + . 'Please head to the Elasticsuite Healthcheck page to get more details and see how to fix them.', + $issuesCount, + $this->getElasticsuiteHealthcheckUrl() + ); + // @codingStandardsIgnoreEnd + } + + /** + * Counts the number of health check issues in an error state. + * + * @return int + */ + private function getIssueCount(): int + { + $issuesCount = 0; + + foreach ($this->healthcheckList->getChecks() as $check) { + if ($check->getStatus() === self::WARNING_STATUS) { + $issuesCount++; + } + } + + return $issuesCount; + } + + /** + * Retrieve a URL to the Elasticsuite Healthcheck page for more information. + * + * @return string + */ + private function getElasticsuiteHealthcheckUrl(): string + { + return $this->urlBuilder->getUrl(self::ROUTE_ELASTICSUITE_HEALTHCHECK); + } +} diff --git a/src/module-elasticsuite-core/Model/System/Message/WarningAboutClusterReplicasMisconfig.php b/src/module-elasticsuite-core/Model/System/Message/WarningAboutClusterReplicasMisconfig.php deleted file mode 100644 index d4510a9cb..000000000 --- a/src/module-elasticsuite-core/Model/System/Message/WarningAboutClusterReplicasMisconfig.php +++ /dev/null @@ -1,158 +0,0 @@ - - * @copyright 2022 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -namespace Smile\ElasticsuiteCore\Model\System\Message; - -use Magento\Framework\Notification\MessageInterface; -use Magento\Framework\UrlInterface; -use Smile\ElasticsuiteCore\Helper\IndexSettings as IndexSettingsHelper; -use Smile\ElasticsuiteCore\Client\Client; - -/** - * ElasticSuite Warning about Cluster mis-configuration for replicas - * - * @category Smile - * @package Smile\ElasticsuiteCore - * @author Vadym Honcharuk - */ -class WarningAboutClusterReplicasMisconfig implements MessageInterface -{ - /** - * Route to Stores -> Configuration section - */ - private const ROUTE_SYSTEM_CONFIG = 'adminhtml/system_config/edit'; - - /** - * Anchor for Stores -> Configuration -> ELASTICSUITE -> Base Settings -> Indices Settings - */ - private const ANCHOR_ES_INDICES_SETTINGS_PATH = 'smile_elasticsuite_core_base_settings_indices_settings-link'; - - /** - * URL for Elasticsuite Indices Settings wiki page - */ - private const ES_INDICES_SETTINGS_WIKI_PAGE = 'https://github.com/Smile-SA/elasticsuite/wiki/ModuleInstall#indices-settings'; - - /** - * @var IndexSettingsHelper - */ - protected $helper; - - /** - * @var Client - */ - protected $client; - - /** - * @var UrlInterface - */ - private $urlBuilder; - - /** - * @param IndexSettingsHelper $indexSettingHelper Index settings helper - * @param Client $client ElasticSearch client - * @param UrlInterface $urlBuilder Url builder - */ - public function __construct( - IndexSettingsHelper $indexSettingHelper, - Client $client, - UrlInterface $urlBuilder - ) { - $this->helper = $indexSettingHelper; - $this->client = $client; - $this->urlBuilder = $urlBuilder; - } - - /** - * {@inheritdoc} - */ - public function isDisplayed() - { - if ($this->helper->getNumberOfReplicas() > 1) { - /* numberOfReplicas should be <= numberOfNodes - 1 */ - if ($this->helper->getNumberOfReplicas() <= $this->getNumberOfNodes() - 1) { - return false; - } - - return true; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getIdentity() - { - return hash('sha256', 'ELASTICSUITE_REPLICAS_WARNING'); - } - - /** - * {@inheritdoc} - */ - public function getSeverity() - { - return self::SEVERITY_MAJOR; - } - - /** - * {@inheritdoc} - */ - public function getText() - { - $messageDetails = ''; - - // @codingStandardsIgnoreStart - $messageDetails .= __( - 'The number of replicas configured for Elasticsuite is incorrect. You cannot use %1 replicas since there is only %2 nodes in your Elasticsearch cluster.', - $this->helper->getNumberOfReplicas(), - $this->getNumberOfNodes() - ) . ''; - $messageDetails .= __( - 'Click here to go to the Elasticsuite Config page and change your Number of Replicas per Index parameter according to our wiki page.', - $this->getElasticsuiteConfigUrl(), - self::ES_INDICES_SETTINGS_WIKI_PAGE - ); - // @codingStandardsIgnoreEnd - - return $messageDetails; - } - - /** - * Get number of nodes from ElasticSearch client - * - * @return int - */ - public function getNumberOfNodes() - { - if (is_array($this->client->nodes()->info()['_nodes']) - && array_key_exists('total', $this->client->nodes()->info()['_nodes'])) { - return (int) $this->client->nodes()->info()['_nodes']['total']; - } - - return 0; - } - - /** - * Get URL to the admin Elasticsuite Configuration page - * - * @return string - */ - private function getElasticsuiteConfigUrl() - { - return $this->urlBuilder->getUrl( - self::ROUTE_SYSTEM_CONFIG, - ['section' => 'smile_elasticsuite_core_base_settings', '_fragment' => self::ANCHOR_ES_INDICES_SETTINGS_PATH] - ); - } -} diff --git a/src/module-elasticsuite-core/etc/acl.xml b/src/module-elasticsuite-core/etc/acl.xml index b00073241..868c2c6d8 100644 --- a/src/module-elasticsuite-core/etc/acl.xml +++ b/src/module-elasticsuite-core/etc/acl.xml @@ -31,6 +31,9 @@ + + + diff --git a/src/module-elasticsuite-core/etc/adminhtml/di.xml b/src/module-elasticsuite-core/etc/adminhtml/di.xml index 39d308f7e..9c27a577a 100644 --- a/src/module-elasticsuite-core/etc/adminhtml/di.xml +++ b/src/module-elasticsuite-core/etc/adminhtml/di.xml @@ -22,7 +22,19 @@ Smile\ElasticsuiteCore\Model\System\Message\NotificationAboutVersions - Smile\ElasticsuiteCore\Model\System\Message\WarningAboutClusterReplicasMisconfig + + Smile\ElasticsuiteCore\Model\System\Message\GenericHealthcheckWarning + + + + + + + + + Smile\ElasticsuiteCore\Model\Healthcheck\GhostIndicesCheck + Smile\ElasticsuiteCore\Model\Healthcheck\ShardsConfigCheck + Smile\ElasticsuiteCore\Model\Healthcheck\ReplicasConfigCheck diff --git a/src/module-elasticsuite-core/etc/adminhtml/menu.xml b/src/module-elasticsuite-core/etc/adminhtml/menu.xml index 3f66f7af0..86e5ef442 100644 --- a/src/module-elasticsuite-core/etc/adminhtml/menu.xml +++ b/src/module-elasticsuite-core/etc/adminhtml/menu.xml @@ -3,14 +3,7 @@ - + + diff --git a/src/module-elasticsuite-core/view/adminhtml/layout/smile_elasticsuite_healthcheck_index.xml b/src/module-elasticsuite-core/view/adminhtml/layout/smile_elasticsuite_healthcheck_index.xml new file mode 100644 index 000000000..fae89ae7f --- /dev/null +++ b/src/module-elasticsuite-core/view/adminhtml/layout/smile_elasticsuite_healthcheck_index.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/src/module-elasticsuite-core/view/adminhtml/templates/healthcheck/list.phtml b/src/module-elasticsuite-core/view/adminhtml/templates/healthcheck/list.phtml new file mode 100644 index 000000000..7add1ba8a --- /dev/null +++ b/src/module-elasticsuite-core/view/adminhtml/templates/healthcheck/list.phtml @@ -0,0 +1,41 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +/** @var \Smile\ElasticsuiteCore\Block\Adminhtml\Healthcheck\Healthcheck $block */ +?> + + + + + = __('Identifier') ?> + = __('Description') ?> + = __('Status') ?> + + + + getHealthchecks() as $index => $check): ?> + + = $check->getIdentifier() ?> + = $check->getDescription() ?> + + + = $check->getStatus() ?> + + + + + + diff --git a/src/module-elasticsuite-core/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-core/view/adminhtml/web/css/source/_module.less index a9b9f6a0e..620cca5bd 100644 --- a/src/module-elasticsuite-core/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-core/view/adminhtml/web/css/source/_module.less @@ -42,3 +42,14 @@ fieldset.radioset-tooltip { line-height: @line-height__l; } } + +.smile_elasticsuite-healthcheck-index { + .data-grid tbody tr:nth-child(odd) td { + background-color: #ffffff; /* White */ + } + + .data-grid tbody tr:nth-child(even) td { + background-color: #f5f5f5; /* Light gray */ + } +} + diff --git a/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterGhostIndices.php b/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterGhostIndices.php deleted file mode 100644 index 27de23dc7..000000000 --- a/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterGhostIndices.php +++ /dev/null @@ -1,150 +0,0 @@ - - * @copyright 2022 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -namespace Smile\ElasticsuiteIndices\Model\System\Message; - -use Magento\Framework\Notification\MessageInterface; -use Magento\Framework\UrlInterface; -use Smile\ElasticsuiteIndices\Model\IndexStatsProvider; - -/** - * ElasticSuite Warning about too much ghost indices in the cluster - * - * @category Smile - * @package Smile\ElasticsuiteIndices - * @author Vadym Honcharuk - */ -class WarningAboutClusterGhostIndices implements MessageInterface -{ - /** - * Route to Elasticsuite -> Indices page - */ - private const ROUTE_ELASTICSUITE_INDICES = 'smile_elasticsuite_indices'; - - public const GHOST_STATUS = 'ghost'; - - /** - * @var IndexStatsProvider - */ - protected $indexStatsProvider; - - /** - * @var UrlInterface - */ - private $urlBuilder; - - /** - * @param IndexStatsProvider $indexStatsProvider Index stats provider - * @param UrlInterface $urlBuilder Url builder - */ - public function __construct( - IndexStatsProvider $indexStatsProvider, - UrlInterface $urlBuilder - ) { - $this->indexStatsProvider = $indexStatsProvider; - $this->urlBuilder = $urlBuilder; - } - - /** - * {@inheritdoc} - * @throws \Exception - */ - public function isDisplayed() - { - if ($this->getNumberOfGhostIndices() && $this->getNumberOfGhostIndices() > 1) { - return true; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getIdentity() - { - return hash('sha256', 'ELASTICSUITE_GHOST_INDICES_WARNING'); - } - - /** - * {@inheritdoc} - */ - public function getSeverity() - { - return self::SEVERITY_MAJOR; - } - - /** - * {@inheritdoc} - * @throws \Exception - */ - public function getText() - { - $messageDetails = ''; - - // @codingStandardsIgnoreStart - $messageDetails .= __( - 'You have %1 ghost indices. Ghost indices have a footprint on your Elasticsearch cluster health. You should consider removing them.', - $this->getNumberOfGhostIndices() - ) . ''; - $messageDetails .= __( - 'Click here to go to the Elasticsuite Indices page to take appropriate actions.', - $this->getElasticsuiteIndicesUrl() - ); - // @codingStandardsIgnoreEnd - - return $messageDetails; - } - - /** - * Get number of the Ghost Elasticsuite Indices - * - * @return mixed - * @throws \Exception - */ - private function getNumberOfGhostIndices() - { - if ($this->indexStatsProvider->getElasticSuiteIndices() !== null) { - $elasticsuiteIndices = $this->indexStatsProvider->getElasticSuiteIndices(); - $ghostIndices = []; - - foreach ($elasticsuiteIndices as $indexName => $indexAlias) { - $indexData = $this->indexStatsProvider->indexStats($indexName, $indexAlias); - - if (array_key_exists('index_status', $indexData) - && $indexData['index_status'] === self::GHOST_STATUS) { - $ghostIndices[] = [ - 'index_name' => $indexData['index_name'], - 'index_status' => $indexData['index_status'], - ]; - } - } - - if (!empty($ghostIndices)) { - return count($ghostIndices); - } - } - - return false; - } - - /** - * Get URL to the admin Elasticsuite Indices Status page - * - * @return string - */ - private function getElasticsuiteIndicesUrl() - { - return $this->urlBuilder->getUrl(self::ROUTE_ELASTICSUITE_INDICES); - } -} diff --git a/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterShardsMisconfig.php b/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterShardsMisconfig.php deleted file mode 100644 index 6ddefbc4b..000000000 --- a/src/module-elasticsuite-indices/Model/System/Message/WarningAboutClusterShardsMisconfig.php +++ /dev/null @@ -1,184 +0,0 @@ - - * @copyright 2022 Smile - * @license Open Software License ("OSL") v. 3.0 - */ - -namespace Smile\ElasticsuiteIndices\Model\System\Message; - -use Magento\Framework\Notification\MessageInterface; -use Magento\Framework\UrlInterface; -use Smile\ElasticsuiteCore\Helper\IndexSettings as IndexSettingsHelper; -use Smile\ElasticsuiteIndices\Model\IndexStatsProvider; - -/** - * ElasticSuite Warning about Cluster mis-configuration for shards - * - * @category Smile - * @package Smile\ElasticsuiteIndices - * @author Vadym Honcharuk - * - * @SuppressWarnings(PHPMD.LongVariable) - */ -class WarningAboutClusterShardsMisconfig implements MessageInterface -{ - /** - * Route to Stores -> Configuration section - */ - private const ROUTE_SYSTEM_CONFIG = 'adminhtml/system_config/edit'; - - /** - * Anchor for Stores -> Configuration -> ELASTICSUITE -> Base Settings -> Indices Settings - */ - private const ANCHOR_ES_INDICES_SETTINGS_PATH = 'smile_elasticsuite_core_base_settings_indices_settings-link'; - - /** - * URL for Elasticsuite Indices Settings wiki page - */ - private const ES_INDICES_SETTINGS_WIKI_PAGE = 'https://github.com/Smile-SA/elasticsuite/wiki/ModuleInstall#indices-settings'; - - public const UNDEFINED_SIZE = 'undefined'; - - /** - * @var IndexSettingsHelper - */ - protected $helper; - - /** - * @var IndexStatsProvider - */ - protected $indexStatsProvider; - - /** - * @var UrlInterface - */ - private $urlBuilder; - - /** - * @param IndexSettingsHelper $indexSettingHelper Index settings helper - * @param IndexStatsProvider $indexStatsProvider Index stats provider - * @param UrlInterface $urlBuilder Url builder - */ - public function __construct( - IndexSettingsHelper $indexSettingHelper, - IndexStatsProvider $indexStatsProvider, - UrlInterface $urlBuilder - ) { - $this->helper = $indexSettingHelper; - $this->indexStatsProvider = $indexStatsProvider; - $this->urlBuilder = $urlBuilder; - } - - /** - * {@inheritdoc} - */ - public function isDisplayed() - { - $numberOfShards = $this->helper->getNumberOfShards(); - - if ($numberOfShards > 1) { - if ($this->getElasticsuiteIndexMaxSize()) { - $indexMaxSize = $this->getElasticsuiteIndexMaxSize()['size_in_bytes']; - - /* 10 Gb => 10737418240 bytes (in binary) */ - if ($indexMaxSize < '10737418240') { - return true; - } - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getIdentity() - { - return hash('sha256', 'ELASTICSUITE_SHARDS_WARNING'); - } - - /** - * {@inheritdoc} - */ - public function getSeverity() - { - return self::SEVERITY_MAJOR; - } - - /** - * {@inheritdoc} - */ - public function getText() - { - $messageDetails = ''; - - // @codingStandardsIgnoreStart - $messageDetails .= __('The number of shards configured for Elasticsuite is incorrect.') . ' '; - $messageDetails .= __( - 'You do not need to use %1 shards since your biggest Elasticsuite index is only %2.', - $this->helper->getNumberOfShards(), - $this->getElasticsuiteIndexMaxSize()['human_size'] - ) . ''; - $messageDetails .= __( - 'Click here to go to the Elasticsuite Config page and change your Number of Shards per Index parameter according to our wiki page.', - $this->getElasticsuiteConfigUrl(), - self::ES_INDICES_SETTINGS_WIKI_PAGE - ); - // @codingStandardsIgnoreEnd - - return $messageDetails; - } - - /** - * Get size of the biggest Elasticsuite Indices - * - * @return mixed - * @throws \Exception - */ - private function getElasticsuiteIndexMaxSize() - { - if ($this->indexStatsProvider->getElasticSuiteIndices() !== null) { - $elasticsuiteIndices = $this->indexStatsProvider->getElasticSuiteIndices(); - $indexSizes = []; - - foreach ($elasticsuiteIndices as $indexName => $indexAlias) { - $indexData = $this->indexStatsProvider->indexStats($indexName, $indexAlias); - - if (array_key_exists('size', $indexData) && array_key_exists('size_in_bytes', $indexData) - && $indexData['size_in_bytes'] !== self::UNDEFINED_SIZE) { - $indexSizes[] = ['human_size' => $indexData['size'], 'size_in_bytes' => $indexData['size_in_bytes']]; - } - } - - if (!empty($indexSizes)) { - $indexSizesInBytes = array_column($indexSizes, "size_in_bytes"); - array_multisort($indexSizesInBytes, SORT_DESC, $indexSizes); - - return current($indexSizes); - } - } - - return false; - } - - /** - * Get URL to the admin Elasticsuite Configuration page - * - * @return string - */ - private function getElasticsuiteConfigUrl() - { - return $this->urlBuilder->getUrl( - self::ROUTE_SYSTEM_CONFIG, - ['section' => 'smile_elasticsuite_core_base_settings', '_fragment' => self::ANCHOR_ES_INDICES_SETTINGS_PATH] - ); - } -} diff --git a/src/module-elasticsuite-indices/etc/adminhtml/di.xml b/src/module-elasticsuite-indices/etc/adminhtml/di.xml deleted file mode 100644 index 50ff740a8..000000000 --- a/src/module-elasticsuite-indices/etc/adminhtml/di.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - Smile\ElasticsuiteIndices\Model\System\Message\WarningAboutClusterShardsMisconfig - Smile\ElasticsuiteIndices\Model\System\Message\WarningAboutClusterGhostIndices - - - - -