This commit is contained in:
liqiang-fit2cloud 2022-12-27 12:04:52 +08:00
commit 848ec1780c
28 changed files with 464 additions and 154 deletions

View File

@ -1,21 +1,21 @@
package io.metersphere.listener; package io.metersphere.listener;
import io.metersphere.base.domain.ApiModule; import io.metersphere.base.domain.*;
import io.metersphere.base.domain.ModuleNode;
import io.metersphere.base.domain.Project;
import io.metersphere.base.mapper.ApiModuleMapper; import io.metersphere.base.mapper.ApiModuleMapper;
import io.metersphere.base.mapper.ApiScenarioModuleMapper;
import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.ext.BaseModuleNodeMapper; import io.metersphere.base.mapper.ext.BaseModuleNodeMapper;
import io.metersphere.commons.constants.KafkaTopicConstants; import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum; import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Component @Component
@ -28,6 +28,8 @@ public class ProjectCreatedListener {
@Resource @Resource
private ApiModuleMapper apiModuleMapper; private ApiModuleMapper apiModuleMapper;
@Resource @Resource
private ApiScenarioModuleMapper apiScenarioModuleMapper;
@Resource
private ProjectMapper projectMapper; private ProjectMapper projectMapper;
@ -52,7 +54,12 @@ public class ProjectCreatedListener {
record.setUpdateTime(System.currentTimeMillis()); record.setUpdateTime(System.currentTimeMillis());
record.setProjectId(projectId); record.setProjectId(projectId);
record.setName(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName()); record.setName(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName());
baseModuleNodeMapper.insert(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getTableName(), record); ApiScenarioModuleExample scenarioModuleExample = new ApiScenarioModuleExample();
scenarioModuleExample.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
List<ApiScenarioModule> scenarioModules = apiScenarioModuleMapper.selectByExample(scenarioModuleExample);
if (CollectionUtils.isEmpty(scenarioModules)) {
baseModuleNodeMapper.insert(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getTableName(), record);
}
ApiModule apiRecord = new ApiModule(); ApiModule apiRecord = new ApiModule();
BeanUtils.copyBean(apiRecord, record); BeanUtils.copyBean(apiRecord, record);
apiRecord.setName(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName()); apiRecord.setName(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName());
@ -60,7 +67,12 @@ public class ProjectCreatedListener {
for (String protocol : protocols) { for (String protocol : protocols) {
apiRecord.setProtocol(protocol); apiRecord.setProtocol(protocol);
apiRecord.setId(UUID.randomUUID().toString()); apiRecord.setId(UUID.randomUUID().toString());
apiModuleMapper.insert(apiRecord); ApiModuleExample apiExample = new ApiModuleExample();
apiExample.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
List<ApiModule> apiList = apiModuleMapper.selectByExample(apiExample);
if (CollectionUtils.isEmpty(apiList)) {
apiModuleMapper.insert(apiRecord);
}
} }
} }
} }

View File

@ -9,6 +9,7 @@ import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiModuleMapper; import io.metersphere.base.mapper.ApiModuleMapper;
import io.metersphere.base.mapper.ApiTestCaseMapper; import io.metersphere.base.mapper.ApiTestCaseMapper;
import io.metersphere.commons.constants.NoticeConstants; import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum;
import io.metersphere.commons.constants.PropertyConstant; import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.enums.ApiTestDataStatus; import io.metersphere.commons.enums.ApiTestDataStatus;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
@ -355,7 +356,7 @@ public class ApiDefinitionImportUtil {
public static void setModule(ApiDefinitionWithBLOBs item, ApiModuleMapper apiModuleMapper) { public static void setModule(ApiDefinitionWithBLOBs item, ApiModuleMapper apiModuleMapper) {
if (item != null && StringUtils.isEmpty(item.getModuleId()) || "default-module".equals(item.getModuleId())) { if (item != null && StringUtils.isEmpty(item.getModuleId()) || "default-module".equals(item.getModuleId())) {
ApiModuleExample example = new ApiModuleExample(); ApiModuleExample example = new ApiModuleExample();
example.createCriteria().andProjectIdEqualTo(item.getProjectId()).andProtocolEqualTo(item.getProtocol()).andNameEqualTo("未规划接口"); example.createCriteria().andProjectIdEqualTo(item.getProjectId()).andProtocolEqualTo(item.getProtocol()).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName());
List<ApiModule> modules = apiModuleMapper.selectByExample(example); List<ApiModule> modules = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(modules)) { if (CollectionUtils.isNotEmpty(modules)) {
item.setModuleId(modules.get(0).getId()); item.setModuleId(modules.get(0).getId());

View File

@ -1003,7 +1003,7 @@ public class ApiDefinitionService {
private void setModule(ApiDefinitionWithBLOBs item) { private void setModule(ApiDefinitionWithBLOBs item) {
if (item != null && StringUtils.isEmpty(item.getModuleId()) || "default-module".equals(item.getModuleId())) { if (item != null && StringUtils.isEmpty(item.getModuleId()) || "default-module".equals(item.getModuleId())) {
ApiModuleExample example = new ApiModuleExample(); ApiModuleExample example = new ApiModuleExample();
example.createCriteria().andProjectIdEqualTo(item.getProjectId()).andProtocolEqualTo(item.getProtocol()).andNameEqualTo("未规划接口"); example.createCriteria().andProjectIdEqualTo(item.getProjectId()).andProtocolEqualTo(item.getProtocol()).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName());
List<ApiModule> modules = apiModuleMapper.selectByExample(example); List<ApiModule> modules = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(modules)) { if (CollectionUtils.isNotEmpty(modules)) {
item.setModuleId(modules.get(0).getId()); item.setModuleId(modules.get(0).getId());
@ -1840,7 +1840,7 @@ public class ApiDefinitionService {
public void initModulePathAndId(String projectId, ApiDefinitionWithBLOBs test) { public void initModulePathAndId(String projectId, ApiDefinitionWithBLOBs test) {
ApiModuleExample example = new ApiModuleExample(); ApiModuleExample example = new ApiModuleExample();
example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(test.getProtocol()).andNameEqualTo("未规划接口").andLevelEqualTo(1); example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(test.getProtocol()).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName()).andLevelEqualTo(1);
List<ApiModule> modules = apiModuleMapper.selectByExample(example); List<ApiModule> modules = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(modules)) { if (CollectionUtils.isNotEmpty(modules)) {
test.setModuleId(modules.get(0).getId()); test.setModuleId(modules.get(0).getId());

View File

@ -1,24 +1,15 @@
package io.metersphere.service.definition; package io.metersphere.service.definition;
import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionRequest; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.ApiModuleDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.DragModuleRequest;
import io.metersphere.api.dto.definition.UpdateApiModuleDTO;
import io.metersphere.api.parse.api.ApiDefinitionImport; import io.metersphere.api.parse.api.ApiDefinitionImport;
import io.metersphere.base.domain.ApiDefinition; import io.metersphere.base.domain.*;
import io.metersphere.base.domain.ApiDefinitionExample;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.domain.ApiModule;
import io.metersphere.base.domain.ApiModuleExample;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.mapper.ApiDefinitionMapper; import io.metersphere.base.mapper.ApiDefinitionMapper;
import io.metersphere.base.mapper.ApiModuleMapper; import io.metersphere.base.mapper.ApiModuleMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionMapper; import io.metersphere.base.mapper.ext.ExtApiDefinitionMapper;
import io.metersphere.base.mapper.ext.ExtApiModuleMapper; import io.metersphere.base.mapper.ext.ExtApiModuleMapper;
import io.metersphere.base.mapper.ext.ExtApiTestCaseMapper; import io.metersphere.base.mapper.ext.ExtApiTestCaseMapper;
import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum;
import io.metersphere.commons.constants.PropertyConstant; import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.enums.ApiTestDataStatus; import io.metersphere.commons.enums.ApiTestDataStatus;
@ -40,19 +31,12 @@ import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.SqlSessionUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -204,7 +188,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
return getNodeTrees(apiModules); return getNodeTrees(apiModules);
} }
public List<ApiModuleDTO> getNodeTreeByCondition(String projectId, String protocol, String versionId, ApiDefinitionRequest request ) { public List<ApiModuleDTO> getNodeTreeByCondition(String projectId, String protocol, String versionId, ApiDefinitionRequest request) {
// 判断当前项目下是否有默认模块没有添加默认模块 // 判断当前项目下是否有默认模块没有添加默认模块
this.getDefaultNode(projectId, protocol); this.getDefaultNode(projectId, protocol);
List<ApiModuleDTO> apiModules = getApiModulesByProjectAndPro(projectId, protocol); List<ApiModuleDTO> apiModules = getApiModulesByProjectAndPro(projectId, protocol);
@ -588,32 +572,33 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
public ApiModule getDefaultNode(String projectId, String protocol) { public ApiModule getDefaultNode(String projectId, String protocol) {
ApiModuleExample example = new ApiModuleExample(); ApiModuleExample example = new ApiModuleExample();
example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol).andNameEqualTo("未规划接口").andParentIdIsNull(); example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
List<ApiModule> list = apiModuleMapper.selectByExample(example); List<ApiModule> list = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
ApiModule record = new ApiModule(); return saveDefault(projectId, protocol);
record.setId(UUID.randomUUID().toString());
record.setName("未规划接口");
record.setProtocol(protocol);
record.setPos(1.0);
record.setLevel(1);
record.setCreateTime(System.currentTimeMillis());
record.setUpdateTime(System.currentTimeMillis());
record.setProjectId(projectId);
record.setCreateUser(SessionUtils.getUserId());
apiModuleMapper.insert(record);
return record;
} else { } else {
return list.get(0); return list.get(0);
} }
} }
public ApiModule getDefaultNodeUnCreateNew(String projectId, String protocol) { @Async
public synchronized ApiModule saveDefault(String projectId, String protocol) {
ApiModuleExample example = new ApiModuleExample(); ApiModuleExample example = new ApiModuleExample();
example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol).andNameEqualTo("未规划接口").andParentIdIsNull(); example.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
List<ApiModule> list = apiModuleMapper.selectByExample(example); List<ApiModule> list = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
return null; ApiModule module = new ApiModule();
module.setId(UUID.randomUUID().toString());
module.setName(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName());
module.setProtocol(protocol);
module.setPos(1.0);
module.setLevel(1);
module.setCreateTime(System.currentTimeMillis());
module.setUpdateTime(System.currentTimeMillis());
module.setProjectId(projectId);
module.setCreateUser(SessionUtils.getUserId());
apiModuleMapper.insert(module);
return module;
} else { } else {
return list.get(0); return list.get(0);
} }
@ -666,7 +651,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
if (protocol.equals("HTTP")) { if (protocol.equals("HTTP")) {
return dealHttp(data, pidChildrenMap, idPathMap, idModuleMap, request, fullCoverage, urlRepeat, importCases); return dealHttp(data, pidChildrenMap, idPathMap, idModuleMap, request, fullCoverage, urlRepeat, importCases);
} else { } else {
return delOtherProtocol(data, pidChildrenMap, idPathMap, idModuleMap, request, fullCoverage, importCases); return delOtherProtocol(data, pidChildrenMap, idPathMap, idModuleMap, request, fullCoverage, importCases);
} }
} }
@ -693,7 +678,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
//处理模块 //处理模块
setModule(moduleMap, pidChildrenMap, idPathMap, idModuleMap, optionData, chooseModule); setModule(moduleMap, pidChildrenMap, idPathMap, idModuleMap, optionData, chooseModule);
return getUpdateApiModuleDTO(chooseModule,idPathMap,optionData,fullCoverage, moduleMap,optionDataCases); return getUpdateApiModuleDTO(chooseModule, idPathMap, optionData, fullCoverage, moduleMap, optionDataCases);
} }
private UpdateApiModuleDTO dealHttp(List<ApiDefinitionWithBLOBs> data, private UpdateApiModuleDTO dealHttp(List<ApiDefinitionWithBLOBs> data,
@ -722,8 +707,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
setModule(moduleMap, pidChildrenMap, idPathMap, idModuleMap, optionData, chooseModule); setModule(moduleMap, pidChildrenMap, idPathMap, idModuleMap, optionData, chooseModule);
return getUpdateApiModuleDTO(chooseModule, idPathMap, optionData, fullCoverage, moduleMap, optionDataCases);
return getUpdateApiModuleDTO(chooseModule,idPathMap,optionData,fullCoverage, moduleMap,optionDataCases);
} }
@ -752,7 +736,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
} }
} }
private UpdateApiModuleDTO getUpdateApiModuleDTO(ApiModuleDTO chooseModule,Map<String, String> idPathMap,List<ApiDefinitionWithBLOBs> optionData,Boolean fullCoverage,Map<String, ApiModule> moduleMap, List<ApiTestCaseWithBLOBs> optionDataCases) { private UpdateApiModuleDTO getUpdateApiModuleDTO(ApiModuleDTO chooseModule, Map<String, String> idPathMap, List<ApiDefinitionWithBLOBs> optionData, Boolean fullCoverage, Map<String, ApiModule> moduleMap, List<ApiTestCaseWithBLOBs> optionDataCases) {
UpdateApiModuleDTO updateApiModuleDTO = new UpdateApiModuleDTO(); UpdateApiModuleDTO updateApiModuleDTO = new UpdateApiModuleDTO();
updateApiModuleDTO.setChooseModule(chooseModule); updateApiModuleDTO.setChooseModule(chooseModule);
updateApiModuleDTO.setIdPathMap(idPathMap); updateApiModuleDTO.setIdPathMap(idPathMap);
@ -822,7 +806,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
//导入时即没选中模块接口自身也没模块的直接返会当前项目当前协议下的默认模块 //导入时即没选中模块接口自身也没模块的直接返会当前项目当前协议下的默认模块
List<ApiModule> moduleList = pidChildrenMap.get(PropertyConstant.ROOT); List<ApiModule> moduleList = pidChildrenMap.get(PropertyConstant.ROOT);
for (ApiModule module : moduleList) { for (ApiModule module : moduleList) {
if (module.getName().equals("未规划接口")) { if (module.getName().equals(ProjectModuleDefaultNodeEnum.API_MODULE_DEFAULT_NODE.getNodeName())) {
datum.setModuleId(module.getId()); datum.setModuleId(module.getId());
datum.setModulePath("/" + module.getName()); datum.setModulePath("/" + module.getName());
} }
@ -846,6 +830,7 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
datum.setModulePath(idPathMap.get(chooseModule.getId())); datum.setModulePath(idPathMap.get(chooseModule.getId()));
} }
} }
public String[] getPathTree(String modulePath) { public String[] getPathTree(String modulePath) {
String substring = modulePath.substring(0, 1); String substring = modulePath.substring(0, 1);
if (substring.equals("/")) { if (substring.equals("/")) {

View File

@ -339,11 +339,19 @@ public class TestPlanScenarioCaseService {
if (envMap != null && !envMap.isEmpty()) { if (envMap != null && !envMap.isEmpty()) {
env = JSON.toJSONString(envMap); env = JSON.toJSONString(envMap);
} }
} else {
Map<String, String> existMap = JSON.parseObject(env, Map.class);
if (existMap.isEmpty()) {
if (envMap != null && !envMap.isEmpty()) {
env = JSON.toJSONString(envMap);
}
}
} }
Map<String, String> map = JSON.parseObject(env, Map.class); Map<String, String> map = JSON.parseObject(env, Map.class);
if (map.isEmpty()) { if (map.isEmpty()) {
continue; continue;
} }
Set<String> set = map.keySet(); Set<String> set = map.keySet();
for (String s : set) { for (String s : set) {
if (StringUtils.isNotBlank(envMap.get(s))) { if (StringUtils.isNotBlank(envMap.get(s))) {

View File

@ -11,6 +11,7 @@ import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.ApiScenarioModuleMapper; import io.metersphere.base.mapper.ApiScenarioModuleMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioModuleMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioModuleMapper;
import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum;
import io.metersphere.commons.constants.PropertyConstant; import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.enums.ApiTestDataStatus; import io.metersphere.commons.enums.ApiTestDataStatus;
@ -29,6 +30,7 @@ import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.SqlSessionUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -157,7 +159,7 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
//回收站数据初始化被删除了的数据挂在默认模块上 //回收站数据初始化被删除了的数据挂在默认模块上
initTrashDataModule(projectId); initTrashDataModule(projectId);
//通过回收站里的接口模块进行反显 //通过回收站里的接口模块进行反显
if(request.getFilters() != null && request.getFilters().get("status") != null){ if (request.getFilters() != null && request.getFilters().get("status") != null) {
List<String> statusList = new ArrayList<>(); List<String> statusList = new ArrayList<>();
statusList.add(ApiTestDataStatus.TRASH.getValue()); statusList.add(ApiTestDataStatus.TRASH.getValue());
request.getFilters().put("status", statusList); request.getFilters().put("status", statusList);
@ -507,20 +509,32 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
public ApiScenarioModule getDefaultNode(String projectId) { public ApiScenarioModule getDefaultNode(String projectId) {
ApiScenarioModuleExample example = new ApiScenarioModuleExample(); ApiScenarioModuleExample example = new ApiScenarioModuleExample();
example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo("未规划场景").andParentIdIsNull(); example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
List<ApiScenarioModule> list = apiScenarioModuleMapper.selectByExample(example); List<ApiScenarioModule> list = apiScenarioModuleMapper.selectByExample(example);
if (CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
ApiScenarioModule record = new ApiScenarioModule(); return saveDefault(projectId);
record.setId(UUID.randomUUID().toString()); } else {
record.setName("未规划场景"); return list.get(0);
record.setPos(1.0); }
record.setLevel(1); }
record.setCreateTime(System.currentTimeMillis());
record.setUpdateTime(System.currentTimeMillis()); @Async
record.setProjectId(projectId); public synchronized ApiScenarioModule saveDefault(String projectId) {
record.setCreateUser(SessionUtils.getUserId()); ApiScenarioModuleExample example = new ApiScenarioModuleExample();
apiScenarioModuleMapper.insert(record); example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName()).andParentIdIsNull();
return record; List<ApiScenarioModule> list = apiScenarioModuleMapper.selectByExample(example);
if (CollectionUtils.isEmpty(list)) {
ApiScenarioModule module = new ApiScenarioModule();
module.setId(UUID.randomUUID().toString());
module.setName(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName());
module.setPos(1.0);
module.setLevel(1);
module.setCreateTime(System.currentTimeMillis());
module.setUpdateTime(System.currentTimeMillis());
module.setProjectId(projectId);
module.setCreateUser(SessionUtils.getUserId());
apiScenarioModuleMapper.insert(module);
return module;
} else { } else {
return list.get(0); return list.get(0);
} }
@ -825,7 +839,7 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
//导入时即没选中模块接口自身也没模块的直接返会当前项目当前协议下的默认模块 //导入时即没选中模块接口自身也没模块的直接返会当前项目当前协议下的默认模块
List<ApiScenarioModule> moduleList = pidChildrenMap.get(PropertyConstant.ROOT); List<ApiScenarioModule> moduleList = pidChildrenMap.get(PropertyConstant.ROOT);
for (ApiScenarioModule module : moduleList) { for (ApiScenarioModule module : moduleList) {
if (module.getName().equals("未规划场景")) { if (module.getName().equals(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName())) {
datum.setApiScenarioModuleId(module.getId()); datum.setApiScenarioModuleId(module.getId());
datum.setModulePath("/" + module.getName()); datum.setModulePath("/" + module.getName());
} }

View File

@ -1478,7 +1478,7 @@ public class ApiScenarioService {
private void replenishScenarioModuleIdPath(String request, ApiScenarioModuleMapper apiScenarioModuleMapper, ApiScenarioWithBLOBs item) { private void replenishScenarioModuleIdPath(String request, ApiScenarioModuleMapper apiScenarioModuleMapper, ApiScenarioWithBLOBs item) {
ApiScenarioModuleExample example = new ApiScenarioModuleExample(); ApiScenarioModuleExample example = new ApiScenarioModuleExample();
example.createCriteria().andProjectIdEqualTo(request).andNameEqualTo("未规划场景"); example.createCriteria().andProjectIdEqualTo(request).andNameEqualTo(ProjectModuleDefaultNodeEnum.API_SCENARIO_DEFAULT_NODE.getNodeName());
List<ApiScenarioModule> modules = apiScenarioModuleMapper.selectByExample(example); List<ApiScenarioModule> modules = apiScenarioModuleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(modules)) { if (CollectionUtils.isNotEmpty(modules)) {
item.setApiScenarioModuleId(modules.get(0).getId()); item.setApiScenarioModuleId(modules.get(0).getId());

View File

@ -78,6 +78,7 @@ export default {
jmxObj.attachFiles = jmxInfo.attachFiles; jmxObj.attachFiles = jmxInfo.attachFiles;
jmxObj.attachByteFiles = jmxInfo.attachByteFiles; jmxObj.attachByteFiles = jmxInfo.attachByteFiles;
jmxObj.scenarioId = row.id; jmxObj.scenarioId = row.id;
jmxObj.caseId = null;
jmxObj.version = row.version; jmxObj.version = row.version;
jmxObj.projectEnvMap = projectEnvMap; jmxObj.projectEnvMap = projectEnvMap;
performanceStore.$patch({ test: { name: row.name, jmx: jmxObj } }); performanceStore.$patch({ test: { name: row.name, jmx: jmxObj } });

View File

@ -545,8 +545,10 @@ export default {
if (this.httpForm.tags instanceof Array) { if (this.httpForm.tags instanceof Array) {
this.httpForm.tags = JSON.stringify(this.httpForm.tags); this.httpForm.tags = JSON.stringify(this.httpForm.tags);
} }
if (this.beforeHttpForm.tags instanceof Array) { if (this.beforeHttpForm) {
this.beforeHttpForm.tags = JSON.stringify(this.beforeHttpForm.tags); if (this.beforeHttpForm.tags instanceof Array) {
this.beforeHttpForm.tags = JSON.stringify(this.beforeHttpForm.tags);
}
} }
}, },
saveApi() { saveApi() {
@ -808,11 +810,11 @@ export default {
} else { } else {
this.versionData = response.data; this.versionData = response.data;
} }
if (this.versionData[0]) {
this.beforeHttpForm = this.versionData[0]; this.beforeHttpForm = this.versionData[0];
this.beforeRequest = JSON.parse(this.versionData[0].request); this.beforeRequest = JSON.parse(this.versionData[0].request);
this.beforeResponse = JSON.parse(this.versionData[0].response); this.beforeResponse = JSON.parse(this.versionData[0].response);
}
let latestVersionData = response.data.filter((v) => v.versionId === this.latestVersionId); let latestVersionData = response.data.filter((v) => v.versionId === this.latestVersionId);
if (latestVersionData.length > 0) { if (latestVersionData.length > 0) {
this.hasLatest = false this.hasLatest = false

View File

@ -1433,6 +1433,7 @@ export default {
jmxObj.attachFiles = jmxInfo.attachFiles; jmxObj.attachFiles = jmxInfo.attachFiles;
jmxObj.attachByteFiles = jmxInfo.attachByteFiles; jmxObj.attachByteFiles = jmxInfo.attachByteFiles;
jmxObj.caseId = reqObj.id; jmxObj.caseId = reqObj.id;
jmxObj.scenarioId = null;
jmxObj.version = row.version; jmxObj.version = row.version;
jmxObj.envId = environment.id; jmxObj.envId = environment.id;
jmxObj.projectEnvMap = projectEnvMap; jmxObj.projectEnvMap = projectEnvMap;
@ -1442,7 +1443,7 @@ export default {
}); });
} }
}) })
.catch((error) => { .catch(() => {
this.$emit('runRefresh', {}); this.$emit('runRefresh', {});
}); });
}, },

View File

@ -36,7 +36,11 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson-starter.version}</version>
</dependency>
</dependencies> </dependencies>

View File

@ -9,4 +9,5 @@ eureka.dashboard.enabled=true
# default eureka # default eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
logging.file.path=/opt/metersphere/logs/${spring.application.name} logging.file.path=/opt/metersphere/logs/${spring.application.name}
spring.redis.redisson.file=file:/opt/metersphere/conf/redisson.yml

View File

@ -45,11 +45,57 @@
</el-input> </el-input>
</div> </div>
<p>{{ $t('api_test.request.headers') }}</p> <!-- 接口测试配置 -->
<el-row> <form-section :title="$t('commons.api')" :init-active=true>
<el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{ $t("commons.batch_add") }}</el-link> <p>{{ $t('api_test.request.headers') }}</p>
</el-row> <el-row>
<ms-api-key-value :items="condition.headers" :isShowEnable="true" :suggestions="headerSuggestions"/> <el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{
$t("commons.batch_add")
}}
</el-link>
</el-row>
<ms-api-key-value :items="condition.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
</form-section>
<!-- UI 配置 -->
<form-section :title="$t('commons.ui_test')" :init-active=false v-if="condition.type !== 'MODULE'">
<el-row :gutter="10" style="padding-top: 10px;">
<el-col :span="6">
<!-- 浏览器驱动 -->
<span style="margin-right: 10px;">{{ $t("ui.browser") }}</span>
<el-select
size="mini"
v-model="httpConfig.browser"
style="width: 100px"
>
<el-option
v-for="b in browsers"
:key="b.value"
:value="b.value"
:label="b.label"
></el-option>
</el-select>
</el-col>
<el-col :span="6">
<!-- 性能模式 -->
<el-checkbox
v-model="httpConfig.headlessEnabled"
>
<span> {{ $t("ui.performance_mode") }}</span>
</el-checkbox>
<ms-instructions-icon size="10" :content="$t('ui.per_tip')"/>
</el-col>
</el-row>
<!-- 当前版本实现免登录是基于 cookie 的但是现在由于安全性问题绝大多数网站都不支持 cookie登录所以先屏蔽了-->
<!-- <el-row :gutter="10">-->
<!-- <el-col :span="24">-->
<!-- <ms-ui-scenario-cookie-table :items="httpConfig.cookie" ref="cookieTable"/>-->
<!-- </el-col>-->
<!-- </el-row>-->
</form-section>
<div style="margin-top: 20px"> <div style="margin-top: 20px">
<el-button v-if="!condition.id" type="primary" style="float: right" size="mini" @click="add"> <el-button v-if="!condition.id" type="primary" style="float: right" size="mini" @click="add">
{{ $t('commons.add') }} {{ $t('commons.add') }}
@ -121,10 +167,12 @@ import {getUUID} from "../../utils";
import {KeyValue} from "../../model/EnvTestModel"; import {KeyValue} from "../../model/EnvTestModel";
import Vue from "vue"; import Vue from "vue";
import BatchAddParameter from "./commons/BatchAddParameter"; import BatchAddParameter from "./commons/BatchAddParameter";
import FormSection from "metersphere-frontend/src/components/form/FormSection";
import MsInstructionsIcon from 'metersphere-frontend/src/components/MsInstructionsIcon';
export default { export default {
name: "MsEnvironmentHttpConfig", name: "MsEnvironmentHttpConfig",
components: {MsApiKeyValue, MsSelectTree, MsTableOperatorButton, BatchAddParameter}, components: {MsApiKeyValue, MsSelectTree, MsTableOperatorButton, BatchAddParameter, FormSection, MsInstructionsIcon},
props: { props: {
httpConfig: new HttpConfig(), httpConfig: new HttpConfig(),
projectId: String, projectId: String,
@ -165,9 +213,21 @@ export default {
socket: "", socket: "",
domain: "", domain: "",
port: 0, port: 0,
headers: [new KeyValue()] headers: [new KeyValue()],
headlessEnabled: true,
browser: 'CHROME'
}, },
beforeCondition: {} beforeCondition: {},
browsers: [
{
label: this.$t("chrome"),
value: "CHROME",
},
{
label: this.$t("firefox"),
value: "FIREFOX",
},
],
}; };
}, },
watch: { watch: {
@ -289,7 +349,7 @@ export default {
list() { list() {
if (this.projectId) { if (this.projectId) {
this.result = getApiModuleByProjectIdAndProtocol(this.projectId, "HTTP").then((response) => { this.result = getApiModuleByProjectIdAndProtocol(this.projectId, "HTTP").then((response) => {
if (response.data && response.data !== null) { if (response.data && response.data !== null) {
this.moduleOptions = response.data; this.moduleOptions = response.data;
} }
}); });

View File

@ -18,12 +18,18 @@
:content="$t('commons.import')" :content="$t('commons.import')"
@click="importJSON" @click="importJSON"
/> />
<ms-table-button <el-dropdown @command="handleExportCommand" class="scenario-ext-btn" trigger="hover"
v-permission="['PROJECT_ENVIRONMENT:READ+EXPORT']" v-permission="['PROJECT_ENVIRONMENT:READ+EXPORT']">
icon="el-icon-box" <ms-table-button
:content="$t('commons.export')" style="margin-left: 10px"
@click="exportJSON" icon="el-icon-box"
/> :content="$t('commons.export')"
/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="exportApi">{{ $t('envrionment.export_variable_tip') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-link <el-link
style="margin-left: 10px" style="margin-left: 10px"
@click="batchAdd" @click="batchAdd"
@ -58,6 +64,31 @@
> >
<ms-table-column prop="num" sortable label="ID" min-width="60"> <ms-table-column prop="num" sortable label="ID" min-width="60">
</ms-table-column> </ms-table-column>
<ms-table-column
prop="scope"
sortable
:label="$t('commons.scope')"
:filters="scopeTypeFilters"
:filter-method="filterScope"
min-width="120">
<template slot-scope="scope">
<el-select
v-model="scope.row.scope"
:placeholder="$t('commons.please_select')"
size="mini"
@change="changeType(scope.row)"
>
<el-option
v-for="item in scopeTypeFilters"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</template>
</ms-table-column>
<ms-table-column <ms-table-column
prop="name" prop="name"
:label="$t('api_test.variable_name')" :label="$t('api_test.variable_name')"
@ -84,9 +115,9 @@
<template slot-scope="scope"> <template slot-scope="scope">
<el-select <el-select
v-model="scope.row.type" v-model="scope.row.type"
v-if="!scope.row.scope || scope.row.scope == 'api'"
:placeholder="$t('commons.please_select')" :placeholder="$t('commons.please_select')"
size="mini" size="mini"
@change="changeType(scope.row)"
> >
<el-option <el-option
v-for="item in typeSelectOptions" v-for="item in typeSelectOptions"
@ -95,6 +126,20 @@
:value="item.value" :value="item.value"
/> />
</el-select> </el-select>
<el-select
v-else
v-model="scope.row.type"
:placeholder="$t('commons.please_select')"
size="mini"
>
<el-option
v-for="item in uiTypeSelectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template> </template>
</ms-table-column> </ms-table-column>
@ -128,14 +173,14 @@
sortable sortable
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.description" size="mini" /> <el-input v-model="scope.row.description" size="mini"/>
</template> </template>
</ms-table-column> </ms-table-column>
<ms-table-column :label="$t('commons.operating')" width="150"> <ms-table-column :label="$t('commons.operating')" width="150">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span> <span>
<el-switch v-model="scope.row.enable" size="mini" /> <el-switch v-model="scope.row.enable" size="mini"/>
<el-tooltip <el-tooltip
effect="dark" effect="dark"
:content="$t('commons.remove')" :content="$t('commons.remove')"
@ -171,7 +216,7 @@
</ms-table-column> </ms-table-column>
</ms-table> </ms-table>
</div> </div>
<batch-add-parameter @batchSave="batchSave" ref="batchAdd" /> <batch-add-parameter @batchSave="batchSave" ref="batchAdd"/>
<api-variable-setting ref="apiVariableSetting"></api-variable-setting> <api-variable-setting ref="apiVariableSetting"></api-variable-setting>
<variable-import <variable-import
ref="variableImport" ref="variableImport"
@ -181,7 +226,7 @@
</template> </template>
<script> <script>
import { KeyValue } from "../../../model/EnvTestModel"; import {KeyValue} from "../../../model/EnvTestModel";
import MsApiVariableInput from "./ApiVariableInput"; import MsApiVariableInput from "./ApiVariableInput";
import BatchAddParameter from "./BatchAddParameter"; import BatchAddParameter from "./BatchAddParameter";
import MsTableButton from "../../MsTableButton"; import MsTableButton from "../../MsTableButton";
@ -189,8 +234,9 @@ import MsTable from "../../table/MsTable";
import MsTableColumn from "../../table/MsTableColumn"; import MsTableColumn from "../../table/MsTableColumn";
import ApiVariableSetting from "./ApiVariableSetting"; import ApiVariableSetting from "./ApiVariableSetting";
import CsvFileUpload from "./variable/CsvFileUpload"; import CsvFileUpload from "./variable/CsvFileUpload";
import { downloadFile, getUUID, operationConfirm } from "../../../utils"; import {downloadFile, getUUID, operationConfirm} from "../../../utils";
import VariableImport from "./variable/VariableImport"; import VariableImport from "./variable/VariableImport";
import _ from "lodash";
export default { export default {
name: "MsApiScenarioVariables", name: "MsApiScenarioVariables",
@ -230,15 +276,25 @@ export default {
}, },
], ],
typeSelectOptions: [ typeSelectOptions: [
{ value: "CONSTANT", label: this.$t("api_test.automation.constant") }, {value: "CONSTANT", label: this.$t("api_test.automation.constant")},
{ value: "LIST", label: this.$t("test_track.case.list") }, {value: "LIST", label: this.$t("test_track.case.list")},
{ value: "CSV", label: "CSV" }, {value: "CSV", label: "CSV"},
{ value: "COUNTER", label: this.$t("api_test.automation.counter") }, {value: "COUNTER", label: this.$t("api_test.automation.counter")},
{ value: "RANDOM", label: this.$t("api_test.automation.random") }, {value: "RANDOM", label: this.$t("api_test.automation.random")},
],
uiTypeSelectOptions: [
{value: "STRING", label: this.$t("api_test.automation.string")},
{value: "ARRAY", label: this.$t("api_test.automation.array")},
{value: "JSON", label: this.$t("api_test.automation.json")},
{value: "NUMBER", label: this.$t("api_test.automation.number")},
], ],
variables: {}, variables: {},
selectVariable: "", selectVariable: "",
editData: {}, editData: {},
scopeTypeFilters: [
{text: this.$t("commons.api"), value: "api"},
{text: this.$t("commons.ui_test"), value: "ui"},
]
}; };
}, },
watch: { watch: {
@ -279,15 +335,15 @@ export default {
if (repeatKey !== "") { if (repeatKey !== "") {
this.$warning( this.$warning(
this.$t("api_test.environment.common_config") + this.$t("api_test.environment.common_config") +
"【" + "【" +
repeatKey + repeatKey +
"】" + "】" +
this.$t("load_test.param_is_duplicate") this.$t("load_test.param_is_duplicate")
); );
} }
if (isNeedCreate) { if (isNeedCreate) {
this.variables.push( this.variables.push(
new KeyValue({ enable: true, id: getUUID(), type: "CONSTANT" }) new KeyValue({enable: true, id: getUUID(), type: "CONSTANT", scope: "api"})
); );
} }
this.$emit("change", this.variables); this.$emit("change", this.variables);
@ -304,6 +360,10 @@ export default {
data.files = []; data.files = [];
data.quotedData = "false"; data.quotedData = "false";
} }
if (!data.scope || data.scope == "ui") {
data.type = 'STRING';
}
}, },
valueText(data) { valueText(data) {
switch (data.type) { switch (data.type) {
@ -320,11 +380,11 @@ export default {
}, },
querySearch(queryString, cb) { querySearch(queryString, cb) {
let restaurants = [ let restaurants = [
{ value: "UTF-8" }, {value: "UTF-8"},
{ value: "UTF-16" }, {value: "UTF-16"},
{ value: "GB2312" }, {value: "GB2312"},
{ value: "ISO-8859-15" }, {value: "ISO-8859-15"},
{ value: "US-ASCll" }, {value: "US-ASCll"},
]; ];
let results = queryString let results = queryString
? restaurants.filter(this.createFilter(queryString)) ? restaurants.filter(this.createFilter(queryString))
@ -346,6 +406,9 @@ export default {
this.$set(item, "description", item.remark); this.$set(item, "description", item.remark);
item.remark = undefined; item.remark = undefined;
} }
if (!item.scope) {
this.$set(item, "scope", "api");
}
index++; index++;
}); });
}, },
@ -369,7 +432,7 @@ export default {
} }
); );
}, },
filter() { filter(scope) {
let datas = []; let datas = [];
this.variables.forEach((item) => { this.variables.forEach((item) => {
if (this.selectVariable && this.selectVariable != "" && item.name) { if (this.selectVariable && this.selectVariable != "" && item.name) {
@ -389,6 +452,12 @@ export default {
}); });
this.variables = datas; this.variables = datas;
}, },
filterScope(value, row) {
if (value == "ui") {
return row.scope == "ui";
}
return !row.scope || row.scope == "api";
},
openSetting(data) { openSetting(data) {
this.$refs.apiVariableSetting.open(data); this.$refs.apiVariableSetting.open(data);
}, },
@ -449,8 +518,15 @@ export default {
this.sortParameters(); this.sortParameters();
}, },
exportJSON() { exportJSON() {
if (this.$refs.variableTable.selectIds.length < 1) { let apiVariable = [];
this.$warning(this.$t("api_test.environment.select_variable")); this.$refs.variableTable.selectRows.forEach((r) => {
if (!r.scope || r.scope != "ui") {
apiVariable.push(r);
}
});
if (apiVariable.length < 1) {
this.$warning(this.$t("api_test.environment.select_api_variable"));
return; return;
} }
let variablesJson = []; let variablesJson = [];
@ -460,7 +536,7 @@ export default {
if (row.type === "CSV") { if (row.type === "CSV") {
messages = this.$t("variables.csv_download"); messages = this.$t("variables.csv_download");
} }
if (row.name) { if (row.name && (!row.scope || row.scope == "api")) {
variablesJson.push(row); variablesJson.push(row);
} }
}); });
@ -493,10 +569,21 @@ export default {
} }
}); });
}, },
handleExportCommand(command){
this.exportJSON();
}
}, },
created() { created() {
if (this.items.length === 0) { if (this.items.length === 0) {
this.items.push(new KeyValue({ enable: true })); this.items.push(new KeyValue({enable: true, scope: "api"}));
} else {
// api
_.forEach(this.items, item => {
if (!item.scope) {
this.$set(item, "scope", "api");
}
})
this.variables = this.items;
} }
}, },
}; };

View File

@ -95,4 +95,6 @@ eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.prefer-ip-address=true eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port} eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
# #
# redisson
spring.redis.redisson.file=file:/opt/metersphere/conf/redisson.yml
spring.session.redis.repository-type=indexed

View File

@ -463,6 +463,15 @@ public class GroupService {
} }
public void removeGroupMember(String userId, String groupId) { public void removeGroupMember(String userId, String groupId) {
Group group = groupMapper.selectByPrimaryKey(groupId);
if (group == null) {
MSException.throwException("group does not exist!");
return;
}
if (!StringUtils.equals(group.getType(), UserGroupType.PROJECT)) {
MSException.throwException("no permission to remove non-project type group users!");
return;
}
UserGroupExample userGroupExample = new UserGroupExample(); UserGroupExample userGroupExample = new UserGroupExample();
userGroupExample.createCriteria() userGroupExample.createCriteria()
.andGroupIdEqualTo(groupId) .andGroupIdEqualTo(groupId)

View File

@ -136,8 +136,8 @@ import MsTableOperator from "metersphere-frontend/src/components/MsTableOperator
import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOperatorButton"; import MsTableOperatorButton from "metersphere-frontend/src/components/MsTableOperatorButton";
import MsTablePagination from "metersphere-frontend/src/components/pagination/TablePagination"; import MsTablePagination from "metersphere-frontend/src/components/pagination/TablePagination";
import ApiEnvironmentConfig from "metersphere-frontend/src/components/environment/ApiEnvironmentConfig"; import ApiEnvironmentConfig from "metersphere-frontend/src/components/environment/ApiEnvironmentConfig";
import {Environment, parseEnvironment} from "metersphere-frontend/src/model/EnvironmentModel"; import {Environment, parseEnvironment, HttpConfig} from "metersphere-frontend/src/model/EnvironmentModel";
import EnvironmentEdit from "metersphere-frontend/src/components/environment/EnvironmentEdit"; import EnvironmentEdit from "./components/EnvironmentEdit";
import MsAsideItem from "metersphere-frontend/src/components/MsAsideItem"; import MsAsideItem from "metersphere-frontend/src/components/MsAsideItem";
import MsAsideContainer from "metersphere-frontend/src/components/MsAsideContainer"; import MsAsideContainer from "metersphere-frontend/src/components/MsAsideContainer";
import ProjectSwitch from "metersphere-frontend/src/components/head/ProjectSwitch"; import ProjectSwitch from "metersphere-frontend/src/components/head/ProjectSwitch";
@ -174,7 +174,7 @@ export default {
projectList: [], projectList: [],
condition: {}, // condition: {}, //
environments: [], environments: [],
currentEnvironment: new Environment(), currentEnvironment: new Environment({httpConfig: new HttpConfig()}),
result: {}, result: {},
loading: false, loading: false,
dialogVisible: false, dialogVisible: false,
@ -288,7 +288,7 @@ export default {
createEnv() { createEnv() {
this.dialogTitle = this.$t('api_test.environment.create'); this.dialogTitle = this.$t('api_test.environment.create');
this.dialogVisible = true; this.dialogVisible = true;
this.currentEnvironment = new Environment(); this.currentEnvironment = new Environment({httpConfig: new HttpConfig()});
this.currentEnvironment.projectId = this.currentProjectId; this.currentEnvironment.projectId = this.currentProjectId;
this.currentEnvironment.currentProjectId = this.currentProjectId; this.currentEnvironment.currentProjectId = this.currentProjectId;
this.ifCreate = true; this.ifCreate = true;

View File

@ -155,7 +155,7 @@ import {REQUEST_HEADERS} from "metersphere-frontend/src/utils/constants";
import {CommonConfig, Environment} from "metersphere-frontend/src/model/EnvironmentModel"; import {CommonConfig, Environment} from "metersphere-frontend/src/model/EnvironmentModel";
import MsApiHostTable from "metersphere-frontend/src/components/environment/commons/ApiHostTable"; import MsApiHostTable from "metersphere-frontend/src/components/environment/commons/ApiHostTable";
import MsDatabaseConfig from "metersphere-frontend/src/components/environment/database/DatabaseConfig"; import MsDatabaseConfig from "metersphere-frontend/src/components/environment/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "metersphere-frontend/src/components/environment/EnvironmentHttpConfig"; import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "metersphere-frontend/src/components/environment/EnvironmentCommonConfig"; import MsEnvironmentCommonConfig from "metersphere-frontend/src/components/environment/EnvironmentCommonConfig";
import MsEnvironmentSSLConfig from "metersphere-frontend/src/components/environment/EnvironmentSSLConfig"; import MsEnvironmentSSLConfig from "metersphere-frontend/src/components/environment/EnvironmentSSLConfig";
import MsApiAuthConfig from "metersphere-frontend/src/components/environment/auth/ApiAuthConfig"; import MsApiAuthConfig from "metersphere-frontend/src/components/environment/auth/ApiAuthConfig";

View File

@ -126,6 +126,7 @@ public class GroupController {
} }
@GetMapping("/rm/{userId}/{groupId}") @GetMapping("/rm/{userId}/{groupId}")
@RequiresPermissions(PermissionConstants.SYSTEM_GROUP_READ)
public void removeGroupMember(@PathVariable String userId, @PathVariable String groupId) { public void removeGroupMember(@PathVariable String userId, @PathVariable String groupId) {
groupService.removeGroupMember(userId, groupId); groupService.removeGroupMember(userId, groupId);
} }

View File

@ -162,10 +162,11 @@ public class GroupService {
public void deleteGroup(String id) { public void deleteGroup(String id) {
Group group = groupMapper.selectByPrimaryKey(id); Group group = groupMapper.selectByPrimaryKey(id);
if (group != null) { if (group == null) {
if (BooleanUtils.isTrue(group.getSystem())) { MSException.throwException("group does not exist!");
MSException.throwException("系统用户组不支持删除!"); }
} if (BooleanUtils.isTrue(group.getSystem())) {
MSException.throwException("系统用户组不支持删除!");
} }
groupMapper.deleteByPrimaryKey(id); groupMapper.deleteByPrimaryKey(id);

View File

@ -385,12 +385,6 @@ public class TestPlanReportService {
TestPlanReportContentWithBLOBs testPlanReportContent = new TestPlanReportContentWithBLOBs(); TestPlanReportContentWithBLOBs testPlanReportContent = new TestPlanReportContentWithBLOBs();
testPlanReportContent.setId(UUID.randomUUID().toString()); testPlanReportContent.setId(UUID.randomUUID().toString());
testPlanReportContent.setTestPlanReportId(returnDTO.getTestPlanReport().getId()); testPlanReportContent.setTestPlanReportId(returnDTO.getTestPlanReport().getId());
if (testPlanReportContent.getStartTime() == null) {
testPlanReportContent.setStartTime(System.currentTimeMillis());
}
if (testPlanReportContent.getEndTime() == null) {
testPlanReportContent.setEndTime(System.currentTimeMillis());
}
testPlanReportContentMapper.insert(testPlanReportContent); testPlanReportContentMapper.insert(testPlanReportContent);
} }
@ -649,8 +643,10 @@ public class TestPlanReportService {
TestPlanReportContentWithBLOBs testPlanReportContent = null; TestPlanReportContentWithBLOBs testPlanReportContent = null;
TestPlanSimpleReportDTO reportDTO = testPlanService.buildPlanReport(testPlan.getId(), false); TestPlanSimpleReportDTO reportDTO = testPlanService.buildPlanReport(testPlan.getId(), false);
if (!testPlanReportContentList.isEmpty()) { if (!testPlanReportContentList.isEmpty()) {
testPlanReportContent = testPlanReportContentList.get(0); testPlanReportContent = parseReportDaoToReportContent(reportDTO, testPlanReportContentList.get(0));
testPlanReportContentMapper.updateByPrimaryKeySelective(parseReportDaoToReportContent(reportDTO, testPlanReportContent)); testPlanReportContent.setStartTime(null);
testPlanReportContent.setEndTime(null);
testPlanReportContentMapper.updateByPrimaryKeySelective(testPlanReportContent);
} }
if (reportDTO.getStartTime() == null) { if (reportDTO.getStartTime() == null) {

View File

@ -65,8 +65,6 @@ import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
@ -1554,6 +1552,10 @@ public class TestPlanService {
envMap = planTestPlanApiCaseService.getApiCaseEnv(planId); envMap = planTestPlanApiCaseService.getApiCaseEnv(planId);
Map<String, List<String>> scenarioEnv = planTestPlanScenarioCaseService.getApiScenarioEnv(planId); Map<String, List<String>> scenarioEnv = planTestPlanScenarioCaseService.getApiScenarioEnv(planId);
if (DiscoveryUtil.hasService(MicroServiceName.UI_TEST)) {
scenarioEnv = mergeUiScenarioEnv(planId, scenarioEnv);
}
Set<String> projectIds = scenarioEnv.keySet(); Set<String> projectIds = scenarioEnv.keySet();
for (String projectId : projectIds) { for (String projectId : projectIds) {
if (envMap.containsKey(projectId)) { if (envMap.containsKey(projectId)) {
@ -1576,6 +1578,32 @@ public class TestPlanService {
return envMap; return envMap;
} }
/**
* 合并ui场景的环境信息
* @param planId
* @param scenarioEnv
* @return
*/
private Map<String, List<String>> mergeUiScenarioEnv(String planId, Map<String, List<String>> scenarioEnv) {
Map<String, List<String>> uiScenarioEnv = planTestPlanUiScenarioCaseService.getUiScenarioEnv(planId);
if (MapUtils.isEmpty(scenarioEnv)) {
return uiScenarioEnv;
}
if (MapUtils.isNotEmpty(uiScenarioEnv)) {
uiScenarioEnv.entrySet().forEach(entry -> {
if (scenarioEnv.containsKey(entry.getKey())) {
List<String> environmentIds = scenarioEnv.get(entry.getKey());
entry.getValue().forEach(eId -> {
if (!environmentIds.contains(eId)) {
environmentIds.add(eId);
}
});
}
});
}
return scenarioEnv;
}
public String runPlan(TestPlanRunRequest testplanRunRequest) { public String runPlan(TestPlanRunRequest testplanRunRequest) {
//检查测试计划下有没有可以执行的用例 //检查测试计划下有没有可以执行的用例
if (!haveExecCase(testplanRunRequest.getTestPlanId(), false)) { if (!haveExecCase(testplanRunRequest.getTestPlanId(), false)) {

View File

@ -175,6 +175,9 @@ export default {
}, },
radioChange(val) { radioChange(val) {
this.$emit("update:environmentType", val); this.$emit("update:environmentType", val);
},
close(){
this.visible = false;
} }
} }

View File

@ -1,5 +1,21 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<env-group-popover
:env-map="projectEnvMap"
:project-ids="projectIds"
:show-env-group="false"
@setProjectEnvMap="setProjectEnvMap"
:environment-type.sync="environmentType"
:group-id="envGroupId"
:is-scenario="false"
@setEnvGroup="setEnvGroup"
:show-config-button-with-out-permission="
showConfigButtonWithOutPermission
"
:project-list="projectList"
ref="envPopover"
class="env-popover"
/>
<ms-table-adv-search-bar :condition.sync="condition" class="adv-search-bar" <ms-table-adv-search-bar :condition.sync="condition" class="adv-search-bar"
v-if="condition.components !== undefined && condition.components.length > 0" v-if="condition.components !== undefined && condition.components.length > 0"
@ -99,6 +115,9 @@ import {
getCustomTableWidth getCustomTableWidth
} from "metersphere-frontend/src/utils/tableUtils"; } from "metersphere-frontend/src/utils/tableUtils";
import MsTableColumn from "metersphere-frontend/src/components/table/MsTableColumn"; import MsTableColumn from "metersphere-frontend/src/components/table/MsTableColumn";
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
import {getApiScenarioEnvByProjectId} from "@/api/remote/api/api-automation";
import {getUiScenarioEnvByProjectId} from "@/api/remote/ui/ui-automation";
export default { export default {
name: "RelevanceUiScenarioList", name: "RelevanceUiScenarioList",
@ -112,6 +131,7 @@ export default {
MsTag, MsTag,
MsTableAdvSearchBar, MsTableAdvSearchBar,
MsTableColumn, MsTableColumn,
EnvGroupPopover,
}, },
props: { props: {
referenced: { referenced: {
@ -147,6 +167,7 @@ export default {
envGroupId: "", envGroupId: "",
versionFilters: [], versionFilters: [],
fieldsWidth: getCustomTableWidth('TEST_PLAN_UI_SCENARIO_CASE'), fieldsWidth: getCustomTableWidth('TEST_PLAN_UI_SCENARIO_CASE'),
projectIds: new Set()
}; };
}, },
computed: { computed: {
@ -244,9 +265,24 @@ export default {
selectCountChange(data) { selectCountChange(data) {
this.selectRows = this.$refs.scenarioTable.selectRows; this.selectRows = this.$refs.scenarioTable.selectRows;
this.$emit("selectCountChange", data); this.$emit("selectCountChange", data);
this.initProjectIds();
}, },
showReport() { showReport() {
},
initProjectIds() {
this.projectIds.clear();
// this.map.clear();
this.selectRows.forEach((row) => {
getUiScenarioEnvByProjectId(row.id).then((res) => {
let data = res.data;
data.projectIds.forEach((d) => this.projectIds.add(d));
// this.map.set(row.id, data.projectIds);
});
});
},
closeEnv(){
this.$refs.envPopover.close();
} }
} }
}; };

View File

@ -87,6 +87,8 @@ export default {
}, },
setProject(projectId) { setProject(projectId) {
this.projectId = projectId; this.projectId = projectId;
this.$refs.apiScenarioList.closeEnv();
this.$refs.apiScenarioList.initProjectIds();
}, },
refresh(data) { refresh(data) {
@ -148,6 +150,10 @@ export default {
let map = this.$refs.apiScenarioList.map; let map = this.$refs.apiScenarioList.map;
let envGroupId = this.$refs.apiScenarioList.envGroupId; let envGroupId = this.$refs.apiScenarioList.envGroupId;
if (!envMap || envMap.size == 0) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
selectRows.forEach(row => { selectRows.forEach(row => {
selectIds.push(row.id); selectIds.push(row.id);
}) })
@ -179,8 +185,6 @@ export default {
this.autoCheckStatus(); this.autoCheckStatus();
this.$refs.baseRelevance.close(); this.$refs.baseRelevance.close();
}); });
}, },
autoCheckStatus() { // autoCheckStatus() { //
if (!this.planId) { if (!this.planId) {

View File

@ -147,7 +147,8 @@
:filters="apiscenariofilters.RESULT_FILTERS" :filters="apiscenariofilters.RESULT_FILTERS"
:label="$t('api_test.automation.last_result')"> :label="$t('api_test.automation.last_result')">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<el-link @click="showReport(row)" :disabled="!row.lastResult || row.lastResult==='PENDING' || row.lastResult==='UnExecute'"> <el-link @click="showReport(row)"
:disabled="!row.lastResult || row.lastResult==='PENDING' || row.lastResult==='UnExecute'">
<ms-test-plan-api-status :status="row.lastResult==='UnExecute' ? 'PENDING' : row.lastResult"/> <ms-test-plan-api-status :status="row.lastResult==='UnExecute' ? 'PENDING' : row.lastResult"/>
</el-link> </el-link>
</template> </template>
@ -176,8 +177,8 @@
:select-row="this.$refs.table ? this.$refs.table.selectRows : new Set()" ref="batchEdit" :select-row="this.$refs.table ? this.$refs.table.selectRows : new Set()" ref="batchEdit"
@batchEdit="batchEdit"/> @batchEdit="batchEdit"/>
<ui-run-mode @handleRunBatch="handleRunBatch" ref="runMode" :custom-run-mode="true" <ui-run-mode @handleRunBatch="handleRunBatch" ref="runMode" :custom-run-mode="true"
:custom-serial-on-sample-error="true"/> :custom-serial-on-sample-error="true" :request="conditionRequest"/>
<ms-task-center ref="taskCenter" :show-menu="false"/> <ms-task-center ref="taskCenter" :show-menu="false"/>
</div> </div>
@ -327,6 +328,8 @@ export default {
] ]
}, },
versionFilters: [], versionFilters: [],
//
conditionRequest: {}
} }
}, },
computed: { computed: {
@ -358,14 +361,14 @@ export default {
}, },
search() { search() {
initCondition(this.condition, this.condition.selectAll); initCondition(this.condition, this.condition.selectAll);
if(this.condition && this.condition.filters && this.condition.filters.last_result){ if (this.condition && this.condition.filters && this.condition.filters.last_result) {
if(this.condition.filters.last_result.length > 0){ if (this.condition.filters.last_result.length > 0) {
//PENDING //PENDING
if(this.condition.filters.last_result.includes("PENDING")){ if (this.condition.filters.last_result.includes("PENDING")) {
this.condition.filters.last_result = [...this.condition.filters.last_result, "UnExecute"] this.condition.filters.last_result = [...this.condition.filters.last_result, "UnExecute"]
} }
//ERROR //ERROR
if(this.condition.filters.last_result.includes("ERROR")){ if (this.condition.filters.last_result.includes("ERROR")) {
this.condition.filters.last_result = [...this.condition.filters.last_result, "FAIL"] this.condition.filters.last_result = [...this.condition.filters.last_result, "FAIL"]
} }
} }
@ -437,12 +440,19 @@ export default {
let rows = this.orderBySelectRows(this.$refs.table.selectRows); let rows = this.orderBySelectRows(this.$refs.table.selectRows);
this.planCaseIds = []; this.planCaseIds = [];
rows.forEach(row => { rows.forEach(row => {
this.planCaseIds.push(row.id); this.planCaseIds.push(row.caseId);
}) })
this.conditionRequest.id = getUUID();
this.conditionRequest.ids = this.planCaseIds;
this.conditionRequest.projectId = this.projectId;
this.conditionRequest.condition = this.condition;
this.$refs.runMode.open(); this.$refs.runMode.open();
}, },
orderBySelectRows(rows) { orderBySelectRows(rows) {
let selectIds = Array.from(rows).map(row => row.id); let selectIds = this.$refs.table.selectIds;
if (rows) {
selectIds = Array.from(rows).map(row => row.id);
}
let array = []; let array = [];
for (let i in this.tableData) { for (let i in this.tableData) {
if (selectIds.indexOf(this.tableData[i].id) !== -1) { if (selectIds.indexOf(this.tableData[i].id) !== -1) {
@ -499,8 +509,8 @@ export default {
}, },
}, },
}, },
this.$t("ui.view_config") this.$t("ui.view_config")
), ),
]) ])
); );
validate = false; validate = false;

View File

@ -7,6 +7,21 @@
:visible.sync="runModeVisible" :visible.sync="runModeVisible"
> >
<div class="mode-container"> <div class="mode-container">
<div>
<div>{{ $t("commons.environment") }}</div>
<env-select-popover :project-ids="projectIds"
:project-list="projectList"
:project-env-map="projectEnvListMap"
:environment-type="'JSON'"
:has-option-group="false"
:group-id="runConfig.environmentGroupId"
@setProjectEnvMap="setProjectEnvMap"
ref="envSelectPopover"
class="mode-row"
></env-select-popover>
</div>
<!-- 浏览器 --> <!-- 浏览器 -->
<div class="browser-row wrap"> <div class="browser-row wrap">
<div class="title">{{ $t("ui.browser") }}</div> <div class="title">{{ $t("ui.browser") }}</div>
@ -175,11 +190,13 @@
<script> <script>
import MsDialogFooter from 'metersphere-frontend/src/components/MsDialogFooter' import MsDialogFooter from 'metersphere-frontend/src/components/MsDialogFooter'
import {getOwnerProjects} from "@/business/utils/sdk-utils"; import {getCurrentProjectID, getOwnerProjects, strMapToObj} from "@/business/utils/sdk-utils";
import {uiScenarioEnvMap} from "@/api/remote/ui/ui-automation";
import EnvSelectPopover from "@/business/plan/env/EnvSelectPopover";
export default { export default {
name: "UiRunMode", name: "UiRunMode",
components: {MsDialogFooter}, components: {MsDialogFooter, EnvSelectPopover},
data() { data() {
return { return {
runModeVisible: false, runModeVisible: false,
@ -207,6 +224,8 @@ export default {
}, },
projectList: [], projectList: [],
projectIds: new Set(), projectIds: new Set(),
projectEnvListMap: {},
caseIdEnvNameMap: {},
}; };
}, },
props: { props: {
@ -262,6 +281,7 @@ export default {
}; };
this.runModeVisible = true; this.runModeVisible = true;
this.getWsProjects(); this.getWsProjects();
this.showPopover();
}, },
changeMode() { changeMode() {
this.runConfig.runWithinResourcePool = false; this.runConfig.runWithinResourcePool = false;
@ -296,6 +316,29 @@ export default {
this.$emit("handleRunBatch", this.runConfig); this.$emit("handleRunBatch", this.runConfig);
this.close(); this.close();
}, },
setProjectEnvMap(projectEnvMap) {
this.runConfig.envMap = strMapToObj(projectEnvMap);
},
showPopover() {
this.showScenarioPopover();
},
showScenarioPopover() {
let currentProjectID = getCurrentProjectID();
this.projectIds.clear();
uiScenarioEnvMap(this.request).then((res) => {
let data = res.data;
this.projectEnvListMap = data;
if (data) {
for (let d in data) {
this.projectIds.add(d);
}
}
if (this.projectIds.size === 0) {
this.projectIds.add(currentProjectID);
}
this.$refs.envSelectPopover.open();
});
},
}, },
}; };
</script> </script>

View File

@ -88,6 +88,7 @@ const TRACK_HEADER = {
{id: 'name', key: '2', label: 'api_test.automation.scenario_name'}, {id: 'name', key: '2', label: 'api_test.automation.scenario_name'},
{id: 'versionId', key: 'd', label: 'commons.version'}, {id: 'versionId', key: 'd', label: 'commons.version'},
{id: 'level', key: '3', label: 'api_test.automation.case_level'}, {id: 'level', key: '3', label: 'api_test.automation.case_level'},
{id: 'envs', key: '8', label: 'commons.environment'},
{id: 'tagNames', key: '4', label: 'api_test.automation.tag'}, {id: 'tagNames', key: '4', label: 'api_test.automation.tag'},
{id: 'stepTotal', key: '7', label: 'api_test.automation.step'}, {id: 'stepTotal', key: '7', label: 'api_test.automation.step'},
{id: 'passRate', key: '9', label: 'api_test.automation.passing_rate'}, {id: 'passRate', key: '9', label: 'api_test.automation.passing_rate'},