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,7 +51,11 @@ public class TestPlanReportExecuteCatch {
}
public synchronized static boolean containsReport(String reportId) {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
if(StringUtils.isEmpty(reportId)){
return false;
}else {
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
}
}
public synchronized static void updateApiTestPlanExecuteInfo(String reportId,

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);
});
}
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,9 +31,12 @@ import java.util.Map;
public class GenerateHashTreeUtil {
public static MsScenario parseScenarioDefinition(String scenarioDefinition) {
MsScenario scenario = JSONObject.parseObject(scenarioDefinition, MsScenario.class);
parse(scenarioDefinition, scenario, scenario.getId(), null);
return scenario;
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) {

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

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();
if (StringUtils.endsWithIgnoreCase(k, "Time")) {
try {
long time = Long.parseLong(value);
value = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss");
} catch (Exception ignore) {
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);
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>
hashTree = (Element) node;
break;
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 outer;
}
}
}
}
} else {
@ -320,9 +328,19 @@ 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);
rootDocument.adoptNode(newNode);
hashTree.appendChild(newNode);
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,12 +1075,16 @@ 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);
}
this.updateExecuteApis(planReportId);
}
public TestPlanSimpleReportDTO getReport(String reportId) {
@ -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,16 +324,21 @@ export default {
if (data) {
this.report = data;
if (this.report.reportVersion && this.report.reportVersion > 1) {
if (data.content) {
let report = JSON.parse(data.content);
this.content = report;
this.fullTreeNodes = report.steps;
this.content.console = report.console;
this.content.error = report.error;
this.content.success = (report.total - report.error);
this.totalTime = report.totalTime;
this.report.status = data.status;
if (!this.isNotRunning) {
setTimeout(this.getReport, 2000)
} else {
if (data.content) {
let report = JSON.parse(data.content);
this.content = report;
this.fullTreeNodes = report.steps;
this.content.console = report.console;
this.content.error = report.error;
this.content.success = (report.total - report.error);
this.totalTime = report.totalTime;
}
this.loading = false;
}
this.loading = false;
} else {
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;
}
@ -639,7 +639,7 @@ export default {
clickResource(resource) {
if (resource.refType && resource.refType === 'API') {
if(resource.protocol==='dubbo://'){
if (resource.protocol === 'dubbo://') {
resource.protocol = 'DUBBO'
}
let definitionData = this.$router.resolve({

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">
<ms-chart ref="chart1" v-if="!loading" :options="dataOption"
:style="{width: chartWidthNumber+'px', height: (h-50) + 'px'}" class="chart-config" :autoresize="true"
id="picChart"/>
<el-row>
<div class="chart-style">
<ms-chart ref="chart1" v-if="!loading" :options="dataOption"
: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

@ -56,50 +56,144 @@ export function LOG_TYPE_MAP(_this) {
return LOG_TYPE_MAP;
}
export function SYSLIST(){
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.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('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,15 +1,17 @@
<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>
<div v-if="isShowConfirmButton(pe.id)" class="empty-environment">
<div v-if="isShowConfirmButton(pe.id)" class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }}
</el-button>
@ -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;
@ -243,17 +245,18 @@ export default {
});
});
if(data&&data.id){
if (data && data.id) {
this.$get('/issues/follow/' + data.id, response => {
this.form.follows = response.data;
for (let i = 0; i < response.data.length; i++) {
if(response.data[i]===this.currentUser().id){
if (response.data[i] === this.currentUser().id) {
this.showFollow = true;
break;
}
}
})
}else {
} 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,46 +19,45 @@
</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">
<div v-if="!isImage" class="comment-desc" style="font-size: 10px;color: #303133">
<pre>{{ comment.description }}</pre>
</div>
<div v-if="isImage" class="demo-image__preview">
<pre>{{ imgDescription }}</pre>
<el-image
:z-index="imageIndex"
style="width: 100px; height: 100px;"
fit="contain"
:src="src"
:preview-src-list="srcList">
:z-index="imageIndex"
style="width: 100px; height: 100px;"
fit="contain"
:src="src"
:preview-src-list="srcList">
</el-image>
</div>
</div>
<el-dialog
: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>
<el-dialog :visible.sync="visible"
:title="$t('commons.edit')"
:destroy-on-close="true"
:close-on-click-modal="false"
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: {
@ -83,16 +83,51 @@ export default {
return {
visible: false,
imgDescription: "",
imageIndex:99999,
src:"",
srcList:[],
imgNameList:[],
imageIndex: 99999,
src: "",
srcList: [],
imgNameList: [],
description: "",
imageMatchPattern:"(\\!\\[)\\S+]\\(\\S+\\)",
imageMatchPattern: "(\\!\\[)\\S+]\\(\\S+\\)",
toolbars: {
bold: false, //
italic: false, //
header: false, //
underline: false, // 线
strikethrough: false, // 线
mark: false, //
superscript: false, //
subscript: false, //
quote: false, //
ol: false, //
ul: false, //
link: false, //
imagelink: true, //
code: false, // code
table: false, //
fullscreen: false, //
readmodel: false, //
htmlcode: false, // html
help: false, //
/* 1.3.5 */
undo: false, //
redo: false, //
trash: false, //
save: false, // eventssave
/* 1.4.2 */
navigation: false, //
/* 2.1.8 */
alignleft: false, //
aligncenter: false, //
alignright: false, //
/* 2.2.1 */
subfield: false, //
preview: false, //
}
}
},
computed:{
isImage(){
computed: {
isImage() {
return this.checkImage(this.comment.description);
}
},
@ -102,7 +137,7 @@ export default {
this.$warning(this.$t('test_track.comment.cannot_delete'));
return;
}
if(this.imgNameList.length > 0){
if (this.imgNameList.length > 0) {
this.imgNameList.forEach(imgName => {
this.$get('/resource/md/delete/' + imgName);
});
@ -121,90 +156,91 @@ 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(){
checkImage() {
this.srcList = [];
let param = this.comment.description;
let returnFlag = false;
if(param){
let message = param+"";
if (param) {
let message = param + "";
let matchIndex = message.indexOf("](/resource/md/get/");
if(matchIndex > 0){
if (matchIndex > 0) {
let messageSplitArr = message.split("](/resource/md/get/");
for(let itemIndex = 0;itemIndex < messageSplitArr.length; itemIndex ++){
for (let itemIndex = 0; itemIndex < messageSplitArr.length; itemIndex++) {
let itemStr = messageSplitArr[itemIndex];
let picNameIndex = itemStr.indexOf("![");
if( picNameIndex < 0){
if (picNameIndex < 0) {
let endUrlIndex = itemStr.indexOf(")");
if( endUrlIndex > 0){
let itemStrArr = itemStr.substr(0,endUrlIndex);
if (endUrlIndex > 0) {
let itemStrArr = itemStr.substr(0, endUrlIndex);
//if(imgNameList.)
if(this.imgNameList.indexOf(itemStrArr) < 0){
if (this.imgNameList.indexOf(itemStrArr) < 0) {
this.imgNameList.push(itemStrArr);
}
let imgUrl = "/resource/md/get/"+itemStrArr;
let imgUrl = "/resource/md/get/" + itemStrArr;
this.src = imgUrl;
if(this.srcList.indexOf(itemStrArr) < 0){
if (this.srcList.indexOf(itemStrArr) < 0) {
this.srcList.push(imgUrl);
}
}
}else{
let inputStr = itemStr.substr(0,picNameIndex);
if(this.imgDescription === ""){
} else {
let inputStr = itemStr.substr(0, picNameIndex);
if (this.imgDescription === "") {
this.imgDescription = inputStr;
}else {
} else {
this.imgDescription = "\n" + inputStr;
}
}
}
}else{
} else {
let imgUrlIndex = message.indexOf("](http");
if(imgUrlIndex > 0){
if (imgUrlIndex > 0) {
let imgUrlSplitArr = message.split("](http");
for(let itemIndex = 0;itemIndex < imgUrlSplitArr.length; itemIndex ++){
for (let itemIndex = 0; itemIndex < imgUrlSplitArr.length; itemIndex++) {
let itemStr = imgUrlSplitArr[itemIndex];
let picNameIndex = itemStr.indexOf("![");
if( picNameIndex < 0){
if (picNameIndex < 0) {
let endUrlIndex = itemStr.indexOf(")");
if( endUrlIndex > 0){
let itemStrArr = itemStr.substr(0,endUrlIndex);
if (endUrlIndex > 0) {
let itemStrArr = itemStr.substr(0, endUrlIndex);
//if(imgNameList.)
if(this.imgNameList.indexOf(itemStrArr) < 0){
if (this.imgNameList.indexOf(itemStrArr) < 0) {
this.imgNameList.push(itemStrArr);
}
let imgUrl = "http"+itemStrArr;
let imgUrl = "http" + itemStrArr;
this.src = imgUrl;
if(this.srcList.indexOf(itemStrArr) < 0){
if (this.srcList.indexOf(itemStrArr) < 0) {
this.srcList.push(imgUrl);
}
}
}else{
let inputStr = itemStr.substr(0,picNameIndex);
if(this.imgDescription === ""){
} else {
let inputStr = itemStr.substr(0, picNameIndex);
if (this.imgDescription === "") {
this.imgDescription = inputStr;
}else {
} else {
this.imgDescription = "\n" + inputStr;
}
}
}
}
}
if(this.srcList.length > 0){
if (this.srcList.length > 0) {
returnFlag = true;
}
}
return returnFlag;
},
checkByUrls(url){
checkByUrls(url) {
let checkResultFlag = false;
if(this.imgNameList.length > 0){
if (this.imgNameList.length > 0) {
this.imgNameList.forEach(imgName => {
if(imgName === url){
if (imgName === url) {
checkResultFlag = true;
}
});
@ -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',
@ -68,18 +70,18 @@ export default {
input_limit: 'Within {0} and {1} characters',
login: 'Sign In',
welcome: 'One-stop open source continuous testing platform',
theme_color:'Theme color',
theme_color: 'Theme color',
username: 'Username',
password: 'Password',
input_username: 'Please enter username',
input_password: 'Please enter password',
test: 'Test',
create_time: 'Created Time',
update_user_id:'Updater ID',
update_user_id: 'Updater ID',
update_time: 'Updated Time',
delete_time: 'Delete Time',
delete_user: 'Deleted by',
delete_user_id:'Deleted by id',
delete_user: 'Deleted By',
delete_user_id: 'Deleted by id',
add: 'Add',
preview: 'Preview',
member: 'Member',
@ -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",
@ -393,20 +400,20 @@ export default {
system_project: "Project",
user_unit: "",
workspace_unit: "",
dash_board:'DashBoard',
all_project:'All Project',
to_be_completed:'Unfinished',
pending_upgrade:'To be update',
information:'Information',
nick_name:'Nick name',
resourceId:'Resource ID',
num:'Number',
original_state:'Original State',
custom_num:'CustomNum',
version:'Version',
is_new:"Is new",
form_config:"Form config",
form_content:"Form content"
dash_board: 'DashBoard',
all_project: 'All Project',
to_be_completed: 'Unfinished',
pending_upgrade: 'To be update',
information: 'Information',
nick_name: 'Nick name',
resourceId: 'Resource ID',
num: 'Number',
original_state: 'Original State',
custom_num: 'CustomNum',
version: 'Version',
is_new: "Is new",
form_config: "Form config",
form_content: "Form content"
},
login: {
normal_Login: "Normal Login",
@ -427,25 +434,25 @@ export default {
invalid: 'invalid',
expired: 'expired',
},
workstation:{
workstation: {
dash_board: 'My DashBoard',
upcoming: 'My Upcoming',
focus: 'My Focus',
creation: 'My Creation',
creation_case:'My Creation Case',
creation_issue:'My Creation Issue',
creation_case: 'My Creation Case',
creation_issue: 'My Creation Issue',
creation_case_tip: 'No use case has been created yet, create it now',
creation_issue_tip:'No defects have been created yet, create them now',
table_name:{
track_case:'Track Case',
track_plan:'Track Plan',
track_review:'Track Review',
track_issue:'Track Issue',
api_definition:'Api Definition',
api_automation:'Api Automation',
api_case:'Api Case',
performance:'Performance',
scenario_case:'Scenario Case'
creation_issue_tip: 'No defects have been created yet, create them now',
table_name: {
track_case: 'Track Case',
track_plan: 'Track Plan',
track_review: 'Track Review',
track_issue: 'Track Issue',
api_definition: 'Api Definition',
api_automation: 'Api Automation',
api_case: 'Api Case',
performance: 'Performance',
scenario_case: 'Scenario Case'
}
},
display: {
@ -511,7 +518,7 @@ export default {
option_value_check: 'Please fill in the full option values',
},
workspace: {
id:'Workspace ID',
id: 'Workspace ID',
create: 'Create Workspace',
update: 'Update Workspace',
delete: 'Delete Workspace',
@ -632,12 +639,12 @@ export default {
zentao_request: 'Zentao request',
input_zentao_request: 'Please enter zentao request type',
input_zentao_url: 'Please enter Zentao address, for example: http://xx.xx.xx.xx/zentao/',
zentao_info:'Zentao Information',
zentao_account:'Zentao Account',
zentao_password:'Zentao Password',
zentao_prompt_information:'This information is the username and password of the user who submitted the defect through ZenTao. If not filled in, the default information configured in the workspace will be used',
zentao_config_tip:"Refer to the value of $config->requestType in the ZenTao configuration file",
zentao_config_path:"Configuration file reference path: /opt/zbox/app/zentao/config/my.php",
zentao_info: 'Zentao Information',
zentao_account: 'Zentao Account',
zentao_password: 'Zentao Password',
zentao_prompt_information: 'This information is the username and password of the user who submitted the defect through ZenTao. If not filled in, the default information configured in the workspace will be used',
zentao_config_tip: "Refer to the value of $config->requestType in the ZenTao configuration file",
zentao_config_path: "Configuration file reference path: /opt/zbox/app/zentao/config/my.php",
use_tip: 'Usage guidelines:',
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
@ -664,11 +671,11 @@ export default {
input_azure_url: 'Please enter Azure Devops Url',
input_azure_id: 'Please enter Azure Organization ID',
use_tip_azure: 'Azure Devops URL+PersonalAccessTokens(User Settings-Personal Access Tokens-New Token)',
jira_prompt_information:'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used'
jira_prompt_information: 'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used'
}
},
project: {
id:'Project ID',
id: 'Project ID',
name: 'Project name',
recent: 'Recent Projects',
create: 'Create Project',
@ -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",
@ -772,7 +779,7 @@ export default {
no_such_user: 'Without this user information, please enter the correct user ID or user Email!',
},
user: {
id:'User ID',
id: 'User ID',
create: 'Create',
modify: 'Modify',
input_name: 'Please enter a user name',
@ -798,7 +805,7 @@ export default {
add_project_batch: 'Batch Add User To Project',
add_workspace_batch: "Batch Add User To Workspace",
add_project_batch_tip: 'Add a read-only user group for members by default (system)',
import_user_tip:'User group only supports system default user group',
import_user_tip: 'User group only supports system default user group',
},
group: {
add: 'Add User Group',
@ -843,7 +850,7 @@ export default {
add: 'Add Role',
},
report: {
id:'Report ID',
id: 'Report ID',
api_test_report: 'Api Test Report',
load_test_report: 'Load Test Report',
test_plan_report: 'Test Plan Report',
@ -890,13 +897,13 @@ export default {
delete_batch_confirm: 'Confirm batch delete report',
response_time: 'Response Time(s)',
max_users: 'VUs',
file_id:'Report File ID',
avg_response_time:'Average Response Time',
tps:'Transactions Per Second',
plan_share_url:'Whether the link jump is logged in'
file_id: 'Report File ID',
avg_response_time: 'Average Response Time',
tps: 'Transactions Per Second',
plan_share_url: 'Whether the link jump is logged in'
},
load_test: {
id:'Load Test ID',
id: 'Load Test ID',
concurrency_thread_group: 'Concurrent read group',
thread_group: 'ThreadGroup',
completed_test_report: 'Completed test report',
@ -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!",
@ -1018,7 +1025,7 @@ export default {
LatencyChart: 'Latency',
BytesThroughputChart: 'Bytes',
Network: 'Network',
url:'URL'
url: 'URL'
},
add_monitor: 'Add Monitor',
monitor_item: 'Monitor',
@ -1063,7 +1070,7 @@ export default {
upload_limit_size: "Upload file size cannot exceed 30MB!",
upload_limit_size_warn: "Upload file size cannot exceed {0} MB!",
upload_limit: "Upload file size cannot exceed",
upload_tip:"Only upload JAR packages"
upload_tip: "Only upload JAR packages"
},
batch_menus: {
select_all_data: "Select all datas({0})",
@ -1079,7 +1086,7 @@ export default {
update_time: "Update time"
},
expect_detail: "Expect",
request_condition:"Request Condition",
request_condition: "Request Condition",
base_info: "Base info",
req_param: "Request params",
rsp_param: "Response Params",
@ -1096,22 +1103,22 @@ 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",
api_project:"Project",
api_project: "Project",
select_comp: {
no_data: "No Data",
add_data: "Add Data"
@ -1233,7 +1240,7 @@ export default {
none: "None"
},
},
case_reduction_error_text:"Please restore first"
case_reduction_error_text: "Please restore first"
},
automation: {
open_expansion: "One-click expansion",
@ -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_id:'Last result id',
passing_rate: "Passing rate",
last_result: "Last Result",
last_result_id: 'Last result id',
passing_rate: "Passing Rate",
success: "Success",
fail: "Fail",
saved: "Saved",
@ -1295,9 +1302,9 @@ export default {
select_table: "Select table",
select_all: "Select all",
check_case: "Please check the Scene Use Case",
api_none:"The original api does not exist",
case_none:"The original case does not exist",
num_none:"The original scene does not exist"
api_none: "The original api does not exist",
case_none: "The original case does not exist",
num_none: "The original scene does not exist"
},
report_name_info: 'Please enter the registration name',
save_case_info: 'Please save the use case first',
@ -1314,7 +1321,7 @@ export default {
generate_report: "Generate report",
},
environment: {
id:'Environment ID',
id: 'Environment ID',
create: 'Create environment',
name: "Environment Name",
socket: "Socket",
@ -1340,7 +1347,7 @@ export default {
copy_warning: "Domain names whose enabling conditions are'none' do not support copying!"
},
scenario: {
id:"Scenario ID",
id: "Scenario ID",
scenario: "Scenario",
dubbo: "Dubbo Config",
config: "Scenario Config",
@ -1423,54 +1430,54 @@ export default {
condition_variable: "Variable, e.g: ${var}",
wait: "wait",
all_name:"name",
all_url:"url",
boolean:"Boolean value",
capitalize:"Start with a capital letter",
cfirst:"Last name (Chinese)",
character:"character",
city:"city",
clast:"First name (Chinese)",
cname:"Chinese name",
color:"colour",
county:"county",
ctitle:"Chinese title",
dataImage:"Data image",
date:"date",
datetime:"Date time",
domain:"field",
email:"E-mail",
first:"surname",
float:"Floating point number",
guid:"guid",
hex:"hexadecimal",
hsl:"hsl",
id:"id",
img:"Picture address",
increment:"increment",
integer:"integer",
ip:"intellectual property right",
last:"name",
lower:"Lowercase letters",
natural:"Natural number",
now:"current time ",
paragraph:"paragraph",
pick:"choice",
protocol:"agreement",
province:"province",
range:"Range",
region:"region",
rgb:"rgb",
rgba:"rgba",
sentence:"sentence",
shuffle:"shuffle the cards",
string:"character string",
time:"time",
title:"title",
tld:"tld",
upper:"capital",
word:"Words",
zip:"Postal Code",
all_name: "name",
all_url: "url",
boolean: "Boolean value",
capitalize: "Start with a capital letter",
cfirst: "Last name (Chinese)",
character: "character",
city: "city",
clast: "First name (Chinese)",
cname: "Chinese name",
color: "colour",
county: "county",
ctitle: "Chinese title",
dataImage: "Data image",
date: "date",
datetime: "Date time",
domain: "field",
email: "E-mail",
first: "surname",
float: "Floating point number",
guid: "guid",
hex: "hexadecimal",
hsl: "hsl",
id: "id",
img: "Picture address",
increment: "increment",
integer: "integer",
ip: "intellectual property right",
last: "name",
lower: "Lowercase letters",
natural: "Natural number",
now: "current time ",
paragraph: "paragraph",
pick: "choice",
protocol: "agreement",
province: "province",
range: "Range",
region: "region",
rgb: "rgb",
rgba: "rgba",
sentence: "sentence",
shuffle: "shuffle the cards",
string: "character string",
time: "time",
title: "title",
tld: "tld",
upper: "capital",
word: "Words",
zip: "Postal Code",
assertions: {
label: "Assertion",
@ -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",
@ -1853,14 +1860,14 @@ export default {
performance_test_case: "Performance Case",
scenario_test_case: "Scenario Case",
report_statistics: "Report Statistics",
sort:'Sort',
other_test_name:'Other Test Name',
demand:{
id:'Demand ID',
name:'Demand Name'
sort: 'Sort',
other_test_name: 'Other Test Name',
demand: {
id: 'Demand ID',
name: 'Demand Name'
},
step_model:'Step Model',
automatic_status_update:"Automatic Status Update",
step_model: 'Step Model',
automatic_status_update: "Automatic Status Update",
case: {
list: "List",
minder: "Minder",
@ -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",
@ -1951,9 +1958,9 @@ export default {
attachment: "Attachment",
upload_time: "Upload Time",
total: "Total Case",
node_id:"Node ID",
node_path:"Node Path",
match_rule:"Test Case Match Rule",
node_id: "Node ID",
node_path: "Node Path",
match_rule: "Test Case Match Rule",
import: {
import: "Import test case",
case_import: "Import test case",
@ -1985,11 +1992,11 @@ export default {
export_tip: "Switch to Interface List and check Use Case Export"
},
case_desc: "Case Desc",
passing_rate:'Case Pass Rate'
passing_rate: 'Case Pass Rate'
},
plan: {
test_plan: "Plan",
test_plan_id:'Plan ID',
test_plan_id: 'Plan ID',
create_plan: "Create test plan",
edit_plan: "Edit test plan",
plan_name: "Name",
@ -2026,10 +2033,10 @@ export default {
api_case: "Api case",
scenario_case: "Scenario case",
execute_result: "Execute Result",
execute_time:'Execute Time',
is_api_case_executing:"Api Case Executing",
is_scenario_executing:'Scenario Executing',
is_performance_executing:'Performance Executing',
execute_time: 'Execute Time',
is_api_case_executing: "Api Case Executing",
is_scenario_executing: 'Scenario Executing',
is_performance_executing: 'Performance Executing',
test_plan_test_case_count: "Track case count",
test_plan_api_case_count: "Api case count",
test_plan_api_scenario_count: "Scenario case count",
@ -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!",
},
@ -2093,7 +2100,7 @@ export default {
cannot_delete: "Cannot delete this comment",
},
module: {
id:'Module ID',
id: 'Module ID',
search: "Search module",
rename: "Rename",
add_submodule: "Add submodule",
@ -2136,7 +2143,7 @@ export default {
relevance_test_case: "Relevance case",
cancel_all_relevance: "Unlink all",
executor: "Executor",
executor_match_rule:"Executor Match Rule",
executor_match_rule: "Executor Match Rule",
execute_result: "Result",
pass: "Pass",
not_pass: "UnPass",
@ -2161,7 +2168,7 @@ export default {
submit_issues: "Commit issues",
operate_step: "Operate step",
edit_component: "Edit component",
component:"component",
component: "component",
base_info: "Base info",
mock_info: "Mock service",
test_result: "Test result",
@ -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",
@ -2206,7 +2213,7 @@ export default {
description: "Issue Describe",
status: "Issue Status",
platform: "Platform",
issue_project:"Project",
issue_project: "Project",
operate: "Operate",
close: "Close",
delete: "Delete",
@ -2216,12 +2223,12 @@ export default {
status_new: 'new',
status_resolved: 'resolved',
status_closed: 'closed',
status_active:'active',
status_delete:'delete',
status_active: 'active',
status_delete: 'delete',
status_in_progress: 'in_progress',
status_rejected: 'rejected',
status_upcoming:'upcoming',
status_reopened:'reopened',
status_upcoming: 'upcoming',
status_reopened: 'reopened',
please_choose_current_owner: "Please choose current owner",
tapd_current_owner: "Tapd Current Owner",
zentao_bug_build: "Zentao Impact version",
@ -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",
@ -2267,11 +2275,11 @@ export default {
issue_list: "Issue List",
all_case: "All Case",
},
reporter:'Reporter',
lastmodify:'Last Modify'
reporter: 'Reporter',
lastmodify: 'Last Modify'
},
test_resource_pool: {
id:'Resource Pool ID',
id: 'Resource Pool ID',
type: 'type',
enable_disable: 'Enable / Disable',
search_by_name: 'Search by name',
@ -2364,7 +2372,7 @@ export default {
task_config: "Task Config",
test_name: 'Test Name',
running_rule: 'Rule',
job:'Job',
job: 'Job',
job_status: 'Status',
running_task: 'Running Task',
please_input_cron_expression: "Please Input Cron Expression",
@ -2497,7 +2505,7 @@ export default {
},
module: {
title: 'Module Management',
path:'Module Path'
path: 'Module Path'
},
table: {
header_display_field: 'Header display field',
@ -2534,7 +2542,7 @@ export default {
script_entry: "Script execution entry",
plugin_id: "Plug-in Id",
script_view: "View script",
warning_tip:'Script content not found'
warning_tip: 'Script content not found'
},
mail: {
input_mail_subject: "Please input subject",

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) : {};
}