diff --git a/backend/pom.xml b/backend/pom.xml index 75b67f5b2b..a86ca5c84c 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -42,7 +42,6 @@ org.springframework.boot spring-boot-configuration-processor - true @@ -53,7 +52,9 @@ org.springframework.boot spring-boot-starter-test + test + org.springframework.boot spring-boot-starter-aop @@ -66,7 +67,6 @@ org.projectlombok lombok - provided @@ -119,10 +119,6 @@ commons-codec commons-codec - - junit - junit - com.alibaba @@ -188,6 +184,16 @@ spring-boot-maven-plugin true + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-configuration-processor + + diff --git a/backend/src/main/java/io/metersphere/api/controller/APIReportController.java b/backend/src/main/java/io/metersphere/api/controller/APIReportController.java new file mode 100644 index 0000000000..aaae01caec --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/controller/APIReportController.java @@ -0,0 +1,56 @@ +package io.metersphere.api.controller; + +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.metersphere.api.dto.APIReportResult; +import io.metersphere.api.dto.DeleteAPIReportRequest; +import io.metersphere.api.dto.QueryAPIReportRequest; +import io.metersphere.api.service.APIReportService; +import io.metersphere.base.domain.ApiTestReport; +import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.commons.utils.PageUtils; +import io.metersphere.commons.utils.Pager; +import io.metersphere.user.SessionUtils; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import javax.annotation.Resource; + +@RestController +@RequestMapping(value = "/api/report") +@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) +public class APIReportController { + + @Resource + private APIReportService apiReportService; + + @GetMapping("recent/{count}") + public List recentTest(@PathVariable int count) { + String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); + QueryAPIReportRequest request = new QueryAPIReportRequest(); + request.setWorkspaceId(currentWorkspaceId); + PageHelper.startPage(1, count, true); + return apiReportService.recentTest(request); + } + + @PostMapping("/list/{goPage}/{pageSize}") + public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPIReportRequest request) { + Page page = PageHelper.startPage(goPage, pageSize, true); + request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + return PageUtils.setPageInfo(page, apiReportService.list(request)); + } + + @GetMapping("/get/{testId}") + public ApiTestReport get(@PathVariable String testId) { + return apiReportService.get(testId); + } + + @PostMapping("/delete") + public void delete(@RequestBody DeleteAPIReportRequest request) { + apiReportService.delete(request); + } + +} 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 412428214f..4d8eb68da8 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -6,13 +6,11 @@ import io.metersphere.api.dto.APITestResult; import io.metersphere.api.dto.DeleteAPITestRequest; import io.metersphere.api.dto.QueryAPITestRequest; import io.metersphere.api.dto.SaveAPITestRequest; -import io.metersphere.api.service.ApiTestService; +import io.metersphere.api.service.APITestService; import io.metersphere.base.domain.ApiTestWithBLOBs; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; -import io.metersphere.controller.request.testplan.SaveTestPlanRequest; -import io.metersphere.service.FileService; import io.metersphere.user.SessionUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; @@ -28,9 +26,7 @@ import javax.annotation.Resource; @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) public class APITestController { @Resource - private ApiTestService apiTestService; - @Resource - private FileService fileService; + private APITestService apiTestService; @GetMapping("recent/{count}") public List recentTest(@PathVariable int count) { @@ -48,9 +44,14 @@ public class APITestController { return PageUtils.setPageInfo(page, apiTestService.list(request)); } - @PostMapping(value = "/save", consumes = {"multipart/form-data"}) - public String save(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List files) { - return apiTestService.save(request, files); + @PostMapping(value = "/create", consumes = {"multipart/form-data"}) + public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List files) { + apiTestService.create(request, files); + } + + @PostMapping(value = "/update", consumes = {"multipart/form-data"}) + public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List files) { + apiTestService.update(request, files); } @GetMapping("/get/{testId}") @@ -63,8 +64,8 @@ public class APITestController { apiTestService.delete(request); } - @PostMapping(value = "/run", consumes = {"multipart/form-data"}) - public String run(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List files) { - return apiTestService.run(request, files); + @PostMapping(value = "/run") + public void run(@RequestBody SaveAPITestRequest request) { + apiTestService.run(request); } } diff --git a/backend/src/main/java/io/metersphere/api/dto/APIReportResult.java b/backend/src/main/java/io/metersphere/api/dto/APIReportResult.java new file mode 100644 index 0000000000..0a90a617e5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/APIReportResult.java @@ -0,0 +1,13 @@ +package io.metersphere.api.dto; + +import io.metersphere.base.domain.ApiTestReport; +import io.metersphere.base.domain.ApiTestWithBLOBs; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class APIReportResult extends ApiTestReport { + + private String projectName; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/DeleteAPIReportRequest.java b/backend/src/main/java/io/metersphere/api/dto/DeleteAPIReportRequest.java new file mode 100644 index 0000000000..208eee4450 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/DeleteAPIReportRequest.java @@ -0,0 +1,11 @@ +package io.metersphere.api.dto; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class DeleteAPIReportRequest { + + private String id; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/QueryAPIReportRequest.java b/backend/src/main/java/io/metersphere/api/dto/QueryAPIReportRequest.java new file mode 100644 index 0000000000..acdd872aab --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/QueryAPIReportRequest.java @@ -0,0 +1,16 @@ +package io.metersphere.api.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class QueryAPIReportRequest { + + private String id; + private String projectId; + private String name; + private String workspaceId; + private boolean recent = false; + +} diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 31f2e7771f..caba502e8c 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -1,45 +1,149 @@ package io.metersphere.api.jmeter; +import io.metersphere.api.service.APIReportService; +import io.metersphere.api.service.APITestService; +import io.metersphere.commons.constants.APITestStatus; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; +import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +/** + * JMeter BackendListener扩展, jmx脚本中使用 + */ public class APIBackendListenerClient extends AbstractBackendListenerClient implements Serializable { - private final AtomicInteger count = new AtomicInteger(); + // 与前端JMXGenerator的SPLIT对应,用于获取 测试名称 和 测试ID + private final static String SPLIT = "@@:"; + // 测试ID作为key + private final Map> queue = new ConcurrentHashMap<>(); @Override public void handleSampleResults(List sampleResults, BackendListenerContext context) { - System.out.println(context.getParameter("id")); sampleResults.forEach(result -> { - for (AssertionResult assertionResult : result.getAssertionResults()) { - System.out.println(assertionResult.getName() + ": " + assertionResult.isError()); - System.out.println(assertionResult.getName() + ": " + assertionResult.isFailure()); - System.out.println(assertionResult.getName() + ": " + assertionResult.getFailureMessage()); + // 将不同的测试脚本按测试ID分开 + String label = result.getSampleLabel(); + if (!label.contains(SPLIT)) { + LogUtil.error("request name format is invalid, name: " + label); + return; } - - println("getSampleLabel", result.getSampleLabel()); - println("getErrorCount", result.getErrorCount()); - println("getRequestHeaders", result.getRequestHeaders()); - println("getResponseHeaders", result.getResponseHeaders()); - println("getSampleLabel", result.getSampleLabel()); - println("getSampleLabel", result.getSampleLabel()); - println("getResponseCode", result.getResponseCode()); - println("getResponseCode size", result.getResponseData().length); - println("getLatency", result.getLatency()); - println("end - start", result.getEndTime() - result.getStartTime()); - println("getTimeStamp", result.getTimeStamp()); - println("getTime", result.getTime()); + String name = label.split(SPLIT)[0]; + String testId = label.split(SPLIT)[1]; + if (!queue.containsKey(testId)) { + List testResults = new ArrayList<>(); + queue.put(testId, testResults); + } + result.setSampleLabel(name); + queue.get(testId).add(result); }); - System.err.println(count.addAndGet(sampleResults.size())); } - private void println(String name, Object value) { - System.out.println(name + ": " + value); + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + APITestService apiTestService = CommonBeanFactory.getBean(APITestService.class); + if (apiTestService == null) { + LogUtil.error("apiTestService is required"); + return; + } + + APIReportService apiReportService = CommonBeanFactory.getBean(APIReportService.class); + if (apiReportService == null) { + LogUtil.error("apiReportService is required"); + return; + } + + queue.forEach((id, sampleResults) -> { + TestResult testResult = new TestResult(); + testResult.setId(id); + testResult.setTotal(sampleResults.size()); + + // key: 场景Id + final Map scenarios = new LinkedHashMap<>(); + + sampleResults.forEach(result -> { + String thread = StringUtils.substringBeforeLast(result.getThreadName(), " "); + String scenarioName = StringUtils.substringBefore(thread, SPLIT); + String scenarioId = StringUtils.substringAfter(thread, SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + scenarioResult.setId(scenarioId); + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); + } + + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(); + testResult.addError(); + } + + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResult().add(requestResult); + + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); + + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + }); + testResult.getScenarios().addAll(scenarios.values()); + apiTestService.changeStatus(id, APITestStatus.Completed); + apiReportService.save(testResult); + }); + queue.clear(); + super.teardownTest(context); } + + private RequestResult getRequestResult(SampleResult result) { + RequestResult requestResult = new RequestResult(); + requestResult.setName(result.getSampleLabel()); + requestResult.setUrl(result.getUrlAsString()); + requestResult.setSuccess(result.isSuccessful()); + requestResult.setBody(result.getSamplerData()); + requestResult.setHeaders(result.getRequestHeaders()); + requestResult.setRequestSize(result.getSentBytes()); + requestResult.setTotalAssertions(result.getAssertionResults().length); + + ResponseResult responseResult = requestResult.getResponseResult(); + responseResult.setBody(result.getResponseDataAsString()); + responseResult.setHeaders(result.getResponseHeaders()); + responseResult.setLatency(result.getLatency()); + responseResult.setResponseCode(result.getResponseCode()); + responseResult.setResponseSize(result.getResponseData().length); + responseResult.setResponseTime(result.getTime()); + responseResult.setResponseMessage(result.getResponseMessage()); + + for (AssertionResult assertionResult : result.getAssertionResults()) { + ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult); + if (responseAssertionResult.isPass()) { + requestResult.addPassAssertions(); + } + responseResult.getAssertions().add(responseAssertionResult); + } + return requestResult; + } + + private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { + ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult(); + responseAssertionResult.setMessage(assertionResult.getFailureMessage()); + responseAssertionResult.setName(assertionResult.getName()); + responseAssertionResult.setPass(!assertionResult.isFailure()); + return responseAssertionResult; + } + } diff --git a/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java b/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java new file mode 100644 index 0000000000..e440a74d70 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter; + +import lombok.Data; + +@Data +public class RequestResult { + + private String name; + + private String url; + + private long requestSize; + + private boolean success; + + private String headers; + + private String cookies; + + private String body; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private final ResponseResult responseResult = new ResponseResult(); + + public void addPassAssertions() { + this.passAssertions++; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/jmeter/ResponseAssertionResult.java b/backend/src/main/java/io/metersphere/api/jmeter/ResponseAssertionResult.java new file mode 100644 index 0000000000..d32c2d8b14 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/ResponseAssertionResult.java @@ -0,0 +1,13 @@ +package io.metersphere.api.jmeter; + +import lombok.Data; + +@Data +public class ResponseAssertionResult { + + private String name; + + private String message; + + private boolean pass; +} diff --git a/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java b/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java new file mode 100644 index 0000000000..f772278e17 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java @@ -0,0 +1,28 @@ +package io.metersphere.api.jmeter; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + + +@Data +public class ResponseResult { + + private String responseCode; + + private String responseMessage; + + private long responseTime; + + private long latency; + + private long responseSize; + + private String headers; + + private String body; + + private final List assertions = new ArrayList<>(); + +} diff --git a/backend/src/main/java/io/metersphere/api/jmeter/ScenarioResult.java b/backend/src/main/java/io/metersphere/api/jmeter/ScenarioResult.java new file mode 100644 index 0000000000..e61ac8f029 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/ScenarioResult.java @@ -0,0 +1,42 @@ +package io.metersphere.api.jmeter; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ScenarioResult { + + private String id; + + private String name; + + private long responseTime; + + private int error = 0; + + private int success = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private final List requestResult = new ArrayList<>(); + + public void addError() { + this.error++; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } +} diff --git a/backend/src/main/java/io/metersphere/api/jmeter/TestResult.java b/backend/src/main/java/io/metersphere/api/jmeter/TestResult.java new file mode 100644 index 0000000000..6d5d96c00d --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/TestResult.java @@ -0,0 +1,41 @@ +package io.metersphere.api.jmeter; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TestResult { + + private String id; + + private int success = 0; + + private int error = 0; + + private int total = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private final List scenarios = new ArrayList<>(); + + public void addError() { + this.error++; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/service/APIReportService.java b/backend/src/main/java/io/metersphere/api/service/APIReportService.java new file mode 100644 index 0000000000..5ef76196bc --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/service/APIReportService.java @@ -0,0 +1,66 @@ +package io.metersphere.api.service; + +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.APIReportResult; +import io.metersphere.api.dto.DeleteAPIReportRequest; +import io.metersphere.api.dto.QueryAPIReportRequest; +import io.metersphere.api.jmeter.TestResult; +import io.metersphere.base.domain.ApiTestReport; +import io.metersphere.base.domain.ApiTestWithBLOBs; +import io.metersphere.base.mapper.ApiTestReportMapper; +import io.metersphere.base.mapper.ext.ExtApiTestReportMapper; +import io.metersphere.commons.constants.APITestStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +import javax.annotation.Resource; + +@Service +@Transactional(rollbackFor = Exception.class) +public class APIReportService { + + @Resource + private APITestService apiTestService; + @Resource + private ApiTestReportMapper apiTestReportMapper; + @Resource + private ExtApiTestReportMapper extApiTestReportMapper; + + public List list(QueryAPIReportRequest request) { + return extApiTestReportMapper.list(request); + } + + public List recentTest(QueryAPIReportRequest request) { + request.setRecent(true); + return extApiTestReportMapper.list(request); + } + + public ApiTestReport get(String id) { + return apiTestReportMapper.selectByPrimaryKey(id); + } + + public List listByTestId(String testId) { + return extApiTestReportMapper.listByTestId(testId); + } + + public void delete(DeleteAPIReportRequest request) { + apiTestReportMapper.deleteByPrimaryKey(request.getId()); + } + + public void save(TestResult result) { + ApiTestWithBLOBs test = apiTestService.get(result.getId()); + ApiTestReport report = new ApiTestReport(); + report.setId(UUID.randomUUID().toString()); + report.setTestId(result.getId()); + report.setName(test.getName()); + report.setDescription(test.getDescription()); + report.setContent(JSONObject.toJSONString(result)); + report.setCreateTime(System.currentTimeMillis()); + report.setUpdateTime(System.currentTimeMillis()); + report.setStatus(APITestStatus.Completed.name()); + apiTestReportMapper.insert(report); + } +} diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java similarity index 80% rename from backend/src/main/java/io/metersphere/api/service/ApiTestService.java rename to backend/src/main/java/io/metersphere/api/service/APITestService.java index b4d1d0720c..5637aefb5c 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -13,22 +13,21 @@ import io.metersphere.commons.constants.APITestStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.i18n.Translator; import io.metersphere.service.FileService; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.List; -import java.util.UUID; import java.util.stream.Collectors; import javax.annotation.Resource; @Service @Transactional(rollbackFor = Exception.class) -public class ApiTestService { +public class APITestService { @Resource private ApiTestMapper apiTestMapper; @@ -50,28 +49,21 @@ public class ApiTestService { return extApiTestMapper.list(request); } - public String save(SaveAPITestRequest request, List files) { + public void create(SaveAPITestRequest request, List files) { if (files == null || files.isEmpty()) { throw new IllegalArgumentException(Translator.get("file_cannot_be_null")); } + ApiTestWithBLOBs test = createTest(request); + saveFile(test.getId(), files); + } - final ApiTestWithBLOBs test; - if (StringUtils.isNotBlank(request.getId())) { - // 删除原来的文件 - deleteFileByTestId(request.getId()); - test = updateTest(request); - } else { - test = createTest(request); + public void update(SaveAPITestRequest request, List files) { + if (files == null || files.isEmpty()) { + throw new IllegalArgumentException(Translator.get("file_cannot_be_null")); } - // 保存新文件 - files.forEach(file -> { - final FileMetadata fileMetadata = fileService.saveFile(file); - ApiTestFile apiTestFile = new ApiTestFile(); - apiTestFile.setTestId(test.getId()); - apiTestFile.setFileId(fileMetadata.getId()); - apiTestFileMapper.insert(apiTestFile); - }); - return test.getId(); + deleteFileByTestId(request.getId()); + ApiTestWithBLOBs test = updateTest(request); + saveFile(test.getId(), files); } public ApiTestWithBLOBs get(String id) { @@ -83,15 +75,15 @@ public class ApiTestService { apiTestMapper.deleteByPrimaryKey(request.getId()); } - public String run(SaveAPITestRequest request, List files) { - String id = save(request, files); - try { - changeStatus(request.getId(), APITestStatus.Running); - jMeterService.run(files.get(0).getInputStream()); - } catch (IOException e) { - MSException.throwException(Translator.get("api_load_script_error")); + public void run(SaveAPITestRequest request) { + ApiTestFile file = getFileByTestId(request.getId()); + if (file == null) { + MSException.throwException(Translator.get("file_cannot_be_null")); } - return id; + byte[] bytes = fileService.loadFileAsBytes(file.getFileId()); + InputStream is = new ByteArrayInputStream(bytes); + changeStatus(request.getId(), APITestStatus.Running); + jMeterService.run(is); } public void changeStatus(String id, APITestStatus status) { @@ -121,7 +113,7 @@ public class ApiTestService { } final ApiTestWithBLOBs test = new ApiTestWithBLOBs(); - test.setId(UUID.randomUUID().toString()); + test.setId(request.getId()); test.setName(request.getName()); test.setProjectId(request.getProjectId()); test.setScenarioDefinition(request.getScenarioDefinition()); @@ -132,6 +124,16 @@ public class ApiTestService { return test; } + private void saveFile(String testId, List files) { + files.forEach(file -> { + final FileMetadata fileMetadata = fileService.saveFile(file); + ApiTestFile apiTestFile = new ApiTestFile(); + apiTestFile.setTestId(testId); + apiTestFile.setFileId(fileMetadata.getId()); + apiTestFileMapper.insert(apiTestFile); + }); + } + private void deleteFileByTestId(String testId) { ApiTestFileExample ApiTestFileExample = new ApiTestFileExample(); ApiTestFileExample.createCriteria().andTestIdEqualTo(testId); diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLog.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLog.java index 6221094d72..466470aad2 100644 --- a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLog.java +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLog.java @@ -6,7 +6,7 @@ import java.io.Serializable; @Data public class LoadTestReportLog implements Serializable { - private Long id; + private String id; private String reportId; diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLogExample.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLogExample.java index 7826fbfbbd..2112d515a4 100644 --- a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLogExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportLogExample.java @@ -114,52 +114,62 @@ public class LoadTestReportLogExample { return (Criteria) this; } - public Criteria andIdEqualTo(Long value) { + public Criteria andIdEqualTo(String value) { addCriterion("id =", value, "id"); return (Criteria) this; } - public Criteria andIdNotEqualTo(Long value) { + public Criteria andIdNotEqualTo(String value) { addCriterion("id <>", value, "id"); return (Criteria) this; } - public Criteria andIdGreaterThan(Long value) { + public Criteria andIdGreaterThan(String value) { addCriterion("id >", value, "id"); return (Criteria) this; } - public Criteria andIdGreaterThanOrEqualTo(Long value) { + public Criteria andIdGreaterThanOrEqualTo(String value) { addCriterion("id >=", value, "id"); return (Criteria) this; } - public Criteria andIdLessThan(Long value) { + public Criteria andIdLessThan(String value) { addCriterion("id <", value, "id"); return (Criteria) this; } - public Criteria andIdLessThanOrEqualTo(Long value) { + public Criteria andIdLessThanOrEqualTo(String value) { addCriterion("id <=", value, "id"); return (Criteria) this; } - public Criteria andIdIn(List values) { + public Criteria andIdLike(String value) { + addCriterion("id like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotLike(String value) { + addCriterion("id not like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { addCriterion("id in", values, "id"); return (Criteria) this; } - public Criteria andIdNotIn(List values) { + public Criteria andIdNotIn(List values) { addCriterion("id not in", values, "id"); return (Criteria) this; } - public Criteria andIdBetween(Long value1, Long value2) { + public Criteria andIdBetween(String value1, String value2) { addCriterion("id between", value1, value2, "id"); return (Criteria) this; } - public Criteria andIdNotBetween(Long value1, Long value2) { + public Criteria andIdNotBetween(String value1, String value2) { addCriterion("id not between", value1, value2, "id"); return (Criteria) this; } diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResult.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResult.java index f22100b725..87d718b3e6 100644 --- a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResult.java +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResult.java @@ -6,7 +6,7 @@ import java.io.Serializable; @Data public class LoadTestReportResult implements Serializable { - private Long id; + private String id; private String reportId; diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResultExample.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResultExample.java index 8e4b6ae61a..93e8598bd8 100644 --- a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResultExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportResultExample.java @@ -114,52 +114,62 @@ public class LoadTestReportResultExample { return (Criteria) this; } - public Criteria andIdEqualTo(Long value) { + public Criteria andIdEqualTo(String value) { addCriterion("id =", value, "id"); return (Criteria) this; } - public Criteria andIdNotEqualTo(Long value) { + public Criteria andIdNotEqualTo(String value) { addCriterion("id <>", value, "id"); return (Criteria) this; } - public Criteria andIdGreaterThan(Long value) { + public Criteria andIdGreaterThan(String value) { addCriterion("id >", value, "id"); return (Criteria) this; } - public Criteria andIdGreaterThanOrEqualTo(Long value) { + public Criteria andIdGreaterThanOrEqualTo(String value) { addCriterion("id >=", value, "id"); return (Criteria) this; } - public Criteria andIdLessThan(Long value) { + public Criteria andIdLessThan(String value) { addCriterion("id <", value, "id"); return (Criteria) this; } - public Criteria andIdLessThanOrEqualTo(Long value) { + public Criteria andIdLessThanOrEqualTo(String value) { addCriterion("id <=", value, "id"); return (Criteria) this; } - public Criteria andIdIn(List values) { + public Criteria andIdLike(String value) { + addCriterion("id like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotLike(String value) { + addCriterion("id not like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { addCriterion("id in", values, "id"); return (Criteria) this; } - public Criteria andIdNotIn(List values) { + public Criteria andIdNotIn(List values) { addCriterion("id not in", values, "id"); return (Criteria) this; } - public Criteria andIdBetween(Long value1, Long value2) { + public Criteria andIdBetween(String value1, String value2) { addCriterion("id between", value1, value2, "id"); return (Criteria) this; } - public Criteria andIdNotBetween(Long value1, Long value2) { + public Criteria andIdNotBetween(String value1, String value2) { addCriterion("id not between", value1, value2, "id"); return (Criteria) this; } diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.java b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.java index d4cf9a43c2..2f8acb3ee4 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.java @@ -11,7 +11,7 @@ public interface LoadTestReportLogMapper { int deleteByExample(LoadTestReportLogExample example); - int deleteByPrimaryKey(Long id); + int deleteByPrimaryKey(String id); int insert(LoadTestReportLog record); @@ -21,7 +21,7 @@ public interface LoadTestReportLogMapper { List selectByExample(LoadTestReportLogExample example); - LoadTestReportLog selectByPrimaryKey(Long id); + LoadTestReportLog selectByPrimaryKey(String id); int updateByExampleSelective(@Param("record") LoadTestReportLog record, @Param("example") LoadTestReportLogExample example); diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.xml index 8e65669499..42d98045f8 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportLogMapper.xml @@ -2,7 +2,7 @@ - + @@ -103,17 +103,17 @@ order by ${orderByClause} - select , from load_test_report_log - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} - + delete from load_test_report_log - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} delete from load_test_report_log @@ -124,7 +124,7 @@ insert into load_test_report_log (id, report_id, resource_id, content) - values (#{id,jdbcType=BIGINT}, #{reportId,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, + values (#{id,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}) @@ -145,7 +145,7 @@ - #{id,jdbcType=BIGINT}, + #{id,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR}, @@ -168,7 +168,7 @@ update load_test_report_log - id = #{record.id,jdbcType=BIGINT}, + id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, @@ -186,7 +186,7 @@ update load_test_report_log - set id = #{record.id,jdbcType=BIGINT}, + set id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, resource_id = #{record.resourceId,jdbcType=VARCHAR}, content = #{record.content,jdbcType=LONGVARCHAR} @@ -196,7 +196,7 @@ update load_test_report_log - set id = #{record.id,jdbcType=BIGINT}, + set id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, resource_id = #{record.resourceId,jdbcType=VARCHAR} @@ -216,19 +216,19 @@ content = #{content,jdbcType=LONGVARCHAR}, - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} update load_test_report_log set report_id = #{reportId,jdbcType=VARCHAR}, resource_id = #{resourceId,jdbcType=VARCHAR}, content = #{content,jdbcType=LONGVARCHAR} - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} update load_test_report_log set report_id = #{reportId,jdbcType=VARCHAR}, resource_id = #{resourceId,jdbcType=VARCHAR} - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.java b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.java index c950f9e1f6..daa0907754 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.java @@ -11,7 +11,7 @@ public interface LoadTestReportResultMapper { int deleteByExample(LoadTestReportResultExample example); - int deleteByPrimaryKey(Long id); + int deleteByPrimaryKey(String id); int insert(LoadTestReportResult record); @@ -21,7 +21,7 @@ public interface LoadTestReportResultMapper { List selectByExample(LoadTestReportResultExample example); - LoadTestReportResult selectByPrimaryKey(Long id); + LoadTestReportResult selectByPrimaryKey(String id); int updateByExampleSelective(@Param("record") LoadTestReportResult record, @Param("example") LoadTestReportResultExample example); diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.xml index 2bc955d3cb..b7863f65ba 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportResultMapper.xml @@ -2,7 +2,7 @@ - + @@ -103,17 +103,17 @@ order by ${orderByClause} - select , from load_test_report_result - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} - + delete from load_test_report_result - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} delete from load_test_report_result @@ -124,7 +124,7 @@ insert into load_test_report_result (id, report_id, report_key, report_value) - values (#{id,jdbcType=BIGINT}, #{reportId,jdbcType=VARCHAR}, #{reportKey,jdbcType=VARCHAR}, + values (#{id,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR}, #{reportKey,jdbcType=VARCHAR}, #{reportValue,jdbcType=LONGVARCHAR}) @@ -145,7 +145,7 @@ - #{id,jdbcType=BIGINT}, + #{id,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR}, @@ -168,7 +168,7 @@ update load_test_report_result - id = #{record.id,jdbcType=BIGINT}, + id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, @@ -186,7 +186,7 @@ update load_test_report_result - set id = #{record.id,jdbcType=BIGINT}, + set id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, report_key = #{record.reportKey,jdbcType=VARCHAR}, report_value = #{record.reportValue,jdbcType=LONGVARCHAR} @@ -196,7 +196,7 @@ update load_test_report_result - set id = #{record.id,jdbcType=BIGINT}, + set id = #{record.id,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, report_key = #{record.reportKey,jdbcType=VARCHAR} @@ -216,19 +216,19 @@ report_value = #{reportValue,jdbcType=LONGVARCHAR}, - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} update load_test_report_result set report_id = #{reportId,jdbcType=VARCHAR}, report_key = #{reportKey,jdbcType=VARCHAR}, report_value = #{reportValue,jdbcType=LONGVARCHAR} - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} update load_test_report_result set report_id = #{reportId,jdbcType=VARCHAR}, report_key = #{reportKey,jdbcType=VARCHAR} - where id = #{id,jdbcType=BIGINT} + where id = #{id,jdbcType=VARCHAR} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.java index bb308447dd..3d6cbd4cb5 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.java @@ -1,6 +1,7 @@ package io.metersphere.base.mapper.ext; -import io.metersphere.controller.request.ReportRequest; +import io.metersphere.api.dto.APIReportResult; +import io.metersphere.api.dto.QueryAPIReportRequest; import io.metersphere.dto.ApiReportDTO; import org.apache.ibatis.annotations.Param; @@ -8,7 +9,8 @@ import java.util.List; public interface ExtApiTestReportMapper { - List getReportList(@Param("reportRequest") ReportRequest request); + List list(@Param("request") QueryAPIReportRequest request); + + List listByTestId(@Param("testId") String testId); - ApiReportDTO getReportTestAndProInfo(@Param("id") String id); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.xml index 30fe798301..0e0b8a0430 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestReportMapper.xml @@ -2,24 +2,43 @@ - + SELECT t.name, t.description, + r.id, r.test_id, r.create_time, r.update_time, r.status, + project.name AS project_name + FROM api_test_report r JOIN api_test t ON r.test_id = t.id + JOIN project ON project.id = t.project_id - - AND ltr.name like CONCAT('%', #{reportRequest.name},'%') + + AND r.name like CONCAT('%', #{request.name},'%') + + + AND project.id = #{request.projectId} + + + AND project.workspace_id = #{request.workspaceId,jdbcType=VARCHAR} + + ORDER BY r.update_time DESC + - + SELECT t.name, t.description, + r.id, r.test_id, r.create_time, r.update_time, r.status, + project.name AS project_name + FROM api_test_report r JOIN api_test t ON r.test_id = t.id + JOIN project ON project.id = t.project_id + + r.test_id = #{testId} + + ORDER BY r.update_time DESC \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/controller/ApiReportController.java b/backend/src/main/java/io/metersphere/controller/ApiReportController.java deleted file mode 100644 index 81ebc58a22..0000000000 --- a/backend/src/main/java/io/metersphere/controller/ApiReportController.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.metersphere.controller; - -import com.github.pagehelper.Page; -import com.github.pagehelper.PageHelper; -import io.metersphere.base.domain.ApiTestReport; -import io.metersphere.commons.constants.RoleConstants; -import io.metersphere.commons.utils.PageUtils; -import io.metersphere.commons.utils.Pager; -import io.metersphere.controller.request.ReportRequest; -import io.metersphere.dto.ApiReportDTO; -import io.metersphere.service.ApiReportService; -import io.metersphere.user.SessionUtils; -import org.apache.shiro.authz.annotation.Logical; -import org.apache.shiro.authz.annotation.RequiresRoles; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import java.util.List; - -@RestController -@RequestMapping(value = "/api/report") -public class ApiReportController { - - @Resource - private ApiReportService apiReportService; - - @GetMapping("/recent/{count}") - @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) - public List recentProjects(@PathVariable int count) { - String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); - ReportRequest request = new ReportRequest(); - request.setWorkspaceId(currentWorkspaceId); - PageHelper.startPage(1, count); - return apiReportService.getRecentReportList(request); - } - - @PostMapping("/list/all/{goPage}/{pageSize}") - public Pager> getReportList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ReportRequest request) { - Page page = PageHelper.startPage(goPage, pageSize, true); - return PageUtils.setPageInfo(page, apiReportService.getReportList(request)); - } - - @PostMapping("/delete/{reportId}") - public void deleteReport(@PathVariable String reportId) { - apiReportService.deleteReport(reportId); - } - - @GetMapping("/test/pro/info/{reportId}") - public ApiReportDTO getReportTestAndProInfo(@PathVariable String reportId) { - return apiReportService.getReportTestAndProInfo(reportId); - } - -} diff --git a/backend/src/main/java/io/metersphere/controller/TestResourcePoolController.java b/backend/src/main/java/io/metersphere/controller/TestResourcePoolController.java index 763b29fe5b..2ce7bf4ff2 100644 --- a/backend/src/main/java/io/metersphere/controller/TestResourcePoolController.java +++ b/backend/src/main/java/io/metersphere/controller/TestResourcePoolController.java @@ -3,11 +3,14 @@ package io.metersphere.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.metersphere.base.domain.TestResourcePool; +import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest; import io.metersphere.dto.TestResourcePoolDTO; import io.metersphere.service.TestResourcePoolService; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -15,6 +18,7 @@ import java.util.List; @RequestMapping("testresourcepool") @RestController +@RequiresRoles(RoleConstants.ADMIN) public class TestResourcePoolController { @Resource @@ -35,6 +39,11 @@ public class TestResourcePoolController { testResourcePoolService.updateTestResourcePool(testResourcePoolDTO); } + @GetMapping("/update/{poolId}/{status}") + public void updateTestResourcePoolStatus(@PathVariable String poolId, @PathVariable String status) { + testResourcePoolService.updateTestResourcePoolStatus(poolId, status); + } + @PostMapping("list/{goPage}/{pageSize}") public Pager> listResourcePools(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryResourcePoolRequest request) { Page page = PageHelper.startPage(goPage, pageSize, true); @@ -42,6 +51,7 @@ public class TestResourcePoolController { } @GetMapping("list/all/valid") + @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) public List listValidResourcePools() { return testResourcePoolService.listValidResourcePools(); } diff --git a/backend/src/main/java/io/metersphere/controller/UserRoleController.java b/backend/src/main/java/io/metersphere/controller/UserRoleController.java index 35a3645201..132f1ba5c7 100644 --- a/backend/src/main/java/io/metersphere/controller/UserRoleController.java +++ b/backend/src/main/java/io/metersphere/controller/UserRoleController.java @@ -27,7 +27,7 @@ public class UserRoleController { @GetMapping("/list/ws/{workspaceId}/{userId}") @RequiresRoles(value = {RoleConstants.ADMIN,RoleConstants.ORG_ADMIN}, logical = Logical.OR) - public List getWorkspaceMemberRole(@PathVariable String workspaceId, @PathVariable String userId) { + public List getWorkspaceMemberRoles(@PathVariable String workspaceId, @PathVariable String userId) { return userRoleService.getWorkspaceMemberRoles(workspaceId, userId); } } diff --git a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java index d40840e9ba..bdd60a871e 100644 --- a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java +++ b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java @@ -63,7 +63,7 @@ public class KubernetesTestEngine extends AbstractEngine { kubernetesProvider.confirmNamespace(context.getNamespace()); // create cm try (KubernetesClient client = kubernetesProvider.getKubernetesClient()) { - String configMapName = context.getTestId() + "-files"; + String configMapName = "jmeter-" + context.getTestId() + "-files"; ConfigMap configMap = client.configMaps().inNamespace(context.getNamespace()).withName(configMapName).get(); if (configMap == null) { ConfigMap item = new ConfigMap(); diff --git a/backend/src/main/java/io/metersphere/service/ApiReportService.java b/backend/src/main/java/io/metersphere/service/ApiReportService.java deleted file mode 100644 index fad1c1903c..0000000000 --- a/backend/src/main/java/io/metersphere/service/ApiReportService.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.metersphere.service; - -import io.metersphere.base.domain.ApiTestReport; -import io.metersphere.base.domain.ApiTestReportExample; -import io.metersphere.base.mapper.ApiTestReportMapper; -import io.metersphere.base.mapper.ext.ExtApiTestReportMapper; -import io.metersphere.controller.request.ReportRequest; -import io.metersphere.dto.ApiReportDTO; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.Resource; -import java.util.List; - -@Service -@Transactional(rollbackFor = Exception.class) -public class ApiReportService { - - @Resource - private ApiTestReportMapper ApiTestReportMapper; - @Resource - private ExtApiTestReportMapper extApiTestReportMapper; - - public List getRecentReportList(ReportRequest request) { - ApiTestReportExample example = new ApiTestReportExample(); - example.setOrderByClause("update_time desc"); - return ApiTestReportMapper.selectByExample(example); - } - - public List getReportList(ReportRequest request) { - return extApiTestReportMapper.getReportList(request); - } - - public void deleteReport(String reportId) { - ApiTestReportMapper.deleteByPrimaryKey(reportId); - } - - public ApiReportDTO getReportTestAndProInfo(String reportId) { - return extApiTestReportMapper.getReportTestAndProInfo(reportId); - } - -} diff --git a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java index 408d9d3717..8496274ee1 100644 --- a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java +++ b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java @@ -228,6 +228,7 @@ public class PerformanceTestService { List testResourceList = testResourceService.getResourcesByPoolId(resourcePoolId); testResourceList.forEach(r -> { LoadTestReportLog record = new LoadTestReportLog(); + record.setId(UUID.randomUUID().toString()); record.setReportId(testReport.getId()); record.setResourceId(r.getId()); record.setContent(StringUtils.EMPTY); diff --git a/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java b/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java index ce74519c38..0be3736ad5 100644 --- a/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java +++ b/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java @@ -72,6 +72,36 @@ public class TestResourcePoolService { testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool); } + + public void updateTestResourcePoolStatus(String poolId, String status) { + TestResourcePool testResourcePool = testResourcePoolMapper.selectByPrimaryKey(poolId); + if (testResourcePool == null) { + MSException.throwException("Resource Pool not found."); + } + testResourcePool.setUpdateTime(System.currentTimeMillis()); + testResourcePool.setStatus(status); + // 禁用资源池 + if (INVALID.name().equals(status)) { + testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool); + return; + } + TestResourcePoolDTO testResourcePoolDTO = new TestResourcePoolDTO(); + try { + BeanUtils.copyProperties(testResourcePoolDTO, testResourcePool); + TestResourceExample example2 = new TestResourceExample(); + example2.createCriteria().andTestResourcePoolIdEqualTo(poolId); + List testResources = testResourceMapper.selectByExampleWithBLOBs(example2); + testResourcePoolDTO.setResources(testResources); + if (validateTestResourcePool(testResourcePoolDTO)) { + testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool); + } else { + MSException.throwException("Resource Pool is invalid."); + } + } catch (IllegalAccessException | InvocationTargetException e) { + LogUtil.error(e); + } + } + public List listResourcePools(QueryResourcePoolRequest request) { TestResourcePoolExample example = new TestResourcePoolExample(); TestResourcePoolExample.Criteria criteria = example.createCriteria(); @@ -96,15 +126,14 @@ public class TestResourcePoolService { return testResourcePoolDTOS; } - private void validateTestResourcePool(TestResourcePoolDTO testResourcePool) { + private boolean validateTestResourcePool(TestResourcePoolDTO testResourcePool) { if (StringUtils.equalsIgnoreCase(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.name())) { - validateK8s(testResourcePool); - return; + return validateK8s(testResourcePool); } - validateNodes(testResourcePool); + return validateNodes(testResourcePool); } - private void validateNodes(TestResourcePoolDTO testResourcePool) { + private boolean validateNodes(TestResourcePoolDTO testResourcePool) { if (CollectionUtils.isEmpty(testResourcePool.getResources())) { MSException.throwException(Translator.get("no_nodes_message")); } @@ -121,19 +150,21 @@ public class TestResourcePoolService { MSException.throwException(Translator.get("duplicate_node_ip")); } testResourcePool.setStatus(VALID.name()); + boolean isValid = true; for (TestResource resource : testResourcePool.getResources()) { NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class); boolean isValidate = validateNode(nodeDTO); if (!isValidate) { testResourcePool.setStatus(ResourceStatusEnum.INVALID.name()); resource.setStatus(ResourceStatusEnum.INVALID.name()); + isValid = false; } else { resource.setStatus(VALID.name()); } resource.setTestResourcePoolId(testResourcePool.getId()); updateTestResource(resource); - } + return isValid; } private boolean validateNode(NodeDTO node) { @@ -145,7 +176,7 @@ public class TestResourcePoolService { } } - private void validateK8s(TestResourcePoolDTO testResourcePool) { + private boolean validateK8s(TestResourcePoolDTO testResourcePool) { if (CollectionUtils.isEmpty(testResourcePool.getResources()) || testResourcePool.getResources().size() != 1) { throw new RuntimeException(Translator.get("only_one_k8s")); @@ -153,18 +184,21 @@ public class TestResourcePoolService { TestResource testResource = testResourcePool.getResources().get(0); testResource.setTestResourcePoolId(testResourcePool.getId()); + boolean isValid; try { KubernetesProvider provider = new KubernetesProvider(testResource.getConfiguration()); provider.validateCredential(); testResource.setStatus(VALID.name()); testResourcePool.setStatus(VALID.name()); + isValid = true; } catch (Exception e) { testResource.setStatus(ResourceStatusEnum.INVALID.name()); testResourcePool.setStatus(ResourceStatusEnum.INVALID.name()); + isValid = false; } deleteTestResource(testResourcePool.getId()); updateTestResource(testResource); - + return isValid; } private void updateTestResource(TestResource testResource) { @@ -189,6 +223,10 @@ public class TestResourcePoolService { List testResourcePools = listResourcePools(request); // 重新校验 pool for (TestResourcePoolDTO pool : testResourcePools) { + // 手动设置成无效的, 排除 + if (INVALID.name().equals(pool.getStatus())) { + continue; + } try { updateTestResourcePool(pool); } catch (MSException e) { @@ -201,4 +239,5 @@ public class TestResourcePoolService { example.createCriteria().andStatusEqualTo(ResourceStatusEnum.VALID.name()); return testResourcePoolMapper.selectByExample(example); } + } diff --git a/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql b/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql index fc63f8c87b..0c7e549d04 100644 --- a/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql +++ b/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS `file_content` ( - `file_id` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT 'File ID', + `file_id` varchar(64) NOT NULL COMMENT 'File ID', `file` longblob COMMENT 'File content', PRIMARY KEY (`file_id`) ) @@ -70,29 +70,24 @@ CREATE TABLE IF NOT EXISTS `load_test_report_detail` ( DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; -CREATE TABLE IF NOT EXISTS `load_test_report_result` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `report_id` varchar(50) NOT NULL, - `report_key` varchar(64) DEFAULT NULL, - `report_value` text, - PRIMARY KEY (`id`), - KEY `load_test_report_result_report_id_report_key_index` (`report_id`,`report_key`) -) - ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_bin; - CREATE TABLE IF NOT EXISTS `load_test_report_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `report_id` varchar(50) NOT NULL, - `resource_id` varchar(50) DEFAULT NULL, - `content` longtext, + `id` varchar(50) NOT NULL, + `report_id` varchar(50) NOT NULL, + `resource_id` varchar(50) DEFAULT NULL, + `content` longtext , PRIMARY KEY (`id`), KEY `load_test_report_log_report_id_resource_name_index` (`report_id`,`resource_id`) -) - ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_bin; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +CREATE TABLE IF NOT EXISTS `load_test_report_result` ( + `id` varchar(50) NOT NULL, + `report_id` varchar(50) NOT NULL, + `report_key` varchar(64) DEFAULT NULL, + `report_value` text , + PRIMARY KEY (`id`), + KEY `load_test_report_result_report_id_report_key_index` (`report_id`,`report_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + CREATE TABLE IF NOT EXISTS `organization` ( `id` varchar(50) NOT NULL COMMENT 'Organization ID', @@ -145,7 +140,7 @@ CREATE TABLE IF NOT EXISTS `system_parameter` ( CREATE TABLE IF NOT EXISTS `test_resource` ( `id` varchar(50) NOT NULL COMMENT 'Test resource ID', - `test_resource_pool_id` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 'Test resource pool ID this test resource belongs to', + `test_resource_pool_id` varchar(50) NOT NULL COMMENT 'Test resource pool ID this test resource belongs to', `configuration` longtext COMMENT 'Test resource configuration', `status` varchar(64) NOT NULL COMMENT 'Test resource status', `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', @@ -217,13 +212,13 @@ CREATE TABLE IF NOT EXISTS `workspace` ( -- api start CREATE TABLE IF NOT EXISTS `api_test` ( - `id` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 'Test ID', - `project_id` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 'Project ID this test belongs to', - `name` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT 'Test name', - `description` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Test description', - `scenario_definition` longtext COLLATE utf8mb4_bin COMMENT 'Scenario definition (JSON format)', - `schedule` longtext COLLATE utf8mb4_bin COMMENT 'Test schedule (cron list)', - `status` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL, + `id` varchar(50) NOT NULL COMMENT 'Test ID', + `project_id` varchar(50) NOT NULL COMMENT 'Project ID this test belongs to', + `name` varchar(64) NOT NULL COMMENT 'Test name', + `description` varchar(255) DEFAULT NULL COMMENT 'Test description', + `scenario_definition` longtext COMMENT 'Scenario definition (JSON format)', + `schedule` longtext COMMENT 'Test schedule (cron list)', + `status` varchar(64) DEFAULT NULL, `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', `update_time` bigint(13) NOT NULL COMMENT 'Update timestamp', PRIMARY KEY (`id`) diff --git a/frontend/src/business/components/api/head/ApiHeaderMenus.vue b/frontend/src/business/components/api/head/ApiHeaderMenus.vue index 40101defe8..a13259966d 100644 --- a/frontend/src/business/components/api/head/ApiHeaderMenus.vue +++ b/frontend/src/business/components/api/head/ApiHeaderMenus.vue @@ -32,7 +32,7 @@ - + @@ -90,7 +90,7 @@ title: this.$t('report.recent'), url: "/api/report/recent/5", index: function (item) { - return '/api/report/view/' + item.id; + return '/api/report/view?id=' + item.id; } } } diff --git a/frontend/src/business/components/api/report/ApiReportList.vue b/frontend/src/business/components/api/report/ApiReportList.vue new file mode 100644 index 0000000000..791b19d2e8 --- /dev/null +++ b/frontend/src/business/components/api/report/ApiReportList.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/frontend/src/business/components/api/report/ApiTestReport.vue b/frontend/src/business/components/api/report/ApiTestReport.vue deleted file mode 100644 index f9ddf554e0..0000000000 --- a/frontend/src/business/components/api/report/ApiTestReport.vue +++ /dev/null @@ -1,169 +0,0 @@ - - - - - diff --git a/frontend/src/business/components/api/test/ApiTestConfig.vue b/frontend/src/business/components/api/test/ApiTestConfig.vue index 808b121781..4c981189f4 100644 --- a/frontend/src/business/components/api/test/ApiTestConfig.vue +++ b/frontend/src/business/components/api/test/ApiTestConfig.vue @@ -16,7 +16,7 @@ {{$t('commons.save')}} - + {{$t('load_test.save_and_run')}} {{$t('commons.cancel')}} @@ -42,6 +42,7 @@ data() { return { + create: false, result: {}, projects: [], change: false, @@ -65,8 +66,10 @@ this.projects = response.data; }) if (this.id) { + this.create = false; this.getTest(this.id); } else { + this.create = true; this.test = new Test(); if (this.$refs.config) { this.$refs.config.reset(); @@ -89,20 +92,28 @@ }); }, saveTest: function () { - this.change = false; - - this.result = this.$request(this.getOptions("/api/save"), response => { - this.test.id = response.data; + this.save(() => { this.$success(this.$t('commons.save_success')); + }) + }, + save: function (callback) { + this.change = false; + let url = this.create ? "/api/create" : "/api/update"; + this.result = this.$request(this.getOptions(url), response => { + this.create = false; + if (callback) callback(); }); }, runTest: function () { this.change = false; - this.result = this.$request(this.getOptions("/api/run"), response => { - this.test.id = response.data; + this.save(() => { this.$success(this.$t('commons.save_success')); - }); + this.result = this.$post("/api/run", {id: this.test.id}, response => { + this.$success(this.$t('api_test.running')); + }); + }) + }, cancel: function () { this.$router.push('/api/test/list/all'); @@ -123,7 +134,6 @@ let jmx = this.test.toJMX(); let blob = new Blob([jmx.xml], {type: "application/octet-stream"}); formData.append("files", new File([blob], jmx.name)); - console.log(jmx.xml) return { method: 'POST', diff --git a/frontend/src/business/components/api/test/ApiTestList.vue b/frontend/src/business/components/api/test/ApiTestList.vue index a27943be27..9506a556a1 100644 --- a/frontend/src/business/components/api/test/ApiTestList.vue +++ b/frontend/src/business/components/api/test/ApiTestList.vue @@ -6,7 +6,7 @@ - + diff --git a/frontend/src/business/components/api/test/model/JMX.js b/frontend/src/business/components/api/test/model/JMX.js index 9888ff102c..7297957f44 100644 --- a/frontend/src/business/components/api/test/model/JMX.js +++ b/frontend/src/business/components/api/test/model/JMX.js @@ -204,6 +204,39 @@ export class ThreadGroup extends DefaultTestElement { } } +export class PostThreadGroup extends DefaultTestElement { + constructor(testName) { + super('PostThreadGroup', 'PostThreadGroupGui', 'PostThreadGroup', testName || 'tearDown Thread Group'); + + this.intProp("ThreadGroup.num_threads", 1); + this.intProp("ThreadGroup.ramp_time", 1); + this.boolProp("ThreadGroup.scheduler", false); + this.stringProp("ThreadGroup.on_sample_error", "continue"); + + let loopAttrs = { + name: "ThreadGroup.main_controller", + elementType: "LoopController", + guiclass: "LoopControlPanel", + testclass: "LoopController", + testname: "Loop Controller", + enabled: "true" + }; + let loopController = this.add(new Element('elementProp', loopAttrs)); + loopController.boolProp('LoopController.continue_forever', false); + loopController.stringProp('LoopController.loops', 1); + } +} + +export class DebugSampler extends DefaultTestElement { + constructor(testName) { + super('DebugSampler', 'TestBeanGUI', 'DebugSampler', testName || 'Debug Sampler'); + + this.boolProp("displayJMeterProperties", false); + this.boolProp("displayJMeterVariables", true); + this.boolProp("displaySystemProperties", false); + } +} + export class HTTPSamplerProxy extends DefaultTestElement { constructor(testName, request) { super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName || 'HTTP Request'); @@ -219,37 +252,6 @@ export class HTTPSamplerProxy extends DefaultTestElement { this.stringProp("HTTPSampler.port", this.request.port); } } - - addRequestArguments(arg) { - if (arg instanceof HTTPSamplerArguments) { - this.add(arg); - } - } - - addRequestBody(body) { - if (body instanceof HTTPSamplerArguments) { - this.boolProp('HTTPSampler.postBodyRaw', true); - this.add(body); - } - } - - putRequestHeader(header) { - if (header instanceof HeaderManager) { - this.put(header); - } - } - - putResponseAssertion(assertion) { - if (assertion instanceof ResponseAssertion) { - this.put(assertion); - } - } - - putDurationAssertion(assertion) { - if (assertion instanceof DurationAssertion) { - this.put(assertion); - } - } } // 这是一个Element @@ -373,7 +375,9 @@ export class BackendListener extends DefaultTestElement { constructor(testName, className, args) { super('BackendListener', 'BackendListenerGui', 'BackendListener', testName || 'Backend Listener'); this.stringProp('classname', className); - this.add(new ElementArguments(args)); + if (args && args.length > 0) { + this.add(new ElementArguments(args)); + } } } @@ -397,3 +401,6 @@ export class ElementArguments extends Element { } } +export class Class { + +} diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index fd1803e47a..5d117138e9 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -5,6 +5,8 @@ import { TestElement, TestPlan, ThreadGroup, + PostThreadGroup, + DebugSampler, HeaderManager, HTTPSamplerArguments, ResponseCodeAssertion, @@ -13,9 +15,21 @@ import { BackendListener } from "./JMX"; -export const generateId = function () { - return Math.floor(Math.random() * 10000); -}; +export const uuid = function () { + let d = new Date().getTime() + let d2 = (performance && performance.now && (performance.now() * 1000)) || 0; + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = Math.random() * 16; + if (d > 0) { + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else { + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); +} export const BODY_TYPE = { KV: "KeyValue", @@ -75,7 +89,7 @@ export class Test extends BaseConfig { constructor(options) { super(); this.version = '1.0.0'; - this.id = null; + this.id = uuid(); this.name = null; this.projectId = null; this.scenarioDefinition = []; @@ -101,7 +115,7 @@ export class Test extends BaseConfig { export class Scenario extends BaseConfig { constructor(options) { super(); - this.id = generateId(); + this.id = uuid(); this.name = null; this.url = null; this.parameters = []; @@ -122,7 +136,7 @@ export class Scenario extends BaseConfig { export class Request extends BaseConfig { constructor(options) { super(); - this.id = generateId(); + this.id = uuid(); this.name = null; this.url = null; this.method = null; @@ -144,6 +158,10 @@ export class Request extends BaseConfig { options.assertions = new Assertions(options.assertions); return options; } + + isValid() { + return !!this.url && !!this.method + } } export class Body extends BaseConfig { @@ -304,14 +322,23 @@ class JMeterTestPlan extends Element { class JMXGenerator { constructor(test) { - if (!test || !test.id || !(test instanceof Test)) return; + if (!test || !(test instanceof Test)) return null; + + if (!test.id) { + test.id = "#NULL_TEST_ID#"; + } + const SPLIT = "@@:"; let testPlan = new TestPlan(test.name); test.scenarioDefinition.forEach(scenario => { - let threadGroup = new ThreadGroup(scenario.name); + let threadGroup = new ThreadGroup(scenario.name + SPLIT + scenario.id); scenario.requests.forEach(request => { - let httpSamplerProxy = new HTTPSamplerProxy(request.name, new JMXRequest(request)); + if (!request.isValid()) return; + + // test.id用于处理结果时区分属于哪个测试 + let name = request.name + SPLIT + test.id; + let httpSamplerProxy = new HTTPSamplerProxy(name, new JMXRequest(request)); this.addRequestHeader(httpSamplerProxy, request); @@ -326,8 +353,15 @@ class JMXGenerator { threadGroup.put(httpSamplerProxy); }) - this.addBackendListener(threadGroup, test.id); + this.addBackendListener(threadGroup); testPlan.put(threadGroup); + + // 暂时不加 + // let tearDownThreadGroup = new PostThreadGroup(); + // tearDownThreadGroup.put(new DebugSampler(test.id)); + // this.addBackendListener(tearDownThreadGroup); + // + // testPlan.put(tearDownThreadGroup); }) this.jmeterTestPlan = new JMeterTestPlan(); @@ -338,14 +372,14 @@ class JMXGenerator { let name = request.name + " Headers"; let headers = request.headers.filter(this.filter); if (headers.length > 0) { - httpSamplerProxy.putRequestHeader(new HeaderManager(name, headers)); + httpSamplerProxy.put(new HeaderManager(name, headers)); } } addRequestArguments(httpSamplerProxy, request) { let args = request.parameters.filter(this.filter) if (args.length > 0) { - httpSamplerProxy.addRequestArguments(new HTTPSamplerArguments(args)); + httpSamplerProxy.add(new HTTPSamplerArguments(args)); } } @@ -357,19 +391,20 @@ class JMXGenerator { body.push({name: '', value: request.body.raw}); } - httpSamplerProxy.addRequestBody(new HTTPSamplerArguments(body)); + httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true); + httpSamplerProxy.add(new HTTPSamplerArguments(body)); } addRequestAssertion(httpSamplerProxy, request) { let assertions = request.assertions; if (assertions.regex.length > 0) { assertions.regex.filter(this.filter).forEach(regex => { - httpSamplerProxy.putResponseAssertion(this.getAssertion(regex)); + httpSamplerProxy.put(this.getAssertion(regex)); }) } if (assertions.duration.isValid()) { - httpSamplerProxy.putDurationAssertion(assertions.duration.type, assertions.duration.value); + httpSamplerProxy.put(assertions.duration.type, assertions.duration.value); } } @@ -387,11 +422,10 @@ class JMXGenerator { } } - addBackendListener(threadGroup, testId) { + addBackendListener(threadGroup) { let testName = 'API Backend Listener'; let className = 'io.metersphere.api.jmeter.APIBackendListenerClient'; - let args = [{name: 'id', value: testId}]; - threadGroup.put(new BackendListener(testName, className, args)); + threadGroup.put(new BackendListener(testName, className)); } filter(config) { diff --git a/frontend/src/business/components/common/components/MsRolesTag.vue b/frontend/src/business/components/common/components/MsRolesTag.vue new file mode 100644 index 0000000000..a51f13c988 --- /dev/null +++ b/frontend/src/business/components/common/components/MsRolesTag.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/src/business/components/common/components/MsTipButton.vue b/frontend/src/business/components/common/components/MsTipButton.vue index b2fbe799f8..c9abd6055f 100644 --- a/frontend/src/business/components/common/components/MsTipButton.vue +++ b/frontend/src/business/components/common/components/MsTipButton.vue @@ -6,6 +6,7 @@ :effect="effect"> diff --git a/frontend/src/business/components/project/MsProject.vue b/frontend/src/business/components/project/MsProject.vue index ae903fcc7f..540944a9fa 100644 --- a/frontend/src/business/components/project/MsProject.vue +++ b/frontend/src/business/components/project/MsProject.vue @@ -31,7 +31,7 @@