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.TestPlanResourceType;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.track.dto.TestPlanReportExecuteCheckResultDTO;
import io.metersphere.utils.LoggerUtil;
import lombok.Getter;
import lombok.Setter;
@ -20,6 +21,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author song.tianyang
@ -30,17 +32,16 @@ import java.util.Map;
public class TestPlanExecuteInfo {
private String reportId;
private String creator;
private Map<String, String> apiCaseExecInfo = new HashMap<>();
private Map<String, String> apiScenarioCaseExecInfo = new HashMap<>();
private Map<String, String> loadCaseExecInfo = new HashMap<>();
private Map<String, String> apiCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> loadCaseExecInfo = new ConcurrentHashMap<>();
private Map<String, String> apiCaseExecuteThreadMap = new HashMap<>();
private Map<String, String> apiScenarioThreadMap = new HashMap<>();
private Map<String, String> loadCaseReportIdMap = new HashMap<>();
private Map<String, String> apiCaseReportMap = new HashMap<>();
private Map<String, String> apiScenarioReportMap = new HashMap<>();
private Map<String, String> apiCaseExecuteThreadMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioThreadMap = new ConcurrentHashMap<>();
private Map<String, String> loadCaseReportIdMap = new ConcurrentHashMap<>();
private Map<String, String> apiCaseReportMap = new ConcurrentHashMap<>();
private Map<String, String> apiScenarioReportMap = new ConcurrentHashMap<>();
private boolean reportDataInDataBase;
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;
this.isApiCaseAllExecuted = true;
@ -129,8 +131,22 @@ public class TestPlanExecuteInfo {
LoggerUtil.info("执行的报告还在队列中,重置超时时间");
lastUnFinishedNumCount = unFinishedCount;
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() {
@ -228,7 +244,7 @@ public class TestPlanExecuteInfo {
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)) {
this.apiCaseReportMap.putAll(apiCaseExecResultInfo);
}
@ -236,6 +252,35 @@ public class TestPlanExecuteInfo {
if (MapUtils.isNotEmpty(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;
import io.metersphere.commons.constants.TestPlanApiExecuteStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,8 +51,12 @@ public class TestPlanReportExecuteCatch {
}
public synchronized static boolean containsReport(String reportId) {
if(StringUtils.isEmpty(reportId)){
return false;
}else {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
}
}
public synchronized static void updateApiTestPlanExecuteInfo(String reportId,
Map<String, String> apiCaseExecInfo, Map<String, String> apiScenarioCaseExecInfo, Map<String, String> loadCaseExecInfo) {

View File

@ -1,5 +1,7 @@
package io.metersphere.api.dto.automation;
import org.junit.internal.runners.statements.Fail;
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) {
module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1);
module.setParentId(parentModule.getId());
module.setProtocol(parentModule.getProtocol());
} else {
module = apiModuleService.getNewModule(name, projectId, 1);
}
@ -121,6 +122,7 @@ public class ApiDefinitionImportUtil {
if (parentModule != null) {
module = apiModuleService.getNewModule(name, projectId, parentModule.getLevel() + 1);
module.setParentId(parentModule.getId());
module.setProtocol(parentModule.getProtocol());
} else {
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) {
if (element != null && element.getEnvironmentId() == null) {
element.setEnvironmentId(enviromentId);
if (element != null) {
element.setEnvironmentId(element.getEnvironmentId() == null ? enviromentId : element.getEnvironmentId());
element.toHashTree(samplerHashTree, element.getHashTree(), config);
}
}

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package io.metersphere.api.exec.queue;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.JmeterThreadUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.utils.LoggerUtil;
@ -23,7 +24,7 @@ public class ExecTask implements Runnable {
jMeterService.addQueue(request);
if (request.getPool() == null || !request.getPool().isPool()) {
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() + " 】执行超时");
}
}

View File

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

View File

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

View File

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

View File

@ -5,10 +5,12 @@ import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.service.ApiExecutionQueueService;
import io.metersphere.api.service.MsResultService;
import io.metersphere.api.service.TestResultService;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.dto.ResultDTO;
import io.metersphere.jmeter.MsExecListener;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
@ -18,13 +20,19 @@ public class APISingleResultListener extends MsExecListener {
LoggerUtil.info("处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + "");
dto.setConsole(CommonBeanFactory.getBean(MsResultService.class).getJmeterLogger(dto.getReportId()));
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
public void testEnded(ResultDTO dto, Map<String, Object> kafkaConfig) {
try {
LoggerUtil.info("进入TEST-END处理报告【" + dto.getReportId() + " 】整体执行完成;" + dto.getRunMode());
// 全局并发队列
PoolExecBlockingQueueUtil.offer(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) {
MsDebugListener resultCollector = new MsDebugListener();
MsResultCollector resultCollector = new MsResultCollector();
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.setEnabled(true);
testPlan.add(testPlan.getArray()[0], resultCollector);
@ -139,8 +139,8 @@ public class JMeterService {
JvmInfoDTO jvmInfoDTO = resources.get(index);
TestResourceDTO testResource = jvmInfoDTO.getTestResource();
String configuration = testResource.getConfiguration();
request.setCorePoolSize(MessageCache.corePoolSize);
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
request.setCorePoolSize(node.getMaxConcurrency());
String nodeIp = node.getIp();
Integer port = node.getPort();
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 = " ";
public static String stop(String name) {
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
@ -24,4 +22,19 @@ public class JmeterThreadUtils {
}
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.ApiExecutionQueueService;
import io.metersphere.api.service.TestResultService;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.config.KafkaConfig;
import io.metersphere.dto.ResultDTO;
@ -34,6 +35,13 @@ public class MsKafkaListener {
// 全局并发队列
PoolExecBlockingQueueUtil.offer(testResult.getReportId());
} 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);
}
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.LogUtil;
import io.metersphere.dto.RequestResult;
import io.metersphere.dto.ResultDTO;
import io.metersphere.jmeter.JMeterBase;
import io.metersphere.utils.JMeterVars;
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 org.apache.commons.collections4.CollectionUtils;
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 {
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";
private MsWebSocketClient client;
@Override
public Object clone() {
MsDebugListener clone = (MsDebugListener) super.clone();
MsResultCollector clone = (MsResultCollector) super.clone();
return clone;
}
@ -100,28 +97,20 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
@Override
public void testEnded(String host) {
LoggerUtil.debug("TestEnded " + this.getName());
SampleResult result = new SampleResult();
result.setResponseCode(TEST_END);
ResultDTO dto = new ResultDTO();
dto.setReportId(this.getName());
try {
if (client != null) {
client.close();
}
} catch (Exception e) {
LogUtil.error(e);
}
MsgDto dto = new MsgDto();
dto.setExecEnd(false);
dto.setContent(TEST_END);
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName());
WebSocketUtils.sendMessageSingle(dto);
WebSocketUtils.onClose(this.getName());
}
@Override
public void testStarted(String host) {
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
@ -142,10 +131,9 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName());
if (client != null) {
client.send(JSON.toJSONString(dto));
}
WebSocketUtils.sendMessageSingle(dto);
} catch (Exception ex) {
LoggerUtil.error("消息推送失败:" + ex.getMessage());
}
}
@ -179,9 +167,7 @@ public class MsDebugListener extends AbstractListenerElement implements SampleLi
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName());
if (client != null) {
client.send(JSON.toJSONString(dto));
}
WebSocketUtils.sendMessageSingle(dto);
}
}
}

View File

@ -116,8 +116,8 @@ public class ApiDefinitionExecResultService {
.operator(SessionUtils.getUserId())
.context(context)
.subject("接口用例通知")
.successMailTemplate("api/CaseResult")
.failedMailTemplate("api/CaseResult")
.successMailTemplate("api/CaseResultSuccess")
.failedMailTemplate("api/CaseResultFailed")
.paramMap(paramMap)
.event(event)
.build();
@ -187,8 +187,6 @@ public class ApiDefinitionExecResultService {
* 定时任务时userID要改为定时任务中的用户
*/
public void saveApiResultByScheduleTask(List<RequestResult> requestResults, ResultDTO dto) {
Map<String, String> apiIdResultMap = new HashMap<>();
Map<String, String> caseReportMap = new HashMap<>();
boolean isFirst = true;
int countExpectProcessResultCount = 0;
if (CollectionUtils.isNotEmpty(requestResults)) {
@ -201,7 +199,7 @@ public class ApiDefinitionExecResultService {
for (RequestResult item : requestResults) {
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";
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
TestPlanApiCase apiCase = testPlanApiCaseService.getById(dto.getTestId());
@ -214,19 +212,19 @@ public class ApiDefinitionExecResultService {
testPlanApiCaseService.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;
}
}
}
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));
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);
if (prevResult != null) {
prevResult.setContent(null);
apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(prevResult);
apiDefinitionExecResultMapper.updateByPrimaryKeySelective(prevResult);
}
if (StringUtils.isNotEmpty(saveResult.getTriggerMode()) && saveResult.getTriggerMode().equals("CASE")) {
saveResult.setTriggerMode(TriggerMode.MANUAL.name());
}
apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(saveResult);
apiDefinitionExecResultMapper.updateByPrimaryKeySelective(saveResult);
return saveResult;
}
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.SwaggerUrlRequest;
import io.metersphere.api.exec.api.ApiExecuteService;
import io.metersphere.api.exec.utils.ApiDefinitionExecResultUtil;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
@ -102,16 +103,12 @@ public class ApiDefinitionService {
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
private ApiTestEnvironmentService environmentService;
@Resource
private EsbApiParamService esbApiParamService;
@Resource
private TcpApiParamService tcpApiParamService;
@Resource
private ApiModuleMapper apiModuleMapper;
@Resource
private SystemParameterService systemParameterService;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private NoticeSendService noticeSendService;
@ -855,6 +852,15 @@ public class ApiDefinitionService {
* @return
*/
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);
}

View File

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

View File

@ -87,6 +87,7 @@ public class ApiScenarioReportService {
apiScenarioReportResultService.save(dto.getReportId(), requestResults);
}
public ApiScenarioReport testEnded(ResultDTO dto) {
if (!StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.toString())) {
// 更新控制台信息
@ -96,6 +97,15 @@ public class ApiScenarioReportService {
example.createCriteria().andReportIdEqualTo(dto.getReportId());
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;
if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
scenarioReport = updatePlanCase(requestResults, dto);
@ -226,7 +236,7 @@ public class ApiScenarioReportService {
}
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());
if (testPlanApiScenario != null) {
if (errorSize > 0) {
@ -234,7 +244,7 @@ public class ApiScenarioReportService {
} else {
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());
testPlanApiScenario.setPassRate(passRate);
@ -245,7 +255,7 @@ public class ApiScenarioReportService {
// 更新场景状态
ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(testPlanApiScenario.getApiScenarioId());
if (scenario != null) {
scenario.setLastResult(errorSize > 0 ? "Fail" : "Success");
scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name());
scenario.setPassRate(passRate);
scenario.setReportId(dto.getReportId());
int executeTimes = 0;
@ -256,11 +266,7 @@ public class ApiScenarioReportService {
apiScenarioMapper.updateByPrimaryKey(scenario);
}
}
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";
}
String status = getStatus(requestResults, dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
return report;
}
@ -269,15 +275,8 @@ public class ApiScenarioReportService {
List<String> testPlanReportIdList = new ArrayList<>();
StringBuilder scenarioNames = new StringBuilder();
Map<String, String> scenarioAndErrorMap = new HashMap<>();
Map<String, String> planScenarioReportMap = new HashMap<>();
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";
}
long errorSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), ScenarioStatus.Error.name())).count();
String status = getStatus(requestResults, dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
if (report != null) {
if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) {
@ -288,15 +287,12 @@ public class ApiScenarioReportService {
report.setScenarioId(testPlanApiScenario.getApiScenarioId());
report.setEndTime(System.currentTimeMillis());
apiScenarioReportMapper.updateByPrimaryKeySelective(report);
planScenarioReportMap.put(dto.getTestId(), report.getId());
if (errorSize > 0) {
scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.FAILD.name());
testPlanApiScenario.setLastResult(ScenarioStatus.Fail.name());
} else {
scenarioAndErrorMap.put(testPlanApiScenario.getId(), TestPlanApiExecuteStatus.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());
testPlanApiScenario.setPassRate(passRate);
@ -312,7 +308,7 @@ public class ApiScenarioReportService {
if (errorSize > 0) {
scenario.setLastResult("Fail");
} else {
scenario.setLastResult("Success");
scenario.setLastResult(ScenarioStatus.Success.name());
}
scenario.setPassRate(passRate);
scenario.setReportId(report.getId());
@ -325,11 +321,6 @@ public class ApiScenarioReportService {
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;
}
@ -339,9 +330,9 @@ public class ApiScenarioReportService {
if (report != null) {
// 更新场景状态
ApiScenarioReportResultExample example = new ApiScenarioReportResultExample();
example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo("Error");
example.createCriteria().andReportIdEqualTo(reportId).andStatusEqualTo(ScenarioStatus.Error.name());
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());
// 更新控制台信息
apiScenarioReportStructureService.update(reportId, resultService.getJmeterLogger(reportId));
@ -351,13 +342,10 @@ public class ApiScenarioReportService {
}
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";
if (dto != null && dto.getArbitraryData() != null && dto.getArbitraryData().containsKey("TIMEOUT") && (Boolean) dto.getArbitraryData().get("TIMEOUT")) {
LoggerUtil.info("报告 【 " + dto.getReportId() + " 】资源 " + dto.getTestId() + " 执行超时");
status = "Timeout";
}
String status = getStatus(requestResults, dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), status, dto.getRunMode());
// 更新场景状态
ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(dto.getTestId());
@ -365,8 +353,8 @@ public class ApiScenarioReportService {
scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId());
}
if (scenario != null) {
scenario.setLastResult(errorSize > 0 ? "Fail" : "Success");
long successSize = requestResults.stream().filter(requestResult -> StringUtils.equalsIgnoreCase(requestResult.getStatus(), "Success")).count();
scenario.setLastResult(errorSize > 0 ? "Fail" : ScenarioStatus.Success.name());
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.setReportId(dto.getReportId());
int executeTimes = 0;
@ -417,7 +405,7 @@ public class ApiScenarioReportService {
String event;
String status;
if (StringUtils.equals(scenario.getLastResult(), "Success")) {
if (StringUtils.equals(scenario.getLastResult(), ScenarioStatus.Success.name())) {
event = NoticeConstants.Event.EXECUTE_SUCCESSFUL;
status = "成功";
} else {
@ -437,8 +425,8 @@ public class ApiScenarioReportService {
.operator(userId)
.context(context)
.subject("接口自动化通知")
.successMailTemplate("api/ScenarioResult")
.failedMailTemplate("api/ScenarioResult")
.successMailTemplate("api/ScenarioResultSuccess")
.failedMailTemplate("api/ScenarioResultFailed")
.paramMap(paramMap)
.event(event)
.build();
@ -707,4 +695,14 @@ public class ApiScenarioReportService {
report.setScenarioId(scenarioId);
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.jmeter.ExecutedHandleSingleton;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioReportMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.ReportTriggerMode;
@ -47,6 +48,8 @@ public class TestResultService {
private TestPlanTestCaseService testPlanTestCaseService;
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
public void saveResults(ResultDTO dto) {
// 处理环境
@ -70,6 +73,14 @@ public class TestResultService {
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) {
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);

View File

@ -114,6 +114,9 @@
<if test="request.platform != null and request.platform != ''">
and issues.platform = #{request.platform}
</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">-->
<!-- 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.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.service.TestPlanService;
import org.quartz.*;
@ -16,25 +17,10 @@ import org.quartz.*;
public class TestPlanTestJob extends MsScheduleJob {
private String projectID;
// private PerformanceTestService performanceTestService;
// private TestPlanScenarioCaseService testPlanScenarioCaseService;
// private TestPlanApiCaseService testPlanApiCaseService;
// private ApiTestCaseService apiTestCaseService;
// private TestPlanReportService testPlanReportService;
// private TestPlanLoadCaseService testPlanLoadCaseService;
private TestPlanService testPlanService;
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);
}
/**
@ -63,7 +49,17 @@ public class TestPlanTestJob extends MsScheduleJob {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
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) {

View File

@ -68,6 +68,14 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
@Value("${jmeter.home}")
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
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
@ -99,7 +107,12 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
} catch (InterruptedException e) {
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();
}

View File

@ -153,16 +153,27 @@ public class ReflexObjectUtil {
GsonDiff diff = new GsonDiff();
Object originalValue = originalColumns.get(i).getOriginalValue();
Object newValue = newColumns.get(i).getOriginalValue();
String oldTags = null;
if (originalValue != null && !StringUtils.equals("null", originalValue.toString())) {
List<String> originalValueArray = JSON.parseArray(originalValue.toString(), String.class);
List<String> newValueArray = JSON.parseArray(newValue.toString(), String.class);
Collections.sort(originalValueArray);
Collections.sort(newValueArray);
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);
Collections.sort(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 diffValue;
if (oldTags != null) {
String diffStr = diff.diff(oldTags, newTags);
String diffValue = diff.apply(newTags, diffStr);
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);
}
// 深度对比

View File

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

View File

@ -35,6 +35,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.alibaba.fastjson.serializer.SerializerFeature.WriteMapNullValue;
@Aspect
@Component
public class SendNoticeAspect {
@ -81,7 +83,7 @@ public class SendNoticeAspect {
Expression titleExp = parser.parseExpression(target);
Object v = titleExp.getValue(context, Object.class);
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) {
LogUtil.error(e.getMessage(), e);
@ -119,7 +121,7 @@ public class SendNoticeAspect {
Object v = titleExp.getValue(context, Object.class);
if (v != null) {
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.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.RunRequest;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.domain.TestResource;
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.utils.CommonBeanFactory;
import io.metersphere.config.JmeterProperties;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.TestResourcePoolService;
import io.metersphere.service.TestResourceService;
@ -42,7 +42,7 @@ public abstract class AbstractEngine implements Engine {
GC_ALGO = CommonBeanFactory.getBean(JmeterProperties.class).getGcAlgo();
}
protected void initApiConfig(RunRequest runRequest) {
protected void initApiConfig(JmeterRunRequestDTO runRequest) {
String resourcePoolId = runRequest.getPoolId();
resourcePool = testResourcePoolService.getResourcePool(resourcePoolId);
if (resourcePool == null || StringUtils.equals(resourcePool.getStatus(), ResourceStatusEnum.DELETE.name())) {

View File

@ -300,12 +300,20 @@ public class EngineFactory {
rootDocument = docBuilder.parse(inputSource);
Element jmeterTestPlan = rootDocument.getDocumentElement();
NodeList childNodes = jmeterTestPlan.getChildNodes();
outer:
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element) {
// jmeterTestPlan的子元素肯定是<hashTree></hashTree>
NodeList childNodes1 = node.getChildNodes();
for (int j = 0; j < childNodes1.getLength(); j++) {
Node item = childNodes1.item(j);
if (StringUtils.equalsIgnoreCase("hashTree", item.getNodeName())) {
hashTree = (Element) node;
break;
break outer;
}
}
}
}
} else {
@ -320,11 +328,21 @@ public class EngineFactory {
NodeList secondChildNodes = secondHashTree.getChildNodes();
for (int j = 0; j < secondChildNodes.getLength(); j++) {
Node item = secondChildNodes.item(j);
Node newNode = item.cloneNode(true);
if (StringUtils.equalsIgnoreCase("TestPlan", item.getNodeName())) {
continue;
}
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.controller.request.EnvironmentGroupRequest;
import io.metersphere.dto.EnvironmentGroupDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -161,7 +162,7 @@ public class EnvironmentGroupService {
private void checkEnvironmentGroup(EnvironmentGroupRequest request) {
String name = request.getName();
if (StringUtils.isBlank(name)) {
MSException.throwException("environment group name is null.");
MSException.throwException(Translator.get("null_environment_group_name"));
}
EnvironmentGroupExample environmentGroupExample = new EnvironmentGroupExample();
@ -173,7 +174,7 @@ public class EnvironmentGroupService {
}
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")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_REVIEW_READ_COMMENT)
@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) {
request.setId(UUID.randomUUID().toString());
return issueCommentService.saveComment(request);

View File

@ -133,7 +133,7 @@ public class TestPlanController {
@PostMapping("/delete/{testPlanId}")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_PLAN_READ_DELETE)
@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 = "测试计划通知")
public int deleteTestPlan(@PathVariable String 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
ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
ApiTestCaseMapper apiTestCaseMapper;
ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
@Resource
ExtApiScenarioReportMapper extApiScenarioReportMapper;
@Resource
LoadTestReportMapper loadTestReportMapper;
@Resource
@ -551,8 +553,6 @@ public class TestPlanReportService {
boolean scenarioIsOk = executeInfo.isScenarioAllExecuted();
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) {
testPlanReport.setIsApiCaseExecuting(false);
}
@ -1075,13 +1075,17 @@ public class TestPlanReportService {
}
public void countReport(String planReportId) {
boolean isTimeOut = this.checkTestPlanReportIsTimeOut(planReportId);
if (isTimeOut) {
TestPlanReportExecuteCheckResultDTO checkResult = this.checkTestPlanReportIsTimeOut(planReportId);
testPlanLog.info("Check PlanReport:" + planReportId + "; result: "+ JSON.toJSONString(checkResult));
if (checkResult.isTimeOut()) {
//判断是否超时超时时强行停止任务
TestPlanReportExecuteCatch.finishAllTask(planReportId);
checkResult.setFinishedCaseChanged(true);
}
if(checkResult.isFinishedCaseChanged()){
this.updateExecuteApis(planReportId);
}
}
public TestPlanSimpleReportDTO getReport(String reportId) {
TestPlanReportContentExample example = new TestPlanReportContentExample();
@ -1216,19 +1220,61 @@ public class TestPlanReportService {
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);
int unFinishNum = executeInfo.countUnFinishedNum();
if (unFinishNum > 0) {
//20分钟没有案例执行结果更新则定位超时
long lastCountTime = executeInfo.getLastFinishedNumCountTime();
long nowTime = System.currentTimeMillis();
testPlanLog.info("ReportId: ["+planReportId+"]; timeCount:"+ (nowTime - lastCountTime));
if (nowTime - lastCountTime > 1200000) {
return true;
TestPlanReportExecuteCheckResultDTO checkResult = executeInfo.countUnFinishedNum();
return checkResult;
}
private void syncReportStatus(String planReportId) {
if (TestPlanReportExecuteCatch.containsReport(planReportId)) {
TestPlanExecuteInfo executeInfo = TestPlanReportExecuteCatch.getTestPlanExecuteInfo(planReportId);
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) {
@ -1239,5 +1285,4 @@ public class TestPlanReportService {
}
TestPlanReportExecuteCatch.remove(planReportId);
}
}

View File

@ -243,6 +243,10 @@ public class TestPlanService {
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlanWithBLOBs());
}
public TestPlanWithBLOBs get(String testPlanId) {
return testPlanMapper.selectByPrimaryKey(testPlanId);
}
public TestPlan editTestPlanWithRequest(AddTestPlanRequest request) {
List<String> principals = request.getPrincipals();
if (!CollectionUtils.isEmpty(principals)) {
@ -1116,8 +1120,8 @@ public class TestPlanService {
testPlanLoadCaseService.update(testPlanLoadCase);
LogUtil.error(e);
}
performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId);
if (StringUtils.isNotEmpty(reportId)) {
performaneThreadIDMap.put(performanceRequest.getTestPlanLoadId(), reportId);
executePerformanceIdMap.put(performanceRequest.getTestPlanLoadId(), TestPlanApiExecuteStatus.RUNNING.name());
} else {
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) {
WebSocketUtils.ONLINE_USER_SESSIONS.put(reportId, session);
log.info("客户端: [" + reportId + "] : 连接成功!");
WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!");
//WebSocketUtils.sendMessageAll("客户端: [" + reportId + "] : 连接成功!");
}
/**
@ -45,7 +45,7 @@ public class IndexWebSocket {
WebSocketUtils.ONLINE_USER_SESSIONS.remove(reportId);
log.info("[" + reportId + "] : 断开连接!");
//并且通知其他人当前用户已经断开连接了
WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!");
//WebSocketUtils.sendMessageAll("[" + reportId + "] : 断开连接!");
session.close();
}

View File

@ -1,5 +1,6 @@
package io.metersphere.websocket.c.to.c;
import io.metersphere.utils.LoggerUtil;
import io.metersphere.websocket.c.to.c.util.MsgDto;
import javax.websocket.RemoteEndpoint;
@ -12,9 +13,13 @@ public class WebSocketUtils {
// 单用户推送
public static void sendMessage(Session session, String message) {
if (session == null) { return; }
if (session == null) {
return;
}
RemoteEndpoint.Async async = session.getAsyncRemote();
if (async == null) { return; }
if (async == null) {
return;
}
async.sendText(message);
}
@ -30,4 +35,20 @@ public class WebSocketUtils {
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
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.idle-timeout=10000
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.username=${spring.datasource.username}
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.idle-timeout=10000
spring.datasource.quartz.hikari.pool-name=DatebookHikariCP
@ -92,7 +92,8 @@ jmeter.home=/opt/jmeter
# quartz
quartz.enabled=true
quartz.scheduler-name=msServerJob
quartz.thread-count=30
quartz.thread-count=60
quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true
# file upload
spring.servlet.multipart.max-file-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
# mock
mock_warning=No matching Mock expectation was found
zentao_test_type_error=请求方式错误
zentao_test_type_error=invalid Zentao request
#项目报告
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 不合法
# mock
mock_warning=未找到匹配的Mock期望
zentao_test_type_error=invalid Zentao request
zentao_test_type_error=无效的 Zentao 请求
#项目报告
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=請求方式錯誤
#项目报告
enterprise_test_report=項目報告
#环境组
null_environment_group_name = 環境組名稱不存在
environment_group_name = 環境組名稱
environment_group_exist = 已存在

View File

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

View File

@ -6,7 +6,7 @@
</head>
<body>
<div>
<p>${operator}执行接口自动化: ${name}, 结果: ${status}</p>
<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

@ -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,6 +324,10 @@ export default {
if (data) {
this.report = data;
if (this.report.reportVersion && this.report.reportVersion > 1) {
this.report.status = data.status;
if (!this.isNotRunning) {
setTimeout(this.getReport, 2000)
} else {
if (data.content) {
let report = JSON.parse(data.content);
this.content = report;
@ -334,6 +338,7 @@ export default {
this.totalTime = report.totalTime;
}
this.loading = false;
}
} else {
this.buildReport();
}
@ -528,10 +533,12 @@ export default {
.report-container .is-active .fail {
color: inherit;
}
.report-console {
height: calc(100vh - 270px);
overflow-y: auto;
}
.export-button {
float: right;
}

View File

@ -356,7 +356,7 @@ export default {
if (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.messageWebSocket.close();
scenario.$emit('hide', this.scenarioId);

View File

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

View File

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

View File

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

View File

@ -282,7 +282,7 @@ export default {
},
getConditions() {
let sampleSelectRows = this.$refs.table.getSelectRows();
let batchParam = buildBatchParam(this);
let batchParam = buildBatchParam(this, undefined, this.projectId);
let param = {};
if (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.environmentId = this.environment.id;
}

View File

@ -13,7 +13,7 @@
:placeholder="$t('api_test.definition.request.run_env')"
clearable>
<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"/>
<template v-slot:empty>
<div class="empty-environment">

View File

@ -945,7 +945,7 @@ export default {
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 {
this.$success(this.$t('commons.save_success'));
}
@ -971,7 +971,7 @@ export default {
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 {
this.$success(this.$t('commons.save_success'));
}

View File

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

View File

@ -4,12 +4,16 @@
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{ $t('api_report.response_code') }} :</div>
<el-tooltip
v-if="responseResult.responseCode"
:content="responseResult.responseCode"
placement="top">
<div class="node-title" :class="response && response.success ?'ms-req-success':'ms-req-error'">
{{ responseResult && responseResult.responseCode ? responseResult.responseCode : '0' }}
</div>
</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>
<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) {
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 title = d.type === 'MENTIONED_ME' ? this.$t('commons.mentioned_me_notice') : this.$t('commons.system_notice');
setTimeout(() => {

View File

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

View File

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

View File

@ -251,7 +251,7 @@
</el-col>
<el-col :span="12">
<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-row>
</div>

View File

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

View File

@ -53,7 +53,7 @@
chartType: "line",
charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}],
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,
options: {},
}

View File

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

View File

@ -60,46 +60,140 @@ export function SYSLIST(){
let sysList = [
{
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.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.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('permission.project_api_report.name'), value: i18n.t('permission.project_api_report.name'), leaf: true}]
{
label: i18n.t('workstation.table_name.api_definition'),
value: i18n.t('workstation.table_name.api_definition'),
leaf: true
},
{
label: i18n.t('workstation.table_name.performance'), 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('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'),
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('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.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.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.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')+"-"+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.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.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.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('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.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('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) {
case "接口自动化" || "Api automation" || "接口自動化":
case "接口自动化" :
case "Api automation" :
case"接口自動化":
url += "/api/automation?resourceId=" + resourceId;
break;
case "测试计划" || "測試計劃" || "Test plan":
case "测试计划" :
case "測試計劃" :
case "Test plan":
url += "/track/plan/view/" + resourceId;
break;
case "用例评审" || "Case review" || "用例評審":
case "用例评审" :
case "Case review" :
case "用例評審":
url += "/track/review/view/" + resourceId;
break;
case "缺陷管理" || "Defect management":
case "缺陷管理" :
case "Defect management":
url += "/track/issue";
break;
case "SWAGGER_TASK" :
url += "/api/definition";
break;
case "接口定义" || "接口定義" || "Api definition":
case "接口定义" :
case "接口定義" :
case "Api definition":
url += "/api/definition?resourceId=" + resourceId;
break;
case "接口定义用例" || "接口定義用例" || "Api definition case":
case "接口定义用例" :
case "接口定義用例":
case "Api definition case":
url += "/api/definition?caseId=" + resourceId;
break;
case "测试报告" || "測試報告" || "Test Report":
case "测试报告" :
case "測試報告" :
case "Test Report":
url += "/api/automation/report";
break;
case "性能测试报告" || "性能測試報告" || "Performance test report" :
case "性能测试报告" :
case "性能測試報告" :
case "Performance test report" :
url += "/performance/report/all";
break;
case "性能测试" || "性能測試" || "Performance test" :
case "性能测试" :
case "性能測試" :
case "Performance test" :
url += "/performance/test/edit/" + resourceId;
break;
case "测试用例" || "測試用例" || "Test case":
case "测试用例" :
case "測試用例" :
case "Test case":
url += "/track/case/all?resourceId=" + resourceId;
break;
case "系统-用户" || "系统-用户" || "System user":
case "系统-用户":
case "System user":
url += "/setting/user";
break;
case "系统-组织" || "系統-組織" || "System organization":
case "系统-组织" :
case "系統-組織" :
case "System organization":
url += "/setting/organization";
break;
case "工作空间" || "系统-工作空间" || "workspace" :
case "工作空间" :
case "系统-工作空间" :
case "workspace" :
url += "/setting/systemworkspace";
break;
case "用户组与权限" || "用戶組與權限" || "Group" :
case "用户组与权限" :
case "用戶組與權限" :
case "Group" :
url += "/setting/usergroup";
break;
case "系统-测试资源池" || "系统-測試資源池" || "System test resource" :
case "系统-测试资源池":
case "系统-測試資源池" :
case "System test resource" :
url += "/setting/testresourcepool";
break;
case "系统-系统参数设置" || "系统-系統參數設置" || "System parameter setting" :
case "系统-系统参数设置":
case "系统-系統參數設置" :
case "System parameter setting" :
url += "/setting/systemparametersetting";
break;
case "工作空间-成员" || "工作空間-成員" || "Workspace member" :
case "工作空间-成员" :
case "工作空間-成員" :
case "Workspace member" :
url += "/setting/member";
break;
case "项目-项目管理" || "項目-項目管理" || "Project project manager" :
case "项目-项目管理" :
case "項目-項目管理" :
case "Project project manager" :
url += "/setting/project/:type";
break;
case "项目-环境设置" || "項目-環境設置" || "Project environment setting" :
case "项目-环境设置" :
case "項目-環境設置" :
case "Project environment setting" :
url += "/project/env";
break;
case "工作空间-模版设置-自定义字段" || "工作空間-模版設置-自定義字段" || "Workspace template settings field" :
case "工作空间-模版设置-自定义字段" :
case "工作空間-模版設置-自定義字段" :
case "Workspace template settings field" :
url += "/setting/workspace/template/field";
break;
case "工作空间-模版设置-用例模版" || "工作空間-模版設置-用例模板" || "Workspace template settings case" :
case "工作空间-模版设置-用例模版" :
case "工作空間-模版設置-用例模板" :
case "Workspace template settings case" :
url += "/setting/workspace/template/case";
break;
case "工作空间-模版设置-缺陷模版" || "工作空間-模版設置-缺陷模板" || "Workspace template settings issue" :
case "工作空间-模版设置-缺陷模版" :
case "工作空間-模版設置-缺陷模板" :
case "Workspace template settings issue" :
url += "/setting/workspace/template/issues";
break;
case "项目-成员" || "項目-成員" || "Project member" :
case "项目-成员":
case "項目-成員" :
case "Project member" :
url += "/project/member";
break;
default:

View File

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

View File

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

View File

@ -1,11 +1,13 @@
<template>
<div v-loading="result.loading">
<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"
:label="environment.name"
: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') }}
</el-button>
<template v-slot:empty>
@ -21,7 +23,8 @@
</span>
</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"/>

View File

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

View File

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

View File

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

View File

@ -165,7 +165,8 @@ export default {
title: '',
description: '',
creator: null,
remark: null
remark: null,
tapdUsers:[]
},
tapdUsers: [],
zentaoUsers: [],
@ -234,8 +235,9 @@ export default {
},
},
methods: {
open(data) {
open(data, type) {
this.result.loading = true;
this.type = type;
this.$nextTick(() => {
getIssuePartTemplateWithProject((template, project) => {
this.currentProject = project;
@ -254,6 +256,7 @@ export default {
}
})
} else {
this.issueId = null;
this.form.follows = [];
}
},
@ -324,6 +327,7 @@ export default {
}
}
this.customFieldForm = parseCustomField(this.form, this.issueTemplate, this.customFieldRules);
this.comments = [];
this.$nextTick(() => {
if (this.$refs.testCaseIssueList) {
this.$refs.testCaseIssueList.initTableData();
@ -421,6 +425,10 @@ export default {
}
},
openComment() {
if (!this.issueId) {
this.$warning(this.$t('test_track.issue.save_before_open_comment'));
return;
}
this.$refs.issueComment.open();
},
getComments() {

View File

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

View File

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

View File

@ -19,13 +19,11 @@
</el-button>
</span>
<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"/>
</span>
<br/>
<!-- <div class="comment-desc" style="font-size: 10px;color: #303133">-->
<!-- <pre>{{ comment.description }}</pre>-->
<!-- </div>-->
<div v-if="!isImage" class="comment-desc" style="font-size: 10px;color: #303133">
<pre>{{ comment.description }}</pre>
</div>
@ -41,24 +39,25 @@
</div>
</div>
<el-dialog
<el-dialog :visible.sync="visible"
:title="$t('commons.edit')"
:visible.sync="visible"
width="30%"
:destroy-on-close="true"
:append-to-body="true"
:close-on-click-modal="false"
show-close>
<el-input
type="textarea"
:rows="5"
v-model="description">
</el-input>
<span slot="footer" class="dialog-footer">
<ms-dialog-footer
@cancel="visible = false"
@confirm="editComment"/>
</span>
append-to-body>
<div>
<div class="editors_div_style">
<div id="editorsDiv">
<ms-mark-down-text prop="description" :data="comment" :toolbars="toolbars"/>
</div>
</div>
<div>
<el-button type="primary" size="mini" class="send-btn" @click="editComment">
{{ $t('test_track.comment.send') }}
</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
@ -66,10 +65,11 @@
<script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import {getCurrentUser} from "@/common/js/utils";
import MsMarkDownText from "@/business/components/track/case/components/MsMarkDownText";
export default {
name: "ReviewCommentItem",
components: {MsDialogFooter},
components: {MsDialogFooter, MsMarkDownText},
props: {
comment: Object,
readOnly: {
@ -89,6 +89,41 @@ export default {
imgNameList: [],
description: "",
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: {
@ -121,13 +156,14 @@ export default {
this.visible = true;
},
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.$success(this.$t('commons.modify_success'));
this.$emit("refresh");
});
},
checkImage() {
this.srcList = [];
let param = this.comment.description;
let returnFlag = false;
if (param) {
@ -269,6 +305,11 @@ pre {
}
/deep/ .el-button--mini, .el-button--mini.is-round {
padding: 4px 9px;
padding: 7px 15px;
}
.send-btn {
margin-top: 5px;
width: 100%;
}
</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: 'createTime', key: '8', label: 'commons.create_time'},
{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: [

View File

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

View File

@ -7,13 +7,15 @@ export default {
yes: "yes",
no: "no",
example: "Demo",
subject: "Subject",
excelFile: "Excel",
xmindFile: "Xmind",
default: "default",
sort_default: "Default",
please_select_import_mode: 'Please select import mode',
please_select_import_module: 'Please select import module',
pass_rate: 'Pass rate',
execution_times: 'Execution times',
pass_rate: 'Pass Rate',
execution_times: 'Execution Times',
cover: 'Cover',
module_title: 'Default module',
save_data_when_page_change: 'Save when page change',
@ -78,7 +80,7 @@ export default {
update_user_id: 'Updater ID',
update_time: 'Updated Time',
delete_time: 'Delete Time',
delete_user: 'Deleted by',
delete_user: 'Deleted By',
delete_user_id: 'Deleted by id',
add: 'Add',
preview: 'Preview',
@ -325,6 +327,11 @@ export default {
send: "Send",
save_as_draft: "Draft",
},
table: {
draft: "Draft",
sended: "Send",
send_error: "Send error",
},
project_report: {
create_report: "Create report",
report_name: "Report name",
@ -706,7 +713,7 @@ export default {
create: "Create Custom Code",
update: "Update Custom Code",
delete: "Delete Custom Code",
language: "language",
language: "Language",
relate_tip: "Create in the Project Settings -> Custom Code Snippet menu",
select_tip: "Please select a custom code!",
none_content: "The custom code snippet is empty",
@ -921,11 +928,11 @@ export default {
test_name_is_null: 'Test name 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!',
file_name: 'File name',
file_name: 'File Name',
file_size: 'File size',
file_type: 'File Type',
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_type: 'Only JMX/CSV/JAR files can be uploaded',
related_file_not_found: "No related test file found!",
@ -1096,18 +1103,18 @@ export default {
api_title: "Api Test",
case_title: "Test Case",
doc_title: "DOC",
api_name: "Api name",
api_status: "Api status",
api_type: "Api type",
api_name: "Api Name",
api_status: "Api Status",
api_type: "Api Type",
api_agreement: "Method",
api_path: "Api path",
api_path: "Api Path",
api_definition_path: "API Path",
api_case_path: "Case Path",
api_principal: "Api principal",
api_last_time: "Last update time",
api_principal: "Api Principal",
api_last_time: "Last Update Time",
api_case_number: "Cases",
api_case_status: "Case status",
api_case_passing_rate: "Use case pass rate",
api_case_status: "Case Status",
api_case_passing_rate: "Use Case Pass Rate",
create_tip: "Note: Detailed interface information can be filled out on the edit page",
api_import: "Api Import",
check_select: "Please check the API",
@ -1264,15 +1271,15 @@ export default {
scenario_test: "Scenario test",
scenario_list: "Scenario List",
add_scenario: "Add scenario",
scenario_name: "Scenario name",
case_level: "Case level",
scenario_name: "Scenario Name",
case_level: "Case Level",
tag: "Tag",
creator: "Creator",
update_time: "Update time",
update_time: "Update Time",
step: "Step",
last_result: "Last result",
last_result: "Last Result",
last_result_id: 'Last result id',
passing_rate: "Passing rate",
passing_rate: "Passing Rate",
success: "Success",
fail: "Fail",
saved: "Saved",
@ -1730,7 +1737,7 @@ export default {
title: "Updated interfaces in the past 7 days",
table_coloum: {
index: "ID",
api_name: "Api name",
api_name: "Api Name",
path: "path",
api_status: "Api status",
update_time: "Update time",
@ -1843,7 +1850,7 @@ export default {
recent_plan: "My recent plan",
recent_case: "My recent case",
recent_review: "My recent review",
pass_rate: "Pass rate",
pass_rate: "Pass Rate",
execution_result: ": Please select the execution result",
actual_result: ": The actual result is empty",
cancel_relevance_success: "Unlinked successfully",
@ -1892,7 +1899,7 @@ export default {
manual: "Manual",
create: "Create test case",
case_type: "Case Type",
name: "Test case name",
name: "Test Case Name",
module: "Module",
project: 'Project',
maintainer: "Maintainer",
@ -2078,7 +2085,7 @@ export default {
comment: {
no_comment: "No Comment",
send_comment: "Post a comment (Ctrl + Enter to send)",
send: "Send",
send: "Confirm",
description_is_null: "Comment content cannot be empty!",
send_success: "Comment successful!",
},
@ -2194,7 +2201,7 @@ export default {
issue: "Issue",
issue_management: "Issue Management",
platform_status: "Platform Status",
issue_resource: "Issue source",
issue_resource: "Issue Source",
create_issue: "Create Issue",
add_issue: "Add Issue",
issue_list: "Issue List",
@ -2229,7 +2236,8 @@ export default {
third_party_integrated: "Third-party Platform Integrated",
use_third_party: "Enable Jira Issue Template",
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: {
name: "Test Plan Report",

View File

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

View File

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

View File

@ -37,6 +37,19 @@ export function getIssuesById(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) {
return planId ? baseGet('/issues/plan/get/' + planId, callback) : {};
}