-
Notifications
You must be signed in to change notification settings - Fork 243
FOUR-26712 Implement the Events and Logic in Backed for Conditional Destination #8540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9e90589
456213f
9a8e663
4c710ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| namespace ProcessMaker\Contracts; | ||
|
|
||
| use ProcessMaker\Models\ProcessRequestToken; | ||
|
|
||
| /** | ||
| * @see \ProcessMaker\Services\ConditionalRedirectService | ||
| * @package ProcessMaker\Contracts | ||
| */ | ||
| interface ConditionalRedirectServiceInterface | ||
| { | ||
| /** | ||
| * Process a set of conditions and return the first that satisfies for an array of data. | ||
| * | ||
| * @param array $conditionalRedirect | ||
| * @param array $data | ||
| * | ||
| * @return array|null | ||
| */ | ||
| public function resolve(array $conditionalRedirect, array $data): ?array; | ||
|
|
||
| /** | ||
| * Process a set of conditions and return the first that satisfies for a process request token. | ||
| * | ||
| * @param array $conditionalRedirect | ||
| * @param ProcessRequestToken $token | ||
| * | ||
| * @return array|null | ||
| */ | ||
| public function resolveForToken(array $conditionalRedirect, ProcessRequestToken $token): ?array; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,221 @@ | ||
| <?php | ||
|
|
||
| namespace ProcessMaker\Services; | ||
|
|
||
| use InvalidArgumentException; | ||
| use ProcessMaker\Contracts\ConditionalRedirectServiceInterface; | ||
| use ProcessMaker\Managers\DataManager; | ||
| use ProcessMaker\Models\Comment; | ||
| use ProcessMaker\Models\FormalExpression; | ||
| use ProcessMaker\Models\ProcessRequest; | ||
| use ProcessMaker\Models\ProcessRequestToken; | ||
|
|
||
| /** | ||
| * ConditionalRedirectService | ||
| * | ||
| * This service handles the evaluation of conditional redirects in ProcessMaker workflows. | ||
| * It processes a set of conditions and returns the first condition that evaluates to true, | ||
| * along with its associated redirect configuration. | ||
| * | ||
| * The service uses FEEL (Friendly Enough Expression Language) expressions to evaluate | ||
| * conditions against process data, allowing for dynamic routing based on runtime data. | ||
| * | ||
| * @package ProcessMaker\Services | ||
| * @since 4.0.0 | ||
| */ | ||
| class ConditionalRedirectService implements ConditionalRedirectServiceInterface | ||
| { | ||
| /** | ||
| * @var FormalExpression | ||
| */ | ||
| private FormalExpression $feel; | ||
|
|
||
| /** | ||
| * @var DataManager | ||
| */ | ||
| private DataManager $dataManager; | ||
|
|
||
| private array $errors = []; | ||
|
|
||
| /** | ||
| * Constructor | ||
| * | ||
| * Initializes the service with required dependencies for expression evaluation | ||
| * and data management. | ||
| */ | ||
| public function __construct() | ||
| { | ||
| $this->feel = new FormalExpression(); | ||
| $this->dataManager = new DataManager(); | ||
| } | ||
|
|
||
| /** | ||
| * Process a set of conditional redirects and return the first condition that evaluates to true | ||
| * | ||
| * This method iterates through an array of conditional redirect configurations, | ||
| * evaluating each condition using FEEL expressions against the provided data. | ||
| * Returns the first condition that evaluates to true, or null if no conditions match. | ||
| * | ||
| * @param array $conditionalRedirect Array of conditional redirect configurations | ||
| * Each item must contain a 'condition' key with a FEEL expression | ||
| * Example: [ | ||
| * [ | ||
| * 'condition' => 'amount > 1000', | ||
| * 'type' => 'externalURL', | ||
| * 'value' => 'https://example.com/approval' | ||
| * ], | ||
| * [ | ||
| * 'condition' => 'status = "urgent"', | ||
| * 'type' => 'taskList', | ||
| * 'value' => null | ||
| * ] | ||
| * ] | ||
| * @param array $data Process data to evaluate conditions against | ||
| * Contains variables from the process instance | ||
| * Example: ['amount' => 1500, 'status' => 'urgent', 'user' => 'john'] | ||
| * | ||
| * @return array|null The first matching conditional redirect configuration, or null if none match | ||
| * | ||
| * @throws InvalidArgumentException When a condition item is missing the required 'condition' key | ||
| * | ||
| * @example | ||
| * ```php | ||
| * $service = new ConditionalRedirectService(); | ||
| * | ||
| * $conditionalRedirect = [ | ||
| * [ | ||
| * 'condition' => 'amount > 1000', | ||
| * 'type' => 'externalURL', | ||
| * 'value' => 'https://example.com/approval' | ||
| * ], | ||
| * [ | ||
| * 'condition' => 'amount <= 1000', | ||
| * 'type' => 'taskList', | ||
| * 'value' => null | ||
| * ] | ||
| * ]; | ||
| * | ||
| * $data = ['amount' => 1500, 'status' => 'pending']; | ||
| * | ||
| * $result = $service->resolve($conditionalRedirect, $data); | ||
| * // Returns: ['condition' => 'amount > 1000', 'type' => 'externalURL', 'value' => 'https://example.com/approval'] | ||
| * ``` | ||
| */ | ||
| public function resolve(array $conditionalRedirect, array $data): ?array | ||
| { | ||
| $this->errors = []; | ||
| foreach ($conditionalRedirect as $item) { | ||
| if (!isset($item['condition'])) { | ||
| throw new InvalidArgumentException('Condition is required'); | ||
| } | ||
|
|
||
| $condition = $item['condition']; | ||
|
|
||
| $this->feel->setBody($condition); | ||
| try { | ||
| $result = ($this->feel)($data); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a new instance is created for everi conditions, if we have 10 then then instances will be created
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } catch (\Throwable $e) { | ||
| $this->errors[] = $e->getMessage(); | ||
| continue; | ||
| } | ||
| if ($result) { | ||
| return $item; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Process conditional redirects for a specific process request token | ||
| * | ||
| * This method is a convenience wrapper that automatically retrieves process data | ||
| * from a ProcessRequestToken and evaluates conditional redirects against that data. | ||
| * It's commonly used when you have a token and want to determine the appropriate | ||
| * redirect based on the current process state and data, it also considers | ||
| * multi-instance tasks. | ||
| * | ||
| * @param array $conditionalRedirect Array of conditional redirect configurations | ||
| * Each item must contain a 'condition' key with a FEEL expression | ||
| * Example: [ | ||
| * [ | ||
| * 'condition' => 'taskStatus = "completed"', | ||
| * 'type' => 'homepageDashboard', | ||
| * 'value' => null | ||
| * ], | ||
| * [ | ||
| * 'condition' => 'taskStatus = "pending"', | ||
| * 'type' => 'taskList', | ||
| * 'value' => null | ||
| * ] | ||
| * ] | ||
| * @param ProcessRequestToken $token The process request token to evaluate conditions against | ||
| * The token contains the process instance data and context | ||
| * | ||
| * @return array|null The first matching conditional redirect configuration, or null if none match | ||
| * | ||
| * @throws InvalidArgumentException When a condition item is missing the required 'condition' key | ||
| * | ||
| * @example | ||
| * ```php | ||
| * $service = new ConditionalRedirectService(); | ||
| * $token = ProcessRequestToken::find(123); | ||
| * | ||
| * $conditionalRedirect = [ | ||
| * [ | ||
| * 'condition' => 'taskStatus = "completed"', | ||
| * 'type' => 'homepageDashboard', | ||
| * 'value' => null | ||
| * ], | ||
| * [ | ||
| * 'condition' => 'taskStatus = "pending"', | ||
| * 'type' => 'taskList', | ||
| * 'value' => null | ||
| * ] | ||
| * ]; | ||
| * | ||
| * $result = $service->resolveForToken($conditionalRedirect, $token); | ||
| * // Returns the appropriate redirect configuration based on the token's data | ||
| * ``` | ||
| * | ||
| * @see resolve() For detailed parameter documentation | ||
| */ | ||
| public function resolveForToken(array $conditionalRedirect, ProcessRequestToken $token): ?array | ||
| { | ||
| $data = $this->dataManager->getData($token); | ||
| $result = $this->resolve($conditionalRedirect, $data); | ||
| if ($this->errors) { | ||
| $case_number = $this->getCaseNumber($token); | ||
| foreach ($this->errors as $error) { | ||
| $this->addLogComment($token, $error, $case_number); | ||
| } | ||
| } | ||
| return $result; | ||
| } | ||
|
|
||
| private function getCaseNumber(ProcessRequestToken $token): ?int | ||
| { | ||
| // get process request from relationship if loaded, otherwise get from database | ||
| if ($token->relationLoaded('processRequest')) { | ||
| $case_number = $token->processRequest->case_number; | ||
| } else { | ||
| // get case_number only to avoid to hidrate all the process request data | ||
| $case_number = ProcessRequest::where('id', $token->process_request_id)->value('case_number'); | ||
| } | ||
|
|
||
| return $case_number; | ||
| } | ||
|
|
||
| private function addLogComment(ProcessRequestToken $token, string $error, string $case_number) | ||
| { | ||
| Comment::create([ | ||
| 'body' => $error, | ||
| 'user_id' => null, | ||
| 'subject' => $error, | ||
| 'type' => 'LOG', | ||
| 'case_number' => $case_number, | ||
| 'commentable_type' => ProcessRequest::class, | ||
| 'commentable_id' => $token->process_request_id, | ||
| ]); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.