From 5ca7d9133c534ddc8f9cf1f895aba86d49e3c020 Mon Sep 17 00:00:00 2001 From: Richard BAYET Date: Mon, 27 Nov 2023 10:55:47 +0100 Subject: [PATCH] [Tracker] Tools to check and fix invalid behavioral data --- .../Api/Client/ClientInterface.php | 18 +++ .../Client/Client.php | 16 ++ .../Console/CheckData.php | 100 ++++++++++++ .../Console/FixData.php | 101 ++++++++++++ .../Model/Data/Checker.php | 92 +++++++++++ .../Model/Data/Checker/DataCheckResult.php | 84 ++++++++++ .../Data/Checker/DataCheckerInterface.php | 51 ++++++ .../Data/Checker/Event/UndefinedSessionId.php | 151 ++++++++++++++++++ .../Checker/Session/UndefinedSessionId.php | 130 +++++++++++++++ .../Model/Data/Fixer/DataFixerInterface.php | 36 +++++ .../Fixer/Event/DeleteUndefinedSessionId.php | 116 ++++++++++++++ .../Session/DeleteUndefinedSessionId.php | 107 +++++++++++++ src/module-elasticsuite-tracker/etc/di.xml | 43 ++++- 13 files changed, 1044 insertions(+), 1 deletion(-) create mode 100644 src/module-elasticsuite-tracker/Console/CheckData.php create mode 100644 src/module-elasticsuite-tracker/Console/FixData.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Checker.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckResult.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckerInterface.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Checker/Event/UndefinedSessionId.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Checker/Session/UndefinedSessionId.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Fixer/DataFixerInterface.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Fixer/Event/DeleteUndefinedSessionId.php create mode 100644 src/module-elasticsuite-tracker/Model/Data/Fixer/Session/DeleteUndefinedSessionId.php diff --git a/src/module-elasticsuite-core/Api/Client/ClientInterface.php b/src/module-elasticsuite-core/Api/Client/ClientInterface.php index f70a87758..25c747e5f 100644 --- a/src/module-elasticsuite-core/Api/Client/ClientInterface.php +++ b/src/module-elasticsuite-core/Api/Client/ClientInterface.php @@ -210,4 +210,22 @@ public function mtermvectors($params); * @return array */ public function reindex(array $params): array; + + /** + * Run a deleteByQuery request. + * + * @param array $params Delete by query params. + * + * @return array + */ + public function deleteByQuery(array $params): array; + + /** + * Run an updateByQuery request. + * + * @param array $params Delete by query params. + * + * @return array + */ + public function updateByQuery(array $params): array; } diff --git a/src/module-elasticsuite-core/Client/Client.php b/src/module-elasticsuite-core/Client/Client.php index d8ce4a308..bad174a18 100644 --- a/src/module-elasticsuite-core/Client/Client.php +++ b/src/module-elasticsuite-core/Client/Client.php @@ -245,6 +245,22 @@ public function reindex(array $params): array return $this->getEsClient()->reindex($params); } + /** + * {@inheritDoc} + */ + public function deleteByQuery(array $params): array + { + return $this->getEsClient()->deleteByQuery($params); + } + + /** + * {@inheritDoc} + */ + public function updateByQuery(array $params): array + { + return $this->getEsClient()->updateByQuery($params); + } + /** * @return \Elasticsearch\Client */ diff --git a/src/module-elasticsuite-tracker/Console/CheckData.php b/src/module-elasticsuite-tracker/Console/CheckData.php new file mode 100644 index 000000000..322e942e5 --- /dev/null +++ b/src/module-elasticsuite-tracker/Console/CheckData.php @@ -0,0 +1,100 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Console; + +use Magento\Framework\Console\Cli; +use Magento\Store\Model\StoreManagerInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker as DataChecker; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\ProgressIndicator; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Check invalid behavioral data console command. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class CheckData extends Command +{ + /** + * @var DataChecker + */ + private $checker; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * + * @param DataChecker $checker Data checker. + * @param StoreManagerInterface $storeManager Store manager. + * @param string|null $name The name of the command; passing null means it must be set in configure(). + * + * @throws LogicException When the command name is empty + */ + public function __construct(DataChecker $checker, StoreManagerInterface $storeManager, string $name = null) + { + parent::__construct($name); + $this->checker = $checker; + $this->storeManager = $storeManager; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('elasticsuite:tracker:check-data'); + $this->setDescription('Check presence of invalid data in indexed tracker data.'); + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.StaticAccess) + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $progressIndicator = new ProgressIndicator($output, 'verbose', 100, ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇']); + $progressIndicator->start('Processing...'); + + $table = new Table($output); + $table->setHeaders(['Store Name', 'Issues (if any)']); + $stores = $this->storeManager->getStores(); + foreach ($stores as $store) { + $progressIndicator->advance(); + if (!$store->getIsActive()) { + continue; + } + $issuesInfo = 'No invalid data.'; + $issues = $this->checker->checkData((int) $store->getId()); + if (!empty($issues)) { + $issuesInfo = sprintf("%s", join("\n", $issues)); + } + $table->addRow([$store->getName(), $issuesInfo]); + } + $progressIndicator->finish('Done'); + $table->render(); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/src/module-elasticsuite-tracker/Console/FixData.php b/src/module-elasticsuite-tracker/Console/FixData.php new file mode 100644 index 000000000..840769bf2 --- /dev/null +++ b/src/module-elasticsuite-tracker/Console/FixData.php @@ -0,0 +1,101 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Console; + +use Magento\Framework\Console\Cli; +use Magento\Store\Model\StoreManagerInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker as DataChecker; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\ProgressIndicator; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Fix invalid behavioral data console command. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class FixData extends Command +{ + /** + * @var DataChecker + */ + private $checker; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * + * @param DataChecker $checker Data checker. + * @param StoreManagerInterface $storeManager Store manager. + * @param string|null $name The name of the command; passing null means it must be set in configure(). + * + * @throws LogicException When the command name is empty + */ + public function __construct(DataChecker $checker, StoreManagerInterface $storeManager, string $name = null) + { + parent::__construct($name); + $this->checker = $checker; + $this->storeManager = $storeManager; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('elasticsuite:tracker:fix-data'); + $this->setDescription('Check and fix invalid data in indexed tracker data.'); + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.StaticAccess) + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $progressIndicator = new ProgressIndicator($output, 'verbose', 100, ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇']); + $progressIndicator->start('Processing...'); + + $table = new Table($output); + $table->setHeaders(['Store Name', 'Fixed issues (if any)']); + $stores = $this->storeManager->getStores(); + foreach ($stores as $store) { + $progressIndicator->advance(); + if (!$store->getIsActive()) { + continue; + } + $issuesInfo = 'Nothing to fix.'; + $issues = $this->checker->checkAndFixData((int) $store->getId()); + if (!empty($issues)) { + $issuesInfo = sprintf("%s", join("\n", $issues)); + } + $table->addRow([$store->getName(), $issuesInfo]); + } + $progressIndicator->finish('Done'); + + $table->render(); + + return Cli::RETURN_SUCCESS; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Checker.php b/src/module-elasticsuite-tracker/Model/Data/Checker.php new file mode 100644 index 000000000..85313aef2 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Checker.php @@ -0,0 +1,92 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data; + +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckerInterface; + +/** + * Behavioral data checker. + * Relies on checkers to check behavioral data for a given store. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class Checker +{ + /** + * @var DataCheckerInterface[] + */ + private array $checkers; + + /** + * Constructor. + * + * @param DataCheckerInterface[] $checkers Data checkers. + */ + public function __construct(array $checkers = []) + { + $this->checkers = $checkers; + } + + /** + * Check behavioral data. + * + * @param int $storeId Store id. + * + * @return string[] + */ + public function checkData(int $storeId): array + { + $data = []; + + foreach ($this->checkers as &$checker) { + $checkResult = $checker->check($storeId); + if ($checkResult->hasInvalidData()) { + $data[] = $checkResult->getDescription(); + } + } + + return $data; + } + + /** + * Check and fix behavioral data when possible. + * + * @param int $storeId Store id. + * + * @return string[] + */ + public function checkAndFixData(int $storeId): array + { + $data = []; + + foreach ($this->checkers as &$checker) { + $checkResult = $checker->check($storeId); + if ($checkResult->hasInvalidData()) { + $status = sprintf("Unfixed: %s", $checkResult->getDescription()); + if ($checker->hasDataFixer()) { + if ($checker->getDataFixer()->fixInvalidData($storeId)) { + $status = sprintf("Fixed %s", $checkResult->getDescription()); + } + } + $data[] = $status; + } + } + + return $data; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckResult.php b/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckResult.php new file mode 100644 index 000000000..0e894e568 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckResult.php @@ -0,0 +1,84 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Checker; + +/** + * Behavioral data check result. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class DataCheckResult +{ + /** + * @var boolean + */ + private $hasInvalidata = false; + + /** + * @var string + */ + private $description = ''; + + /** + * If any invalid data found. + * + * @return bool + */ + public function hasInvalidData(): bool + { + return $this->hasInvalidata; + } + + /** + * Set if any invalid data was found. + * + * @param bool $hasInvalidData Invalid data found. + * + * @return $this + */ + public function setInvalidData($hasInvalidData): DataCheckResult + { + $this->hasInvalidata = $hasInvalidData; + + return $this; + } + + /** + * Get invalid data/problem description. + * + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Set description of the invalid data/problem. + * + * @param string $description Description of invalid data/problem. + * + * @return $this + */ + public function setDescription($description): DataCheckResult + { + $this->description = $description; + + return $this; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckerInterface.php b/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckerInterface.php new file mode 100644 index 000000000..97970e95c --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Checker/DataCheckerInterface.php @@ -0,0 +1,51 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Checker; + +use Smile\ElasticsuiteTracker\Model\Data\Fixer\DataFixerInterface; + +/** + * Behavioral data checker interface. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +interface DataCheckerInterface +{ + /** + * Perform checks on behavioral data. + * + * @param int $storeId Store id. + * + * @return DataCheckResult + */ + public function check($storeId): DataCheckResult; + + /** + * Returns true if a fixer is available for the invalid data. + * + * @return bool + */ + public function hasDataFixer(): bool; + + /** + * Returns the invalid data fixer if available. + * + * @return DataFixerInterface|null + */ + public function getDataFixer(): ?DataFixerInterface; +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Checker/Event/UndefinedSessionId.php b/src/module-elasticsuite-tracker/Model/Data/Checker/Event/UndefinedSessionId.php new file mode 100644 index 000000000..588885ad8 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Checker/Event/UndefinedSessionId.php @@ -0,0 +1,151 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Checker\Event; + +use Magento\Framework\Search\SearchEngineInterface; +use Smile\ElasticsuiteCore\Search\Request\Builder; +use Smile\ElasticsuiteCore\Search\RequestInterface; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteTracker\Api\EventIndexInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckerInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckResult; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckResultFactory; +use Smile\ElasticsuiteTracker\Model\Data\Fixer\DataFixerInterface; + +/** + * Behavioral data checker for undefined session ids in events. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class UndefinedSessionId implements DataCheckerInterface +{ + /** + * @var DataCheckResultFactory + */ + private $checkResultFactory; + + /** + * @var Builder + */ + private $searchRequestBuilder; + + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * @var SearchEngineInterface + */ + private $searchEngine; + + /** + * @var ?DataFixerInterface + */ + private $dataFixer; + + /** + * Constructor. + * + * @param DataCheckResultFactory $checkResultFactory Data checker result factory. + * @param Builder $searchRequestBuilder Search request builder. + * @param QueryFactory $queryFactory Search query factory. + * @param SearchEngineInterface $searchEngine Search engine. + * @param DataFixerInterface|null $dataFixer Invalid data fixer. + */ + public function __construct( + DataCheckResultFactory $checkResultFactory, + Builder $searchRequestBuilder, + QueryFactory $queryFactory, + SearchEngineInterface $searchEngine, + ?DataFixerInterface $dataFixer = null + ) { + $this->checkResultFactory = $checkResultFactory; + $this->searchRequestBuilder = $searchRequestBuilder; + $this->queryFactory = $queryFactory; + $this->searchEngine = $searchEngine; + $this->dataFixer = $dataFixer; + } + + /** + * Check that no event has the field session.id missing. + * + * @param int $storeId Store id. + * + * @return DataCheckResult + */ + public function check($storeId): DataCheckResult + { + /** @var DataCheckResult $checkResult */ + $checkResult = $this->checkResultFactory->create([]); + + try { + $request = $this->getSearchRequest($storeId); + $response = $this->searchEngine->search($request); + if ($response->count() > 0) { + $checkResult->setInvalidData(true); + $checkResult->setDescription(sprintf("%d events without any defined session id.", $response->count())); + } + } catch (\LogicException $e) { + ; + } + + return $checkResult; + } + + /** + * {@inheritDoc} + */ + public function hasDataFixer(): bool + { + return $this->dataFixer instanceof DataFixerInterface; + } + + /** + * {@inheritDoc} + */ + public function getDataFixer(): ?DataFixerInterface + { + return $this->dataFixer; + } + + /** + * Build search request used to check invalid event data. + * + * @param int $storeId Store id. + * + * @return RequestInterface + */ + private function getSearchRequest($storeId): RequestInterface + { + $queryFilters = [ + $this->queryFactory->create( + QueryInterface::TYPE_BOOL, + [ + 'should' => [ + $this->queryFactory->create(QueryInterface::TYPE_MISSING, ['field' => 'session.uid']), + $this->queryFactory->create(QueryInterface::TYPE_TERM, ['field' => 'session.uid', 'value' => 'null']), + ], + ] + ), + ]; + + return $this->searchRequestBuilder->create($storeId, EventIndexInterface::INDEX_IDENTIFIER, 0, 0, null, [], [], $queryFilters); + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Checker/Session/UndefinedSessionId.php b/src/module-elasticsuite-tracker/Model/Data/Checker/Session/UndefinedSessionId.php new file mode 100644 index 000000000..a28de6541 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Checker/Session/UndefinedSessionId.php @@ -0,0 +1,130 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Checker\Session; + +use Magento\Framework\Search\SearchEngineInterface; +use Smile\ElasticsuiteCore\Search\Request\Builder; +use Smile\ElasticsuiteCore\Search\RequestInterface; +use Smile\ElasticsuiteTracker\Api\SessionIndexInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckerInterface; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckResult; +use Smile\ElasticsuiteTracker\Model\Data\Checker\DataCheckResultFactory; +use Smile\ElasticsuiteTracker\Model\Data\Fixer\DataFixerInterface; + +/** + * Behavioral data checker for undefined session ids in sessions. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class UndefinedSessionId implements DataCheckerInterface +{ + /** + * @var DataCheckResultFactory + */ + private $checkResultFactory; + + /** + * @var Builder + */ + private $searchRequestBuilder; + + /** + * @var SearchEngineInterface + */ + private $searchEngine; + + /** + * @var ?DataFixerInterface + */ + private $dataFixer; + + /** + * Constructor. + * + * @param DataCheckResultFactory $checkResultFactory Data check result factory. + * @param Builder $searchRequestBuilder Search request builder. + * @param SearchEngineInterface $searchEngine Search engine. + * @param DataFixerInterface|null $dataFixer Invalid data fixer. + */ + public function __construct( + DataCheckResultFactory $checkResultFactory, + Builder $searchRequestBuilder, + SearchEngineInterface $searchEngine, + ?DataFixerInterface $dataFixer = null + ) { + $this->checkResultFactory = $checkResultFactory; + $this->searchRequestBuilder = $searchRequestBuilder; + $this->searchEngine = $searchEngine; + $this->dataFixer = $dataFixer; + } + + /** + * Check that no session has a session_id valued to null. + * + * @param int $storeId Store id. + * + * @return DataCheckResult + */ + public function check($storeId): DataCheckResult + { + $checkResult = $this->checkResultFactory->create([]); + + try { + $request = $this->getSearchRequest($storeId); + $response = $this->searchEngine->search($request); + if ($response->count() > 0) { + $checkResult->setInvalidData(true); + $checkResult->setDescription(sprintf("%d sessions with an undefined id.", $response->count())); + } + } catch (\LogicException $e) { + ; + } + + return $checkResult; + } + + /** + * {@inheritDoc} + */ + public function hasDataFixer(): bool + { + return $this->dataFixer instanceof DataFixerInterface; + } + + /** + * {@inheritDoc} + */ + public function getDataFixer(): ?DataFixerInterface + { + return $this->dataFixer; + } + + /** + * Build search request used to check invalid session data. + * + * @param int $storeId Store id. + * + * @return RequestInterface + */ + private function getSearchRequest($storeId): RequestInterface + { + $queryFilters = ['session_id' => 'null']; + + return $this->searchRequestBuilder->create($storeId, SessionIndexInterface::INDEX_IDENTIFIER, 0, 0, null, [], [], $queryFilters); + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Fixer/DataFixerInterface.php b/src/module-elasticsuite-tracker/Model/Data/Fixer/DataFixerInterface.php new file mode 100644 index 000000000..e1b3062df --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Fixer/DataFixerInterface.php @@ -0,0 +1,36 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Fixer; + +/** + * Behavioral data fixer interface. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +interface DataFixerInterface +{ + /** + * Fix invalid data in a given behavioral index for a given store. + * Returns true if the data was fixed. + * + * @param int $storeId Store id. + * + * @return bool + */ + public function fixInvalidData(int $storeId): bool; +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Fixer/Event/DeleteUndefinedSessionId.php b/src/module-elasticsuite-tracker/Model/Data/Fixer/Event/DeleteUndefinedSessionId.php new file mode 100644 index 000000000..ae9b04ccb --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Fixer/Event/DeleteUndefinedSessionId.php @@ -0,0 +1,116 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Fixer\Event; + +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; +use Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder as QueryBuilder; +use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteTracker\Api\EventIndexInterface; +use Smile\ElasticsuiteTracker\Model\Data\Fixer\DataFixerInterface; + +/** + * Behavioral data fixer for undefined session ids in events. + * Delete strategy. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class DeleteUndefinedSessionId implements DataFixerInterface +{ + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * @var QueryBuilder + */ + private $queryBuilder; + + /** + * @var IndexSettingsInterface + */ + private $indexSettings; + + /** + * @var ClientInterface + */ + private $client; + + /** + * Constructor. + * + * @param QueryFactory $queryFactory Query factory. + * @param QueryBuilder $queryBuilder Query Builder. + * @param IndexSettingsInterface $indexSettings Index settings. + * @param ClientInterface $client Elasticsearch client. + */ + public function __construct( + QueryFactory $queryFactory, + QueryBuilder $queryBuilder, + IndexSettingsInterface $indexSettings, + ClientInterface $client + ) { + $this->queryFactory = $queryFactory; + $this->queryBuilder = $queryBuilder; + $this->indexSettings = $indexSettings; + $this->client = $client; + } + + /** + * {@inheritDoc} + */ + public function fixInvalidData(int $storeId): bool + { + $success = true; + + try { + $indexAlias = $this->indexSettings->getIndexAliasFromIdentifier( + EventIndexInterface::INDEX_IDENTIFIER, + $storeId + ); + + $query = $this->queryBuilder->buildQuery( + $this->queryFactory->create( + QueryInterface::TYPE_BOOL, + [ + 'should' => [ + $this->queryFactory->create(QueryInterface::TYPE_MISSING, ['field' => 'session.uid']), + $this->queryFactory->create(QueryInterface::TYPE_TERM, ['field' => 'session.uid', 'value' => 'null']), + ], + ] + ) + ); + + $indicesNames = $this->client->getIndicesNameByAlias($indexAlias); + foreach ($indicesNames as $indexName) { + $params = [ + 'index' => $indexName, + // 'type' => '_doc', + 'body' => ['query' => $query], + ]; + $this->client->deleteByQuery($params); + } + } catch (\Exception $e) { + $success = false; + } + + return $success; + } +} diff --git a/src/module-elasticsuite-tracker/Model/Data/Fixer/Session/DeleteUndefinedSessionId.php b/src/module-elasticsuite-tracker/Model/Data/Fixer/Session/DeleteUndefinedSessionId.php new file mode 100644 index 000000000..b8e3afde6 --- /dev/null +++ b/src/module-elasticsuite-tracker/Model/Data/Fixer/Session/DeleteUndefinedSessionId.php @@ -0,0 +1,107 @@ + + * @copyright 2023 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +declare(strict_types = 1); + +namespace Smile\ElasticsuiteTracker\Model\Data\Fixer\Session; + +use Smile\ElasticsuiteCore\Api\Client\ClientInterface; +use Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder as QueryBuilder; +use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteTracker\Api\SessionIndexInterface; +use Smile\ElasticsuiteTracker\Model\Data\Fixer\DataFixerInterface; + +/** + * Behavioral data fixer for undefined session ids in sessions. + * Delete strategy. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Richard Bayet + */ +class DeleteUndefinedSessionId implements DataFixerInterface +{ + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * @var QueryBuilder + */ + private $queryBuilder; + + /** + * @var IndexSettingsInterface + */ + private $indexSettings; + + /** + * @var ClientInterface + */ + private $client; + + /** + * Constructor. + * + * @param QueryFactory $queryFactory Query factory. + * @param QueryBuilder $queryBuilder Query Builder. + * @param IndexSettingsInterface $indexSettings Index settings. + * @param ClientInterface $client Elasticsearch client. + */ + public function __construct( + QueryFactory $queryFactory, + QueryBuilder $queryBuilder, + IndexSettingsInterface $indexSettings, + ClientInterface $client + ) { + $this->queryFactory = $queryFactory; + $this->queryBuilder = $queryBuilder; + $this->indexSettings = $indexSettings; + $this->client = $client; + } + + /** + * {@inheritDoc} + */ + public function fixInvalidData(int $storeId): bool + { + $success = true; + + try { + $indexAlias = $this->indexSettings->getIndexAliasFromIdentifier( + SessionIndexInterface::INDEX_IDENTIFIER, + $storeId + ); + + $query = $this->queryBuilder->buildQuery( + $this->queryFactory->create(QueryInterface::TYPE_TERM, ['field' => 'session_id', 'value' => 'null']) + ); + + $indicesNames = $this->client->getIndicesNameByAlias($indexAlias); + foreach ($indicesNames as $indexName) { + $this->client->deleteByQuery([ + 'index' => $indexName, + // 'type' => '_doc', + 'body' => ['query' => $query], + ]); + } + } catch (\Exception $e) { + $success = false; + } + + return $success; + } +} diff --git a/src/module-elasticsuite-tracker/etc/di.xml b/src/module-elasticsuite-tracker/etc/di.xml index 2aab6e00a..d5856f6c8 100644 --- a/src/module-elasticsuite-tracker/etc/di.xml +++ b/src/module-elasticsuite-tracker/etc/di.xml @@ -74,5 +74,46 @@ - + + + + Smile\ElasticsuiteTracker\Console\CheckData + Smile\ElasticsuiteTracker\Console\FixData + + + + + + + Smile\ElasticsuiteTracker\Model\Data\Checker\Proxy + + + + + + Smile\ElasticsuiteTracker\Model\Data\Checker\Proxy + + + + + + + Smile\ElasticsuiteTracker\Model\Data\Checker\Event\UndefinedSessionId + Smile\ElasticsuiteTracker\Model\Data\Checker\Session\UndefinedSessionId + + + + + + + \Smile\ElasticsuiteTracker\Model\Data\Fixer\Event\DeleteUndefinedSessionId + + + + + + Smile\ElasticsuiteTracker\Model\Data\Fixer\Session\DeleteUndefinedSessionId + + +