diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java index 97118ba469..cb383953fe 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -9,7 +9,6 @@ import io.metersphere.api.dto.SaveAPITestRequest; import io.metersphere.api.service.APITestService; import io.metersphere.base.domain.ApiTest; import io.metersphere.base.domain.ApiTestWithBLOBs; -import io.metersphere.base.domain.LoadTest; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; @@ -19,10 +18,10 @@ import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.Resource; - import java.util.List; +import javax.annotation.Resource; + @RestController @RequestMapping(value = "/api") @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) 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/base/mapper/ext/ExtUserRoleMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java index 4bc8eb87cc..f504f373c0 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java @@ -27,4 +27,5 @@ public interface ExtUserRoleMapper { List getBesideOrgMemberList(@Param("orgId") String orgId); + List getTestManagerAndTestUserList(@Param("request") QueryMemberRequest request); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml index 5b15cd3e29..f45f2d2d17 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml @@ -89,7 +89,12 @@ join role r on r.id = ur.role_id where w.id = #{workspaceId} and ur.user_id = #{userId} - - - + \ No newline at end of file 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/controller/UserController.java b/backend/src/main/java/io/metersphere/controller/UserController.java index 0deb7b9ba6..d674014dbd 100644 --- a/backend/src/main/java/io/metersphere/controller/UserController.java +++ b/backend/src/main/java/io/metersphere/controller/UserController.java @@ -274,4 +274,14 @@ public class UserController { return userService.updateUserPassword(request); } + /** + * 获取工作空间成员用户 不分页 + */ + @PostMapping("/ws/member/tester/list") + @RequiresRoles(value = {RoleConstants.ORG_ADMIN, RoleConstants.TEST_MANAGER, + RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) + public List getTestManagerAndTestUserList(@RequestBody QueryMemberRequest request) { + return userService.getTestManagerAndTestUserList(request); + } + } diff --git a/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java b/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java index a86ef92aa1..903cb29163 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java +++ b/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java @@ -86,7 +86,12 @@ public class DockerTestEngine extends AbstractEngine { testRequest.setTestData(context.getTestData()); testRequest.setEnv(context.getEnv()); - restTemplate.postForObject(uri, testRequest, String.class); + try { + restTemplate.postForObject(uri, testRequest, String.class); + } catch (Exception e) { + LogUtil.error(e); + MSException.throwException(Translator.get("start_engine_fail")); + } } @Override diff --git a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java index faa70de519..25ce59ebf9 100644 --- a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -85,6 +85,7 @@ public class JmeterDocumentParser implements DocumentParser { if (nodeNameEquals(ele, HASH_TREE_ELEMENT)) { parseHashTree(ele); } else if (nodeNameEquals(ele, TEST_PLAN)) { + processSetupTestPlan(ele); processTearDownTestPlan(ele); processCheckoutConfigTestElement(ele); processCheckoutDnsCacheManager(ele); @@ -436,6 +437,87 @@ public class JmeterDocumentParser implements DocumentParser { } } + private void processSetupTestPlan(Element ele) { + Document document = ele.getOwnerDocument(); + Node hashTree = ele.getNextSibling(); + while (!(hashTree instanceof Element)) { + hashTree = hashTree.getNextSibling(); + } + + KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); + String bootstrapServers = kafkaProperties.getBootstrapServers(); + String[] servers = StringUtils.split(bootstrapServers, ","); + for (String s : servers) { + String[] ipAndPort = StringUtils.split(s, ":"); + Element setupElement = document.createElement("SetupThreadGroup"); + setupElement.setAttribute("guiclass", "SetupThreadGroupGui"); + setupElement.setAttribute("testclass", "SetupThreadGroup"); + setupElement.setAttribute("testname", "setUp Thread Group"); + setupElement.setAttribute("enabled", "true"); + setupElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "stoptestnow")); + Element elementProp = document.createElement("elementProp"); + elementProp.setAttribute("name", "ThreadGroup.main_controller"); + elementProp.setAttribute("elementType", "LoopController"); + elementProp.setAttribute("guiclass", "LoopControlPanel"); + elementProp.setAttribute("testclass", "LoopController"); + elementProp.setAttribute("testname", "Loop Controller"); + elementProp.setAttribute("enabled", "true"); + elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false)); + elementProp.appendChild(createIntProp(document, "LoopController.loops", 1)); + setupElement.appendChild(elementProp); + setupElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1")); + setupElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1")); + setupElement.appendChild(createStringProp(document, "ThreadGroup.duration", "")); + setupElement.appendChild(createStringProp(document, "ThreadGroup.delay", "")); + setupElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); + setupElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); + hashTree.appendChild(setupElement); + + Element setupHashTree = document.createElement(HASH_TREE_ELEMENT); + + Element tcpSampler = document.createElement("TCPSampler"); + tcpSampler.setAttribute("guiclass", "TCPSamplerGui"); + tcpSampler.setAttribute("testclass", "TCPSampler"); + tcpSampler.setAttribute("testname", "TCP Sampler"); + tcpSampler.setAttribute("enabled", "true"); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.classname", "TCPClientImpl")); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.server", ipAndPort[0])); + tcpSampler.appendChild(createBoolProp(document, "TCPSampler.reUseConnection", true)); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.port", ipAndPort[1])); + tcpSampler.appendChild(createBoolProp(document, "TCPSampler.nodelay", false)); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.timeout", "100")); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.ctimeout", "100")); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.request", "1010")); + tcpSampler.appendChild(createBoolProp(document, "TCPSampler.closeConnection", false)); + tcpSampler.appendChild(createStringProp(document, "TCPSampler.EolByte", "0")); + tcpSampler.appendChild(createStringProp(document, "ConfigTestElement.username", "")); + tcpSampler.appendChild(createStringProp(document, "ConfigTestElement.password", "")); + + Element tcpSamplerHashTree = document.createElement(HASH_TREE_ELEMENT); + + Element responseAssertion = document.createElement("ResponseAssertion"); + responseAssertion.setAttribute("guiclass", "AssertionGui"); + responseAssertion.setAttribute("testclass", "ResponseAssertion"); + responseAssertion.setAttribute("testname", "Response Assertion"); + responseAssertion.setAttribute("enabled", "true"); + Element collectionProp = document.createElement("collectionProp"); + collectionProp.setAttribute("name", "Asserion.test_strings"); + collectionProp.appendChild(createStringProp(document, "49586", "200")); + responseAssertion.appendChild(collectionProp); + responseAssertion.appendChild(createStringProp(document, "Assertion.custom_message", "")); + responseAssertion.appendChild(createStringProp(document, "Assertion.test_field", "Assertion.response_code")); + responseAssertion.appendChild(createBoolProp(document, "Assertion.assume_success", false)); + responseAssertion.appendChild(createIntProp(document, "Assertion.test_type", 8)); + tcpSamplerHashTree.appendChild(responseAssertion); + // 添加空的hashtree + tcpSamplerHashTree.appendChild(document.createElement(HASH_TREE_ELEMENT)); + + setupHashTree.appendChild(tcpSampler); + setupHashTree.appendChild(tcpSamplerHashTree); + + hashTree.appendChild(setupHashTree); + } + } private void processTearDownTestPlan(Element ele) { /*true*/ diff --git a/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java index b539a94f88..87da37f9f6 100644 --- a/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java +++ b/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java @@ -11,6 +11,7 @@ import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.config.KafkaProperties; import io.metersphere.controller.request.OrderRequest; import io.metersphere.dto.DashboardTestDTO; import io.metersphere.dto.LoadTestDTO; @@ -28,6 +29,9 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; @@ -62,6 +66,8 @@ public class PerformanceTestService { private TestResourceService testResourceService; @Resource private ReportService reportService; + @Resource + private KafkaProperties kafkaProperties; public List list(QueryTestPlanRequest request) { request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); @@ -197,6 +203,8 @@ public class PerformanceTestService { if (StringUtils.equalsAny(loadTest.getStatus(), PerformanceTestStatus.Running.name(), PerformanceTestStatus.Starting.name())) { MSException.throwException(Translator.get("load_test_is_running")); } + // check kafka + checkKafka(); LogUtil.info("Load test started " + loadTest.getName()); // engine type (NODE) @@ -212,6 +220,31 @@ public class PerformanceTestService { return engine.getReportId(); } + private void checkKafka() { + String bootstrapServers = kafkaProperties.getBootstrapServers(); + String[] servers = StringUtils.split(bootstrapServers, ","); + try { + for (String s : servers) { + String[] ipAndPort = s.split(":"); + //1,建立tcp + String ip = ipAndPort[0]; + int port = Integer.parseInt(ipAndPort[1]); + Socket soc = new Socket(); + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + //2.输入内容 + String content = "1010"; + byte[] bs = content.getBytes(); + OutputStream os = soc.getOutputStream(); + os.write(bs); + //3.关闭 + soc.close(); + } + } catch (Exception e) { + LogUtil.error(e); + MSException.throwException(Translator.get("load_test_kafka_invalid")); + } + } + private void startEngine(LoadTestWithBLOBs loadTest, Engine engine) { LoadTestReport testReport = new LoadTestReport(); testReport.setId(engine.getReportId()); diff --git a/backend/src/main/java/io/metersphere/service/UserService.java b/backend/src/main/java/io/metersphere/service/UserService.java index 1028f851f0..934b4bbe2a 100644 --- a/backend/src/main/java/io/metersphere/service/UserService.java +++ b/backend/src/main/java/io/metersphere/service/UserService.java @@ -457,4 +457,7 @@ public class UserService { return extUserMapper.getDefaultLanguage(key); } + public List getTestManagerAndTestUserList(QueryMemberRequest request) { + return extUserRoleMapper.getTestManagerAndTestUserList(request); + } } 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/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 32cd3cb002..2afe88e538 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -64,5 +64,4 @@ kafka.ssl.provider= kafka.ssl.truststore-type= # jmeter -jmeter.image=registry.fit2cloud.com/metersphere/jmeter-master:0.0.4 jmeter.home=/opt/jmeter \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 26299d19df..00701a76ea 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -34,6 +34,7 @@ run_load_test_file_not_found=Unable to run test, unable to get test file meta in run_load_test_file_content_not_found=Cannot run test, cannot get test file content, test ID= run_load_test_file_init_error=Failed to run test, failed to initialize run environment, test ID= load_test_is_running=Load test is running, please wait. +load_test_kafka_invalid=Kafka is not available, please check the configuration cannot_edit_load_test_running=Cannot modify the running test test_not_found=Test cannot be found: test_not_running=Test is not running diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index a4a484939f..4605816ba7 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -34,6 +34,7 @@ run_load_test_file_not_found=无法运行测试,无法获取测试文件元信 run_load_test_file_content_not_found=无法运行测试,无法获取测试文件内容,测试ID: run_load_test_file_init_error=无法运行测试,初始化运行环境失败,测试ID: load_test_is_running=测试正在运行, 请等待 +load_test_kafka_invalid=Kafka 不可用,请检查配置 cannot_edit_load_test_running=不能修改正在运行的测试 test_not_found=测试不存在: test_not_running=测试未运行 diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 84ff087fb3..265ae7182f 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -34,6 +34,7 @@ run_load_test_file_not_found=無法運行測試,無法獲取測試文件元信 run_load_test_file_content_not_found=無法運行測試,無法獲取測試文件內容,測試ID: run_load_test_file_init_error=無法運行測試,初始化運行環境失敗,測試ID: load_test_is_running=測試正在運行, 請等待 +load_test_kafka_invalid=Kafka 不可用,請檢查配置 cannot_edit_load_test_running=不能修改正在運行的測試 test_not_found=測試不存在: test_not_running=測試未運行 diff --git a/frontend/src/business/components/api/head/ApiHeaderMenus.vue b/frontend/src/business/components/api/head/ApiHeaderMenus.vue index e59943b152..7246c6d84f 100644 --- a/frontend/src/business/components/api/head/ApiHeaderMenus.vue +++ b/frontend/src/business/components/api/head/ApiHeaderMenus.vue @@ -35,7 +35,7 @@ - + diff --git a/frontend/src/business/components/api/report/ApiReportView.vue b/frontend/src/business/components/api/report/ApiReportView.vue index 8f4997d733..bf8ed1dc1a 100644 --- a/frontend/src/business/components/api/report/ApiReportView.vue +++ b/frontend/src/business/components/api/report/ApiReportView.vue @@ -63,7 +63,12 @@ this.$get(url, response => { this.report = response.data || {}; if (this.isNotRunning) { - this.content = JSON.parse(this.report.content); + try { + this.content = JSON.parse(this.report.content); + } catch (e) { + console.log(this.report.content) + throw e; + } this.getFails(); this.loading = false; } else { diff --git a/frontend/src/business/components/api/test/ApiTestConfig.vue b/frontend/src/business/components/api/test/ApiTestConfig.vue index 1929790b1c..d224c9a9bb 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')}} + @@ -36,6 +39,9 @@ {{$t('api_test.create_performance_test')}} + + {{$t('api_test.export_config')}} + @@ -54,7 +60,7 @@ import {Test} from "./model/ScenarioModel" import MsApiReportStatus from "../report/ApiReportStatus"; import MsApiReportDialog from "./ApiReportDialog"; - import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils"; + import {checkoutTestManagerOrTestUser, downloadFile} from "../../../../common/js/utils"; export default { name: "MsApiTestConfig", @@ -165,13 +171,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" @@ -204,6 +204,9 @@ path: "/performance/test/create" }) break; + case "export": + downloadFile(this.test.name + ".json", this.test.export()); + break; } } }, 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/ApiKeyValue.vue b/frontend/src/business/components/api/test/components/ApiKeyValue.vue index 5497d10a32..3bc2803cc2 100644 --- a/frontend/src/business/components/api/test/components/ApiKeyValue.vue +++ b/frontend/src/business/components/api/test/components/ApiKeyValue.vue @@ -10,7 +10,7 @@ :placeholder="$t('api_test.key')" show-word-limit/> - diff --git a/frontend/src/business/components/api/test/components/ApiRequestForm.vue b/frontend/src/business/components/api/test/components/ApiRequestForm.vue index b4d529a443..83184a8476 100644 --- a/frontend/src/business/components/api/test/components/ApiRequestForm.vue +++ b/frontend/src/business/components/api/test/components/ApiRequestForm.vue @@ -5,9 +5,10 @@ - - + + @@ -22,8 +23,8 @@ - + @@ -74,7 +75,7 @@ {max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'} ], url: [ - {max: 100, required: true, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}, + {max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'}, {validator: validateURL, trigger: 'blur'} ] } @@ -96,7 +97,7 @@ // 添加一个空的,用于填写 parameters.push(new KeyValue()); this.request.parameters = parameters; - this.request.url = url.toString(); + this.request.url = this.getURL(url); } catch (e) { this.$error(this.$t('api_test.request.url_invalid'), 2000) } @@ -116,7 +117,7 @@ url.searchParams.append(parameter.name, parameter.value); } }) - this.request.url = url.toString(); + this.request.url = this.getURL(url); }, addProtocol(url) { if (url) { @@ -125,6 +126,9 @@ } } return url; + }, + getURL(url) { + return decodeURIComponent(url.origin + url.pathname) + "?" + url.searchParams.toString(); } }, 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/common/js/utils.js b/frontend/src/common/js/utils.js index 918b4929bd..5297ab22b0 100644 --- a/frontend/src/common/js/utils.js +++ b/frontend/src/common/js/utils.js @@ -125,11 +125,13 @@ export function _filter(filters, condition) { if (!condition.filters) { condition.filters = {}; } - for(let filter in filters) { - if (filters[filter] && filters[filter].length > 0) { - condition.filters[filter] = filters[filter]; - } else { - condition.filters[filter] = null; + for (let filter in filters) { + if (filters.hasOwnProperty(filter)) { + if (filters[filter] && filters[filter].length > 0) { + condition.filters[filter] = filters[filter]; + } else { + condition.filters[filter] = null; + } } } } @@ -137,7 +139,7 @@ export function _filter(filters, condition) { //表格数据排序 export function _sort(column, condition) { column.prop = humpToLine(column.prop); - if (column.order == 'descending') { + if (column.order === 'descending') { column.order = 'desc'; } else { column.order = 'asc'; @@ -147,13 +149,28 @@ export function _sort(column, condition) { } let hasProp = false; condition.orders.forEach(order => { - if (order.name == column.prop) { + if (order.name === column.prop) { order.type = column.order; hasProp = true; - return; } }); if (!hasProp) { condition.orders.push({name: column.prop, type: column.order}); } } + +export function downloadFile(name, content) { + const blob = new Blob([content]); + if ("download" in document.createElement("a")) { + // 非IE下载 + // chrome/firefox + let aTag = document.createElement('a'); + aTag.download = name; + aTag.href = URL.createObjectURL(blob); + aTag.click(); + URL.revokeObjectURL(aTag.href) + } else { + // IE10+下载 + navigator.msSaveBlob(blob, name) + } +} diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 11477227aa..b5f7174ca9 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -155,7 +155,7 @@ export default { 'special_characters_are_not_supported': 'Special characters are not supported', 'mobile_number_format_is_incorrect': 'Mobile number format is incorrect', 'email_format_is_incorrect': 'Email format is incorrect', - 'password_format_is_incorrect': 'Password format is incorrect (At least 8-16 characters, at least 1 uppercase letter, 1 lowercase letter and 1 number)', + 'password_format_is_incorrect': 'Valid password: 8-16 digits, English upper and lower case letters + numbers ', 'old_password': 'Old Password', 'new_password': 'New Password', 'remove_member': 'Are you sure you want to remove this member', @@ -268,6 +268,7 @@ export default { 'download_log_file': 'Download', 'user_name': 'Creator', 'special_characters_are_not_supported': 'Test name does not support special characters', + 'pressure_config_params_is_empty': 'Pressure configuration parameters cannot be empty!' }, api_test: { creator: "Creator", @@ -283,6 +284,7 @@ export default { key: "Key", value: "Value", create_performance_test: "Create Performance Test", + export_config: "Export Configuration", scenario: { config: "Scenario Config", input_name: "Please enter the scenario name", @@ -517,6 +519,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..732338ecbb 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -154,7 +154,7 @@ export default { 'special_characters_are_not_supported': '不支持特殊字符', 'mobile_number_format_is_incorrect': '手机号码格式不正确', 'email_format_is_incorrect': '邮箱格式不正确', - 'password_format_is_incorrect': '密码格式不正确(至少8-16个字符,至少1个大写字母,1个小写字母和1个数字)', + 'password_format_is_incorrect': '有效密码:8-16位,英文大小写字母+数字', 'old_password': '旧密码', 'new_password': '新密码', 'remove_member': '确定要移除该成员吗', @@ -268,6 +268,7 @@ export default { 'pressure_prediction_chart': '压力预估图', 'user_name': '创建人', 'special_characters_are_not_supported': '测试名称不支持特殊字符', + 'pressure_config_params_is_empty': '压力配置参数不能为空!' }, api_test: { creator: "创建人", @@ -282,6 +283,7 @@ export default { key: "键", value: "值", create_performance_test: "创建性能测试", + export_config: "导出配置", scenario: { config: "场景配置", input_name: "请输入场景名称", @@ -516,6 +518,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..126aeb415d 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -152,7 +152,7 @@ export default { 'special_characters_are_not_supported': '不支持特殊字符', 'mobile_number_format_is_incorrect': '手機號碼格式不正確', 'email_format_is_incorrect': '郵箱格式不正確', - 'password_format_is_incorrect': '密碼格式不正確(至少8-16個字符,至少1個大寫字母,1個小寫字母和1個數字)', + 'password_format_is_incorrect': '有效密碼:8-16比特,英文大小寫字母+數位', 'old_password': '舊密碼', 'new_password': '新密碼', 'remove_member': '確定要移除該成員嗎', @@ -266,6 +266,7 @@ export default { 'pressure_prediction_chart': '壓力預估圖', 'user_name': '創建人', 'special_characters_are_not_supported': '測試名稱不支持特殊字符', + 'pressure_config_params_is_empty': '壓力配置參數不能為空!' }, api_test: { title: "測試", @@ -280,6 +281,7 @@ export default { key: "鍵", value: "值", create_performance_test: "創建性能測試", + export_config: "匯出配寘", scenario: { creator: "創建人", config: "場景配寘", @@ -515,6 +517,7 @@ export default { create_template: "新建模版", report_template: "測試報告模版", test_detail: "測試詳情", + failure_case: "失敗用例", } }, test_resource_pool: {