Skip to content

Commit

Permalink
pia/smartbooster/sandbox-gp#13 Add CleanMonitoringCommand to delete c…
Browse files Browse the repository at this point in the history
…ron, clean api calls or other entity easily through a bundle configuration
  • Loading branch information
mathieu-ducrot committed Sep 23, 2024
1 parent 7bf47c9 commit 1c25895
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_clean_monitoring_command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Added
- `CleanMonitoringCommand` to delete cron, clean api calls or other entity easily through a bundle configuration
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"symfony/form": "^5.4|^6.2",
"symfony/framework-bundle": "^5.4|^6.2",
"symfony/security-bundle": "^5.4|^6.2",
"symfony/translation": "^5.4|^6.2",
"theofidry/alice-data-fixtures": "^1.5"
},
"require-dev": {
Expand Down
7 changes: 7 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ services:
# Service Fidry\AliceDataFixtures\Loader\PurgerLoader not exist, and it must be aliased. AbstractFixtures need it.
Fidry\AliceDataFixtures\Loader\PurgerLoader: '@fidry_alice_data_fixtures.doctrine.purger_loader'
# Command
Smart\CoreBundle\Command\CleanMonitoringCommand:
arguments:
- '@Smart\CoreBundle\Monitoring\ProcessMonitor'
- '@Smart\CoreBundle\Config\IniOverrideConfig'
- '@Doctrine\ORM\EntityManagerInterface'
- '@translator'
tags: [ 'console.command' ]
Smart\CoreBundle\Command\CommandPoolHelper:
arguments:
- '@Symfony\Component\HttpKernel\KernelInterface'
Expand Down
158 changes: 158 additions & 0 deletions src/Command/CleanMonitoringCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

namespace Smart\CoreBundle\Command;

use App\Entity\Monitoring\ApiCall;
use App\Entity\Monitoring\Cron;
use Doctrine\ORM\EntityManagerInterface;
use Smart\CoreBundle\Config\IniOverrideConfig;
use Smart\CoreBundle\Entity\ProcessInterface;
use Smart\CoreBundle\Monitoring\ProcessMonitor;
use Smart\CoreBundle\Utils\StringUtils;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Contracts\Translation\TranslatorInterface;

#[AsCommand(
name: 'cron:smart-clean-monitoring',
description: 'Commande de nettoyage des process de monitoring (ou autre entité) en base.',
)]
class CleanMonitoringCommand extends Command
{
public const BATCH_SIZE = 50;

/**
* @var array
* Example of configuration :
* smart_clean_monitoring:
* older_than: 1 week
* count_organization:
* older_than: 1 day
* api_organization_update:
* older_than: 1 week
* class: api_call
* where: o.status = 'success'
* properties_to_clean:
* - logs
* - data
* - inputData
* - headers
* - outputResponse
*/
private array $commandConfigs = [];

public function __construct(
private readonly ProcessMonitor $processMonitor,
protected readonly IniOverrideConfig $config,
private readonly EntityManagerInterface $entityManager,
private readonly TranslatorInterface $translator
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$exitCode = Command::SUCCESS;
$io = new SymfonyStyle($input, $output);
$this->processMonitor->setConsoleIo($io);
$this->config->increaseMemoryLimit();
$process = $this->processMonitor->start(new Cron($this->getName()), true); // @phpstan-ignore-line

try {
$nbCleanedEntities = 0;
$propertyAccessor = PropertyAccess::createPropertyAccessor();

foreach ($this->commandConfigs as $key => $config) {
$targetClass = $this->getTargetClass($config);
$olderThanProperty = $config['older_than_property'];
$olderThan = $config['older_than'];
$dateTime = new \DateTime();
$dateTime->modify('-' . $olderThan);

$qb = $this->entityManager->getRepository($targetClass)->createQueryBuilder('o')
->andWhere("o.$olderThanProperty <= :started_at")
->setParameter('started_at', $dateTime)
;
// MDT if it is a monitoring entity, we used the key of the config to filter the type
$isProcess = in_array(ProcessInterface::class, class_implements($targetClass));
if ($isProcess) {
if ($targetClass === Cron::class) { // @phpstan-ignore-line
$key = str_replace('_', '-', $key);
}
$qb->andWhere('o.type = :type')->setParameter('type', $key);
}
$whereCondition = $config['where'];
if ($whereCondition !== null) {
$qb->andWhere($whereCondition);
}
$entities = $qb->getQuery()->getResult();
$nbEntities = count($entities);
$translatedTargetClass = $this->translator->trans('label.' . StringUtils::getEntityShortName($targetClass) . 's');
$this->processMonitor->logSection(sprintf(
"%d %s%s à nettoyer depuis %s%s",
$nbEntities,
$translatedTargetClass,
$isProcess ? (' ' . $key) : '',
$olderThan,
$whereCondition ? " (avec la condition $whereCondition)" : ''
));

$i = 1;
$propertiesToClean = $config['properties_to_clean'];
$removeEntity = empty($propertiesToClean);
foreach ($entities as $entity) {
$this->processMonitor->log(sprintf("%d) #%d début nettoyage ...", $i, $entity->getId()));
if ($removeEntity) {
$this->entityManager->remove($entity);
} else {
foreach ($propertiesToClean as $property) {
$propertyAccessor->setValue($entity, $property, null);
}
}
if ($i % self::BATCH_SIZE === 0) {
$this->entityManager->flush();
}
$nbCleanedEntities++;
$i++;
}
$this->entityManager->flush();
if ($nbEntities > 0) {
$this->processMonitor->log("Fin du nettoyage des $translatedTargetClass.");
}
}

$this->processMonitor->logSuccess("$nbCleanedEntities entités nettoyées.");
} catch (\Exception $e) {
$exitCode = Command::FAILURE;
$this->processMonitor->logException($e);
} finally {
$this->processMonitor->end($process, $exitCode === Command::SUCCESS);
}

return $exitCode;
}

/**
* @return class-string<object>
*/
private function getTargetClass(array $config): string
{
$class = $config['class'];
if ($class === 'cron') {
return Cron::class; // @phpstan-ignore-line
} elseif ($class === 'api_call') {
return ApiCall::class; // @phpstan-ignore-line
} else {
return $class;
}
}

public function setCommandConfigs(array $commandConfigs): void
{
$this->commandConfigs = $commandConfigs;
}
}
21 changes: 21 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Smart\CoreBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

Expand All @@ -22,9 +23,29 @@ public function getConfigTreeBuilder(): TreeBuilder
->prototype('scalar')->end()
->defaultValue([])
->end()
->append($this->getCleanMonitoringCommandConfigsDefinition())
->end()
;

return $treeBuilder;
}

private function getCleanMonitoringCommandConfigsDefinition(): ArrayNodeDefinition
{
return (new TreeBuilder('clean_monitoring_command_configs'))->getRootNode()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('older_than')->isRequired()->end()
->scalarNode('older_than_property')->defaultValue('startedAt')->end()
->scalarNode('class')->defaultValue('cron')->end()
->scalarNode('where')->defaultNull()->end()
->arrayNode('properties_to_clean')
->scalarPrototype()->end()
->end()
->end()
->end()
;
}
}
4 changes: 4 additions & 0 deletions src/DependencyInjection/SmartCoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Smart\CoreBundle\DependencyInjection;

use Smart\CoreBundle\Command\CleanMonitoringCommand;
use Smart\CoreBundle\Monitoring\ApiCallMonitor;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
Expand All @@ -21,5 +22,8 @@ public function load(array $configs, ContainerBuilder $container): void
$config = $this->processConfiguration(new Configuration(), $configs);
$apiCallMonitor = $container->getDefinition(ApiCallMonitor::class);
$apiCallMonitor->addMethodCall('setRestartAllowedRoutes', [$config['monitoring_api_restart_allowed_routes']]);

$cleanMonitoringCommand = $container->getDefinition(CleanMonitoringCommand::class);
$cleanMonitoringCommand->addMethodCall('setCommandConfigs', [$config['clean_monitoring_command_configs']]);
}
}
Empty file removed translations/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions translations/messages.fr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cron:
smart_clean_monitoring.label: Commande de nettoyage

0 comments on commit 1c25895

Please sign in to comment.