From 85ddb654a05b1bc11288b6a332fa02cddca28c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Wed, 7 Jun 2023 20:37:06 +0000 Subject: [PATCH 01/13] FOUR-8841 Improve start and complete events using the existing main class TokenRepository - Request creation --- .../Managers/WorkflowManagerRabbitMq.php | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index ad7d1e02a3..4fc416583d 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -34,22 +34,12 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface $version = $definitions->getLatestVersion(); $userId = $this->getCurrentUserId(); - // Create inmediatly a new process request - $request = ProcessRequest::create([ - 'process_id' => $definitions->id, - 'user_id' => $userId, - 'callable_id' => $event->getProcess()->getId(), - 'status' => 'ACTIVE', - 'data' => $data, - 'name' => $definitions->name, - 'do_not_sanitize' => [], - 'initiated_at' => Carbon::now(), - 'process_version_id' => $version->getKey(), - 'signal_events' => [], - ]); - - // Create triggered - // TO DO: + //Create a new data store + $process = $event->getProcess(); + $dataStorage = $process->getRepository()->createDataStore(); + $dataStorage->setData($data); + $request = $process->getEngine()->createExecutionInstance($process, $dataStorage); + $event->start($request); // Dispatch start process action $this->dispatchAction([ @@ -78,6 +68,7 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface ], ]); + //Return the instance created return $request; } From ee3732c4d14e5052e8c3fe01815b34425f382d04 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Mon, 12 Jun 2023 12:20:51 -0400 Subject: [PATCH 02/13] Refactor Persistance Handler --- ProcessMaker/Http/Kernel.php | 6 + .../Managers/WorkflowManagerRabbitMq.php | 58 +++--- .../Nayra/MessageBrokers/ServiceKafka.php | 9 +- .../Nayra/MessageBrokers/ServiceRabbitMq.php | 5 +- .../Nayra/Repositories/Deserializer.php | 172 ++++++++++++++++++ .../Nayra/Repositories/PersistanceHandler.php | 100 ++++++++++ .../Repositories/PersistanceRequestTrait.php | 36 ++++ .../Repositories/PersistanceTokenTrait.php | 135 ++++++++++++++ .../Providers/RouteServiceProvider.php | 15 ++ .../ExecutionInstanceRepository.php | 27 ++- ProcessMaker/Repositories/TokenRepository.php | 6 +- routes/api.php | 16 -- routes/engine.php | 21 +++ 13 files changed, 556 insertions(+), 50 deletions(-) create mode 100644 ProcessMaker/Nayra/Repositories/Deserializer.php create mode 100644 ProcessMaker/Nayra/Repositories/PersistanceHandler.php create mode 100644 ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php create mode 100644 ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php create mode 100644 routes/engine.php diff --git a/ProcessMaker/Http/Kernel.php b/ProcessMaker/Http/Kernel.php index 4430954207..9865b838a5 100644 --- a/ProcessMaker/Http/Kernel.php +++ b/ProcessMaker/Http/Kernel.php @@ -45,6 +45,12 @@ class Kernel extends HttpKernel // API Middleware is defined with routeMiddleware below. // See routes/api.php ], + 'engine' => [ + 'auth:api', + 'setlocale', + 'bindings', + 'sanitize', + ], ]; /** diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 167088d465..a73fb0e649 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -48,9 +48,6 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'signal_events' => [], ]); - // Create triggered - // TO DO: - // Dispatch start process action $this->dispatchAction([ 'bpmn' => $version->getKey(), @@ -76,6 +73,9 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface ] ], ], + 'session' => [ + 'user_id' => $userId, + ], ]); //Return the instance created @@ -100,18 +100,9 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac // Get complementary information $version = $definitions->getLatestVersion(); + $userId = $this->getCurrentUserId(); - // Get open tokens - $tokensRows = []; - $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->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, - ]); - } + $state = $this->serializeState($instance); // Dispatch complete task action $this->dispatchAction([ @@ -123,19 +114,38 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac 'element_id' => $token->element_id, 'data'=> $data, ], - 'state' => [ - 'requests' => [ - [ - 'id' => $instance->uuid, - 'callable_id' => $instance->callable_id, - 'data' => $instance->data, - 'tokens' => $tokensRows, - ] - ], - ] + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], ]); } + 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, + ]); + } + return [ + 'requests' => [ + [ + 'id' => $instance->uuid, + 'callable_id' => $instance->callable_id, + 'data' => $instance->data, + 'tokens' => $tokensRows, + ], + ], + ]; + } + /** * Get the ID of the currently authenticated user. * diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php index 6536ed9c23..7d770cd180 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php @@ -4,7 +4,7 @@ use Junges\Kafka\Facades\Kafka; use Junges\Kafka\Contracts\KafkaConsumerMessage; -use ProcessMaker\Nayra\Repositories\EntityRepositoryFactory; +use ProcessMaker\Nayra\Repositories\PersistanceHandler; class ServiceKafka { @@ -65,11 +65,11 @@ public function worker() // Get transactions $transactions = $message->getBody(); - // Store transsactions + // Store transactions $this->storeData($transactions); })->build(); - // Consume incomming messages + // Consume incoming messages $consumer->consume(); } @@ -80,8 +80,9 @@ public function worker() */ private function storeData(array $transactions) { + $handler = new PersistanceHandler(); foreach ($transactions as $transaction) { - EntityRepositoryFactory::createRepository($transaction['entity'])->save($transaction); + $handler->save($transaction); } } } diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php index 0ea16bd9fa..e6b8c482cb 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php @@ -4,7 +4,7 @@ use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; -use ProcessMaker\Nayra\Repositories\EntityRepositoryFactory; +use ProcessMaker\Nayra\Repositories\PersistanceHandler; class ServiceRabbitMq { @@ -123,8 +123,9 @@ public function worker(): void */ private function storeData(array $transactions): void { + $handler = new PersistanceHandler(); foreach ($transactions as $transaction) { - EntityRepositoryFactory::createRepository($transaction['entity'])->save($transaction); + $handler->save($transaction); } } } diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php new file mode 100644 index 0000000000..c724c59d76 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -0,0 +1,172 @@ +instanceRepository = new ExecutionInstanceRepository(); + $this->instanceRepository->setAbortIfInstanceNotFound(false); + // do not load all the active tokens to improve persistance performance + $this->instanceRepository->setLoadTokens(false); + $this->tokenRepository = new TokenRepository($this->instanceRepository); + $this->factory = new DefinitionsRepository(); + } + + private function findProcessDefinition($modelId): BpmnDocument + { + if (!isset($this->definitions[$modelId])) { + $version = ProcessVersion::find($modelId); + $model = $version->process; + $definition = new BpmnDocument($model); + $definition->setFactory($this->factory); + $definition->loadXML($version->bpmn); + $this->definitions[$modelId] = $definition; + } + + return $this->definitions[$modelId]; + } + + private function findRequest($instanceId, BpmnDocument $definition, $callableId): ProcessRequest + { + if (isset($this->requests[$instanceId])) { + return $this->requests[$instanceId]; + } + $instance = $this->instanceRepository->loadExecutionInstanceByUid($instanceId, $definition); + if (!$instance && !is_numeric($instanceId)) { + $instance = $this->instanceRepository->createExecutionInstance(); + $instance->setId($instanceId); + $instance->uuid = $instanceId; + $instance->setProcess($definition->getProcess($callableId)); + $dataStore = $this->factory->createDataStore(); + $instance->setDataStore($dataStore); + } elseif (!$instance) { + throw new Exception("ProcessRequest {$instanceId} not found"); + } + $this->requests[$instance->getId()] = $instance; + $this->requests[$instance->uuid] = $instance; + + return $instance; + } + + private function findRequestToken($tokenId, $instanceId, $elementId, BpmnDocument $definition): ProcessRequestToken + { + if (isset($this->tokens[$tokenId])) { + return $this->tokens[$tokenId]; + } + $element = $definition->getElementInstanceById($elementId); + $process = $element->getProcess(); + $instance = $this->findRequest($instanceId, $definition, $process->getId()); + // find token + $tokens = $instance->getTokens(); + $token = $tokens->findFirst(function ($token) use ($tokenId) { + return $token->getId() === $tokenId; + }); + if ($token) { + $this->tokens[$token->getId()] = $token; + $this->tokens[$token->uuid] = $token; + + return $token; + } + // find token in the database + $token = $this->tokenRepository->loadTokenByUid($tokenId); + if (!$token && !is_numeric($tokenId)) { + // create token if not found + $token = $this->tokenRepository->createTokenInstance(); + $token->setId($tokenId); + $token->uuid = $tokenId; + } elseif ($token) { + $tokenInfo = [ + 'id' => $token->getKey(), + 'status' => $token->status, + 'index' => $token->element_index, + 'element_ref' => $token->element_id, + ]; + $properties = array_merge($token->token_properties ?: [], $tokenInfo); + $token->setProperties($properties); + } else { + throw new Exception("ProcessRequestToken {$tokenId} not found"); + } + // Add token to element and instance + if ($element instanceof FlowNodeInterface) { + $element->addToken($instance, $token); + } else { + throw new Exception('Invalid element type ' . $elementId . ' for token ' . $tokenId); + } + $token->setInstance($instance); + // Add token to the index array + $this->tokens[$token->getId()] = $token; + $this->tokens[$token->uuid] = $token; + + return $token; + } + + public function unserializeEntity($serialized):EntityInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + return $definition->getElementInstanceById($serialized['id']); + } + + public function unserializeToken(array $serialized):TokenInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + $token = $this->findRequestToken($serialized['token_id'], $serialized['instance_id'], $serialized['element_id'], $definition); + $properties = $serialized['properties'] ?? []; + unset($properties['id']); + $properties = array_merge($token->getProperties(), $properties); + $token->setProperties($properties); + + return $token; + } + + public function unserializeTokensCollection(array $serialized):CollectionInterface + { + $collection = new Collection(); + foreach ($serialized as $item) { + $collection->push($this->unserializeToken($item)); + } + + return $collection; + } + + public function unserializeInstance(array $serialized):ExecutionInstanceInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + $instance = $this->findRequest($serialized['id'], $definition, $serialized['process_id']); + $properties = $serialized['properties'] ?? []; + unset($properties['id']); + $properties = array_merge($instance->getProperties(), $properties); + $instance->setProperties($properties); + + return $instance; + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistanceHandler.php b/ProcessMaker/Nayra/Repositories/PersistanceHandler.php new file mode 100644 index 0000000000..b691c1ad40 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistanceHandler.php @@ -0,0 +1,100 @@ +deserializer = new Deserializer(); + $this->instanceRepository = new ExecutionInstanceRepository(); + $this->tokenRepository = new TokenRepository($this->instanceRepository); + } + + public function save(array $transaction) + { + dump($transaction); + // initialize the session + if (isset($transaction['session']) && !empty($transaction['session']['user_id']) && Auth::id() !== $transaction['session']['user_id']) { + Auth::loginUsingId($transaction['session']['user_id']); + } + 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; + default: + throw new Exception('Unknown transaction type ' . $transaction['type']); + } + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php new file mode 100644 index 0000000000..c25aa5023f --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php @@ -0,0 +1,36 @@ +deserializer->unserializeInstance($transaction['instance']); + $this->instanceRepository->persistInstanceCreated($instance); + } + + public function persistInstanceCompleted(array $transaction) + { + $instance = $this->deserializer->unserializeInstance($transaction['instance']); + $this->instanceRepository->persistInstanceCompleted($instance); + } + + public function persistInstanceCollaboration(array $transaction) + { + $targetInstance = $this->deserializer->unserializeInstance($transaction['target_instance']); + $targetParticipant = $this->deserializer->unserializeEntity($transaction['target_participant']); + $sourceInstance = $this->deserializer->unserializeInstance($transaction['source_instance']); + $sourceParticipant = $this->deserializer->unserializeEntity($transaction['source_participant']); + $this->instanceRepository->persistInstanceCollaboration($targetInstance, $targetParticipant, $sourceInstance, $sourceParticipant); + } +} diff --git a/ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php b/ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php new file mode 100644 index 0000000000..ce069f9a12 --- /dev/null +++ b/ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php @@ -0,0 +1,135 @@ +deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityActivated($activity, $token); + } + + public function persistActivityException(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityException($activity, $token); + } + + public function persistActivityCompleted(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityCompleted($activity, $token); + } + + public function persistActivityClosed(array $transaction) + { + $activity = $this->deserializer->unserializeEntity($transaction['activity']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistActivityClosed($activity, $token); + } + + public function persistThrowEventTokenArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenArrives($event, $token); + } + + public function persistThrowEventTokenConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenConsumed($event, $token); + } + + public function persistThrowEventTokenPassed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistThrowEventTokenPassed($event, $token); + } + + public function persistGatewayTokenArrives(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenArrives($gateway, $token); + } + + public function persistGatewayTokenConsumed(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenConsumed($gateway, $token); + } + + public function persistGatewayTokenPassed(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistGatewayTokenPassed($gateway, $token); + } + + public function persistCatchEventTokenArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventTokenArrives($event, $token); + } + + public function persistCatchEventTokenConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventTokenConsumed($event, $token); + } + + public function persistCatchEventTokenPassed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistCatchEventTokenPassed($event, $consumedTokens); + } + + public function persistCatchEventMessageArrives(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventMessageArrives($event, $token); + } + + public function persistCatchEventMessageConsumed(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['catch_event']); + $token = $this->deserializer->unserializeToken($transaction['token']); + $this->tokenRepository->persistCatchEventMessageConsumed($event, $token); + } + + public function persistStartEventTriggered(array $transaction) + { + $event = $this->deserializer->unserializeEntity($transaction['start_event']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistStartEventTriggered($event, $consumedTokens); + } + + public function persistEventBasedGatewayActivated(array $transaction) + { + $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); + $passedToken = $this->deserializer->unserializeToken($transaction['passed_token']); + $consumedTokens = $this->deserializer->unserializeTokensCollection($transaction['consumed_tokens']); + $this->tokenRepository->persistEventBasedGatewayActivated($gateway, $passedToken, $consumedTokens); + } +} diff --git a/ProcessMaker/Providers/RouteServiceProvider.php b/ProcessMaker/Providers/RouteServiceProvider.php index e200d3b6f7..921bc4e692 100644 --- a/ProcessMaker/Providers/RouteServiceProvider.php +++ b/ProcessMaker/Providers/RouteServiceProvider.php @@ -61,6 +61,8 @@ public function map() $this->mapApiRoutes(); $this->mapWebRoutes(); + + $this->mapEngineRoutes(); } /** @@ -89,4 +91,17 @@ protected function mapApiRoutes() Route::middleware('api') ->group(base_path('routes/api.php')); } + + /** + * Define the "engine" routes for the application. + * + * These routes are typically stateless and high performant. + * + * @return void + */ + protected function mapEngineRoutes() + { + Route::middleware('engine') + ->group(base_path('routes/engine.php')); + } } diff --git a/ProcessMaker/Repositories/ExecutionInstanceRepository.php b/ProcessMaker/Repositories/ExecutionInstanceRepository.php index ca95546334..f80bf6bb33 100644 --- a/ProcessMaker/Repositories/ExecutionInstanceRepository.php +++ b/ProcessMaker/Repositories/ExecutionInstanceRepository.php @@ -20,6 +20,18 @@ class ExecutionInstanceRepository implements ExecutionInstanceRepositoryInterfac { use RepositoryTrait; + private bool $abortIfInstanceNotFound = true; + private bool $loadTokens = true; + + public function setAbortIfInstanceNotFound(bool $abortIfInstanceNotFound) + { + $this->abortIfInstanceNotFound = $abortIfInstanceNotFound; + } + public function setLoadTokens(bool $loadTokens) + { + $this->loadTokens = $loadTokens; + } + /** * Create an execution instance. * @@ -42,19 +54,28 @@ public function createExecutionInstance() */ public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage) { - $instance = Instance::find($instanceId); - if (!$instance) { + if (is_numeric($instanceId)) { + $instance = Instance::find($instanceId); + } else { + $instance = Instance::where('uuid', $instanceId)->first(); + } + if (!$instance && $this->abortIfInstanceNotFound) { abort(404, 'Instance not found'); + } elseif (!$instance) { + return null; } $callableId = $instance->callable_id; $process = $storage->getProcess($callableId); $dataStore = $storage->getFactory()->createDataStore(); $dataStore->setData($instance->data); - $instance->setId($instanceId); + $instance->setId($instance->getKey()); $instance->setProcess($process); $instance->setDataStore($dataStore); $process->getTransitions($storage->getFactory()); + if (!$this->loadTokens) { + return $instance; + } //Load tokens: $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->get(); foreach ($tokens as $token) { diff --git a/ProcessMaker/Repositories/TokenRepository.php b/ProcessMaker/Repositories/TokenRepository.php index d9d35cb433..697e5cc6c2 100644 --- a/ProcessMaker/Repositories/TokenRepository.php +++ b/ProcessMaker/Repositories/TokenRepository.php @@ -61,8 +61,12 @@ public function createTokenInstance(): TokenInterface return $token; } - public function loadTokenByUid($uid): TokenInterface + public function loadTokenByUid($uid): ?TokenInterface { + if (is_numeric($uid)) { + return Token::find($uid); + } + return Token::where('uuid', $uid)->first(); } /** diff --git a/routes/api.php b/routes/api.php index f657894495..735ea1a8e2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -34,22 +34,6 @@ use ProcessMaker\Http\Controllers\Process\ModelerController; use ProcessMaker\Http\Controllers\TestStatusController; -// Engine -Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () { - - // List of Processes that the user can start - Route::get('start_processes', [ProcessController::class, 'startProcesses'])->name('processes.start'); // Filtered in controller - - // Start a process - Route::post('process_events/{process}', [ProcessController::class, 'triggerStartEvent'])->name('process_events.trigger')->middleware('can:start,process'); - - // Update task - Route::put('tasks/{task}', [TaskController::class, 'update'])->name('tasks.update')->middleware('can:update,task'); - - // Trigger intermediate event - Route::post('requests/{request}/events/{event}', [ProcessRequestController::class, 'activateIntermediateEvent'])->name('requests.update,request'); -}); - Route::middleware('auth:api', 'setlocale', 'bindings', 'sanitize')->prefix('api/1.0')->name('api.')->group(function () { // Users Route::get('users', [UserController::class, 'index'])->name('users.index'); // Permissions handled in the controller diff --git a/routes/engine.php b/routes/engine.php new file mode 100644 index 0000000000..92f67def38 --- /dev/null +++ b/routes/engine.php @@ -0,0 +1,21 @@ +name('api.')->group(function () { + // List of Processes that the user can start + Route::get('start_processes', [ProcessController::class, 'startProcesses'])->name('processes.start'); // Filtered in controller + + // Start a process + Route::post('process_events/{process}', [ProcessController::class, 'triggerStartEvent'])->name('process_events.trigger')->middleware('can:start,process'); + + // Update task + Route::put('tasks/{task}', [TaskController::class, 'update'])->name('tasks.update')->middleware('can:update,task'); + + // Trigger intermediate event + Route::post('requests/{request}/events/{event}', [ProcessRequestController::class, 'activateIntermediateEvent'])->name('requests.update,request'); +}); From a1e87fa63b220e72ac8fb1cefab8fef23957a0c3 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 13 Jun 2023 13:48:33 -0400 Subject: [PATCH 03/13] Install Message Queue Extension dependency --- .circleci/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5f2d22b341..785c136906 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,6 +86,16 @@ jobs: echo "deb http://deb.debian.org/debian jessie main" | sudo tee -a /etc/apt/sources.list sudo apt-get update sudo apt-get install libssl1.0.0 + # Install Message Queue Extension dependency + - run: + name: 'Install Message Queue Extension dependency' + command: | + sudo apt-get install librabbitmq-dev librdkafka-dev + sudo docker-php-ext-install sockets + sudo pecl install amqp + sudo docker-php-ext-enable amqp + sudo pecl install rdkafka + sudo docker-php-ext-enable rdkafka # Download and cache dependencies - restore_cache: keys: From 8698988d34c86b6dd2cbe6b549cefdb32ffc441b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Tue, 13 Jun 2023 18:20:26 +0000 Subject: [PATCH 04/13] FOUR-8841 Improve start and complete events using the existing main class TokenRepository - Completing changes, clean and reorder code --- .../Managers/WorkflowManagerRabbitMq.php | 26 ++-- .../Nayra/MessageBrokers/ServiceKafka.php | 4 +- .../Nayra/MessageBrokers/ServiceRabbitMq.php | 4 +- .../Nayra/Repositories/Deserializer.php | 147 +++++++++++++----- ...anceHandler.php => PersistenceHandler.php} | 23 ++- ...tTrait.php => PersistenceRequestTrait.php} | 29 ++-- ...kenTrait.php => PersistenceTokenTrait.php} | 96 ++++++++++-- .../Repositories/ProcessRequestRepository.php | 101 ------------ .../ProcessRequestTokenRepository.php | 115 -------------- .../ExecutionInstanceRepository.php | 110 ++++++++----- ProcessMaker/Repositories/TokenRepository.php | 13 +- 11 files changed, 325 insertions(+), 343 deletions(-) rename ProcessMaker/Nayra/Repositories/{PersistanceHandler.php => PersistenceHandler.php} (92%) rename ProcessMaker/Nayra/Repositories/{PersistanceRequestTrait.php => PersistenceRequestTrait.php} (71%) rename ProcessMaker/Nayra/Repositories/{PersistanceTokenTrait.php => PersistenceTokenTrait.php} (71%) delete mode 100644 ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php delete mode 100644 ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index a73fb0e649..3276417cae 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -48,6 +48,9 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'signal_events' => [], ]); + // Serialize instance + $state = $this->serializeState($request); + // Dispatch start process action $this->dispatchAction([ 'bpmn' => $version->getKey(), @@ -56,23 +59,14 @@ public function triggerStartEvent(Definitions $definitions, StartEventInterface 'instance_id' => $request->uuid, 'request_id' => $request->getKey(), 'element_id' => $event->getId(), - 'data'=> $data, + 'data' => $data, 'extra_properties' => [ 'user_id' => $userId, 'process_id' => $definitions->id, 'request_id' => $request->getKey(), ], ], - 'state' => [ - 'requests' => [ - $request->uuid => [ - 'id' => $request->uuid, - 'callable_id' => $request->callable_id, - 'data' => $request->data, - 'tokens' => [], - ] - ], - ], + 'state' => $state, 'session' => [ 'user_id' => $userId, ], @@ -101,7 +95,6 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac // Get complementary information $version = $definitions->getLatestVersion(); $userId = $this->getCurrentUserId(); - $state = $this->serializeState($instance); // Dispatch complete task action @@ -112,7 +105,7 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac 'request_id' => $token->process_request_id, 'token_id' => $token->uuid, 'element_id' => $token->element_id, - 'data'=> $data, + 'data' => $data, ], 'state' => $state, 'session' => [ @@ -121,6 +114,12 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac ]); } + /** + * Build a state object + * + * @param ProcessRequest $instance + * @return array + */ private function serializeState(ProcessRequest $instance) { // Get open tokens @@ -134,6 +133,7 @@ private function serializeState(ProcessRequest $instance) 'element_id' => $token->element_id, ]); } + return [ 'requests' => [ [ diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php index 7d770cd180..5791c6af20 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceKafka.php @@ -4,7 +4,7 @@ use Junges\Kafka\Facades\Kafka; use Junges\Kafka\Contracts\KafkaConsumerMessage; -use ProcessMaker\Nayra\Repositories\PersistanceHandler; +use ProcessMaker\Nayra\Repositories\PersistenceHandler; class ServiceKafka { @@ -80,7 +80,7 @@ public function worker() */ private function storeData(array $transactions) { - $handler = new PersistanceHandler(); + $handler = new PersistenceHandler(); foreach ($transactions as $transaction) { $handler->save($transaction); } diff --git a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php index e6b8c482cb..66e2e77a95 100644 --- a/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php +++ b/ProcessMaker/Nayra/MessageBrokers/ServiceRabbitMq.php @@ -4,7 +4,7 @@ use PhpAmqpLib\Connection\AMQPStreamConnection; use PhpAmqpLib\Message\AMQPMessage; -use ProcessMaker\Nayra\Repositories\PersistanceHandler; +use ProcessMaker\Nayra\Repositories\PersistenceHandler; class ServiceRabbitMq { @@ -123,7 +123,7 @@ public function worker(): void */ private function storeData(array $transactions): void { - $handler = new PersistanceHandler(); + $handler = new PersistenceHandler(); foreach ($transactions as $transaction) { $handler->save($transaction); } diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php index c724c59d76..62c1a9ec14 100644 --- a/ProcessMaker/Nayra/Repositories/Deserializer.php +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -3,14 +3,12 @@ namespace ProcessMaker\Nayra\Repositories; use Exception; -use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; use ProcessMaker\Models\ProcessVersion; use ProcessMaker\Nayra\Bpmn\Collection; use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EntityInterface; -use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; use ProcessMaker\Repositories\BpmnDocument; @@ -21,86 +19,128 @@ class Deserializer { private $definitions = []; - private $requests = []; - private $tokens = []; - private ExecutionInstanceRepository $instanceRepository; - private TokenRepository $tokenRepository; - private DefinitionsRepository $factory; + /** + * Deserializer constructor. + */ public function __construct() { $this->instanceRepository = new ExecutionInstanceRepository(); $this->instanceRepository->setAbortIfInstanceNotFound(false); - // do not load all the active tokens to improve persistance performance + // Do not load all the active tokens to improve persistence performance $this->instanceRepository->setLoadTokens(false); $this->tokenRepository = new TokenRepository($this->instanceRepository); $this->factory = new DefinitionsRepository(); } + /** + * Find process definition + * + * @param int $modelId + * @return BpmnDocument + */ private function findProcessDefinition($modelId): BpmnDocument { if (!isset($this->definitions[$modelId])) { $version = ProcessVersion::find($modelId); $model = $version->process; + $definition = new BpmnDocument($model); $definition->setFactory($this->factory); $definition->loadXML($version->bpmn); + $this->definitions[$modelId] = $definition; } return $this->definitions[$modelId]; } - private function findRequest($instanceId, BpmnDocument $definition, $callableId): ProcessRequest + /** + * Find process request + * + * @param int|string $instanceId + * @param BpmnDocument $definition + * @param string $processId + * @return ProcessRequest + * + * @throws Exception + */ + private function findRequest($instanceId, BpmnDocument $definition, $processId): ProcessRequest { + // Return process request if already stored if (isset($this->requests[$instanceId])) { return $this->requests[$instanceId]; } + + // Load process request $instance = $this->instanceRepository->loadExecutionInstanceByUid($instanceId, $definition); + + // If not exists, create a new one if (!$instance && !is_numeric($instanceId)) { $instance = $this->instanceRepository->createExecutionInstance(); $instance->setId($instanceId); $instance->uuid = $instanceId; - $instance->setProcess($definition->getProcess($callableId)); + $instance->setProcess($definition->getProcess($processId)); $dataStore = $this->factory->createDataStore(); $instance->setDataStore($dataStore); } elseif (!$instance) { - throw new Exception("ProcessRequest {$instanceId} not found"); + throw new Exception("ProcessRequest {$instanceId} not found."); } + + // Store process request finded $this->requests[$instance->getId()] = $instance; $this->requests[$instance->uuid] = $instance; return $instance; } + /** + * Find process request token + * + * @param int|string $tokenId + * @param int|string $instanceId + * @param string $elementId + * @param BpmnDocument $definition + * @return ProcessRequestToken + * + * @throws Exception + */ private function findRequestToken($tokenId, $instanceId, $elementId, BpmnDocument $definition): ProcessRequestToken { + // Return process request token if already stored if (isset($this->tokens[$tokenId])) { return $this->tokens[$tokenId]; } + + // Load process request $element = $definition->getElementInstanceById($elementId); $process = $element->getProcess(); $instance = $this->findRequest($instanceId, $definition, $process->getId()); - // find token + + // Load tokens of process request $tokens = $instance->getTokens(); $token = $tokens->findFirst(function ($token) use ($tokenId) { return $token->getId() === $tokenId; }); + + // Store and return process request token if finded if ($token) { $this->tokens[$token->getId()] = $token; $this->tokens[$token->uuid] = $token; return $token; } - // find token in the database + + // Load process request token $token = $this->tokenRepository->loadTokenByUid($tokenId); + + // If not exists, create a new one if (!$token && !is_numeric($tokenId)) { - // create token if not found $token = $this->tokenRepository->createTokenInstance(); $token->setId($tokenId); $token->uuid = $tokenId; @@ -114,41 +154,84 @@ private function findRequestToken($tokenId, $instanceId, $elementId, BpmnDocumen $properties = array_merge($token->token_properties ?: [], $tokenInfo); $token->setProperties($properties); } else { - throw new Exception("ProcessRequestToken {$tokenId} not found"); - } - // Add token to element and instance - if ($element instanceof FlowNodeInterface) { - $element->addToken($instance, $token); - } else { - throw new Exception('Invalid element type ' . $elementId . ' for token ' . $tokenId); + throw new Exception("ProcessRequestToken {$tokenId} not found."); } + + // Set process request to the token $token->setInstance($instance); - // Add token to the index array + + // Store process request token finded $this->tokens[$token->getId()] = $token; $this->tokens[$token->uuid] = $token; return $token; } - public function unserializeEntity($serialized):EntityInterface + /** + * Return a process request from serialized data + * + * @param array $serialized + * @return ExecutionInstanceInterface + */ + public function unserializeInstance(array $serialized): ExecutionInstanceInterface { + // Extract properties $definition = $this->findProcessDefinition($serialized['model_id']); - return $definition->getElementInstanceById($serialized['id']); + $instance = $this->findRequest($serialized['id'], $definition, $serialized['process_id']); + $properties = $serialized['properties'] ?? []; + + // Remove the id + unset($properties['id']); + + // Set process request properties + $properties = array_merge($instance->getProperties(), $properties); + $instance->setProperties($properties); + + return $instance; } - public function unserializeToken(array $serialized):TokenInterface + /** + * Return a process request token from serialized data + * + * @param array $serialized + * @return TokenInterface + */ + public function unserializeToken(array $serialized): TokenInterface { + // Extract properties $definition = $this->findProcessDefinition($serialized['model_id']); $token = $this->findRequestToken($serialized['token_id'], $serialized['instance_id'], $serialized['element_id'], $definition); $properties = $serialized['properties'] ?? []; + + // Remove the id unset($properties['id']); + + // Set process request token properties $properties = array_merge($token->getProperties(), $properties); $token->setProperties($properties); return $token; } - public function unserializeTokensCollection(array $serialized):CollectionInterface + /** + * Return entity from serialized data + * + * @param array $serialized + * @return EntityInterface + */ + public function unserializeEntity(array $serialized): EntityInterface + { + $definition = $this->findProcessDefinition($serialized['model_id']); + return $definition->getElementInstanceById($serialized['id']); + } + + /** + * Return tokens collection from serialized data + * + * @param array $serialized + * @return CollectionInterface + */ + public function unserializeTokensCollection(array $serialized): CollectionInterface { $collection = new Collection(); foreach ($serialized as $item) { @@ -157,16 +240,4 @@ public function unserializeTokensCollection(array $serialized):CollectionInterfa return $collection; } - - public function unserializeInstance(array $serialized):ExecutionInstanceInterface - { - $definition = $this->findProcessDefinition($serialized['model_id']); - $instance = $this->findRequest($serialized['id'], $definition, $serialized['process_id']); - $properties = $serialized['properties'] ?? []; - unset($properties['id']); - $properties = array_merge($instance->getProperties(), $properties); - $instance->setProperties($properties); - - return $instance; - } } diff --git a/ProcessMaker/Nayra/Repositories/PersistanceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php similarity index 92% rename from ProcessMaker/Nayra/Repositories/PersistanceHandler.php rename to ProcessMaker/Nayra/Repositories/PersistenceHandler.php index b691c1ad40..2ed1f1a56b 100644 --- a/ProcessMaker/Nayra/Repositories/PersistanceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -7,17 +7,18 @@ use ProcessMaker\Repositories\ExecutionInstanceRepository; use ProcessMaker\Repositories\TokenRepository; -class PersistanceHandler +class PersistenceHandler { - use PersistanceRequestTrait; - use PersistanceTokenTrait; + use PersistenceRequestTrait; + use PersistenceTokenTrait; protected Deserializer $deserializer; - protected ExecutionInstanceRepository $instanceRepository; - protected TokenRepository $tokenRepository; + /** + * PersistenceHandler constructor + */ public function __construct() { $this->deserializer = new Deserializer(); @@ -25,13 +26,21 @@ public function __construct() $this->tokenRepository = new TokenRepository($this->instanceRepository); } + /** + * Save data + * + * @param array $transaction + * + * @throws Exception + */ public function save(array $transaction) { - dump($transaction); - // initialize the session + // Initialize session if (isset($transaction['session']) && !empty($transaction['session']['user_id']) && Auth::id() !== $transaction['session']['user_id']) { Auth::loginUsingId($transaction['session']['user_id']); } + + // Save data according to the type switch ($transaction['type']) { case 'activity_activated': $this->persistActivityActivated($transaction); diff --git a/ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php similarity index 71% rename from ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php rename to ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php index c25aa5023f..574662d42b 100644 --- a/ProcessMaker/Nayra/Repositories/PersistanceRequestTrait.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php @@ -2,35 +2,44 @@ namespace ProcessMaker\Nayra\Repositories; -use ProcessMaker\Repositories\ExecutionInstanceRepository; -use ProcessMaker\Repositories\TokenRepository; - -trait PersistanceRequestTrait +trait PersistenceRequestTrait { - protected Deserializer $deserializer; - - protected ExecutionInstanceRepository $instanceRepository; - - protected TokenRepository $tokenRepository; - + /** + * Store data related to the event Process Instance Created + * + * @param array $transaction + */ public function persistInstanceCreated(array $transaction) { $instance = $this->deserializer->unserializeInstance($transaction['instance']); $this->instanceRepository->persistInstanceCreated($instance); } + /** + * Store data related to the event Process Instance Completed + * + * @param array $transaction + */ public function persistInstanceCompleted(array $transaction) { $instance = $this->deserializer->unserializeInstance($transaction['instance']); $this->instanceRepository->persistInstanceCompleted($instance); } + /** + * Store collaboration data + * + * @param array $transaction + */ public function persistInstanceCollaboration(array $transaction) { + // Return elements from serialized data $targetInstance = $this->deserializer->unserializeInstance($transaction['target_instance']); $targetParticipant = $this->deserializer->unserializeEntity($transaction['target_participant']); $sourceInstance = $this->deserializer->unserializeInstance($transaction['source_instance']); $sourceParticipant = $this->deserializer->unserializeEntity($transaction['source_participant']); + + // Persists collaboration between two instances $this->instanceRepository->persistInstanceCollaboration($targetInstance, $targetParticipant, $sourceInstance, $sourceParticipant); } } diff --git a/ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php similarity index 71% rename from ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php rename to ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php index ce069f9a12..c05cc193cb 100644 --- a/ProcessMaker/Nayra/Repositories/PersistanceTokenTrait.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceTokenTrait.php @@ -2,17 +2,13 @@ namespace ProcessMaker\Nayra\Repositories; -use ProcessMaker\Repositories\ExecutionInstanceRepository; -use ProcessMaker\Repositories\TokenRepository; - -trait PersistanceTokenTrait +trait PersistenceTokenTrait { - protected Deserializer $deserializer; - - protected ExecutionInstanceRepository $instanceRepository; - - protected TokenRepository $tokenRepository; - + /** + * Persists instance and token data when a token arrives to an activity + * + * @param array $transaction + */ public function persistActivityActivated(array $transaction) { $activity = $this->deserializer->unserializeEntity($transaction['activity']); @@ -20,6 +16,11 @@ public function persistActivityActivated(array $transaction) $this->tokenRepository->persistActivityActivated($activity, $token); } + /** + * Persists instance and token data when a token within an activity change to error state + * + * @param array $transaction + */ public function persistActivityException(array $transaction) { $activity = $this->deserializer->unserializeEntity($transaction['activity']); @@ -27,6 +28,11 @@ public function persistActivityException(array $transaction) $this->tokenRepository->persistActivityException($activity, $token); } + /** + * Persists instance and token data when a token is completed within an activity + * + * @param array $transaction + */ public function persistActivityCompleted(array $transaction) { $activity = $this->deserializer->unserializeEntity($transaction['activity']); @@ -34,6 +40,11 @@ public function persistActivityCompleted(array $transaction) $this->tokenRepository->persistActivityCompleted($activity, $token); } + /** + * Persists instance and token data when a token is closed by an activity + * + * @param array $transaction + */ public function persistActivityClosed(array $transaction) { $activity = $this->deserializer->unserializeEntity($transaction['activity']); @@ -41,6 +52,11 @@ public function persistActivityClosed(array $transaction) $this->tokenRepository->persistActivityClosed($activity, $token); } + /** + * Persists instance and token data when a throw event token arrives + * + * @param array $transaction + */ public function persistThrowEventTokenArrives(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['event']); @@ -48,6 +64,11 @@ public function persistThrowEventTokenArrives(array $transaction) $this->tokenRepository->persistThrowEventTokenArrives($event, $token); } + /** + * Persists instance and token data when a throw event token is consumed + * + * @param array $transaction + */ public function persistThrowEventTokenConsumed(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['event']); @@ -55,6 +76,11 @@ public function persistThrowEventTokenConsumed(array $transaction) $this->tokenRepository->persistThrowEventTokenConsumed($event, $token); } + /** + * Persists instance and token data when a throw event token is passed + * + * @param array $transaction + */ public function persistThrowEventTokenPassed(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['event']); @@ -62,6 +88,11 @@ public function persistThrowEventTokenPassed(array $transaction) $this->tokenRepository->persistThrowEventTokenPassed($event, $token); } + /** + * Persists instance and token data when a gateway token arrives + * + * @param array $transaction + */ public function persistGatewayTokenArrives(array $transaction) { $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); @@ -69,6 +100,11 @@ public function persistGatewayTokenArrives(array $transaction) $this->tokenRepository->persistGatewayTokenArrives($gateway, $token); } + /** + * Persists instance and token data when a gateway token is consumed + * + * @param array $transaction + */ public function persistGatewayTokenConsumed(array $transaction) { $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); @@ -76,6 +112,11 @@ public function persistGatewayTokenConsumed(array $transaction) $this->tokenRepository->persistGatewayTokenConsumed($gateway, $token); } + /** + * Persists instance and token data when a gateway token is passed + * + * @param array $transaction + */ public function persistGatewayTokenPassed(array $transaction) { $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); @@ -83,6 +124,11 @@ public function persistGatewayTokenPassed(array $transaction) $this->tokenRepository->persistGatewayTokenPassed($gateway, $token); } + /** + * Persists instance and token data when a catch event token arrives + * + * @param array $transaction + */ public function persistCatchEventTokenArrives(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['catch_event']); @@ -90,6 +136,11 @@ public function persistCatchEventTokenArrives(array $transaction) $this->tokenRepository->persistCatchEventTokenArrives($event, $token); } + /** + * Persists instance and token data when a catch event token is consumed + * + * @param array $transaction + */ public function persistCatchEventTokenConsumed(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['catch_event']); @@ -97,6 +148,11 @@ public function persistCatchEventTokenConsumed(array $transaction) $this->tokenRepository->persistCatchEventTokenConsumed($event, $token); } + /** + * Persists instance and token data when a catch event token is passed + * + * @param array $transaction + */ public function persistCatchEventTokenPassed(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['catch_event']); @@ -104,6 +160,11 @@ public function persistCatchEventTokenPassed(array $transaction) $this->tokenRepository->persistCatchEventTokenPassed($event, $consumedTokens); } + /** + * Persists instance and token data when a catch event message arrives + * + * @param array $transaction + */ public function persistCatchEventMessageArrives(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['catch_event']); @@ -111,6 +172,11 @@ public function persistCatchEventMessageArrives(array $transaction) $this->tokenRepository->persistCatchEventMessageArrives($event, $token); } + /** + * Persists instance and token data when a catch event message is consumed + * + * @param array $transaction + */ public function persistCatchEventMessageConsumed(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['catch_event']); @@ -118,6 +184,11 @@ public function persistCatchEventMessageConsumed(array $transaction) $this->tokenRepository->persistCatchEventMessageConsumed($event, $token); } + /** + * Persists tokens that triggered a Start Event + * + * @param array $transaction + */ public function persistStartEventTriggered(array $transaction) { $event = $this->deserializer->unserializeEntity($transaction['start_event']); @@ -125,6 +196,11 @@ public function persistStartEventTriggered(array $transaction) $this->tokenRepository->persistStartEventTriggered($event, $consumedTokens); } + /** + * Persists instance and token data when a token is consumed in a event based gateway + * + * @param array $transaction + */ public function persistEventBasedGatewayActivated(array $transaction) { $gateway = $this->deserializer->unserializeEntity($transaction['gateway']); diff --git a/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php b/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php deleted file mode 100644 index 99e3ceb48c..0000000000 --- a/ProcessMaker/Nayra/Repositories/ProcessRequestRepository.php +++ /dev/null @@ -1,101 +0,0 @@ - $properties['process_id'], - 'data' => $properties['data'], - 'status' => $properties['status'], - 'user_id' => $properties['user_id'], - 'callable_id' => $properties['callable_id'], - 'name' => $properties['name'], - 'process_version_id' => $properties['process_version_id'], - ]); - - // Store temporally the relation between uid and id - $this->storeUid($request->uuid, $request->id); - - return $request; - } catch (Exception $e) { - // Log the error - Log::error("Cannot create request: {$e->getMessage()}"); - return null; - } - } - - /** - * Update a request - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequest - */ - public function update(array $transaction): ? Model - { - try { - // Get the id mapped - $id = $this->resolveId($transaction['id']); - - // If not exists throws an error - if (!$id) { - throw new Exception("Cannot find id for uid {$transaction['id']}"); - } - - // Update the request - $properties = $transaction['properties']; - $model = ProcessRequest::find($id); - $model->fill($properties); - $model->save(); - - // Notify to engine - $model->notifyProcessUpdated('ACTIVITY_ACTIVATED'); - - // Trigger event - if ($model->status === 'COMPLETED') { - event(new ProcessCompleted($model)); - } - - return $model; - } catch (Exception $e) { - // Log the error - Log::error("Cannot update request: {$e->getMessage()}"); - return null; - } - } - - /** - * Save the request - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequest - */ - public function save(array $transaction): ? Model - { - if ($transaction['type'] === 'create') { - return $this->create($transaction); - } - if ($transaction['type'] === 'update') { - return $this->update($transaction); - } - } -} diff --git a/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php b/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php deleted file mode 100644 index 799ac7ae7e..0000000000 --- a/ProcessMaker/Nayra/Repositories/ProcessRequestTokenRepository.php +++ /dev/null @@ -1,115 +0,0 @@ -first(); - - // Overrides the request id with the correct value - $properties['request_id'] = $request->getKey(); - - // Complete missing values - if (!isset($properties['user_id'])) { - $properties['user_id'] = $request->user_id; - } - if (!isset($properties['process_id'])) { - $properties['process_id'] = $request->process_id; - } - } - - try { - // Create new request token - $token = ProcessRequestToken::create([ - 'user_id' => $properties['user_id'] ?? null, - 'process_id' => $properties['process_id'] ?? null, - 'process_request_id' => $properties['request_id'], - 'element_id' => $properties['element_id'], - 'element_name' => $properties['element_name'], - 'element_type' => $properties['element_type'], - 'status' => $properties['status'], - // TO DO: - //'due_at' => '', - //'riskchanges_at' => '', - 'self_service_groups' => [], - 'token_properties' => [], - ]); - - // Store temporally the relation between uid and id - $this->storeUid($token->uuid, $token->id); - - return $token; - } catch (Exception $e) { - // Log the error - Log::error("Cannot create token: {$e->getMessage()}"); - return null; - } - } - - /** - * Update a request token - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequestToken - */ - public function update(array $transaction): ? Model - { - try { - // Get the id mapped - $id = $this->resolveId($transaction['id']); - - // If not exists throws an error - if (!$id) { - throw new Exception("Cannot find id for uid {$transaction['id']}"); - } - - // Update the request token - $properties = $transaction['properties']; - $model = ProcessRequestToken::find($id); - $model->fill($properties); - $model->save(); - - return $model; - } catch (Exception $e) { - // Log the error - Log::error("Cannot update token: {$e->getMessage()}"); - return null; - } - } - - /** - * Save the request token - * - * @param array $transaction - * @return \ProcessMaker\Models\ProcessRequestToken - */ - public function save(array $transaction): ? Model - { - if ($transaction['type'] === 'create') { - return $this->create($transaction); - } - if ($transaction['type'] === 'update') { - return $this->update($transaction); - } - } -} diff --git a/ProcessMaker/Repositories/ExecutionInstanceRepository.php b/ProcessMaker/Repositories/ExecutionInstanceRepository.php index f80bf6bb33..ef5c47b0c5 100644 --- a/ProcessMaker/Repositories/ExecutionInstanceRepository.php +++ b/ProcessMaker/Repositories/ExecutionInstanceRepository.php @@ -5,7 +5,6 @@ use Carbon\Carbon; use ProcessMaker\Models\ProcessCollaboration; use ProcessMaker\Models\ProcessRequest; -use ProcessMaker\Models\ProcessRequest as Instance; use ProcessMaker\Nayra\Contracts\Bpmn\ParticipantInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; use ProcessMaker\Nayra\Contracts\Repositories\ExecutionInstanceRepositoryInterface; @@ -23,10 +22,21 @@ class ExecutionInstanceRepository implements ExecutionInstanceRepositoryInterfac private bool $abortIfInstanceNotFound = true; private bool $loadTokens = true; + /** + * Set not found flag + * + * @param bool $abortIfInstanceNotFound + */ public function setAbortIfInstanceNotFound(bool $abortIfInstanceNotFound) { $this->abortIfInstanceNotFound = $abortIfInstanceNotFound; } + + /** + * Set load tokens flag + * + * @param bool $loadTokens + */ public function setLoadTokens(bool $loadTokens) { $this->loadTokens = $loadTokens; @@ -37,9 +47,9 @@ public function setLoadTokens(bool $loadTokens) * * @return ExecutionInstanceInterface */ - public function createExecutionInstance() + public function createExecutionInstance(): ExecutionInstanceInterface { - $instance = new Instance(); + $instance = new ProcessRequest(); $instance->setId(uniqid('request', true)); return $instance; @@ -49,34 +59,48 @@ public function createExecutionInstance() * Load an execution instance from a persistent storage. * * @param string $instanceId + * @param StorageInterface $storage * - * @return \ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface + * @return ExecutionInstanceInterface */ - public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage) + public function loadExecutionInstanceByUid($instanceId, StorageInterface $storage): ExecutionInstanceInterface { + // Get process request if (is_numeric($instanceId)) { - $instance = Instance::find($instanceId); + $instance = ProcessRequest::find($instanceId); } else { - $instance = Instance::where('uuid', $instanceId)->first(); + $instance = ProcessRequest::where('uuid', $instanceId)->first(); } + + // Finish if process request not exists if (!$instance && $this->abortIfInstanceNotFound) { abort(404, 'Instance not found'); } elseif (!$instance) { return null; } + + // Get process $callableId = $instance->callable_id; $process = $storage->getProcess($callableId); + + // Set data store $dataStore = $storage->getFactory()->createDataStore(); $dataStore->setData($instance->data); + + // Set process request properties $instance->setId($instance->getKey()); $instance->setProcess($process); $instance->setDataStore($dataStore); + + // Get transitions $process->getTransitions($storage->getFactory()); + // Finish if is not necessary load the tokens if (!$this->loadTokens) { return $instance; } - //Load tokens: + + // Load tokens $tokens = $instance->tokens()->where('status', '!=', 'CLOSED')->get(); foreach ($tokens as $token) { $tokenInfo = [ @@ -97,12 +121,10 @@ public function loadExecutionInstanceByUid($instanceId, StorageInterface $storag * Create or update an execution instance to a persistent storage. * * @param \ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface $instance - * - * @return $this */ public function storeExecutionInstance(ExecutionInstanceInterface $instance) { - // TODO: Implement store() method. or Remove from Interface + // TODO: Implement store() method or Remove from Interface } /** @@ -114,18 +136,21 @@ public function storeExecutionInstance(ExecutionInstanceInterface $instance) */ public function persistInstanceCreated(ExecutionInstanceInterface $instance) { - //Get instance data + // Get instance data $data = $instance->getDataStore()->getData(); - //Get the process + + // Get the process $process = $instance->getProcess(); - //Get process definition + + // Get process definition $definition = $process->getOwnerDocument()->getModel(); + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save the row + // Save process request $instance->callable_id = $process->getId(); $instance->process_id = $definition->getKey(); $instance->process_version_id = $definition->getLatestVersion()->getKey(); @@ -136,39 +161,31 @@ public function persistInstanceCreated(ExecutionInstanceInterface $instance) $instance->do_not_sanitize = SanitizeHelper::getDoNotSanitizeFields($definition); $instance->data = $data; $instance->saveOrFail(); + + // Set id $instance->setId($instance->getKey()); + // Persists collaboration $this->persistCollaboration($instance); } - private function findParticipantFor(ExecutionInstanceInterface $instance) - { - $collaboration = $instance->getProcess()->getEngine()->getEventDefinitionBus()->getCollaboration(); - if (!$collaboration) { - return; - } - foreach ($collaboration->getParticipants() as $participant) { - if ($participant->getProcess()->getId() === $instance->getProcess()->getId()) { - return $participant; - } - } - } - /** * Persists instance when an error occurs * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceError(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + // Save instance with error $instance->status = 'ERROR'; $instance->mergeLatestStoredData(); $instance->saveOrFail(); @@ -178,16 +195,19 @@ public function persistInstanceError(ExecutionInstanceInterface $instance) * Persists instance data related to the event Process Instance Completed * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + + // Save updated instance $instance->status = 'ACTIVE'; $instance->mergeLatestStoredData(); $instance->saveOrFail(); @@ -197,16 +217,19 @@ public function persistInstanceUpdated(ExecutionInstanceInterface $instance) * Persists instance data related to the event Process Instance Completed * * @param ExecutionInstanceInterface $instance - * * @return mixed */ public function persistInstanceCompleted(ExecutionInstanceInterface $instance) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } - //Save instance + + // Save completed instance $instance->status = 'COMPLETED'; $instance->completed_at = Carbon::now(); $instance->mergeLatestStoredData(); @@ -219,14 +242,19 @@ public function persistInstanceCompleted(ExecutionInstanceInterface $instance) * @param ExecutionInstanceInterface $instance Target instance * @param ParticipantInterface $participant Participant related to the target instance * @param ExecutionInstanceInterface $source Source instance - * @param ParticipantInterface $sourceParticipant + * @param ParticipantInterface $sourceParticipant Source participant */ public function persistInstanceCollaboration(ExecutionInstanceInterface $instance, ParticipantInterface $participant = null, ExecutionInstanceInterface $source, ParticipantInterface $sourceParticipant = null) { + // Get process $process = $instance->getProcess(); + + // Do nothing if is not persistent if ($process->isNonPersistent()) { return; } + + // Get collaboration id if not exists if ($source->process_collaboration_id === null) { $collaboration = new ProcessCollaboration(); $collaboration->process_id = $instance->process->getKey(); @@ -234,6 +262,8 @@ public function persistInstanceCollaboration(ExecutionInstanceInterface $instanc $source->process_collaboration_id = $collaboration->getKey(); $source->saveOrFail(); } + + // Save collaboration $instance->process_collaboration_id = $source->process_collaboration_id; $instance->participant_id = $participant ? $participant->getId() : null; $instance->saveOrFail(); @@ -242,15 +272,17 @@ public function persistInstanceCollaboration(ExecutionInstanceInterface $instanc /** * Persist current collaboration. * - * @param ProcessRequest $instance - * @return void + * @param ProcessRequest $request */ private function persistCollaboration(ProcessRequest $request) { + // Get valid engine $engine = $request->getProcess()->getEngine(); if (count($engine->getExecutionInstances()) <= 1) { return; } + + // Get current collaboration $collaboration = null; foreach ($engine->getExecutionInstances() as $instance) { if ($instance->collaboration) { @@ -258,11 +290,15 @@ private function persistCollaboration(ProcessRequest $request) break; } } + + // 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(); } diff --git a/ProcessMaker/Repositories/TokenRepository.php b/ProcessMaker/Repositories/TokenRepository.php index 697e5cc6c2..a839075a91 100644 --- a/ProcessMaker/Repositories/TokenRepository.php +++ b/ProcessMaker/Repositories/TokenRepository.php @@ -5,9 +5,7 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; -use ProcessMaker\Exception\InvalidUserAssignmentException; use ProcessMaker\Models\ProcessCollaboration; -use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequest as Instance; use ProcessMaker\Models\ProcessRequestToken as Token; use ProcessMaker\Models\User; @@ -18,7 +16,6 @@ use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface; use ProcessMaker\Nayra\Contracts\Bpmn\EventBasedGatewayInterface; -use ProcessMaker\Nayra\Contracts\Bpmn\FlowInterface; use ProcessMaker\Nayra\Contracts\Bpmn\GatewayInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\ServiceTaskInterface; @@ -61,7 +58,7 @@ public function createTokenInstance(): TokenInterface return $token; } - public function loadTokenByUid($uid): ?TokenInterface + public function loadTokenByUid($uid): ? TokenInterface { if (is_numeric($uid)) { return Token::find($uid); @@ -523,19 +520,19 @@ private function removeParentFromData(Instance $instance) private function getActivityType($activity) { - if ($activity instanceof ScriptTaskInterface) { + if ($activity instanceof ScriptTaskInterface) { return 'scriptTask'; } - if ($activity instanceof ServiceTaskInterface) { + if ($activity instanceof ServiceTaskInterface) { return 'serviceTask'; } - if ($activity instanceof CallActivityInterface) { + if ($activity instanceof CallActivityInterface) { return 'callActivity'; } - if ($activity instanceof ActivityInterface) { + if ($activity instanceof ActivityInterface) { return 'task'; } From e9fc5f4583ff3ac79d0132c5ba6e3da610c8fe0b Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Thu, 15 Jun 2023 12:01:36 -0400 Subject: [PATCH 05/13] Fix DDL tests with transactions (Mysql) --- tests/TestCase.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index d830459283..b3da12bc29 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,11 +3,13 @@ namespace Tests; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; +use Exception; use Illuminate\Database\DatabaseManager; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Bus; +use PDOException; use ProcessMaker\Jobs\RefreshArtisanCaches; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestLock; @@ -21,12 +23,15 @@ abstract class TestCase extends BaseTestCase use ArraySubsetAsserts; public $withPermissions = false; + protected $skipTeardownPDOException = false; /** * Run additional setUps from traits. */ protected function setUp(): void { + $this->skipTeardownPDOException = false; + parent::setUp(); $this->disableSetContentMiddleware(); @@ -76,7 +81,13 @@ public function setUpMockConfigCache(): void */ protected function tearDown(): void { - parent::tearDown(); + try { + parent::tearDown(); + } catch (PDOException $e) { + if (!$this->skipTeardownPDOException) { + throw $e; + } + } foreach (get_class_methods($this) as $method) { $imethod = strtolower($method); if (strpos($imethod, 'teardown') === 0 && $imethod !== 'teardown') { From 1473c0e4393be1e2ac188fdb26c4715810e90ec7 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Fri, 16 Jun 2023 09:35:24 -0400 Subject: [PATCH 06/13] Use stored data table to increase the performance of the search --- .../Api/ProcessRequestController.php | 8 ++-- ProcessMaker/Traits/ExtendedPMQL.php | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index 92abf0c58c..0f6ddda7d1 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -134,10 +134,13 @@ public function index(Request $request, $getTotal = false, User $user = null) if (!empty($filter)) { $query->filter($filter); } - + + $query->nonSystem(); + $pmql = $request->input('pmql', ''); if (!empty($pmql)) { try { + $query->getModel()->useDataStoreTable($query, $request->input('data_store_table', ''), $request->input('data_store_columns', [])); $query->pmql($pmql); } catch (SyntaxError $e) { return response(['message' => __('Your PMQL contains invalid syntax.')], 400); @@ -145,9 +148,6 @@ public function index(Request $request, $getTotal = false, User $user = null) return response(['message' => $e->getMessage(), 'field' => $e->getField()], 400); } } - - $query->nonSystem(); - try { if ($getTotal === true) { return $query->count(); diff --git a/ProcessMaker/Traits/ExtendedPMQL.php b/ProcessMaker/Traits/ExtendedPMQL.php index 1136a7a492..afb3139b93 100644 --- a/ProcessMaker/Traits/ExtendedPMQL.php +++ b/ProcessMaker/Traits/ExtendedPMQL.php @@ -19,6 +19,23 @@ trait ExtendedPMQL PMQL::scopePMQL as parentScopePMQL; } + protected $dataStoreTable = ''; + protected $dataStoreColumns = []; + + /** + * Setup the PMQL to use the data store table + */ + public function useDataStoreTable(Builder $query, string $table, array $map) + { + $this->dataStoreTable = $table; + $this->dataStoreColumns = []; + foreach ($map as $variable) { + $this->dataStoreColumns[$variable['name']] = $variable['column']; + } + // inner join to the data store table + $query->join($table, $this->getTable() . '.id', '=', $table . '.process_request_id'); + } + /** * PMQL scope that extends the standard PMQL scope by supporting any custom * aliases specified in the model. @@ -83,6 +100,28 @@ private function handle(Expression $expression, Builder $builder, User $user = n $field = $expression->field->field(); $model = $builder->getModel(); + // use data store table if field is prefixed with "data." and the field is in the map + if (strpos($field, 'data.') === 0 && isset($this->dataStoreColumns[substr($field, 5)])) { + $variableName = substr($field, 5); + $columnName = $this->dataStoreColumns[$variableName]; + $realFieldName = $this->dataStoreTable . '.' . $columnName; + $value = $this->parseValue($expression); + if ($value instanceof IntervalExpression) { + $value = $value->toEloquent(); + } + return function($query) use($expression, $value, $realFieldName) { + switch ($expression->operator) { + case Expression::OPERATOR_IN: + $query->whereIn($realFieldName, $value); + break; + case Expression::OPERATOR_NOT_IN: + $query->whereNotIn($realFieldName, $value); + break; + default: + $query->where($realFieldName, $expression->operator, $value); + } + }; + } if (is_string($field)) { // Parse our value $value = $this->parseValue($expression); From 313da6abfdc7d28807030dc800519529bc879adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Fri, 16 Jun 2023 14:15:52 +0000 Subject: [PATCH 07/13] FOUR-8558 Refactor Trigger Intermediate Event functions to use Nayra BPMN engine - Message catch event --- .../Managers/WorkflowManagerRabbitMq.php | 39 +++++++++++++++++++ .../Nayra/Repositories/Deserializer.php | 8 ++++ 2 files changed, 47 insertions(+) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 3276417cae..144ec5e520 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -16,6 +16,7 @@ class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements Workflow { const ACTION_START_PROCESS = 'START_PROCESS'; const ACTION_COMPLETE_TASK = 'COMPLETE_TASK'; + const ACTION_TRIGGER_INTERMEDIATE_EVENT = 'TRIGGER_INTERMEDIATE_EVENT'; /** * Trigger a start event and return the process request instance. @@ -114,6 +115,44 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac ]); } + /** + * Complete a catch event + * + * @param Definitions $definitions + * @param ExecutionInstanceInterface $instance + * @param TokenInterface $token + * @param array $data + * + * @return void + */ + public function completeCatchEvent(Definitions $definitions, ExecutionInstanceInterface $instance, TokenInterface $token, array $data) + { + // Validate data + $element = $token->getDefinition(true); + $this->validateData($data, $definitions, $element); + + // Get complementary information + $version = $definitions->getLatestVersion(); + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version->getKey(), + 'action' => self::ACTION_TRIGGER_INTERMEDIATE_EVENT, + 'params' => [ + 'request_id' => $token->process_request_id, + 'token_id' => $token->uuid, + 'element_id' => $token->element_id, + 'data' => $data, + ], + 'state' => $state, + 'session' => [ + 'user_id' => $userId, + ], + ]); + } + /** * Build a state object * diff --git a/ProcessMaker/Nayra/Repositories/Deserializer.php b/ProcessMaker/Nayra/Repositories/Deserializer.php index 62c1a9ec14..c2c019dc6d 100644 --- a/ProcessMaker/Nayra/Repositories/Deserializer.php +++ b/ProcessMaker/Nayra/Repositories/Deserializer.php @@ -187,6 +187,14 @@ public function unserializeInstance(array $serialized): ExecutionInstanceInterfa $properties = array_merge($instance->getProperties(), $properties); $instance->setProperties($properties); + // Set request data + if (!empty($serialized['data']) && is_array($serialized['data'])) { + $dataStore = $instance->getDataStore(); + foreach ($serialized['data'] as $key => $value) { + $dataStore->putData($key, $value); + } + } + return $instance; } From 1067af4db7597c92e664981c4be9b4be5db60dd4 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Fri, 16 Jun 2023 15:31:27 -0400 Subject: [PATCH 08/13] Remove unused class --- tests/TestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index b3da12bc29..7709701d2a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,7 +3,6 @@ namespace Tests; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; -use Exception; use Illuminate\Database\DatabaseManager; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; From 229eb8927d76a919a5d3dd021ed3336c18bb135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Tue, 20 Jun 2023 13:21:22 +0000 Subject: [PATCH 09/13] FOUR-8555 Refactor runScriptTask function to use Nayra BPMN engine --- .../Managers/WorkflowManagerRabbitMq.php | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index 144ec5e520..ab920a11f8 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -4,12 +4,14 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use ProcessMaker\Contracts\WorkflowManagerInterface; use ProcessMaker\Facades\MessageBrokerService; use ProcessMaker\Models\Process as Definitions; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; +use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements WorkflowManagerInterface @@ -17,6 +19,7 @@ class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements Workflow 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'; /** * Trigger a start event and return the process request instance. @@ -116,7 +119,7 @@ public function completeTask(Definitions $definitions, ExecutionInstanceInterfac } /** - * Complete a catch event + * Complete a catch event. * * @param Definitions $definitions * @param ExecutionInstanceInterface $instance @@ -154,7 +157,41 @@ public function completeCatchEvent(Definitions $definitions, ExecutionInstanceIn } /** - * Build a state object + * Run a script task. + * + * @param ScriptTaskInterface $scriptTask + * @param TokenInterface $token + */ + public function runScripTask(ScriptTaskInterface $scriptTask, TokenInterface $token) + { + // Log execution + Log::info('Dispatch a script task: ' . $scriptTask->getId() . ' #' . $token->getId()); + + // Get complementary information + $instance = $token->processRequest; + $version = $instance->process->getLatestVersion(); + $userId = $this->getCurrentUserId(); + $state = $this->serializeState($instance); + + // Dispatch complete task action + $this->dispatchAction([ + 'bpmn' => $version->getKey(), + 'action' => self::ACTION_RUN_SCRIPT, + '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. * * @param ProcessRequest $instance * @return array @@ -200,7 +237,7 @@ private function getCurrentUserId(): ? int } /** - * Send payload + * Send payload. * * @param array $action */ From 30f181b3b0c677942597ca918ec0afb167de3ec5 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Tue, 20 Jun 2023 11:41:49 -0400 Subject: [PATCH 10/13] Add environment variables to script tasks --- .../Managers/WorkflowManagerRabbitMq.php | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php index ab920a11f8..db6789e4f2 100644 --- a/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php +++ b/ProcessMaker/Nayra/Managers/WorkflowManagerRabbitMq.php @@ -7,11 +7,14 @@ use Illuminate\Support\Facades\Log; use ProcessMaker\Contracts\WorkflowManagerInterface; use ProcessMaker\Facades\MessageBrokerService; +use ProcessMaker\GenerateAccessToken; +use ProcessMaker\Models\EnvironmentVariable; use ProcessMaker\Models\Process as Definitions; use ProcessMaker\Models\ProcessRequest; +use ProcessMaker\Models\User; +use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface; use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface; -use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface; use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface; class WorkflowManagerRabbitMq extends WorkflowManagerDefault implements WorkflowManagerInterface @@ -237,14 +240,60 @@ private function getCurrentUserId(): ? int } /** - * Send payload. + * Get the ID of the currently authenticated user. + * + * @return int|null + */ + private function getCurrentUser(): ? User + { + // Get the id from the current user + $webGuard = Auth::user(); + $apiGuard = Auth::guard('api')->user(); + + return $webGuard ?: $apiGuard; + } + + /** + * Send payload * * @param array $action */ private function dispatchAction(array $action): void { + // add environment variables to session + $environmentVariables = $this->getEnvironmentVariables(); + $action['session']['env'] = $environmentVariables; $subject = 'requests'; $thread = $action['collaboration_id'] ?? 0; MessageBrokerService::sendMessage($subject, $thread, $action); } + + /** + * Get the environment variables. + * + * @return array + */ + private function getEnvironmentVariables() + { + $variablesParameter = []; + EnvironmentVariable::chunk(50, function ($variables) use (&$variablesParameter) { + foreach ($variables as $variable) { + $variablesParameter[$variable['name']] = $variable['value']; + } + }); + + // Add the url to the host + $variablesParameter['HOST_URL'] = config('app.docker_host_url'); + + $user = $this->getCurrentUser(); + if ($user) { + $token = new GenerateAccessToken($user); + $environmentVariables['API_TOKEN'] = $token->getToken(); + $environmentVariables['API_HOST'] = config('app.url') . '/api/1.0'; + $environmentVariables['APP_URL'] = config('app.url'); + $environmentVariables['API_SSL_VERIFY'] = (config('app.api_ssl_verify') ? '1' : '0'); + } + + return $variablesParameter; + } } From 139b1ec1c10f99656ac3c49e549b6cb55b65ffcd Mon Sep 17 00:00:00 2001 From: Sanja Date: Fri, 16 Jun 2023 08:30:32 -0700 Subject: [PATCH 11/13] Update query to check if the column 'asset_type' exists --- ProcessMaker/Traits/HideSystemResources.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Traits/HideSystemResources.php b/ProcessMaker/Traits/HideSystemResources.php index a327f3ff68..e23777cea5 100644 --- a/ProcessMaker/Traits/HideSystemResources.php +++ b/ProcessMaker/Traits/HideSystemResources.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Traits; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessCategory; @@ -56,7 +57,11 @@ public function scopeNonSystem($query) } elseif (static::class === Process::class) { $systemCategory = ProcessCategory::where('is_system', true)->pluck('id'); - return $query->whereNotIn('process_category_id', $systemCategory)->where('is_template', false)->whereNull('asset_type'); + return $query->whereNotIn('process_category_id', $systemCategory) + ->where('is_template', false) + ->when(Schema::hasColumn('processes', 'asset_type'), function ($query) { + return $query->whereNull('asset_type'); + }); } elseif (static::class === Screen::class) { $systemCategory = ScreenCategory::where('is_system', true)->pluck('id'); From 37075348199362c1156e88b5ab0abf6d998c9d05 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 21 Jun 2023 10:58:01 -0400 Subject: [PATCH 12/13] Update request when running --- .../Nayra/Repositories/PersistenceHandler.php | 3 +++ .../Repositories/PersistenceRequestTrait.php | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php index 2ed1f1a56b..8f1595b1fa 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceHandler.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceHandler.php @@ -102,6 +102,9 @@ public function save(array $transaction) case 'instance_collaboration': $this->persistInstanceCollaboration($transaction); break; + case 'instance_updated': + $this->persistInstanceUpdated($transaction); + break; default: throw new Exception('Unknown transaction type ' . $transaction['type']); } diff --git a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php index 574662d42b..b38e6de995 100644 --- a/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php +++ b/ProcessMaker/Nayra/Repositories/PersistenceRequestTrait.php @@ -2,8 +2,12 @@ namespace ProcessMaker\Nayra\Repositories; +use ProcessMaker\Repositories\ExecutionInstanceRepository; + trait PersistenceRequestTrait { + protected ExecutionInstanceRepository $instanceRepository; + /** * Store data related to the event Process Instance Created * @@ -42,4 +46,15 @@ public function persistInstanceCollaboration(array $transaction) // Persists collaboration between two instances $this->instanceRepository->persistInstanceCollaboration($targetInstance, $targetParticipant, $sourceInstance, $sourceParticipant); } + + /** + * Store data related to the event Process Instance Updated + * + * @param array $transaction + */ + public function persistInstanceUpdated(array $transaction) + { + $instance = $this->deserializer->unserializeInstance($transaction['instance']); + $this->instanceRepository->persistInstanceUpdated($instance); + } } From 151cc6da0bfa3c0c116b20120838d4bc4f841398 Mon Sep 17 00:00:00 2001 From: David Callizaya Date: Wed, 21 Jun 2023 10:59:58 -0400 Subject: [PATCH 13/13] Validate data store table is not empty --- ProcessMaker/Traits/ExtendedPMQL.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ProcessMaker/Traits/ExtendedPMQL.php b/ProcessMaker/Traits/ExtendedPMQL.php index afb3139b93..37b839afd9 100644 --- a/ProcessMaker/Traits/ExtendedPMQL.php +++ b/ProcessMaker/Traits/ExtendedPMQL.php @@ -33,7 +33,9 @@ public function useDataStoreTable(Builder $query, string $table, array $map) $this->dataStoreColumns[$variable['name']] = $variable['column']; } // inner join to the data store table - $query->join($table, $this->getTable() . '.id', '=', $table . '.process_request_id'); + if ($table) { + $query->join($table, $this->getTable() . '.id', '=', $table . '.process_request_id'); + } } /**