diff --git a/extension/Hello/config/workflow.yaml b/extension/Hello/config/workflow.yaml index 0d9fe7a08..77815da16 100644 --- a/extension/Hello/config/workflow.yaml +++ b/extension/Hello/config/workflow.yaml @@ -53,6 +53,20 @@ framework: settings: failed: bye # transition name for filed result successful: world # transition name for successful result + hello-interactive: + from: start + to: middle + metadata: + task: ~ # not neccessary to have a task for the interactive transition. + transitionResolver: # Resolver needs for resolve next transition + name: interactive # By setting interactive transitionResolver name current transition will be recognized as interactive and appropriate actions will be done. + settings: + question: 'What would you like to execute next?' + choices: + world: # Transition name should be enabled as a next transition in the current workflow. + description: Executes hello:php task # (optional). Will be printer in the console to provide more information to the user. + bye: + description: Executes bye:world task # (optional). Will be printer in the console to provide more information to the user. world: from: middle to: end diff --git a/src/Extension/Exception/UniqueValueException.php b/src/Extension/Exception/UniqueValueException.php new file mode 100644 index 000000000..c740ccbd5 --- /dev/null +++ b/src/Extension/Exception/UniqueValueException.php @@ -0,0 +1,14 @@ +cliValueReceiver = $cliValueReceiver; + } + + /** + * @return string + */ + public function getName(): string + { + return static::RESOLVER_NAME; + } + + /** + * @param \SprykerSdk\SdkContracts\Entity\ContextInterface $context + * @param array $settings + * + * @return string|null + */ + public function resolveTransition(ContextInterface $context, array $settings): ?string + { + $choiceValues = $this->getChoiceValues($settings); + $flippedValues = array_flip($choiceValues); + + $answer = $this->cliValueReceiver->receiveValue(new ReceiverValue( + $settings[static::QUESTION] ?? '', + null, + 'string', + array_values($choiceValues), + )); + + return $flippedValues[$answer]; + } + + /** + * @param array $settings + * + * @throws \SprykerSdk\Sdk\Extension\Exception\UniqueValueException + * + * @return array + */ + protected function getChoiceValues(array $settings): array + { + $choices = $settings[static::CHOICES] ?? []; + $choiceValues = []; + foreach ($choices as $transition => $choice) { + $choiceValues[(string)$transition] = (string)$choice['description']; + } + + $duplicates = $this->getDuplicateDescriptions($choiceValues); + if ($duplicates) { + throw new UniqueValueException(sprintf('Descriptions for choices must be unique: `%s`', implode('`,`', $duplicates))); + } + + return $choiceValues; + } + + /** + * @param array $choiceValues + * + * @return array + */ + protected function getDuplicateDescriptions(array $choiceValues): array + { + $duplicates = array_diff_assoc($choiceValues, array_unique($choiceValues)); + + return array_unique($duplicates); + } +} diff --git a/src/Infrastructure/Event/Workflow/WorkflowTransitionListener.php b/src/Infrastructure/Event/Workflow/WorkflowTransitionListener.php index 06187bb09..cb4283f7b 100644 --- a/src/Infrastructure/Event/Workflow/WorkflowTransitionListener.php +++ b/src/Infrastructure/Event/Workflow/WorkflowTransitionListener.php @@ -150,9 +150,6 @@ public function execute(TransitionEvent $event): void protected function tryRunTask(TransitionEvent $event, WorkflowTransitionInterface $transition): void { $task = $this->getTransitionMeta($event, static::META_KEY_TASK); - if (!$task) { - return; - } $shouldRunTask = in_array($transition->getState(), [ WorkflowTransitionInterface::WORKFLOW_TRANSITION_STARTED, @@ -167,8 +164,9 @@ protected function tryRunTask(TransitionEvent $event, WorkflowTransitionInterfac $allowToFail = $this->getTransitionMeta($event, static::META_ALLOW_TO_FAIL); $context = $this->getContext($event); - - $context = $this->taskExecutor->execute($context, $task); + if ($task) { + $context = $this->taskExecutor->execute($context, $task); + } $resolvedNextTransition = $this->resolverNextTransition($event, $context); if (!$allowToFail && !$resolvedNextTransition && $context->getExitCode() !== ContextInterface::SUCCESS_EXIT_CODE) { diff --git a/src/Presentation/Console/Command/RunWorkflowCommand.php b/src/Presentation/Console/Command/RunWorkflowCommand.php index 0f45d321d..e481433b9 100644 --- a/src/Presentation/Console/Command/RunWorkflowCommand.php +++ b/src/Presentation/Console/Command/RunWorkflowCommand.php @@ -8,6 +8,7 @@ namespace SprykerSdk\Sdk\Presentation\Console\Command; use SprykerSdk\Sdk\Core\Application\Dependency\ContextFactoryInterface; +use SprykerSdk\Sdk\Core\Application\Dependency\ProjectSettingRepositoryInterface; use SprykerSdk\Sdk\Core\Application\Dto\ReceiverValue; use SprykerSdk\Sdk\Core\Application\Service\ProjectWorkflow; use SprykerSdk\Sdk\Infrastructure\Service\CliValueReceiver; @@ -62,22 +63,31 @@ class RunWorkflowCommand extends Command */ protected ContextFactoryInterface $contextFactory; + /** + * @var \SprykerSdk\Sdk\Core\Application\Dependency\ProjectSettingRepositoryInterface + */ + protected ProjectSettingRepositoryInterface $projectSettingRepository; + /** * @param \SprykerSdk\Sdk\Core\Application\Service\ProjectWorkflow $projectWorkflow * @param \SprykerSdk\Sdk\Infrastructure\Service\CliValueReceiver $cliValueReceiver * @param \SprykerSdk\Sdk\Infrastructure\Service\WorkflowRunner $workflowRunner * @param \SprykerSdk\Sdk\Core\Application\Dependency\ContextFactoryInterface $contextFactory + * @param \SprykerSdk\Sdk\Core\Application\Dependency\ProjectSettingRepositoryInterface $projectSettingRepository */ public function __construct( ProjectWorkflow $projectWorkflow, CliValueReceiver $cliValueReceiver, WorkflowRunner $workflowRunner, - ContextFactoryInterface $contextFactory + ContextFactoryInterface $contextFactory, + ProjectSettingRepositoryInterface $projectSettingRepository ) { $this->projectWorkflow = $projectWorkflow; $this->cliValueReceiver = $cliValueReceiver; $this->workflowRunner = $workflowRunner; $this->contextFactory = $contextFactory; + $this->projectSettingRepository = $projectSettingRepository; + parent::__construct(static::NAME); } @@ -106,18 +116,16 @@ public function execute(InputInterface $input, OutputInterface $output): int { /** @var string|null $workflowName */ $workflowName = $input->getArgument(static::ARG_WORKFLOW_NAME); + $projectWorkflows = (array)$this->projectSettingRepository->getOneByPath(ProjectWorkflow::WORKFLOW)->getValues(); - $initializedWorkflows = $this->projectWorkflow->findInitializedWorkflows(); - - if ($workflowName && $initializedWorkflows && !in_array($workflowName, $initializedWorkflows)) { - $output->writeln('You haven\'t initialized a workflow with "sdk:workflow:init"' . - ' or you don\'t have any open tasks defined'); + if ($workflowName && !in_array($workflowName, $projectWorkflows)) { + $output->writeln('You don\'t have any active a workflows".'); return static::FAILURE; } if (!$workflowName) { - $workflows = $initializedWorkflows ?: $this->projectWorkflow->getProjectWorkflows() ?: $this->projectWorkflow->getAll(); + $workflows = $projectWorkflows ?: $this->projectWorkflow->getProjectWorkflows() ?: $this->projectWorkflow->getAll(); $workflowName = count($workflows) > 1 ? $this->cliValueReceiver->receiveValue( new ReceiverValue( 'You have more than one initialized workflow. You have to select one.', diff --git a/src/Presentation/Console/Resources/config/services.yaml b/src/Presentation/Console/Resources/config/services.yaml index bb5596fae..5dd253777 100644 --- a/src/Presentation/Console/Resources/config/services.yaml +++ b/src/Presentation/Console/Resources/config/services.yaml @@ -32,6 +32,7 @@ services: - "@cli_value_receiver" - "@workflow_runner" - "@context_factory" + - "@project_setting_repository" qa_automation_command: autowire: false class: SprykerSdk\Sdk\Presentation\Console\Command\QaAutomationCommand diff --git a/tests/Sdk/Unit/Extension/Workflow/InteractionAnswerBasedTransitionResolverTest.php b/tests/Sdk/Unit/Extension/Workflow/InteractionAnswerBasedTransitionResolverTest.php new file mode 100644 index 000000000..9a2a8d8b9 --- /dev/null +++ b/tests/Sdk/Unit/Extension/Workflow/InteractionAnswerBasedTransitionResolverTest.php @@ -0,0 +1,86 @@ +createMock(ValueReceiverInterface::class); + $valueReceiver->expects($this->once()) + ->method('receiveValue') + ->willReturn('Choice two description.'); + $context = $this->createMock(ContextInterface::class); + + $resolveTransition = new InteractionAnswerBasedTransitionResolver($valueReceiver); + + $settings = [ + InteractionAnswerBasedTransitionResolver::QUESTION => 'Interactive question', + InteractionAnswerBasedTransitionResolver::CHOICES => [ + 'choice one' => [ + 'description' => 'Choice one description.', + ], + 'choice two' => [ + 'description' => 'Choice two description.', + ], + ], + ]; + + // Act + $transition = $resolveTransition->resolveTransition($context, $settings); + + // Assert + $this->assertSame('choice two', $transition); + } + + /** + * @return void + */ + public function testResolveTransitionWithEqualDescription(): void + { + // Arrange + $valueReceiver = $this->createMock(ValueReceiverInterface::class); + $context = $this->createMock(ContextInterface::class); + + $resolveTransition = new InteractionAnswerBasedTransitionResolver($valueReceiver); + + $settings = [ + InteractionAnswerBasedTransitionResolver::QUESTION => 'Interactive question', + InteractionAnswerBasedTransitionResolver::CHOICES => [ + 'choice one' => [ + 'description' => 'Choice one description.', + ], + 'choice two' => [ + 'description' => 'Choice one description.', + ], + ], + ]; + + // Assert + $this->expectException(UniqueValueException::class); + + // Act + $resolveTransition->resolveTransition($context, $settings); + } +}