Add auto-generated export descriptions and helper service

Introduce `ExportDescriptionHelper` to dynamically generate default descriptions for exports based on current settings. Update controllers, templates, and test cases to support and display the new auto-generated descriptions. This also adds a warning in the UI to prompt users to adjust these descriptions as needed.
This commit is contained in:
2025-05-26 16:44:50 +02:00
parent be448c650e
commit 3a016aa12a
7 changed files with 277 additions and 3 deletions

View File

@@ -0,0 +1,174 @@
<?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\Entity\User;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportConfigProcessor;
use Chill\MainBundle\Export\ExportDescriptionHelper;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class ExportDescriptionHelperTest extends TestCase
{
use ProphecyTrait;
private const JSON_HAPPY_SCENARIO = <<<'JSON'
{
"export": {
"form": [],
"version": 1
},
"centers": {
"centers": [
1
],
"regroupments": []
},
"filters": {
"my_filter_string": {
"form": {
"accepted_socialissues": [
2,
3
]
},
"enabled": true,
"version": 1
},
"my_filter_array": {
"form": {
"misc": true
},
"enabled": true,
"version": 1
},
"my_filter_translatable": {
"form": {
"misc": true
},
"enabled": true,
"version": 1
}
},
"formatter": {
"form": {
"format": "xlsx",
"activity_user_aggregator": {
"order": 1
}
},
"version": 1
},
"aggregators": {
"my_aggregator": {
"form": {"key": 1},
"enabled": true,
"version": 1
}
},
"pick_formatter": "spreadsheet"
}
JSON;
public function testDescribeHappyScenario(): void
{
$options = json_decode(self::JSON_HAPPY_SCENARIO, true);
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user = new User());
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
$exportConfigNormalizer->denormalizeConfig('my_export', Argument::type('array'))->willReturn($options);
$export = $this->prophesize(ExportInterface::class);
$export->getTitle()->willReturn('Title');
$myFilterString = $this->prophesize(FilterInterface::class);
$myFilterString->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn($string0 = 'This is a filter description');
$myFilterArray = $this->prophesize(FilterInterface::class);
$myFilterArray->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn([$string1 = 'This is a filter with %argument%', $arg1 = ['%argument%' => 'zero']]);
$myFilterTranslatable = $this->prophesize(FilterInterface::class);
$myFilterTranslatable->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))
->willReturn(new class () implements TranslatableInterface {
public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return 'translatable';
}
});
$myAggregator = $this->prophesize(AggregatorInterface::class);
$myAggregator->getTitle()->willReturn('Some aggregator');
$token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']);
$tokenStorage = new TokenStorage();
$tokenStorage->setToken($token);
$exportManager = new ExportManager(
new NullLogger(),
$security->reveal(),
$this->prophesize(AuthorizationHelperInterface::class)->reveal(),
$tokenStorage,
['my_export' => $export->reveal()],
['my_aggregator' => $myAggregator->reveal()],
[
'my_filter_string' => $myFilterString->reveal(),
'my_filter_array' => $myFilterArray->reveal(),
'my_filter_translatable' => $myFilterTranslatable->reveal(),
],
[],
);
$exportConfigProcessor = new ExportConfigProcessor($exportManager);
$translator = $this->prophesize(TranslatorInterface::class);
$translator->trans('Title')->shouldBeCalled()->willReturn('Title');
$translator->trans($string0)->shouldBeCalled()->willReturn($string0);
$translator->trans($string1, $arg1)->shouldBeCalled()->willReturn($string1);
$translator->trans('Some aggregator')->shouldBeCalled()->willReturn('Some aggregator');
$exportDescriptionHelper = new ExportDescriptionHelper(
$exportManager,
$exportConfigNormalizer->reveal(),
$exportConfigProcessor,
$translator->reveal(),
$security->reveal(),
);
$actual = $exportDescriptionHelper->describe('my_export', $options);
self::assertIsArray($actual);
self::assertEquals($actual[0], 'Title');
self::assertEquals($actual[1], 'This is a filter description');
self::assertEquals($actual[2], 'This is a filter with %argument%');
self::assertEquals($actual[3], 'translatable');
self::assertEquals($actual[4], 'Some aggregator');
}
}

View File

@@ -348,7 +348,6 @@ final class ExportManagerTest extends KernelTestCase
$logger ?? self::getContainer()->get(LoggerInterface::class),
$authorizationChecker ?? self::getContainer()->get('security.authorization_checker'),
$authorizationHelper ?? self::getContainer()->get('chill.main.security.authorization.helper'),
$tokenStorage,
$exports,
$aggregators,
$filters,