From fdafe7c82b767e264055d0093897b805e4ec793c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 Jan 2022 17:09:00 +0100 Subject: [PATCH 1/5] normalizer for entityworkflow --- .../Entity/Workflow/EntityWorkflow.php | 1 - .../EntitWorkflowStepNormalizer.php | 69 +++++++++++++++++++ .../Normalizer/EntityWorkflowNormalizer.php | 59 ++++++++++++++++ .../Workflow/Helper/MetadataExtractor.php | 10 +-- 4 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Serializer/Normalizer/EntitWorkflowStepNormalizer.php create mode 100644 src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowNormalizer.php diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index 0d7142e9f..76a23be7c 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -51,7 +51,6 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) */ private ?int $id = null; diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntitWorkflowStepNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntitWorkflowStepNormalizer.php new file mode 100644 index 000000000..ac6c31983 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntitWorkflowStepNormalizer.php @@ -0,0 +1,69 @@ +metadataExtractor = $metadataExtractor; + } + + /** + * @param EntityWorkflowStep $object + */ + public function normalize($object, ?string $format = null, array $context = []): array + { + $data = [ + 'type' => 'entity_workflow_step', + 'id' => $object->getId(), + 'comment' => $object->getComment(), + 'currentStep' => $this->metadataExtractor->buildArrayPresentationForPlace($object->getEntityWorkflow(), $object), + 'finalizeAfter' => $object->isFinalizeAfter(), + 'isFreezed' => false, + 'isFinalized' => false, + 'previousId' => null, + 'nextId' => null, + 'by' => null, + 'at' => null, + ]; + + if (null !== $previous = $object->getPrevious()) { + $data['previousId'] = $previous->getId(); + $data['isFreezed'] = $previous->isFreezeAfter(); + $data['isFinalized'] = $previous->isFreezeAfter(); + $data['by'] = $previous->getTransitionBy(); + $data['at'] = $previous->getTransitionAt(); + } + + if (null !== $next = $object->getNext()) { + $data['nextId'] = $next->getId(); + } + + return $data; + } + + public function supportsNormalization($data, ?string $format = null): bool + { + return $data instanceof EntityWorkflowStep && 'json' === $format; + } +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowNormalizer.php new file mode 100644 index 000000000..8fad9d864 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/EntityWorkflowNormalizer.php @@ -0,0 +1,59 @@ +metadataExtractor = $metadataExtractor; + $this->registry = $registry; + } + + /** + * @param EntityWorkflow $object + * + * @return array + */ + public function normalize($object, ?string $format = null, array $context = []) + { + $workflow = $this->registry->get($object, $object->getWorkflowName()); + + return [ + 'type' => 'entity_workflow', + 'id' => $object->getId(), + 'relatedEntityClass' => $object->getRelatedEntityClass(), + 'relatedEntityId' => $object->getRelatedEntityId(), + 'workflow' => $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow), + 'current_step' => $this->metadataExtractor->buildArrayPresentationForPlace($object), + 'steps' => $this->normalizer->normalize($object->getStepsChained(), $format, $context), + ]; + } + + public function supportsNormalization($data, ?string $format = null): bool + { + return $data instanceof EntityWorkflow && 'json' === $format; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php b/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php index 1f5af77c5..1484f6618 100644 --- a/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php +++ b/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Workflow\Helper; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; +use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\WorkflowInterface; @@ -51,16 +52,17 @@ class MetadataExtractor return $workflowsList; } - public function buildArrayPresentationForPlace(EntityWorkflow $entityWorkflow): array + public function buildArrayPresentationForPlace(EntityWorkflow $entityWorkflow, ?EntityWorkflowStep $step = null): array { $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $step ??= $entityWorkflow->getCurrentStep(); - $markingMetadata = $workflow->getMetadataStore()->getPlaceMetadata($entityWorkflow->getCurrentStep()->getCurrentStep()); + $markingMetadata = $workflow->getMetadataStore()->getPlaceMetadata($step->getCurrentStep()); $text = array_key_exists('label', $markingMetadata) ? - $this->translatableStringHelper->localize($markingMetadata['label']) : $entityWorkflow->getCurrentStep()->getCurrentStep(); + $this->translatableStringHelper->localize($markingMetadata['label']) : $step->getCurrentStep(); - return ['name' => $entityWorkflow->getCurrentStep()->getCurrentStep(), 'text' => $text]; + return ['name' => $step->getCurrentStep(), 'text' => $text]; } public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array From 86e7b0f00795caa41e5c9482f4c70c60ad94468a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 28 Jan 2022 23:42:21 +0100 Subject: [PATCH 2/5] rewrite workflow and handle finalize differently --- .../Entity/Workflow/EntityWorkflow.php | 34 +++++++----- .../Entity/Workflow/EntityWorkflowStep.php | 22 ++++---- .../ChillMainBundle/Form/WorkflowStepType.php | 52 +++++++++++++++---- .../views/Workflow/_decision.html.twig | 8 +-- .../views/Workflow/macro_breadcrumb.html.twig | 44 ++++++++++------ ...ntityWorkflowTransitionEventSubscriber.php | 24 ++++++++- .../NotificationOnTransition.php | 2 +- .../Templating/WorkflowTwigExtension.php | 4 ++ .../WorkflowTwigExtensionRuntime.php | 15 ++++++ .../migrations/Version20220128211748.php | 33 ++++++++++++ .../translations/messages.fr.yml | 2 +- 11 files changed, 180 insertions(+), 60 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220128211748.php diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index 76a23be7c..c7a322338 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -25,6 +25,7 @@ use Iterator; use RuntimeException; use Symfony\Component\Serializer\Annotation as Serializer; use function count; +use function is_array; /** * @ORM\Entity @@ -72,6 +73,11 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface */ private Collection $steps; + /** + * @var null|array|EntityWorkflowStep[] + */ + private ?array $stepsChainedCache = null; + /** * @ORM\ManyToMany(targetEntity=User::class) * @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final") @@ -129,10 +135,6 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface if (!$this->steps->contains($step)) { $this->steps[] = $step; $step->setEntityWorkflow($this); - - if ($this->isFinalize()) { - $step->setFinalizeAfter(true); - } } return $this; @@ -253,27 +255,33 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface public function getStepsChained(): array { + if (is_array($this->stepsChainedCache)) { + return $this->stepsChainedCache; + } + $iterator = $this->steps->getIterator(); - $previous = $next = $current = null; + $current = null; $steps = []; $iterator->rewind(); - while ($iterator->valid()) { + do { $previous = $current; - $steps[] = $current = $iterator->current(); + $current = $iterator->current(); + $steps[] = $current; + $current->setPrevious($previous); $iterator->next(); if ($iterator->valid()) { - $next = $iterator->current(); + $current->setNext($iterator->current()); } else { - $next = null; + $current->setNext(null); } + } while ($iterator->valid()); - $current->setNext($next); - } + $this->stepsChainedCache = $steps; return $steps; } @@ -308,7 +316,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface return $this->workflowName; } - public function isFinalize(): bool + public function isFinal(): bool { $steps = $this->getStepsChained(); @@ -320,7 +328,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface /** @var EntityWorkflowStep $last */ $last = end($steps); - return $last->getPrevious()->isFinalizeAfter(); + return $last->isFinal(); } public function isFreeze(): bool diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php index 6bd39ae0c..c68247174 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php @@ -53,11 +53,6 @@ class EntityWorkflowStep */ private ?EntityWorkflow $entityWorkflow = null; - /** - * @ORM\Column(type="boolean", options={"default": false}) - */ - private bool $finalizeAfter = false; - /** * @ORM\Column(type="boolean", options={"default": false}) */ @@ -70,6 +65,11 @@ class EntityWorkflowStep */ private ?int $id = null; + /** + * @ORM\Column(type="boolean", options={"default": false}) + */ + private bool $isFinal = false; + /** * filled by @see{EntityWorkflow::getStepsChained}. */ @@ -187,9 +187,9 @@ class EntityWorkflowStep return $this->transitionByEmail; } - public function isFinalizeAfter(): bool + public function isFinal(): bool { - return $this->finalizeAfter; + return $this->isFinal; } public function isFreezeAfter(): bool @@ -244,16 +244,16 @@ class EntityWorkflowStep return $this; } - public function setFinalizeAfter(bool $finalizeAfter): EntityWorkflowStep + public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep { - $this->finalizeAfter = $finalizeAfter; + $this->freezeAfter = $freezeAfter; return $this; } - public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep + public function setIsFinal(bool $isFinal): EntityWorkflowStep { - $this->freezeAfter = $freezeAfter; + $this->isFinal = $isFinal; return $this; } diff --git a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php index 6600e330a..18ac10c47 100644 --- a/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php +++ b/src/Bundle/ChillMainBundle/Form/WorkflowStepType.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Workflow\EntityWorkflowManager; use LogicException; use Symfony\Component\Form\AbstractType; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition; +use function array_key_exists; class WorkflowStepType extends AbstractType { @@ -31,10 +33,13 @@ class WorkflowStepType extends AbstractType private Registry $registry; - public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry) + private TranslatableStringHelperInterface $translatableStringHelper; + + public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry, TranslatableStringHelperInterface $translatableStringHelper) { $this->entityWorkflowManager = $entityWorkflowManager; $this->registry = $registry; + $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -42,6 +47,7 @@ class WorkflowStepType extends AbstractType /** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */ $entityWorkflow = $options['entity_workflow']; $handler = $this->entityWorkflowManager->getHandler($entityWorkflow); + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); if (true === $options['transition']) { if (null === $options['entity_workflow']) { @@ -53,20 +59,49 @@ class WorkflowStepType extends AbstractType ->getEnabledTransitions($entityWorkflow); $choices = array_combine( - array_map(static function (Transition $transition) { return $transition->getName(); }, $transitions), + array_map( + static function (Transition $transition) { + return $transition->getName(); + }, + $transitions + ), $transitions ); $builder ->add('transition', ChoiceType::class, [ - 'label' => 'workflow.Transition', + 'label' => 'workflow.Transition to apply', 'mapped' => false, 'multiple' => false, 'expanded' => true, 'choices' => $choices, - 'choice_label' => static function (Transition $transition) { - return implode(', ', $transition->getTos()); - }, + 'choice_label' => function (Transition $transition) use ($workflow) { + $meta = $workflow->getMetadataStore()->getTransitionMetadata($transition); + + if (array_key_exists('label', $meta)) { + return $this->translatableStringHelper->localize($meta['label']); + } + + return $transition->getName(); + }, + 'choice_attr' => static function (Transition $transition) use ($workflow) { + $toFinal = true; + + foreach ($transition->getTos() as $to) { + $meta = $workflow->getMetadataStore()->getPlaceMetadata($to); + + if ( + !array_key_exists('isFinal', $meta) || false === $meta['isFinal'] + ) { + $toFinal = false; + } + } + + return [ + 'data-is-transition' => 'data-is-transition', + 'data-to-final' => $toFinal ? '1' : '0', + ]; + }, ]) ->add('future_dest_users', PickUserDynamicType::class, [ 'label' => 'workflow.dest for next steps', @@ -88,11 +123,6 @@ class WorkflowStepType extends AbstractType } $builder - ->add('finalizeAfter', CheckboxType::class, [ - 'required' => false, - 'label' => 'workflow.Finalize', - 'help' => 'workflow.The workflow will be finalized', - ]) ->add('comment', ChillTextareaType::class, [ 'required' => false, 'label' => 'Comment', diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig index 5751449c3..4b14b39bc 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig @@ -5,14 +5,10 @@ {{ form_row(transition_form.transition) }} -
- {{ form_row(transition_form.finalizeAfter) }} -
- {% if transition_form.freezeAfter is defined %} {{ form_row(transition_form.freezeAfter) }} {% endif %} - +
{{ form_row(transition_form.future_dest_users) }}
@@ -31,7 +27,7 @@ {% else %}
- {% if entity_workflow.currentStep.isFinalizeAfter %} + {% if entity_workflow.currentStep.isFinal %}

{{ 'workflow.This workflow is finalized'|trans }}

{% else %}

{{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}

diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig index 2bae33678..d4dd122d5 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/macro_breadcrumb.html.twig @@ -1,13 +1,24 @@ {% macro popoverContent(step) %}
    -
  • - {{ 'By'|trans ~ ' : ' }} - {{ step.transitionBy|chill_entity_render_box }} -
  • -
  • - {{ 'Le'|trans ~ ' : ' }} - {{ step.transitionAt|format_datetime('short', 'short') }} -
  • + {% if step.previous is not null %} +
  • + {{ 'By'|trans ~ ' : ' }} + {{ step.previous.transitionBy|chill_entity_render_box }} +
  • +
  • + {{ 'Le'|trans ~ ' : ' }} + {{ step.previous.transitionAt|format_datetime('short', 'short') }} +
  • + {% else %} +
  • + {{ 'Creation by'|trans ~ ' : ' }} + {{ step.entityWorkflow.createdBy|chill_entity_render_box }} +
  • +
  • + {{ 'Le'|trans ~ ' : ' }} + {{ step.entityWorkflow.createdAt|format_datetime('short', 'short') }} +
  • + {% endif %}
{% endmacro %} @@ -15,18 +26,21 @@ {% if step.previous is not null and step.previous.freezeAfter == true %} {% endif %} - {{ step.currentStep }} + {% if step.previous is not null %} + {% set transition = chill_workflow_transition_by_string(step.entityWorkflow, step.previous.transitionAfter) %} + {% set labels = workflow_metadata(step.entityWorkflow, 'label', transition) %} + {% set label = labels is null ? step.previous.transitionAfter : labels|localize_translatable_string %} + {{ label }} + {% endif %} {% endmacro %} {% macro breadcrumb(_ctx) %}