Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
8c7f60b88d
|
@ -16,6 +16,7 @@ public class TestPlanScenarioRequest {
|
||||||
private String moduleId;
|
private String moduleId;
|
||||||
private List<String> moduleIds;
|
private List<String> moduleIds;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String status;
|
||||||
private String workspaceId;
|
private String workspaceId;
|
||||||
private String userId;
|
private String userId;
|
||||||
private String planId;
|
private String planId;
|
||||||
|
|
|
@ -37,4 +37,6 @@ public class ApiDefinitionBatchProcessingRequest {
|
||||||
|
|
||||||
private List<String> dataIds;
|
private List<String> dataIds;
|
||||||
|
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class MsTCPSampler extends MsTestElement {
|
||||||
samplerHashTree.add(tcpConfig());
|
samplerHashTree.add(tcpConfig());
|
||||||
tree.set(tcpSampler(config), samplerHashTree);
|
tree.set(tcpSampler(config), samplerHashTree);
|
||||||
setUserParameters(samplerHashTree);
|
setUserParameters(samplerHashTree);
|
||||||
if (tcpPreProcessor != null) {
|
if (tcpPreProcessor != null && StringUtils.isNotBlank(tcpPreProcessor.getScript())) {
|
||||||
samplerHashTree.add(tcpPreProcessor.getJSR223PreProcessor());
|
samplerHashTree.add(tcpPreProcessor.getJSR223PreProcessor());
|
||||||
}
|
}
|
||||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||||
|
|
|
@ -487,7 +487,7 @@ public class ApiDefinitionService {
|
||||||
public void editApiByParam(ApiBatchRequest request) {
|
public void editApiByParam(ApiBatchRequest request) {
|
||||||
List<String> ids = request.getIds();
|
List<String> ids = request.getIds();
|
||||||
if (request.isSelectAllDate()) {
|
if (request.isSelectAllDate()) {
|
||||||
ids = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds());
|
ids = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds(),request.getProtocol());
|
||||||
}
|
}
|
||||||
//name在这里只是查询参数
|
//name在这里只是查询参数
|
||||||
request.setName(null);
|
request.setName(null);
|
||||||
|
@ -551,20 +551,21 @@ public class ApiDefinitionService {
|
||||||
public void deleteByParams(ApiDefinitionBatchProcessingRequest request) {
|
public void deleteByParams(ApiDefinitionBatchProcessingRequest request) {
|
||||||
List<String> apiIds = request.getDataIds();
|
List<String> apiIds = request.getDataIds();
|
||||||
if (request.isSelectAllDate()) {
|
if (request.isSelectAllDate()) {
|
||||||
apiIds = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds());
|
apiIds = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds(),request.getProtocol());
|
||||||
}
|
}
|
||||||
ApiDefinitionExample example = new ApiDefinitionExample();
|
ApiDefinitionExample example = new ApiDefinitionExample();
|
||||||
example.createCriteria().andIdIn(apiIds);
|
example.createCriteria().andIdIn(apiIds);
|
||||||
apiDefinitionMapper.deleteByExample(example);
|
apiDefinitionMapper.deleteByExample(example);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getAllApiIdsByFontedSelect(Map<String, List<String>> filters, String name, List<String> moduleIds, String projectId, List<String> unSelectIds) {
|
private List<String> getAllApiIdsByFontedSelect(Map<String, List<String>> filters, String name, List<String> moduleIds, String projectId, List<String> unSelectIds,String protocol) {
|
||||||
ApiDefinitionRequest request = new ApiDefinitionRequest();
|
ApiDefinitionRequest request = new ApiDefinitionRequest();
|
||||||
request.setFilters(filters);
|
request.setFilters(filters);
|
||||||
request.setName(name);
|
request.setName(name);
|
||||||
request.setModuleIds(moduleIds);
|
request.setModuleIds(moduleIds);
|
||||||
request.setProjectId(projectId);
|
request.setProjectId(projectId);
|
||||||
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||||
|
request.setProtocol(protocol);
|
||||||
List<ApiDefinitionResult> resList = extApiDefinitionMapper.list(request);
|
List<ApiDefinitionResult> resList = extApiDefinitionMapper.list(request);
|
||||||
List<String> ids = new ArrayList<>(0);
|
List<String> ids = new ArrayList<>(0);
|
||||||
if (!resList.isEmpty()) {
|
if (!resList.isEmpty()) {
|
||||||
|
@ -577,7 +578,7 @@ public class ApiDefinitionService {
|
||||||
public void removeToGcByParams(ApiDefinitionBatchProcessingRequest request) {
|
public void removeToGcByParams(ApiDefinitionBatchProcessingRequest request) {
|
||||||
List<String> apiIds = request.getDataIds();
|
List<String> apiIds = request.getDataIds();
|
||||||
if (request.isSelectAllDate()) {
|
if (request.isSelectAllDate()) {
|
||||||
apiIds = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds());
|
apiIds = this.getAllApiIdsByFontedSelect(request.getFilters(), request.getName(), request.getModuleIds(), request.getProjectId(), request.getUnSelectIds(),request.getProtocol());
|
||||||
}
|
}
|
||||||
extApiDefinitionMapper.removeToGc(apiIds);
|
extApiDefinitionMapper.removeToGc(apiIds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,17 @@
|
||||||
<when test="request.status == 'Trash'">
|
<when test="request.status == 'Trash'">
|
||||||
and a.status = 'Trash'
|
and a.status = 'Trash'
|
||||||
</when>
|
</when>
|
||||||
<otherwise>
|
<when test="request.status == null">
|
||||||
and a.status != 'Trash'
|
and a.status != 'Trash'
|
||||||
|
</when>
|
||||||
|
<when test="request.status == ''">
|
||||||
|
and a.status != 'Trash'
|
||||||
|
</when>
|
||||||
|
<when test="request.status == 'running'">
|
||||||
|
and t.status IS NULL
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
and t.status = #{request.status}
|
||||||
</otherwise>
|
</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
where 1
|
where 1
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
<if test="request.name != null">
|
<if test="request.name != null">
|
||||||
and (lt.name like CONCAT('%', #{request.name},'%') or lt.num like CONCAT('%', #{request.name},'%'))
|
and (lt.name like CONCAT('%', #{request.name},'%') or lt.num like CONCAT('%', #{request.name},'%'))
|
||||||
</if>
|
</if>
|
||||||
|
<if test="request.status != null">
|
||||||
|
and tplc.status like CONCAT('%', #{request.status},'%')
|
||||||
|
</if>
|
||||||
<if test="request.filters != null and request.filters.size() > 0">
|
<if test="request.filters != null and request.filters.size() > 0">
|
||||||
<foreach collection="request.filters.entrySet()" index="key" item="values">
|
<foreach collection="request.filters.entrySet()" index="key" item="values">
|
||||||
<if test="values != null and values.size() > 0">
|
<if test="values != null and values.size() > 0">
|
||||||
|
|
|
@ -46,6 +46,11 @@
|
||||||
<if test="request.name != null and request.name!=''">
|
<if test="request.name != null and request.name!=''">
|
||||||
and c.name like CONCAT('%', #{request.name},'%')
|
and c.name like CONCAT('%', #{request.name},'%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="request.status != null and request.status!=''">
|
||||||
|
and t.last_result like CONCAT('%', #{request.status},'%')
|
||||||
|
</if>
|
||||||
|
|
||||||
|
|
||||||
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
|
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
|
||||||
and c.api_scenario_module_id in
|
and c.api_scenario_module_id in
|
||||||
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
|
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.metersphere.service.CheckPermissionService;
|
||||||
import io.metersphere.track.dto.TestCaseNodeDTO;
|
import io.metersphere.track.dto.TestCaseNodeDTO;
|
||||||
import io.metersphere.track.request.testcase.DragNodeRequest;
|
import io.metersphere.track.request.testcase.DragNodeRequest;
|
||||||
import io.metersphere.track.request.testcase.QueryNodeRequest;
|
import io.metersphere.track.request.testcase.QueryNodeRequest;
|
||||||
|
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
|
||||||
import io.metersphere.track.service.TestCaseNodeService;
|
import io.metersphere.track.service.TestCaseNodeService;
|
||||||
import org.apache.shiro.authz.annotation.Logical;
|
import org.apache.shiro.authz.annotation.Logical;
|
||||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||||
|
@ -47,6 +48,15 @@ public class TestCaseNodeController {
|
||||||
return testCaseNodeService.getNodeByPlanId(planId);
|
return testCaseNodeService.getNodeByPlanId(planId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list/plan/{planId}/{runResult}")
|
||||||
|
public List<TestCaseNodeDTO> getNodeByPlanIdAndRunResult(@PathVariable String planId,@PathVariable String runResult) {
|
||||||
|
checkPermissionService.checkTestPlanOwner(planId);
|
||||||
|
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
|
||||||
|
request.setPlanId(planId);
|
||||||
|
request.setStatus(runResult);
|
||||||
|
return testCaseNodeService.getNodeByQueryRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list/review/{reviewId}")
|
@GetMapping("/list/review/{reviewId}")
|
||||||
public List<TestCaseNodeDTO> getNodeByReviewId(@PathVariable String reviewId) {
|
public List<TestCaseNodeDTO> getNodeByReviewId(@PathVariable String reviewId) {
|
||||||
checkPermissionService.checkTestReviewOwner(reviewId);
|
checkPermissionService.checkTestReviewOwner(reviewId);
|
||||||
|
|
|
@ -15,12 +15,14 @@ import java.util.List;
|
||||||
@Setter
|
@Setter
|
||||||
public class TestPlanReportDTO {
|
public class TestPlanReportDTO {
|
||||||
private String id;
|
private String id;
|
||||||
|
private String testPlanId;
|
||||||
private String name;
|
private String name;
|
||||||
private String testPlanName;
|
private String testPlanName;
|
||||||
private String creator;
|
private String creator;
|
||||||
private long createTime;
|
private long createTime;
|
||||||
private String triggerMode;
|
private String triggerMode;
|
||||||
private String status;
|
private String status;
|
||||||
|
private String reportComponents;
|
||||||
|
|
||||||
private TestCaseReportAdvanceStatusResultDTO executeResult;
|
private TestCaseReportAdvanceStatusResultDTO executeResult;
|
||||||
private List<TestCaseReportModuleResultDTO> moduleExecuteResult;
|
private List<TestCaseReportModuleResultDTO> moduleExecuteResult;
|
||||||
|
|
|
@ -14,6 +14,7 @@ public class LoadCaseRequest extends TestPlanLoadCase {
|
||||||
private String projectId;
|
private String projectId;
|
||||||
private List<String> caseIds;
|
private List<String> caseIds;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String status;
|
||||||
private Map<String, List<String>> filters;
|
private Map<String, List<String>> filters;
|
||||||
private List<OrderRequest> orders;
|
private List<OrderRequest> orders;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.metersphere.base.domain.*;
|
||||||
import io.metersphere.base.mapper.*;
|
import io.metersphere.base.mapper.*;
|
||||||
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
||||||
import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper;
|
import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper;
|
||||||
|
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
|
||||||
import io.metersphere.commons.constants.TestCaseConstants;
|
import io.metersphere.commons.constants.TestCaseConstants;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
import io.metersphere.exception.ExcelException;
|
import io.metersphere.exception.ExcelException;
|
||||||
|
@ -13,9 +14,11 @@ import io.metersphere.i18n.Translator;
|
||||||
import io.metersphere.service.NodeTreeService;
|
import io.metersphere.service.NodeTreeService;
|
||||||
import io.metersphere.track.dto.TestCaseDTO;
|
import io.metersphere.track.dto.TestCaseDTO;
|
||||||
import io.metersphere.track.dto.TestCaseNodeDTO;
|
import io.metersphere.track.dto.TestCaseNodeDTO;
|
||||||
|
import io.metersphere.track.dto.TestPlanCaseDTO;
|
||||||
import io.metersphere.track.request.testcase.DragNodeRequest;
|
import io.metersphere.track.request.testcase.DragNodeRequest;
|
||||||
import io.metersphere.track.request.testcase.QueryNodeRequest;
|
import io.metersphere.track.request.testcase.QueryNodeRequest;
|
||||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||||
|
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.ibatis.session.ExecutorType;
|
import org.apache.ibatis.session.ExecutorType;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
|
@ -43,6 +46,8 @@ public class TestCaseNodeService extends NodeTreeService<TestCaseNodeDTO> {
|
||||||
@Resource
|
@Resource
|
||||||
TestPlanTestCaseMapper testPlanTestCaseMapper;
|
TestPlanTestCaseMapper testPlanTestCaseMapper;
|
||||||
@Resource
|
@Resource
|
||||||
|
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
|
||||||
|
@Resource
|
||||||
ExtTestCaseMapper extTestCaseMapper;
|
ExtTestCaseMapper extTestCaseMapper;
|
||||||
@Resource
|
@Resource
|
||||||
SqlSessionFactory sqlSessionFactory;
|
SqlSessionFactory sqlSessionFactory;
|
||||||
|
@ -136,6 +141,32 @@ public class TestCaseNodeService extends NodeTreeService<TestCaseNodeDTO> {
|
||||||
return testCaseNodeMapper.deleteByExample(testCaseNodeExample);
|
return testCaseNodeMapper.deleteByExample(testCaseNodeExample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前计划下
|
||||||
|
* 有关联数据的节点
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return List<TestCaseNodeDTO>
|
||||||
|
*/
|
||||||
|
public List<TestCaseNodeDTO> getNodeByQueryRequest(QueryTestPlanCaseRequest request) {
|
||||||
|
|
||||||
|
List<TestCaseNodeDTO> list = new ArrayList<>();
|
||||||
|
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(request.getPlanId());
|
||||||
|
projectIds.forEach(id -> {
|
||||||
|
Project project = projectMapper.selectByPrimaryKey(id);
|
||||||
|
String name = project.getName();
|
||||||
|
List<TestCaseNodeDTO> nodeList = getNodeDTO(id, request);
|
||||||
|
TestCaseNodeDTO testCaseNodeDTO = new TestCaseNodeDTO();
|
||||||
|
testCaseNodeDTO.setId(project.getId());
|
||||||
|
testCaseNodeDTO.setName(name);
|
||||||
|
testCaseNodeDTO.setLabel(name);
|
||||||
|
testCaseNodeDTO.setChildren(nodeList);
|
||||||
|
list.add(testCaseNodeDTO);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前计划下
|
* 获取当前计划下
|
||||||
* 有关联数据的节点
|
* 有关联数据的节点
|
||||||
|
@ -187,6 +218,37 @@ public class TestCaseNodeService extends NodeTreeService<TestCaseNodeDTO> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<TestCaseNodeDTO> getNodeDTO(String projectId, QueryTestPlanCaseRequest request) {
|
||||||
|
List<TestPlanCaseDTO> testPlanTestCases = extTestPlanTestCaseMapper.listByPlanId(request);
|
||||||
|
if (testPlanTestCases.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
|
||||||
|
|
||||||
|
List<String> caseIds = testPlanTestCases.stream()
|
||||||
|
.map(TestPlanCaseDTO::getCaseId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
TestCaseExample testCaseExample = new TestCaseExample();
|
||||||
|
testCaseExample.createCriteria().andIdIn(caseIds);
|
||||||
|
List<String> dataNodeIds = testCaseMapper.selectByExample(testCaseExample).stream()
|
||||||
|
.map(TestCase::getNodeId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(testCaseNodes);
|
||||||
|
|
||||||
|
Iterator<TestCaseNodeDTO> iterator = nodeTrees.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
TestCaseNodeDTO rootNode = iterator.next();
|
||||||
|
if (pruningTree(rootNode, dataNodeIds)) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeTrees;
|
||||||
|
}
|
||||||
|
|
||||||
private List<TestCaseNodeDTO> getNodeDTO(String projectId, String planId) {
|
private List<TestCaseNodeDTO> getNodeDTO(String projectId, String planId) {
|
||||||
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
|
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
|
||||||
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId);
|
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId);
|
||||||
|
|
|
@ -27,6 +27,7 @@ import io.metersphere.track.request.report.QueryTestPlanReportRequest;
|
||||||
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
|
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
|
||||||
import io.metersphere.track.request.testplan.LoadCaseRequest;
|
import io.metersphere.track.request.testplan.LoadCaseRequest;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.python.bouncycastle.pqc.math.linearalgebra.IntUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@ -194,6 +195,12 @@ public class TestPlanReportService {
|
||||||
returnDTO.setProjectName(testProject);
|
returnDTO.setProjectName(testProject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
returnDTO.setId(report.getId());
|
||||||
|
returnDTO.setName(report.getName());
|
||||||
|
returnDTO.setStartTime(report.getStartTime());
|
||||||
|
returnDTO.setEndTime(report.getEndTime());
|
||||||
|
returnDTO.setTestPlanId(report.getTestPlanId());
|
||||||
|
returnDTO.setReportComponents(report.getComponents());
|
||||||
return returnDTO;
|
return returnDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,21 +234,33 @@ public class TestPlanReportService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if(StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(),triggerMode)){
|
}else if(StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(),triggerMode)){
|
||||||
issuesInfo = ReportTriggerMode.TEST_PLAN_SCHEDULE.name();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testPlanReport.setEndTime(System.currentTimeMillis());
|
testPlanReport.setEndTime(System.currentTimeMillis());
|
||||||
testPlanReport.setUpdateTime(System.currentTimeMillis());
|
testPlanReport.setUpdateTime(System.currentTimeMillis());
|
||||||
|
|
||||||
//手动触发的需要保存手工执行的信息
|
//手动触发的需要保存手工执行的信息
|
||||||
|
int [] componentIndexArr = null;
|
||||||
|
if(StringUtils.equals(ReportTriggerMode.MANUAL.name(),triggerMode)){
|
||||||
|
componentIndexArr = new int[]{1,2,3,4,5};
|
||||||
|
}else {
|
||||||
|
componentIndexArr = new int[]{1,3,4};
|
||||||
|
}
|
||||||
|
testPlanReport.setComponents(JSONArray.toJSONString(componentIndexArr));
|
||||||
|
|
||||||
JSONObject content = JSONObject.parseObject("{\"components\":[1,2,3,4,5]}");
|
// JSONObject content = JSONObject.parseObject("{\"components\":[1,2,3,4,5]}");
|
||||||
JSONArray componentIds = content.getJSONArray("components");
|
JSONArray componentIds = JSONArray.parseArray(testPlanReport.getComponents());
|
||||||
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
|
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
|
||||||
testPlanService.buildApiCaseReport(testPlanReport.getTestPlanId(), components);
|
testPlanService.buildApiCaseReport(testPlanReport.getTestPlanId(), components);
|
||||||
testPlanService.buildScenarioCaseReport(testPlanReport.getTestPlanId(), components);
|
testPlanService.buildScenarioCaseReport(testPlanReport.getTestPlanId(), components);
|
||||||
testPlanService.buildLoadCaseReport(testPlanReport.getTestPlanId(), components);
|
testPlanService.buildLoadCaseReport(testPlanReport.getTestPlanId(), components);
|
||||||
|
|
||||||
|
if(StringUtils.equals(ReportTriggerMode.MANUAL.name(),triggerMode)){
|
||||||
|
List<Issues> issues = testPlanService.buildFunctionalCaseReport(testPlanReport.getTestPlanId(), components);
|
||||||
|
issuesInfo = JSONArray.toJSONString(issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//只针对定时任务做处理
|
//只针对定时任务做处理
|
||||||
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
|
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
|
||||||
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
|
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="passRate" :label="$t('api_test.automation.passing_rate')"
|
<el-table-column prop="passRate" :label="$t('api_test.automation.passing_rate')"
|
||||||
show-overflow-tooltip/>
|
show-overflow-tooltip/>
|
||||||
<el-table-column fixed="right" :label="$t('commons.operating')" width="200px" v-if="!referenced">
|
<el-table-column fixed="right" :label="$t('commons.operating')" width="190px" v-if="!referenced">
|
||||||
<template v-slot:default="{row}">
|
<template v-slot:default="{row}">
|
||||||
<div v-if="trashEnable">
|
<div v-if="trashEnable">
|
||||||
<ms-table-operator-button :tip="$t('commons.reduction')" icon="el-icon-refresh-left" @exec="reductionApi(row)" v-tester/>
|
<ms-table-operator-button :tip="$t('commons.reduction')" icon="el-icon-refresh-left" @exec="reductionApi(row)" v-tester/>
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
<ms-table-operator-button :tip="$t('api_test.automation.copy')" icon="el-icon-document-copy" type=""
|
<ms-table-operator-button :tip="$t('api_test.automation.copy')" icon="el-icon-document-copy" type=""
|
||||||
@exec="copy(row)"/>
|
@exec="copy(row)"/>
|
||||||
<ms-table-operator-button :tip="$t('api_test.automation.remove')" icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
|
<ms-table-operator-button :tip="$t('api_test.automation.remove')" icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
|
||||||
<ms-scenario-extend-buttons @openScenario="openScenario" :row="row"/>
|
<ms-scenario-extend-buttons style="display: contents" @openScenario="openScenario" :row="row"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
|
@ -242,7 +242,6 @@
|
||||||
this.apiCaseList.unshift(obj);
|
this.apiCaseList.unshift(obj);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
copyCase(data) {
|
copyCase(data) {
|
||||||
this.apiCaseList.unshift(data);
|
this.apiCaseList.unshift(data);
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<ms-database-config :configs="environment.config.databaseConfigs"/>
|
<ms-database-config :configs="environment.config.databaseConfigs"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
||||||
<ms-tcp-config :config="environment.config.tcpConfig"/>
|
<environment-tcp-config :config="environment.config.tcpConfig"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
|
@ -44,12 +44,12 @@
|
||||||
import MsDatabaseConfig from "../request/database/DatabaseConfig";
|
import MsDatabaseConfig from "../request/database/DatabaseConfig";
|
||||||
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
|
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
|
||||||
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
|
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
|
||||||
import MsTcpConfig from "../request/tcp/TcpConfig";
|
import EnvironmentTcpConfig from "./EnvironmentTcpConfig";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EnvironmentEdit",
|
name: "EnvironmentEdit",
|
||||||
components: {
|
components: {
|
||||||
MsTcpConfig,
|
EnvironmentTcpConfig,
|
||||||
MsEnvironmentCommonConfig,
|
MsEnvironmentCommonConfig,
|
||||||
MsEnvironmentHttpConfig,
|
MsEnvironmentHttpConfig,
|
||||||
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
|
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {parseEnvironment} from "../../model/EnvironmentModel";
|
import {parseEnvironment} from "../../model/EnvironmentModel";
|
||||||
import ApiEnvironmentConfig from "../../../test/components/ApiEnvironmentConfig";
|
import ApiEnvironmentConfig from "../../../definition/components/environment/ApiEnvironmentConfig";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EnvironmentSelect",
|
name: "EnvironmentSelect",
|
||||||
|
|
|
@ -105,10 +105,13 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.response.responseResult.headers.indexOf("Content-Type: application/json") > 0) {
|
if (this.response.responseResult.headers.indexOf("Content-Type: application/json") > 0) {
|
||||||
|
this.mode = BODY_FORMAT.JSON;
|
||||||
|
this.$nextTick(() => {
|
||||||
if (this.$refs.modeDropdown) {
|
if (this.$refs.modeDropdown) {
|
||||||
this.$refs.modeDropdown.handleCommand(BODY_FORMAT.JSON);
|
this.$refs.modeDropdown.handleCommand(BODY_FORMAT.JSON);
|
||||||
this.msCodeReload();
|
this.msCodeReload();
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
msCodeReload() {
|
msCodeReload() {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
:options="options"
|
:options="options"
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
:group="group"
|
:group="group"
|
||||||
|
@click="onClick"
|
||||||
:watch-shallow="watchShallow"
|
:watch-shallow="watchShallow"
|
||||||
:manual-update="manualUpdate"
|
:manual-update="manualUpdate"
|
||||||
:autoresize="autoresize"/>
|
:autoresize="autoresize"/>
|
||||||
|
@ -27,12 +28,18 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
this.defaultInitOptions = this.defaultInitOptions || {};
|
this.defaultInitOptions = this.defaultInitOptions || {};
|
||||||
// 默认渲染svg
|
// 默认渲染svg
|
||||||
// BUG: 渲染svg之后 图上的legend 太多会不显示
|
// BUG: 渲染svg之后 图上的legend 太多会不显示
|
||||||
// if (!this.defaultInitOptions.renderer) {
|
// if (!this.defaultInitOptions.renderer) {
|
||||||
// this.defaultInitOptions.renderer = 'svg';
|
// this.defaultInitOptions.renderer = 'svg';
|
||||||
// }
|
// }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick(params){
|
||||||
|
this.$emit('onClick', params.data)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ms-chart :options="options">
|
<ms-chart :options="options" @onClick="onClick">
|
||||||
</ms-chart>
|
</ms-chart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -107,7 +107,10 @@ export default {
|
||||||
itemNames.push(item.name);
|
itemNames.push(item.name);
|
||||||
});
|
});
|
||||||
this.dataNames = itemNames;
|
this.dataNames = itemNames;
|
||||||
}
|
},
|
||||||
|
onClick(params){
|
||||||
|
this.$emit('onClick', params);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -201,6 +201,13 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
_handleDeleteNoMsg(report) {
|
||||||
|
this.result = this.$post(this.deletePath + report.id, {}, () => {
|
||||||
|
this.initTableData();
|
||||||
|
// 发送广播,刷新 head 上的最新列表
|
||||||
|
PerformanceEvent.$emit(LIST_CHANGE);
|
||||||
|
});
|
||||||
|
},
|
||||||
_handleDelete(report) {
|
_handleDelete(report) {
|
||||||
this.result = this.$post(this.deletePath + report.id, {}, () => {
|
this.result = this.$post(this.deletePath + report.id, {}, () => {
|
||||||
this.$success(this.$t('commons.delete_success'));
|
this.$success(this.$t('commons.delete_success'));
|
||||||
|
@ -245,10 +252,11 @@ export default {
|
||||||
callback: (action) => {
|
callback: (action) => {
|
||||||
if (action === 'confirm') {
|
if (action === 'confirm') {
|
||||||
this.selectRows.forEach(row => {
|
this.selectRows.forEach(row => {
|
||||||
this._handleDelete(row);
|
this._handleDeleteNoMsg(row);
|
||||||
})
|
});
|
||||||
}
|
this.$success(this.$t('commons.delete_success'));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</template>
|
</template>
|
||||||
</ms-test-plan-header-bar>
|
</ms-test-plan-header-bar>
|
||||||
<test-plan-functional v-if="activeIndex === 'functional'" :plan-id="planId"/>
|
<test-plan-functional v-if="activeIndex === 'functional'" :redirectCharType="redirectCharType" :clickType="clickType" :plan-id="planId"/>
|
||||||
<test-plan-api v-if="activeIndex === 'api'" :plan-id="planId"/>
|
<test-plan-api v-if="activeIndex === 'api'" :redirectCharType="redirectCharType" :clickType="clickType" :plan-id="planId"/>
|
||||||
<test-plan-load v-if="activeIndex === 'load'" :plan-id="planId"/>
|
<test-plan-load v-if="activeIndex === 'load'" :redirectCharType="redirectCharType" :clickType="clickType" :plan-id="planId"/>
|
||||||
<test-case-statistics-report-view :test-plan="currentPlan" v-if="activeIndex === 'report'"/>
|
<test-case-statistics-report-view :test-plan="currentPlan" v-if="activeIndex === 'report'"/>
|
||||||
|
|
||||||
<test-report-template-list @openReport="openReport" ref="testReportTemplateList"/>
|
<test-report-template-list @openReport="openReport" ref="testReportTemplateList"/>
|
||||||
|
@ -61,7 +61,11 @@
|
||||||
testPlans: [],
|
testPlans: [],
|
||||||
currentPlan: {},
|
currentPlan: {},
|
||||||
activeIndex: "functional",
|
activeIndex: "functional",
|
||||||
isMenuShow: true
|
isMenuShow: true,
|
||||||
|
//报表跳转过来的参数-通过哪个图表跳转的
|
||||||
|
redirectCharType:'',
|
||||||
|
//报表跳转过来的参数-通过哪种数据跳转的
|
||||||
|
clickType:'',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -71,14 +75,30 @@
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route.params.planId'() {
|
'$route.params.planId'() {
|
||||||
this.activeIndex = "functional";
|
this.genRedirectParam();
|
||||||
this.getTestPlans();
|
this.getTestPlans();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getTestPlans();
|
this.getTestPlans();
|
||||||
},
|
},
|
||||||
|
activated() {
|
||||||
|
this.genRedirectParam();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
genRedirectParam(){
|
||||||
|
this.redirectCharType = this.$route.params.charType;
|
||||||
|
this.clickType = this.$route.params.clickType;
|
||||||
|
if(this.redirectCharType != ""){
|
||||||
|
if(this.redirectCharType=='scenario'){
|
||||||
|
this.activeIndex = 'api';
|
||||||
|
}else if(this.redirectCharType != null && this.redirectCharType != ''){
|
||||||
|
this.activeIndex = this.redirectCharType;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
this.activeIndex = "functional";
|
||||||
|
}
|
||||||
|
},
|
||||||
getTestPlans() {
|
getTestPlans() {
|
||||||
this.$post('/test/plan/list/all', {}, response => {
|
this.$post('/test/plan/list/all', {}, response => {
|
||||||
this.testPlans = response.data;
|
this.testPlans = response.data;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
@setModuleOptions="setModuleOptions"
|
@setModuleOptions="setModuleOptions"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
:is-read-only="true"
|
:is-read-only="true"
|
||||||
|
:redirectCharType="redirectCharType"
|
||||||
ref="apiNodeTree">
|
ref="apiNodeTree">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<div class="model-change-radio">
|
<div class="model-change-radio">
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
:is-case-relevance="true"
|
:is-case-relevance="true"
|
||||||
:model="'plan'"
|
:model="'plan'"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
|
:clickType="clickType"
|
||||||
@refresh="refreshTree"
|
@refresh="refreshTree"
|
||||||
@relevanceCase="openTestCaseRelevanceDialog"
|
@relevanceCase="openTestCaseRelevanceDialog"
|
||||||
ref="apiCaseList"/>
|
ref="apiCaseList"/>
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
:select-node-ids="selectNodeIds"
|
:select-node-ids="selectNodeIds"
|
||||||
:trash-enable="trashEnable"
|
:trash-enable="trashEnable"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
|
:clickType="clickType"
|
||||||
@refresh="refreshTree"
|
@refresh="refreshTree"
|
||||||
@relevanceCase="openTestCaseRelevanceDialog"
|
@relevanceCase="openTestCaseRelevanceDialog"
|
||||||
ref="apiScenarioList"/>
|
ref="apiScenarioList"/>
|
||||||
|
@ -119,18 +122,34 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'planId'
|
'planId',
|
||||||
|
'redirectCharType',
|
||||||
|
'clickType'
|
||||||
],
|
],
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.checkRedirectCharType();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
model() {
|
model() {
|
||||||
this.selectNodeIds = [];
|
this.selectNodeIds = [];
|
||||||
this.moduleOptions = {};
|
this.moduleOptions = {};
|
||||||
|
},
|
||||||
|
redirectCharType(){
|
||||||
|
if(this.redirectCharType=='scenario'){
|
||||||
|
this.model = 'scenario';
|
||||||
|
}else{
|
||||||
|
this.model = 'api';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
checkRedirectCharType(){
|
||||||
|
if(this.redirectCharType=='scenario'){
|
||||||
|
this.model = 'scenario';
|
||||||
|
}else{
|
||||||
|
this.model = 'api';
|
||||||
|
}
|
||||||
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
this.refreshTree();
|
this.refreshTree();
|
||||||
this.refreshTable();
|
this.refreshTable();
|
||||||
|
|
|
@ -165,6 +165,7 @@ export default {
|
||||||
selectCase: {},
|
selectCase: {},
|
||||||
result: {},
|
result: {},
|
||||||
moduleId: "",
|
moduleId: "",
|
||||||
|
status:'default',
|
||||||
deletePath: "/test/case/delete",
|
deletePath: "/test/case/delete",
|
||||||
selectRows: new Set(),
|
selectRows: new Set(),
|
||||||
buttons: [
|
buttons: [
|
||||||
|
@ -224,12 +225,16 @@ export default {
|
||||||
'api'
|
'api'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
planId: String
|
planId: String,
|
||||||
|
clickType:String
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
this.getMaintainerOptions();
|
this.getMaintainerOptions();
|
||||||
this.initTable();
|
this.initTable();
|
||||||
},
|
},
|
||||||
|
activated() {
|
||||||
|
this.status ='default'
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectNodeIds() {
|
selectNodeIds() {
|
||||||
this.initTable();
|
this.initTable();
|
||||||
|
@ -278,6 +283,14 @@ export default {
|
||||||
if (this.currentProtocol != null) {
|
if (this.currentProtocol != null) {
|
||||||
this.condition.protocol = this.currentProtocol;
|
this.condition.protocol = this.currentProtocol;
|
||||||
}
|
}
|
||||||
|
if(this.clickType){
|
||||||
|
if(this.status =='default'){
|
||||||
|
this.condition.status = this.clickType;
|
||||||
|
}else{
|
||||||
|
this.condition.status = null;
|
||||||
|
}
|
||||||
|
this.status = 'all';
|
||||||
|
}
|
||||||
this.result = this.$post('/test/plan/api/case/list/' + this.currentPage + "/" + this.pageSize, this.condition, response => {
|
this.result = this.$post('/test/plan/api/case/list/' + this.currentPage + "/" + this.pageSize, this.condition, response => {
|
||||||
this.total = response.data.itemCount;
|
this.total = response.data.itemCount;
|
||||||
this.tableData = response.data.listObject;
|
this.tableData = response.data.listObject;
|
||||||
|
|
|
@ -97,7 +97,8 @@
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
selectNodeIds: Array,
|
selectNodeIds: Array,
|
||||||
planId: String
|
planId: String,
|
||||||
|
clickType:String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -111,6 +112,7 @@
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
reportId: "",
|
reportId: "",
|
||||||
|
status:'default',
|
||||||
infoDb: false,
|
infoDb: false,
|
||||||
runVisible: false,
|
runVisible: false,
|
||||||
projectId: "",
|
projectId: "",
|
||||||
|
@ -144,7 +146,14 @@
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.condition.moduleIds = this.selectNodeIds;
|
this.condition.moduleIds = this.selectNodeIds;
|
||||||
this.condition.planId = this.planId;
|
this.condition.planId = this.planId;
|
||||||
|
if(this.clickType){
|
||||||
|
if(this.status =='default'){
|
||||||
|
this.condition.status = this.clickType;
|
||||||
|
}else{
|
||||||
|
this.condition.status = null;
|
||||||
|
}
|
||||||
|
this.status = 'all';
|
||||||
|
}
|
||||||
let url = "/test/plan/scenario/case/list/" + this.currentPage + "/" + this.pageSize;
|
let url = "/test/plan/scenario/case/list/" + this.currentPage + "/" + this.pageSize;
|
||||||
this.$post(url, this.condition, response => {
|
this.$post(url, this.condition, response => {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
|
|
|
@ -257,6 +257,7 @@ export default {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
status:'default',
|
||||||
selectRows: new Set(),
|
selectRows: new Set(),
|
||||||
testPlan: {},
|
testPlan: {},
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
|
@ -318,6 +319,7 @@ export default {
|
||||||
planId: {
|
planId: {
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
|
clickType:String,
|
||||||
selectNodeIds: {
|
selectNodeIds: {
|
||||||
type: Array
|
type: Array
|
||||||
},
|
},
|
||||||
|
@ -352,6 +354,14 @@ export default {
|
||||||
// param.planId = this.planId;
|
// param.planId = this.planId;
|
||||||
this.condition.planId = this.planId;
|
this.condition.planId = this.planId;
|
||||||
}
|
}
|
||||||
|
if(this.clickType){
|
||||||
|
if(this.status =='default'){
|
||||||
|
this.condition.status = this.clickType;
|
||||||
|
}else{
|
||||||
|
this.condition.status = null;
|
||||||
|
}
|
||||||
|
this.status = 'all';
|
||||||
|
}
|
||||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||||
// param.nodeIds = this.selectNodeIds;
|
// param.nodeIds = this.selectNodeIds;
|
||||||
this.condition.nodeIds = this.selectNodeIds;
|
this.condition.nodeIds = this.selectNodeIds;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
|
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
|
||||||
@refresh="refresh"
|
@refresh="refresh"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
|
:clickType="clickType"
|
||||||
:select-node-ids="selectNodeIds"
|
:select-node-ids="selectNodeIds"
|
||||||
:select-parent-nodes="selectParentNodes"
|
:select-parent-nodes="selectParentNodes"
|
||||||
ref="testPlanTestCaseList"/>
|
ref="testPlanTestCaseList"/>
|
||||||
|
@ -52,9 +53,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'planId'
|
'planId',
|
||||||
|
'redirectCharType',
|
||||||
|
'clickType'
|
||||||
],
|
],
|
||||||
mounted() {
|
// activated() {
|
||||||
|
// this.search();
|
||||||
|
// this.checkTipsType();
|
||||||
|
// },
|
||||||
|
// mounted() {
|
||||||
|
activated(){
|
||||||
this.initData();
|
this.initData();
|
||||||
this.openTestCaseEdit(this.$route.path);
|
this.openTestCaseEdit(this.$route.path);
|
||||||
},
|
},
|
||||||
|
@ -88,7 +96,11 @@
|
||||||
},
|
},
|
||||||
getNodeTreeByPlanId() {
|
getNodeTreeByPlanId() {
|
||||||
if (this.planId) {
|
if (this.planId) {
|
||||||
this.result = this.$get("/case/node/list/plan/" + this.planId, response => {
|
let url = "/case/node/list/plan/" + this.planId;
|
||||||
|
if(this.clickType){
|
||||||
|
url = url+"/"+this.clickType;
|
||||||
|
}
|
||||||
|
this.result = this.$get(url, response => {
|
||||||
this.treeNodes = response.data;
|
this.treeNodes = response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
class="table-list"
|
class="table-list"
|
||||||
@refresh="refresh"
|
@refresh="refresh"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
|
:clickType="clickType"
|
||||||
:select-project-id="selectProjectId"
|
:select-project-id="selectProjectId"
|
||||||
:select-parent-nodes="selectParentNodes"
|
:select-parent-nodes="selectParentNodes"
|
||||||
@relevanceCase="openTestCaseRelevanceDialog"
|
@relevanceCase="openTestCaseRelevanceDialog"
|
||||||
|
@ -51,7 +52,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'planId'
|
'planId',
|
||||||
|
'redirectCharType',
|
||||||
|
'clickType'
|
||||||
],
|
],
|
||||||
watch: {
|
watch: {
|
||||||
planId() {
|
planId() {
|
||||||
|
|
|
@ -140,6 +140,7 @@ export default {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
status: 'default',
|
||||||
screenHeight: document.documentElement.clientHeight - 330,//屏幕高度
|
screenHeight: document.documentElement.clientHeight - 330,//屏幕高度
|
||||||
buttons: [
|
buttons: [
|
||||||
// {
|
// {
|
||||||
|
@ -171,7 +172,8 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
planId: String
|
planId: String,
|
||||||
|
clickType: String,
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initTable();
|
this.initTable();
|
||||||
|
@ -187,12 +189,19 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initTable() {
|
initTable() {
|
||||||
console.log('init')
|
|
||||||
this.selectRows = new Set();
|
this.selectRows = new Set();
|
||||||
this.condition.testPlanId = this.planId;
|
this.condition.testPlanId = this.planId;
|
||||||
if (this.selectProjectId && this.selectProjectId !== 'root') {
|
if (this.selectProjectId && this.selectProjectId !== 'root') {
|
||||||
this.condition.projectId = this.selectProjectId;
|
this.condition.projectId = this.selectProjectId;
|
||||||
}
|
}
|
||||||
|
if (this.clickType) {
|
||||||
|
if (this.status == 'default') {
|
||||||
|
this.condition.status = this.clickType;
|
||||||
|
}else{
|
||||||
|
this.condition.status = null;
|
||||||
|
}
|
||||||
|
this.status = 'all';
|
||||||
|
}
|
||||||
this.$post("/test/plan/load/case/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
|
this.$post("/test/plan/load/case/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
let {itemCount, listObject} = data;
|
let {itemCount, listObject} = data;
|
||||||
|
@ -278,7 +287,7 @@ export default {
|
||||||
},
|
},
|
||||||
updateStatus(loadCase, status) {
|
updateStatus(loadCase, status) {
|
||||||
this.$post('/test/plan/load/case/update', {id: loadCase.id, status: status}, () => {
|
this.$post('/test/plan/load/case/update', {id: loadCase.id, status: status}, () => {
|
||||||
this.$post('/test/plan/edit/status/' + loadCase.testPlanId, {},() => {
|
this.$post('/test/plan/edit/status/' + loadCase.testPlanId, {}, () => {
|
||||||
this.initTable();
|
this.initTable();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<base-info-component id="baseInfoComponent" :report-info="metric" v-if="preview.id == 1"/>
|
<base-info-component id="baseInfoComponent" :report-info="metric" v-if="preview.id == 1"/>
|
||||||
<test-result-component id="testResultComponent" :test-results="metric.moduleExecuteResult" v-if="preview.id == 2"/>
|
<test-result-component id="testResultComponent" :test-results="metric.moduleExecuteResult" v-if="preview.id == 2"/>
|
||||||
<!--<test-result-chart-component id="resultChartComponent" :execute-result="metric.executeResult" v-if="preview.id == 3"/>-->
|
<!--<test-result-chart-component id="resultChartComponent" :execute-result="metric.executeResult" v-if="preview.id == 3"/>-->
|
||||||
<test-result-advance-chart-component id="resultChartComponent" :execute-result="metric.executeResult" v-if="preview.id == 3"/>
|
<test-result-advance-chart-component id="resultChartComponent" :execute-result="metric.executeResult" :source="source" :planId="planId" v-if="preview.id == 3"/>
|
||||||
<!--<failure-result-component id="failureResultComponent" :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>-->
|
<!--<failure-result-component id="failureResultComponent" :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>-->
|
||||||
<failure-result-advance-component id="failureResultComponent" :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>
|
<failure-result-advance-component id="failureResultComponent" :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>
|
||||||
<defect-list-component id="defectListComponent" :defect-list="metric.issues" v-if="preview.id == 5"/>
|
<defect-list-component id="defectListComponent" :defect-list="metric.issues" v-if="preview.id == 5"/>
|
||||||
|
@ -53,6 +53,8 @@
|
||||||
metric: {
|
metric: {
|
||||||
type: Object
|
type: Object
|
||||||
},
|
},
|
||||||
|
source:String,
|
||||||
|
planId:String,
|
||||||
isReport: {
|
isReport: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
|
|
@ -4,22 +4,22 @@
|
||||||
|
|
||||||
<div class="char-component">
|
<div class="char-component">
|
||||||
<div class="char-item" v-if="showFunctional">
|
<div class="char-item" v-if="showFunctional">
|
||||||
<ms-pie-chart v-if="isShow" :text="'功能测试用例'"
|
<ms-pie-chart v-if="isShow" :text="'功能测试用例'" @onClick="onFuncCharClick"
|
||||||
:name="$t('test_track.plan_view.test_result')" :data="functionalCharData"/>
|
:name="$t('test_track.plan_view.test_result')" :data="functionalCharData"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="char-item" v-if="showApi">
|
<div class="char-item" v-if="showApi">
|
||||||
<ms-pie-chart v-if="isShow" :text="'接口测试用例'"
|
<ms-pie-chart v-if="isShow" :text="'接口测试用例'" @onClick="onApiCharClick"
|
||||||
:name="$t('test_track.plan_view.test_result')" :data="apiCharData"/>
|
:name="$t('test_track.plan_view.test_result')" :data="apiCharData"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="char-item" v-if="showScenario">
|
<div class="char-item" v-if="showScenario">
|
||||||
<ms-pie-chart v-if="isShow" :text="'场景测试用例'"
|
<ms-pie-chart v-if="isShow" :text="'场景测试用例'" @onClick="onScenarioCharClick"
|
||||||
:name="$t('test_track.plan_view.test_result')" :data="scenarioCharData"/>
|
:name="$t('test_track.plan_view.test_result')" :data="scenarioCharData"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="char-item" v-if="showLoad">
|
<div class="char-item" v-if="showLoad">
|
||||||
<ms-pie-chart v-if="isShow" :text="'性能测试用例'"
|
<ms-pie-chart v-if="isShow" :text="'性能测试用例'" @onClick="onLoadCharClick"
|
||||||
:name="$t('test_track.plan_view.test_result')" :data="loadCharData"/>
|
:name="$t('test_track.plan_view.test_result')" :data="loadCharData"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,36 +53,38 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
planId:String,
|
||||||
|
source:String,
|
||||||
executeResult: {
|
executeResult: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default() {
|
default() {
|
||||||
return {
|
return {
|
||||||
functionalResult: [
|
functionalResult: [
|
||||||
{status: 'Pass', count: '235'},
|
{status: 'Pass', count: '0'},
|
||||||
{status: 'Failure', count: '310'},
|
{status: 'Failure', count: '0'},
|
||||||
{status: 'Blocking', count: '274'},
|
{status: 'Blocking', count: '0'},
|
||||||
{status: 'Skip', count: '335'},
|
{status: 'Skip', count: '0'},
|
||||||
{status: 'Underway', count: '245'},
|
{status: 'Underway', count: '0'},
|
||||||
{status: 'Prepare', count: '265'},
|
{status: 'Prepare', count: '0'},
|
||||||
],
|
],
|
||||||
apiResult: [
|
apiResult: [
|
||||||
{status: 'Pass', count: '235'},
|
{status: 'Pass', count: '0'},
|
||||||
{status: 'Failure', count: '310'},
|
{status: 'Failure', count: '0'},
|
||||||
{status: 'Underway', count: '245'},
|
{status: 'Underway', count: '0'},
|
||||||
],
|
],
|
||||||
scenarioResult: [
|
scenarioResult: [
|
||||||
{status: 'Pass', count: '205'},
|
{status: 'Pass', count: '0'},
|
||||||
{status: 'Failure', count: '350'},
|
{status: 'Failure', count: '0'},
|
||||||
{status: 'Underway', count: '110'},
|
{status: 'Underway', count: '0'},
|
||||||
],
|
],
|
||||||
loadResult: [
|
loadResult: [
|
||||||
{status: 'Pass', count: '205'},
|
{status: 'Pass', count: '0'},
|
||||||
{status: 'Failure', count: '350'},
|
{status: 'Failure', count: '0'},
|
||||||
{status: 'Underway', count: '110'},
|
{status: 'Underway', count: '0'},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showFunctional() {
|
showFunctional() {
|
||||||
|
@ -163,6 +165,59 @@
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.isShow = true;
|
this.isShow = true;
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
onvertDataStatus(status){
|
||||||
|
if(status == this.$t('test_track.plan_view.pass')){
|
||||||
|
status = "Pass";
|
||||||
|
}else if(status == this.$t('test_track.plan_view.failure')){
|
||||||
|
status = "Failure";
|
||||||
|
}else if(status == this.$t('test_track.plan_view.blocking')){
|
||||||
|
status = "Blocking";
|
||||||
|
}else if(status == this.$t('test_track.plan.plan_status_prepare')){
|
||||||
|
status = "Prepare";
|
||||||
|
}else if (status == this.$t('test_track.plan.plan_status_running')){
|
||||||
|
status = "running";
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
},
|
||||||
|
onFuncCharClick(params){
|
||||||
|
let clickType = params['name'];
|
||||||
|
clickType = this.onvertDataStatus(clickType);
|
||||||
|
this.redirectPage('functional',clickType);
|
||||||
|
},
|
||||||
|
onApiCharClick(params){
|
||||||
|
let clickType = params['name'];
|
||||||
|
clickType = this.onvertDataStatus(clickType);
|
||||||
|
if(clickType=="Failure"){
|
||||||
|
clickType = "error";
|
||||||
|
}else if(clickType=="Pass"){
|
||||||
|
clickType = "success";
|
||||||
|
}
|
||||||
|
this.redirectPage('api',clickType);
|
||||||
|
},
|
||||||
|
onScenarioCharClick(params){
|
||||||
|
let clickType = params['name'];
|
||||||
|
clickType = this.onvertDataStatus(clickType);
|
||||||
|
if(clickType=="Failure"){
|
||||||
|
clickType = "Fail";
|
||||||
|
}else if(clickType=="Pass"){
|
||||||
|
clickType = "Success";
|
||||||
|
}
|
||||||
|
this.redirectPage('scenario',clickType);
|
||||||
|
},
|
||||||
|
onLoadCharClick(params){
|
||||||
|
let clickType = params['name'];
|
||||||
|
clickType = this.onvertDataStatus(clickType);
|
||||||
|
if(clickType=="Failure"){
|
||||||
|
clickType = "error";
|
||||||
|
}
|
||||||
|
this.redirectPage('load',clickType);
|
||||||
|
},
|
||||||
|
redirectPage(charType,clickType){
|
||||||
|
if(this.source == "ReportView"){
|
||||||
|
this.$router.push({name:'planView',params:{planId:this.planId,charType:charType,clickType:clickType}});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,10 @@
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.isShow = true;
|
this.isShow = true;
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
onClick(params){
|
||||||
|
this.$emit('onClick', params)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
// this.refreshTestPlanList();
|
this.refreshTestPlanList();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.refreshTestPlanList();
|
this.refreshTestPlanList();
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column min-width="300" prop="name" :label="$t('test_track.report.list.name')" show-overflow-tooltip></el-table-column>
|
<el-table-column min-width="300" prop="name" :label="$t('test_track.report.list.name')" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column prop="testPlanName" sortable :label="$t('test_track.report.list.test_plan')" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="testPlanName" min-width="150" sortable :label="$t('test_track.report.list.test_plan')" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column prop="creator" :label="$t('test_track.report.list.creator')" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="creator" :label="$t('test_track.report.list.creator')" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column prop="createTime" sortable :label="$t('test_track.report.list.create_time' )" show-overflow-tooltip>
|
<el-table-column prop="createTime" sortable :label="$t('test_track.report.list.create_time' )" show-overflow-tooltip>
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
|
|
|
@ -19,12 +19,6 @@
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12" class="head-right">
|
<el-col :span="12" class="head-right">
|
||||||
<!-- <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleSave">-->
|
|
||||||
<!-- {{$t('commons.save')}}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- <el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleEdit">-->
|
|
||||||
<!-- {{$t('test_track.plan_view.edit_component')}}-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExport(report.name)">
|
<el-button :disabled="!isTestManagerOrTestUser" plain size="mini" @click="handleExport(report.name)">
|
||||||
{{$t('test_track.plan_view.export_report')}}
|
{{$t('test_track.plan_view.export_report')}}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -34,7 +28,7 @@
|
||||||
<div class="container" ref="resume" id="app">
|
<div class="container" ref="resume" id="app">
|
||||||
<el-main>
|
<el-main>
|
||||||
<div v-for="(item, index) in previews" :key="item.id">
|
<div v-for="(item, index) in previews" :key="item.id">
|
||||||
<template-component :isReportView="true" :metric="metric" :preview="item" :index="index" ref="templateComponent"/>
|
<template-component :source="source" :isReportView="true" :metric="metric" :planId="planId" :preview="item" :index="index" ref="templateComponent"/>
|
||||||
</div>
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,6 +74,7 @@
|
||||||
previews: [],
|
previews: [],
|
||||||
report: {},
|
report: {},
|
||||||
reportId: '',
|
reportId: '',
|
||||||
|
source:"ReportView",
|
||||||
reportComponents:[1,3,4],
|
reportComponents:[1,3,4],
|
||||||
metric: {},
|
metric: {},
|
||||||
planId: '',
|
planId: '',
|
||||||
|
@ -100,6 +95,11 @@
|
||||||
mounted() {
|
mounted() {
|
||||||
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
|
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
reportComponents() {
|
||||||
|
this.initPreviews();
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
listenGoBack() {
|
listenGoBack() {
|
||||||
//监听浏览器返回操作,关闭该对话框
|
//监听浏览器返回操作,关闭该对话框
|
||||||
|
@ -118,13 +118,6 @@
|
||||||
this.listenGoBack();
|
this.listenGoBack();
|
||||||
},
|
},
|
||||||
getReport() {
|
getReport() {
|
||||||
// this.result = this.$get('/case/report/get/' + this.reportId, response => {
|
|
||||||
// this.report = response.data;
|
|
||||||
// this.report.content = JSON.parse(response.data.content);
|
|
||||||
// if (this.report.content.customComponent) {
|
|
||||||
// this.report.content.customComponent = jsonToMap(this.report.content.customComponent);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
this.getMetric();
|
this.getMetric();
|
||||||
this.initPreviews();
|
this.initPreviews();
|
||||||
},
|
},
|
||||||
|
@ -149,42 +142,19 @@
|
||||||
this.$emit('refresh');
|
this.$emit('refresh');
|
||||||
this.showDialog = false;
|
this.showDialog = false;
|
||||||
},
|
},
|
||||||
handleEdit() {
|
|
||||||
this.$refs.templateEdit.open(this.reportId, true);
|
|
||||||
},
|
|
||||||
handleSave() {
|
|
||||||
let param = {};
|
|
||||||
this.buildParam(param);
|
|
||||||
this.result = this.$post('/case/report/edit', param, () => {
|
|
||||||
this.$success(this.$t('commons.save_success'));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
buildParam(param) {
|
|
||||||
let content = {};
|
|
||||||
content.components = [];
|
|
||||||
this.previews.forEach(item => {
|
|
||||||
content.components.push(item.id);
|
|
||||||
if (!this.componentMap.get(item.id)) {
|
|
||||||
content.customComponent = new Map();
|
|
||||||
content.customComponent.set(item.id, {title: item.title, content: item.content})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
param.name = this.report.name;
|
|
||||||
if (content.customComponent) {
|
|
||||||
content.customComponent = mapToJson(content.customComponent);
|
|
||||||
}
|
|
||||||
param.content = JSON.stringify(content);
|
|
||||||
param.id = this.report.id;
|
|
||||||
if (this.metric.startTime) {
|
|
||||||
param.startTime = this.metric.startTime.getTime();
|
|
||||||
}
|
|
||||||
if (this.metric.endTime) {
|
|
||||||
param.endTime = this.metric.endTime.getTime();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getMetric() {
|
getMetric() {
|
||||||
this.result = this.$get('/test/plan/report/getMetric/' + this.reportId, response => {
|
this.result = this.$get('/test/plan/report/getMetric/' + this.reportId, response => {
|
||||||
this.metric = response.data;
|
this.metric = response.data;
|
||||||
|
let components = response.data.reportComponents;
|
||||||
|
this.planId = response.data.testPlanId;
|
||||||
|
this.report.name = response.data.name;
|
||||||
|
this.report.startTime = response.data.startTime;
|
||||||
|
this.report.endTime = response.data.endTime;
|
||||||
|
if(components === null || components === ''){
|
||||||
|
this.reportComponents = [1,3,4];
|
||||||
|
}else {
|
||||||
|
this.reportComponents = JSON.parse(components);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.metric.failureTestCases) {
|
if (!this.metric.failureTestCases) {
|
||||||
this.metric.failureTestCases = [];
|
this.metric.failureTestCases = [];
|
||||||
|
|
Loading…
Reference in New Issue