From ad5069a8b03b2f3b1a963053b32e4254a491872e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 17 Nov 2017 17:41:40 +0100 Subject: [PATCH 01/19] Refactor ES client. --- .../Api/Client/ClientFactoryInterface.php | 2 + .../Api/Client/ClientInterface.php | 162 +++++++++++++++++ .../Client/Client.php | 171 ++++++++++++++++++ .../Client/ClientFactory.php | 2 + .../Cluster/ClusterInfo.php | 8 +- .../Index/IndexOperation.php | 69 +++---- .../Search/Adapter/Elasticsuite/Adapter.php | 16 +- .../Adapter/Elasticsuite/Spellchecker.php | 14 +- .../Test/Unit/Cluster/ClusterInfoTest.php | 12 +- .../Test/Unit/Index/IndexOperationTest.php | 25 +-- .../Adapter/Elasticsuite/AdapterTest.php | 17 +- src/module-elasticsuite-core/etc/di.xml | 3 + .../Model/Indexer/IndexHandler.php | 13 +- 13 files changed, 412 insertions(+), 102 deletions(-) create mode 100644 src/module-elasticsuite-core/Api/Client/ClientInterface.php create mode 100644 src/module-elasticsuite-core/Client/Client.php diff --git a/src/module-elasticsuite-core/Api/Client/ClientFactoryInterface.php b/src/module-elasticsuite-core/Api/Client/ClientFactoryInterface.php index 49d705593..47f5375a9 100644 --- a/src/module-elasticsuite-core/Api/Client/ClientFactoryInterface.php +++ b/src/module-elasticsuite-core/Api/Client/ClientFactoryInterface.php @@ -20,6 +20,8 @@ * @category Smile_Elasticsuite * @package Smile\ElasticsuiteCore * @author Aurelien FOUCRET + * + * @deprecated Use Smile\ElasticsuiteCore\Api\Client\ClientInterface instead. */ interface ClientFactoryInterface { diff --git a/src/module-elasticsuite-core/Api/Client/ClientInterface.php b/src/module-elasticsuite-core/Api/Client/ClientInterface.php new file mode 100644 index 000000000..a3d9f02cc --- /dev/null +++ b/src/module-elasticsuite-core/Api/Client/ClientInterface.php @@ -0,0 +1,162 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Api\Client; + +/** + * ElasticSearch injectable client. + * + * @category Smile_Elasticsuite + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +interface ClientInterface +{ + /** + * Returns server information. + * + * @return array + */ + public function info(); + + /** + * Try to connect the server and returns : + * - true if succeed + * - false if failed + * + * @return boolean + */ + public function ping(); + + /** + * Create an index. + * + * @param string $indexName Index name. + * @param array $indexSettings Index settings. + * + * @return void + */ + public function createIndex($indexName, $indexSettings); + + /** + * Delete an index. + * + * @param string $indexName Index name. + * + * @return void + */ + public function deleteIndex($indexName); + + /** + * Check if an index exists. + * + * @param string $indexName Index name. + * + * @return boolean + */ + public function indexExists($indexName); + + /** + * Update index settings. + * + * @param string $indexName Index name. + * @param array $indexSettings Index settings. + * + * @return void + */ + public function putIndexSettings($indexName, $indexSettings); + + /** + * Update index mapping. + * + * @param string $indexName Index name. + * @param string $type Type. + * @param array $mapping Mapping definition. + * + * @return void + */ + public function putMapping($indexName, $type, $mapping); + + /** + * Optimize an index (force segment merging). + * + * @param string $indexName Index name. + * + * @return void + */ + public function forceMerge($indexName); + + /** + * Force index refresh. + * + * @param string $indexName Index name. + * + * @return void + */ + public function refreshIndex($indexName); + + /** + * Retrieve the list of all index having a specified alias. + * + * @param string $indexAlias Index alias. + * + * @return string[] + */ + public function getIndicesNameByAlias($indexAlias); + + /** + * Update alias definition. + * + * @param array $aliasActions Alias actions. + * + * @return void + */ + public function updateAliases($aliasActions); + + /** + * Run a bulk request. + * + * @param array $bulkParams Bulk data. + * + * return array + */ + public function bulk($bulkParams); + + /** + * Run a search request. + * + * @param array $params Search request params. + * + * @return array + */ + public function search($params); + + /** + * Returns index stats. + * + * @param string $indexName Index name. + * + * @return array + */ + public function indexStats($indexName); + + /** + * Run a termvectors request. + * + * @param array $params Term vectors request params. + * + * @return array + */ + public function termvectors($params); +} diff --git a/src/module-elasticsuite-core/Client/Client.php b/src/module-elasticsuite-core/Client/Client.php new file mode 100644 index 000000000..7fc725f9a --- /dev/null +++ b/src/module-elasticsuite-core/Client/Client.php @@ -0,0 +1,171 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Client; + +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; + +/** + * ElasticSearch client implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + * + * @SuppressWarnings(TooManyPublicMethods) + */ +class Client implements ClientInterface +{ + /** + * @var \Elasticsearch\Client + */ + private $esClient; + + /** + * Constructor. + * + * @param ClientFactory $esClientFactory ElasticSearch client factory. + */ + public function __construct(ClientFactory $esClientFactory) + { + $this->esClient = $esClientFactory->createClient(); + } + + /** + * {@inheritDoc} + */ + public function info() + { + return $this->esClient->info(); + } + + /** + * {@inheritDoc} + */ + public function ping() + { + return $this->esClient->ping(); + } + + /** + * {@inheritDoc} + */ + public function createIndex($indexName, $indexSettings) + { + $this->esClient->indices()->create(['index' => $indexName, 'body' => $indexSettings]); + } + + /** + * {@inheritDoc} + */ + public function deleteIndex($indexName) + { + $this->esClient->indices()->delete(['index' => $indexName]); + } + + /** + * {@inheritDoc} + */ + public function indexExists($indexName) + { + return $this->esClient->indices()->exists(['index' => $indexName]); + } + + /** + * {@inheritDoc} + */ + public function putIndexSettings($indexName, $indexSettings) + { + $this->esClient->indices()->putSettings(['index' => $indexName, 'body' => $indexSettings]); + } + + /** + * {@inheritDoc} + */ + public function putMapping($indexName, $type, $mapping) + { + $this->esClient->indices()->putMapping(['index' => $indexName, 'type' => $type, 'body' => [$type => $mapping]]); + } + + /** + * {@inheritDoc} + */ + public function forceMerge($indexName) + { + $this->esClient->indices()->forceMerge(['index' => $indexName]); + } + + /** + * {@inheritDoc} + */ + public function refreshIndex($indexName) + { + $this->esClient->indices()->refresh(['index' => $indexName]); + } + + /** + * {@inheritDoc} + */ + public function getIndicesNameByAlias($indexAlias) + { + $indices = []; + try { + $indices = $this->esClient->indices()->getMapping(['index' => $indexAlias]); + } catch (\Elasticsearch\Common\Exceptions\Missing404Exception $e) { + ; + } + + return array_keys($indices); + } + + /** + * {@inheritDoc} + */ + public function updateAliases($aliasActions) + { + $this->esClient->indices()->updateAliases(['body' => ['actions' => $aliasActions]]); + } + + /** + * {@inheritDoc} + */ + public function bulk($bulkParams) + { + return $this->esClient->bulk($bulkParams); + } + + /** + * {@inheritDoc} + */ + public function search($params) + { + return $this->esClient->search($params); + } + + /** + * {@inheritDoc} + */ + public function indexStats($indexName) + { + return $this->esClient->indices()->stats(['index' => $indexName]); + } + + /** + * {@inheritDoc} + */ + public function termvectors($params) + { + return $this->esClient->termvectors($params); + } +} diff --git a/src/module-elasticsuite-core/Client/ClientFactory.php b/src/module-elasticsuite-core/Client/ClientFactory.php index ee3731310..bc0400733 100644 --- a/src/module-elasticsuite-core/Client/ClientFactory.php +++ b/src/module-elasticsuite-core/Client/ClientFactory.php @@ -22,6 +22,8 @@ * @category Smile * @package Smile\ElasticsuiteCore * @author Aurelien FOUCRET + * + * @deprecated Use Smile\ElasticsuiteCore\Api\Client\ClientInterface instead. */ class ClientFactory implements ClientFactoryInterface { diff --git a/src/module-elasticsuite-core/Cluster/ClusterInfo.php b/src/module-elasticsuite-core/Cluster/ClusterInfo.php index 9f5ad23dd..80aa7a120 100644 --- a/src/module-elasticsuite-core/Cluster/ClusterInfo.php +++ b/src/module-elasticsuite-core/Cluster/ClusterInfo.php @@ -26,7 +26,7 @@ class ClusterInfo implements ClusterInfoInterface { /** - * @var \Elasticsearch\Client + * @var \Smile\ElasticsuiteCore\Api\Client\ClientInterface */ private $client; @@ -38,11 +38,11 @@ class ClusterInfo implements ClusterInfoInterface /** * Constructor. * - * @param \Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface $clientFactory ElasticSearch client factory. + * @param \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client ElasticSearch client. */ - public function __construct(\Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface $clientFactory) + public function __construct(\Smile\ElasticsuiteCore\Api\Client\ClientInterface $client) { - $this->client = $clientFactory->createClient(); + $this->client = $client; } /** diff --git a/src/module-elasticsuite-core/Index/IndexOperation.php b/src/module-elasticsuite-core/Index/IndexOperation.php index e82cc5b2c..3e585afb6 100644 --- a/src/module-elasticsuite-core/Index/IndexOperation.php +++ b/src/module-elasticsuite-core/Index/IndexOperation.php @@ -15,18 +15,10 @@ namespace Smile\ElasticsuiteCore\Index; use Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface; -use Magento\Framework\ObjectManagerInterface; -use Smile\ElasticsuiteCore\Api\Index\IndexInterface; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; -use Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface; -use Smile\ElasticsuiteCore\Api\Index\Bulk\BulkRequestInterface; -use Psr\Log\LoggerInterface; -use Smile\ElasticsuiteCore\Api\Index\Bulk\BulkResponseInterface; /** * Default implementation of operation on indices (\Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface). * - * * @category Smile_Elasticsuite * @package Smile\ElasticsuiteCore * @author Aurelien FOUCRET @@ -54,31 +46,31 @@ class IndexOperation implements IndexOperationInterface private $indicesConfiguration; /** - * @var \Elasticsearch\Client + * @var \Smile\ElasticsuiteCore\Api\Client\ClientInterface */ private $client; /** - * @var \Psr\Log\LoggerInterface; + * @var \Psr\Log\LoggerInterface */ private $logger; /** * Instanciate the index operation manager. * - * @param ObjectManagerInterface $objectManager Object manager. - * @param ClientFactoryInterface $clientFactory ES client factory. - * @param IndexSettingsInterface $indexSettings ES settings. - * @param LoggerInterface $logger Logger access. + * @param \Magento\Framework\ObjectManagerInterface $objectManager Object manager. + * @param \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client ES client. + * @param \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface $indexSettings ES settings. + * @param \Psr\Log\LoggerInterface $logger Logger access. */ public function __construct( - ObjectManagerInterface $objectManager, - ClientFactoryInterface $clientFactory, - IndexSettingsInterface $indexSettings, - LoggerInterface $logger + \Magento\Framework\ObjectManagerInterface $objectManager, + \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client, + \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface $indexSettings, + \Psr\Log\LoggerInterface $logger ) { $this->objectManager = $objectManager; - $this->client = $clientFactory->createClient(); + $this->client = $client; $this->indexSettings = $indexSettings; $this->indicesConfiguration = $indexSettings->getIndicesConfig(); $this->logger = $logger; @@ -107,7 +99,7 @@ public function indexExists($indexIdentifier, $store) if (!isset($this->indicesByIdentifier[$indexIdentifier])) { $indexName = $this->indexSettings->getIndexAliasFromIdentifier($indexIdentifier, $store); - $exists = $this->client->indices()->exists(['index' => $indexName]); + $exists = $this->client->indexExists($indexName); } return $exists; @@ -141,14 +133,10 @@ public function createIndex($indexIdentifier, $store) $indexSettings = ['settings' => $this->indexSettings->getCreateIndexSettings()]; $indexSettings['settings']['analysis'] = $this->indexSettings->getAnalysisSettings($store); - $this->client->indices()->create(['index' => $index->getName(), 'body' => $indexSettings]); + $this->client->createIndex($index->getName(), $indexSettings); foreach ($index->getTypes() as $currentType) { - $this->client->indices()->putMapping([ - 'index' => $index->getName(), - 'type' => $currentType->getName(), - 'body' => [$currentType->getName() => $currentType->getMapping()->asArray()], - ]); + $this->client->putMapping($index->getName(), $currentType->getName(), $currentType->getMapping()->asArray()); } @@ -158,17 +146,15 @@ public function createIndex($indexIdentifier, $store) /** * {@inheritDoc} */ - public function installIndex(IndexInterface $index, $store) + public function installIndex(\Smile\ElasticsuiteCore\Api\Index\IndexInterface $index, $store) { if ($index->needInstall()) { $indexIdentifier = $index->getIdentifier(); $indexName = $index->getName(); $indexAlias = $this->indexSettings->getIndexAliasFromIdentifier($indexIdentifier, $store); - $this->client->indices()->forceMerge(['index' => $indexName]); - $this->client->indices()->putSettings( - ['index' => $indexName, 'body' => $this->indexSettings->getInstallIndexSettings()] - ); + $this->client->forceMerge($indexName); + $this->client->putIndexSettings($indexName, $this->indexSettings->getInstallIndexSettings()); $this->proceedIndexInstall($indexName, $indexAlias); } @@ -187,7 +173,7 @@ public function createBulk() /** * {@inheritDoc} */ - public function executeBulk(BulkRequestInterface $bulk) + public function executeBulk(\Smile\ElasticsuiteCore\Api\Index\Bulk\BulkRequestInterface $bulk) { if ($bulk->isEmpty()) { throw new \LogicException('Can not execute empty bulk.'); @@ -198,7 +184,7 @@ public function executeBulk(BulkRequestInterface $bulk) $rawBulkResponse = $this->client->bulk($bulkParams); /** - * @var BulkResponseInterface + * @var \Smile\ElasticsuiteCore\Api\Index\Bulk\BulkResponseInterface */ $bulkResponse = $this->objectManager->create( 'Smile\ElasticsuiteCore\Api\Index\Bulk\BulkResponseInterface', @@ -236,10 +222,10 @@ public function executeBulk(BulkRequestInterface $bulk) /** * {@inheritDoc} */ - public function refreshIndex(IndexInterface $index) + public function refreshIndex(\Smile\ElasticsuiteCore\Api\Index\IndexInterface $index) { try { - $this->client->indices()->refresh(['index' => $index->getName()]); + $this->client->refreshIndex($index->getName()); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } @@ -263,27 +249,24 @@ public function proceedIndexInstall($indexName, $indexAlias) $aliasActions = [['add' => ['index' => $indexName, 'alias' => $indexAlias]]]; $deletedIndices = []; - try { - $oldIndices = $this->client->indices()->getMapping(['index' => $indexAlias]); - } catch (\Elasticsearch\Common\Exceptions\Missing404Exception $e) { - $oldIndices = []; - } + $oldIndices = $this->client->getIndicesNameByAlias($indexAlias); - foreach (array_keys($oldIndices) as $oldIndexName) { + foreach ($oldIndices as $oldIndexName) { if ($oldIndexName != $indexName) { $deletedIndices[] = $oldIndexName; $aliasActions[] = ['remove' => ['index' => $oldIndexName, 'alias' => $indexAlias]]; } } - $this->client->indices()->updateAliases(['body' => ['actions' => $aliasActions]]); + $this->client->updateAliases($aliasActions); foreach ($deletedIndices as $deletedIndex) { - $this->client->indices()->delete(['index' => $deletedIndex]); + $this->client->deleteIndex($deletedIndex); } } /** + * Init the index object. * * @param string $indexIdentifier An index indentifier. * @param integer|string|\Magento\Store\Api\Data\StoreInterface $store The store. diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Adapter.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Adapter.php index eaf6bbcec..d7780f910 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Adapter.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Adapter.php @@ -18,7 +18,7 @@ use Magento\Framework\Search\RequestInterface; use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponseFactory; use Psr\Log\LoggerInterface; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; /** * ElasticSuite Search Adapter. @@ -40,7 +40,7 @@ class Adapter implements AdapterInterface private $logger; /** - * @var \Elasticsearch\Client + * @var ClientInterface */ private $client; @@ -52,20 +52,20 @@ class Adapter implements AdapterInterface /** * Constructor. * - * @param QueryResponseFactory $responseFactory Search response factory. - * @param Request\Mapper $requestMapper Search request mapper. - * @param ClientFactoryInterface $clientFactory ES Client Factory. - * @param LoggerInterface $logger Logger. + * @param QueryResponseFactory $responseFactory Search response factory. + * @param Request\Mapper $requestMapper Search request mapper. + * @param ClientInterface $client ES Client Factory. + * @param LoggerInterface $logger Logger. */ public function __construct( QueryResponseFactory $responseFactory, Request\Mapper $requestMapper, - ClientFactoryInterface $clientFactory, + ClientInterface $client, LoggerInterface $logger ) { $this->responseFactory = $responseFactory; $this->logger = $logger; - $this->client = $clientFactory->createClient(); + $this->client = $client; $this->requestMapper = $requestMapper; } diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php index dc4c7a262..95c3b09c8 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php @@ -16,7 +16,7 @@ use Smile\ElasticsuiteCore\Api\Search\SpellcheckerInterface; use Smile\ElasticsuiteCore\Api\Search\Spellchecker\RequestInterface; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; use Smile\ElasticsuiteCore\Api\Index\MappingInterface; use Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterface; use Smile\ElasticsuiteCore\Helper\Cache as CacheHelper; @@ -32,7 +32,7 @@ class Spellchecker implements SpellcheckerInterface { /** - * @var \Elasticsearch\Client + * @var ClientInterface */ private $client; @@ -44,12 +44,12 @@ class Spellchecker implements SpellcheckerInterface /** * Constructor. * - * @param ClientFactoryInterface $clientFactory ES Client Factory. - * @param CacheHelper $cacheHelper ES cache helper. + * @param ClientInterface $client ES Client Factory. + * @param CacheHelper $cacheHelper ES cache helper. */ - public function __construct(ClientFactoryInterface $clientFactory, CacheHelper $cacheHelper) + public function __construct(ClientInterface $client, CacheHelper $cacheHelper) { - $this->client = $clientFactory->createClient(); + $this->client = $client; $this->cacheHelper = $cacheHelper; } @@ -124,7 +124,7 @@ private function getCacheKey(RequestInterface $request) */ private function getCutoffrequencyLimit(RequestInterface $request) { - $indexStatsResponse = $this->client->indices()->stats(['index' => $request->getIndex()]); + $indexStatsResponse = $this->client->indexStats($request->getIndex()); $indexStats = current($indexStatsResponse['indices']); $totalIndexedDocs = $indexStats['total']['docs']['count']; diff --git a/src/module-elasticsuite-core/Test/Unit/Cluster/ClusterInfoTest.php b/src/module-elasticsuite-core/Test/Unit/Cluster/ClusterInfoTest.php index 0e0701a2e..0e978d43a 100644 --- a/src/module-elasticsuite-core/Test/Unit/Cluster/ClusterInfoTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Cluster/ClusterInfoTest.php @@ -15,7 +15,6 @@ namespace Smile\ElasticsuiteCore\Test\Unit\Cluster; use Smile\ElasticsuiteCore\Cluster\ClusterInfo; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; /** * Cluster information tests. @@ -33,13 +32,14 @@ class ClusterInfoTest extends \PHPUnit\Framework\TestCase */ public function testGetServerVersion() { - $client = $this->getMockBuilder(\Elasticsearch\Client::class)->disableOriginalConstructor()->getMock(); - $client->method('info')->will($this->returnValue(['version' => ['number' => '1.0.0']])); + $clientMock = $this->getMockBuilder(\Smile\ElasticsuiteCore\Api\Client\ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); - $clientFactoryMock = $this->getMockBuilder(ClientFactoryInterface::class)->getMock(); - $clientFactoryMock->method('createClient')->will($this->returnValue($client)); + $clientMock->method('info') + ->will($this->returnValue(['version' => ['number' => '1.0.0']])); - $clusterInfo = new ClusterInfo($clientFactoryMock); + $clusterInfo = new ClusterInfo($clientMock); $this->assertEquals('1.0.0', $clusterInfo->getServerVersion()); } diff --git a/src/module-elasticsuite-core/Test/Unit/Index/IndexOperationTest.php b/src/module-elasticsuite-core/Test/Unit/Index/IndexOperationTest.php index 774080b42..535f85040 100644 --- a/src/module-elasticsuite-core/Test/Unit/Index/IndexOperationTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Index/IndexOperationTest.php @@ -49,12 +49,13 @@ class IndexOperationTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { + $this->initClientMock(); + $objectManager = $this->getObjectManagerMock(); - $clientFactory = $this->getClientFactoryMock(); $indexSettings = $this->getIndexSettingsMock(); $logger = $this->getLoggerMock(); - $this->indexOperation = new IndexOperation($objectManager, $clientFactory, $indexSettings, $logger); + $this->indexOperation = new IndexOperation($objectManager, $this->clientMock, $indexSettings, $logger); } /** @@ -236,26 +237,16 @@ private function getObjectManagerMock() * * @return PHPUnit_Framework_MockObject_MockObject */ - private function getClientFactoryMock() + private function initClientMock() { - $this->clientMock = $this->getMockBuilder(\Elasticsearch\Client::class) - ->disableOriginalConstructor() - ->getMock(); - - $indicesNamespaceMock = $this->getMockBuilder(\Elasticsearch\Namespaces\IndicesNamespace::class) + $this->clientMock = $this->getMockBuilder(\Smile\ElasticsuiteCore\Api\Client\ClientInterface::class) ->disableOriginalConstructor() ->getMock(); - $indicesExistsMethodStub = function ($index) { - return $index['index'] === 'index_identifier_store_code'; + $indicesExistsMethodStub = function ($indexName) { + return $indexName === 'index_identifier_store_code'; }; - $indicesNamespaceMock->method('exists')->will($this->returnCallback($indicesExistsMethodStub)); - $this->clientMock->method('indices')->will($this->returnValue($indicesNamespaceMock)); - - $clientFactoryMock = $this->getMockBuilder(\Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface::class)->getMock(); - $clientFactoryMock->method('createClient')->will($this->returnValue($this->clientMock)); - - return $clientFactoryMock; + $this->clientMock->method('indexExists')->will($this->returnCallback($indicesExistsMethodStub)); } /** diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/AdapterTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/AdapterTest.php index e14a67cbd..c7ebb2e33 100644 --- a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/AdapterTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/AdapterTest.php @@ -15,7 +15,7 @@ namespace Smile\ElasticsuiteCore\Test\Unit\Search\Adapter\Elasticsuite; use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Adapter; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Mapper; use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse; use Psr\Log\LoggerInterface; @@ -32,7 +32,7 @@ class AdapterTest extends \PHPUnit\Framework\TestCase { /** - * @var \Elasticsearch\Client + * @var \Smile\ElasticsuiteCore\Api\Client\ClientInterface */ private $client; @@ -85,10 +85,10 @@ protected function setUp() { $queryResponseFactory = $this->getQueryResponseFactoryMock(); $requestMapper = $this->getRequestMapperMock(); - $clientFactory = $this->getClientFactoryMock(); + $client = $this->getClientMock(); $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); - $this->adapter = new Adapter($queryResponseFactory, $requestMapper, $clientFactory, $logger); + $this->adapter = new Adapter($queryResponseFactory, $requestMapper, $client, $logger); } /** @@ -127,13 +127,10 @@ private function getQueryResponseFactoryMock() * * @return PHPUnit_Framework_MockObject_MockObject */ - private function getClientFactoryMock() + private function getClientMock() { - $this->client = $this->getMockBuilder(\Elasticsearch\Client::class)->disableOriginalConstructor()->getMock(); + $this->client = $this->getMockBuilder(ClientInterface::class)->disableOriginalConstructor()->getMock(); - $clientFactoryMock = $this->getMockBuilder(ClientFactoryInterface::class)->getMock(); - $clientFactoryMock->method('createClient')->will($this->returnValue($this->client)); - - return $clientFactoryMock; + return $this->client; } } diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index b90f08229..8b4a1bce4 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -21,6 +21,9 @@ + + diff --git a/src/module-elasticsuite-thesaurus/Model/Indexer/IndexHandler.php b/src/module-elasticsuite-thesaurus/Model/Indexer/IndexHandler.php index d33323f03..038a6706e 100644 --- a/src/module-elasticsuite-thesaurus/Model/Indexer/IndexHandler.php +++ b/src/module-elasticsuite-thesaurus/Model/Indexer/IndexHandler.php @@ -14,7 +14,7 @@ namespace Smile\ElasticsuiteThesaurus\Model\Indexer; -use Smile\ElasticsuiteCore\Api\Client\ClientFactoryInterface; +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; use Smile\ElasticsuiteCore\Helper\IndexSettings as IndexSettingsHelper; use Smile\ElasticsuiteCore\Helper\Cache as CacheHelper; use Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface; @@ -30,7 +30,7 @@ class IndexHandler { /** - * @var \Elasticsearch\Client + * @var ClientInterface */ private $client; @@ -52,18 +52,18 @@ class IndexHandler /** * Constructor. * - * @param ClientFactoryInterface $clientFactory ES Client factory. + * @param ClientInterface $clientFactory ES Client factory. * @param IndexOperationInterface $indexManager ES index management tool * @param IndexSettingsHelper $indexSettingsHelper Index settings helper. * @param CacheHelper $cacheHelper ES caching helper. */ public function __construct( - ClientFactoryInterface $clientFactory, + ClientInterface $clientFactory, IndexOperationInterface $indexManager, IndexSettingsHelper $indexSettingsHelper, CacheHelper $cacheHelper ) { - $this->client = $clientFactory->createClient(); + $this->client = $clientFactory; $this->indexSettingsHelper = $indexSettingsHelper; $this->indexManager = $indexManager; $this->cacheHelper = $cacheHelper; @@ -84,8 +84,7 @@ public function reindex($storeId, $synonyms, $expansions) $indexName = $this->indexSettingsHelper->createIndexNameFromIdentifier($indexIdentifier, $storeId); $indexAlias = $this->indexSettingsHelper->getIndexAliasFromIdentifier($indexIdentifier, $storeId); $indexSettings = ['settings' => $this->getIndexSettings($synonyms, $expansions)]; - - $this->client->indices()->create(['index' => $indexName, 'body' => $indexSettings]); + $this->client->createIndex($indexName, $indexSettings); $this->indexManager->proceedIndexInstall($indexName, $indexAlias); $this->cacheHelper->cleanIndexCache(ThesaurusIndex::INDEX_IDENTIER, $storeId); } From 1c936244f9c9380c5b6015bc001576504c87df9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Fri, 17 Nov 2017 14:46:48 +0100 Subject: [PATCH 02/19] Tracking data ingestion. --- .../Index/Mapping.php | 40 ++--- .../Api/EventIndexInterface.php | 50 ++++++ .../Api/EventProcessorInterface.php | 34 ++++ .../Api/EventQueueInterface.php | 52 +++++++ .../Block/Variables/Page/Catalog.php | 2 +- .../Controller/Tracker/Hit.php | 61 ++++++++ .../Cron/IndexLogEvent.php | 63 ++++++++ .../Helper/Data.php | 34 +--- .../Model/Event/Processor/CustomerSession.php | 54 +++++++ .../Model/Event/Processor/OrderItems.php | 39 +++++ .../Event/Processor/ProductListFilters.php | 46 ++++++ .../Model/EventIndex.php | 75 +++++++++ .../Model/EventQueue.php | 79 ++++++++++ .../Model/ResourceModel/EventIndex.php | 147 ++++++++++++++++++ .../Model/ResourceModel/EventQueue.php | 111 +++++++++++++ .../Setup/UpgradeSchema.php | 79 ++++++++++ .../etc/adminhtml/system.xml | 3 - .../etc/crontab.xml | 14 ++ src/module-elasticsuite-tracker/etc/di.xml | 34 ++++ .../etc/elasticsuite_indices.xml | 100 ++++++++++++ .../etc/frontend/routes.xml | 23 +++ .../etc/module.xml | 22 ++- .../view/frontend/web/js/tracking.js | 7 +- 23 files changed, 1106 insertions(+), 63 deletions(-) create mode 100644 src/module-elasticsuite-tracker/Api/EventIndexInterface.php create mode 100644 src/module-elasticsuite-tracker/Api/EventProcessorInterface.php create mode 100644 src/module-elasticsuite-tracker/Api/EventQueueInterface.php create mode 100644 src/module-elasticsuite-tracker/Controller/Tracker/Hit.php create mode 100644 src/module-elasticsuite-tracker/Cron/IndexLogEvent.php create mode 100644 src/module-elasticsuite-tracker/Model/Event/Processor/CustomerSession.php create mode 100644 src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php create mode 100644 src/module-elasticsuite-tracker/Model/Event/Processor/ProductListFilters.php create mode 100644 src/module-elasticsuite-tracker/Model/EventIndex.php create mode 100644 src/module-elasticsuite-tracker/Model/EventQueue.php create mode 100644 src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php create mode 100644 src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php create mode 100644 src/module-elasticsuite-tracker/Setup/UpgradeSchema.php create mode 100644 src/module-elasticsuite-tracker/etc/crontab.xml create mode 100644 src/module-elasticsuite-tracker/etc/di.xml create mode 100644 src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml create mode 100644 src/module-elasticsuite-tracker/etc/frontend/routes.xml diff --git a/src/module-elasticsuite-core/Index/Mapping.php b/src/module-elasticsuite-core/Index/Mapping.php index c987334a1..89d64bacd 100644 --- a/src/module-elasticsuite-core/Index/Mapping.php +++ b/src/module-elasticsuite-core/Index/Mapping.php @@ -292,33 +292,21 @@ private function addField(array $properties, FieldInterface $field) // Read property config from the field. $property = $field->getMappingPropertyConfig(); - if ($field->isNested()) { - /* - * Nested field management : - * - * For nested field we need to - * - change the insertion root to the parent field. - * - create the parent field with type nested if not yet exists. - * - using the suffix name of the field instead of the name including nested path. - * - * Ex: "price.is_discount" field has to be inserted with name "is_discount" into the "price" field. - * - */ - $nestedPath = $field->getNestedPath(); - - if (!isset($properties[$nestedPath])) { - $properties[$nestedPath] = ['type' => FieldInterface::FIELD_TYPE_NESTED, 'properties' => []]; + $fieldPathArray = explode('.', $fieldName); + $currentPathArray = []; + $fieldPathSize = count($fieldPathArray); + + for ($i = 0; $i < $fieldPathSize - 1; $i++) { + $currentPathArray[] = $fieldPathArray[$i]; + $currentPath = implode('.', $currentPathArray); + + if ($field->isNested() && $field->getNestedPath() == $currentPath && !isset($fieldRoot[$fieldPathArray[$i]])) { + $fieldRoot[$fieldPathArray[$i]] = ['type' => FieldInterface::FIELD_TYPE_NESTED, 'properties' => []]; + } elseif (!isset($fieldRoot[$fieldPathArray[$i]])) { + $fieldRoot[$fieldPathArray[$i]] = ['type' => FieldInterface::FIELD_TYPE_OBJECT, 'properties' => []]; } - $fieldRoot = &$properties[$nestedPath]['properties']; - $fieldName = $field->getNestedFieldName(); - } elseif (strstr($fieldName, '.')) { - $fieldPathArray = explode('.', $fieldName); - if (!isset($properties[current($fieldPathArray)])) { - $properties[current($fieldPathArray)] = ['type' => FieldInterface::FIELD_TYPE_OBJECT, 'properties' => []]; - } - $fieldRoot = &$properties[current($fieldPathArray)]['properties']; - $fieldName = end($fieldPathArray); + $fieldRoot = &$fieldRoot[$fieldPathArray[$i]]['properties']; } /* @@ -333,7 +321,7 @@ private function addField(array $properties, FieldInterface $field) $copyToRoot['copy_to'] = $copyToProperties; } - $fieldRoot[$fieldName] = $property; + $fieldRoot[end($fieldPathArray)] = $property; return $properties; } diff --git a/src/module-elasticsuite-tracker/Api/EventIndexInterface.php b/src/module-elasticsuite-tracker/Api/EventIndexInterface.php new file mode 100644 index 000000000..4ff1aad05 --- /dev/null +++ b/src/module-elasticsuite-tracker/Api/EventIndexInterface.php @@ -0,0 +1,50 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Api; + +use Composer\EventDispatcher\Event; + +/** + * Tracker event log index. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +interface EventIndexInterface +{ + /** + * @var string + */ + const INDEX_IDENTIFIER = 'tracking_log_event'; + + /** + * Index a single event. + * + * @param array $event Event + * + * @return void + */ + public function indexEvent($event); + + /** + * Index a multiple events. + * + * @param array $events Events + * + * @return void + */ + public function indexEvents($events); +} diff --git a/src/module-elasticsuite-tracker/Api/EventProcessorInterface.php b/src/module-elasticsuite-tracker/Api/EventProcessorInterface.php new file mode 100644 index 000000000..4fdcda1fc --- /dev/null +++ b/src/module-elasticsuite-tracker/Api/EventProcessorInterface.php @@ -0,0 +1,34 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Api; + +/** + * Tracker event log processor. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +interface EventProcessorInterface +{ + /** + * Modify an event before it is inserted into the queue. + * + * @param array $eventData $eventData + * + * @return array + */ + public function process($eventData); +} diff --git a/src/module-elasticsuite-tracker/Api/EventQueueInterface.php b/src/module-elasticsuite-tracker/Api/EventQueueInterface.php new file mode 100644 index 000000000..ca1574dc9 --- /dev/null +++ b/src/module-elasticsuite-tracker/Api/EventQueueInterface.php @@ -0,0 +1,52 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Api; + +/** + * Event log queue : store event to be indexed into ES. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +interface EventQueueInterface +{ + /** + * Add an event to the queue. + * + * @param array $eventData Event data. + * + * @return void + */ + public function addEvent($eventData); + + /** + * Delete event by id. + * + * @param int[] $eventIds Event ids to delete. + * + * @return void + */ + public function deleteEvents($eventIds); + + /** + * Retrieve event from the queue. + * + * @param int $limit Max number of event to retrieve. + * + * @return array + */ + public function getEvents($limit = null); +} diff --git a/src/module-elasticsuite-tracker/Block/Variables/Page/Catalog.php b/src/module-elasticsuite-tracker/Block/Variables/Page/Catalog.php index 6399267fc..dd20b82bf 100644 --- a/src/module-elasticsuite-tracker/Block/Variables/Page/Catalog.php +++ b/src/module-elasticsuite-tracker/Block/Variables/Page/Catalog.php @@ -132,7 +132,7 @@ private function getLayerVariables() if (is_array($filterValue)) { $filterValue = implode('|', $filterValue); } - $variables['product_list.filters.' . $identifier] = $filterValue; + $variables['product_list.filters.' . $identifier] = html_entity_decode($filterValue); } } diff --git a/src/module-elasticsuite-tracker/Controller/Tracker/Hit.php b/src/module-elasticsuite-tracker/Controller/Tracker/Hit.php new file mode 100644 index 000000000..bb74d4ce4 --- /dev/null +++ b/src/module-elasticsuite-tracker/Controller/Tracker/Hit.php @@ -0,0 +1,61 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Controller\Tracker; + +/** + * Hit event collector. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class Hit extends \Magento\Framework\App\Action\Action +{ + /** + * @var string + */ + const PIXEL = '6wzwc+flkuJiYGDg9fRwCQLSjCDMwQQkJ5QH3wNSbCVBfsEMYJC3jH0ikOLxdHEMqZiTnJCQAOSxMDB+E7cIBcl7uvq5rHNKaAIA'; + + /** + * @var \Smile\ElasticsuiteTracker\Api\EventQueueInterface + */ + private $logEventQueue; + + /** + * Constructor. + * + * @param \Magento\Framework\App\Action\Context $context Context. + * @param \Smile\ElasticsuiteTracker\Api\EventQueueInterface $logEventQueue Event queue. + */ + public function __construct( + \Magento\Framework\App\Action\Context $context, + \Smile\ElasticsuiteTracker\Api\EventQueueInterface $logEventQueue + ) { + parent::__construct($context); + $this->logEventQueue = $logEventQueue; + } + + /** + * {@inheritDoc} + */ + public function execute() + { + $this->getResponse()->setBody(base64_decode(self::PIXEL)); + $this->getResponse()->setHeader('Content-Type', 'image/png'); + $this->getResponse()->sendResponse(); + + $this->logEventQueue->addEvent($this->getRequest()->getParams()); + } +} diff --git a/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php new file mode 100644 index 000000000..c23653ec7 --- /dev/null +++ b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php @@ -0,0 +1,63 @@ + +* @copyright 2016 Smile +* @license Open Software License ("OSL") v. 3.0 +*/ + +namespace Smile\ElasticsuiteTracker\Cron; + +/** + * Cron task used to index tracker log event. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class IndexLogEvent +{ + /** + * @var \Smile\ElasticsuiteTracker\Api\EventQueueInterface + */ + private $eventQueue; + + /** + * @var \Smile\ElasticsuiteTracker\Api\EventIndexInterface + */ + private $eventIndex; + + /** + * Constructor. + * + * @param \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue Pending events queue. + * @param \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex Event index. + */ + public function __construct( + \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue, + \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex + ) { + $this->eventQueue = $eventQueue; + $this->eventIndex = $eventIndex; + } + + /** + * Run the indexation. + * + * @return void + */ + public function execute() + { + $events = $this->eventQueue->getEvents(); + if (!empty($events)) { + $this->eventIndex->indexEvents($events); + $this->eventQueue->deleteEvents(array_column($events, 'event_id')); + } + } +} diff --git a/src/module-elasticsuite-tracker/Helper/Data.php b/src/module-elasticsuite-tracker/Helper/Data.php index 0387a9a31..ad132f02e 100644 --- a/src/module-elasticsuite-tracker/Helper/Data.php +++ b/src/module-elasticsuite-tracker/Helper/Data.php @@ -13,8 +13,6 @@ */ namespace Smile\ElasticsuiteTracker\Helper; -use Magento\Framework\App\Helper; - /** * Smile Tracker helper * @@ -30,12 +28,6 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper */ const CONFIG_IS_ENABLED_XPATH = 'smile_elasticsuite_tracker/general/enabled'; - /** - * Tracking URL configuration path - * @var string - */ - const CONFIG_BASE_URL_XPATH = 'smile_elasticsuite_tracker/general/base_url'; - /** * Coookie configuration configuration path * @var string @@ -57,26 +49,22 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper private $storeManager; /** - * Magento assets repository - * - * @var \Magento\Framework\View\Asset\Repository + * @var \Magento\Framework\UrlInterface */ - private $assetRepository; + private $urlBuilder; /** * PHP Constructor * - * @param \Magento\Framework\App\Helper\Context $context The current context - * @param \Magento\Store\Model\StoreManagerInterface $storeManager The Store Manager - * @param \Magento\Framework\View\Asset\Repository $assetRepository The asset repository + * @param \Magento\Framework\App\Helper\Context $context The current context + * @param \Magento\Store\Model\StoreManagerInterface $storeManager The Store Manager */ public function __construct( \Magento\Framework\App\Helper\Context $context, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\View\Asset\Repository $assetRepository + \Magento\Store\Model\StoreManagerInterface $storeManager ) { + $this->urlBuilder = $context->getUrlBuilder(); $this->storeManager = $storeManager; - $this->assetRepository = $assetRepository; parent::__construct($context); } @@ -97,15 +85,7 @@ public function isEnabled() */ public function getBaseUrl() { - $result = $this->scopeConfig->getValue(self::CONFIG_BASE_URL_XPATH); - - if (!$result) { - $params = ['_secure' => $this->_getRequest()->isSecure()]; - - return $this->assetRepository->getUrlWithParams("Smile_ElasticsuiteTracker::hit.png", $params); - } - - return $result; + return $this->urlBuilder->getUrl('elasticsuite/tracker/hit'); } /** diff --git a/src/module-elasticsuite-tracker/Model/Event/Processor/CustomerSession.php b/src/module-elasticsuite-tracker/Model/Event/Processor/CustomerSession.php new file mode 100644 index 000000000..d71b2d619 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Event/Processor/CustomerSession.php @@ -0,0 +1,54 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model\Event\Processor; + +use Smile\ElasticsuiteTracker\Api\EventProcessorInterface; + +/** + * Add the customer id to the logged events. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class CustomerSession implements EventProcessorInterface +{ + /** + * @var \Magento\Customer\Model\Session + */ + private $customerSession; + + /** + * Constructor. + * + * @param \Magento\Customer\Model\Session $customerSession Customer session. + */ + public function __construct(\Magento\Customer\Model\Session $customerSession) + { + $this->customerSession = $customerSession; + } + + /** + * {@inheritDoc} + */ + public function process($eventData) + { + if ($this->customerSession->getCustomerId() !== null) { + $eventData['session']['customer_id'] = $this->customerSession->getCustomerId(); + } + + return $eventData; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php b/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php new file mode 100644 index 000000000..c42e95ab0 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php @@ -0,0 +1,39 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model\Event\Processor; + +use Smile\ElasticsuiteTracker\Api\EventProcessorInterface; + +/** + * Process order items logged events. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class OrderItems implements EventProcessorInterface +{ + /** + * {@inheritDoc} + */ + public function process($eventData) + { + if (isset($eventData['page']['order']) && isset($eventData['page']['order']['items'])) { + $eventData['page']['order']['items'] = array_values($eventData['page']['order']['items']); + } + + return $eventData; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Event/Processor/ProductListFilters.php b/src/module-elasticsuite-tracker/Model/Event/Processor/ProductListFilters.php new file mode 100644 index 000000000..0af54eb75 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Event/Processor/ProductListFilters.php @@ -0,0 +1,46 @@ + +* @copyright 2016 Smile +* @license Open Software License ("OSL") v. 3.0 +*/ + +namespace Smile\ElasticsuiteTracker\Model\Event\Processor; + +use Smile\ElasticsuiteTracker\Api\EventProcessorInterface; + +/** + * Process product list filters in logged events. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class ProductListFilters implements EventProcessorInterface +{ + /** + * {@inheritDoc} + */ + public function process($eventData) + { + if (isset($eventData['page']['product_list']['filters'])) { + $filters = []; + foreach ($eventData['page']['product_list']['filters'] as $filterName => $filterValues) { + $filterValues = explode('|', $filterValues); + foreach ($filterValues as $filterValue) { + $filters[] = ['name' => $filterName, 'value' => $filterValue]; + } + } + $eventData['page']['product_list']['filters'] = $filters; + } + + return $eventData; + } +} diff --git a/src/module-elasticsuite-tracker/Model/EventIndex.php b/src/module-elasticsuite-tracker/Model/EventIndex.php new file mode 100644 index 000000000..24b7d8fe8 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/EventIndex.php @@ -0,0 +1,75 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model; + +use Smile\ElasticsuiteTracker\Api\EventIndexInterface; + +/** + * Event index implementation. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class EventIndex implements EventIndexInterface +{ + /** + * @var ResourceModel\EventIndex + */ + private $resourceModel; + + /** + * @var \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface + */ + private $indexOperation; + + /** + * + * @param ResourceModel\EventIndex $resourceModel Resource model. + * @param \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation Index operation. + */ + public function __construct( + ResourceModel\EventIndex $resourceModel, + \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation + ) { + $this->resourceModel = $resourceModel; + $this->indexOperation = $indexOperation; + } + + /** + * {@inheritDoc} + */ + public function indexEvent($event) + { + $this->indexEvents([$event]); + } + + /** + * {@inheritDoc} + */ + public function indexEvents($events) + { + $bulk = $this->indexOperation->createBulk(); + + foreach ($events as $event) { + $index = $this->resourceModel->getIndex($event); + $bulk->addDocument($index, $index->getDefaultSearchType(), $event['event_id'], $event); + } + + if ($bulk->isEmpty() === false) { + $this->indexOperation->executeBulk($bulk); + } + } +} diff --git a/src/module-elasticsuite-tracker/Model/EventQueue.php b/src/module-elasticsuite-tracker/Model/EventQueue.php new file mode 100644 index 000000000..762a95871 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/EventQueue.php @@ -0,0 +1,79 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model; + +use Smile\ElasticsuiteTracker\Api\EventQueueInterface; +use Smile\ElasticsuiteTracker\Api\EventProcessorInterface; + +/** + * Tracker log event queue. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class EventQueue implements EventQueueInterface +{ + /** + * @var ResourceModel\EventQueue + */ + private $resourceModel; + + /** + * + * @var EventProcessorInterface + */ + private $processors; + + /** + * Constructor. + * + * @param ResourceModel\EventQueue $resourceModel Resource model. + * @param EventProcessorInterface $eventProcessors Event processors. + */ + public function __construct(ResourceModel\EventQueue $resourceModel, $eventProcessors = []) + { + $this->resourceModel = $resourceModel; + $this->processors = $eventProcessors; + } + + /** + * {@inheritDoc} + */ + public function addEvent($eventData) + { + foreach ($this->processors as $processor) { + $eventData = $processor->process($eventData); + } + + $this->resourceModel->saveEvent($eventData); + } + + /** + * {@inheritDoc} + */ + public function getEvents($limit = null) + { + return $this->resourceModel->getEvents($limit); + } + + /** + * {@inheritDoc} + */ + public function deleteEvents($eventIds) + { + $this->resourceModel->deleteEvents($eventIds); + } +} diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php b/src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php new file mode 100644 index 000000000..0f5b1b366 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php @@ -0,0 +1,147 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model\ResourceModel; + +use Smile\ElasticsuiteTracker\Api\EventIndexInterface; + +/** + * Event index resource model. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class EventIndex +{ + /** + * @var \Smile\ElasticsuiteCore\Api\Index\IndexInterface[] + */ + private $indices; + + /** + * @var \Smile\ElasticsuiteCore\Api\Index\IndexFactoryInterface + */ + private $indexFactory; + + /** + * @var \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface + */ + private $indexSettings; + + /** + * @var \Smile\ElasticsuiteCore\Api\Client\ClientInterface + */ + private $client; + + /** + * Constructor. + * + * @param \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface $indexSettings Index settings. + * @param \Smile\ElasticsuiteCore\Api\Index\IndexInterfaceFactory $indexFactory Index factory. + * @param \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client ES client. + */ + public function __construct( + \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface $indexSettings, + \Smile\ElasticsuiteCore\Api\Index\IndexInterfaceFactory $indexFactory, + \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client + ) { + $this->client = $client; + $this->indexFactory = $indexFactory; + $this->indexSettings = $indexSettings; + } + + /** + * Get index using event data. + * + * @param array $event Event. + * + * @return \Smile\ElasticsuiteCore\Api\Index\IndexInterface + */ + public function getIndex($event) + { + $indexAlias = $this->getIndexAlias($event); + $indexName = $this->getIndexName($event); + $indexIdentifier = $this->getIndexIdenifier(); + + if (!isset($this->indices[$indexName])) { + $indexSettings = $this->indexSettings->getIndicesConfig(); + $indexConfig = array_merge(['identifier' => $indexAlias, 'name' => $indexName], $indexSettings[$indexIdentifier]); + $this->indices[$indexName] = $this->indexFactory->create($indexConfig); + $this->createIndexIfNotExists($this->indices[$indexName], $event['page']['store_id']); + } + + return $this->indices[$indexName]; + } + + /** + * Create the event index if not exists. + * + * @param \Smile\ElasticsuiteCore\Api\Index\IndexInterface $index Index. + * @param int $store Store id. + * + * @return void + */ + private function createIndexIfNotExists(\Smile\ElasticsuiteCore\Api\Index\IndexInterface $index, $store) + { + if ($this->client->indexExists($index->getName()) === false) { + $indexSettings = $this->indexSettings->getInstallIndexSettings(); + $indexSettings['analysis'] = $this->indexSettings->getAnalysisSettings($store); + $this->client->createIndex($index->getName(), $indexSettings); + $this->client->updateAliases([['add' => ['index' => $index->getName(), 'alias' => $index->getIdentifier()]]]); + foreach ($index->getTypes() as $currentType) { + $this->client->putMapping($index->getName(), $currentType->getName(), $currentType->getMapping()->asArray()); + } + } + } + + /** + * Search index identifier. + * + * @return string + */ + private function getIndexIdenifier() + { + return EventIndexInterface::INDEX_IDENTIFIER; + } + + /** + * Build index alias from an event. + * + * @param array $event Event. + * + * @return string + */ + private function getIndexAlias($event) + { + $indexIdentifier = $this->getIndexIdenifier(); + + return $this->indexSettings->getIndexAliasFromIdentifier($indexIdentifier, $event['page']['store_id']); + } + + /** + * Build index name from an event. + * + * @param arrat $event Event. + * + * @return string + */ + private function getIndexName($event) + { + $indexAlias = $this->getIndexAlias($event); + $date = substr($event['date'], 0, 10); + + return sprintf("%s_%s", $indexAlias, str_replace("-", "", $date)); + } +} diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php b/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php new file mode 100644 index 000000000..eb1f08720 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php @@ -0,0 +1,111 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model\ResourceModel; + +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; + +/** + * Tracker log event queue resource model. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class EventQueue extends AbstractDb +{ + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $jsonSerializer; + + /** + * Constructor. + * + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context Context. + * @param \Magento\Framework\Serialize\Serializer\Json $jsonSerializer JSON serializer. + * @param string $connectionName DB connection name. + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Framework\Serialize\Serializer\Json $jsonSerializer, + $connectionName = null + ) { + parent::__construct($context, $connectionName); + + $this->jsonSerializer = $jsonSerializer; + } + + /** + * Save an event to the queue. + * + * @param array $eventData Event data. + * + * @return void + */ + public function saveEvent($eventData) + { + $serializedEvent = $this->jsonSerializer->serialize($eventData); + $insertData = ['event_id' => md5($serializedEvent . time()), 'data' => $serializedEvent]; + $this->getConnection()->insertOnDuplicate($this->getMainTable(), $insertData, ['data']); + } + + /** + * Get event from the queue. + * + * @param integer $limit Max number of events to be retrieved. + * + * @return void + */ + public function getEvents($limit = null) + { + $select = $this->getConnection()->select()->from($this->getMainTable()); + + if ($limit !== null) { + $select->limit($limit); + } + + $eventData = $this->getConnection()->fetchAll($select); + + foreach ($eventData as &$currentEvent) { + $currentEvent = array_merge($currentEvent, $this->jsonSerializer->unserialize($currentEvent['data'])); + unset($currentEvent['data']); + } + + return $eventData; + } + + /** + * Clean event from the reindex queue. + * + * @param array $eventIds Event ids to be deleted. + * + * @return void + */ + public function deleteEvents(array $eventIds) + { + $connection = $this->getConnection(); + $connection->delete($this->getMainTable(), $connection->quoteInto('event_id IN(?)', $eventIds)); + } + + /** + * {@inheritDoc} + * + * @SuppressWarnings(PHPMD.CamelCaseMethodName) + */ + protected function _construct() + { + $this->_init('elasticsuite_tracker_log_event', 'event_id'); + } +} diff --git a/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php b/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php new file mode 100644 index 000000000..b62c29a61 --- /dev/null +++ b/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php @@ -0,0 +1,79 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Setup; + +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; + +/** + * Tracker Module Installer + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * Create table for the tracking module. + * + * @param SchemaSetupInterface $setup The setup interface + * @param ModuleContextInterface $context The module Context + * + * @return void + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), "1.1.0") < 0) { + $setup->startSetup(); + + $logTable = $setup->getConnection()->newTable($setup->getTable('elasticsuite_tracker_log_event')) + ->addColumn( + 'event_id', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + '32', + ['nullable' => false, 'primary' => true], + 'Event Id' + ) + ->addColumn( + 'date', + \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], + 'Event date' + ) + ->addColumn( + 'data', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + '2M', + ['nullable' => false], + 'Event data' + ) + ->addColumn( + 'data', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + '2M', + ['nullable' => false], + 'Event data' + ); + + $setup->getConnection()->createTable($logTable); + } + + $setup->endSetup(); + } +} diff --git a/src/module-elasticsuite-tracker/etc/adminhtml/system.xml b/src/module-elasticsuite-tracker/etc/adminhtml/system.xml index 8f29ea859..4f7517a00 100644 --- a/src/module-elasticsuite-tracker/etc/adminhtml/system.xml +++ b/src/module-elasticsuite-tracker/etc/adminhtml/system.xml @@ -26,9 +26,6 @@ Magento\Config\Model\Config\Source\Yesno - - - diff --git a/src/module-elasticsuite-tracker/etc/crontab.xml b/src/module-elasticsuite-tracker/etc/crontab.xml new file mode 100644 index 000000000..390638eb5 --- /dev/null +++ b/src/module-elasticsuite-tracker/etc/crontab.xml @@ -0,0 +1,14 @@ + + + + + + * * * * * + + + diff --git a/src/module-elasticsuite-tracker/etc/di.xml b/src/module-elasticsuite-tracker/etc/di.xml new file mode 100644 index 000000000..c2512295d --- /dev/null +++ b/src/module-elasticsuite-tracker/etc/di.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + Smile\ElasticsuiteTracker\Model\Event\Processor\CustomerSession + Smile\ElasticsuiteTracker\Model\Event\Processor\OrderItems + Smile\ElasticsuiteTracker\Model\Event\Processor\ProductListFilters + + + + + diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml new file mode 100644 index 000000000..228abe938 --- /dev/null +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/module-elasticsuite-tracker/etc/frontend/routes.xml b/src/module-elasticsuite-tracker/etc/frontend/routes.xml new file mode 100644 index 000000000..028351712 --- /dev/null +++ b/src/module-elasticsuite-tracker/etc/frontend/routes.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/src/module-elasticsuite-tracker/etc/module.xml b/src/module-elasticsuite-tracker/etc/module.xml index 67531bedb..4c18953f1 100644 --- a/src/module-elasticsuite-tracker/etc/module.xml +++ b/src/module-elasticsuite-tracker/etc/module.xml @@ -1,5 +1,25 @@ + - + + + + diff --git a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js index 94ecc68a0..d08184844 100644 --- a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js +++ b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js @@ -57,10 +57,7 @@ var smileTracker = (function () { this.addPageVar("url", window.location.pathname); // Page title tracking - this.addPageVar("title", encodeURI(document.title)); - - // Current timestamp in seconds - this.addPageVar("time", parseInt((new Date().getTime() / 1000), 10)); + this.addPageVar("title", document.title); } // Append GA campaign variable to the tracked variables @@ -176,7 +173,7 @@ var smileTracker = (function () { } function transformVarName(varName, prefix) { - return prefix + "." + varName; + return prefix + "[" + varName.replace(/[.]/g, "][") + "]"; } function initSession() { From ce95ad3bd6db45db5b77e7d001263ed08739f07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 22 Nov 2017 14:38:18 +0100 Subject: [PATCH 03/19] Create search request container for the tracker log events. --- .../etc/elasticsuite_search_request.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml new file mode 100644 index 000000000..a037e3f97 --- /dev/null +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml @@ -0,0 +1,22 @@ + + + + + + + From 802969a23d5d0893e86886e5f43150c05b0e5392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 22 Nov 2017 14:38:52 +0100 Subject: [PATCH 04/19] Update tracker module version. --- src/module-elasticsuite-tracker/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-tracker/composer.json b/src/module-elasticsuite-tracker/composer.json index 20002b75b..a3c5edbce 100644 --- a/src/module-elasticsuite-tracker/composer.json +++ b/src/module-elasticsuite-tracker/composer.json @@ -21,7 +21,7 @@ "magento/framework": ">=101.0.0", "magento/magento-composer-installer": "*" }, - "version": "2.4.0", + "version": "2.5.0", "autoload": { "files": [ "registration.php" From 3cf37c86ff31e80ab0989af3efee7874ade065bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 22 Nov 2017 15:17:10 +0100 Subject: [PATCH 05/19] Append exists query support. --- .../Request/Query/Builder/Exists.php | 46 +++++++++ .../Search/Request/Query/Exists.php | 93 +++++++++++++++++++ .../Search/Request/QueryInterface.php | 1 + .../Request/Query/Builder/ExistsTest.php | 69 ++++++++++++++ src/module-elasticsuite-core/etc/di.xml | 3 + 5 files changed, 212 insertions(+) create mode 100644 src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Exists.php create mode 100644 src/module-elasticsuite-core/Search/Request/Query/Exists.php create mode 100644 src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/ExistsTest.php diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Exists.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Exists.php new file mode 100644 index 000000000..3477e0a08 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Exists.php @@ -0,0 +1,46 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\BuilderInterface; + +/** + * Build an ES exists field query. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class Exists implements BuilderInterface +{ + /** + * {@inheritDoc} + */ + public function buildQuery(QueryInterface $query) + { + if ($query->getType() !== QueryInterface::TYPE_EXISTS) { + throw new \InvalidArgumentException("Query builder : invalid query type {$query->getType()}"); + } + + $searchQuery = ['exists' => ['field' => $query->getField()]]; + + if ($query->getName()) { + $searchQuery['exists']['_name'] = $query->getName(); + } + + return $searchQuery; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/Query/Exists.php b/src/module-elasticsuite-core/Search/Request/Query/Exists.php new file mode 100644 index 000000000..2802abaad --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/Query/Exists.php @@ -0,0 +1,93 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Request\Query; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Exists field definition implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class Exists implements QueryInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $field; + + /** + * @var integer + */ + private $boost; + + /** + * Constructor. + * + * @param string $field Query field. + * @param string $name Query name. + * @param integer $boost Query boost. + */ + public function __construct( + $field, + $name = null, + $boost = QueryInterface::DEFAULT_BOOST_VALUE + ) { + $this->name = $name; + $this->boost = $boost; + $this->field = $field; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritDoc} + */ + public function getBoost() + { + return $this->boost; + } + + /** + * {@inheritDoc} + */ + public function getType() + { + return QueryInterface::TYPE_EXISTS; + } + + /** + * Negated query. + * + * @return string + */ + public function getField() + { + return $this->field; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/QueryInterface.php b/src/module-elasticsuite-core/Search/Request/QueryInterface.php index 217a0f401..3bd8feeb2 100644 --- a/src/module-elasticsuite-core/Search/Request/QueryInterface.php +++ b/src/module-elasticsuite-core/Search/Request/QueryInterface.php @@ -32,6 +32,7 @@ interface QueryInterface extends \Magento\Framework\Search\Request\QueryInterfac const TYPE_NOT = 'notQuery'; const TYPE_MULTIMATCH = 'multiMatchQuery'; const TYPE_COMMON = 'commonQuery'; + const TYPE_EXISTS = 'existsQuery'; const TYPE_MISSING = 'missingQuery'; const TYPE_FUNCTIONSCORE = 'functionScore'; } diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/ExistsTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/ExistsTest.php new file mode 100644 index 000000000..c08fc0f26 --- /dev/null +++ b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/ExistsTest.php @@ -0,0 +1,69 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ +namespace Smile\ElasticsuiteCore\Test\Unit\Search\Adapter\Elasticsuite\Request\Query\Builder; + +use Smile\ElasticsuiteCore\Search\Request\Query\Exists as ExistsQuery; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Exists as ExistsQueryBuilder; + +/** + * Exists search request query test case. + * + * @category Smile_Elasticsuite + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class ExistsTest extends AbstractSimpleQueryBuilderTest +{ + /** + * Test the builder with mandatory params only. + * + * @return void + */ + public function testAnonymousMissingQueryBuilder() + { + $builder = $this->getQueryBuilder(); + + $missingQuery = new ExistsQuery('field'); + $query = $builder->buildQuery($missingQuery); + + $this->assertArrayHasKey('exists', $query); + $this->assertArrayHasKey('field', $query['exists']); + $this->assertArrayNotHasKey('_name', $query['exists']); + } + + /** + * Test the builder with mandatory + name params. + * + * @return void + */ + public function testNamedMissingQueryBuilder() + { + $builder = $this->getQueryBuilder(); + + $missingQuery = new ExistsQuery('field', 'queryName'); + $query = $builder->buildQuery($missingQuery); + + $this->assertArrayHasKey('_name', $query['exists']); + $this->assertEquals('queryName', $query['exists']['_name']); + } + + /** + * {@inheritDoc} + */ + protected function getQueryBuilder() + { + return new ExistsQueryBuilder(); + } +} diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index 8b4a1bce4..d0900b755 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -79,6 +79,7 @@ filteredQueryFactory nestedQueryFactory notQueryFactory + existsQueryFactory missingQueryFactory termQueryFactory termsQueryFactory @@ -95,6 +96,7 @@ + @@ -112,6 +114,7 @@ Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Nested\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Not\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Missing\Proxy + Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Exists\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Term\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Terms\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Range\Proxy From 2536b7737d6ef27a628f0dee53ab2413be97045d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Mon, 27 Nov 2017 14:19:48 +0100 Subject: [PATCH 06/19] Agg : Date histogram bucket support. --- .../Aggregation/Builder/DateHistogram.php | 50 +++++++++++++++++++ .../Aggregation/Bucket/DateHistogram.php | 50 +++++++++++++++++++ src/module-elasticsuite-core/etc/di.xml | 3 ++ 3 files changed, 103 insertions(+) create mode 100644 src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder/DateHistogram.php create mode 100644 src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder/DateHistogram.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder/DateHistogram.php new file mode 100644 index 000000000..40f3fac57 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder/DateHistogram.php @@ -0,0 +1,50 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\Builder; + +use Smile\ElasticsuiteCore\Search\Request\BucketInterface; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\BuilderInterface; + +/** + * Build an ES date histogram aggregation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class DateHistogram implements BuilderInterface +{ + /** + * Build the aggregation. + * + * @param BucketInterface $bucket Histogram bucket. + * + * @return array + */ + public function buildBucket(BucketInterface $bucket) + { + if ($bucket->getType() !== BucketInterface::TYPE_DATE_HISTOGRAM) { + throw new \InvalidArgumentException("Query builder : invalid aggregation type {$bucket->getType()}."); + } + + $aggParams = [ + 'field' => $bucket->getField(), + 'interval' => $bucket->getInterval(), + 'min_doc_count' => $bucket->getMinDocCount(), + ]; + + return ['date_histogram' => $aggParams]; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php b/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php new file mode 100644 index 000000000..1212bc3c4 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php @@ -0,0 +1,50 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Request\Aggregation\Bucket; + +use Smile\ElasticsuiteCore\Search\Request\BucketInterface; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Date historgram bucket implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class DateHistogram extends Histogram +{ + public function __construct( + $name, + $field, + array $metrics, + array $childBuckets = [], + $nestedPath = null, + QueryInterface $filter = null, + QueryInterface $nestedFilter = null, + $interval = "1d", + $minDocCount = 0 + ) { + parent::__construct($name, $field, $metrics, $childBuckets, $nestedPath, $filter, $nestedFilter, $interval, $minDocCount); + } + + /** + * {@inheritDoc} + */ + public function getType() + { + return BucketInterface::TYPE_DATE_HISTOGRAM; + } +} diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index d0900b755..a9c9ea903 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -131,6 +131,7 @@ termBucketFactory histogramBucketFactory + dateHistogramBucketFactory queryGroupBucketFactory @@ -138,6 +139,7 @@ + @@ -145,6 +147,7 @@ Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\Builder\Term\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\Builder\Histogram\Proxy + Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\Builder\DateHistogram\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Aggregation\Builder\QueryGroup\Proxy From 02eec88dd6dd0b1504ecb7f8614fb211b4e8928a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Mon, 27 Nov 2017 14:53:03 +0100 Subject: [PATCH 07/19] ES metric support. --- .../Request/Aggregation/Builder.php | 4 ++ .../Response/AggregationFactory.php | 3 + .../Aggregation/AggregationBuilder.php | 29 +++++++- .../Search/Request/Aggregation/Metric.php | 68 +++++++++++++++++++ .../Search/Request/MetricInterface.php | 57 ++++++++++++++++ .../Request/Aggregation/BuilderTest.php | 1 + .../Aggregation/AggregationBuilderTest.php | 28 +++++--- 7 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php create mode 100644 src/module-elasticsuite-core/Search/Request/MetricInterface.php diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder.php index 48c51a308..6ab9c767c 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Aggregation/Builder.php @@ -69,6 +69,10 @@ public function buildAggregations(array $buckets = []) $subAggregations = array_merge($subAggregations, $this->buildAggregations($bucket->getChildBuckets())); } + foreach ($bucket->getMetrics() as $metric) { + $subAggregations[$metric->getName()] = [$metric->getType() => ['field' => $metric->getField()]]; + } + if (!empty($subAggregations)) { $aggregation['aggregations'] = $subAggregations; } diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php index 90f99aca6..f6477526d 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php @@ -139,6 +139,9 @@ private function getMetrics($rawValue) foreach ($rawValue as $metricName => $value) { if (!is_array($value) || !isset($value['buckets'])) { $metricName = $metricName == 'doc_count' ? 'count' : $metricName; + if (is_array($value) && isset($value['value'])) { + $value = $value['value']; + } $metrics[$metricName] = $value; } } diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php index 9c77236d3..c3d12efb7 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php @@ -39,6 +39,11 @@ class AggregationBuilder */ private $queryBuilder; + /** + * @var MetricFactory + */ + private $metricFactory; + /** * Constructor. * @@ -47,9 +52,11 @@ class AggregationBuilder */ public function __construct( AggregationFactory $aggregationFactory, + MetricFactory $metricFactory, QueryBuilder $queryBuilder ) { $this->aggregationFactory = $aggregationFactory; + $this->metricFactory = $metricFactory; $this->queryBuilder = $queryBuilder; } @@ -92,6 +99,8 @@ public function buildAggregations( $bucketParams = $aggregationParams['config']; } + $bucketParams['metrics'] = $this->getMetrics($containerConfiguration, $aggregationParams); + $buckets[] = $this->aggregationFactory->create($bucketType, $bucketParams); } @@ -131,7 +140,6 @@ private function getBucketParams(FieldInterface $field, array $aggregationParams $bucketParams = [ 'field' => $bucketField, 'name' => $field->getName(), - 'metrics' => [], 'filter' => array_diff_key($filters, [$field->getName() => true]), ]; @@ -149,4 +157,23 @@ private function getBucketParams(FieldInterface $field, array $aggregationParams return $bucketParams; } + + private function getMetrics(ContainerConfigurationInterface $containerConfiguration, array $aggregationParams) + { + $metrics = []; + if (isset($aggregationParams['config']['metrics'])) { + foreach ($aggregationParams['config']['metrics'] as $metricName => $metricConfig) { + try { + $field = $containerConfiguration->getMapping()->getField($metricConfig['field']); + $metricConfig['field'] = $field->getName(); + } catch (\Exception $e) { + ; + } + + $metrics[] = $this->metricFactory->create(['name' => $metricName] + $metricConfig); + } + } + + return $metrics; + } } diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php b/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php new file mode 100644 index 000000000..0cc513597 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php @@ -0,0 +1,68 @@ + +* @copyright 2016 Smile +* @license Open Software License ("OSL") v. 3.0 +*/ + +namespace Smile\ElasticsuiteCore\Search\Request\Aggregation; + +use Smile\ElasticsuiteCore\Search\Request\MetricInterface; +use Magento\Framework\Search\Request\Aggregation\Metric; + +/** + * ElasticSuite metric implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Aurelien FOUCRET + */ +class Metric extends \Magento\Framework\Search\Request\Aggregation\Metric implements MetricInterface +{ + /** + * @var string + */ + private $field; + + /** + * @var string + */ + private $name; + + /** + * Constructor. + * + * @param string $name Metric name. + * @param string $type Metric type. + * @param string $field Metric field. + */ + public function __construct($name, $type, $field) + { + parent::__construct($type); + $this->field = $field; + $this->name = $name; + } + + /** + * {@inheritDoc} + */ + public function getField() + { + return $this->field; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->name; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/MetricInterface.php b/src/module-elasticsuite-core/Search/Request/MetricInterface.php new file mode 100644 index 000000000..efe83f25d --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/MetricInterface.php @@ -0,0 +1,57 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Request; + +/** + * Interface for metrics. + * + * @category Smile + * @package Smile\ElasticsuiteCatalog + * @author Aurelien FOUCRET + */ +interface MetricInterface +{ + /** + * Available metric types. + */ + const TYPE_AVG = 'avg'; + const TYPE_MIN = 'min'; + const TYPE_MAX = 'max'; + const TYPE_SUM = 'sum'; + const TYPE_STATS = 'stats'; + const TYPE_EXTENDED_STATS = 'extended_stats'; + const TYPE_CARDINALITY = 'cardinality'; + + /** + * Metric type. + * + * @return string + */ + public function getType(); + + /** + * Metric field. + * + * @return string + */ + public function getField(); + + /** + * Metric name. + * + * @return string + */ + public function getName(); +} diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Aggregation/BuilderTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Aggregation/BuilderTest.php index 4709114e4..4194a8631 100644 --- a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Aggregation/BuilderTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Aggregation/BuilderTest.php @@ -160,6 +160,7 @@ private function createBucket($name, $type) $bucket->method('getName')->will($this->returnValue($name)); $bucket->method('getType')->will($this->returnValue($type)); + $bucket->method('getMetrics')->will($this->returnValue([])); return $bucket; } diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Request/Aggregation/AggregationBuilderTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Request/Aggregation/AggregationBuilderTest.php index 816770d82..74a6cdbc2 100644 --- a/src/module-elasticsuite-core/Test/Unit/Search/Request/Aggregation/AggregationBuilderTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Search/Request/Aggregation/AggregationBuilderTest.php @@ -23,6 +23,7 @@ use Smile\ElasticsuiteCore\Index\Mapping; use Smile\ElasticsuiteCore\Search\Request\QueryInterface; use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteCore\Search\Request\Aggregation\MetricFactory; /** * Search request query builder test case. @@ -40,7 +41,7 @@ class AggregationBuilderTest extends \PHPUnit\Framework\TestCase */ public function testSimpleAggBuilder() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); $containerConfig = $this->getContainerConfiguration(); $aggregations = [ @@ -70,7 +71,7 @@ public function testSimpleAggBuilder() */ public function testFilteredAggBuilder() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); $containerConfig = $this->getContainerConfiguration(); $aggregations = [ @@ -107,7 +108,7 @@ public function testFilteredAggBuilder() */ public function testNestedAggBuilder() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); $containerConfig = $this->getContainerConfiguration(); $aggregations = [ @@ -139,7 +140,7 @@ public function testNestedAggBuilder() */ public function testNestedFilteredAggBuilder() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); $containerConfig = $this->getContainerConfiguration(); $aggregations = [ @@ -170,9 +171,9 @@ public function testNestedFilteredAggBuilder() */ public function testUnknownFieldAggregation() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); $containerConfig = $this->getContainerConfiguration(); - $aggregations = ['invalidField' => ['type' => 'aggType', 'config' => ['foo' => 'bar']]]; + $aggregations = ['invalidField' => ['type' => 'aggType', 'config' => ['foo' => 'bar', 'metrics' => []]]]; $buckets = $builder->buildAggregations($containerConfig, $aggregations, []); @@ -188,9 +189,10 @@ public function testUnknownFieldAggregation() */ public function testNotFilterableFieldAggregation() { - $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getQueryBuilder()); + $builder = new AggregationBuilder($this->getAggregationFactory(), $this->getMetricFactory(), $this->getQueryBuilder()); + $containerConfig = $this->getContainerConfiguration(); - $aggregations = ['notFilterableField' => ['type' => 'aggType', 'config' => ['foo' => 'bar']]]; + $aggregations = ['notFilterableField' => ['type' => 'aggType', 'config' => ['foo' => 'bar', 'metrics' => []]]]; $buckets = $builder->buildAggregations($containerConfig, $aggregations, []); @@ -268,4 +270,14 @@ private function getMapping() return new Mapping('entity_id', $fields); } + + /** + * Metrics factory used during tests. + * + * @return PHPUnit_Framework_MockObject_MockObject + */ + private function getMetricFactory() + { + return $this->getMockBuilder(MetricFactory::class)->getMock(); + } } From d2e90390fa193e608f630211ea2dd22a851a74c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Mon, 27 Nov 2017 15:05:20 +0100 Subject: [PATCH 08/19] Code cleaning. --- .../Request/Aggregation/AggregationBuilder.php | 9 +++++++++ .../Request/Aggregation/Bucket/DateHistogram.php | 13 +++++++++++++ .../Search/Request/Aggregation/Metric.php | 1 - 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php index c3d12efb7..509f0293d 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php @@ -48,6 +48,7 @@ class AggregationBuilder * Constructor. * * @param AggregationFactory $aggregationFactory Factory used to instantiate buckets. + * @param MetricFactory $metricFactory Factory used to instantiate metrics. * @param QueryBuilder $queryBuilder Factory used to create queries inside filtered or nested aggs. */ public function __construct( @@ -158,6 +159,14 @@ private function getBucketParams(FieldInterface $field, array $aggregationParams return $bucketParams; } + /** + * Build buckets metric. + * + * @param ContainerConfigurationInterface $containerConfiguration Container config. + * @param array $aggregationParams Aggregation params. + * + * @return \Smile\ElasticsuiteCore\Search\Request\Aggregation\Metric[] + */ private function getMetrics(ContainerConfigurationInterface $containerConfiguration, array $aggregationParams) { $metrics = []; diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php b/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php index 1212bc3c4..f764e022d 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/Bucket/DateHistogram.php @@ -26,6 +26,19 @@ */ class DateHistogram extends Histogram { + /** + * Constructor. + * + * @param string $name Bucket name. + * @param string $field Bucket field. + * @param Metric[] $metrics Bucket metrics. + * @param BucketInterface[] $childBuckets Child buckets. + * @param string $nestedPath Nested path for nested bucket. + * @param QueryInterface $filter Bucket filter. + * @param QueryInterface $nestedFilter Nested filter for the bucket. + * @param integer $interval Histogram interval. + * @param integer $minDocCount Histogram min doc count. + */ public function __construct( $name, $field, diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php b/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php index 0cc513597..1ba9bd4e3 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/Metric.php @@ -15,7 +15,6 @@ namespace Smile\ElasticsuiteCore\Search\Request\Aggregation; use Smile\ElasticsuiteCore\Search\Request\MetricInterface; -use Magento\Framework\Search\Request\Aggregation\Metric; /** * ElasticSuite metric implementation. From 05086e5fca438a68f5ef245b8908e40cebf3618c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 15:10:27 +0100 Subject: [PATCH 09/19] Better handling of aggregation names. --- .../Search/Request/Aggregation/AggregationBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php index 509f0293d..7bc75735e 100644 --- a/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php +++ b/src/module-elasticsuite-core/Search/Request/Aggregation/AggregationBuilder.php @@ -140,7 +140,7 @@ private function getBucketParams(FieldInterface $field, array $aggregationParams $bucketParams = [ 'field' => $bucketField, - 'name' => $field->getName(), + 'name' => isset($aggregationParams['config']['name']) ? $aggregationParams['config']['name'] : $field->getName(), 'filter' => array_diff_key($filters, [$field->getName() => true]), ]; From 54598e405c788a4742fb2ccfc28a5bc25e839c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 15:11:11 +0100 Subject: [PATCH 10/19] Hiearchical nested aggregation response parsing. --- .../Response/AggregationFactory.php | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php index f6477526d..543dca3ed 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Response/AggregationFactory.php @@ -119,6 +119,14 @@ private function getBucketValues($rawBucket) 'aggregations' => $this->getSubAggregations($value), ]; + $subAggregationsNames = $valueParams['aggregations']->getBucketNames(); + + foreach (array_keys($valueParams['metrics']) as $metricName) { + if (in_array($metricName, $subAggregationsNames)) { + unset($valueParams['metrics'][$metricName]); + } + } + $values[] = $this->valueFactory->create($valueParams); } @@ -139,7 +147,9 @@ private function getMetrics($rawValue) foreach ($rawValue as $metricName => $value) { if (!is_array($value) || !isset($value['buckets'])) { $metricName = $metricName == 'doc_count' ? 'count' : $metricName; - if (is_array($value) && isset($value['value'])) { + if (is_array($value) && isset($value['value_as_string'])) { + $value = $value['value_as_string']; + } elseif (is_array($value) && isset($value['value'])) { $value = $value['value']; } $metrics[$metricName] = $value; @@ -161,8 +171,14 @@ private function getSubAggregations($rawValue) $subAggregations = []; foreach ($rawValue as $key => $value) { - if (is_array($value) && isset($value['buckets'])) { - $subAggregations[$key] = $value; + if (is_array($value)) { + while (is_array($value) && isset($value[$key]) && is_array($value[$key])) { + $value = $value[$key]; + } + + if (isset($value['buckets'])) { + $subAggregations[$key] = $value; + } } } From 7cccb408dd58e769506aa1b3c0535ee25b165819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 15:13:07 +0100 Subject: [PATCH 11/19] Append tracker sessions index and search requests. --- .../etc/elasticsuite_indices.xml | 15 +++++++++++++++ .../etc/elasticsuite_search_request.xml | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml index 228abe938..793f61db1 100644 --- a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml @@ -97,4 +97,19 @@ + + + + + + + + + + + + + + + diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml index a037e3f97..7e006ce2c 100644 --- a/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_search_request.xml @@ -19,4 +19,6 @@ + + From d84867e1c28653c2da58c2abb09eb919792f2532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 15:14:26 +0100 Subject: [PATCH 12/19] Fix order items indexing. --- .../Model/Event/Processor/OrderItems.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php b/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php index c42e95ab0..41c8f1b3f 100644 --- a/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php +++ b/src/module-elasticsuite-tracker/Model/Event/Processor/OrderItems.php @@ -32,6 +32,12 @@ public function process($eventData) { if (isset($eventData['page']['order']) && isset($eventData['page']['order']['items'])) { $eventData['page']['order']['items'] = array_values($eventData['page']['order']['items']); + + foreach ($eventData['page']['order']['items'] as &$item) { + if (isset($item['category_ids'])) { + $item['category_ids'] = explode(',', $item['category_ids']); + } + } } return $eventData; From 8ed8619be6ebc28cb0bd05765973d7033d56fd12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 15:39:50 +0100 Subject: [PATCH 13/19] Event indexing refactoring. --- .../Model/EventIndex.php | 12 ++-- .../EventIndex.php => IndexResolver.php} | 62 ++++++++----------- 2 files changed, 32 insertions(+), 42 deletions(-) rename src/module-elasticsuite-tracker/Model/{ResourceModel/EventIndex.php => IndexResolver.php} (71%) diff --git a/src/module-elasticsuite-tracker/Model/EventIndex.php b/src/module-elasticsuite-tracker/Model/EventIndex.php index 24b7d8fe8..62ca9cc4e 100644 --- a/src/module-elasticsuite-tracker/Model/EventIndex.php +++ b/src/module-elasticsuite-tracker/Model/EventIndex.php @@ -26,9 +26,9 @@ class EventIndex implements EventIndexInterface { /** - * @var ResourceModel\EventIndex + * @var IndexResolver */ - private $resourceModel; + private $indexResolver; /** * @var \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface @@ -37,14 +37,14 @@ class EventIndex implements EventIndexInterface /** * - * @param ResourceModel\EventIndex $resourceModel Resource model. + * @param IndexResolver $indexResolver Resource model. * @param \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation Index operation. */ public function __construct( - ResourceModel\EventIndex $resourceModel, + IndexResolver $indexResolver, \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation ) { - $this->resourceModel = $resourceModel; + $this->indexResolver = $indexResolver; $this->indexOperation = $indexOperation; } @@ -64,7 +64,7 @@ public function indexEvents($events) $bulk = $this->indexOperation->createBulk(); foreach ($events as $event) { - $index = $this->resourceModel->getIndex($event); + $index = $this->indexResolver->getIndex(self::INDEX_IDENTIFIER, $event['page']['store_id'], $event['date']); $bulk->addDocument($index, $index->getDefaultSearchType(), $event['event_id'], $event); } diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php b/src/module-elasticsuite-tracker/Model/IndexResolver.php similarity index 71% rename from src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php rename to src/module-elasticsuite-tracker/Model/IndexResolver.php index 0f5b1b366..6546ae152 100644 --- a/src/module-elasticsuite-tracker/Model/ResourceModel/EventIndex.php +++ b/src/module-elasticsuite-tracker/Model/IndexResolver.php @@ -12,23 +12,21 @@ * @license Open Software License ("OSL") v. 3.0 */ -namespace Smile\ElasticsuiteTracker\Model\ResourceModel; - -use Smile\ElasticsuiteTracker\Api\EventIndexInterface; +namespace Smile\ElasticsuiteTracker\Model; /** - * Event index resource model. + * Resolve tracking indices. * * @category Smile * @package Smile\ElasticsuiteTracker * @author Aurelien FOUCRET */ -class EventIndex +class IndexResolver { /** * @var \Smile\ElasticsuiteCore\Api\Index\IndexInterface[] */ - private $indices; + private $indices = []; /** * @var \Smile\ElasticsuiteCore\Api\Index\IndexFactoryInterface @@ -57,29 +55,30 @@ public function __construct( \Smile\ElasticsuiteCore\Api\Index\IndexInterfaceFactory $indexFactory, \Smile\ElasticsuiteCore\Api\Client\ClientInterface $client ) { - $this->client = $client; - $this->indexFactory = $indexFactory; - $this->indexSettings = $indexSettings; + $this->client = $client; + $this->indexFactory = $indexFactory; + $this->indexSettings = $indexSettings; } /** - * Get index using event data. + * Get index by identifier, store and date. * - * @param array $event Event. + * @param string $indexIdentifier Index identifier. + * @param int $storeId Store id. + * @param string $date Date. * * @return \Smile\ElasticsuiteCore\Api\Index\IndexInterface */ - public function getIndex($event) + public function getIndex($indexIdentifier, $storeId, $date) { - $indexAlias = $this->getIndexAlias($event); - $indexName = $this->getIndexName($event); - $indexIdentifier = $this->getIndexIdenifier(); + $indexAlias = $this->getIndexAlias($indexIdentifier, $storeId); + $indexName = $this->getIndexName($indexIdentifier, $storeId, $date); if (!isset($this->indices[$indexName])) { $indexSettings = $this->indexSettings->getIndicesConfig(); $indexConfig = array_merge(['identifier' => $indexAlias, 'name' => $indexName], $indexSettings[$indexIdentifier]); $this->indices[$indexName] = $this->indexFactory->create($indexConfig); - $this->createIndexIfNotExists($this->indices[$indexName], $event['page']['store_id']); + $this->createIndexIfNotExists($this->indices[$indexName], $storeId); } return $this->indices[$indexName]; @@ -96,7 +95,7 @@ public function getIndex($event) private function createIndexIfNotExists(\Smile\ElasticsuiteCore\Api\Index\IndexInterface $index, $store) { if ($this->client->indexExists($index->getName()) === false) { - $indexSettings = $this->indexSettings->getInstallIndexSettings(); + $indexSettings = array_merge($this->indexSettings->getCreateIndexSettings(), $this->indexSettings->getInstallIndexSettings()); $indexSettings['analysis'] = $this->indexSettings->getAnalysisSettings($store); $this->client->createIndex($index->getName(), $indexSettings); $this->client->updateAliases([['add' => ['index' => $index->getName(), 'alias' => $index->getIdentifier()]]]); @@ -106,41 +105,32 @@ private function createIndexIfNotExists(\Smile\ElasticsuiteCore\Api\Index\IndexI } } - /** - * Search index identifier. - * - * @return string - */ - private function getIndexIdenifier() - { - return EventIndexInterface::INDEX_IDENTIFIER; - } - /** * Build index alias from an event. * - * @param array $event Event. + * @param string $indexIdentifier Index identifier. + * @param int $storeId Store id. * * @return string */ - private function getIndexAlias($event) + private function getIndexAlias($indexIdentifier, $storeId) { - $indexIdentifier = $this->getIndexIdenifier(); - - return $this->indexSettings->getIndexAliasFromIdentifier($indexIdentifier, $event['page']['store_id']); + return $this->indexSettings->getIndexAliasFromIdentifier($indexIdentifier, $storeId); } /** * Build index name from an event. * - * @param arrat $event Event. + * @param string $indexIdentifier Index identifier. + * @param int $storeId Store id. + * @param string $date Date. * * @return string */ - private function getIndexName($event) + private function getIndexName($indexIdentifier, $storeId, $date) { - $indexAlias = $this->getIndexAlias($event); - $date = substr($event['date'], 0, 10); + $indexAlias = $this->getIndexAlias($indexIdentifier, $storeId); + $date = substr($date, 0, 10); return sprintf("%s_%s", $indexAlias, str_replace("-", "", $date)); } From 3a0d5ec1e89ef4df82afdf547e12c1beab1f3ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 16:13:17 +0100 Subject: [PATCH 14/19] Session indexer. --- .../Api/SessionIndexInterface.php | 50 ++++++ .../Cron/IndexLogEvent.php | 19 ++- .../Model/EventIndex.php | 1 + .../Model/ResourceModel/SessionIndex.php | 148 ++++++++++++++++++ .../Model/SessionIndex.php | 108 +++++++++++++ src/module-elasticsuite-tracker/etc/di.xml | 1 + 6 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 src/module-elasticsuite-tracker/Api/SessionIndexInterface.php create mode 100644 src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php create mode 100644 src/module-elasticsuite-tracker/Model/SessionIndex.php diff --git a/src/module-elasticsuite-tracker/Api/SessionIndexInterface.php b/src/module-elasticsuite-tracker/Api/SessionIndexInterface.php new file mode 100644 index 000000000..42bd77297 --- /dev/null +++ b/src/module-elasticsuite-tracker/Api/SessionIndexInterface.php @@ -0,0 +1,50 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Api; + +use Composer\EventDispatcher\Event; + +/** + * Tracker event log index. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +interface SessionIndexInterface +{ + /** + * @var string + */ + const INDEX_IDENTIFIER = 'tracking_log_session'; + + /** + * Index a single event. + * + * @param array $event Event. + * + * @return void + */ + public function indexEvent($event); + + /** + * Index a multiple events. + * + * @param array $event Events. + * + * @return void + */ + public function indexEvents($event); +} diff --git a/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php index c23653ec7..a76f67bab 100644 --- a/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php +++ b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php @@ -33,18 +33,26 @@ class IndexLogEvent */ private $eventIndex; + /** + * @var \Smile\ElasticsuiteTracker\Api\SessionIndexInterface + */ + private $sessionIndex; + /** * Constructor. * - * @param \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue Pending events queue. - * @param \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex Event index. + * @param \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue Pending events queue. + * @param \Smile\ElasticsuiteTracker\Api\SessionIndexInterface $eventIndex Event index. + * @param \Smile\ElasticsuiteTracker\Api\EventIndexInterface $sessionIndex Session index. */ public function __construct( \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue, - \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex + \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex, + \Smile\ElasticsuiteTracker\Api\SessionIndexInterface $sessionIndex ) { - $this->eventQueue = $eventQueue; - $this->eventIndex = $eventIndex; + $this->eventQueue = $eventQueue; + $this->eventIndex = $eventIndex; + $this->sessionIndex = $sessionIndex; } /** @@ -57,6 +65,7 @@ public function execute() $events = $this->eventQueue->getEvents(); if (!empty($events)) { $this->eventIndex->indexEvents($events); + $this->sessionIndex->indexEvents($events); $this->eventQueue->deleteEvents(array_column($events, 'event_id')); } } diff --git a/src/module-elasticsuite-tracker/Model/EventIndex.php b/src/module-elasticsuite-tracker/Model/EventIndex.php index 62ca9cc4e..4fa565f95 100644 --- a/src/module-elasticsuite-tracker/Model/EventIndex.php +++ b/src/module-elasticsuite-tracker/Model/EventIndex.php @@ -36,6 +36,7 @@ class EventIndex implements EventIndexInterface private $indexOperation; /** + * Constructor. * * @param IndexResolver $indexResolver Resource model. * @param \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation Index operation. diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php b/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php new file mode 100644 index 000000000..27cc4d6e3 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php @@ -0,0 +1,148 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model\ResourceModel; + +use Smile\ElasticsuiteTracker\Api\EventIndexInterface; +use Smile\ElasticsuiteCore\Search\Request\BucketInterface; +use Smile\ElasticsuiteCore\Search\Request\MetricInterface; + +/** + * Session index resource model. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class SessionIndex +{ + /** + * @var array + */ + private $metrics = [ + 'start_date' => ['type' => MetricInterface::TYPE_MIN, 'field' => 'date'], + 'end_date' => ['type' => MetricInterface::TYPE_MAX, 'field' => 'date'], + ]; + + /** + * @var array + */ + private $buckets = [ + 'session.vid' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'visitor_id']], + 'session.customer_id' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'customer_id']], + 'page.product.id' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'product_view']], + 'page.category.id' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'category_view']], + 'page.search.query' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'search_query']], + 'page.order.items.product_id' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'product_sale']], + 'page.order.items.category_ids' => ['type' => BucketInterface::TYPE_TERM, 'config' => ['name' => 'category_sale']], + ]; + + /** + * @var \Smile\ElasticsuiteCore\Search\Request\Builder + */ + private $searchRequestBuilder; + + /** + * @var \Magento\Framework\Search\SearchEngineInterface + */ + private $searchEngine; + + /** + * Constructor. + * + * @param \Smile\ElasticsuiteCore\Search\Request\Builder $searchRequestBuilder Search request builder. + * @param \Magento\Framework\Search\SearchEngineInterface $searchEngine Search engine. + */ + public function __construct( + \Smile\ElasticsuiteCore\Search\Request\Builder $searchRequestBuilder, + \Magento\Framework\Search\SearchEngineInterface $searchEngine + ) { + $this->searchRequestBuilder = $searchRequestBuilder; + $this->searchEngine = $searchEngine; + } + + /** + * Retrieve session data to be indexed. + * + * @param int $storeId Store id. + * @param string[] $sessionIds Session ids. + * + * @return array + */ + public function getSessionData($storeId, $sessionIds) + { + $data = []; + $searchRequest = $this->getSearchRequest($storeId, $sessionIds); + $searchResponse = $this->searchEngine->search($searchRequest); + + foreach ($searchResponse->getAggregations()->getBucket('session_id')->getValues() as $sessionValue) { + $sessionData = $this->processSessionData($sessionValue); + $sessionData['store_id'] = $storeId; + unset($sessionData['count']); + + $data[] = array_filter($sessionData); + } + + return $data; + } + + /** + * Build search request used to collect aggregated session data. + * + * @param int $storeId + * @param string[] $sessionIds + * + * @return \Smile\ElasticsuiteCore\Search\RequestInterface + */ + private function getSearchRequest($storeId, $sessionIds) + { + $eventIndexIdentifier = EventIndexInterface::INDEX_IDENTIFIER; + + $queryFilters = ['session.uid' => $sessionIds]; + + $bucketConfig = ['name' => 'session_id', 'childBuckets' => $this->buckets, 'metrics' => $this->metrics]; + $buckets = ['session.uid' => ['type' => BucketInterface::TYPE_TERM, 'config' => $bucketConfig]]; + + return $this->searchRequestBuilder->create($storeId, $eventIndexIdentifier, 0, 0, null, [], [], $queryFilters, $buckets); + } + + /** + * Prepare session data from search aggregation response. + * + * @param \Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\Aggregation\Value $value Aggregation value. + * + * @return array + */ + private function processSessionData(\Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\Aggregation\Value $value) + { + $data = ['session_id' => $value->getValue()]; + + foreach ($value->getAggregations()->getBuckets() as $bucket) { + $bucketName = $bucket->getName(); + $bucketValues = []; + + foreach ($bucket->getValues() as $bucketValue) { + $bucketValues[] = $bucketValue->getValue(); + } + + $data[$bucketName] = $bucketValues; + } + + foreach ($value->getMetrics() as $metricName => $metricValue) { + $data[$metricName] = $metricValue; + } + + return $data; + } +} diff --git a/src/module-elasticsuite-tracker/Model/SessionIndex.php b/src/module-elasticsuite-tracker/Model/SessionIndex.php new file mode 100644 index 000000000..e06dacf13 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/SessionIndex.php @@ -0,0 +1,108 @@ + + * @copyright 2016 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Model; + +use Smile\ElasticsuiteTracker\Api\SessionIndexInterface; + +/** + * Session index implementation. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Aurelien FOUCRET + */ +class SessionIndex implements SessionIndexInterface +{ + /** + * @var ResourceModel\SessionIndex + */ + private $resourceModel; + + /** + * @var IndexResolver + */ + private $indexResolver; + + /** + * @var \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface + */ + private $indexOperation; + + /** + * Constructor. + * + * @param IndexResolver $indexResolver Index resolver. + * @param ResourceModel\SessionIndex $resourceModel Resource model. + * @param \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation Index operation. + */ + public function __construct( + IndexResolver $indexResolver, + ResourceModel\SessionIndex $resourceModel, + \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface $indexOperation + ) { + $this->resourceModel = $resourceModel; + $this->indexOperation = $indexOperation; + $this->indexResolver = $indexResolver; + } + + /** + * {@inheritDoc} + */ + public function indexEvent($event) + { + $this->indexEvents([$event]); + } + + /** + * {@inheritDoc} + */ + public function indexEvents($events) + { + $sessionIdsByStore = $this->getSessionIdsByStore($events); + $bulk = $this->indexOperation->createBulk(); + $indices = []; + + foreach ($sessionIdsByStore as $storeId => $sessionIds) { + $sessionData = $this->resourceModel->getSessionData($storeId, $sessionIds); + + foreach ($sessionData as $session) { + $index = $this->indexResolver->getIndex(self::INDEX_IDENTIFIER, $session['store_id'], $session['start_date']); + $bulk->addDocument($index, $index->getDefaultSearchType(), $session['session_id'], $session); + } + } + + if ($bulk->isEmpty() === false) { + $this->indexOperation->executeBulk($bulk); + } + } + + /** + * Extract session ids from event by store. + * + * @param array $events Events + * + * @return array + */ + private function getSessionIdsByStore($events) + { + $sessionIdsByStore = []; + + foreach ($events as $event) { + $sessionIdsByStore[$event['page']['store_id']][] = $event['session']['uid']; + } + + return $sessionIdsByStore; + } +} diff --git a/src/module-elasticsuite-tracker/etc/di.xml b/src/module-elasticsuite-tracker/etc/di.xml index c2512295d..29c975a96 100644 --- a/src/module-elasticsuite-tracker/etc/di.xml +++ b/src/module-elasticsuite-tracker/etc/di.xml @@ -20,6 +20,7 @@ + From 956b9ed40555b158e5ce2b3df46edb765de9d3c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 29 Nov 2017 16:26:12 +0100 Subject: [PATCH 15/19] Index refresh policy. --- src/module-elasticsuite-tracker/Model/EventIndex.php | 8 +++++++- .../Model/ResourceModel/SessionIndex.php | 4 ++-- src/module-elasticsuite-tracker/Model/SessionIndex.php | 5 +++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/module-elasticsuite-tracker/Model/EventIndex.php b/src/module-elasticsuite-tracker/Model/EventIndex.php index 4fa565f95..907c54def 100644 --- a/src/module-elasticsuite-tracker/Model/EventIndex.php +++ b/src/module-elasticsuite-tracker/Model/EventIndex.php @@ -62,15 +62,21 @@ public function indexEvent($event) */ public function indexEvents($events) { - $bulk = $this->indexOperation->createBulk(); + $bulk = $this->indexOperation->createBulk(); + $indices = []; foreach ($events as $event) { $index = $this->indexResolver->getIndex(self::INDEX_IDENTIFIER, $event['page']['store_id'], $event['date']); + $indices[$index->getName()] = $index; $bulk->addDocument($index, $index->getDefaultSearchType(), $event['event_id'], $event); } if ($bulk->isEmpty() === false) { $this->indexOperation->executeBulk($bulk); } + + foreach ($indices as $index) { + $this->indexOperation->refreshIndex($index); + } } } diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php b/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php index 27cc4d6e3..81e06a8ce 100644 --- a/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php +++ b/src/module-elasticsuite-tracker/Model/ResourceModel/SessionIndex.php @@ -100,8 +100,8 @@ public function getSessionData($storeId, $sessionIds) /** * Build search request used to collect aggregated session data. * - * @param int $storeId - * @param string[] $sessionIds + * @param int $storeId Current store Id. + * @param string[] $sessionIds Session ids. * * @return \Smile\ElasticsuiteCore\Search\RequestInterface */ diff --git a/src/module-elasticsuite-tracker/Model/SessionIndex.php b/src/module-elasticsuite-tracker/Model/SessionIndex.php index e06dacf13..2b8567951 100644 --- a/src/module-elasticsuite-tracker/Model/SessionIndex.php +++ b/src/module-elasticsuite-tracker/Model/SessionIndex.php @@ -79,6 +79,7 @@ public function indexEvents($events) foreach ($sessionData as $session) { $index = $this->indexResolver->getIndex(self::INDEX_IDENTIFIER, $session['store_id'], $session['start_date']); + $indices[$index->getName()] = $index; $bulk->addDocument($index, $index->getDefaultSearchType(), $session['session_id'], $session); } } @@ -86,6 +87,10 @@ public function indexEvents($events) if ($bulk->isEmpty() === false) { $this->indexOperation->executeBulk($bulk); } + + foreach ($indices as $index) { + $this->indexOperation->refreshIndex($index); + } } /** From 9d178fdbd467e3bf0c1cb179ec73eac582e86afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 6 Dec 2017 08:56:53 +0100 Subject: [PATCH 16/19] Fulltext query support in query filter builder. --- .../Request/Query/Filter/QueryBuilder.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/module-elasticsuite-core/Search/Request/Query/Filter/QueryBuilder.php b/src/module-elasticsuite-core/Search/Request/Query/Filter/QueryBuilder.php index 2224014ad..3ffbb243c 100644 --- a/src/module-elasticsuite-core/Search/Request/Query/Filter/QueryBuilder.php +++ b/src/module-elasticsuite-core/Search/Request/Query/Filter/QueryBuilder.php @@ -37,16 +37,17 @@ class QueryBuilder * @var array */ private $mappedConditions = [ - 'eq' => 'values', - 'seq' => 'values', - 'in' => 'values', - 'from' => 'gte', - 'moreq' => 'gte', - 'gteq' => 'gte', - 'to' => 'lte', - 'lteq' => 'lte', - 'like' => 'queryText', - 'in_set' => 'values', + 'eq' => 'values', + 'seq' => 'values', + 'in' => 'values', + 'from' => 'gte', + 'moreq' => 'gte', + 'gteq' => 'gte', + 'to' => 'lte', + 'lteq' => 'lte', + 'like' => 'queryText', + 'fulltext' => 'queryText', + 'in_set' => 'values', ]; /** From 9c304fd5a20da59363a90ce295ed9f13487ae528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 6 Dec 2017 08:57:41 +0100 Subject: [PATCH 17/19] Searchable string in event log and session data. --- .../etc/elasticsuite_indices.xml | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml index 793f61db1..3b78f6a9e 100644 --- a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml @@ -48,8 +48,12 @@ - - + + true + + + true + @@ -62,12 +66,18 @@ - + + true + - - + + true + + + true + @@ -101,11 +111,15 @@ + + - + + true + From 7eed75971e83823ef4227873c07d32bdf0629d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 6 Dec 2017 09:46:45 +0100 Subject: [PATCH 18/19] Checkstyle --- .../Setup/UpgradeSchema.php | 2 +- src/module-elasticsuite-tracker/etc/crontab.xml | 17 ++++++++++++++--- src/module-elasticsuite-tracker/etc/di.xml | 2 +- .../etc/frontend/routes.xml | 3 +-- src/module-elasticsuite-tracker/etc/module.xml | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php b/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php index b62c29a61..9ed529b60 100644 --- a/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php +++ b/src/module-elasticsuite-tracker/Setup/UpgradeSchema.php @@ -5,7 +5,7 @@ * versions in the future. * * @category Smile - * @package Smile\ElasticsuiteCore + * @package Smile\ElasticsuiteTracker * @author Aurelien FOUCRET * @copyright 2016 Smile * @license Open Software License ("OSL") v. 3.0 diff --git a/src/module-elasticsuite-tracker/etc/crontab.xml b/src/module-elasticsuite-tracker/etc/crontab.xml index 390638eb5..d675c195f 100644 --- a/src/module-elasticsuite-tracker/etc/crontab.xml +++ b/src/module-elasticsuite-tracker/etc/crontab.xml @@ -1,10 +1,21 @@ + --> diff --git a/src/module-elasticsuite-tracker/etc/di.xml b/src/module-elasticsuite-tracker/etc/di.xml index 29c975a96..625bd5e22 100644 --- a/src/module-elasticsuite-tracker/etc/di.xml +++ b/src/module-elasticsuite-tracker/etc/di.xml @@ -10,7 +10,7 @@ * * * @category Smile - * @package Smile\ElasticsuiteCore + * @package Smile\ElasticsuiteTracker * @author Aurelien FOUCRET * @copyright 2016 Smile * @license Open Software License ("OSL") v. 3.0 diff --git a/src/module-elasticsuite-tracker/etc/frontend/routes.xml b/src/module-elasticsuite-tracker/etc/frontend/routes.xml index 028351712..447ae8d1e 100644 --- a/src/module-elasticsuite-tracker/etc/frontend/routes.xml +++ b/src/module-elasticsuite-tracker/etc/frontend/routes.xml @@ -7,13 +7,12 @@ * versions in the future. * * @category Smile - * @package Smile\ElasticsuiteCatalog + * @package Smile\ElasticsuiteTracker * @author Aurelien FOUCRET * @copyright 2016 Smile * @license Open Software License ("OSL") v. 3.0 */ --> - diff --git a/src/module-elasticsuite-tracker/etc/module.xml b/src/module-elasticsuite-tracker/etc/module.xml index 4c18953f1..54dd94295 100644 --- a/src/module-elasticsuite-tracker/etc/module.xml +++ b/src/module-elasticsuite-tracker/etc/module.xml @@ -10,7 +10,7 @@ * * * @category Smile - * @package Smile\ElasticsuiteCore + * @package Smile\ElasticsuiteTracker * @author Aurelien FOUCRET * @copyright 2016 Smile * @license Open Software License ("OSL") v. 3.0 From 99c94c8bb4400945a0bd28bd432baec569f7ed12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 6 Dec 2017 14:20:43 +0100 Subject: [PATCH 19/19] Fix comments. --- src/module-elasticsuite-tracker/Model/IndexResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module-elasticsuite-tracker/Model/IndexResolver.php b/src/module-elasticsuite-tracker/Model/IndexResolver.php index 6546ae152..3a7b21a9c 100644 --- a/src/module-elasticsuite-tracker/Model/IndexResolver.php +++ b/src/module-elasticsuite-tracker/Model/IndexResolver.php @@ -29,7 +29,7 @@ class IndexResolver private $indices = []; /** - * @var \Smile\ElasticsuiteCore\Api\Index\IndexFactoryInterface + * @var \Smile\ElasticsuiteCore\Api\Index\IndexInterfaceFactory */ private $indexFactory;