Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
0dc4e71eb0
|
@ -1,4 +1,4 @@
|
|||
# MeterSphere 开源持续测试平台
|
||||
# MeterSphere 一站式开源持续测试平台
|
||||
|
||||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/176186d132df448b955f8bdd5e6ef9c0)](https://app.codacy.com/gh/metersphere/metersphere?utm_source=github.com&utm_medium=referral&utm_content=metersphere/metersphere&utm_campaign=Badge_Grade_Dashboard)
|
||||
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/metersphere/metersphere)](https://github.com/metersphere/metersphere/releases/latest)
|
||||
|
@ -11,7 +11,7 @@
|
|||
| ------------------------------------------------------------------------------------------------------------ |
|
||||
| 我们正在寻找开发者,欢迎加入我们共同打造更好用、更强大的 MeterSphere。联系我们: [metersphere@fit2cloud.com](mailto:metersphere@fit2cloud.com) |
|
||||
|
||||
MeterSphere 是一站式的开源企业级持续测试平台,涵盖测试跟踪、接口测试、性能测试、团队协作等功能,兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。
|
||||
MeterSphere 是一站式开源持续测试平台,涵盖测试跟踪、接口测试、性能测试、团队协作等功能,兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。
|
||||
|
||||
- 测试跟踪: 远超 TestLink 的使用体验;
|
||||
- 接口测试: 类似 Postman 的体验;
|
||||
|
|
10
ROADMAP.md
10
ROADMAP.md
|
@ -55,11 +55,17 @@
|
|||
- [x] 测试跟踪:支持对接禅道同步缺陷
|
||||
- [x] 其他:Jenkins 插件支持 pipeline 方式调用
|
||||
|
||||
## v1.6 (开发中)
|
||||
- [ ] 新增接口管理功能
|
||||
- [ ] 全新接口自动化使用方式
|
||||
- [ ] 测试跟踪测试计划分类型展示
|
||||
- [ ] 优化消息通知配置及实现方式
|
||||
|
||||
## 规划中
|
||||
|
||||
- [ ] 接口测试支持添加 WebSocket 协议请求
|
||||
- [ ] 接口管理功能
|
||||
- [ ] 集成云平台动态管理测试资源池
|
||||
- [ ] 支持 K8s 集群作为测试资源池
|
||||
- [ ] 测试跟踪测试用例及接口测试增加版本管理
|
||||
- [ ] 测试跟踪测试用例增加思维导图展示形式
|
||||
- [ ] 移动端测试支持
|
||||
- [ ] UI 功能测试支持
|
||||
|
|
|
@ -58,6 +58,11 @@ public class ApiAutomationController {
|
|||
apiAutomationService.removeToGc(ids);
|
||||
}
|
||||
|
||||
@PostMapping("/reduction")
|
||||
public void reduction(@RequestBody List<String> ids) {
|
||||
apiAutomationService.reduction(ids);
|
||||
}
|
||||
|
||||
@GetMapping("/getApiScenario/{id}")
|
||||
public ApiScenario getScenarioDefinition(@PathVariable String id) {
|
||||
return apiAutomationService.getApiScenario(id);
|
||||
|
|
|
@ -6,10 +6,7 @@ import io.metersphere.api.dto.APIReportResult;
|
|||
import io.metersphere.api.dto.ApiTestImportRequest;
|
||||
import io.metersphere.api.dto.automation.ApiScenarioRequest;
|
||||
import io.metersphere.api.dto.automation.ReferenceDTO;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
|
||||
import io.metersphere.api.dto.definition.ApiDefinitionResult;
|
||||
import io.metersphere.api.dto.definition.RunDefinitionRequest;
|
||||
import io.metersphere.api.dto.definition.SaveApiDefinitionRequest;
|
||||
import io.metersphere.api.dto.definition.*;
|
||||
import io.metersphere.api.service.ApiDefinitionService;
|
||||
import io.metersphere.base.domain.ApiDefinition;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
|
@ -64,6 +61,11 @@ public class ApiDefinitionController {
|
|||
apiDefinitionService.removeToGc(ids);
|
||||
}
|
||||
|
||||
@PostMapping("/reduction")
|
||||
public void reduction(@RequestBody List<String> ids) {
|
||||
apiDefinitionService.reduction(ids);
|
||||
}
|
||||
|
||||
@GetMapping("/get/{id}")
|
||||
public ApiDefinition get(@PathVariable String id) {
|
||||
return apiDefinitionService.get(id);
|
||||
|
@ -100,4 +102,10 @@ public class ApiDefinitionController {
|
|||
return apiDefinitionService.getReference(request);
|
||||
}
|
||||
|
||||
@PostMapping("/batch/edit")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public void editApiBath(@RequestBody ApiBatchRequest request) {
|
||||
apiDefinitionService.editApiBath(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import io.metersphere.base.domain.ApiScenario;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApiScenarioDTO extends ApiScenario {
|
||||
|
||||
private String projectName;
|
||||
private String userName;
|
||||
private String tagName;
|
||||
private List<String> tagNames;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package io.metersphere.api.dto.definition;
|
||||
|
||||
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
|
||||
import io.metersphere.controller.request.OrderRequest;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApiBatchRequest extends ApiDefinitionWithBLOBs {
|
||||
private List<String> ids;
|
||||
private List<OrderRequest> orders;
|
||||
private String projectId;
|
||||
}
|
|
@ -17,6 +17,7 @@ import io.metersphere.commons.utils.CommonBeanFactory;
|
|||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
@ -47,7 +48,7 @@ public class MsScenario extends MsTestElement {
|
|||
if (!this.isEnable()) {
|
||||
return;
|
||||
}
|
||||
if (environmentId != null) {
|
||||
if (StringUtils.isNotEmpty(environmentId)) {
|
||||
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
|
||||
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentId);
|
||||
config.setConfig(JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class));
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.metersphere.commons.constants.APITestStatus;
|
|||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.constants.ReportTriggerMode;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.ServiceUtils;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.track.dto.TestPlanDTO;
|
||||
|
@ -67,11 +68,12 @@ public class ApiAutomationService {
|
|||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
public List<ApiScenarioDTO> list(ApiScenarioRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
List<ApiScenarioDTO> list = extApiScenarioMapper.list(request);
|
||||
ApiTagExample example = new ApiTagExample();
|
||||
example.createCriteria().andProjectIdEqualTo(request.getProjectId());
|
||||
List<ApiTag> tags = apiTagMapper.selectByExample(example);
|
||||
Map<String, String> tagMap = tags.stream().collect(Collectors.toMap(ApiTag::getId, ApiTag::getName));
|
||||
List<ApiScenarioDTO> list = extApiScenarioMapper.list(request);
|
||||
Gson gs = new Gson();
|
||||
list.forEach(item -> {
|
||||
if (item.getTagId() != null) {
|
||||
|
@ -81,7 +83,11 @@ public class ApiAutomationService {
|
|||
buf.append(",");
|
||||
});
|
||||
if (buf != null && buf.length() > 0) {
|
||||
item.setTagName(buf.toString().substring(0, buf.toString().length() - 1));
|
||||
String tagNames = buf.toString().substring(0, buf.toString().length() - 1);
|
||||
List<String> tagList = Arrays.asList(tagNames.split(","));
|
||||
item.setTagNames(tagList);
|
||||
} else {
|
||||
item.setTagNames(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -124,7 +130,7 @@ public class ApiAutomationService {
|
|||
scenario.setDescription(request.getDescription());
|
||||
apiScenarioMapper.insert(scenario);
|
||||
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
List<String> bodyUploadIds = request.getBodyUploadIds();
|
||||
apiDefinitionService.createBodyFiles(bodyUploadIds, bodyFiles);
|
||||
}
|
||||
|
||||
|
@ -166,12 +172,12 @@ public class ApiAutomationService {
|
|||
apiScenarioMapper.deleteByExample(example);
|
||||
}
|
||||
|
||||
public void removeToGc(List<String> ids) {
|
||||
ApiScenario record = new ApiScenario();
|
||||
record.setStatus(ScenarioStatus.Trash.name());
|
||||
ApiScenarioExample example = new ApiScenarioExample();
|
||||
example.createCriteria().andIdIn(ids);
|
||||
apiScenarioMapper.updateByExampleSelective(record, example);
|
||||
public void removeToGc(List<String> apiIds) {
|
||||
extApiScenarioMapper.removeToGc(apiIds);
|
||||
}
|
||||
|
||||
public void reduction(List<String> apiIds) {
|
||||
extApiScenarioMapper.reduction(apiIds);
|
||||
}
|
||||
|
||||
private void checkNameExist(SaveApiScenarioRequest request) {
|
||||
|
@ -242,12 +248,18 @@ public class ApiAutomationService {
|
|||
JSONObject element = JSON.parseObject(item.getScenarioDefinition());
|
||||
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
|
||||
// 多态JSON普通转换会丢失内容,需要通过 ObjectMapper 获取
|
||||
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
|
||||
new TypeReference<LinkedList<MsTestElement>>() {});
|
||||
LinkedList<KeyValue> variables = mapper.readValue(element.getString("variables"),
|
||||
new TypeReference<LinkedList<KeyValue>>() {});
|
||||
scenario.setHashTree(elements);
|
||||
scenario.setVariables(variables);
|
||||
if (element!= null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
|
||||
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
|
||||
new TypeReference<LinkedList<MsTestElement>>() {
|
||||
});
|
||||
scenario.setHashTree(elements);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(element.getString("variables"))) {
|
||||
LinkedList<KeyValue> variables = mapper.readValue(element.getString("variables"),
|
||||
new TypeReference<LinkedList<KeyValue>>() {
|
||||
});
|
||||
scenario.setVariables(variables);
|
||||
}
|
||||
LinkedList<MsTestElement> scenarios = new LinkedList<>();
|
||||
scenarios.add(scenario);
|
||||
group.setHashTree(scenarios);
|
||||
|
|
|
@ -163,6 +163,10 @@ public class ApiDefinitionService {
|
|||
extApiDefinitionMapper.removeToGc(apiIds);
|
||||
}
|
||||
|
||||
public void reduction(List<String> apiIds) {
|
||||
extApiDefinitionMapper.reduction(apiIds);
|
||||
}
|
||||
|
||||
public void deleteBodyFiles(String apiId) {
|
||||
File file = new File(BODY_FILE_DIR + "/" + apiId);
|
||||
FileUtil.deleteContents(file);
|
||||
|
@ -371,4 +375,15 @@ public class ApiDefinitionService {
|
|||
dto.setTestPlanList(extTestPlanMapper.selectReference(planRequest));
|
||||
return dto;
|
||||
}
|
||||
|
||||
public void editApiBath(ApiBatchRequest request) {
|
||||
ApiDefinitionExample definitionExample = new ApiDefinitionExample();
|
||||
definitionExample.createCriteria().andIdIn(request.getIds());
|
||||
|
||||
ApiDefinitionWithBLOBs definitionWithBLOBs = new ApiDefinitionWithBLOBs();
|
||||
BeanUtils.copyBean(definitionWithBLOBs, request);
|
||||
definitionWithBLOBs.setUpdateTime(System.currentTimeMillis());
|
||||
apiDefinitionMapper.updateByExampleSelective(definitionWithBLOBs, definitionExample);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,11 +21,10 @@ import org.apache.ibatis.session.SqlSessionFactory;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class ApiScenarioModuleService {
|
||||
|
@ -124,16 +123,17 @@ public class ApiScenarioModuleService {
|
|||
}
|
||||
}
|
||||
|
||||
private List<ApiScenarioDTO> queryByModuleIds(List<String> nodeIds) {
|
||||
private List<ApiScenarioDTO> queryByModuleIds(DragApiScenarioModuleRequest request) {
|
||||
ApiScenarioRequest apiScenarioRequest = new ApiScenarioRequest();
|
||||
apiScenarioRequest.setModuleIds(nodeIds);
|
||||
apiScenarioRequest.setProjectId(request.getProjectId());
|
||||
apiScenarioRequest.setModuleIds(request.getNodeIds());
|
||||
return apiAutomationService.list(apiScenarioRequest);
|
||||
}
|
||||
|
||||
public int editNode(DragApiScenarioModuleRequest request) {
|
||||
request.setUpdateTime(System.currentTimeMillis());
|
||||
checkApiScenarioModuleExist(request);
|
||||
List<ApiScenarioDTO> apiScenarios = queryByModuleIds(request.getNodeIds());
|
||||
List<ApiScenarioDTO> apiScenarios = queryByModuleIds(request);
|
||||
|
||||
apiScenarios.forEach(apiScenario -> {
|
||||
StringBuilder path = new StringBuilder(apiScenario.getModulePath());
|
||||
|
@ -172,7 +172,7 @@ public class ApiScenarioModuleService {
|
|||
|
||||
List<String> nodeIds = request.getNodeIds();
|
||||
|
||||
List<ApiScenarioDTO> apiScenarios = queryByModuleIds(nodeIds);
|
||||
List<ApiScenarioDTO> apiScenarios = queryByModuleIds(request);
|
||||
|
||||
ApiScenarioModuleDTO nodeTree = request.getNodeTree();
|
||||
|
||||
|
|
|
@ -15,4 +15,6 @@ public interface ExtApiDefinitionMapper {
|
|||
|
||||
int removeToGc(@Param("ids") List<String> ids);
|
||||
|
||||
int reduction(@Param("ids") List<String> ids);
|
||||
|
||||
}
|
|
@ -255,4 +255,15 @@
|
|||
#{v}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="reduction">
|
||||
update api_definition
|
||||
set
|
||||
status = 'Underway'
|
||||
where id in
|
||||
<foreach collection="ids" item="v" separator="," open="(" close=")">
|
||||
#{v}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
</mapper>
|
|
@ -16,4 +16,7 @@ public interface ExtApiScenarioMapper {
|
|||
|
||||
List<ApiScenario> selectReference(@Param("request") ApiScenarioRequest request);
|
||||
|
||||
int removeToGc(@Param("ids") List<String> ids);
|
||||
|
||||
int reduction(@Param("ids") List<String> ids);
|
||||
}
|
||||
|
|
|
@ -88,5 +88,25 @@
|
|||
</where>
|
||||
</select>
|
||||
|
||||
<update id="removeToGc">
|
||||
update api_scenario
|
||||
set
|
||||
status = 'Trash'
|
||||
where id in
|
||||
<foreach collection="ids" item="v" separator="," open="(" close=")">
|
||||
#{v}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="reduction">
|
||||
update api_scenario
|
||||
set
|
||||
status = 'Underway'
|
||||
where id in
|
||||
<foreach collection="ids" item="v" separator="," open="(" close=")">
|
||||
#{v}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
|
||||
</mapper>
|
|
@ -106,7 +106,7 @@
|
|||
<select id="checkLoadTestOwner" resultType="int">
|
||||
SELECT COUNT(1)
|
||||
FROM load_test
|
||||
LEFT JOIN project ON api_test.project_id = project.id
|
||||
LEFT JOIN project ON load_test.project_id = project.id
|
||||
<where>
|
||||
<if test="testId != null">
|
||||
and load_test.id = #{testId}
|
||||
|
|
|
@ -4,5 +4,5 @@ public enum ResourcePoolTypeEnum {
|
|||
/**
|
||||
* node controller 资源池
|
||||
*/
|
||||
NODE
|
||||
NODE, K8S
|
||||
}
|
||||
|
|
|
@ -5,14 +5,17 @@ import io.metersphere.commons.constants.UserSource;
|
|||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.controller.request.LoginRequest;
|
||||
import io.metersphere.service.BaseDisplayService;
|
||||
import io.metersphere.service.UserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping
|
||||
|
@ -22,6 +25,8 @@ public class LoginController {
|
|||
private UserService userService;
|
||||
@Resource
|
||||
private Environment env;
|
||||
@Resource
|
||||
private BaseDisplayService baseDisplayService;
|
||||
|
||||
@GetMapping(value = "/isLogin")
|
||||
public ResultHolder isLogin() {
|
||||
|
@ -66,4 +71,9 @@ public class LoginController {
|
|||
public String getDefaultLanguage() {
|
||||
return userService.getDefaultLanguage();
|
||||
}
|
||||
|
||||
@GetMapping("display/file/{imageName}")
|
||||
public ResponseEntity<byte[]> image(@PathVariable("imageName") String imageName) throws IOException {
|
||||
return baseDisplayService.getImage(imageName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.metersphere.controller;
|
|||
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.TestResourcePool;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
|
@ -14,7 +13,6 @@ import org.apache.shiro.authz.annotation.RequiresRoles;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequestMapping("testresourcepool")
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.base.domain.SystemParameter;
|
||||
import io.metersphere.base.domain.SystemParameterExample;
|
||||
import io.metersphere.base.mapper.SystemParameterMapper;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class BaseDisplayService {
|
||||
@Resource
|
||||
private SystemParameterMapper systemParameterMapper;
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
public List<SystemParameter> getParamList(String type) {
|
||||
SystemParameterExample example = new SystemParameterExample();
|
||||
example.createCriteria().andParamKeyLike(type + "%");
|
||||
return systemParameterMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
public byte[] loadFileAsBytes(String fileId) {
|
||||
return fileService.loadFileAsBytes(fileId);
|
||||
}
|
||||
|
||||
public ResponseEntity<byte[]> getImage(String imageName) throws IOException {
|
||||
byte[] bytes = null;
|
||||
List<SystemParameter> paramList = getParamList("ui." + imageName);
|
||||
if (!CollectionUtils.isEmpty(paramList)) {
|
||||
SystemParameter sp = paramList.get(0);
|
||||
String paramValue = sp.getParamValue();
|
||||
if (StringUtils.isNotBlank(paramValue)) {
|
||||
bytes = loadFileAsBytes(paramValue);
|
||||
}
|
||||
}
|
||||
|
||||
MediaType contentType = MediaType.parseMediaType("application/octet-stream");
|
||||
if (bytes == null) {
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
|
||||
switch (imageName) {
|
||||
case "logo":
|
||||
bytes = IOUtils.toByteArray(resolver.getResources("/static/img/logo-light-MeterSphere.*.svg")[0].getInputStream());
|
||||
contentType = MediaType.valueOf("image/svg+xml");
|
||||
break;
|
||||
case "loginImage":
|
||||
bytes = IOUtils.toByteArray(resolver.getResources("/static/img/info.*.png")[0].getInputStream());
|
||||
break;
|
||||
case "loginLogo":
|
||||
bytes = IOUtils.toByteArray(resolver.getResources("/static/img/logo-dark-MeterSphere.*.svg")[0].getInputStream());
|
||||
contentType = MediaType.valueOf("image/svg+xml");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(contentType)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + imageName + "\"")
|
||||
.body(bytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.dto.TestResourcePoolDTO;
|
||||
|
||||
public interface KubernetesResourcePoolService {
|
||||
boolean validate(TestResourcePoolDTO testResourcePool);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.base.domain.TestResource;
|
||||
import io.metersphere.base.domain.TestResourceExample;
|
||||
import io.metersphere.base.mapper.TestResourceMapper;
|
||||
import io.metersphere.commons.constants.ResourceStatusEnum;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.dto.NodeDTO;
|
||||
import io.metersphere.dto.TestResourcePoolDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
|
||||
|
||||
@Service
|
||||
public class NodeResourcePoolService {
|
||||
private final static String nodeControllerUrl = "http://%s:%s/status";
|
||||
|
||||
@Resource(name = "restTemplateWithTimeOut")
|
||||
private RestTemplate restTemplateWithTimeOut;
|
||||
@Resource
|
||||
private TestResourceMapper testResourceMapper;
|
||||
|
||||
public boolean validate(TestResourcePoolDTO testResourcePool) {
|
||||
if (CollectionUtils.isEmpty(testResourcePool.getResources())) {
|
||||
MSException.throwException(Translator.get("no_nodes_message"));
|
||||
}
|
||||
|
||||
deleteTestResource(testResourcePool.getId());
|
||||
List<String> nodeIps = testResourcePool.getResources().stream()
|
||||
.map(resource -> {
|
||||
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
|
||||
return nodeDTO.getIp();
|
||||
})
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (nodeIps.size() < testResourcePool.getResources().size()) {
|
||||
MSException.throwException(Translator.get("duplicate_node_ip"));
|
||||
}
|
||||
testResourcePool.setStatus(VALID.name());
|
||||
boolean isValid = true;
|
||||
for (TestResource resource : testResourcePool.getResources()) {
|
||||
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
|
||||
boolean isValidate = validateNode(nodeDTO);
|
||||
if (!isValidate) {
|
||||
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
|
||||
resource.setStatus(ResourceStatusEnum.INVALID.name());
|
||||
isValid = false;
|
||||
} else {
|
||||
resource.setStatus(VALID.name());
|
||||
}
|
||||
resource.setTestResourcePoolId(testResourcePool.getId());
|
||||
updateTestResource(resource);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
private boolean validateNode(NodeDTO node) {
|
||||
try {
|
||||
ResponseEntity<String> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class);
|
||||
return HttpStatus.OK.equals(entity.getStatusCode());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTestResource(TestResource testResource) {
|
||||
testResource.setUpdateTime(System.currentTimeMillis());
|
||||
testResource.setCreateTime(System.currentTimeMillis());
|
||||
if (StringUtils.isBlank(testResource.getId())) {
|
||||
testResource.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
// 如果是更新操作,插入与原来的ID相同的数据
|
||||
testResourceMapper.insertSelective(testResource);
|
||||
}
|
||||
|
||||
private void deleteTestResource(String testResourcePoolId) {
|
||||
TestResourceExample testResourceExample = new TestResourceExample();
|
||||
testResourceExample.createCriteria().andTestResourcePoolIdEqualTo(testResourcePoolId);
|
||||
testResourceMapper.deleteByExample(testResourceExample);
|
||||
}
|
||||
}
|
|
@ -1,26 +1,20 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.LoadTestMapper;
|
||||
import io.metersphere.base.mapper.TestResourceMapper;
|
||||
import io.metersphere.base.mapper.TestResourcePoolMapper;
|
||||
import io.metersphere.commons.constants.ResourceStatusEnum;
|
||||
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest;
|
||||
import io.metersphere.dto.NodeDTO;
|
||||
import io.metersphere.dto.TestResourcePoolDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
@ -40,16 +34,14 @@ import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public class TestResourcePoolService {
|
||||
|
||||
private final static String nodeControllerUrl = "http://%s:%s/status";
|
||||
|
||||
@Resource
|
||||
private TestResourcePoolMapper testResourcePoolMapper;
|
||||
@Resource
|
||||
private TestResourceMapper testResourceMapper;
|
||||
@Resource
|
||||
private RestTemplate restTemplateWithTimeOut;
|
||||
@Resource
|
||||
private LoadTestMapper loadTestMapper;
|
||||
@Resource
|
||||
private NodeResourcePoolService nodeResourcePoolService;
|
||||
|
||||
public TestResourcePoolDTO addTestResourcePool(TestResourcePoolDTO testResourcePool) {
|
||||
checkTestResourcePool(testResourcePool);
|
||||
|
@ -168,61 +160,14 @@ public class TestResourcePoolService {
|
|||
}
|
||||
|
||||
private boolean validateTestResourcePool(TestResourcePoolDTO testResourcePool) {
|
||||
return validateNodes(testResourcePool);
|
||||
}
|
||||
|
||||
private boolean validateNodes(TestResourcePoolDTO testResourcePool) {
|
||||
if (CollectionUtils.isEmpty(testResourcePool.getResources())) {
|
||||
MSException.throwException(Translator.get("no_nodes_message"));
|
||||
}
|
||||
|
||||
deleteTestResource(testResourcePool.getId());
|
||||
List<String> nodeIps = testResourcePool.getResources().stream()
|
||||
.map(resource -> {
|
||||
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
|
||||
return nodeDTO.getIp();
|
||||
})
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (nodeIps.size() < testResourcePool.getResources().size()) {
|
||||
MSException.throwException(Translator.get("duplicate_node_ip"));
|
||||
}
|
||||
testResourcePool.setStatus(VALID.name());
|
||||
boolean isValid = true;
|
||||
for (TestResource resource : testResourcePool.getResources()) {
|
||||
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
|
||||
boolean isValidate = validateNode(nodeDTO);
|
||||
if (!isValidate) {
|
||||
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
|
||||
resource.setStatus(ResourceStatusEnum.INVALID.name());
|
||||
isValid = false;
|
||||
} else {
|
||||
resource.setStatus(VALID.name());
|
||||
if (StringUtils.equalsIgnoreCase(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.name())) {
|
||||
KubernetesResourcePoolService resourcePoolService = CommonBeanFactory.getBean(KubernetesResourcePoolService.class);
|
||||
if (resourcePoolService == null) {
|
||||
return false;
|
||||
}
|
||||
resource.setTestResourcePoolId(testResourcePool.getId());
|
||||
updateTestResource(resource);
|
||||
return resourcePoolService.validate(testResourcePool);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private boolean validateNode(NodeDTO node) {
|
||||
try {
|
||||
ResponseEntity<String> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class);
|
||||
return HttpStatus.OK.equals(entity.getStatusCode());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTestResource(TestResource testResource) {
|
||||
testResource.setUpdateTime(System.currentTimeMillis());
|
||||
testResource.setCreateTime(System.currentTimeMillis());
|
||||
if (StringUtils.isBlank(testResource.getId())) {
|
||||
testResource.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
// 如果是更新操作,插入与原来的ID相同的数据
|
||||
testResourceMapper.insertSelective(testResource);
|
||||
return nodeResourcePoolService.validate(testResourcePool);
|
||||
}
|
||||
|
||||
private void deleteTestResource(String testResourcePoolId) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bb494fc68a2367359c9048fa7250c7618de4afb6
|
||||
Subproject commit 1fe20ba15a7ca3fe9f77ddf866021e7c7dfe5969
|
|
@ -57,74 +57,75 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {WORKSPACE_ID} from '@/common/js/constants';
|
||||
import {getCurrentUser, getUUID,getCurrentProjectID} from "@/common/js/utils";
|
||||
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
|
||||
export default {
|
||||
name: "MsAddBasisScenario",
|
||||
components: {MsDialogFooter},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
scenarioForm: {},
|
||||
visible: false,
|
||||
currentModule: {},
|
||||
userOptions: [],
|
||||
rule: {
|
||||
name: [
|
||||
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
|
||||
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
|
||||
],
|
||||
principal: [{
|
||||
required: true,
|
||||
message: this.$t('api_test.automation.scenario.select_principal'),
|
||||
trigger: 'change'
|
||||
}],
|
||||
},
|
||||
}
|
||||
}
|
||||
,
|
||||
methods: {
|
||||
saveScenario(saveAs) {
|
||||
this.$refs['scenarioForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
let path = "/api/automation/create";
|
||||
this.setParameter();
|
||||
this.result = this.$post(path, this.scenarioForm, () => {
|
||||
this.visible = false;
|
||||
if (saveAs) {
|
||||
this.scenarioForm.request = JSON.stringify(this.scenarioForm.request);
|
||||
this.$parent.saveAsEdit(this.scenarioForm);
|
||||
} else {
|
||||
this.$parent.refresh(this.currentModule);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
setParameter() {
|
||||
this.scenarioForm.projectId = getCurrentProjectID();
|
||||
this.scenarioForm.id = getUUID().substring(0, 8);
|
||||
this.scenarioForm.protocol = this.currentProtocol;
|
||||
if (this.currentModule != null) {
|
||||
this.scenarioForm.modulePath = this.currentModule.method !== undefined ? this.currentModule.method : null;
|
||||
this.scenarioForm.apiScenarioModuleId = this.currentModule.id;
|
||||
import {WORKSPACE_ID} from '@/common/js/constants';
|
||||
import {getCurrentUser, getUUID, getCurrentProjectID} from "@/common/js/utils";
|
||||
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
|
||||
|
||||
export default {
|
||||
name: "MsAddBasisScenario",
|
||||
components: {MsDialogFooter},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
scenarioForm: {},
|
||||
visible: false,
|
||||
currentModule: {},
|
||||
userOptions: [],
|
||||
rule: {
|
||||
name: [
|
||||
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
|
||||
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
|
||||
],
|
||||
principal: [{
|
||||
required: true,
|
||||
message: this.$t('api_test.automation.scenario.select_principal'),
|
||||
trigger: 'change'
|
||||
}],
|
||||
},
|
||||
}
|
||||
}
|
||||
,
|
||||
methods: {
|
||||
saveScenario(saveAs) {
|
||||
this.$refs['scenarioForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
let path = "/api/automation/create";
|
||||
this.setParameter();
|
||||
this.$fileUpload(path, null, [], this.scenarioForm, () => {
|
||||
this.visible = false;
|
||||
if (saveAs) {
|
||||
this.scenarioForm.request = JSON.stringify(this.scenarioForm.request);
|
||||
this.$parent.saveAsEdit(this.scenarioForm);
|
||||
} else {
|
||||
this.$parent.refresh(this.currentModule);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
setParameter() {
|
||||
this.scenarioForm.projectId = getCurrentProjectID();
|
||||
this.scenarioForm.id = getUUID().substring(0, 8);
|
||||
this.scenarioForm.protocol = this.currentProtocol;
|
||||
if (this.currentModule != null && this.currentModule != "newId") {
|
||||
this.scenarioForm.modulePath = this.currentModule.method !== undefined ? this.currentModule.method : null;
|
||||
this.scenarioForm.apiScenarioModuleId = this.currentModule.id;
|
||||
}
|
||||
},
|
||||
getMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
|
||||
this.userOptions = response.data;
|
||||
});
|
||||
},
|
||||
open(currentModule) {
|
||||
this.scenarioForm = {principal: getCurrentUser().id};
|
||||
this.currentModule = currentModule;
|
||||
this.getMaintainerOptions();
|
||||
this.visible = true;
|
||||
}
|
||||
},
|
||||
getMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
|
||||
this.userOptions = response.data;
|
||||
});
|
||||
},
|
||||
open(currentModule) {
|
||||
this.scenarioForm = {principal: getCurrentUser().id};
|
||||
this.currentModule = currentModule;
|
||||
this.getMaintainerOptions();
|
||||
this.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<el-input v-model="tagForm.name" autocomplete="off" :placeholder="$t('commons.name')"/>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button @click="saveTag">{{$t('commons.save')}}</el-button>
|
||||
<el-button style="margin-left: 20px" @click="saveTag">{{$t('commons.save')}}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
@ -72,7 +72,7 @@
|
|||
},
|
||||
methods: {
|
||||
saveTag() {
|
||||
if (this.tagData.id != undefined && this.tagForm.id != null) {
|
||||
if (this.tagForm.id != undefined && this.tagForm.id != null) {
|
||||
this.path = "/api/tag/update";
|
||||
} else {
|
||||
this.path = "/api/tag/create";
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</el-input>
|
||||
</div>
|
||||
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
|
||||
<ms-api-request-form :headers="request.headers " :request="request" v-if="request.protocol==='HTTP'"/>
|
||||
<ms-api-request-form :referenced="true" :headers="request.headers " :request="request" v-if="request.protocol==='HTTP'"/>
|
||||
<ms-tcp-basis-parameters :request="request" v-if="request.protocol==='TCP'"/>
|
||||
<ms-sql-basis-parameters :request="request" v-if="request.protocol==='SQL'"/>
|
||||
<ms-dubbo-basis-parameters :request="request" v-if="request.protocol==='DUBBO' || request.protocol==='dubbo://'"/>
|
||||
|
|
|
@ -25,9 +25,11 @@
|
|||
</template>
|
||||
|
||||
</el-table-column>
|
||||
<el-table-column prop="tagName" :label="$t('api_test.automation.tag')" show-overflow-tooltip>
|
||||
<el-table-column prop="tagNames" :label="$t('api_test.automation.tag')" width="200px">
|
||||
<template v-slot:default="scope">
|
||||
<ms-tag type="success" effect="plain" v-if="scope.row.tagName!=undefined" :content="scope.row.tagName"/>
|
||||
<div v-for="itemName in scope.row.tagNames" :key="itemName">
|
||||
<ms-tag type="success" effect="plain" :content="itemName"/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" :label="$t('api_test.automation.creator')" show-overflow-tooltip/>
|
||||
|
@ -65,7 +67,7 @@
|
|||
:total="total"/>
|
||||
<div>
|
||||
<!-- 执行结果 -->
|
||||
<el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
|
||||
<el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="true" :modal="false" size="90%">
|
||||
<ms-api-report-detail @refresh="search" :infoDb="infoDb" :report-id="reportId" :currentProjectId="projectId"/>
|
||||
</el-drawer>
|
||||
<!--测试计划-->
|
||||
|
@ -210,8 +212,8 @@
|
|||
this.$emit('edit', row);
|
||||
},
|
||||
reductionApi(row) {
|
||||
let obj = {id: row.id, projectId: row.projectId, name: row.name, status: 'Underway'}
|
||||
this.$fileUpload("/api/automation/update", null, [], obj, () => {
|
||||
let obj = [row.id];
|
||||
this.$post("/api/automation/reduction", obj, response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.search();
|
||||
})
|
||||
|
@ -265,5 +267,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/deep/ .el-drawer__header {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -30,11 +30,9 @@
|
|||
class="ms-el-input" size="mini"></el-input>
|
||||
</template>
|
||||
<!-- 如果不是编辑状态 -->
|
||||
<span v-else>
|
||||
<i class="el-icon-delete" v-if="data.id==='gc'"/>
|
||||
<i class="el-icon-folder" v-else/>
|
||||
<span class="node-title" v-text="data.name"></span>
|
||||
</span>
|
||||
<i class="el-icon-delete" v-if="data.isEdit!=1 && data.id==='gc'"/>
|
||||
<i class="el-icon-folder" v-if="data.isEdit!=1 && data.id!='gc'"/>
|
||||
<span class="node-title" v-if="data.isEdit!=1" v-text="data.name"></span>
|
||||
|
||||
<span class="node-operate child">
|
||||
<el-tooltip
|
||||
|
@ -117,6 +115,7 @@
|
|||
},
|
||||
methods: {
|
||||
getApiModuleTree() {
|
||||
this.nextFlag = true;
|
||||
let projectId = getCurrentProjectID();
|
||||
if (projectId) {
|
||||
if (this.expandedNode.length === 0) {
|
||||
|
@ -265,6 +264,7 @@
|
|||
const children = parent.data.children || parent.data
|
||||
const index = children.findIndex(d => d.id !== undefined && data.id !== undefined && d.id === data.id)
|
||||
children.splice(index, 1);
|
||||
this.getApiModuleTree();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -313,7 +313,7 @@
|
|||
editApiModule(node, data) {
|
||||
let projectId = getCurrentProjectID();
|
||||
if (!projectId) {
|
||||
this.$error("$t('api_test.select_project')");
|
||||
this.$error(this.$t('api_test.select_project'));
|
||||
return;
|
||||
}
|
||||
let url = "";
|
||||
|
|
|
@ -261,7 +261,7 @@
|
|||
<ms-run :debug="true" :environment="currentEnvironmentId" :reportId="reportId" :run-data="debugData"
|
||||
@runRefresh="runRefresh" ref="runTest"/>
|
||||
<!-- 调试结果 -->
|
||||
<el-drawer :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
|
||||
<el-drawer :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr" :withHeader="true" :modal="false" size="90%">
|
||||
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="projectId"/>
|
||||
</el-drawer>
|
||||
|
||||
|
@ -352,6 +352,9 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.currentScenario.apiScenarioModuleId) {
|
||||
this.currentScenario.apiScenarioModuleId = "";
|
||||
}
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.operatingElements = ELEMENTS.get("ALL");
|
||||
this.getMaintainerOptions();
|
||||
|
@ -450,8 +453,12 @@
|
|||
this.reload();
|
||||
},
|
||||
addScenario(arr) {
|
||||
if (arr.length > 0) {
|
||||
if (arr && arr.length > 0) {
|
||||
arr.forEach(item => {
|
||||
if (item.id === this.currentScenario.id) {
|
||||
this.$error("不能引用或复制自身!");
|
||||
return;
|
||||
}
|
||||
item.enable === undefined ? item.enable = true : item.enable;
|
||||
this.scenarioDefinition.push(item);
|
||||
})
|
||||
|
@ -476,7 +483,7 @@
|
|||
request.enable === undefined ? request.enable = true : request.enable;
|
||||
request.active = false;
|
||||
request.resourceId = getUUID();
|
||||
if (referenced === 'REF') {
|
||||
if (referenced === 'REF' || !request.hashTree) {
|
||||
request.hashTree = [];
|
||||
}
|
||||
if (this.selectedTreeNode != undefined) {
|
||||
|
@ -496,7 +503,7 @@
|
|||
request.enable === undefined ? request.enable = true : request.enable;
|
||||
request.active = false;
|
||||
request.resourceId = getUUID();
|
||||
if (referenced === 'REF') {
|
||||
if (referenced === 'REF' || !request.hashTree) {
|
||||
request.hashTree = [];
|
||||
}
|
||||
if (this.selectedTreeNode != undefined) {
|
||||
|
@ -506,6 +513,8 @@
|
|||
}
|
||||
})
|
||||
this.apiListVisible = false;
|
||||
this.currentRow.cases = [];
|
||||
this.currentRow.apis = [];
|
||||
this.sort();
|
||||
this.reload();
|
||||
},
|
||||
|
@ -601,6 +610,7 @@
|
|||
},
|
||||
allowDrag() {
|
||||
this.sort();
|
||||
this.reload();
|
||||
},
|
||||
nodeExpand(data) {
|
||||
if (data.resourceId) {
|
||||
|
@ -708,10 +718,6 @@
|
|||
}
|
||||
},
|
||||
setParameter() {
|
||||
this.currentScenario.projectId = this.projectId;
|
||||
if (!this.currentScenario.id) {
|
||||
this.currentScenario.id = getUUID();
|
||||
}
|
||||
this.currentScenario.stepTotal = this.scenarioDefinition.length;
|
||||
this.currentScenario.modulePath = this.getPath(this.currentScenario.apiScenarioModuleId);
|
||||
// 构建一个场景对象 方便引用处理
|
||||
|
@ -861,4 +867,8 @@
|
|||
/deep/ .el-step__icon.is-text {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
/deep/ .el-drawer__header {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
if (response.data) {
|
||||
response.data.forEach(item => {
|
||||
let scenarioDefinition = JSON.parse(item.scenarioDefinition);
|
||||
let obj = {id: item.id, name: item.name, type: "scenario", referenced: 'Copy', resourceId: getUUID(), hashTree: scenarioDefinition};
|
||||
let obj = {id: item.id, name: item.name, type: "scenario", referenced: 'Copy', resourceId: getUUID(), hashTree: scenarioDefinition.hashTree};
|
||||
scenarios.push(obj);
|
||||
})
|
||||
this.$emit('addScenario', scenarios);
|
||||
|
|
|
@ -193,6 +193,7 @@
|
|||
this.total = response.data.itemCount;
|
||||
this.tableData = response.data.listObject;
|
||||
});
|
||||
this.selectRows = new Set();
|
||||
},
|
||||
handleSelect(selection, row) {
|
||||
row.hashTree = [];
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
:currentRow="currentRow"
|
||||
@editApi="editApi"
|
||||
@handleCase="handleCase"
|
||||
@handleEditBatch="handleEditBatch"
|
||||
@showExecResult="showExecResult"
|
||||
ref="apiList"/>
|
||||
|
||||
<!-- 添加/编辑测试窗口-->
|
||||
|
@ -51,10 +51,10 @@
|
|||
|
||||
<!-- 快捷调试 -->
|
||||
<div v-else-if="item.type=== 'debug'" class="ms-api-div">
|
||||
<ms-debug-http-page :currentProtocol="currentProtocol" @saveAs="editApi" v-if="currentProtocol==='HTTP'"/>
|
||||
<ms-debug-jdbc-page :currentProtocol="currentProtocol" @saveAs="editApi" v-if="currentProtocol==='SQL'"/>
|
||||
<ms-debug-tcp-page :currentProtocol="currentProtocol" @saveAs="editApi" v-if="currentProtocol==='TCP'"/>
|
||||
<ms-debug-dubbo-page :currentProtocol="currentProtocol" @saveAs="editApi" v-if="currentProtocol==='DUBBO'"/>
|
||||
<ms-debug-http-page :currentProtocol="currentProtocol" :testCase="item.api" @saveAs="editApi" v-if="currentProtocol==='HTTP'"/>
|
||||
<ms-debug-jdbc-page :currentProtocol="currentProtocol" :testCase="item.api" @saveAs="editApi" v-if="currentProtocol==='SQL'"/>
|
||||
<ms-debug-tcp-page :currentProtocol="currentProtocol" :testCase="item.api" @saveAs="editApi" v-if="currentProtocol==='TCP'"/>
|
||||
<ms-debug-dubbo-page :currentProtocol="currentProtocol" :testCase="item.api" @saveAs="editApi" v-if="currentProtocol==='DUBBO'"/>
|
||||
</div>
|
||||
|
||||
<!-- 测试-->
|
||||
|
@ -154,7 +154,10 @@
|
|||
}
|
||||
},
|
||||
handleTabAdd(e) {
|
||||
let api = {status: "Underway", method: "GET", userId: getCurrentUser().id, url: "", protocol: this.currentProtocol};
|
||||
let api = {
|
||||
status: "Underway", method: "GET", userId: getCurrentUser().id,
|
||||
url: "", protocol: this.currentProtocol, environmentId: ""
|
||||
};
|
||||
this.handleTabsEdit(this.$t('api_test.definition.request.title'), e, api);
|
||||
},
|
||||
handleTabClose() {
|
||||
|
@ -195,8 +198,8 @@
|
|||
});
|
||||
this.apiDefaultTab = newTabName;
|
||||
},
|
||||
debug() {
|
||||
this.handleTabsEdit(this.$t('api_test.definition.request.fast_debug'), "debug");
|
||||
debug(id) {
|
||||
this.handleTabsEdit(this.$t('api_test.definition.request.fast_debug'), "debug",id);
|
||||
},
|
||||
editApi(row) {
|
||||
let name = this.$t('api_test.definition.request.edit_api');
|
||||
|
@ -205,11 +208,6 @@
|
|||
}
|
||||
this.handleTabsEdit(name, "ADD", row);
|
||||
},
|
||||
handleEditBatch(rows) {
|
||||
rows.forEach(row => {
|
||||
this.handleTabsEdit(this.$t('api_test.definition.request.edit_api') + "-" + row.name, "ADD", row);
|
||||
})
|
||||
},
|
||||
handleCase(api) {
|
||||
this.currentApi = api;
|
||||
this.showCasePage = false;
|
||||
|
@ -253,6 +251,9 @@
|
|||
},
|
||||
changeProtocol(data) {
|
||||
this.currentProtocol = data;
|
||||
},
|
||||
showExecResult(row){
|
||||
this.debug(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
|
||||
<label class="ms-api-label">{{$t('test_track.case.priority')}}</label>
|
||||
<el-select size="small" v-model="item.priority" class="ms-api-select">
|
||||
<el-select size="small" v-model="item.priority" class="ms-api-select" @change="changePriority(item)">
|
||||
<el-option v-for="grd in priorities" :key="grd.id" :label="grd.name" :value="grd.id"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
|
@ -57,16 +57,19 @@
|
|||
|
||||
<el-col :span="4">
|
||||
<ms-tip-button @click="singleRun(item)" :tip="$t('api_test.run')" icon="el-icon-video-play"
|
||||
style="background-color: #409EFF;color: white" size="mini" circle/>
|
||||
style="background-color: #409EFF;color: white" size="mini" :disabled="item.type=='create'" circle/>
|
||||
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
|
||||
size="mini" circle/>
|
||||
size="mini" :disabled="item.type=='create'" circle/>
|
||||
<ms-tip-button @click="deleteCase(index,item)" :tip="$t('commons.delete')" icon="el-icon-delete"
|
||||
size="mini" circle/>
|
||||
size="mini" :disabled="item.type=='create'" circle/>
|
||||
<ms-api-extend-btns :row="item"/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="3">
|
||||
<div v-if="item.type!='create'">{{getResult(item.execResult)}}</div>
|
||||
<el-link type="danger" v-if="item.execResult && item.execResult==='error'" @click="showExecResult(item)">{{getResult(item.execResult)}}</el-link>
|
||||
<el-link v-else-if="item.execResult && item.execResult==='success'" @click="showExecResult(item)">{{getResult(item.execResult)}}</el-link>
|
||||
<div v-else> {{getResult(item.execResult)}}</div>
|
||||
|
||||
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
|
||||
<span> {{item.updateTime | timestampFormatDate }}</span>
|
||||
{{item.updateUser}}
|
||||
|
@ -222,7 +225,6 @@
|
|||
this.$warning(this.$t('api_test.environment.select_environment'));
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.apiCaseList.length > 0) {
|
||||
this.apiCaseList.forEach(item => {
|
||||
if (item.type != "create") {
|
||||
|
@ -231,9 +233,13 @@
|
|||
this.runData.push(item.request);
|
||||
}
|
||||
})
|
||||
this.loading = true;
|
||||
/*触发执行操作*/
|
||||
this.reportId = getUUID().substring(0, 8);
|
||||
if (this.runData.length > 0) {
|
||||
this.loading = true;
|
||||
/*触发执行操作*/
|
||||
this.reportId = getUUID().substring(0, 8);
|
||||
} else {
|
||||
this.$warning("没有可执行的用例!");
|
||||
}
|
||||
} else {
|
||||
this.$warning("没有可执行的用例!");
|
||||
}
|
||||
|
@ -265,7 +271,7 @@
|
|||
});
|
||||
},
|
||||
copyCase(data) {
|
||||
let obj = {name: data.name, priority: data.priority, type: 'create', active: false, request: data.request};
|
||||
let obj = {name: "copy_"+data.name, priority: data.priority, type: 'create', active: false, request: data.request};
|
||||
this.apiCaseList.unshift(obj);
|
||||
},
|
||||
addCase() {
|
||||
|
@ -329,6 +335,9 @@
|
|||
for (let index in response.data) {
|
||||
let test = response.data[index];
|
||||
test.request = JSON.parse(test.request);
|
||||
if (!test.request.hashTree) {
|
||||
test.request.hashTree = [];
|
||||
}
|
||||
}
|
||||
this.apiCaseList = response.data;
|
||||
if (this.apiCaseList.length == 0) {
|
||||
|
@ -343,6 +352,11 @@
|
|||
return true;
|
||||
}
|
||||
},
|
||||
changePriority(row) {
|
||||
if (row.type != 'create') {
|
||||
this.saveTestCase(row);
|
||||
}
|
||||
},
|
||||
saveTestCase(row) {
|
||||
if (this.validate(row)) {
|
||||
return;
|
||||
|
@ -380,6 +394,10 @@
|
|||
},
|
||||
handleClose() {
|
||||
this.visible = false;
|
||||
},
|
||||
showExecResult(row) {
|
||||
this.visible = false;
|
||||
this.$emit('showExecResult', row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,11 @@
|
|||
break;
|
||||
}
|
||||
if (this.currentApi.response != null && this.currentApi.response != 'null' && this.currentApi.response != undefined) {
|
||||
this.response = new ResponseFactory(JSON.parse(this.currentApi.response));
|
||||
if (Object.prototype.toString.call(this.currentApi.response).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
|
||||
this.response = this.currentApi.response;
|
||||
} else {
|
||||
this.response = new ResponseFactory(JSON.parse(this.currentApi.response));
|
||||
}
|
||||
} else {
|
||||
this.response = {headers: [], body: new Body(), statusCode: [], type: "HTTP"};
|
||||
}
|
||||
|
@ -73,6 +77,9 @@
|
|||
} else {
|
||||
this.reqUrl = "/api/definition/create";
|
||||
}
|
||||
if (!this.request.hashTree) {
|
||||
this.request.hashTree = [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
runTest(data) {
|
||||
|
|
|
@ -81,7 +81,9 @@
|
|||
<ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
</el-card>
|
||||
<ms-api-case-list @refresh="initApiTable" :currentApi="selectApi" ref="caseList"/>
|
||||
<ms-api-case-list @refresh="initApiTable" @showExecResult="showExecResult" :currentApi="selectApi" ref="caseList"/>
|
||||
<!--批量编辑-->
|
||||
<ms-batch-edit ref="batchEdit" @batchEdit="batchEdit" :typeArr="typeArr" :value-arr="valueArr"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
@ -99,8 +101,10 @@
|
|||
import MsContainer from "../../../common/components/MsContainer";
|
||||
import MsBottomContainer from "./BottomContainer";
|
||||
import ShowMoreBtn from "../../../../components/track/case/components/ShowMoreBtn";
|
||||
import {API_METHOD_COLOUR} from "../model/JsonData";
|
||||
import MsBatchEdit from "./basis/BatchEdit";
|
||||
import {API_METHOD_COLOUR, REQ_METHOD, API_STATUS} from "../model/JsonData";
|
||||
import {getCurrentProjectID} from "@/common/js/utils";
|
||||
import {WORKSPACE_ID} from '../../../../../common/js/constants';
|
||||
|
||||
export default {
|
||||
name: "ApiList",
|
||||
|
@ -114,7 +118,8 @@
|
|||
MsApiCaseList,
|
||||
MsContainer,
|
||||
MsBottomContainer,
|
||||
ShowMoreBtn
|
||||
ShowMoreBtn,
|
||||
MsBatchEdit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -127,6 +132,16 @@
|
|||
{name: this.$t('api_test.definition.request.batch_delete'), handleClick: this.handleDeleteBatch},
|
||||
{name: this.$t('api_test.definition.request.batch_edit'), handleClick: this.handleEditBatch}
|
||||
],
|
||||
typeArr: [
|
||||
{id: 'status', name: this.$t('api_test.definition.api_case_status')},
|
||||
{id: 'method', name: this.$t('api_test.definition.api_type')},
|
||||
{id: 'userId', name: this.$t('api_test.definition.api_principal')},
|
||||
],
|
||||
valueArr: {
|
||||
status: API_STATUS,
|
||||
method: REQ_METHOD,
|
||||
userId: [],
|
||||
},
|
||||
methodColorMap: new Map(API_METHOD_COLOUR),
|
||||
tableData: [],
|
||||
currentPage: 1,
|
||||
|
@ -147,6 +162,7 @@
|
|||
created: function () {
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.initApiTable();
|
||||
this.getMaintainerOptions();
|
||||
},
|
||||
watch: {
|
||||
currentModule() {
|
||||
|
@ -158,6 +174,7 @@
|
|||
},
|
||||
methods: {
|
||||
initApiTable() {
|
||||
this.selectRows = new Set();
|
||||
this.condition.filters = ["Prepare", "Underway", "Completed"];
|
||||
if (this.currentModule != null) {
|
||||
if (this.currentModule.id == "root") {
|
||||
|
@ -181,6 +198,12 @@
|
|||
this.tableData = response.data.listObject;
|
||||
});
|
||||
},
|
||||
getMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
|
||||
this.valueArr.userId = response.data;
|
||||
});
|
||||
},
|
||||
handleSelect(selection, row) {
|
||||
row.hashTree = [];
|
||||
if (this.selectRows.has(row)) {
|
||||
|
@ -230,10 +253,8 @@
|
|||
this.$emit('editApi', row);
|
||||
},
|
||||
reductionApi(row) {
|
||||
row.status = 'Underway';
|
||||
row.request = null;
|
||||
row.response = null;
|
||||
this.$fileUpload("/api/definition/update", null, [], row, () => {
|
||||
let ids = [row.id];
|
||||
this.$post('/api/definition/reduction/', ids, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.search();
|
||||
});
|
||||
|
@ -270,11 +291,30 @@
|
|||
}
|
||||
},
|
||||
handleEditBatch() {
|
||||
this.$emit('handleEditBatch', this.selectRows);
|
||||
this.$refs.batchEdit.open();
|
||||
},
|
||||
batchEdit(form) {
|
||||
let arr = Array.from(this.selectRows);
|
||||
let ids = arr.map(row => row.id);
|
||||
let param = {};
|
||||
param[form.type] = form.value;
|
||||
param.ids = ids;
|
||||
this.$post('/api/definition/batch/edit', param, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.initApiTable();
|
||||
});
|
||||
},
|
||||
handleTestCase(api) {
|
||||
this.selectApi = api;
|
||||
let request = JSON.parse(api.request);
|
||||
let request = {};
|
||||
if (Object.prototype.toString.call(api.request).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
|
||||
request = api.request;
|
||||
} else {
|
||||
request = JSON.parse(api.request);
|
||||
}
|
||||
if (!request.hashTree) {
|
||||
request.hashTree = [];
|
||||
}
|
||||
this.selectApi.url = request.path;
|
||||
this.$refs.caseList.open(this.selectApi);
|
||||
},
|
||||
|
@ -304,6 +344,9 @@
|
|||
return this.methodColorMap.get(method);
|
||||
}
|
||||
},
|
||||
showExecResult(row) {
|
||||
this.$emit('showExecResult', row);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -35,11 +35,10 @@
|
|||
@node-click="selectModule"
|
||||
@node-drag-end="handleDragEnd"
|
||||
:filter-node-method="filterNode"
|
||||
draggable
|
||||
:draggable="true"
|
||||
:allow-drop="allowDrop"
|
||||
:allow-drag="allowDrag" ref="tree">
|
||||
<span class="custom-tree-node father"
|
||||
slot-scope="{ node, data }">
|
||||
<span class="custom-tree-node father" slot-scope="{ node, data }">
|
||||
<!-- 如果是编辑状态 -->
|
||||
<template v-if="data.isEdit==1">
|
||||
<el-input ref="input"
|
||||
|
@ -48,12 +47,9 @@
|
|||
class="ms-el-input" size="mini"></el-input>
|
||||
</template>
|
||||
<!-- 如果不是编辑状态 -->
|
||||
<div v-else>
|
||||
<i class="el-icon-delete" v-if="data.id==='gc'"/>
|
||||
<i class="el-icon-folder" v-else/>
|
||||
<span class="node-title" v-text="data.name"></span>
|
||||
</div>
|
||||
|
||||
<i class="el-icon-delete" v-if="data.isEdit!=1 && data.id==='gc'"/>
|
||||
<i class="el-icon-folder" v-if="data.isEdit!=1 && data.id!='gc'"/>
|
||||
<span class="node-title" v-if="data.isEdit!=1" v-text="data.name"></span>
|
||||
<span class="node-operate child">
|
||||
<el-tooltip
|
||||
v-if="data.id!='root' && data.id!='gc'"
|
||||
|
@ -138,6 +134,7 @@
|
|||
if (this.expandedNode.length === 0) {
|
||||
this.expandedNode.push("root");
|
||||
}
|
||||
this.nextFlag = true;
|
||||
this.result = this.$get("/api/module/list/" + this.projectId + "/" + this.protocol, response => {
|
||||
if (response.data != undefined && response.data != null) {
|
||||
this.data[1].children = response.data;
|
||||
|
@ -449,28 +446,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.father .child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.father:hover .child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
width: 0px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0px 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.node-operate > i {
|
||||
color: #409eff;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
/deep/ .el-tree-node__content {
|
||||
height: 33px;
|
||||
}
|
||||
|
@ -502,4 +477,26 @@
|
|||
width: 90px;
|
||||
}
|
||||
|
||||
.father .child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.father:hover .child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
width: 0px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
padding: 0px 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.node-operate > i {
|
||||
color: #409eff;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -103,8 +103,12 @@
|
|||
let threadGroup = new ThreadGroup();
|
||||
threadGroup.hashTree = [];
|
||||
testPlan.hashTree = [threadGroup];
|
||||
|
||||
this.runData.forEach(item => {
|
||||
if (!item.useEnvironment) {
|
||||
this.$error(this.$t("api_test.environment.select_environment"));
|
||||
this.$emit('runRefresh', {});
|
||||
return;
|
||||
}
|
||||
threadGroup.hashTree.push(item);
|
||||
})
|
||||
let reqObj = {id: this.reportId, testElement: testPlan};
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="25%"
|
||||
class="batch-edit-dialog"
|
||||
:destroy-on-close="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form :model="form" label-position="right" label-width="150px" size="medium" ref="form" :rules="rules">
|
||||
<el-form-item :label="$t('test_track.case.batch_update', [size])" prop="type">
|
||||
<el-select v-model="form.type" style="width: 80%" @change="changeType">
|
||||
<el-option v-for="(type, index) in typeArr" :key="index" :value="type.id" :label="type.name"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('test_track.case.updated_attr_value')" prop="value">
|
||||
<el-select v-model="form.value" style="width: 80%" :filterable="filterable">
|
||||
<el-option v-for="(option, index) in options" :key="index" :value="option.id" :label="option.label">
|
||||
<div v-if="option.email">
|
||||
<span>{{option.id}}({{option.name}})</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<ms-dialog-footer
|
||||
@cancel="dialogVisible = false"
|
||||
@confirm="submit('form')"/>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "BatchEdit",
|
||||
components: {
|
||||
MsDialogFooter
|
||||
},
|
||||
props: {
|
||||
typeArr: Array,
|
||||
valueArr: Object,
|
||||
dialogTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('api_test.definition.request.batch_edit')
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
form: {},
|
||||
size: 0,
|
||||
rules: {
|
||||
type: {required: true, message: this.$t('test_track.case.please_select_attr'), trigger: ['blur', 'change']},
|
||||
value: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur', 'change']}
|
||||
},
|
||||
options: [],
|
||||
filterable: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(form) {
|
||||
this.$refs[form].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$emit("batchEdit", this.form);
|
||||
this.dialogVisible = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
open() {
|
||||
this.dialogVisible = true;
|
||||
this.size = this.$parent.selectRows.size;
|
||||
listenGoBack(this.handleClose);
|
||||
},
|
||||
handleClose() {
|
||||
this.form = {};
|
||||
this.options = [];
|
||||
removeGoBackListener(this.handleClose);
|
||||
},
|
||||
changeType(val) {
|
||||
this.$set(this.form, "value", "");
|
||||
this.filterable = val === "maintainerOptions";
|
||||
this.options = this.valueArr[val];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -48,7 +48,7 @@
|
|||
<el-col :span="3">
|
||||
<div class="ms-api-header-select">
|
||||
<el-input size="small" :placeholder="$t('api_test.definition.request.select_case')"
|
||||
v-model="condition.name" @blur="getApiTest"/>
|
||||
v-model="condition.name" @blur="getApiTest" @keyup.enter.native="getApiTest"/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
|
@ -103,6 +103,7 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.environment = undefined;
|
||||
this.getEnvironments();
|
||||
},
|
||||
watch: {
|
||||
|
@ -118,16 +119,6 @@
|
|||
this.environments.forEach(environment => {
|
||||
parseEnvironment(environment);
|
||||
});
|
||||
let hasEnvironment = false;
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === this.api.environmentId) {
|
||||
hasEnvironment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEnvironment) {
|
||||
this.environment = undefined;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.environment = undefined;
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
props: {
|
||||
currentProtocol: String,
|
||||
scenario: Boolean,
|
||||
testCase: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -66,7 +67,27 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.request = createComponent("DubboSampler");
|
||||
if (this.testCase) {
|
||||
// 执行结果信息
|
||||
let url = "/api/definition/report/getReport/" + this.testCase.id;
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data.content);
|
||||
this.responseData = data;
|
||||
}
|
||||
});
|
||||
this.request = this.testCase.request;
|
||||
if (this.request) {
|
||||
this.debugForm.method = this.request.method;
|
||||
if (this.request.url) {
|
||||
this.debugForm.url = this.request.url;
|
||||
} else {
|
||||
this.debugForm.url = this.request.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.request = createComponent("DubboSampler");
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugResultId() {
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
components: {MsRequestResultTail, MsResponseResult, MsApiRequestForm, MsRequestMetric, MsResponseText, MsRun},
|
||||
props: {
|
||||
currentProtocol: String,
|
||||
testCase: {},
|
||||
scenario: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
@ -79,12 +80,32 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.createHttp();
|
||||
if (this.testCase) {
|
||||
// 执行结果信息
|
||||
let url = "/api/definition/report/getReport/" + this.testCase.id;
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data.content);
|
||||
this.responseData = data;
|
||||
}
|
||||
});
|
||||
this.request = this.testCase.request;
|
||||
if (this.request) {
|
||||
this.debugForm.method = this.request.method;
|
||||
if (this.request.url) {
|
||||
this.debugForm.url = this.request.url;
|
||||
} else {
|
||||
this.debugForm.url = this.request.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.createHttp();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugResultId() {
|
||||
this.getResult()
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleCommand(e) {
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
props: {
|
||||
currentProtocol: String,
|
||||
scenario: Boolean,
|
||||
testCase: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -68,7 +69,27 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.request = createComponent("JDBCSampler");
|
||||
if (this.testCase) {
|
||||
// 执行结果信息
|
||||
let url = "/api/definition/report/getReport/" + this.testCase.id;
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data.content);
|
||||
this.responseData = data;
|
||||
}
|
||||
});
|
||||
this.request = this.testCase.request;
|
||||
if (this.request) {
|
||||
this.debugForm.method = this.request.method;
|
||||
if (this.request.url) {
|
||||
this.debugForm.url = this.request.url;
|
||||
} else {
|
||||
this.debugForm.url = this.request.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.request = createComponent("JDBCSampler");
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugResultId() {
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
props: {
|
||||
currentProtocol: String,
|
||||
scenario: Boolean,
|
||||
testCase: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -67,7 +68,27 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.request = createComponent("TCPSampler");
|
||||
if (this.testCase) {
|
||||
// 执行结果信息
|
||||
let url = "/api/definition/report/getReport/" + this.testCase.id;
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data.content);
|
||||
this.responseData = data;
|
||||
}
|
||||
});
|
||||
this.request = this.testCase.request;
|
||||
if (this.request) {
|
||||
this.debugForm.method = this.request.method;
|
||||
if (this.request.url) {
|
||||
this.debugForm.url = this.request.url;
|
||||
} else {
|
||||
this.debugForm.url = this.request.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.request = createComponent("TCPSampler");
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugResultId() {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<el-row>
|
||||
<el-col :span="21">
|
||||
<!-- HTTP 请求参数 -->
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%">
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%" v-loading="isReloadData">
|
||||
<el-tabs v-model="activeName" class="request-tabs">
|
||||
<!-- 请求头-->
|
||||
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
|
||||
|
@ -58,23 +58,23 @@
|
|||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
|
||||
<div v-if="!referenced">
|
||||
<div v-for="row in request.hashTree" :key="row.id">
|
||||
<!-- 前置脚本 -->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"
|
||||
:jsr223-processor="row"/>
|
||||
<!--后置脚本-->
|
||||
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"
|
||||
:jsr223-processor="row"/>
|
||||
<!--断言规则-->
|
||||
<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>
|
||||
<!--提取规则-->
|
||||
<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<!--操作按钮-->
|
||||
<el-col :span="3" class="ms-left-cell">
|
||||
<el-col :span="3" class="ms-left-cell" v-if="!referenced">
|
||||
<el-button class="ms-left-buttion" size="small" @click="addPre">+{{$t('api_test.definition.request.pre_script')}}</el-button>
|
||||
<br/>
|
||||
<el-button class="ms-left-buttion" size="small" @click="addPost">+{{$t('api_test.definition.request.post_script')}}</el-button>
|
||||
|
@ -116,6 +116,7 @@
|
|||
return [];
|
||||
}
|
||||
},
|
||||
referenced: Boolean,
|
||||
isShowEnable: Boolean,
|
||||
jsonPathList: Array,
|
||||
isReadOnly: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="request-form">
|
||||
<component :is="component" :is-read-only="isReadOnly" :request="request" :headers="headers" :isShowEnable="isShowEnable"/>
|
||||
<component :is="component" :is-read-only="isReadOnly" :referenced="referenced" :request="request" :headers="headers" :isShowEnable="isShowEnable"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -17,6 +17,10 @@
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
referenced: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
|
@ -236,7 +236,7 @@
|
|||
let hasEnvironment = false;
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === this.api.environmentId) {
|
||||
this.api.environment = this.environments[i];
|
||||
this.api.environmentId = this.environments[i].id;
|
||||
hasEnvironment = true;
|
||||
break;
|
||||
}
|
||||
|
@ -261,7 +261,8 @@
|
|||
environmentChange(value) {
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === value) {
|
||||
this.api.request.useEnvironment = this.environments[i].id;
|
||||
this.api.environmentId = value;
|
||||
this.api.request.useEnvironment = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
|
||||
<el-submenu :class="{'deactivation':!isProjectActivation}"
|
||||
v-permission="['test_manager','test_user','test_viewer']" index="3">
|
||||
<template v-slot:title>{{ $t('commons.project') }}</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent"/>
|
||||
<template v-slot:title>
|
||||
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
|
||||
{{ $t('commons.project') }}: {{currentProject}}
|
||||
</span>
|
||||
</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
|
||||
<el-divider class="menu-divider"/>
|
||||
<el-menu-item :index="'/setting/project/create'">
|
||||
<font-awesome-icon :icon="['fa', 'plus']"/>
|
||||
|
@ -108,6 +112,7 @@ export default {
|
|||
isProjectActivation: true,
|
||||
isRouterAlive: true,
|
||||
apiTestProjectPath: '',
|
||||
currentProject: ''
|
||||
}
|
||||
},
|
||||
// watch: {
|
||||
|
|
|
@ -26,13 +26,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
ROLE_ORG_ADMIN,
|
||||
ROLE_TEST_MANAGER,
|
||||
ROLE_TEST_USER,
|
||||
ROLE_TEST_VIEWER,
|
||||
WORKSPACE_ID
|
||||
} from '../../../../common/js/constants';
|
||||
import {
|
||||
PROJECT_ID,
|
||||
ROLE_ORG_ADMIN,
|
||||
ROLE_TEST_MANAGER,
|
||||
ROLE_TEST_USER,
|
||||
ROLE_TEST_VIEWER,
|
||||
WORKSPACE_ID
|
||||
} from '../../../../common/js/constants';
|
||||
import {getCurrentUser, hasRoles, saveLocalStorage} from "../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
|
@ -107,6 +108,7 @@
|
|||
if (response.data.workspaceId) {
|
||||
localStorage.setItem("workspace_id", response.data.workspaceId);
|
||||
}
|
||||
localStorage.removeItem(PROJECT_ID)
|
||||
this.$router.push('/');
|
||||
window.location.reload();
|
||||
});
|
||||
|
@ -119,6 +121,7 @@
|
|||
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
|
||||
saveLocalStorage(response);
|
||||
localStorage.setItem("workspace_id", workspaceId);
|
||||
localStorage.removeItem(PROJECT_ID)
|
||||
this.$router.push('/');
|
||||
window.location.reload();
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div v-loading="result.loading" class="search-list">
|
||||
<el-input placeholder="搜索项目"
|
||||
prefix-icon="el-icon-search"
|
||||
v-model="search_text"
|
||||
v-model="searchString"
|
||||
clearable
|
||||
class="search-input"
|
||||
size="small"/>
|
||||
|
@ -11,13 +11,13 @@
|
|||
无数据
|
||||
</span>
|
||||
</div>
|
||||
<div v-else style="height: 120px;overflow: auto">
|
||||
<div v-else style="height: 150px;overflow: auto">
|
||||
<el-menu-item :key="i.id" v-for="i in items" @click="change(i.id)">
|
||||
<template slot="title">
|
||||
<div class="title">
|
||||
{{ i.name }}
|
||||
<i class="el-icon-check" v-if="i.id === currentProjectId"></i>
|
||||
</div>
|
||||
<i class="el-icon-check" v-if="i.id === currentProjectId"></i>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</div>
|
||||
|
@ -32,53 +32,91 @@ import {PROJECT_ID, ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "@
|
|||
export default {
|
||||
name: "SearchList",
|
||||
props: {
|
||||
options: Object
|
||||
options: Object,
|
||||
currentProject: String
|
||||
},
|
||||
created() {
|
||||
if (getCurrentUser().lastProjectId) {
|
||||
localStorage.setItem(PROJECT_ID, getCurrentUser().lastProjectId);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
computed: {
|
||||
currentProjectId() {
|
||||
return localStorage.getItem(PROJECT_ID)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
items: [],
|
||||
search_text: '',
|
||||
searchArray: [],
|
||||
searchString: '',
|
||||
userId: getCurrentUser().id,
|
||||
currentProjectId: localStorage.getItem(PROJECT_ID)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search_text(val) {
|
||||
if (!val) {
|
||||
this.init();
|
||||
} else {
|
||||
this.search();
|
||||
}
|
||||
searchString(val) {
|
||||
this.query(val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init: function () {
|
||||
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
|
||||
this.result = this.$get(this.options.url, (response) => {
|
||||
this.result = this.$get("/project/listAll", response => {
|
||||
this.items = response.data;
|
||||
this.items = this.items.splice(0, 3);
|
||||
this.searchArray = response.data;
|
||||
if (!getCurrentProjectID() && this.items.length > 0) {
|
||||
this.change(this.items[0].id);
|
||||
}
|
||||
});
|
||||
let projectId = getCurrentProjectID();
|
||||
this.changeProjectName(projectId);
|
||||
})
|
||||
}
|
||||
},
|
||||
search() {
|
||||
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
|
||||
this.result = this.$post("/project/search", {name: this.search_text},response => {
|
||||
this.result = this.$post("/project/search", {name: this.searchString},response => {
|
||||
this.items = response.data;
|
||||
})
|
||||
}
|
||||
},
|
||||
query(queryString) {
|
||||
this.items = queryString ? this.searchArray.filter(this.createFilter(queryString)) : this.searchArray;
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return item => {
|
||||
return (item.name.toLowerCase().indexOf(queryString.toLowerCase()) !== -1);
|
||||
};
|
||||
},
|
||||
change(projectId) {
|
||||
let currentProjectId = getCurrentProjectID();
|
||||
if (projectId === currentProjectId) {
|
||||
return;
|
||||
}
|
||||
this.$post("/user/update/current", {id: this.userId, lastProjectId: projectId}, () => {
|
||||
localStorage.setItem(PROJECT_ID, projectId);
|
||||
window.location.reload();
|
||||
if (this.$route.path.indexOf('/track/review/view/') >= 0) {
|
||||
this.$router.replace('/track/review/all');
|
||||
} else if (this.$route.path.indexOf('/track/plan/view/') >= 0) {
|
||||
this.$router.replace('/track/plan/all');
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
this.changeProjectName(projectId);
|
||||
});
|
||||
},
|
||||
changeProjectName(projectId) {
|
||||
if (projectId) {
|
||||
let project = this.searchArray.filter(p => p.id === projectId);
|
||||
if (project.length > 0) {
|
||||
this.$emit("update:currentProject", project[0].name);
|
||||
}
|
||||
} else {
|
||||
this.$emit("update:currentProject", '选择项目');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<div id="menu-bar">
|
||||
<el-row type="flex">
|
||||
<el-col :span="8">
|
||||
<el-col :span="10">
|
||||
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
|
||||
|
||||
<el-submenu v-permission="['test_manager','test_user','test_viewer']"
|
||||
index="3" popper-class="submenu">
|
||||
<template v-slot:title>{{ $t('commons.project') }}</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent"/>
|
||||
<template v-slot:title>
|
||||
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
|
||||
{{ $t('commons.project') }}: {{currentProject}}
|
||||
</span>
|
||||
</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
|
||||
<el-divider/>
|
||||
<el-menu-item :index="'/setting/project/create'">
|
||||
<font-awesome-icon :icon="['fa', 'plus']"/>
|
||||
|
@ -39,12 +43,12 @@
|
|||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="4" >
|
||||
<el-row type="flex" justify="center">
|
||||
<ms-create-test :to="'/performance/test/create'"/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="8"/>
|
||||
<el-col :span="10"/>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -98,7 +102,7 @@ export default {
|
|||
router(item) {
|
||||
}
|
||||
},
|
||||
input2: ''
|
||||
currentProject: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
</el-submenu>
|
||||
|
||||
<el-menu-item v-for="menu in project" :key="menu.index" :index="'/setting/project/all'" class="setting-item"
|
||||
v-permission="menu.roles">
|
||||
v-permission="['test_user','test_manager', 'org_admin', 'admin']">
|
||||
<template v-slot:title>
|
||||
<font-awesome-icon class="icon" :icon="['fa', 'bars']" size="lg"/>
|
||||
<span>{{ $t(menu.title) }}</span>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
border
|
||||
:cell-style="rowClass"
|
||||
:header-cell-style="headClass">
|
||||
<el-table-column :label="$t('schedule.event')" prop="events" min-width="15%">
|
||||
<el-table-column :label="$t('schedule.event')" prop="events" min-width="13%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.event" size="mini"
|
||||
:placeholder="$t('organization.message.select_events')"
|
||||
|
@ -30,7 +30,7 @@
|
|||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('schedule.receiver')" prop="userIds" min-width="20%">
|
||||
<el-table-column :label="$t('schedule.receiver')" prop="userIds" min-width="18%">
|
||||
<template v-slot:default="{row}">
|
||||
<el-select v-model="row.userIds" filterable multiple size="mini"
|
||||
:placeholder="$t('commons.please_select')" style="width: 100%;" :disabled="!row.isSet">
|
||||
|
@ -64,7 +64,7 @@
|
|||
:disabled="!scope.row.isSet||!scope.row.isReadOnly"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.operating')" prop="result" min-width="20%">
|
||||
<el-table-column :label="$t('commons.operating')" prop="result" min-width="25%">
|
||||
<template v-slot:default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
|
@ -214,11 +214,7 @@ export default {
|
|||
}
|
||||
},
|
||||
addTask(data) {
|
||||
let list = [];
|
||||
list.push(data);
|
||||
let param = {};
|
||||
param.messageDetail = list;
|
||||
this.result = this.$post("/notice/save/message/task", param, () => {
|
||||
this.result = this.$post("/notice/save/message/task", data, () => {
|
||||
this.initForm()
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
})
|
||||
|
|
|
@ -88,7 +88,7 @@ import MsTablePagination from "../../common/pagination/TablePagination";
|
|||
import MsTableHeader from "../../common/components/MsTableHeader";
|
||||
import MsTableOperator from "../../common/components/MsTableOperator";
|
||||
import MsDialogFooter from "../../common/components/MsDialogFooter";
|
||||
import {_sort, getCurrentUser, listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
||||
import {_sort, getCurrentProjectID, getCurrentUser, listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
||||
import MsContainer from "../../common/components/MsContainer";
|
||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import MsDeleteConfirm from "../../common/components/MsDeleteConfirm";
|
||||
|
@ -96,6 +96,7 @@ import MsTableOperatorButton from "../../common/components/MsTableOperatorButton
|
|||
import ApiEnvironmentConfig from "../../api/test/components/ApiEnvironmentConfig";
|
||||
import TemplateComponent from "../../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
|
||||
import {ApiEvent, LIST_CHANGE, PerformanceEvent, TrackEvent} from "@/business/components/common/head/ListEvent";
|
||||
import {PROJECT_ID} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "MsProject",
|
||||
|
@ -216,6 +217,10 @@ export default {
|
|||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$get('/project/delete/' + project.id, () => {
|
||||
if (project.id === getCurrentProjectID()) {
|
||||
localStorage.removeItem(PROJECT_ID);
|
||||
this.$post("/user/update/current", {id: getCurrentUser().id, lastProjectId: ''});
|
||||
}
|
||||
Message.success(this.$t('commons.delete_success'));
|
||||
this.list();
|
||||
// 发送广播,刷新 head 上的最新列表
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<el-table-column prop="type" :label="$t('test_resource_pool.type')">
|
||||
<template v-slot:default="scope">
|
||||
<span v-if="scope.row.type === 'NODE'">Node</span>
|
||||
<span v-if="scope.row.type === 'K8S'" v-xpack>Kubernetes</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" :label="$t('test_resource_pool.enable_disable')">
|
||||
|
@ -52,7 +53,7 @@
|
|||
:destroy-on-close="true"
|
||||
v-loading="result.loading"
|
||||
>
|
||||
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule"
|
||||
ref="createTestResourcePoolForm">
|
||||
<el-form-item :label="$t('commons.name')" prop="name">
|
||||
<el-input v-model="form.name" autocomplete="off"/>
|
||||
|
@ -64,9 +65,40 @@
|
|||
<el-select v-model="form.type" :placeholder="$t('test_resource_pool.select_pool_type')"
|
||||
@change="changeResourceType()">
|
||||
<el-option key="NODE" value="NODE" label="Node">Node</el-option>
|
||||
<el-option key="K8S" value="K8S" label="Kubernetes" v-xpack>Kubernetes</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-for="(item,index) in infoList " :key="index">
|
||||
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="controllerUrl" label="Controller URL">
|
||||
<el-input v-model="item.controllerUrl" autocomplete="new-password"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="masterUrl" label="Master URL">
|
||||
<el-input v-model="item.masterUrl" autocomplete="new-password"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="password" label="Token">
|
||||
<el-input v-model="item.token" type="password" show-password autocomplete="new-password"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')">
|
||||
<el-input-number v-model="item.maxConcurrency" :min="1" :max="1000000000"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="node-line" v-if="form.type === 'NODE'">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
|
@ -115,7 +147,7 @@
|
|||
:title="$t('test_resource_pool.update_resource_pool')" :visible.sync="updateVisible" width="70%"
|
||||
:destroy-on-close="true"
|
||||
@close="closeFunc">
|
||||
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
|
||||
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule"
|
||||
ref="updateTestResourcePoolForm">
|
||||
<el-form-item :label="$t('commons.name')" prop="name">
|
||||
<el-input v-model="form.name" autocomplete="off"/>
|
||||
|
@ -127,9 +159,40 @@
|
|||
<el-select v-model="form.type" :placeholder="$t('test_resource_pool.select_pool_type')"
|
||||
@change="changeResourceType()">
|
||||
<el-option key="NODE" value="NODE" label="Node">Node</el-option>
|
||||
<el-option key="K8S" value="K8S" label="Kubernetes" v-xpack>Kubernetes</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-for="(item,index) in infoList " :key="index">
|
||||
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="controllerUrl" label="Controller URL">
|
||||
<el-input v-model="item.controllerUrl" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="masterUrl" label="Master URL">
|
||||
<el-input v-model="item.masterUrl" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="password" label="Token">
|
||||
<el-input v-model="item.token" type="password" show-password autocomplete="off"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')">
|
||||
<el-input-number v-model="item.maxConcurrency" :min="1" :max="1000000000"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="node-line" v-if="form.type === 'NODE'">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
<div id="menu-bar" v-if="isRouterAlive">
|
||||
<el-row type="flex">
|
||||
<el-col :span="12">
|
||||
<el-col :span="16">
|
||||
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router
|
||||
:default-active='$route.path'>
|
||||
<el-submenu :class="{'deactivation':!isProjectActivation}"
|
||||
v-permission="['test_manager','test_user','test_viewer']" index="3" popper-class="submenu">
|
||||
<template v-slot:title>{{ $t('commons.project') }}</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent"/>
|
||||
<template v-slot:title>
|
||||
<span style="display: inline-block;width: 150px;white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" :title="currentProject">
|
||||
{{ $t('commons.project') }}: {{currentProject}}
|
||||
</span>
|
||||
</template>
|
||||
<search-list ref="projectRecent" :options="projectRecent" :current-project.sync="currentProject"/>
|
||||
<el-divider/>
|
||||
<el-menu-item :index="'/setting/project/create'">
|
||||
<font-awesome-icon :icon="['fa', 'plus']"/>
|
||||
|
@ -54,7 +58,7 @@
|
|||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<el-col :span="12"/>
|
||||
<el-col :span="8"/>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
|
@ -78,6 +82,7 @@ export default {
|
|||
testCaseReviewEditPath: '',
|
||||
testCaseProjectPath: '',
|
||||
isProjectActivation: true,
|
||||
currentProject: '',
|
||||
projectRecent: {
|
||||
title: this.$t('project.recent'),
|
||||
url: "/project/recent/5",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a
|
||||
Subproject commit 5835db186d17a3d305073e58affb4e88a71b32f0
|
|
@ -96,7 +96,6 @@ export function getCurrentProjectID() {
|
|||
export function saveLocalStorage(response) {
|
||||
// 登录信息保存 cookie
|
||||
localStorage.setItem(TokenKey, JSON.stringify(response.data));
|
||||
localStorage.setItem(PROJECT_ID, response.data.lastProjectId);
|
||||
let rolesArray = response.data.roles;
|
||||
let roles = rolesArray.map(r => r.id);
|
||||
// 保存角色
|
||||
|
|
|
@ -264,6 +264,10 @@ export default {
|
|||
.login-logo {
|
||||
background: url(../assets/logo-dark-MeterSphere.svg);
|
||||
}
|
||||
|
||||
.logo-header {
|
||||
background: url(../assets/logo-light-MeterSphere.svg);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
|
Loading…
Reference in New Issue