mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-22 23:53:50 +00:00
[export] sort filters and aggregators by title
This commit is contained in:
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
@@ -54,20 +55,17 @@ class ExportManager
|
||||
*/
|
||||
private array $formatters = [];
|
||||
|
||||
private readonly string|\Stringable|\Symfony\Component\Security\Core\User\UserInterface $user;
|
||||
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly AuthorizationCheckerInterface $authorizationChecker,
|
||||
private readonly AuthorizationHelperInterface $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
iterable $exports,
|
||||
iterable $aggregators,
|
||||
iterable $filters
|
||||
// iterable $formatters,
|
||||
// iterable $exportElementProvider
|
||||
) {
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->exports = iterator_to_array($exports);
|
||||
$this->aggregators = iterator_to_array($aggregators);
|
||||
$this->filters = iterator_to_array($filters);
|
||||
@@ -91,20 +89,24 @@ class ExportManager
|
||||
*
|
||||
* @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias
|
||||
*/
|
||||
public function &getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): iterable
|
||||
public function getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
|
||||
{
|
||||
if ($export instanceof DirectExportInterface) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
$filters = [];
|
||||
|
||||
foreach ($this->filters as $alias => $filter) {
|
||||
if (
|
||||
\in_array($filter->applyOn(), $export->supportsModifiers(), true)
|
||||
&& $this->isGrantedForElement($filter, $export, $centers)
|
||||
) {
|
||||
yield $alias => $filter;
|
||||
$filters[$alias] = $filter;
|
||||
}
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,22 +114,26 @@ class ExportManager
|
||||
*
|
||||
* @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array
|
||||
*
|
||||
* @return iterable<string, AggregatorInterface>|null a \Generator that contains aggretagors. The key is the filter's alias
|
||||
* @return array<string, AggregatorInterface> an array that contains aggregators. The key is the filter's alias
|
||||
*/
|
||||
public function &getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): ?iterable
|
||||
public function getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
|
||||
{
|
||||
if ($export instanceof ListInterface || $export instanceof DirectExportInterface) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
$aggregators = [];
|
||||
|
||||
foreach ($this->aggregators as $alias => $aggregator) {
|
||||
if (
|
||||
\in_array($aggregator->applyOn(), $export->supportsModifiers(), true)
|
||||
&& $this->isGrantedForElement($aggregator, $export, $centers)
|
||||
) {
|
||||
yield $alias => $aggregator;
|
||||
$aggregators[$alias] = $aggregator;
|
||||
}
|
||||
}
|
||||
|
||||
return $aggregators;
|
||||
}
|
||||
|
||||
public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void
|
||||
@@ -347,6 +353,17 @@ class ExportManager
|
||||
return $this->filters[$alias];
|
||||
}
|
||||
|
||||
public function getAllFilters(): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
foreach ($this->filters as $alias => $filter) {
|
||||
$filters[$alias] = $filter;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* get all filters.
|
||||
*
|
||||
@@ -452,7 +469,7 @@ class ExportManager
|
||||
if (null === $centers || [] === $centers) {
|
||||
// we want to try if at least one center is reachable
|
||||
return [] !== $this->authorizationHelper->getReachableCenters(
|
||||
$this->user,
|
||||
$this->tokenStorage->getToken()->getUser(),
|
||||
$role
|
||||
);
|
||||
}
|
||||
@@ -484,11 +501,17 @@ class ExportManager
|
||||
{
|
||||
$r = [];
|
||||
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($centers as $center) {
|
||||
$r[] = [
|
||||
'center' => $center,
|
||||
'circles' => $this->authorizationHelper->getReachableScopes(
|
||||
$this->user,
|
||||
$user,
|
||||
$element->requiredRole(),
|
||||
$center
|
||||
),
|
||||
|
37
src/Bundle/ChillMainBundle/Export/SortExportElement.php
Normal file
37
src/Bundle/ChillMainBundle/Export/SortExportElement.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final readonly class SortExportElement
|
||||
{
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<int|string, FilterInterface> $elements
|
||||
*/
|
||||
public function sortFilters(array &$elements): void
|
||||
{
|
||||
uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, AggregatorInterface> $elements
|
||||
*/
|
||||
public function sortAggregators(array &$elements): void
|
||||
{
|
||||
uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Form\Type\Export;
|
||||
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\SortExportElement;
|
||||
use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
@@ -28,15 +29,7 @@ class ExportType extends AbstractType
|
||||
|
||||
final public const PICK_FORMATTER_KEY = 'pick_formatter';
|
||||
|
||||
/**
|
||||
* @var ExportManager
|
||||
*/
|
||||
protected $exportManager;
|
||||
|
||||
public function __construct(ExportManager $exportManager)
|
||||
{
|
||||
$this->exportManager = $exportManager;
|
||||
}
|
||||
public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
@@ -58,12 +51,12 @@ class ExportType extends AbstractType
|
||||
if ($export instanceof \Chill\MainBundle\Export\ExportInterface) {
|
||||
// add filters
|
||||
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
|
||||
$this->sortExportElement->sortFilters($filters);
|
||||
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]);
|
||||
|
||||
foreach ($filters as $alias => $filter) {
|
||||
$filterBuilder->add($alias, FilterType::class, [
|
||||
'filter_alias' => $alias,
|
||||
'export_manager' => $this->exportManager,
|
||||
'filter' => $filter,
|
||||
'label' => $filter->getTitle(),
|
||||
'constraints' => [
|
||||
new ExportElementConstraint(['element' => $filter]),
|
||||
@@ -76,6 +69,7 @@ class ExportType extends AbstractType
|
||||
// add aggregators
|
||||
$aggregators = $this->exportManager
|
||||
->getAggregatorsApplyingOn($export, $options['picked_centers']);
|
||||
$this->sortExportElement->sortAggregators($aggregators);
|
||||
$aggregatorBuilder = $builder->create(
|
||||
self::AGGREGATOR_KEY,
|
||||
FormType::class,
|
||||
|
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Form\Type\Export;
|
||||
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
@@ -25,8 +26,7 @@ class FilterType extends AbstractType
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$exportManager = $options['export_manager'];
|
||||
$filter = $exportManager->getFilter($options['filter_alias']);
|
||||
$filter = $options['filter'];
|
||||
|
||||
$builder
|
||||
->add(self::ENABLED_FIELD, CheckboxType::class, [
|
||||
@@ -46,8 +46,9 @@ class FilterType extends AbstractType
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired('filter_alias')
|
||||
->setRequired('export_manager')
|
||||
$resolver
|
||||
->setRequired('filter')
|
||||
->setAllowedTypes('filter', [FilterInterface::class])
|
||||
->setDefault('compound', true)
|
||||
->setDefault('error_bubbling', false);
|
||||
}
|
||||
|
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Export\SortExportElement;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class SortExportElementTest extends KernelTestCase
|
||||
{
|
||||
private SortExportElement $sortExportElement;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
$this->sortExportElement = new SortExportElement($this->makeTranslator());
|
||||
}
|
||||
|
||||
public function testSortFilterRealData(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$sorter = self::$container->get(SortExportElement::class);
|
||||
$translator = self::$container->get(TranslatorInterface::class);
|
||||
$exportManager = self::$container->get(ExportManager::class);
|
||||
$filters = $exportManager->getAllFilters();
|
||||
|
||||
$sorter->sortFilters($filters);
|
||||
|
||||
$previousName = null;
|
||||
foreach ($filters as $filter) {
|
||||
if (null === $previousName) {
|
||||
$previousName = $translator->trans($filter->getTitle());
|
||||
continue;
|
||||
}
|
||||
|
||||
$current = $translator->trans($filter->getTitle());
|
||||
if ($current === $previousName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::assertEquals(-1, $previousName <=> $current, sprintf("comparing '%s' and '%s'", $previousName, $current));
|
||||
$previousName = $current;
|
||||
}
|
||||
}
|
||||
|
||||
public function testSortAggregator(): void
|
||||
{
|
||||
$aggregators = [
|
||||
'foo' => $a = $this->makeAggregator('a'),
|
||||
'zop' => $q = $this->makeAggregator('q'),
|
||||
'bar' => $c = $this->makeAggregator('c'),
|
||||
'baz' => $b = $this->makeAggregator('b'),
|
||||
];
|
||||
|
||||
$this->sortExportElement->sortAggregators($aggregators);
|
||||
|
||||
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($aggregators));
|
||||
self::assertSame($a, $aggregators['foo']);
|
||||
self::assertSame($b, $aggregators['baz']);
|
||||
self::assertSame($c, $aggregators['bar']);
|
||||
self::assertSame($q, $aggregators['zop']);
|
||||
}
|
||||
|
||||
public function testSortFilter(): void
|
||||
{
|
||||
$filters = [
|
||||
'foo' => $a = $this->makeFilter('a'),
|
||||
'zop' => $q = $this->makeFilter('q'),
|
||||
'bar' => $c = $this->makeFilter('c'),
|
||||
'baz' => $b = $this->makeFilter('b'),
|
||||
];
|
||||
|
||||
$this->sortExportElement->sortFilters($filters);
|
||||
|
||||
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($filters));
|
||||
self::assertSame($a, $filters['foo']);
|
||||
self::assertSame($b, $filters['baz']);
|
||||
self::assertSame($c, $filters['bar']);
|
||||
self::assertSame($q, $filters['zop']);
|
||||
}
|
||||
|
||||
private function makeTranslator(): TranslatorInterface
|
||||
{
|
||||
return new class () implements TranslatorInterface {
|
||||
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
|
||||
{
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return 'en';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function makeAggregator(string $title): AggregatorInterface
|
||||
{
|
||||
return new class ($title) implements AggregatorInterface {
|
||||
public function __construct(private readonly string $title) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, mixed $data)
|
||||
{
|
||||
return fn ($v) => $v;
|
||||
}
|
||||
|
||||
public function getQueryKeys($data)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data) {}
|
||||
|
||||
public function applyOn()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function makeFilter(string $title): FilterInterface
|
||||
{
|
||||
return new class ($title) implements FilterInterface {
|
||||
public function __construct(private readonly string $title) {}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
return ['a', []];
|
||||
}
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data) {}
|
||||
|
||||
public function applyOn()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -54,3 +54,5 @@ services:
|
||||
- { name: chill.export_formatter, alias: 'csv_pivoted_list' }
|
||||
|
||||
Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~
|
||||
|
||||
Chill\MainBundle\Export\SortExportElement: ~
|
||||
|
Reference in New Issue
Block a user