merge: 合并dev最新修改

This commit is contained in:
BugKing 2021-12-22 21:09:11 +08:00
commit 77bfdd1a37
86 changed files with 1067 additions and 584 deletions

View File

@ -9,6 +9,7 @@ import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.commons.constants.TestPlanApiExecuteStatus; import io.metersphere.commons.constants.TestPlanApiExecuteStatus;
import io.metersphere.commons.constants.TestPlanResourceType; import io.metersphere.commons.constants.TestPlanResourceType;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.track.dto.TestPlanReportExecuteCheckResultDTO;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -20,6 +21,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @author song.tianyang * @author song.tianyang
@ -30,17 +32,16 @@ import java.util.Map;
public class TestPlanExecuteInfo { public class TestPlanExecuteInfo {
private String reportId; private String reportId;
private String creator; private String creator;
private Map<String, String> apiCaseExecInfo = new HashMap<>(); private Map<String, String> apiCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioCaseExecInfo = new HashMap<>(); private Map<String, String> apiScenarioCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> loadCaseExecInfo = new HashMap<>(); private Map<String, String> loadCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiCaseExecuteThreadMap = new HashMap<>(); private Map<String, String> apiCaseExecuteThreadMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioThreadMap = new HashMap<>(); private Map<String, String> apiScenarioThreadMap = new ConcurrentHashMap<>();
private Map<String, String> loadCaseReportIdMap = new HashMap<>(); private Map<String, String> loadCaseReportIdMap = new ConcurrentHashMap<>();
private Map<String, String> apiCaseReportMap = new HashMap<>();
private Map<String, String> apiScenarioReportMap = new HashMap<>();
private Map<String, String> apiCaseReportMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioReportMap = new ConcurrentHashMap<>();
private boolean reportDataInDataBase; private boolean reportDataInDataBase;
int lastUnFinishedNumCount = 0; int lastUnFinishedNumCount = 0;
@ -83,7 +84,8 @@ public class TestPlanExecuteInfo {
} }
} }
public synchronized int countUnFinishedNum() { public synchronized TestPlanReportExecuteCheckResultDTO countUnFinishedNum() {
TestPlanReportExecuteCheckResultDTO executeCheck = new TestPlanReportExecuteCheckResultDTO();
int unFinishedCount = 0; int unFinishedCount = 0;
this.isApiCaseAllExecuted = true; this.isApiCaseAllExecuted = true;
@ -129,8 +131,22 @@ public class TestPlanExecuteInfo {
LoggerUtil.info("执行的报告还在队列中,重置超时时间"); LoggerUtil.info("执行的报告还在队列中,重置超时时间");
lastUnFinishedNumCount = unFinishedCount; lastUnFinishedNumCount = unFinishedCount;
lastFinishedNumCountTime = System.currentTimeMillis(); lastFinishedNumCountTime = System.currentTimeMillis();
executeCheck.setFinishedCaseChanged(true);
} else if (unFinishedCount == 0) {
executeCheck.setFinishedCaseChanged(true);
} else {
executeCheck.setFinishedCaseChanged(false);
} }
return unFinishedCount;
executeCheck.setTimeOut(false);
if (unFinishedCount > 0) {
//20分钟没有案例执行结果更新则定位超时
long nowTime = System.currentTimeMillis();
if (nowTime - lastFinishedNumCountTime > 1200000) {
executeCheck.setTimeOut(true);
}
}
return executeCheck;
} }
public Map<String, Map<String, String>> getExecutedResult() { public Map<String, Map<String, String>> getExecutedResult() {
@ -228,7 +244,7 @@ public class TestPlanExecuteInfo {
this.countUnFinishedNum(); this.countUnFinishedNum();
} }
public void updateReport(Map<String, String> apiCaseExecResultInfo, Map<String, String> apiScenarioCaseExecResultInfo) { public synchronized void updateReport(Map<String, String> apiCaseExecResultInfo, Map<String, String> apiScenarioCaseExecResultInfo) {
if (MapUtils.isNotEmpty(apiCaseExecResultInfo)) { if (MapUtils.isNotEmpty(apiCaseExecResultInfo)) {
this.apiCaseReportMap.putAll(apiCaseExecResultInfo); this.apiCaseReportMap.putAll(apiCaseExecResultInfo);
} }
@ -236,6 +252,35 @@ public class TestPlanExecuteInfo {
if (MapUtils.isNotEmpty(apiScenarioCaseExecResultInfo)) { if (MapUtils.isNotEmpty(apiScenarioCaseExecResultInfo)) {
this.apiScenarioReportMap.putAll(apiScenarioCaseExecResultInfo); this.apiScenarioReportMap.putAll(apiScenarioCaseExecResultInfo);
} }
}
public Map<String, String> getRunningApiCaseReportMap() {
//key: reportId, value: testPlanApiCaseId
Map<String, String> returnMap = new HashMap<>();
for (Map.Entry<String, String> entry : apiCaseExecInfo.entrySet()) {
String planCaseId = entry.getKey();
String status = entry.getValue();
if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) {
if (apiCaseExecuteThreadMap.containsKey(planCaseId)) {
returnMap.put(apiCaseExecuteThreadMap.get(planCaseId), planCaseId);
}
}
}
return returnMap;
}
public Map<String, String> getRunningScenarioReportMap() {
//key: reportId, value: testPlanApiScenarioId
Map<String, String> returnMap = new HashMap<>();
for (Map.Entry<String, String> entry : apiScenarioCaseExecInfo.entrySet()) {
String planScenarioId = entry.getKey();
String status = entry.getValue();
if (StringUtils.equalsIgnoreCase(status, TestPlanApiExecuteStatus.RUNNING.name())) {
if (apiScenarioThreadMap.containsKey(planScenarioId)) {
returnMap.put(apiScenarioThreadMap.get(planScenarioId), planScenarioId);
}
}
}
return returnMap;
} }
} }

View File

@ -1,6 +1,7 @@
package io.metersphere.api.cache; package io.metersphere.api.cache;
import io.metersphere.commons.constants.TestPlanApiExecuteStatus; import io.metersphere.commons.constants.TestPlanApiExecuteStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,7 +51,11 @@ public class TestPlanReportExecuteCatch {
} }
public synchronized static boolean containsReport(String reportId) { public synchronized static boolean containsReport(String reportId) {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId); if(StringUtils.isEmpty(reportId)){
return false;
}else {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
}
} }
public synchronized static void updateApiTestPlanExecuteInfo(String reportId, public synchronized static void updateApiTestPlanExecuteInfo(String reportId,

View File

@ -1,5 +1,7 @@
package io.metersphere.api.dto.automation; package io.metersphere.api.dto.automation;
import org.junit.internal.runners.statements.Fail;
public enum ScenarioStatus { public enum ScenarioStatus {
Saved, Success, Fail, Trash,Underway Saved, Success, Error, Timeout, Fail, Trash, Underway
} }

View File

@ -61,6 +61,7 @@ public class ApiDefinitionImportUtil {
if (parentModule != null) { if (parentModule != null) {
module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1); module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1);
module.setParentId(parentModule.getId()); module.setParentId(parentModule.getId());
module.setProtocol(parentModule.getProtocol());
} else { } else {
module = apiModuleService.getNewModule(name, projectId, 1); module = apiModuleService.getNewModule(name, projectId, 1);
} }
@ -121,6 +122,7 @@ public class ApiDefinitionImportUtil {
if (parentModule != null) { if (parentModule != null) {
module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1); module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1);
module.setParentId(parentModule.getId()); module.setParentId(parentModule.getId());
module.setProtocol(parentModule.getProtocol());
} else { } else {
module = apiModuleService.getNewModule(name, projectId, 1); module = apiModuleService.getNewModule(name, projectId, 1);
} }

View File

@ -84,8 +84,8 @@ public class JMeterScriptUtil {
} }
private static void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config, String enviromentId) { private static void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config, String enviromentId) {
if (element != null && element.getEnvironmentId() == null) { if (element != null) {
element.setEnvironmentId(enviromentId); element.setEnvironmentId(element.getEnvironmentId() == null ? enviromentId : element.getEnvironmentId());
element.toHashTree(samplerHashTree, element.getHashTree(), config); element.toHashTree(samplerHashTree, element.getHashTree(), config);
} }
} }

View File

@ -246,8 +246,8 @@ public class Swagger3Parser extends SwaggerAbstractParser {
msResponse.setType(RequestType.HTTP); msResponse.setType(RequestType.HTTP);
// todo 状态码要调整 // todo 状态码要调整
msResponse.setStatusCode(new ArrayList<>()); msResponse.setStatusCode(new ArrayList<>());
ApiResponse apiResponse = responses.get("200");
if (responses != null) { if (responses != null) {
ApiResponse apiResponse = responses.get("200");
if (apiResponse == null) { if (apiResponse == null) {
responses.forEach((responseCode, response) -> { responses.forEach((responseCode, response) -> {
parseResponseHeader(response, msResponse.getHeaders()); parseResponseHeader(response, msResponse.getHeaders());
@ -257,10 +257,10 @@ public class Swagger3Parser extends SwaggerAbstractParser {
parseResponseHeader(apiResponse, msResponse.getHeaders()); parseResponseHeader(apiResponse, msResponse.getHeaders());
parseResponseBody(apiResponse, msResponse.getBody()); parseResponseBody(apiResponse, msResponse.getBody());
} }
responses.forEach((responseCode, response) -> {
parseResponseCode(msResponse.getStatusCode(), responseCode, response);
});
} }
responses.forEach((responseCode, response) -> {
parseResponseCode(msResponse.getStatusCode(), responseCode, response);
});
return msResponse; return msResponse;
} }

View File

@ -164,7 +164,7 @@ public class MsTCPSampler extends MsTestElement {
}); });
} }
//根据配置将脚本放置在私有脚本之后 //根据配置将脚本放置在私有脚本之后
JMeterScriptUtil.setScript(envConfig, samplerHashTree, GlobalScriptFilterRequest.TCP.name(), enviromentId, config, false); JMeterScriptUtil.setScript(envConfig, samplerHashTree, GlobalScriptFilterRequest.TCP.name(), enviromentId, config, true);
} }
private void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config) { private void addItemHashTree(MsTestElement element, HashTree samplerHashTree, ParameterConfig config) {

View File

@ -136,8 +136,13 @@ public class TestPlanApiExecuteService {
JmeterRunRequestDTO runRequest = new JmeterRunRequestDTO(testPlanApiCase.getId(), reportId, request.getTriggerMode(), hashTree); JmeterRunRequestDTO runRequest = new JmeterRunRequestDTO(testPlanApiCase.getId(), reportId, request.getTriggerMode(), hashTree);
if (request.getConfig() != null) { if (request.getConfig() != null) {
runRequest.setPool(GenerateHashTreeUtil.isResourcePool(request.getConfig().getResourcePoolId())); runRequest.setPool(GenerateHashTreeUtil.isResourcePool(request.getConfig().getResourcePoolId()));
runRequest.setPoolId(request.getConfig().getResourcePoolId());
} }
runRequest.setTestPlanReportId(request.getPlanReportId()); runRequest.setTestPlanReportId(request.getPlanReportId());
runRequest.setReportType(executionQueue.getReportType());
runRequest.setTestPlanReportId(request.getPlanReportId());
runRequest.setRunType(RunModeConstants.PARALLEL.toString());
runRequest.setQueueId(executionQueue.getId());
jMeterService.run(runRequest); jMeterService.run(runRequest);
} catch (Exception e) { } catch (Exception e) {
executeErrorList.add(testPlanApiCase.getId()); executeErrorList.add(testPlanApiCase.getId());

View File

@ -1,6 +1,7 @@
package io.metersphere.api.exec.queue; package io.metersphere.api.exec.queue;
import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.JmeterThreadUtils;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
@ -23,7 +24,7 @@ public class ExecTask implements Runnable {
jMeterService.addQueue(request); jMeterService.addQueue(request);
if (request.getPool() == null || !request.getPool().isPool()) { if (request.getPool() == null || !request.getPool().isPool()) {
Object res = PoolExecBlockingQueueUtil.take(request.getReportId()); Object res = PoolExecBlockingQueueUtil.take(request.getReportId());
if (res == null) { if (res == null && !JmeterThreadUtils.isRunning(request.getReportId(), request.getTestId())) {
LoggerUtil.info("执行报告:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】执行超时"); LoggerUtil.info("执行报告:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】执行超时");
} }
} }

View File

@ -33,7 +33,7 @@ public class PoolExecBlockingQueueUtil {
if (StringUtils.isNotEmpty(key) && !queue.containsKey(key)) { if (StringUtils.isNotEmpty(key) && !queue.containsKey(key)) {
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE); BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);
queue.put(key, blockingQueue); queue.put(key, blockingQueue);
return blockingQueue.poll(5, TimeUnit.MINUTES); return blockingQueue.poll(10, TimeUnit.MINUTES);
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error("初始化队列失败:" + e.getMessage()); LogUtil.error("初始化队列失败:" + e.getMessage());

View File

@ -91,7 +91,6 @@ public class ApiDefinitionExecResultUtil {
apiResult.setStartTime(System.currentTimeMillis()); apiResult.setStartTime(System.currentTimeMillis());
apiResult.setType(ApiRunMode.DEFINITION.name()); apiResult.setType(ApiRunMode.DEFINITION.name());
apiResult.setStatus(status); apiResult.setStatus(status);
return apiResult; return apiResult;
} }
} }

View File

@ -31,9 +31,12 @@ import java.util.Map;
public class GenerateHashTreeUtil { public class GenerateHashTreeUtil {
public static MsScenario parseScenarioDefinition(String scenarioDefinition) { public static MsScenario parseScenarioDefinition(String scenarioDefinition) {
MsScenario scenario = JSONObject.parseObject(scenarioDefinition, MsScenario.class); if(StringUtils.isNotEmpty(scenarioDefinition)) {
parse(scenarioDefinition, scenario, scenario.getId(), null); MsScenario scenario = JSONObject.parseObject(scenarioDefinition, MsScenario.class);
return scenario; parse(scenarioDefinition, scenario, scenario.getId(), null);
return scenario;
}
return null;
} }
public static void parse(String scenarioDefinition, MsScenario scenario, String id, String reportType) { public static void parse(String scenarioDefinition, MsScenario scenario, String id, String reportType) {

View File

@ -5,10 +5,12 @@ import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.service.ApiExecutionQueueService; import io.metersphere.api.service.ApiExecutionQueueService;
import io.metersphere.api.service.MsResultService; import io.metersphere.api.service.MsResultService;
import io.metersphere.api.service.TestResultService; import io.metersphere.api.service.TestResultService;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.ResultDTO; import io.metersphere.dto.ResultDTO;
import io.metersphere.jmeter.MsExecListener; import io.metersphere.jmeter.MsExecListener;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.Map; import java.util.Map;
@ -18,13 +20,19 @@ public class APISingleResultListener extends MsExecListener {
LoggerUtil.info("处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + ""); LoggerUtil.info("处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + "");
dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId())); dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId()));
CommonBeanFactory.getBean(TestResultService.class).saveResults(dto); CommonBeanFactory.getBean(TestResultService.class).saveResults(dto);
// 更新报告最后接收到请求的时间
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCENARIO.name(),
ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(),
ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
CommonBeanFactory.getBean(TestResultService.class).editReportTime(dto);
}
} }
@Override @Override
public void testEnded(ResultDTO dto, Map<String, Object> kafkaConfig) { public void testEnded(ResultDTO dto, Map<String, Object> kafkaConfig) {
try { try {
LoggerUtil.info("进入TEST-END处理报告【" + dto.getReportId() + " 】整体执行完成;" + dto.getRunMode()); LoggerUtil.info("进入TEST-END处理报告【" + dto.getReportId() + " 】整体执行完成;" + dto.getRunMode());
// 全局并发队列 // 全局并发队列
PoolExecBlockingQueueUtil.offer(dto.getReportId()); PoolExecBlockingQueueUtil.offer(dto.getReportId());
dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId())); dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId()));

View File

@ -68,9 +68,9 @@ public class JMeterService {
} }
private void addDebugListener(String testId, HashTree testPlan) { private void addDebugListener(String testId, HashTree testPlan) {
MsDebugListener resultCollector = new MsDebugListener(); MsResultCollector resultCollector = new MsResultCollector();
resultCollector.setName(testId); resultCollector.setName(testId);
resultCollector.setProperty(TestElement.TEST_CLASS, MsDebugListener.class.getName()); resultCollector.setProperty(TestElement.TEST_CLASS, MsResultCollector.class.getName());
resultCollector.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ViewResultsFullVisualizer")); resultCollector.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ViewResultsFullVisualizer"));
resultCollector.setEnabled(true); resultCollector.setEnabled(true);
testPlan.add(testPlan.getArray()[0], resultCollector); testPlan.add(testPlan.getArray()[0], resultCollector);
@ -139,8 +139,8 @@ public class JMeterService {
JvmInfoDTO jvmInfoDTO = resources.get(index); JvmInfoDTO jvmInfoDTO = resources.get(index);
TestResourceDTO testResource = jvmInfoDTO.getTestResource(); TestResourceDTO testResource = jvmInfoDTO.getTestResource();
String configuration = testResource.getConfiguration(); String configuration = testResource.getConfiguration();
request.setCorePoolSize(MessageCache.corePoolSize);
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class); NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
request.setCorePoolSize(node.getMaxConcurrency());
String nodeIp = node.getIp(); String nodeIp = node.getIp();
Integer port = node.getPort(); Integer port = node.getPort();
String uri = String.format(BASE_URL + "/jmeter/api/start", nodeIp, port); String uri = String.format(BASE_URL + "/jmeter/api/start", nodeIp, port);

View File

@ -7,9 +7,7 @@ public class JmeterThreadUtils {
private final static String THREAD_SPLIT = " "; private final static String THREAD_SPLIT = " ";
public static String stop(String name) { public static String stop(String name) {
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount(); int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads]; Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads); currentGroup.enumerate(lstThreads);
@ -24,4 +22,19 @@ public class JmeterThreadUtils {
} }
return threadNames.toString(); return threadNames.toString();
} }
public static boolean isRunning(String reportId, String testId) {
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
for (int i = 0; i < noThreads; i++) {
if (StringUtils.isNotEmpty(reportId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(reportId)) {
return true;
} else if (StringUtils.isNotEmpty(testId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(testId)) {
return true;
}
}
return false;
}
} }

View File

@ -7,6 +7,7 @@ import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.service.ApiEnvironmentRunningParamService; import io.metersphere.api.service.ApiEnvironmentRunningParamService;
import io.metersphere.api.service.ApiExecutionQueueService; import io.metersphere.api.service.ApiExecutionQueueService;
import io.metersphere.api.service.TestResultService; import io.metersphere.api.service.TestResultService;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.config.KafkaConfig; import io.metersphere.config.KafkaConfig;
import io.metersphere.dto.ResultDTO; import io.metersphere.dto.ResultDTO;
@ -34,6 +35,13 @@ public class MsKafkaListener {
// 全局并发队列 // 全局并发队列
PoolExecBlockingQueueUtil.offer(testResult.getReportId()); PoolExecBlockingQueueUtil.offer(testResult.getReportId());
} else { } else {
// 更新报告最后接收到请求的时间
if (StringUtils.equalsAny(testResult.getRunMode(), ApiRunMode.SCENARIO.name(),
ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(),
ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
CommonBeanFactory.getBean(TestResultService.class).editReportTime(testResult);
}
testResultService.saveResults(testResult); testResultService.saveResults(testResult);
} }
LoggerUtil.info("执行内容存储结束"); LoggerUtil.info("执行内容存储结束");

View File

@ -23,11 +23,10 @@ import io.metersphere.api.service.MsResultService;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.RequestResult; import io.metersphere.dto.RequestResult;
import io.metersphere.dto.ResultDTO;
import io.metersphere.jmeter.JMeterBase; import io.metersphere.jmeter.JMeterBase;
import io.metersphere.utils.JMeterVars; import io.metersphere.utils.JMeterVars;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import io.metersphere.websocket.c.to.c.MsWebSocketClient; import io.metersphere.websocket.c.to.c.WebSocketUtils;
import io.metersphere.websocket.c.to.c.util.MsgDto; import io.metersphere.websocket.c.to.c.util.MsgDto;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -44,7 +43,7 @@ import java.util.Map;
/** /**
* 实时结果监听 * 实时结果监听
*/ */
public class MsDebugListener extends AbstractListenerElement implements SampleListener, Clearable, Serializable, public class MsResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
TestStateListener, Remoteable, NoThreadClone { TestStateListener, Remoteable, NoThreadClone {
private static final String ERROR_LOGGING = "MsResultCollector.error_logging"; // $NON-NLS-1$ private static final String ERROR_LOGGING = "MsResultCollector.error_logging"; // $NON-NLS-1$
@ -55,11 +54,9 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
public static final String TEST_END = "MS_TEST_END"; public static final String TEST_END = "MS_TEST_END";
private MsWebSocketClient client;
@Override @Override
public Object clone() { public Object clone() {
MsDebugListener clone = (MsDebugListener) super.clone(); MsResultCollector clone = (MsResultCollector) super.clone();
return clone; return clone;
} }
@ -100,28 +97,20 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
@Override @Override
public void testEnded(String host) { public void testEnded(String host) {
LoggerUtil.debug("TestEnded " + this.getName()); LoggerUtil.debug("TestEnded " + this.getName());
SampleResult result = new SampleResult(); MsgDto dto = new MsgDto();
result.setResponseCode(TEST_END); dto.setExecEnd(false);
ResultDTO dto = new ResultDTO(); dto.setContent(TEST_END);
dto.setReportId(this.getName()); dto.setReportId("send." + this.getName());
try { dto.setToReport(this.getName());
if (client != null) { LoggerUtil.debug("send. " + this.getName());
client.close(); WebSocketUtils.sendMessageSingle(dto);
} WebSocketUtils.onClose(this.getName());
} catch (Exception e) {
LogUtil.error(e);
}
} }
@Override @Override
public void testStarted(String host) { public void testStarted(String host) {
LogUtil.debug("TestStarted " + this.getName()); LogUtil.debug("TestStarted " + this.getName());
try {
client = new MsWebSocketClient("ws://127.0.0.1:8081/ws/" + "send." + this.getName());
client.connect();
} catch (Exception e) {
LogUtil.error(e);
}
} }
@Override @Override
@ -142,10 +131,9 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
dto.setReportId("send." + this.getName()); dto.setReportId("send." + this.getName());
dto.setToReport(this.getName()); dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName()); LoggerUtil.debug("send. " + this.getName());
if (client != null) { WebSocketUtils.sendMessageSingle(dto);
client.send(JSON.toJSONString(dto));
}
} catch (Exception ex) { } catch (Exception ex) {
LoggerUtil.error("消息推送失败:" + ex.getMessage());
} }
} }
@ -179,9 +167,7 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
dto.setReportId("send." + this.getName()); dto.setReportId("send." + this.getName());
dto.setToReport(this.getName()); dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName()); LoggerUtil.debug("send. " + this.getName());
if (client != null) { WebSocketUtils.sendMessageSingle(dto);
client.send(JSON.toJSONString(dto));
}
} }
} }
} }

View File

@ -116,8 +116,8 @@ public class ApiDefinitionExecResultService {
.operator(SessionUtils.getUserId()) .operator(SessionUtils.getUserId())
.context(context) .context(context)
.subject("接口用例通知") .subject("接口用例通知")
.successMailTemplate("api/CaseResult") .successMailTemplate("api/CaseResultSuccess")
.failedMailTemplate("api/CaseResult") .failedMailTemplate("api/CaseResultFailed")
.paramMap(paramMap) .paramMap(paramMap)
.event(event) .event(event)
.build(); .build();
@ -187,8 +187,6 @@ public class ApiDefinitionExecResultService {
* 定时任务时userID要改为定时任务中的用户 * 定时任务时userID要改为定时任务中的用户
*/ */
public void saveApiResultByScheduleTask(List<RequestResult> requestResults, ResultDTO dto) { public void saveApiResultByScheduleTask(List<RequestResult> requestResults, ResultDTO dto) {
Map<String, String> apiIdResultMap = new HashMap<>();
Map<String, String> caseReportMap = new HashMap<>();
boolean isFirst = true; boolean isFirst = true;
int countExpectProcessResultCount = 0; int countExpectProcessResultCount = 0;
if (CollectionUtils.isNotEmpty(requestResults)) { if (CollectionUtils.isNotEmpty(requestResults)) {
@ -201,7 +199,7 @@ public class ApiDefinitionExecResultService {
for (RequestResult item : requestResults) { for (RequestResult item : requestResults) {
if (!StringUtils.startsWithAny(item.getName(), "PRE_PROCESSOR_ENV_", "POST_PROCESSOR_ENV_")) { if (!StringUtils.startsWithAny(item.getName(), "PRE_PROCESSOR_ENV_", "POST_PROCESSOR_ENV_")) {
ApiDefinitionExecResult saveResult = this.save(item, dto.getReportId(), dto.getConsole(), countExpectProcessResultCount, dto.getRunMode(), dto.getTestId(), isFirst); this.save(item, dto.getReportId(), dto.getConsole(), countExpectProcessResultCount, dto.getRunMode(), dto.getTestId(), isFirst);
String status = item.isSuccess() ? "success" : "error"; String status = item.isSuccess() ? "success" : "error";
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) { if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
TestPlanApiCase apiCase = testPlanApiCaseService.getById(dto.getTestId()); TestPlanApiCase apiCase = testPlanApiCaseService.getById(dto.getTestId());
@ -214,19 +212,19 @@ public class ApiDefinitionExecResultService {
testPlanApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime()); testPlanApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime());
testCaseReviewApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime()); testCaseReviewApiCaseService.setExecResult(dto.getTestId(), status, item.getStartTime());
} }
if (StringUtils.isNotEmpty(dto.getTestId())) {
apiIdResultMap.put(dto.getTestId(), item.isSuccess() ? TestPlanApiExecuteStatus.SUCCESS.name() : TestPlanApiExecuteStatus.FAILD.name());
}
//更新报告ID
caseReportMap.put(dto.getTestId(), saveResult.getId());
isFirst = false; isFirst = false;
} }
} }
} }
updateTestCaseStates(dto.getTestId()); updateTestCaseStates(dto.getTestId());
Map<String, String> apiIdResultMap = new HashMap<>();
long errorSize = requestResults.stream().filter(requestResult -> requestResult.getError() > 0).count();
String status = errorSize > 0 || requestResults.isEmpty() ? TestPlanApiExecuteStatus.FAILD.name() : TestPlanApiExecuteStatus.SUCCESS.name();
if (StringUtils.isNotEmpty(dto.getReportId())) {
apiIdResultMap.put(dto.getReportId(), status);
}
testPlanLog.info("TestPlanReportId[" + dto.getTestPlanReportId() + "] APICASE OVER. API CASE STATUS:" + JSONObject.toJSONString(apiIdResultMap)); testPlanLog.info("TestPlanReportId[" + dto.getTestPlanReportId() + "] APICASE OVER. API CASE STATUS:" + JSONObject.toJSONString(apiIdResultMap));
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(dto.getTestPlanReportId(), apiIdResultMap, null, null); TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(dto.getTestPlanReportId(), apiIdResultMap, null, null);
TestPlanReportExecuteCatch.updateTestPlanReport(dto.getTestPlanReportId(), caseReportMap, null);
} }
/** /**
@ -348,13 +346,13 @@ public class ApiDefinitionExecResultService {
ApiDefinitionExecResult prevResult = extApiDefinitionExecResultMapper.selectMaxResultByResourceIdAndType(item.getName(), type); ApiDefinitionExecResult prevResult = extApiDefinitionExecResultMapper.selectMaxResultByResourceIdAndType(item.getName(), type);
if (prevResult != null) { if (prevResult != null) {
prevResult.setContent(null); prevResult.setContent(null);
apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(prevResult); apiDefinitionExecResultMapper.updateByPrimaryKeySelective(prevResult);
} }
if (StringUtils.isNotEmpty(saveResult.getTriggerMode()) && saveResult.getTriggerMode().equals("CASE")) { if (StringUtils.isNotEmpty(saveResult.getTriggerMode()) && saveResult.getTriggerMode().equals("CASE")) {
saveResult.setTriggerMode(TriggerMode.MANUAL.name()); saveResult.setTriggerMode(TriggerMode.MANUAL.name());
} }
apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(saveResult); apiDefinitionExecResultMapper.updateByPrimaryKeySelective(saveResult);
return saveResult; return saveResult;
} }
return null; return null;

View File

@ -22,6 +22,7 @@ import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult; import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult;
import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest; import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest;
import io.metersphere.api.exec.api.ApiExecuteService; import io.metersphere.api.exec.api.ApiExecuteService;
import io.metersphere.api.exec.utils.ApiDefinitionExecResultUtil;
import io.metersphere.api.parse.ApiImportParser; import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
@ -102,16 +103,12 @@ public class ApiDefinitionService {
@Resource @Resource
private ApiTestCaseMapper apiTestCaseMapper; private ApiTestCaseMapper apiTestCaseMapper;
@Resource @Resource
private ApiTestEnvironmentService environmentService;
@Resource
private EsbApiParamService esbApiParamService; private EsbApiParamService esbApiParamService;
@Resource @Resource
private TcpApiParamService tcpApiParamService; private TcpApiParamService tcpApiParamService;
@Resource @Resource
private ApiModuleMapper apiModuleMapper; private ApiModuleMapper apiModuleMapper;
@Resource @Resource
private SystemParameterService systemParameterService;
@Resource
private TestPlanMapper testPlanMapper; private TestPlanMapper testPlanMapper;
@Resource @Resource
private NoticeSendService noticeSendService; private NoticeSendService noticeSendService;
@ -855,6 +852,15 @@ public class ApiDefinitionService {
* @return * @return
*/ */
public MsExecResponseDTO run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) { public MsExecResponseDTO run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
if (!request.isDebug()) {
String testId = request.getTestElement() != null &&
CollectionUtils.isNotEmpty(request.getTestElement().getHashTree()) &&
CollectionUtils.isNotEmpty(request.getTestElement().getHashTree().get(0).getHashTree()) ?
request.getTestElement().getHashTree().get(0).getHashTree().get(0).getName() : request.getId();
ApiDefinitionExecResult result = ApiDefinitionExecResultUtil.add(testId, APITestStatus.Running.name(), request.getId());
result.setTriggerMode(TriggerMode.MANUAL.name());
apiDefinitionExecResultMapper.insert(result);
}
return apiExecuteService.debug(request, bodyFiles); return apiExecuteService.debug(request, bodyFiles);
} }

View File

@ -2,6 +2,7 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.RunModeDataDTO; import io.metersphere.api.dto.RunModeDataDTO;
import io.metersphere.api.dto.automation.ScenarioStatus;
import io.metersphere.api.exec.queue.DBTestQueue; import io.metersphere.api.exec.queue.DBTestQueue;
import io.metersphere.api.exec.scenario.ApiScenarioSerialService; import io.metersphere.api.exec.scenario.ApiScenarioSerialService;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
@ -179,9 +180,8 @@ public class ApiExecutionQueueService {
public void timeOut() { public void timeOut() {
final int SECOND_MILLIS = 1000; final int SECOND_MILLIS = 1000;
final int MINUTE_MILLIS = 60 * SECOND_MILLIS; final int MINUTE_MILLIS = 60 * SECOND_MILLIS;
long now = System.currentTimeMillis(); // 二十分钟前的超时报告
// 八分钟前的数据 final long now = System.currentTimeMillis() - (20 * MINUTE_MILLIS);
now = now - 8 * MINUTE_MILLIS;
ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample(); ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample();
example.createCriteria().andCreateTimeLessThan(now); example.createCriteria().andCreateTimeLessThan(now);
List<ApiExecutionQueueDetail> queueDetails = executionQueueDetailMapper.selectByExample(example); List<ApiExecutionQueueDetail> queueDetails = executionQueueDetailMapper.selectByExample(example);
@ -190,14 +190,14 @@ public class ApiExecutionQueueService {
queueDetails.forEach(item -> { queueDetails.forEach(item -> {
if (StringUtils.equalsAny(item.getType(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { if (StringUtils.equalsAny(item.getType(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId()); ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId());
if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name())) { if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name()) && report.getUpdateTime() < now) {
report.setStatus("timeout"); report.setStatus(ScenarioStatus.Timeout.name());
apiScenarioReportMapper.updateByPrimaryKeySelective(report); apiScenarioReportMapper.updateByPrimaryKeySelective(report);
} }
} else { } else {
ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByPrimaryKey(item.getReportId()); ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByPrimaryKey(item.getReportId());
if (result != null && StringUtils.equalsAny(result.getStatus(), TestPlanReportStatus.RUNNING.name())) { if (result != null && StringUtils.equalsAny(result.getStatus(), TestPlanReportStatus.RUNNING.name())) {
result.setStatus("timeout"); result.setStatus(ScenarioStatus.Timeout.name());
apiDefinitionExecResultMapper.updateByPrimaryKeySelective(result); apiDefinitionExecResultMapper.updateByPrimaryKeySelective(result);
} }
} }
@ -211,8 +211,8 @@ public class ApiExecutionQueueService {
if (CollectionUtils.isNotEmpty(executionQueues)) { if (CollectionUtils.isNotEmpty(executionQueues)) {
executionQueues.forEach(item -> { executionQueues.forEach(item -> {
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId()); ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(item.getReportId());
if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name())) { if (report != null && StringUtils.equalsAny(report.getStatus(), TestPlanReportStatus.RUNNING.name()) && report.getUpdateTime() < now) {
report.setStatus("timeout"); report.setStatus(ScenarioStatus.Timeout.name());
apiScenarioReportMapper.updateByPrimaryKeySelective(report); apiScenarioReportMapper.updateByPrimaryKeySelective(report);
} }
}); });

View File

@ -87,6 +87,7 @@ public class ApiScenarioReportService {
apiScenarioReportResultService.save(dto.getReportId(), requestResults); apiScenarioReportResultService.save(dto.getReportId(), requestResults);
} }
public ApiScenarioReport testEnded(ResultDTO dto) { public ApiScenarioReport testEnded(ResultDTO dto) {
if (!StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.toString())) { if (!StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.toString())) {
// 更新控制台信息 // 更新控制台信息
@ -96,6 +97,15 @@ public class ApiScenarioReportService {
example.createCriteria().andReportIdEqualTo(dto.getReportId()); example.createCriteria().andReportIdEqualTo(dto.getReportId());
List<ApiScenarioReportResult> requestResults = apiScenarioReportResultMapper.selectByExample(example); List<ApiScenarioReportResult> requestResults = apiScenarioReportResultMapper.selectByExample(example);
if (StringUtils.isNotEmpty(dto.getTestPlanReportId())) {
String status = getStatus(requestResults, dto);
Map<String, String> reportMap = new HashMap<String, String>() {{
this.put(dto.getReportId(), status);
}};
testPlanLog.info("TestPlanReportId" + JSONArray.toJSONString(dto.getReportId()) + " EXECUTE OVER. SCENARIO STATUS : " + JSONObject.toJSONString(reportMap));
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(dto.getTestPlanReportId(), null, reportMap, null);
}
ApiScenarioReport scenarioReport; ApiScenarioReport scenarioReport;
if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) { if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
scenarioReport = updatePlanCase(requestResults, dto); scenarioReport = updatePlanCase(requestResults, dto);
@ -226,7 +236,7 @@ public class ApiScenarioReportService {
} }
public ApiScenarioReport updatePlanCase(List<ApiScenarioReportResult> requestResults, ResultDTO dto) { public ApiScenarioReport updatePlanCase(List<ApiScenarioReportResult> requestResults, ResultDTO dto) {
long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count(); long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count();
TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId()); TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId());
if (testPlanApiScenario != null) { if (testPlanApiScenario != null) {
if (errorSize > 0) { if (errorSize > 0) {
@ -234,7 +244,7 @@ public class ApiScenarioReportService {
} else { } else {
testPlanApiScenario.setLastResult(ScenarioStatus.Success.name()); testPlanApiScenario.setLastResult(ScenarioStatus.Success.name());
} }
long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count();
String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size()); String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size());
testPlanApiScenario.setPassRate(passRate); testPlanApiScenario.setPassRate(passRate);
@ -245,7 +255,7 @@ public class ApiScenarioReportService {
// 更新场景状态 // 更新场景状态
ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(testPlanApiScenario.getApiScenarioId()); ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(testPlanApiScenario.getApiScenarioId());
if (scenario != null) { if (scenario != null) {
scenario.setLastResult(errorSize > 0 ? "Fail" : "Success"); scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name());
scenario.setPassRate(passRate); scenario.setPassRate(passRate);
scenario.setReportId(dto.getReportId()); scenario.setReportId(dto.getReportId());
int executeTimes = 0; int executeTimes = 0;
@ -256,11 +266,7 @@ public class ApiScenarioReportService {
apiScenarioMapper.updateByPrimaryKey(scenario); apiScenarioMapper.updateByPrimaryKey(scenario);
} }
} }
String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success"; String status = getStatus(requestResults, dto);
if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) {
LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时");
status = "Timeout";
}
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
return report; return report;
} }
@ -269,15 +275,8 @@ public class ApiScenarioReportService {
List<String> testPlanReportIdList = new ArrayList<>(); List<String> testPlanReportIdList = new ArrayList<>();
StringBuilder scenarioNames = new StringBuilder(); StringBuilder scenarioNames = new StringBuilder();
Map<String, String> scenarioAndErrorMap = new HashMap<>(); long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count();
Map<String, String> planScenarioReportMap = new HashMap<>(); String status = getStatus(requestResults, dto);
long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count();
String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success";
if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) {
LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时");
status = "Timeout";
}
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
if (report != null) { if (report != null) {
if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) { if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) {
@ -288,15 +287,12 @@ public class ApiScenarioReportService {
report.setScenarioId(testPlanApiScenario.getApiScenarioId()); report.setScenarioId(testPlanApiScenario.getApiScenarioId());
report.setEndTime(System.currentTimeMillis()); report.setEndTime(System.currentTimeMillis());
apiScenarioReportMapper.updateByPrimaryKeySelective(report); apiScenarioReportMapper.updateByPrimaryKeySelective(report);
planScenarioReportMap.put(dto.getTestId(), report.getId());
if (errorSize > 0) { if (errorSize > 0) {
scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.FAILD.name());
testPlanApiScenario.setLastResult(ScenarioStatus.Fail.name()); testPlanApiScenario.setLastResult(ScenarioStatus.Fail.name());
} else { } else {
scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.SUCCESS.name());
testPlanApiScenario.setLastResult(ScenarioStatus.Success.name()); testPlanApiScenario.setLastResult(ScenarioStatus.Success.name());
} }
long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count();
String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size()); String passRate = new DecimalFormat("0%").format((float) successSize / requestResults.size());
testPlanApiScenario.setPassRate(passRate); testPlanApiScenario.setPassRate(passRate);
@ -312,7 +308,7 @@ public class ApiScenarioReportService {
if (errorSize > 0) { if (errorSize > 0) {
scenario.setLastResult("Fail"); scenario.setLastResult("Fail");
} else { } else {
scenario.setLastResult("Success"); scenario.setLastResult(ScenarioStatus.Success.name());
} }
scenario.setPassRate(passRate); scenario.setPassRate(passRate);
scenario.setReportId(report.getId()); scenario.setReportId(report.getId());
@ -325,11 +321,6 @@ public class ApiScenarioReportService {
apiScenarioMapper.updateByPrimaryKey(scenario); apiScenarioMapper.updateByPrimaryKey(scenario);
} }
} }
testPlanLog.info("TestPlanReportId" + JSONArray.toJSONString(testPlanReportIdList) + " EXECUTE OVER. SCENARIO STATUS : " + JSONObject.toJSONString(scenarioAndErrorMap));
for (String item : testPlanReportIdList) {
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(item, null, scenarioAndErrorMap, null);
TestPlanReportExecuteCatch.updateTestPlanReport(item, null, planScenarioReportMap);
}
} }
return report; return report;
} }
@ -339,9 +330,9 @@ public class ApiScenarioReportService {
if (report != null) { if (report != null) {
// 更新场景状态 // 更新场景状态
ApiScenarioReportResultExample example = new ApiScenarioReportResultExample(); ApiScenarioReportResultExample example = new ApiScenarioReportResultExample();
example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo("Error"); example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo(ScenarioStatus.Error.name());
long size = apiScenarioReportResultMapper.countByExample(example); long size = apiScenarioReportResultMapper.countByExample(example);
report.setStatus(size > 0 ? "Error" : "Success"); report.setStatus(size > 0 ? ScenarioStatus.Error.name() : ScenarioStatus.Success.name());
report.setEndTime(System.currentTimeMillis()); report.setEndTime(System.currentTimeMillis());
// 更新控制台信息 // 更新控制台信息
apiScenarioReportStructureService.update(reportId, resultService.getJmeterLogger(reportId)); apiScenarioReportStructureService.update(reportId, resultService.getJmeterLogger(reportId));
@ -351,13 +342,10 @@ public class ApiScenarioReportService {
} }
public ApiScenarioReport updateScenario(List<ApiScenarioReportResult> requestResults, ResultDTO dto) { public ApiScenarioReport updateScenario(List<ApiScenarioReportResult> requestResults, ResultDTO dto) {
long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Error")).count(); long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count();
// 更新报告状态 // 更新报告状态
String status = errorSize > 0 || requestResults.isEmpty() ? "Error" : "Success"; String status = getStatus(requestResults, dto);
if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) {
LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时");
status = "Timeout";
}
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode()); ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
// 更新场景状态 // 更新场景状态
ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(dto.getTestId()); ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(dto.getTestId());
@ -365,8 +353,8 @@ public class ApiScenarioReportService {
scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId()); scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId());
} }
if (scenario != null) { if (scenario != null) {
scenario.setLastResult(errorSize > 0 ? "Fail" : "Success"); scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name());
long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count(); long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Success.name())).count();
scenario.setPassRate(new DecimalFormat("0%").format((float) successSize / requestResults.size())); scenario.setPassRate(new DecimalFormat("0%").format((float) successSize / requestResults.size()));
scenario.setReportId(dto.getReportId()); scenario.setReportId(dto.getReportId());
int executeTimes = 0; int executeTimes = 0;
@ -417,7 +405,7 @@ public class ApiScenarioReportService {
String event; String event;
String status; String status;
if (StringUtils.equals(scenario.getLastResult(), "Success")) { if (StringUtils.equals(scenario.getLastResult(), ScenarioStatus.Success.name())) {
event = NoticeConstants.Event.EXECUTE_SUCCESSFUL; event = NoticeConstants.Event.EXECUTE_SUCCESSFUL;
status = "成功"; status = "成功";
} else { } else {
@ -437,8 +425,8 @@ public class ApiScenarioReportService {
.operator(userId) .operator(userId)
.context(context) .context(context)
.subject("接口自动化通知") .subject("接口自动化通知")
.successMailTemplate("api/ScenarioResult") .successMailTemplate("api/ScenarioResultSuccess")
.failedMailTemplate("api/ScenarioResult") .failedMailTemplate("api/ScenarioResultFailed")
.paramMap(paramMap) .paramMap(paramMap)
.event(event) .event(event)
.build(); .build();
@ -707,4 +695,14 @@ public class ApiScenarioReportService {
report.setScenarioId(scenarioId); report.setScenarioId(scenarioId);
return report; return report;
} }
private String getStatus(List<ApiScenarioReportResult> requestResults, ResultDTO dto) {
long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count();
String status = errorSize > 0 || requestResults.isEmpty() ? ScenarioStatus.Error.name() : ScenarioStatus.Success.name();
if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) {
LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时");
status = ScenarioStatus.Timeout.name();
}
return status;
}
} }

View File

@ -3,6 +3,7 @@ package io.metersphere.api.service;
import io.metersphere.api.dto.automation.ApiTestReportVariable; import io.metersphere.api.dto.automation.ApiTestReportVariable;
import io.metersphere.api.jmeter.ExecutedHandleSingleton; import io.metersphere.api.jmeter.ExecutedHandleSingleton;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.NoticeConstants; import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.ReportTriggerMode; import io.metersphere.commons.constants.ReportTriggerMode;
@ -47,6 +48,8 @@ public class TestResultService {
private TestPlanTestCaseService testPlanTestCaseService; private TestPlanTestCaseService testPlanTestCaseService;
@Resource @Resource
private ApiTestCaseService apiTestCaseService; private ApiTestCaseService apiTestCaseService;
@Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
public void saveResults(ResultDTO dto) { public void saveResults(ResultDTO dto) {
// 处理环境 // 处理环境
@ -70,6 +73,14 @@ public class TestResultService {
updateTestCaseStates(requestResults, dto.getRunMode()); updateTestCaseStates(requestResults, dto.getRunMode());
} }
public void editReportTime(ResultDTO dto) {
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(dto.getReportId());
if (report != null) {
report.setUpdateTime(System.currentTimeMillis());
apiScenarioReportMapper.updateByPrimaryKey(report);
}
}
public void testEnded(ResultDTO dto) { public void testEnded(ResultDTO dto) {
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) { if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
ApiScenarioReport scenarioReport = apiScenarioReportService.testEnded(dto); ApiScenarioReport scenarioReport = apiScenarioReportService.testEnded(dto);

View File

@ -114,6 +114,9 @@
<if test="request.platform != null and request.platform != ''"> <if test="request.platform != null and request.platform != ''">
and issues.platform = #{request.platform} and issues.platform = #{request.platform}
</if> </if>
<if test="request.id != null and request.id != ''">
and issues.id = #{request.id}
</if>
<!-- <if test="request.ids != null and request.ids.size() > 0">--> <!-- <if test="request.ids != null and request.ids.size() > 0">-->
<!-- and issues.id in--> <!-- and issues.id in-->

View File

@ -3,6 +3,7 @@ package io.metersphere.job.sechedule;
import io.metersphere.commons.constants.ReportTriggerMode; import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup; import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.service.TestPlanService; import io.metersphere.track.service.TestPlanService;
import org.quartz.*; import org.quartz.*;
@ -16,25 +17,10 @@ import org.quartz.*;
public class TestPlanTestJob extends MsScheduleJob { public class TestPlanTestJob extends MsScheduleJob {
private String projectID; private String projectID;
// private PerformanceTestService performanceTestService;
// private TestPlanScenarioCaseService testPlanScenarioCaseService;
// private TestPlanApiCaseService testPlanApiCaseService;
// private ApiTestCaseService apiTestCaseService;
// private TestPlanReportService testPlanReportService;
// private TestPlanLoadCaseService testPlanLoadCaseService;
private TestPlanService testPlanService; private TestPlanService testPlanService;
public TestPlanTestJob() { public TestPlanTestJob() {
// this.performanceTestService = CommonBeanFactory.getBean(PerformanceTestService.class);
// this.testPlanScenarioCaseService = CommonBeanFactory.getBean(TestPlanScenarioCaseService.class);
// this.testPlanApiCaseService = CommonBeanFactory.getBean(TestPlanApiCaseService.class);
// this.apiTestCaseService = CommonBeanFactory.getBean(ApiTestCaseService.class);
// this.testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
// this.testPlanLoadCaseService = CommonBeanFactory.getBean(TestPlanLoadCaseService.class);
this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class); this.testPlanService = CommonBeanFactory.getBean(TestPlanService.class);
} }
/** /**
@ -63,7 +49,17 @@ public class TestPlanTestJob extends MsScheduleJob {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String config = jobDataMap.getString("config"); String config = jobDataMap.getString("config");
testPlanService.run(this.resourceId, this.projectID, this.userId, ReportTriggerMode.SCHEDULE.name(),config); String runResourceId = this.resourceId;
String runProjectId = this.projectID;
String runUserId = this.userId;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
LogUtil.info("Start test_plan_scehdule. test_plan_id:" + runResourceId);
testPlanService.run(runResourceId, runProjectId, runUserId, ReportTriggerMode.SCHEDULE.name(),config);
}
});
thread.start();
} }
public static JobKey getJobKey(String testId) { public static JobKey getJobKey(String testId) {

View File

@ -68,6 +68,14 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
@Value("${jmeter.home}") @Value("${jmeter.home}")
private String jmeterHome; private String jmeterHome;
@Value("${quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock}")
private String acquireTriggersWithinLock;
@Value("${quartz.enabled}")
private boolean quartzEnable;
@Value("${quartz.scheduler-name}")
private String quartzScheduleName;
@Value("${quartz.thread-count}")
private int quartzThreadCount;
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
@ -99,7 +107,12 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
LogUtil.info("开始启动定时任务。 相关设置:" +
"quartz.acquireTriggersWithinLock :" + acquireTriggersWithinLock + "\r\n" +
"quartz.enabled :" + quartzEnable + "\r\n" +
"quartz.scheduler-name :" + quartzScheduleName + "\r\n" +
"quartz.thread-count :" + quartzThreadCount + "\r\n"
);
scheduleService.startEnableSchedules(); scheduleService.startEnableSchedules();
} }

View File

@ -153,16 +153,27 @@ public class ReflexObjectUtil {
GsonDiff diff = new GsonDiff(); GsonDiff diff = new GsonDiff();
Object originalValue = originalColumns.get(i).getOriginalValue(); Object originalValue = originalColumns.get(i).getOriginalValue();
Object newValue = newColumns.get(i).getOriginalValue(); Object newValue = newColumns.get(i).getOriginalValue();
List<String> originalValueArray = JSON.parseArray(originalValue.toString(), String.class); String oldTags = null;
if (originalValue != null && !StringUtils.equals("null", originalValue.toString())) {
List<String> originalValueArray = JSON.parseArray(originalValue.toString(), String.class);
Collections.sort(originalValueArray);
Object originalObject = JSON.toJSON(originalValueArray);
oldTags = ApiDefinitionDiffUtil.JSON_START + ((originalColumns.get(i) != null && originalObject != null) ? originalObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END;
}
List<String> newValueArray = JSON.parseArray(newValue.toString(), String.class); List<String> newValueArray = JSON.parseArray(newValue.toString(), String.class);
Collections.sort(originalValueArray);
Collections.sort(newValueArray); Collections.sort(newValueArray);
Object originalObject = JSON.toJSON(originalValueArray);
Object newObject = JSON.toJSON(newValueArray); Object newObject = JSON.toJSON(newValueArray);
String oldTags = ApiDefinitionDiffUtil.JSON_START + ((originalColumns.get(i) != null && originalObject != null) ? originalObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END;
String newTags = ApiDefinitionDiffUtil.JSON_START + ((newColumns.get(i) != null && newObject != null) ? newObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END; String newTags = ApiDefinitionDiffUtil.JSON_START + ((newColumns.get(i) != null && newObject != null) ? newObject.toString() : "\"\"") + ApiDefinitionDiffUtil.JSON_END;
String diffStr = diff.diff(oldTags, newTags); String diffValue;
String diffValue = diff.apply(newTags, diffStr); if (oldTags != null) {
String diffStr = diff.diff(oldTags, newTags);
diffValue = diff.apply(newTags, diffStr);
} else {
int indexAdd = newTags.indexOf("[");
String substring = newTags.substring(0, indexAdd + 2);
String substring1 = newTags.substring(indexAdd + 2);
diffValue = substring + "++" + substring1;
}
column.setDiffValue(diffValue); column.setDiffValue(diffValue);
} }
// 深度对比 // 深度对比

View File

@ -19,11 +19,10 @@ import io.metersphere.track.service.TestCaseReviewService;
import io.metersphere.track.service.TestCaseService; import io.metersphere.track.service.TestCaseService;
import io.metersphere.track.service.TestPlanService; import io.metersphere.track.service.TestPlanService;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -128,29 +127,31 @@ public abstract class AbstractNoticeSender implements NoticeSender {
} }
protected String getContent(String template, Map<String, Object> context) { protected String getContent(String template, Map<String, Object> context) {
if (MapUtils.isNotEmpty(context)) { // 处理 null
for (String k : context.keySet()) { context.forEach((k, v) -> {
if (context.get(k) != null) { if (v == null) {
String value = handleTime(k, context); context.put(k, "");
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", value);
} else {
template = RegExUtils.replaceAll(template, "\\$\\{" + k + "}", "");
}
} }
} });
return template; // 处理时间格式的数据
handleTime(context);
StringSubstitutor sub = new StringSubstitutor(context);
return sub.replace(template);
} }
private String handleTime(String k, Map<String, Object> context) { private void handleTime(Map<String, Object> context) {
String value = context.get(k).toString(); context.forEach((k, v) -> {
if (StringUtils.endsWithIgnoreCase(k, "Time")) { if (StringUtils.endsWithIgnoreCase(k, "Time")) {
try { try {
long time = Long.parseLong(value); String value = v.toString();
value = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss"); long time = Long.parseLong(value);
} catch (Exception ignore) { v = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss");
context.put(k, v);
} catch (Exception ignore) {
}
} }
} });
return value;
} }
protected List<UserDetail> getUserDetails(List<String> userIds) { protected List<UserDetail> getUserDetails(List<String> userIds) {

View File

@ -35,6 +35,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.alibaba.fastjson.serializer.SerializerFeature.WriteMapNullValue;
@Aspect @Aspect
@Component @Component
public class SendNoticeAspect { public class SendNoticeAspect {
@ -81,7 +83,7 @@ public class SendNoticeAspect {
Expression titleExp = parser.parseExpression(target); Expression titleExp = parser.parseExpression(target);
Object v = titleExp.getValue(context, Object.class); Object v = titleExp.getValue(context, Object.class);
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler); Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
memberValues.put("source", JSON.toJSONString(v)); memberValues.put("source", JSON.toJSONString(v, WriteMapNullValue));
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), e); LogUtil.error(e.getMessage(), e);
@ -119,7 +121,7 @@ public class SendNoticeAspect {
Object v = titleExp.getValue(context, Object.class); Object v = titleExp.getValue(context, Object.class);
if (v != null) { if (v != null) {
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler); Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
memberValues.put("source", JSON.toJSONString(v)); memberValues.put("source", JSON.toJSONString(v, WriteMapNullValue));
} }
} }

View File

@ -3,7 +3,6 @@ package io.metersphere.performance.engine;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.RunRequest;
import io.metersphere.base.domain.LoadTestReportWithBLOBs; import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.domain.TestResource; import io.metersphere.base.domain.TestResource;
import io.metersphere.base.domain.TestResourcePool; import io.metersphere.base.domain.TestResourcePool;
@ -13,6 +12,7 @@ import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.config.JmeterProperties; import io.metersphere.config.JmeterProperties;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.performance.service.PerformanceTestService; import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.TestResourcePoolService; import io.metersphere.service.TestResourcePoolService;
import io.metersphere.service.TestResourceService; import io.metersphere.service.TestResourceService;
@ -42,7 +42,7 @@ public abstract class AbstractEngine implements Engine {
GC_ALGO = CommonBeanFactory.getBean(JmeterProperties.class).getGcAlgo(); GC_ALGO = CommonBeanFactory.getBean(JmeterProperties.class).getGcAlgo();
} }
protected void initApiConfig(RunRequest runRequest) { protected void initApiConfig(JmeterRunRequestDTO runRequest) {
String resourcePoolId = runRequest.getPoolId(); String resourcePoolId = runRequest.getPoolId();
resourcePool = testResourcePoolService.getResourcePool(resourcePoolId); resourcePool = testResourcePoolService.getResourcePool(resourcePoolId);
if (resourcePool == null || StringUtils.equals(resourcePool.getStatus(), ResourceStatusEnum.DELETE.name())) { if (resourcePool == null || StringUtils.equals(resourcePool.getStatus(), ResourceStatusEnum.DELETE.name())) {

View File

@ -300,12 +300,20 @@ public class EngineFactory {
rootDocument = docBuilder.parse(inputSource); rootDocument = docBuilder.parse(inputSource);
Element jmeterTestPlan = rootDocument.getDocumentElement(); Element jmeterTestPlan = rootDocument.getDocumentElement();
NodeList childNodes = jmeterTestPlan.getChildNodes(); NodeList childNodes = jmeterTestPlan.getChildNodes();
outer:
for (int i = 0; i < childNodes.getLength(); i++) { for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i); Node node = childNodes.item(i);
if (node instanceof Element) { if (node instanceof Element) {
// jmeterTestPlan的子元素肯定是<hashTree></hashTree> // jmeterTestPlan的子元素肯定是<hashTree></hashTree>
hashTree = (Element) node; NodeList childNodes1 = node.getChildNodes();
break; for (int j = 0; j < childNodes1.getLength(); j++) {
Node item = childNodes1.item(j);
if (StringUtils.equalsIgnoreCase("hashTree", item.getNodeName())) {
hashTree = (Element) node;
break outer;
}
}
} }
} }
} else { } else {
@ -320,9 +328,19 @@ public class EngineFactory {
NodeList secondChildNodes = secondHashTree.getChildNodes(); NodeList secondChildNodes = secondHashTree.getChildNodes();
for (int j = 0; j < secondChildNodes.getLength(); j++) { for (int j = 0; j < secondChildNodes.getLength(); j++) {
Node item = secondChildNodes.item(j); Node item = secondChildNodes.item(j);
Node newNode = item.cloneNode(true); if (StringUtils.equalsIgnoreCase("TestPlan", item.getNodeName())) {
rootDocument.adoptNode(newNode); continue;
hashTree.appendChild(newNode); }
if (StringUtils.equalsIgnoreCase("hashTree", item.getNodeName())) {
NodeList itemChildNodes = item.getChildNodes();
for (int k = 0; k < itemChildNodes.getLength(); k++) {
Node item1 = itemChildNodes.item(k);
Node newNode = item1.cloneNode(true);
rootDocument.adoptNode(newNode);
hashTree.appendChild(newNode);
}
}
} }
} }
} }

View File

@ -11,6 +11,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.EnvironmentGroupRequest; import io.metersphere.controller.request.EnvironmentGroupRequest;
import io.metersphere.dto.EnvironmentGroupDTO; import io.metersphere.dto.EnvironmentGroupDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -161,7 +162,7 @@ public class EnvironmentGroupService {
private void checkEnvironmentGroup(EnvironmentGroupRequest request) { private void checkEnvironmentGroup(EnvironmentGroupRequest request) {
String name = request.getName(); String name = request.getName();
if (StringUtils.isBlank(name)) { if (StringUtils.isBlank(name)) {
MSException.throwException("environment group name is null."); MSException.throwException(Translator.get("null_environment_group_name"));
} }
EnvironmentGroupExample environmentGroupExample = new EnvironmentGroupExample(); EnvironmentGroupExample environmentGroupExample = new EnvironmentGroupExample();
@ -173,7 +174,7 @@ public class EnvironmentGroupService {
} }
if (environmentGroupMapper.countByExample(environmentGroupExample) > 0) { if (environmentGroupMapper.countByExample(environmentGroupExample) > 0) {
MSException.throwException("环境组名称 " + request.getName() + " 已存在!"); MSException.throwException(Translator.get("environment_group_name")+request.getName()+Translator.get("environment_group_exist"));
} }
} }

View File

@ -29,7 +29,7 @@ public class IssueCommentController {
@PostMapping("/save") @PostMapping("/save")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT) @RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT)
@SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#targetClass.get(#request.issuesId)", targetClass = IssuesService.class, @SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#targetClass.get(#request.issuesId)", targetClass = IssuesService.class,
event = NoticeConstants.Event.COMMENT, mailTemplate = "track/IssuesCommentUpdate", subject = "缺陷评论更新通知") event = NoticeConstants.Event.COMMENT, mailTemplate = "track/IssuesCommentUpdate", subject = "缺陷")
public IssueComment saveComment(@RequestBody IssuesRelevanceRequest request) { public IssueComment saveComment(@RequestBody IssuesRelevanceRequest request) {
request.setId(UUID.randomUUID().toString()); request.setId(UUID.randomUUID().toString());
return issueCommentService.saveComment(request); return issueCommentService.saveComment(request);

View File

@ -133,7 +133,7 @@ public class TestPlanController {
@PostMapping("/delete/{testPlanId}") @PostMapping("/delete/{testPlanId}")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_DELETE) @RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_DELETE)
@MsAuditLog(module = "track_test_plan", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#testPlanId)", msClass = TestPlanService.class) @MsAuditLog(module = "track_test_plan", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#testPlanId)", msClass = TestPlanService.class)
@SendNotice(taskType = NoticeConstants.TaskType.TEST_PLAN_TASK, target = "#targetClass.getTestPlan(#testPlanId)", targetClass = TestPlanService.class, @SendNotice(taskType = NoticeConstants.TaskType.TEST_PLAN_TASK, target = "#targetClass.get(#testPlanId)", targetClass = TestPlanService.class,
event = NoticeConstants.Event.DELETE, mailTemplate = "track/TestPlanDelete", subject = "测试计划通知") event = NoticeConstants.Event.DELETE, mailTemplate = "track/TestPlanDelete", subject = "测试计划通知")
public int deleteTestPlan(@PathVariable String testPlanId) { public int deleteTestPlan(@PathVariable String testPlanId) {
checkPermissionService.checkTestPlanOwner(testPlanId); checkPermissionService.checkTestPlanOwner(testPlanId);

View File

@ -0,0 +1,11 @@
package io.metersphere.track.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestPlanReportExecuteCheckResultDTO {
private boolean isTimeOut;
private boolean isFinishedCaseChanged;
}

View File

@ -81,7 +81,9 @@ public class TestPlanReportService {
@Resource @Resource
ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper; ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource @Resource
ApiTestCaseMapper apiTestCaseMapper; ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
@Resource
ExtApiScenarioReportMapper extApiScenarioReportMapper;
@Resource @Resource
LoadTestReportMapper loadTestReportMapper; LoadTestReportMapper loadTestReportMapper;
@Resource @Resource
@ -551,8 +553,6 @@ public class TestPlanReportService {
boolean scenarioIsOk = executeInfo.isScenarioAllExecuted(); boolean scenarioIsOk = executeInfo.isScenarioAllExecuted();
boolean performanceIsOk = executeInfo.isLoadCaseAllExecuted(); boolean performanceIsOk = executeInfo.isLoadCaseAllExecuted();
testPlanLog.info("ReportId[" + testPlanReport.getId() + "] count over. Testplan Execute Result: Api is over ->" + apiCaseIsOk + "; scenario is over ->" + scenarioIsOk + "; performance is over ->" + performanceIsOk);
if (apiCaseIsOk) { if (apiCaseIsOk) {
testPlanReport.setIsApiCaseExecuting(false); testPlanReport.setIsApiCaseExecuting(false);
} }
@ -1075,12 +1075,16 @@ public class TestPlanReportService {
} }
public void countReport(String planReportId) { public void countReport(String planReportId) {
boolean isTimeOut = this.checkTestPlanReportIsTimeOut(planReportId); TestPlanReportExecuteCheckResultDTO checkResult = this.checkTestPlanReportIsTimeOut(planReportId);
if (isTimeOut) { testPlanLog.info("Check PlanReport:" + planReportId + "; result: "+ JSON.toJSONString(checkResult));
if (checkResult.isTimeOut()) {
//判断是否超时超时时强行停止任务 //判断是否超时超时时强行停止任务
TestPlanReportExecuteCatch.finishAllTask(planReportId); TestPlanReportExecuteCatch.finishAllTask(planReportId);
checkResult.setFinishedCaseChanged(true);
}
if(checkResult.isFinishedCaseChanged()){
this.updateExecuteApis(planReportId);
} }
this.updateExecuteApis(planReportId);
} }
public TestPlanSimpleReportDTO getReport(String reportId) { public TestPlanSimpleReportDTO getReport(String reportId) {
@ -1216,19 +1220,61 @@ public class TestPlanReportService {
testPlanReportContentMapper.updateByExampleSelective(bloBs,example); testPlanReportContentMapper.updateByExampleSelective(bloBs,example);
} }
private boolean checkTestPlanReportIsTimeOut(String planReportId) { private TestPlanReportExecuteCheckResultDTO checkTestPlanReportIsTimeOut(String planReportId) {
//同步数据库更新状态信息
try {
this.syncReportStatus(planReportId);
} catch (Exception e) {
LogUtil.info("联动数据库同步执行状态失败! " + e.getMessage());
LogUtil.error(e);
}
TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId); TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId);
int unFinishNum = executeInfo.countUnFinishedNum(); TestPlanReportExecuteCheckResultDTO checkResult = executeInfo.countUnFinishedNum();
if (unFinishNum > 0) { return checkResult;
//20分钟没有案例执行结果更新则定位超时 }
long lastCountTime = executeInfo.getLastFinishedNumCountTime();
long nowTime = System.currentTimeMillis(); private void syncReportStatus(String planReportId) {
testPlanLog.info("ReportId: ["+planReportId+"]; timeCount:"+ (nowTime - lastCountTime)); if (TestPlanReportExecuteCatch.containsReport(planReportId)) {
if (nowTime - lastCountTime > 1200000) { TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId);
return true; if (executeInfo != null) {
//同步接口案例结果
Map<String, String> updateCaseStatusMap = new HashMap<>();
Map<String, String> apiCaseReportMap = executeInfo.getRunningApiCaseReportMap();
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
List<ApiDefinitionExecResult> execList = extApiDefinitionExecResultMapper.selectStatusByIdList(apiCaseReportMap.keySet());
for (ApiDefinitionExecResult report : execList) {
String reportId = report.getId();
String status = report.getStatus();
if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) {
String planCaseId = apiCaseReportMap.get(reportId);
if (StringUtils.isNotEmpty(planCaseId)) {
updateCaseStatusMap.put(planCaseId, status);
}
}
}
}
//同步场景结果
Map<String, String> updateScenarioStatusMap = new HashMap<>();
Map<String, String> scenarioReportMap = executeInfo.getRunningScenarioReportMap();
if (MapUtils.isNotEmpty(scenarioReportMap)) {
List<ApiScenarioReport> reportList = extApiScenarioReportMapper.selectStatusByIds(scenarioReportMap.keySet());
for (ApiScenarioReport report : reportList) {
String reportId = report.getId();
String status = report.getStatus();
if (!StringUtils.equalsAnyIgnoreCase(status, "Running", "Waiting")) {
String planScenarioId = scenarioReportMap.get(reportId);
if (StringUtils.isNotEmpty(planScenarioId)) {
updateScenarioStatusMap.put(planScenarioId, status);
}
}
}
}
testPlanLog.info("ReportID:"+planReportId+" 本次数据库同步,案例ID"+JSON.toJSONString(apiCaseReportMap.keySet())+";场景ID"+JSON.toJSONString(scenarioReportMap.keySet())+"; 同步结果,案例:"+JSON.toJSONString(updateCaseStatusMap)+";场景:"+JSON.toJSONString(updateScenarioStatusMap));
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, updateCaseStatusMap, updateScenarioStatusMap, null);
}else {
testPlanLog.info("同步数据库查询执行信息失败! 报告ID在缓存中未找到"+planReportId);
} }
} }
return false;
} }
private void finishTestPlanReport(String planReportId) { private void finishTestPlanReport(String planReportId) {
@ -1239,5 +1285,4 @@ public class TestPlanReportService {
} }
TestPlanReportExecuteCatch.remove(planReportId); TestPlanReportExecuteCatch.remove(planReportId);
} }
} }

View File

@ -243,6 +243,10 @@ public class TestPlanService {
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlanWithBLOBs()); return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlanWithBLOBs());
} }
public TestPlanWithBLOBs get(String testPlanId) {
return testPlanMapper.selectByPrimaryKey(testPlanId);
}
public TestPlan editTestPlanWithRequest(AddTestPlanRequest request) { public TestPlan editTestPlanWithRequest(AddTestPlanRequest request) {
List<String> principals = request.getPrincipals(); List<String> principals = request.getPrincipals();
if (!CollectionUtils.isEmpty(principals)) { if (!CollectionUtils.isEmpty(principals)) {
@ -1116,8 +1120,8 @@ public class TestPlanService {
testPlanLoadCaseService.update(testPlanLoadCase); testPlanLoadCaseService.update(testPlanLoadCase);
LogUtil.error(e); LogUtil.error(e);
} }
performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId);
if (StringUtils.isNotEmpty(reportId)) { if (StringUtils.isNotEmpty(reportId)) {
performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId);
executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.RUNNING.name()); executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.RUNNING.name());
} else { } else {
executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.PREPARE.name()); executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.PREPARE.name());

View File

@ -22,7 +22,7 @@ public class IndexWebSocket {
public void openSession(@PathParam("reportId") String reportId, Session session) { public void openSession(@PathParam("reportId") String reportId, Session session) {
WebSocketUtils.ONLINE_USER_SESSIONS.put(reportId, session); WebSocketUtils.ONLINE_USER_SESSIONS.put(reportId, session);
log.info("客户端: [" + reportId + "] : 连接成功!"); log.info("客户端: [" + reportId + "] : 连接成功!");
WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!"); //WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!");
} }
/** /**
@ -45,7 +45,7 @@ public class IndexWebSocket {
WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId); WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId);
log.info("[" + reportId + "] : 断开连接!"); log.info("[" + reportId + "] : 断开连接!");
//并且通知其他人当前用户已经断开连接了 //并且通知其他人当前用户已经断开连接了
WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!"); //WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!");
session.close(); session.close();
} }

View File

@ -1,5 +1,6 @@
package io.metersphere.websocket.c.to.c; package io.metersphere.websocket.c.to.c;
import io.metersphere.utils.LoggerUtil;
import io.metersphere.websocket.c.to.c.util.MsgDto; import io.metersphere.websocket.c.to.c.util.MsgDto;
import javax.websocket.RemoteEndpoint; import javax.websocket.RemoteEndpoint;
@ -12,9 +13,13 @@ public class WebSocketUtils {
// 单用户推送 // 单用户推送
public static void sendMessage(Session session, String message) { public static void sendMessage(Session session, String message) {
if (session == null) { return; } if (session == null) {
return;
}
RemoteEndpoint.Async async = session.getAsyncRemote(); RemoteEndpoint.Async async = session.getAsyncRemote();
if (async == null) { return; } if (async == null) {
return;
}
async.sendText(message); async.sendText(message);
} }
@ -30,4 +35,20 @@ public class WebSocketUtils {
sendMessage(session, message); sendMessage(session, message);
}); });
} }
//当前的Session 移除
public static void onClose(String reportId) {
try {
if (WebSocketUtils.ONLINE_USER_SESSIONS.containsKey(reportId)) {
WebSocketUtils.ONLINE_USER_SESSIONS.get(reportId).close();
WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId);
}
if (WebSocketUtils.ONLINE_USER_SESSIONS.containsKey(("send." + reportId))) {
WebSocketUtils.ONLINE_USER_SESSIONS.get(("send." + reportId)).close();
WebSocketUtils.ONLINE_USER_SESSIONS.remove(("send." + reportId));
}
} catch (Exception e) {
LoggerUtil.error("关闭socket失败" + e.getMessage());
}
}
} }

View File

@ -1,19 +0,0 @@
package io.metersphere.websocket.c.to.c.util;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author: jason
* @Date: 2020-12-23
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class SocketClient {
private Integer userId;
private String username;
}

@ -1 +1 @@
Subproject commit a1e39f9c1baf30f52da50ab34ea1bacbfbeff60c Subproject commit 3cac4fd4c1cb2b3dfb4bd82a6a09cc55ae2f2d34

View File

@ -12,7 +12,7 @@ server.ssl.key-alias=localhost
# Hikari # Hikari
spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=100 spring.datasource.hikari.maximum-pool-size=200
spring.datasource.hikari.auto-commit=true spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=10000 spring.datasource.hikari.idle-timeout=10000
spring.datasource.hikari.pool-name=DatebookHikariCP spring.datasource.hikari.pool-name=DatebookHikariCP
@ -23,7 +23,7 @@ spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.quartz.url=${spring.datasource.url} spring.datasource.quartz.url=${spring.datasource.url}
spring.datasource.quartz.username=${spring.datasource.username} spring.datasource.quartz.username=${spring.datasource.username}
spring.datasource.quartz.password=${spring.datasource.password} spring.datasource.quartz.password=${spring.datasource.password}
spring.datasource.quartz.hikari.maximum-pool-size=50 spring.datasource.quartz.hikari.maximum-pool-size=200
spring.datasource.quartz.hikari.auto-commit=true spring.datasource.quartz.hikari.auto-commit=true
spring.datasource.quartz.hikari.idle-timeout=10000 spring.datasource.quartz.hikari.idle-timeout=10000
spring.datasource.quartz.hikari.pool-name=DatebookHikariCP spring.datasource.quartz.hikari.pool-name=DatebookHikariCP
@ -92,7 +92,8 @@ jmeter.home=/opt/jmeter
# quartz # quartz
quartz.enabled=true quartz.enabled=true
quartz.scheduler-name=msServerJob quartz.scheduler-name=msServerJob
quartz.thread-count=30 quartz.thread-count=60
quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true
# file upload # file upload
spring.servlet.multipart.max-file-size=500MB spring.servlet.multipart.max-file-size=500MB
spring.servlet.multipart.max-request-size=500MB spring.servlet.multipart.max-request-size=500MB

View File

@ -285,6 +285,10 @@ test_case_status=Case status
id_not_rightful=ID is not rightful id_not_rightful=ID is not rightful
# mock # mock
mock_warning=No matching Mock expectation was found mock_warning=No matching Mock expectation was found
zentao_test_type_error=请求方式错误 zentao_test_type_error=invalid Zentao request
#项目报告 #项目报告
enterprise_test_report=Enterprise report enterprise_test_report=Enterprise report
#环境组
null_environment_group_name = Environment group name is null
environment_group_name = Environment group name
environment_group_exist = already exists

View File

@ -284,6 +284,10 @@ test_case_status=用例状态
id_not_rightful=ID 不合法 id_not_rightful=ID 不合法
# mock # mock
mock_warning=未找到匹配的Mock期望 mock_warning=未找到匹配的Mock期望
zentao_test_type_error=invalid Zentao request zentao_test_type_error=无效的 Zentao 请求
#项目报告 #项目报告
enterprise_test_report=项目报告 enterprise_test_report=项目报告
#环境组
null_environment_group_name = 环境组名称不存在
environment_group_name = 环境组名称
environment_group_exist = 已存在

View File

@ -287,3 +287,7 @@ mock_warning=未找到匹配的Mock期望
zentao_test_type_error=請求方式錯誤 zentao_test_type_error=請求方式錯誤
#项目报告 #项目报告
enterprise_test_report=項目報告 enterprise_test_report=項目報告
#环境组
null_environment_group_name = 環境組名稱不存在
environment_group_name = 環境組名稱
environment_group_exist = 已存在

View File

@ -6,7 +6,7 @@
</head> </head>
<body> <body>
<div> <div>
<p>${operator}执行接口用例: ${name}, 结果: ${status}</p> <p>${operator}执行接口用例失败: ${name}</p>
</div> </div>
</body> </body>
</html> </html>

View File

@ -6,7 +6,7 @@
</head> </head>
<body> <body>
<div> <div>
<p>${operator}执行接口自动化: ${name}, 结果: ${status}</p> <p>${operator}执行接口用例成功: ${name}</p>
</div> </div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MeterSphere</title>
</head>
<body>
<div>
<p>${operator}执行接口自动化失败: ${name}</p>
</div>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MeterSphere</title>
</head>
<body>
<div>
<p>${operator}执行接口自动化成功: ${name}</p>
</div>
</body>
</html>

View File

@ -324,16 +324,21 @@ export default {
if (data) { if (data) {
this.report = data; this.report = data;
if (this.report.reportVersion && this.report.reportVersion > 1) { if (this.report.reportVersion && this.report.reportVersion > 1) {
if (data.content) { this.report.status = data.status;
let report = JSON.parse(data.content); if (!this.isNotRunning) {
this.content = report; setTimeout(this.getReport, 2000)
this.fullTreeNodes = report.steps; } else {
this.content.console = report.console; if (data.content) {
this.content.error = report.error; let report = JSON.parse(data.content);
this.content.success = (report.total - report.error); this.content = report;
this.totalTime = report.totalTime; this.fullTreeNodes = report.steps;
this.content.console = report.console;
this.content.error = report.error;
this.content.success = (report.total - report.error);
this.totalTime = report.totalTime;
}
this.loading = false;
} }
this.loading = false;
} else { } else {
this.buildReport(); this.buildReport();
} }
@ -528,10 +533,12 @@ export default {
.report-container .is-active .fail { .report-container .is-active .fail {
color: inherit; color: inherit;
} }
.report-console { .report-console {
height: calc(100vh - 270px); height: calc(100vh - 270px);
overflow-y: auto; overflow-y: auto;
} }
.export-button { .export-button {
float: right; float: right;
} }

View File

@ -356,7 +356,7 @@ export default {
if (e.data) { if (e.data) {
this.runningEvaluation(e.data); this.runningEvaluation(e.data);
} }
if (e.data && e.data.indexOf("断开连接") !== -1) { if (e.data && e.data.indexOf("MS_TEST_END") !== -1) {
this.getReport(); this.getReport();
this.messageWebSocket.close(); this.messageWebSocket.close();
scenario.$emit('hide', this.scenarioId); scenario.$emit('hide', this.scenarioId);

View File

@ -454,7 +454,8 @@ export default {
permissions: ['PROJECT_API_SCENARIO:READ+DELETE'] permissions: ['PROJECT_API_SCENARIO:READ+DELETE']
}, },
{ {
name: "批量恢复", handleClick: this.handleBatchRestore name: this.$t('commons.batch_restore'),
handleClick: this.handleBatchRestore
}, },
], ],
unTrashButtons: [ unTrashButtons: [
@ -794,7 +795,7 @@ export default {
// todo // todo
if (this.condition.selectAll) { if (this.condition.selectAll) {
this.$warning("暂不支持批量添加所有场景到测试计划!"); this.$warning(this.$t('api_test.scenario.warning_context'));
} }
this.planVisible = false; this.planVisible = false;
@ -1010,7 +1011,7 @@ export default {
this.$get("/api/automation/checkScenarioEnv/" + this.currentScenario.id, res => { this.$get("/api/automation/checkScenarioEnv/" + this.currentScenario.id, res => {
let data = res.data; let data = res.data;
if (!data) { if (!data) {
this.$warning("请为场景选择环境!"); this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return false; return false;
} }
this.reportId = getUUID().substring(0, 8); this.reportId = getUUID().substring(0, 8);

View File

@ -789,7 +789,7 @@ export default {
} }
this.runningEvaluation(e.data); this.runningEvaluation(e.data);
this.message = getUUID(); this.message = getUUID();
if (e.data && e.data.indexOf("断开连接") !== -1) { if (e.data && e.data.indexOf("MS_TEST_END") !== -1) {
this.runScenario = undefined; this.runScenario = undefined;
this.debugLoading = false; this.debugLoading = false;
this.message = "stop"; this.message = "stop";

View File

@ -148,7 +148,7 @@
}, },
getConditions() { getConditions() {
let sampleSelectRows = this.selectRows; let sampleSelectRows = this.selectRows;
let param = buildBatchParam(this); let param = buildBatchParam(this, undefined, this.projectId);
param.ids = Array.from(sampleSelectRows).map(row => row.id); param.ids = Array.from(sampleSelectRows).map(row => row.id);
return param; return param;
}, },

View File

@ -282,7 +282,7 @@ export default {
}, },
getConditions() { getConditions() {
let sampleSelectRows = this.$refs.table.getSelectRows(); let sampleSelectRows = this.$refs.table.getSelectRows();
let batchParam = buildBatchParam(this); let batchParam = buildBatchParam(this, undefined, this.projectId);
let param = {}; let param = {};
if (batchParam.condition) { if (batchParam.condition) {
param = batchParam.condition; param = batchParam.condition;

View File

@ -364,7 +364,7 @@ export default {
} }
} }
} }
if (databaseConfigsOptions.length > 0) { if (databaseConfigsOptions.length > 0 && this.request.environmentId !== this.environment.id) {
this.request.dataSourceId = databaseConfigsOptions[0].id; this.request.dataSourceId = databaseConfigsOptions[0].id;
this.request.environmentId = this.environment.id; this.request.environmentId = this.environment.id;
} }
@ -639,7 +639,7 @@ export default {
clickResource(resource) { clickResource(resource) {
if (resource.refType && resource.refType === 'API') { if (resource.refType && resource.refType === 'API') {
if(resource.protocol==='dubbo://'){ if (resource.protocol === 'dubbo://') {
resource.protocol = 'DUBBO' resource.protocol = 'DUBBO'
} }
let definitionData = this.$router.resolve({ let definitionData = this.$router.resolve({

View File

@ -13,7 +13,7 @@
:placeholder="$t('api_test.definition.request.run_env')" :placeholder="$t('api_test.definition.request.run_env')"
clearable> clearable>
<el-option v-for="(environment, index) in environments" :key="index" <el-option v-for="(environment, index) in environments" :key="index"
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')" :label="environment.name"
:value="environment.id"/> :value="environment.id"/>
<template v-slot:empty> <template v-slot:empty>
<div class="empty-environment"> <div class="empty-environment">

View File

@ -945,7 +945,7 @@ export default {
apiNames += ";" + item; apiNames += ";" + item;
} }
}); });
this.$error("请先恢复[" + apiNames + "]接口"); this.$error(this.$t('api_test.definition.case_reduction_error_text') + "[" + apiNames + "]" + this.$t("api_test.home_page.api_details_card.title"));
} else { } else {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
} }
@ -971,7 +971,7 @@ export default {
apiNames += ";" + item; apiNames += ";" + item;
} }
}); });
this.$error("请先恢复[" + apiNames + "]接口"); this.$error(this.$t('api_test.definition.case_reduction_error_text') + "[" + apiNames + "]" + this.$t("api_test.home_page.api_details_card.title"));
} else { } else {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
} }

View File

@ -302,7 +302,8 @@ export default {
permissions: ['PROJECT_API_DEFINITION:READ+DELETE_API'] permissions: ['PROJECT_API_DEFINITION:READ+DELETE_API']
}, },
{ {
name: "批量恢复", handleClick: this.handleBatchRestore name: this.$t('commons.batch_restore'),
handleClick: this.handleBatchRestore
}, },
], ],
tableOperatorButtons: [], tableOperatorButtons: [],

View File

@ -4,12 +4,16 @@
<el-col> <el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_code') }} :</div> <div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_code') }} :</div>
<el-tooltip <el-tooltip
v-if="responseResult.responseCode"
:content="responseResult.responseCode" :content="responseResult.responseCode"
placement="top"> placement="top">
<div class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'"> <div class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'">
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }} {{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
</div> </div>
</el-tooltip> </el-tooltip>
<div v-else class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'">
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
</div>
</el-col> </el-col>
<el-col> <el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_time') }} :</div> <div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_time') }} :</div>

View File

@ -165,7 +165,7 @@ export default {
if (now - d.createTime > 10 * 1000) { if (now - d.createTime > 10 * 1000) {
return; return;
} }
d.user = this.userMap[d.operator]; d.user = this.userMap[d.operator] || {name: 'MS'};
let message = d.user.name + getOperation(d.operation) + getResource(d) + ": " + d.resourceName; let message = d.user.name + getOperation(d.operation) + getResource(d) + ": " + d.resourceName;
let title = d.type === 'MENTIONED_ME' ? this.$t('commons.mentioned_me_notice') : this.$t('commons.system_notice'); let title = d.type === 'MENTIONED_ME' ? this.$t('commons.mentioned_me_notice') : this.$t('commons.system_notice');
setTimeout(() => { setTimeout(() => {

View File

@ -443,7 +443,7 @@ export default {
created() { created() {
this.isReadOnly = !hasPermission('PROJECT_PERFORMANCE_REPORT:READ+DELETE'); this.isReadOnly = !hasPermission('PROJECT_PERFORMANCE_REPORT:READ+DELETE');
this.reportId = this.$route.path.split('/')[4]; this.reportId = this.$route.path.split('/')[4];
if (!this.reportId && this.perReportId) { if (this.perReportId) {
this.reportId = this.perReportId; this.reportId = this.perReportId;
} }
this.getReport(this.reportId); this.getReport(this.reportId);
@ -452,6 +452,9 @@ export default {
'$route'(to) { '$route'(to) {
if (to.name === "perReportView") { if (to.name === "perReportView") {
this.reportId = to.path.split('/')[4]; this.reportId = to.path.split('/')[4];
if (this.perReportId) {
this.reportId = this.perReportId;
}
this.getReport(this.reportId); this.getReport(this.reportId);
this.initBreadcrumb((response) => { this.initBreadcrumb((response) => {
this.initReportTimeInfo(); this.initReportTimeInfo();

View File

@ -171,7 +171,6 @@ export default {
created() { created() {
this.data = []; this.data = [];
this.instances = []; this.instances = [];
this.id = this.$route.path.split('/')[4];
this.getResource(); this.getResource();
}, },
methods: { methods: {
@ -350,13 +349,6 @@ export default {
}, },
}, },
watch: { watch: {
'$route'(to) {
if (to.name === "perReportView") {
this.id = to.path.split('/')[4];
this.init = false;
this.getResource();
}
},
report: { report: {
handler(val) { handler(val) {
if (!val.status || !val.id) { if (!val.status || !val.id) {

View File

@ -251,7 +251,7 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div> <div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart> <ms-chart v-if="rampUpTimeVisible" class="chart-container" ref="chart1" :options="options" :autoresize="true"/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>

View File

@ -28,10 +28,12 @@
size="lg" @click="unFullScreen"/> size="lg" @click="unFullScreen"/>
</div> </div>
</el-row> </el-row>
<el-row style="overflow: auto"> <el-row>
<ms-chart ref="chart1" v-if="!loading" :options="dataOption" <div class="chart-style">
:style="{width: chartWidthNumber+'px', height: (h-50) + 'px'}" class="chart-config" :autoresize="true" <ms-chart ref="chart1" v-if="!loading" :options="dataOption"
id="picChart"/> :style="{width: chartWidthNumber+'px', height: (h-70) + 'px'}" class="chart-config" :autoresize="true"
id="picChart"/>
</div>
</el-row> </el-row>
</el-card> </el-card>
</div> </div>
@ -73,7 +75,7 @@ export default {
{id: 'pie', name: this.$t('commons.report_statistics.pie')} {id: 'pie', name: this.$t('commons.report_statistics.pie')}
], ],
order: "", order: "",
orders: [{id: '', name: '默认排序'}, {id: 'desc', name: this.$t('commons.report_statistics.desc')}, { orders: [{id: '', name: this.$t('commons.sort_default')}, {id: 'desc', name: this.$t('commons.report_statistics.desc')}, {
id: 'asc', id: 'asc',
name: this.$t('commons.report_statistics.asc') name: this.$t('commons.report_statistics.asc')
}], }],
@ -223,6 +225,9 @@ export default {
height: calc(100vh / 1.95); height: calc(100vh / 1.95);
} }
.chart-style{
overflow: auto;
}
.tip { .tip {
float: left; float: left;
font-size: 14px; font-size: 14px;

View File

@ -53,7 +53,7 @@
chartType: "line", chartType: "line",
charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}], charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}],
order: "", order: "",
orders: [{id: '', name: '默认排序'},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}], orders: [{id: '', name: this.$t('commons.sort_default')},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}],
loading: false, loading: false,
options: {}, options: {},
} }

View File

@ -88,8 +88,8 @@
{id: 'BATCH_UPDATE', label: this.$t('api_test.definition.request.batch_edit')}, {id: 'BATCH_UPDATE', label: this.$t('api_test.definition.request.batch_edit')},
{id: 'BATCH_ADD', label: this.$t('commons.batch_add')}, {id: 'BATCH_ADD', label: this.$t('commons.batch_add')},
{id: 'UN_ASSOCIATE_CASE', label: this.$t('test_track.case.unlink')}, {id: 'UN_ASSOCIATE_CASE', label: this.$t('test_track.case.unlink')},
{id: 'BATCH_RESTORE', label: "批量恢复"}, {id: 'BATCH_RESTORE', label: this.$t('commons.batch_restore')},
{id: 'BATCH_GC', label: "批量回收"} {id: 'BATCH_GC', label: this.$t('commons.batch_gc')}
], ],
LOG_TYPE_MAP: new Map([ LOG_TYPE_MAP: new Map([
['CREATE', this.$t('api_test.definition.request.create_info')], ['CREATE', this.$t('api_test.definition.request.create_info')],

View File

@ -56,50 +56,144 @@ export function LOG_TYPE_MAP(_this) {
return LOG_TYPE_MAP; return LOG_TYPE_MAP;
} }
export function SYSLIST(){ export function SYSLIST() {
let sysList = [ let sysList = [
{ {
label: i18n.t('test_track.test_track'), value: i18n.t('test_track.test_track'), children: [ label: i18n.t('test_track.test_track'), value: i18n.t('test_track.test_track'), children: [
{label: i18n.t('permission.project_track_case.name'), value: i18n.t('permission.project_track_case.name'), leaf: true}, {
label: i18n.t('permission.project_track_case.name'),
value: i18n.t('permission.project_track_case.name'),
leaf: true
},
{label: i18n.t('test_track.review.test_review'), value: i18n.t('test_track.review.test_review'), leaf: true}, {label: i18n.t('test_track.review.test_review'), value: i18n.t('test_track.review.test_review'), leaf: true},
{label: i18n.t('test_track.plan.test_plan'), value: i18n.t('test_track.plan.test_plan'), leaf: true}, {label: i18n.t('test_track.plan.test_plan'), value: i18n.t('test_track.plan.test_plan'), leaf: true},
{label: i18n.t('test_track.issue.issue_management'), value: i18n.t('test_track.issue.issue_management'), leaf: true}, {
label: i18n.t('test_track.issue.issue_management'),
value: i18n.t('test_track.issue.issue_management'),
leaf: true
},
{label: i18n.t('commons.report'), value: i18n.t('commons.report'), leaf: true}] {label: i18n.t('commons.report'), value: i18n.t('commons.report'), leaf: true}]
}, },
{ {
label: i18n.t('commons.api'), value: i18n.t('commons.api'), children: [ label: i18n.t('commons.api'), value: i18n.t('commons.api'), children: [
{label: i18n.t('workstation.table_name.api_definition'), value: i18n.t('workstation.table_name.api_definition'), leaf: true}, {
{label: i18n.t('workstation.table_name.api_automation'), value: i18n.t('workstation.table_name.api_automation'), leaf: true}, label: i18n.t('workstation.table_name.api_definition'),
{label: i18n.t('permission.project_api_report.name'), value: i18n.t('permission.project_api_report.name'), leaf: true}] value: i18n.t('workstation.table_name.api_definition'),
leaf: true
},
{
label: i18n.t('workstation.table_name.api_automation'),
value: i18n.t('workstation.table_name.api_automation'),
leaf: true
},
{
label: i18n.t('permission.project_api_report.name'),
value: i18n.t('permission.project_api_report.name'),
leaf: true
}]
}, },
{ {
label: i18n.t('workstation.table_name.performance'), value: i18n.t('workstation.table_name.performance'), children: [ label: i18n.t('workstation.table_name.performance'),
{label: i18n.t('workstation.table_name.performance'), value: i18n.t('workstation.table_name.performance'), leaf: true}, value: i18n.t('workstation.table_name.performance'),
children: [
{
label: i18n.t('workstation.table_name.performance'),
value: i18n.t('workstation.table_name.performance'),
leaf: true
},
{label: i18n.t('report.load_test_report'), value: i18n.t('report.load_test_report'), leaf: true}] {label: i18n.t('report.load_test_report'), value: i18n.t('report.load_test_report'), leaf: true}]
}, },
{ {
label: i18n.t('commons.system_setting'), value: i18n.t('commons.system_setting'), children: [ label: i18n.t('commons.system_setting'), value: i18n.t('commons.system_setting'), children: [
{label: i18n.t('commons.system')+"-"+i18n.t('commons.user'), value: i18n.t('commons.system')+"-"+i18n.t('commons.user'), leaf: true}, {
{label: i18n.t('commons.system')+"-"+i18n.t('commons.test_resource_pool'), value: i18n.t('commons.system')+"-"+i18n.t('commons.test_resource_pool'), leaf: true}, label: i18n.t('commons.system') + "-" + i18n.t('commons.user'),
{label: i18n.t('commons.system')+"-"+i18n.t('commons.system_parameter_setting'), value: i18n.t('commons.system')+"-"+i18n.t('commons.system_parameter_setting'), leaf: true}, value: i18n.t('commons.system') + "-" + i18n.t('commons.user'),
{label: i18n.t('commons.system')+"-"+i18n.t('commons.quota'), value: i18n.t('commons.system')+"-"+i18n.t('commons.quota'), leaf: true}, leaf: true
{label: i18n.t('commons.system')+"-"+i18n.t('license.title'), value: i18n.t('commons.system')+"-"+i18n.t('license.title'), leaf: true}, },
{
label: i18n.t('commons.system') + "-" + i18n.t('commons.test_resource_pool'),
value: i18n.t('commons.system') + "-" + i18n.t('commons.test_resource_pool'),
leaf: true
},
{
label: i18n.t('commons.system') + "-" + i18n.t('commons.system_parameter_setting'),
value: i18n.t('commons.system') + "-" + i18n.t('commons.system_parameter_setting'),
leaf: true
},
{
label: i18n.t('commons.system') + "-" + i18n.t('commons.quota'),
value: i18n.t('commons.system') + "-" + i18n.t('commons.quota'),
leaf: true
},
{
label: i18n.t('commons.system') + "-" + i18n.t('license.title'),
value: i18n.t('commons.system') + "-" + i18n.t('license.title'),
leaf: true
},
{label: i18n.t('commons.workspace'), value: i18n.t('commons.workspace'), leaf: true}, {label: i18n.t('commons.workspace'), value: i18n.t('commons.workspace'), leaf: true},
{label: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_service.name'), value: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_service.name'), leaf: true}, {
{label: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_message.name'), value: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_message.name'), leaf: true}, label: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_service.name'),
{label: i18n.t('commons.workspace')+"-"+i18n.t('permission.project_user.name'), value: i18n.t('commons.workspace')+"-"+i18n.t('permission.project_user.name'), leaf: true}, value: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_service.name'),
{label: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_template.name'), value: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_template.name'), leaf: true}, leaf: true
{label: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_project_manager.name'), value: i18n.t('commons.workspace')+"-"+i18n.t('permission.workspace_project_manager.name'), leaf: true}, },
{
label: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_message.name'),
value: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_message.name'),
leaf: true
},
{
label: i18n.t('commons.workspace') + "-" + i18n.t('permission.project_user.name'),
value: i18n.t('commons.workspace') + "-" + i18n.t('permission.project_user.name'),
leaf: true
},
{
label: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_template.name'),
value: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_template.name'),
leaf: true
},
{
label: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_project_manager.name'),
value: i18n.t('commons.workspace') + "-" + i18n.t('permission.workspace_project_manager.name'),
leaf: true
},
{label: i18n.t('commons.project')+"-"+i18n.t('project.manager'), value: i18n.t('commons.project')+"-"+i18n.t('project.manager'), leaf: true}, {
{label: i18n.t('commons.project')+"-"+i18n.t('permission.project_user.name'), value: i18n.t('commons.project')+"-"+i18n.t('permission.project_user.name'), leaf: true}, label: i18n.t('commons.project') + "-" + i18n.t('project.manager'),
{label: i18n.t('commons.project')+"-"+i18n.t('api_test.jar_config.jar_manage'), value: i18n.t('commons.project')+"-"+i18n.t('api_test.jar_config.jar_manage'), leaf: true}, value: i18n.t('commons.project') + "-" + i18n.t('project.manager'),
{label: i18n.t('commons.project')+"-"+i18n.t('permission.workspace_project_environment.name'), value: i18n.t('commons.project')+"-"+i18n.t('permission.workspace_project_environment.name'), leaf: true}, leaf: true
{label: i18n.t('commons.project')+"-"+i18n.t('permission.project_file.name'), value: i18n.t('commons.project')+"-"+i18n.t('permission.project_file.name'), leaf: true}, },
{
label: i18n.t('commons.project') + "-" + i18n.t('permission.project_user.name'),
value: i18n.t('commons.project') + "-" + i18n.t('permission.project_user.name'),
leaf: true
},
{
label: i18n.t('commons.project') + "-" + i18n.t('api_test.jar_config.jar_manage'),
value: i18n.t('commons.project') + "-" + i18n.t('api_test.jar_config.jar_manage'),
leaf: true
},
{
label: i18n.t('commons.project') + "-" + i18n.t('permission.workspace_project_environment.name'),
value: i18n.t('commons.project') + "-" + i18n.t('permission.workspace_project_environment.name'),
leaf: true
},
{
label: i18n.t('commons.project') + "-" + i18n.t('permission.project_file.name'),
value: i18n.t('commons.project') + "-" + i18n.t('permission.project_file.name'),
leaf: true
},
{label: i18n.t('commons.personal_information')+"-"+i18n.t('commons.personal_setting'), value: i18n.t('commons.personal_information')+"-"+i18n.t('commons.personal_setting'), leaf: true}, {
{label: i18n.t('commons.personal_information')+"-API Keys", value: i18n.t('commons.personal_information')+"-API Keys", leaf: true} label: i18n.t('commons.personal_information') + "-" + i18n.t('commons.personal_setting'),
value: i18n.t('commons.personal_information') + "-" + i18n.t('commons.personal_setting'),
leaf: true
},
{
label: i18n.t('commons.personal_information') + "-API Keys",
value: i18n.t('commons.personal_information') + "-API Keys",
leaf: true
}
] ]
}, },
]; ];
@ -121,76 +215,120 @@ export function getUrl(d) {
} }
} }
switch (d.operModule) { switch (d.operModule) {
case "接口自动化" || "Api automation" || "接口自動化": case "接口自动化" :
case "Api automation" :
case"接口自動化":
url += "/api/automation?resourceId=" + resourceId; url += "/api/automation?resourceId=" + resourceId;
break; break;
case "测试计划" || "測試計劃" || "Test plan": case "测试计划" :
case "測試計劃" :
case "Test plan":
url += "/track/plan/view/" + resourceId; url += "/track/plan/view/" + resourceId;
break; break;
case "用例评审" || "Case review" || "用例評審": case "用例评审" :
case "Case review" :
case "用例評審":
url += "/track/review/view/" + resourceId; url += "/track/review/view/" + resourceId;
break; break;
case "缺陷管理" || "Defect management": case "缺陷管理" :
case "Defect management":
url += "/track/issue"; url += "/track/issue";
break; break;
case "SWAGGER_TASK" : case "SWAGGER_TASK" :
url += "/api/definition"; url += "/api/definition";
break; break;
case "接口定义" || "接口定義" || "Api definition": case "接口定义" :
case "接口定義" :
case "Api definition":
url += "/api/definition?resourceId=" + resourceId; url += "/api/definition?resourceId=" + resourceId;
break; break;
case "接口定义用例" || "接口定義用例" || "Api definition case": case "接口定义用例" :
case "接口定義用例":
case "Api definition case":
url += "/api/definition?caseId=" + resourceId; url += "/api/definition?caseId=" + resourceId;
break; break;
case "测试报告" || "測試報告" || "Test Report": case "测试报告" :
case "測試報告" :
case "Test Report":
url += "/api/automation/report"; url += "/api/automation/report";
break; break;
case "性能测试报告" || "性能測試報告" || "Performance test report" : case "性能测试报告" :
case "性能測試報告" :
case "Performance test report" :
url += "/performance/report/all"; url += "/performance/report/all";
break; break;
case "性能测试" || "性能測試" || "Performance test" : case "性能测试" :
case "性能測試" :
case "Performance test" :
url += "/performance/test/edit/" + resourceId; url += "/performance/test/edit/" + resourceId;
break; break;
case "测试用例" || "測試用例" || "Test case": case "测试用例" :
case "測試用例" :
case "Test case":
url += "/track/case/all?resourceId=" + resourceId; url += "/track/case/all?resourceId=" + resourceId;
break; break;
case "系统-用户" || "系统-用户" || "System user": case "系统-用户":
case "System user":
url += "/setting/user"; url += "/setting/user";
break; break;
case "系统-组织" || "系統-組織" || "System organization": case "系统-组织" :
case "系統-組織" :
case "System organization":
url += "/setting/organization"; url += "/setting/organization";
break; break;
case "工作空间" || "系统-工作空间" || "workspace" : case "工作空间" :
case "系统-工作空间" :
case "workspace" :
url += "/setting/systemworkspace"; url += "/setting/systemworkspace";
break; break;
case "用户组与权限" || "用戶組與權限" || "Group" : case "用户组与权限" :
case "用戶組與權限" :
case "Group" :
url += "/setting/usergroup"; url += "/setting/usergroup";
break; break;
case "系统-测试资源池" || "系统-測試資源池" || "System test resource" : case "系统-测试资源池":
case "系统-測試資源池" :
case "System test resource" :
url += "/setting/testresourcepool"; url += "/setting/testresourcepool";
break; break;
case "系统-系统参数设置" || "系统-系統參數設置" || "System parameter setting" : case "系统-系统参数设置":
case "系统-系統參數設置" :
case "System parameter setting" :
url += "/setting/systemparametersetting"; url += "/setting/systemparametersetting";
break; break;
case "工作空间-成员" || "工作空間-成員" || "Workspace member" : case "工作空间-成员" :
case "工作空間-成員" :
case "Workspace member" :
url += "/setting/member"; url += "/setting/member";
break; break;
case "项目-项目管理" || "項目-項目管理" || "Project project manager" : case "项目-项目管理" :
case "項目-項目管理" :
case "Project project manager" :
url += "/setting/project/:type"; url += "/setting/project/:type";
break; break;
case "项目-环境设置" || "項目-環境設置" || "Project environment setting" : case "项目-环境设置" :
case "項目-環境設置" :
case "Project environment setting" :
url += "/project/env"; url += "/project/env";
break; break;
case "工作空间-模版设置-自定义字段" || "工作空間-模版設置-自定義字段" || "Workspace template settings field" : case "工作空间-模版设置-自定义字段" :
case "工作空間-模版設置-自定義字段" :
case "Workspace template settings field" :
url += "/setting/workspace/template/field"; url += "/setting/workspace/template/field";
break; break;
case "工作空间-模版设置-用例模版" || "工作空間-模版設置-用例模板" || "Workspace template settings case" : case "工作空间-模版设置-用例模版" :
case "工作空間-模版設置-用例模板" :
case "Workspace template settings case" :
url += "/setting/workspace/template/case"; url += "/setting/workspace/template/case";
break; break;
case "工作空间-模版设置-缺陷模版" || "工作空間-模版設置-缺陷模板" || "Workspace template settings issue" : case "工作空间-模版设置-缺陷模版" :
case "工作空間-模版設置-缺陷模板" :
case "Workspace template settings issue" :
url += "/setting/workspace/template/issues"; url += "/setting/workspace/template/issues";
break; break;
case "项目-成员" || "項目-成員" || "Project member" : case "项目-成员":
case "項目-成員" :
case "Project member" :
url += "/project/member"; url += "/project/member";
break; break;
default: default:

View File

@ -339,7 +339,7 @@ export default {
this.$warning(this.$t('commons.check_project_tip')); this.$warning(this.$t('commons.check_project_tip'));
return; return;
} }
this.showPublic = true this.showPublic = false
if (tab.name === 'add') { if (tab.name === 'add') {
let label = this.$t('test_track.case.create'); let label = this.$t('test_track.case.create');
let name = getUUID().substring(0, 8); let name = getUUID().substring(0, 8);

View File

@ -10,7 +10,7 @@
:key="index" :key="index"
:comment="comment" :comment="comment"
:read-only="readOnly" :read-only="readOnly"
@refresh="getComments()"/> @refresh="getComments()" api-url="/test/case"/>
<div v-if="comments.length === 0" style="text-align: center"> <div v-if="comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">

View File

@ -115,7 +115,7 @@
<review-comment-item v-for="(comment,index) in comments" <review-comment-item v-for="(comment,index) in comments"
:key="index" :key="index"
:comment="comment" :comment="comment"
@refresh="getComments"/> @refresh="getComments" api-url="/test/case"/>
<div v-if="comments.length === 0" style="text-align: center"> <div v-if="comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">

View File

@ -102,7 +102,7 @@
<review-comment-item v-for="(comment,index) in comments" <review-comment-item v-for="(comment,index) in comments"
:key="index" :key="index"
:comment="comment" :comment="comment"
@refresh="getComments" :disabled="true"/> @refresh="getComments" :disabled="true" api-url="/test/case"/>
<div v-if="comments.length === 0" style="text-align: center"> <div v-if="comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">

View File

@ -1,15 +1,17 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px;"> <div v-for="pe in data" :key="pe.id" style="margin-left: 20px;">
<el-select v-model="pe['selectEnv']" placeholder="请选择环境" style="margin-top: 8px;width: 200px;" size="small"> <el-select v-model="pe['selectEnv']" :placeholder="$t('api_test.environment.select_environment')"
style="margin-top: 8px;width: 200px;" size="small">
<el-option v-for="(environment, index) in pe.envs" :key="index" <el-option v-for="(environment, index) in pe.envs" :key="index"
:label="environment.name" :label="environment.name"
:value="environment.id"/> :value="environment.id"/>
<el-button class="ms-scenario-button" v-if="isShowConfirmButton(pe.id)" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)"> <el-button class="ms-scenario-button" v-if="isShowConfirmButton(pe.id)" size="mini" type="primary"
@click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }} {{ $t('api_test.environment.environment_config') }}
</el-button> </el-button>
<template v-slot:empty> <template v-slot:empty>
<div v-if="isShowConfirmButton(pe.id)" class="empty-environment"> <div v-if="isShowConfirmButton(pe.id)" class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)"> <el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }} {{ $t('api_test.environment.environment_config') }}
</el-button> </el-button>
@ -21,7 +23,8 @@
</span> </span>
</div> </div>
<el-button type="primary" @click="handleConfirm" size="small" class="env-confirm"> </el-button> <el-button type="primary" @click="handleConfirm" size="small" class="env-confirm">{{ $t('commons.confirm') }}
</el-button>
<!-- 环境配置 --> <!-- 环境配置 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/> <api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>

View File

@ -41,6 +41,9 @@ import {addIssueHotBox, getSelectedNodeData, handleIssueAdd, handleIssueBatch} f
import IssueRelateList from "@/business/components/track/case/components/IssueRelateList"; import IssueRelateList from "@/business/components/track/case/components/IssueRelateList";
import TestPlanIssueEdit from "@/business/components/track/case/components/TestPlanIssueEdit"; import TestPlanIssueEdit from "@/business/components/track/case/components/TestPlanIssueEdit";
import {getIssuesById} from "@/network/Issue"; import {getIssuesById} from "@/network/Issue";
const {getIssuesListById} = require("@/network/Issue");
const {getCurrentWorkspaceId} = require("@/common/js/utils");
export default { export default {
name: "TestCaseMinder", name: "TestCaseMinder",
components: {TestPlanIssueEdit, IssueRelateList, MsModuleMinder}, components: {TestPlanIssueEdit, IssueRelateList, MsModuleMinder},
@ -84,7 +87,11 @@ name: "TestCaseMinder",
moveEnable() { moveEnable() {
// //
return !this.condition.orders || this.condition.orders.length < 1; return !this.condition.orders || this.condition.orders.length < 1;
},
workspaceId(){
return getCurrentWorkspaceId();
} }
}, },
watch: { watch: {
selectNode() { selectNode() {
@ -119,7 +126,7 @@ name: "TestCaseMinder",
isNotDisableNode = true; isNotDisableNode = true;
} }
if (node.data.type === 'issue') { if (node.data.type === 'issue') {
getIssuesById(node.data.id, (data) => { getIssuesListById(node.data.id, this.projectId,this.workspaceId,(data) => {
data.customFields = JSON.parse(data.customFields); data.customFields = JSON.parse(data.customFields);
this.$refs.issueEdit.open(data); this.$refs.issueEdit.open(data);
}); });

View File

@ -88,7 +88,7 @@ export default {
} }
this.result = this.$post('/issues/comment/save', comment, () => { this.result = this.$post('/issues/comment/save', comment, () => {
this.$success(this.$t('test_track.comment.send_success')); this.$success(this.$t('test_track.comment.send_success'));
this.refresh(comment.IssueId); this.refresh(comment.issuesId);
this.from.description = ''; this.from.description = '';
this.dialogTableVisible = false; this.dialogTableVisible = false;
}); });

View File

@ -29,10 +29,10 @@ export default {
} }
}, },
methods: { methods: {
open(data) { open(data, type) {
this.visible = true; this.visible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.issueEditDetail.open(data); this.$refs.issueEditDetail.open(data, type);
}) })
}, },
handleClose() { handleClose() {

View File

@ -165,7 +165,8 @@ export default {
title: '', title: '',
description: '', description: '',
creator: null, creator: null,
remark: null remark: null,
tapdUsers:[]
}, },
tapdUsers: [], tapdUsers: [],
zentaoUsers: [], zentaoUsers: [],
@ -234,8 +235,9 @@ export default {
}, },
}, },
methods: { methods: {
open(data) { open(data, type) {
this.result.loading = true; this.result.loading = true;
this.type = type;
this.$nextTick(() => { this.$nextTick(() => {
getIssuePartTemplateWithProject((template, project) => { getIssuePartTemplateWithProject((template, project) => {
this.currentProject = project; this.currentProject = project;
@ -243,17 +245,18 @@ export default {
}); });
}); });
if(data&&data.id){ if (data && data.id) {
this.$get('/issues/follow/' + data.id, response => { this.$get('/issues/follow/' + data.id, response => {
this.form.follows = response.data; this.form.follows = response.data;
for (let i = 0; i < response.data.length; i++) { for (let i = 0; i < response.data.length; i++) {
if(response.data[i]===this.currentUser().id){ if (response.data[i] === this.currentUser().id) {
this.showFollow = true; this.showFollow = true;
break; break;
} }
} }
}) })
}else { } else {
this.issueId = null;
this.form.follows = []; this.form.follows = [];
} }
}, },
@ -324,6 +327,7 @@ export default {
} }
} }
this.customFieldForm = parseCustomField(this.form, this.issueTemplate, this.customFieldRules); this.customFieldForm = parseCustomField(this.form, this.issueTemplate, this.customFieldRules);
this.comments = [];
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.testCaseIssueList) { if (this.$refs.testCaseIssueList) {
this.$refs.testCaseIssueList.initTableData(); this.$refs.testCaseIssueList.initTableData();
@ -421,6 +425,10 @@ export default {
} }
}, },
openComment() { openComment() {
if (!this.issueId) {
this.$warning(this.$t('test_track.issue.save_before_open_comment'));
return;
}
this.$refs.issueComment.open(); this.$refs.issueComment.open();
}, },
getComments() { getComments() {

View File

@ -295,17 +295,17 @@ export default {
}, },
handleEdit(data) { handleEdit(data) {
this.$refs.issueEdit.open(data); this.$refs.issueEdit.open(data, 'edit');
}, },
handleCreate() { handleCreate() {
this.$refs.issueEdit.open(); this.$refs.issueEdit.open(null, 'add');
}, },
handleCopy(data) { handleCopy(data) {
let copyData = {}; let copyData = {};
Object.assign(copyData, data); Object.assign(copyData, data);
copyData.id = null; copyData.id = null;
copyData.name = data.name + '_copy'; copyData.name = data.name + '_copy';
this.$refs.issueEdit.open(copyData); this.$refs.issueEdit.open(copyData, 'copy');
}, },
handleDelete(data) { handleDelete(data) {
this.page.result = this.$get('issues/delete/' + data.id, () => { this.page.result = this.$get('issues/delete/' + data.id, () => {

View File

@ -450,7 +450,9 @@ export default {
if (this.planId) { if (this.planId) {
this.$post("/test/plan/scenario/case/run", param, response => { this.$post("/test/plan/scenario/case/run", param, response => {
this.runVisible = true; this.runVisible = true;
this.reportId = response.data; if (response.data && response.data.length > 0) {
this.reportId = response.data[0].reportId;
}
}); });
} }
if (this.reviewId) { if (this.reviewId) {

View File

@ -19,46 +19,45 @@
</el-button> </el-button>
</span> </span>
<span class="comment-delete"> <span class="comment-delete">
<el-link icon="el-icon-edit" v-if="!isImage" style="font-size: 9px;margin-right: 6px;" @click="openEdit" :disabled="readOnly"/> <el-link icon="el-icon-edit" style="font-size: 9px;margin-right: 6px;" @click="openEdit" :disabled="readOnly"/>
<el-link icon="el-icon-close" @click="deleteComment" :disabled="readOnly"/> <el-link icon="el-icon-close" @click="deleteComment" :disabled="readOnly"/>
</span> </span>
<br/> <br/>
<!-- <div class="comment-desc" style="font-size: 10px;color: #303133">-->
<!-- <pre>{{ comment.description }}</pre>--> <div v-if="!isImage" class="comment-desc" style="font-size: 10px;color: #303133">
<!-- </div>-->
<div v-if="!isImage" class="comment-desc" style="font-size: 10px;color: #303133">
<pre>{{ comment.description }}</pre> <pre>{{ comment.description }}</pre>
</div> </div>
<div v-if="isImage" class="demo-image__preview"> <div v-if="isImage" class="demo-image__preview">
<pre>{{ imgDescription }}</pre> <pre>{{ imgDescription }}</pre>
<el-image <el-image
:z-index="imageIndex" :z-index="imageIndex"
style="width: 100px; height: 100px;" style="width: 100px; height: 100px;"
fit="contain" fit="contain"
:src="src" :src="src"
:preview-src-list="srcList"> :preview-src-list="srcList">
</el-image> </el-image>
</div> </div>
</div> </div>
<el-dialog <el-dialog :visible.sync="visible"
:title="$t('commons.edit')" :title="$t('commons.edit')"
:visible.sync="visible" :destroy-on-close="true"
width="30%" :close-on-click-modal="false"
:destroy-on-close="true" append-to-body>
:append-to-body="true"
:close-on-click-modal="false" <div>
show-close> <div class="editors_div_style">
<el-input <div id="editorsDiv">
type="textarea" <ms-mark-down-text prop="description" :data="comment" :toolbars="toolbars"/>
:rows="5" </div>
v-model="description"> </div>
</el-input> <div>
<span slot="footer" class="dialog-footer"> <el-button type="primary" size="mini" class="send-btn" @click="editComment">
<ms-dialog-footer {{ $t('test_track.comment.send') }}
@cancel="visible = false" </el-button>
@confirm="editComment"/> </div>
</span> </div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -66,10 +65,11 @@
<script> <script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter"; import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import {getCurrentUser} from "@/common/js/utils"; import {getCurrentUser} from "@/common/js/utils";
import MsMarkDownText from "@/business/components/track/case/components/MsMarkDownText";
export default { export default {
name: "ReviewCommentItem", name: "ReviewCommentItem",
components: {MsDialogFooter}, components: {MsDialogFooter, MsMarkDownText},
props: { props: {
comment: Object, comment: Object,
readOnly: { readOnly: {
@ -83,16 +83,51 @@ export default {
return { return {
visible: false, visible: false,
imgDescription: "", imgDescription: "",
imageIndex:99999, imageIndex: 99999,
src:"", src: "",
srcList:[], srcList: [],
imgNameList:[], imgNameList: [],
description: "", description: "",
imageMatchPattern:"(\\!\\[)\\S+]\\(\\S+\\)", imageMatchPattern: "(\\!\\[)\\S+]\\(\\S+\\)",
toolbars: {
bold: false, //
italic: false, //
header: false, //
underline: false, // 线
strikethrough: false, // 线
mark: false, //
superscript: false, //
subscript: false, //
quote: false, //
ol: false, //
ul: false, //
link: false, //
imagelink: true, //
code: false, // code
table: false, //
fullscreen: false, //
readmodel: false, //
htmlcode: false, // html
help: false, //
/* 1.3.5 */
undo: false, //
redo: false, //
trash: false, //
save: false, // eventssave
/* 1.4.2 */
navigation: false, //
/* 2.1.8 */
alignleft: false, //
aligncenter: false, //
alignright: false, //
/* 2.2.1 */
subfield: false, //
preview: false, //
}
} }
}, },
computed:{ computed: {
isImage(){ isImage() {
return this.checkImage(this.comment.description); return this.checkImage(this.comment.description);
} }
}, },
@ -102,7 +137,7 @@ export default {
this.$warning(this.$t('test_track.comment.cannot_delete')); this.$warning(this.$t('test_track.comment.cannot_delete'));
return; return;
} }
if(this.imgNameList.length > 0){ if (this.imgNameList.length > 0) {
this.imgNameList.forEach(imgName => { this.imgNameList.forEach(imgName => {
this.$get('/resource/md/delete/' + imgName); this.$get('/resource/md/delete/' + imgName);
}); });
@ -121,90 +156,91 @@ export default {
this.visible = true; this.visible = true;
}, },
editComment() { editComment() {
this.$post(this.apiUrl + "/comment/edit", {id: this.comment.id, description: this.description}, () => { this.$post(this.apiUrl + "/comment/edit", {id: this.comment.id, description: this.comment.description}, () => {
this.visible = false; this.visible = false;
this.$success(this.$t('commons.modify_success')); this.$success(this.$t('commons.modify_success'));
this.$emit("refresh"); this.$emit("refresh");
}); });
}, },
checkImage(){ checkImage() {
this.srcList = [];
let param = this.comment.description; let param = this.comment.description;
let returnFlag = false; let returnFlag = false;
if(param){ if (param) {
let message = param+""; let message = param + "";
let matchIndex = message.indexOf("](/resource/md/get/"); let matchIndex = message.indexOf("](/resource/md/get/");
if(matchIndex > 0){ if (matchIndex > 0) {
let messageSplitArr = message.split("](/resource/md/get/"); let messageSplitArr = message.split("](/resource/md/get/");
for(let itemIndex = 0;itemIndex < messageSplitArr.length; itemIndex ++){ for (let itemIndex = 0; itemIndex < messageSplitArr.length; itemIndex++) {
let itemStr = messageSplitArr[itemIndex]; let itemStr = messageSplitArr[itemIndex];
let picNameIndex = itemStr.indexOf("!["); let picNameIndex = itemStr.indexOf("![");
if( picNameIndex < 0){ if (picNameIndex < 0) {
let endUrlIndex = itemStr.indexOf(")"); let endUrlIndex = itemStr.indexOf(")");
if( endUrlIndex > 0){ if (endUrlIndex > 0) {
let itemStrArr = itemStr.substr(0,endUrlIndex); let itemStrArr = itemStr.substr(0, endUrlIndex);
//if(imgNameList.) //if(imgNameList.)
if(this.imgNameList.indexOf(itemStrArr) < 0){ if (this.imgNameList.indexOf(itemStrArr) < 0) {
this.imgNameList.push(itemStrArr); this.imgNameList.push(itemStrArr);
} }
let imgUrl = "/resource/md/get/"+itemStrArr; let imgUrl = "/resource/md/get/" + itemStrArr;
this.src = imgUrl; this.src = imgUrl;
if(this.srcList.indexOf(itemStrArr) < 0){ if (this.srcList.indexOf(itemStrArr) < 0) {
this.srcList.push(imgUrl); this.srcList.push(imgUrl);
} }
} }
}else{ } else {
let inputStr = itemStr.substr(0,picNameIndex); let inputStr = itemStr.substr(0, picNameIndex);
if(this.imgDescription === ""){ if (this.imgDescription === "") {
this.imgDescription = inputStr; this.imgDescription = inputStr;
}else { } else {
this.imgDescription = "\n" + inputStr; this.imgDescription = "\n" + inputStr;
} }
} }
} }
}else{ } else {
let imgUrlIndex = message.indexOf("](http"); let imgUrlIndex = message.indexOf("](http");
if(imgUrlIndex > 0){ if (imgUrlIndex > 0) {
let imgUrlSplitArr = message.split("](http"); let imgUrlSplitArr = message.split("](http");
for(let itemIndex = 0;itemIndex < imgUrlSplitArr.length; itemIndex ++){ for (let itemIndex = 0; itemIndex < imgUrlSplitArr.length; itemIndex++) {
let itemStr = imgUrlSplitArr[itemIndex]; let itemStr = imgUrlSplitArr[itemIndex];
let picNameIndex = itemStr.indexOf("!["); let picNameIndex = itemStr.indexOf("![");
if( picNameIndex < 0){ if (picNameIndex < 0) {
let endUrlIndex = itemStr.indexOf(")"); let endUrlIndex = itemStr.indexOf(")");
if( endUrlIndex > 0){ if (endUrlIndex > 0) {
let itemStrArr = itemStr.substr(0,endUrlIndex); let itemStrArr = itemStr.substr(0, endUrlIndex);
//if(imgNameList.) //if(imgNameList.)
if(this.imgNameList.indexOf(itemStrArr) < 0){ if (this.imgNameList.indexOf(itemStrArr) < 0) {
this.imgNameList.push(itemStrArr); this.imgNameList.push(itemStrArr);
} }
let imgUrl = "http"+itemStrArr; let imgUrl = "http" + itemStrArr;
this.src = imgUrl; this.src = imgUrl;
if(this.srcList.indexOf(itemStrArr) < 0){ if (this.srcList.indexOf(itemStrArr) < 0) {
this.srcList.push(imgUrl); this.srcList.push(imgUrl);
} }
} }
}else{ } else {
let inputStr = itemStr.substr(0,picNameIndex); let inputStr = itemStr.substr(0, picNameIndex);
if(this.imgDescription === ""){ if (this.imgDescription === "") {
this.imgDescription = inputStr; this.imgDescription = inputStr;
}else { } else {
this.imgDescription = "\n" + inputStr; this.imgDescription = "\n" + inputStr;
} }
} }
} }
} }
} }
if(this.srcList.length > 0){ if (this.srcList.length > 0) {
returnFlag = true; returnFlag = true;
} }
} }
return returnFlag; return returnFlag;
}, },
checkByUrls(url){ checkByUrls(url) {
let checkResultFlag = false; let checkResultFlag = false;
if(this.imgNameList.length > 0){ if (this.imgNameList.length > 0) {
this.imgNameList.forEach(imgName => { this.imgNameList.forEach(imgName => {
if(imgName === url){ if (imgName === url) {
checkResultFlag = true; checkResultFlag = true;
} }
}); });
@ -269,6 +305,11 @@ pre {
} }
/deep/ .el-button--mini, .el-button--mini.is-round { /deep/ .el-button--mini, .el-button--mini.is-round {
padding: 4px 9px; padding: 7px 15px;
}
.send-btn {
margin-top: 5px;
width: 100%;
} }
</style> </style>

@ -1 +1 @@
Subproject commit a2a8dde5beb470590e7860e9d4376a56ce41d791 Subproject commit 88b54ba98e8e0bdc6262f63310424263309493ea

View File

@ -189,7 +189,7 @@ export let CUSTOM_TABLE_HEADER = {
{id: 'createUser', key: '7', label: 'commons.create_user'}, {id: 'createUser', key: '7', label: 'commons.create_user'},
{id: 'createTime', key: '8', label: 'commons.create_time'}, {id: 'createTime', key: '8', label: 'commons.create_time'},
{id: 'desc', key: '9', label: 'test_track.case.case_desc'}, {id: 'desc', key: '9', label: 'test_track.case.case_desc'},
{id: 'lastExecResult', key: '10', label: 'test_track.plan_view.execute_result'}, {id: 'lastExecResult', key: '0', label: 'test_track.plan_view.execute_result'},
], ],
//缺陷列表 //缺陷列表
ISSUE_LIST: [ ISSUE_LIST: [

View File

@ -191,14 +191,14 @@ export function getLabel(vueObj, type) {
} }
export function buildBatchParam(vueObj, selectIds) { export function buildBatchParam(vueObj, selectIds, projectId) {
let param = {}; let param = {};
if (vueObj.selectRows) { if (vueObj.selectRows) {
param.ids = selectIds ? selectIds: Array.from(vueObj.selectRows).map(row => row.id); param.ids = selectIds ? selectIds: Array.from(vueObj.selectRows).map(row => row.id);
} else { } else {
param.ids = selectIds; param.ids = selectIds;
} }
param.projectId = getCurrentProjectID(); param.projectId = projectId ? projectId : getCurrentProjectID();
param.condition = vueObj.condition; param.condition = vueObj.condition;
return param; return param;
} }

View File

@ -7,13 +7,15 @@ export default {
yes: "yes", yes: "yes",
no: "no", no: "no",
example: "Demo", example: "Demo",
subject: "Subject",
excelFile: "Excel", excelFile: "Excel",
xmindFile: "Xmind", xmindFile: "Xmind",
default: "default", default: "default",
sort_default: "Default",
please_select_import_mode: 'Please select import mode', please_select_import_mode: 'Please select import mode',
please_select_import_module: 'Please select import module', please_select_import_module: 'Please select import module',
pass_rate: 'Pass rate', pass_rate: 'Pass Rate',
execution_times: 'Execution times', execution_times: 'Execution Times',
cover: 'Cover', cover: 'Cover',
module_title: 'Default module', module_title: 'Default module',
save_data_when_page_change: 'Save when page change', save_data_when_page_change: 'Save when page change',
@ -68,18 +70,18 @@ export default {
input_limit: 'Within {0} and {1} characters', input_limit: 'Within {0} and {1} characters',
login: 'Sign In', login: 'Sign In',
welcome: 'One-stop open source continuous testing platform', welcome: 'One-stop open source continuous testing platform',
theme_color:'Theme color', theme_color: 'Theme color',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',
input_username: 'Please enter username', input_username: 'Please enter username',
input_password: 'Please enter password', input_password: 'Please enter password',
test: 'Test', test: 'Test',
create_time: 'Created Time', create_time: 'Created Time',
update_user_id:'Updater ID', update_user_id: 'Updater ID',
update_time: 'Updated Time', update_time: 'Updated Time',
delete_time: 'Delete Time', delete_time: 'Delete Time',
delete_user: 'Deleted by', delete_user: 'Deleted By',
delete_user_id:'Deleted by id', delete_user_id: 'Deleted by id',
add: 'Add', add: 'Add',
preview: 'Preview', preview: 'Preview',
member: 'Member', member: 'Member',
@ -325,6 +327,11 @@ export default {
send: "Send", send: "Send",
save_as_draft: "Draft", save_as_draft: "Draft",
}, },
table: {
draft: "Draft",
sended: "Send",
send_error: "Send error",
},
project_report: { project_report: {
create_report: "Create report", create_report: "Create report",
report_name: "Report name", report_name: "Report name",
@ -393,20 +400,20 @@ export default {
system_project: "Project", system_project: "Project",
user_unit: "", user_unit: "",
workspace_unit: "", workspace_unit: "",
dash_board:'DashBoard', dash_board: 'DashBoard',
all_project:'All Project', all_project: 'All Project',
to_be_completed:'Unfinished', to_be_completed: 'Unfinished',
pending_upgrade:'To be update', pending_upgrade: 'To be update',
information:'Information', information: 'Information',
nick_name:'Nick name', nick_name: 'Nick name',
resourceId:'Resource ID', resourceId: 'Resource ID',
num:'Number', num: 'Number',
original_state:'Original State', original_state: 'Original State',
custom_num:'CustomNum', custom_num: 'CustomNum',
version:'Version', version: 'Version',
is_new:"Is new", is_new: "Is new",
form_config:"Form config", form_config: "Form config",
form_content:"Form content" form_content: "Form content"
}, },
login: { login: {
normal_Login: "Normal Login", normal_Login: "Normal Login",
@ -427,25 +434,25 @@ export default {
invalid: 'invalid', invalid: 'invalid',
expired: 'expired', expired: 'expired',
}, },
workstation:{ workstation: {
dash_board: 'My DashBoard', dash_board: 'My DashBoard',
upcoming: 'My Upcoming', upcoming: 'My Upcoming',
focus: 'My Focus', focus: 'My Focus',
creation: 'My Creation', creation: 'My Creation',
creation_case:'My Creation Case', creation_case: 'My Creation Case',
creation_issue:'My Creation Issue', creation_issue: 'My Creation Issue',
creation_case_tip: 'No use case has been created yet, create it now', creation_case_tip: 'No use case has been created yet, create it now',
creation_issue_tip:'No defects have been created yet, create them now', creation_issue_tip: 'No defects have been created yet, create them now',
table_name:{ table_name: {
track_case:'Track Case', track_case: 'Track Case',
track_plan:'Track Plan', track_plan: 'Track Plan',
track_review:'Track Review', track_review: 'Track Review',
track_issue:'Track Issue', track_issue: 'Track Issue',
api_definition:'Api Definition', api_definition: 'Api Definition',
api_automation:'Api Automation', api_automation: 'Api Automation',
api_case:'Api Case', api_case: 'Api Case',
performance:'Performance', performance: 'Performance',
scenario_case:'Scenario Case' scenario_case: 'Scenario Case'
} }
}, },
display: { display: {
@ -511,7 +518,7 @@ export default {
option_value_check: 'Please fill in the full option values', option_value_check: 'Please fill in the full option values',
}, },
workspace: { workspace: {
id:'Workspace ID', id: 'Workspace ID',
create: 'Create Workspace', create: 'Create Workspace',
update: 'Update Workspace', update: 'Update Workspace',
delete: 'Delete Workspace', delete: 'Delete Workspace',
@ -632,12 +639,12 @@ export default {
zentao_request: 'Zentao request', zentao_request: 'Zentao request',
input_zentao_request: 'Please enter zentao request type', input_zentao_request: 'Please enter zentao request type',
input_zentao_url: 'Please enter Zentao address, for example: http://xx.xx.xx.xx/zentao/', input_zentao_url: 'Please enter Zentao address, for example: http://xx.xx.xx.xx/zentao/',
zentao_info:'Zentao Information', zentao_info: 'Zentao Information',
zentao_account:'Zentao Account', zentao_account: 'Zentao Account',
zentao_password:'Zentao Password', zentao_password: 'Zentao Password',
zentao_prompt_information:'This information is the username and password of the user who submitted the defect through ZenTao. If not filled in, the default information configured in the workspace will be used', zentao_prompt_information: 'This information is the username and password of the user who submitted the defect through ZenTao. If not filled in, the default information configured in the workspace will be used',
zentao_config_tip:"Refer to the value of $config->requestType in the ZenTao configuration file", zentao_config_tip: "Refer to the value of $config->requestType in the ZenTao configuration file",
zentao_config_path:"Configuration file reference path: /opt/zbox/app/zentao/config/my.php", zentao_config_path: "Configuration file reference path: /opt/zbox/app/zentao/config/my.php",
use_tip: 'Usage guidelines:', use_tip: 'Usage guidelines:',
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"', use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)', use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
@ -664,11 +671,11 @@ export default {
input_azure_url: 'Please enter Azure Devops Url', input_azure_url: 'Please enter Azure Devops Url',
input_azure_id: 'Please enter Azure Organization ID', input_azure_id: 'Please enter Azure Organization ID',
use_tip_azure: 'Azure Devops URL+PersonalAccessTokens(User Settings-Personal Access Tokens-New Token)', use_tip_azure: 'Azure Devops URL+PersonalAccessTokens(User Settings-Personal Access Tokens-New Token)',
jira_prompt_information:'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used' jira_prompt_information: 'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used'
} }
}, },
project: { project: {
id:'Project ID', id: 'Project ID',
name: 'Project name', name: 'Project name',
recent: 'Recent Projects', recent: 'Recent Projects',
create: 'Create Project', create: 'Create Project',
@ -706,7 +713,7 @@ export default {
create: "Create Custom Code", create: "Create Custom Code",
update: "Update Custom Code", update: "Update Custom Code",
delete: "Delete Custom Code", delete: "Delete Custom Code",
language: "language", language: "Language",
relate_tip: "Create in the Project Settings -> Custom Code Snippet menu", relate_tip: "Create in the Project Settings -> Custom Code Snippet menu",
select_tip: "Please select a custom code!", select_tip: "Please select a custom code!",
none_content: "The custom code snippet is empty", none_content: "The custom code snippet is empty",
@ -772,7 +779,7 @@ export default {
no_such_user: 'Without this user information, please enter the correct user ID or user Email!', no_such_user: 'Without this user information, please enter the correct user ID or user Email!',
}, },
user: { user: {
id:'User ID', id: 'User ID',
create: 'Create', create: 'Create',
modify: 'Modify', modify: 'Modify',
input_name: 'Please enter a user name', input_name: 'Please enter a user name',
@ -798,7 +805,7 @@ export default {
add_project_batch: 'Batch Add User To Project', add_project_batch: 'Batch Add User To Project',
add_workspace_batch: "Batch Add User To Workspace", add_workspace_batch: "Batch Add User To Workspace",
add_project_batch_tip: 'Add a read-only user group for members by default (system)', add_project_batch_tip: 'Add a read-only user group for members by default (system)',
import_user_tip:'User group only supports system default user group', import_user_tip: 'User group only supports system default user group',
}, },
group: { group: {
add: 'Add User Group', add: 'Add User Group',
@ -843,7 +850,7 @@ export default {
add: 'Add Role', add: 'Add Role',
}, },
report: { report: {
id:'Report ID', id: 'Report ID',
api_test_report: 'Api Test Report', api_test_report: 'Api Test Report',
load_test_report: 'Load Test Report', load_test_report: 'Load Test Report',
test_plan_report: 'Test Plan Report', test_plan_report: 'Test Plan Report',
@ -890,13 +897,13 @@ export default {
delete_batch_confirm: 'Confirm batch delete report', delete_batch_confirm: 'Confirm batch delete report',
response_time: 'Response Time(s)', response_time: 'Response Time(s)',
max_users: 'VUs', max_users: 'VUs',
file_id:'Report File ID', file_id: 'Report File ID',
avg_response_time:'Average Response Time', avg_response_time: 'Average Response Time',
tps:'Transactions Per Second', tps: 'Transactions Per Second',
plan_share_url:'Whether the link jump is logged in' plan_share_url: 'Whether the link jump is logged in'
}, },
load_test: { load_test: {
id:'Load Test ID', id: 'Load Test ID',
concurrency_thread_group: 'Concurrent read group', concurrency_thread_group: 'Concurrent read group',
thread_group: 'ThreadGroup', thread_group: 'ThreadGroup',
completed_test_report: 'Completed test report', completed_test_report: 'Completed test report',
@ -921,11 +928,11 @@ export default {
test_name_is_null: 'Test name cannot be empty! ', test_name_is_null: 'Test name cannot be empty! ',
project_is_null: 'Project cannot be empty! ', project_is_null: 'Project cannot be empty! ',
jmx_is_null: 'Must contain a JMX file, and can only contain a JMX file!', jmx_is_null: 'Must contain a JMX file, and can only contain a JMX file!',
file_name: 'File name', file_name: 'File Name',
file_size: 'File size', file_size: 'File size',
file_type: 'File Type', file_type: 'File Type',
file_status: 'File Status', file_status: 'File Status',
last_modify_time: 'Modify time', last_modify_time: 'Modify Time',
upload_tips: 'Drag files here, or <em> click to upload </em>', upload_tips: 'Drag files here, or <em> click to upload </em>',
upload_type: 'Only JMX/CSV/JAR files can be uploaded', upload_type: 'Only JMX/CSV/JAR files can be uploaded',
related_file_not_found: "No related test file found!", related_file_not_found: "No related test file found!",
@ -1018,7 +1025,7 @@ export default {
LatencyChart: 'Latency', LatencyChart: 'Latency',
BytesThroughputChart: 'Bytes', BytesThroughputChart: 'Bytes',
Network: 'Network', Network: 'Network',
url:'URL' url: 'URL'
}, },
add_monitor: 'Add Monitor', add_monitor: 'Add Monitor',
monitor_item: 'Monitor', monitor_item: 'Monitor',
@ -1063,7 +1070,7 @@ export default {
upload_limit_size: "Upload file size cannot exceed 30MB!", upload_limit_size: "Upload file size cannot exceed 30MB!",
upload_limit_size_warn: "Upload file size cannot exceed {0} MB!", upload_limit_size_warn: "Upload file size cannot exceed {0} MB!",
upload_limit: "Upload file size cannot exceed", upload_limit: "Upload file size cannot exceed",
upload_tip:"Only upload JAR packages" upload_tip: "Only upload JAR packages"
}, },
batch_menus: { batch_menus: {
select_all_data: "Select all datas({0})", select_all_data: "Select all datas({0})",
@ -1079,7 +1086,7 @@ export default {
update_time: "Update time" update_time: "Update time"
}, },
expect_detail: "Expect", expect_detail: "Expect",
request_condition:"Request Condition", request_condition: "Request Condition",
base_info: "Base info", base_info: "Base info",
req_param: "Request params", req_param: "Request params",
rsp_param: "Response Params", rsp_param: "Response Params",
@ -1096,22 +1103,22 @@ export default {
api_title: "Api Test", api_title: "Api Test",
case_title: "Test Case", case_title: "Test Case",
doc_title: "DOC", doc_title: "DOC",
api_name: "Api name", api_name: "Api Name",
api_status: "Api status", api_status: "Api Status",
api_type: "Api type", api_type: "Api Type",
api_agreement: "Method", api_agreement: "Method",
api_path: "Api path", api_path: "Api Path",
api_definition_path: "API Path", api_definition_path: "API Path",
api_case_path: "Case Path", api_case_path: "Case Path",
api_principal: "Api principal", api_principal: "Api Principal",
api_last_time: "Last update time", api_last_time: "Last Update Time",
api_case_number: "Cases", api_case_number: "Cases",
api_case_status: "Case status", api_case_status: "Case Status",
api_case_passing_rate: "Use case pass rate", api_case_passing_rate: "Use Case Pass Rate",
create_tip: "Note: Detailed interface information can be filled out on the edit page", create_tip: "Note: Detailed interface information can be filled out on the edit page",
api_import: "Api Import", api_import: "Api Import",
check_select: "Please check the API", check_select: "Please check the API",
api_project:"Project", api_project: "Project",
select_comp: { select_comp: {
no_data: "No Data", no_data: "No Data",
add_data: "Add Data" add_data: "Add Data"
@ -1233,7 +1240,7 @@ export default {
none: "None" none: "None"
}, },
}, },
case_reduction_error_text:"Please restore first" case_reduction_error_text: "Please restore first"
}, },
automation: { automation: {
open_expansion: "One-click expansion", open_expansion: "One-click expansion",
@ -1264,15 +1271,15 @@ export default {
scenario_test: "Scenario test", scenario_test: "Scenario test",
scenario_list: "Scenario List", scenario_list: "Scenario List",
add_scenario: "Add scenario", add_scenario: "Add scenario",
scenario_name: "Scenario name", scenario_name: "Scenario Name",
case_level: "Case level", case_level: "Case Level",
tag: "Tag", tag: "Tag",
creator: "Creator", creator: "Creator",
update_time: "Update time", update_time: "Update Time",
step: "Step", step: "Step",
last_result: "Last result", last_result: "Last Result",
last_result_id:'Last result id', last_result_id: 'Last result id',
passing_rate: "Passing rate", passing_rate: "Passing Rate",
success: "Success", success: "Success",
fail: "Fail", fail: "Fail",
saved: "Saved", saved: "Saved",
@ -1295,9 +1302,9 @@ export default {
select_table: "Select table", select_table: "Select table",
select_all: "Select all", select_all: "Select all",
check_case: "Please check the Scene Use Case", check_case: "Please check the Scene Use Case",
api_none:"The original api does not exist", api_none: "The original api does not exist",
case_none:"The original case does not exist", case_none: "The original case does not exist",
num_none:"The original scene does not exist" num_none: "The original scene does not exist"
}, },
report_name_info: 'Please enter the registration name', report_name_info: 'Please enter the registration name',
save_case_info: 'Please save the use case first', save_case_info: 'Please save the use case first',
@ -1314,7 +1321,7 @@ export default {
generate_report: "Generate report", generate_report: "Generate report",
}, },
environment: { environment: {
id:'Environment ID', id: 'Environment ID',
create: 'Create environment', create: 'Create environment',
name: "Environment Name", name: "Environment Name",
socket: "Socket", socket: "Socket",
@ -1340,7 +1347,7 @@ export default {
copy_warning: "Domain names whose enabling conditions are'none' do not support copying!" copy_warning: "Domain names whose enabling conditions are'none' do not support copying!"
}, },
scenario: { scenario: {
id:"Scenario ID", id: "Scenario ID",
scenario: "Scenario", scenario: "Scenario",
dubbo: "Dubbo Config", dubbo: "Dubbo Config",
config: "Scenario Config", config: "Scenario Config",
@ -1423,54 +1430,54 @@ export default {
condition_variable: "Variable, e.g: ${var}", condition_variable: "Variable, e.g: ${var}",
wait: "wait", wait: "wait",
all_name:"name", all_name: "name",
all_url:"url", all_url: "url",
boolean:"Boolean value", boolean: "Boolean value",
capitalize:"Start with a capital letter", capitalize: "Start with a capital letter",
cfirst:"Last name (Chinese)", cfirst: "Last name (Chinese)",
character:"character", character: "character",
city:"city", city: "city",
clast:"First name (Chinese)", clast: "First name (Chinese)",
cname:"Chinese name", cname: "Chinese name",
color:"colour", color: "colour",
county:"county", county: "county",
ctitle:"Chinese title", ctitle: "Chinese title",
dataImage:"Data image", dataImage: "Data image",
date:"date", date: "date",
datetime:"Date time", datetime: "Date time",
domain:"field", domain: "field",
email:"E-mail", email: "E-mail",
first:"surname", first: "surname",
float:"Floating point number", float: "Floating point number",
guid:"guid", guid: "guid",
hex:"hexadecimal", hex: "hexadecimal",
hsl:"hsl", hsl: "hsl",
id:"id", id: "id",
img:"Picture address", img: "Picture address",
increment:"increment", increment: "increment",
integer:"integer", integer: "integer",
ip:"intellectual property right", ip: "intellectual property right",
last:"name", last: "name",
lower:"Lowercase letters", lower: "Lowercase letters",
natural:"Natural number", natural: "Natural number",
now:"current time ", now: "current time ",
paragraph:"paragraph", paragraph: "paragraph",
pick:"choice", pick: "choice",
protocol:"agreement", protocol: "agreement",
province:"province", province: "province",
range:"Range", range: "Range",
region:"region", region: "region",
rgb:"rgb", rgb: "rgb",
rgba:"rgba", rgba: "rgba",
sentence:"sentence", sentence: "sentence",
shuffle:"shuffle the cards", shuffle: "shuffle the cards",
string:"character string", string: "character string",
time:"time", time: "time",
title:"title", title: "title",
tld:"tld", tld: "tld",
upper:"capital", upper: "capital",
word:"Words", word: "Words",
zip:"Postal Code", zip: "Postal Code",
assertions: { assertions: {
label: "Assertion", label: "Assertion",
@ -1730,7 +1737,7 @@ export default {
title: "Updated interfaces in the past 7 days", title: "Updated interfaces in the past 7 days",
table_coloum: { table_coloum: {
index: "ID", index: "ID",
api_name: "Api name", api_name: "Api Name",
path: "path", path: "path",
api_status: "Api status", api_status: "Api status",
update_time: "Update time", update_time: "Update time",
@ -1843,7 +1850,7 @@ export default {
recent_plan: "My recent plan", recent_plan: "My recent plan",
recent_case: "My recent case", recent_case: "My recent case",
recent_review: "My recent review", recent_review: "My recent review",
pass_rate: "Pass rate", pass_rate: "Pass Rate",
execution_result: ": Please select the execution result", execution_result: ": Please select the execution result",
actual_result: ": The actual result is empty", actual_result: ": The actual result is empty",
cancel_relevance_success: "Unlinked successfully", cancel_relevance_success: "Unlinked successfully",
@ -1853,14 +1860,14 @@ export default {
performance_test_case: "Performance Case", performance_test_case: "Performance Case",
scenario_test_case: "Scenario Case", scenario_test_case: "Scenario Case",
report_statistics: "Report Statistics", report_statistics: "Report Statistics",
sort:'Sort', sort: 'Sort',
other_test_name:'Other Test Name', other_test_name: 'Other Test Name',
demand:{ demand: {
id:'Demand ID', id: 'Demand ID',
name:'Demand Name' name: 'Demand Name'
}, },
step_model:'Step Model', step_model: 'Step Model',
automatic_status_update:"Automatic Status Update", automatic_status_update: "Automatic Status Update",
case: { case: {
list: "List", list: "List",
minder: "Minder", minder: "Minder",
@ -1892,7 +1899,7 @@ export default {
manual: "Manual", manual: "Manual",
create: "Create test case", create: "Create test case",
case_type: "Case Type", case_type: "Case Type",
name: "Test case name", name: "Test Case Name",
module: "Module", module: "Module",
project: 'Project', project: 'Project',
maintainer: "Maintainer", maintainer: "Maintainer",
@ -1951,9 +1958,9 @@ export default {
attachment: "Attachment", attachment: "Attachment",
upload_time: "Upload Time", upload_time: "Upload Time",
total: "Total Case", total: "Total Case",
node_id:"Node ID", node_id: "Node ID",
node_path:"Node Path", node_path: "Node Path",
match_rule:"Test Case Match Rule", match_rule: "Test Case Match Rule",
import: { import: {
import: "Import test case", import: "Import test case",
case_import: "Import test case", case_import: "Import test case",
@ -1985,11 +1992,11 @@ export default {
export_tip: "Switch to Interface List and check Use Case Export" export_tip: "Switch to Interface List and check Use Case Export"
}, },
case_desc: "Case Desc", case_desc: "Case Desc",
passing_rate:'Case Pass Rate' passing_rate: 'Case Pass Rate'
}, },
plan: { plan: {
test_plan: "Plan", test_plan: "Plan",
test_plan_id:'Plan ID', test_plan_id: 'Plan ID',
create_plan: "Create test plan", create_plan: "Create test plan",
edit_plan: "Edit test plan", edit_plan: "Edit test plan",
plan_name: "Name", plan_name: "Name",
@ -2026,10 +2033,10 @@ export default {
api_case: "Api case", api_case: "Api case",
scenario_case: "Scenario case", scenario_case: "Scenario case",
execute_result: "Execute Result", execute_result: "Execute Result",
execute_time:'Execute Time', execute_time: 'Execute Time',
is_api_case_executing:"Api Case Executing", is_api_case_executing: "Api Case Executing",
is_scenario_executing:'Scenario Executing', is_scenario_executing: 'Scenario Executing',
is_performance_executing:'Performance Executing', is_performance_executing: 'Performance Executing',
test_plan_test_case_count: "Track case count", test_plan_test_case_count: "Track case count",
test_plan_api_case_count: "Api case count", test_plan_api_case_count: "Api case count",
test_plan_api_scenario_count: "Scenario case count", test_plan_api_scenario_count: "Scenario case count",
@ -2078,7 +2085,7 @@ export default {
comment: { comment: {
no_comment: "No Comment", no_comment: "No Comment",
send_comment: "Post a comment (Ctrl + Enter to send)", send_comment: "Post a comment (Ctrl + Enter to send)",
send: "Send", send: "Confirm",
description_is_null: "Comment content cannot be empty!", description_is_null: "Comment content cannot be empty!",
send_success: "Comment successful!", send_success: "Comment successful!",
}, },
@ -2093,7 +2100,7 @@ export default {
cannot_delete: "Cannot delete this comment", cannot_delete: "Cannot delete this comment",
}, },
module: { module: {
id:'Module ID', id: 'Module ID',
search: "Search module", search: "Search module",
rename: "Rename", rename: "Rename",
add_submodule: "Add submodule", add_submodule: "Add submodule",
@ -2136,7 +2143,7 @@ export default {
relevance_test_case: "Relevance case", relevance_test_case: "Relevance case",
cancel_all_relevance: "Unlink all", cancel_all_relevance: "Unlink all",
executor: "Executor", executor: "Executor",
executor_match_rule:"Executor Match Rule", executor_match_rule: "Executor Match Rule",
execute_result: "Result", execute_result: "Result",
pass: "Pass", pass: "Pass",
not_pass: "UnPass", not_pass: "UnPass",
@ -2161,7 +2168,7 @@ export default {
submit_issues: "Commit issues", submit_issues: "Commit issues",
operate_step: "Operate step", operate_step: "Operate step",
edit_component: "Edit component", edit_component: "Edit component",
component:"component", component: "component",
base_info: "Base info", base_info: "Base info",
mock_info: "Mock service", mock_info: "Mock service",
test_result: "Test result", test_result: "Test result",
@ -2194,7 +2201,7 @@ export default {
issue: "Issue", issue: "Issue",
issue_management: "Issue Management", issue_management: "Issue Management",
platform_status: "Platform Status", platform_status: "Platform Status",
issue_resource: "Issue source", issue_resource: "Issue Source",
create_issue: "Create Issue", create_issue: "Create Issue",
add_issue: "Add Issue", add_issue: "Add Issue",
issue_list: "Issue List", issue_list: "Issue List",
@ -2206,7 +2213,7 @@ export default {
description: "Issue Describe", description: "Issue Describe",
status: "Issue Status", status: "Issue Status",
platform: "Platform", platform: "Platform",
issue_project:"Project", issue_project: "Project",
operate: "Operate", operate: "Operate",
close: "Close", close: "Close",
delete: "Delete", delete: "Delete",
@ -2216,12 +2223,12 @@ export default {
status_new: 'new', status_new: 'new',
status_resolved: 'resolved', status_resolved: 'resolved',
status_closed: 'closed', status_closed: 'closed',
status_active:'active', status_active: 'active',
status_delete:'delete', status_delete: 'delete',
status_in_progress: 'in_progress', status_in_progress: 'in_progress',
status_rejected: 'rejected', status_rejected: 'rejected',
status_upcoming:'upcoming', status_upcoming: 'upcoming',
status_reopened:'reopened', status_reopened: 'reopened',
please_choose_current_owner: "Please choose current owner", please_choose_current_owner: "Please choose current owner",
tapd_current_owner: "Tapd Current Owner", tapd_current_owner: "Tapd Current Owner",
zentao_bug_build: "Zentao Impact version", zentao_bug_build: "Zentao Impact version",
@ -2229,7 +2236,8 @@ export default {
third_party_integrated: "Third-party Platform Integrated", third_party_integrated: "Third-party Platform Integrated",
use_third_party: "Enable Jira Issue Template", use_third_party: "Enable Jira Issue Template",
update_third_party_bugs: "Update the defects of third-party platforms", update_third_party_bugs: "Update the defects of third-party platforms",
sync_bugs: "Synchronization Issue" sync_bugs: "Synchronization Issue",
save_before_open_comment: "Please save issue before comment",
}, },
report: { report: {
name: "Test Plan Report", name: "Test Plan Report",
@ -2267,11 +2275,11 @@ export default {
issue_list: "Issue List", issue_list: "Issue List",
all_case: "All Case", all_case: "All Case",
}, },
reporter:'Reporter', reporter: 'Reporter',
lastmodify:'Last Modify' lastmodify: 'Last Modify'
}, },
test_resource_pool: { test_resource_pool: {
id:'Resource Pool ID', id: 'Resource Pool ID',
type: 'type', type: 'type',
enable_disable: 'Enable / Disable', enable_disable: 'Enable / Disable',
search_by_name: 'Search by name', search_by_name: 'Search by name',
@ -2364,7 +2372,7 @@ export default {
task_config: "Task Config", task_config: "Task Config",
test_name: 'Test Name', test_name: 'Test Name',
running_rule: 'Rule', running_rule: 'Rule',
job:'Job', job: 'Job',
job_status: 'Status', job_status: 'Status',
running_task: 'Running Task', running_task: 'Running Task',
please_input_cron_expression: "Please Input Cron Expression", please_input_cron_expression: "Please Input Cron Expression",
@ -2497,7 +2505,7 @@ export default {
}, },
module: { module: {
title: 'Module Management', title: 'Module Management',
path:'Module Path' path: 'Module Path'
}, },
table: { table: {
header_display_field: 'Header display field', header_display_field: 'Header display field',
@ -2534,7 +2542,7 @@ export default {
script_entry: "Script execution entry", script_entry: "Script execution entry",
plugin_id: "Plug-in Id", plugin_id: "Plug-in Id",
script_view: "View script", script_view: "View script",
warning_tip:'Script content not found' warning_tip: 'Script content not found'
}, },
mail: { mail: {
input_mail_subject: "Please input subject", input_mail_subject: "Please input subject",

View File

@ -7,9 +7,11 @@ export default {
yes: "是", yes: "是",
no: "否", no: "否",
example: "示例", example: "示例",
subject: "主题",
excelFile: "表格文件.xls", excelFile: "表格文件.xls",
xmindFile: "思维导图.xmind", xmindFile: "思维导图.xmind",
default: "默认值", default: "默认值",
sort_default: "默认排序",
please_select_import_mode: '请选择导入模式', please_select_import_mode: '请选择导入模式',
please_select_import_module: '请选择导入模块', please_select_import_module: '请选择导入模块',
pass_rate: '通过率', pass_rate: '通过率',
@ -326,6 +328,11 @@ export default {
send: "发送", send: "发送",
save_as_draft: "保存草稿", save_as_draft: "保存草稿",
}, },
table: {
draft: "草稿箱",
sended: "已发送",
send_error: "发送失败",
},
project_report: { project_report: {
create_report: "创建报告", create_report: "创建报告",
report_name: "报告名称", report_name: "报告名称",
@ -2082,7 +2089,7 @@ export default {
comment: { comment: {
no_comment: "暂无评论", no_comment: "暂无评论",
send_comment: "发表评论Ctrl+Enter发送", send_comment: "发表评论Ctrl+Enter发送",
send: "发送", send: "确定",
description_is_null: "评论内容不能为空!", description_is_null: "评论内容不能为空!",
send_success: "评论成功!", send_success: "评论成功!",
cannot_edit: "无法编辑此评论!", cannot_edit: "无法编辑此评论!",
@ -2233,7 +2240,8 @@ export default {
third_party_integrated: "集成第三方平台", third_party_integrated: "集成第三方平台",
use_third_party: "使用 Jira 缺陷模板", use_third_party: "使用 Jira 缺陷模板",
update_third_party_bugs: "更新第三方平台的缺陷", update_third_party_bugs: "更新第三方平台的缺陷",
sync_bugs: "同步缺陷" sync_bugs: "同步缺陷",
save_before_open_comment: "请先保存缺陷再添加评论",
}, },
report: { report: {
name: "测试计划报告", name: "测试计划报告",

View File

@ -7,9 +7,11 @@ export default {
yes: "是", yes: "是",
no: "否", no: "否",
example: "示例", example: "示例",
subject: "主題",
excelFile: "表格文件.xls", excelFile: "表格文件.xls",
xmindFile: "思維導圖.xmind", xmindFile: "思維導圖.xmind",
default: "默認值", default: "默認值",
sort_default: "默認排序",
please_select_import_mode: '請選擇導入模式', please_select_import_mode: '請選擇導入模式',
please_select_import_module: '請選擇導入模塊', please_select_import_module: '請選擇導入模塊',
pass_rate: '通過率', pass_rate: '通過率',
@ -326,6 +328,11 @@ export default {
send: "發送", send: "發送",
save_as_draft: "保存草稿", save_as_draft: "保存草稿",
}, },
table: {
draft: "草稿箱",
sended: "已發送",
send_error: "發送失敗",
},
project_report: { project_report: {
create_report: "創建報告", create_report: "創建報告",
report_name: "報告名稱", report_name: "報告名稱",
@ -2082,7 +2089,7 @@ export default {
comment: { comment: {
no_comment: "暫無評論", no_comment: "暫無評論",
send_comment: "發表評論Ctrl+Enter發送", send_comment: "發表評論Ctrl+Enter發送",
send: "發送", send: "確定",
description_is_null: "評論內容不能為空!", description_is_null: "評論內容不能為空!",
send_success: "評論成功!", send_success: "評論成功!",
cannot_edit: "無法編輯此評論!", cannot_edit: "無法編輯此評論!",
@ -2233,7 +2240,8 @@ export default {
third_party_integrated: "集成第三方平臺", third_party_integrated: "集成第三方平臺",
use_third_party: "使用 Jira 缺陷模板", use_third_party: "使用 Jira 缺陷模板",
update_third_party_bugs: "更新第三方平臺的缺陷", update_third_party_bugs: "更新第三方平臺的缺陷",
sync_bugs: "同步缺陷" sync_bugs: "同步缺陷",
save_before_open_comment: "請先保存缺陷再添加評論",
}, },
report: { report: {
name: "測試計劃報告", name: "測試計劃報告",

View File

@ -37,6 +37,19 @@ export function getIssuesById(id, callback) {
return id ? baseGet('/issues/get/' + id, callback) : {}; return id ? baseGet('/issues/get/' + id, callback) : {};
} }
export function getIssuesListById(id,projectId,workspaceId,callback) {
let condition ={
id:id,
projectId : projectId,
workspaceId: workspaceId
};
return post('issues/list/' + 1 + '/' + 10, condition, (response) => {
if (callback) {
callback(response.data.listObject[0]);
}
});
}
export function getIssuesByPlanId(planId, callback) { export function getIssuesByPlanId(planId, callback) {
return planId ? baseGet('/issues/plan/get/' + planId, callback) : {}; return planId ? baseGet('/issues/plan/get/' + planId, callback) : {};
} }