From 6b33eaf43a4ddf6ddd8982824bd7404cf9e9a101 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 22 Jun 2023 17:04:38 -0400 Subject: [PATCH 01/18] Set default MESSAGE_BROKER_DRIVER for phpunittests --- phpunit.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml b/phpunit.xml index 6628c79e14..3e5a0f996d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -44,6 +44,7 @@ + From 929eafc22c9a0bfde1f2fdd21f62cc519f292aac Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 22 Jun 2023 17:07:17 -0400 Subject: [PATCH 02/18] Refactor triggerBoundaryEvent --- .../Managers/WorkflowManagerRabbitMq.php | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index db6789e4f2..e6aa89486f 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -12,6 +12,7 @@ use ProcessMaker\Models\Process as Definitions; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\User; +use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; @@ -23,6 +24,7 @@ class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements Workflow const ACTION_COMPLETE_TASK = 'COMPLETE_TASK'; const ACTION_TRIGGER_INTERMEDIATE_EVENT = 'TRIGGER_INTERMEDIATE_EVENT'; const ACTION_RUN_SCRIPT = 'RUN_SCRIPT'; + const ACTION_TRIGGER_BOUNDARY_EVENT = 'TRIGGER_BOUNDARY_EVENT'; /** * Trigger a start event and return the process request instance. @@ -193,6 +195,49 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to ]); } + /** + * Trigger a boundary event + * + * @param Definitions $definitions + * @param ExecutionInstanceInterface $instance + * @param TokenInterface $token + * @param BoundaryEventInterface $boundaryEvent + * @param array $data + * + * @return void + */ + public function triggerBoundaryEvent( + Definitions $definitions, + ExecutionInstanceInterface $instance, + TokenInterface $token, + BoundaryEventInterface $boundaryEvent, + array $data + ) { + //Validate data + $this->validateData($data, $definitions, $boundaryEvent); + + // Get complementary information + $version = $instance->process->getLatestVersion(); + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version->getKey(), + 'action' => self::ACTION_TRIGGER_BOUNDARY_EVENT, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $token->element_id, + 'data' => [], + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** * Build a state object. * From f92b3b7c4aab55d1038c1a0aeac569438abb8c44 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 22 Jun 2023 17:14:42 -0400 Subject: [PATCH 03/18] Fix typo --- ProcessMaker/Jobs/BoundaryEvent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProcessMaker/Jobs/BoundaryEvent.php b/ProcessMaker/Jobs/BoundaryEvent.php index f45602e1ba..d5a56a7f0c 100644 --- a/ProcessMaker/Jobs/BoundaryEvent.php +++ b/ProcessMaker/Jobs/BoundaryEvent.php @@ -62,7 +62,7 @@ public function action(BpmnDocumentInterface $definitions, TokenInterface $token } /** - * Updata data for a message event + * Update data for a message event * * If variableName is set, then the event payload will be set to that variable name. * If the data name exists, then the data is merged. From 7d6024efc7932f34bd7e95ab48fa17c3a7ee9694 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Fri, 23 Jun 2023 14:10:02 -0400 Subject: [PATCH 04/18] Fix version id for bpmn actions --- .../Nayra/Managers/WorkflowManagerRabbitMq.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index e6aa89486f..7d1b2962fe 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -25,6 +25,7 @@ class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements Workflow const ACTION_TRIGGER_INTERMEDIATE_EVENT = 'TRIGGER_INTERMEDIATE_EVENT'; const ACTION_RUN_SCRIPT = 'RUN_SCRIPT'; const ACTION_TRIGGER_BOUNDARY_EVENT = 'TRIGGER_BOUNDARY_EVENT'; + const ACTION_TRIGGER_MESSAGE_EVENT = 'TRIGGER_MESSAGE_EVENT'; /** * Trigger a start event and return the process request instance. @@ -102,13 +103,13 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac $this->validateData($data, $definitions, $element); // Get complementary information - $version = $definitions->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_COMPLETE_TASK, 'params' => [ 'request_id' => $token->process_request_id, @@ -140,7 +141,7 @@ public function completeCatchEvent(Definitions $definitions, ExecutionInstanceIn $this->validateData($data, $definitions, $element); // Get complementary information - $version = $definitions->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); @@ -174,13 +175,13 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to // Get complementary information $instance = $token->processRequest; - $version = $instance->process->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_RUN_SCRIPT, 'params' => [ 'request_id' => $token->process_request_id, @@ -217,13 +218,13 @@ public function triggerBoundaryEvent( $this->validateData($data, $definitions, $boundaryEvent); // Get complementary information - $version = $instance->process->getLatestVersion(); + $version = $instance->process_version_id; $userId = $this->getCurrentUserId(); $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_TRIGGER_BOUNDARY_EVENT, 'params' => [ 'request_id' => $token->process_request_id, From be80665ce802075d17716a85de589f28dad3cec8 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Fri, 23 Jun 2023 14:11:24 -0400 Subject: [PATCH 05/18] Refactor of throwMessageEvent --- .../Managers/WorkflowManagerRabbitMq.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 7d1b2962fe..193c901fdd 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -239,6 +239,39 @@ public function triggerBoundaryEvent( ]); } + /** + * Triggers a message event in the process instance based on provided parameters. + * + * @param $instanceId of the process instance that is to be triggered + * @param $elementId of the catch message event element + * @param $messageRef of the message event that is to be triggered + * @param $payload (optional) array of key-value pairs that are to be stored in the data store + */ + public function throwMessageEvent($instanceId, $elementId, $messageRef, array $payload = []) + { + // Get complementary information + $instance = ProcessRequest::find($instanceId); + $version = $instance->process_version_id; + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version, + 'action' => self::ACTION_TRIGGER_MESSAGE_EVENT, + 'params' => [ + 'instance_id' => $instanceId, + 'element_id' => $elementId, + 'message_ref' => $messageRef, + 'data' => $payload, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** * Build a state object. * From 85f0771bb031619f23311b62d257bbd680533761 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 27 Jun 2023 09:55:41 -0400 Subject: [PATCH 06/18] Implementation of Task Scheduler-Nayra --- ProcessMaker/Models/Process.php | 2 +- .../Managers/WorkflowManagerRabbitMq.php | 3 +- .../Nayra/Repositories/Deserializer.php | 24 +++++++++++ .../Nayra/Repositories/PersistenceHandler.php | 13 ++++++ .../PersistenceTimerEventsTrait.php | 42 +++++++++++++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php diff --git a/ProcessMaker/Models/Process.php b/ProcessMaker/Models/Process.php index 09ee450575..cb4bf19c3f 100644 --- a/ProcessMaker/Models/Process.php +++ b/ProcessMaker/Models/Process.php @@ -584,7 +584,7 @@ private function scalateToManagerIfEnabled($user, $activity, $token, $assignment if ($escalateToManager) { $user = WorkflowUserManager::escalateToManager($token, $user); } else { - $res = WorkflowManager::runProcess($assignmentProcess, 'assign', [ + $res = (new \ProcessMaker\Nayra\Managers\WorkflowManagerDefault)->runProcess($assignmentProcess, 'assign', [ 'user_id' => $user, 'process_id' => $this->id, 'request_id' => $token->getInstance()->getId(), diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index e6aa89486f..648481f803 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -228,7 +228,7 @@ public function triggerBoundaryEvent( 'params' => [ 'request_id' => $token->process_request_id, 'token_id' => $token->uuid, - 'element_id' => $token->element_id, + 'element_id' => $boundaryEvent->getId(), 'data' => [], ], 'state' => $state, @@ -255,6 +255,7 @@ private function serializeState(ProcessRequest $instance) 'status' => $token->status, 'index' => $token->element_index, 'element_id' => $token->element_id, + 'created_at' => $token->created_at->getTimestamp(), ]); } diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php index c2c019dc6d..4485a81f3e 100644 --- a/ProcessMaker/Nayra/Repositories/Deserializer.php +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -9,6 +9,7 @@ use ProcessMaker\Nayra\Bpmn\Collection; use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; use ProcessMaker\Repositories\BpmnDocument; @@ -19,10 +20,15 @@ class Deserializer { private $definitions = []; + private $requests = []; + private $tokens = []; + private ExecutionInstanceRepository $instanceRepository; + private TokenRepository $tokenRepository; + private DefinitionsRepository $factory; /** @@ -230,6 +236,7 @@ public function unserializeToken(array $serialized): TokenInterface public function unserializeEntity(array $serialized): EntityInterface { $definition = $this->findProcessDefinition($serialized['model_id']); + return $definition->getElementInstanceById($serialized['id']); } @@ -248,4 +255,21 @@ public function unserializeTokensCollection(array $serialized): CollectionInterf return $collection; } + + /** + * Return event definition from serialized data + * + * @param array $serialized + * @return EventDefinitionInterface + */ + public function unserializeEventDefinition(array $serialized): EventDefinitionInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + $element = $definition->getElementInstanceById($serialized['element_id']); + $node = $element->getBpmnElement(); + $childNode = $node->childNodes->item($serialized['index']); + $eventDefinition = $childNode->getBpmnElementInstance(); + + return $eventDefinition; + } } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php index 8f1595b1fa..1600e38155 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Facades\Auth; +use ProcessMaker\Managers\TaskSchedulerManager; use ProcessMaker\Repositories\ExecutionInstanceRepository; use ProcessMaker\Repositories\TokenRepository; @@ -11,10 +12,12 @@ class PersistenceHandler { use PersistenceRequestTrait; use PersistenceTokenTrait; + use PersistenceTimerEventsTrait; protected Deserializer $deserializer; protected ExecutionInstanceRepository $instanceRepository; protected TokenRepository $tokenRepository; + protected TaskSchedulerManager $taskSchedulerManager; /** * PersistenceHandler constructor @@ -24,6 +27,7 @@ public function __construct() $this->deserializer = new Deserializer(); $this->instanceRepository = new ExecutionInstanceRepository(); $this->tokenRepository = new TokenRepository($this->instanceRepository); + $this->taskSchedulerManager = new TaskSchedulerManager(); } /** @@ -105,6 +109,15 @@ public function save(array $transaction) case 'instance_updated': $this->persistInstanceUpdated($transaction); break; + case 'schedule_date': + $this->persistScheduleDate($transaction); + break; + case 'schedule_cycle': + $this->persistScheduleCycle($transaction); + break; + case 'schedule_duration': + $this->persistScheduleDuration($transaction); + break; default: throw new Exception('Unknown transaction type ' . $transaction['type']); } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php new file mode 100644 index 0000000000..cdbcfd65c0 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistenceTimerEventsTrait.php @@ -0,0 +1,42 @@ +deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleDate($dateTime, $eventDefinition, $element, $token); + } + + public function persistScheduleCycle(array $transaction) + { + $cycle = unserialize($transaction['cycle']); + $eventDefinition = $this->deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleCycle($cycle, $eventDefinition, $element, $token); + } + + public function persistScheduleDuration(array $transaction) + { + $duration = unserialize($transaction['duration']); + $eventDefinition = $this->deserializer->unserializeEventDefinition($transaction['event_definition']); + $element = $this->deserializer->unserializeEntity($transaction['element']); + $token = $transaction['token'] ? $this->deserializer->unserializeToken($transaction['token']) : null; + $this->taskSchedulerManager->scheduleDuration($duration, $eventDefinition, $element, $token); + } +} From 04fc467a04f75b595b7b4b32bd8e006f2f8fa606 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 27 Jun 2023 11:39:28 -0400 Subject: [PATCH 07/18] Run default engine for assignment process --- ProcessMaker/Models/Process.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Models/Process.php b/ProcessMaker/Models/Process.php index 09ee450575..325c90ec6e 100644 --- a/ProcessMaker/Models/Process.php +++ b/ProcessMaker/Models/Process.php @@ -27,6 +27,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Storage\BpmnDocumentInterface; +use ProcessMaker\Nayra\Managers\WorkflowManagerDefault; use ProcessMaker\Nayra\Storage\BpmnDocument; use ProcessMaker\Package\WebEntry\Models\WebentryRoute; use ProcessMaker\Rules\BPMNValidation; @@ -584,7 +585,7 @@ private function scalateToManagerIfEnabled($user, $activity, $token, $assignment if ($escalateToManager) { $user = WorkflowUserManager::escalateToManager($token, $user); } else { - $res = WorkflowManager::runProcess($assignmentProcess, 'assign', [ + $res = (new WorkflowManagerDefault)->runProcess($assignmentProcess, 'assign', [ 'user_id' => $user, 'process_id' => $this->id, 'request_id' => $token->getInstance()->getId(), From 619238db6f26e42595fc8895d8bbf53374024e92 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 27 Jun 2023 14:36:58 -0400 Subject: [PATCH 08/18] Fix duplicated route name --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index 7d8a24780a..40bda4153a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -122,7 +122,7 @@ Route::get('processes', [ProcessController::class, 'index'])->name('processes.index')->middleware('can:view-processes'); Route::get('processes/{process}', [ProcessController::class, 'show'])->name('processes.show')->middleware('can:view-processes'); Route::post('processes/{process}/export', [ProcessController::class, 'export'])->name('processes.export')->middleware('can:export-processes'); - Route::get('processes/{process}/bpmn', [ProcessController::class, 'downloadBpmn'])->name('processes.export')->middleware('can:view-processes'); + Route::get('processes/{process}/bpmn', [ProcessController::class, 'downloadBpmn'])->name('processes.export.bpmn')->middleware('can:view-processes'); Route::post('processes/import', [ProcessController::class, 'import'])->name('processes.import')->middleware('can:import-processes'); Route::post('processes/import/validation', [ProcessController::class, 'preimportValidation'])->name('processes.preimportValidation')->middleware('can:import-processes'); Route::get('processes/import/{code}/is_ready', [ProcessController::class, 'import_ready'])->name('processes.import_is_ready')->middleware('can:import-processes'); From 8964b8a0244f1f95de4d420871db5870318bf644 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 28 Jun 2023 07:56:24 -0400 Subject: [PATCH 09/18] Fix issue getting version id --- ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 193c901fdd..917069c47a 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -147,7 +147,7 @@ public function completeCatchEvent(Definitions $definitions, ExecutionInstanceIn // Dispatch complete task action $this->dispatchAction([ - 'bpmn' => $version->getKey(), + 'bpmn' => $version, 'action' => self::ACTION_TRIGGER_INTERMEDIATE_EVENT, 'params' => [ 'request_id' => $token->process_request_id, From 253f4dbe496e2085c6e219d2373d9065ecf41bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Thu, 29 Jun 2023 14:54:52 +0000 Subject: [PATCH 10/18] FOUR-8556 Refactor runServiceTask function to use Nayra BPMN engine --- .../Managers/WorkflowManagerRabbitMq.php | 103 +++++++++++++++++- .../Nayra/MessageBrokers/ServiceRabbitMq.php | 11 +- .../Nayra/Repositories/PersistenceHandler.php | 10 ++ 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index ecab7b0813..542447172c 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -6,22 +6,27 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; use ProcessMaker\Contracts\WorkflowManagerInterface; +use ProcessMaker\Exception\ScriptException; use ProcessMaker\Facades\MessageBrokerService; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\GenerateAccessToken; +use ProcessMaker\Managers\DataManager; use ProcessMaker\Models\EnvironmentVariable; use ProcessMaker\Models\Process as Definitions; use ProcessMaker\Models\ProcessRequest; +use ProcessMaker\Models\Script; use ProcessMaker\Models\User; use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; +use Throwable; class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements WorkflowManagerInterface { const ACTION_START_PROCESS = 'START_PROCESS'; - const ACTION_COMPLETE_TASK = 'COMPLETE_TASK'; const ACTION_TRIGGER_INTERMEDIATE_EVENT = 'TRIGGER_INTERMEDIATE_EVENT'; const ACTION_RUN_SCRIPT = 'RUN_SCRIPT'; @@ -197,6 +202,102 @@ public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $to ]); } + /** + * Run a service task. + * + * @param ServiceTaskInterface $serviceTask + * @param TokenInterface $token + */ + public function runServiceTask(ServiceTaskInterface $serviceTask, TokenInterface $token) + { + // Log execution + Log::info('Dispatch a service task: ' . $serviceTask->getId()); + + // Get complementary information + $element = $token->getDefinition(true); + $instance = $token->processRequest; + $processModel = $instance->process; + $version = $instance->process_version_id; + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Exit if the task was completed or closed + if (!$token || !$element) { + return; + } + + // Get service task configuration + $implementation = $element->getImplementation(); + $configuration = json_decode($element->getProperty('config'), true); + $errorHandling = json_decode($element->getProperty('errorHandling'), true); + + // Check to see if we've failed parsing. If so, let's convert to empty array. + if ($configuration === null) { + $configuration = []; + } + + try { + if (empty($implementation)) { + throw new ScriptException('Service task implementation not defined'); + } else { + $script = Script::where('key', $implementation)->first(); + } + + // Check if service task implementation exists + $existsImpl = WorkflowManager::existsServiceImplementation($implementation); + if (!$existsImpl && empty($script)) { + throw new ScriptException('Service task not implemented: ' . $implementation); + } + + // Get data + $dataManager = new DataManager(); + $data = $dataManager->getData($token); + + // Run implementation/script + if ($existsImpl) { + $response = [ + 'output' => WorkflowManager::runServiceImplementation($implementation, $data, $configuration, $token->getId(), $errorHandling), + ]; + } else { + $response = $script->runScript($data, $configuration, $token->getId(), $errorHandling); + } + + // Update data + if (is_array($response['output'])) { + // Validate data + $this->validateData($response['output'], $processModel, $element); + $dataManager = new DataManager(); + $dataManager->updateData($token, $response['output']); + } + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version, + 'action' => self::ACTION_COMPLETE_TASK, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $token->element_id, + 'data' => $response['output'], + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } catch (Throwable $exception) { + // Change to error status + $token->setStatus(ServiceTaskInterface::TOKEN_STATE_FAILING); + $error = $element->getRepository()->createError(); + $error->setName($exception->getMessage()); + $token->setProperty('error', $error); + + // Log message errors + Log::info('Service task failed: ' . $implementation . ' - ' . $exception->getMessage()); + Log::error($exception->getTraceAsString()); + } + } + /** * Trigger a boundary event * diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php index b621030626..7408d9457e 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php @@ -61,16 +61,15 @@ public function disconnect(): void public function sendMessage(string $subject, string $collaborationId, mixed $body): void { // Connect to RabbitMQ - $this->connect(); + if (!($this->connection instanceof AMQPStreamConnection) || !$this->connection->isConnected()) { + $this->connect(); + } // Prepare the message to send $message = new AMQPMessage(json_encode(['data' => $body, 'collaboration_id' => $collaborationId])); // Publish the message $this->channel->basic_publish($message, '', self::QUEUE_NAME_PUBLISH); - - // Close connection to RabbitMQ - $this->disconnect(); } /** @@ -114,7 +113,9 @@ public function worker(): void } // Disconnect from service - $this->disconnect(); + if ($this->connection->isConnected()) { + $this->disconnect(); + } } /** diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php index 1600e38155..51088a65df 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Facades\Auth; +use ProcessMaker\Listeners\BpmnSubscriber; use ProcessMaker\Managers\TaskSchedulerManager; use ProcessMaker\Repositories\ExecutionInstanceRepository; use ProcessMaker\Repositories\TokenRepository; @@ -118,6 +119,15 @@ public function save(array $transaction) case 'schedule_duration': $this->persistScheduleDuration($transaction); break; + case 'service_task_activated': + // Get object instances + $token = $this->deserializer->unserializeToken($transaction['token']); + $serviceTask = $this->deserializer->unserializeEntity($transaction['activity']); + + // Trigger service task listener + $subscriber = new BpmnSubscriber(); + $subscriber->onServiceTaskActivated($serviceTask, $token); + break; default: throw new Exception('Unknown transaction type ' . $transaction['type']); } From 8e8208d6b5f648cac3d3215c7110f6d19aeb203b Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 3 Jul 2023 09:00:29 -0400 Subject: [PATCH 11/18] Implement Signals with NayraService --- ProcessMaker/Facades/WorkflowManager.php | 2 + ProcessMaker/Jobs/CatchSignalEventRequest.php | 5 +- ProcessMaker/Jobs/ThrowSignalEvent.php | 5 +- ProcessMaker/Models/ProcessCollaboration.php | 4 + ProcessMaker/Models/ProcessRequest.php | 1 + .../Nayra/Managers/WorkflowManagerDefault.php | 36 ++++ .../Managers/WorkflowManagerRabbitMq.php | 134 +++++++++++-- .../Nayra/Repositories/Deserializer.php | 2 +- .../Nayra/Repositories/PersistenceHandler.php | 186 ++++++++++-------- .../Repositories/PersistenceRequestTrait.php | 1 + .../ExecutionInstanceRepository.php | 59 +++--- ...oration_uuid_to_process_requests_table.php | 35 ++++ ...d_uuid_to_process_collaborations_table.php | 35 ++++ ...te_process_requests_collaboration_uuid.php | 59 ++++++ 14 files changed, 434 insertions(+), 130 deletions(-) create mode 100644 database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php create mode 100644 database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php create mode 100644 upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php diff --git a/ProcessMaker/Facades/WorkflowManager.php b/ProcessMaker/Facades/WorkflowManager.php index dd2f2b362a..e03bc9223f 100644 --- a/ProcessMaker/Facades/WorkflowManager.php +++ b/ProcessMaker/Facades/WorkflowManager.php @@ -14,6 +14,8 @@ * @method static mixed runServiceTask(\ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface $serviceTask, Token $token) * @method static void throwSignalEventDefinition(\ProcessMaker\Nayra\Contracts\Bpmn\EventDefinitionInterface $sourceEventDefinition, \ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface $token) * @method static void throwSignalEvent($signalRef, array $data = [], array $exclude = []) + * @method static void throwSignalEventProcess($processId, $signalRef, array $data) + * @method static void throwSignalEventRequest(\ProcessMaker\Models\ProcessRequest $request, $signalRef, array $data) * @method static void throwMessageEvent($instanceId, $elementId, $messageRef, array $payload = []) * @method static void onDataValidation(callable $callback) * @method static void validateData(array $data, $definitions, $element) diff --git a/ProcessMaker/Jobs/CatchSignalEventRequest.php b/ProcessMaker/Jobs/CatchSignalEventRequest.php index 3fdf5d88c6..0473c4078e 100644 --- a/ProcessMaker/Jobs/CatchSignalEventRequest.php +++ b/ProcessMaker/Jobs/CatchSignalEventRequest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\Models\ProcessRequest; class CatchSignalEventRequest implements ShouldQueue @@ -34,10 +35,10 @@ public function __construct($chunck, $signalRef, $payload) public function handle() { - $this->payload = unpackTemporalData($this->payload_uid); + $payload = unpackTemporalData($this->payload_uid); foreach ($this->chunck as $requestId) { $request = ProcessRequest::find($requestId); - CatchSignalEventInRequest::dispatchNow($request, $this->payload, $this->signalRef); + WorkflowManager::throwSignalEventRequest($request, $this->signalRef, $payload); } removeTemporalData($this->payload_uid); } diff --git a/ProcessMaker/Jobs/ThrowSignalEvent.php b/ProcessMaker/Jobs/ThrowSignalEvent.php index abba928603..2588c48a99 100644 --- a/ProcessMaker/Jobs/ThrowSignalEvent.php +++ b/ProcessMaker/Jobs/ThrowSignalEvent.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; @@ -47,11 +48,11 @@ public function handle() ->pluck('id') ->toArray(); foreach ($processes as $process) { - CatchSignalEventProcess::dispatch( + WorkflowManager::throwSignalEventProcess( $process, $this->signalRef, $this->data - )->onQueue('bpmn'); + ); } $count = ProcessRequest::whereNotIn('id', $this->excludeRequests) ->where('status', 'ACTIVE') diff --git a/ProcessMaker/Models/ProcessCollaboration.php b/ProcessMaker/Models/ProcessCollaboration.php index 004034212c..bfd893c405 100644 --- a/ProcessMaker/Models/ProcessCollaboration.php +++ b/ProcessMaker/Models/ProcessCollaboration.php @@ -2,6 +2,8 @@ namespace ProcessMaker\Models; +use ProcessMaker\Traits\HasUuids; + /** * Represents an Eloquent model of a Request which is an instance of a Process. * @@ -12,6 +14,8 @@ */ class ProcessCollaboration extends ProcessMakerModel { + use HasUuids; + protected $connection = 'processmaker'; /** diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 03f8643a57..f963d47589 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -41,6 +41,7 @@ * @property string $name * @property string $status * @property string $data + * @property string $collaboration_uuid * @property \Carbon\Carbon $initiated_at * @property \Carbon\Carbon $completed_at * @property \Carbon\Carbon $updated_at diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php index e82a230fd3..7f3f99adf3 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php @@ -10,6 +10,8 @@ use ProcessMaker\Jobs\BoundaryEvent; use ProcessMaker\Jobs\CallProcess; use ProcessMaker\Jobs\CatchEvent; +use ProcessMaker\Jobs\CatchSignalEventInRequest; +use ProcessMaker\Jobs\CatchSignalEventProcess; use ProcessMaker\Jobs\CompleteActivity; use ProcessMaker\Jobs\RunScriptTask; use ProcessMaker\Jobs\RunServiceTask; @@ -18,6 +20,7 @@ use ProcessMaker\Jobs\ThrowSignalEvent; use ProcessMaker\Models\FormalExpression; use ProcessMaker\Models\Process as Definitions; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken as Token; use ProcessMaker\Nayra\Contracts\Bpmn\BoundaryEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; @@ -29,6 +32,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ThrowEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; +use ProcessMaker\Repositories\DefinitionsRepository; class WorkflowManagerDefault implements WorkflowManagerInterface { @@ -255,6 +259,38 @@ public function throwSignalEvent($signalRef, array $data = [], array $exclude = ThrowSignalEvent::dispatch($signalRef, $data, $exclude)->onQueue('bpmn'); } + /** + * Throw a signal event by signalRef into a specific process. + * + * @param int $process + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventProcess($processId, $signalRef, array $data) + { + CatchSignalEventProcess::dispatch( + $processId, + $signalRef, + $data + )->onQueue('bpmn'); + } + + /** + * Throw a signal event by signalRef into a specific request. + * + * @param ProcessRequest $request + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventRequest(ProcessRequest $request, $signalRef, array $data) + { + CatchSignalEventInRequest::dispatchNow( + $request, + $data, + $signalRef + )->onQueue('bpmn'); + } + /** * Catch a signal event. * diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 542447172c..c29cec1cd8 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -11,8 +11,11 @@ use ProcessMaker\Facades\WorkflowManager; use ProcessMaker\GenerateAccessToken; use ProcessMaker\Managers\DataManager; +use ProcessMaker\Managers\SignalManager; use ProcessMaker\Models\EnvironmentVariable; use ProcessMaker\Models\Process as Definitions; +use ProcessMaker\Models\Process; +use ProcessMaker\Models\ProcessCollaboration; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\Script; use ProcessMaker\Models\User; @@ -32,6 +35,7 @@ class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements Workflow const ACTION_RUN_SCRIPT = 'RUN_SCRIPT'; const ACTION_TRIGGER_BOUNDARY_EVENT = 'TRIGGER_BOUNDARY_EVENT'; const ACTION_TRIGGER_MESSAGE_EVENT = 'TRIGGER_MESSAGE_EVENT'; + const ACTION_TRIGGER_SIGNAL_EVENT = 'TRIGGER_SIGNAL_EVENT'; /** * Trigger a start event and return the process request instance. @@ -51,6 +55,9 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface $userId = $this->getCurrentUserId(); // Create immediately a new process request + $collaboration = ProcessCollaboration::create([ + 'process_id' => $definitions->id, + ]); $request = ProcessRequest::create([ 'process_id' => $definitions->id, 'user_id' => $userId, @@ -62,6 +69,8 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'initiated_at' => Carbon::now(), 'process_version_id' => $version->getKey(), 'signal_events' => [], + 'collaboration_uuid' => $collaboration->uuid, + 'process_collaboration_id' => $collaboration->id, ]); // Serialize instance @@ -374,6 +383,85 @@ public function throwMessageEvent($instanceId, $elementId, $messageRef, array $p ]); } + /** + * Throw a signal event by signalRef into a specific process. + * + * @param int $processId + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventProcess($processId, $signalRef, array $data) + { + // Get complementary information + $userId = $this->getCurrentUserId(); + // get process variable + $process = Process::find($processId); + $definitions = $process->getDefinitions(); + $catches = SignalManager::getSignalCatchEvents($signalRef, $definitions); + $processVariable = ''; + foreach ($catches as $catch) { + $processVariable = $definitions->getStartEvent($catch['id'])->getBpmnElement()->getAttribute('pm:config'); + } + if ($processVariable) { + $data = [ + $processVariable => $data, + ]; + } + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $process->getLatestVersion()->getKey(), + 'action' => self::ACTION_TRIGGER_SIGNAL_EVENT, + 'params' => [ + 'signal_ref' => $signalRef, + 'data' => $data, + ], + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + + /** + * Throw a signal event by signalRef into a specific request. + * + * @param Process $process + * @param string $signalRef + * @param array $data + */ + public function throwSignalEventRequest(ProcessRequest $request, $signalRef, array $data) + { + // Get complementary information + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($request); + // Get process variable + $definitions = $request->processVersion->getDefinitions(); + $catches = SignalManager::getSignalCatchEvents($signalRef, $definitions); + $processVariable = ''; + foreach ($catches as $catch) { + $processVariable = $definitions->getStartEvent($catch['id'])->getBpmnElement()->getAttribute('pm:config'); + } + if ($processVariable) { + $data = [ + $processVariable => $data, + ]; + } + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $request->process_version_id, + 'action' => self::ACTION_TRIGGER_SIGNAL_EVENT, + 'params' => [ + 'instance_id' => $request->uuid, + 'request_id' => $request->id, + 'signal_ref' => $signalRef, + 'data' => $data, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** * Build a state object. * @@ -382,28 +470,36 @@ public function throwMessageEvent($instanceId, $elementId, $messageRef, array $p */ private function serializeState(ProcessRequest $instance) { - // Get open tokens - $tokensRows = []; - $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->where('status', '!=', 'TRIGGERED')->get(); - foreach ($tokens as $token) { - $tokensRows[] = array_merge($token->token_properties ?: [], [ - 'id' => $token->uuid, - 'status' => $token->status, - 'index' => $token->element_index, - 'element_id' => $token->element_id, - 'created_at' => $token->created_at->getTimestamp(), - ]); + if ($instance->collaboration) { + $requests = $instance->collaboration->requests()->where('status', 'ACTIVE')->get(); + } else { + $requests = collect([$instance]); } + $requests = $requests->map(function ($request) { + // Get open tokens + $tokensRows = []; + $tokens = $request->tokens()->where('status', '!=', 'CLOSED')->where('status', '!=', 'TRIGGERED')->get(); + foreach ($tokens as $token) { + $tokensRows[] = array_merge($token->token_properties ?: [], [ + 'id' => $token->uuid, + 'status' => $token->status, + 'index' => $token->element_index, + 'element_id' => $token->element_id, + 'created_at' => $token->created_at->getTimestamp(), + ]); + } + + return [ + 'id' => $request->uuid, + 'callable_id' => $request->callable_id, + 'collaboration_uuid' => $request->collaboration_uuid, + 'data' => $request->data, + 'tokens' => $tokensRows, + ]; + }); return [ - 'requests' => [ - [ - 'id' => $instance->uuid, - 'callable_id' => $instance->callable_id, - 'data' => $instance->data, - 'tokens' => $tokensRows, - ], - ], + 'requests' => $requests->toArray(), ]; } diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php index 4485a81f3e..002f7031ad 100644 --- a/ProcessMaker/Nayra/Repositories/Deserializer.php +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -208,7 +208,7 @@ public function unserializeInstance(array $serialized): ExecutionInstanceInterfa * Return a process request token from serialized data * * @param array $serialized - * @return TokenInterface + * @return TokenInterface|ProcessRequestToken */ public function unserializeToken(array $serialized): TokenInterface { diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php index 51088a65df..d66b07ded5 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -4,8 +4,10 @@ use Exception; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use ProcessMaker\Listeners\BpmnSubscriber; use ProcessMaker\Managers\TaskSchedulerManager; +use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Repositories\ExecutionInstanceRepository; use ProcessMaker\Repositories\TokenRepository; @@ -16,8 +18,11 @@ class PersistenceHandler use PersistenceTimerEventsTrait; protected Deserializer $deserializer; + protected ExecutionInstanceRepository $instanceRepository; + protected TokenRepository $tokenRepository; + protected TaskSchedulerManager $taskSchedulerManager; /** @@ -45,91 +50,104 @@ public function save(array $transaction) Auth::loginUsingId($transaction['session']['user_id']); } - // Save data according to the type - switch ($transaction['type']) { - case 'activity_activated': - $this->persistActivityActivated($transaction); - break; - case 'activity_exception': - $this->persistActivityException($transaction); - break; - case 'activity_completed': - $this->persistActivityCompleted($transaction); - break; - case 'activity_closed': - $this->persistActivityClosed($transaction); - break; - case 'throw_event_token_arrives': - $this->persistThrowEventTokenArrives($transaction); - break; - case 'throw_event_token_consumed': - $this->persistThrowEventTokenConsumed($transaction); - break; - case 'throw_event_token_passed': - $this->persistThrowEventTokenPassed($transaction); - break; - case 'gateway_token_arrives': - $this->persistGatewayTokenArrives($transaction); - break; - case 'gateway_token_consumed': - $this->persistGatewayTokenConsumed($transaction); - break; - case 'gateway_token_passed': - $this->persistGatewayTokenPassed($transaction); - break; - case 'catch_event_token_arrives': - $this->persistCatchEventTokenArrives($transaction); - break; - case 'catch_event_token_consumed': - $this->persistCatchEventTokenConsumed($transaction); - break; - case 'catch_event_token_passed': - $this->persistCatchEventTokenPassed($transaction); - break; - case 'catch_event_message_arrives': - $this->persistCatchEventMessageArrives($transaction); - break; - case 'catch_event_message_consumed': - $this->persistCatchEventMessageConsumed($transaction); - break; - case 'start_event_triggered': - $this->persistStartEventTriggered($transaction); - break; - case 'event_based_gateway_activated': - $this->persistEventBasedGatewayActivated($transaction); - break; - case 'instance_created': - $this->persistInstanceCreated($transaction); - break; - case 'instance_completed': - $this->persistInstanceCompleted($transaction); - break; - case 'instance_collaboration': - $this->persistInstanceCollaboration($transaction); - break; - case 'instance_updated': - $this->persistInstanceUpdated($transaction); - break; - case 'schedule_date': - $this->persistScheduleDate($transaction); - break; - case 'schedule_cycle': - $this->persistScheduleCycle($transaction); - break; - case 'schedule_duration': - $this->persistScheduleDuration($transaction); - break; - case 'service_task_activated': - // Get object instances - $token = $this->deserializer->unserializeToken($transaction['token']); - $serviceTask = $this->deserializer->unserializeEntity($transaction['activity']); + try { + // Save data according to the type + switch ($transaction['type']) { + case 'activity_activated': + $this->persistActivityActivated($transaction); + break; + case 'activity_exception': + $this->persistActivityException($transaction); + break; + case 'activity_completed': + $this->persistActivityCompleted($transaction); + break; + case 'activity_closed': + $this->persistActivityClosed($transaction); + break; + case 'throw_event_token_arrives': + $this->persistThrowEventTokenArrives($transaction); + break; + case 'throw_event_token_consumed': + $this->persistThrowEventTokenConsumed($transaction); + break; + case 'throw_event_token_passed': + $this->persistThrowEventTokenPassed($transaction); + break; + case 'gateway_token_arrives': + $this->persistGatewayTokenArrives($transaction); + break; + case 'gateway_token_consumed': + $this->persistGatewayTokenConsumed($transaction); + break; + case 'gateway_token_passed': + $this->persistGatewayTokenPassed($transaction); + break; + case 'catch_event_token_arrives': + $this->persistCatchEventTokenArrives($transaction); + break; + case 'catch_event_token_consumed': + $this->persistCatchEventTokenConsumed($transaction); + break; + case 'catch_event_token_passed': + $this->persistCatchEventTokenPassed($transaction); + break; + case 'catch_event_message_arrives': + $this->persistCatchEventMessageArrives($transaction); + break; + case 'catch_event_message_consumed': + $this->persistCatchEventMessageConsumed($transaction); + break; + case 'start_event_triggered': + $this->persistStartEventTriggered($transaction); + break; + case 'event_based_gateway_activated': + $this->persistEventBasedGatewayActivated($transaction); + break; + case 'instance_created': + $this->persistInstanceCreated($transaction); + break; + case 'instance_completed': + $this->persistInstanceCompleted($transaction); + break; + case 'instance_collaboration': + $this->persistInstanceCollaboration($transaction); + break; + case 'instance_updated': + $this->persistInstanceUpdated($transaction); + break; + case 'schedule_date': + $this->persistScheduleDate($transaction); + break; + case 'schedule_cycle': + $this->persistScheduleCycle($transaction); + break; + case 'schedule_duration': + $this->persistScheduleDuration($transaction); + break; + case 'service_task_activated': + // Get object instances + $token = $this->deserializer->unserializeToken($transaction['token']); + $serviceTask = $this->deserializer->unserializeEntity($transaction['activity']); - // Trigger service task listener - $subscriber = new BpmnSubscriber(); - $subscriber->onServiceTaskActivated($serviceTask, $token); - break; - default: - throw new Exception('Unknown transaction type ' . $transaction['type']); + // Trigger service task listener + $subscriber = new BpmnSubscriber(); + $subscriber->onServiceTaskActivated($serviceTask, $token); + break; + default: + throw new Exception('Unknown transaction type ' . $transaction['type']); + } + } catch (Exception $error) { + Log::error($error->getMessage()); + if ($transaction['token']) { + $token = $this->deserializer->unserializeToken($transaction['token']); + $request = $token->getInstance(); + if ($request && $request instanceof ProcessRequest) { + $request->logError($error); + $request->status = 'ERROR'; + $request->save(); + } + } } } } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php index b38e6de995..bb0f3d64c8 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php @@ -7,6 +7,7 @@ trait PersistenceRequestTrait { protected ExecutionInstanceRepository $instanceRepository; + protected Deserializer $deserializer; /** * Store data related to the event Process Instance Created diff --git a/ProcessMaker/Repositories/ExecutionInstanceRepository.php b/ProcessMaker/Repositories/ExecutionInstanceRepository.php index ef5c47b0c5..281d8d21f3 100644 --- a/ProcessMaker/Repositories/ExecutionInstanceRepository.php +++ b/ProcessMaker/Repositories/ExecutionInstanceRepository.php @@ -61,9 +61,9 @@ public function createExecutionInstance(): ExecutionInstanceInterface * @param string $instanceId * @param StorageInterface $storage * - * @return ExecutionInstanceInterface + * @return ExecutionInstanceInterface|null */ - public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ExecutionInstanceInterface + public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ?ExecutionInstanceInterface { // Get process request if (is_numeric($instanceId)) { @@ -152,6 +152,7 @@ public function persistInstanceCreated(ExecutionInstanceInterface $instance) // Save process request $instance->callable_id = $process->getId(); + $instance->collaboration_uuid = $instance->getProperty('collaboration_uuid', null); $instance->process_id = $definition->getKey(); $instance->process_version_id = $definition->getLatestVersion()->getKey(); $instance->user_id = pmUser() ? pmUser()->getKey() : null; @@ -208,7 +209,9 @@ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) } // Save updated instance - $instance->status = 'ACTIVE'; + if (!$instance->status) { + $instance->status = 'ACTIVE'; + } $instance->mergeLatestStoredData(); $instance->saveOrFail(); } @@ -278,28 +281,40 @@ private function persistCollaboration(ProcessRequest $request) { // Get valid engine $engine = $request->getProcess()->getEngine(); - if (count($engine->getExecutionInstances()) <= 1) { - return; - } + if ($engine) { + if (count($engine->getExecutionInstances()) <= 1) { + return; + } - // Get current collaboration - $collaboration = null; - foreach ($engine->getExecutionInstances() as $instance) { - if ($instance->collaboration) { - $collaboration = $instance->collaboration; - break; + // Get current collaboration + $collaboration = null; + foreach ($engine->getExecutionInstances() as $instance) { + if ($instance->collaboration) { + $collaboration = $instance->collaboration; + break; + } } - } - // If not exists a collaboration, create a new one - if (!$collaboration) { - $collaboration = new ProcessCollaboration(); - $collaboration->process_id = $request->process->getKey(); - $collaboration->saveOrFail(); - } + // If not exists a collaboration, create a new one + if (!$collaboration) { + $collaboration = new ProcessCollaboration(); + $collaboration->process_id = $request->process->getKey(); + $collaboration->saveOrFail(); + } - // Update collaboration id - $request->process_collaboration_id = $collaboration->id; - $request->saveOrFail(); + // Update collaboration id + $request->process_collaboration_id = $collaboration->id; + $request->saveOrFail(); + } elseif ($request->collaboration_uuid) { + // find by uuid or create + $collaboration = ProcessCollaboration::firstOrCreate([ + 'uuid' => $request->collaboration_uuid, + 'process_id' => $request->process_id, + ]); + if ($collaboration) { + $request->process_collaboration_id = $collaboration->id; + $request->saveOrFail(); + } + } } } diff --git a/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php new file mode 100644 index 0000000000..c4ee9f5a10 --- /dev/null +++ b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php @@ -0,0 +1,35 @@ +uuid('collaboration_uuid')->nullable()->after('id'); + $table->index('collaboration_uuid', 'idx_collaboration_uuid'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('process_requests', function (Blueprint $table) { + // drop collaboration_uuid column + $table->dropColumn('collaboration_uuid'); + }); + } +} diff --git a/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php b/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php new file mode 100644 index 0000000000..3a34452b65 --- /dev/null +++ b/database/migrations/2023_06_29_110207_add_uuid_to_process_collaborations_table.php @@ -0,0 +1,35 @@ +uuid('uuid')->nullable()->after('id'); + $table->index('uuid', 'idx_uuid'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('process_collaborations', function (Blueprint $table) { + // drop collaboration_uuid column + $table->dropColumn('uuid'); + }); + } +} diff --git a/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php new file mode 100644 index 0000000000..daff8d1a8a --- /dev/null +++ b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php @@ -0,0 +1,59 @@ +uuid = ProcessCollaboration::generateUuid(); + $collaboration->save(); + } + foreach (ProcessRequest::cursor() as $processRequest) { + if ($processRequest->process_collaboration_id) { + $processRequest->collaboration_uuid = $processRequest->collaboration->uuid; + } else { + $processRequest->collaboration_uuid = ProcessCollaboration::generateUuid(); + } + $processRequest->save(); + } + } + + /** + * Reverse the upgrade migration. + * + * @return void + */ + public function down() + { + // + } +} From 794d6efb371399362acaf7433d780d16d9116e85 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 3 Jul 2023 11:34:07 -0400 Subject: [PATCH 12/18] Fix sync dispatch of throwSignalEventRequest --- ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php index 7f3f99adf3..46f9674878 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerDefault.php @@ -284,11 +284,11 @@ public function throwSignalEventProcess($processId, $signalRef, array $data) */ public function throwSignalEventRequest(ProcessRequest $request, $signalRef, array $data) { - CatchSignalEventInRequest::dispatchNow( + CatchSignalEventInRequest::dispatchSync( $request, $data, $signalRef - )->onQueue('bpmn'); + ); } /** From 889848eba131f92ab9f2a47d7c7280f00bf4f0ae Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 3 Jul 2023 13:21:27 -0400 Subject: [PATCH 13/18] Validate rabbitmq port --- config/rabbitmq.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/rabbitmq.php b/config/rabbitmq.php index c1d178c458..5ead5f127a 100644 --- a/config/rabbitmq.php +++ b/config/rabbitmq.php @@ -1,5 +1,10 @@ Enqueue\AmqpLib\AmqpConnectionFactory::class, 'host' => env('RABBITMQ_HOST', '127.0.0.1'), - 'port' => env('RABBITMQ_PORT', 5672), + 'port' => $_port, 'vhost' => env('RABBITMQ_VHOST', '/'), 'login' => env('RABBITMQ_LOGIN', 'guest'), From f1f53c3e737a8e7e3106efe540e51431280ec81b Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 3 Jul 2023 13:21:27 -0400 Subject: [PATCH 14/18] Validate rabbitmq port --- config/rabbitmq.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/rabbitmq.php b/config/rabbitmq.php index c1d178c458..5ead5f127a 100644 --- a/config/rabbitmq.php +++ b/config/rabbitmq.php @@ -1,5 +1,10 @@ Enqueue\AmqpLib\AmqpConnectionFactory::class, 'host' => env('RABBITMQ_HOST', '127.0.0.1'), - 'port' => env('RABBITMQ_PORT', 5672), + 'port' => $_port, 'vhost' => env('RABBITMQ_VHOST', '/'), 'login' => env('RABBITMQ_LOGIN', 'guest'), From 950bad1f2905a739cd4a12f0e28bf43ee2ed2acb Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 5 Jul 2023 09:38:02 -0400 Subject: [PATCH 15/18] move column next to the collaboration id --- ..._110207_add_collaboration_uuid_to_process_requests_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php index c4ee9f5a10..fb8d5152f7 100644 --- a/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php +++ b/database/migrations/2023_06_29_110207_add_collaboration_uuid_to_process_requests_table.php @@ -15,7 +15,7 @@ public function up() { Schema::table('process_requests', function (Blueprint $table) { // add collaboration_uuid column - $table->uuid('collaboration_uuid')->nullable()->after('id'); + $table->uuid('collaboration_uuid')->nullable()->after('process_collaboration_id'); $table->index('collaboration_uuid', 'idx_collaboration_uuid'); }); } From 0b83f746bb560df3b15e5cdc282167ecb5bac011 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 5 Jul 2023 09:38:28 -0400 Subject: [PATCH 16/18] Improve upgrade script --- ...te_process_requests_collaboration_uuid.php | 60 ++++++------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php index daff8d1a8a..355ba01782 100644 --- a/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php +++ b/upgrades/2023_06_29_110455_populate_process_requests_collaboration_uuid.php @@ -6,26 +6,6 @@ class PopulateProcessRequestsCollaborationUuid extends Upgrade { - /** - * Run any validations/pre-run checks to ensure the environment, settings, - * packages installed, etc. are right correct to run this upgrade. - * - * Throw a \RuntimeException if the conditions are *NOT* correct for this - * upgrade migration to run. If this is not a required upgrade, then it - * will be skipped. Otherwise the exception thrown will be caught, noted, - * and will prevent the remaining migrations from continuing to run. - * - * Returning void or null denotes the checks were successful. - * - * @return void - * - * @throws \RuntimeException - */ - public function preflightChecks() - { - // - } - /** * Run the upgrade migration. * @@ -33,27 +13,25 @@ public function preflightChecks() */ public function up() { - foreach (ProcessCollaboration::cursor() as $collaboration) { - $collaboration->uuid = ProcessCollaboration::generateUuid(); - $collaboration->save(); - } - foreach (ProcessRequest::cursor() as $processRequest) { - if ($processRequest->process_collaboration_id) { - $processRequest->collaboration_uuid = $processRequest->collaboration->uuid; - } else { - $processRequest->collaboration_uuid = ProcessCollaboration::generateUuid(); - } - $processRequest->save(); - } - } + $batchSize = 5; + ProcessCollaboration::whereNull('uuid') + ->chunkById($batchSize, function ($collaborations) { + foreach ($collaborations as $collaboration) { + $collaboration->uuid = ProcessCollaboration::generateUuid(); + $collaboration->save(); + } + }); - /** - * Reverse the upgrade migration. - * - * @return void - */ - public function down() - { - // + ProcessRequest::select(['id', 'process_collaboration_id', 'collaboration_uuid'])->whereNull('collaboration_uuid') + ->chunkById($batchSize, function ($requests) { + foreach ($requests as $request) { + if ($request->process_collaboration_id) { + $request->collaboration_uuid = $request->collaboration->uuid; + } else { + $request->collaboration_uuid = ProcessCollaboration::generateUuid(); + } + $request->save(); + } + }); } } From aae29ea8cfa053ca8166b5eb133184c853e373cb Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 5 Jul 2023 10:23:44 -0400 Subject: [PATCH 17/18] Fix code --- ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index c29cec1cd8..53247c6ef6 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -424,7 +424,7 @@ public function throwSignalEventProcess($processId, $signalRef, array $data) /** * Throw a signal event by signalRef into a specific request. * - * @param Process $process + * @param ProcessRequest $request * @param string $signalRef * @param array $data */ From 80f6113d6444bc8cc74004451cd5ff984d561263 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 5 Jul 2023 17:38:20 -0400 Subject: [PATCH 18/18] Check invalid environment variable --- ProcessMaker/Models/EnvironmentVariable.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Models/EnvironmentVariable.php b/ProcessMaker/Models/EnvironmentVariable.php index a5f4605580..07efd060bf 100644 --- a/ProcessMaker/Models/EnvironmentVariable.php +++ b/ProcessMaker/Models/EnvironmentVariable.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Models; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\Rule; use ProcessMaker\Traits\Exportable; @@ -53,7 +54,13 @@ public function setValueAttribute($value) */ public function getValueAttribute() { - return decrypt($this->attributes['value']); + try { + return decrypt($this->attributes['value']); + } catch (\Exception $e) { + Log::error('EnvironmentVariable: ' . $this->attributes['value']. "\n" . $e->getMessage()); + Log::error($e->getTraceAsString()); + return null; + } } public static function rules($existing = null)