Refactor ExportController to handle async generation of exports

This commit is contained in:
Julien Fastré 2025-03-13 14:18:23 +01:00
parent 46ebfca28f
commit db073fc920
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\SavedExport;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\DirectExportInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportFormHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager;
@ -36,11 +37,11 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
@ -69,6 +70,7 @@ class ExportController extends AbstractController
ParameterBagInterface $parameterBag,
private readonly MessageBusInterface $messageBus,
private readonly ClockInterface $clock,
private readonly ExportConfigNormalizer $exportConfigNormalizer,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
@ -76,6 +78,12 @@ class ExportController extends AbstractController
#[Route(path: '/{_locale}/exports/download/{alias}', name: 'chill_main_export_download', methods: ['GET'])]
public function downloadResultAction(Request $request, mixed $alias)
{
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new UnauthorizedHttpException('Only regular users can generate exports');
}
/** @var ExportManager $exportManager */
$exportManager = $this->exportManager;
$export = $exportManager->getExport($alias);
@ -84,6 +92,20 @@ class ExportController extends AbstractController
[$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key, $savedExport);
$config = [
'centers' => $dataCenters,
'export' => $dataExport['export']['export'] ?? [],
'filters' => $dataExport['export']['filters'] ?? [],
'aggregators' => $dataExport['export']['aggregators'] ?? [],
'formatter' => $dataFormatter,
];
$generation = new ExportGeneration('alias', $config, $this->clock->now()->add(new \DateInterval('P6M')));
$this->entityManager->persist($generation);
$this->entityManager->flush();
$this->messageBus->dispatch(new ExportRequestGenerationMessage($generation, $user));
$formatterAlias = $exportManager->getFormatterAlias($dataExport['export']);
if (null !== $formatterAlias) {
@ -282,7 +304,7 @@ class ExportController extends AbstractController
'formatter', 'generate_formatter' => [
'export_alias' => $alias,
'formatter_alias' => $exportManager->getFormatterAlias($data['export']),
'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']),
'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']['aggregators']),
],
default => [
'export_alias' => $alias,
@ -437,13 +459,14 @@ class ExportController extends AbstractController
* and redirect to the `generate` action.
*
* The data from previous steps is removed from session.
*
* @param string $alias
*
* @return RedirectResponse
*/
private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport)
private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport): Response
{
$user = $this->getUser();
if (!$user instanceof User) {
throw new AccessDeniedHttpException('only regular users can generate export');
}
$dataCenters = $this->session->get('centers_step_raw', null);
$dataFormatter = $this->session->get('formatter_step_raw', null);
$dataExport = $this->session->get('export_step_raw', null);
@ -456,28 +479,75 @@ class ExportController extends AbstractController
]);
}
$parameters = [
'formatter' => $dataFormatter ?? [],
'export' => $dataExport ?? [],
'centers' => $dataCenters ?? [],
'alias' => $alias,
];
unset($parameters['_token']);
$key = md5(uniqid((string) random_int(0, mt_getrandmax()), false));
$dataToNormalize = $this->buildExportDataForNormalization(
$alias,
$dataCenters,
$dataExport,
$dataFormatter,
);
$this->redis->setEx($key, 3600, \serialize($parameters));
$this->entityManager->persist(
$exportGeneration = new ExportGeneration(
$alias,
$this->exportConfigNormalizer->normalizeConfig($alias, $dataToNormalize),
$this->clock->now()->add(new \DateInterval('P6M')),
)
);
$this->entityManager->flush();
$this->messageBus->dispatch(new ExportRequestGenerationMessage($exportGeneration, $user));
// remove data from session
$this->session->remove('centers_step_raw');
$this->session->remove('export_step_raw');
$this->session->remove('export_step');
$this->session->remove('formatter_step_raw');
$this->session->remove('formatter_step');
return $this->redirectToRoute('chill_main_export_download', [
'key' => $key,
'alias' => $alias,
'from_saved' => $savedExport?->getId(),
]);
return new Response('ok: '.$exportGeneration->getId());
}
/**
* Build the export form data into a way suitable for normalization.
*
* @param string $alias the export alias
* @param array $dataCenters Raw data from center step
* @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
{
if ($this->filterStatsByCenters) {
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
$formCenters->submit($dataCenters);
$dataCenters = $formCenters->getData();
} else {
$dataCenters = ['centers' => []];
}
$formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, null);
$formExport->submit($dataExport);
$dataExport = $formExport->getData();
if (\count($dataFormatter) > 0) {
$formFormatter = $this->createCreateFormExport(
$alias,
'generate_formatter',
$dataExport,
null,
);
$formFormatter->submit($dataFormatter);
$dataFormatter = $formFormatter->getData();
}
dump($dataExport);
return [
'centers' => $dataCenters['centers'],
'export' => $dataExport['export']['export'] ?? [],
'filters' => $dataExport['export']['filters'] ?? [],
'aggregators' => $dataExport['export']['aggregators'] ?? [],
'pick_formatter' => $dataExport['export']['pick_formatter']['alias'],
'formatter' => $dataFormatter['formatter'] ?? [],
];
}
private function rebuildData($key, ?SavedExport $savedExport)