生成report
This commit is contained in:
parent
94b7ceb1e9
commit
39aed31aa5
|
@ -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<APIReportResult> 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<APIReportResult>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPIReportRequest request) {
|
||||
Page<Object> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<APITestResult> 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<MultipartFile> files) {
|
||||
return apiTestService.save(request, files);
|
||||
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
|
||||
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List<MultipartFile> files) {
|
||||
apiTestService.create(request, files);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List<MultipartFile> 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<MultipartFile> files) {
|
||||
return apiTestService.run(request, files);
|
||||
@PostMapping(value = "/run")
|
||||
public void run(@RequestBody SaveAPITestRequest request) {
|
||||
apiTestService.run(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class DeleteAPIReportRequest {
|
||||
|
||||
private String id;
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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<String, List<SampleResult>> queue = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void handleSampleResults(List<SampleResult> 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<SampleResult> 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<String, ScenarioResult> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<ResponseAssertionResult> assertions = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -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> 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;
|
||||
}
|
||||
}
|
|
@ -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<ScenarioResult> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<APIReportResult> list(QueryAPIReportRequest request) {
|
||||
return extApiTestReportMapper.list(request);
|
||||
}
|
||||
|
||||
public List<APIReportResult> recentTest(QueryAPIReportRequest request) {
|
||||
request.setRecent(true);
|
||||
return extApiTestReportMapper.list(request);
|
||||
}
|
||||
|
||||
public ApiTestReport get(String id) {
|
||||
return apiTestReportMapper.selectByPrimaryKey(id);
|
||||
}
|
||||
|
||||
public List<APIReportResult> 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);
|
||||
}
|
||||
}
|
|
@ -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<MultipartFile> files) {
|
||||
public void create(SaveAPITestRequest request, List<MultipartFile> 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<MultipartFile> 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<MultipartFile> 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<MultipartFile> 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);
|
|
@ -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<ApiReportDTO> getReportList(@Param("reportRequest") ReportRequest request);
|
||||
List<APIReportResult> list(@Param("request") QueryAPIReportRequest request);
|
||||
|
||||
List<APIReportResult> listByTestId(@Param("testId") String testId);
|
||||
|
||||
ApiReportDTO getReportTestAndProInfo(@Param("id") String id);
|
||||
}
|
||||
|
|
|
@ -2,24 +2,43 @@
|
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="io.metersphere.base.mapper.ext.ExtApiTestReportMapper">
|
||||
|
||||
<select id="getReportList" resultType="io.metersphere.dto.ApiReportDTO">
|
||||
select ltr.id, ltr.name, ltr.test_id as testId, ltr.description,
|
||||
ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, lt.name as testName
|
||||
from api_test_report ltr left join api_test lt on ltr.test_id = lt.id
|
||||
<resultMap id="BaseResultMap" type="io.metersphere.api.dto.APIReportResult"
|
||||
extends="io.metersphere.base.mapper.ApiTestReportMapper.BaseResultMap">
|
||||
<result column="project_name" property="projectName"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="list" resultMap="BaseResultMap">
|
||||
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
|
||||
<where>
|
||||
<if test="reportRequest.name != null">
|
||||
AND ltr.name like CONCAT('%', #{reportRequest.name},'%')
|
||||
<if test="request.name != null">
|
||||
AND r.name like CONCAT('%', #{request.name},'%')
|
||||
</if>
|
||||
<if test="request.projectId != null">
|
||||
AND project.id = #{request.projectId}
|
||||
</if>
|
||||
<if test="request.workspaceId != null">
|
||||
AND project.workspace_id = #{request.workspaceId,jdbcType=VARCHAR}
|
||||
</if>
|
||||
</where>
|
||||
<if test="request.recent">
|
||||
ORDER BY r.update_time DESC
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="getReportTestAndProInfo" resultType="io.metersphere.dto.ApiReportDTO">
|
||||
select ltr.id, ltr.name, ltr.test_id as testId, ltr.description,
|
||||
ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, ltr.content as content,
|
||||
lt.name as testName,
|
||||
p.id as projectId, p.name as projectName
|
||||
from api_test_report ltr left join api_test lt on ltr.test_id = lt.id left join project p on lt.project_id = p.id
|
||||
where ltr.id = #{id}
|
||||
<select id="listByTestId" resultMap="BaseResultMap">
|
||||
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
|
||||
<where>
|
||||
r.test_id = #{testId}
|
||||
</where>
|
||||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -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<ApiTestReport> 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<List<ApiReportDTO>> getReportList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ReportRequest request) {
|
||||
Page<Object> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ApiTestReport> getRecentReportList(ReportRequest request) {
|
||||
ApiTestReportExample example = new ApiTestReportExample();
|
||||
example.setOrderByClause("update_time desc");
|
||||
return ApiTestReportMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
public List<ApiReportDTO> getReportList(ReportRequest request) {
|
||||
return extApiTestReportMapper.getReportList(request);
|
||||
}
|
||||
|
||||
public void deleteReport(String reportId) {
|
||||
ApiTestReportMapper.deleteByPrimaryKey(reportId);
|
||||
}
|
||||
|
||||
public ApiReportDTO getReportTestAndProInfo(String reportId) {
|
||||
return extApiTestReportMapper.getReportTestAndProInfo(reportId);
|
||||
}
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@
|
|||
<template v-slot:title>{{$t('commons.report')}}</template>
|
||||
<ms-recent-list :options="reportRecent"/>
|
||||
<el-divider/>
|
||||
<ms-show-all :index="'/api/report/all'"/>
|
||||
<ms-show-all :index="'/api/report/list/all'"/>
|
||||
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<div class="container" v-loading="result.loading">
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<template v-slot:header>
|
||||
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
|
||||
:show-create="false"/>
|
||||
</template>
|
||||
<el-table :data="tableData" class="table-content">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('commons.name')"
|
||||
width="150"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
:label="$t('commons.description')"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="250"
|
||||
:label="$t('commons.create_time')">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="250"
|
||||
:label="$t('commons.update_time')">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="150"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleView(scope.row)" type="primary" icon="el-icon-s-data" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsTablePagination from "../../common/pagination/TablePagination";
|
||||
import MsTableHeader from "../../common/components/MsTableHeader";
|
||||
|
||||
export default {
|
||||
components: {MsTableHeader, MsTablePagination},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
condition: {name: ""},
|
||||
projectId: null,
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next(self => {
|
||||
self.testId = to.params.testId;
|
||||
self.search();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
let param = {
|
||||
name: this.condition.name,
|
||||
};
|
||||
|
||||
if (this.testId !== 'all') {
|
||||
param.testId = this.testId;
|
||||
}
|
||||
|
||||
let url = "/api/report/list/" + this.currentPage + "/" + this.pageSize
|
||||
this.result = this.$post(url, param, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
});
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleView(report) {
|
||||
this.$router.push({
|
||||
path: '/api/report/view/' + report.id,
|
||||
})
|
||||
},
|
||||
handleDelete(report) {
|
||||
this.$alert(this.$t('load_test.delete_confirm') + report.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this.result = this.$post("/api/report/delete", {id: report.id}, () => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-content {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,169 +0,0 @@
|
|||
<template>
|
||||
<div class="container" v-loading="result.loading">
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<template v-slot:header>
|
||||
<el-row type="flex" justify="space-between" align="middle">
|
||||
<span class="title">{{$t('commons.report')}}</span>
|
||||
<span class="search">
|
||||
<el-input type="text" size="small" :placeholder="$t('report.search_by_name')"
|
||||
prefix-icon="el-icon-search"
|
||||
maxlength="60"
|
||||
v-model="condition" @change="search" clearable/>
|
||||
</span>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table :data="tableData" class="test-content">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('commons.name')"
|
||||
width="150"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
:label="$t('commons.description')"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="testName"
|
||||
:label="$t('report.test_name')"
|
||||
width="150"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="250"
|
||||
:label="$t('commons.create_time')">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="250"
|
||||
:label="$t('commons.update_time')">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
width="150"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleEdit(scope.row)" type="primary" icon="el-icon-edit" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<div class="table-page">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[5, 10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ApiTestReport",
|
||||
created: function () {
|
||||
this.initTableData();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
queryPath: "/api/report/list/all",
|
||||
deletePath: "/api/report/delete/",
|
||||
condition: "",
|
||||
projectId: null,
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loading: false,
|
||||
testId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initTableData() {
|
||||
let param = {
|
||||
name: this.condition,
|
||||
};
|
||||
this.result = this.$post(this.buildPagePath(this.queryPath), param, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.initTableData();
|
||||
},
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.initTableData();
|
||||
},
|
||||
handleCurrentChange(current) {
|
||||
this.currentPage = current;
|
||||
this.initTableData();
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(report) {
|
||||
this.$router.push({
|
||||
path: '/api/report/view/' + report.id
|
||||
})
|
||||
},
|
||||
handleDelete(report) {
|
||||
this.$alert(this.$t('load_test.delete_confirm') + report.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(report);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(report) {
|
||||
this.result = this.$post(this.deletePath + report.id, {}, () => {
|
||||
this.$message({
|
||||
message: this.$t('commons.delete_success'),
|
||||
type: 'success'
|
||||
});
|
||||
this.initTableData();
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-page {
|
||||
padding-top: 20px;
|
||||
margin-right: -9px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -16,7 +16,7 @@
|
|||
{{$t('commons.save')}}
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" plain :disabled="isDisabled" @click="runTest">
|
||||
<el-button type="primary" plain @click="runTest">
|
||||
{{$t('load_test.save_and_run')}}
|
||||
</el-button>
|
||||
<el-button type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
|
||||
|
@ -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',
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
|
||||
@create="create" :createTip="$t('load_test.create')"/>
|
||||
</template>
|
||||
<el-table :data="tableData" class="test-content">
|
||||
<el-table :data="tableData" class="table-content">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('commons.name')"
|
||||
|
@ -70,8 +70,7 @@
|
|||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loading: false,
|
||||
testId: null,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -128,7 +127,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-content {
|
||||
.table-content {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -16,7 +16,6 @@ import PersonSetting from "../../settings/personal/PersonSetting";
|
|||
import SystemWorkspace from "../../settings/system/SystemWorkspace";
|
||||
import PerformanceChart from "../../performance/report/components/PerformanceChart";
|
||||
import PerformanceTestReport from "../../performance/report/PerformanceTestReport";
|
||||
import ApiTestReport from "../../api/report/ApiTestReport";
|
||||
import ApiTest from "../../api/ApiTest";
|
||||
import PerformanceTest from "../../performance/PerformanceTest";
|
||||
import ApiTestConfig from "../../api/test/ApiTestConfig";
|
||||
|
@ -30,6 +29,7 @@ import TestPlan from "../../track/plan/TestPlan";
|
|||
import TestPlanView from "../../track/plan/view/TestPlanView";
|
||||
import TestCase from "../../track/case/TestCase";
|
||||
import TestTrack from "../../track/TestTrack";
|
||||
import ApiReportList from "../../api/report/ApiReportList";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
@ -117,13 +117,13 @@ const router = new VueRouter({
|
|||
component: MsProject
|
||||
},
|
||||
{
|
||||
path: "report/:type",
|
||||
name: "fucReport",
|
||||
component: ApiTestReport
|
||||
path: "report/list/:testId",
|
||||
name: "ApiReportList",
|
||||
component: ApiReportList
|
||||
},
|
||||
{
|
||||
path: "report/view/:reportId",
|
||||
name: "fucReportView",
|
||||
name: "ApiReportView",
|
||||
component: ApiReportView
|
||||
}
|
||||
]
|
||||
|
|
|
@ -176,6 +176,7 @@ export default {
|
|||
},
|
||||
api_test: {
|
||||
save_and_run: "保存并执行",
|
||||
running: "正在执行",
|
||||
reset: "重置",
|
||||
input_name: "请输入测试名称",
|
||||
select_project: "请选择项目",
|
||||
|
|
Loading…
Reference in New Issue