This commit is contained in:
liqiang-fit2cloud 2022-12-19 10:39:12 +08:00
commit db78596dba
104 changed files with 1565 additions and 495 deletions

View File

@ -122,18 +122,16 @@ public class MsScenario extends MsTestElement {
// 环境变量
Arguments arguments = ElementUtil.getConfigArguments(this.isEnvironmentEnable() ?
newConfig : config, this.getName(), this.getProjectId(), this.getVariables());
if (arguments != null && ((this.variableEnable == null || this.variableEnable)
|| (this.mixEnable == null || this.mixEnable))) {
if (arguments != null && !arguments.getArguments().isEmpty()) {
Arguments valueSupposeMock = ParameterConfig.valueSupposeMock(arguments);
// 这里加入自定义变量解决ForEach循环控制器取值问题循环控制器无法从vars中取值
if ((this.variableEnable == null || this.variableEnable)
|| (this.mixEnable == null || this.mixEnable)) {
if (BooleanUtils.isTrue(this.variableEnable) || BooleanUtils.isTrue(this.mixEnable)) {
scenarioTree.add(ElementUtil.argumentsToUserParameters(valueSupposeMock));
} else {
} else if (config != null && StringUtils.equals(this.getId(), config.getScenarioId())) {
scenarioTree.add(valueSupposeMock);
}
}
if (this.variableEnable == null || this.variableEnable) {
if (BooleanUtils.isTrue(this.variableEnable) || BooleanUtils.isTrue(this.mixEnable)) {
ElementUtil.addCsvDataSet(scenarioTree, variables, this.isEnvironmentEnable() ? newConfig : config, "shareMode.group");
ElementUtil.addCounter(scenarioTree, variables);
ElementUtil.addRandom(scenarioTree, variables);

View File

@ -83,23 +83,21 @@ public class ApiCaseExecuteService {
if (StringUtils.equals(EnvironmentType.GROUP.toString(), request.getConfig().getEnvironmentType()) && StringUtils.isNotEmpty(request.getConfig().getEnvironmentGroupId())) {
request.getConfig().setEnvMap(environmentGroupProjectService.getEnvMap(request.getConfig().getEnvironmentGroupId()));
}
LoggerUtil.debug("开始查询测试计划用例");
LoggerUtil.info("开始查询测试计划用例", request.getPlanIds().size());
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andIdIn(request.getPlanIds());
example.setOrderByClause("`order` DESC");
List<TestPlanApiCase> planApiCases = testPlanApiCaseMapper.selectByExample(example);
List<TestPlanApiCase> planApiCases = this.selectByPlanApiCaseIds(request.getPlanIds());
if (CollectionUtils.isEmpty(planApiCases)) {
return responseDTOS;
}
if (StringUtils.isEmpty(request.getTriggerMode())) {
request.setTriggerMode(ApiRunMode.API_PLAN.name());
}
LoggerUtil.debug("查询到测试计划用例 " + planApiCases.size());
LoggerUtil.info("查询到测试计划用例 " + planApiCases.size());
Map<String, ApiDefinitionExecResultWithBLOBs> executeQueue = request.isRerun() ? request.getExecuteQueue() : new LinkedHashMap<>();
String status = request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString()) ? ApiReportStatus.PENDING.name()
: ApiReportStatus.RUNNING.name();
String status = StringUtils.equals(request.getConfig().getMode(), RunModeConstants.SERIAL.toString())
? ApiReportStatus.PENDING.name() : ApiReportStatus.RUNNING.name();
// 查出用例
List<String> apiCaseIds = planApiCases.stream().map(TestPlanApiCase::getApiCaseId).collect(Collectors.toList());
@ -117,23 +115,24 @@ public class ApiCaseExecuteService {
//处理环境配置为空时的情况
RunModeConfigDTO runModeConfigDTO = new RunModeConfigDTO();
BeanUtils.copyBean(runModeConfigDTO, request.getConfig());
if (MapUtils.isEmpty(runModeConfigDTO.getEnvMap())) {
ApiTestCase testCase = caseMap.get(testPlanApiCase.getApiCaseId());
if (testCase != null) {
runModeConfigDTO.setEnvMap(new HashMap<>() {{
this.put(testCase.getProjectId(), testPlanApiCase.getEnvironmentId());
}});
}
ApiTestCase testCase = caseMap.get(testPlanApiCase.getApiCaseId());
if (testCase == null) {
continue;
}
ApiDefinitionExecResultWithBLOBs report = ApiDefinitionExecResultUtil.addResult(request, runModeConfigDTO, testPlanApiCase, status, caseMap, resourcePoolId);
if (MapUtils.isEmpty(runModeConfigDTO.getEnvMap())) {
runModeConfigDTO.setEnvMap(new HashMap<>() {{
this.put(testCase.getProjectId(), testPlanApiCase.getEnvironmentId());
}});
}
ApiDefinitionExecResultWithBLOBs report = ApiDefinitionExecResultUtil.addResult(request, runModeConfigDTO, testPlanApiCase, status, testCase, resourcePoolId);
executeQueue.put(testPlanApiCase.getId(), report);
responseDTOS.add(new MsExecResponseDTO(testPlanApiCase.getId(), report.getId(), request.getTriggerMode()));
LoggerUtil.debug("预生成测试用例结果报告:" + report.getName() + ", ID " + report.getId());
LoggerUtil.info("预生成测试用例结果报告:" + report.getName(), report.getId());
}
apiCaseResultService.batchSave(executeQueue);
}
LoggerUtil.debug("开始生成测试计划队列");
LoggerUtil.info("开始生成测试计划队列");
String reportType = request.getConfig().getReportType();
String poolId = request.getConfig().getResourcePoolId();
String runMode = StringUtils.equals(request.getTriggerMode(), TriggerMode.MANUAL.name()) ? ApiRunMode.API_PLAN.name() : ApiRunMode.SCHEDULE_API_PLAN.name();
@ -157,6 +156,16 @@ public class ApiCaseExecuteService {
return responseDTOS;
}
public List<TestPlanApiCase> selectByPlanApiCaseIds(List<String> planApiCaseIds) {
if (CollectionUtils.isEmpty(planApiCaseIds)) {
return new ArrayList<>();
}
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andIdIn(planApiCaseIds);
example.setOrderByClause("`order` DESC");
return testPlanApiCaseMapper.selectByExample(example);
}
public Map<String, List<String>> checkEnv(List<ApiTestCaseWithBLOBs> caseList) {
Map<String, List<String>> projectEnvMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(caseList)) {

View File

@ -297,8 +297,8 @@ public class ApiExecuteService {
// 线程组
MsThreadGroup group = new MsThreadGroup();
group.setLabel(testCaseWithBLOBs.getName());
group.setName(testCaseWithBLOBs.getId());
group.setLabel(request.getReportId());
group.setName(request.getReportId());
group.setOnSampleError(true);
LinkedList<MsTestElement> hashTrees = new LinkedList<>();
hashTrees.add(element);

View File

@ -30,9 +30,9 @@ public class MsKafkaListener {
@Resource
private TestResultService testResultService;
// 线程池维护线程的最少数量
private final static int CORE_POOL_SIZE = 20;
private final static int CORE_POOL_SIZE = 5;
// 线程池维护线程的最大数量
private final static int MAX_POOL_SIZE = 20;
private final static int MAX_POOL_SIZE = 5;
// 线程池维护线程所允许的空闲时间
private final static int KEEP_ALIVE_TIME = 1;
// 线程池所使用的缓冲队列大小

View File

@ -46,10 +46,12 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.assertions.*;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.extractor.BeanShellPostProcessor;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.extractor.RegexExtractor;
import org.apache.jmeter.extractor.XPath2Extractor;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.modifiers.BeanShellPreProcessor;
import org.apache.jmeter.modifiers.JSR223PreProcessor;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
@ -110,10 +112,6 @@ public class JmeterDefinitionParser extends ApiImportAbstractParser<ApiDefinitio
for (MsTestElement element : results) {
ApiDefinitionWithBLOBs apiDefinitionWithBLOBs = buildApiDefinition(element);
if (apiDefinitionWithBLOBs != null) {
if (element.getHashTree() != null) {
element.getHashTree().clear();
}
element.setEnable(true);
apiDefinitionWithBLOBs.setRequest(JSON.toJSONString(element));
HttpResponse defaultHttpResponse = getDefaultHttpResponse();
@ -394,6 +392,10 @@ public class JmeterDefinitionParser extends ApiImportAbstractParser<ApiDefinitio
BeanUtils.copyBean(elementNode, jsr223Sampler);
((MsJSR223PreProcessor) elementNode).setScript(jsr223Sampler.getPropertyAsString("script"));
((MsJSR223PreProcessor) elementNode).setScriptLanguage(jsr223Sampler.getPropertyAsString("scriptLanguage"));
} else if (key instanceof BeanShellPostProcessor) {
elementNode = MsJmeterParser.getMsTestElement((BeanShellPostProcessor) key);
} else if (key instanceof BeanShellPreProcessor) {
elementNode = MsJmeterParser.getMsTestElement((BeanShellPreProcessor) key);
}
// 断言规则
else if (key instanceof ResponseAssertion || key instanceof JSONPathAssertion || key instanceof XPath2Assertion || key instanceof JSR223Assertion || key instanceof DurationAssertion) {

View File

@ -345,7 +345,7 @@ public class Swagger3Parser extends SwaggerAbstractParser {
JsonSchemaItem jsonSchemaItem = parseSchema(schema, refSet);
if (jsonSchemaItem == null) {
jsonSchemaItem = new JsonSchemaItem();
if (StringUtils.isNotBlank(schema.getType())) {
if (schema != null && StringUtils.isNotBlank(schema.getType())) {
jsonSchemaItem.setType(schema.getType());
}
}
@ -575,7 +575,8 @@ public class Swagger3Parser extends SwaggerAbstractParser {
}
private Schema getSchema(Schema schema) {
if (StringUtils.isBlank(schema.get$ref()) && StringUtils.isNotBlank(schema.getType()) && StringUtils.equals(schema.getType(),"string")) {
if (schema != null && StringUtils.isBlank(schema.get$ref()) && StringUtils.isNotBlank(schema.getType()) && StringUtils.equals(schema.getType(), "string")) {
ObjectSchema objectSchema = new ObjectSchema();
objectSchema.setExample(schema.getExample());
schema = objectSchema;
@ -702,7 +703,6 @@ public class Swagger3Parser extends SwaggerAbstractParser {
// 设置响应体
JSONObject responseObject = JSONUtil.parseObject(apiDefinition.getResponse());
JSONObject jsonObject = buildResponseBody(responseObject);
swaggerApiInfo.setResponses(JSONUtil.parseObjectNode(jsonObject.toString()));
// 设置请求参数列表
List<JSONObject> paramsList = buildParameters(requestObject);
@ -835,12 +835,12 @@ public class Swagger3Parser extends SwaggerAbstractParser {
parsedParam.put(PropertyConstant.ITEMS, item);
} else if (StringUtils.equals(type, PropertyConstant.OBJECT)) {
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.OBJECT);
JSONObject properties = requestBody.optJSONObject(PropertyConstant.REQUIRED);
JSONObject properties = requestBody.optJSONObject(PropertyConstant.PROPERTIES);
JSONObject jsonObject = buildFormDataSchema(properties);
if (StringUtils.isNotBlank(requestBody.optString("description"))) {
parsedParam.put("description", requestBody.optString("description"));
}
parsedParam.put(PropertyConstant.REQUIRED, jsonObject.optJSONObject(PropertyConstant.REQUIRED));
parsedParam.put(PropertyConstant.PROPERTIES, jsonObject.optJSONObject(PropertyConstant.PROPERTIES));
} else if (StringUtils.equals(type, PropertyConstant.INTEGER)) {
parsedParam.put(PropertyConstant.TYPE, PropertyConstant.INTEGER);
parsedParam.put("format", "int64");
@ -944,7 +944,7 @@ public class Swagger3Parser extends SwaggerAbstractParser {
}
properties.put(key, property);
}
schema.put(PropertyConstant.REQUIRED, properties);
schema.put(PropertyConstant.PROPERTIES, properties);
return schema;
}
@ -997,7 +997,12 @@ public class Swagger3Parser extends SwaggerAbstractParser {
statusCodeInfo.put("description", StringUtils.EMPTY);
// 返回code
JSONArray statusCode = response.optJSONArray("statusCode");
responseBody.put(statusCode.toString(), statusCodeInfo);
for (int i = 0; i < statusCode.length(); i++) {
JSONObject jsonObject = statusCode.getJSONObject(i);
jsonObject.get("name");
statusCodeInfo.put("description", jsonObject.get("value"));
responseBody.put(jsonObject.get("name").toString(), statusCodeInfo);
}
return responseBody;
}

View File

@ -42,14 +42,10 @@ import io.metersphere.commons.constants.LoopConstants;
import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.constants.RequestTypeConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.*;
import io.metersphere.environment.service.BaseEnvironmentService;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.request.BodyFile;
import io.metersphere.commons.utils.JSONUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -59,10 +55,12 @@ import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.TransactionController;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.extractor.BeanShellPostProcessor;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.extractor.RegexExtractor;
import org.apache.jmeter.extractor.XPath2Extractor;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.modifiers.BeanShellPreProcessor;
import org.apache.jmeter.modifiers.JSR223PreProcessor;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
@ -746,6 +744,10 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
BeanUtils.copyBean(elementNode, jsr223Sampler);
((MsJSR223PostProcessor) elementNode).setScript(jsr223Sampler.getPropertyAsString("script"));
((MsJSR223PostProcessor) elementNode).setScriptLanguage(jsr223Sampler.getPropertyAsString("scriptLanguage"));
} else if (key instanceof BeanShellPostProcessor) {
elementNode = getMsTestElement((BeanShellPostProcessor) key);
} else if (key instanceof BeanShellPreProcessor) {
elementNode = getMsTestElement((BeanShellPreProcessor) key);
}
// 前置脚本
else if (key instanceof JSR223PreProcessor) {
@ -853,4 +855,26 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
}
}
}
public static MsTestElement getMsTestElement(BeanShellPreProcessor key) {
MsTestElement elementNode;
BeanShellPreProcessor beanShellPreProcessor = key;
elementNode = new MsJSR223PreProcessor();
BeanUtils.copyBean(elementNode, beanShellPreProcessor);
((MsJSR223PreProcessor) elementNode).setJsrEnable(false);
((MsJSR223PreProcessor) elementNode).setScript(beanShellPreProcessor.getPropertyAsString("script"));
((MsJSR223PreProcessor) elementNode).setScriptLanguage(beanShellPreProcessor.getPropertyAsString("scriptLanguage"));
return elementNode;
}
public static MsTestElement getMsTestElement(BeanShellPostProcessor key) {
MsTestElement elementNode;
BeanShellPostProcessor beanShellPostProcessor = key;
elementNode = new MsJSR223PostProcessor();
((MsJSR223PostProcessor) elementNode).setJsrEnable(false);
BeanUtils.copyBean(elementNode, beanShellPostProcessor);
((MsJSR223PostProcessor) elementNode).setScript(beanShellPostProcessor.getPropertyAsString("script"));
((MsJSR223PostProcessor) elementNode).setScriptLanguage(beanShellPostProcessor.getPropertyAsString("scriptLanguage"));
return elementNode;
}
}

View File

@ -105,6 +105,8 @@ public interface ExtApiDefinitionMapper {
List<ApiDefinition> selectApiBaseInfoByProjectIdAndProtocolAndStatus(@Param("projectId") String projectId, @Param("protocol") String protocol, @Param("versionId") String versionId, @Param("status") String status);
List<ApiDefinition> selectApiBaseInfoByCondition(@Param("projectId") String projectId, @Param("protocol") String protocol, @Param("versionId") String versionId, @Param("status") String status, @Param("request") ApiDefinitionRequest request);
void updateNoModuleApiToDefaultModule(@Param("projectId") String projectId, @Param("protocol") String protocol, @Param("status") String status, @Param("versionId") String versionId, @Param("moduleId") String moduleId);
List<String> selectApiIdInExecutionInfoByProjectIdIsNull();

View File

@ -290,6 +290,7 @@
api_definition.status, api_definition.user_id, api_definition.create_time, api_definition.update_time,
api_definition.delete_user_id, api_definition.create_user,api_definition.delete_time, api_definition.remark,
api_definition.version_id,
api_definition.latest,
project_version.name as version_name, api_definition.ref_id, user.name as user_name
from api_definition
left join user on api_definition.user_id = user.id
@ -1237,6 +1238,22 @@
AND latest IS TRUE
</if>
</select>
<select id="selectApiBaseInfoByCondition" resultType="io.metersphere.base.domain.ApiDefinition">
select id, module_id
from api_definition
<include refid="queryWhereCondition"/>
AND project_id = #{projectId}
AND protocol = #{protocol}
AND status = #{status}
<if test="versionId != null">
AND version_id = #{versionId}
</if>
<if test="versionId == null">
AND latest IS TRUE
</if>
</select>
<select id="selectApiIdInExecutionInfoByProjectIdIsNull" resultType="java.lang.String">
SELECT DISTINCT source_id FROM api_execution_info
WHERE project_id IS NULL

View File

@ -102,6 +102,8 @@ public interface ExtApiScenarioMapper {
List<ApiScenario> selectBaseInfoByProjectIdAndStatus(@Param("projectId") String projectId, @Param("status") String status);
List<ApiScenario> selectBaseInfoByCondition(@Param("projectId") String projectId, @Param("status") String status, @Param("request") ApiScenarioRequest request);
List<ApiCountChartResult> countByRequest(ApiCountRequest request);
List<ApiScenarioDTO> relevanceScenarioList(@Param("request") ApiScenarioRequest request);

View File

@ -189,7 +189,7 @@
<select id="list" resultMap="BaseResultMap">
select api_scenario.id, api_scenario.project_id, api_scenario.tags, api_scenario.user_id, api_scenario.num,
api_scenario.custom_num, api_scenario.version, api_scenario.environment_type, api_scenario.environment_group_id,
api_scenario.version_id, api_scenario.ref_id, project_version.name as version_name,
api_scenario.version_id, api_scenario.ref_id,api_scenario.latest, project_version.name as version_name,
<if test="request.selectEnvironment == true">
api_scenario.environment_json as env,
</if>
@ -913,6 +913,15 @@
AND latest IS TRUE
</select>
<select id="selectBaseInfoByCondition" resultType="io.metersphere.base.domain.ApiScenario">
select api_scenario.id, api_scenario.api_scenario_module_id
from api_scenario
<include refid="queryWhereCondition"/>
AND api_scenario.project_id = #{projectId}
AND api_scenario.status = #{status}
AND api_scenario.latest IS TRUE
</select>
<select id="countByRequest" resultType="io.metersphere.api.dto.ApiCountChartResult">
select
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">

View File

@ -11,9 +11,6 @@
<if test="versionId != null">
AND version_id = #{versionId}
</if>
<if test="versionId == null">
AND latest = 1
</if>
)
AND reference_id IS NOT NULL
</select>

View File

@ -8,10 +8,10 @@ import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ReportTypeConstants;
import io.metersphere.commons.constants.TriggerMode;
import io.metersphere.commons.enums.StorageEnums;
import io.metersphere.dto.RunModeConfigDTO;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@ -27,7 +27,7 @@ public class ApiDefinitionExecResultUtil {
apiResult.setStartTime(System.currentTimeMillis());
apiResult.setEndTime(System.currentTimeMillis());
apiResult.setTriggerMode(TriggerMode.BATCH.name());
apiResult.setActuator("LOCAL");
apiResult.setActuator(StorageEnums.LOCAL.name());
if (config != null && GenerateHashTreeUtil.isResourcePool(config.getResourcePoolId()).isPool()) {
apiResult.setActuator(config.getResourcePoolId());
}
@ -42,31 +42,33 @@ public class ApiDefinitionExecResultUtil {
return apiResult;
}
public static ApiDefinitionExecResultWithBLOBs addResult(BatchRunDefinitionRequest request, RunModeConfigDTO runModeConfigDTO, TestPlanApiCase key, String status,
Map<String, ApiTestCase> caseMap, String poolId) {
public static ApiDefinitionExecResultWithBLOBs addResult(
BatchRunDefinitionRequest request,
RunModeConfigDTO runModeConfigDTO,
TestPlanApiCase key,
String status,
ApiTestCase testCase,
String poolId) {
ApiDefinitionExecResultWithBLOBs apiResult = new ApiDefinitionExecResultWithBLOBs();
apiResult.setId(UUID.randomUUID().toString());
apiResult.setCreateTime(System.currentTimeMillis());
apiResult.setStartTime(System.currentTimeMillis());
apiResult.setEndTime(System.currentTimeMillis());
apiResult.setReportType(ReportTypeConstants.API_INDEPENDENT.name());
ApiTestCase testCase = caseMap.get(key.getApiCaseId());
if (testCase != null) {
apiResult.setName(testCase.getName());
apiResult.setProjectId(testCase.getProjectId());
apiResult.setVersionId(testCase.getVersionId());
}
apiResult.setTriggerMode(request.getTriggerMode());
apiResult.setActuator("LOCAL");
apiResult.setActuator(StorageEnums.LOCAL.name());
if (StringUtils.isNotEmpty(poolId)) {
apiResult.setActuator(poolId);
}
if (StringUtils.isEmpty(request.getUserId())) {
if (SessionUtils.getUser() != null) {
apiResult.setUserId(SessionUtils.getUser().getId());
}
} else {
apiResult.setUserId(request.getUserId());
apiResult.setUserId(request.getUserId());
if (StringUtils.isEmpty(apiResult.getUserId())) {
apiResult.setUserId(SessionUtils.getUserId());
}
apiResult.setResourceId(key.getId());
@ -92,7 +94,7 @@ public class ApiDefinitionExecResultUtil {
apiResult.setStartTime(System.currentTimeMillis());
apiResult.setEndTime(System.currentTimeMillis());
apiResult.setTriggerMode(TriggerMode.BATCH.name());
apiResult.setActuator("LOCAL");
apiResult.setActuator(StorageEnums.LOCAL.name());
apiResult.setUserId(userId);
apiResult.setResourceId(resourceId);
apiResult.setStartTime(System.currentTimeMillis());

View File

@ -103,7 +103,7 @@ public class MockApiUtils {
} else if (StringUtils.equalsIgnoreCase(type, "XML")) {
if (bodyObj.has("raw")) {
String xmlStr = bodyObj.optString("raw");
xmlStr = xmlStr.replaceAll("\r","").replaceAll("\n","");
xmlStr = xmlStr.replaceAll("\r", "").replaceAll("\n", "");
JSONObject matchObj = XMLUtil.xmlStringToJSONObject(xmlStr);
returnJson = matchObj;
}
@ -360,7 +360,10 @@ public class MockApiUtils {
bodyParams = new JSONArray();
bodyParams.put(paramJson);
} else {
bodyParams.put(((JSONObject) paramJson));
JSONArray oldArray = returnParams.getBodyParams();
if (!JsonStructUtils.checkJsonArrayCompliance(oldArray, ((JSONObject) paramJson))) {
bodyParams.put(((JSONObject) paramJson));
}
}
returnParams.setBodyParams(bodyParams);
}

View File

@ -1,5 +1,6 @@
package io.metersphere.controller.definition;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiModuleDTO;
import io.metersphere.api.dto.definition.DragModuleRequest;
import io.metersphere.service.definition.ApiModuleService;
@ -37,6 +38,13 @@ public class ApiModuleController {
return apiModuleService.getNodeTreeByProjectId(projectId, protocol, versionId);
}
@PostMapping("/list/{projectId}/{protocol}")
public List<ApiModuleDTO> searchNodeByProjectId(@PathVariable String projectId, @PathVariable String protocol, @RequestBody ApiDefinitionRequest request) {
String userId = SessionUtils.getUserId();
ApiDefinitionDefaultApiTypeUtil.addUserSelectApiType(userId, protocol);
return apiModuleService.getNodeTreeByCondition(projectId, protocol, null, request);
}
@GetMapping("/trash/list/{projectId}/{protocol}/{versionId}")
public List<ApiModuleDTO> getTrashNodeByProtocolAndProjectId(@PathVariable String projectId, @PathVariable String protocol,
@PathVariable String versionId) {
@ -48,6 +56,11 @@ public class ApiModuleController {
return apiModuleService.getTrashNodeTreeByProtocolAndProjectId(projectId, protocol, null);
}
@PostMapping("/trash/list/{projectId}/{protocol}")
public List<ApiModuleDTO> searchTrashNodeByProtocolAndProjectId(@PathVariable String projectId, @PathVariable String protocol, @RequestBody ApiDefinitionRequest request) {
return apiModuleService.getTrashNodeTreeByProtocolAndProjectId(projectId, protocol, null, request);
}
@GetMapping("/trash-count/{projectId}/{protocol}")
public long trashCount(@PathVariable String projectId, @PathVariable String protocol) {
String userId = SessionUtils.getUserId();

View File

@ -79,7 +79,7 @@ public class ApiHomeController {
long effectiveApiCount = apiDefinitionService.countEffectiveByProjectId(projectId, versionId);
long apiHasCase = apiDefinitionService.countApiByProjectIdAndHasCase(projectId, versionId);
List<ApiDefinition> apiNoCaseList = apiDefinitionService.selectEffectiveIdByProjectIdAndHaveNotCase(projectId, versionId);
Map<String, Map<String, String>> scenarioUrlList = apiAutomationService.selectScenarioUseUrlByProjectId(projectId, versionId);
Map<String, Map<String, String>> scenarioUrlList = apiAutomationService.selectScenarioUseUrlByProjectId(projectId, null);
int apiInScenario = apiAutomationService.getApiIdInScenario(projectId, scenarioUrlList, apiNoCaseList).size();
if (effectiveApiCount == 0) {
@ -185,7 +185,6 @@ public class ApiHomeController {
apiCountResult.setCoveredCount(coveredDTO.covered);
apiCountResult.setNotCoveredCount(coveredDTO.notCovered);
apiCountResult.setApiCoveredRate(coveredDTO.rateOfCovered);
return apiCountResult;
}

View File

@ -1,6 +1,7 @@
package io.metersphere.controller.scenario;
import io.metersphere.api.dto.automation.ApiScenarioModuleDTO;
import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.DragApiScenarioModuleRequest;
import io.metersphere.service.scenario.ApiScenarioModuleService;
import io.metersphere.base.domain.ApiScenarioModule;
@ -24,11 +25,21 @@ public class ApiScenarioModuleController {
return apiScenarioModuleService.getNodeTreeByProjectId(projectId);
}
@PostMapping("/list/{projectId}")
public List<ApiScenarioModuleDTO> searchNodeByProjectId(@PathVariable String projectId, @RequestBody ApiScenarioRequest request) {
return apiScenarioModuleService.getNodeTreeByProjectId(projectId, request);
}
@GetMapping("/trash/list/{projectId}")
public List<ApiScenarioModuleDTO> getTrashNodeByProjectId(@PathVariable String projectId) {
return apiScenarioModuleService.getTrashNodeTreeByProjectId(projectId);
}
@PostMapping("/trash/list/{projectId}")
public List<ApiScenarioModuleDTO> searchTrashNodeByProjectId(@PathVariable String projectId, @RequestBody ApiScenarioRequest request) {
return apiScenarioModuleService.getTrashNodeTreeByProjectId(projectId, request);
}
@PostMapping("/add")
@MsAuditLog(module = OperLogModule.API_AUTOMATION, type = OperLogConstants.CREATE, title = "#node.name", content = "#msClass.getLogDetails(#node)", msClass = ApiScenarioModuleService.class)
public String addNode(@RequestBody ApiScenarioModule node) {

View File

@ -137,6 +137,8 @@ public class MockConfigService {
}
if (request.getApiId() != null) {
criteria.andApiIdEqualTo(request.getApiId());
} else {
return new MockConfigResponse(null, new ArrayList<>());
}
if (request.getProjectId() != null) {
criteria.andProjectIdEqualTo(request.getProjectId());

View File

@ -320,9 +320,8 @@ public class ApiDefinitionImportUtilService {
String chooseModulePath = getChooseModulePath(idPathMap, chooseModule, chooseModuleParentId);
//这样的过滤规则下可能存在重复接口如果是覆盖模块需要按照去重规则再次去重否则就加上接口原有的模块
if (fullCoverage) {
List<ApiDefinitionWithBLOBs> singleOptionData = new ArrayList<>();
removeHttpChooseModuleRepeat(optionData, singleOptionData, chooseModulePath);
optionData = singleOptionData;
removeHttpChooseModuleRepeat(optionData, chooseModulePath);
// optionData = singleOptionData;
optionMap = optionData.stream().collect(Collectors.toMap(t -> t.getName().concat(t.getMethod()).concat(t.getPath()).concat(chooseModulePath), api -> api));
} else {
getChooseModuleUrlRepeatOptionMap(optionData, optionMap, chooseModulePath);
@ -359,19 +358,6 @@ public class ApiDefinitionImportUtilService {
removeSameData(repeatDataMap, optionMap, optionData, moduleMap, optionDataCases);
}
}
//最后在整个体统内检查一遍防止在有选择的模块时未找到重复直接创建的情况
if (CollectionUtils.isNotEmpty(repeatApiDefinitionWithBLOBs)) {
repeatDataMap = repeatApiDefinitionWithBLOBs.stream().collect(Collectors.groupingBy(t -> t.getName().concat(t.getMethod()).concat(t.getPath()).concat(t.getModulePath())));
optionMap = optionData.stream().collect(Collectors.toMap(t -> t.getName().concat(t.getMethod()).concat(t.getPath()).concat(t.getModulePath()), api -> api));
if (fullCoverage) {
startCover(toUpdateList, optionData, optionMap, repeatDataMap, updateVersionId, optionDataCases, oldCaseMap,null);
} else {
//不覆盖,同一接口不做更新
if (CollectionUtils.isNotEmpty(repeatApiDefinitionWithBLOBs)) {
removeSameData(repeatDataMap, optionMap, optionData, moduleMap, optionDataCases);
}
}
}
//将原来的case和更改的case组合在一起为了同步的设置
List<String> caseIds = optionDataCases.stream().map(ApiTestCase::getId).filter(StringUtils::isNotBlank).collect(Collectors.toList());
buildCases(optionDataCases, oldCaseMap, caseIds);
@ -462,18 +448,20 @@ public class ApiDefinitionImportUtilService {
return s;
}
private static void removeHttpChooseModuleRepeat(List<ApiDefinitionWithBLOBs> optionData, List<ApiDefinitionWithBLOBs> singleOptionData, String chooseModulePath) {
private static void removeHttpChooseModuleRepeat(List<ApiDefinitionWithBLOBs> optionData, String chooseModulePath) {
LinkedHashMap<String, List<ApiDefinitionWithBLOBs>> methodPathMap = optionData.stream().collect(Collectors.groupingBy(t -> t.getName().concat(t.getMethod()).concat(t.getPath()).concat(chooseModulePath), LinkedHashMap::new, Collectors.toList()));
methodPathMap.forEach((k, v) -> singleOptionData.add(v.get(v.size() - 1)));
methodPathMap.forEach((k, v) -> {
if (v.size() > 1) {
for (int i = 0; i < v.size() - 1; i++) {
optionData.remove(v.get(i));
}
}
});
}
private static void getChooseModuleUrlRepeatOptionMap(List<ApiDefinitionWithBLOBs> optionData, Map<String, ApiDefinitionWithBLOBs> optionMap, String chooseModulePath) {
for (ApiDefinitionWithBLOBs optionDatum : optionData) {
if (optionDatum.getModulePath() == null) {
optionMap.put(optionDatum.getName().concat(optionDatum.getMethod()).concat(optionDatum.getPath()).concat(chooseModulePath), optionDatum);
} else {
optionMap.put(optionDatum.getName().concat(optionDatum.getMethod()).concat(optionDatum.getPath()).concat(chooseModulePath).concat(optionDatum.getModulePath()), optionDatum);
}
optionMap.put(optionDatum.getName().concat(optionDatum.getMethod()).concat(optionDatum.getPath()).concat(chooseModulePath), optionDatum);
}
}

View File

@ -26,6 +26,7 @@ import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.*;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.enums.StorageEnums;
import io.metersphere.commons.enums.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
@ -170,7 +171,6 @@ public class ApiDefinitionService {
private static final String SCHEDULE = "schedule";
public List<ApiDefinitionResult> list(ApiDefinitionRequest request) {
request = this.initRequest(request, true, true);
List<ApiDefinitionResult> resList = extApiDefinitionMapper.list(request);
@ -354,8 +354,8 @@ public class ApiDefinitionService {
definitionList = this.selectEffectiveIdByProjectId(request.getProjectId(), versionId);
}
if (CollectionUtils.isNotEmpty(definitionList)) {
//如果查询条件中有未覆盖/已覆盖 则需要解析出没有用例的接口中有多少是符合场景覆盖规律的然后将这些接口的id作为查询参数
Map<String, Map<String, String>> scenarioUrlList = apiAutomationService.selectScenarioUseUrlByProjectId(request.getProjectId(), versionId);
//如果查询条件中有未覆盖/已覆盖 则需要解析出没有用例的接口中有多少是符合场景覆盖规律的然后将这些接口的id作为查询参数. 这里不根据版本筛选覆盖的url
Map<String, Map<String, String>> scenarioUrlList = apiAutomationService.selectScenarioUseUrlByProjectId(request.getProjectId(), null);
List<String> apiIdInScenario = apiAutomationService.getApiIdInScenario(request.getProjectId(), scenarioUrlList, definitionList);
if (CollectionUtils.isNotEmpty(apiIdInScenario)) {
request.setCoverageIds(apiIdInScenario);
@ -783,7 +783,10 @@ public class ApiDefinitionService {
apiTestCaseService.updateByApiDefinitionId(ids, test, request.getTriggerUpdate());
}
ApiDefinitionWithBLOBs result = apiDefinitionMapper.selectByPrimaryKey(test.getId());
//checkAndSetLatestVersion(result.getRefId());
String defaultVersion = baseProjectVersionMapper.getDefaultVersion(request.getProjectId());
if (StringUtils.equalsIgnoreCase(request.getVersionId(), defaultVersion)) {
checkAndSetLatestVersion(result.getRefId());
}
// 存储附件关系
extFileAssociationService.saveApi(test.getId(), request.getRequest(), FileAssociationTypeEnums.API.name());
@ -935,21 +938,6 @@ public class ApiDefinitionService {
}
/**
* 获取存储执行结果报告
*
@ -1010,7 +998,7 @@ public class ApiDefinitionService {
apiReportEnvConfig.setResourcePoolName(resourcePool.getName());
}
} else {
apiReportEnvConfig.setResourcePoolName("LOCAL");
apiReportEnvConfig.setResourcePoolName(StorageEnums.LOCAL.name());
}
return apiReportEnvConfig;
}
@ -1685,9 +1673,11 @@ public class ApiDefinitionService {
} else {
ApiDefinitionExample example = new ApiDefinitionExample();
ApiDefinitionExample.Criteria criteria = example.createCriteria();
criteria.andProjectIdEqualTo(projectId).andStatusNotEqualTo("Trash").andLatestEqualTo(true);
criteria.andProjectIdEqualTo(projectId).andStatusNotEqualTo("Trash");
if (StringUtils.isNotBlank(versionId)) {
criteria.andVersionIdEqualTo(versionId);
} else {
criteria.andLatestEqualTo(true);
}
return apiDefinitionMapper.countByExample(example);
}
@ -1919,6 +1909,11 @@ public class ApiDefinitionService {
return apiList.stream().collect(Collectors.groupingBy(ApiDefinition::getModuleId));
}
public Map<String, List<ApiDefinition>> selectApiBaseInfoGroupByModuleId(String projectId, String protocol, String versionId, String status, ApiDefinitionRequest request) {
List<ApiDefinition> apiList = extApiDefinitionMapper.selectApiBaseInfoByCondition(projectId, protocol, versionId, status, request);
return apiList.stream().collect(Collectors.groupingBy(ApiDefinition::getModuleId));
}
/**
* 将模块删除了的接口模块改为默认模块
*

View File

@ -102,6 +102,19 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
return getNodeTrees(trashModuleList);
}
public List<ApiModuleDTO> getTrashNodeTreeByProtocolAndProjectId(String projectId, String protocol, String versionId, ApiDefinitionRequest request) {
//回收站数据初始化检查是否存在模块被删除的接口则把接口挂再默认节点上
initTrashDataModule(projectId, protocol, versionId);
//通过回收站里的接口模块进行反显
Map<String, List<ApiDefinition>> trashApiMap =
apiDefinitionService.selectApiBaseInfoGroupByModuleId(projectId, protocol, versionId,
ApiTestDataStatus.TRASH.getValue(), request);
//查找回收站里的模块
List<ApiModuleDTO> trashModuleList = this.selectTreeStructModuleById(trashApiMap.keySet());
this.initApiCount(trashModuleList, trashApiMap);
return getNodeTrees(trashModuleList);
}
private void initApiCount(List<ApiModuleDTO> apiModules, Map<String, List<ApiDefinition>> trashApiMap) {
if (CollectionUtils.isNotEmpty(apiModules) && MapUtils.isNotEmpty(trashApiMap)) {
apiModules.forEach(node -> {
@ -192,6 +205,53 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
return getNodeTrees(apiModules);
}
public List<ApiModuleDTO> getNodeTreeByCondition(String projectId, String protocol, String versionId, ApiDefinitionRequest request ) {
// 判断当前项目下是否有默认模块没有添加默认模块
this.getDefaultNode(projectId, protocol);
List<ApiModuleDTO> apiModules = getApiModulesByProjectAndPro(projectId, protocol);
request.setProjectId(projectId);
request.setProtocol(protocol);
List<String> list = new ArrayList<>();
list.add(ApiTestDataStatus.PREPARE.getValue());
list.add(ApiTestDataStatus.UNDERWAY.getValue());
list.add(ApiTestDataStatus.COMPLETED.getValue());
Map<String, List<String>> filters = new LinkedHashMap<>();
filters.put("status", list);
request.setFilters(filters);
//优化 所有统计SQL一次查询出来
List<String> allModuleIdList = new ArrayList<>();
for (ApiModuleDTO node : apiModules) {
List<String> moduleIds = new ArrayList<>();
moduleIds = this.nodeList(apiModules, node.getId(), moduleIds);
moduleIds.add(node.getId());
for (String moduleId : moduleIds) {
if (!allModuleIdList.contains(moduleId)) {
allModuleIdList.add(moduleId);
}
}
}
request.setModuleIds(allModuleIdList);
if (StringUtils.isNotBlank(versionId)) {
request.setVersionId(versionId);
}
List<Map<String, Object>> moduleCountList = extApiDefinitionMapper.moduleCountByCollection(request);
Map<String, Integer> moduleCountMap = this.parseModuleCountList(moduleCountList);
apiModules.forEach(node -> {
List<String> moduleIds = new ArrayList<>();
moduleIds = this.nodeList(apiModules, node.getId(), moduleIds);
moduleIds.add(node.getId());
int countNum = 0;
for (String moduleId : moduleIds) {
if (moduleCountMap.containsKey(moduleId)) {
countNum += moduleCountMap.get(moduleId).intValue();
}
}
node.setCaseNum(countNum);
});
return getNodeTrees(apiModules);
}
private Map<String, Integer> parseModuleCountList(List<Map<String, Object>> moduleCountList) {
Map<String, Integer> returnMap = new HashMap<>();
for (Map<String, Object> map : moduleCountList) {

View File

@ -14,6 +14,7 @@ import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.enums.StorageEnums;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.NodeDTO;
@ -203,7 +204,7 @@ public class ExtApiTaskService extends TaskService {
if (StringUtils.isEmpty(reportId)) {
return;
}
if (StringUtils.isNotEmpty(actuator) && !StringUtils.equals(actuator, "LOCAL")) {
if (StringUtils.isNotEmpty(actuator) && !StringUtils.equals(actuator, StorageEnums.LOCAL.name())) {
if (poolMap.containsKey(actuator)) {
poolMap.get(actuator).add(reportId);
} else {

View File

@ -100,6 +100,47 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
return getNodeTrees(nodes);
}
public List<ApiScenarioModuleDTO> getNodeTreeByProjectId(String projectId, ApiScenarioRequest request) {
// 判断当前项目下是否有默认模块没有添加默认模块
this.getDefaultNode(projectId);
List<ApiScenarioModuleDTO> nodes = extApiScenarioModuleMapper.getNodeTreeByProjectId(projectId);
request.setProjectId(projectId);
List<String> list = new ArrayList<>();
list.add(ApiTestDataStatus.PREPARE.getValue());
list.add(ApiTestDataStatus.UNDERWAY.getValue());
list.add(ApiTestDataStatus.COMPLETED.getValue());
Map<String, List<String>> filters = new LinkedHashMap<>();
filters.put("status", list);
request.setFilters(filters);
List<String> allModuleIdList = new ArrayList<>();
for (ApiScenarioModuleDTO node : nodes) {
List<String> moduleIds = new ArrayList<>();
moduleIds = this.nodeList(nodes, node.getId(), moduleIds);
moduleIds.add(node.getId());
for (String moduleId : moduleIds) {
if (!allModuleIdList.contains(moduleId)) {
allModuleIdList.add(moduleId);
}
}
}
request.setModuleIds(allModuleIdList);
List<Map<String, Object>> moduleCountList = extApiScenarioMapper.listModuleByCollection(request);
Map<String, Integer> moduleCountMap = this.parseModuleCountList(moduleCountList);
nodes.forEach(node -> {
List<String> moduleIds = new ArrayList<>();
moduleIds = this.nodeList(nodes, node.getId(), moduleIds);
moduleIds.add(node.getId());
int countNum = 0;
for (String moduleId : moduleIds) {
if (moduleCountMap.containsKey(moduleId)) {
countNum += moduleCountMap.get(moduleId).intValue();
}
}
node.setCaseNum(countNum);
});
return getNodeTrees(nodes);
}
public List<ApiScenarioModuleDTO> getTrashNodeTreeByProjectId(String projectId) {
//回收站数据初始化被删除了的数据挂在默认模块上
initTrashDataModule(projectId);
@ -112,6 +153,23 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
return getNodeTrees(trashModuleList);
}
public List<ApiScenarioModuleDTO> getTrashNodeTreeByProjectId(String projectId, ApiScenarioRequest request) {
//回收站数据初始化被删除了的数据挂在默认模块上
initTrashDataModule(projectId);
//通过回收站里的接口模块进行反显
if(request.getFilters() != null && request.getFilters().get("status") != null){
List<String> statusList = new ArrayList<>();
statusList.add(ApiTestDataStatus.TRASH.getValue());
request.getFilters().put("status", statusList);
}
Map<String, List<ApiScenario>> trashApiMap = apiAutomationService.selectApiBaseInfoGroupByModuleId(projectId,
ApiTestDataStatus.TRASH.getValue(), request);
//查找回收站里的模块
List<ApiScenarioModuleDTO> trashModuleList = this.selectTreeStructModuleById(trashApiMap.keySet());
this.initApiCount(trashModuleList, trashApiMap);
return getNodeTrees(trashModuleList);
}
private void initApiCount(List<ApiScenarioModuleDTO> moduleDTOList, Map<String, List<ApiScenario>> scenarioMap) {
if (org.apache.commons.collections.CollectionUtils.isNotEmpty(moduleDTOList) && MapUtils.isNotEmpty(scenarioMap)) {
moduleDTOList.forEach(node -> {

View File

@ -790,7 +790,7 @@ public class ApiScenarioReportService {
if (initModel.getConfig() != null && StringUtils.isNotBlank(initModel.getConfig().getResourcePoolId())) {
report.setActuator(initModel.getConfig().getResourcePoolId());
} else {
report.setActuator("LOCAL");
report.setActuator(StorageEnums.LOCAL.name());
}
report.setTriggerMode(initModel.getTriggerMode());
report.setReportVersion(2);

View File

@ -10,6 +10,7 @@ import io.metersphere.commons.constants.MsTestElementConstants;
import io.metersphere.commons.constants.PropertyConstant;
import io.metersphere.commons.constants.ReportTypeConstants;
import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.enums.StorageEnums;
import io.metersphere.commons.utils.*;
import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.RequestResult;
@ -714,7 +715,7 @@ public class ApiScenarioReportStructureService {
dto.setPoolName(resourcePool.getName());
}
} else {
dto.setPoolName("LOCAL");
dto.setPoolName(StorageEnums.LOCAL.name());
}
if (runModeConfigDTO != null && StringUtils.isNotBlank(runModeConfigDTO.getMode())) {
dto.setMode(runModeConfigDTO.getMode());

View File

@ -19,6 +19,7 @@ import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.constants.ReportTypeConstants;
import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.enums.StorageEnums;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.JSON;
import io.metersphere.constants.RunModeConstants;
@ -111,7 +112,7 @@ public class ApiScenarioRerunService {
config.setMode(RunModeConstants.PARALLEL.toString());
}
config.setReportType(RunModeConstants.SET_REPORT.toString());
if (!StringUtils.equalsAnyIgnoreCase(reportResults.get(0).getActuator(), "LOCAL")) {
if (!StringUtils.equalsAnyIgnoreCase(reportResults.get(0).getActuator(), StorageEnums.LOCAL.name())) {
config.setResourcePoolId(reportResults.get(0).getActuator());
}
request.setConfig(config);
@ -165,7 +166,7 @@ public class ApiScenarioRerunService {
config.setMode(RunModeConstants.PARALLEL.toString());
}
config.setReportName(reportDTO.getName());
if (!StringUtils.equalsAnyIgnoreCase(reportDTO.getActuator(), "LOCAL")) {
if (!StringUtils.equalsAnyIgnoreCase(reportDTO.getActuator(), StorageEnums.LOCAL.name())) {
config.setResourcePoolId(reportDTO.getActuator());
}
config.setReportType(RunModeConstants.SET_REPORT.toString());
@ -241,7 +242,7 @@ public class ApiScenarioRerunService {
continue;
}
runModeConfig.setReportType(report.getReportType());
if (!StringUtils.equalsAnyIgnoreCase(report.getActuator(), "LOCAL")) {
if (!StringUtils.equalsAnyIgnoreCase(report.getActuator(), StorageEnums.LOCAL.name())) {
runModeConfig.setResourcePoolId(report.getActuator());
}
executeQueue.put(testPlanApiCase.getId(), report);
@ -293,7 +294,7 @@ public class ApiScenarioRerunService {
// 执行配置
RunModeConfigDTO config = new RunModeConfigDTO();
config.setMode(RunModeConstants.PARALLEL.toString());
if (!StringUtils.equalsAnyIgnoreCase(reports.get(0).getActuator(), "LOCAL")) {
if (!StringUtils.equalsAnyIgnoreCase(reports.get(0).getActuator(), StorageEnums.LOCAL.name())) {
config.setResourcePoolId(reports.get(0).getActuator());
}

View File

@ -409,7 +409,10 @@ public class ApiScenarioService {
if (relationshipEdgeService != null) {
relationshipEdgeService.initRelationshipEdge(beforeScenario, scenario);
}
//checkAndSetLatestVersion(beforeScenario.getRefId());
String defaultVersion = baseProjectVersionMapper.getDefaultVersion(request.getProjectId());
if (StringUtils.equalsIgnoreCase(request.getVersionId(), defaultVersion)) {
checkAndSetLatestVersion(beforeScenario.getRefId());
}
// 存储附件关系
extFileAssociationService.saveScenario(scenario.getId(), request.getScenarioDefinition());
return scenario;
@ -2204,6 +2207,11 @@ public class ApiScenarioService {
return apiScenarioList.stream().collect(Collectors.groupingBy(ApiScenario::getApiScenarioModuleId));
}
public Map<String, List<ApiScenario>> selectApiBaseInfoGroupByModuleId(String projectId, String status, ApiScenarioRequest request) {
List<ApiScenario> apiScenarioList = extApiScenarioMapper.selectBaseInfoByCondition(projectId, status, request);
return apiScenarioList.stream().collect(Collectors.groupingBy(ApiScenario::getApiScenarioModuleId));
}
public List<ApiCountChartResult> countByRequest(ApiCountRequest request) {
return extApiScenarioMapper.countByRequest(request);
}

View File

@ -5,6 +5,11 @@ export function getApiModules(projectId, protocol, currentVersion) {
return get(url);
}
export function postApiModules(projectId, protocol, currentVersion, param) {
let url = '/api/module/list/' + projectId + '/' + protocol + (currentVersion ? '/' + currentVersion : '');
return post(url, param);
}
export function getApiModuleByProjectIdAndProtocol(projectId, protocol) {
let url = '/api/module/list/' + projectId + '/' + protocol;
return get(url);
@ -15,6 +20,11 @@ export function getApiModuleByTrash(projectId, protocol, currentVersion) {
return get(url);
}
export function postApiModuleByTrash(projectId, protocol, currentVersion, param) {
let url = '/api/module/trash/list/' + projectId + '/' + protocol + '/' + (currentVersion ? '/' + currentVersion : '');
return post(url, param);
}
export function getUserDefaultApiType() {
let url = '/api/module/default-type';
return get(url);

View File

@ -5,6 +5,11 @@ export function getModuleByProjectId(projectId) {
return get(url);
}
export function postModuleByProjectId(projectId, param) {
let url = '/api/automation/module/list/' + projectId;
return post(url, param);
}
export function getModuleByRelevanceProjectId(relevanceProjectId) {
let url = '/api/automation/module/list/' + relevanceProjectId;
return get(url);
@ -15,6 +20,11 @@ export function getModuleByTrash(projectId) {
return get(url);
}
export function postModuleByTrash(projectId, param) {
let url = '/api/automation/module/trash/list/' + projectId;
return post(url, param);
}
export function editScenarioModule(params) {
return post('/api/automation/module/edit', params);
}

View File

@ -830,6 +830,7 @@ export default {
}
},
search(projectId) {
this.$EventBus.$emit("scenarioConditionBus", this.condition)
if (this.needRefreshModule()) {
this.$emit('refreshTree');
}

View File

@ -55,7 +55,7 @@ import {
getModuleByProjectId,
getModuleByRelevanceProjectId,
getModuleByTrash,
posScenarioModule,
posScenarioModule, postModuleByProjectId, postModuleByTrash,
} from '@/api/scenario-module';
export default {
@ -112,6 +112,7 @@ export default {
filterText: '',
trashEnable: false,
},
param: {},
data: [],
currentModule: undefined,
operators: [
@ -155,6 +156,7 @@ export default {
this.filter();
},
'condition.trashEnable'() {
this.param = {};
this.$emit('enableTrash', this.condition.trashEnable);
},
relevanceProjectId() {
@ -162,9 +164,21 @@ export default {
},
isTrashData() {
this.condition.trashEnable = this.isTrashData;
this.list();
this.param = {};
},
},
created(){
this.$EventBus.$on("scenarioConditionBus", (param)=>{
this.param = param;
this.list();
})
},
beforeDestroy() {
this.$EventBus.$off("scenarioConditionBus", (param)=>{
this.param = param;
this.list();
})
},
methods: {
handleImport() {
if (this.projectId) {
@ -188,11 +202,11 @@ export default {
this.setData(response);
});
} else if (this.isTrashData) {
this.result = getModuleByTrash(projectId ? projectId : this.projectId).then((response) => {
this.result = postModuleByTrash(projectId ? projectId : this.projectId, this.param).then((response) => {
this.setData(response);
});
} else {
this.result = getModuleByProjectId(projectId ? projectId : this.projectId).then((response) => {
this.result = postModuleByProjectId(projectId ? projectId : this.projectId, this.param).then((response) => {
this.setData(response);
});
}
@ -287,14 +301,14 @@ export default {
if (!this.projectId) {
return;
}
getModuleByTrash(this.projectId).then((response) => {
postModuleByTrash(this.projectId, this.param).then((response) => {
this.setModuleList(response, selectNodeId);
});
} else {
if (!this.projectId) {
return;
}
getModuleByProjectId(this.projectId).then((response) => {
postModuleByProjectId(this.projectId, this.param).then((response) => {
this.setModuleList(response, selectNodeId);
});
}

View File

@ -23,7 +23,7 @@
</div>
</div>
<el-form :model="formData" :rules="rules" label-width="105px" v-loading="result" ref="form">
<el-form :model="formData" :rules="rules" label-width="110px" v-loading="result" ref="form">
<el-row>
<el-col :span="11">
<el-form-item :label="$t('commons.import_module')">
@ -37,8 +37,8 @@
checkStrictly />
</el-form-item>
<el-form-item :label="$t('commons.import_mode')" prop="modeId">
<el-select size="small" v-model="formData.modeId" class="project-select" clearable>
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id" />
<el-select size="small" v-model="formData.modeId" class="project-select" clearable style="width: 100%">
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-checkbox size="mini" v-if="formData.modeId === 'fullCoverage'" v-model="formData.coverModule">
{{ this.$t('commons.cover_scenario') }}
@ -56,7 +56,7 @@
<el-form-item
v-xpack
v-if="projectVersionEnable && formData.modeId === 'fullCoverage'"
:label="$t('api_test.api_import.data_update_version')"
:label="$t('api_test.scenario_import.data_update_version')"
prop="versionId">
<el-select size="small" v-model="formData.updateVersionId" clearable style="width: 100%">
<el-option v-for="item in versionOptions" :key="item.id" :label="item.name" :value="item.id" />
@ -65,7 +65,7 @@
<el-form-item
v-xpack
v-if="projectVersionEnable && formData.modeId === 'fullCoverage'"
:label="$t('api_test.api_import.data_new_version')"
:label="$t('api_test.scenario_import.data_new_version')"
prop="versionId">
<el-select size="small" v-model="formData.versionId" clearable style="width: 100%">
<el-option v-for="item in versionOptions" :key="item.id" :label="item.name" :value="item.id" />
@ -426,9 +426,6 @@ export default {
margin-left: 10px;
}
.el-form {
padding: 30px 10px;
}
.dialog-footer {
float: right;

View File

@ -1,6 +1,6 @@
<template>
<div class="json-schema-editor" style="padding: 0 10px">
<el-row class="row" :gutter="10">
<el-row class="row" :gutter="10" v-if="reloadSelfOver">
<el-col :style="{ minWidth: `${200 - 10 * deep}px` }" class="ms-col-name">
<div :style="{ marginLeft: `${10 * deep}px` }" class="ms-col-name-c" />
<span
@ -401,6 +401,7 @@ export default {
countAdd: 1,
modalVisible: false,
reloadItemOver: true,
reloadSelfOver: true,
advancedValue: {},
addProp: {}, //
customProps: [],
@ -484,15 +485,19 @@ export default {
changeAllItemsType(changeType) {
if (this.isArray(this.pickValue) && this.pickValue.items && this.pickValue.items.length > 0) {
this.pickValue.items.forEach((item) => {
item.type = changeType;
delete item['properties'];
delete item['items'];
delete item['required'];
delete item['mock'];
this.$set(item, 'type', changeType);
if (changeType === 'array') {
this.$set(item, 'items', [{ type: 'string', mock: { mock: '' } }]);
}
});
this.$nextTick(() => {
this.reloadSelf();
this.reloadItems();
});
}
},
onCheck(e) {
@ -609,6 +614,13 @@ export default {
this.reloadItemOver = true;
});
},
reloadSelf() {
this.reloadSelfOver = false;
this.$nextTick(() => {
this.reloadSelfOver = true;
});
},
editScenarioAdvance(data) {
this.$emit('editScenarioAdvance', data);
},

View File

@ -0,0 +1,11 @@
const value = {
description: null,
};
const attr = {
description: {
name: '描述',
type: 'string',
},
};
const wrapper = { value, attr };
export default wrapper;

View File

@ -4,6 +4,7 @@ import _array from './array';
import _boolean from './boolean';
import _integer from './integer';
import _number from './number';
import _null from './null';
const TYPE_NAME = ['string', 'number', 'integer', 'object', 'array', 'boolean', 'null'];
@ -14,7 +15,7 @@ const TYPE = {
boolean: _boolean,
integer: _integer,
number: _number,
null: { description: null },
null: _null,
};
export { TYPE, TYPE_NAME };

View File

@ -212,6 +212,14 @@ export default {
this.assignKey(to, from, key);
}
}
//null
if (to.type && from.type) {
to.type = from.type;
if (from.type === 'null') {
this.assign(Object(to.mock), { mock: '' });
}
}
let property = ['description', 'maxLength', 'minLength', 'pattern', 'format', 'enum', 'default'];
//
for (let key in to) {

View File

@ -849,6 +849,7 @@ export default {
this.search();
},
search() {
this.$EventBus.$emit("apiConditionBus", this.condition)
this.changeSelectDataRangeAll();
this.initTable();
},

View File

@ -52,7 +52,7 @@ import {
getApiModuleByTrash,
getApiModules,
getUserDefaultApiType,
posModule,
posModule, postApiModuleByTrash, postApiModules,
} from '@/api/definition-module';
import MsAddBasisApi from '../basis/AddBasisApi';
import SelectMenu from '@/business/commons/SelectMenu';
@ -83,6 +83,7 @@ export default {
},
data: [],
currentModule: {},
param: {},
};
},
props: {
@ -155,6 +156,7 @@ export default {
this.list();
},
'condition.trashEnable'() {
this.param = {};
this.$emit('enableTrash', this.condition.trashEnable);
},
relevanceProjectId() {
@ -164,6 +166,7 @@ export default {
this.list();
},
isTrashData() {
this.param = {};
this.condition.trashEnable = this.isTrashData;
this.list();
},
@ -173,6 +176,18 @@ export default {
}
},
},
created(){
this.$EventBus.$on("apiConditionBus", (param)=>{
this.param = param;
this.list();
})
},
beforeDestroy() {
this.$EventBus.$off("apiConditionBus", (param)=>{
this.param = param;
this.list();
})
},
methods: {
initProtocol() {
//
@ -220,11 +235,11 @@ export default {
}
);
} else if (this.isTrashData) {
this.result = getApiModuleByTrash(projectId, this.condition.protocol, this.currentVersion).then((response) => {
this.result = postApiModuleByTrash(projectId, this.condition.protocol, this.currentVersion, this.param).then((response) => {
this.setData(response);
});
} else {
this.result = getApiModules(projectId, this.condition.protocol, this.currentVersion).then((response) => {
this.result = postApiModules(projectId, this.condition.protocol, this.currentVersion, this.param).then((response) => {
this.setData(response);
});
}
@ -329,16 +344,20 @@ export default {
this.setNohupData(response, selectNodeId);
});
} else if (this.isTrashData) {
getApiModuleByTrash(
postApiModuleByTrash(
this.projectId,
this.condition.protocol + (this.currentVersion ? '/' + this.currentVersion : '')
this.condition.protocol,
this.currentVersion,
this.param
).then((response) => {
this.setNohupData(response, selectNodeId);
});
} else {
getApiModules(
postApiModules(
this.projectId,
this.condition.protocol + (this.currentVersion ? '/' + this.currentVersion : '')
this.condition.protocol,
this.currentVersion,
this.param
).then((response) => {
this.setNohupData(response, selectNodeId);
});

View File

@ -25,17 +25,17 @@
<el-row style="margin: 20px">
<span style="margin-right: 10px"> {{ $t('api_test.request.cert_alias') }}: </span>
<span style="margin-right: 10px">
<el-input size="small" style="width: 350px" v-model="request.alias" />
<el-input size="small" style="width: 350px" v-model="request.alias" :disabled="isReadOnly"/>
</span>
</el-row>
<el-row style="margin: 20px">
<span style="margin-right: 10px">
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects" @change="changeFollow">{{
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects" @change="changeFollow" :disabled="isReadOnly">{{
$t('api_test.request.follow_redirects')
}}</el-checkbox>
</span>
<span style="margin-left: 10px; margin-right: 10px">
<el-checkbox class="follow-redirects-item" v-model="request.autoRedirects" @change="changeAuto">{{
<el-checkbox class="follow-redirects-item" v-model="request.autoRedirects" @change="changeAuto" :disabled="isReadOnly">{{
$t('api_definition.request.auto_redirects')
}}</el-checkbox>
</span>

View File

@ -65,6 +65,9 @@ export default {
} else {
this.$EventBus.$emit('API_TEST_ERROR', this.reportId);
}
} else {
this.loading = true;
this.socketSync();
}
});
}

View File

@ -10,7 +10,7 @@
:placeholder="$t('home.dashboard.public.default_version')"
size="small"
style="height: 100%">
<el-option v-for="item in versions" :key="item.id" :label="item.name" :value="item.id"> </el-option>
<el-option v-for="item in versions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-row>
<el-row :gutter="16">
@ -233,6 +233,7 @@ export default {
.api-home-layout :deep(.dashboard-title) {
font-size: 18px;
font-weight: 500;
color: #1f2329;
}
.api-home-layout :deep(.common-amount) {

View File

@ -123,15 +123,13 @@ export default {
tableData: [],
currentPage: 1,
pageSize: 5,
versionId: '',
total: 0,
condition: {
filters: {},
},
};
},
activated() {
this.search();
},
methods: {
clickRow(row, column) {
if (column.property !== 'status') {
@ -143,10 +141,13 @@ export default {
}
},
search(versionId) {
if (versionId) {
this.versionId = versionId;
}
let projectId = getCurrentProjectID();
this.loading = true;
this.loadError = false;
this.result = getRunningTask(projectId, versionId, this.currentPage, this.pageSize)
this.result = getRunningTask(projectId, this.versionId, this.currentPage, this.pageSize)
.then((response) => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;

View File

@ -156,12 +156,14 @@ export default {
}
},
search(versionId) {
this.versionId = versionId;
if (versionId) {
this.versionId = versionId;
}
let projectId = getCurrentProjectID();
this.loading = true;
this.loadError = false;
this.result = definitionWeekList(projectId, versionId, this.currentPage, this.pageSize)
this.result = definitionWeekList(projectId, this.versionId, this.currentPage, this.pageSize)
.then((response) => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;

View File

@ -118,9 +118,9 @@
<template slot-scope="scope">
<el-select
v-model="scope.row.type"
v-if="!scope.row.scope || scope.row.scope == 'api'"
:placeholder="$t('commons.please_select')"
size="mini"
@change="changeType(scope.row)"
>
<el-option
v-for="item in typeSelectOptions"
@ -129,6 +129,20 @@
:value="item.value"
/>
</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>
</ms-table-column>
@ -271,6 +285,12 @@ export default {
{value: "COUNTER", label: this.$t("api_test.automation.counter")},
{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: {},
selectVariable: "",
editData: {},
@ -343,6 +363,10 @@ export default {
data.files = [];
data.quotedData = "false";
}
if (!data.scope || data.scope == "ui") {
data.type = 'STRING';
}
},
valueText(data) {
switch (data.type) {

View File

@ -3,7 +3,7 @@
<el-col :span="16">
<el-select v-model="selfQuantity" placeholder=" " size="mini" filterable default-first-option
allow-create
class="timing_select" :disabled="selfChoose">
class="timing_select" :disabled="selfChoose" @change="chooseChange(true)">
<el-option
v-for="item in quantityOptions"
:key="item"
@ -12,7 +12,7 @@
</el-option>
</el-select>
<el-select v-model="selfUnit" placeholder=" " size="mini"
class="timing_select" :disabled="selfChoose">
class="timing_select" :disabled="selfChoose" @change="chooseChange(true)">
<el-option
v-for="item in unitOptions"
:key="item.value"
@ -58,6 +58,7 @@ export default {
type: Array,
default() {
return [
{value: "H", label: this.$t('commons.date_unit.hour')},
{value: "D", label: this.$t('commons.date_unit.day')},
{value: "M", label: this.$t('commons.date_unit.month')},
{value: "Y", label: this.$t('commons.date_unit.year')},
@ -66,8 +67,11 @@ export default {
}
},
watch: {
expr(val) {
this.parseExpr(val);
expr: {
handler(val) {
this.parseExpr(val);
},
immediate: true
},
choose(val) {
this.selfChoose = val;

View File

@ -8,9 +8,9 @@
:class="{ 'el-icon-arrow-left pointer' : !active, 'el-icon-arrow-down pointer' : active}"
@click="active=!active"></span>
</el-tooltip>
<template v-if="active">
<div v-show="active">
<slot></slot>
</template>
</div>
</div>
</div>
</template>

View File

@ -156,10 +156,16 @@ export default {
if (!hasLicense()) {
return;
}
ModuleEvent.$on(MODULE_CHANGE, (key, status) => {
getModuleList().then(() => {
this.menuKey++;
getModuleList()
.then(response => {
response.data.forEach(m => {
this.modules[m.key] = m.status;
});
localStorage.setItem('modules', JSON.stringify(this.modules));
})
.catch(() => {
});
ModuleEvent.$on(MODULE_CHANGE, (key, status) => {
this.$set(this.modules, key, status);
this.menuKey++;
});

View File

@ -10,7 +10,7 @@
<font-awesome-icon class="icon global focusing" :icon="['fas', 'tasks']"/>
</el-badge>
</div>
<font-awesome-icon @click="showTaskCenter" class="icon global focusing" :icon="['fas', 'tasks']" v-else/>
<font-awesome-icon @click="open('API')" class="icon global focusing" :icon="['fas', 'tasks']" v-else/>
</el-tooltip>
</div>
<el-drawer
@ -299,7 +299,9 @@ export default {
if (activeName) {
this.activeName = activeName;
}
this.showTaskCenter();
this.init(true);
this.taskVisible = true;
setTimeout(this.showTaskCenter, 2000);
},
getPercentage(status) {
if (status) {

View File

@ -12,6 +12,9 @@
<font-awesome-icon v-if="scope.row.isCurrent"
class="icon global focusing" :icon="['fas', 'tag']"/>
{{ scope.row.name }}
<el-tag v-if="scope.row.id === dataLatestId" size="mini" type="primary">
{{ $t('api_test.api_import.latest_version') }}&nbsp;
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" column-key="status"
@ -52,11 +55,11 @@
placement="bottom"
width="100"
trigger="hover"
v-if="!scope.row.latest"
v-if="scope.row.isCheckout || scope.row.status !== 'open'"
>
<div style="text-align: left;">
<el-link @click="setLatest(scope.row)" v-if="hasLatest && !scope.row.isCurrent && scope.row.isCheckout"
:disabled="scope.row.isCurrent || isRead">
<el-link @click="setLatest(scope.row)" v-if="hasLatest && scope.row.isCheckout"
:disabled="isRead || scope.row.id === dataLatestId">
{{ $t('project.version.set_new') }}&nbsp;
</el-link>
<br/>
@ -66,8 +69,6 @@
</div>
<span slot="reference">...</span>
</el-popover>
</template>
</el-table-column>
</el-table>
@ -109,7 +110,7 @@ export default {
hasLatest: {
type: Boolean,
default: false
},
}
},
data() {
return {
@ -118,6 +119,7 @@ export default {
versionOptions: [],
userData: {},
currentVersion: {},
dataLatestId: ''
};
},
methods: {
@ -176,6 +178,10 @@ export default {
this.loading = false;
return;
}
let latestData = versionData.filter((v) => v.latest === true);
if (latestData) {
this.dataLatestId = latestData[0].versionId;
}
this.versionOptions.forEach(version => {
let vs = versionData.filter(v => v.versionId === version.id);
version.isCheckout = vs.length > 0; //
@ -210,7 +216,7 @@ export default {
testUsers() {
this.updateUserDataByExternal();
}
}
},
};
</script>

View File

@ -1915,10 +1915,14 @@ const message = {
no_cover_tip_scenario_1: "1. The same Scenario that already exists in the system will not be changed",
no_cover_tip_scenario_2: "2. Add Scenario that do not exist in the system",
import_version: 'Import version',
data_update_version: 'New API created to',
data_new_version: 'The same API is updated to',
data_update_version: 'The same API is updated to',
data_new_version: 'New API created to',
latest_version: 'Latest version',
},
scenario_import: {
data_update_version: 'The same Scenario is updated to',
data_new_version: 'New Scenario created to',
},
home_page: {
unit_of_measurement: "",
unit_of_count: "",
@ -3185,7 +3189,7 @@ const message = {
store_window_handle: "Window Handle(storeWindowHandle)",
store_title: "Web page title (storeTitle)",
wait_time: "Wait time",
per_tip: "After enabling the performance mode, the memory and cpu usage will be reduced, and the running results will not show step screenshots",
per_tip: "After enabling the performance mode, the memory and cpu usage will be reduced",
fail_over: "Failed to terminate",
validate_tip: "Check means a hard assertion (assert), if the assertion fails, the program will terminate. Unchecked means a soft assertion (verify), if the assertion fails, the program will not terminate.",
scenario: "Scenario",

View File

@ -1928,6 +1928,10 @@ const message = {
data_new_version: '新增API创建到',
latest_version: '最新版本',
},
scenario_import: {
data_update_version: '同一场景更新到',
data_new_version: '新增场景创建到',
},
home_page: {
unit_of_measurement: "个",
unit_of_count: "个",
@ -3200,7 +3204,7 @@ const message = {
store_window_handle: "窗口 Handle(storeWindowHandle)",
store_title: "网页标题(storeTitle)",
wait_time: "等待时间",
per_tip: "启用性能模式后将减少内存和cpu的占用,运行结果不展示步骤截图",
per_tip: "启用性能模式后将减少内存和cpu的占用",
fail_over: "失败终止",
validate_tip: "勾选表示为硬断言assert如果断言失败程序会终止。不勾选表示为软断言verify如果断言失败程序不会终止。",
scenario: "场景",

View File

@ -1924,6 +1924,10 @@ const message = {
data_new_version: '新增API創建到',
latest_version: '最新版本',
},
scenario_import: {
data_update_version: '同一場景更新到',
data_new_version: '新增場景創建到',
},
home_page: {
unit_of_measurement: "個",
unit_of_count: "個",
@ -2916,28 +2920,24 @@ const message = {
ui_automation: "UI 自動化",
ui_element: "元素庫",
report: "測試報告",
ui_debug_mode: 'UI自動化調試管道',
ui_local_debug: '本地調試',
ui_server_debug: '後端調試',
all_element: "全部元素",
element_name: "元素名稱",
element_locator: "元素定位",
element_locator_type: "定位類型",
screenshot: "截圖",
update_user: "更新人",
create_user: "創建人",
all_scenario: "全部場景",
scenario_list: "場景列表",
log: "日誌",
performance_mode: "性能模式",
error_handling: "錯誤處理",
other_settings: "其他設置",
step_results: "步骤结果",
treatment_method: "處理方式",
scenario_steps: "場景步驟",
basic_information: "基礎信息",
check_element: "請勾選元素",
check_subitem: '請選擇子分類',
selenium_tip: "支持 Selenium-IDE 插件格式導入",
selenium_export_tip: "通過 MeterSphere 導出 side 文件",
elementObject: "元素對象",
elementLocator: "元素定位",
elementType: "所屬分類",
not_selected: "(未選擇元素)",
not_selected_location: "(未選擇元素定位)",
location: "定位",
run: "運行",
locate_type: "定位方式",
coord: "坐標",
enable_or_not: "啟用/禁用",
enable: "啟用",
disable: "禁用",
resolution: "分辨率",
ignore_fail: "忽略異常並繼續執行",
not_ignore_fail: "終止流程",
cmdValidation: "斷言",
cmdValidateValue: "斷言值",
cmdValidateText: "彈窗文本",
@ -2964,10 +2964,138 @@ const message = {
cmdElse: "Else",
cmdElseIf: "ElseIf",
close: "關閉網頁",
cmdExtraction: "提取參數",
cmdExtraction: "數據提取",
cmdExtractWindow: "提取窗口信息",
cmdExtractElement: "提取元素信息",
valiate_fail: "校驗失敗,請檢查必填項",
ui_debug_mode: 'UI自動化調試方式',
ui_local_debug: '本地調試',
ui_server_debug: '後端調試',
all_element: "全部元素",
element_name: "元素名稱",
element_locator: "元素定位",
element_locator_type: "定位類型",
screenshot: "截圖",
update_user: "更新人",
create_user: "創建人",
all_scenario: "全部場景",
log: "日誌",
performance_mode: "性能模式",
error_handling: "錯誤處理",
other_settings: "其他設置",
step_results: "步驟結果",
treatment_method: "處理方式",
scenario_steps: "場景步驟",
basic_information: "基礎信息",
step_type: "步驟類型",
input_or_not: "是否輸入",
input_content: "輸入內容",
insert_content: "鍵入內容",
append_content: "追加輸入",
append_tip: "勾選,在現有內容後面追加輸入;<br/>不勾選,清空現有內容後再進行輸入",
pls_input: "請輸入內容",
opt_type: "操作方式:",
yes: "是",
no: "否",
confirm: "確定",
cancel: "取消",
press_button: "點擊彈窗確定按鈕或取消按鈕",
param_null: "參數不能為空",
operation_object: "操作對象",
sub_item: "子選項",
value: "值",
select: "選擇",
option: "選項( Option )",
index: "索引( Index )",
s_value: "值( Value )",
text: "文本( Text )",
set_itera: "設置遍歷",
foreach_tip: "設置循環叠代,支持數組行數據,例如: [1,2,3];也可輸入變量",
intervals: "間隔時間",
condition_type: "條件類型",
please_select: "請選擇",
condition_list: "條件列表:通過列表的方式設置多個條件",
condition_list_: "條件列表",
condition_exp: "條件表達式:表達式判斷為真,則執行裏面的步驟",
condition_exp_: "條件表達式",
expression: "表達式",
if_tip: "變量請使用${var},字符串請加單引號,如:${name} === '張三'",
input_c_tip: "'可編輯段落的元素 contenteditable 屬性必須為 true, 才可實現輸入;例:&lt;p contenteditable=&quot;true&quot;&gt;這是一段可編輯的段落。請試著編輯該文本。&lt;/p&gt;'",
input: "輸入框",
editable_p: "可編輯段落",
click_type: "點擊方式",
set_click_point: "設置鼠標點擊位置",
click_tip_1: "勾選,可控製鼠標在元素上的點擊位置",
element_location: "元素位置",
click_point: "點擊位置",
x: "橫坐標",
y: "縱坐標",
click_tip_2: "默認元素的左上角為00通過設置相對位置控製鼠標在元素上的點擊位置",
click: "單擊",
dclick: "雙擊",
press: "按下",
standup: "彈起",
mouse_start: "鼠標起始位置",
drag_start: "被拖拽的元素起點的位置",
mouse_end: "鼠標終點位置",
drag_end: "被拖拽的元素最終的位置",
move_type: "移動方式",
mouse_location: "鼠標位置",
relative_location: "相對坐標位置",
move_tip: "相對位置元素當前的位置坐標為00",
mouse_out_e: "鼠標移出元素",
mouse_in_e: "鼠標移入元素",
mouse_e_to_c: "鼠標從元素移到坐標位置",
url: "網頁地址",
sf_tip: "如果是切換 frame需要傳入索引或者元素定位後再切換",
sf_index: "frame 索引號",
select_index: "選擇當前頁面的第幾個 frame",
select_f_tip: "例:比如索引值輸入 1那麽效果會切換到當前頁面的第 2 個 frame(索引值從 0 開始計算)",
exit_frame: "退出當前 frame(回到主頁面)",
select_frame_index: "根據 frame 索引號切換到指定 frame",
select_by_location: "根據定位方式切換 frame",
sw_tip1: "如果是切換到指定窗口,需要傳入句柄",
handle_id: "句柄 ID",
window_handle: "窗口句柄 ID",
frame_index: "網頁索引號",
window_index: "窗口網頁索引號",
select_open_window: "選擇打開過的第幾個網頁;",
s_w_t1: "例:比如索引值輸入 3那麽效果會切換到已經打開過的第 3 個窗口(索引值從 1 開始計算)",
switch_by_id: "根據句柄 ID 切換到指定窗口",
switch_by_index: "根據網頁索引號切換到指定窗口",
swicth_to_default: "切換到初始窗口",
ws_tip1: "指定尺寸,根據輸入的寬度和高度,設置窗口的大小",
size: "尺寸:",
by_pixel: "以像素為單位",
width: "寬度",
height: "高度",
times: "循環次數",
set_times: "設置循環的次數,可輸入變量",
wait_text: "等待文本",
wait_timeout: "等待超時",
wait_for_text: "等待元素等於給定的定值(Text)",
wait_for_ele_pre: "等待元素存在",
wait_for_ele_visible: "等待元素顯示",
wait_for_ele_not_visible: "等待元素不顯示",
wait_for_ele_not_pre: "等待元素不存在",
wait_for_ele_edi: "等待元素可編輯",
wait_for_ele_not_edi: "等待元素不可編輯",
wait_tip: "針對元素的Text屬性指頁面展示出來的文本內容等待超時時間為15000ms",
exe_first: "先執行後判斷",
while_t_1: "先執行後判斷類似 doWhile ,先執行一次循環體再判斷條件",
while_t_2: "變量請使用${var},字符串請加單引號,如:${name} === '張三'",
loop_time_out: "循環超時時間",
operation: '操作',
use_pixel: '指定尺寸(像素為單位)',
fullscreen: '窗口最大化',
program_controller: '流程控製',
input_operation: '輸入操作',
mouse_operation: '鼠標操作',
element_operation: '元素操作',
dialog_operation: '彈窗操作',
browser_operation: '瀏覽器操作',
check_element: "請勾選元素",
check_subitem: '請選擇子分類',
pause: '等待時間',
browser: "瀏覽器",
inner_drag: "元素內部拖拽",
@ -2979,14 +3107,14 @@ const message = {
not_screentshot: "不截圖",
error_step_screenshot: "出現異常截圖",
downloadScreenshot: "下載截圖文件",
description: "備注",
description: "描述",
scenario_title: "場景",
custom_command_title: "指令",
custom_command_label: "自定義指令",
automation_list: "自動化列",
automation_list: "自動化列",
create_custom_command: "創建指令",
create_custom_command_label: "創建自定義指令",
import_by_list_label: "UI列導入",
import_by_list_label: "UI列導入",
open_custom_command_label: "打開指令",
debug_result_label: "調試結果",
scenario_ref_label: "場景引用",
@ -2997,13 +3125,162 @@ const message = {
command_name_label: "指令名稱",
command_steps_label: "指令步驟",
command_step_info: "在右側添加指令步驟",
default_module: "默認模塊",
executing: "正在執行...",
unexecute: "未執行",
check_command: "請勾選指令",
ip: "ip地址",
cword: "詞語",
csentence: "句子",
cparagraph: "段落",
loading: "加載中...",
close_dialog: "關閉",
unknown_scenario: "創建場景",
unknown_instruction: "創建指令",
unknown_element: "創建元素",
scenario_ref_add_warning: "引用的場景/指令步驟及子步驟都無法添加其他步驟!",
batch_editing_steps: "批量編輯步驟",
wait_time_config: "超時時間設置",
wait_element_timeout: "等待元素超時時間",
more_config_options: "更多高級設置選項",
updated_config_info: "更新後選項為",
config_success: "配置成功",
cmdFileUpload: "文件上傳",
relevant_file: "關聯需要上傳的文件",
validate_tips: "判斷實際的結果是否與期望的一致,可添加多條斷言",
instruction: "指令",
screen_tip: "場景步驟如果觸發原生彈窗alert或prompt或不存在頁面時截圖不生效",
ele_css_attr: "元素CSS屬性",
ele_css_tip1: "如元素的 CSS 屬性color 屬性font-size 屬性等",
store_css_attr: "CSS屬性(storeCssAttribute)",
validate_type: "請選擇斷言方式",
expect_value: "期望值",
expect_title: "請輸入期望的網頁標題",
title_tip: "斷言當前窗口的標題是否跟期望值一致,完全匹配則斷言成功,否則失敗",
input_var: "請輸入變量",
input_expect: "請輸入期望值",
var_tip: "斷言變量與期望值是否匹配",
confirm_after_success: "成功後是否點擊確認按鈕",
input_expect_text: "請輸入期望的彈窗文本",
input_window_tip: "僅支持彈窗文本的斷言,選擇是,則斷言成功後會點擊彈窗上的確認按鈕,選擇否,則斷言成功後不點擊任何按鈕",
select_value: "所選元素的值等於期望(SelectedValue)",
select_label: "下拉框選項顯示的文本等於期望(SelectedLabel) ",
not_select_value: "所選元素的值不等於期望(NotSelectedValue) ",
assert_check: "元素被選中(Checked)",
assert_editable: "元素可編輯(Editable)",
assert_element_present: "元素存在(ElementPresent)",
assert_element_not_present: "元素不存在(ElementNotPresent)",
assert_element_not_checked: "元素未被選中(NotChecked)",
assert_element_not_editable: "元素不可編輯(NotEditable)",
assert_element_not_text: "元素文本不等於期望(NotText)",
assert_text: "元素文本等於期望(Text)",
assert_value: "元素值等於期望(Value)",
script_tip: "僅支持js腳本設置的腳本將在瀏覽器執行",
script_type: "腳本類型",
set_var: "設置變量",
async: "異步",
sync: "同步",
return: "有返回值",
no_return: "無返回值",
sample_obj: "普通對象",
is_string: "是否為字符串類型",
like_string_tip: "如字符串、數字、json對象、數組等",
like_string_tip2: "註意:作為對象類型存儲時如果不是一個合法的 js 對象類型(如非法特殊字符、空格影響),可能會生成報告失敗。",
ele_pro: "元素屬性",
like_ele_tip: "如元素的 name 屬性id 屬性value 屬性等",
xpath_locator: "xpath 路徑",
xpath_tip: "只支持 xpath 方式的元素定位,返回的是一個數值",
store: "普通對象(store)",
store_text: "元素文本(storeText)",
store_value: "元素值(storeValue)",
store_attr: "元素屬性(storeAttribute)",
store_xpath_count: "匹配 xpath 的元素數量(storeXpathCount)",
store_window_handle: "窗口 Handle(storeWindowHandle)",
store_title: "網頁標題(storeTitle)",
wait_time: "等待時間",
per_tip: "啟用性能模式後將減少內存和cpu的占用",
fail_over: "失敗終止",
validate_tip: "勾選表示為硬斷言assert如果斷言失敗程序會終止。不勾選表示為軟斷言verify如果斷言失敗程序不會終止。",
scenario: "場景",
extract_type: "請選擇提取信息類型",
input_handle_name: "請輸入存儲窗口 Handle 變量名",
extract_tip: "將提取的內容保存到變量中",
input_window_title: "請輸入存儲網頁標題變量名",
revoke: "撤回",
is_required: "是否必填",
remark: "備註",
result: "執行結果",
var_step: "變量產生步驟",
param_required_tip: "調試保存後,自動校驗變量使用情況, 必填為自定義步驟內使用到的參數;非必填為自定義步驟內未使用到的冗余參數",
name: "名稱",
parameter_configuration: "參數配置",
enter_parameters: "入參",
out_parameters: "出參",
opt_ele: "操作元素",
dst_ele: "目標元素",
drag_type: "拖拽方式",
drag_end_point: "被拖拽的元素最終的位置",
add_file: "添加文件",
file: "文件: 【",
been_deleted: "】 已被刪除!請重新選擇文件!",
click_force: "強製點擊",
click_tip_3: "勾選,元素被遮擋,可強製點擊",
pls_sel_loctype: "請選擇定位類型",
pls_input_locator: "請填寫定位",
import_template: "導入模板",
download_template: "下載模板",
import_desc: "導入說明",
el_import_tip_1: "1.如果導入的ID已存在,則更新元素;",
el_import_tip_2: "2.如果導入的ID為空或ID不存在則新增元素;",
el_import: "元素導入",
empty_text: "暫無數據",
confirm_del_el: "確認刪除元素 ",
confirm_del: "確認刪除 ",
confirm_del_in : "確認刪除指令 ",
deng: "等 ",
ge_instruction: "個指令 ",
ge_el: "個元素 ",
ge_scenario: " 個場景 ",
view_ref: "查看引用",
unplanned_element: "未規劃元素",
scenario_instruction: "場景/指令",
element_beening: "模塊下的元素被",
element_beening_desc: "元素被場景",
reference: "引用",
continue_or_not: "是否繼續",
continue_or_not_delete: "是否繼續刪除",
unplanned_scenario: "未規劃場景",
unplanned_module: "未規劃模塊",
confrim_del_node: "確定刪除節點 ",
and_all_sub_node: " 及其子節點下所有資源?",
instruction_is_referenced: "指令被引用:",
scenario_is_referenced: "場景被引用:",
confirm_del_ins: "確認刪除指令",
confirm_del_scen: "確認刪除場景",
check_grid: "連接失敗,請檢查 selenium-grid 服務狀態",
check_grid_ip: "連接失敗,請檢查 selenium-grid 地址信息",
view_config: "查看配置信息",
config_ip: "沒有檢測到本地ip和端口信息請在",
personal_info: "個人信息",
in_config: "中設置",
assert_in_text: "元素文本包含期望(InText)",
assert_not_in_text: "元素文本不包含期望(NotInText)",
equal: "等於",
not_equal: "不等於",
contain: "包含",
not_contain: "不包含",
greater: "大於",
greater_equal: "大於等於",
lower: "小於",
lower_equal: "小於等於",
null: "空",
not_null: "非空",
assertion_configuration: "斷言配置",
smart_variable_enable: "優先使用當前場景變量,沒有則使用原場景變量",
use_origin_variable_scene: "使用原場景變量",
use_origin_env_run: "使用原場景環境執行",
open_new: "追加頁面",
open_new_tip: "追加頁面在新的頁面打開url不勾選覆蓋當前url",
},
project_application: {
workstation: {

View File

@ -84,6 +84,7 @@ export class HttpConfig extends BaseConfig {
this.protocol = 'https';
this.port = undefined;
this.conditions = [];
this.cookie = options.cookie ? options.cookie : [new Cookie()];
this.isMock = false;
this.description = "";
this.set(options);
@ -93,6 +94,7 @@ export class HttpConfig extends BaseConfig {
initOptions(options = {}) {
options.headers = options.headers || [new KeyValue()];
options.cookie = options.cookie || [new Cookie()];
return options;
}
}
@ -111,6 +113,20 @@ export class Host extends BaseConfig {
}
}
export class Cookie extends BaseConfig {
constructor(options = {}) {
super();
this.cookie = undefined;
this.expireTime = "1D";
this.updateTime = undefined;
this.relevanceId = undefined;
this.enable = false;
this.set(options);
}
}
/* ---------- Functions ------- */

View File

@ -411,7 +411,7 @@ export function downloadPDF(ele, pdfName) {
let imgWidth = 595.28;
let imgHeight = (595.28 / contentWidth) * contentHeight;
let pageData = canvas.toDataURL("image/jpeg", 0.1);
let pageData = canvas.toDataURL("image/jpeg", 1.0);
let pdf = new JsPDF("", "pt", "a4");
//有两个高度需要区分一个是html页面的实际高度和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围无需分页

View File

@ -541,9 +541,11 @@ export function getCustomFieldValue(row, field, members) {
return val;
} else if (field.type === 'multipleInput') {
let val = '';
item.value.forEach(i => {
val += i + ' ';
});
if (item.value instanceof Array) {
item.value.forEach(i => {
val += i + ' ';
});
}
return val;
} else if (field.type === 'datetime' || field.type === 'date') {
return datetimeFormat(item.value);

View File

@ -18,6 +18,10 @@ export function getCurrentProjectID() {
return getCurrentUser().lastProjectId;
}
export function setCurrentProjectID(projectId) {
sessionStorage.setItem(PROJECT_ID, projectId);
}
export function getCurrentUser() {
try {
const store = useUserStore();

View File

@ -421,7 +421,7 @@ public class FileUtils {
public static byte[] fileToByte(File tradeFile) {
byte[] buffer = null;
try (FileInputStream fis = new FileInputStream(tradeFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1) {
@ -429,7 +429,7 @@ public class FileUtils {
}
buffer = bos.toByteArray();
} catch (Exception e) {
LoggerUtil.error(e);
LogUtil.error(e);
}
return buffer;
}

View File

@ -321,11 +321,17 @@ public class PerformanceTestService {
loadTest.setCreateUser(SessionUtils.getUserId());
loadTest.setOrder(oldLoadTest.getOrder());
loadTest.setRefId(oldLoadTest.getRefId());
if (oldLoadTest.getLatest()) {
loadTest.setLatest(false);
}
//插入文件
copyLoadTestFiles(testId, loadTest.getId());
loadTestMapper.insertSelective(loadTest);
}
//checkAndSetLatestVersion(loadTest.getRefId());
String defaultVersion = baseProjectVersionMapper.getDefaultVersion(request.getProjectId());
if (StringUtils.equalsIgnoreCase(request.getVersionId(), defaultVersion)) {
checkAndSetLatestVersion(loadTest.getRefId());
}
return loadTest;
}

View File

@ -70,6 +70,8 @@ public class GroupService {
@Resource
private BaseUserService baseUserService;
private static final String GLOBAL = "global";
private static final String SUPER_GROUP = "super_group";
// 服务权限拼装顺序
private static final String[] servicePermissionLoadOrder = {MicroServiceName.PROJECT_MANAGEMENT,
@ -223,6 +225,9 @@ public class GroupService {
}
public void editGroupPermission(EditGroupRequest request) {
if (StringUtils.equals(request.getUserGroupId(), SUPER_GROUP)) {
return;
}
List<GroupPermission> permissions = request.getPermissions();
if (CollectionUtils.isEmpty(permissions)) {
return;

View File

@ -283,14 +283,6 @@ public class ProjectService {
return project;
}
public boolean isThirdPartTemplate(Project project) {
if (project.getThirdPartTemplate() != null && project.getThirdPartTemplate() && project.getPlatform().equals(IssuesManagePlatform.Jira.name())) {
return true;
}
return false;
}
public List<Project> getByCaseTemplateId(String templateId) {
ProjectExample example = new ProjectExample();
example.createCriteria().andCaseTemplateIdEqualTo(templateId);

View File

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

View File

@ -45,7 +45,7 @@
</el-input>
</div>
<!-- 接口测试配置 -->
<!-- 接口测试配置 -->
<form-section :title="$t('commons.api')" :init-active=true>
<p>{{ $t('api_test.request.headers') }}</p>
<el-row>
@ -55,28 +55,14 @@
</el-link>
</el-row>
<ms-api-key-value :items="condition.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
<div style="margin-top: 20px">
<el-button v-if="!condition.id" type="primary" style="float: right" size="mini" @click="add">
{{ $t('commons.add') }}
</el-button>
<div v-else>
<el-button type="primary" style="float: right;margin-left: 10px" size="mini" @click="clear">
{{ $t('commons.clear') }}
</el-button>
<el-button type="primary" style="float: right" size="mini" @click="update(condition)">{{
$t('commons.update')
}}
</el-button>
</div>
</div>
</form-section>
<!-- UI 配置 -->
<form-section :title="$t('commons.ui_test')" :init-active="false">
<!-- 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>
<span style="margin-right: 10px;">{{ $t("ui.browser") }}</span>
<el-select
size="mini"
v-model="httpConfig.browser"
@ -102,13 +88,29 @@
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="24">
<ms-ui-scenario-cookie-table :items="httpConfig.cookie"/>
</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">
<el-button v-if="!condition.id" type="primary" style="float: right" size="mini" @click="add">
{{ $t('commons.add') }}
</el-button>
<div v-else>
<el-button type="primary" style="float: right;margin-left: 10px" size="mini" @click="clear">
{{ $t('commons.clear') }}
</el-button>
<el-button type="primary" style="float: right" size="mini" @click="update(condition)">{{
$t('commons.update')
}}
</el-button>
</div>
</div>
</el-form-item>
</div>
<div class="ms-border">
@ -174,9 +176,10 @@ export default {
name: "MsEnvironmentHttpConfig",
components: {
MsUiScenarioCookieTable,
FormSection, MsApiKeyValue, MsSelectTree, MsTableOperatorButton, BatchAddParameter, MsInstructionsIcon},
FormSection, MsApiKeyValue, MsSelectTree, MsTableOperatorButton, BatchAddParameter, MsInstructionsIcon
},
props: {
httpConfig: new HttpConfig({cookie: []}),
httpConfig: new HttpConfig(),
projectId: String,
isReadOnly: {
type: Boolean,
@ -185,9 +188,6 @@ export default {
},
created() {
this.list();
if (this.httpConfig && !this.httpConfig.cookie) {
this.$set(this.httpConfig, "cookie", []);
}
},
data() {
let socketValidator = (rule, value, callback) => {
@ -220,7 +220,7 @@ export default {
port: 0,
headers: [new KeyValue()],
headlessEnabled: true,
browser : 'CHROME'
browser: 'CHROME'
},
beforeCondition: {},
browsers: [
@ -354,7 +354,7 @@ export default {
list() {
if (this.projectId) {
this.result = getApiModuleByProjectIdAndProtocol(this.projectId, "HTTP").then((response) => {
if (response.data && response.data !== null) {
if (response.data && response.data !== null) {
this.moduleOptions = response.data;
}
});
@ -507,6 +507,9 @@ export default {
this.$refs["httpConfig"].validate((valid) => {
isValidate = valid;
});
// if (this.$refs.cookieTable && !this.$refs.cookieTable.validate()) {
// return false;
// }
return isValidate;
},
batchAdd() {

View File

@ -16,10 +16,10 @@
:data="variables"
:total="items.length"
:screen-height="'100px'"
:batch-operators="batchButtons"
:remember-order="true"
:highlightCurrentRow="true"
@refresh="onChange"
:enable-selection="false"
ref="variableTable"
>
<ms-table-column prop="cookie" label="cookie" min-width="160">
@ -28,44 +28,44 @@
v-model="scope.row.cookie"
size="mini"
:placeholder="$t('cookie')"
@change="change"
@change="change(scope.row)"
/>
</template>
</ms-table-column>
<ms-table-column
prop="userName"
:label="$t('api_test.request.sql.username')"
min-width="200"
>
<template slot-scope="scope">
<el-input
v-model="scope.row.userName"
size="mini"
maxlength="200"
:placeholder="$t('api_test.request.sql.username')"
show-word-limit
@change="change"
/>
</template>
</ms-table-column>
<!-- <ms-table-column-->
<!-- prop="userName"-->
<!-- :label="$t('api_test.request.sql.username')"-->
<!-- min-width="200"-->
<!-- >-->
<!-- <template slot-scope="scope">-->
<!-- <el-input-->
<!-- v-model="scope.row.userName"-->
<!-- size="mini"-->
<!-- maxlength="200"-->
<!-- :placeholder="$t('api_test.request.sql.username')"-->
<!-- show-word-limit-->
<!-- @change="change"-->
<!-- />-->
<!-- </template>-->
<!-- </ms-table-column>-->
<ms-table-column
prop="password"
:label="$t('api_test.request.tcp.password')"
min-width="140"
>
<template slot-scope="scope">
<el-input
v-model="scope.row.password"
size="mini"
maxlength="200"
show-password
:placeholder="$t('api_test.request.tcp.password')"
@change="change"
/>
</template>
</ms-table-column>
<!-- <ms-table-column-->
<!-- prop="password"-->
<!-- :label="$t('api_test.request.tcp.password')"-->
<!-- min-width="140"-->
<!-- >-->
<!-- <template slot-scope="scope">-->
<!-- <el-input-->
<!-- v-model="scope.row.password"-->
<!-- size="mini"-->
<!-- maxlength="200"-->
<!-- show-password-->
<!-- :placeholder="$t('api_test.request.tcp.password')"-->
<!-- @change="change"-->
<!-- />-->
<!-- </template>-->
<!-- </ms-table-column>-->
<ms-table-column
prop="description"
@ -74,7 +74,7 @@
:editContent="'aaa'"
>
<template slot-scope="scope">
<mini-timing-item :expr="scope.row.expireTime"></mini-timing-item>
<mini-timing-item :expr.sync="scope.row.expireTime" @chooseChange="change(scope.row)"></mini-timing-item>
</template>
</ms-table-column>
@ -95,7 +95,8 @@
<el-switch v-model="scope.row.enable" size="mini"></el-switch>
<el-tooltip
effect="dark"
:content="$t('关联登录场景/指令')"
:content="$t('environment.relevance_ui')"
v-if="!existCookieConfig"
placement="top-start"
>
<el-button
@ -103,26 +104,26 @@
circle
size="mini"
@click="openRelevance"
v-if="!existCookieConfig"
style="margin-left: 10px"
/>
</el-tooltip>
<el-dropdown @command="handleCommand" v-if="existCookieConfig">
<el-button
icon="el-icon-paperclip"
circle
size="mini"
style="margin-left: 10px"
/>
<el-dropdown @command="handleCommand" v-if="existCookieConfig">
<el-button
icon="el-icon-paperclip"
circle
size="mini"
style="margin-left: 10px"
/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="view">{{ $t("environment.view_ui_relevane") }}</el-dropdown-item>
<el-dropdown-item command="cancelRelevance">{{ $t("environment.cancel_ui_relevane") }}
</el-dropdown-item>
<el-dropdown-item command="relevance">{{ $t("environment.re_ui_relevane") }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="view">查看关联</el-dropdown-item>
<el-dropdown-item command="cancelRelevance">取消关联</el-dropdown-item>
<el-dropdown-item command="relevance">重新关联</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-tooltip>
</template>
</ms-table-column>
</ms-table>
@ -158,6 +159,7 @@ import VariableImport from "metersphere-frontend/src/components/environment/Vari
import _ from "lodash";
import MiniTimingItem from "metersphere-frontend/src/components/environment/commons/MiniTimingItem";
import UiScenarioEditRelevance from "@/business/menu/environment/components/ui-related/UiScenarioEditRelevance";
import {getCurrentProjectID, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
export default {
name: "MsUiScenarioCookieTable",
@ -218,8 +220,8 @@ export default {
},
watch: {
items: {
handler(v) {
this.variables = v;
handler(val) {
this.variables = val;
this.sortParameters();
},
immediate: true,
@ -235,7 +237,7 @@ export default {
},
immediate: true,
deep: true,
}
},
},
methods: {
remove: function (index) {
@ -271,6 +273,7 @@ export default {
this.$t("load_test.param_is_duplicate")
);
}
this.variables[0].updateTime = new Date().getTime();
this.$emit("change", this.variables);
},
changeType(data) {
@ -314,23 +317,22 @@ export default {
},
sortParameters() {
let index = 1;
this.variables.forEach((item) => {
item.num = index;
if (!item.type || item.type === "text") {
item.type = "CONSTANT";
}
if (!item.id) {
item.id = getUUID();
}
if (item.remark) {
this.$set(item, "description", item.remark);
item.remark = undefined;
}
if (!item.scope) {
this.$set(item, "scope", "api");
}
index++;
});
if (this.variables) {
this.variables.forEach((item) => {
item.num = index;
if (!item.type || item.type === "text") {
item.type = "CONSTANT";
}
if (!item.id) {
item.id = getUUID();
}
if (item.remark) {
this.$set(item, "description", item.remark);
item.remark = undefined;
}
index++;
});
}
},
handleDeleteBatch() {
operationConfirm(
@ -509,17 +511,36 @@ export default {
handleCommand(c) {
switch (c) {
case "view":
this.redirectPage(this.variables[0].relevanceId);
break;
case "cancelRelevance":
this.variables[0].relevanceId = null;
this.$success(this.$t("organization.integration.successful_operation"));
break;
case "relevance":
this.openRelevance();
this.openRelevance("scenario", "scenario",);
break;
default:
break;
}
},
redirectPage(resourceId) {
let uuid = getUUID().substring(1, 5);
let projectId = getCurrentProjectID();
let workspaceId = getCurrentWorkspaceId();
let prefix = '/#';
if (
this.$route &&
this.$route.path.startsWith('/#')
) {
prefix = '';
}
let path = `/ui/automation/?redirectID=${uuid}&dataType=scenario&projectId=${projectId}&workspaceId=${workspaceId}&resourceId=${resourceId}`;
let data = this.$router.resolve({
path: path,
});
window.open(data.href, '_blank');
},
openRelevance() {
this.$refs.relevanceUiDialog.open();
},
@ -527,19 +548,31 @@ export default {
this.variables[0].relevanceId = id.keys().next().value.id;
this.$refs.relevanceUiDialog.close();
this.$success(this.$t('commons.save_success'));
},
validate() {
if (!this.variables[0].enable) {
return true;
}
let cookieConfig = this.variables[0];
if (!cookieConfig) {
this.$warning(this.$t("配置错误"));
return false;
}
if (!cookieConfig.expireTime || cookieConfig.expireTime === "") {
this.$warning(this.$t("environment.need_expire_time"));
return false;
}
if (!cookieConfig.relevanceId) {
this.$warning(this.$t("environment.need_relevance_ui_scenario"));
return false;
}
return true;
}
},
created() {
if (this.items.length === 0) {
this.items.push(new KeyValue({enable: true, expireTime: '1Y'}));
} else {
// api
_.forEach(this.items, item => {
if (!item.scope) {
this.$set(item, "scope", "api");
}
})
this.variables = this.items;
if (!this.items || this.items.length === 0) {
this.items = [];
this.items.push(new KeyValue({id: getUUID(), enable: true, expireTime: '1M'}));
}
},
};

View File

@ -35,6 +35,15 @@ const message = {
},
project_version: {
version_time: 'Version cycle',
},
environment: {
export_variable_tip : "Export interface test variables",
need_expire_time : "Please enter an expiration time",
need_relevance_ui_scenario : "Please associate the login scenario",
view_ui_relevane : "View Relevane",
cancel_ui_relevane : "Relevant",
re_ui_relevane : "Relevane",
relevance_ui : "Relevance login scene/command",
}
}

View File

@ -37,7 +37,13 @@ const message = {
version_time: '版本周期',
},
environment: {
export_variable_tip : "导出接口测试变量"
export_variable_tip : "导出接口测试变量",
need_expire_time : "请输入过期时间",
need_relevance_ui_scenario : "请关联登录场景",
view_ui_relevane : "查看关联",
cancel_ui_relevane : "取消关联",
re_ui_relevane : "重新关联",
relevance_ui : "关联登录场景/指令",
}
}

View File

@ -35,6 +35,15 @@ const message = {
},
project_version: {
version_time: '版本週期',
},
environment: {
export_variable_tip : "導出接口測試變量",
need_expire_time : "請輸入過期時間",
need_relevance_ui_scenario : "請關聯登錄場景",
view_ui_relevane : "查看關聯",
cancel_ui_relevane : "取消關聯",
re_ui_relevane : "重新關聯",
relevance_ui : "關聯登錄場景/指令",
}
}

View File

@ -5,7 +5,6 @@ import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
@ -14,6 +13,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
@ -24,8 +24,6 @@ public class CleanSessionJob {
@Resource
private RedisIndexedSessionRepository redisIndexedSessionRepository;
@Value("${spring.session.timeout:43200s}")
private Duration timeout;
/**
* 清理没有绑定user的session
@ -55,7 +53,7 @@ public class CleanSessionJob {
userCount.put(userId, count);
LogUtil.info(key + " : " + userId + " 过期时间: " + expire);
if (expire != null && expire.intValue() == -1) {
redisIndexedSessionRepository.getSessionRedisOperations().expire(key, timeout);
redisIndexedSessionRepository.getSessionRedisOperations().expire(key, Duration.of(30, ChronoUnit.SECONDS));
}
}
}

View File

@ -235,6 +235,10 @@ public class GroupService {
}
public void editGroupPermission(EditGroupRequest request) {
// 超级用户组禁止修改权限
if (StringUtils.equals(request.getUserGroupId(), SUPER_GROUP)) {
return;
}
List<GroupPermission> permissions = request.getPermissions();
if (CollectionUtils.isEmpty(permissions)) {
return;

View File

@ -18,8 +18,10 @@ import io.metersphere.platform.loader.PlatformPluginManager;
import io.metersphere.request.IntegrationRequest;
import io.metersphere.utils.PluginManagerUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -46,6 +48,9 @@ public class PlatformPluginService {
private BaseIntegrationService baseIntegrationService;
@Resource
private KafkaTemplate<String, String> kafkaTemplate;
@Resource
@Lazy
private PluginService pluginService;
private static final String PLUGIN_DOWNLOAD_URL = "https://github.com/metersphere/metersphere-platform-plugin";
@ -58,6 +63,13 @@ public class PlatformPluginService {
return pluginManager;
}
/**
* 新开一个事务保证发送 kafka 消息是在事务提交之后
* 因为接受消息需要判断数据库是否有数据
* @param file
* @return
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public PluginWithBLOBs addPlatformPlugin(MultipartFile file) {
String id = UUID.randomUUID().toString();
@ -75,7 +87,6 @@ public class PlatformPluginService {
plugin.setPluginId(pluginMetaInfo.getKey() + "-" + pluginMetaInfo.getVersion());
plugin.setScriptId(pluginMetaInfo.getKey());
plugin.setSourcePath("");
// plugin.setFormOption(item.getFormOption());
plugin.setFormScript(JSON.toJSONString(map));
plugin.setClazzName("");
plugin.setSourceName(file.getOriginalFilename());
@ -84,11 +95,16 @@ public class PlatformPluginService {
plugin.setCreateUserId(SessionUtils.getUserId());
plugin.setXpack(pluginMetaInfo.isXpack());
plugin.setScenario(PluginScenario.platform.name());
// 初始化项目默认节点
kafkaTemplate.send(KafkaTopicConstants.PLATFORM_PLUGIN_ADD, id);
pluginService.addPlugin(plugin);
return plugin;
}
public void notifiedPlatformPluginAdd(String pluginId) {
// 初始化项目默认节点
kafkaTemplate.send(KafkaTopicConstants.PLATFORM_PLUGIN_ADD, pluginId);
}
/**
* 查询所有平台插件并加载
*/

View File

@ -115,7 +115,7 @@ public class PluginService {
checkPluginExist(file);
if (StringUtils.equalsIgnoreCase(scenario, PluginScenario.platform.name())) {
PluginWithBLOBs plugin = platformPluginService.addPlatformPlugin(file);
addPlugin(plugin);
platformPluginService.notifiedPlatformPluginAdd(plugin.getId());
} else {
List<PluginWithBLOBs> plugins = apiPluginService.addApiPlugin(file);
plugins.forEach(this::addPlugin);

View File

@ -126,7 +126,7 @@ public class SystemProjectService {
if (quotaService != null) {
quotaService.projectUseDefaultQuota(pjId);
}
// 创建默认版本
addProjectVersion(project);
// 初始化项目应用管理
@ -339,11 +339,6 @@ public class SystemProjectService {
return project;
}
public boolean isThirdPartTemplate(Project project) {
return project.getThirdPartTemplate() != null && project.getThirdPartTemplate() && project.getPlatform().equals(IssuesManagePlatform.Jira.name());
}
public List<Project> getByCaseTemplateId(String templateId) {
ProjectExample example = new ProjectExample();
example.createCriteria().andCaseTemplateIdEqualTo(templateId);

View File

@ -0,0 +1,110 @@
-- v2_5_modify_workspace_name_length
-- 创建人 liyuhao
-- 创建时间 2022-11-29 13:19:51
ALTER TABLE workspace
MODIFY name varchar(100) NOT NULL COMMENT 'Workspace name';
-- v2_5_init_super_group_permission
-- 创建人 liyuhao
-- 创建时间 2022-12-05 14:38:10
DROP PROCEDURE IF EXISTS init_super_permission;
DELIMITER //
CREATE PROCEDURE init_super_permission()
BEGIN
SET @permission_str = 'SYSTEM_USER:READ, SYSTEM_USER:READ+CREATE, SYSTEM_USER:READ+IMPORT, SYSTEM_USER:READ+EDIT,
SYSTEM_USER:READ+DELETE, SYSTEM_USER:READ+EDIT_PASSWORD, SYSTEM_WORKSPACE:READ, SYSTEM_WORKSPACE:READ+CREATE,
SYSTEM_WORKSPACE:READ+EDIT, SYSTEM_WORKSPACE:READ+DELETE, SYSTEM_GROUP:READ, SYSTEM_GROUP:READ+CREATE,
SYSTEM_GROUP:READ+EDIT, SYSTEM_GROUP:READ+SETTING_PERMISSION, SYSTEM_GROUP:READ+DELETE, SYSTEM_TEST_POOL:READ,
SYSTEM_TEST_POOL:READ+CREATE, SYSTEM_TEST_POOL:READ+EDIT, SYSTEM_TEST_POOL:READ+DELETE,
SYSTEM_SETTING:READ, SYSTEM_SETTING:READ+EDIT, SYSTEM_QUOTA:READ, SYSTEM_QUOTA:READ+EDIT,
SYSTEM_AUTH:READ, SYSTEM_AUTH:READ+EDIT, SYSTEM_OPERATING_LOG:READ, WORKSPACE_SERVICE:READ,
WORKSPACE_SERVICE:READ+EDIT, WORKSPACE_USER:READ, WORKSPACE_USER:READ+CREATE, WORKSPACE_USER:READ+EDIT,
WORKSPACE_USER:READ+DELETE, WORKSPACE_PROJECT_MANAGER:READ, WORKSPACE_PROJECT_MANAGER:READ+CREATE,
WORKSPACE_PROJECT_MANAGER:READ+EDIT, WORKSPACE_PROJECT_MANAGER:READ+DELETE,
WORKSPACE_PROJECT_MANAGER:READ+ENVIRONMENT_CONFIG, WORKSPACE_PROJECT_MANAGER:READ+ADD_USER,
WORKSPACE_PROJECT_MANAGER:READ+EDIT_USER, WORKSPACE_PROJECT_MANAGER:READ+DELETE_USER,
WORKSPACE_PROJECT_ENVIRONMENT:READ, WORKSPACE_PROJECT_ENVIRONMENT:READ+CREATE,
WORKSPACE_PROJECT_ENVIRONMENT:READ+EDIT, WORKSPACE_PROJECT_ENVIRONMENT:READ+DELETE,
WORKSPACE_PROJECT_ENVIRONMENT:READ+COPY, WORKSPACE_PROJECT_ENVIRONMENT:READ+IMPORT,
WORKSPACE_PROJECT_ENVIRONMENT:READ+EXPORT, WORKSPACE_PROJECT_ENVIRONMENT:READ+CREATE_GROUP,
WORKSPACE_PROJECT_ENVIRONMENT:READ+EDIT_GROUP, WORKSPACE_PROJECT_ENVIRONMENT:READ+COPY_GROUP,
WORKSPACE_PROJECT_ENVIRONMENT:READ+DELETE_GROUP, WORKSPACE_QUOTA:READ, WORKSPACE_QUOTA:READ+EDIT,
WORKSPACE_OPERATING_LOG:READ, SYSTEM_PLUGIN:UPLOAD, SYSTEM_PLUGIN:DEL, SYSTEM_PLUGIN:READ,
PERSONAL_INFORMATION:READ+EDIT, PERSONAL_INFORMATION:READ+API_KEYS, PERSONAL_INFORMATION:READ+EDIT_PASSWORD,
PERSONAL_INFORMATION:READ+THIRD_ACCOUNT, PERSONAL_INFORMATION:READ+UI_SETTING,
PROJECT_USER:READ, PROJECT_USER:READ+CREATE, PROJECT_USER:READ+EDIT, PROJECT_USER:READ+DELETE,
PROJECT_GROUP:READ, PROJECT_GROUP:READ+CREATE, PROJECT_GROUP:READ+EDIT, PROJECT_GROUP:READ+DELETE,
PROJECT_GROUP:READ+SETTING_PERMISSION, PROJECT_MANAGER:READ, PROJECT_MANAGER:READ+EDIT,
PROJECT_APP_MANAGER:READ+EDIT, PROJECT_ENVIRONMENT:READ, PROJECT_ENVIRONMENT:READ+CREATE,
PROJECT_ENVIRONMENT:READ+EDIT, PROJECT_ENVIRONMENT:READ+DELETE, PROJECT_ENVIRONMENT:READ+COPY,
PROJECT_ENVIRONMENT:READ+IMPORT, PROJECT_ENVIRONMENT:READ+EXPORT, PROJECT_OPERATING_LOG:READ,
PROJECT_FILE:READ, PROJECT_FILE:READ+UPLOAD+JAR, PROJECT_FILE:READ+DOWNLOAD+JAR, PROJECT_FILE:READ+DELETE+JAR,
PROJECT_FILE:READ+BATCH+DELETE, PROJECT_FILE:READ+BATCH+DOWNLOAD, PROJECT_FILE:READ+BATCH+MOVE,
PROJECT_TEMPLATE:READ, PROJECT_TEMPLATE:READ+CASE_TEMPLATE, PROJECT_TEMPLATE:READ+ISSUE_TEMPLATE,
PROJECT_TEMPLATE:READ+API_TEMPLATE, PROJECT_TEMPLATE:READ+CUSTOM, PROJECT_MESSAGE:READ, PROJECT_MESSAGE:READ+EDIT,
PROJECT_MESSAGE:READ+DELETE, PROJECT_CUSTOM_CODE:READ, PROJECT_CUSTOM_CODE:READ+CREATE, PROJECT_CUSTOM_CODE:READ+EDIT,
PROJECT_CUSTOM_CODE:READ+DELETE, PROJECT_CUSTOM_CODE:READ+COPY, PROJECT_VERSION:READ, PROJECT_VERSION:READ+CREATE,
PROJECT_VERSION:READ+EDIT, PROJECT_VERSION:READ+DELETE, PROJECT_VERSION:READ+ENABLE, PROJECT_ERROR_REPORT_LIBRARY:READ,
PROJECT_ERROR_REPORT_LIBRARY:READ+CREATE, PROJECT_ERROR_REPORT_LIBRARY:READ+EDIT, PROJECT_ERROR_REPORT_LIBRARY:READ+DELETE,
PROJECT_ERROR_REPORT_LIBRARY:READ+BATCH_DELETE, PROJECT_TRACK_HOME:READ, PROJECT_TRACK_CASE:READ,
PROJECT_TRACK_CASE:READ+CREATE, PROJECT_TRACK_CASE:READ+EDIT, PROJECT_TRACK_CASE:READ+DELETE, PROJECT_TRACK_CASE:READ+COPY,
PROJECT_TRACK_CASE:READ+IMPORT, PROJECT_TRACK_CASE:READ+EXPORT, PROJECT_TRACK_CASE:READ+RECOVER,
PROJECT_TRACK_CASE:READ+BATCH_EDIT, PROJECT_TRACK_CASE:READ+BATCH_MOVE, PROJECT_TRACK_CASE:READ+BATCH_COPY,
PROJECT_TRACK_CASE:READ+BATCH_DELETE, PROJECT_TRACK_CASE:READ+BATCH_REDUCTION, PROJECT_TRACK_CASE:READ+BATCH_LINK_DEMAND,
PROJECT_TRACK_CASE:READ+GENERATE_DEPENDENCIES, PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC, PROJECT_TRACK_REVIEW:READ,
PROJECT_TRACK_REVIEW:READ+CREATE, PROJECT_TRACK_REVIEW:READ+EDIT, PROJECT_TRACK_REVIEW:READ+DELETE,
PROJECT_TRACK_REVIEW:READ+REVIEW, PROJECT_TRACK_REVIEW:READ+COMMENT, PROJECT_TRACK_REVIEW:READ+RELEVANCE_OR_CANCEL,
PROJECT_TRACK_PLAN:READ, PROJECT_TRACK_PLAN:READ+CREATE, PROJECT_TRACK_PLAN:READ+EDIT, PROJECT_TRACK_PLAN:READ+DELETE,
PROJECT_TRACK_PLAN:READ+COPY, PROJECT_TRACK_PLAN:READ+RUN, PROJECT_TRACK_PLAN:READ+CASE_BATCH_RUN,
PROJECT_TRACK_PLAN:READ+CASE_BATCH_EDIT, PROJECT_TRACK_PLAN:READ+BATCH_DELETE, PROJECT_TRACK_PLAN:READ+SCHEDULE,
PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL, PROJECT_TRACK_PLAN:READ+CASE_BATCH_DELETE, PROJECT_TRACK_ISSUE:READ,
PROJECT_TRACK_ISSUE:READ+CREATE, PROJECT_TRACK_ISSUE:READ+EDIT, PROJECT_TRACK_ISSUE:READ+DELETE, PROJECT_TRACK_REPORT:READ,
PROJECT_TRACK_REPORT:READ+DELETE, PROJECT_TRACK_REPORT:READ+EXPORT, PROJECT_API_HOME:READ, PROJECT_API_DEFINITION:READ,
PROJECT_API_DEFINITION:READ+CREATE_API, PROJECT_API_DEFINITION:READ+EDIT_API, PROJECT_API_DEFINITION:READ+DELETE_API,
PROJECT_API_DEFINITION:READ+COPY_API, PROJECT_API_DEFINITION:READ+CREATE_CASE, PROJECT_API_DEFINITION:READ+EDIT_CASE,
PROJECT_API_DEFINITION:READ+DELETE_CASE, PROJECT_API_DEFINITION:READ+COPY_CASE, PROJECT_API_DEFINITION:READ+IMPORT_API,
PROJECT_API_DEFINITION:READ+EXPORT_API, PROJECT_API_DEFINITION:READ+TIMING_SYNC, PROJECT_API_DEFINITION:READ+CREATE_PERFORMANCE,
PROJECT_API_DEFINITION:READ+RUN, PROJECT_API_DEFINITION:READ+DEBUG, PROJECT_API_DEFINITION:READ+MOCK, PROJECT_API_SCENARIO:READ,
PROJECT_API_SCENARIO:READ+CREATE, PROJECT_API_SCENARIO:READ+EDIT, PROJECT_API_SCENARIO:READ+DELETE, PROJECT_API_SCENARIO:READ+COPY,
PROJECT_API_SCENARIO:READ+RUN, PROJECT_API_SCENARIO:READ+DEBUG, PROJECT_API_SCENARIO:READ+SCHEDULE, PROJECT_API_SCENARIO:READ+IMPORT_SCENARIO,
PROJECT_API_SCENARIO:READ+EXPORT_SCENARIO, PROJECT_API_SCENARIO:READ+MOVE_BATCH, PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE,
PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH, PROJECT_API_SCENARIO:READ+BATCH_COPY, PROJECT_API_REPORT:READ,
PROJECT_API_REPORT:READ+DELETE, PROJECT_API_REPORT:READ+EXPORT, PROJECT_UI_ELEMENT:READ, PROJECT_UI_ELEMENT:READ+CREATE,
PROJECT_UI_ELEMENT:READ+EDIT, PROJECT_UI_ELEMENT:READ+DELETE, PROJECT_UI_ELEMENT:READ+COPY, PROJECT_UI_SCENARIO:READ,
PROJECT_UI_SCENARIO:READ+CREATE, PROJECT_UI_SCENARIO:READ+EDIT, PROJECT_UI_SCENARIO:READ+DELETE, PROJECT_UI_SCENARIO:READ+COPY,
PROJECT_UI_SCENARIO:READ+RUN, PROJECT_UI_SCENARIO:READ+DEBUG, PROJECT_UI_SCENARIO:READ+IMPORT_SCENARIO,
PROJECT_UI_SCENARIO:READ+EXPORT_SCENARIO, PROJECT_UI_SCENARIO:READ+MOVE_BATCH, PROJECT_UI_SCENARIO:READ+BATCH_COPY, PROJECT_UI_REPORT:READ,
PROJECT_UI_REPORT:READ+DELETE, PROJECT_PERFORMANCE_HOME:READ, PROJECT_PERFORMANCE_TEST:READ, PROJECT_PERFORMANCE_TEST:READ+CREATE,
PROJECT_PERFORMANCE_TEST:READ+EDIT, PROJECT_PERFORMANCE_TEST:READ+DELETE, PROJECT_PERFORMANCE_TEST:READ+COPY,
PROJECT_PERFORMANCE_TEST:READ+RUN, PROJECT_PERFORMANCE_TEST:READ+SCHEDULE, PROJECT_PERFORMANCE_REPORT:READ,
PROJECT_PERFORMANCE_REPORT:READ+DELETE, PROJECT_PERFORMANCE_REPORT:READ+EXPORT, PROJECT_PERFORMANCE_REPORT:READ+COMPARE,
PROJECT_REPORT_ANALYSIS:READ, PROJECT_REPORT_ANALYSIS:READ+EXPORT, PROJECT_REPORT_ANALYSIS:READ+UPDATE,
PROJECT_REPORT_ANALYSIS:READ+CREATE, PROJECT_ENTERPRISE_REPORT:READ+EXPORT, PROJECT_ENTERPRISE_REPORT:READ+CREATE,
PROJECT_ENTERPRISE_REPORT:READ+DELETE,PROJECT_ENTERPRISE_REPORT:READ+COPY,
PROJECT_ENTERPRISE_REPORT:READ+SCHEDULE, PROJECT_ENTERPRISE_REPORT:READ+EDIT';
SET @i = 1;
SET @count = CHAR_LENGTH(@permission_str) - CHAR_LENGTH(REPLACE(@permission_str, ',', '')) + 1;
WHILE @i <= @count
DO
SET @original_str = SUBSTRING_INDEX(SUBSTRING_INDEX(@permission_str, ',', @i), ',', -1);
SET @permission = TRIM(REPLACE(@original_str, CHAR(10), ''));
SET @module = SUBSTRING_INDEX(@permission, ':', 1);
INSERT INTO user_group_permission (id, group_id, permission_id, module_id)
VALUES (UUID(), 'super_group', @permission, @module);
SET @i = @i + 1;
END WHILE;
END
//
DELIMITER ;
CALL init_super_permission();
DROP PROCEDURE IF EXISTS init_super_permission;
-- v2_5_insert_super_group
-- 创建人 liyuhao
INSERT INTO `group` (id, name, description, `system`, type, create_time, update_time, creator, scope_id)
VALUES ('super_group', '超级管理员(系统)', '拥有系统全部工作空间以及项目的操作权限', 1, 'SYSTEM', UNIX_TIMESTAMP() * 1000,
UNIX_TIMESTAMP() * 1000, 'system', 'system');

View File

@ -99,7 +99,10 @@ export default {
},
isReadOnly() {
return function (data) {
const isDefaultSystemGroup = (this.group.id === 'admin' || this.group.id === 'super_group') && data.resource.id === 'SYSTEM_GROUP';
if (this.group.id === 'super_group') {
return true;
}
const isDefaultSystemGroup = this.group.id === 'admin' && data.resource.id === 'SYSTEM_GROUP';
return this.readOnly || isDefaultSystemGroup;
}
}

View File

@ -44,9 +44,13 @@ export default {
computed: {
isReadOnly() {
return function (permission) {
//
if (this.group.id === 'super_group') {
return true;
}
//
const isSystemGroupPermission = permission.id === 'SYSTEM_GROUP:READ' || permission.id === 'SYSTEM_GROUP:READ+SETTING_PERMISSION';
const isDefaultSystemGroup = (this.group.id === 'admin' || this.group.id === 'super_group') && isSystemGroupPermission;
const isDefaultSystemGroup = this.group.id === 'admin' && isSystemGroupPermission;
return this.readOnly || isDefaultSystemGroup;
}
}

View File

@ -63,8 +63,9 @@
<script>
import MsValidLicense from "./ValidLicense";
import {hasPermission} from "metersphere-frontend/src/utils/permission";
import {getLicense, saveLicense} from "../../../api/license";
import {hasPermission, saveLicense, hasLicense} from "metersphere-frontend/src/utils/permission";
import {getLicense} from "../../../api/license";
import {getModuleList} from "metersphere-frontend/src/api/module";
export default {
name: "MxLicense",
@ -108,7 +109,17 @@ export default {
this.license.licenseCount = value.license.licenseCount;
this.license.status = value.status;
saveLicense(value.status);
window.location.reload();
if (hasLicense()) {
getModuleList()
.then(response => {
let modules = {};
response.data.forEach(m => {
modules[m.key] = m.status;
});
localStorage.setItem('modules', JSON.stringify(modules));
location.reload();
})
}
}
},
}

View File

@ -271,7 +271,7 @@
test_case.review_status, test_case.tags,
test_case.demand_id, test_case.demand_name, test_case.`status`,
test_case.custom_num, test_case.step_model, test_case.create_user,
test_case.ref_id
test_case.ref_id,test_case.latest
</if>
from test_case
left join project_version on project_version.id = test_case.version_id

View File

@ -1,5 +1,6 @@
package io.metersphere.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.metersphere.dto.TestPlanCaseDTO;
import io.metersphere.plan.dto.TestPlanSimpleReportDTO;
import io.metersphere.plan.service.TestPlanReportService;
@ -44,7 +45,7 @@ public class ShareController {
@GetMapping("/report/export/{shareId}/{planId}/{lang}")
public void exportHtmlReport(@PathVariable String shareId, @PathVariable String planId,
@PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException {
@PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
shareInfoService.validate(shareId, planId);
testPlanService.exportPlanReport(planId, lang, response);
}

View File

@ -252,7 +252,7 @@ public class TestPlanController {
}
@GetMapping("/report/export/{planId}/{lang}")
public void exportHtmlReport(@PathVariable String planId, @PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException {
public void exportHtmlReport(@PathVariable String planId, @PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
testPlanService.exportPlanReport(planId, lang, response);
}
@ -262,7 +262,7 @@ public class TestPlanController {
}
@GetMapping("/report/db/export/{reportId}/{lang}")
public void exportHtmlDbReport(@PathVariable String reportId, @PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException {
public void exportHtmlDbReport(@PathVariable String reportId, @PathVariable(required = false) String lang, HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
testPlanService.exportPlanDbReport(reportId, lang, response);
}
@ -376,8 +376,13 @@ public class TestPlanController {
testPlanService.resetStatus(planId);
}
@GetMapping("/ext/report/{planId}")
public TestPlanExtReportDTO getExtReport(@PathVariable String planId) throws JsonProcessingException {
return testPlanService.getExtReport(planId);
@GetMapping("/ext/report/{reportId}")
public TestPlanExtReportDTO getExtReport(@PathVariable String reportId) throws JsonProcessingException {
return testPlanService.getExtInfoByReportId(reportId);
}
@GetMapping("/ext/plan/{planId}")
public TestPlanExtReportDTO getExtPlan(@PathVariable String planId) throws JsonProcessingException {
return testPlanService.getExtInfoByPlanId(planId);
}
}

View File

@ -28,6 +28,7 @@ public class TestPlanSimpleReportDTO extends TestPlanReportContent {
* projectEnvMap: <项目,运行环境>
*/
private String runMode;
private String resourcePool;
private String envGroupName;
private Map<String, List<String>> projectEnvMap;

View File

@ -1,6 +1,7 @@
package io.metersphere.plan.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
@ -559,6 +560,11 @@ public class TestPlanReportService {
resourceId = planExecutionQueues.get(0).getResourceId();
testPlanExecutionQueueMapper.deleteByExample(testPlanExecutionQueueExample);
}
testPlanReportMapper.updateByPrimaryKey(testPlanReport);
//发送通知
testPlanMessageService.checkTestPlanStatusAndSendMessage(testPlanReport, content, isSendMessage);
if (runMode != null && StringUtils.equalsIgnoreCase(runMode, RunModeConstants.SERIAL.name()) && resourceId != null) {
TestPlanExecutionQueueExample queueExample = new TestPlanExecutionQueueExample();
queueExample.createCriteria().andReportIdIsNotNull().andResourceIdEqualTo(resourceId);
@ -573,12 +579,20 @@ public class TestPlanReportService {
TestPlanRequestUtil.changeStringToBoolean(jsonObject);
TestPlanRunRequest runRequest = JSON.parseObject(JSON.toJSONString(jsonObject), TestPlanRunRequest.class);
runRequest.setReportId(testPlanExecutionQueue.getReportId());
testPlanService.runPlan(runRequest);
runRequest.setTestPlanId(testPlan.getId());
try {
HttpHeaderUtils.runAsUser("admin");
//如果运行测试计划的过程中出现异常则整个事务会回滚 删除队列的事务也不会提交也不会执行后面的测试计划
testPlanService.runPlan(runRequest);
} catch (Exception e) {
LogUtil.error("执行队列中的下一个测试计划失败! ", e);
this.finishedTestPlanReport(runRequest.getReportId(), TestPlanReportStatus.FAILED.name());
} finally {
HttpHeaderUtils.clearUser();
}
}
testPlanReportMapper.updateByPrimaryKey(testPlanReport);
}
//发送通知
testPlanMessageService.checkTestPlanStatusAndSendMessage(testPlanReport, content, isSendMessage);
return testPlanReport;
}
@ -1044,6 +1058,16 @@ public class TestPlanReportService {
testPlanReportDTO.setId(reportId);
TestPlanReport testPlanReport = testPlanReportMapper.selectByPrimaryKey(testPlanReportContent.getTestPlanReportId());
testPlanReportDTO.setName(testPlanReport.getName());
TestPlanService testPlanService = CommonBeanFactory.getBean(TestPlanService.class);
TestPlanExtReportDTO extReport = null;
try {
extReport = testPlanService.getExtInfoByReportId(reportId);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
if (extReport != null) {
BeanUtils.copyBean(testPlanReportDTO, extReport);
}
return testPlanReportDTO;
}
@ -1363,7 +1387,7 @@ public class TestPlanReportService {
example.setOrderByClause("create_time desc");
example.createCriteria().andTestPlanIdEqualTo(planId);
List<TestPlanReport> testPlanReports = testPlanReportMapper.selectByExample(example);
if(CollectionUtils.isNotEmpty(testPlanReports)){
if (CollectionUtils.isNotEmpty(testPlanReports)) {
return testPlanReports.get(0).getId();
}
return null;

View File

@ -65,8 +65,6 @@ import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
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.servlet.ServletOutputStream;
@ -1385,15 +1383,22 @@ public class TestPlanService {
return report;
}
public void exportPlanReport(String planId, String lang, HttpServletResponse response) throws UnsupportedEncodingException {
public void exportPlanReport(String planId, String lang, HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
TestPlanSimpleReportDTO report = buildPlanReport(planId, true);
report.setLang(lang);
TestPlanExtReportDTO extReport = getExtInfoByPlanId(planId);
if(extReport != null) {
BeanUtils.copyBean(report, extReport);
}
render(report, response);
}
public void exportPlanDbReport(String reportId, String lang, HttpServletResponse response) throws UnsupportedEncodingException {
public void exportPlanDbReport(String reportId, String lang, HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
TestPlanSimpleReportDTO report = testPlanReportService.getReport(reportId);
TestPlanExtReportDTO extReport = getExtInfoByReportId(reportId);
if(extReport != null) {
BeanUtils.copyBean(report, extReport);
}
Set<String> serviceIdSet = DiscoveryUtil.getServiceIdSet();
if (serviceIdSet.contains(MicroServiceName.API_TEST)) {
report.setApiAllCases(planTestPlanApiCaseService.buildResponse(report.getApiAllCases()));
@ -1543,6 +1548,10 @@ public class TestPlanService {
envMap = planTestPlanApiCaseService.getApiCaseEnv(planId);
Map<String, List<String>> scenarioEnv = planTestPlanScenarioCaseService.getApiScenarioEnv(planId);
if (DiscoveryUtil.hasService(MicroServiceName.UI_TEST)) {
scenarioEnv = mergeUiScenarioEnv(planId, scenarioEnv);
}
Set<String> projectIds = scenarioEnv.keySet();
for (String projectId : projectIds) {
if (envMap.containsKey(projectId)) {
@ -1565,6 +1574,32 @@ public class TestPlanService {
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) {
//检查测试计划下有没有可以执行的用例
if (!haveExecCase(testplanRunRequest.getTestPlanId(), false)) {
@ -1960,7 +1995,53 @@ public class TestPlanService {
this.deleteTestPlans(ids);
}
public TestPlanExtReportDTO getExtReport(String planId) throws JsonProcessingException {
public TestPlanExtReportDTO getExtInfoByReportId(String reportId) throws JsonProcessingException {
TestPlanExtReportDTO testPlanExtReportDTO = new TestPlanExtReportDTO();
Set<String> serviceIdSet = DiscoveryUtil.getServiceIdSet();
if (serviceIdSet.contains(MicroServiceName.API_TEST)) {
List<ApiDefinitionExecResultWithBLOBs> apiDefinitionLists = planTestPlanApiCaseService.selectExtForPlanReport(reportId);
if(CollectionUtils.isNotEmpty(apiDefinitionLists)){
ApiDefinitionExecResultWithBLOBs apiDefinition = apiDefinitionLists.get(0);
convertEnvConfig(apiDefinition.getEnvConfig(), testPlanExtReportDTO);
getResourcePool(apiDefinition.getActuator(), testPlanExtReportDTO);
return testPlanExtReportDTO;
}
}
if (serviceIdSet.contains(MicroServiceName.UI_TEST)) {
List<UiScenarioReportWithBLOBs> apiDefinitionLists = planTestPlanUiScenarioCaseService.selectExtForPlanReport(reportId);
if(CollectionUtils.isNotEmpty(apiDefinitionLists)){
UiScenarioReportWithBLOBs apiDefinition = apiDefinitionLists.get(0);
convertEnvConfig(apiDefinition.getEnvConfig(), testPlanExtReportDTO);
getResourcePool(apiDefinition.getActuator(), testPlanExtReportDTO);
return testPlanExtReportDTO;
}
}
return testPlanExtReportDTO;
}
private void convertEnvConfig(String envConfig, TestPlanExtReportDTO testPlanExtReportDTO) throws JsonProcessingException {
if(StringUtils.isEmpty(envConfig)){
return;
}
EnvConfig env = objectMapper.readValue(envConfig, EnvConfig.class);
if(StringUtils.isNotEmpty(env.getMode())){
if(RunMode.RUN_MODE_SERIAL.getCode().equals(env.getMode())){
testPlanExtReportDTO.setRunMode(RunMode.RUN_MODE_SERIAL.getDesc());
} else if (RunMode.RUN_MODE_PARALLEL.getCode().equals(env.getMode())) {
testPlanExtReportDTO.setRunMode(RunMode.RUN_MODE_PARALLEL.getDesc());
}
}
}
private void getResourcePool(String actuator, TestPlanExtReportDTO testPlanExtReportDTO){
if(StringUtils.isEmpty(actuator)){
return;
}
TestResourcePool testResourcePool = testResourcePoolMapper.selectByPrimaryKey(actuator);
testPlanExtReportDTO.setResourcePool(testResourcePool == null ? null : testResourcePool.getName());
}
public TestPlanExtReportDTO getExtInfoByPlanId(String planId) throws JsonProcessingException {
String reportId = testPlanReportService.getLastReportByPlanId(planId);
if(StringUtils.isEmpty(reportId)){
return null;
@ -1985,28 +2066,6 @@ public class TestPlanService {
return testPlanExtReportDTO;
}
}
return null;
}
private void convertEnvConfig(String envConfig, TestPlanExtReportDTO testPlanExtReportDTO) throws JsonProcessingException {
if(StringUtils.isEmpty(envConfig)){
return;
}
EnvConfig env = objectMapper.readValue(envConfig, EnvConfig.class);
if(StringUtils.isNotEmpty(env.getMode())){
if(RunMode.RUN_MODE_SERIAL.getCode().equals(env.getMode())){
testPlanExtReportDTO.setRunMode(RunMode.RUN_MODE_SERIAL.getDesc());
} else if (RunMode.RUN_MODE_PARALLEL.getCode().equals(env.getMode())) {
testPlanExtReportDTO.setRunMode(RunMode.RUN_MODE_PARALLEL.getDesc());
}
}
}
private void getResourcePool(String actuator, TestPlanExtReportDTO testPlanExtReportDTO){
if(StringUtils.isEmpty(actuator)){
return;
}
TestResourcePool testResourcePool = testResourcePoolMapper.selectByPrimaryKey(actuator);
testPlanExtReportDTO.setResourcePool(testResourcePool == null ? null : testResourcePool.getName());
return testPlanExtReportDTO;
}
}

View File

@ -153,4 +153,8 @@ public class PlanTestPlanUiScenarioCaseService extends UiTestService {
public List<UiScenarioReportWithBLOBs> selectExtForPlanReport(String planId) {
return microService.getForDataArray(serviceName, BASE_URL + "/get/report/ext/" + planId, UiScenarioReportWithBLOBs.class);
}
public Map<String, List<String>> getUiScenarioEnv(String planId) {
return microService.getForData(serviceName, BASE_URL + "/get/env/" + planId, Map.class);
}
}

View File

@ -170,9 +170,10 @@ public class IssuesService {
issues = platformPluginService.getPlatform(project.getPlatform())
.addIssue(platformIssuesUpdateRequest);
issues.setPlatform(project.getPlatform());
insertIssues(issues);
issuesRequest.setId(issues.getId());
issues.setPlatform(project.getPlatform());
issuesRequest.setPlatformId(issues.getPlatformId());
// 用例与第三方缺陷平台中的缺陷关联
handleTestCaseIssues(issuesRequest);
@ -496,8 +497,6 @@ public class IssuesService {
public List<String> getPlatforms(Project project) {
String workspaceId = project.getWorkspaceId();
boolean tapd = isIntegratedPlatform(workspaceId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(workspaceId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(workspaceId, IssuesManagePlatform.Zentao.toString());
boolean azure = isIntegratedPlatform(workspaceId, IssuesManagePlatform.AzureDevops.toString());
List<String> platforms = new ArrayList<>();
@ -510,20 +509,6 @@ public class IssuesService {
}
if (jira) {
String jiraKey = project.getJiraKey();
if (StringUtils.isNotBlank(jiraKey) && PlatformPluginService.isPluginPlatform(project.getPlatform())) {
platforms.add(IssuesManagePlatform.Jira.name());
}
}
if (zentao) {
String zentaoId = project.getZentaoId();
if (StringUtils.isNotBlank(zentaoId) && StringUtils.equals(project.getPlatform(), IssuesManagePlatform.Zentao.toString())) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
if (azure) {
String azureDevopsId = project.getAzureDevopsId();
if (StringUtils.isNotBlank(azureDevopsId) && StringUtils.equals(project.getPlatform(), IssuesManagePlatform.AzureDevops.toString())) {
@ -637,13 +622,13 @@ public class IssuesService {
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
List<IssuesDao> issuesDaos = listByWorkspaceId(issuesRequest);
if (CollectionUtils.isNotEmpty(issuesDaos)) {
issuesDaos.parallelStream().forEach(issuesDao -> {
issuesDaos.forEach(issuesDao -> {
delete(issuesDao.getId());
});
}
} else {
if (CollectionUtils.isNotEmpty(request.getBatchDeleteIds())) {
request.getBatchDeleteIds().parallelStream().forEach(id -> delete(id));
request.getBatchDeleteIds().forEach(id -> delete(id));
}
}
}
@ -945,10 +930,10 @@ public class IssuesService {
}
private String getDefaultCustomField(Project project) {
if (!trackProjectService.isThirdPartTemplate(project)) {
return getDefaultCustomFields(project.getId());
if (isThirdPartTemplate(project)) {
return null;
}
return null;
return getDefaultCustomFields(project.getId());
}
public void syncPluginThirdPartyIssues(List<IssuesDao> issues, Project project, String defaultCustomFields) {
@ -1057,10 +1042,6 @@ public class IssuesService {
}
private void syncAllPluginIssueAttachment(Project project, IssueSyncRequest syncIssuesResult) {
// todo 所有平台改造完之后删除
if (!StringUtils.equals(project.getPlatform(), IssuesManagePlatform.Jira.name())) {
return;
}
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
AttachmentModuleRelationMapper batchAttachmentModuleRelationMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class);
@ -1510,7 +1491,8 @@ public class IssuesService {
public boolean isThirdPartTemplate(Project project) {
return project.getThirdPartTemplate() != null
&& project.getThirdPartTemplate()
&& PlatformPluginService.isPluginPlatform(project.getPlatform());
&& PlatformPluginService.isPluginPlatform(project.getPlatform())
&& platformPluginService.isThirdPartTemplateSupport(project.getPlatform());
}
public void checkThirdProjectExist(Project project) {
@ -1709,6 +1691,7 @@ public class IssuesService {
IssueTemplateDao issueTemplateDao;
Project project = baseProjectService.getProjectById(projectId);
if (PlatformPluginService.isPluginPlatform(project.getPlatform())
&& platformPluginService.isThirdPartTemplateSupport(project.getPlatform())
&& project.getThirdPartTemplate()) {
// 第三方Jira平台
issueTemplateDao = getThirdPartTemplate(project.getId());
@ -1794,13 +1777,13 @@ public class IssuesService {
}
public void saveImportData(List<IssuesUpdateRequest> issues) {
issues.parallelStream().forEach(issue -> {
issues.forEach(issue -> {
addIssues(issue, null);
});
}
public void updateImportData(List<IssuesUpdateRequest> issues) {
issues.parallelStream().forEach(issue -> {
issues.forEach(issue -> {
updateIssues(issue);
});
}

View File

@ -463,7 +463,11 @@ public class TestCaseService {
dealWithOtherInfoOfNewVersion(testCase, oldTestCase.getId());
testCaseMapper.insertSelective(testCase);
}
//checkAndSetLatestVersion(testCase.getRefId());
String defaultVersion = baseProjectVersionMapper.getDefaultVersion(testCase.getProjectId());
if (StringUtils.equalsIgnoreCase(testCase.getVersionId(), defaultVersion)) {
checkAndSetLatestVersion(testCase.getRefId());
}
}
/**
@ -704,14 +708,8 @@ public class TestCaseService {
}
public int deleteToGcBatch(TestCaseBatchRequest request) {
List<String> ids = new ArrayList<String>();
if (request.getCondition() != null && request.getCondition().isSelectAll()) {
List<TestCaseDTO> testCaseDTOS = listTestCase(request.getCondition());
ids = testCaseDTOS.stream().map(TestCaseDTO::getId).collect(Collectors.toList());
} else {
ids = request.getIds();
}
return deleteToGcBatch(ids, null);
ServiceUtils.getSelectAllIds(request, request.getCondition(), (query) -> extTestCaseMapper.selectIds(query));
return deleteToGcBatch(request.getIds(), null);
}
public int deleteToGcBatch(List<String> ids, String projectId) {
@ -2434,13 +2432,8 @@ public class TestCaseService {
}
public void reduction(TestCaseBatchRequest request) {
List<String> ids = new ArrayList<>();
if (request.getCondition() != null && request.getCondition().isSelectAll()) {
List<TestCaseDTO> allReductionTestCases = listTestCase(request.getCondition());
ids = allReductionTestCases.stream().map(TestCaseDTO::getId).collect(Collectors.toList());
} else {
ids = request.getIds();
}
ServiceUtils.getSelectAllIds(request, request.getCondition(), (query) -> extTestCaseMapper.selectIds(query));
List<String> ids = request.getIds();
if (CollectionUtils.isNotEmpty(ids)) {
extTestCaseMapper.checkOriginalStatusByIds(ids);

View File

@ -26,13 +26,6 @@ public class TrackProjectService {
@Resource
ProjectMapper projectMapper;
public boolean isThirdPartTemplate(Project project) {
if (project.getThirdPartTemplate() != null && project.getThirdPartTemplate() && project.getPlatform().equals(IssuesManagePlatform.Jira.name())) {
return true;
}
return false;
}
public boolean useCustomNum(String projectId) {
return useCustomNum(baseProjectService.getProjectById(projectId));
}

View File

@ -52,11 +52,21 @@ public abstract class AbstractCustomFieldValidator {
try {
// [a, b] => ["a","b"]
if (!StringUtils.equals(value, "[]")) {
value = value.replace("[", "[\"")
.replace("]", "\"]")
.replace(",", "\",\"")
.replace("", "\"\"")
.replace(StringUtils.SPACE, StringUtils.EMPTY);
if (!value.contains("[\"")) {
value = value.replace("[", "[\"");
}
if (!value.contains("\"]")) {
value = value.replace("]", "\"]");
}
if (!value.contains("\",\"")) {
value = value.replace(",", "\",\"");
}
if (!value.contains("\"\"")) {
value = value.replace("", "\"\"");
}
value = value.replace(StringUtils.SPACE, StringUtils.EMPTY);
}
return JSON.parseArray(value, String.class);
} catch (Exception e) {

View File

@ -1,21 +1,33 @@
package io.metersphere.validate;
import io.metersphere.commons.utils.JSON;
import io.metersphere.exception.CustomFieldValidateException;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.exception.CustomFieldValidateException;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
public class CustomFieldMultipleTextValidator extends AbstractCustomFieldValidator {
public CustomFieldMultipleTextValidator() {
this.isKVOption = true;
}
@Override
public void validate(CustomFieldDao customField, String value) throws CustomFieldValidateException {
validateRequired(customField, value);
if (StringUtils.isNotBlank(value)) {
try {
JSON.parseArray(value);
parse2Array(customField.getName(), value);
} catch (Exception e) {
CustomFieldValidateException.throwException(String.format(Translator.get("custom_field_array_tip"), customField.getName()));
}
}
}
@Override
public Object parse2Key(String keyOrValuesStr, CustomFieldDao customField) {
if (StringUtils.isBlank(keyOrValuesStr)) {
return StringUtils.EMPTY;
}
return parse2Array(keyOrValuesStr);
}
}

View File

@ -309,9 +309,13 @@ export function testPlanLoadCaseEditStatus(planId) {
return post(BASE_URL + `edit/status/${planId}`, new Promise(() => {}));
}
export function getTestPlanExtReport(planId) {
if (planId) {
return get('/test/plan/ext/report/' + planId);
export function getTestPlanExtReport(planId, reportId) {
if (reportId) {
return get('/test/plan/ext/report/' + reportId);
} else if (planId) {
return get('/test/plan/ext/plan/' + planId);
} else {
return {};
}
}

View File

@ -0,0 +1,15 @@
import {get, post} from "metersphere-frontend/src/plugins/request"
const BASE_URL = "/ui/automation/";
export function getUiScenarioEnvByProjectId(id) {
return get(BASE_URL + `env-project-ids/${id}`);
}
export function uiAutomationReduction(param) {
return post(BASE_URL + 'reduction', param);
}
export function uiScenarioEnvMap(params) {
return post(BASE_URL + 'env/map', params);
}

View File

@ -465,7 +465,7 @@ export default {
this.isXpack = false;
}
if (hasLicense()) {
this.getVersionHistory();
this.getDefaultVersion();
}
//

View File

@ -425,7 +425,8 @@ export default {
let platform = this.issueTemplate.platform;
this.platformTransitions = null;
if (this.form.platformId) {
//
if (this.form.platformId && this.form.id) {
let data = {
platformKey: this.form.platformId,
projectId: getCurrentProjectID(),

View File

@ -201,7 +201,7 @@ export default {
//
temp.selectEnv = envs.filter(e => e.id === envId).length === 0 ? null : envId;
}
if (this.projectEnvMap) {
if (this.projectEnvMap && Object.keys(this.projectEnvMap).length > 0) {
let projectEnvMapElement = this.projectEnvMap[d];
if (projectEnvMapElement.length>0) {
projectEnvMapElement.forEach(envId => {

View File

@ -76,13 +76,13 @@ import TestPlanFunctional from "./comonents/functional/TestPlanFunctional";
import TestPlanApi from "./comonents/api/TestPlanApi";
import TestPlanUi from "./comonents/ui/TestPlanUi";
import TestPlanLoad from "@/business/plan/view/comonents/load/TestPlanLoad";
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
import {getCurrentProjectID, setCurrentProjectID} from "metersphere-frontend/src/utils/token";
import {hasLicense} from "metersphere-frontend/src/utils/permission"
import TestPlanReportContent from "@/business/plan/view/comonents/report/detail/TestPlanReportContent";
import IsChangeConfirm from "metersphere-frontend/src/components/IsChangeConfirm";
import {PROJECT_ID, WORKSPACE_ID} from "metersphere-frontend/src/utils/constants";
import {useStore} from "@/store";
import {testPlanListAll} from "@/api/remote/plan/test-plan";
import {testPlanGet, testPlanListAll} from "@/api/remote/plan/test-plan";
import {isProjectVersionEnable} from "@/business/utils/sdk-utils";
export default {
@ -207,11 +207,23 @@ export default {
testPlanListAll({projectId: getCurrentProjectID()})
.then(response => {
this.testPlans = response.data;
let hasPlan = false;
this.testPlans.forEach(plan => {
if (this.planId && plan.id === this.planId) {
hasPlan = true;
this.currentPlan = plan;
}
});
if (!hasPlan) {
if (this.planId) {
testPlanGet(this.planId)
.then((r) => {
let plan = r.data;
setCurrentProjectID(plan.projectId);
location.reload();
});
}
}
});
},
changePlan(plan) {

View File

@ -511,13 +511,11 @@ export default {
singleRun(row) {
let reportId = getUUID().substring(0, 8);
this.rowLoading = row.id;
run(row.id, reportId)
.then(() => {
this.runningReport.add(reportId);
this.$refs.apiCaseResult.open(reportId);
},error =>{
this.rowLoading = "";
});
this.runningReport.add(reportId);
// websock
// websock
this.$refs.apiCaseResult.open(reportId);
run(row.id, reportId);
},
handleTestEnd(reportId) {
if (this.runningReport.has(reportId)) {

View File

@ -71,13 +71,13 @@ export default {
isDb: Boolean,
shareId: String,
reportId: String,
runMode: String,
resourcePool: String,
needMoveBar: Boolean
},
data() {
return {
report: {},
runMode: '',
resourcePool: '',
loading: false,
shareUrl: ''
};
@ -139,6 +139,8 @@ export default {
getReport() {
if (this.isTemplate) {
this.report = "#report";
this.runMode = this.report.runMode;
this.resourcePool = this.report.resourcePool;
if (this.report.lang) {
this.$setLang(this.report.lang);
}
@ -151,6 +153,8 @@ export default {
.then((r) => {
this.loading = false;
this.report = r.data;
this.runMode = r.data.runMode;
this.resourcePool = r.data.resourcePool;
this.report.config = this.getDefaultConfig(this.report);
});
} else {
@ -183,8 +187,7 @@ export default {
});
}
}
getTestPlanExtReport(this.planId).then((response) => {
getTestPlanExtReport(this.planId, this.reportId).then((response) => {
this.runMode = response.data.runMode;
this.resourcePool = response.data.resourcePool;
})

View File

@ -239,7 +239,7 @@ export default {
this.responseLoading = false;
if (response.data) {
let data = response.data;
if (data && data.content) {
if (data) {
this.showResponse = true;
try {
this.response = JSON.parse(data.content);

View File

@ -1,5 +1,21 @@
<template>
<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"
v-if="condition.components !== undefined && condition.components.length > 0"
@ -99,6 +115,9 @@ import {
getCustomTableWidth
} from "metersphere-frontend/src/utils/tableUtils";
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 {
name: "RelevanceUiScenarioList",
@ -112,6 +131,7 @@ export default {
MsTag,
MsTableAdvSearchBar,
MsTableColumn,
EnvGroupPopover,
},
props: {
referenced: {
@ -147,6 +167,7 @@ export default {
envGroupId: "",
versionFilters: [],
fieldsWidth: getCustomTableWidth('TEST_PLAN_UI_SCENARIO_CASE'),
projectIds: new Set()
};
},
computed: {
@ -244,10 +265,22 @@ export default {
selectCountChange(data) {
this.selectRows = this.$refs.scenarioTable.selectRows;
this.$emit("selectCountChange", data);
this.initProjectIds();
},
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);
});
});
},
}
};
</script>

View File

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

View File

@ -147,7 +147,8 @@
:filters="apiscenariofilters.RESULT_FILTERS"
:label="$t('api_test.automation.last_result')">
<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"/>
</el-link>
</template>
@ -176,8 +177,8 @@
:select-row="this.$refs.table ? this.$refs.table.selectRows : new Set()" ref="batchEdit"
@batchEdit="batchEdit"/>
<ui-run-mode @handleRunBatch="handleRunBatch" ref="runMode" :custom-run-mode="true"
:custom-serial-on-sample-error="true"/>
<ui-run-mode @handleRunBatch="handleRunBatch" ref="runMode" :custom-run-mode="true"
:custom-serial-on-sample-error="true" :request="conditionRequest"/>
<ms-task-center ref="taskCenter" :show-menu="false"/>
</div>
@ -327,6 +328,8 @@ export default {
]
},
versionFilters: [],
//
conditionRequest: {}
}
},
computed: {
@ -358,14 +361,14 @@ export default {
},
search() {
initCondition(this.condition, this.condition.selectAll);
if(this.condition && this.condition.filters && this.condition.filters.last_result){
if(this.condition.filters.last_result.length > 0){
if (this.condition && this.condition.filters && this.condition.filters.last_result) {
if (this.condition.filters.last_result.length > 0) {
//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"]
}
//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"]
}
}
@ -437,12 +440,19 @@ export default {
let rows = this.orderBySelectRows(this.$refs.table.selectRows);
this.planCaseIds = [];
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();
},
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 = [];
for (let i in this.tableData) {
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;

View File

@ -7,6 +7,21 @@
:visible.sync="runModeVisible"
>
<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="title">{{ $t("ui.browser") }}</div>
@ -175,11 +190,13 @@
<script>
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 {
name: "UiRunMode",
components: {MsDialogFooter},
components: {MsDialogFooter, EnvSelectPopover},
data() {
return {
runModeVisible: false,
@ -207,6 +224,8 @@ export default {
},
projectList: [],
projectIds: new Set(),
projectEnvListMap: {},
caseIdEnvNameMap: {},
};
},
props: {
@ -262,6 +281,7 @@ export default {
};
this.runModeVisible = true;
this.getWsProjects();
this.showPopover();
},
changeMode() {
this.runConfig.runWithinResourcePool = false;
@ -296,6 +316,29 @@ export default {
this.$emit("handleRunBatch", this.runConfig);
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>

Some files were not shown because too many files have changed in this diff Show More