Skip to content

FOUR-20337: [WINTER] Speed up In-Flight Modeler UI Component #7860

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

Merged
merged 22 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f131993
FOUR-20534:Implement the improvements in Request and Cases
fagubla Dec 10, 2024
901b61b
add to Requests
fagubla Dec 11, 2024
50b32df
CR
fagubla Dec 12, 2024
b1603f8
Merge pull request #7816 from ProcessMaker/feature/FOUR-20534
pmPaulis Dec 12, 2024
fcca1c6
FOUR-20544: Review the general status of the process
fagubla Dec 12, 2024
7012868
Merge branch 'epic/FOUR-20336-FOUR-20318' into feature/FOUR-20544
fagubla Dec 12, 2024
7fe04f5
Merge pull request #7823 from ProcessMaker/feature/FOUR-20544
pmPaulis Dec 13, 2024
5d3c352
FOUR-20543:Review the tooltip status of each task
fagubla Dec 17, 2024
b32bb90
Merge remote-tracking branch 'origin/release-2024-fall' into epic/FOU…
pmPaulis Dec 18, 2024
50e6c16
Merge remote-tracking branch 'origin/release-2024-fall' into epic/FOU…
pmPaulis Dec 18, 2024
0d487ca
correction tooltip
fagubla Dec 18, 2024
3d1cb73
CR
fagubla Dec 18, 2024
5f633d7
Merge pull request #7843 from ProcessMaker/feature/FOUR-20543
pmPaulis Dec 18, 2024
5a9c3ab
Merge remote-tracking branch 'origin/epic/FOUR-20336-FOUR-20337' into…
pmPaulis Dec 18, 2024
8507272
FOUR-20279:Optimize the In-Flight Modeler UI Component
fagubla Dec 19, 2024
1d73d3d
console error
fagubla Dec 19, 2024
2c94a12
Merge pull request #7845 from ProcessMaker/feature/FOUR-20279
pmPaulis Dec 19, 2024
129044e
Merge remote-tracking branch 'origin/epic/FOUR-20336-FOUR-20337' into…
pmPaulis Dec 19, 2024
a167b65
FOUR-21372:We can move the elements of the process in the Request - O…
fagubla Dec 23, 2024
3a9b575
Merge pull request #7852 from ProcessMaker/bugfix/FOUR-21372
henryjonathanquispe Dec 23, 2024
17db778
Merge remote-tracking branch 'origin/epic/FOUR-20336-FOUR-20337' into…
pmPaulis Jan 6, 2025
6060b71
Merge remote-tracking branch 'origin/release-2025-winter' into epic/F…
pmPaulis Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion ProcessMaker/Http/Controllers/CasesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
namespace ProcessMaker\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use ProcessMaker\Events\ModelerStarting;
use ProcessMaker\Events\ScreenBuilderStarting;
use ProcessMaker\Http\Controllers\Controller;
use ProcessMaker\Http\Controllers\Process\ModelerController;
use ProcessMaker\Managers\ModelerManager;
use ProcessMaker\Managers\ScreenBuilderManager;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\Screen;
use ProcessMaker\Package\PackageComments\PackageServiceProvider;
use ProcessMaker\ProcessTranslations\ScreenTranslation;
use ProcessMaker\Traits\ProcessMapTrait;

class CasesController extends Controller
{
use ProcessMapTrait;
/**
* Get the list of requests.
*
Expand All @@ -38,6 +43,10 @@ public function show($case_number)
// Load event ScreenBuilderStarting
$manager = app(ScreenBuilderManager::class);
event(new ScreenBuilderStarting($manager, 'FORM'));
// Load event ModelerStarting
$managerModeler = app(ModelerManager::class);
event(new ModelerStarting($managerModeler));

// Get all the request related to this case number
$allRequests = ProcessRequest::where('case_number', $case_number)->get();
$parentRequest = null;
Expand Down Expand Up @@ -76,6 +85,14 @@ public function show($case_number)
// Get the summary screen tranlations
$this->summaryScreenTranslation($request);

// Load the process map
$inflightData = $this->loadProcessMap($request);
$bpmn = $inflightData['bpmn'];

// Get all PM-Blocks
$modelerController = new ModelerController();
$pmBlockList = $modelerController->getPmBlockList();

// Return the view
return view('cases.edit', compact(
'request',
Expand All @@ -85,7 +102,11 @@ public function show($case_number)
'canViewComments',
'canPrintScreens',
'isProcessManager',
'manager'
'manager',
'managerModeler',
'bpmn',
'inflightData',
'pmBlockList'
));
}

Expand Down
8 changes: 8 additions & 0 deletions ProcessMaker/Http/Controllers/RequestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
use ProcessMaker\ProcessTranslations\ScreenTranslation;
use ProcessMaker\RetryProcessRequest;
use ProcessMaker\Traits\HasControllerAddons;
use ProcessMaker\Traits\ProcessMapTrait;
use ProcessMaker\Traits\SearchAutocompleteTrait;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class RequestController extends Controller
{
use SearchAutocompleteTrait;
use HasControllerAddons;
use ProcessMapTrait;

/**
* Get the list of requests.
Expand Down Expand Up @@ -182,6 +184,10 @@ public function show(ProcessRequest $request, Media $mediaItems)
}
$this->summaryScreenTranslation($request);

//Load the process map
$inflightData = $this->loadProcessMap($request);
$bpmn = $inflightData['bpmn'];

if (isset($_SERVER['HTTP_USER_AGENT']) && MobileHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
return view('requests.showMobile', compact(
'request',
Expand Down Expand Up @@ -217,6 +223,8 @@ public function show(ProcessRequest $request, Media $mediaItems)
'eligibleRollbackTask',
'errorTask',
'userConfiguration',
'bpmn',
'inflightData',
));
}

Expand Down
45 changes: 45 additions & 0 deletions ProcessMaker/Traits/ProcessMapTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ProcessMaker\Traits;

use Illuminate\Support\Collection;
use ProcessMaker\Bpmn\Process;
use ProcessMaker\Models\ProcessRequest;
use SimpleXMLElement;

Expand All @@ -17,6 +18,9 @@ private function loadAndPrepareXML(string $bpmn): SimpleXMLElement
$xml = simplexml_load_string($bpmn);
$namespaces = $xml->getNamespaces(true);

// Register the BPMN namespace explicitly
$xml->registerXPathNamespace('bpmn', 'http://www.omg.org/spec/BPMN/20100524/MODEL');

foreach ($namespaces as $prefix => $ns) {
$xml->registerXPathNamespace($prefix, $ns);
}
Expand Down Expand Up @@ -108,4 +112,45 @@ private function getCountFlag(int $sourceCount, int $targetCount, string $source

return $maxToken->status === 'ACTIVE' && $sourceCount === $targetCount;
}

private function loadProcessMap(ProcessRequest $request): array
{
$processRequest = ProcessRequest::find($request->id);
$bpmn = $request->process->bpmn;
$filteredCompletedNodes = [];
$requestInProgressNodes = [];
$requestIdleNodes = [];

if ($processRequest) {
$requestCompletedNodes = $processRequest->tokens()
->whereIn('status', ['CLOSED', 'COMPLETED', 'TRIGGERED'])
->pluck('element_id');
$requestInProgressNodes = $processRequest->tokens()
->whereIn('status', ['ACTIVE', 'INCOMING'])
->pluck('element_id');

// Remove any node that is 'ACTIVE' from the completed list.
$filteredCompletedNodes = $requestCompletedNodes->diff($requestInProgressNodes)->values();

// Obtain In-Progress nodes that were completed before
$matchingNodes = $requestInProgressNodes->intersect($requestCompletedNodes);

// 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(' '), $matchingNodes->implode(' '));
$filteredCompletedNodes = $filteredCompletedNodes->merge($sequenceFlowNodes);
}

return [
'bpmn' => $bpmn,
'requestCompletedNodes' => $filteredCompletedNodes,
'requestInProgressNodes' => $requestInProgressNodes,
'requestIdleNodes' => $requestIdleNodes,
'requestId' => $request->id,
];
}
}
2 changes: 2 additions & 0 deletions resources/js/requests/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import FilesMobile from "./components/FilesMobile.vue";
import RequestHeaderMobile from "./components/RequestHeaderMobile.vue";
import FilterMobile from "../Mobile/FilterMobile.vue";
import FilterMixin from "../Mobile/FilterMixin";
import NewOverview from "../../jscomposition/cases/casesDetail/components/NewOverview.vue";

Vue.component("DataSummary", DataSummary);
Vue.component("RequestDetail", RequestDetail);
Expand All @@ -31,6 +32,7 @@ Vue.component("SummaryMobile", SummaryMobile);
Vue.component("FilesMobile", FilesMobile);
Vue.component("RequestHeaderMobile", RequestHeaderMobile);
Vue.component("FilterMobile", FilterMobile);
Vue.component("NewOverview", NewOverview);
Vue.mixin(FilterMixin);

Vue.use("vue-form-renderer", VueFormRenderer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import RequestTable from "./RequestTable.vue";
import TabHistory from "./TabHistory.vue";
import CompletedForms from "./CompletedForms.vue";
import TabFiles from "./TabFiles.vue";
import Overview from "./Overview.vue";
import Overview from "./NewOverview.vue";
import TabSummary from "./TabSummary.vue";
import ErrorsTab from "./ErrorsTab.vue";
import { getRequestCount, getRequestStatus, isErrors } from "../variables/index";
Expand Down Expand Up @@ -67,7 +67,7 @@ const tabs = [
name: translate.t("Summary"),
href: "#summary",
current: "summary",
show: getRequestStatus() !== 'ERROR',
show: getRequestStatus() !== "ERROR",
content: TabSummary,
},
{
Expand Down
44 changes: 44 additions & 0 deletions resources/jscomposition/cases/casesDetail/components/MapLegend.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div
id="map-legend"
class="tw-absolute tw-top-12 tw-right-12 tw-rounded-md tw-shadow-md tw-bg-white tw-z-10"
>
<div class="tw-p-4 tw-flex tw-flex-col">
<p
v-for="(status, index) in statusLegend"
:key="index"
class="tw-font-bold tw-mb-2 tw-flex tw-items-center tw-gap-2"
>
<span
:style="{ backgroundColor: status.color }"
class="tw-inline-block tw-w-1 tw-h-8 tw-mr-4 tw-border-r-3 tw-border-r-gray-400 tw-transform tw-rotate-45"
/>
{{ status.name }}
</p>
</div>
</div>
</template>

<script setup>
import { ref } from "vue";

const translate = ProcessMaker.i18n;

const statusLegend = ref(
[
{
name: translate.t("In Progress"),
color: "#3FA6FF",
},
{
name: translate.t("Completed"),
color: "#00BA7C",
},
{
name: translate.t("Pending / Not Executed"),
color: "#CCCCCC",
},
],
);

</script>
145 changes: 145 additions & 0 deletions resources/jscomposition/cases/casesDetail/components/NewOverview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<div
class="tw-w-full tw-h-full tw-overflow-hidden tw-relative"
data-test="body-container"
>
<h4 class="tw-fixed tw-z-10 tw-p-4">
{{ processTitle }}
</h4>
<MapLegend />
<ProcessMapTooltip
v-show="showTooltip"
ref="tooltipRef"
:enabled="enableTooltip"
:node-id="tooltip.nodeId"
:node-name="tooltip.nodeName"
:request-id="inflightData.requestId"
:style="{
left: `${tooltip.newX}px`,
top: `${tooltip.newY}px`
}"
@is-loading="isTooltipLoading"
/>
<transition
name="fade"
mode="in-out"
>
<Modeler
ref="modelerRef"
:key="keyModeler"
:decorations="decorations"
:request-completed-nodes="inflightData.requestCompletedNodes"
:request-in-progress-nodes="inflightData.requestInProgressNodes"
:request-idle-nodes="inflightData.requestIdleNodes"
:read-only="true"
@set-xml-manager="xmlManager = $event"
@click="handleClick"
/>
</transition>
</div>
</template>

<script setup>
import {
ref, watchEffect, onMounted, computed, nextTick, onBeforeUnmount,
} from "vue";
import { Modeler } from "@processmaker/modeler";
import ProcessMapTooltip from "../../../../js/processes/modeler/components/ProcessMapTooltip.vue";
import MapLegend from "./MapLegend.vue";
import { getInflightData, getProcessName } from "../variables";

const translate = ProcessMaker.i18n;
const processTitle = ref(`${getProcessName()} ${translate.t("In-Flight Map")}`);
const keyModeler = ref(Math.random());
const modelerRef = ref("");
const tooltipRef = ref(null);
const enableTooltip = ref(true);
const decorations = ref({
borderOutline: {},
});
const xmlManager = ref();
const tooltip = ref({
isActive: false,
isLoading: false,
nodeId: null,
nodeName: null,
allowedNodes: [
"bpmn:Task",
"bpmn:ManualTask",
"bpmn:SequenceFlow",
"bpmn:ScriptTask",
"bpmn:CallActivity",
"bpmn:ServiceTask",
],
coordinates: { x: 0, y: 0 },
newX: 0,
newY: 0,
});
const inflightData = ref(getInflightData());

const isMappingActive = computed(() => (window.ProcessMaker.modeler.enableProcessMapping !== undefined
? window.ProcessMaker.modeler.enableProcessMapping
: true));

const showTooltip = computed(() => enableTooltip.value && tooltip.value.isActive);

const calculateTooltipPosition = () => {
const rectTooltip = tooltipRef.value.$el.getBoundingClientRect();
tooltip.value.newY = tooltip.value.coordinates.y - rectTooltip.height - 20;
if (tooltip.value.newY <= 0) {
tooltip.value.newY = 10;
}
tooltip.value.newX = tooltip.value.coordinates.x - (rectTooltip.width / 2);
if (tooltip.value.newX < 0) {
tooltip.value.newX = 0;
} else if (tooltip.value.newX + rectTooltip.width > window.innerWidth) {
tooltip.value.newX = window.innerWidth - rectTooltip.width;
}
};

const setupTooltip = ({ event, node }) => {
const isNodeTooltipAllowed = tooltip.value.allowedNodes.includes(node.$type);
if ((isNodeTooltipAllowed && !tooltip.value.isActive)
|| (isNodeTooltipAllowed && tooltip.value.nodeId !== node.id)) {
tooltip.value.nodeId = node.id;
tooltip.value.nodeName = node.name;
tooltip.value.isActive = true;
nextTick(() => {
tooltip.value.coordinates = { x: event.clientX, y: event.clientY };
calculateTooltipPosition();
});
} else if (tooltip.value.nodeId === node.id && tooltip.value.isActive) {
tooltip.value.isActive = false;
}
};

const isTooltipLoading = (value) => {
tooltip.value.isLoading = value;
};

const handleClick = (payload) => {
if (isMappingActive.value) {
setupTooltip(payload);
}
};

watchEffect(() => {
if (!tooltip.value.isLoading) {
nextTick(() => {
calculateTooltipPosition();
});
}
});

onMounted(() => {
ProcessMaker.$modeler = modelerRef.value;
});

onBeforeUnmount(() => {
ProcessMaker.$modeler = null;
modelerRef.value.reset({ readOnly: true });
modelerRef.value.reset({ panMode: true });
modelerRef.value = null;
tooltipRef.value = null;
});
</script>
4 changes: 4 additions & 0 deletions resources/jscomposition/cases/casesDetail/variables/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export const getRequestCount = () => requestCount;
export const getErrors = () => errorLogs;

export const isErrors = () => request.status === "ERROR";

export const getInflightData = () => inflightData;

export const getXML = () => inflightData.bpmn;
Loading
Loading