Add notification to user groups on workflow transition

Implemented NotificationToUserGroupsOnTransition to send group emails upon workflow completion. Also updated NotificationOnTransition to prevent double notifications and created a unit test for the new functionality.
This commit is contained in:
Julien Fastré 2024-10-18 19:25:03 +02:00
parent 29fa086fde
commit 91a4b45607
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
4 changed files with 247 additions and 2 deletions

View File

@ -0,0 +1,9 @@
Chers membres du groupe {{ user_group.label|localize_translatable_string }},
Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ place.text }}
Vous pouvez visualiser le workflow sur cette page:
{{ absolute_url(path('chill_main_workflow_show', {'id': entity_workflow.id, '_locale': 'fr'})) }}
Cordialement,

View File

@ -0,0 +1,141 @@
<?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\Workflow\EventSubscriber;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\EventSubscriber\NotificationToUserGroupsOnTransition;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\BodyRendererInterface;
use Symfony\Component\Mime\RawMessage;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
use Twig\Environment;
/**
* @internal
*
* @coversNothing
*/
class NotificationToUserGroupsOnTransitionTest extends KernelTestCase
{
use ProphecyTrait;
private Environment $twig;
private BodyRendererInterface $bodyRenderer;
protected function setUp(): void
{
self::bootKernel();
$this->twig = self::getContainer()->get('twig');
$this->bodyRenderer = self::getContainer()->get(BodyRendererInterface::class);
}
public function testOnCompletedSendNotificationToUserGroupWithEmailAddress(): void
{
$entityWorkflow = new EntityWorkflow();
$reflection = new \ReflectionClass($entityWorkflow);
$idProperty = $reflection->getProperty('id');
$idProperty->setValue($entityWorkflow, 1);
$entityWorkflow->setWorkflowName('dummy');
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers = [$ug = new UserGroup()];
$ug->setEmail('test@email.com')->setLabel(['fr' => 'test group']);
$mailer = $this->prophesize(MailerInterface::class);
$sendMethod = $mailer->send(Argument::that(function (RawMessage $message): bool {
if (!$message instanceof TemplatedEmail) {
return false;
}
$this->bodyRenderer->render($message);
return 'test@email.com' === $message->getTo()[0]->getAddress();
}));
$sendMethod->shouldBeCalledOnce();
$metadataExtractor = $this->prophesize(MetadataExtractor::class);
$metadataExtractor->buildArrayPresentationForWorkflow(Argument::type(Workflow::class))->willReturn(['name' => 'dummy', 'text' => 'Dummy Workflow']);
$metadataExtractor->buildArrayPresentationForPlace($entityWorkflow)->willReturn(['name' => 'to_one', 'text' => 'Dummy Place']);
$registry = $this->buildRegistryWithEventSubscriber($mailer->reveal(), $metadataExtractor->reveal());
$workflow = $registry->get($entityWorkflow, 'dummy');
$workflow->apply($entityWorkflow, 'to_one', ['context' => $dto, 'transition' => 'to_one', 'transitionAt' => new \DateTimeImmutable(), 'byUser' => new User()]);
}
public function testOnCompletedSendNotificationToUserGroupWithoutAnyEmailAddress(): void
{
$entityWorkflow = new EntityWorkflow();
$reflection = new \ReflectionClass($entityWorkflow);
$idProperty = $reflection->getProperty('id');
$idProperty->setValue($entityWorkflow, 1);
$entityWorkflow->setWorkflowName('dummy');
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers = [$ug = new UserGroup()];
$mailer = $this->prophesize(MailerInterface::class);
$mailer->send(Argument::any())->shouldNotBeCalled();
$metadataExtractor = $this->prophesize(MetadataExtractor::class);
$metadataExtractor->buildArrayPresentationForWorkflow(Argument::type(Workflow::class))->willReturn(['name' => 'dummy', 'text' => 'Dummy Workflow']);
$metadataExtractor->buildArrayPresentationForPlace($entityWorkflow)->willReturn(['name' => 'to_one', 'text' => 'Dummy Place']);
$registry = $this->buildRegistryWithEventSubscriber($mailer->reveal(), $metadataExtractor->reveal());
$workflow = $registry->get($entityWorkflow, 'dummy');
$workflow->apply($entityWorkflow, 'to_one', ['context' => $dto, 'transition' => 'to_one', 'transitionAt' => new \DateTimeImmutable(), 'byUser' => new User()]);
}
public function buildRegistryWithEventSubscriber(MailerInterface $mailer, MetadataExtractor $metadataExtractor): Registry
{
$builder = new DefinitionBuilder();
$builder
->setInitialPlaces('initial')
->addPlaces(['initial', 'to_one'])
->addTransition(new Transition('to_one', 'initial', 'to_one'));
$metadata = new InMemoryMetadataStore(
['label' => ['fr' => 'dummy workflow']],
);
$builder->setMetadataStore($metadata);
$workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher = new EventDispatcher(), 'dummy');
$registry = new Registry();
$registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface {
public function supports(WorkflowInterface $workflow, object $subject): bool
{
return true;
}
});
$notificationEventSubscriber = new NotificationToUserGroupsOnTransition($this->twig, $metadataExtractor, $registry, $mailer);
$eventDispatcher->addSubscriber($notificationEventSubscriber);
return $registry;
}
}

View File

@ -42,8 +42,8 @@ class NotificationOnTransition implements EventSubscriberInterface
/** /**
* Send a notification to:. * Send a notification to:.
* *
* * the dests of the new step; * * the dests of the new step, or the members of a user group if the user group has no email;
* * the users which subscribed to workflow, on each step, or on final * * the users which subscribed to workflow, on each step, or on final;
* *
* **Warning** take care that this method must be executed **after** the dest users are added to * **Warning** take care that this method must be executed **after** the dest users are added to
* the step (@see{EntityWorkflowStep::addDestUser}). Currently, this is done during * the step (@see{EntityWorkflowStep::addDestUser}). Currently, this is done during
@ -74,6 +74,11 @@ class NotificationOnTransition implements EventSubscriberInterface
// the users within groups // the users within groups
$entityWorkflow->getCurrentStep()->getDestUserGroups()->reduce( $entityWorkflow->getCurrentStep()->getDestUserGroups()->reduce(
function (array $accumulator, UserGroup $userGroup) { function (array $accumulator, UserGroup $userGroup) {
if ($userGroup->hasEmail()) {
// this prevent users to be notified twice if they will already be notiied by the group
return $accumulator;
}
foreach ($userGroup->getUsers() as $user) { foreach ($userGroup->getUsers() as $user) {
$accumulator[] = $user; $accumulator[] = $user;
} }

View File

@ -0,0 +1,90 @@
<?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\Workflow\EventSubscriber;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Workflow\Event\Event;
use Symfony\Component\Workflow\Registry;
final readonly class NotificationToUserGroupsOnTransition implements EventSubscriberInterface
{
public function __construct(
private \Twig\Environment $engine,
private MetadataExtractor $metadataExtractor,
private Registry $registry,
private MailerInterface $mailer,
) {}
public static function getSubscribedEvents(): array
{
return [
'workflow.completed' => ['onCompletedSendNotification', 2048],
];
}
/**
* Send a notification to:.
*
* * the dests of the new step;
* * the users which subscribed to workflow, on each step, or on final
*
* **Warning** take care that this method must be executed **after** the dest users are added to
* the step (@see{EntityWorkflowStep::addDestUser}). Currently, this is done during
*
* @see{EntityWorkflowTransitionEventSubscriber::addDests}.
*/
public function onCompletedSendNotification(Event $event): void
{
if (!$event->getSubject() instanceof EntityWorkflow) {
return;
}
/** @var EntityWorkflow $entityWorkflow */
$entityWorkflow = $event->getSubject();
$place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow);
$workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow(
$this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName())
);
// send to groups
foreach ($entityWorkflow->getCurrentStep()->getDestUserGroups() as $userGroup) {
if (!$userGroup->hasEmail()) {
continue;
}
$context = [
'entity_workflow' => $entityWorkflow,
'user_group' => $userGroup,
'place' => $place,
'workflow' => $workflow,
'is_dest' => true,
];
$email = new TemplatedEmail();
$email
->htmlTemplate('@ChillMain/Workflow/workflow_notification_on_transition_completed_content_to_user_group.fr.txt.twig')
->context($context)
->subject(
$this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context)
)
->to($userGroup->getEmail());
$this->mailer->send($email);
}
}
}