diff --git a/backend/src/main/java/io/metersphere/api/dto/SaveAPITestRequest.java b/backend/src/main/java/io/metersphere/api/dto/SaveAPITestRequest.java index 8f93f2b0a8..ccddb708c7 100644 --- a/backend/src/main/java/io/metersphere/api/dto/SaveAPITestRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/SaveAPITestRequest.java @@ -1,8 +1,11 @@ package io.metersphere.api.dto; +import io.metersphere.api.dto.scenario.Scenario; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Setter @Getter public class SaveAPITestRequest { @@ -13,5 +16,5 @@ public class SaveAPITestRequest { private String name; - private String scenarioDefinition; + private List scenarioDefinition; } diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java new file mode 100644 index 0000000000..63db961c19 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario; + +import lombok.Data; + +import java.util.List; + +@Data +public class Body { + private String type; + private String raw; + private List kvs; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/KeyValue.java b/backend/src/main/java/io/metersphere/api/dto/scenario/KeyValue.java new file mode 100644 index 0000000000..26d1450608 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/KeyValue.java @@ -0,0 +1,9 @@ +package io.metersphere.api.dto.scenario; + +import lombok.Data; + +@Data +public class KeyValue { + private String name; + private String value; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Request.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Request.java new file mode 100644 index 0000000000..ce55174015 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Request.java @@ -0,0 +1,19 @@ +package io.metersphere.api.dto.scenario; + +import io.metersphere.api.dto.scenario.assertions.Assertions; +import io.metersphere.api.dto.scenario.extract.Extract; +import lombok.Data; + +import java.util.List; + +@Data +public class Request { + private String name; + private String url; + private String method; + private List parameters; + private List headers; + private Body body; + private Assertions assertions; + private Extract extract; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Scenario.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Scenario.java new file mode 100644 index 0000000000..df60feb3b6 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Scenario.java @@ -0,0 +1,14 @@ +package io.metersphere.api.dto.scenario; + +import lombok.Data; + +import java.util.List; + +@Data +public class Scenario { + private String name; + private String url; + private List variables; + private List headers; + private List requests; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionDuration.java b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionDuration.java new file mode 100644 index 0000000000..af9f5ebbf1 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionDuration.java @@ -0,0 +1,14 @@ +package io.metersphere.api.dto.scenario.assertions; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AssertionDuration extends AssertionType { + private long value; + + public AssertionDuration() { + setType(AssertionType.DURATION); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionRegex.java b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionRegex.java new file mode 100644 index 0000000000..8b787d5c1a --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionRegex.java @@ -0,0 +1,16 @@ +package io.metersphere.api.dto.scenario.assertions; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AssertionRegex extends AssertionType { + private String subject; + private String expression; + private String description; + + public AssertionRegex() { + setType(AssertionType.REGEX); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionType.java b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionType.java new file mode 100644 index 0000000000..5ae8414478 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/AssertionType.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.assertions; + +import lombok.Data; + +@Data +public class AssertionType { + public final static String REGEX = "Regex"; + public final static String DURATION = "Duration"; + public final static String TEXT = "Text"; + + private String type; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/Assertions.java b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/Assertions.java new file mode 100644 index 0000000000..678fcdc0ce --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/assertions/Assertions.java @@ -0,0 +1,11 @@ +package io.metersphere.api.dto.scenario.assertions; + +import lombok.Data; + +import java.util.List; + +@Data +public class Assertions { + private List regex; + private AssertionDuration duration; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/Extract.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/Extract.java new file mode 100644 index 0000000000..3a390df6f1 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/Extract.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; + +import java.util.List; + +@Data +public class Extract { + private List regex; + private List json; + private List xpath; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractCommon.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractCommon.java new file mode 100644 index 0000000000..6757bc4cad --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractCommon.java @@ -0,0 +1,13 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtractCommon extends ExtractType { + private String variable; + private String value; // value: ${variable} + private String expression; + private String description; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractJSONPath.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractJSONPath.java new file mode 100644 index 0000000000..d151bf2118 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractJSONPath.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtractJSONPath extends ExtractCommon { + public ExtractJSONPath() { + setType(ExtractType.JSON_PATH); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractRegex.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractRegex.java new file mode 100644 index 0000000000..3a2216fa27 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractRegex.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtractRegex extends ExtractCommon { + public ExtractRegex() { + setType(ExtractType.REGEX); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractType.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractType.java new file mode 100644 index 0000000000..2df0b02e22 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractType.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; + +@Data +public class ExtractType { + public final static String REGEX = "Regex"; + public final static String JSON_PATH = "JSONPath"; + public final static String XPATH = "XPath"; + + private String type; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractXPath.java b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractXPath.java new file mode 100644 index 0000000000..82e801b3b4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/extract/ExtractXPath.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto.scenario.extract; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ExtractXPath extends ExtractCommon { + public ExtractXPath() { + setType(ExtractType.XPATH); + } +} diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index e454d5afdd..9c1cdec6e0 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import com.alibaba.fastjson.JSONObject; import io.metersphere.api.dto.APITestResult; import io.metersphere.api.dto.QueryAPITestRequest; import io.metersphere.api.dto.SaveAPITestRequest; @@ -25,6 +26,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.List; import java.util.Objects; +import java.util.Random; import java.util.UUID; import java.util.stream.Collectors; @@ -73,10 +75,17 @@ public class APITestService { } public void copy(SaveAPITestRequest request) { + request.setName(request.getName() + " Copy"); + try { + checkNameExist(request); + } catch (Exception e) { + request.setName(request.getName() + " " + new Random().nextInt(1000)); + } + // copy test ApiTestWithBLOBs copy = get(request.getId()); copy.setId(UUID.randomUUID().toString()); - copy.setName(copy.getName() + " Copy"); + copy.setName(request.getName()); copy.setCreateTime(System.currentTimeMillis()); copy.setUpdateTime(System.currentTimeMillis()); copy.setStatus(APITestStatus.Saved.name()); @@ -96,6 +105,10 @@ public class APITestService { return apiTestMapper.selectByPrimaryKey(id); } + public List getApiTestByProjectId(String projectId) { + return extApiTestMapper.getApiTestByProjectId(projectId); + } + public void delete(String testId) { deleteFileByTestId(testId); apiReportService.deleteByTestId(testId); @@ -138,7 +151,7 @@ public class APITestService { test.setId(request.getId()); test.setName(request.getName()); test.setProjectId(request.getProjectId()); - test.setScenarioDefinition(request.getScenarioDefinition()); + test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition())); test.setUpdateTime(System.currentTimeMillis()); test.setStatus(APITestStatus.Saved.name()); apiTestMapper.updateByPrimaryKeySelective(test); @@ -151,7 +164,7 @@ public class APITestService { test.setId(request.getId()); test.setName(request.getName()); test.setProjectId(request.getProjectId()); - test.setScenarioDefinition(request.getScenarioDefinition()); + test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition())); test.setCreateTime(System.currentTimeMillis()); test.setUpdateTime(System.currentTimeMillis()); test.setStatus(APITestStatus.Saved.name()); @@ -194,7 +207,4 @@ public class APITestService { } } - public List getApiTestByProjectId(String projectId) { - return extApiTestMapper.getApiTestByProjectId(projectId); - } } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml index d82ed5dbba..cabc50ee96 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml @@ -36,6 +36,9 @@ #{nodeId} + + and test_plan_test_case.status = #{request.status} + and test_plan_test_case.executor = #{request.executor} diff --git a/backend/src/main/java/io/metersphere/commons/utils/MathUtils.java b/backend/src/main/java/io/metersphere/commons/utils/MathUtils.java new file mode 100644 index 0000000000..a14895041f --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/utils/MathUtils.java @@ -0,0 +1,18 @@ +package io.metersphere.commons.utils; + +import java.math.BigDecimal; + +public class MathUtils { + + /** + * 获取百分比 + * 保留一位小数 + * @param value + * @return + */ + public static double getPercentWithDecimal(double value) { + return new BigDecimal(value * 100) + .setScale(1, BigDecimal.ROUND_HALF_UP) + .doubleValue(); + } +} diff --git a/backend/src/main/java/io/metersphere/track/Factory/ReportComponentFactory.java b/backend/src/main/java/io/metersphere/track/Factory/ReportComponentFactory.java new file mode 100644 index 0000000000..cf0f0e83a7 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/Factory/ReportComponentFactory.java @@ -0,0 +1,36 @@ +package io.metersphere.track.Factory; + +import io.metersphere.track.domain.*; +import io.metersphere.track.dto.TestPlanDTO; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class ReportComponentFactory { + + public static ReportComponent createComponent(String componentId, TestPlanDTO testPlan) { + if (StringUtils.equals("1", componentId)) { + return new ReportBaseInfoComponent(testPlan); + } else if (StringUtils.equals("2", componentId)) { + return new ReportResultComponent(testPlan); + } else if (StringUtils.equals("3", componentId)) { + return new ReportResultChartComponent(testPlan); + } else if (StringUtils.equals("4", componentId)) { + return new ReportFailureResultComponent(testPlan); + } + return null; + } + + public static List createComponents(List componentIds, TestPlanDTO testPlan) { + List components = new ArrayList<>(); + componentIds.forEach(id -> { + ReportComponent component = createComponent(id, testPlan); + if (component != null) { + components.add(component); + } + }); + return components; + } + +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportBaseInfoComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportBaseInfoComponent.java new file mode 100644 index 0000000000..04dbfee877 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/domain/ReportBaseInfoComponent.java @@ -0,0 +1,30 @@ +package io.metersphere.track.domain; + +import io.metersphere.track.dto.TestCaseReportMetricDTO; +import io.metersphere.track.dto.TestPlanCaseDTO; +import io.metersphere.track.dto.TestPlanDTO; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class ReportBaseInfoComponent extends ReportComponent { + private Set executorsSet = new HashSet<>(); + + public ReportBaseInfoComponent(TestPlanDTO testPlan) { + super(testPlan); + componentId = "1"; + } + + @Override + public void readRecord(TestPlanCaseDTO testCase) { + executorsSet.add(testCase.getExecutor()); + } + + @Override + public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) { + testCaseReportMetric.setProjectName(testPlan.getProjectName()); + testCaseReportMetric.setPrincipal(testPlan.getPrincipal()); + testCaseReportMetric.setExecutors(new ArrayList<>(this.executorsSet)); + } +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportComponent.java new file mode 100644 index 0000000000..3ae9a3ab21 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/domain/ReportComponent.java @@ -0,0 +1,15 @@ +package io.metersphere.track.domain; + +import io.metersphere.track.dto.TestCaseReportMetricDTO; +import io.metersphere.track.dto.TestPlanCaseDTO; +import io.metersphere.track.dto.TestPlanDTO; + +public abstract class ReportComponent { + protected String componentId; + protected TestPlanDTO testPlan; + public ReportComponent(TestPlanDTO testPlan) { + this.testPlan = testPlan; + } + public abstract void readRecord(TestPlanCaseDTO testCase); + public abstract void afterBuild(TestCaseReportMetricDTO testCaseReportMetric); +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportFailureResultComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportFailureResultComponent.java new file mode 100644 index 0000000000..ba47414374 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/domain/ReportFailureResultComponent.java @@ -0,0 +1,30 @@ +package io.metersphere.track.domain; + +import io.metersphere.commons.constants.TestPlanTestCaseStatus; +import io.metersphere.track.dto.TestCaseReportMetricDTO; +import io.metersphere.track.dto.TestPlanCaseDTO; +import io.metersphere.track.dto.TestPlanDTO; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class ReportFailureResultComponent extends ReportComponent { + private List failureTestCases = new ArrayList<>(); + public ReportFailureResultComponent(TestPlanDTO testPlan) { + super(testPlan); + componentId = "4"; + } + + @Override + public void readRecord(TestPlanCaseDTO testCase) { + if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Failure.name())) { + this.failureTestCases.add(testCase); + } + } + + @Override + public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) { + testCaseReportMetric.setFailureTestCases(failureTestCases); + } +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportResultChartComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportResultChartComponent.java new file mode 100644 index 0000000000..52deed64ee --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/domain/ReportResultChartComponent.java @@ -0,0 +1,41 @@ +package io.metersphere.track.domain; + +import io.metersphere.track.dto.TestCaseReportMetricDTO; +import io.metersphere.track.dto.TestCaseReportStatusResultDTO; +import io.metersphere.track.dto.TestPlanCaseDTO; +import io.metersphere.track.dto.TestPlanDTO; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class ReportResultChartComponent extends ReportComponent { + Map reportStatusResultMap = new HashMap<>(); + + public ReportResultChartComponent(TestPlanDTO testPlan) { + super(testPlan); + componentId = "3"; + } + + + @Override + public void readRecord(TestPlanCaseDTO testCase) { + getStatusResultMap(reportStatusResultMap, testCase); + } + + @Override + public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) { + testCaseReportMetric.setExecuteResult(new ArrayList<>(reportStatusResultMap.values())); + } + + private void getStatusResultMap(Map reportStatusResultMap, TestPlanCaseDTO testCase) { + TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus()); + if (statusResult == null) { + statusResult = new TestCaseReportStatusResultDTO(); + statusResult.setStatus(testCase.getStatus()); + statusResult.setCount(0); + } + statusResult.setCount(statusResult.getCount() + 1); + reportStatusResultMap.put(testCase.getStatus(), statusResult); + } +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java new file mode 100644 index 0000000000..f094e45fdc --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java @@ -0,0 +1,105 @@ +package io.metersphere.track.domain; + +import com.alibaba.fastjson.JSON; +import io.metersphere.base.domain.TestCaseNode; +import io.metersphere.base.domain.TestCaseNodeExample; +import io.metersphere.base.mapper.TestCaseNodeMapper; +import io.metersphere.commons.constants.TestPlanTestCaseStatus; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.MathUtils; +import io.metersphere.track.dto.*; +import io.metersphere.track.service.TestCaseNodeService; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +public class ReportResultComponent extends ReportComponent { + + private List nodeTrees = new ArrayList<>(); + private Map> childIdMap = new HashMap<>(); + private Map moduleResultMap = new HashMap<>(); + + public ReportResultComponent(TestPlanDTO testPlan) { + super(testPlan); + componentId = "2"; + init(); + } + + public void init() { + TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService"); + TestCaseNodeMapper testCaseNodeMapper = (TestCaseNodeMapper) CommonBeanFactory.getBean("testCaseNodeMapper"); + TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); + testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId()); + List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + nodeTrees = testCaseNodeService.getNodeTrees(nodes); + nodeTrees.forEach(item -> { + Set childIds = new HashSet<>(); + getChildIds(item, childIds); + childIdMap.put(item.getId(), childIds); + }); + } + + @Override + public void readRecord(TestPlanCaseDTO testCase) { + getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees); + } + + @Override + public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) { + + nodeTrees.forEach(rootNode -> { + TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId()); + if (moduleResult != null) { + moduleResult.setModuleName(rootNode.getName()); + } + }); + + for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) { + moduleResult.setPassRate(MathUtils.getPercentWithDecimal(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount())); + if (moduleResult.getCaseCount() <= 0) { + moduleResultMap.remove(moduleResult.getModuleId()); + } + } + testCaseReportMetric.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values())); + } + + private void getChildIds(TestCaseNodeDTO rootNode, Set childIds) { + + childIds.add(rootNode.getId()); + + List children = rootNode.getChildren(); + + if(children != null) { + Iterator iterator = children.iterator(); + while(iterator.hasNext()){ + getChildIds(iterator.next(), childIds); + } + } + } + + private void getModuleResultMap(Map> childIdMap, Map moduleResultMap, TestPlanCaseDTO testCase, List nodeTrees) { + childIdMap.forEach((rootNodeId, childIds) -> { + if (childIds.contains(testCase.getNodeId())) { + TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId); + if (moduleResult == null) { + moduleResult = new TestCaseReportModuleResultDTO(); + moduleResult.setCaseCount(0); + moduleResult.setPassCount(0); + moduleResult.setIssuesCount(0); + moduleResult.setModuleId(rootNodeId); + } + moduleResult.setCaseCount(moduleResult.getCaseCount() + 1); + if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) { + moduleResult.setPassCount(moduleResult.getPassCount() + 1); + } + if (StringUtils.isNotBlank(testCase.getIssues())) { + if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) { + moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1); + }; + } + moduleResultMap.put(rootNodeId, moduleResult); + return; + } + }); + } +} diff --git a/backend/src/main/java/io/metersphere/track/dto/TestCaseReportMetricDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestCaseReportMetricDTO.java index 68bc086c1e..caf925bf40 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestCaseReportMetricDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestCaseReportMetricDTO.java @@ -1,5 +1,9 @@ package io.metersphere.track.dto; +import io.metersphere.track.domain.ReportBaseInfoComponent; +import io.metersphere.track.domain.ReportFailureResultComponent; +import io.metersphere.track.domain.ReportResultChartComponent; +import io.metersphere.track.domain.ReportResultComponent; import lombok.Getter; import lombok.Setter; @@ -11,6 +15,7 @@ public class TestCaseReportMetricDTO { private List executeResult; private List moduleExecuteResult; + private List failureTestCases; private List executors; private String principal; private Long startTime; diff --git a/backend/src/main/java/io/metersphere/track/request/testplancase/QueryTestPlanCaseRequest.java b/backend/src/main/java/io/metersphere/track/request/testplancase/QueryTestPlanCaseRequest.java index 434a3ee576..c4af29338c 100644 --- a/backend/src/main/java/io/metersphere/track/request/testplancase/QueryTestPlanCaseRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testplancase/QueryTestPlanCaseRequest.java @@ -25,4 +25,6 @@ public class QueryTestPlanCaseRequest extends TestPlanTestCase { private String workspaceId; private String name; + + private String status; } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index 14a64840b7..284da5d934 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -2,11 +2,10 @@ package io.metersphere.track.service; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.*; -import io.metersphere.base.mapper.TestCaseMapper; -import io.metersphere.base.mapper.TestCaseNodeMapper; -import io.metersphere.base.mapper.TestPlanMapper; -import io.metersphere.base.mapper.TestPlanTestCaseMapper; +import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtProjectMapper; import io.metersphere.base.mapper.ext.ExtTestPlanMapper; import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper; @@ -14,10 +13,14 @@ import io.metersphere.commons.constants.TestPlanStatus; import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; +import io.metersphere.commons.utils.MathUtils; import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.request.ProjectRequest; +import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.dto.ProjectDTO; +import io.metersphere.track.Factory.ReportComponentFactory; +import io.metersphere.track.domain.ReportComponent; import io.metersphere.track.dto.*; import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest; import io.metersphere.track.request.testcase.QueryTestPlanRequest; @@ -27,6 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -58,15 +62,16 @@ public class TestPlanService { @Resource SqlSessionFactory sqlSessionFactory; + @Lazy @Resource - TestCaseNodeMapper testCaseNodeMapper; - - @Resource - TestCaseNodeService testCaseNodeService; + TestPlanTestCaseService testPlanTestCaseService; @Resource ExtProjectMapper extProjectMapper; + @Resource + TestCaseReportMapper testCaseReportMapper; + public void addTestPlan(TestPlan testPlan) { if (getTestPlanByName(testPlan.getName()).size() > 0) { MSException.throwException(Translator.get("plan_name_already_exists")); @@ -226,23 +231,17 @@ public class TestPlanService { } }); } - testPlan.setPassRate(getPercentWithTwoDecimals(testPlan.getTested() == 0 ? 0 : testPlan.getPassed()*1.0/testPlan.getTested())); - testPlan.setTestRate(getPercentWithTwoDecimals(testPlan.getTotal() == 0 ? 0 : testPlan.getTested()*1.0/testPlan.getTotal())); + testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed()*1.0/testPlan.getTested())); + testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested()*1.0/testPlan.getTotal())); }); return testPlans; } - private double getPercentWithTwoDecimals(double value) { - return new BigDecimal(value * 100) - .setScale(1, BigDecimal.ROUND_HALF_UP) - .doubleValue(); - } - public List listTestCaseByPlanId(String planId) { QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest(); request.setPlanId(planId); - return extTestPlanTestCaseMapper.list(request); + return testPlanTestCaseService.list(request); } public List listTestCaseByProjectIds(List projectIds) { @@ -255,107 +254,26 @@ public class TestPlanService { QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest(); queryTestPlanRequest.setId(planId); + TestPlanDTO testPlan = extTestPlanMapper.list(queryTestPlanRequest).get(0); + TestCaseReport testCaseReport = testCaseReportMapper.selectByPrimaryKey(testPlan.getReportId()); + JSONObject content = JSONObject.parseObject(testCaseReport.getContent()); + JSONArray componentIds = content.getJSONArray("components"); - Set executors = new HashSet<>(); - Map reportStatusResultMap = new HashMap<>(); - - TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); - testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId()); - List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); - - List nodeTrees = testCaseNodeService.getNodeTrees(nodes); - - Map> childIdMap = new HashMap<>(); - nodeTrees.forEach(item -> { - Set childIds = new HashSet<>(); - getChildIds(item, childIds); - childIdMap.put(item.getId(), childIds); - }); + List components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan); List testPlanTestCases = listTestCaseByPlanId(planId); - - Map moduleResultMap = new HashMap<>(); - for (TestPlanCaseDTO testCase: testPlanTestCases) { - executors.add(testCase.getExecutor()); - getStatusResultMap(reportStatusResultMap, testCase); - getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees); - } - - nodeTrees.forEach(rootNode -> { - TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId()); - if (moduleResult != null) { - moduleResult.setModuleName(rootNode.getName()); - } - }); - - for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) { - moduleResult.setPassRate(getPercentWithTwoDecimals(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount())); - if (moduleResult.getCaseCount() <= 0) { - moduleResultMap.remove(moduleResult.getModuleId()); - } + components.forEach(component -> { + component.readRecord(testCase); + }); } TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO(); - testCaseReportMetricDTO.setProjectName(testPlan.getProjectName()); - testCaseReportMetricDTO.setPrincipal(testPlan.getPrincipal()); - testCaseReportMetricDTO.setExecutors(new ArrayList<>(executors)); - testCaseReportMetricDTO.setExecuteResult(new ArrayList<>(reportStatusResultMap.values())); - testCaseReportMetricDTO.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values())); - - return testCaseReportMetricDTO; - } - - private void getStatusResultMap(Map reportStatusResultMap, TestPlanCaseDTO testCase) { - TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus()); - if (statusResult == null) { - statusResult = new TestCaseReportStatusResultDTO(); - statusResult.setStatus(testCase.getStatus()); - statusResult.setCount(0); - } - statusResult.setCount(statusResult.getCount() + 1); - reportStatusResultMap.put(testCase.getStatus(), statusResult); - } - - private void getModuleResultMap(Map> childIdMap, Map moduleResultMap, TestPlanCaseDTO testCase, List nodeTrees) { - childIdMap.forEach((rootNodeId, childIds) -> { - if (childIds.contains(testCase.getNodeId())) { - TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId); - if (moduleResult == null) { - moduleResult = new TestCaseReportModuleResultDTO(); - moduleResult.setCaseCount(0); - moduleResult.setPassCount(0); - moduleResult.setIssuesCount(0); - moduleResult.setModuleId(rootNodeId); - } - moduleResult.setCaseCount(moduleResult.getCaseCount() + 1); - if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) { - moduleResult.setPassCount(moduleResult.getPassCount() + 1); - } - if (StringUtils.isNotBlank(testCase.getIssues())) { - if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) { - moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1); - }; - } - moduleResultMap.put(rootNodeId, moduleResult); - return; - } + components.forEach(component -> { + component.afterBuild(testCaseReportMetricDTO); }); - } - - private void getChildIds(TestCaseNodeDTO rootNode, Set childIds) { - - childIds.add(rootNode.getId()); - - List children = rootNode.getChildren(); - - if(children != null) { - Iterator iterator = children.iterator(); - while(iterator.hasNext()){ - getChildIds(iterator.next(), childIds); - } - } + return testCaseReportMetricDTO; } public List getTestPlanByIds(List planIds) { diff --git a/frontend/src/business/components/api/test/ApiTestConfig.vue b/frontend/src/business/components/api/test/ApiTestConfig.vue index 1929790b1c..2605d077fd 100644 --- a/frontend/src/business/components/api/test/ApiTestConfig.vue +++ b/frontend/src/business/components/api/test/ApiTestConfig.vue @@ -5,7 +5,8 @@ - @@ -17,7 +18,8 @@ {{$t('commons.save')}} - + {{$t('load_test.save_and_run')}} @@ -25,7 +27,8 @@ {{$t('api_test.run')}} - {{$t('commons.cancel')}} + {{$t('commons.cancel')}} + @@ -165,13 +168,7 @@ }, getOptions(url) { let formData = new FormData(); - let request = { - id: this.test.id, - projectId: this.test.projectId, - name: this.test.name, - scenarioDefinition: JSON.stringify(this.test.scenarioDefinition) - } - let requestJson = JSON.stringify(request); + let requestJson = JSON.stringify(this.test); formData.append('request', new Blob([requestJson], { type: "application/json" diff --git a/frontend/src/business/components/api/test/ApiTestList.vue b/frontend/src/business/components/api/test/ApiTestList.vue index eac8067f29..7274cb5c94 100644 --- a/frontend/src/business/components/api/test/ApiTestList.vue +++ b/frontend/src/business/components/api/test/ApiTestList.vue @@ -143,7 +143,7 @@ }); }, handleCopy(test) { - this.result = this.$post("/api/copy", {id: test.id}, () => { + this.result = this.$post("/api/copy", {projectId: test.projectId, id: test.id, name: test.name}, () => { this.$success(this.$t('commons.copy_success')); this.search(); }); diff --git a/frontend/src/business/components/api/test/components/assertion/ApiAssertionResponseTime.vue b/frontend/src/business/components/api/test/components/assertion/ApiAssertionDuration.vue similarity index 91% rename from frontend/src/business/components/api/test/components/assertion/ApiAssertionResponseTime.vue rename to frontend/src/business/components/api/test/components/assertion/ApiAssertionDuration.vue index 1009ed163e..b992735f0b 100644 --- a/frontend/src/business/components/api/test/components/assertion/ApiAssertionResponseTime.vue +++ b/frontend/src/business/components/api/test/components/assertion/ApiAssertionDuration.vue @@ -15,13 +15,13 @@ + + diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue index dfb5d74f8f..911bddafe9 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue @@ -6,6 +6,7 @@ + @@ -14,6 +15,7 @@ + @@ -25,9 +27,12 @@ import TestResultComponent from "./TestResultComponent"; import TestResultChartComponent from "./TestResultChartComponent"; import RichTextComponent from "./RichTextComponent"; + import FailureResultComponent from "./FailureResultComponent"; export default { name: "TemplateComponent", - components: {RichTextComponent, TestResultChartComponent, TestResultComponent, BaseInfoComponent}, + components: { + FailureResultComponent, + RichTextComponent, TestResultChartComponent, TestResultComponent, BaseInfoComponent}, props: { preview: { type: Object diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportTemplateEdit.vue b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportTemplateEdit.vue index a303bfb5bf..318c7cac11 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportTemplateEdit.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportTemplateEdit.vue @@ -76,10 +76,11 @@ [1, { name: this.$t('test_track.plan_view.base_info'), id: 1 , type: 'system'}], [2, { name: this.$t('test_track.plan_view.test_result'), id: 2 , type: 'system'}], [3, { name: this.$t('test_track.plan_view.result_distribution'), id: 3 ,type: 'system'}], - [4, { name: this.$t('test_track.plan_view.custom_component'), id: 4 ,type: 'custom'}] + [4, { name: this.$t('test_track.plan_view.failure_case'), id: 4 ,type: 'system'}], + [5, { name: this.$t('test_track.plan_view.custom_component'), id: 5 ,type: 'custom'}] ] ), - components: [4], + components: [5], previews: [], template: {}, isReport: false @@ -107,13 +108,13 @@ } this.template = { name: '', - content: { - components: [1,2,3,4], - customComponent: new Map() + content: { + components: [1,2,3,4,5], + customComponent: new Map() } }; this.previews = []; - this.components = [4]; + this.components = [5]; if (id) { this.type = 'edit'; this.getTemplateById(id); diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue index 276b238798..a9f7514615 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue @@ -68,7 +68,8 @@ [1, { name: this.$t('test_track.plan_view.base_info'), id: 1 , type: 'system'}], [2, { name: this.$t('test_track.plan_view.test_result'), id: 2 , type: 'system'}], [3, { name: this.$t('test_track.plan_view.result_distribution'), id: 3 ,type: 'system'}], - [4, { name: this.$t('test_track.plan_view.custom_component'), id: 4 ,type: 'custom'}] + [4, { name: this.$t('test_track.plan_view.failure_case'), id: 4 ,type: 'system'}], + [5, { name: this.$t('test_track.plan_view.custom_component'), id: 5 ,type: 'custom'}] ] ), isTestManagerOrTestUser: false @@ -165,6 +166,15 @@ getMetric() { this.result = this.$get('/test/plan/get/metric/' + this.planId, response => { this.metric = response.data; + if (!this.metric.failureTestCases) { + this.metric.failureTestCases = []; + } + if (!this.metric.executeResult) { + this.metric.executeResult = []; + } + if (!this.metric.moduleExecuteResult) { + this.metric.moduleExecuteResult = []; + } if (this.report.startTime) { this.metric.startTime = new Date(this.report.startTime); } diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 11477227aa..e39c96ac8f 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -517,6 +517,7 @@ export default { create_template: "Create template", report_template: "Report template", test_detail: "Test detail", + failure_case: "Failure case", } }, test_resource_pool: { diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 048c438be0..1d98b815b9 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -516,6 +516,7 @@ export default { create_template: "新建模版", report_template: "测试报告模版", test_detail: "测试详情", + failure_case: "失败用例", } }, test_resource_pool: { diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 058358eb8c..4b4b99cb5a 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -515,6 +515,7 @@ export default { create_template: "新建模版", report_template: "測試報告模版", test_detail: "測試詳情", + failure_case: "失敗用例", } }, test_resource_pool: {