diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php
index 92abf0c58c..8e4a863629 100644
--- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php
+++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php
@@ -8,10 +8,8 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
-use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Notification;
@@ -20,23 +18,26 @@
use ProcessMaker\Facades\WorkflowManager;
use ProcessMaker\Http\Controllers\Controller;
use ProcessMaker\Http\Resources\ApiCollection;
+use ProcessMaker\Http\Resources\ApiResource;
use ProcessMaker\Http\Resources\ProcessRequests as ProcessRequestResource;
use ProcessMaker\Jobs\CancelRequest;
use ProcessMaker\Jobs\TerminateRequest;
use ProcessMaker\Models\Comment;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestToken;
-use ProcessMaker\Models\Setting;
use ProcessMaker\Models\User;
use ProcessMaker\Nayra\Contracts\Bpmn\CatchEventInterface;
use ProcessMaker\Notifications\ProcessCanceledNotification;
use ProcessMaker\Query\SyntaxError;
use ProcessMaker\RetryProcessRequest;
+use ProcessMaker\Traits\ProcessMapTrait;
use Symfony\Component\HttpFoundation\IpUtils;
use Throwable;
class ProcessRequestController extends Controller
{
+ use ProcessMapTrait;
+
const DOMAIN_CACHE_TIME = 86400;
/**
@@ -173,7 +174,7 @@ public function index(Request $request, $getTotal = false, User $user = null)
}
if (isset($response)) {
- //Map each item through its resource
+ // Map each item through its resource
$response = $response->map(function ($processRequest) use ($request) {
return new ProcessRequestResource($processRequest);
});
@@ -608,4 +609,86 @@ private function getTaskName($fields, $request)
return $token->element_name;
}
+
+ /**
+ * Get Information of the last token for the element query
+ *
+ * @Parameter
+ * Request $httpRequest
+ * ProcessRequest $request
+ * @return
+ * object data {
+ * element_id,
+ * element_name,
+ * created_at,
+ * completed_at,
+ * username,
+ * sequenceFlow,
+ * count
+ * }
+ */
+ public function getRequestToken(Request $httpRequest, ProcessRequest $request)
+ {
+ $httpRequest->validate([
+ 'element_id' => 'required|string',
+ ]);
+
+ $elementId = null;
+ $maxTokenId = $this->getMaxTokenId($request, $httpRequest->element_id);
+ if ($maxTokenId === null) {
+ $bpmn = $request->process->versions()
+ ->where('id', $request->process_version_id)
+ ->firstOrFail()
+ ->bpmn;
+
+ // Get the source and target node for the sequence flow.
+ $xml = $this->loadAndPrepareXML($bpmn);
+ $targetAndSourceRef = $this->getRefNodes($xml, $httpRequest->element_id);
+
+ if ($targetAndSourceRef->isNotEmpty()) {
+ $targetRef = $targetAndSourceRef['targetRef'];
+ $sourceRef = $targetAndSourceRef['sourceRef'];
+
+ // Get the token counts for the target and source nodes.
+ $targetTokensCount = $this->getTokenCount($request, $targetRef);
+ $sourceTokensCount = $this->getTokenCount($request, $sourceRef);
+
+ // Get the minimum repeated node ID.
+ $elementId = ($sourceTokensCount < $targetTokensCount) ? $sourceRef : $targetRef;
+ }
+
+ // Get the maximum node ID.
+ $httpRequest->merge(['element_id' => $elementId]);
+ $maxTokenId = $this->getMaxTokenId($request, $httpRequest->element_id);
+ }
+
+ $token = $request->tokens()
+ ->where('id', $maxTokenId)
+ ->select('user_id', 'element_id', 'element_name', 'created_at', 'completed_at', 'status')
+ ->with([
+ 'user' => fn ($query) => $query->select('id', 'username'),
+ ])
+ ->firstOrFail();
+
+ // Flags if the object clicked is a Sequence Flow.
+ $token->is_sequence_flow = $elementId ? true : false;
+
+ $translatedStatus = match ($token->status) {
+ 'CLOSED' => __('Completed'),
+ 'ACTIVE' => __('In Progress'),
+ default => $token->status,
+ };
+ $token->status_translation = $translatedStatus;
+ $token->completed_by = $token->completed_at ? ($token->user['username'] ?? '-') : '-';
+
+ // Get the number of times the flow has run.
+ $tokensCount = $request->tokens()
+ ->where([
+ 'element_id' => $httpRequest->element_id,
+ 'process_request_id'=> $request->id,
+ ])->count();
+ $token->count = $tokensCount;
+
+ return new ApiResource($token);
+ }
}
diff --git a/ProcessMaker/Http/Controllers/Process/ModelerController.php b/ProcessMaker/Http/Controllers/Process/ModelerController.php
index 688f02c0de..27162ed2da 100644
--- a/ProcessMaker/Http/Controllers/Process/ModelerController.php
+++ b/ProcessMaker/Http/Controllers/Process/ModelerController.php
@@ -3,6 +3,7 @@
namespace ProcessMaker\Http\Controllers\Process;
use Illuminate\Http\Request;
+use Illuminate\Support\Collection;
use ProcessMaker\Events\ModelerStarting;
use ProcessMaker\Http\Controllers\Controller;
use ProcessMaker\Managers\ModelerManager;
@@ -11,10 +12,13 @@
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\PackageHelper;
use ProcessMaker\Traits\HasControllerAddons;
+use ProcessMaker\Traits\ProcessMapTrait;
+use SimpleXMLElement;
class ModelerController extends Controller
{
use HasControllerAddons;
+ use ProcessMapTrait;
/**
* Invokes the Process Modeler for rendering.
@@ -53,6 +57,7 @@ public function inflight(ModelerManager $manager, Process $process, Request $req
$bpmn = $process->bpmn;
$requestCompletedNodes = [];
$requestInProgressNodes = [];
+ $requestIdleNodes = [];
// Use the process version that was active when the request was started.
$processRequest = ProcessRequest::find($request->request_id);
@@ -62,18 +67,28 @@ public function inflight(ModelerManager $manager, Process $process, Request $req
->firstOrFail()
->bpmn;
- $requestCompletedNodes = $processRequest->tokens()->where('status', 'CLOSED')->pluck('element_id');
+ $requestCompletedNodes = $processRequest->tokens()->whereIn('status', ['CLOSED', 'TRIGGERED'])->pluck('element_id');
$requestInProgressNodes = $processRequest->tokens()->where('status', 'ACTIVE')->pluck('element_id');
- // Remove any node that is 'ACTIVE' from the 'CLOSED' list.
+ // Remove any node that is 'ACTIVE' from the completed list.
$filteredCompletedNodes = $requestCompletedNodes->diff($requestInProgressNodes)->values();
+
+ // Get idle nodes.
+ $xml = $this->loadAndPrepareXML($bpmn);
+ $nodeIds = $this->getNodeIds($xml);
+ $requestIdleNodes = $nodeIds->diff($filteredCompletedNodes)->diff($requestInProgressNodes)->values();
+
+ // Add completed sequence flow to the list of completed nodes.
+ $sequenceFlowNodes = $this->getCompletedSequenceFlow($xml, $filteredCompletedNodes->implode(' '), $requestInProgressNodes->implode(' '));
+ $filteredCompletedNodes = $filteredCompletedNodes->merge($sequenceFlowNodes);
}
return view('processes.modeler.inflight', [
'manager' => $manager,
- 'process' => $process,
'bpmn' => $bpmn,
'requestCompletedNodes' => $filteredCompletedNodes,
'requestInProgressNodes' => $requestInProgressNodes,
+ 'requestIdleNodes' => $requestIdleNodes,
+ 'requestId' => $request->request_id,
]);
}
}
diff --git a/ProcessMaker/Traits/ProcessMapTrait.php b/ProcessMaker/Traits/ProcessMapTrait.php
new file mode 100644
index 0000000000..cf9b5f55b8
--- /dev/null
+++ b/ProcessMaker/Traits/ProcessMapTrait.php
@@ -0,0 +1,99 @@
+getNamespaces(true);
+
+ foreach ($namespaces as $prefix => $ns) {
+ $xml->registerXPathNamespace($prefix, $ns);
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Get the maximum token ID for a given element ID.
+ */
+ private function getMaxTokenId(ProcessRequest $request, ?string $elementId): ?int
+ {
+ return $request->tokens()
+ ->where('element_id', $elementId)
+ ->max('id');
+ }
+
+ /**
+ * Get the token count for a given element ID.
+ */
+ private function getTokenCount(ProcessRequest $request, string $elementId): int
+ {
+ return $request->tokens()
+ ->where([
+ 'element_id' => $elementId,
+ 'process_request_id' => $request->id,
+ ])
+ ->count();
+ }
+
+ /**
+ * Filter the XML using the provided XPath query and return a Collection of string values.
+ */
+ private function filterXML(SimpleXMLElement $xml, string $xpathQuery): Collection
+ {
+ $elements = $xml->xpath($xpathQuery);
+
+ return collect(array_map('strval', $elements));
+ }
+
+ /**
+ * Filter the XML to get IDs of all nodes excluding "lanes" and "pools" nodes.
+ */
+ private function getNodeIds(SimpleXMLElement $xml): Collection
+ {
+ $query = '//*[name() != "bpmn:lane" and name() != "bpmn:participant"]/@id';
+
+ return $this->filterXML($xml, $query);
+ }
+
+ /**
+ * Performs an XPath query to get sequenceFlow elements
+ * whose 'sourceRef' attribute is in the string of completed nodes
+ * and 'targetRef' attribute is in the string of in-progress and completed nodes.
+ */
+ private function getCompletedSequenceFlow(SimpleXMLElement $xml, string $completedNodesStr, string $inProgressNodesStr): Collection
+ {
+ $inProgressAndCompletedNodes = $completedNodesStr . ' ' . $inProgressNodesStr;
+ $query = '//bpmn:sequenceFlow[contains("' . $completedNodesStr . '", @sourceRef) and contains("' . $inProgressAndCompletedNodes . '", @targetRef)]/@id';
+
+ return $this->filterXML($xml, $query);
+ }
+
+ /**
+ * Performs an XPath query to get the targetRef and SourceRef Node Id
+ */
+ private function getRefNodes(SimpleXMLElement $xml, string $sequenceFlowId): Collection
+ {
+ $sequenceFlowNode = $xml->xpath("//bpmn:sequenceFlow[@id='{$sequenceFlowId}']");
+
+ if (empty($sequenceFlowNode)) {
+ return collect();
+ }
+
+ return collect([
+ 'targetRef' => (string) $sequenceFlowNode[0]['targetRef'],
+ 'sourceRef' => (string) $sequenceFlowNode[0]['sourceRef'],
+ ]);
+ }
+}
diff --git a/resources/js/processes/modeler/components/ProcessMap.vue b/resources/js/processes/modeler/components/ProcessMap.vue
index 8d8fe2431d..044d715a28 100644
--- a/resources/js/processes/modeler/components/ProcessMap.vue
+++ b/resources/js/processes/modeler/components/ProcessMap.vue
@@ -9,10 +9,13 @@
v-show="tooltip.isActive"
ref="tooltip"
:node-id="tooltip.nodeId"
+ :node-name="tooltip.nodeName"
+ :request-id="requestId"
:style="{
left: `${tooltip.newX}px`,
top: `${tooltip.newY}px`
}"
+ @is-loading="getIsLoading"
/>
- {{ nodeId }} -
-- Status:Complete -
-- Completed By:UserName -
-- Time Started:11/21/23 16:51 -
-- Time Completed:11/21/23 16:53 -
++ {{ tokenResult.element_name }} +
++ {{ $t('Status') }}: + {{ tokenResult.status_translation }} +
++ {{ $t('Completed By') }}: + {{ tokenResult.completed_by }} +
++ {{ $t('Time Started') }}: + {{ tokenResult.created_at }} +
++ {{ $t('Time Completed') }}: + {{ tokenResult.completed_at }} +
++ + {{ repeatMessage }} + +
++ {{ nodeName }} +
++ {{ tokenResult.message }} +
+