From d934244f398567ef8d1bd51b6fcfe732f710d297 Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Wed, 26 Jun 2024 17:01:15 +0700 Subject: [PATCH 1/5] TE-614: Check end envent of subprocess --- .../processes/FlowMixedSubProcess.p.json | 33 +++++--- .../inspector/internal/PathFinder.java | 3 +- .../inspector/internal/ProcessGraph.java | 20 ++++- .../inspector/internal/WorkflowPath.java | 79 ++++++++++++++++--- 4 files changed, 107 insertions(+), 28 deletions(-) diff --git a/process-inspector-test/processes/FlowMixedSubProcess.p.json b/process-inspector-test/processes/FlowMixedSubProcess.p.json index 90e179b..ba6c301 100644 --- a/process-inspector-test/processes/FlowMixedSubProcess.p.json +++ b/process-inspector-test/processes/FlowMixedSubProcess.p.json @@ -35,12 +35,12 @@ } ] }, "visual" : { - "at" : { "x" : 288, "y" : 128 }, + "at" : { "x" : 256, "y" : 128 }, "labelOffset" : { "x" : 16, "y" : -24 } }, "connect" : [ { "id" : "f1", "to" : "f4", "condition" : "ivp==\"TaskA.ivp\"", "var" : "in1" }, - { "id" : "f7", "to" : "S30", "via" : [ { "x" : 288, "y" : 328 } ], "condition" : "ivp==\"TaskB.ivp\"" } + { "id" : "f7", "to" : "S30", "via" : [ { "x" : 256, "y" : 272 } ], "condition" : "ivp==\"TaskB.ivp\"" } ] }, { "id" : "S10", @@ -191,10 +191,10 @@ ] } ], "visual" : { - "at" : { "x" : 536, "y" : 80 } + "at" : { "x" : 408, "y" : 88 } }, "connect" : [ - { "id" : "f12", "to" : "S40", "via" : [ { "x" : 784, "y" : 80 } ] }, + { "id" : "f12", "to" : "S40", "via" : [ { "x" : 560, "y" : 88 } ] }, { "id" : "f16", "to" : "S20" } ] }, { @@ -215,12 +215,12 @@ } ] }, "visual" : { - "at" : { "x" : 384, "y" : 128 }, + "at" : { "x" : 320, "y" : 128 }, "labelOffset" : { "x" : -8, "y" : -8 } }, "connect" : [ - { "id" : "f6", "to" : "S20", "via" : [ { "x" : 384, "y" : 200 } ], "condition" : "ivp==\"TaskB.ivp\"" }, - { "id" : "f5", "to" : "S10", "via" : [ { "x" : 384, "y" : 80 } ], "condition" : "ivp==\"TaskA.ivp\"" } + { "id" : "f6", "to" : "S20", "via" : [ { "x" : 320, "y" : 184 } ], "condition" : "ivp==\"TaskB.ivp\"" }, + { "id" : "f5", "to" : "S10", "via" : [ { "x" : 320, "y" : 88 } ], "condition" : "ivp==\"TaskA.ivp\"" } ] }, { "id" : "S20", @@ -331,7 +331,7 @@ ] } ], "visual" : { - "at" : { "x" : 536, "y" : 200 } + "at" : { "x" : 408, "y" : 184 } }, "connect" : [ { "id" : "f11", "to" : "S40" }, @@ -414,7 +414,7 @@ ] } ], "visual" : { - "at" : { "x" : 688, "y" : 328 } + "at" : { "x" : 488, "y" : 272 } }, "connect" : [ { "id" : "f10", "to" : "S40" } @@ -545,7 +545,14 @@ ] }, "task" : { - "name" : "SubD-TaskB" + "name" : "SubD-TaskD", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] } }, "visual" : { @@ -579,7 +586,7 @@ ] } ], "visual" : { - "at" : { "x" : 784, "y" : 200 } + "at" : { "x" : 560, "y" : 184 } }, "connect" : [ { "id" : "f9", "to" : "f8" } @@ -588,7 +595,7 @@ "id" : "f8", "type" : "TaskEnd", "visual" : { - "at" : { "x" : 920, "y" : 200 } + "at" : { "x" : 680, "y" : 184 } } }, { "id" : "f14", @@ -608,7 +615,7 @@ } }, "visual" : { - "at" : { "x" : 160, "y" : 128 } + "at" : { "x" : 152, "y" : 128 } }, "connect" : [ { "id" : "f15", "to" : "f3", "var" : "in1" } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java index 56ebcfc..9402672 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java @@ -42,6 +42,7 @@ import ch.ivyteam.ivy.process.model.diagram.value.Label; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; import ch.ivyteam.ivy.process.model.element.event.end.TaskEnd; +import ch.ivyteam.ivy.process.model.element.event.start.EmbeddedStart; import ch.ivyteam.ivy.process.model.element.event.start.RequestStart; import ch.ivyteam.ivy.process.model.element.gateway.Alternative; import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; @@ -506,7 +507,7 @@ private SubProcessGroup findPathOfSubProcess(ProcessElement subProcessElement, S .map(ProcessElement::getElement) .map(SequenceFlow.class::cast).orElse(null); - BaseElement start = processGraph.findStartElementOfProcess((SequenceFlow)lastElement, processElement); + EmbeddedStart start = processGraph.findStartElementOfProcess((SequenceFlow)lastElement, processElement); List path = findAnalysisPaths(new CommonElement(start), flowName, findType, emptyList()); SubProcessGroup subProcessGroup = new SubProcessGroup(processElement, path); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java index 3d1b607..ccbca43 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java @@ -23,6 +23,8 @@ import ch.ivyteam.ivy.process.model.element.SingleTaskCreator; import ch.ivyteam.ivy.process.model.element.TaskAndCaseModifier; import ch.ivyteam.ivy.process.model.element.activity.SubProcessCall; +import ch.ivyteam.ivy.process.model.element.event.end.EmbeddedEnd; +import ch.ivyteam.ivy.process.model.element.event.end.TaskEnd; import ch.ivyteam.ivy.process.model.element.event.start.EmbeddedStart; import ch.ivyteam.ivy.process.model.element.event.start.RequestStart; import ch.ivyteam.ivy.process.model.element.gateway.Alternative; @@ -65,10 +67,11 @@ public List getParentElementNamesEmbeddedProcessElement(BaseElement pare return result; } - public BaseElement findStartElementOfProcess(SequenceFlow sequenceFlow, EmbeddedProcessElement embeddedProcessElement) { - BaseElement start = findStartElementOfProcess(embeddedProcessElement).stream() + public EmbeddedStart findStartElementOfProcess(SequenceFlow sequenceFlow, EmbeddedProcessElement embeddedProcessElement) { + EmbeddedStart start = findStartElementOfProcess(embeddedProcessElement).stream() .filter(it -> sequenceFlow == null || ((EmbeddedStart) it).getConnectedOuterSequenceFlow().equals(sequenceFlow)) .findFirst() + .map(EmbeddedStart.class::cast) .orElse(null); return start; } @@ -166,6 +169,14 @@ public boolean isSubProcessCall(BaseElement element) { return element instanceof SubProcessCall; } + public boolean isTaskEnd(BaseElement element) { + return element instanceof TaskEnd; + } + + public boolean isEmbeddedEnd(BaseElement element) { + return element instanceof EmbeddedEnd; + } + public boolean isHandledAsTask(SubProcessCall subProcessCall) { return containPrefixs(subProcessCall.getParameters().getCode(), "APAConfig.handleAsTask"); } @@ -179,10 +190,11 @@ private boolean containPrefixs(String content, String... prefix) { return List.of(prefix).stream().allMatch(it -> content.contains(it)); } - private List findStartElementOfProcess(EmbeddedProcessElement embeddedProcessElement) { + private List findStartElementOfProcess(EmbeddedProcessElement embeddedProcessElement) { EmbeddedProcess process = embeddedProcessElement.getEmbeddedProcess(); - List starts = process.getElements().stream() + List starts = process.getElements().stream() .filter(EmbeddedStart.class::isInstance) + .map(EmbeddedStart.class::cast) .toList(); return starts; } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java index bdcc5f5..db0197c 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java @@ -14,11 +14,15 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.map.HashedMap; import org.apache.commons.lang3.StringUtils; +import com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; import com.axonivy.utils.process.inspector.internal.model.CommonElement; import com.axonivy.utils.process.inspector.internal.model.ProcessElement; @@ -29,12 +33,14 @@ import com.axonivy.utils.process.inspector.model.DetectedTask; import com.axonivy.utils.process.inspector.model.ElementTask; +import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.HierarchicElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; import ch.ivyteam.ivy.process.model.element.SingleTaskCreator; import ch.ivyteam.ivy.process.model.element.TaskAndCaseModifier; import ch.ivyteam.ivy.process.model.element.activity.SubProcessCall; +import ch.ivyteam.ivy.process.model.element.event.end.EmbeddedEnd; import ch.ivyteam.ivy.process.model.element.event.end.TaskEnd; import ch.ivyteam.ivy.process.model.element.gateway.Alternative; import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; @@ -91,7 +97,7 @@ protected List convertToDetectedElements(Map convertPathToDetectedElements(ProcessElement start Duration timeUntilStart = timeUntilStarts.get(startElement); Duration durationStart = timeUntilEnd(result, timeUntilStart); - for (ProcessElement element : path.getElements()) { - // CommonElement(RequestStart) + int lengh = path.getElements().size(); + + for (int i = 0; i < lengh; i++) { + ProcessElement element = path.getElements().get(i); + ProcessElement nextElement = i < lengh - 1 ? path.getElements().get(i + 1) : null; + + //It is start node -> Ignored if (processGraph.isRequestStart(element.getElement())) { continue; } + //It is system task -> Ignored if (element instanceof CommonElement && processGraph.isTaskAndCaseModifier(element.getElement()) && processGraph.isSystemTask(element.getElement())) { continue; } - // CommonElement(Alternative) - if (processGraph.isAlternative(element.getElement()) && this.isEnableDescribeAlternative) { + //It is alternative -> base on this.isEnableDescribeAlternative to decide + if (this.isEnableDescribeAlternative && processGraph.isAlternative(element.getElement())) { var detectedAlternative = createDetectedAlternative(element); if (detectedAlternative != null) { result.add(detectedAlternative); @@ -152,8 +164,7 @@ private List convertPathToDetectedElements(ProcessElement start if (processGraph.isSubProcessCall(element.getElement())) { SubProcessCall subProcessCall = (SubProcessCall) element.getElement(); if (processGraph.isHandledAsTask(subProcessCall)) { - var detectedSubProcessCall = createDetectedTaskFromSubProcessCall(subProcessCall, useCase, - durationStart); + var detectedSubProcessCall = createDetectedTaskFromSubProcessCall(subProcessCall, useCase, durationStart); if (detectedSubProcessCall != null) { result.add(detectedSubProcessCall); durationStart = timeUntilEnd(result, timeUntilStart); @@ -161,7 +172,7 @@ private List convertPathToDetectedElements(ProcessElement start } } - // CommonElement(SingleTaskCreator) + // // It is User Task - CommonElement(SingleTaskCreator) if (processGraph.isSingleTaskCreator(element.getElement())) { SingleTaskCreator singleTask = (SingleTaskCreator) element.getElement(); var detectedTask = createDetectedTask(singleTask, useCase, durationStart); @@ -172,8 +183,10 @@ private List convertPathToDetectedElements(ProcessElement start continue; } - if (element instanceof SubProcessGroup) { - List subPaths = ((SubProcessGroup) element).getInternalPaths(); + // It is EmbeddedProcessElement + if (element instanceof SubProcessGroup) { + List internalPaths = ((SubProcessGroup) element).getInternalPaths(); + List subPaths = getAnalysisPathBaseOnNextSequenceFlow(internalPaths, nextElement.getElement()); List allTaskFromSubPath = new ArrayList<>(); for(AnalysisPath subPath : subPaths) { ProcessElement startSubElement = subPath.getElements().get(0); @@ -464,4 +477,50 @@ private String getTaskName(TaskConfig taskConfig) { return defaultIfBlank(taskNameFromRawMacro, taskIdentifier); } + + private List getAnalysisPathBaseOnNextSequenceFlow(List subPaths, BaseElement sequenceFlow) { + List result = new ArrayList<>(); + + if (sequenceFlow instanceof SequenceFlow) { + for (AnalysisPath path : subPaths) { + ProcessElement lastProcessElement = AnalysisPathHelper.getLastElement(path); + BaseElement lastElement = lastProcessElement.getElement(); + + if (processGraph.isTaskEnd(lastElement)) { + result.add(path); + continue; + } + + if (processGraph.isTaskSwitchGateway(lastElement)) { + Map> validInternalPaths = new LinkedHashMap<>(); + ((TaskParallelGroup) lastProcessElement).getInternalPaths().forEach((key, value) -> { + var validPaths = getAnalysisPathBaseOnNextSequenceFlow(value, sequenceFlow); + if (isNotEmpty(validPaths)) { + validInternalPaths.put(key, validPaths); + } + }); + + int size = path.getElements().size(); + List newPath = path.getElements().stream().limit(size - 1).collect(Collectors.toList()); + if (MapUtils.isNotEmpty(validInternalPaths)) { + TaskParallelGroup taskGroup = new TaskParallelGroup(lastElement); + taskGroup.setInternalPaths(validInternalPaths); + newPath.add(taskGroup); + } + continue; + } + + if (processGraph.isEmbeddedEnd(lastElement)) { + BaseElement outerSequenceFlow = ((EmbeddedEnd) lastElement).getConnectedOuterSequenceFlow(); + if(outerSequenceFlow.getPid().equals(sequenceFlow.getPid())) { + result.add(path); + } + continue; + } + + } + } + return result; + } } + \ No newline at end of file From b73c93458c165280829404ff43e8b154fb251e9a Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Mon, 1 Jul 2024 10:54:27 +0700 Subject: [PATCH 2/5] TE-614: Refactor code --- .../inspector/test/FlowMixedSubProcess.java | 2 +- .../inspector/internal/PathFinder.java | 100 ++++---------- .../inspector/internal/ProcessGraph.java | 7 + .../inspector/internal/WorkflowPath.java | 6 +- .../internal/helper/AnalysisPathHelper.java | 122 ++++++++++++------ 5 files changed, 118 insertions(+), 119 deletions(-) diff --git a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java index 2fe00b8..17ade8a 100644 --- a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java +++ b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java @@ -30,7 +30,7 @@ void shouldFindAllTasks() throws Exception { var start = ProcessGraphHelper.findByElementName(process, "start"); var detectedTasks = processInspector.findAllTasks(start, UseCase.BIGPROJECT); - var expected = Arrays.array("TaskA", "SubA-TaskA", "SubA-TaskC", "SubA-TaskB", "SubD-TaskB", "SubB-TaskA", "SubD-TaskC", "SubC-TaskA", "SubD-TaskB", "SubD-TaskA"); + var expected = Arrays.array("TaskA", "SubA-TaskA", "SubA-TaskC", "SubA-TaskB", "SubD-TaskB", "SubB-TaskA", "SubD-TaskC", "SubC-TaskA", "SubD-TaskD", "SubD-TaskA"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java index 9402672..6bd8f56 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java @@ -2,6 +2,9 @@ import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addAllToPath; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addToPath; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathFrom; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathTo; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getPathByStartElements; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.replaceFirstElement; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -167,8 +170,7 @@ private ProcessElement findIntersectionTaskSwitchGateway(Map> mergePath(Map> source, ProcessElement intersection, List subPath) { - Map> pathBeforeIntersection = getPathBeforeIntersection(source, - intersection); + Map> pathBeforeIntersection = getAnalysisPathTo(source, intersection); Map> pathNotIntersection = source.entrySet().stream() .filter(entry -> !pathBeforeIntersection.keySet().contains(entry.getKey())).collect(toMap( @@ -234,14 +236,14 @@ private List convertToAnalysisPaths(Map startElements = intersectionEntry.getValue(); - var paths = getPathByStartElements(source, startElements); + Map> paths = getPathByStartElements(source, startElements); - var pathBeforeIntersection = getPathBeforeIntersection(paths, intersection); + Map> pathBeforeIntersection = getAnalysisPathTo(paths, intersection); // Call recursion inside convertToTaskParallelGroup TaskParallelGroup taskGroup = convertToTaskParallelGroup(pathBeforeIntersection); - List subPathAfterIntersection = getAnalysisPathFromIntersection(paths, intersection); + List subPathAfterIntersection = getAnalysisPathFrom(paths, intersection); result.addAll(addToPath(List.of(new AnalysisPath(List.of(taskGroup))), subPathAfterIntersection)); } @@ -250,87 +252,31 @@ private List convertToAnalysisPaths(Map> getPathHaveNoIntersection( - Map> source, Map> intersections) { - List startElementWithIntersection = intersections.values().stream().flatMap(Collection::stream) + Map> source, + Map> intersections) { + + List startElementWithIntersection = intersections.values().stream() + .flatMap(Collection::stream) .toList(); - return source.entrySet().stream().filter(entry -> !startElementWithIntersection.contains(entry.getKey())) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, - LinkedHashMap::new)); + return source.entrySet().stream() + .filter(entry -> !startElementWithIntersection.contains(entry.getKey())) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new)); } - private TaskParallelGroup convertToTaskParallelGroupWithInternalPath( - Map> internalPaths) { + private TaskParallelGroup convertToTaskParallelGroupWithInternalPath(Map> internalPaths) { TaskParallelGroup taskGroup = new TaskParallelGroup(null); - taskGroup.setInternalPaths(convertToInternalPathForTaskParallelGroup(internalPaths)); - return taskGroup; - } - - private Map> convertToInternalPathForTaskParallelGroup( - Map> internalPath) { + Map> result = new LinkedHashMap<>(); - internalPath.entrySet().forEach(it -> { - NodeElement element = (NodeElement) it.getKey().getElement(); - result.put(element.getIncoming().get(0), it.getValue()); - }); - return result; - } - - private List getAnalysisPathFromIntersection(Map> source, - ProcessElement intersection) { - Set result = new HashSet<>(); - List paths = source.values().stream().flatMap(Collection::stream).toList(); - for (AnalysisPath path : paths) { - List elements = path.getElements(); - int index = elements.indexOf(intersection); - if (index >= 0) { - List beforeIntersection = elements.subList(index, elements.size() - 1); - result.add(new AnalysisPath(beforeIntersection)); - } - } - - return new ArrayList<>(result); - } - - private Map> getPathByStartElements( - Map> source, Set elements) { - Map> result = new LinkedHashMap<>(); - - elements.forEach(key -> { - List paths = source.get(key); - if (isNotEmpty(paths)) { - result.put(key, paths); - } + internalPaths.entrySet().forEach(it -> { + SequenceFlow sequenceFlow = processGraph.getFirstIncoming(it.getKey().getElement()); + result.put(sequenceFlow, it.getValue()); }); - - return result; - } - - private Map> getPathBeforeIntersection( - Map> paths, ProcessElement intersection) { - Map> pathBeforeIntersection = new LinkedHashMap<>(); - for (Entry> entry : paths.entrySet()) { - List beforeIntersections = getAnalysisPathBeforeIntersection(entry.getValue(), intersection); - if (isNotEmpty(beforeIntersections)) { - pathBeforeIntersection.put(entry.getKey(), beforeIntersections); - } - } - - return pathBeforeIntersection; + + taskGroup.setInternalPaths(result); + return taskGroup; } - private List getAnalysisPathBeforeIntersection(List paths, - ProcessElement intersection) { - List result = new ArrayList<>(); - for (AnalysisPath path : paths) { - int index = path.getElements().indexOf(intersection); - if (index >= 0) { - List beforeIntersection = path.getElements().subList(0, index); - result.add(new AnalysisPath(beforeIntersection)); - } - } - return result; - } private Map> getLastIntersectionByStartElements(Map> paths) { var intersections = getAllIntersectionTaskSwitchGatewayWithStartElement(paths); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java index ccbca43..4086640 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java @@ -186,6 +186,13 @@ public String getAlternativeNameId(BaseElement alternative) { .collect(Collectors.joining("-")); } + public SequenceFlow getFirstIncoming(BaseElement element) { + if(element instanceof NodeElement) { + return ((NodeElement) element).getIncoming().stream().findFirst().orElse(null); + } + return null; + } + private boolean containPrefixs(String content, String... prefix) { return List.of(prefix).stream().allMatch(it -> content.contains(it)); } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java index db0197c..12554b0 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java @@ -97,7 +97,7 @@ protected List convertToDetectedElements(Map convertPathToDetectedElements(ProcessElement start // It is EmbeddedProcessElement if (element instanceof SubProcessGroup) { - List internalPaths = ((SubProcessGroup) element).getInternalPaths(); - List subPaths = getAnalysisPathBaseOnNextSequenceFlow(internalPaths, nextElement.getElement()); + List subPaths = ((SubProcessGroup) element).getInternalPaths(); + //List subPaths = getAnalysisPathBaseOnNextSequenceFlow(internalPaths, nextElement.getElement()); List allTaskFromSubPath = new ArrayList<>(); for(AnalysisPath subPath : subPaths) { ProcessElement startSubElement = subPath.getElements().get(0); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java index 5364248..afeecdb 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java @@ -4,9 +4,13 @@ import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.collections4.ListUtils; @@ -47,7 +51,8 @@ public static List addAllToPath(List paths, List addAllToPath(List paths, Map> pathOptions) { + public static List addAllToPath(List paths, + Map> pathOptions) { List result = new ArrayList<>(); if (pathOptions.isEmpty()) { result.addAll(paths); @@ -68,7 +73,6 @@ public static List addAllToPath(List paths, Map replaceFirstElement(ProcessElement element, List subPaths) { if (subPaths.isEmpty()) { @@ -84,82 +88,124 @@ public static List replaceFirstElement(ProcessElement element, Lis return result; } - + public static int getLastIndex(AnalysisPath path) { List elements = path.getElements(); return elements.size() == 0 ? 0 : elements.size() - 1; } - + public static ProcessElement getLastElement(AnalysisPath path) { List elements = path.getElements(); int size = elements.size(); return size == 0 ? null : elements.get(size - 1); } - + public static NodeElement getFirstNodeElement(List paths) { - NodeElement startNode = AnalysisPathHelper.getAllProcessElement(paths).stream() - .map(ProcessElement::getElement) - .filter(NodeElement.class::isInstance) - .findFirst() - .map(NodeElement.class::cast) - .orElse(null); + NodeElement startNode = AnalysisPathHelper.getAllProcessElement(paths).stream().map(ProcessElement::getElement) + .filter(NodeElement.class::isInstance).findFirst().map(NodeElement.class::cast).orElse(null); return startNode; } - - public static List removeLastElementByClassType(List paths , Class clazz) { + + public static List removeLastElementByClassType(List paths, Class clazz) { List result = new ArrayList<>(); for (AnalysisPath path : paths) { int lastIndex = AnalysisPathHelper.getLastIndex(path); List pathElements = new ArrayList<>(path.getElements()); ProcessElement lastElement = pathElements.get(lastIndex); - + if (lastElement instanceof CommonElement && clazz.isInstance(lastElement.getElement())) { pathElements.remove(lastIndex); } - + result.add(new AnalysisPath(pathElements)); } return result; } - + public static List getAllProcessElement(List paths) { - List elements = paths.stream() - .map(AnalysisPath::getElements) - .flatMap(List::stream) - .flatMap(it -> getAllProcessElement(it).stream()) - .toList(); + List elements = paths.stream().map(AnalysisPath::getElements).flatMap(List::stream) + .flatMap(it -> getAllProcessElement(it).stream()).toList(); return elements; } - + public static List getAllProcessElement(ProcessElement element) { - if(element instanceof CommonElement) { + if (element instanceof CommonElement) { return List.of(element); } - - if(element instanceof TaskParallelGroup) { + + if (element instanceof TaskParallelGroup) { List result = new ArrayList<>(); - + TaskParallelGroup group = (TaskParallelGroup) element; - if(group.getElement() != null) { + if (group.getElement() != null) { result.add(new CommonElement(group.getElement())); } - - for(Entry> entry : group.getInternalPaths().entrySet()) { - List allProcessElement = entry.getValue().stream() - .map(AnalysisPath::getElements) - .flatMap(List::stream) - .flatMap(it -> getAllProcessElement(it).stream()) - .toList(); - - result.add(new CommonElement(entry.getKey())); + + for (Entry> entry : group.getInternalPaths().entrySet()) { + List allProcessElement = entry.getValue().stream().map(AnalysisPath::getElements) + .flatMap(List::stream).flatMap(it -> getAllProcessElement(it).stream()).toList(); + + result.add(new CommonElement(entry.getKey())); result.addAll(allProcessElement); } - + return result; } - + return emptyList(); } + + public static > Map getPathByStartElements(Map source, Set keys) { + Map result = new LinkedHashMap<>(); + keys.forEach(key -> { + V paths = source.get(key); + if (isNotEmpty(paths)) { + result.put(key, paths); + } + }); + return result; + } + + public static List getAnalysisPathFrom(Map> source, ProcessElement from) { + Set result = new HashSet<>(); + List paths = source.values().stream().flatMap(Collection::stream).toList(); + + for (AnalysisPath path : paths) { + List elements = path.getElements(); + int index = elements.indexOf(from); + if (index >= 0) { + List afterFrom = elements.subList(index, elements.size() - 1); + result.add(new AnalysisPath(afterFrom)); + } + } + + return new ArrayList<>(result); + + } + + public static Map> getAnalysisPathTo(Map> source, ProcessElement to) { + Map> pathBeforeIntersection = new LinkedHashMap<>(); + for (Entry> entry : source.entrySet()) { + List beforeTo = getAnalysisPathTo(entry.getValue(), to); + if (isNotEmpty(beforeTo)) { + pathBeforeIntersection.put(entry.getKey(), beforeTo); + } + } + + return pathBeforeIntersection; + } + + private static List getAnalysisPathTo(List source, ProcessElement to) { + List result = new ArrayList<>(); + for (AnalysisPath path : source) { + int index = path.getElements().indexOf(to); + if (index >= 0) { + List beforeIntersection = path.getElements().subList(0, index); + result.add(new AnalysisPath(beforeIntersection)); + } + } + return result; + } } From e8ad45ea06ad5fdc6a2cbd3f79b46b810bbcbd88 Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Mon, 1 Jul 2024 17:18:40 +0700 Subject: [PATCH 3/5] TE-614: Check last end event base on next sequence flow --- process-inspector-test/.gitignore | 41 +-- .../processes/FlowMixedSubProcess.p.json | 285 ++++++++++++----- .../inspector/test/FlowMixedSubProcess.java | 6 +- .../inspector/internal/PathFinder.java | 295 ++++++++---------- .../inspector/internal/ProcessGraph.java | 157 ++++++++-- .../inspector/internal/WorkflowPath.java | 279 +++++++++-------- .../internal/helper/AnalysisPathHelper.java | 61 ++++ .../internal/model/DetectedEmbeddedEnd.java | 33 ++ .../internal/model/DetectedPath.java | 52 +++ .../internal/model/DetectedTaskEnd.java | 16 + 10 files changed, 804 insertions(+), 421 deletions(-) create mode 100644 process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedEmbeddedEnd.java create mode 100644 process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedPath.java create mode 100644 process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java diff --git a/process-inspector-test/.gitignore b/process-inspector-test/.gitignore index 1b2547b..fba859a 100644 --- a/process-inspector-test/.gitignore +++ b/process-inspector-test/.gitignore @@ -1,19 +1,22 @@ -# general -Thumbs.db -.DS_Store -*~ -*.log - -# java -*.class -hs_err_pid* - -# maven -target/ -lib/mvn-deps/ - -# ivy -classes/ -src_dataClasses/ -src_wsproc/ -logs/ +# general +Thumbs.db +.DS_Store +*~ +*.log + +# java +*.class +hs_err_pid* + +# maven +target/ +lib/mvn-deps/ + +# ivy +classes/ +src_dataClasses/ +src_wsproc/ +logs/ + +# test +.temp-* diff --git a/process-inspector-test/processes/FlowMixedSubProcess.p.json b/process-inspector-test/processes/FlowMixedSubProcess.p.json index ba6c301..5513aa0 100644 --- a/process-inspector-test/processes/FlowMixedSubProcess.p.json +++ b/process-inspector-test/processes/FlowMixedSubProcess.p.json @@ -150,7 +150,7 @@ "import com.axonivy.utils.process.inspector.APAConfig;", "import com.axonivy.utils.process.inspector.test.UseCase;", "", - "APAConfig.setEstimate(6,TimeUnit.HOURS,UseCase.BIGPROJECT);" + "APAConfig.setEstimate(17,TimeUnit.HOURS,UseCase.BIGPROJECT);" ] } }, @@ -163,8 +163,10 @@ }, { "id" : "S10-f10", "type" : "TaskEnd", + "name" : "End", "visual" : { - "at" : { "x" : 544, "y" : 168 } + "at" : { "x" : 544, "y" : 168 }, + "labelOffset" : { "x" : 13, "y" : 33 } } }, { "id" : "S10-f12", @@ -231,50 +233,28 @@ "type" : "EmbeddedStart", "name" : "from SubB", "visual" : { - "at" : { "x" : 192, "y" : 168 } + "at" : { "x" : 224, "y" : 168 }, + "labelOffset" : { "x" : 11, "y" : 27 } }, "parentConnector" : "f6", "connect" : [ - { "id" : "S20-f0", "to" : "S20-f7", "var" : "in1" } + { "id" : "S20-f0", "to" : "S20-f11", "var" : "in1" } ] }, { "id" : "S20-g1", "type" : "EmbeddedEnd", "name" : "to SubD", "visual" : { - "at" : { "x" : 632, "y" : 72 }, + "at" : { "x" : 784, "y" : 72 }, "labelOffset" : { "x" : 3, "y" : 43 } }, "parentConnector" : "f11" - }, { - "id" : "S20-f1", - "type" : "UserTask", - "name" : "SubB-TaskA", - "config" : { - "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", - "task" : { - "name" : "SubB-TaskA", - "code" : [ - "import java.util.concurrent.TimeUnit;", - "import com.axonivy.utils.process.inspector.APAConfig;", - "import com.axonivy.utils.process.inspector.test.UseCase;", - "", - "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" - ] - } - }, - "visual" : { - "at" : { "x" : 456, "y" : 120 } - }, - "connect" : [ - { "id" : "S20-f2", "to" : "S20-f3", "var" : "in1" } - ] }, { "id" : "S20-g2", "type" : "EmbeddedEnd", "name" : "to SubC", "visual" : { - "at" : { "x" : 624, "y" : 152 }, + "at" : { "x" : 784, "y" : 168 }, "labelOffset" : { "x" : 3, "y" : 27 } }, "parentConnector" : "f13" @@ -295,22 +275,23 @@ } ] }, "visual" : { - "at" : { "x" : 552, "y" : 120 } + "at" : { "x" : 568, "y" : 120 } }, "connect" : [ - { "id" : "S20-f4", "to" : "S20-g1", "condition" : "ivp==\"TaskA.ivp\"" }, - { "id" : "S20-f5", "to" : "S20-g2", "condition" : "ivp==\"TaskB.ivp\"" } + { "id" : "S20-f4", "to" : "S20-f13", "via" : [ { "x" : 568, "y" : 72 } ], "condition" : "ivp==\"TaskA.ivp\"" }, + { "id" : "S20-f5", "to" : "S20-f15", "via" : [ { "x" : 568, "y" : 168 } ], "condition" : "ivp==\"TaskB.ivp\"" } ] }, { "id" : "S20-g3", "type" : "EmbeddedStart", "name" : "from SubA", "visual" : { - "at" : { "x" : 208, "y" : 56 } + "at" : { "x" : 224, "y" : 72 }, + "labelOffset" : { "x" : 11, "y" : 27 } }, "parentConnector" : "f16", "connect" : [ - { "id" : "S20-f6", "to" : "S20-f7", "var" : "in2" } + { "id" : "S20-f6", "to" : "S20-f9", "var" : "in2" } ] }, { "id" : "S20-f7", @@ -324,11 +305,150 @@ } ] }, "visual" : { - "at" : { "x" : 328, "y" : 120 } + "at" : { "x" : 440, "y" : 120 } + }, + "connect" : [ + { "id" : "S20-f1", "to" : "S20-f2", "condition" : "ivp==\"TaskA.ivp\"" } + ] + }, { + "id" : "S20-f9", + "type" : "UserTask", + "name" : "SubB-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(2,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 344, "y" : 72 } + }, + "connect" : [ + { "id" : "S20-f10", "to" : "S20-f7", "via" : [ { "x" : 440, "y" : 72 } ], "var" : "in2" } + ] + }, { + "id" : "S20-f11", + "type" : "UserTask", + "name" : "SubB-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskB", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 344, "y" : 168 } + }, + "connect" : [ + { "id" : "S20-f12", "to" : "S20-f7", "via" : [ { "x" : 440, "y" : 168 } ], "var" : "in1" } + ] + }, { + "id" : "S20-f13", + "type" : "UserTask", + "name" : "SubB-TaskC", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskC", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(4,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 664, "y" : 72 } + }, + "connect" : [ + { "id" : "S20-f14", "to" : "S20-g1" } + ] + }, { + "id" : "S20-f15", + "type" : "UserTask", + "name" : "SubB-TaskD", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskD", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 664, "y" : 168 } + }, + "connect" : [ + { "id" : "S20-f16", "to" : "S20-g2" } + ] + }, { + "id" : "S20-f2", + "type" : "Alternative", + "config" : { + "conditions" : { + "S20-f18" : "true", + "S20-f8" : "" + } + }, + "visual" : { + "at" : { "x" : 512, "y" : 120 } }, "connect" : [ - { "id" : "S20-f8", "to" : "S20-f1", "condition" : "ivp==\"TaskA.ivp\"" } + { "id" : "S20-f8", "to" : "S20-f3", "var" : "in1" }, + { "id" : "S20-f18", "to" : "S20-f17" } ] + }, { + "id" : "S20-f17", + "type" : "UserTask", + "name" : "SubB-TaskE", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskE", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(13,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 512, "y" : 240 } + }, + "connect" : [ + { "id" : "S20-f20", "to" : "S20-f19" } + ] + }, { + "id" : "S20-f19", + "type" : "TaskEnd", + "name" : "End", + "visual" : { + "at" : { "x" : 784, "y" : 240 }, + "labelOffset" : { "x" : 13, "y" : 33 } + } } ], "visual" : { "at" : { "x" : 408, "y" : 184 } @@ -346,54 +466,33 @@ "type" : "EmbeddedStart", "name" : "from split1", "visual" : { - "at" : { "x" : 224, "y" : 64 } + "at" : { "x" : 216, "y" : 64 }, + "labelOffset" : { "x" : 11, "y" : 27 } }, "parentConnector" : "f7", "connect" : [ - { "id" : "S30-f0", "to" : "S30-f4", "var" : "in1" } + { "id" : "S30-f0", "to" : "S30-f8", "var" : "in1" } ] }, { "id" : "S30-g1", "type" : "EmbeddedEnd", "name" : "to SubD", "visual" : { - "at" : { "x" : 672, "y" : 64 }, + "at" : { "x" : 496, "y" : 112 }, "labelOffset" : { "x" : 27, "y" : 43 } }, "parentConnector" : "f10" - }, { - "id" : "S30-f1", - "type" : "UserTask", - "name" : "SubC-TaskA", - "config" : { - "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", - "task" : { - "name" : "SubC-TaskA", - "code" : [ - "import java.util.concurrent.TimeUnit;", - "import com.axonivy.utils.process.inspector.APAConfig;", - "import com.axonivy.utils.process.inspector.test.UseCase;", - "", - "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" - ] - } - }, - "visual" : { - "at" : { "x" : 448, "y" : 64 } - }, - "connect" : [ - { "id" : "S30-f2", "to" : "S30-g1" } - ] }, { "id" : "S30-g2", "type" : "EmbeddedStart", "name" : "from SubB", "visual" : { - "at" : { "x" : 216, "y" : 136 } + "at" : { "x" : 216, "y" : 168 }, + "labelOffset" : { "x" : 11, "y" : 27 } }, "parentConnector" : "f13", "connect" : [ - { "id" : "S30-f3", "to" : "S30-f4", "var" : "in2" } + { "id" : "S30-f3", "to" : "S30-f6", "var" : "in2" } ] }, { "id" : "S30-f4", @@ -407,10 +506,56 @@ } ] }, "visual" : { - "at" : { "x" : 320, "y" : 64 } + "at" : { "x" : 432, "y" : 112 } + }, + "connect" : [ + { "id" : "S30-f1", "to" : "S30-g1", "condition" : "ivp==\"TaskA.ivp\"" } + ] + }, { + "id" : "S30-f6", + "type" : "UserTask", + "name" : "SubC-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubC-TaskB", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(4,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 328, "y" : 168 } + }, + "connect" : [ + { "id" : "S30-f7", "to" : "S30-f4", "via" : [ { "x" : 432, "y" : 168 } ], "var" : "in2" } + ] + }, { + "id" : "S30-f8", + "type" : "UserTask", + "name" : "SubC-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "SubC-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import com.axonivy.utils.process.inspector.test.UseCase;", + "", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 328, "y" : 64 } }, "connect" : [ - { "id" : "S30-f5", "to" : "S30-f1", "condition" : "ivp==\"TaskA.ivp\"" } + { "id" : "S30-f9", "to" : "S30-f4", "via" : [ { "x" : 432, "y" : 64 } ], "var" : "in1" } ] } ], "visual" : { @@ -448,7 +593,7 @@ } }, "visual" : { - "at" : { "x" : 552, "y" : 152 } + "at" : { "x" : 544, "y" : 152 } }, "connect" : [ { "id" : "S40-f2", "to" : "S40-g0" } @@ -465,7 +610,7 @@ } ] }, "visual" : { - "at" : { "x" : 432, "y" : 144 } + "at" : { "x" : 416, "y" : 152 } }, "connect" : [ { "id" : "S40-f4", "to" : "S40-f1", "condition" : "ivp==\"TaskA.ivp\"" } @@ -526,7 +671,7 @@ "at" : { "x" : 296, "y" : 80 } }, "connect" : [ - { "id" : "S40-f8", "to" : "S40-f3", "var" : "in3" } + { "id" : "S40-f8", "to" : "S40-f3", "via" : [ { "x" : 416, "y" : 80 } ], "var" : "in3" } ] }, { "id" : "S40-f9", @@ -559,7 +704,7 @@ "at" : { "x" : 296, "y" : 224 } }, "connect" : [ - { "id" : "S40-f10", "to" : "S40-f3", "var" : "in1" } + { "id" : "S40-f10", "to" : "S40-f3", "via" : [ { "x" : 416, "y" : 224 } ], "var" : "in1" } ] }, { "id" : "S40-f11", diff --git a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java index 17ade8a..0320706 100644 --- a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java +++ b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowMixedSubProcess.java @@ -29,8 +29,10 @@ public void setupForEach() { void shouldFindAllTasks() throws Exception { var start = ProcessGraphHelper.findByElementName(process, "start"); var detectedTasks = processInspector.findAllTasks(start, UseCase.BIGPROJECT); - - var expected = Arrays.array("TaskA", "SubA-TaskA", "SubA-TaskC", "SubA-TaskB", "SubD-TaskB", "SubB-TaskA", "SubD-TaskC", "SubC-TaskA", "SubD-TaskD", "SubD-TaskA"); + + var expected = Arrays.array("TaskA", "SubC-TaskA", "SubB-TaskB", "SubA-TaskA", "SubA-TaskC", "SubA-TaskB", + "SubD-TaskB", "SubB-TaskA", "SubB-TaskC", "SubB-TaskD", "SubB-TaskE", "SubD-TaskC", "SubC-TaskB", + "SubD-TaskD", "SubD-TaskA"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java index 6bd8f56..b378717 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java @@ -2,19 +2,18 @@ import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addAllToPath; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addToPath; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.findIncomingsFromPaths; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAllStartElementOfTaskSwitchGateways; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathFrom; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathTo; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getPathByStartElements; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.replaceFirstElement; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toMap; +import static org.apache.commons.collections4.CollectionUtils.isEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import static org.apache.commons.collections4.ListUtils.union; -import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.ArrayList; import java.util.Arrays; @@ -26,9 +25,9 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.SetUtils; +import org.apache.commons.collections4.MapUtils; import com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; @@ -38,15 +37,12 @@ import com.axonivy.utils.process.inspector.internal.model.TaskParallelGroup; import ch.ivyteam.ivy.process.model.BaseElement; -import ch.ivyteam.ivy.process.model.HierarchicElement; import ch.ivyteam.ivy.process.model.NodeElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; -import ch.ivyteam.ivy.process.model.diagram.edge.DiagramEdge; -import ch.ivyteam.ivy.process.model.diagram.value.Label; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; +import ch.ivyteam.ivy.process.model.element.event.end.EmbeddedEnd; import ch.ivyteam.ivy.process.model.element.event.end.TaskEnd; import ch.ivyteam.ivy.process.model.element.event.start.EmbeddedStart; -import ch.ivyteam.ivy.process.model.element.event.start.RequestStart; import ch.ivyteam.ivy.process.model.element.gateway.Alternative; import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; @@ -106,36 +102,28 @@ private Map> findPathWithParentElement(List

getParentPathOf(ProcessElement startElement,String flowName, FindType findType, List paths) throws Exception { - ProcessElement parentElement = getParentElement(startElement); - + private List getParentPathOf(ProcessElement startElement,String flowName, FindType findType, List paths) throws Exception { List result = paths; + + EmbeddedProcessElement parentElement = processGraph.getParentElement(startElement.getElement()); if(parentElement != null) { - SubProcessGroup subProcess = new SubProcessGroup((EmbeddedProcessElement) parentElement.getElement(), paths); - - Map> parentPaths = findPath(List.of(parentElement), flowName, findType); - List subPaths = parentPaths.getOrDefault(parentElement, emptyList()); + SubProcessGroup subProcess = new SubProcessGroup(parentElement, paths); + ProcessElement parentProcessElement = new CommonElement(parentElement); + Map> parentPaths = findPath(List.of(parentProcessElement), flowName, findType); + List subPaths = parentPaths.getOrDefault(parentProcessElement, emptyList()); List fullParentPath = replaceFirstElement(subProcess, subPaths); - result = getParentPathOf(parentElement, flowName, findType, fullParentPath); + result = getParentPathOf(parentProcessElement, flowName, findType, fullParentPath); } return result; } - - private ProcessElement getParentElement(ProcessElement startElement) { - if (startElement.getElement() instanceof HierarchicElement) { - var parentElement = ((HierarchicElement) startElement.getElement()).getParent(); - if (parentElement instanceof EmbeddedProcessElement) { - return new CommonElement(parentElement); - } - } - return null; - } private Map> findPath(List froms, String flowName, FindType findType) throws Exception { Map> result = new LinkedHashMap<>(); @@ -187,8 +175,7 @@ private Map> mergePath(Map> pathBeforeIntersection) { + private TaskParallelGroup convertToTaskParallelGroup(Map> pathBeforeIntersection) { Map> pathHaveNoIntersection = emptyMap(); Map> pathHaveIntersection = pathBeforeIntersection; @@ -201,8 +188,7 @@ private TaskParallelGroup convertToTaskParallelGroup( pathHaveIntersection = pathBeforeIntersection.entrySet().stream() .filter(entry -> !startEleWithoutIntersection.contains(entry.getKey())) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, - LinkedHashMap::new)); + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new)); } List pathsWithIntersection = convertToAnalysisPaths(pathHaveIntersection); @@ -211,9 +197,9 @@ private TaskParallelGroup convertToTaskParallelGroup( internalPath.putAll(pathHaveNoIntersection); if (isNotEmpty(pathsWithIntersection)) { - ProcessElement key = pathBeforeIntersection.keySet().stream() - .filter(it -> !startEleWithoutIntersection.contains(it)).findFirst().get(); + .filter(it -> !startEleWithoutIntersection.contains(it)) + .findFirst().get(); internalPath.put(key, pathsWithIntersection); } @@ -277,7 +263,6 @@ private TaskParallelGroup convertToTaskParallelGroupWithInternalPath(Map> getLastIntersectionByStartElements(Map> paths) { var intersections = getAllIntersectionTaskSwitchGatewayWithStartElement(paths); @@ -291,23 +276,9 @@ private Map> getLastIntersectionByStartEleme return keepIntersections; } - private Map> getAllIntersectionTaskSwitchGatewayWithStartElement(Map> paths) { - Map> intersectNodes = new LinkedHashMap<>(); - - for (ProcessElement startElement : paths.keySet()) { - for (AnalysisPath path : paths.getOrDefault(startElement, emptyList())) { - for (ProcessElement element : path.getElements()) { - if (element.getElement() instanceof TaskSwitchGateway) { - TaskSwitchGateway taskSwitch = (TaskSwitchGateway) element.getElement(); - if (taskSwitch.getIncoming().size() > 1) { - Set startElements = intersectNodes.getOrDefault(element, emptySet()); + private Map> getAllIntersectionTaskSwitchGatewayWithStartElement(Map> source) { + Map> intersectNodes = getAllStartElementOfTaskSwitchGateways(source); - intersectNodes.put(element, SetUtils.union(startElements, Set.of(startElement))); - } - } - } - } - } return intersectNodes.entrySet().stream() .sorted(Map.Entry .>comparingByValue( @@ -324,7 +295,7 @@ private List findAnalysisPaths(ProcessElement startElement, String List path = emptyList(); ProcessElement from = startElement; // Prevent loop - if (isContains(currentPath, from)) { + if (AnalysisPathHelper.isContains(currentPath, from)) { return path; } @@ -357,9 +328,9 @@ private List findAnalysisPaths(ProcessElement startElement, String return path; } - if (from.getElement() instanceof TaskSwitchGateway) { - //In case only one out going -> it should run as normal node - if(((TaskSwitchGateway)from.getElement()).getOutgoing().size() > 1) { + if (processGraph.isTaskSwitchGateway(from.getElement())) { + //In case only one out going -> it should run as normal node + if(processGraph.isMultiIOutgoing(from.getElement())) { // Call recursion for next TasksSwitchGateway List nextPathOfTaskSwitchGateway = findAnalysisPaths(from, flowName, findType, newPath); path = addToPath(path, nextPathOfTaskSwitchGateway); @@ -403,24 +374,6 @@ private Map> findAnalysisPathForNextNode(Proces return pathOptions; } - private boolean isContains(List currentPaths, final ProcessElement from) { - boolean isContains = false; - if (from.getElement() instanceof NodeElement && from.getElement() instanceof RequestStart == false) { - NodeElement node = (NodeElement) from.getElement(); - if (node.getIncoming().size() > 0) { - SequenceFlow sequenceFlow = node.getIncoming().get(0); - List pathWithConnectToFrom = currentPaths.stream().filter(path -> { - int lastIndex = AnalysisPathHelper.getLastIndex(path); - return sequenceFlow.equals(path.getElements().get(lastIndex).getElement()); - }).toList(); - - isContains = pathWithConnectToFrom.stream().map(AnalysisPath::getElements).flatMap(List::stream) - .anyMatch(it -> it.getElement().equals(from.getElement())); - } - } - return isContains; - } - private TaskParallelGroup getTaskParallelGroup(ProcessElement from, String flowName, FindType findType, List currentPath) throws Exception { TaskParallelGroup result = new TaskParallelGroup(from.getElement()); @@ -474,7 +427,8 @@ private List getSequenceFlows(NodeElement from, String flowName, F // Always is priority check flow from flowOverrides first. if (from instanceof Alternative) { String flowIdFromOrverride = this.processFlowOverrides.get(from.getPid().getRawPid()); - flow = from.getOutgoing().stream().filter(out -> out.getPid().getRawPid().equals(flowIdFromOrverride)) + flow = from.getOutgoing().stream() + .filter(out -> out.getPid().getRawPid().equals(flowIdFromOrverride)) .findFirst(); } @@ -489,7 +443,7 @@ private List getSequenceFlows(NodeElement from, String flowName, F private Optional getSequenceFlow(NodeElement nodeElement, String flowName) throws Exception { List outs = nodeElement.getOutgoing(); - if (CollectionUtils.isEmpty(outs)) { + if (isEmpty(outs)) { return Optional.empty(); } @@ -515,7 +469,7 @@ private Optional getSequenceFlow(NodeElement nodeElement, String f } private Optional findSequenceFlowByDefaultPath(List outs) throws Exception { - List defaultPathOuts = outs.stream().filter(out -> isDefaultPath(out)).toList(); + List defaultPathOuts = outs.stream().filter(out -> processGraph.isDefaultPath(out)).toList(); if (defaultPathOuts.size() > 1) { // Throw exception throw new Exception("Have more than one out going with default path"); @@ -524,9 +478,8 @@ private Optional findSequenceFlowByDefaultPath(List } } - private Optional findSequenceFlowByFlowName(List outs, String flowName) - throws Exception { - List flowNameOuts = outs.stream().filter(out -> hasFlowName(out, flowName)).toList(); + private Optional findSequenceFlowByFlowName(List outs, String flowName) throws Exception { + List flowNameOuts = outs.stream().filter(out -> processGraph.hasFlowName(out, flowName)).toList(); if (flowNameOuts.size() > 1) { // Throw exception throw new Exception("Have more than one out going with flowname " + flowName); @@ -540,7 +493,7 @@ private boolean hasFlowNameOrEmpty(SequenceFlow sequenceFlow, String flowName) { return true; } - if (hasFlowName(sequenceFlow, flowName)) { + if (processGraph.hasFlowName(sequenceFlow, flowName)) { return true; } @@ -551,40 +504,16 @@ private boolean hasFlowNameOrEmpty(SequenceFlow sequenceFlow, String flowName) { return false; } - private boolean hasFlowName(SequenceFlow sequenceFlow, String flowName) { - String label = Optional.ofNullable(sequenceFlow) - .map(SequenceFlow::getEdge) - .map(DiagramEdge::getLabel) - .map(Label::getText) - .orElse(null); - - return isNotBlank(label) && label.contains(flowName); - } - - private boolean isDefaultPath(SequenceFlow flow) { - NodeElement sourceElement = flow.getSource(); - if (sourceElement instanceof Alternative) { - return isDefaultPath((Alternative) sourceElement, flow); - } - - return false; - } - - private boolean isDefaultPath(Alternative alternative, SequenceFlow sequenceFlow) { - String currentElementId = sequenceFlow.getPid().getFieldIds(); - List nextTargetIds = processGraph.getNextTargetIdsByCondition(alternative, EMPTY); - return nextTargetIds.contains(currentElementId); - } - private boolean isStartTaskSwitchGateway(ProcessElement element) { - return element.getElement() instanceof TaskSwitchGateway - && ((TaskSwitchGateway) element.getElement()).getOutgoing().size() > 1; + return processGraph.isTaskSwitchGateway(element.getElement()) && processGraph.isMultiIOutgoing(element.getElement()); } private boolean isJoinTaskSwitchGateway(List paths, ProcessElement from) { BaseElement baseElement = from.getElement(); boolean result = false; - if (baseElement instanceof TaskSwitchGateway && ((TaskSwitchGateway) baseElement).getIncoming().size() > 1) { + + if (processGraph.isTaskSwitchGateway(baseElement) && processGraph.isMultiIncoming(baseElement)) { + boolean hasFullInComing = haveFullInComingJoinTaskSwitchGateway(paths, from); boolean hasStartBefore = false; @@ -609,19 +538,16 @@ private boolean haveFullInComingJoinTaskSwitchGateway(List paths, BaseElement baseElement = from.getElement(); boolean hasFullInComing = false; //Make sure it is join task switch - if (baseElement instanceof TaskSwitchGateway) { - var taskSwitchGateway = (TaskSwitchGateway) baseElement; - - if (taskSwitchGateway.getIncoming().size() > 1) { - List sequenceFlowToParalletTasks = findIncomingsFromPaths(paths, from); - - NodeElement startNode = AnalysisPathHelper.getFirstNodeElement(paths); - List sequenceFlows = getSequenceFlowOf(from, startNode); - - long count = sequenceFlowToParalletTasks.stream().filter(el -> sequenceFlows.contains(el)).count(); - if (count >= sequenceFlows.size()) { - hasFullInComing = true; - } + if (processGraph.isTaskSwitchGateway(baseElement) && processGraph.isMultiIncoming(baseElement)) { + + List sequenceFlowToParalletTasks = findIncomingsFromPaths(paths, from); + + NodeElement startNode = AnalysisPathHelper.getFirstNodeElement(paths); + List sequenceFlows = processGraph.getSequenceFlowOf((NodeElement) baseElement, startNode); + + long count = sequenceFlowToParalletTasks.stream().filter(el -> sequenceFlows.contains(el)).count(); + if (count >= sequenceFlows.size()) { + hasFullInComing = true; } } return hasFullInComing; @@ -631,30 +557,6 @@ private ProcessElement getJoinTaskSwithGateWay(TaskParallelGroup taskParallelGro List elements = AnalysisPathHelper.getAllProcessElement(taskParallelGroup); return AnalysisPathHelper.getLastElement(new AnalysisPath(elements)); } - - private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow, List pathChecked) { - if(startNode == null) { - return false; - } - NodeElement node = sequenceFlow.getSource(); - if(pathChecked.contains(node)) { - return false; - } - - if(startNode.equals(node)) { - return true; - } - - // Maybe have a loop here. - List sequenceFlows = node.getIncoming(); - for (SequenceFlow flow : sequenceFlows) { - List newPathChecked = union(pathChecked, List.of(node, flow)); - if (isStartedFromOf(startNode, flow, newPathChecked)) { - return true; - } - } - return false; - } private boolean shouldStopFindTask(SubProcessGroup element, FindType findType) { List paths = element.getInternalPaths(); @@ -673,34 +575,89 @@ private boolean shouldStopFindTask(List paths, FindType findType) } return false; } - - private List getSequenceFlowOf(ProcessElement from, NodeElement startNode) { - long numberOfStarts = processGraph.countStartElement(from.getElement()); - List incomings = ((NodeElement)from.getElement()).getIncoming(); - - List sequenceFlows = emptyList(); - if(numberOfStarts == 1) { - //If there are only on start node -> just get incoming - sequenceFlows = incomings; - } else { - //TODO: Should find another solution to check - sequenceFlows = incomings.stream().filter(it -> isStartedFromOf(startNode, it, emptyList())).toList(); + + private Map> correctInternalPathByNextSequence(Map> source) { + Map> result = new LinkedHashMap<>(); + + for (Entry> entry : source.entrySet()) { + List paths = correctInternalPathByNextSequence(entry.getValue()); + result.put(entry.getKey(), paths); } - return sequenceFlows; + return result; } - private List findIncomingsFromPaths(List paths, ProcessElement from) { - List elements = AnalysisPathHelper.getAllProcessElement(paths).stream() - .map(ProcessElement::getElement) - .toList(); + private List correctInternalPathByNextSequence(List paths) { + List result = new ArrayList<>(); - List sequenceFlows = elements.stream() - .filter(SequenceFlow.class::isInstance) - .map(SequenceFlow.class::cast) - .filter(it -> it.getTarget().equals(from.getElement())) - .distinct() - .toList(); + for (AnalysisPath path : paths) { + int lengh = path.getElements().size(); + List elements = new ArrayList<>(); + for (int i = 0; i < lengh; i++) { + ProcessElement element = path.getElements().get(i); + ProcessElement nextElement = i < lengh - 1 ? path.getElements().get(i + 1) : null; + + if (element instanceof SubProcessGroup) { + SubProcessGroup subProcessGroup = (SubProcessGroup) element; + List subPaths = subProcessGroup.getInternalPaths(); + subPaths = getAnalysisPathBaseOnNextSequenceFlow(subPaths, nextElement); + + SubProcessGroup subElement = new SubProcessGroup((EmbeddedProcessElement) element.getElement(), subPaths); + elements.add(subElement); + + } else { + elements.add(element); + } + } + result.add(new AnalysisPath(elements)); + } - return sequenceFlows; + return result; + } + private List getAnalysisPathBaseOnNextSequenceFlow(List internalPaths, ProcessElement nextElement) { + if(nextElement == null) { + return internalPaths; + } + + List result = new ArrayList<>(); + BaseElement sequenceFlow = nextElement.getElement(); + if (sequenceFlow instanceof SequenceFlow) { + for (AnalysisPath path : internalPaths) { + ProcessElement lastProcessElement = AnalysisPathHelper.getLastElement(path); + BaseElement lastElement = lastProcessElement.getElement(); + + if (processGraph.isTaskEnd(lastElement)) { + result.add(path); + continue; + } + + if (processGraph.isTaskSwitchGateway(lastElement)) { + Map> validInternalPaths = new LinkedHashMap<>(); + ((TaskParallelGroup) lastProcessElement).getInternalPaths().forEach((key, value) -> { + var validPaths = getAnalysisPathBaseOnNextSequenceFlow(value, nextElement); + if (isNotEmpty(validPaths)) { + validInternalPaths.put(key, validPaths); + } + }); + + int size = path.getElements().size(); + List newPath = path.getElements().stream().limit(size - 1).collect(Collectors.toList()); + if (MapUtils.isNotEmpty(validInternalPaths)) { + TaskParallelGroup taskGroup = new TaskParallelGroup(lastElement); + taskGroup.setInternalPaths(validInternalPaths); + newPath.add(taskGroup); + } + result.add(new AnalysisPath(newPath)); + continue; + } + + if (processGraph.isEmbeddedEnd(lastElement)) { + if (processGraph.isConnectOuterOf((EmbeddedEnd) lastElement, (SequenceFlow) sequenceFlow)) { + result.add(path); + } + continue; + } + } + } + return result; } } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java index 4086640..1c0d4af 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java @@ -1,7 +1,10 @@ package com.axonivy.utils.process.inspector.internal; +import static java.util.Collections.emptyList; +import static org.apache.commons.collections4.ListUtils.union; import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.ArrayList; import java.util.Arrays; @@ -17,8 +20,11 @@ import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.EmbeddedProcess; +import ch.ivyteam.ivy.process.model.HierarchicElement; import ch.ivyteam.ivy.process.model.NodeElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; +import ch.ivyteam.ivy.process.model.diagram.edge.DiagramEdge; +import ch.ivyteam.ivy.process.model.diagram.value.Label; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; import ch.ivyteam.ivy.process.model.element.SingleTaskCreator; import ch.ivyteam.ivy.process.model.element.TaskAndCaseModifier; @@ -76,31 +82,6 @@ public EmbeddedStart findStartElementOfProcess(SequenceFlow sequenceFlow, Embedd return start; } - public long countStartElement(BaseElement element) { - if(element instanceof NodeElement) { - BaseElement parent = ((NodeElement) element).getParent(); - if(parent instanceof EmbeddedProcessElement) { - return findStartElementOfProcess((EmbeddedProcessElement)parent).stream() - .map(EmbeddedStart.class::cast) - .filter(it -> it.getOutgoing().size() > 0) - .count(); - } else { - return ((NodeElement) element).getRootProcess().getElements().stream() - .filter(RequestStart.class::isInstance) - .count(); - } - } - return 0; - } - - public List getNextTargetIdsByCondition(Alternative alternative, String condition) { - IvyScriptExpression script = IvyScriptExpression.script(defaultString(condition)); - List nextTargetIds = alternative.getConditions().conditions().entrySet().stream() - .filter(entry -> script.equals(entry.getValue())).map(Entry::getKey).toList(); - - return nextTargetIds; - } - public TaskConfig getStartTaskConfig(SequenceFlow sequenceFlow) { BaseElement taskSwitchGateway = sequenceFlow.getSource(); TaskConfig taskConfig = null; @@ -192,6 +173,115 @@ public SequenceFlow getFirstIncoming(BaseElement element) { } return null; } + + public List getOutgoing(BaseElement element) { + if(element instanceof NodeElement) { + return ((NodeElement) element).getOutgoing(); + } + return emptyList(); + } + + public boolean isMultiIncoming(BaseElement element) { + if(element instanceof NodeElement) { + return ((NodeElement) element).getIncoming().size() > 1; + } + return false; + } + + public boolean isMultiIOutgoing(BaseElement element) { + if(element instanceof NodeElement) { + return ((NodeElement) element).getOutgoing().size() > 1; + } + return false; + } + + public List getSequenceFlowOf(NodeElement from, NodeElement startNode) { + long numberOfStarts = countStartElement(from); + List incomings = from.getIncoming(); + + List sequenceFlows = emptyList(); + if(numberOfStarts == 1) { + //If there are only on start node -> just get incoming + sequenceFlows = incomings; + } else { + //TODO: Should find another solution to check + sequenceFlows = incomings.stream().filter(it -> isStartedFromOf(startNode, it, emptyList())).toList(); + } + return sequenceFlows; + } + + + public boolean hasFlowName(SequenceFlow sequenceFlow, String flowName) { + String label = Optional.ofNullable(sequenceFlow) + .map(SequenceFlow::getEdge) + .map(DiagramEdge::getLabel) + .map(Label::getText) + .orElse(null); + + return isNotBlank(label) && label.contains(flowName); + } + + public boolean isDefaultPath(SequenceFlow flow) { + NodeElement sourceElement = flow.getSource(); + if (sourceElement instanceof Alternative) { + return isDefaultPath((Alternative) sourceElement, flow); + } + + return false; + } + + public EmbeddedProcessElement getParentElement(BaseElement element) { + if (element instanceof HierarchicElement) { + var parentElement = ((HierarchicElement) element).getParent(); + if (parentElement instanceof EmbeddedProcessElement) { + return (EmbeddedProcessElement) parentElement; + } + } + return null; + } + + public boolean isConnectOuterOf(EmbeddedEnd end, SequenceFlow sequenceFlow) { + BaseElement outerSequenceFlow = ((EmbeddedEnd) end).getConnectedOuterSequenceFlow(); + return outerSequenceFlow.getPid().equals(sequenceFlow.getPid()); + } + + private boolean isDefaultPath(Alternative alternative, SequenceFlow sequenceFlow) { + String currentElementId = sequenceFlow.getPid().getFieldIds(); + List nextTargetIds = getNextTargetIdsByCondition(alternative, EMPTY); + return nextTargetIds.contains(currentElementId); + } + + private List getNextTargetIdsByCondition(Alternative alternative, String condition) { + IvyScriptExpression script = IvyScriptExpression.script(defaultString(condition)); + List nextTargetIds = alternative.getConditions().conditions().entrySet().stream() + .filter(entry -> script.equals(entry.getValue())).map(Entry::getKey).toList(); + + return nextTargetIds; + } + + private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow, List pathChecked) { + if(startNode == null) { + return false; + } + NodeElement node = sequenceFlow.getSource(); + if(pathChecked.contains(node)) { + return false; + } + + if(startNode.equals(node)) { + return true; + } + + // Maybe have a loop here. + List sequenceFlows = node.getIncoming(); + for (SequenceFlow flow : sequenceFlows) { + List newPathChecked = union(pathChecked, List.of(node, flow)); + if (isStartedFromOf(startNode, flow, newPathChecked)) { + return true; + } + } + return false; + } private boolean containPrefixs(String content, String... prefix) { return List.of(prefix).stream().allMatch(it -> content.contains(it)); @@ -205,4 +295,21 @@ private List findStartElementOfProcess(EmbeddedProcessElement emb .toList(); return starts; } + + private long countStartElement(BaseElement element) { + if(element instanceof NodeElement) { + BaseElement parent = ((NodeElement) element).getParent(); + if(parent instanceof EmbeddedProcessElement) { + return findStartElementOfProcess((EmbeddedProcessElement)parent).stream() + .map(EmbeddedStart.class::cast) + .filter(it -> it.getOutgoing().size() > 0) + .count(); + } else { + return ((NodeElement) element).getRootProcess().getElements().stream() + .filter(RequestStart.class::isInstance) + .count(); + } + } + return 0; + } } \ No newline at end of file diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java index 12554b0..920f05e 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java @@ -14,17 +14,17 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.stream.Collector; -import java.util.stream.Collectors; import org.apache.commons.collections4.ListUtils; -import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.map.HashedMap; import org.apache.commons.lang3.StringUtils; import com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; import com.axonivy.utils.process.inspector.internal.model.CommonElement; +import com.axonivy.utils.process.inspector.internal.model.DetectedEmbeddedEnd; +import com.axonivy.utils.process.inspector.internal.model.DetectedPath; +import com.axonivy.utils.process.inspector.internal.model.DetectedTaskEnd; import com.axonivy.utils.process.inspector.internal.model.ProcessElement; import com.axonivy.utils.process.inspector.internal.model.SubProcessGroup; import com.axonivy.utils.process.inspector.internal.model.TaskParallelGroup; @@ -33,7 +33,6 @@ import com.axonivy.utils.process.inspector.model.DetectedTask; import com.axonivy.utils.process.inspector.model.ElementTask; -import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.HierarchicElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; @@ -74,41 +73,48 @@ protected WorkflowPath setIsEnableDescribeAlternative(boolean isEnableDescribeAl return this; } - protected List findAllTasks(Map timeUntilStarts, Enum useCase) - throws Exception { + protected List findAllTasks(Map timeUntilStarts, Enum useCase) throws Exception { Map> paths = this.pathFinder(timeUntilStarts, null).findAllTask(); List detectedTasks = convertToDetectedElements(paths, useCase, timeUntilStarts); return detectedTasks; } - protected List findTaskOnPath(Map timeUntilStarts, Enum useCase, - String flowName) throws Exception { + protected List findTaskOnPath(Map timeUntilStarts, Enum useCase, String flowName) throws Exception { Map> paths = this.pathFinder(timeUntilStarts, flowName).findTaskOnPath(); List detectedTasks = convertToDetectedElements(paths, useCase, timeUntilStarts); return detectedTasks; } - protected List convertToDetectedElements(Map> allPaths, Enum useCase, Map timeUntilStarts) { - List result = new ArrayList<>(); - for (Entry> path : allPaths.entrySet()) { - List elements = convertPathToDetectedElements(path.getKey(), path.getValue(), useCase, - timeUntilStarts); - result.addAll(elements); - } + private List convertToDetectedElements(Map> allPaths, Enum useCase, Map timeUntilStarts) { + List detectedPaths = convertToDetectedPaths(allPaths, useCase, timeUntilStarts); + + List result = detectedPaths.stream() + .map(DetectedPath::getElements) + .flatMap(List::stream) + .toList(); + result = keepMaxTimeUtilEndDetectedElement(result); return result; } + + private List convertToDetectedPaths(Map> allPaths, Enum useCase, Map timeUntilStarts) { + List detectedPaths = new ArrayList<>(); + for (Entry> path : allPaths.entrySet()) { + List elements = convertPathToDetectedPaths(path.getKey(), path.getValue(), useCase, timeUntilStarts); + detectedPaths.addAll(elements); + } + return detectedPaths; + } - private List convertPathToDetectedElements(ProcessElement startElement, List paths, - Enum useCase, Map timeUntilStarts) { - List> allPaths = new ArrayList<>(); + private List convertPathToDetectedPaths(ProcessElement startElement, List paths, Enum useCase, Map timeUntilStarts) { + List result = new ArrayList<>(); paths.forEach(path -> { ProcessElement correctedStartElement = correctStartElement(startElement, path, timeUntilStarts); - allPaths.add(convertPathToDetectedElements(correctedStartElement, path, useCase, timeUntilStarts)); - }); - List result = allPaths.stream().flatMap(List::stream).toList(); + DetectedPath detectedPath = convertDetectedPath(correctedStartElement, path, useCase, timeUntilStarts); + result.add(detectedPath); + }); return result; } @@ -128,18 +134,13 @@ private ProcessElement correctStartElement(ProcessElement startElement, Analysis return startElement; } - private List convertPathToDetectedElements(ProcessElement startElement, AnalysisPath path, - Enum useCase, Map timeUntilStarts) { - List result = new ArrayList<>(); + private DetectedPath convertDetectedPath(ProcessElement startElement, AnalysisPath path, Enum useCase, Map timeUntilStarts) { + List delectedElements = new ArrayList<>(); Duration timeUntilStart = timeUntilStarts.get(startElement); - Duration durationStart = timeUntilEnd(result, timeUntilStart); - - int lengh = path.getElements().size(); - - for (int i = 0; i < lengh; i++) { - ProcessElement element = path.getElements().get(i); - ProcessElement nextElement = i < lengh - 1 ? path.getElements().get(i + 1) : null; - + Duration durationStart = timeUntilEnd(delectedElements, timeUntilStart); + + for (ProcessElement element : path.getElements()) { + //It is start node -> Ignored if (processGraph.isRequestStart(element.getElement())) { continue; @@ -156,7 +157,7 @@ private List convertPathToDetectedElements(ProcessElement start if (this.isEnableDescribeAlternative && processGraph.isAlternative(element.getElement())) { var detectedAlternative = createDetectedAlternative(element); if (detectedAlternative != null) { - result.add(detectedAlternative); + delectedElements.add(detectedAlternative); } } @@ -166,8 +167,8 @@ private List convertPathToDetectedElements(ProcessElement start if (processGraph.isHandledAsTask(subProcessCall)) { var detectedSubProcessCall = createDetectedTaskFromSubProcessCall(subProcessCall, useCase, durationStart); if (detectedSubProcessCall != null) { - result.add(detectedSubProcessCall); - durationStart = timeUntilEnd(result, timeUntilStart); + delectedElements.add(detectedSubProcessCall); + durationStart = timeUntilEnd(delectedElements, timeUntilStart); } } } @@ -177,29 +178,21 @@ private List convertPathToDetectedElements(ProcessElement start SingleTaskCreator singleTask = (SingleTaskCreator) element.getElement(); var detectedTask = createDetectedTask(singleTask, useCase, durationStart); if (detectedTask != null) { - result.add(detectedTask); - durationStart = timeUntilEnd(result, timeUntilStart); + delectedElements.add(detectedTask); + durationStart = timeUntilEnd(delectedElements, timeUntilStart); } continue; } // It is EmbeddedProcessElement if (element instanceof SubProcessGroup) { - List subPaths = ((SubProcessGroup) element).getInternalPaths(); - //List subPaths = getAnalysisPathBaseOnNextSequenceFlow(internalPaths, nextElement.getElement()); - List allTaskFromSubPath = new ArrayList<>(); - for(AnalysisPath subPath : subPaths) { - ProcessElement startSubElement = subPath.getElements().get(0); - var startedForSubProcess = new HashedMap<>(timeUntilStarts); - startedForSubProcess.put(startSubElement, durationStart); - - List subResult = convertPathToDetectedElements(startSubElement, subPath, useCase , startedForSubProcess); - allTaskFromSubPath.addAll(subResult); - } + SubProcessGroup subProcessGroup = (SubProcessGroup) element; + var detectedPathsFromSubProcess = convertSubProcessGroupToDetectedPaths(subProcessGroup, useCase, durationStart, timeUntilStarts); - if(isNotEmpty(allTaskFromSubPath)) { - result.addAll(allTaskFromSubPath); - durationStart = getMaxDurationUntilEnd(allTaskFromSubPath); + if(isNotEmpty(detectedPathsFromSubProcess)) { + var detectedElementsFromSubProcess = detectedPathsFromSubProcess.stream().flatMap(it -> it.getElements().stream()).toList(); + delectedElements.addAll(detectedElementsFromSubProcess); + durationStart = getMaxDurationUntilEndFromSubProcessGroup(detectedPathsFromSubProcess); } continue; @@ -209,10 +202,12 @@ private List convertPathToDetectedElements(ProcessElement start var startedForGroup = element.getElement() == null ? timeUntilStarts : Map.of(element, durationStart); TaskParallelGroup group = (TaskParallelGroup) element; - var tasks = convertTaskParallelGroupToDetectedElement(group, useCase, startedForGroup); - if (isNotEmpty(tasks)) { - result.addAll(tasks); - durationStart = getMaxDurationUntilEnd(tasks); + var detectedPaths = convertTaskParallelGroupToDetectedPaths(group, useCase, startedForGroup); + if (isNotEmpty(detectedPaths)) { + var detectedElementsTaskParallelGroup = detectedPaths.stream().flatMap(it -> it.getElements().stream()).toList(); + durationStart = getMaxDurationUntilEnd(detectedElementsTaskParallelGroup); + + delectedElements.addAll(detectedElementsTaskParallelGroup); } continue; } @@ -221,31 +216,53 @@ private List convertPathToDetectedElements(ProcessElement start SequenceFlow sequenceFlow = (SequenceFlow) element.getElement(); if (sequenceFlow.getSource() instanceof TaskSwitchGateway) { var startTask = createStartTaskFromTaskSwitchGateway(sequenceFlow, durationStart, useCase); - if (startTask != null) { - result.add(startTask); - durationStart = timeUntilEnd(result, timeUntilStart); + if (startTask != null) { + delectedElements.add(startTask); + durationStart = timeUntilEnd(delectedElements, timeUntilStart); } continue; } } + + if (element instanceof CommonElement && processGraph.isEmbeddedEnd(element.getElement())) { + EmbeddedEnd embeddedEnd = (EmbeddedEnd) element.getElement(); + var detectedEmbeddedEnd = createDetectedEmbeddedEnd(embeddedEnd, durationStart); + delectedElements.add(detectedEmbeddedEnd); + } + + if (element instanceof CommonElement && processGraph.isTaskEnd(element.getElement())) { + TaskEnd taskEnd = (TaskEnd) element.getElement(); + var detectedTaskEnd = createDetectedTaskEnd(taskEnd, durationStart); + delectedElements.add(detectedTaskEnd); + } } - return result; + return new DetectedPath(delectedElements); + } + + private List convertSubProcessGroupToDetectedPaths(SubProcessGroup group, Enum useCase, Duration durationStart, Map timeUntilStartAts) { + List subPaths = group.getInternalPaths(); + List detectedPaths = new ArrayList<>(); + for (AnalysisPath subPath : subPaths) { + ProcessElement startSubElement = subPath.getElements().get(0); + var startedForSubProcess = new HashedMap<>(timeUntilStartAts); + startedForSubProcess.put(startSubElement, durationStart); + + DetectedPath subResult = convertDetectedPath(startSubElement, subPath, useCase, startedForSubProcess); + detectedPaths.add(subResult); + } + return detectedPaths; } - private List convertTaskParallelGroupToDetectedElement(TaskParallelGroup group, Enum useCase, - Map timeUntilStartAts) { - List tasksWithTaskEnd = convertTaskParallelGroupToDetectedElement(group, useCase, - timeUntilStartAts, true); - List tasksInternal = convertTaskParallelGroupToDetectedElement(group, useCase, - timeUntilStartAts, false); + private List convertTaskParallelGroupToDetectedPaths(TaskParallelGroup group, Enum useCase, Map timeUntilStartAts) { + List tasksWithTaskEnd = convertTaskParallelGroupToDetectedPath(group, useCase, timeUntilStartAts, true); + List tasksInternal = convertTaskParallelGroupToDetectedPath(group, useCase, timeUntilStartAts, false); return ListUtils.union(tasksWithTaskEnd, tasksInternal); } - private List convertTaskParallelGroupToDetectedElement(TaskParallelGroup group, Enum useCase, - Map timeUntilStartAts, boolean withTaskEnd) { + private List convertTaskParallelGroupToDetectedPath(TaskParallelGroup group, Enum useCase, Map timeUntilStartAts, boolean withTaskEnd) { Map> internalPath = getInternalPath(group.getInternalPaths(), withTaskEnd); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (Entry> entry : internalPath.entrySet()) { SequenceFlow sequenceFlow = entry.getKey(); Duration startedAt = timeUntilStartAts.get(group); @@ -253,12 +270,11 @@ private List convertTaskParallelGroupToDetectedElement(TaskPara ProcessElement key = new CommonElement(sequenceFlow); Map> path = Map.of(key, entry.getValue()); Map startTimeDuration = null; - - List tasks = emptyList(); + if (group.getElement() != null) { var startTask = createStartTaskFromTaskSwitchGateway(sequenceFlow, startedAt, useCase); if (startTask != null) { - result.add(startTask); + result.add(new DetectedPath(startTask)); startedAt = ((DetectedTask) startTask).getTimeUntilEnd(); } @@ -268,15 +284,16 @@ private List convertTaskParallelGroupToDetectedElement(TaskPara startTimeDuration = timeUntilStartAts; } - tasks = convertToDetectedElements(path, useCase, startTimeDuration); - result.addAll(tasks); + List detectedPaths = convertToDetectedPaths(path, useCase, startTimeDuration); + result.addAll(detectedPaths); } return result; } private Duration timeUntilEnd(List detectedElements, Duration defaultAt) { - List detectedTasks = detectedElements.stream().filter(item -> item instanceof DetectedTask) + List detectedTasks = detectedElements.stream() + .filter(item -> item instanceof DetectedTask) .map(DetectedTask.class::cast).toList(); int size = detectedTasks.size(); Duration duration = defaultAt; @@ -290,6 +307,22 @@ private Duration timeUntilEnd(List detectedElements, Duration d return duration; } + private Duration getMaxDurationUntilEndFromSubProcessGroup(List detectedPaths) { + Duration maxDurationUntilEnd = detectedPaths.stream() + .flatMap(it -> it.getElements().stream()) + .filter(item -> item instanceof DetectedEmbeddedEnd == true) + .map(DetectedEmbeddedEnd.class::cast) + .map(DetectedEmbeddedEnd::getTimeUntilEnd) + .max(Duration::compareTo) + .orElse(null); + + if (maxDurationUntilEnd == null || maxDurationUntilEnd.isNegative()) { + maxDurationUntilEnd = Duration.ZERO; + } + + return maxDurationUntilEnd; + } + private Duration getMaxDurationUntilEnd(List detectedElements) { Duration maxDurationUntilEnd = detectedElements.stream() .filter(item -> item instanceof DetectedTask == true) @@ -321,8 +354,10 @@ private DetectedElement createStartTaskFromTaskSwitchGateway(SequenceFlow sequen private DetectedAlternative createDetectedAlternative(ProcessElement processElement) { if (processGraph.isAlternative(processElement.getElement())) { Alternative alternative = (Alternative) processElement.getElement(); - List options = alternative.getOutgoing().stream() - .map(item -> convertToDetectedElement(item)).toList(); + List options = alternative.getOutgoing() + .stream() + .map(item -> convertToDetectedElement(item)) + .toList(); String elementName = alternative.getName(); String pid = alternative.getPid().getRawPid(); DetectedAlternative result = new DetectedAlternative(pid, elementName, options); @@ -375,6 +410,24 @@ private DetectedElement createDetectedTask(TaskAndCaseModifier task, TaskConfig return detectedTask; } + + private DetectedElement createDetectedEmbeddedEnd(EmbeddedEnd element, Duration timeUntilStartAt) { + String pid = element.getPid().getRawPid(); + String connectedOuterSequenceFlowPid = element.getConnectedOuterSequenceFlow().getPid().getRawPid(); + String elementName = element.getName(); + + var detectedEmbeddedEnd = new DetectedEmbeddedEnd(pid, elementName, connectedOuterSequenceFlowPid, timeUntilStartAt); + return detectedEmbeddedEnd; + } + + private DetectedElement createDetectedTaskEnd(TaskEnd element, Duration timeUntilStartAt) { + String pid = element.getPid().getRawPid(); + String elementName = element.getName(); + + var detectedTaskEnd = new DetectedTaskEnd(pid, elementName, timeUntilStartAt); + return detectedTaskEnd; + } + private List keepMaxTimeUtilEndDetectedElement(List detectedElements) { Map taskGroupWithMaxTimeUntilEnd = detectedElements.stream() .filter(DetectedTask.class::isInstance).map(DetectedTask.class::cast) @@ -432,21 +485,24 @@ private boolean isNotContains(List detectedElements, DetectedEl return !pids.contains(detectedElement.getPid()); } - private Map> getInternalPath(Map> internalPath, - boolean withTaskEnd) { + private Map> getInternalPath(Map> internalPath, boolean withTaskEnd) { Map> paths = new LinkedHashMap<>(); // Priority the path go to end first for (SequenceFlow sf : internalPath.keySet()) { - List analysisPaths = new ArrayList<>(); - for (AnalysisPath path : internalPath.get(sf)) { - ProcessElement last = getLast(path.getElements()); - if (withTaskEnd && last.getElement() instanceof TaskEnd) { - analysisPaths.add(path); - } else if (!withTaskEnd && last.getElement() instanceof TaskEnd == false) { - analysisPaths.add(path); - } - } + + List analysisPaths = internalPath.get(sf).stream() + .filter(it -> { + ProcessElement last = AnalysisPathHelper.getLastElement(it); + if (withTaskEnd && processGraph.isTaskEnd(last.getElement())) { + return true; + } else if (!withTaskEnd && !processGraph.isTaskEnd(last.getElement())) { + return true; + } else { + return false; + } + }).toList(); + if (isNotEmpty(analysisPaths)) { paths.put(sf, analysisPaths); } @@ -455,10 +511,6 @@ private Map> getInternalPath(Map T getLast(List elements) { - return elements.stream().reduce((first, second) -> second).orElse(null); - } - private PathFinder pathFinder(Map startAtElements, String flowName) { return new PathFinder().setProcessFlowOverrides(processFlowOverrides).setFlowName(flowName) .setStartElements(startAtElements.keySet().stream().toList()); @@ -477,50 +529,5 @@ private String getTaskName(TaskConfig taskConfig) { return defaultIfBlank(taskNameFromRawMacro, taskIdentifier); } - - private List getAnalysisPathBaseOnNextSequenceFlow(List subPaths, BaseElement sequenceFlow) { - List result = new ArrayList<>(); - - if (sequenceFlow instanceof SequenceFlow) { - for (AnalysisPath path : subPaths) { - ProcessElement lastProcessElement = AnalysisPathHelper.getLastElement(path); - BaseElement lastElement = lastProcessElement.getElement(); - - if (processGraph.isTaskEnd(lastElement)) { - result.add(path); - continue; - } - - if (processGraph.isTaskSwitchGateway(lastElement)) { - Map> validInternalPaths = new LinkedHashMap<>(); - ((TaskParallelGroup) lastProcessElement).getInternalPaths().forEach((key, value) -> { - var validPaths = getAnalysisPathBaseOnNextSequenceFlow(value, sequenceFlow); - if (isNotEmpty(validPaths)) { - validInternalPaths.put(key, validPaths); - } - }); - - int size = path.getElements().size(); - List newPath = path.getElements().stream().limit(size - 1).collect(Collectors.toList()); - if (MapUtils.isNotEmpty(validInternalPaths)) { - TaskParallelGroup taskGroup = new TaskParallelGroup(lastElement); - taskGroup.setInternalPaths(validInternalPaths); - newPath.add(taskGroup); - } - continue; - } - - if (processGraph.isEmbeddedEnd(lastElement)) { - BaseElement outerSequenceFlow = ((EmbeddedEnd) lastElement).getConnectedOuterSequenceFlow(); - if(outerSequenceFlow.getPid().equals(sequenceFlow.getPid())) { - result.add(path); - } - continue; - } - - } - } - return result; - } } \ No newline at end of file diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java index afeecdb..cb35d4a 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java @@ -1,6 +1,7 @@ package com.axonivy.utils.process.inspector.internal.helper; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import java.util.ArrayList; @@ -13,14 +14,18 @@ import java.util.Set; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.SetUtils; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; import com.axonivy.utils.process.inspector.internal.model.CommonElement; import com.axonivy.utils.process.inspector.internal.model.ProcessElement; import com.axonivy.utils.process.inspector.internal.model.TaskParallelGroup; +import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.NodeElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; +import ch.ivyteam.ivy.process.model.element.event.start.RequestStart; +import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; public class AnalysisPathHelper { public static List addToPath(List paths, List subPaths) { @@ -197,6 +202,62 @@ public static Map> getAnalysisPathTo(Map findIncomingsFromPaths(List paths, ProcessElement from) { + List elements = getAllProcessElement(paths).stream() + .map(ProcessElement::getElement) + .toList(); + + List sequenceFlows = elements.stream() + .filter(SequenceFlow.class::isInstance) + .map(SequenceFlow.class::cast) + .filter(it -> it.getTarget().equals(from.getElement())) + .distinct() + .toList(); + + return sequenceFlows; + } + + public static boolean isContains(List currentPaths, final ProcessElement from) { + boolean isContains = false; + if (from.getElement() instanceof NodeElement && from.getElement() instanceof RequestStart == false) { + NodeElement node = (NodeElement) from.getElement(); + + if (node.getIncoming().size() > 0) { + SequenceFlow sequenceFlow = node.getIncoming().get(0); + List pathWithConnectToFrom = currentPaths.stream().filter(path -> { + int lastIndex = getLastIndex(path); + return sequenceFlow.equals(path.getElements().get(lastIndex).getElement()); + }).toList(); + + isContains = pathWithConnectToFrom.stream() + .map(AnalysisPath::getElements) + .flatMap(List::stream) + .anyMatch(it -> it.getElement().equals(from.getElement())); + } + } + return isContains; + } + + public static Map> getAllStartElementOfTaskSwitchGateways(Map> source) { + Map> result = new LinkedHashMap<>(); + + for (ProcessElement startElement : source.keySet()) { + List paths = source.getOrDefault(startElement, emptyList()); + for (AnalysisPath path : paths) { + for (ProcessElement element : path.getElements()) { + if (element.getElement() instanceof TaskSwitchGateway) { + if (((TaskSwitchGateway) element.getElement()).getIncoming().size() > 1) { + Set startElements = result.getOrDefault(element, emptySet()); + + result.put(element, SetUtils.union(startElements, Set.of(startElement))); + } + } + } + } + } + + return result; + } private static List getAnalysisPathTo(List source, ProcessElement to) { List result = new ArrayList<>(); for (AnalysisPath path : source) { diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedEmbeddedEnd.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedEmbeddedEnd.java new file mode 100644 index 0000000..ba0068a --- /dev/null +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedEmbeddedEnd.java @@ -0,0 +1,33 @@ +package com.axonivy.utils.process.inspector.internal.model; + +import java.time.Duration; + +import com.axonivy.utils.process.inspector.model.DetectedElement; + +public class DetectedEmbeddedEnd extends DetectedElement{ + private String connectedOuterSequenceFlowPid; + private Duration timeUntilStart; + private Duration timeUntilEnd; + + public DetectedEmbeddedEnd(String pid, String elementName, String connectedOuterSequenceFlowPid, Duration timeUntilStart) { + super(pid, elementName); + this.connectedOuterSequenceFlowPid = connectedOuterSequenceFlowPid; + this.timeUntilStart = this.timeUntilEnd = timeUntilStart; + } + + public String getConnectedOuterSequenceFlowPid() { + return connectedOuterSequenceFlowPid; + } + + public void setConnectedOuterSequenceFlowPid(String connectedOuterSequenceFlowPid) { + this.connectedOuterSequenceFlowPid = connectedOuterSequenceFlowPid; + } + + public Duration getTimeUntilStart() { + return timeUntilStart; + } + + public Duration getTimeUntilEnd() { + return timeUntilEnd; + } +} diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedPath.java new file mode 100644 index 0000000..1a16e6c --- /dev/null +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedPath.java @@ -0,0 +1,52 @@ +package com.axonivy.utils.process.inspector.internal.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.axonivy.utils.process.inspector.model.DetectedElement; + +public class DetectedPath { + private List elements; + + public DetectedPath() { + this.elements = new ArrayList<>(); + } + + public DetectedPath(DetectedElement element) { + this.elements = List.of(element); + } + + public DetectedPath(List elements) { + this.elements = elements; + } + + public List getElements() { + return this.elements; + } + + @Override + public int hashCode() { + return Objects.hash(this.elements); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (!(other instanceof DetectedPath)) { + return false; + } + DetectedPath path = (DetectedPath) other; + return Objects.equals(path.elements, this.elements); + } + + @Override + public String toString() { + return Objects.toString(elements); + } +} diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java new file mode 100644 index 0000000..0bcd4af --- /dev/null +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java @@ -0,0 +1,16 @@ +package com.axonivy.utils.process.inspector.internal.model; + +import java.time.Duration; + +import com.axonivy.utils.process.inspector.model.DetectedElement; + +public class DetectedTaskEnd extends DetectedElement { + + private Duration timeUntilStart; + private Duration timeUntilEnd; + + public DetectedTaskEnd(String pid, String elementName, Duration timeUntilStart) { + super(pid, elementName); + this.timeUntilStart = this.timeUntilEnd = timeUntilStart; + } +} From db79b631f53158f8ca8a881e2e1942be084b0e18 Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Thu, 4 Jul 2024 16:34:42 +0700 Subject: [PATCH 4/5] TE-624: Check next element of parallel task --- .../processes/FlowParallelInOrder.p.json | 61 +++++++-- .../test/FlowParallelInOrderCaseTest.java | 6 +- .../test/FlowParallelInOrderTest.java | 2 +- .../inspector/internal/PathFinder.java | 119 ++++++++++------- .../inspector/internal/ProcessGraph.java | 9 ++ .../inspector/internal/WorkflowPath.java | 116 +++++++---------- .../internal/helper/AnalysisPathHelper.java | 120 ++++++++++++------ .../internal/model/DetectedTaskEnd.java | 9 +- .../internal/model/SubProcessGroup.java | 6 +- .../internal/model/TaskParallelGroup.java | 15 +-- 10 files changed, 270 insertions(+), 193 deletions(-) diff --git a/process-inspector-test/processes/FlowParallelInOrder.p.json b/process-inspector-test/processes/FlowParallelInOrder.p.json index ba33259..98a0925 100644 --- a/process-inspector-test/processes/FlowParallelInOrder.p.json +++ b/process-inspector-test/processes/FlowParallelInOrder.p.json @@ -12,7 +12,7 @@ "signature" : "start" }, "visual" : { - "at" : { "x" : 128, "y" : 168 } + "at" : { "x" : 128, "y" : 104 } }, "connect" : [ { "id" : "f2", "to" : "f3", "var" : "in1" } @@ -21,7 +21,7 @@ "id" : "f1", "type" : "TaskEnd", "visual" : { - "at" : { "x" : 864, "y" : 168 } + "at" : { "x" : 736, "y" : 104 } } }, { "id" : "f3", @@ -48,15 +48,22 @@ "", "APAConfig.setEstimate(2,TimeUnit.HOURS,UseCase.SMALLPROJECT);" ] + }, { + "id" : "TaskC", + "name" : "Task 1C", + "responsible" : { + "activator" : "SYSTEM" + } } ] }, "visual" : { - "at" : { "x" : 200, "y" : 168 }, + "at" : { "x" : 200, "y" : 104 }, "labelOffset" : { "x" : -8, "y" : -8 } }, "connect" : [ - { "id" : "f5", "to" : "f4", "via" : [ { "x" : 200, "y" : 112 } ], "condition" : "ivp==\"TaskA.ivp\"" }, - { "id" : "f9", "to" : "f8", "via" : [ { "x" : 200, "y" : 224 } ], "condition" : "ivp==\"TaskB.ivp\"" } + { "id" : "f5", "to" : "f4", "via" : [ { "x" : 200, "y" : 56 } ], "condition" : "ivp==\"TaskA.ivp\"" }, + { "id" : "f9", "to" : "f8", "via" : [ { "x" : 216, "y" : 144 } ], "condition" : "ivp==\"TaskB.ivp\"" }, + { "id" : "f132", "to" : "f115", "via" : [ { "x" : 200, "y" : 224 } ], "condition" : "ivp==\"TaskC.ivp\"" } ] }, { "id" : "f4", @@ -77,7 +84,7 @@ } }, "visual" : { - "at" : { "x" : 312, "y" : 112 } + "at" : { "x" : 280, "y" : 56 } }, "connect" : [ { "id" : "f7", "to" : "f6" } @@ -101,10 +108,10 @@ } }, "visual" : { - "at" : { "x" : 480, "y" : 112 } + "at" : { "x" : 432, "y" : 56 } }, "connect" : [ - { "id" : "f11", "to" : "f10", "via" : [ { "x" : 576, "y" : 112 } ], "var" : "in1" } + { "id" : "f11", "to" : "f10", "via" : [ { "x" : 520, "y" : 56 } ], "var" : "in1" } ] }, { "id" : "f8", @@ -125,10 +132,10 @@ } }, "visual" : { - "at" : { "x" : 400, "y" : 224 } + "at" : { "x" : 352, "y" : 144 } }, "connect" : [ - { "id" : "f12", "to" : "f10", "via" : [ { "x" : 576, "y" : 224 } ], "var" : "in2" } + { "id" : "f12", "to" : "f10", "via" : [ { "x" : 520, "y" : 144 } ], "var" : "in2" } ] }, { "id" : "f10", @@ -143,7 +150,7 @@ } ] }, "visual" : { - "at" : { "x" : 576, "y" : 168 }, + "at" : { "x" : 520, "y" : 104 }, "labelOffset" : { "x" : -16, "y" : 24 } }, "connect" : [ @@ -160,7 +167,7 @@ } }, "visual" : { - "at" : { "x" : 712, "y" : 168 } + "at" : { "x" : 624, "y" : 104 } }, "connect" : [ { "id" : "f15", "to" : "f1" } @@ -1093,6 +1100,36 @@ "connect" : [ { "id" : "f114", "to" : "f102", "var" : "in2" } ] + }, { + "id" : "f115", + "type" : "UserTask", + "name" : "Task E", + "config" : { + "dialog" : "com.axonivy.utils.process.inspector.test.Dummy:start()", + "task" : { + "name" : "Task E", + "code" : [ + "import com.axonivy.utils.process.inspector.test.UseCase;", + "import com.axonivy.utils.process.inspector.APAConfig;", + "import java.util.concurrent.TimeUnit;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.SMALLPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 360, "y" : 224 } + }, + "connect" : [ + { "id" : "f144", "to" : "f135" } + ] + }, { + "id" : "f135", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 552, "y" : 224 } + } } ], "layout" : { "colors" : { diff --git a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderCaseTest.java b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderCaseTest.java index 37eb04e..f8a4cd5 100644 --- a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderCaseTest.java +++ b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderCaseTest.java @@ -37,7 +37,7 @@ void shouldshouldFindAllTasksAtStart(BpmClient bpmClient) throws Exception { var detectedTasks = processInspector.findAllTasks(icase, null); - var expected = Arrays.array("Task 1A", "Task A", "Task B", "Task 1B", "Task C", "Task D"); + var expected = Arrays.array("Task E", "Task 1A", "Task A", "Task B", "Task 1B", "Task C", "Task D"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } @@ -60,7 +60,7 @@ void shouldFindTasksOnPathByCaseAtTaskBAndTaskC(BpmClient bpmClient) throws Exce ExecutionResult result = bpmClient.start().process(FLOW_PARALLEL_IN_ORDER.elementName("start")).execute(); List parallelTasks = result.workflow().activeTasks(); for (ITask task : parallelTasks) { - result = bpmClient.start().task(task).as().everybody().execute(); + result = bpmClient.start().task(task).as().systemUser().execute(); } List activeTasks = result.workflow().activeTasks(); @@ -74,7 +74,7 @@ void shouldFindTasksOnPathByCaseAtTaskBAndTaskC(BpmClient bpmClient) throws Exce var detectedTasks = processInspector.findTasksOnPath(icase, null, null); - var expected = Arrays.array("Task C", "Task B", "Task D"); + var expected = Arrays.array("Task E", "Task C", "Task B", "Task D"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); diff --git a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderTest.java b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderTest.java index 34e9c39..0196df8 100644 --- a/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderTest.java +++ b/process-inspector-test/src_test/com/axonivy/utils/process/inspector/test/FlowParallelInOrderTest.java @@ -38,7 +38,7 @@ void shouldFindAllTasksAtStart() throws Exception { List detectedTasks = processInspector.findAllTasks(start, null).stream() .map(DetectedTask.class::cast).toList(); - var expected = Arrays.array("Task 1A", "Task A", "Task B", "Task 1B", "Task C", "Task D"); + var expected = Arrays.array("Task E", "Task 1A", "Task A", "Task B", "Task 1B", "Task C", "Task D"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java index b378717..f2c1a78 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/PathFinder.java @@ -2,10 +2,13 @@ import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addAllToPath; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.addToPath; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.convertToAnalysisPath; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.findIncomingsFromPaths; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAllStartElementOfTaskSwitchGateways; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathFrom; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getAnalysisPathTo; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getInternalPath; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getLastProcessElements; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getPathByStartElements; import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.replaceFirstElement; import static java.util.Collections.emptyList; @@ -27,8 +30,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.collections4.MapUtils; - import com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; import com.axonivy.utils.process.inspector.internal.model.CommonElement; @@ -166,7 +167,7 @@ private Map> mergePath(Map fullPathWithIntersection = addToPath(List.of(new AnalysisPath(List.of(taskGroup))), subPath); + List fullPathWithIntersection = addToPath(new AnalysisPath(List.of(taskGroup)), subPath); Map> result = new LinkedHashMap<>(); result.putAll(pathNotIntersection); @@ -231,7 +232,7 @@ private List convertToAnalysisPaths(Map subPathAfterIntersection = getAnalysisPathFrom(paths, intersection); - result.addAll(addToPath(List.of(new AnalysisPath(List.of(taskGroup))), subPathAfterIntersection)); + result.addAll(addToPath(new AnalysisPath(List.of(taskGroup)), subPathAfterIntersection)); } return result; @@ -251,15 +252,14 @@ private Map> getPathHaveNoIntersection( } private TaskParallelGroup convertToTaskParallelGroupWithInternalPath(Map> internalPaths) { - TaskParallelGroup taskGroup = new TaskParallelGroup(null); - + Map> result = new LinkedHashMap<>(); internalPaths.entrySet().forEach(it -> { SequenceFlow sequenceFlow = processGraph.getFirstIncoming(it.getKey().getElement()); result.put(sequenceFlow, it.getValue()); }); - taskGroup.setInternalPaths(result); + TaskParallelGroup taskGroup = new TaskParallelGroup(null, convertToAnalysisPath(result)); return taskGroup; } @@ -332,13 +332,12 @@ private List findAnalysisPaths(ProcessElement startElement, String //In case only one out going -> it should run as normal node if(processGraph.isMultiIOutgoing(from.getElement())) { // Call recursion for next TasksSwitchGateway - List nextPathOfTaskSwitchGateway = findAnalysisPaths(from, flowName, findType, newPath); - path = addToPath(path, nextPathOfTaskSwitchGateway); + List nextPaths = findAnalysisPaths(from, flowName, findType, newPath); + path = addToPath(path, nextPaths); } } } - var newPath = addToPath(currentPath, path); //It stop finding tasks when the end node is TaskEnd for TASKS_ON_PATH case if (shouldStopFindTask(newPath, findType)) { @@ -346,54 +345,84 @@ private List findAnalysisPaths(ProcessElement startElement, String } // Call recursion for next normal node var pathOptions = findAnalysisPathForNextNode(from, flowName, findType, newPath); - path = addAllToPath(path, pathOptions); + path = addToPath(path, pathOptions); } return path; } - private Map> findAnalysisPathForNextNode(ProcessElement from, String flowName, - FindType findType, List currentPath) throws Exception { - + private List findAnalysisPathForNextNode(ProcessElement from, String flowName, FindType findType, List currentPath) throws Exception { List outs = getSequenceFlows((NodeElement) from.getElement(), flowName, findType); + if (from.getElement() instanceof Alternative && outs.isEmpty()) { String mgs = String.format("Not found path after element: \"%s\"", processGraph.getAlternativeNameId(from.getElement())); - throw new Exception(mgs); + throw new Exception(mgs); } - Map> pathOptions = new LinkedHashMap<>(); - for (SequenceFlow out : outs) { - CommonElement outElement = new CommonElement(out); - List newPath = addAllToPath(currentPath, Arrays.asList(outElement)); - - ProcessElement nextStartElement = new CommonElement(out.getTarget()); - List nextOfPath = findAnalysisPaths(nextStartElement, flowName, findType, newPath); - pathOptions.put(out, nextOfPath); - } + var paths = findAnalysisPaths(outs, flowName, findType, currentPath); - return pathOptions; + return convertToAnalysisPath(paths); } - private TaskParallelGroup getTaskParallelGroup(ProcessElement from, String flowName, FindType findType, - List currentPath) throws Exception { - TaskParallelGroup result = new TaskParallelGroup(from.getElement()); + private TaskParallelGroup getTaskParallelGroup(ProcessElement from, String flowName, FindType findType, List currentPath) throws Exception { List outs = getSequenceFlows((NodeElement) from.getElement(), flowName, findType); + + List newPath = addAllToPath(currentPath, Arrays.asList(from)); + var paths = findAnalysisPaths(outs, flowName, findType, newPath); + + List convertedPaths = convertToAnalysisPath(paths); + List lastElements = getLastProcessElements(convertedPaths); + ProcessElement joinTask = lastElements.stream() + .filter(it -> it.getElement()instanceof TaskSwitchGateway ) + .findFirst().orElse(null); + + List internalPaths = new ArrayList<>() ; + //Check join incoming is less then split task + if (joinTask != null && ((NodeElement) joinTask.getElement()).getIncoming().size() < outs.size()) { + List startPathWithoutTaskEnd = getInternalPath(convertedPaths, false).stream() + .map(AnalysisPathHelper::getFirsElement) + .map(ProcessElement::getElement) + .filter(SequenceFlow.class::isInstance) + .map(SequenceFlow.class::cast) + .distinct().toList(); + + Map> subInteralPaths = new LinkedHashMap<>(); + for(Entry> entry : paths.entrySet()) { + if(startPathWithoutTaskEnd.contains(entry.getKey())) { + subInteralPaths.put(entry.getKey(), entry.getValue()); + } else { + ProcessElement startElement = new CommonElement(entry.getKey()); + internalPaths.addAll(addToPath(new AnalysisPath(startElement), entry.getValue())); + } + } + + TaskParallelGroup subTaskGroup = new TaskParallelGroup(from.getElement(), convertToAnalysisPath(subInteralPaths)); + + List nextPaths = findAnalysisPaths(joinTask, flowName, findType, emptyList()); + var newInternalPaths = addToPath(new AnalysisPath(subTaskGroup), nextPaths); + + internalPaths.addAll(newInternalPaths); + } else { + internalPaths = convertToAnalysisPath(paths); + } + + TaskParallelGroup result = new TaskParallelGroup(from.getElement(), internalPaths); + return result; + } - Map> paths = new LinkedHashMap<>(); + private Map> findAnalysisPaths (List outs, String flowName, FindType findType, List currentPath) throws Exception { + Map> pathOptions = new LinkedHashMap<>(); for (SequenceFlow out : outs) { CommonElement outElement = new CommonElement(out); - List newPath = addAllToPath(currentPath, Arrays.asList(from, outElement)); + List newPath = addAllToPath(currentPath, Arrays.asList(outElement)); ProcessElement nextStartElement = new CommonElement(out.getTarget()); - List nextOfPath = findAnalysisPaths(nextStartElement, flowName, findType, newPath); - paths.put(out, nextOfPath); + List nextPaths = findAnalysisPaths(nextStartElement, flowName, findType, newPath); + pathOptions.put(out, nextPaths); } - - result.setInternalPaths(paths); - - return result; + return pathOptions; } - + /** * Find path on sub process */ @@ -613,6 +642,7 @@ private List correctInternalPathByNextSequence(List return result; } + private List getAnalysisPathBaseOnNextSequenceFlow(List internalPaths, ProcessElement nextElement) { if(nextElement == null) { return internalPaths; @@ -630,20 +660,19 @@ private List getAnalysisPathBaseOnNextSequenceFlow(List> validInternalPaths = new LinkedHashMap<>(); - ((TaskParallelGroup) lastProcessElement).getInternalPaths().forEach((key, value) -> { - var validPaths = getAnalysisPathBaseOnNextSequenceFlow(value, nextElement); + if (lastProcessElement instanceof TaskParallelGroup && processGraph.isTaskSwitchGateway(lastElement)) { + List validInternalPaths = new ArrayList<>(); + ((TaskParallelGroup) lastProcessElement).getInternalPaths().forEach(value -> { + var validPaths = getAnalysisPathBaseOnNextSequenceFlow(List.of(value), nextElement); if (isNotEmpty(validPaths)) { - validInternalPaths.put(key, validPaths); + validInternalPaths.addAll(validPaths); } }); int size = path.getElements().size(); List newPath = path.getElements().stream().limit(size - 1).collect(Collectors.toList()); - if (MapUtils.isNotEmpty(validInternalPaths)) { - TaskParallelGroup taskGroup = new TaskParallelGroup(lastElement); - taskGroup.setInternalPaths(validInternalPaths); + if (isNotEmpty(validInternalPaths)) { + TaskParallelGroup taskGroup = new TaskParallelGroup(lastElement, validInternalPaths); newPath.add(taskGroup); } result.add(new AnalysisPath(newPath)); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java index 1c0d4af..cf4ed27 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/ProcessGraph.java @@ -98,6 +98,15 @@ public TaskConfig getStartTaskConfig(SequenceFlow sequenceFlow) { return taskConfig; } + public boolean isSystemTask(TaskConfig task) { + if(task instanceof TaskConfig) { + String roleName = ((TaskConfig) task).getActivator().getName(); + return Role.SYSTEM.name().equals(roleName); + } + + return false; + } + public boolean isSystemTask(BaseElement task) { if (task instanceof TaskAndCaseModifier) { return ((TaskAndCaseModifier) task).getAllTaskConfigs().stream() diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java index 920f05e..91e7b82 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java @@ -1,5 +1,6 @@ package com.axonivy.utils.process.inspector.internal; +import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getInternalPath; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toMap; @@ -9,7 +10,6 @@ import java.time.Duration; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -19,7 +19,6 @@ import org.apache.commons.collections4.map.HashedMap; import org.apache.commons.lang3.StringUtils; -import com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper; import com.axonivy.utils.process.inspector.internal.model.AnalysisPath; import com.axonivy.utils.process.inspector.internal.model.CommonElement; import com.axonivy.utils.process.inspector.internal.model.DetectedEmbeddedEnd; @@ -91,8 +90,9 @@ private List convertToDetectedElements(Map detectedPaths = convertToDetectedPaths(allPaths, useCase, timeUntilStarts); List result = detectedPaths.stream() - .map(DetectedPath::getElements) + .map(DetectedPath::getElements) .flatMap(List::stream) + .filter(it -> it instanceof DetectedTask || it instanceof DetectedAlternative) .toList(); result = keepMaxTimeUtilEndDetectedElement(result); @@ -185,11 +185,11 @@ private DetectedPath convertDetectedPath(ProcessElement startElement, AnalysisPa } // It is EmbeddedProcessElement - if (element instanceof SubProcessGroup) { + if (element instanceof SubProcessGroup) { SubProcessGroup subProcessGroup = (SubProcessGroup) element; var detectedPathsFromSubProcess = convertSubProcessGroupToDetectedPaths(subProcessGroup, useCase, durationStart, timeUntilStarts); - if(isNotEmpty(detectedPathsFromSubProcess)) { + if(isNotEmpty(detectedPathsFromSubProcess)) { var detectedElementsFromSubProcess = detectedPathsFromSubProcess.stream().flatMap(it -> it.getElements().stream()).toList(); delectedElements.addAll(detectedElementsFromSubProcess); durationStart = getMaxDurationUntilEndFromSubProcessGroup(detectedPathsFromSubProcess); @@ -216,8 +216,8 @@ private DetectedPath convertDetectedPath(ProcessElement startElement, AnalysisPa SequenceFlow sequenceFlow = (SequenceFlow) element.getElement(); if (sequenceFlow.getSource() instanceof TaskSwitchGateway) { var startTask = createStartTaskFromTaskSwitchGateway(sequenceFlow, durationStart, useCase); - if (startTask != null) { - delectedElements.add(startTask); + if (startTask != null) { + delectedElements.add(startTask); durationStart = timeUntilEnd(delectedElements, timeUntilStart); } continue; @@ -227,20 +227,20 @@ private DetectedPath convertDetectedPath(ProcessElement startElement, AnalysisPa if (element instanceof CommonElement && processGraph.isEmbeddedEnd(element.getElement())) { EmbeddedEnd embeddedEnd = (EmbeddedEnd) element.getElement(); var detectedEmbeddedEnd = createDetectedEmbeddedEnd(embeddedEnd, durationStart); - delectedElements.add(detectedEmbeddedEnd); + delectedElements.add(detectedEmbeddedEnd); } if (element instanceof CommonElement && processGraph.isTaskEnd(element.getElement())) { TaskEnd taskEnd = (TaskEnd) element.getElement(); - var detectedTaskEnd = createDetectedTaskEnd(taskEnd, durationStart); - delectedElements.add(detectedTaskEnd); + var detectedTaskEnd = createDetectedTaskEnd(taskEnd); + delectedElements.add(detectedTaskEnd); } } return new DetectedPath(delectedElements); } private List convertSubProcessGroupToDetectedPaths(SubProcessGroup group, Enum useCase, Duration durationStart, Map timeUntilStartAts) { - List subPaths = group.getInternalPaths(); + List subPaths = group.getInternalPaths(); List detectedPaths = new ArrayList<>(); for (AnalysisPath subPath : subPaths) { ProcessElement startSubElement = subPath.getElements().get(0); @@ -260,32 +260,40 @@ private List convertTaskParallelGroupToDetectedPaths(TaskParallelG } private List convertTaskParallelGroupToDetectedPath(TaskParallelGroup group, Enum useCase, Map timeUntilStartAts, boolean withTaskEnd) { - Map> internalPath = getInternalPath(group.getInternalPaths(), withTaskEnd); + List internalPaths = getInternalPath(group.getInternalPaths(), withTaskEnd); - List result = new ArrayList<>(); - for (Entry> entry : internalPath.entrySet()) { - SequenceFlow sequenceFlow = entry.getKey(); + List result = new ArrayList<>(); + for (AnalysisPath path : internalPaths) { + //Map> path = Map.of(key, entry.getValue()); + + ProcessElement startElement = path.getElements().get(0); + Map startTimeDuration = new HashedMap<>(timeUntilStartAts); + List subProcessElements = null; Duration startedAt = timeUntilStartAts.get(group); - - ProcessElement key = new CommonElement(sequenceFlow); - Map> path = Map.of(key, entry.getValue()); - Map startTimeDuration = null; - if (group.getElement() != null) { - var startTask = createStartTaskFromTaskSwitchGateway(sequenceFlow, startedAt, useCase); - if (startTask != null) { - result.add(new DetectedPath(startTask)); - startedAt = ((DetectedTask) startTask).getTimeUntilEnd(); + if (startElement.getElement() instanceof SequenceFlow) { + if (group.getElement() != null) { + SequenceFlow sequenceFlow = (SequenceFlow) startElement.getElement(); + var startTask = createStartTaskFromTaskSwitchGateway(sequenceFlow, startedAt, useCase); + if (startTask != null) { + result.add(new DetectedPath(startTask)); + startedAt = ((DetectedTask) startTask).getTimeUntilEnd(); + } + startTimeDuration.put(startElement, startedAt); + } else { + startElement = path.getElements().get(1); } + subProcessElements = path.getElements().stream().skip(1).toList(); - startTimeDuration = Map.of(key, startedAt); - - } else { - startTimeDuration = timeUntilStartAts; + + } else { + startElement = path.getElements().get(0); + startTimeDuration.put(startElement, startedAt); + subProcessElements = path.getElements(); } - - List detectedPaths = convertToDetectedPaths(path, useCase, startTimeDuration); - result.addAll(detectedPaths); + + DetectedPath detectedPath = convertDetectedPath(startElement, new AnalysisPath(subProcessElements), useCase, startTimeDuration); + result.add(detectedPath); } return result; @@ -342,9 +350,9 @@ private DetectedElement createStartTaskFromTaskSwitchGateway(SequenceFlow sequen Enum useCase) { DetectedElement task = null; if (sequenceFlow.getSource() instanceof TaskSwitchGateway) { - TaskSwitchGateway taskSwitchGateway = (TaskSwitchGateway) sequenceFlow.getSource(); - if (!processGraph.isSystemTask(taskSwitchGateway)) { - TaskConfig startTask = processGraph.getStartTaskConfig(sequenceFlow); + TaskConfig startTask = processGraph.getStartTaskConfig(sequenceFlow); + if(!processGraph.isSystemTask(startTask)) { + TaskSwitchGateway taskSwitchGateway = (TaskSwitchGateway) sequenceFlow.getSource(); task = createDetectedTask(taskSwitchGateway, startTask, useCase, timeUntilStartedAt); } } @@ -414,17 +422,17 @@ private DetectedElement createDetectedTask(TaskAndCaseModifier task, TaskConfig private DetectedElement createDetectedEmbeddedEnd(EmbeddedEnd element, Duration timeUntilStartAt) { String pid = element.getPid().getRawPid(); String connectedOuterSequenceFlowPid = element.getConnectedOuterSequenceFlow().getPid().getRawPid(); - String elementName = element.getName(); + String elementName = element.getName(); var detectedEmbeddedEnd = new DetectedEmbeddedEnd(pid, elementName, connectedOuterSequenceFlowPid, timeUntilStartAt); return detectedEmbeddedEnd; } - private DetectedElement createDetectedTaskEnd(TaskEnd element, Duration timeUntilStartAt) { - String pid = element.getPid().getRawPid(); - String elementName = element.getName(); - - var detectedTaskEnd = new DetectedTaskEnd(pid, elementName, timeUntilStartAt); + private DetectedElement createDetectedTaskEnd(TaskEnd element) { + String pid = element.getPid().getRawPid(); + String elementName = element.getName(); + + var detectedTaskEnd = new DetectedTaskEnd(pid, elementName); return detectedTaskEnd; } @@ -484,33 +492,7 @@ private boolean isNotContains(List detectedElements, DetectedEl List pids = detectedElements.stream().map(DetectedElement::getPid).toList(); return !pids.contains(detectedElement.getPid()); } - - private Map> getInternalPath(Map> internalPath, boolean withTaskEnd) { - Map> paths = new LinkedHashMap<>(); - - // Priority the path go to end first - for (SequenceFlow sf : internalPath.keySet()) { - - List analysisPaths = internalPath.get(sf).stream() - .filter(it -> { - ProcessElement last = AnalysisPathHelper.getLastElement(it); - if (withTaskEnd && processGraph.isTaskEnd(last.getElement())) { - return true; - } else if (!withTaskEnd && !processGraph.isTaskEnd(last.getElement())) { - return true; - } else { - return false; - } - }).toList(); - - if (isNotEmpty(analysisPaths)) { - paths.put(sf, analysisPaths); - } - } - - return paths; - } - + private PathFinder pathFinder(Map startAtElements, String flowName) { return new PathFinder().setProcessFlowOverrides(processFlowOverrides).setFlowName(flowName) .setStartElements(startAtElements.keySet().stream().toList()); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java index cb35d4a..33fd946 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/helper/AnalysisPathHelper.java @@ -24,18 +24,23 @@ import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.NodeElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; +import ch.ivyteam.ivy.process.model.element.event.end.TaskEnd; import ch.ivyteam.ivy.process.model.element.event.start.RequestStart; import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; public class AnalysisPathHelper { + public static List addToPath(AnalysisPath path, List subPaths) { + return addToPath(List.of(path), subPaths); + } + public static List addToPath(List paths, List subPaths) { if (subPaths.isEmpty()) { return paths; } - List result = paths; + List result = new ArrayList<>(); for (AnalysisPath path : subPaths) { - result = addAllToPath(result, path.getElements()); + result.addAll(addAllToPath(paths, path.getElements())); } return result; @@ -55,30 +60,7 @@ public static List addAllToPath(List paths, List addAllToPath(List paths, - Map> pathOptions) { - List result = new ArrayList<>(); - if (pathOptions.isEmpty()) { - result.addAll(paths); - } else { - pathOptions.entrySet().forEach(it -> { - ProcessElement sequenceFlowElement = new CommonElement(it.getKey()); - if (it.getValue().isEmpty()) { - result.addAll(addAllToPath(paths, List.of(sequenceFlowElement))); - } else { - it.getValue().forEach(path -> { - List elememts = ListUtils.union(List.of(sequenceFlowElement), - path.getElements()); - - result.addAll(addAllToPath(paths, elememts)); - }); - } - }); - } - return result; - } - + public static List replaceFirstElement(ProcessElement element, List subPaths) { if (subPaths.isEmpty()) { return List.of(new AnalysisPath(List.of(element))); @@ -104,10 +86,20 @@ public static ProcessElement getLastElement(AnalysisPath path) { int size = elements.size(); return size == 0 ? null : elements.get(size - 1); } + + public static ProcessElement getFirsElement(AnalysisPath path) { + List elements = path.getElements(); + int size = elements.size(); + return size == 0 ? null : elements.get(0); + } public static NodeElement getFirstNodeElement(List paths) { - NodeElement startNode = AnalysisPathHelper.getAllProcessElement(paths).stream().map(ProcessElement::getElement) - .filter(NodeElement.class::isInstance).findFirst().map(NodeElement.class::cast).orElse(null); + NodeElement startNode = AnalysisPathHelper.getAllProcessElement(paths).stream() + .map(ProcessElement::getElement) + .filter(NodeElement.class::isInstance) + .findFirst() + .map(NodeElement.class::cast) + .orElse(null); return startNode; } @@ -122,7 +114,6 @@ public static List removeLastElementByClassType(List if (lastElement instanceof CommonElement && clazz.isInstance(lastElement.getElement())) { pathElements.remove(lastIndex); } - result.add(new AnalysisPath(pathElements)); } @@ -130,7 +121,9 @@ public static List removeLastElementByClassType(List } public static List getAllProcessElement(List paths) { - List elements = paths.stream().map(AnalysisPath::getElements).flatMap(List::stream) + List elements = paths.stream() + .map(AnalysisPath::getElements) + .flatMap(List::stream) .flatMap(it -> getAllProcessElement(it).stream()).toList(); return elements; } @@ -148,19 +141,40 @@ public static List getAllProcessElement(ProcessElement element) result.add(new CommonElement(group.getElement())); } - for (Entry> entry : group.getInternalPaths().entrySet()) { - List allProcessElement = entry.getValue().stream().map(AnalysisPath::getElements) - .flatMap(List::stream).flatMap(it -> getAllProcessElement(it).stream()).toList(); - - result.add(new CommonElement(entry.getKey())); - result.addAll(allProcessElement); - } + List allProcessElement = group.getInternalPaths().stream() + .map(AnalysisPath::getElements) + .flatMap(List::stream) + .flatMap(it -> getAllProcessElement(it).stream()) + .toList(); + result.addAll(allProcessElement); return result; } return emptyList(); } + + public static List convertToAnalysisPath(Map> interalPaths) { + List result = new ArrayList<>(); + interalPaths.entrySet().forEach(it -> { + var paths = addToPath(new AnalysisPath(new CommonElement(it.getKey())), it.getValue()); + result.addAll(paths); + }); + return result; + } + + public static List getLastProcessElements(List paths) { + List result = new ArrayList<>(); + paths.stream().map(AnalysisPathHelper::getLastElement).forEach(it -> { + if (it instanceof TaskParallelGroup) { + result.addAll(getLastProcessElements(((TaskParallelGroup) it).getInternalPaths())); + } else { + result.add(it); + } + }); + + return result.stream().distinct().toList(); + } public static > Map getPathByStartElements(Map source, Set keys) { Map result = new LinkedHashMap<>(); @@ -186,14 +200,13 @@ public static List getAnalysisPathFrom(Map(result); - + return new ArrayList<>(result); } public static Map> getAnalysisPathTo(Map> source, ProcessElement to) { - Map> pathBeforeIntersection = new LinkedHashMap<>(); + Map> pathBeforeIntersection = new LinkedHashMap<>(); for (Entry> entry : source.entrySet()) { - List beforeTo = getAnalysisPathTo(entry.getValue(), to); + List beforeTo = subAnalysisPathTo(entry.getValue(), to); if (isNotEmpty(beforeTo)) { pathBeforeIntersection.put(entry.getKey(), beforeTo); } @@ -258,7 +271,30 @@ public static Map> getAllStartElementOfTaskS return result; } - private static List getAnalysisPathTo(List source, ProcessElement to) { + + public static List getInternalPath(List internalPaths, boolean withTaskEnd) { + List paths = new ArrayList<>(); + + // Priority the path go to end first + List analysisPaths = internalPaths.stream().filter(it -> { + ProcessElement last = AnalysisPathHelper.getLastElement(it); + if (withTaskEnd && last.getElement() instanceof TaskEnd == true) { + return true; + } else if (!withTaskEnd && last.getElement() instanceof TaskEnd == false) { + return true; + } else { + return false; + } + }).toList(); + + if (isNotEmpty(analysisPaths)) { + paths.addAll(analysisPaths); + } + + return paths; + } + + private static List subAnalysisPathTo(List source, ProcessElement to) { List result = new ArrayList<>(); for (AnalysisPath path : source) { int index = path.getElements().indexOf(to); diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java index 0bcd4af..3bbcc69 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/DetectedTaskEnd.java @@ -1,16 +1,9 @@ package com.axonivy.utils.process.inspector.internal.model; -import java.time.Duration; - import com.axonivy.utils.process.inspector.model.DetectedElement; public class DetectedTaskEnd extends DetectedElement { - - private Duration timeUntilStart; - private Duration timeUntilEnd; - - public DetectedTaskEnd(String pid, String elementName, Duration timeUntilStart) { + public DetectedTaskEnd(String pid, String elementName) { super(pid, elementName); - this.timeUntilStart = this.timeUntilEnd = timeUntilStart; } } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/SubProcessGroup.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/SubProcessGroup.java index 6890bfe..a59065a 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/SubProcessGroup.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/SubProcessGroup.java @@ -13,12 +13,8 @@ public class SubProcessGroup implements ProcessElement { private EmbeddedProcessElement element; List internalPaths; - public SubProcessGroup(EmbeddedProcessElement element) { - this.element = element; - } - public SubProcessGroup(EmbeddedProcessElement element, List internalPaths) { - this(element); + this.element = element; this.internalPaths = internalPaths; } diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/TaskParallelGroup.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/TaskParallelGroup.java index a484946..cfc9ed5 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/TaskParallelGroup.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/model/TaskParallelGroup.java @@ -3,29 +3,24 @@ import static org.apache.commons.lang3.StringUtils.EMPTY; import java.util.List; -import java.util.Map; import java.util.Objects; import ch.ivyteam.ivy.process.model.BaseElement; -import ch.ivyteam.ivy.process.model.connector.SequenceFlow; import ch.ivyteam.ivy.process.model.value.PID; public class TaskParallelGroup implements ProcessElement { private BaseElement element; - private Map> internalPaths; + private List internalPaths; - public TaskParallelGroup(BaseElement element) { + public TaskParallelGroup(BaseElement element, List internalPaths) { this.element = element; + this.internalPaths = internalPaths; } - - public Map> getInternalPaths() { + + public List getInternalPaths() { return internalPaths; } - public void setInternalPaths(Map> internalPaths) { - this.internalPaths = internalPaths; - } - @Override public int hashCode() { return Objects.hash(this.element, this.internalPaths); From 3b5aa5aed44d27176cd11a65a73bbfafc4afe275 Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Fri, 5 Jul 2024 21:42:59 +0700 Subject: [PATCH 5/5] TE-624: Check null for embedded end connected outer --- .../utils/process/inspector/internal/WorkflowPath.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java index 91e7b82..5469df8 100644 --- a/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java +++ b/process-inspector/src/com/axonivy/utils/process/inspector/internal/WorkflowPath.java @@ -3,6 +3,7 @@ import static com.axonivy.utils.process.inspector.internal.helper.AnalysisPathHelper.getInternalPath; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toMap; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.lang3.StringUtils.EMPTY; @@ -418,11 +419,11 @@ private DetectedElement createDetectedTask(TaskAndCaseModifier task, TaskConfig return detectedTask; } - private DetectedElement createDetectedEmbeddedEnd(EmbeddedEnd element, Duration timeUntilStartAt) { String pid = element.getPid().getRawPid(); - String connectedOuterSequenceFlowPid = element.getConnectedOuterSequenceFlow().getPid().getRawPid(); - String elementName = element.getName(); + String elementName = element.getName(); + String connectedOuterSequenceFlowPid = ofNullable(element.getConnectedOuterSequenceFlow()) + .map(it -> it.getPid().getRawPid()).orElse(null); var detectedEmbeddedEnd = new DetectedEmbeddedEnd(pid, elementName, connectedOuterSequenceFlowPid, timeUntilStartAt); return detectedEmbeddedEnd;