diff --git a/process-analyzer-test/processes/FlowSubprocess.p.json b/process-analyzer-test/processes/FlowSubprocess.p.json index eed919c..2105bf1 100644 --- a/process-analyzer-test/processes/FlowSubprocess.p.json +++ b/process-analyzer-test/processes/FlowSubprocess.p.json @@ -328,7 +328,7 @@ "at" : { "x" : 96, "y" : 440 } }, "connect" : [ - { "id" : "f18", "to" : "f32", "var" : "in1" } + { "id" : "f18", "to" : "S70" } ] }, { "id" : "S40", @@ -686,30 +686,6 @@ "visual" : { "at" : { "x" : 808, "y" : 440 } } - }, { - "id" : "f32", - "type" : "UserTask", - "name" : "TaskA4", - "config" : { - "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", - "task" : { - "name" : "TaskA4", - "code" : [ - "import com.axonivy.utils.process.analyzer.APAConfig;", - "import com.axonivy.utils.process.analyzer.test.UseCase;", - "import java.util.concurrent.TimeUnit;", - "", - "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);", - "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.SMALLPROJECT);" - ] - } - }, - "visual" : { - "at" : { "x" : 224, "y" : 440 } - }, - "connect" : [ - { "id" : "f33", "to" : "f17", "var" : "in1" } - ] }, { "id" : "S60", "type" : "EmbeddedProcess", @@ -762,5 +738,169 @@ "connect" : [ { "id" : "f25", "to" : "f27", "var" : "in2" } ] - } ] + }, { + "id" : "S70", + "type" : "EmbeddedProcess", + "name" : "Sub Process 0", + "elements" : [ { + "id" : "S70-g0", + "type" : "EmbeddedStart", + "visual" : { + "at" : { "x" : 64, "y" : 256 } + }, + "parentConnector" : "f18", + "connect" : [ + { "id" : "S70-f0", "to" : "S70-f1" } + ] + }, { + "id" : "S70-g1", + "type" : "EmbeddedEnd", + "visual" : { + "at" : { "x" : 592, "y" : 256 } + }, + "parentConnector" : "f29" + }, { + "id" : "S70-f1", + "type" : "UserTask", + "name" : "Sub0-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "Sub0-TaskA", + "code" : [ + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "import java.util.concurrent.TimeUnit;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.SMALLPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 184, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-f2", "to" : "S70-f3" } + ] + }, { + "id" : "S70-f3", + "type" : "Alternative", + "config" : { + "conditions" : { + "S70-f4" : "true", + "S70-f7" : "" + } + }, + "visual" : { + "at" : { "x" : 320, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-f7", "to" : "S70-f22" }, + { "id" : "S70-f4", "to" : "S70-S10", "label" : { + "name" : "{happy}" + } } + ] + }, { + "id" : "S70-f22", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 320, "y" : 352 }, + "color" : "End" + } + }, { + "id" : "S70-S10", + "type" : "EmbeddedProcess", + "name" : "Sub Process 0-0", + "elements" : [ { + "id" : "S70-S10-g0", + "type" : "EmbeddedStart", + "visual" : { + "at" : { "x" : 64, "y" : 256 } + }, + "parentConnector" : "S70-f4", + "connect" : [ + { "id" : "S70-S10-f0", "to" : "S70-S10-f1" } + ] + }, { + "id" : "S70-S10-g1", + "type" : "EmbeddedEnd", + "visual" : { + "at" : { "x" : 560, "y" : 256 } + }, + "parentConnector" : "S70-f8" + }, { + "id" : "S70-S10-f1", + "type" : "UserTask", + "name" : "Sub00-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "Sub00-TaskA" + } + }, + "visual" : { + "at" : { "x" : 192, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-S10-f2", "to" : "S70-S10-f3" } + ] + }, { + "id" : "S70-S10-f3", + "type" : "Alternative", + "config" : { + "conditions" : { + "S70-S10-f4" : "true" + } + }, + "visual" : { + "at" : { "x" : 320, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-S10-f4", "to" : "S70-S10-f5" }, + { "id" : "S70-S10-f7", "to" : "S70-S10-f22" } + ] + }, { + "id" : "S70-S10-f5", + "type" : "UserTask", + "name" : "Sub00-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "Sub00-TaskB" + } + }, + "visual" : { + "at" : { "x" : 432, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-S10-f6", "to" : "S70-S10-g1" } + ] + }, { + "id" : "S70-S10-f22", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 320, "y" : 328 }, + "color" : "End" + } + } ], + "visual" : { + "at" : { "x" : 448, "y" : 256 } + }, + "connect" : [ + { "id" : "S70-f8", "to" : "S70-g1" } + ] + } ], + "visual" : { + "at" : { "x" : 224, "y" : 440 } + }, + "connect" : [ + { "id" : "f29", "to" : "f17", "var" : "in1" } + ] + } ], + "layout" : { + "colors" : { + "End" : "#ef0606" + } + } } \ No newline at end of file diff --git a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessTest.java b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessTest.java index 98bb33b..f39d82a 100644 --- a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessTest.java +++ b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessTest.java @@ -51,12 +51,23 @@ void shouldFindAllTasksAtStart4() throws Exception { var start4 = ProcessGraphHelper.findByElementName(process, "start4"); var detectedTasks = processAnalyzer.findAllTasks(start4, null); - var expected = Arrays.array("TaskA4", "Sub1-TaskA", "Sub2-TaskE", "Sub2-TaskB", "Sub2-TaskC", "Sub3-TaskA", "Sub2-TaskA", "Sub2-TaskD"); + var expected = Arrays.array("Sub0-TaskA", "Sub00-TaskA", "Sub00-TaskB", "Sub1-TaskA", "Sub2-TaskE", "Sub2-TaskB", "Sub2-TaskC", "Sub3-TaskA", "Sub2-TaskA", "Sub2-TaskD"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } + @Test + void shouldFindAllTasksAtStart4OnEndPath() throws Exception { + var start4 = ProcessGraphHelper.findByElementName(process, "start4"); + var detectedTasks = processAnalyzer.findTasksOnPath(start4, null, "happy"); + + var expected = Arrays.array("Sub0-TaskA", "Sub00-TaskA"); + var taskNames = getTaskNames(detectedTasks); + + assertArrayEquals(expected, taskNames); + } + @Test void shouldFindTaskParentNames() throws Exception { var start = ProcessGraphHelper.findByElementName(process, "start"); diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/PathFinder.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/PathFinder.java index 344fe5f..399b77e 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/PathFinder.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/PathFinder.java @@ -41,7 +41,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.SingleTaskCreator; -import ch.ivyteam.ivy.process.model.element.TaskAndCaseModifier; +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.Alternative; import ch.ivyteam.ivy.process.model.element.gateway.TaskSwitchGateway; @@ -56,7 +56,7 @@ private enum FindType { private List froms; private String flowName; private Map processFlowOverrides = emptyMap(); - + public PathFinder() { this.processGraph = new ProcessGraph(); } @@ -133,8 +133,7 @@ private ProcessElement getParentElement(ProcessElement startElement) { return null; } - private Map> findPath(List froms, String flowName, - FindType findType) throws Exception { + private Map> findPath(List froms, String flowName, FindType findType) throws Exception { Map> result = new LinkedHashMap<>(); for (ProcessElement from : froms) { List path = findAnalysisPaths(from, flowName, findType, emptyList()); @@ -147,10 +146,10 @@ private Map> findPath(List fr } // Find again from intersection task - List subPath = findAnalysisPaths(new CommonElement(intersectionTask.getElement()), flowName, - findType, emptyList()); + ProcessElement startElement = new CommonElement(intersectionTask.getElement()); + Map> subPaths = findPath(List.of(startElement), flowName, findType); - Map> fullPath = mergePath(result, intersectionTask, subPath); + Map> fullPath = mergePath(result, intersectionTask, subPaths.getOrDefault(startElement, emptyList())); return fullPath; } @@ -270,7 +269,8 @@ private Map> convertToInternalPathForTaskParall Map> internalPath) { Map> result = new LinkedHashMap<>(); internalPath.entrySet().forEach(it -> { - result.put(((TaskAndCaseModifier) it.getKey().getElement()).getIncoming().get(0), it.getValue()); + NodeElement element = (NodeElement) it.getKey().getElement(); + result.put(element.getIncoming().get(0), it.getValue()); }); return result; } @@ -386,8 +386,8 @@ private List findAnalysisPaths(ProcessElement startElement, String if (from.getElement() instanceof NodeElement) { if (from.getElement() instanceof EmbeddedProcessElement) { - SubProcessGroup subProcessGroup = findPathOfSubProcess(from, flowName, findType, currentPath); - path = AnalysisPathHelper.removeLastElementByClassType(path, EmbeddedProcessElement.class); + SubProcessGroup subProcessGroup = findPathOfSubProcess(from, flowName, findType, currentPath); + path = AnalysisPathHelper.removeLastElementByClassType(path, EmbeddedProcessElement.class); path = addAllToPath(path, List.of(subProcessGroup)); } @@ -421,14 +421,18 @@ private List findAnalysisPaths(ProcessElement startElement, String } // Call recursion for next normal node - var pathOptions = findAnalysisPathForNextNode(from, flowName, findType, currentPath); + var newPath = addToPath(currentPath, path); + if (shouldStopFindTask(newPath, findType)) { + return path; + } + var pathOptions = findAnalysisPathForNextNode(from, flowName, findType, newPath); path = addAllToPath(path, pathOptions); } return path; } - private Map> findAnalysisPathForNextNode(ProcessElement from, String flowName, + private Map> findAnalysisPathForNextNode(ProcessElement from, String flowName, FindType findType, List currentPath) throws Exception { List outs = getSequenceFlows((NodeElement) from.getElement(), flowName, findType); @@ -440,7 +444,7 @@ private Map> findAnalysisPathForNextNode(Proces 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); @@ -500,7 +504,6 @@ private SubProcessGroup findPathOfSubProcess(ProcessElement subProcessElement, S .map(ProcessElement::getElement) .map(SequenceFlow.class::cast).orElse(null); - //TODO: Which subprocess with more than one EmbeddedStart, how to handle it? BaseElement start = processGraph.findStartElementOfProcess((SequenceFlow)lastElement, processElement); List path = findAnalysisPaths(new CommonElement(start), flowName, findType, emptyList()); @@ -641,7 +644,6 @@ private boolean isJoinTaskSwitchGateway(List paths, ProcessElement } result = hasStartBefore && !hasFullInComing; - } return result; } @@ -691,8 +693,6 @@ private ProcessElement getJoinTaskSwithGateWay(TaskParallelGroup taskParallelGro return size > 0 ? elements.get(size - 1) : null; } - - private List getSequenceFlowOfTaskSwitchGateway(TaskSwitchGateway taskSwitchGateway, NodeElement startNode) { List sequenceFlows = taskSwitchGateway.getIncoming(); @@ -717,4 +717,22 @@ private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow } return false; } + + private boolean shouldStopFindTask(SubProcessGroup element, FindType findType) { + List paths = element.getInternalPaths(); + return shouldStopFindTask(paths, findType); + } + + private boolean shouldStopFindTask(List paths, FindType findType) { + + if (FindType.TASKS_ON_PATH.equals(findType) && isNotEmpty(paths)) { + ProcessElement lastElement = AnalysisPathHelper.getLastElement(paths.get(0)); + if (lastElement.getElement() instanceof TaskEnd) { + return true; + } else if (lastElement instanceof SubProcessGroup) { + return shouldStopFindTask((SubProcessGroup) lastElement, findType); + } + } + return false; + } } diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/helper/AnalysisPathHelper.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/helper/AnalysisPathHelper.java index 1fd1297..264d781 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/helper/AnalysisPathHelper.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/helper/AnalysisPathHelper.java @@ -89,6 +89,12 @@ public static int getLastIndex(AnalysisPath path) { 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 List removeLastElementByClassType(List paths , Class clazz) { List result = new ArrayList<>();