diff --git a/process-analyzer-demo/src/com/axonivy/utils/process/analyzer/demo/ProcessAnalyzerBean.java b/process-analyzer-demo/src/com/axonivy/utils/process/analyzer/demo/ProcessAnalyzerBean.java index cf66159c..3bcac57b 100644 --- a/process-analyzer-demo/src/com/axonivy/utils/process/analyzer/demo/ProcessAnalyzerBean.java +++ b/process-analyzer-demo/src/com/axonivy/utils/process/analyzer/demo/ProcessAnalyzerBean.java @@ -11,9 +11,11 @@ import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; import javax.faces.event.AjaxBehaviorEvent; import org.primefaces.component.selectoneradio.SelectOneRadio; +import org.primefaces.model.FilterMeta; import com.axonivy.utils.process.analyzer.AdvancedProcessAnalyzer; import com.axonivy.utils.process.analyzer.demo.constant.FindType; @@ -23,6 +25,7 @@ import com.axonivy.utils.process.analyzer.internal.ProcessAnalyzer; import com.axonivy.utils.process.analyzer.model.DetectedAlternative; import com.axonivy.utils.process.analyzer.model.DetectedElement; +import com.axonivy.utils.process.analyzer.model.DetectedTask; import ch.ivyteam.ivy.environment.Ivy; import ch.ivyteam.ivy.process.model.BaseElement; @@ -45,7 +48,9 @@ public class ProcessAnalyzerBean { private List analyzers = new ArrayList<>(); private List processes = emptyList(); - + + private List filterBy; + private Analyzer selectedAnalyzer = null; AdvancedProcessAnalyzer processAnalyzer; @@ -73,7 +78,19 @@ public Analyzer getSelectedAnalyzer() { public void setSelectedAnalyzer(Analyzer selectedAnalyzer) { this.selectedAnalyzer = selectedAnalyzer; } + + public List getFilterBy() { + return filterBy; + } + + public void setFilterBy(List filterBy) { + this.filterBy = filterBy; + } + @PostConstruct + public void init() { + filterBy = new ArrayList<>(); + } public List getAllTaskModifier() { this.processAnalyzer = new ProcessAnalyzer(); return getElementOfProcess(this.selectedAnalyzer.getProcess()).stream() @@ -196,6 +213,17 @@ public String getShortPid(String pid) { return pid.substring(index + 1); } + public List getParentElementNames(){ + if(selectedAnalyzer != null) { + return selectedAnalyzer.getTasks().stream() + .filter(DetectedTask.class::isInstance) + .map(DetectedTask.class::cast) + .map(DetectedTask::getDisplayParentElementNames) + .distinct() + .toList(); + } + return emptyList(); + } private AdvancedProcessAnalyzer updateProcessAnalyzer(Analyzer analyzer) { Map flowOverrides = getProcessFlowOverride(analyzer); processAnalyzer.disableDescribeAlternativeElements(); diff --git a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/AnalyzerDetail.xhtml b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/AnalyzerDetail.xhtml index f639364c..5120aa45 100644 --- a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/AnalyzerDetail.xhtml +++ b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/AnalyzerDetail.xhtml @@ -45,7 +45,8 @@ + filterBy="#{data.processAnalyzerBean.filterBy}" + styleClass="ui-datatable-sm ui-datatable-gridlines ui-datatable-header-filter"> @@ -56,12 +57,17 @@ title="#{task.pid}" /> - + - - + + @@ -69,7 +75,20 @@ value="#{data.processAnalyzerBean.getDisplayDuration(task.estimatedDuration)}" /> - + + + + + + + + + diff --git a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/resources/analyzer-detail.css b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/resources/analyzer-detail.css index 64ebfda8..f0770dc0 100644 --- a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/resources/analyzer-detail.css +++ b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerDetail/resources/analyzer-detail.css @@ -3,4 +3,18 @@ .ui-datatable-data .text-overflow { white-space: nowrap; text-overflow: ellipsis; -} \ No newline at end of file +} +.ui-datatable-header-filter .ui-selectonemenu { + width: 100%; + min-width: 100% !important; +} + +.ui-datatable-header-filter .ui-selectonemenu-label { + white-space: nowrap; + text-overflow: ellipsis; +} + +table thead tr { + vertical-align: text-top; +} + \ No newline at end of file diff --git a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerSelection/AnalyzerSelection.xhtml b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerSelection/AnalyzerSelection.xhtml index ece7a525..8c6bc561 100644 --- a/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerSelection/AnalyzerSelection.xhtml +++ b/process-analyzer-demo/src_hd/com/axonivy/utils/process/analyzer/demo/component/AnalyzerSelection/AnalyzerSelection.xhtml @@ -13,15 +13,16 @@ - + + styleClass="customPanelGrid ui-grid-selection" contentStyleClass="ui-fluid"> + value="#{data.processAnalyzerBean.selectedAnalyzer.process}" + filter="true" filterMatchMode="contains"> + value="#{data.processAnalyzerBean.selectedAnalyzer.startElement}" + filter="true" filterMatchMode="contains"> + value="#{data.processAnalyzerBean.selectedAnalyzer.findType}" + filter="true" filterMatchMode="contains"> @@ -55,7 +58,8 @@ + value="#{data.processAnalyzerBean.selectedAnalyzer.useCase}" + filter="true" filterMatchMode="contains"> useCase) throws Exception + + /** + * This method can be used to calculate expected duration from a starting point + * using a named flow or default flow. For parallel segments of the process, it + * will still use the “critical path” (same logic like worst case duration). + * + * @param startElement - Element where we start traversing the process + * @param useCase - Use case that should be used to read duration values. + * Durations will be set to 0 in case not provided. + * @return + * @throws Exception + */ + public Duration calculateDurationOfPath(BaseElement startElement, Enum useCase, String flowName) throws Exception; ``` ### Example diff --git a/process-analyzer-product/product.json b/process-analyzer-product/product.json index 5f537e93..c9a078c0 100644 --- a/process-analyzer-product/product.json +++ b/process-analyzer-product/product.json @@ -10,6 +10,12 @@ "artifactId": "process-analyzer-demo", "version": "${version}", "type": "iar" + }, + { + "groupId": "com.axonivy.utils.process.analyzer", + "artifactId": "process-analyzer-test", + "version": "${version}", + "type": "iar" } ], "repositories": [ diff --git a/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowMixedSubProcessData.ivyClass b/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowMixedSubProcessData.ivyClass new file mode 100644 index 00000000..71e50664 --- /dev/null +++ b/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowMixedSubProcessData.ivyClass @@ -0,0 +1,2 @@ +FlowMixedSubProcessData #class +com.axonivy.utils.estimator.test #namespace diff --git a/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowTriggerCallData.ivyClass b/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowTriggerCallData.ivyClass new file mode 100644 index 00000000..3d7735c1 --- /dev/null +++ b/process-analyzer-test/dataclasses/com/axonivy/utils/estimator/test/FlowTriggerCallData.ivyClass @@ -0,0 +1,2 @@ +FlowTriggerCallData #class +com.axonivy.utils.estimator.test #namespace diff --git a/process-analyzer-test/processes/FlowMixedSubProcess.p.json b/process-analyzer-test/processes/FlowMixedSubProcess.p.json new file mode 100644 index 00000000..3642601d --- /dev/null +++ b/process-analyzer-test/processes/FlowMixedSubProcess.p.json @@ -0,0 +1,617 @@ +{ + "$schema" : "https://json-schema.axonivy.com/process/11.2.2/process.json", + "id" : "1902A31FF5BCFF03", + "config" : { + "data" : "com.axonivy.utils.estimator.test.FlowMixedSubProcessData" + }, + "elements" : [ { + "id" : "f0", + "type" : "RequestStart", + "name" : "start", + "config" : { + "signature" : "start" + }, + "visual" : { + "at" : { "x" : 32, "y" : 128 } + }, + "connect" : [ + { "id" : "f2", "to" : "f14", "var" : "in1" } + ] + }, { + "id" : "f3", + "type" : "TaskSwitchGateway", + "name" : "split1", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + }, { + "id" : "TaskB", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 288, "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" : "S10", + "type" : "EmbeddedProcess", + "name" : "SubA", + "elements" : [ { + "id" : "S10-g0", + "type" : "EmbeddedStart", + "name" : "from split2", + "visual" : { + "at" : { "x" : 104, "y" : 264 }, + "labelOffset" : { "x" : 11, "y" : 27 } + }, + "parentConnector" : "f5", + "connect" : [ + { "id" : "S10-f0", "to" : "S10-f1" } + ] + }, { + "id" : "S10-g1", + "type" : "EmbeddedEnd", + "name" : "to SubD", + "visual" : { + "at" : { "x" : 560, "y" : 264 }, + "labelOffset" : { "x" : 11, "y" : 35 } + }, + "parentConnector" : "f12" + }, { + "id" : "S10-f1", + "type" : "UserTask", + "name" : "SubA-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubA-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 240, "y" : 264 } + }, + "connect" : [ + { "id" : "S10-f2", "to" : "S10-f6" } + ] + }, { + "id" : "S10-g2", + "type" : "EmbeddedEnd", + "name" : "to SubB", + "visual" : { + "at" : { "x" : 560, "y" : 360 }, + "labelOffset" : { "x" : 27, "y" : 35 } + }, + "parentConnector" : "f16" + }, { + "id" : "S10-f3", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + }, { + "id" : "TaskB", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 440, "y" : 264 } + }, + "connect" : [ + { "id" : "S10-f4", "to" : "S10-g1", "condition" : "ivp==\"TaskA.ivp\"" }, + { "id" : "S10-f5", "to" : "S10-f12", "condition" : "ivp==\"TaskB.ivp\"" } + ] + }, { + "id" : "S10-f6", + "type" : "Alternative", + "config" : { + "conditions" : { + "S10-f7" : "true" + } + }, + "visual" : { + "at" : { "x" : 368, "y" : 264 } + }, + "connect" : [ + { "id" : "S10-f7", "to" : "S10-f3", "var" : "in1" }, + { "id" : "S10-f9", "to" : "S10-f8" } + ] + }, { + "id" : "S10-f8", + "type" : "UserTask", + "name" : "SubA-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubA-TaskB", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(6,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 368, "y" : 168 } + }, + "connect" : [ + { "id" : "S10-f11", "to" : "S10-f10" } + ] + }, { + "id" : "S10-f10", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 544, "y" : 168 } + } + }, { + "id" : "S10-f12", + "type" : "UserTask", + "name" : "SubA-TaskC", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubA-TaskC", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 440, "y" : 360 } + }, + "connect" : [ + { "id" : "S10-f13", "to" : "S10-g2" } + ] + } ], + "visual" : { + "at" : { "x" : 536, "y" : 80 } + }, + "connect" : [ + { "id" : "f12", "to" : "S40", "via" : [ { "x" : 784, "y" : 80 } ] }, + { "id" : "f16", "to" : "S20" } + ] + }, { + "id" : "f4", + "type" : "TaskSwitchGateway", + "name" : "split2", + "config" : { + "tasks" : [ { + "id" : "TaskB", + "responsible" : { + "activator" : "SYSTEM" + } + }, { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 384, "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" : "S20", + "type" : "EmbeddedProcess", + "name" : "SubB", + "elements" : [ { + "id" : "S20-g0", + "type" : "EmbeddedStart", + "name" : "from SubB", + "visual" : { + "at" : { "x" : 192, "y" : 168 } + }, + "parentConnector" : "f6", + "connect" : [ + { "id" : "S20-f0", "to" : "S20-f7", "var" : "in1" } + ] + }, { + "id" : "S20-g1", + "type" : "EmbeddedEnd", + "name" : "to SubD", + "visual" : { + "at" : { "x" : 632, "y" : 72 }, + "labelOffset" : { "x" : 3, "y" : 43 } + }, + "parentConnector" : "f11" + }, { + "id" : "S20-f1", + "type" : "UserTask", + "name" : "SubB-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubB-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.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 }, + "labelOffset" : { "x" : 3, "y" : 27 } + }, + "parentConnector" : "f13" + }, { + "id" : "S20-f3", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + }, { + "id" : "TaskB", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 552, "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-g3", + "type" : "EmbeddedStart", + "name" : "from SubA", + "visual" : { + "at" : { "x" : 208, "y" : 56 } + }, + "parentConnector" : "f16", + "connect" : [ + { "id" : "S20-f6", "to" : "S20-f7", "var" : "in2" } + ] + }, { + "id" : "S20-f7", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 328, "y" : 120 } + }, + "connect" : [ + { "id" : "S20-f8", "to" : "S20-f1", "condition" : "ivp==\"TaskA.ivp\"" } + ] + } ], + "visual" : { + "at" : { "x" : 536, "y" : 200 } + }, + "connect" : [ + { "id" : "f11", "to" : "S40" }, + { "id" : "f13", "to" : "S30" } + ] + }, { + "id" : "S30", + "type" : "EmbeddedProcess", + "name" : "SubC", + "elements" : [ { + "id" : "S30-g0", + "type" : "EmbeddedStart", + "name" : "from split1", + "visual" : { + "at" : { "x" : 224, "y" : 64 } + }, + "parentConnector" : "f7", + "connect" : [ + { "id" : "S30-f0", "to" : "S30-f4", "var" : "in1" } + ] + }, { + "id" : "S30-g1", + "type" : "EmbeddedEnd", + "name" : "to SubD", + "visual" : { + "at" : { "x" : 672, "y" : 64 }, + "labelOffset" : { "x" : 27, "y" : 43 } + }, + "parentConnector" : "f10" + }, { + "id" : "S30-f1", + "type" : "UserTask", + "name" : "SubC-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubC-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.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 } + }, + "parentConnector" : "f13", + "connect" : [ + { "id" : "S30-f3", "to" : "S30-f4", "var" : "in2" } + ] + }, { + "id" : "S30-f4", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 320, "y" : 64 } + }, + "connect" : [ + { "id" : "S30-f5", "to" : "S30-f1", "condition" : "ivp==\"TaskA.ivp\"" } + ] + } ], + "visual" : { + "at" : { "x" : 688, "y" : 328 } + }, + "connect" : [ + { "id" : "f10", "to" : "S40" } + ] + }, { + "id" : "S40", + "type" : "EmbeddedProcess", + "name" : "SubD", + "elements" : [ { + "id" : "S40-g0", + "type" : "EmbeddedEnd", + "visual" : { + "at" : { "x" : 672, "y" : 152 } + }, + "parentConnector" : "f9" + }, { + "id" : "S40-f1", + "type" : "UserTask", + "name" : "SubD-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubD-TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 552, "y" : 152 } + }, + "connect" : [ + { "id" : "S40-f2", "to" : "S40-g0" } + ] + }, { + "id" : "S40-f3", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 432, "y" : 144 } + }, + "connect" : [ + { "id" : "S40-f4", "to" : "S40-f1", "condition" : "ivp==\"TaskA.ivp\"" } + ] + }, { + "id" : "S40-g1", + "type" : "EmbeddedStart", + "name" : "from SubC", + "visual" : { + "at" : { "x" : 168, "y" : 224 }, + "labelOffset" : { "x" : 19, "y" : 35 } + }, + "parentConnector" : "f10", + "connect" : [ + { "id" : "S40-f0", "to" : "S40-f9", "var" : "in1" } + ] + }, { + "id" : "S40-g2", + "type" : "EmbeddedStart", + "name" : "from subB", + "visual" : { + "at" : { "x" : 168, "y" : 152 } + }, + "parentConnector" : "f11", + "connect" : [ + { "id" : "S40-f5", "to" : "S40-f11", "var" : "in2" } + ] + }, { + "id" : "S40-g3", + "type" : "EmbeddedStart", + "name" : "from SubA", + "visual" : { + "at" : { "x" : 168, "y" : 80 }, + "labelOffset" : { "x" : 11, "y" : 27 } + }, + "parentConnector" : "f12", + "connect" : [ + { "id" : "S40-f6", "to" : "S40-f7", "var" : "in3" } + ] + }, { + "id" : "S40-f7", + "type" : "UserTask", + "name" : "SubD-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubD-TaskB", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(3,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 296, "y" : 80 } + }, + "connect" : [ + { "id" : "S40-f8", "to" : "S40-f3", "var" : "in3" } + ] + }, { + "id" : "S40-f9", + "type" : "UserTask", + "name" : "SubD-TaskD", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "call" : { + "map" : { }, + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + }, + "task" : { + "name" : "SubD-TaskB" + } + }, + "visual" : { + "at" : { "x" : 296, "y" : 224 } + }, + "connect" : [ + { "id" : "S40-f10", "to" : "S40-f3", "var" : "in1" } + ] + }, { + "id" : "S40-f11", + "type" : "UserTask", + "name" : "SubD-TaskC", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubD-TaskC", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(4,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 296, "y" : 152 } + }, + "connect" : [ + { "id" : "S40-f12", "to" : "S40-f3", "var" : "in2" } + ] + } ], + "visual" : { + "at" : { "x" : 784, "y" : 200 } + }, + "connect" : [ + { "id" : "f9", "to" : "f8" } + ] + }, { + "id" : "f8", + "type" : "TaskEnd", + "visual" : { + "at" : { "x" : 920, "y" : 200 } + } + }, { + "id" : "f14", + "type" : "UserTask", + "name" : "TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "TaskA", + "code" : [ + "import java.util.concurrent.TimeUnit;", + "import com.axonivy.utils.process.analyzer.APAConfig;", + "import com.axonivy.utils.process.analyzer.test.UseCase;", + "", + "APAConfig.setEstimate(5,TimeUnit.HOURS,UseCase.BIGPROJECT);" + ] + } + }, + "visual" : { + "at" : { "x" : 160, "y" : 128 } + }, + "connect" : [ + { "id" : "f15", "to" : "f3", "var" : "in1" } + ] + } ] +} \ No newline at end of file diff --git a/process-analyzer-test/processes/FlowSubprocess.p.json b/process-analyzer-test/processes/FlowSubprocess.p.json index eed919cd..2583e7d3 100644 --- a/process-analyzer-test/processes/FlowSubprocess.p.json +++ b/process-analyzer-test/processes/FlowSubprocess.p.json @@ -41,7 +41,7 @@ "id" : "S10-g1", "type" : "EmbeddedEnd", "visual" : { - "at" : { "x" : 576, "y" : 256 } + "at" : { "x" : 448, "y" : 256 } }, "parentConnector" : "f3" }, { @@ -90,10 +90,23 @@ ] } ], "visual" : { - "at" : { "x" : 312, "y" : 256 } + "at" : { "x" : 208, "y" : 256 } + }, + "connect" : [ + { "id" : "S10-f1", "to" : "S10-f2" } + ] + }, { + "id" : "S10-f2", + "type" : "WaitEvent", + "config" : { + "javaClass" : "com.axonivy.utils.process.analyzer.test.WaitTaskBean", + "eventId" : "com.axonivy.utils.process.analyzer.test.WaitTaskBean.createEventIdentifierForTask()" + }, + "visual" : { + "at" : { "x" : 352, "y" : 256 } }, "connect" : [ - { "id" : "S10-f1", "to" : "S10-g1" } + { "id" : "S10-f3", "to" : "S10-g1" } ] } ], "visual" : { @@ -163,7 +176,7 @@ "findTasksOnPath( start.ivp ) => TaskA, TaskB" ], "visual" : { - "at" : { "x" : 824, "y" : 167 }, + "at" : { "x" : 968, "y" : 63 }, "size" : { "width" : 476, "height" : 97 } } }, { @@ -191,7 +204,7 @@ "size" : { "width" : 128, "height" : 60 } }, "connect" : [ - { "id" : "f9", "to" : "f8" } + { "id" : "f9", "to" : "S80" } ] }, { "id" : "f6", @@ -210,7 +223,7 @@ "id" : "f8", "type" : "TaskEnd", "visual" : { - "at" : { "x" : 488, "y" : 192 } + "at" : { "x" : 728, "y" : 192 } } }, { "id" : "f10", @@ -237,13 +250,13 @@ }, "parentConnector" : "f11", "connect" : [ - { "id" : "S30-f0", "to" : "S30-f1" } + { "id" : "S30-f0", "to" : "S30-f5", "var" : "in1" } ] }, { "id" : "S30-g1", "type" : "EmbeddedEnd", "visual" : { - "at" : { "x" : 496, "y" : 256 } + "at" : { "x" : 672, "y" : 256 } }, "parentConnector" : "f13" }, { @@ -267,7 +280,7 @@ } }, "visual" : { - "at" : { "x" : 208, "y" : 256 } + "at" : { "x" : 424, "y" : 256 } }, "connect" : [ { "id" : "S30-f2", "to" : "S30-f3" } @@ -283,11 +296,83 @@ } }, "visual" : { - "at" : { "x" : 368, "y" : 256 } + "at" : { "x" : 568, "y" : 256 } }, "connect" : [ { "id" : "S30-f4", "to" : "S30-g1" } ] + }, { + "id" : "S30-f5", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + }, { + "id" : "TaskB", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 136, "y" : 256 } + }, + "connect" : [ + { "id" : "S30-f6", "to" : "S30-f7", "via" : [ { "x" : 136, "y" : 208 } ], "condition" : "ivp==\"TaskA.ivp\"" }, + { "id" : "S30-f12", "to" : "S30-f11", "via" : [ { "x" : 136, "y" : 304 } ], "condition" : "ivp==\"TaskB.ivp\"" } + ] + }, { + "id" : "S30-f7", + "type" : "UserTask", + "name" : "SubA-TaskA", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubA-TaskA" + } + }, + "visual" : { + "at" : { "x" : 232, "y" : 208 } + }, + "connect" : [ + { "id" : "S30-f8", "to" : "S30-f9", "via" : [ { "x" : 320, "y" : 208 } ], "var" : "in1" } + ] + }, { + "id" : "S30-f9", + "type" : "TaskSwitchGateway", + "config" : { + "tasks" : [ { + "id" : "TaskA", + "responsible" : { + "activator" : "SYSTEM" + } + } ] + }, + "visual" : { + "at" : { "x" : 320, "y" : 256 } + }, + "connect" : [ + { "id" : "S30-f10", "to" : "S30-f1", "condition" : "ivp==\"TaskA.ivp\"" } + ] + }, { + "id" : "S30-f11", + "type" : "UserTask", + "name" : "SubA-TaskB", + "config" : { + "dialog" : "com.axonivy.utils.process.analyzer.test.Dummy:start()", + "task" : { + "name" : "SubA-TaskB" + } + }, + "visual" : { + "at" : { "x" : 232, "y" : 304 } + }, + "connect" : [ + { "id" : "S30-f13", "to" : "S30-f9", "via" : [ { "x" : 320, "y" : 304 } ], "var" : "in2" } + ] } ], "visual" : { "at" : { "x" : 288, "y" : 312 } @@ -328,7 +413,7 @@ "at" : { "x" : 96, "y" : 440 } }, "connect" : [ - { "id" : "f18", "to" : "f32", "var" : "in1" } + { "id" : "f18", "to" : "S70" } ] }, { "id" : "S40", @@ -686,30 +771,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 +823,226 @@ "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" } + ] + }, { + "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" : { + "End" : "#ef0606" + } + } } \ No newline at end of file diff --git a/process-analyzer-test/processes/FlowTriggerCall.p.json b/process-analyzer-test/processes/FlowTriggerCall.p.json new file mode 100644 index 00000000..c1c52f2f --- /dev/null +++ b/process-analyzer-test/processes/FlowTriggerCall.p.json @@ -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" } + ] + } ] +} \ No newline at end of file diff --git a/process-analyzer-test/src/com/axonivy/utils/process/analyzer/test/WaitTaskBean.java b/process-analyzer-test/src/com/axonivy/utils/process/analyzer/test/WaitTaskBean.java new file mode 100644 index 00000000..c5e48b31 --- /dev/null +++ b/process-analyzer-test/src/com/axonivy/utils/process/analyzer/test/WaitTaskBean.java @@ -0,0 +1,56 @@ +package com.axonivy.utils.process.analyzer.test; + +import java.util.UUID; + +import org.eclipse.core.runtime.IProgressMonitor; + +import ch.ivyteam.ivy.process.intermediateevent.IProcessIntermediateEventBean; +import ch.ivyteam.ivy.service.ServiceException; + +public class WaitTaskBean implements IProcessIntermediateEventBean { + private boolean isRunning = false; + + public static String createEventIdentifierForTask() { + return UUID.randomUUID().toString(); + } + + @Override + public String getName() { + return null; + } + + @Override + public String getDescription() { + return null; + } + + @Override + public void start(IProgressMonitor monitor) throws ServiceException { + isRunning = true; + } + + @Override + public void stop(IProgressMonitor monitor) throws ServiceException { + isRunning = false; + } + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public void poll() { + } + + @Override + public boolean isMoreThanOneInstanceSupported() { + return false; + } + + @Override + public Class getResultObjectClass() { + return null; + } + +} diff --git a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowExampleComplexTest.java b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowExampleComplexTest.java index 80de531d..aae4459d 100644 --- a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowExampleComplexTest.java +++ b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowExampleComplexTest.java @@ -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); } @@ -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 diff --git a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowMixedSubProcess.java b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowMixedSubProcess.java new file mode 100644 index 00000000..8844c0f1 --- /dev/null +++ b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowMixedSubProcess.java @@ -0,0 +1,37 @@ +package com.axonivy.utils.process.analyzer.test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.assertj.core.util.Arrays; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.axonivy.utils.process.analyzer.internal.ProcessAnalyzer; + +import ch.ivyteam.ivy.environment.IvyTest; + +@IvyTest +public class FlowMixedSubProcess extends FlowExampleTest { + private static final String PROCESS_NAME = "FlowMixedSubProcess"; + + @BeforeAll + public static void setup() { + setup(PROCESS_NAME); + } + + @BeforeEach + public void setupForEach() { + processAnalyzer = new ProcessAnalyzer(); + } + + @Test + void shouldFindAllTasks() throws Exception { + var start = ProcessGraphHelper.findByElementName(process, "start"); + var detectedTasks = processAnalyzer.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 taskNames = getTaskNames(detectedTasks); + assertArrayEquals(expected, taskNames); + } +} diff --git a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessCaseTest.java b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessCaseTest.java index cb669909..239e0f15 100644 --- a/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessCaseTest.java +++ b/process-analyzer-test/src_test/com/axonivy/utils/process/analyzer/test/FlowSubProcessCaseTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Duration; +import java.util.List; import org.assertj.core.util.Arrays; import org.junit.jupiter.api.BeforeEach; @@ -16,6 +17,7 @@ import ch.ivyteam.ivy.bpm.engine.client.element.BpmProcess; import ch.ivyteam.ivy.bpm.exec.client.IvyProcessTest; import ch.ivyteam.ivy.workflow.ICase; +import ch.ivyteam.ivy.workflow.ITask; @IvyProcessTest public class FlowSubProcessCaseTest extends FlowExampleTest { @@ -29,11 +31,17 @@ public void setupForEach() { @Test void shouldFindAllTasksAtStart3(BpmClient bpmClient) throws Exception { ExecutionResult result = bpmClient.start().process(FLOW_SUB_PROCESS.elementName("start3")).execute(); - ICase icase = result.workflow().activeCase(); + List parallelTasks = result.workflow().activeTasks(); + + for (ITask task : parallelTasks) { + result = bpmClient.start().task(task).as().systemUser().execute(); + } + + ICase icase = result.workflow().activeCase(); var detectedTasks = processAnalyzer.findAllTasks(icase, null); - var expected = Arrays.array("CallSubProcess A", "TaskC", "TaskB"); + var expected = Arrays.array("SubA-TaskA", "SubA-TaskB", "CallSubProcess A", "TaskC", "TaskB"); var taskNames = getTaskNames(detectedTasks); assertArrayEquals(expected, taskNames); } @@ -50,6 +58,24 @@ void shouldFindAllTasksAtStart(BpmClient bpmClient) throws Exception { assertArrayEquals(expected, taskNames); } + @Test + void shouldFindAllTasksAtWaitTask(BpmClient bpmClient) throws Exception { + ExecutionResult result = bpmClient.start().process(FLOW_SUB_PROCESS.elementName("start")).execute(); + ICase icase = result.workflow().activeCase(); + + List activeTasks = result.workflow().activeTasks(); + ITask taskA = findTaskByElementName(activeTasks, "Task A"); + + bpmClient.mock().uiOf(FLOW_SUB_PROCESS.elementName("Task A")).withNoAction(); + result = bpmClient.start().task(taskA).as().everybody().execute(); + + var detectedTasks = processAnalyzer.findAllTasks(icase, null); + + var expected = Arrays.array("Task B"); + var taskNames = getTaskNames(detectedTasks); + assertArrayEquals(expected, taskNames); + } + @Test void shouldCalculateWorstCaseDuration(BpmClient bpmClient) throws Exception { ExecutionResult result = bpmClient.start().process(FLOW_SUB_PROCESS.elementName("start")).execute(); 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 98bb33bb..20234239 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"); @@ -68,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()); diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/AdvancedProcessAnalyzer.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/AdvancedProcessAnalyzer.java index 3072c559..7837b24d 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/AdvancedProcessAnalyzer.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/AdvancedProcessAnalyzer.java @@ -175,7 +175,7 @@ public List findTasksOnPath(List startAtElements, /** * This method can be used to calculate expected duration from a starting point * using a named flow or default flow. For parallel segments of the process, it - * will still use the “critical path” (same logic like worst case duration). * + * will still use the “critical path” (same logic like worst case duration). * * @param startElement - Element where we start traversing the process * @param useCase - Use case that should be used to read duration values. @@ -189,7 +189,7 @@ public Duration calculateDurationOfPath(BaseElement startElement, Enum useCas /** * This method can be used to calculate expected duration from a starting point * using a named flow or default flow. For parallel segments of the process, it - * will still use the “critical path” (same logic like worst case duration). + * will still use the “critical pathâ€� (same logic like worst case duration). * * @param useCase - Use case that should be used to read duration values. * Durations will be set to 0 in case not provided. 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 344fe5fd..f94f1fba 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 @@ -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; @@ -40,8 +41,7 @@ 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; +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(); } @@ -110,7 +110,7 @@ private List getParentPathOf(ProcessElement startElement,String fl List result = paths; if(parentElement != null) { - SubProcessGroup subProcess = new SubProcessGroup(parentElement.getElement(), paths); + SubProcessGroup subProcess = new SubProcessGroup((EmbeddedProcessElement) parentElement.getElement(), paths); Map> parentPaths = findPath(List.of(parentElement), flowName, findType); List subPaths = parentPaths.getOrDefault(parentElement, emptyList()); @@ -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)); } @@ -420,15 +420,21 @@ private List findAnalysisPaths(ProcessElement startElement, String } } + + var newPath = addToPath(currentPath, path); + //It stop finding tasks when the end node is TaskEnd for TASKS_ON_PATH case + if (shouldStopFindTask(newPath, findType)) { + return path; + } // Call recursion for next normal node - var pathOptions = findAnalysisPathForNextNode(from, flowName, findType, currentPath); + 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 +446,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); @@ -452,8 +458,8 @@ private Map> findAnalysisPathForNextNode(Proces private boolean isContains(List 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 pathWithConnectToFrom = currentPaths.stream().filter(path -> { @@ -500,7 +506,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()); @@ -600,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); } @@ -641,7 +650,6 @@ private boolean isJoinTaskSwitchGateway(List paths, ProcessElement } result = hasStartBefore && !hasFullInComing; - } return result; } @@ -650,31 +658,19 @@ private boolean haveFullInComingJoinTaskSwitchGateway(List 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 elements = AnalysisPathHelper.getAllProcessElement(paths).stream() - .map(ProcessElement::getElement) - .toList(); + + if (taskSwitchGateway.getIncoming().size() > 1) { + List sequenceFlowToParalletTasks = findIncomingsFromPaths(paths, from); + + NodeElement startNode = AnalysisPathHelper.getFirstNodeElement(paths); + List sequenceFlows = getSequenceFlowOf(from, startNode); - List 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 sequenceFlows = getSequenceFlowOfTaskSwitchGateway(taskSwitchGateway, firstNode); long count = sequenceFlowToParalletTasks.stream().filter(el -> sequenceFlows.contains(el)).count(); if (count >= sequenceFlows.size()) { hasFullInComing = true; @@ -686,24 +682,18 @@ private boolean haveFullInComingJoinTaskSwitchGateway(List paths, private ProcessElement getJoinTaskSwithGateWay(TaskParallelGroup taskParallelGroup) { List elements = AnalysisPathHelper.getAllProcessElement(taskParallelGroup); - - int size = elements.size(); - return size > 0 ? elements.get(size - 1) : null; + return AnalysisPathHelper.getLastElement(new AnalysisPath(elements)); } - - - private List getSequenceFlowOfTaskSwitchGateway(TaskSwitchGateway taskSwitchGateway, NodeElement startNode) { - List 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 pathChecked) { if(startNode == null) { return false; } NodeElement node = sequenceFlow.getSource(); + if(pathChecked.contains(node)) { + return false; + } + if(startNode.equals(node)) { return true; } @@ -711,10 +701,59 @@ private boolean isStartedFromOf(NodeElement startNode, SequenceFlow sequenceFlow // Maybe have a loop here. List sequenceFlows = node.getIncoming(); for (SequenceFlow flow : sequenceFlows) { - if (isStartedFromOf(startNode, flow)) { + 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(); + 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; + } + + 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(); + } + return sequenceFlows; + } + + private List findIncomingsFromPaths(List paths, ProcessElement from) { + List elements = AnalysisPathHelper.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; + } } diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessAnalyzer.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessAnalyzer.java index afeaac84..df300bd5 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessAnalyzer.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessAnalyzer.java @@ -34,7 +34,7 @@ public class ProcessAnalyzer implements AdvancedProcessAnalyzer { private static final List OPEN_TASK_STATES = List.of(TaskState.SUSPENDED, TaskState.PARKED, - TaskState.RESUMED); + TaskState.RESUMED, TaskState.WAITING_FOR_INTERMEDIATE_EVENT); private boolean isEnableDescribeAlternative; private Map durationOverrides = emptyMap(); @@ -220,8 +220,11 @@ private List getStartElements(ICase icase) { } private List getCaseITasks(ICase icase) { - List tasks = icase.tasks().all().stream().filter(task -> OPEN_TASK_STATES.contains(task.getState())) + List tasks = icase.tasks().all().stream() + .filter(task -> OPEN_TASK_STATES.contains(task.getState())) + .filter(it -> it.getCase().uuid().equals(icase.uuid())) .toList(); + return tasks; } diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessGraph.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessGraph.java index df332f5b..5d739df4 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessGraph.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/ProcessGraph.java @@ -11,12 +11,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import com.axonivy.utils.process.analyzer.model.ElementTask; import ch.ivyteam.ivy.process.model.BaseElement; import ch.ivyteam.ivy.process.model.EmbeddedProcess; +import ch.ivyteam.ivy.process.model.NodeElement; import ch.ivyteam.ivy.process.model.connector.SequenceFlow; import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; import ch.ivyteam.ivy.process.model.element.SingleTaskCreator; @@ -64,21 +67,36 @@ public List getParentElementNamesEmbeddedProcessElement(BaseElement pare return result; } - public BaseElement findStartElementOfProcess(SequenceFlow sequenceFlow, EmbeddedProcessElement embeddedProcessElement) { - EmbeddedProcess process = embeddedProcessElement.getEmbeddedProcess(); - BaseElement start = process.getElements().stream() - .filter(item -> item instanceof EmbeddedStart) + public BaseElement findStartElementOfProcess(SequenceFlow sequenceFlow, EmbeddedProcessElement embeddedProcessElement) { + BaseElement start = findStartElementOfProcess(embeddedProcessElement).stream() .filter(it -> sequenceFlow == null || ((EmbeddedStart) it).getConnectedOuterSequenceFlow().equals(sequenceFlow)) .findFirst() .orElse(null); 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; } @@ -158,7 +176,16 @@ public String getAlternativeNameId(BaseElement alternative) { return Stream.of(alternative.getName(), alternative.getPid().getRawPid()).filter(StringUtils::isNotEmpty) .collect(Collectors.joining("-")); } + private boolean containPrefixs(String content, String... prefix) { return List.of(prefix).stream().allMatch(it -> content.contains(it)); } + + private List findStartElementOfProcess(EmbeddedProcessElement embeddedProcessElement) { + EmbeddedProcess process = embeddedProcessElement.getEmbeddedProcess(); + List starts = process.getElements().stream() + .filter(EmbeddedStart.class::isInstance) + .toList(); + return starts; + } } \ No newline at end of file diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/WorkflowPath.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/WorkflowPath.java index 1f4dd55d..fcd15207 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/WorkflowPath.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/WorkflowPath.java @@ -16,6 +16,7 @@ import java.util.Optional; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.map.HashedMap; import org.apache.commons.lang3.StringUtils; import com.axonivy.utils.process.analyzer.internal.model.AnalysisPath; @@ -83,8 +84,7 @@ protected List findTaskOnPath(Map tim return detectedTasks; } - protected List convertToDetectedElements(Map> allPaths, - Enum useCase, Map timeUntilStarts) { + 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, @@ -177,7 +177,9 @@ private List convertPathToDetectedElements(ProcessElement start List allTaskFromSubPath = new ArrayList<>(); for(AnalysisPath subPath : subPaths) { ProcessElement startSubElement = subPath.getElements().get(0); - var startedForSubProcess = Map.of(startSubElement, durationStart); + var startedForSubProcess = new HashedMap<>(timeUntilStarts); + startedForSubProcess.put(startSubElement, durationStart); + List subResult = convertPathToDetectedElements(startSubElement, subPath, useCase , startedForSubProcess); allTaskFromSubPath.addAll(subResult); } @@ -276,10 +278,14 @@ private Duration timeUntilEnd(List detectedElements, Duration d } private Duration getMaxDurationUntilEnd(List detectedElements) { - Duration maxDurationUntilEnd = detectedElements.stream().filter(item -> item instanceof DetectedTask == true) - .map(DetectedTask.class::cast).map(DetectedTask::getTimeUntilEnd).max(Duration::compareTo).orElse(null); - - if(maxDurationUntilEnd.isNegative()) { + Duration maxDurationUntilEnd = detectedElements.stream() + .filter(item -> item instanceof DetectedTask == true) + .map(DetectedTask.class::cast) + .map(DetectedTask::getTimeUntilEnd) + .max(Duration::compareTo) + .orElse(null); + + if (maxDurationUntilEnd == null || maxDurationUntilEnd.isNegative()) { maxDurationUntilEnd = Duration.ZERO; } 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 2536f86a..fd9884f7 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 @@ -15,7 +15,7 @@ import com.axonivy.utils.process.analyzer.internal.model.ProcessElement; import com.axonivy.utils.process.analyzer.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; public class AnalysisPathHelper { @@ -90,7 +90,23 @@ public static int getLastIndex(AnalysisPath path) { return elements.size() == 0 ? 0 : elements.size() - 1; } - public static List removeLastElementByClassType(List paths , Class clazz) { + 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); + return startNode; + } + + public static List removeLastElementByClassType(List paths , Class clazz) { List result = new ArrayList<>(); for (AnalysisPath path : paths) { diff --git a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/model/SubProcessGroup.java b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/model/SubProcessGroup.java index 0a5f1c4c..248c0ed8 100644 --- a/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/model/SubProcessGroup.java +++ b/process-analyzer/src/com/axonivy/utils/process/analyzer/internal/model/SubProcessGroup.java @@ -6,17 +6,18 @@ import java.util.Objects; import ch.ivyteam.ivy.process.model.BaseElement; +import ch.ivyteam.ivy.process.model.element.EmbeddedProcessElement; import ch.ivyteam.ivy.process.model.value.PID; public class SubProcessGroup implements ProcessElement { - private BaseElement element; + private EmbeddedProcessElement element; List internalPaths; - public SubProcessGroup(BaseElement element) { + public SubProcessGroup(EmbeddedProcessElement element) { this.element = element; } - public SubProcessGroup(BaseElement element, List internalPaths) { + public SubProcessGroup(EmbeddedProcessElement element, List internalPaths) { this(element); this.internalPaths = internalPaths; }