From f75f5dc412ccc2b0676e81a57b759df97d7343d2 Mon Sep 17 00:00:00 2001 From: Agata Firlejczyk Date: Tue, 7 Apr 2020 15:58:32 +0200 Subject: [PATCH 01/14] Support for ES6+ Refactoring base module. Add option to choose ES version in configuration. Changes in base module to make it work with different elasticsearch versions: 5 6, 7; Refactoring vsbridge:reindex command. --- README.md | 3 + composer.json | 98 +++--- docs/configuration.md | 5 + .../Model/Indexer/Action/Agreement.php | 13 +- .../Model/Indexer/Agreement.php | 90 ----- .../etc/di.xml | 13 +- .../etc/vsbridge.xml | 4 + .../etc/vsbridge_indices.xml | 6 - .../Product/CustomOptionConverter.php | 2 +- .../Model/Indexer/Action/Attribute.php | 12 +- .../Model/Indexer/Action/Category.php | 11 +- .../Model/Indexer/Action/Product.php | 13 +- .../Model/Indexer/Attribute.php | 95 ----- .../Model/Indexer/Category.php | 105 ------ .../DataProvider/Category/AttributeData.php | 4 +- .../DataProvider/Product/AttributeData.php | 2 +- .../DataProvider/Product/ConfigurableData.php | 2 +- .../Model/Indexer/Product.php | 106 ------ .../etc/di.xml | 95 +++-- .../etc/vsbridge.xml | 6 + .../etc/vsbridge_indices.xml | 31 -- .../Model/Indexer/Action/CmsBlock.php | 5 +- .../Model/Indexer/Action/CmsPage.php | 7 +- .../Model/Indexer/CmsBlock.php | 93 ----- .../Model/Indexer/CmsPage.php | 93 ----- .../Indexer/DataProvider/CmsContentFilter.php | 6 - .../Model/ResourceModel/CmsPage.php | 11 +- src/module-vsbridge-indexer-cms/etc/di.xml | 48 ++- .../etc/vsbridge.xml | 5 + .../etc/vsbridge_indices.xml | 16 - .../Index/DataProviderResolverInterface.php | 19 + .../TransactionKeyInterface.php | 2 +- .../Api/Index/TypeInterface.php | 11 - .../Api/IndexInterface.php | 6 + .../Api/IndexOperationInterface.php | 7 - .../Command/Rebuild/IndicesProvider.php | 68 ++++ .../Console/Command/Rebuild/Rebuild.php | 160 +++++++++ .../Command/Rebuild/RebuildFactory.php | 36 ++ .../Console/Command/RebuildEsIndexCommand.php | 327 +++--------------- .../Elasticsearch/Client.php | 34 +- .../ConfigParserNotExistException.php | 10 + .../ConfigurationNotFoundException.php | 11 + .../Exception/ConnectionDisabledException.php | 8 - .../Exception/IndexNotExistException.php | 18 + .../Index/BulkLogger.php | 2 +- .../{Indexer => Index}/ConvertValue.php | 4 +- .../{Indexer => Index}/DataFilter.php | 5 +- .../DataProvider/TransactionKey.php | 4 +- .../Index/DataProviderResolver.php | 72 ++++ .../Index/Index.php | 3 +- .../Index/IndexOperations.php | 38 +- .../Index/IndexSettings.php | 109 +++--- .../Index/Indices/Config/Converter.php | 48 +++ .../{Indicies => Indices}/Config/Data.php | 2 +- .../{Indicies => Indices}/Config/Reader.php | 8 +- .../Config/SchemaLocator.php | 4 +- .../Index/Indices/ConfigES5Parser.php | 63 ++++ .../Index/Indices/ConfigES6plusParser.php | 63 ++++ .../Index/Indices/ConfigParserInterface.php | 16 + .../Index/Indices/ConfigResolver.php | 71 ++++ .../Index/Indicies/Config.php | 119 ------- .../Index/Indicies/Config/Converter.php | 122 ------- .../Index/Mapping/GeneralMapping.php | 2 +- .../MappingFactory.php} | 17 +- .../{Indexer => Index}/TransactionKey.php | 4 +- .../Index/Type.php | 31 +- .../Indexer/Action/AbstractAction.php | 93 +++++ .../Indexer/Action/ActionFactory.php | 43 +++ .../Indexer/Action/Full.php | 64 ++++ .../Indexer/Action/Rows.php | 27 ++ .../Indexer/Base.php | 67 ++++ .../Indexer/DataProviderProcessorFactory.php | 43 --- .../Indexer/GenericIndexerHandler.php | 85 +++-- .../Indexer/RebuildActionInterface.php | 21 ++ .../Indexer/RebuildActionPool.php | 52 +++ .../Indexer/StoreManager.php | 34 +- .../System/Config/Source/Version.php | 45 +++ .../Model/ElasticsearchResolver.php | 39 +++ .../Model/ElasticsearchResolverInterface.php | 20 ++ .../Test/Unit/Index/IndexOperationsTest.php | 9 +- .../Test/Unit/Index/IndexSettingsTest.php | 27 +- .../Index/Indices/ConfigES5ParserTest.php | 106 ++++++ .../Index/Indices/ConfigES6plusParserTest.php | 102 ++++++ .../etc/adminhtml/system.xml | 4 + .../etc/config.xml | 1 + src/module-vsbridge-indexer-core/etc/di.xml | 56 ++- .../etc/vsbridge.xsd | 15 + .../etc/vsbridge_indices.xsd | 39 --- .../Model/Indexer/Action/Review.php | 5 +- .../Model/Indexer/Review.php | 94 ----- src/module-vsbridge-indexer-review/etc/di.xml | 21 +- .../etc/vsbridge.xml | 4 + .../etc/vsbridge_indices.xml | 10 - .../Model/Indexer/Action/TaxRule.php | 13 +- .../Model/Indexer/TaxRules.php | 93 ----- src/module-vsbridge-indexer-tax/etc/di.xml | 22 +- .../etc/vsbridge.xml | 4 + .../etc/vsbridge_indices.xml | 12 - 98 files changed, 1956 insertions(+), 1838 deletions(-) delete mode 100644 src/module-vsbridge-indexer-agreement/Model/Indexer/Agreement.php create mode 100644 src/module-vsbridge-indexer-agreement/etc/vsbridge.xml delete mode 100644 src/module-vsbridge-indexer-agreement/etc/vsbridge_indices.xml delete mode 100644 src/module-vsbridge-indexer-catalog/Model/Indexer/Attribute.php delete mode 100644 src/module-vsbridge-indexer-catalog/Model/Indexer/Category.php delete mode 100644 src/module-vsbridge-indexer-catalog/Model/Indexer/Product.php create mode 100755 src/module-vsbridge-indexer-catalog/etc/vsbridge.xml delete mode 100755 src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml delete mode 100644 src/module-vsbridge-indexer-cms/Model/Indexer/CmsBlock.php delete mode 100644 src/module-vsbridge-indexer-cms/Model/Indexer/CmsPage.php create mode 100644 src/module-vsbridge-indexer-cms/etc/vsbridge.xml delete mode 100644 src/module-vsbridge-indexer-cms/etc/vsbridge_indices.xml create mode 100644 src/module-vsbridge-indexer-core/Api/Index/DataProviderResolverInterface.php rename src/module-vsbridge-indexer-core/Api/{Indexer => Index}/TransactionKeyInterface.php (87%) create mode 100644 src/module-vsbridge-indexer-core/Console/Command/Rebuild/IndicesProvider.php create mode 100644 src/module-vsbridge-indexer-core/Console/Command/Rebuild/Rebuild.php create mode 100644 src/module-vsbridge-indexer-core/Console/Command/Rebuild/RebuildFactory.php create mode 100644 src/module-vsbridge-indexer-core/Exception/ConfigParserNotExistException.php create mode 100644 src/module-vsbridge-indexer-core/Exception/ConfigurationNotFoundException.php create mode 100644 src/module-vsbridge-indexer-core/Exception/IndexNotExistException.php rename src/module-vsbridge-indexer-core/{Indexer => Index}/ConvertValue.php (93%) rename src/module-vsbridge-indexer-core/{Indexer => Index}/DataFilter.php (90%) rename src/module-vsbridge-indexer-core/{Indexer => Index}/DataProvider/TransactionKey.php (87%) create mode 100644 src/module-vsbridge-indexer-core/Index/DataProviderResolver.php create mode 100644 src/module-vsbridge-indexer-core/Index/Indices/Config/Converter.php rename src/module-vsbridge-indexer-core/Index/{Indicies => Indices}/Config/Data.php (93%) rename src/module-vsbridge-indexer-core/Index/{Indicies => Indices}/Config/Reader.php (86%) rename src/module-vsbridge-indexer-core/Index/{Indicies => Indices}/Config/SchemaLocator.php (92%) create mode 100644 src/module-vsbridge-indexer-core/Index/Indices/ConfigES5Parser.php create mode 100644 src/module-vsbridge-indexer-core/Index/Indices/ConfigES6plusParser.php create mode 100644 src/module-vsbridge-indexer-core/Index/Indices/ConfigParserInterface.php create mode 100644 src/module-vsbridge-indexer-core/Index/Indices/ConfigResolver.php delete mode 100644 src/module-vsbridge-indexer-core/Index/Indicies/Config.php delete mode 100644 src/module-vsbridge-indexer-core/Index/Indicies/Config/Converter.php rename src/module-vsbridge-indexer-core/{Indexer/MappingProcessorFactory.php => Index/MappingFactory.php} (63%) rename src/module-vsbridge-indexer-core/{Indexer => Index}/TransactionKey.php (83%) create mode 100644 src/module-vsbridge-indexer-core/Indexer/Action/AbstractAction.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/Action/ActionFactory.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/Action/Full.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/Action/Rows.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/Base.php delete mode 100644 src/module-vsbridge-indexer-core/Indexer/DataProviderProcessorFactory.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/RebuildActionInterface.php create mode 100644 src/module-vsbridge-indexer-core/Indexer/RebuildActionPool.php create mode 100644 src/module-vsbridge-indexer-core/Model/Adminhtml/System/Config/Source/Version.php create mode 100644 src/module-vsbridge-indexer-core/Model/ElasticsearchResolver.php create mode 100644 src/module-vsbridge-indexer-core/Model/ElasticsearchResolverInterface.php create mode 100644 src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES5ParserTest.php create mode 100644 src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES6plusParserTest.php create mode 100644 src/module-vsbridge-indexer-core/etc/vsbridge.xsd delete mode 100644 src/module-vsbridge-indexer-core/etc/vsbridge_indices.xsd delete mode 100644 src/module-vsbridge-indexer-review/Model/Indexer/Review.php create mode 100644 src/module-vsbridge-indexer-review/etc/vsbridge.xml delete mode 100644 src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml delete mode 100644 src/module-vsbridge-indexer-tax/Model/Indexer/TaxRules.php create mode 100644 src/module-vsbridge-indexer-tax/etc/vsbridge.xml delete mode 100644 src/module-vsbridge-indexer-tax/etc/vsbridge_indices.xml diff --git a/README.md b/README.md index 42ea7f7f..c06f3e12 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,10 @@ Note: If a docker with ElasticSearch is disabled, Indexer will display error: "N *Update on Schedule* mode observes changes in corresponding tables, and probably will be more relevant in most cases. It is the default mode in any bigger stores. + + ### Compatibility +--- Tested with ES: 5.6.11, 6.8.0, 7.6.2 -- Vue Storefront >= 1.4.4 Module was tested on: diff --git a/composer.json b/composer.json index bcaf2613..ea8de6c7 100644 --- a/composer.json +++ b/composer.json @@ -1,57 +1,47 @@ { - "name": "divante/magento2-vsbridge-indexer", - "type": "magento2-component", - "license": "MIT", - "authors": [{ - "name": "Agata", - "email": "afirlejczyk@divante.pl" - }], - "keywords": [ - "magento", - "magento2", - "vuestorefront" - ], - "repositories": [ - { - "type": "composer", - "url": "https://repo.magento.com/" - } - ], - "require": { - "php": ">=7.0.2", - "magento/framework": ">=101.0.0", - "magento/module-store": ">=100.2.0", - "magento/module-backend": ">=100.2.0", - "magento/module-catalog": ">=102.0.0", - "magento/magento-composer-installer": "*", - "elasticsearch/elasticsearch": "~5.1|~6.1" - }, - "replace": { - "divante/module-vsf-indexer-core": "self.version", - "divante/module-vsf-indexer-catalog": "self.version", - "divante/module-vsf-indexer-tax": "self.version", - "divante/module-vsf-indexer-cms": "self.version" - }, - "autoload": { - "files": [ - "src/module-vsbridge-indexer-agreement/registration.php", - "src/module-vsbridge-indexer-core/registration.php", - "src/module-vsbridge-indexer-catalog/registration.php", - "src/module-vsbridge-indexer-cms/registration.php", - "src/module-vsbridge-indexer-review/registration.php", - "src/module-vsbridge-indexer-tax/registration.php", - "src/module-vsbridge-downloadable/registration.php" + "name": "divante/magento2-vsbridge-indexer", + "type": "magento2-component", + "license": "MIT", + "keywords": [ + "magento", + "magento2", + "vuestorefront" ], - "psr-4": { - "Divante\\VsbridgeIndexerAgreement\\": "src/module-vsbridge-indexer-agreement", - "Divante\\VsbridgeIndexerCore\\": "src/module-vsbridge-indexer-core", - "Divante\\VsbridgeIndexerCatalog\\": "src/module-vsbridge-indexer-catalog", - "Divante\\VsbridgeIndexerCms\\": "src/module-vsbridge-indexer-cms", - "Divante\\VsbridgeIndexerReview\\": "src/module-vsbridge-indexer-review", - "Divante\\VsbridgeIndexerTax\\": "src/module-vsbridge-indexer-tax", - "Divante\\VsbridgeDownloadable\\": "src/module-vsbridge-downloadable" - } - }, - "minimum-stability": "dev", - "prefer-stable": true + "repositories": [ + { + "type": "composer", + "url": "https://repo.magento.com/" + } + ], + "require": { + "php": ">=7.0.2", + "magento/framework": ">=101.0.0", + "magento/module-store": ">=100.2.0", + "magento/module-backend": ">=100.2.0", + "magento/module-catalog": ">=102.0.0", + "magento/magento-composer-installer": "*", + "elasticsearch/elasticsearch": "~5.1|~6.1|~7.1" + }, + "autoload": { + "files": [ + "src/module-vsbridge-indexer-core/registration.php", + "src/module-vsbridge-indexer-catalog/registration.php", + "src/module-vsbridge-indexer-cms/registration.php", + "src/module-vsbridge-indexer-review/registration.php", + "src/module-vsbridge-indexer-tax/registration.php", + "src/module-vsbridge-downloadable/registration.php", + "src/module-vsbridge-indexer-agreement/registration.php" + ], + "psr-4": { + "Divante\\VsbridgeIndexerAgreement\\": "src/module-vsbridge-indexer-agreement", + "Divante\\VsbridgeIndexerCore\\": "src/module-vsbridge-indexer-core", + "Divante\\VsbridgeIndexerCatalog\\": "src/module-vsbridge-indexer-catalog", + "Divante\\VsbridgeIndexerCms\\": "src/module-vsbridge-indexer-cms", + "Divante\\VsbridgeIndexerReview\\": "src/module-vsbridge-indexer-review", + "Divante\\VsbridgeIndexerTax\\": "src/module-vsbridge-indexer-tax", + "Divante\\VsbridgeDownloadable\\": "src/module-vsbridge-downloadable" + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/docs/configuration.md b/docs/configuration.md index b483e8e0..898e46c9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -5,6 +5,11 @@ Go to the new ‘Indexer’ section (Stores → Configuration → Vuestorefront Enable to export data to elasticsearch. By default indexing is disable. ![](./images/config-general-enable.png) + +1. ####General settings → Elasticsearch version + Select ES version depends on which ES you are using/is used by magento. + If you are using ES 5.x.x, choose Elasticsearch5, if you are using version 6.x.x or 7.x.x choose Elasticsearch6+. + By default Elasticsearch5 option is selected. 1. ####General settings → List of stores to reindex diff --git a/src/module-vsbridge-indexer-agreement/Model/Indexer/Action/Agreement.php b/src/module-vsbridge-indexer-agreement/Model/Indexer/Action/Agreement.php index 07861398..8beea3aa 100644 --- a/src/module-vsbridge-indexer-agreement/Model/Indexer/Action/Agreement.php +++ b/src/module-vsbridge-indexer-agreement/Model/Indexer/Action/Agreement.php @@ -1,15 +1,14 @@ indexHandler = $indexerHandler; - $this->agreementAction = $action; - $this->storeManager = $storeManager; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->agreementAction->rebuild($store->getId(), $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->agreementAction->rebuild($store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-agreement/etc/di.xml b/src/module-vsbridge-indexer-agreement/etc/di.xml index 3ec84e5a..557a0350 100644 --- a/src/module-vsbridge-indexer-agreement/etc/di.xml +++ b/src/module-vsbridge-indexer-agreement/etc/di.xml @@ -1,15 +1,18 @@ - + + - vue_storefront_catalog agreement - + + - Divante\VsbridgeIndexerAgreement\Indexer\AgreementIndexerHandlerVirtual + + Divante\VsbridgeIndexerAgreement\Model\Indexer\Action\Agreement + diff --git a/src/module-vsbridge-indexer-agreement/etc/vsbridge.xml b/src/module-vsbridge-indexer-agreement/etc/vsbridge.xml new file mode 100644 index 00000000..26884c27 --- /dev/null +++ b/src/module-vsbridge-indexer-agreement/etc/vsbridge.xml @@ -0,0 +1,4 @@ + + + diff --git a/src/module-vsbridge-indexer-agreement/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-agreement/etc/vsbridge_indices.xml deleted file mode 100644 index 3e540428..00000000 --- a/src/module-vsbridge-indexer-agreement/etc/vsbridge_indices.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php index ef313517..5b3b773d 100644 --- a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php +++ b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php @@ -9,7 +9,7 @@ namespace Divante\VsbridgeIndexerCatalog\ArrayConverter\Product; -use Divante\VsbridgeIndexerCore\Indexer\DataFilter; +use Divante\VsbridgeIndexerCore\Index\DataFilter; use Divante\VsbridgeIndexerCatalog\Api\ArrayConverter\Product\CustomOptionConverterInterface; /** diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Attribute.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Attribute.php index 306c4159..089b639d 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Attribute.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Attribute.php @@ -1,21 +1,16 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\Action; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Attribute as ResourceModel; use Divante\VsbridgeIndexerCatalog\Index\Mapping\Attribute as AttributeMapping; use Divante\VsbridgeIndexerCore\Api\ConvertValueInterface; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class Attribute */ -class Attribute +class Attribute implements RebuildActionInterface { /** * @var ResourceModel @@ -50,11 +45,12 @@ public function __construct( } /** + * @param int $storeId * @param array $attributeIds * * @return \Traversable */ - public function rebuild(array $attributeIds = []) + public function rebuild(int $storeId, array $attributeIds): \Traversable { $lastAttributeId = 0; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php index 813da968..66a2900a 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php @@ -1,19 +1,14 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\Action; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category as ResourceModel; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class Category */ -class Category +class Category implements RebuildActionInterface { /** * @var ResourceModel @@ -37,7 +32,7 @@ public function __construct(ResourceModel $resourceModel) * @return \Generator * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function rebuild($storeId = 1, array $categoryIds = []) + public function rebuild(int $storeId, array $categoryIds): \Traversable { $lastCategoryId = 0; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php index c9817921..4a064b60 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php @@ -1,19 +1,14 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\Action; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product as ResourceModel; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class Product */ -class Product +class Product implements RebuildActionInterface { /** * @var ResourceModel @@ -38,7 +33,7 @@ public function __construct(ResourceModel $resourceModel) * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function rebuild($storeId = 1, array $productIds = []) + public function rebuild(int $storeId, array $productIds) : \Traversable { $lastProductId = 0; @@ -48,7 +43,7 @@ public function rebuild($storeId = 1, array $productIds = []) } do { - $products = $this->resourceModel->getProducts($storeId, $productIds, $lastProductId); + $products = $this->resourceModel->getProducts($storeId, $productIds, $lastProductId, 10); /** @var array $product */ foreach ($products as $product) { diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Attribute.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Attribute.php deleted file mode 100644 index fd462a48..00000000 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Attribute.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Model\Indexer; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Attribute as AttributeAction; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; - -/** - * Class Attribute - */ -class Attribute implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var AttributeAction - */ - private $attributeAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * Attribute constructor. - * - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param AttributeAction $action - */ - public function __construct( - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - AttributeAction $action - ) { - $this->indexHandler = $indexerHandler; - $this->attributeAction = $action; - $this->storeManager = $storeManager; - } - - /** - * @param int[] $ids - * - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->attributeAction->rebuild($ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->attributeAction->rebuild(), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Category.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Category.php deleted file mode 100644 index 59cc2653..00000000 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Category.php +++ /dev/null @@ -1,105 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Model\Indexer; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Category as Action; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCore\Cache\Processor as CacheProcessor; - -/** - * Class Category - */ -class Category implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var Action - */ - private $categoryAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * @var CacheProcessor - */ - private $cacheProcessor; - - /** - * Category constructor. - * - * @param CacheProcessor $cacheProcessor - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param Action $action - */ - public function __construct( - CacheProcessor $cacheProcessor, - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - Action $action - ) { - $this->categoryAction = $action; - $this->storeManager = $storeManager; - $this->indexHandler = $indexerHandler; - $this->cacheProcessor = $cacheProcessor; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $storeId = $store->getId(); - $this->indexHandler->saveIndex($this->categoryAction->rebuild($storeId, $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - $this->cacheProcessor->cleanCacheByDocIds($storeId, $this->indexHandler->getTypeName(), $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->categoryAction->rebuild($store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - $this->cacheProcessor->cleanCacheByTags($store->getId(), [$this->indexHandler->getTypeName()]); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php index 733708bc..b4651be8 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php @@ -9,7 +9,6 @@ namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Category; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\Children as CategoryChildrenResource; -use Divante\VsbridgeIndexerCore\Indexer\DataFilter; use Divante\VsbridgeIndexerCatalog\Model\Attributes\CategoryAttributes; use Divante\VsbridgeIndexerCatalog\Model\Attributes\CategoryChildAttributes; use Divante\VsbridgeIndexerCatalog\Model\SystemConfig\CategoryConfigInterface; @@ -17,6 +16,7 @@ use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\AttributeDataProvider; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\ProductCount as ProductCountResourceModel; use Divante\VsbridgeIndexerCatalog\Api\DataProvider\Category\AttributeDataProviderInterface; +use Divante\VsbridgeIndexerCore\Index\DataFilter; /** * Class AttributeData @@ -67,7 +67,7 @@ class AttributeData implements AttributeDataProviderInterface private $productCountResource; /** - * @var \Divante\VsbridgeIndexerCore\Indexer\DataFilter + * @var \Divante\VsbridgeIndexerCore\Index\DataFilter */ private $dataFilter; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php index dacf52f4..fc91bee4 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php @@ -10,7 +10,7 @@ use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product\AttributeDataProvider; use Divante\VsbridgeIndexerCore\Api\DataProviderInterface; -use Divante\VsbridgeIndexerCore\Indexer\DataFilter; +use Divante\VsbridgeIndexerCore\Index\DataFilter; use Divante\VsbridgeIndexerCatalog\Api\CatalogConfigurationInterface; use Divante\VsbridgeIndexerCatalog\Api\SlugGeneratorInterface; use Divante\VsbridgeIndexerCatalog\Model\ProductUrlPathGenerator; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php index 1ba35219..1a592c25 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php @@ -9,7 +9,7 @@ namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product; use Divante\VsbridgeIndexerCore\Api\DataProviderInterface; -use Divante\VsbridgeIndexerCore\Indexer\DataFilter; +use Divante\VsbridgeIndexerCore\Index\DataFilter; use Divante\VsbridgeIndexerCatalog\Api\LoadInventoryInterface; use Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\Configurable\LoadChildrenRawAttributes; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Product.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Product.php deleted file mode 100644 index 6fd3a418..00000000 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Product.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Model\Indexer; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Product as ProductAction; - -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Cache\Processor as CacheProcessor; - -/** - * Class Product - */ -class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var ProductAction - */ - private $productAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * @var CacheProcessor - */ - private $cacheProcessor; - - /** - * Product constructor. - * - * @param CacheProcessor $cacheProcessor - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param ProductAction $action - */ - public function __construct( - CacheProcessor $cacheProcessor, - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - ProductAction $action - ) { - $this->productAction = $action; - $this->indexHandler = $indexerHandler; - $this->storeManager = $storeManager; - $this->cacheProcessor = $cacheProcessor; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $storeId = $store->getId(); - $this->indexHandler->saveIndex($this->productAction->rebuild($storeId, $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - $this->cacheProcessor->cleanCacheByDocIds($storeId, $this->indexHandler->getTypeName(), $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->productAction->rebuild($store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - $this->cacheProcessor->cleanCacheByTags($store->getId(), [$this->indexHandler->getTypeName()]); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-catalog/etc/di.xml b/src/module-vsbridge-indexer-catalog/etc/di.xml index ce759c93..56d6f607 100644 --- a/src/module-vsbridge-indexer-catalog/etc/di.xml +++ b/src/module-vsbridge-indexer-catalog/etc/di.xml @@ -38,66 +38,60 @@ - + - vue_storefront_catalog - product + attribute - + + - vue_storefront_catalog - product + category - + + - vue_storefront_catalog - category + product - - vue_storefront_catalog - attribute + product - - - - - - - - - - Divante\VsbridgeIndexerCatalog\Indexer\ProductIndexerHandlerVirtual - - Divante\VsbridgeIndexerCatalog\Indexer\ProductCategoryHandlerVirtual - + + - Divante\VsbridgeIndexerCatalog\Indexer\CategoryIndexerHandlerVirtual + + Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Attribute + Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Product + Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Category + - - - Divante\VsbridgeIndexerCatalog\Indexer\AttributeIndexerHandlerVirtual - + + + + + + + type="Divante\VsbridgeIndexerCore\Index\DataFilter"> level @@ -115,7 +109,7 @@ + type="Divante\VsbridgeIndexerCore\Index\DataFilter"> sort_order @@ -274,11 +268,36 @@ - + - + Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductCategoryProcessor::INDEXER_ID + + + + + + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\AttributeData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\BundleOptionsData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\CategoryData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\PriceData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\MediaGalleryData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ProductLinksData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\Inventory + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ConfigurableData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\CustomOptions + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\AttributesMetadata + + + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Category\AttributeData + + + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Attribute\Options + + + + diff --git a/src/module-vsbridge-indexer-catalog/etc/vsbridge.xml b/src/module-vsbridge-indexer-catalog/etc/vsbridge.xml new file mode 100755 index 00000000..a418c128 --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/etc/vsbridge.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml deleted file mode 100755 index f75a53f2..00000000 --- a/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\AttributeData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\BundleOptionsData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\CategoryData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\PriceData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\MediaGalleryData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ProductLinksData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\Inventory - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ConfigurableData - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\CustomOptions - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\AttributesMetadata - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ParentSku - - - - - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Category\AttributeData - - - - - Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Attribute\Options - - - - diff --git a/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsBlock.php b/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsBlock.php index 2dc78653..bfee6629 100644 --- a/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsBlock.php +++ b/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsBlock.php @@ -9,11 +9,12 @@ namespace Divante\VsbridgeIndexerCms\Model\Indexer\Action; use Divante\VsbridgeIndexerCms\Model\ResourceModel\CmsBlock as CmsBlockResource; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class CmsBlock */ -class CmsBlock +class CmsBlock implements RebuildActionInterface { /** * @var CmsBlockResource @@ -37,7 +38,7 @@ public function __construct( * * @return \Traversable */ - public function rebuild($storeId = 1, array $blockIds = []) + public function rebuild(int $storeId, array $blockIds): \Traversable { $lastBlockId = 0; diff --git a/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsPage.php b/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsPage.php index 157c5275..0356665d 100644 --- a/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsPage.php +++ b/src/module-vsbridge-indexer-cms/Model/Indexer/Action/CmsPage.php @@ -9,11 +9,12 @@ namespace Divante\VsbridgeIndexerCms\Model\Indexer\Action; use Divante\VsbridgeIndexerCms\Model\ResourceModel\CmsPage as CmsPageResource; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class CmsPage */ -class CmsPage +class CmsPage implements RebuildActionInterface { /** * @var CmsPageResource @@ -36,7 +37,7 @@ public function __construct(CmsPageResource $cmsBlockResource) * * @return \Traversable */ - public function rebuild($storeId = 1, array $pageIds = []) + public function rebuild(int $storeId, array $pageIds): \Traversable { $lastPageId = 0; @@ -46,7 +47,7 @@ public function rebuild($storeId = 1, array $pageIds = []) foreach ($cmsPages as $pageData) { $lastPageId = (int)$pageData['page_id']; $pageData['id'] = $lastPageId; - $pageData['content'] = $pageData['content']; + $pageData['content'] = (string) $pageData['content']; $pageData['active'] = (bool)$pageData['is_active']; if (isset($pageData['sort_order'])) { diff --git a/src/module-vsbridge-indexer-cms/Model/Indexer/CmsBlock.php b/src/module-vsbridge-indexer-cms/Model/Indexer/CmsBlock.php deleted file mode 100644 index 08790d7c..00000000 --- a/src/module-vsbridge-indexer-cms/Model/Indexer/CmsBlock.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCms\Model\Indexer; - -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCms\Model\Indexer\Action\CmsBlock as CmsBlockAction; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; - -/** - * Class CmsBlock - */ -class CmsBlock implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var CmsBlockAction - */ - private $cmsBlockAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * CmsBlock constructor. - * - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param CmsBlockAction $action - */ - public function __construct( - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - CmsBlockAction $action - ) { - $this->indexHandler = $indexerHandler; - $this->cmsBlockAction = $action; - $this->storeManager = $storeManager; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->cmsBlockAction->rebuild($store->getId(), $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->cmsBlockAction->rebuild($store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-cms/Model/Indexer/CmsPage.php b/src/module-vsbridge-indexer-cms/Model/Indexer/CmsPage.php deleted file mode 100644 index dd31f631..00000000 --- a/src/module-vsbridge-indexer-cms/Model/Indexer/CmsPage.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCms\Model\Indexer; - -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCms\Model\Indexer\Action\CmsPage as CmsPageAction; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; - -/** - * Class CmsPage - */ -class CmsPage implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var CmsPageAction - */ - private $cmsPageAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * CmsBlock constructor. - * - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param CmsPageAction $action - */ - public function __construct( - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - CmsPageAction $action - ) { - $this->indexHandler = $indexerHandler; - $this->storeManager = $storeManager; - $this->cmsPageAction = $action; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->cmsPageAction->rebuild($store->getId(), $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->cmsPageAction->rebuild($store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-cms/Model/Indexer/DataProvider/CmsContentFilter.php b/src/module-vsbridge-indexer-cms/Model/Indexer/DataProvider/CmsContentFilter.php index d482d27a..2daa2a25 100644 --- a/src/module-vsbridge-indexer-cms/Model/Indexer/DataProvider/CmsContentFilter.php +++ b/src/module-vsbridge-indexer-cms/Model/Indexer/DataProvider/CmsContentFilter.php @@ -1,10 +1,4 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCms\Model\Indexer\DataProvider; diff --git a/src/module-vsbridge-indexer-cms/Model/ResourceModel/CmsPage.php b/src/module-vsbridge-indexer-cms/Model/ResourceModel/CmsPage.php index c7ba9026..a92d6951 100644 --- a/src/module-vsbridge-indexer-cms/Model/ResourceModel/CmsPage.php +++ b/src/module-vsbridge-indexer-cms/Model/ResourceModel/CmsPage.php @@ -52,7 +52,7 @@ public function __construct( */ public function loadPages($storeId = 1, array $pageIds = [], $fromId = 0, $limit = 1000) { - $metaData = $this->getCmsPageMetaData(); + $metaData = $this->metaDataPool->getMetadata(PageInterface::class); $linkFieldId = $metaData->getLinkField(); $select = $this->getConnection()->select()->from(['cms_page' => $metaData->getEntityTable()]); @@ -89,13 +89,4 @@ private function getConnection() { return $this->resource->getConnection(); } - - /** - * @return \Magento\Framework\EntityManager\EntityMetadataInterface - * @throws \Exception - */ - private function getCmsPageMetaData() - { - return $this->metaDataPool->getMetadata(PageInterface::class); - } } diff --git a/src/module-vsbridge-indexer-cms/etc/di.xml b/src/module-vsbridge-indexer-cms/etc/di.xml index 37d9ccf5..af27eb8e 100644 --- a/src/module-vsbridge-indexer-cms/etc/di.xml +++ b/src/module-vsbridge-indexer-cms/etc/di.xml @@ -1,29 +1,39 @@ - + + - vue_storefront_catalog cms_block - + - Divante\VsbridgeIndexerCms\Indexer\CmsBlockIndexerHandlerVirtual + cms_page - + - + - vue_storefront_catalog - cms_page + + Divante\VsbridgeIndexerCms\Model\Indexer\Action\CmsPage + Divante\VsbridgeIndexerCms\Model\Indexer\Action\CmsBlock + - - + + + - Divante\VsbridgeIndexerCms\Indexer\CmsPageIndexerHandlerVirtual + + + Divante\VsbridgeIndexerCms\Model\Indexer\DataProvider\Page\ContentData + + + Divante\VsbridgeIndexerCms\Model\Indexer\DataProvider\Page\ContentData + + @@ -33,4 +43,16 @@ + + + + Magento\Widget\Model\Template\FilterEmulate + Magento\Widget\Model\Template\FilterEmulate + + + + + WidgetEmulateFilterProvider + + diff --git a/src/module-vsbridge-indexer-cms/etc/vsbridge.xml b/src/module-vsbridge-indexer-cms/etc/vsbridge.xml new file mode 100644 index 00000000..c73f258e --- /dev/null +++ b/src/module-vsbridge-indexer-cms/etc/vsbridge.xml @@ -0,0 +1,5 @@ + + + + diff --git a/src/module-vsbridge-indexer-cms/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-cms/etc/vsbridge_indices.xml deleted file mode 100644 index f8131068..00000000 --- a/src/module-vsbridge-indexer-cms/etc/vsbridge_indices.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - Divante\VsbridgeIndexerCms\Model\Indexer\DataProvider\Block\ContentData - - - - - Divante\VsbridgeIndexerCms\Model\Indexer\DataProvider\Page\ContentData - - - - diff --git a/src/module-vsbridge-indexer-core/Api/Index/DataProviderResolverInterface.php b/src/module-vsbridge-indexer-core/Api/Index/DataProviderResolverInterface.php new file mode 100644 index 00000000..bf56e399 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Api/Index/DataProviderResolverInterface.php @@ -0,0 +1,19 @@ +collectionFactory = $collectionFactory; + $this->excludedIndices = $excludedIndices; + } + + /** + * @return \Magento\Framework\Indexer\IndexerInterface[] + */ + public function getValidIndices() + { + $indexers = $this->collectionFactory->create()->getItems(); + + return array_filter($indexers, function ($indexer) { + if (in_array($indexer->getId(), $this->excludedIndices)) { + return false; + } + + return substr($indexer->getId(), 0, 9) === self::VSBRIDGE_INDEXER_PREFIX; + }); + } + + /** + * @return string[] + */ + public function getInvalidIndicesNames(): array + { + $invalid = []; + + foreach ($this->getValidIndices() as $indexer) { + if (!$indexer->isWorking()) { + continue; + } + + $invalid[] = $indexer->getTitle(); + } + + return $invalid; + } +} diff --git a/src/module-vsbridge-indexer-core/Console/Command/Rebuild/Rebuild.php b/src/module-vsbridge-indexer-core/Console/Command/Rebuild/Rebuild.php new file mode 100644 index 00000000..b0981368 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Console/Command/Rebuild/Rebuild.php @@ -0,0 +1,160 @@ +indexOperations = $indexOperations; + $this->indexerStoreManager = $indexerStoreManager; + $this->indexerRegistry = $indexerRegistry; + $this->output = $output; + $this->indicesProvider = $indicesProvider; + $this->elasticsearchResolver = $elasticsearchResolver; + } + + /** + * @param string|null $storeId + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function execute(string $storeId = null) + { + if (!$this->validate()) { + return; + } + + if ($storeId && !$this->indexerStoreManager->isStoreAllowedToReindex($storeId)) { + $this->output->writeln("Store " . $storeId . " is not allowed."); + return; + } + + $storeList = $this->indexerStoreManager->getStores($storeId); + + foreach ($storeList as $store) { + $this->output->writeln( + "Reindexing all VS indexes for store " . $store->getName() . "..." + ); + $this->reindexStore($store); + $this->output->writeln("Reindexing has completed!"); + } + } + + /** + * Validate indices + * + * @return bool + */ + private function validate(): bool + { + $invalidIndices = $this->indicesProvider->getInvalidIndicesNames(); + + if (!empty($invalidIndices)) { + $message = 'Some indices has invalid status: '. implode(', ', $invalidIndices) . '. '; + $message .= 'Please change indices status to VALID manually or use bin/magento vsbridge:reset command.'; + $this->output->writeln("WARNING: Indexation can't be executed. $message"); + + return false; + } + + return true; + } + + /** + * Reindex each vsbridge index for the specified store + * + * @param StoreInterface $store + */ + private function reindexStore(StoreInterface $store) + { + $this->indexerStoreManager->override([$store]); + $esVersion = $this->elasticsearchResolver->getVersion(); + + if ($esVersion === ElasticsearchResolverInterface::DEFAULT_ES_VERSION) { + $index = $this->indexOperations->createIndex(IndexSettings::DUMMY_INDEX_IDENTIFIER, $store); + $this->indexerRegistry->setFullReIndexationIsInProgress(); + } + + foreach ($this->indicesProvider->getValidIndices() as $indexer) { + try { + $startTime = microtime(true); + $indexer->reindexAll(); + + $resultTime = microtime(true) - $startTime; + + $this->output->writeln( + $indexer->getTitle() . ' index has been rebuilt successfully in ' . gmdate('H:i:s', $resultTime) + ); + } catch (LocalizedException $e) { + $this->output->writeln(sprintf('%s', $e->getMessage())); + } catch (\Exception $e) { + $this->output->writeln($indexer->getTitle()); + $this->output->writeln(sprintf('%s', $e->getMessage())); + } + } + + if ($esVersion === ElasticsearchResolverInterface::DEFAULT_ES_VERSION) { + $this->indexOperations->switchIndexer($store->getId(), $index->getName(), $index->getAlias()); + $this->output->writeln( + sprintf('Index name: %s, index alias: %s', $index->getName(), $index->getAlias()) + ); + } + } +} diff --git a/src/module-vsbridge-indexer-core/Console/Command/Rebuild/RebuildFactory.php b/src/module-vsbridge-indexer-core/Console/Command/Rebuild/RebuildFactory.php new file mode 100644 index 00000000..3d221744 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Console/Command/Rebuild/RebuildFactory.php @@ -0,0 +1,36 @@ +objectManager = $objectManager; + } + + /** + * @param OutputInterface $output + * @return Rebuild + */ + public function create(OutputInterface $output): Rebuild + { + return $this->objectManager->create(Rebuild::class, ['output' => $output]); + } +} diff --git a/src/module-vsbridge-indexer-core/Console/Command/RebuildEsIndexCommand.php b/src/module-vsbridge-indexer-core/Console/Command/RebuildEsIndexCommand.php index 223032a7..1c49b910 100644 --- a/src/module-vsbridge-indexer-core/Console/Command/RebuildEsIndexCommand.php +++ b/src/module-vsbridge-indexer-core/Console/Command/RebuildEsIndexCommand.php @@ -1,84 +1,52 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCore\Console\Command; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCore\Api\IndexOperationInterface; -use Divante\VsbridgeIndexerCore\Model\IndexerRegistry; -use Magento\Framework\App\ObjectManagerFactory; -use Magento\Framework\Console\Cli; +use Divante\VsbridgeIndexerCore\Console\Command\Rebuild\RebuildFactory; +use Divante\VsbridgeIndexerCore\Console\Command\Rebuild\Rebuild; +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Framework\App\State; use Magento\Framework\Event\ManagerInterface; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Indexer\IndexerInterface; -use Magento\Indexer\Console\Command\AbstractIndexerCommand; -use Magento\Store\Api\Data\StoreInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Magento\Store\Model\StoreManagerInterface; /** * Class IndexerReindexCommand */ -class RebuildEsIndexCommand extends AbstractIndexerCommand +class RebuildEsIndexCommand extends Command { const INPUT_STORE = 'store'; const INPUT_ALL_STORES = 'all'; - const INDEX_IDENTIFIER = 'vue_storefront_catalog'; - - /** - * @var IndexOperationInterface - */ - private $indexOperations; - - /** - * @var StoreManager - */ - private $indexerStoreManager; - - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var IndexerRegistry - */ - private $indexerRegistry; + /** @var RebuildFactory */ + private $rebuildIndicesFactory; - /** - * @var array - */ - private $excludeIndices = []; + /** @var State */ + private $appState; - /** - * @var ManagerInterface - */ + /** @var ManagerInterface */ private $eventManager; /** * RebuildEsIndexCommand constructor. - * - * @param ObjectManagerFactory $objectManagerFactory - * @param ManagerInterface $eventManager - * @param array $excludeIndices + * @param RebuildFactory $rebuildIndicesFactory + * @param ManagerInterface $manager + * @param State $appState */ public function __construct( - ObjectManagerFactory $objectManagerFactory, - ManagerInterface $eventManager, // Proxy - array $excludeIndices = [] + RebuildFactory $rebuildIndicesFactory, + ManagerInterface $manager, + State $appState ) { - $this->excludeIndices = $excludeIndices; - parent::__construct($objectManagerFactory); - $this->eventManager = $eventManager; + $this->appState = $appState; + $this->eventManager = $manager; + $this->rebuildIndicesFactory = $rebuildIndicesFactory; + + parent::__construct(); } /** @@ -111,242 +79,43 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->initObjectManager(); $output->setDecorated(true); - $storeId = $input->getOption(self::INPUT_STORE); - $allStores = $input->getOption(self::INPUT_ALL_STORES); - - $invalidIndices = $this->getInvalidIndices(); - if (!empty($invalidIndices)) { - $message = 'Some indices has invalid status: '. implode(', ', $invalidIndices) . '. '; - $message .= 'Please change indices status to VALID manually or use bin/magento vsbridge:reset command.'; - $output->writeln("WARNING: Indexation can't be executed. $message"); - return; + try { + $this->appState->getAreaCode(); + } catch (LocalizedException $exception) { + $this->appState->setAreaCode(FrontNameResolver::AREA_CODE); } - if (!$storeId && !$allStores) { + $store = $input->getOption(self::INPUT_STORE); + $allStores = $input->getOption(self::INPUT_ALL_STORES); + + if (!$store && !$allStores) { $output->writeln( - "Not enough information provided, nothing has been reindexed. Try using --help for more information." + "Not enough information provided, nothing has been reindexed. +Try using --help for more information." ); - } else { - $this->reindex($output, $storeId, $allStores); - } - } - /** - * @return array - */ - private function getInvalidIndices() - { - $invalid = []; - - foreach ($this->getIndexers() as $indexer) { - if ($indexer->isWorking()) { - $invalid[] = $indexer->getTitle(); - } - } - - return $invalid; - } - - /*** - * @param OutputInterface $output - * @param $storeId - * @param $allStores - * - * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function reindex(OutputInterface $output, $storeId, $allStores) - { - $this->eventManager->dispatch('vsbridge_indexer_reindex_before', [ - 'storeId' => $storeId, - 'allStores' => $allStores, - ]); - - if ($storeId) { - $store = $this->getStoreManager()->getStore($storeId); - $returnValue = false; - - if ($this->isAllowedToReindex($store)) { - $output->writeln("Reindexing all VS indexes for store " . $store->getName() . "..."); - $returnValue = $this->reindexStore($store, $output); - $output->writeln("Reindexing has completed!"); - } else { - $output->writeln("Store " . $store->getName() . " is not allowed."); - } - - return $returnValue; - } elseif ($allStores) { - $output->writeln("Reindexing all stores..."); - $returnValues = []; - $allowedStores = $this->getStoresAllowedToReindex(); - - /** @var \Magento\Store\Api\Data\StoreInterface $store */ - foreach ($allowedStores as $store) { - $output->writeln("Reindexing store " . $store->getName() . "..."); - $returnValues[] = $this->reindexStore($store, $output); - } - - $output->writeln("All stores have been reindexed!"); - - // If failure returned in any store return failure now - return in_array(Cli::RETURN_FAILURE, $returnValues) ? Cli::RETURN_FAILURE : Cli::RETURN_SUCCESS; - } - - $this->eventManager->dispatch('vsbridge_indexer_reindex_after', [ - 'storeId' => $storeId, - 'allStores' => $allStores, - ]); - } - - /** - * Check if Store is allowed to reindex - * - * @param StoreInterface $store - * - * @return bool - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function isAllowedToReindex(\Magento\Store\Api\Data\StoreInterface $store): bool - { - $allowedStores = $this->getStoresAllowedToReindex(); - - foreach ($allowedStores as $allowedStore) { - if ($store->getId() === $allowedStore->getId()) { - return true; - } - } - - return false; - } - - /** - * Reindex each vsbridge index for the specified store - * - * @param \Magento\Store\Api\Data\StoreInterface $store - * @param \Symfony\Component\Console\Output\OutputInterface $output - * - * @return int - */ - private function reindexStore(StoreInterface $store, OutputInterface $output) - { - $this->getIndexerStoreManager()->override([$store]); - $index = $this->getIndexOperations()->createIndex(self::INDEX_IDENTIFIER, $store); - $this->getIndexerRegistry()->setFullReIndexationIsInProgress(); - - $returnValue = Cli::RETURN_FAILURE; - - foreach ($this->getIndexers() as $indexer) { - try { - $startTime = microtime(true); - $indexer->reindexAll(); - - $resultTime = microtime(true) - $startTime; - $output->writeln( - $indexer->getTitle() . ' index has been rebuilt successfully in ' . gmdate('H:i:s', $resultTime) - ); - $returnValue = Cli::RETURN_SUCCESS; - } catch (LocalizedException $e) { - $output->writeln("" . $e->getMessage() . ""); - } catch (\Exception $e) { - $output->writeln("" . $indexer->getTitle() . ' indexer process unknown error:'); - $output->writeln("" . $e->getMessage() . ""); - } + return; } - $this->getIndexOperations()->switchIndexer($store->getId(), $index->getName(), $index->getAlias()); - - $output->writeln( - sprintf('Index name: %s, index alias: %s', $index->getName(), $index->getAlias()) + $this->eventManager->dispatch( + 'vsbridge_indexer_reindex_before', + [ + 'store' => $store, + 'allStores' => $allStores, + ] ); - return $returnValue; - } - - /** - * @return IndexerInterface[] - */ - private function getIndexers() - { - /** @var IndexerInterface[] */ - $indexers = $this->getAllIndexers(); - $vsbridgeIndexers = []; - - foreach ($indexers as $indexer) { - $indexId = $indexer->getId(); - - if (substr($indexId, 0, 9) === 'vsbridge_' && !in_array($indexId, $this->excludeIndices)) { - $vsbridgeIndexers[] = $indexer; - } - } - - return $vsbridgeIndexers; - } - - /** - * @return array - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function getStoresAllowedToReindex(): array - { - return $this->getIndexerStoreManager()->getStores(); - } - - /** - * @return StoreManagerInterface - */ - private function getStoreManager() - { - if (null === $this->storeManager) { - $this->storeManager = $this->getObjectManager()->get(StoreManagerInterface::class); - } - - return $this->storeManager; - } - - /** - * @return StoreManager - */ - private function getIndexerStoreManager() - { - if (null === $this->indexerStoreManager) { - $this->indexerStoreManager = $this->getObjectManager()->get(StoreManager::class); - } - - return $this->indexerStoreManager; - } - - /** - * @return IndexerRegistry - */ - private function getIndexerRegistry() - { - if (null === $this->indexerRegistry) { - $this->indexerRegistry = $this->getObjectManager()->get(IndexerRegistry::class); - } + $rebuildIndicesCommand = $this->rebuildIndicesFactory->create($output); + $rebuildIndicesCommand->execute($input->getOption(self::INPUT_STORE)); - return $this->indexerRegistry; - } - - /** - * @return IndexOperationInterface - */ - private function getIndexOperations() - { - if (null === $this->indexOperations) { - $this->indexOperations = $this->getObjectManager()->get(IndexOperationInterface::class); - } - - return $this->indexOperations; - } - - /** - * Initiliaze object manager - */ - private function initObjectManager() - { - $this->getObjectManager(); + $this->eventManager->dispatch( + 'vsbridge_indexer_reindex_after', + [ + 'store' => $store, + 'allStores' => $allStores, + ] + ); } } diff --git a/src/module-vsbridge-indexer-core/Elasticsearch/Client.php b/src/module-vsbridge-indexer-core/Elasticsearch/Client.php index 66c656a5..05c3f634 100644 --- a/src/module-vsbridge-indexer-core/Elasticsearch/Client.php +++ b/src/module-vsbridge-indexer-core/Elasticsearch/Client.php @@ -3,6 +3,7 @@ namespace Divante\VsbridgeIndexerCore\Elasticsearch; use Divante\VsbridgeIndexerCore\Api\Client\ClientInterface; +use Divante\VsbridgeIndexerCore\Model\ElasticsearchResolverInterface; /** * Class Client @@ -14,14 +15,23 @@ class Client implements ClientInterface */ private $client; + /** + * @var ElasticsearchResolverInterface + */ + private $esVersionResolver; + /** * Client constructor. * + * @param ElasticsearchResolverInterface $esVersionResolver * @param \Elasticsearch\Client $client */ - public function __construct(\Elasticsearch\Client $client) - { + public function __construct( + ElasticsearchResolverInterface $esVersionResolver, + \Elasticsearch\Client $client + ) { $this->client = $client; + $this->esVersionResolver = $esVersionResolver; } /** @@ -161,13 +171,19 @@ public function deleteIndex(string $indexName) */ public function putMapping(string $indexName, string $type, array $mapping) { - $this->client->indices()->putMapping( - [ - 'index' => $indexName, - 'type' => $type, - 'body' => [$type => $mapping], - ] - ); + $requestPayload = [ + 'index' => $indexName, + 'type' => $type, + 'body' => [$type => $mapping] + ]; + + $esVersion = $this->esVersionResolver->getVersion(); + + if ($esVersion === ElasticsearchResolverInterface::ES_6_PLUS_VERSION) { + $requestPayload['include_type_name'] = true; + } + + $this->client->indices()->putMapping($requestPayload); } /** diff --git a/src/module-vsbridge-indexer-core/Exception/ConfigParserNotExistException.php b/src/module-vsbridge-indexer-core/Exception/ConfigParserNotExistException.php new file mode 100644 index 00000000..008b8d8d --- /dev/null +++ b/src/module-vsbridge-indexer-core/Exception/ConfigParserNotExistException.php @@ -0,0 +1,10 @@ + - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCore\Exception; /** * Class ConnectionDisabledException - * - * @package \Divante\VsbridgeIndexerCore */ class ConnectionDisabledException extends \RuntimeException { diff --git a/src/module-vsbridge-indexer-core/Exception/IndexNotExistException.php b/src/module-vsbridge-indexer-core/Exception/IndexNotExistException.php new file mode 100644 index 00000000..3746e498 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Exception/IndexNotExistException.php @@ -0,0 +1,18 @@ +validateProviders($typeDataProviders); + } + + $this->dataProviders = $dataProviders; + $this->transactionKeyDataProvider = $transactionKey; + } + + /** + * Check if validators implements DataProviderInterface + * + * @param array $dataProviders + * + * @return void + */ + private function validateProviders(array $dataProviders) + { + foreach ($dataProviders as $dataProvider) { + if (! $dataProvider instanceof DataProviderInterface) { + throw new \InvalidArgumentException( + 'DataProvider must implement ' . DataProviderInterface::class + ); + } + } + } + + /** + * @param string $indexName + * @return DataProviderInterface[] + */ + public function getDataProviders(string $indexName) + { + $dataProviders = $this->dataProviders[$indexName] ?? []; + $dataProviders[] = $this->transactionKeyDataProvider; + + return $dataProviders; + } +} diff --git a/src/module-vsbridge-indexer-core/Index/Index.php b/src/module-vsbridge-indexer-core/Index/Index.php index 6a61be07..421f1d65 100644 --- a/src/module-vsbridge-indexer-core/Index/Index.php +++ b/src/module-vsbridge-indexer-core/Index/Index.php @@ -10,7 +10,6 @@ */ class Index implements IndexInterface { - /** * Name of the index. * @@ -26,6 +25,8 @@ class Index implements IndexInterface private $types; /** + * Index alias + * * @var string */ private $alias; diff --git a/src/module-vsbridge-indexer-core/Index/IndexOperations.php b/src/module-vsbridge-indexer-core/Index/IndexOperations.php index 80dc99ef..5858ce21 100644 --- a/src/module-vsbridge-indexer-core/Index/IndexOperations.php +++ b/src/module-vsbridge-indexer-core/Index/IndexOperations.php @@ -1,10 +1,4 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCore\Index; @@ -20,6 +14,8 @@ use Divante\VsbridgeIndexerCore\Config\OptimizationSettings; use Divante\VsbridgeIndexerCore\Elasticsearch\ClientResolver; use Divante\VsbridgeIndexerCore\Exception\ConnectionUnhealthyException; +use Divante\VsbridgeIndexerCore\Exception\ConfigurationNotFoundException; +use Divante\VsbridgeIndexerCore\Exception\IndexNotExistException; use Magento\Store\Api\Data\StoreInterface; /** @@ -145,13 +141,11 @@ public function indexExists($storeId, $indexName) */ public function getIndexByName($indexIdentifier, StoreInterface $store) { - $indexAlias = $this->getIndexAlias($store); + $indexAlias = $this->indexSettings->getIndexAlias($indexIdentifier, $store); if (!isset($this->indicesByIdentifier[$indexAlias])) { if (!$this->indexExists($store->getId(), $indexAlias)) { - throw new \LogicException( - "{$indexIdentifier} index does not exist yet." - ); + throw new IndexNotExistException($indexIdentifier); } $this->initIndex($indexIdentifier, $store, true); @@ -160,14 +154,6 @@ public function getIndexByName($indexIdentifier, StoreInterface $store) return $this->indicesByIdentifier[$indexAlias]; } - /** - * @inheritdoc - */ - public function getIndexAlias(StoreInterface $store) - { - return $this->indexSettings->getIndexAlias($store); - } - /** * @inheritdoc */ @@ -196,6 +182,14 @@ public function createIndex($indexIdentifier, StoreInterface $store) return $index; } + /** + * @inheritdoc + */ + public function getIndexAlias($indexIdentifier, StoreInterface $store) + { + return $this->indexSettings->getIndexAlias($indexIdentifier, $store); + } + /** * @inheritdoc */ @@ -252,11 +246,11 @@ private function initIndex($indexIdentifier, StoreInterface $store, $existingInd $this->getIndicesConfiguration(); if (!isset($this->indicesConfiguration[$indexIdentifier])) { - throw new \LogicException('No configuration found'); + throw new ConfigurationNotFoundException(); } - $indexAlias = $this->getIndexAlias($store); - $indexName = $this->indexSettings->createIndexName($store); + $indexName = $this->indexSettings->createIndexName($indexIdentifier, $store); + $indexAlias = $this->indexSettings->getIndexAlias($indexIdentifier, $store); if ($existingIndex) { $indexName = $indexAlias; @@ -298,7 +292,7 @@ public function getBatchIndexingSize() private function getIndicesConfiguration() { if (null === $this->indicesConfiguration) { - $this->indicesConfiguration = $this->indexSettings->getIndicesConfig(); + $this->indicesConfiguration = $this->indexSettings->getConfig(); } return $this->indicesConfiguration; diff --git a/src/module-vsbridge-indexer-core/Index/IndexSettings.php b/src/module-vsbridge-indexer-core/Index/IndexSettings.php index ff844937..4a89220c 100644 --- a/src/module-vsbridge-indexer-core/Index/IndexSettings.php +++ b/src/module-vsbridge-indexer-core/Index/IndexSettings.php @@ -2,7 +2,9 @@ namespace Divante\VsbridgeIndexerCore\Index; -use Divante\VsbridgeIndexerCore\Index\Indicies\Config as IndicesConfig; +use Divante\VsbridgeIndexerCore\Index\Indices\Config; +use Divante\VsbridgeIndexerCore\Index\Indices\ConfigParserInterface; +use Divante\VsbridgeIndexerCore\Index\Indices\ConfigResolver; use Divante\VsbridgeIndexerCore\Config\IndicesSettings; use Magento\Framework\Intl\DateTimeFactory; use Magento\Store\Api\Data\StoreInterface; @@ -13,15 +15,17 @@ */ class IndexSettings { + const DUMMY_INDEX_IDENTIFIER = 'vue_storefront_catalog'; + /** * @var StoreManagerInterface */ private $storeManager; /** - * @var IndicesConfig + * @var ConfigResolver */ - private $indicesConfig; + private $configResolver; /** * @var IndicesSettings @@ -37,36 +41,30 @@ class IndexSettings * IndexSettings constructor. * * @param StoreManagerInterface $storeManager - * @param IndicesConfig $config - * @param IndicesSettings $settingsConfig + * @param ConfigResolver $config * @param DateTimeFactory $dateTimeFactory + * @param IndicesSettings $settingsConfig */ public function __construct( StoreManagerInterface $storeManager, - IndicesConfig $config, + ConfigResolver $config, IndicesSettings $settingsConfig, DateTimeFactory $dateTimeFactory ) { - $this->indicesConfig = $config; + $this->configResolver = $config; $this->configuration = $settingsConfig; $this->storeManager = $storeManager; $this->dateTimeFactory = $dateTimeFactory; } /** - * @return int - */ - public function getBatchIndexingSize() - { - return $this->configuration->getBatchIndexingSize(); - } - - /** + * Retrieve vsbridge configuration + * * @return array */ - public function getIndicesConfig() + public function getConfig(): array { - return $this->indicesConfig->get(); + return $this->configResolver->resolve(); } /** @@ -75,23 +73,25 @@ public function getIndicesConfig() public function getEsConfig() { return [ - 'index.mapping.total_fields.limit' => $this->configuration->getFieldsLimit(), - 'analysis' => [ - 'analyzer' => [ - 'autocomplete' => [ - 'tokenizer' => 'autocomplete', - 'filter' => ['lowercase'], + 'settings' => [ + 'index.mapping.total_fields.limit' => $this->configuration->getFieldsLimit(), + 'analysis' => [ + 'analyzer' => [ + 'autocomplete' => [ + 'tokenizer' => 'autocomplete', + 'filter' => ['lowercase'], + ], + 'autocomplete_search' => [ + 'tokenizer'=> 'lowercase' + ] ], - 'autocomplete_search' => [ - 'tokenizer'=> 'lowercase' - ] - ], - 'tokenizer' => [ - 'autocomplete' => [ - 'type' => 'edge_ngram', - 'min_gram' => 2, - 'max_gram' => 10, - 'token_chars' => ['letter'], + 'tokenizer' => [ + 'autocomplete' => [ + 'type' => 'edge_ngram', + 'min_gram' => 2, + 'max_gram' => 10, + 'token_chars' => ['letter'], + ] ] ] ] @@ -99,32 +99,41 @@ public function getEsConfig() } /** + * @param string $indexIdentifier * @param StoreInterface $store * * @return string */ - public function createIndexName(StoreInterface $store) + public function createIndexName($indexIdentifier, StoreInterface $store) { - $name = $this->getIndexAlias($store); + $name = $this->getIndexAlias($indexIdentifier, $store); $currentDate = $this->dateTimeFactory->create(); return $name . '_' . $currentDate->getTimestamp(); } /** + * Create index alias + * + * @param string $indexIdentifier * @param StoreInterface $store * * @return string */ - public function getIndexAlias(StoreInterface $store) + public function getIndexAlias(string $indexIdentifier, StoreInterface $store) { - $indexNamePrefix = $this->configuration->getIndexNamePrefix(); + $indexNamePrefix = $this->getIndexNamePrefix(); $storeIdentifier = $this->getStoreIdentifier($store); if ($storeIdentifier) { $indexNamePrefix .= '_' . $storeIdentifier; } + $indexNamePrefix .= + $indexIdentifier === self::DUMMY_INDEX_IDENTIFIER + ? '' + : '_' . $indexIdentifier; + return strtolower($indexNamePrefix); } @@ -143,8 +152,30 @@ private function getStoreIdentifier(StoreInterface $store) } } - $indexIdentifier = $this->configuration->getIndexIdentifier(); + return ('code' === $this->getIndexIdentifier()) ? $store->getCode() : (string) $store->getId(); + } + + /** + * @return string + */ + private function getIndexNamePrefix() + { + return $this->configuration->getIndexNamePrefix(); + } - return ('code' === $indexIdentifier) ? $store->getCode() : (string) $store->getId(); + /** + * @return string + */ + private function getIndexIdentifier() + { + return $this->configuration->getIndexIdentifier(); + } + + /** + * @return int + */ + public function getBatchIndexingSize() + { + return $this->configuration->getBatchIndexingSize(); } } diff --git a/src/module-vsbridge-indexer-core/Index/Indices/Config/Converter.php b/src/module-vsbridge-indexer-core/Index/Indices/Config/Converter.php new file mode 100644 index 00000000..35786a49 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Index/Indices/Config/Converter.php @@ -0,0 +1,48 @@ +query($indexSearchPath) as $indexNode) { + $indexIdentifier = $indexNode->getAttribute('identifier'); + $indices[$indexIdentifier] = $this->parseTypeConfig($indexNode); + } + + return $indices; + } + + /** + * Parse type node configuration. + * + * @param \DOMNode $typeRootNode Type node to be parsed. + * + * @return array + */ + private function parseTypeConfig(\DOMNode $typeRootNode) + { + $mapping = $typeRootNode->getAttribute('mapping'); + + return ['mapping' => $mapping]; + } +} diff --git a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Data.php b/src/module-vsbridge-indexer-core/Index/Indices/Config/Data.php similarity index 93% rename from src/module-vsbridge-indexer-core/Index/Indicies/Config/Data.php rename to src/module-vsbridge-indexer-core/Index/Indices/Config/Data.php index 67ff25c6..54ba11cc 100644 --- a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Data.php +++ b/src/module-vsbridge-indexer-core/Index/Indices/Config/Data.php @@ -6,7 +6,7 @@ * @license See LICENSE_DIVANTE.txt for license details. */ -namespace Divante\VsbridgeIndexerCore\Index\Indicies\Config; +namespace Divante\VsbridgeIndexerCore\Index\Indices\Config; use Magento\Framework\Config\CacheInterface; use Magento\Framework\Config\Data as DataConfig; diff --git a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Reader.php b/src/module-vsbridge-indexer-core/Index/Indices/Config/Reader.php similarity index 86% rename from src/module-vsbridge-indexer-core/Index/Indicies/Config/Reader.php rename to src/module-vsbridge-indexer-core/Index/Indices/Config/Reader.php index fe90bd5e..855f90b7 100644 --- a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Reader.php +++ b/src/module-vsbridge-indexer-core/Index/Indices/Config/Reader.php @@ -6,7 +6,7 @@ * @license See LICENSE_DIVANTE.txt for license details. */ -namespace Divante\VsbridgeIndexerCore\Index\Indicies\Config; +namespace Divante\VsbridgeIndexerCore\Index\Indices\Config; use Magento\Framework\Config\Dom; use Magento\Framework\Config\Reader\Filesystem; @@ -18,7 +18,7 @@ */ class Reader extends Filesystem { - const FILE_NAME = 'vsbridge_indices.xml'; + const FILE_NAME = 'vsbridge.xml'; /** * List of attributes by XPath used as ids during the file merge process. @@ -26,9 +26,7 @@ class Reader extends Filesystem * @var array */ private $idAttributes = [ - '/indices/index' => 'identifier', - '/indices/index/type' => 'name', - '/indices/index/type/data_providers/data_provider' => 'name', + '/config/type' => 'identifier', ]; /** diff --git a/src/module-vsbridge-indexer-core/Index/Indicies/Config/SchemaLocator.php b/src/module-vsbridge-indexer-core/Index/Indices/Config/SchemaLocator.php similarity index 92% rename from src/module-vsbridge-indexer-core/Index/Indicies/Config/SchemaLocator.php rename to src/module-vsbridge-indexer-core/Index/Indices/Config/SchemaLocator.php index ae2724e0..0b1db2f1 100644 --- a/src/module-vsbridge-indexer-core/Index/Indicies/Config/SchemaLocator.php +++ b/src/module-vsbridge-indexer-core/Index/Indices/Config/SchemaLocator.php @@ -6,7 +6,7 @@ * @license See LICENSE_DIVANTE.txt for license details. */ -namespace Divante\VsbridgeIndexerCore\Index\Indicies\Config; +namespace Divante\VsbridgeIndexerCore\Index\Indices\Config; use Magento\Framework\Config\SchemaLocatorInterface; use Magento\Framework\Module\Dir; @@ -19,7 +19,7 @@ class SchemaLocator implements SchemaLocatorInterface /** * XML schema for config file. */ - const CONFIG_FILE_SCHEMA = 'vsbridge_indices.xsd'; + const CONFIG_FILE_SCHEMA = 'vsbridge.xsd'; /** * Path to corresponding XSD file with validation rules for merged config diff --git a/src/module-vsbridge-indexer-core/Index/Indices/ConfigES5Parser.php b/src/module-vsbridge-indexer-core/Index/Indices/ConfigES5Parser.php new file mode 100644 index 00000000..37384ef7 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Index/Indices/ConfigES5Parser.php @@ -0,0 +1,63 @@ +mappingProviderProcessorFactory = $mappingProcessorFactory; + $this->typeFactory = $typeInterfaceFactory; + } + + /** + * @param array $indexConfigData + * + * @return array + */ + public function parse(array $indexConfigData): array + { + $types = []; + + foreach ($indexConfigData as $typeName => $typeConfigData) { + $mapping = $this->mappingProviderProcessorFactory->get($typeConfigData['mapping']); + $types[$typeName] = $this->typeFactory->create( + [ + 'name' => $typeName, + 'mapping' => $mapping, + ] + ); + } + + return [ + IndexSettings::DUMMY_INDEX_IDENTIFIER => ['types' => $types] + ]; + } +} diff --git a/src/module-vsbridge-indexer-core/Index/Indices/ConfigES6plusParser.php b/src/module-vsbridge-indexer-core/Index/Indices/ConfigES6plusParser.php new file mode 100644 index 00000000..82b752f6 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Index/Indices/ConfigES6plusParser.php @@ -0,0 +1,63 @@ +mappingProviderProcessorFactory = $mappingProcessorFactory; + $this->typeFactory = $typeInterfaceFactory; + } + + /** + * @param array $indexConfigData + * + * @return array + */ + public function parse(array $indexConfigData): array + { + $indices = []; + + foreach ($indexConfigData as $typeName => $typeConfigData) { + $mapping = $this->mappingProviderProcessorFactory->get($typeConfigData['mapping']); + $type = $this->typeFactory->create( + [ + 'name' => $typeName, + 'mapping' => $mapping, + ] + ); + + $indices[$typeName] = ['types' => [IndexInterface::DUMMY_INDEX_TYPE => $type]]; + } + + return $indices; + } +} diff --git a/src/module-vsbridge-indexer-core/Index/Indices/ConfigParserInterface.php b/src/module-vsbridge-indexer-core/Index/Indices/ConfigParserInterface.php new file mode 100644 index 00000000..b5cb269c --- /dev/null +++ b/src/module-vsbridge-indexer-core/Index/Indices/ConfigParserInterface.php @@ -0,0 +1,16 @@ +configData = $configData; + $this->configParsers = $configParsers; + $this->objectManager = $objectManager; + $this->elasticsearchResolver = $elasticsearchResolver; + } + + /** + * @return array + */ + public function resolve(): array + { + $currentEsVersion = $this->elasticsearchResolver->getVersion(); + + if (!isset($this->configParsers[$currentEsVersion])) { + throw new ConfigParserNotExistException(__('There is no parser for: ' . $currentEsVersion)); + } + + /** @var ConfigParserInterface $configParser */ + $configParser = $this->objectManager->get($this->configParsers[$currentEsVersion]); + + return $configParser->parse($this->configData->get()); + } +} diff --git a/src/module-vsbridge-indexer-core/Index/Indicies/Config.php b/src/module-vsbridge-indexer-core/Index/Indicies/Config.php deleted file mode 100644 index 02e5c49e..00000000 --- a/src/module-vsbridge-indexer-core/Index/Indicies/Config.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCore\Index\Indicies; - -use Divante\VsbridgeIndexerCore\Api\Index\TypeInterfaceFactory as TypeFactoryInterface; -use Divante\VsbridgeIndexerCore\Indexer\DataProviderProcessorFactory; -use Divante\VsbridgeIndexerCore\Indexer\MappingProcessorFactory; - -/** - * Class Config - */ -class Config -{ - /** - * Factory used to build mapping types. - * - * @var TypeFactoryInterface - */ - private $typeFactory; - - /** - * @var DataProviderProcessorFactory - */ - private $dataProviderFactoryProcessor; - - /** - * @var MappingProcessorFactory - */ - private $mappingProviderProcessorFactory; - - /** - * @var \Divante\VsbridgeIndexerCore\Indexer\DataProvider\TransactionKey - */ - private $transactionKey; - - /** - * Config\Data - */ - private $configData; - - /** - * Config constructor. - * - * @param Config\Data $configData - * @param \Divante\VsbridgeIndexerCore\Indexer\DataProvider\TransactionKey $transactionKey - * @param TypeFactoryInterface $typeInterfaceFactory - * @param MappingProcessorFactory $mappingProcessorFactory - * @param DataProviderProcessorFactory $dataProviderFactoryProcessor - */ - public function __construct( - Config\Data $configData, - \Divante\VsbridgeIndexerCore\Indexer\DataProvider\TransactionKey $transactionKey, - TypeFactoryInterface $typeInterfaceFactory, - MappingProcessorFactory $mappingProcessorFactory, - DataProviderProcessorFactory $dataProviderFactoryProcessor - ) { - $this->configData = $configData; - $this->transactionKey = $transactionKey; - $this->mappingProviderProcessorFactory = $mappingProcessorFactory; - $this->dataProviderFactoryProcessor = $dataProviderFactoryProcessor; - $this->typeFactory = $typeInterfaceFactory; - } - - /** - * @return array - */ - public function get() - { - $configData = $this->configData->get(); - $indicesConfig = []; - - foreach ($configData as $indexIdentifier => $indexConfig) { - $indicesConfig[$indexIdentifier] = $this->initIndexConfig($indexConfig); - } - - return $indicesConfig; - } - - /** - * @param array $indexConfigData - * - * @return array - */ - private function initIndexConfig(array $indexConfigData) - { - $types = []; - - foreach ($indexConfigData['types'] as $typeName => $typeConfigData) { - $dataProviders = ['transaction_key' => $this->transactionKey]; - - foreach ($typeConfigData['data_providers'] as $dataProviderName => $dataProviderClass) { - $dataProviders[$dataProviderName] = - $this->dataProviderFactoryProcessor->get($dataProviderClass); - } - - $mapping = null; - - if (isset($typeConfigData['mapping'][0])) { - $mapping = $this->mappingProviderProcessorFactory->get($typeConfigData['mapping'][0]); - } - - $types[$typeName] = $this->typeFactory->create( - [ - 'name' => $typeName, - 'dataProviders' => $dataProviders, - 'mapping' => $mapping, - ] - ); - } - - return ['types' => $types]; - } -} diff --git a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Converter.php b/src/module-vsbridge-indexer-core/Index/Indicies/Config/Converter.php deleted file mode 100644 index 27e9621b..00000000 --- a/src/module-vsbridge-indexer-core/Index/Indicies/Config/Converter.php +++ /dev/null @@ -1,122 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCore\Index\Indicies\Config; - -use Magento\Framework\Config\ConverterInterface; - -/** - * Class Converter - */ -class Converter implements ConverterInterface -{ - /** - * - */ - const ROOT_NODE_NAME = 'indices'; - - /** - * - */ - const INDEX_NODE_TYPE = 'index'; - - /** - * - */ - const TYPE_NODE_TYPE = 'type'; - - /** - * - */ - const DATA_PROVIDERS_PATH = 'data_providers/data_provider'; - - /** - * @param \DOMDocument $source - * - * @return array - */ - public function convert($source) - { - $indices = []; - - $xpath = new \DOMXPath($source); - $indexSearchPath = sprintf("/%s/%s", self::ROOT_NODE_NAME, self::INDEX_NODE_TYPE); - - foreach ($xpath->query($indexSearchPath) as $indexNode) { - $indexIdentifier = $indexNode->getAttribute('identifier'); - $indices[$indexIdentifier] = $this->parseIndexConfig($xpath, $indexNode); - } - - return $indices; - } - - /** - * Parse index node configuration. - * - * @param \DOMXPath $xpath XPath access to the document parsed. - * @param \DOMNode $indexRootNode Index node to be parsed. - * - * @return array - */ - private function parseIndexConfig(\DOMXPath $xpath, \DOMNode $indexRootNode) - { - $indexConfig = ['types' => []]; - $typesSearchPath = sprintf('%s', self::TYPE_NODE_TYPE); - $xpath->query($typesSearchPath, $indexRootNode); - - foreach ($xpath->query($typesSearchPath, $indexRootNode) as $typeNode) { - $typeParams = $this->parseTypeConfig($xpath, $typeNode); - $indexConfig['types'][$typeNode->getAttribute('name')] = $typeParams; - } - - return $indexConfig; - } - - /** - * Parse type node configuration. - * - * @param \DOMXPath $xpath XPath access to the document parsed. - * @param \DOMNode $typeRootNode Type node to be parsed. - * - * @return array - */ - private function parseTypeConfig(\DOMXPath $xpath, \DOMNode $typeRootNode) - { - $datasources = $this->parseDataProviders($xpath, $typeRootNode); - $mapping = $typeRootNode->getAttribute('mapping'); - $mappingOptions = []; - - if ($mapping) { - $mappingOptions[] = $mapping; - } - - return [ - 'mapping' => $mappingOptions, - 'data_providers' => $datasources, - ]; - } - - /** - * Parse dataprovides from type node configuration. - * - * @param \DOMXPath $xpath XPath access to the document parsed. - * @param \DOMNode $typeRootNode Type node to be parsed. - * - * @return array - */ - private function parseDataProviders(\DOMXPath $xpath, \DOMNode $typeRootNode) - { - $datasources = []; - - foreach ($xpath->query(self::DATA_PROVIDERS_PATH, $typeRootNode) as $datasourceNode) { - $datasources[$datasourceNode->getAttribute('name')] = $datasourceNode->nodeValue; - } - - return $datasources; - } -} diff --git a/src/module-vsbridge-indexer-core/Index/Mapping/GeneralMapping.php b/src/module-vsbridge-indexer-core/Index/Mapping/GeneralMapping.php index 497e347e..24b16d3b 100644 --- a/src/module-vsbridge-indexer-core/Index/Mapping/GeneralMapping.php +++ b/src/module-vsbridge-indexer-core/Index/Mapping/GeneralMapping.php @@ -11,7 +11,7 @@ use Divante\VsbridgeIndexerCore\Api\Mapping\FieldInterface; /** - * Class GeneralMapping + * Mapping for common fields for shared fields */ class GeneralMapping { diff --git a/src/module-vsbridge-indexer-core/Indexer/MappingProcessorFactory.php b/src/module-vsbridge-indexer-core/Index/MappingFactory.php similarity index 63% rename from src/module-vsbridge-indexer-core/Indexer/MappingProcessorFactory.php rename to src/module-vsbridge-indexer-core/Index/MappingFactory.php index c796fdd3..96c724b2 100644 --- a/src/module-vsbridge-indexer-core/Indexer/MappingProcessorFactory.php +++ b/src/module-vsbridge-indexer-core/Index/MappingFactory.php @@ -6,28 +6,29 @@ * @license See LICENSE_DIVANTE.txt for license details. */ -namespace Divante\VsbridgeIndexerCore\Indexer; +namespace Divante\VsbridgeIndexerCore\Index; + +use Magento\Framework\ObjectManagerInterface; /** - * Class MappingProcessorFactory + * Class MappingFactory */ -class MappingProcessorFactory +class MappingFactory { /** * Object Manager instance * - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ private $objectManager; /** * Constructor * - * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager */ - public function __construct( - \Magento\Framework\ObjectManagerInterface $objectManager - ) { + public function __construct(ObjectManagerInterface $objectManager) + { $this->objectManager = $objectManager; } diff --git a/src/module-vsbridge-indexer-core/Indexer/TransactionKey.php b/src/module-vsbridge-indexer-core/Index/TransactionKey.php similarity index 83% rename from src/module-vsbridge-indexer-core/Indexer/TransactionKey.php rename to src/module-vsbridge-indexer-core/Index/TransactionKey.php index 47e464d6..4d9f6a78 100644 --- a/src/module-vsbridge-indexer-core/Indexer/TransactionKey.php +++ b/src/module-vsbridge-indexer-core/Index/TransactionKey.php @@ -6,9 +6,9 @@ * @license See LICENSE_DIVANTE.txt for license details. */ -namespace Divante\VsbridgeIndexerCore\Indexer; +namespace Divante\VsbridgeIndexerCore\Index; -use Divante\VsbridgeIndexerCore\Api\Indexer\TransactionKeyInterface; +use Divante\VsbridgeIndexerCore\Api\Index\TransactionKeyInterface; /** * Class TransactionKey diff --git a/src/module-vsbridge-indexer-core/Index/Type.php b/src/module-vsbridge-indexer-core/Index/Type.php index eaeef737..6ab4774b 100644 --- a/src/module-vsbridge-indexer-core/Index/Type.php +++ b/src/module-vsbridge-indexer-core/Index/Type.php @@ -30,25 +30,16 @@ class Type implements TypeInterface */ private $mapping; - /** - * Type dataProviders. - * - * @var - */ - private $dataProviders; - /** * Type constructor. * * @param $name * @param MappingInterface|null $mapping - * @param array $dataProviders */ - public function __construct($name, MappingInterface $mapping = null, array $dataProviders) + public function __construct($name, MappingInterface $mapping = null) { $this->name = $name; $this->mapping = $mapping; - $this->dataProviders = $dataProviders; } /** @@ -66,24 +57,4 @@ public function getMapping() { return $this->mapping; } - - /** - * @inheritdoc - */ - public function getDataProviders() - { - return $this->dataProviders; - } - - /** - * @inheritdoc - */ - public function getDataProvider(string $name) - { - if (!isset($this->dataProviders[$name])) { - throw new \Exception("DataProvider $name does not exists."); - } - - return $this->dataProviders[$name]; - } } diff --git a/src/module-vsbridge-indexer-core/Indexer/Action/AbstractAction.php b/src/module-vsbridge-indexer-core/Indexer/Action/AbstractAction.php new file mode 100644 index 00000000..e1a2c9fd --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/Action/AbstractAction.php @@ -0,0 +1,93 @@ +storeManager = $storeManager; + $this->indexerFactory = $indexerHandlerFactory; + $this->action = $actionPool; + $this->typeName = $typeName; + } + + /** + * Execute action for given ids + * + * @param array $ids + * + * @return void + */ + abstract public function execute(array $ids); + + /** + * @return \Magento\Store\Api\Data\StoreInterface[] + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getStores() + { + return $this->storeManager->getStores(); + } + + /** + * @param int $storeId + * @param array $ids + * + * @return \Traversable + */ + public function rebuild(int $storeId, array $ids) + { + $action = $this->action->getAction($this->typeName); + + return $action->rebuild($storeId, $ids); + } + + /** + * @return GenericIndexerHandler + */ + public function getIndexerHandler(): GenericIndexerHandler + { + return $this->indexerFactory->create(['typeName' => $this->typeName]); + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/Action/ActionFactory.php b/src/module-vsbridge-indexer-core/Indexer/Action/ActionFactory.php new file mode 100644 index 00000000..4e77e11a --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/Action/ActionFactory.php @@ -0,0 +1,43 @@ +actions = $actions; + $this->objectManager = $objectManager; + } + + /** + * Return Action class base on action type: full, rows + * @param string $actionType + * @param string $entityType + * + * @return AbstractAction + */ + public function create(string $actionType, string $entityType): AbstractAction + { + $actionFactoryClass = $this->actions[$actionType]; + + return $this->objectManager->create($actionFactoryClass, ['typeName' => $entityType]); + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/Action/Full.php b/src/module-vsbridge-indexer-core/Indexer/Action/Full.php new file mode 100644 index 00000000..87bd038b --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/Action/Full.php @@ -0,0 +1,64 @@ +esVersionResolver = $esVersionResolver; + } + + /** + * Execute full reindex + * + * @param array $ids + * + * @return void + */ + public function execute(array $ids) + { + $esVersion = $this->esVersionResolver->getVersion(); + $stores = $this->getStores(); + + if ($esVersion === ElasticsearchResolverInterface::DEFAULT_ES_VERSION) { + foreach ($stores as $store) { + $this->getIndexerHandler()->saveIndex($this->rebuild((int) $store->getId(), []), $store); + $this->getIndexerHandler()->cleanUpByTransactionKey($store); + } + } else { + foreach ($stores as $store) { + $this->getIndexerHandler()->createIndex($store); + $this->getIndexerHandler()->saveIndex($this->rebuild((int) $store->getId(), []), $store); + } + } + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php b/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php new file mode 100644 index 00000000..83588cdc --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php @@ -0,0 +1,27 @@ +getStores(); + + foreach ($stores as $store) { + $this->getIndexerHandler()->saveIndex($this->rebuild((int) $store->getId(), $ids), $store); + $this->getIndexerHandler()->cleanUpByTransactionKey($store); + } + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/Base.php b/src/module-vsbridge-indexer-core/Indexer/Base.php new file mode 100644 index 00000000..f99ee633 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/Base.php @@ -0,0 +1,67 @@ +typeName = $typeName; + $this->actionFactory = $actionFactory; + } + + /** + * @inheritdoc + */ + public function execute($ids) + { + return $this->actionFactory->create('rows', $this->typeName)->execute($ids); + } + + /** + * @inheritdoc + */ + public function executeFull() + { + return $this->actionFactory->create('full', $this->typeName)->execute([]); + } + + /** + * @inheritdoc + */ + public function executeList(array $ids) + { + $this->execute($ids); + } + + /** + * @inheritdoc + */ + public function executeRow($id) + { + $this->execute([$id]); + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/DataProviderProcessorFactory.php b/src/module-vsbridge-indexer-core/Indexer/DataProviderProcessorFactory.php deleted file mode 100644 index 8ddb1c97..00000000 --- a/src/module-vsbridge-indexer-core/Indexer/DataProviderProcessorFactory.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCore\Indexer; - -/** - * Class DataProviderProcessorFactory - */ -class DataProviderProcessorFactory -{ - /** - * Object Manager instance - * - * @var \Magento\Framework\ObjectManagerInterface - */ - private $objectManager; - - /** - * Constructor - * - * @param \Magento\Framework\ObjectManagerInterface $objectManager - */ - public function __construct( - \Magento\Framework\ObjectManagerInterface $objectManager - ) { - $this->objectManager = $objectManager; - } - - /** - * @param string $instanceName - * - * @return mixed - */ - public function get($instanceName) - { - return $this->objectManager->get($instanceName); - } -} diff --git a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php index 4c9d10ea..d117e604 100644 --- a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php +++ b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php @@ -1,26 +1,23 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCore\Indexer; use Divante\VsbridgeIndexerCore\Api\BulkLoggerInterface; use Divante\VsbridgeIndexerCore\Api\DataProviderInterface; use Divante\VsbridgeIndexerCore\Api\IndexInterface; -use Divante\VsbridgeIndexerCore\Api\Indexer\TransactionKeyInterface; +use Divante\VsbridgeIndexerCore\Api\Index\TransactionKeyInterface; use Divante\VsbridgeIndexerCore\Api\IndexOperationInterface; use Divante\VsbridgeIndexerCore\Exception\ConnectionDisabledException; use Divante\VsbridgeIndexerCore\Exception\ConnectionUnhealthyException; use Divante\VsbridgeIndexerCore\Logger\IndexerLogger; +use Divante\VsbridgeIndexerCore\Index\IndexSettings; use Divante\VsbridgeIndexerCore\Model\IndexerRegistry; use Exception; use Magento\Framework\Indexer\SaveHandler\Batch; use Magento\Store\Api\Data\StoreInterface; use Traversable; +use Divante\VsbridgeIndexerCore\Api\Index\DataProviderResolverInterface; +use Divante\VsbridgeIndexerCore\Model\ElasticsearchResolverInterface; /** * Class IndexerHandler @@ -29,6 +26,11 @@ */ class GenericIndexerHandler { + /** + * @var ElasticsearchResolverInterface + */ + private $esVersionResolver; + /** * @var Batch */ @@ -69,40 +71,49 @@ class GenericIndexerHandler */ private $bulkLogger; + /** + * @var DataProviderResolverInterface + */ + private $dataProviderResolver; + /** * GenericIndexerHandler constructor. - * + * @param DataProviderResolverInterface $dataProviderResolver + * @param ElasticsearchResolverInterface $esVersionResolver * @param BulkLoggerInterface $bulkLogger * @param IndexOperationInterface $indexOperationProvider * @param IndexerLogger $indexerLogger + * @param IndexOperationInterface $indexOperations * @param IndexerRegistry $indexerRegistry * @param Batch $batch * @param TransactionKeyInterface $transactionKey - * @param string $indexIdentifier * @param string $typeName */ public function __construct( + DataProviderResolverInterface $dataProviderResolver, + ElasticsearchResolverInterface $esVersionResolver, BulkLoggerInterface $bulkLogger, IndexOperationInterface $indexOperationProvider, IndexerLogger $indexerLogger, + IndexOperationInterface $indexOperations, IndexerRegistry $indexerRegistry, Batch $batch, TransactionKeyInterface $transactionKey, - string $indexIdentifier, string $typeName ) { + $this->esVersionResolver = $esVersionResolver; + $this->dataProviderResolver = $dataProviderResolver; $this->bulkLogger = $bulkLogger; $this->batch = $batch; - $this->indexOperations = $indexOperationProvider; + $this->indexOperations = $indexOperations; $this->typeName = $typeName; - $this->indexIdentifier = $indexIdentifier; $this->indexerLogger = $indexerLogger; $this->indexerRegistry = $indexerRegistry; - $this->transactionKey = $transactionKey->load(); + $this->transactionKey = $transactionKey; } /** - * Update documents in ES + * Partial document update in ES * * @param Traversable $documents * @param StoreInterface $store @@ -119,7 +130,7 @@ public function updateIndex(Traversable $documents, StoreInterface $store, array $storeId = (int)$store->getId(); $dataProviders = []; - foreach ($type->getDataProviders() as $name => $dataProvider) { + foreach ($this->getDataProviders() as $name => $dataProvider) { if (in_array($name, $requireDataProvides)) { $dataProviders[] = $dataProvider; } @@ -132,7 +143,6 @@ public function updateIndex(Traversable $documents, StoreInterface $store, array $batchSize = $this->indexOperations->getBatchIndexingSize(); foreach ($this->batch->getItems($documents, $batchSize) as $docs) { - /** @var DataProviderInterface $datasource */ foreach ($dataProviders as $datasource) { if (!empty($docs)) { $docs = $datasource->addData($docs, $storeId); @@ -175,12 +185,11 @@ public function saveIndex(Traversable $documents, StoreInterface $store) { try { $index = $this->getIndex($store); - $type = $index->getType($this->typeName); $storeId = (int)$store->getId(); $batchSize = $this->indexOperations->getBatchIndexingSize(); foreach ($this->batch->getItems($documents, $batchSize) as $docs) { - foreach ($type->getDataProviders() as $dataProvider) { + foreach ($this->getDataProviders() as $dataProvider) { if (!empty($docs)) { $docs = $dataProvider->addData($docs, $storeId); } @@ -227,11 +236,11 @@ public function saveIndex(Traversable $documents, StoreInterface $store) public function cleanUpByTransactionKey(StoreInterface $store, array $docIds = null) { try { - $indexAlias = $this->indexOperations->getIndexAlias($store); + $indexAlias = $this->indexOperations->getIndexAlias($this->getIdentifier(), $store); if ($this->indexOperations->indexExists($store->getId(), $indexAlias)) { - $index = $this->indexOperations->getIndexByName($this->indexIdentifier, $store); - $transactionKeyQuery = ['must_not' => ['term' => ['tsk' => $this->transactionKey]]]; + $index = $this->indexOperations->getIndexByName($this->getIdentifier(), $store); + $transactionKeyQuery = ['must_not' => ['term' => ['tsk' => $this->transactionKey->load()]]]; $query = ['query' => ['bool' => $transactionKeyQuery]]; if ($docIds) { @@ -251,6 +260,14 @@ public function cleanUpByTransactionKey(StoreInterface $store, array $docIds = n } } + /** + * @return DataProviderInterface[] + */ + private function getDataProviders() + { + return $this->dataProviderResolver->getDataProviders($this->typeName); + } + /** * Get Index * @@ -261,14 +278,22 @@ public function cleanUpByTransactionKey(StoreInterface $store, array $docIds = n private function getIndex(StoreInterface $store) { try { - $index = $this->indexOperations->getIndexByName($this->indexIdentifier, $store); + $index = $this->indexOperations->getIndexByName($this->getIdentifier(), $store); } catch (Exception $e) { - $index = $this->indexOperations->createIndex($this->indexIdentifier, $store); + $index = $this->indexOperations->createIndex($this->getIdentifier(), $store); } return $index; } + /** + * @param StoreInterface $store + */ + public function createIndex(StoreInterface $store) + { + $this->indexOperations->createIndex($this->getIdentifier(), $store); + } + /** * Get type name * @@ -278,4 +303,18 @@ public function getTypeName() { return $this->typeName; } + + /** + * @return string + */ + private function getIdentifier(): string + { + $esVersion = $this->esVersionResolver->getVersion(); + + if ($esVersion === ElasticsearchResolverInterface::DEFAULT_ES_VERSION) { + return IndexSettings::DUMMY_INDEX_IDENTIFIER; + } + + return $this->typeName; + } } diff --git a/src/module-vsbridge-indexer-core/Indexer/RebuildActionInterface.php b/src/module-vsbridge-indexer-core/Indexer/RebuildActionInterface.php new file mode 100644 index 00000000..9701198b --- /dev/null +++ b/src/module-vsbridge-indexer-core/Indexer/RebuildActionInterface.php @@ -0,0 +1,21 @@ +actions = $actions; + $this->objectManager = $objectManager; + } + + /** + * @param string $entityType + * @return RebuildActionInterface + */ + public function getAction($entityType): ?RebuildActionInterface + { + if (isset($this->actions[$entityType])) { + return $this->objectManager->get($this->actions[$entityType]); + } + + return null; + } +} diff --git a/src/module-vsbridge-indexer-core/Indexer/StoreManager.php b/src/module-vsbridge-indexer-core/Indexer/StoreManager.php index f1f40c8b..43d81754 100644 --- a/src/module-vsbridge-indexer-core/Indexer/StoreManager.php +++ b/src/module-vsbridge-indexer-core/Indexer/StoreManager.php @@ -1,10 +1,4 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerCore\Indexer; @@ -46,7 +40,20 @@ public function __construct( } /** - * @param int|null $storeId + * @param string $store + * @return bool + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function isStoreAllowedToReindex(string $store) + { + $storeModel = $this->storeManager->getStore($store); + $allowedStores = $this->getAllStoresAllowedToReindex(); + + return isset($allowedStores[$storeModel->getCode()]); + } + + /** + * @param int|string|null $storeId * * @return array|\Magento\Store\Api\Data\StoreInterface[] * @throws \Magento\Framework\Exception\NoSuchEntityException @@ -82,4 +89,17 @@ public function override(array $stores) { $this->loadedStores = $stores; } + + /** + * @return \Magento\Store\Api\Data\StoreInterface[] + */ + private function getAllStoresAllowedToReindex() + { + $allowedStoreIds = $this->generalSettings->getStoresToIndex(); + $storesByCode = $this->storeManager->getStores(false, true); + + return array_filter($storesByCode, function ($store) use ($allowedStoreIds) { + return in_array($store->getId(), $allowedStoreIds); + }); + } } diff --git a/src/module-vsbridge-indexer-core/Model/Adminhtml/System/Config/Source/Version.php b/src/module-vsbridge-indexer-core/Model/Adminhtml/System/Config/Source/Version.php new file mode 100644 index 00000000..54d06985 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Model/Adminhtml/System/Config/Source/Version.php @@ -0,0 +1,45 @@ +engines = $engines; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + $options = []; + + foreach ($this->engines as $key => $label) { + $options[] = [ + 'value' => $key, + 'label' => $label, + ]; + } + + return $options; + } +} diff --git a/src/module-vsbridge-indexer-core/Model/ElasticsearchResolver.php b/src/module-vsbridge-indexer-core/Model/ElasticsearchResolver.php new file mode 100644 index 00000000..4a01e3b4 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Model/ElasticsearchResolver.php @@ -0,0 +1,39 @@ +scopeConfig = $scopeConfig; + } + + /** + * @inheridoc + * + * @return string + */ + public function getVersion(): string + { + return (string)$this->scopeConfig->getValue(self::ES_VERSION_XPATH); + } +} diff --git a/src/module-vsbridge-indexer-core/Model/ElasticsearchResolverInterface.php b/src/module-vsbridge-indexer-core/Model/ElasticsearchResolverInterface.php new file mode 100644 index 00000000..7b3ea2d1 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Model/ElasticsearchResolverInterface.php @@ -0,0 +1,20 @@ +willReturn($indexMock); - $this->esIndexSettingsMock->method('getIndicesConfig')->willReturn($this->indicesXmlConfiguration); + $this->esIndexSettingsMock->method('getConfig')->willReturn($this->indicesXmlConfiguration); $this->esIndexSettingsMock->method('getIndexAlias')->willReturn($alias); $this->esIndexSettingsMock->method('createIndexName')->willReturn($name); $this->esIndexSettingsMock->method('getEsConfig')->willReturn([]); @@ -166,7 +165,7 @@ public function testCreateNewIndex() ]) ->willReturn($indexMock); - $this->esIndexSettingsMock->method('getIndicesConfig')->willReturn($this->indicesXmlConfiguration); + $this->esIndexSettingsMock->method('getConfig')->willReturn($this->indicesXmlConfiguration); $this->esIndexSettingsMock->method('getIndexAlias')->willReturn($alias); $this->esIndexSettingsMock->method('createIndexName')->willReturn($name); $this->esIndexSettingsMock->method('getEsConfig')->willReturn([]); diff --git a/src/module-vsbridge-indexer-core/Test/Unit/Index/IndexSettingsTest.php b/src/module-vsbridge-indexer-core/Test/Unit/Index/IndexSettingsTest.php index 8fa116c8..a2983cf2 100644 --- a/src/module-vsbridge-indexer-core/Test/Unit/Index/IndexSettingsTest.php +++ b/src/module-vsbridge-indexer-core/Test/Unit/Index/IndexSettingsTest.php @@ -2,9 +2,11 @@ declare(strict_types=1); +namespace Divante\VsbridgeIndexerCore\Test\Unit\Index; + use Divante\VsbridgeIndexerCore\Config\IndicesSettings; use Divante\VsbridgeIndexerCore\Index\IndexSettings; -use Divante\VsbridgeIndexerCore\Index\Indicies\Config; +use Divante\VsbridgeIndexerCore\Index\Indices\ConfigResolver; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use PHPUnit\Framework\TestCase; @@ -23,7 +25,7 @@ class IndexSettingsTest extends TestCase /** * @var IndicesSettings|PHPUnit_Framework_MockObject_MockObject */ - private $configurationSettings; + private $configResolver; /** * @var Store|PHPUnit_Framework_MockObject_MockObject @@ -45,8 +47,7 @@ class IndexSettingsTest extends TestCase */ protected function setUp() { - $this->storeManagerMock = $this->getMockBuilder( - StoreManagerInterface::class) + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) ->disableOriginalConstructor() ->setMethods([]) ->getMockForAbstractClass(); @@ -56,11 +57,11 @@ protected function setUp() ->getMock(); $this->indicesSettingsMock = $this->createMock(IndicesSettings::class); - $this->configurationSettings = $this->createMock(Config::class); + $this->configResolver = $this->createMock(ConfigResolver::class); $this->esIndexSettings = new IndexSettings( $this->storeManagerMock, - $this->configurationSettings, + $this->configResolver, $this->indicesSettingsMock, new DateTimeFactory() ); @@ -71,7 +72,7 @@ protected function setUp() * * @dataProvider provideStores */ - public function testGetIndexAlias(string $storeCode) + public function testGetIndexAlias(string $identifier, string $storeCode) { $indexPrefix = 'vuestorefront'; $this->indicesSettingsMock->method('addIdentifierToDefaultStoreView')->willReturn(true); @@ -79,11 +80,12 @@ public function testGetIndexAlias(string $storeCode) $this->indicesSettingsMock->method('getIndexIdentifier')->willReturn('code'); $this->storeMock->method('getCode')->willReturn($storeCode); + $indexPrefix .= $identifier === IndexSettings::DUMMY_INDEX_IDENTIFIER ? '' : '_' . $identifier; $expectedAlias = strtolower(sprintf('%s_%s', $indexPrefix, $storeCode)); $this->assertEquals( $expectedAlias, - $this->esIndexSettings->getIndexAlias($this->storeMock) + $this->esIndexSettings->getIndexAlias($identifier, $this->storeMock) ); } @@ -93,9 +95,12 @@ public function testGetIndexAlias(string $storeCode) public function provideStores() { return [ - ['de_code'], - ['De_code'], - ['DE_CODE'], + ['vue_storefront_catalog', 'de_code'], + ['vue_storefront_catalog', 'De_code'], + ['vue_storefront_catalog', 'DE_CODE'], + ['product', 'de_code'], + ['product', 'de_code'], + ['product', 'DE_CODE'], ]; } } diff --git a/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES5ParserTest.php b/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES5ParserTest.php new file mode 100644 index 00000000..64cd2a06 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES5ParserTest.php @@ -0,0 +1,106 @@ + ['mapping' => null], + 'cms' => ['mapping' => null], + ]; + + protected function setUp() + { + $this->mappingProviderFactoryMock = $this->getMockBuilder(MappingFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configDataMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->typeFactoryMock = $this->getMockBuilder(TypeFactoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->esConfigParser = new ConfigES5Parser( + $this->typeFactoryMock, + $this->mappingProviderFactoryMock + ); + + parent::setUp(); // TODO: Change the autogenerated stub + } + + /** + * Test parsing config for ES5 + */ + public function testGetConfigForEs5() + { + $mappingMock = $this->getMockBuilder(MappingInterface::class) + ->getMock(); + $this->mappingProviderFactoryMock + ->method('get') + ->willReturn($mappingMock); + + $returnMapValue = []; + foreach (array_keys($this->mockupXmlParseData) as $entity) { + $returnMapValue[] = [ + [ + 'name' => $entity, + 'mapping' => $mappingMock, + ], + $this->createType($entity, $mappingMock) + ]; + } + + $this->typeFactoryMock + ->method('create') + ->will($this->returnValueMap($returnMapValue)); + + $expectedConfig = [ + 'vue_storefront_catalog' => [ + 'types' => [ + 'taxrule' => $this->createType('taxrule', $mappingMock), + 'cms' => $this->createType('cms', $mappingMock) + ] + ] + ]; + + $es5config = $this->esConfigParser->parse($this->mockupXmlParseData); + $this->assertEquals($expectedConfig, $es5config); + } + + /** + * @param string $typeName + * @param $mapping + * @return Type + */ + private function createType(string $typeName, $mapping): Type + { + return new Type($typeName, $mapping); + } +} diff --git a/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES6plusParserTest.php b/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES6plusParserTest.php new file mode 100644 index 00000000..02346b79 --- /dev/null +++ b/src/module-vsbridge-indexer-core/Test/Unit/Index/Indices/ConfigES6plusParserTest.php @@ -0,0 +1,102 @@ + ['mapping' => null], + 'cms' => ['mapping' => null], + ]; + + protected function setUp() + { + $this->mappingProviderFactoryMock = $this->getMockBuilder(MappingFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configDataMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->typeFactoryMock = $this->getMockBuilder(TypeFactoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->esConfigParser = new ConfigES6plusParser( + $this->typeFactoryMock, + $this->mappingProviderFactoryMock + ); + + parent::setUp(); // TODO: Change the autogenerated stub + } + + /** + * Test parsing config for ES5 + */ + public function testGetConfigForEs5() + { + $mappingMock = $this->getMockBuilder(MappingInterface::class) + ->getMock(); + $this->mappingProviderFactoryMock + ->method('get') + ->willReturn($mappingMock); + + $returnMapValue = []; + foreach (array_keys($this->mockupXmlParseData) as $entity) { + $returnMapValue[] = [ + [ + 'name' => $entity, + 'mapping' => $mappingMock, + ], + $this->createType($entity, $mappingMock) + ]; + } + + $this->typeFactoryMock + ->method('create') + ->will($this->returnValueMap($returnMapValue)); + + $expectedConfig = [ + 'taxrule' => ['types' => ['taxrule' => $this->createType('taxrule', $mappingMock)]], + 'cms' => ['types' => ['cms' => $this->createType('cms', $mappingMock)]] + ]; + + $es5config = $this->esConfigParser->parse($this->mockupXmlParseData); + $this->assertEquals($expectedConfig, $es5config); + } + + /** + * @param string $typeName + * @param $mapping + * @return Type + */ + private function createType(string $typeName, $mapping): Type + { + return new Type($typeName, $mapping); + } +} diff --git a/src/module-vsbridge-indexer-core/etc/adminhtml/system.xml b/src/module-vsbridge-indexer-core/etc/adminhtml/system.xml index 4f33affd..525547dc 100644 --- a/src/module-vsbridge-indexer-core/etc/adminhtml/system.xml +++ b/src/module-vsbridge-indexer-core/etc/adminhtml/system.xml @@ -19,6 +19,10 @@ Enable to run indexes with Elasticsearch. Magento\Config\Model\Config\Source\Yesno + + + Divante\VsbridgeIndexerCore\Model\Adminhtml\System\Config\Source\Version + For every store view separated index is created in Elastic diff --git a/src/module-vsbridge-indexer-core/etc/config.xml b/src/module-vsbridge-indexer-core/etc/config.xml index 41230cef..b89e316c 100644 --- a/src/module-vsbridge-indexer-core/etc/config.xml +++ b/src/module-vsbridge-indexer-core/etc/config.xml @@ -26,6 +26,7 @@ 1 0 + elasticsearch5 0 diff --git a/src/module-vsbridge-indexer-core/etc/di.xml b/src/module-vsbridge-indexer-core/etc/di.xml index 34af8fd7..f92e3afc 100644 --- a/src/module-vsbridge-indexer-core/etc/di.xml +++ b/src/module-vsbridge-indexer-core/etc/di.xml @@ -8,21 +8,52 @@ */ --> - + - + - + + + + + + Divante\VsbridgeIndexerCore\Indexer\Action\Full + Divante\VsbridgeIndexerCore\Indexer\Action\Rows + + + + + + + + Elasticsearch5 + Elasticsearch6+ + + + + + + + + Divante\VsbridgeIndexerCore\Index\Indices\ConfigES5Parser + Divante\VsbridgeIndexerCore\Index\Indices\ConfigES6plusParser + + + + + + vsbridgeIndexerLogger @@ -32,7 +63,24 @@ - + + + + + Divante\VsbridgeIndexerCore\Api\IndexOperationInterface + + + + + + + + Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler + + + + + Divante\VsbridgeIndexerCore\Logger\IndexerLogger diff --git a/src/module-vsbridge-indexer-core/etc/vsbridge.xsd b/src/module-vsbridge-indexer-core/etc/vsbridge.xsd new file mode 100644 index 00000000..5e0f6dcc --- /dev/null +++ b/src/module-vsbridge-indexer-core/etc/vsbridge.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/module-vsbridge-indexer-core/etc/vsbridge_indices.xsd b/src/module-vsbridge-indexer-core/etc/vsbridge_indices.xsd deleted file mode 100644 index 17803ace..00000000 --- a/src/module-vsbridge-indexer-core/etc/vsbridge_indices.xsd +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php b/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php index 5646d3fb..b0072838 100644 --- a/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php +++ b/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php @@ -12,11 +12,12 @@ namespace Divante\VsbridgeIndexerReview\Model\Indexer\Action; use Divante\VsbridgeIndexerReview\Model\ResourceModel\Review as ResourceModel; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; /** * Class Review */ -class Review +class Review implements RebuildActionInterface { /** * @var ResourceModel @@ -39,7 +40,7 @@ public function __construct(ResourceModel $resource) * * @return \Traversable */ - public function rebuild(int $storeId = 1, array $reviewIds = []) + public function rebuild(int $storeId, array $reviewIds): \Traversable { $lastReviewId = 0; diff --git a/src/module-vsbridge-indexer-review/Model/Indexer/Review.php b/src/module-vsbridge-indexer-review/Model/Indexer/Review.php deleted file mode 100644 index 09d43c24..00000000 --- a/src/module-vsbridge-indexer-review/Model/Indexer/Review.php +++ /dev/null @@ -1,94 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerReview\Model\Indexer; - -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerReview\Model\Indexer\Action\Review as Action; - -/** - * Class Review - */ -class Review implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var Action - */ - private $action; - - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * Review constructor. - * - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param Action $action - */ - public function __construct( - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - Action $action - ) { - $this->action = $action; - $this->storeManager = $storeManager; - $this->indexHandler = $indexerHandler; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->action->rebuild((int)$store->getId(), $ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->action->rebuild((int)$store->getId()), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-review/etc/di.xml b/src/module-vsbridge-indexer-review/etc/di.xml index 06796ed6..292f65e7 100644 --- a/src/module-vsbridge-indexer-review/etc/di.xml +++ b/src/module-vsbridge-indexer-review/etc/di.xml @@ -1,16 +1,27 @@ - + - vue_storefront_catalog review - + - Divante\VsbridgeIndexerReview\Indexer\ReviewIndexerHandlerVirtual + + Divante\VsbridgeIndexerReview\Model\Indexer\Action\Review + + + + + + + + + Divante\VsbridgeIndexerReview\Model\Indexer\DataProvider\Ratings + + diff --git a/src/module-vsbridge-indexer-review/etc/vsbridge.xml b/src/module-vsbridge-indexer-review/etc/vsbridge.xml new file mode 100644 index 00000000..700926a6 --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/vsbridge.xml @@ -0,0 +1,4 @@ + + + diff --git a/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml deleted file mode 100644 index de65ca1f..00000000 --- a/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Divante\VsbridgeIndexerReview\Model\Indexer\DataProvider\Ratings - - - - diff --git a/src/module-vsbridge-indexer-tax/Model/Indexer/Action/TaxRule.php b/src/module-vsbridge-indexer-tax/Model/Indexer/Action/TaxRule.php index 80e9fd40..7247d59e 100644 --- a/src/module-vsbridge-indexer-tax/Model/Indexer/Action/TaxRule.php +++ b/src/module-vsbridge-indexer-tax/Model/Indexer/Action/TaxRule.php @@ -1,19 +1,15 @@ - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ namespace Divante\VsbridgeIndexerTax\Model\Indexer\Action; use Divante\VsbridgeIndexerTax\ResourceModel\Rules as RulesResourceModel; +use Divante\VsbridgeIndexerCore\Indexer\RebuildActionInterface; + /** * Class TaxRule */ -class TaxRule +class TaxRule implements RebuildActionInterface { /** * @var RulesResourceModel @@ -29,11 +25,12 @@ public function __construct(RulesResourceModel $resource) } /** + * @param int $storeId * @param array $taxRuleIds * * @return \Traversable */ - public function rebuild(array $taxRuleIds = []) + public function rebuild(int $storeId, array $taxRuleIds): \Traversable { $lastTaxRuleId = 0; diff --git a/src/module-vsbridge-indexer-tax/Model/Indexer/TaxRules.php b/src/module-vsbridge-indexer-tax/Model/Indexer/TaxRules.php deleted file mode 100644 index 2159fa30..00000000 --- a/src/module-vsbridge-indexer-tax/Model/Indexer/TaxRules.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerTax\Model\Indexer; - -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerTax\Model\Indexer\Action\TaxRule; - -/** - * Class TaxRules - */ -class TaxRules implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var TaxRule - */ - private $action; - - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * TaxRules constructor. - * - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param TaxRule $action - */ - public function __construct( - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - TaxRule $action - ) { - $this->action = $action; - $this->storeManager = $storeManager; - $this->indexHandler = $indexerHandler; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->action->rebuild($ids), $store); - $this->indexHandler->cleanUpByTransactionKey($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->indexHandler->saveIndex($this->action->rebuild(), $store); - $this->indexHandler->cleanUpByTransactionKey($store); - } - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-tax/etc/di.xml b/src/module-vsbridge-indexer-tax/etc/di.xml index 00236e11..f1107a94 100644 --- a/src/module-vsbridge-indexer-tax/etc/di.xml +++ b/src/module-vsbridge-indexer-tax/etc/di.xml @@ -1,16 +1,28 @@ - + - vue_storefront_catalog taxrule - + - Divante\VsbridgeIndexerTax\Indexer\TaxRuleIndexerHandlerVirtual + + + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxClasses + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxRates + + + + + + + + + Divante\VsbridgeIndexerTax\Model\Indexer\Action\TaxRule + diff --git a/src/module-vsbridge-indexer-tax/etc/vsbridge.xml b/src/module-vsbridge-indexer-tax/etc/vsbridge.xml new file mode 100644 index 00000000..9c94a60c --- /dev/null +++ b/src/module-vsbridge-indexer-tax/etc/vsbridge.xml @@ -0,0 +1,4 @@ + + + diff --git a/src/module-vsbridge-indexer-tax/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-tax/etc/vsbridge_indices.xml deleted file mode 100644 index e83597e8..00000000 --- a/src/module-vsbridge-indexer-tax/etc/vsbridge_indices.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxClasses - Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxRates - - - - From 2d27df7766f2e84e9b55bbcc609aa00e4b8d0098 Mon Sep 17 00:00:00 2001 From: Agata Date: Sun, 20 Sep 2020 23:02:34 +0200 Subject: [PATCH 02/14] Support for ES6+. Fix reindex rows action. --- src/module-vsbridge-indexer-core/Indexer/Action/Rows.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php b/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php index 83588cdc..d2e9257d 100644 --- a/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php +++ b/src/module-vsbridge-indexer-core/Indexer/Action/Rows.php @@ -21,7 +21,7 @@ public function execute(array $ids) foreach ($stores as $store) { $this->getIndexerHandler()->saveIndex($this->rebuild((int) $store->getId(), $ids), $store); - $this->getIndexerHandler()->cleanUpByTransactionKey($store); + $this->getIndexerHandler()->cleanUpByTransactionKey($store, $ids); } } } From 60d52e121b96a20014364f7fa20bca71f986e20c Mon Sep 17 00:00:00 2001 From: Agata Date: Mon, 28 Sep 2020 17:36:58 +0200 Subject: [PATCH 03/14] Remove references. Add test for LoadTierPrices - testing private method: getWebsiteId. --- .../DataProvider/Product/ProductLinksData.php | 4 +- .../Model/Product/LoadTierPrices.php | 72 +++++------ .../Model/ResourceModel/Product/Category.php | 10 +- .../Test/Model/LoadTierPricesTest.php | 120 ++++++++++++++++++ 4 files changed, 162 insertions(+), 44 deletions(-) create mode 100644 src/module-vsbridge-indexer-catalog/Test/Model/LoadTierPricesTest.php diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ProductLinksData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ProductLinksData.php index 45b5e34f..4d3096f2 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ProductLinksData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ProductLinksData.php @@ -40,8 +40,8 @@ public function addData(array $indexData, $storeId) $this->resourceModel->clear(); $this->resourceModel->setProducts($indexData); - foreach ($indexData as &$productDTO) { - $productDTO['product_links'] = $this->resourceModel->getLinkedProduct($productDTO); + foreach ($indexData as $productId => $productDTO) { + $indexData[$productId]['product_links'] = $this->resourceModel->getLinkedProduct($productDTO); } $this->resourceModel->clear(); diff --git a/src/module-vsbridge-indexer-catalog/Model/Product/LoadTierPrices.php b/src/module-vsbridge-indexer-catalog/Model/Product/LoadTierPrices.php index fcb84069..cbe6613d 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Product/LoadTierPrices.php +++ b/src/module-vsbridge-indexer-catalog/Model/Product/LoadTierPrices.php @@ -80,37 +80,38 @@ public function __construct( */ public function execute(array $indexData, int $storeId): array { - if ($this->syncTierPrices()) { - $linkField = $this->productMetaData->get()->getLinkField(); - $linkFieldIds = array_column($indexData, $linkField); - $websiteId = $this->getWebsiteId($storeId); - - $tierPrices = $this->tierPriceResource->loadTierPrices($websiteId, $linkFieldIds); - /** @var \Magento\Catalog\Model\Product\Attribute\Backend\TierPrice $backend */ - $backend = $this->getTierPriceAttribute()->getBackend(); - - foreach ($indexData as $productId => $product) { - $linkFieldValue = $product[$linkField]; - - if (isset($tierPrices[$linkFieldValue])) { - $tierRowsData = $tierPrices[$linkFieldValue]; - $tierRowsData = $backend->preparePriceData( - $tierRowsData, - $indexData[$productId]['type_id'], - $websiteId - ); - - foreach ($tierRowsData as $tierRowData) { - if (Group::NOT_LOGGED_IN_ID === $tierRowData['cust_group'] && $tierRowData['price_qty'] == 1) { - $price = (float) $indexData[$productId]['price']; - $price = min((float) $tierRowData['price'], $price); - $indexData[$productId]['price'] = $price; - } - - $indexData[$productId]['tier_prices'][] = $this->prepareTierPrices($tierRowData); + if (!$this->syncTierPrices()) { + return $indexData; + } + + $linkField = $this->productMetaData->get()->getLinkField(); + $linkFieldIds = array_column($indexData, $linkField); + $websiteId = $this->getWebsiteId($storeId); + + $tierPrices = $this->tierPriceResource->loadTierPrices($websiteId, $linkFieldIds); + /** @var \Magento\Catalog\Model\Product\Attribute\Backend\TierPrice $backend */ + $backend = $this->getTierPriceAttribute()->getBackend(); + + foreach ($indexData as $productId => $product) { + $linkFieldValue = $product[$linkField]; + $indexData[$productId]['tier_prices'] = []; + + if (isset($tierPrices[$linkFieldValue])) { + $tierRowsData = $tierPrices[$linkFieldValue]; + $tierRowsData = $backend->preparePriceData( + $tierRowsData, + $indexData[$productId]['type_id'], + $websiteId + ); + + foreach ($tierRowsData as $tierRowData) { + if (Group::NOT_LOGGED_IN_ID === $tierRowData['cust_group'] && $tierRowData['price_qty'] == 1) { + $price = (float) $indexData[$productId]['price']; + $price = min((float) $tierRowData['price'], $price); + $indexData[$productId]['price'] = $price; } - } else { - $indexData[$productId]['tier_prices'] = []; + + $indexData[$productId]['tier_prices'][] = $this->prepareTierPrices($tierRowData); } } } @@ -150,15 +151,10 @@ private function prepareTierPrices(array $productTierPrice) private function getWebsiteId($storeId) { $attribute = $this->getTierPriceAttribute(); - $websiteId = 0; - - if ($attribute->isScopeGlobal()) { - $websiteId = 0; - } elseif ($storeId) { - $websiteId = (int) ($this->storeManager->getStore($storeId)->getWebsiteId()); - } - return $websiteId; + return $attribute->isScopeGlobal() + ? 0 + : $this->storeManager->getStore($storeId)->getWebsiteId(); } /** diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php index 0796ea2a..ad4c1ad2 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php @@ -82,10 +82,12 @@ public function loadCategoryData($storeId, array $productIds) $storeCategoryName = $this->loadCategoryNames(array_unique($categoryIds), $storeId); - foreach ($categoryData as &$categoryDataRow) { - $categoryDataRow['name'] = ''; - if (isset($storeCategoryName[(int) $categoryDataRow['category_id']])) { - $categoryDataRow['name'] = $storeCategoryName[(int) $categoryDataRow['category_id']]; + foreach ($categoryData as $index => $categoryDataRow) { + $categoryId = (int) $categoryDataRow['category_id']; + $categoryData[$index]['name'] = ''; + + if (isset($storeCategoryName[$categoryId])) { + $categoryData[$index]['name'] = $storeCategoryName[$categoryId]; } } diff --git a/src/module-vsbridge-indexer-catalog/Test/Model/LoadTierPricesTest.php b/src/module-vsbridge-indexer-catalog/Test/Model/LoadTierPricesTest.php new file mode 100644 index 00000000..f27fb675 --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Test/Model/LoadTierPricesTest.php @@ -0,0 +1,120 @@ +attributeDataProviderMock = $this->getMockBuilder(AttributeDataProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->tierPriceResourceMock = $this->getMockBuilder(TierPrices::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->productMetaDataMock = $this->getMockBuilder(ProductMetaData::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configSettingsMock = $this->getMockBuilder(CatalogConfigurationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeMock = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->attributeMock = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loadTierPrices = new LoadTierPrices( + $this->configSettingsMock, + $this->tierPriceResourceMock, + $this->storeManagerMock, + $this->productMetaDataMock, + $this->attributeDataProviderMock + ); + } + + public function testGetWebsiteIdWhenScopeIsNotGlobal() + { + $storeId = 1; + $reflector = new ReflectionClass( LoadTierPrices::class ); + $method = $reflector->getMethod( 'getWebsiteId' ); + $method->setAccessible( true ); + + $this->attributeMock->method('isScopeGlobal')->willReturn(false); + + $this->storeManagerMock->method('getStore')->willReturn($this->storeMock); + $this->storeMock->method('getWebsiteId')->willReturn(1); + + $this->attributeDataProviderMock->method('getAttributeByCode') + ->willReturn($this->attributeMock); + + $result = $method->invokeArgs( $this->loadTierPrices, array( $storeId ) ); + $this->assertEquals( 1, $result ); + } + + public function testGetWebsiteIdWhenScopeIsGlobal() + { + $storeId = 1; + $reflector = new ReflectionClass( LoadTierPrices::class ); + $method = $reflector->getMethod( 'getWebsiteId' ); + $method->setAccessible( true ); + + $this->attributeDataProviderMock->method('getAttributeByCode') + ->willReturn($this->attributeMock); + + $this->attributeMock->method('isScopeGlobal')->willReturn(true); + $result = $method->invokeArgs( $this->loadTierPrices, array( $storeId ) ); + $this->assertEquals( 0, $result ); + } +} From f6ac9359f0c68dda1c72b36c057d0c1e4eb36e0d Mon Sep 17 00:00:00 2001 From: Agata Date: Tue, 29 Sep 2020 17:49:12 +0200 Subject: [PATCH 04/14] Remove vsbridge_product_category indexer. Remove partial product update in ES. --- .../Console/Command/IndexerInfoCommand.php | 28 ----- .../Console/Command/IndexerReindexCommand.php | 29 ----- .../Model/Indexer/ProductCategory.php | 115 ------------------ .../Indexer/ProductCategoryProcessor.php | 30 ----- .../Category/Save/UpdateProductPlugin.php | 63 ---------- .../Indexer/DataCollectionPlugin.php | 52 -------- .../etc/di.xml | 16 --- .../etc/indexer.xml | 5 - .../etc/mview.xml | 4 - .../Api/BulkRequestInterface.php | 13 -- .../Index/BulkRequest.php | 31 ----- .../Indexer/GenericIndexerHandler.php | 60 --------- 12 files changed, 446 deletions(-) delete mode 100644 src/module-vsbridge-indexer-catalog/Console/Command/IndexerInfoCommand.php delete mode 100644 src/module-vsbridge-indexer-catalog/Console/Command/IndexerReindexCommand.php delete mode 100644 src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategory.php delete mode 100644 src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategoryProcessor.php delete mode 100644 src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateProductPlugin.php delete mode 100644 src/module-vsbridge-indexer-catalog/Plugin/Ui/DataProvider/Indexer/DataCollectionPlugin.php diff --git a/src/module-vsbridge-indexer-catalog/Console/Command/IndexerInfoCommand.php b/src/module-vsbridge-indexer-catalog/Console/Command/IndexerInfoCommand.php deleted file mode 100644 index 387b69e1..00000000 --- a/src/module-vsbridge-indexer-catalog/Console/Command/IndexerInfoCommand.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @copyright 2020 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Console\Command; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductCategoryProcessor; - -/** - * @inheritDoc - */ -class IndexerInfoCommand extends \Magento\Indexer\Console\Command\IndexerInfoCommand -{ - /** - * @inheritdoc - */ - protected function getAllIndexers() - { - $indexers = parent::getAllIndexers(); - unset($indexers[ProductCategoryProcessor::INDEXER_ID]); - - return $indexers; - } -} diff --git a/src/module-vsbridge-indexer-catalog/Console/Command/IndexerReindexCommand.php b/src/module-vsbridge-indexer-catalog/Console/Command/IndexerReindexCommand.php deleted file mode 100644 index d77490d1..00000000 --- a/src/module-vsbridge-indexer-catalog/Console/Command/IndexerReindexCommand.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Console\Command; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductCategoryProcessor; -use Symfony\Component\Console\Input\InputInterface; - -/** - * Class IndexerReindexCommand - */ -class IndexerReindexCommand extends \Magento\Indexer\Console\Command\IndexerReindexCommand -{ - /** - * @inheritdoc - */ - protected function getAllIndexers() - { - $indexers = parent::getAllIndexers(); - unset($indexers[ProductCategoryProcessor::INDEXER_ID]); - - return $indexers; - } -} diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategory.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategory.php deleted file mode 100644 index 038c1c61..00000000 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategory.php +++ /dev/null @@ -1,115 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Model\Indexer; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\Action\Product as ProductAction; -use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; -use Divante\VsbridgeIndexerCore\Indexer\StoreManager; -use Divante\VsbridgeIndexerCore\Cache\Processor as CacheProcessor; - -/** - * Class ProductCategory - */ -class ProductCategory implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface -{ - /** - * @var GenericIndexerHandler - */ - private $indexHandler; - - /** - * @var ProductAction - */ - private $productAction; - - /** - * @var StoreManager - */ - private $storeManager; - - /** - * @var CacheProcessor - */ - private $cacheProcessor; - - /** - * Category constructor. - * - * @param CacheProcessor $cacheProcessor - * @param GenericIndexerHandler $indexerHandler - * @param StoreManager $storeManager - * @param ProductAction $action - */ - public function __construct( - CacheProcessor $cacheProcessor, - GenericIndexerHandler $indexerHandler, - StoreManager $storeManager, - ProductAction $action - ) { - $this->productAction = $action; - $this->storeManager = $storeManager; - $this->indexHandler = $indexerHandler; - $this->cacheProcessor = $cacheProcessor; - } - - /** - * @inheritdoc - */ - public function execute($ids) - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->rebuild($store, $ids); - } - } - - /** - * @inheritdoc - */ - public function executeFull() - { - $stores = $this->storeManager->getStores(); - - foreach ($stores as $store) { - $this->rebuild($store); - } - } - - /** - * @param \Magento\Store\Api\Data\StoreInterface $store - * @param array $productIds - * - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - private function rebuild($store, array $productIds = []) - { - $this->indexHandler->updateIndex( - $this->productAction->rebuild($store->getId(), $productIds), - $store, - ['category_data'] - ); - } - - /** - * @inheritdoc - */ - public function executeList(array $ids) - { - $this->execute($ids); - } - - /** - * @inheritdoc - */ - public function executeRow($id) - { - $this->execute([$id]); - } -} diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategoryProcessor.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategoryProcessor.php deleted file mode 100644 index b53e31ef..00000000 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/ProductCategoryProcessor.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Model\Indexer; - -/** - * Class ProductCategoryProcessor - */ -class ProductCategoryProcessor extends \Magento\Framework\Indexer\AbstractProcessor -{ - /** - * Indexer ID - */ - const INDEXER_ID = 'vsbridge_product_category'; - - /** - * Mark Vsbridge Product indexer as invalid - * - * @return void - */ - public function markIndexerAsInvalid() - { - $this->getIndexer()->invalidate(); - } -} diff --git a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateProductPlugin.php b/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateProductPlugin.php deleted file mode 100644 index 3a16cde7..00000000 --- a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateProductPlugin.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @copyright 2019 Divante Sp. z o.o. - * @license See LICENSE_DIVANTE.txt for license details. - */ - -namespace Divante\VsbridgeIndexerCatalog\Plugin\Indexer\Category\Save; - -use Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductCategoryProcessor; -use Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductProcessor; - -/** - * Class UpdateProductPlugin - */ -class UpdateProductPlugin -{ - /** - * @var ProductCategoryProcessor - */ - private $productCategoryProcessor; - - /** - * @var ProductProcessor - */ - private $productProcessor; - - /** - * UpdateProduct constructor. - * - * @param ProductProcessor $productProcessor - * @param ProductCategoryProcessor $processor - */ - public function __construct( - ProductProcessor $productProcessor, - ProductCategoryProcessor $processor - ) { - $this->productProcessor = $productProcessor; - $this->productCategoryProcessor = $processor; - } - - /** - * Update product category data in ES after changing category products - * - * @param \Magento\Catalog\Model\Category $category - * @return \Magento\Catalog\Model\Category - */ - public function afterSave(\Magento\Catalog\Model\Category $category) - { - $isChangedProductList = $category->getData('is_changed_product_list'); - - if (!$isChangedProductList) { - return $category; - } - - if (!$this->productProcessor->isIndexerScheduled() && !$this->productCategoryProcessor->isIndexerScheduled()) { - $this->productCategoryProcessor->reindexList($category->getAffectedProductIds()); - } - - return $category; - } -} diff --git a/src/module-vsbridge-indexer-catalog/Plugin/Ui/DataProvider/Indexer/DataCollectionPlugin.php b/src/module-vsbridge-indexer-catalog/Plugin/Ui/DataProvider/Indexer/DataCollectionPlugin.php deleted file mode 100644 index 9bd512e0..00000000 --- a/src/module-vsbridge-indexer-catalog/Plugin/Ui/DataProvider/Indexer/DataCollectionPlugin.php +++ /dev/null @@ -1,52 +0,0 @@ -findIndexerKey($items); - - if ($keyToRemove) { - unset($items[$keyToRemove]); - $subject->removeItemByKey($keyToRemove); - } - - return $items; - } - - /** - * @param \Magento\Framework\DataObject[] $items - * - * @return int - */ - private function findIndexerKey($items): int - { - $keyToRemove = - 1; - - foreach ($items as $key => $item) { - if ($item->getData('indexer_id') === ProductCategoryProcessor::INDEXER_ID) { - $keyToRemove = $key; - break; - } - } - - return $keyToRemove; - } -} diff --git a/src/module-vsbridge-indexer-catalog/etc/di.xml b/src/module-vsbridge-indexer-catalog/etc/di.xml index 56d6f607..b3078fed 100644 --- a/src/module-vsbridge-indexer-catalog/etc/di.xml +++ b/src/module-vsbridge-indexer-catalog/etc/di.xml @@ -1,8 +1,5 @@ - - - @@ -85,10 +82,6 @@ - - - @@ -173,7 +166,6 @@ - @@ -268,14 +260,6 @@ - - - - Divante\VsbridgeIndexerCatalog\Model\Indexer\ProductCategoryProcessor::INDEXER_ID - - - - diff --git a/src/module-vsbridge-indexer-catalog/etc/indexer.xml b/src/module-vsbridge-indexer-catalog/etc/indexer.xml index ce3eed4c..d1322e28 100644 --- a/src/module-vsbridge-indexer-catalog/etc/indexer.xml +++ b/src/module-vsbridge-indexer-catalog/etc/indexer.xml @@ -19,9 +19,4 @@ Vsbridge Attributes Indexer Update Product Attributes Meta Data in Elastic - - Vsbridge Product Category Indexer - Partial Product update - update category, category_ids fields in ES - diff --git a/src/module-vsbridge-indexer-catalog/etc/mview.xml b/src/module-vsbridge-indexer-catalog/etc/mview.xml index 123a0a42..d623ab6f 100644 --- a/src/module-vsbridge-indexer-catalog/etc/mview.xml +++ b/src/module-vsbridge-indexer-catalog/etc/mview.xml @@ -18,10 +18,6 @@ - - - - diff --git a/src/module-vsbridge-indexer-core/Api/BulkRequestInterface.php b/src/module-vsbridge-indexer-core/Api/BulkRequestInterface.php index 804e46b1..61cc9cc1 100644 --- a/src/module-vsbridge-indexer-core/Api/BulkRequestInterface.php +++ b/src/module-vsbridge-indexer-core/Api/BulkRequestInterface.php @@ -55,17 +55,4 @@ public function addDocuments($index, $type, array $data); * @return \Divante\VsbridgeIndexerCore\Api\BulkRequestInterface */ public function deleteDocuments($index, $type, array $docIds); - - /** - * Update several documents to the index. - * - * $data format have to be an array of all documents with document id as key. - * - * @param string $index Index the documents have to be added to. - * @param string $type Document type. - * @param array $data Document data. - * - * @return \Divante\VsbridgeIndexerCore\Api\BulkRequestInterface - */ - public function updateDocuments($index, $type, array $data); } diff --git a/src/module-vsbridge-indexer-core/Index/BulkRequest.php b/src/module-vsbridge-indexer-core/Index/BulkRequest.php index 8785902d..51df857b 100644 --- a/src/module-vsbridge-indexer-core/Index/BulkRequest.php +++ b/src/module-vsbridge-indexer-core/Index/BulkRequest.php @@ -96,37 +96,6 @@ private function addDocument($index, $type, $docId, array $data) return $this; } - /** - * @inheritdoc - */ - public function updateDocuments($index, $type, array $data) - { - foreach ($data as $docId => $documentData) { - $documentData = $this->prepareDocument($documentData); - $this->updateDocument($index, $type, $docId, $documentData); - } - - return $this; - } - - /** - * @inheritdoc - */ - private function updateDocument($index, $type, $docId, array $data) - { - $this->bulkData[] = [ - 'update' => [ - '_index' => $index, - '_id' => $docId, - '_type' => $type, - ] - ]; - - $this->bulkData[] = ['doc' => $data]; - - return $this; - } - /** * @inheritdoc */ diff --git a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php index d117e604..90720ad0 100644 --- a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php +++ b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php @@ -112,66 +112,6 @@ public function __construct( $this->transactionKey = $transactionKey; } - /** - * Partial document update in ES - * - * @param Traversable $documents - * @param StoreInterface $store - * @param array $requireDataProvides - * - * @return $this - * @throws ConnectionUnhealthyException - */ - public function updateIndex(Traversable $documents, StoreInterface $store, array $requireDataProvides) - { - try { - $index = $this->getIndex($store); - $type = $index->getType($this->typeName); - $storeId = (int)$store->getId(); - $dataProviders = []; - - foreach ($this->getDataProviders() as $name => $dataProvider) { - if (in_array($name, $requireDataProvides)) { - $dataProviders[] = $dataProvider; - } - } - - if (empty($dataProviders)) { - return $this; - } - - $batchSize = $this->indexOperations->getBatchIndexingSize(); - - foreach ($this->batch->getItems($documents, $batchSize) as $docs) { - foreach ($dataProviders as $datasource) { - if (!empty($docs)) { - $docs = $datasource->addData($docs, $storeId); - } - } - - $bulkRequest = $this->indexOperations->createBulk()->updateDocuments( - $index->getName(), - $this->typeName, - $docs - ); - - $this->indexOperations->optimizeEsIndexing($storeId, $index->getName()); - $response = $this->indexOperations->executeBulk($storeId, $bulkRequest); - $this->indexOperations->cleanAfterOptimizeEsIndexing($storeId, $index->getName()); - $this->bulkLogger->log($response); - $docs = null; - } - - $this->indexOperations->refreshIndex($store->getId(), $index); - } catch (ConnectionDisabledException $exception) { - // do nothing, ES indexer disabled in configuration - } catch (ConnectionUnhealthyException $exception) { - $this->indexerLogger->error($exception->getMessage()); - $this->indexOperations->cleanAfterOptimizeEsIndexing($storeId, $index->getName()); - throw $exception; - } - } - /** * Save documents in ES * From 11cde7e14a8a15076dade973c4e7eb43614f7152 Mon Sep 17 00:00:00 2001 From: Agata Date: Tue, 29 Sep 2020 18:00:35 +0200 Subject: [PATCH 05/14] Cleanup usage of DataFilter. Use DataFilter only to remove unwanted/unnecessary fields. --- .../Product/CustomOptionConverter.php | 2 - .../Model/Indexer/Action/Category.php | 5 +-- .../DataProvider/Category/AttributeData.php | 33 ++------------ .../DataProvider/Product/AttributeData.php | 10 +---- .../etc/di.xml | 18 -------- .../Index/ConvertValue.php | 4 ++ .../Index/DataFilter.php | 45 +++---------------- 7 files changed, 16 insertions(+), 101 deletions(-) diff --git a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php index 5b3b773d..d3bd4443 100644 --- a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php +++ b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php @@ -112,8 +112,6 @@ private function prepareOption(array $option): array { $option = $this->unsetFields($option); - $option = $this->dataFilter->execute($option, $this->fieldsToDelete); - if ('drop_down' === $option['type']) { $option['type'] = 'select'; } diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php index 66a2900a..f44a6953 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Category.php @@ -45,10 +45,9 @@ public function rebuild(int $storeId, array $categoryIds): \Traversable foreach ($categories as $category) { $lastCategoryId = $category['entity_id']; - $categoryData['id'] = (int)$category['entity_id']; - $categoryData = $category; + $category['id'] = (int)$category['entity_id']; - yield $lastCategoryId => $categoryData; + yield $lastCategoryId => $category; } } while (!empty($categories)); } diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php index b4651be8..12cb74b4 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php @@ -16,7 +16,6 @@ use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\AttributeDataProvider; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\ProductCount as ProductCountResourceModel; use Divante\VsbridgeIndexerCatalog\Api\DataProvider\Category\AttributeDataProviderInterface; -use Divante\VsbridgeIndexerCore\Index\DataFilter; /** * Class AttributeData @@ -66,11 +65,6 @@ class AttributeData implements AttributeDataProviderInterface */ private $productCountResource; - /** - * @var \Divante\VsbridgeIndexerCore\Index\DataFilter - */ - private $dataFilter; - /** * @var array */ @@ -101,7 +95,6 @@ class AttributeData implements AttributeDataProviderInterface * @param CategoryConfigInterface $configSettings * @param CategoryAttributes $categoryAttributes * @param CategoryChildAttributes $categoryChildAttributes - * @param DataFilter $dataFilter */ public function __construct( AttributeDataProvider $attributeResource, @@ -110,15 +103,13 @@ public function __construct( ApplyCategorySlugInterface $applyCategorySlug, CategoryConfigInterface $configSettings, CategoryAttributes $categoryAttributes, - CategoryChildAttributes $categoryChildAttributes, - DataFilter $dataFilter + CategoryChildAttributes $categoryChildAttributes ) { $this->settings = $configSettings; $this->applyCategorySlug = $applyCategorySlug; $this->productCountResource = $productCountResource; $this->attributeResourceModel = $attributeResource; $this->childrenResourceModel = $childrenResource; - $this->dataFilter = $dataFilter; $this->categoryAttributes = $categoryAttributes; $this->childAttributes = $categoryChildAttributes; } @@ -285,7 +276,9 @@ private function prepareCategory(array $categoryDTO, int $storeId) } $categoryDTO = array_diff_key($categoryDTO, array_flip($this->fieldsToRemove)); - $categoryDTO = $this->filterData($categoryDTO); + $categoryDTO['parent_id'] = (int) $categoryDTO['parent_id']; + $categoryDTO['position'] = (int) $categoryDTO['position']; + $categoryDTO['level'] = (int) $categoryDTO['level']; return $categoryDTO; } @@ -341,22 +334,4 @@ private function addSlug(array $categoryDTO) { return $this->applyCategorySlug->execute($categoryDTO); } - - /** - * @param array $categoryData - * - * @return array - */ - private function filterData(array $categoryData) - { - return $this->getDataFilter()->execute($categoryData); - } - - /** - * @return DataFilter - */ - private function getDataFilter() - { - return $this->dataFilter; - } } diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php index fc91bee4..0e707368 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/AttributeData.php @@ -10,7 +10,6 @@ use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product\AttributeDataProvider; use Divante\VsbridgeIndexerCore\Api\DataProviderInterface; -use Divante\VsbridgeIndexerCore\Index\DataFilter; use Divante\VsbridgeIndexerCatalog\Api\CatalogConfigurationInterface; use Divante\VsbridgeIndexerCatalog\Api\SlugGeneratorInterface; use Divante\VsbridgeIndexerCatalog\Model\ProductUrlPathGenerator; @@ -26,11 +25,6 @@ class AttributeData implements DataProviderInterface */ private $resourceModel; - /** - * @var DataFilter - */ - private $dataFilter; - /** * @var CatalogConfigurationInterface */ @@ -54,10 +48,10 @@ class AttributeData implements DataProviderInterface /** * AttributeData constructor. * + * @param ProductAttributes $productAttributes * @param CatalogConfigurationInterface $configSettings * @param SlugGeneratorInterface $slugGenerator * @param ProductUrlPathGenerator $productUrlPathGenerator - * @param DataFilter $dataFilter * @param AttributeDataProvider $resourceModel */ public function __construct( @@ -65,13 +59,11 @@ public function __construct( CatalogConfigurationInterface $configSettings, SlugGeneratorInterface $slugGenerator, ProductUrlPathGenerator $productUrlPathGenerator, - DataFilter $dataFilter, AttributeDataProvider $resourceModel ) { $this->slugGenerator = $slugGenerator; $this->settings = $configSettings; $this->resourceModel = $resourceModel; - $this->dataFilter = $dataFilter; $this->productAttributes = $productAttributes; $this->productUrlPathGenerator = $productUrlPathGenerator; } diff --git a/src/module-vsbridge-indexer-catalog/etc/di.xml b/src/module-vsbridge-indexer-catalog/etc/di.xml index b3078fed..9994662a 100644 --- a/src/module-vsbridge-indexer-catalog/etc/di.xml +++ b/src/module-vsbridge-indexer-catalog/etc/di.xml @@ -83,24 +83,6 @@ - - - - level - id - parent_id - position - children_count - - - - - - Divante\VsbridgeIndexerCatalog\Indexer\CategoryDataFilterVirtual - - - diff --git a/src/module-vsbridge-indexer-core/Index/ConvertValue.php b/src/module-vsbridge-indexer-core/Index/ConvertValue.php index e36abb37..270a3a88 100644 --- a/src/module-vsbridge-indexer-core/Index/ConvertValue.php +++ b/src/module-vsbridge-indexer-core/Index/ConvertValue.php @@ -69,6 +69,10 @@ private function getFieldTypeByCode(array $mapping, string $field) return $this->castMapping[$type]; } + if (strstr($field, 'is_') || strstr($field, 'has_')) { + return 'bool'; + } + return null; } diff --git a/src/module-vsbridge-indexer-core/Index/DataFilter.php b/src/module-vsbridge-indexer-core/Index/DataFilter.php index 0b5bf5f7..74b0544a 100644 --- a/src/module-vsbridge-indexer-core/Index/DataFilter.php +++ b/src/module-vsbridge-indexer-core/Index/DataFilter.php @@ -9,56 +9,21 @@ namespace Divante\VsbridgeIndexerCore\Index; /** - * Class responsible for removing fields from array (if provided) and casting values - * TODO check if still need this to use this + * Class responsible for removing fields from array */ class DataFilter { /** - * @var array - */ - private $integerProperties = []; - - /** - * @var array - */ - private $floatProperties = []; - - /** - * DataFilter constructor. - * - * @param array $integerProperties - * @param array $floatProperties - */ - public function __construct( - array $integerProperties = [], - array $floatProperties = [] - ) { - $this->integerProperties = $integerProperties; - $this->floatProperties = $floatProperties; - } - - /** - * @param array $dtoToFilter - * @param array|null $blackList + * @param array $dtoToFilter + * @param array $blackList * * @return array */ - public function execute(array $dtoToFilter, array $blackList = null) + public function execute(array $dtoToFilter, array $blackList) { foreach ($dtoToFilter as $key => $val) { - if ($blackList && in_array($key, $blackList)) { + if (in_array($key, $blackList)) { unset($dtoToFilter[$key]); - } else { - if (strstr($key, 'is_') || strstr($key, 'has_')) { - $dtoToFilter[$key] = (bool)$val; - } else { - if (in_array($key, $this->integerProperties)) { - $dtoToFilter[$key] = (int)$val; - } elseif (in_array($key, $this->floatProperties)) { - $dtoToFilter[$key] = (float)$val; - } - } } } From 63b01d885d1011d93318d567934e6e4f66b03663 Mon Sep 17 00:00:00 2001 From: Agata Date: Wed, 30 Sep 2020 20:24:01 +0200 Subject: [PATCH 06/14] Remove virtualtype CustomOptionsDataFilterVirtual --- .../Product/CustomOptionConverter.php | 10 +++++++--- src/module-vsbridge-indexer-catalog/etc/di.xml | 18 ------------------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php index d3bd4443..352049d5 100644 --- a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php +++ b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php @@ -77,7 +77,7 @@ public function process(array $options, array $optionValues): array */ private function prepareValue(array $option): array { - $option = $this->unsetFields($option); + $option = $this->filterData($option); unset($option['option_id']); return $option; @@ -88,9 +88,13 @@ private function prepareValue(array $option): array * * @return array */ - private function unsetFields(array $option): array + private function filterData(array $option): array { $option = $this->dataFilter->execute($option, $this->fieldsToDelete); + $option['sort_order'] = (int) $option['sort_order']; + $option['option_id'] = (int) $option['sort_order']; + $option['option_type_id'] = (int) $option['sort_order']; + $option['price'] = (float) $option['price']; if (isset($option['sku']) !== true) { unset($option['sku']); @@ -110,7 +114,7 @@ private function unsetFields(array $option): array */ private function prepareOption(array $option): array { - $option = $this->unsetFields($option); + $option = $this->filterData($option); if ('drop_down' === $option['type']) { $option['type'] = 'select'; diff --git a/src/module-vsbridge-indexer-catalog/etc/di.xml b/src/module-vsbridge-indexer-catalog/etc/di.xml index 9994662a..efb29f24 100644 --- a/src/module-vsbridge-indexer-catalog/etc/di.xml +++ b/src/module-vsbridge-indexer-catalog/etc/di.xml @@ -83,24 +83,6 @@ - - - - sort_order - option_id - option_type_id - - - price - - - - - - Divante\VsbridgeIndexerCatalog\Indexer\CustomOptionsDataFilterVirtual - - From f247268f0ccce58ac48f93c66d08a10378924098 Mon Sep 17 00:00:00 2001 From: Agata Date: Wed, 30 Sep 2020 21:00:12 +0200 Subject: [PATCH 07/14] ES6+ support. Remove some unnecessary code from previos version/concept of the solution. --- src/module-vsbridge-indexer-core/etc/di.xml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/module-vsbridge-indexer-core/etc/di.xml b/src/module-vsbridge-indexer-core/etc/di.xml index f92e3afc..ebd9a8e5 100644 --- a/src/module-vsbridge-indexer-core/etc/di.xml +++ b/src/module-vsbridge-indexer-core/etc/di.xml @@ -64,22 +64,6 @@ - - - - Divante\VsbridgeIndexerCore\Api\IndexOperationInterface - - - - - - - - Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler - - - - Divante\VsbridgeIndexerCore\Logger\IndexerLogger From cec6475595961df41be6470f30839876060db77c Mon Sep 17 00:00:00 2001 From: Agata Date: Wed, 30 Sep 2020 21:15:44 +0200 Subject: [PATCH 08/14] Version 2.0 - update release notes what change in version 2.0 --- CHANGELOG.MD | 6 ++++ README.md | 19 ++++++++++-- docs/configuration.md | 17 +++++----- docs/images/config-general-enable.png | Bin 36553 -> 0 bytes docs/images/config-general.png | Bin 57654 -> 81161 bytes docs/upgrade-to-2.0.md | 43 ++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 12 deletions(-) delete mode 100644 docs/images/config-general-enable.png create mode 100644 docs/upgrade-to-2.0.md diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 70e52b87..0b3ebf89 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -2,6 +2,12 @@ ## Unreleased +## [2.0] + +- Added support for ES6+. You can choose ES version in configuration (by default ES5 is selected). For ES 5 only one index is created, for ES6+ we have separated indices per type +- vsbridge_indexer.xml was replaced with vsbridge.xml. You declare type/entity and mapping in vsbridge.xml +- Dataproviders configuration was moved to di.xml + ## [1.21.0] (2020.09.16) ### Fixed diff --git a/README.md b/README.md index c06f3e12..8a744f8a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,21 @@ Sign up for a demo at https://vuestorefront.io/ (Vue Storefront integrated with ## Overview + + +### Version 1.x +Pull Requests should be made against 1.x branch. Changes from this branch won't be merge to master. +Only fixes will be accepted. + +### Version 2.0 +Support ES5 and ES6+. + +##### To read what changed click [here](CHANGELOG.MD#20) + +##### How to upgrade to 2.0 +Click here to find out [more](docs/upgrade-to-2.0.md) + + ### Version 1.5.0/1.5.1 - support for aliases. Command ` php bin/magento vsbridge:reindex --all` will reindex all data to new index. It will create new index and update aliases at the end. @@ -79,7 +94,7 @@ Configure the module in Magento panel and run full indexation. **Check configuration [here](docs/configuration.md)** -### Update VSF/VSF-API configuration +### Update VSF/VSF-API configuration for ES5 **Important**: It is crucial to update configuration `elasticsearch.index` in the VSF and `elasticsearch.indices` in VSF-API *Index Alias Prefix* → define prefixes for ElasticSearch indexes. The panel allows adding prefix only to the catalog name e.g.: *vue_storefront_catalog*. For each store (store view) index name is generated on the base of defined prefix and either ID or Store Code. Aliases cannot be created. @@ -190,7 +205,7 @@ Note: If a docker with ElasticSearch is disabled, Indexer will display error: "N ### Compatibility ---- Tested with ES: 5.6.11, 6.8.0, 7.6.2 +--- version 2.0 - tested with ES: 5.6.11, 6.8.0, 7.6.2 -- Vue Storefront >= 1.4.4 Module was tested on: diff --git a/docs/configuration.md b/docs/configuration.md index 898e46c9..375409b2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,19 +1,16 @@ ### Magento Configuration Go to the new ‘Indexer’ section (Stores → Configuration → Vuestorefront → Indexer), available now in the in the Magento Panel, and configure it in the listed areas: -1. ####General settings → Enable VS Bridge - - Enable to export data to elasticsearch. By default indexing is disable. +1. ####General settings - ![](./images/config-general-enable.png) + **Enable VS Bridge** → Enable to export data to elasticsearch. By default indexing is disable. -1. ####General settings → Elasticsearch version + **Elasticsearch version** + Select ES version depends on which ES you are using/is used by magento. If you are using ES 5.x.x, choose Elasticsearch5, if you are using version 6.x.x or 7.x.x choose Elasticsearch6+. - By default Elasticsearch5 option is selected. - -1. ####General settings → List of stores to reindex - - Select stores for which data must be exported to ElasticSearch. By default stores 0 to 1 are selected. For each store view, a new, separate ElasticSearch index is created. + By default Elasticsearch5 option is selected. + + **List of stores to reindex** → Select stores for which data must be exported to ElasticSearch. By default store with ID 1 is selected. For each store view, a new, separate ElasticSearch index is created. ![](./images/config-general.png) diff --git a/docs/images/config-general-enable.png b/docs/images/config-general-enable.png deleted file mode 100644 index 7c989c9a57ad40caa289b7e6140e91b837057c58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36553 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 De4(ll literal 57654 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 diff --git a/docs/upgrade-to-2.0.md b/docs/upgrade-to-2.0.md new file mode 100644 index 00000000..9bf9316b --- /dev/null +++ b/docs/upgrade-to-2.0.md @@ -0,0 +1,43 @@ +1. Vsbridge_indexer.xml was replaced with vsbridge.xml +- Vsbridge.xml keep information about type/entity and mapping. +- Datapvoriderds were moved to di.xml +Example: + +Before: +``` + + + + + + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxClasses + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxRates + + + + +``` + +After: +**vsbridge:xml** +``` + + + +``` + +**di.xml** +``` + + + + + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxClasses + Divante\VsbridgeIndexerTax\Model\Indexer\DataProvider\TaxRates + + + + +``` From aa710b6f7ce758215df9fe3fd2eef8f05e8feefd Mon Sep 17 00:00:00 2001 From: Agata Date: Thu, 8 Oct 2020 19:55:43 +0200 Subject: [PATCH 09/14] Fix exporting custom options. --- .../ArrayConverter/Product/CustomOptionConverter.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php index 352049d5..eb0b1958 100644 --- a/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php +++ b/src/module-vsbridge-indexer-catalog/ArrayConverter/Product/CustomOptionConverter.php @@ -60,7 +60,7 @@ public function process(array $options, array $optionValues): array $optionValue = $this->prepareValue($optionValue); $options[$optionId]['values'][] = $optionValue; } - + foreach ($options as $option) { $productId = $option['product_id']; $option = $this->prepareOption($option); @@ -114,6 +114,7 @@ private function filterData(array $option): array */ private function prepareOption(array $option): array { + $option['is_require'] = (boolean)$option['is_require']; $option = $this->filterData($option); if ('drop_down' === $option['type']) { From 8091ea67e24721a0877465debf84ed10f921211b Mon Sep 17 00:00:00 2001 From: Agata Date: Thu, 8 Oct 2020 20:15:46 +0200 Subject: [PATCH 10/14] Restore correct product limit for export. --- .../Model/Indexer/Action/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php index 4a064b60..ad4f31c4 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/Action/Product.php @@ -43,7 +43,7 @@ public function rebuild(int $storeId, array $productIds) : \Traversable } do { - $products = $this->resourceModel->getProducts($storeId, $productIds, $lastProductId, 10); + $products = $this->resourceModel->getProducts($storeId, $productIds, $lastProductId); /** @var array $product */ foreach ($products as $product) { From 2931814f106d218424976bdefe9b9d140b93db22 Mon Sep 17 00:00:00 2001 From: Agata Date: Fri, 30 Oct 2020 19:21:39 +0100 Subject: [PATCH 11/14] Update readme. --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8a744f8a..3c2e9479 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Sign up for a demo at https://vuestorefront.io/ (Vue Storefront integrated with ### Version 1.x -Pull Requests should be made against 1.x branch. Changes from this branch won't be merge to master. +Pull Requests should be made against 1.x branch. Changes from this branch won't be merge to main branch. Only fixes will be accepted. ### Version 2.0 @@ -29,8 +29,7 @@ Support ES5 and ES6+. ##### How to upgrade to 2.0 Click here to find out [more](docs/upgrade-to-2.0.md) - -### Version 1.5.0/1.5.1 - support for aliases. +##### Version 1.5.0/1.5.1 - support for aliases. Command ` php bin/magento vsbridge:reindex --all` will reindex all data to new index. It will create new index and update aliases at the end. From 128b6759c25b41f3b54df5755be02b6737554752 Mon Sep 17 00:00:00 2001 From: mamsincl Date: Mon, 2 Nov 2020 17:58:19 +0000 Subject: [PATCH 12/14] Update tax class fetcher since the limitation of fetchAssoc() only one Customer and Product class being fetched by --- src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php b/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php index d5ed75b7..c36277fd 100644 --- a/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php +++ b/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php @@ -50,7 +50,7 @@ public function loadTaxClasses(array $ruleIds) $select->distinct(true); - return $this->getConnection()->fetchAssoc($select); + return $this->getConnection()->fetchAll($select); } /** From 98adbd7e48aa9e2cc17a28c06fc2ea375b3a0200 Mon Sep 17 00:00:00 2001 From: mamsincl Date: Mon, 2 Nov 2020 18:29:45 +0000 Subject: [PATCH 13/14] Amend SQL query --- .../ResourceModel/TaxClasses.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php b/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php index c36277fd..1a8fee85 100644 --- a/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php +++ b/src/module-vsbridge-indexer-tax/ResourceModel/TaxClasses.php @@ -43,14 +43,15 @@ public function loadTaxClasses(array $ruleIds) $this->resource->getTableName('tax_calculation'), [ 'tax_calculation_rule_id', - 'customer_tax_class_id', - 'product_tax_class_id', + 'customer_tax_class_ids' => new \Zend_Db_Expr('GROUP_CONCAT(DISTINCT(`customer_tax_class_id`))'), + 'product_tax_class_ids' => new \Zend_Db_Expr('GROUP_CONCAT(DISTINCT(`product_tax_class_id`))'), ] - )->where('tax_calculation_rule_id IN (?)', $ruleIds); + )->where('tax_calculation_rule_id IN (?)', $ruleIds + )->group('tax_calculation_rule_id'); $select->distinct(true); - return $this->getConnection()->fetchAll($select); + return $this->getConnection()->fetchAssoc($select); } /** From 201f1181bf51ab4c42583465c2b5098c1b6f3351 Mon Sep 17 00:00:00 2001 From: mamsincl Date: Mon, 2 Nov 2020 18:31:24 +0000 Subject: [PATCH 14/14] Update query result processing --- .../Model/Indexer/DataProvider/TaxClasses.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/module-vsbridge-indexer-tax/Model/Indexer/DataProvider/TaxClasses.php b/src/module-vsbridge-indexer-tax/Model/Indexer/DataProvider/TaxClasses.php index b8bfc239..61487ec4 100644 --- a/src/module-vsbridge-indexer-tax/Model/Indexer/DataProvider/TaxClasses.php +++ b/src/module-vsbridge-indexer-tax/Model/Indexer/DataProvider/TaxClasses.php @@ -40,10 +40,19 @@ public function addData(array $indexData, $storeId) foreach ($taxClasses as $data) { $ruleId = $data['tax_calculation_rule_id']; - $indexData[$ruleId]['customer_tax_class_ids'][] = (int)$data['customer_tax_class_id']; - $indexData[$ruleId]['product_tax_class_ids'][] = (int)$data['product_tax_class_id']; + $indexData[$ruleId]['customer_tax_class_ids'] = $this->explodeAsInt($data['customer_tax_class_ids']); + $indexData[$ruleId]['product_tax_class_ids'] = $this->explodeAsInt($data['product_tax_class_ids']); } return $indexData; } + + /** + * @param string $concatIds + * @return array + */ + protected function explodeAsInt(string $concatIds) : array + { + return array_map('intval', explode(',', $concatIds)); + } }