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.
This commit is contained in:
Julien Fastré 2025-04-25 18:23:37 +02:00
parent f3fd18e6fb
commit ff6ec45575
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 158 additions and 9 deletions

View File

@ -114,7 +114,6 @@ class ExportController extends AbstractController
*/ */
protected function createCreateFormExport(string $alias, string $step, array $data, ?SavedExport $savedExport): FormInterface protected function createCreateFormExport(string $alias, string $step, array $data, ?SavedExport $savedExport): FormInterface
{ {
/** @var ExportManager $exportManager */
$exportManager = $this->exportManager; $exportManager = $this->exportManager;
$isGenerate = str_starts_with($step, 'generate_'); $isGenerate = str_starts_with($step, 'generate_');
$canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT); $canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT);
@ -126,7 +125,7 @@ class ExportController extends AbstractController
$options = match ($step) { $options = match ($step) {
'export', 'generate_export' => [ 'export', 'generate_export' => [
'export_alias' => $alias, 'export_alias' => $alias,
'picked_centers' => $this->exportFormHelper->getPickedCenters($data), 'picked_centers' => $this->filterStatsByCenters ? $this->exportFormHelper->getPickedCenters($data): [],
'can_edit_full' => $canEditFull, 'can_edit_full' => $canEditFull,
'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']), 'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']),
'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']), 'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']),
@ -314,6 +313,7 @@ class ExportController extends AbstractController
$dataCenters, $dataCenters,
$dataExport, $dataExport,
$dataFormatter, $dataFormatter,
$savedExport,
); );
$this->entityManager->persist( $this->entityManager->persist(
@ -344,7 +344,7 @@ class ExportController extends AbstractController
* @param array $dataExport Raw data from export step * @param array $dataExport Raw data from export step
* @param array $dataFormatter Raw data from formatter 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) { if ($this->filterStatsByCenters) {
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null); $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
@ -360,7 +360,7 @@ class ExportController extends AbstractController
$dataCenters = ['centers' => [], 'regroupments' => []]; $dataCenters = ['centers' => [], 'regroupments' => []];
} }
$formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, null); $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport);
$formExport->submit($dataExport); $formExport->submit($dataExport);
$dataExport = $formExport->getData(); $dataExport = $formExport->getData();
@ -369,7 +369,7 @@ class ExportController extends AbstractController
$alias, $alias,
'generate_formatter', 'generate_formatter',
$dataExport, $dataExport,
null, $savedExport
); );
$formFormatter->submit($dataFormatter); $formFormatter->submit($dataFormatter);
$dataFormatter = $formFormatter->getData(); $dataFormatter = $formFormatter->getData();

View File

@ -15,11 +15,14 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\Exception\UnauthorizedGenerationException; use Chill\MainBundle\Export\Exception\UnauthorizedGenerationException;
use Chill\MainBundle\Form\Type\Export\ExportType; 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\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
/** /**
@ -27,6 +30,8 @@ use Symfony\Component\HttpFoundation\Response;
*/ */
final readonly class ExportGenerator final readonly class ExportGenerator
{ {
private bool $filterStatsByCenters;
public function __construct( public function __construct(
private ExportManager $exportManager, private ExportManager $exportManager,
private ExportConfigNormalizer $configNormalizer, private ExportConfigNormalizer $configNormalizer,
@ -34,7 +39,11 @@ final readonly class ExportGenerator
private AuthorizationHelperInterface $authorizationHelper, private AuthorizationHelperInterface $authorizationHelper,
private CenterRegroupementResolver $centerRegroupementResolver, private CenterRegroupementResolver $centerRegroupementResolver,
private ExportConfigProcessor $exportConfigProcessor, 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 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 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())); $authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole()));
if ($authorizedCenters->isEmpty()) { if ($authorizedCenters->isEmpty()) {
throw new UnauthorizedGenerationException('No authorized centers'); throw new UnauthorizedGenerationException('No authorized centers');

View File

@ -25,6 +25,7 @@ use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormattedExportGeneration;
use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\ORM\NativeQuery; use Doctrine\ORM\NativeQuery;
@ -33,6 +34,8 @@ use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* @internal * @internal
@ -43,6 +46,13 @@ class ExportGeneratorTest extends TestCase
{ {
use ProphecyTrait; use ProphecyTrait;
private function buildParameter(bool $filterStat): ParameterBagInterface
{
return new ParameterBag(
['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStat]]]
);
}
public function testGenerateHappyScenario() public function testGenerateHappyScenario()
{ {
$initialData = ['initial' => 'test']; $initialData = ['initial' => 'test'];
@ -133,18 +143,22 @@ class ExportGeneratorTest extends TestCase
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$centerRepository->findActive()->shouldNotBeCalled();
$generator = new ExportGenerator( $generator = new ExportGenerator(
$exportManager->reveal(), $exportManager->reveal(),
$exportConfigNormalizer->reveal(), $exportConfigNormalizer->reveal(),
new NullLogger(), new NullLogger(),
$authorizationHelper->reveal(), $authorizationHelper->reveal(),
new CenterRegroupementResolver(), new CenterRegroupementResolver(),
new ExportConfigProcessor($exportManager->reveal()) new ExportConfigProcessor($exportManager->reveal()),
$this->buildParameter(true),
$centerRepository->reveal(),
); );
$actual = $generator->generate('dummy', $initialData, $user); $actual = $generator->generate('dummy', $initialData, $user);
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
self::assertEquals('export result', $actual->content); self::assertEquals('export result', $actual->content);
self::assertEquals('text/text', $actual->contentType); self::assertEquals('text/text', $actual->contentType);
} }
@ -201,13 +215,18 @@ class ExportGeneratorTest extends TestCase
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$centerRepository->findActive()->shouldNotBeCalled();
$generator = new ExportGenerator( $generator = new ExportGenerator(
$exportManager->reveal(), $exportManager->reveal(),
$exportConfigNormalizer->reveal(), $exportConfigNormalizer->reveal(),
new NullLogger(), new NullLogger(),
$authorizationHelper->reveal(), $authorizationHelper->reveal(),
new CenterRegroupementResolver(), new CenterRegroupementResolver(),
new ExportConfigProcessor($exportManager->reveal()) new ExportConfigProcessor($exportManager->reveal()),
$this->buildParameter(true),
$centerRepository->reveal(),
); );
$actual = $generator->generate('dummy', $initialData, $user); $actual = $generator->generate('dummy', $initialData, $user);
@ -250,6 +269,9 @@ class ExportGeneratorTest extends TestCase
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$centerRepository->findActive()->shouldNotBeCalled();
$generator = new ExportGenerator( $generator = new ExportGenerator(
$exportManager->reveal(), $exportManager->reveal(),
$exportConfigNormalizer->reveal(), $exportConfigNormalizer->reveal(),
@ -257,6 +279,8 @@ class ExportGeneratorTest extends TestCase
$authorizationHelper->reveal(), $authorizationHelper->reveal(),
new CenterRegroupementResolver(), new CenterRegroupementResolver(),
new ExportConfigProcessor($exportManager->reveal()), new ExportConfigProcessor($exportManager->reveal()),
$this->buildParameter(true),
$centerRepository->reveal(),
); );
$actual = $generator->generate('dummy', $initialData, $user); $actual = $generator->generate('dummy', $initialData, $user);
@ -265,4 +289,116 @@ class ExportGeneratorTest extends TestCase
self::assertEquals('export result', $actual->content); self::assertEquals('export result', $actual->content);
self::assertEquals('text/text', $actual->contentType); 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);
}
} }