Skip to content

Commit

Permalink
Merge pull request #26 from axonivy-market/TE-612-fix-loop-error
Browse files Browse the repository at this point in the history
TE-612: Fix loop with SubProcessCall
  • Loading branch information
trungmaihova authored Jun 18, 2024
2 parents 98a945d + 22cfb71 commit 4bfadcb
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FlowTriggerCallData #class
com.axonivy.utils.estimator.test #namespace
63 changes: 60 additions & 3 deletions process-analyzer-test/processes/FlowSubprocess.p.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"findTasksOnPath( start.ivp ) => TaskA, TaskB"
],
"visual" : {
"at" : { "x" : 824, "y" : 167 },
"at" : { "x" : 968, "y" : 63 },
"size" : { "width" : 476, "height" : 97 }
}
}, {
Expand Down Expand Up @@ -191,7 +191,7 @@
"size" : { "width" : 128, "height" : 60 }
},
"connect" : [
{ "id" : "f9", "to" : "f8" }
{ "id" : "f9", "to" : "S80" }
]
}, {
"id" : "f6",
Expand All @@ -210,7 +210,7 @@
"id" : "f8",
"type" : "TaskEnd",
"visual" : {
"at" : { "x" : 488, "y" : 192 }
"at" : { "x" : 728, "y" : 192 }
}
}, {
"id" : "f10",
Expand Down Expand Up @@ -969,6 +969,63 @@
"connect" : [
{ "id" : "f29", "to" : "f17", "var" : "in1" }
]
}, {
"id" : "S80",
"type" : "EmbeddedProcess",
"name" : "Sub2",
"elements" : [ {
"id" : "S80-g0",
"type" : "EmbeddedStart",
"visual" : {
"at" : { "x" : 64, "y" : 256 }
},
"parentConnector" : "f9",
"connect" : [
{ "id" : "S80-f0", "to" : "S80-f3" }
]
}, {
"id" : "S80-g1",
"type" : "EmbeddedEnd",
"visual" : {
"at" : { "x" : 520, "y" : 256 }
},
"parentConnector" : "f32"
}, {
"id" : "S80-f1",
"type" : "UserTask",
"name" : "Sub2-TaskA",
"config" : {
"dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()",
"task" : {
"name" : "Sub2-TaskA"
}
},
"visual" : {
"at" : { "x" : 368, "y" : 256 }
},
"connect" : [
{ "id" : "S80-f2", "to" : "S80-g1" }
]
}, {
"id" : "S80-f3",
"type" : "TriggerCall",
"name" : "Sub2-TriggerA",
"config" : {
"processCall" : "FlowTriggerCall:start()"
},
"visual" : {
"at" : { "x" : 192, "y" : 256 }
},
"connect" : [
{ "id" : "S80-f4", "to" : "S80-f1" }
]
} ],
"visual" : {
"at" : { "x" : 512, "y" : 192 }
},
"connect" : [
{ "id" : "f32", "to" : "f8" }
]
} ],
"layout" : {
"colors" : {
Expand Down
44 changes: 44 additions & 0 deletions process-analyzer-test/processes/FlowTriggerCall.p.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"$schema" : "https://json-schema.axonivy.com/process/11.2.2/process.json",
"id" : "19024F53FFE1E44F",
"config" : {
"data" : "com.axonivy.utils.estimator.test.FlowTriggerCallData"
},
"elements" : [ {
"id" : "f0",
"type" : "RequestStart",
"name" : "start",
"config" : {
"signature" : "start",
"triggerable" : true
},
"visual" : {
"at" : { "x" : 96, "y" : 64 }
},
"connect" : [
{ "id" : "f2", "to" : "f3" }
]
}, {
"id" : "f1",
"type" : "TaskEnd",
"visual" : {
"at" : { "x" : 352, "y" : 64 }
}
}, {
"id" : "f3",
"type" : "UserTask",
"name" : "Trigger-TaskA",
"config" : {
"dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()",
"task" : {
"name" : "Trigger-TaskA"
}
},
"visual" : {
"at" : { "x" : 224, "y" : 64 }
},
"connect" : [
{ "id" : "f4", "to" : "f1" }
]
} ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void shouldFindTasksOnPathAtStart() throws Exception {
var start = ProcessGraphHelper.findByElementName(process, "start");
var detectedTasks = processAnalyzer.findTasksOnPath(start, UseCase.BIGPROJECT, "internal");

var expected = Arrays.array("Task A", "Task B", "Task2B", "Task G", "Task2A", "Task H");
var expected = Arrays.array("Task A", "Task B", "Task2A", "Task H", "Task2B", "Task G");
var taskNames = (getTaskNames(detectedTasks));
assertArrayEquals(expected, taskNames);
}
Expand All @@ -120,7 +120,7 @@ void shouldCalculateEstimateDuratioUseCaseBIGPROJECTAtTaskBAndTaskC() throws Exc
var taskC = ProcessGraphHelper.findByElementName(process, "Task C");
Duration duration = processAnalyzer.calculateWorstCaseDuration(List.of(taskB, taskC), UseCase.BIGPROJECT);

assertEquals(Duration.ofHours(18), duration);
assertEquals(Duration.ofHours(15), duration);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,15 @@ void shouldFindTaskParentNames() throws Exception {
}

@Test
void shouldFindSubProcessTest() throws Exception {
void shouldFindSubProcessTestAtStart2() throws Exception {
var start2 = ProcessGraphHelper.findByElementName(process, "start2");
var detectedTasks = processAnalyzer.findAllTasks(start2, UseCase.BIGPROJECT);

var expected = Arrays.array("Task sub", "Sub2-TaskA");
var taskNames = getTaskNames(detectedTasks);
assertArrayEquals(expected, taskNames);

var detectedTask = (DetectedTask) detectedTasks.get(0);

assertEquals(1, detectedTasks.size());
assertEquals("18DE58E0441486DF-f5", detectedTask.getPid());
assertEquals("Custom info", detectedTask.getCustomInfo());
assertEquals("FlowSubProcessCall", detectedTask.getElementName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toMap;
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;
Expand Down Expand Up @@ -40,7 +41,6 @@
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.event.end.TaskEnd;
import ch.ivyteam.ivy.process.model.element.event.start.RequestStart;
import ch.ivyteam.ivy.process.model.element.gateway.Alternative;
Expand Down Expand Up @@ -110,7 +110,7 @@ private List<AnalysisPath> getParentPathOf(ProcessElement startElement,String fl

List<AnalysisPath> result = paths;
if(parentElement != null) {
SubProcessGroup subProcess = new SubProcessGroup(parentElement.getElement(), paths);
SubProcessGroup subProcess = new SubProcessGroup((EmbeddedProcessElement) parentElement.getElement(), paths);

Map<ProcessElement, List<AnalysisPath>> parentPaths = findPath(List.of(parentElement), flowName, findType);
List<AnalysisPath> subPaths = parentPaths.getOrDefault(parentElement, emptyList());
Expand Down Expand Up @@ -458,8 +458,8 @@ private Map<SequenceFlow, List<AnalysisPath>> findAnalysisPathForNextNode(Proces

private boolean isContains(List<AnalysisPath> currentPaths, final ProcessElement from) {
boolean isContains = false;
if (from.getElement() instanceof SingleTaskCreator && from.getElement() instanceof RequestStart == false) {
SingleTaskCreator node = (SingleTaskCreator) from.getElement();
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<AnalysisPath> pathWithConnectToFrom = currentPaths.stream().filter(path -> {
Expand Down Expand Up @@ -605,8 +605,12 @@ private boolean hasFlowNameOrEmpty(SequenceFlow sequenceFlow, String flowName) {
}

private boolean hasFlowName(SequenceFlow sequenceFlow, String flowName) {
String label = Optional.ofNullable(sequenceFlow).map(SequenceFlow::getEdge).map(DiagramEdge::getLabel)
.map(Label::getText).orElse(null);
String label = Optional.ofNullable(sequenceFlow)
.map(SequenceFlow::getEdge)
.map(DiagramEdge::getLabel)
.map(Label::getText)
.orElse(null);

return isNotBlank(label) && label.contains(flowName);
}

Expand Down Expand Up @@ -654,31 +658,19 @@ private boolean haveFullInComingJoinTaskSwitchGateway(List<AnalysisPath> paths,
if(from == null) {
return false;
}

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<BaseElement> elements = AnalysisPathHelper.getAllProcessElement(paths).stream()
.map(ProcessElement::getElement)
.toList();

if (taskSwitchGateway.getIncoming().size() > 1) {
List<SequenceFlow> sequenceFlowToParalletTasks = findIncomingsFromPaths(paths, from);

NodeElement startNode = AnalysisPathHelper.getFirstNodeElement(paths);
List<SequenceFlow> sequenceFlows = getSequenceFlowOf(from, startNode);

List<BaseElement> sequenceFlowToParalletTasks = elements.stream()
.filter(SequenceFlow.class::isInstance)
.filter(it -> ((SequenceFlow)it).getTarget() instanceof TaskSwitchGateway)
.distinct()
.toList();

NodeElement firstNode = elements.stream()
.filter(NodeElement.class::isInstance)
.findFirst()
.map(NodeElement.class::cast)
.orElse(null);
//TODO: Should find another solution to check
List<SequenceFlow> sequenceFlows = getSequenceFlowOfTaskSwitchGateway(taskSwitchGateway, firstNode);
long count = sequenceFlowToParalletTasks.stream().filter(el -> sequenceFlows.contains(el)).count();
if (count >= sequenceFlows.size()) {
hasFullInComing = true;
Expand All @@ -690,33 +682,30 @@ private boolean haveFullInComingJoinTaskSwitchGateway(List<AnalysisPath> paths,

private ProcessElement getJoinTaskSwithGateWay(TaskParallelGroup taskParallelGroup) {
List<ProcessElement> elements = AnalysisPathHelper.getAllProcessElement(taskParallelGroup);

int size = elements.size();
return size > 0 ? elements.get(size - 1) : null;
return AnalysisPathHelper.getLastElement(new AnalysisPath(elements));
}

private List<SequenceFlow> getSequenceFlowOfTaskSwitchGateway(TaskSwitchGateway taskSwitchGateway, NodeElement startNode) {
List<SequenceFlow> sequenceFlows = taskSwitchGateway.getIncoming();

return sequenceFlows.stream().filter(it -> isStartedFromOf(startNode, it)).toList();
}

private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow) {
private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow, List<BaseElement> 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<SequenceFlow> sequenceFlows = node.getIncoming();
for (SequenceFlow flow : sequenceFlows) {
if (isStartedFromOf(startNode, flow)) {
List<BaseElement> newPathChecked = union(pathChecked, List.of(node, flow));
if (isStartedFromOf(startNode, flow, newPathChecked)) {
return true;
}
}
}
return false;
}

Expand All @@ -737,4 +726,34 @@ private boolean shouldStopFindTask(List<AnalysisPath> paths, FindType findType)
}
return false;
}

private List<SequenceFlow> getSequenceFlowOf(ProcessElement from, NodeElement startNode) {
long numberOfStarts = processGraph.countStartElement(from.getElement());
List<SequenceFlow> incomings = ((NodeElement)from.getElement()).getIncoming();

List<SequenceFlow> 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;
}

private List<SequenceFlow> findIncomingsFromPaths(List<AnalysisPath> paths, ProcessElement from) {
List<BaseElement> elements = AnalysisPathHelper.getAllProcessElement(paths).stream()
.map(ProcessElement::getElement)
.toList();

List<SequenceFlow> sequenceFlows = elements.stream()
.filter(SequenceFlow.class::isInstance)
.map(SequenceFlow.class::cast)
.filter(it -> it.getTarget().equals(from.getElement()))
.distinct()
.toList();

return sequenceFlows;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,11 @@ private List<BaseElement> getStartElements(ICase icase) {
}

private List<ITask> getCaseITasks(ICase icase) {
List<ITask> tasks = icase.tasks().all().stream().filter(task -> OPEN_TASK_STATES.contains(task.getState()))
List<ITask> tasks = icase.tasks().all().stream()
.filter(task -> OPEN_TASK_STATES.contains(task.getState()))
.filter(it -> it.getCase().uuid().equals(icase.uuid()))
.toList();

return tasks;
}

Expand Down
Loading

0 comments on commit 4bfadcb

Please sign in to comment.