From ff6ec45575d037eb933c1421c21c0f97c04e8fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 25 Apr 2025 18:23:37 +0200 Subject: [PATCH] Add center filtering logic to export generation Introduced a `filterStatsByCenters` configuration in `ExportGenerator` to enable conditional center filtering during exports. Updated related methods and tests to account for this parameter, ensuring compatibility with both filtered and unfiltered scenarios. --- .../Controller/ExportController.php | 10 +- .../Export/ExportGenerator.php | 15 +- .../Tests/Export/ExportGeneratorTest.php | 142 +++++++++++++++++- 3 files changed, 158 insertions(+), 9 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/ExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportController.php index 3f7cfc186..66f0c8507 100644 --- a/src/Bundle/ChillMainBundle/Controller/ExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/ExportController.php @@ -114,7 +114,6 @@ class ExportController extends AbstractController */ protected function createCreateFormExport(string $alias, string $step, array $data, ?SavedExport $savedExport): FormInterface { - /** @var ExportManager $exportManager */ $exportManager = $this->exportManager; $isGenerate = str_starts_with($step, 'generate_'); $canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT); @@ -126,7 +125,7 @@ class ExportController extends AbstractController $options = match ($step) { 'export', 'generate_export' => [ 'export_alias' => $alias, - 'picked_centers' => $this->exportFormHelper->getPickedCenters($data), + 'picked_centers' => $this->filterStatsByCenters ? $this->exportFormHelper->getPickedCenters($data): [], 'can_edit_full' => $canEditFull, 'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']), 'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']), @@ -314,6 +313,7 @@ class ExportController extends AbstractController $dataCenters, $dataExport, $dataFormatter, + $savedExport, ); $this->entityManager->persist( @@ -344,7 +344,7 @@ class ExportController extends AbstractController * @param array $dataExport Raw data from export step * @param array $dataFormatter Raw data from formatter step */ - private function buildExportDataForNormalization(string $alias, array $dataCenters, array $dataExport, array $dataFormatter): array + private function buildExportDataForNormalization(string $alias, array|null $dataCenters, array $dataExport, array $dataFormatter, ?SavedExport $savedExport): array { if ($this->filterStatsByCenters) { $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null); @@ -360,7 +360,7 @@ class ExportController extends AbstractController $dataCenters = ['centers' => [], 'regroupments' => []]; } - $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, null); + $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport); $formExport->submit($dataExport); $dataExport = $formExport->getData(); @@ -369,7 +369,7 @@ class ExportController extends AbstractController $alias, 'generate_formatter', $dataExport, - null, + $savedExport ); $formFormatter->submit($dataFormatter); $dataFormatter = $formFormatter->getData(); diff --git a/src/Bundle/ChillMainBundle/Export/ExportGenerator.php b/src/Bundle/ChillMainBundle/Export/ExportGenerator.php index d01152016..1904379b3 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportGenerator.php +++ b/src/Bundle/ChillMainBundle/Export/ExportGenerator.php @@ -15,11 +15,14 @@ use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\Exception\UnauthorizedGenerationException; use Chill\MainBundle\Form\Type\Export\ExportType; +use Chill\MainBundle\Repository\CenterRepository; +use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\QueryBuilder; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\Response; /** @@ -27,6 +30,8 @@ use Symfony\Component\HttpFoundation\Response; */ final readonly class ExportGenerator { + private bool $filterStatsByCenters; + public function __construct( private ExportManager $exportManager, private ExportConfigNormalizer $configNormalizer, @@ -34,7 +39,11 @@ final readonly class ExportGenerator private AuthorizationHelperInterface $authorizationHelper, private CenterRegroupementResolver $centerRegroupementResolver, private ExportConfigProcessor $exportConfigProcessor, - ) {} + ParameterBagInterface $parameterBag, + private CenterRepositoryInterface $centerRepository, + ) { + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration { @@ -134,6 +143,10 @@ final readonly class ExportGenerator private function filterCenters(User $byUser, array $centers, array $regroupements, ExportInterface|DirectExportInterface $export): array { + if (!$this->filterStatsByCenters) { + return $this->centerRepository->findActive(); + } + $authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole())); if ($authorizedCenters->isEmpty()) { throw new UnauthorizedGenerationException('No authorized centers'); diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php index b1d709c79..97ad48cf8 100644 --- a/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php @@ -25,6 +25,7 @@ use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormatterInterface; +use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; use Doctrine\ORM\NativeQuery; @@ -33,6 +34,8 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Log\NullLogger; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; /** * @internal @@ -43,6 +46,13 @@ class ExportGeneratorTest extends TestCase { use ProphecyTrait; + private function buildParameter(bool $filterStat): ParameterBagInterface + { + return new ParameterBag( + ['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStat]]] + ); + } + public function testGenerateHappyScenario() { $initialData = ['initial' => 'test']; @@ -133,18 +143,22 @@ class ExportGeneratorTest extends TestCase $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + $generator = new ExportGenerator( $exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger(), $authorizationHelper->reveal(), new CenterRegroupementResolver(), - new ExportConfigProcessor($exportManager->reveal()) + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), ); $actual = $generator->generate('dummy', $initialData, $user); - self::assertInstanceOf(FormattedExportGeneration::class, $actual); self::assertEquals('export result', $actual->content); self::assertEquals('text/text', $actual->contentType); } @@ -201,13 +215,18 @@ class ExportGeneratorTest extends TestCase $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + $generator = new ExportGenerator( $exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger(), $authorizationHelper->reveal(), new CenterRegroupementResolver(), - new ExportConfigProcessor($exportManager->reveal()) + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), ); $actual = $generator->generate('dummy', $initialData, $user); @@ -250,6 +269,9 @@ class ExportGeneratorTest extends TestCase $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + $generator = new ExportGenerator( $exportManager->reveal(), $exportConfigNormalizer->reveal(), @@ -257,6 +279,8 @@ class ExportGeneratorTest extends TestCase $authorizationHelper->reveal(), new CenterRegroupementResolver(), new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), ); $actual = $generator->generate('dummy', $initialData, $user); @@ -265,4 +289,116 @@ class ExportGeneratorTest extends TestCase self::assertEquals('export result', $actual->content); self::assertEquals('text/text', $actual->contentType); } + + public function testGenerateHappyScenarioWithoutCenterFiltering() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [ + 'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']], + 'disabled_filter' => ['enabled' => false], + ], + 'aggregators' => [ + 'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']], + 'disabled_aggregator' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [], 'regroupments' => []], + ]; + $user = new User(); + $centerA = new Center(); + $centerB = new Center(); + + $export = $this->prophesize(ExportInterface::class); + $filter = $this->prophesize(FilterInterface::class); + $filter->applyOn()->willReturn('tagada'); + $aggregator = $this->prophesize(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('tsointsoin'); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(QueryBuilder::class); + + // required methods + $export->initiateQuery( + ['tagada', 'tsointsoin'], + Argument::that(function ($arg) use ($centerB, $centerA) { + if (!is_array($arg)) { + return false; + } + if (2 !== count($arg)) { + return false; + } + + foreach ($arg as $item) { + if ([] !== $item['circles']) { + return false; + } + if (!in_array($item['center'], [$centerA, $centerB], true)) { + return false; + } + } + + return true; + }), + ['key' => 'form1'], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->requiredRole()->willReturn('dummy_role'); + + $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + ['dummy_filter' => $formFilterData], + ['dummy_aggregator' => $formAggregatorData], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal()); + $exportManager->hasFilter('dummy_filter')->willReturn(true); + $exportManager->hasFilter('disabled_filter')->willReturn(true); + $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal()); + $exportManager->hasAggregator('dummy_aggregator')->willReturn(true); + $exportManager->hasAggregator('disabled_aggregator')->willReturn(true); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->shouldNotBeCalled(); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->willReturn([$centerA, $centerB])->shouldBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(false), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } }