diff --git a/ROADMAP.md b/ROADMAP.md index a6d51bbbb4..440f6fdbc5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -55,11 +55,17 @@ - [x] 测试跟踪:支持对接禅道同步缺陷 - [x] 其他:Jenkins 插件支持 pipeline 方式调用 +## v1.6 (开发中) +- [ ] 新增接口管理功能 +- [ ] 全新接口自动化使用方式 +- [ ] 测试跟踪测试计划分类型展示 +- [ ] 优化消息通知配置及实现方式 + ## 规划中 - [ ] 接口测试支持添加 WebSocket 协议请求 -- [ ] 接口管理功能 - [ ] 集成云平台动态管理测试资源池 -- [ ] 支持 K8s 集群作为测试资源池 +- [ ] 测试跟踪测试用例及接口测试增加版本管理 +- [ ] 测试跟踪测试用例增加思维导图展示形式 - [ ] 移动端测试支持 - [ ] UI 功能测试支持 diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index f4452ed120..710f2d3aa3 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -58,6 +58,11 @@ public class ApiAutomationController { apiAutomationService.removeToGc(ids); } + @PostMapping("/reduction") + public void reduction(@RequestBody List ids) { + apiAutomationService.reduction(ids); + } + @GetMapping("/getApiScenario/{id}") public ApiScenario getScenarioDefinition(@PathVariable String id) { return apiAutomationService.getApiScenario(id); diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java b/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java index b27b4737b6..354f37d5e8 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java @@ -64,6 +64,11 @@ public class ApiDefinitionController { apiDefinitionService.removeToGc(ids); } + @PostMapping("/reduction") + public void reduction(@RequestBody List ids) { + apiDefinitionService.reduction(ids); + } + @GetMapping("/get/{id}") public ApiDefinition get(@PathVariable String id) { return apiDefinitionService.get(id); diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/ApiScenarioDTO.java b/backend/src/main/java/io/metersphere/api/dto/automation/ApiScenarioDTO.java index 173f7897da..efecbd4e2b 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/ApiScenarioDTO.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/ApiScenarioDTO.java @@ -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 tagNames; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java index 6be0d1fe99..92290a861e 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java @@ -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)); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 498e178e37..9d652d367f 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -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 list(ApiScenarioRequest request) { + request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); + List list = extApiScenarioMapper.list(request); ApiTagExample example = new ApiTagExample(); example.createCriteria().andProjectIdEqualTo(request.getProjectId()); List tags = apiTagMapper.selectByExample(example); Map tagMap = tags.stream().collect(Collectors.toMap(ApiTag::getId, ApiTag::getName)); - List 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 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 bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); + List bodyUploadIds = request.getBodyUploadIds(); apiDefinitionService.createBodyFiles(bodyUploadIds, bodyFiles); } @@ -166,12 +172,12 @@ public class ApiAutomationService { apiScenarioMapper.deleteByExample(example); } - public void removeToGc(List 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 apiIds) { + extApiScenarioMapper.removeToGc(apiIds); + } + + public void reduction(List apiIds) { + extApiScenarioMapper.reduction(apiIds); } private void checkNameExist(SaveApiScenarioRequest request) { @@ -244,12 +250,14 @@ public class ApiAutomationService { // 多态JSON普通转换会丢失内容,需要通过 ObjectMapper 获取 if (StringUtils.isNotEmpty(element.getString("hashTree"))) { LinkedList elements = mapper.readValue(element.getString("hashTree"), - new TypeReference>() {}); + new TypeReference>() { + }); scenario.setHashTree(elements); } if (StringUtils.isNotEmpty(element.getString("variables"))) { LinkedList variables = mapper.readValue(element.getString("variables"), - new TypeReference>() {}); + new TypeReference>() { + }); scenario.setVariables(variables); } LinkedList scenarios = new LinkedList<>(); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index 5851ca24ec..a1c60272fa 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -163,6 +163,10 @@ public class ApiDefinitionService { extApiDefinitionMapper.removeToGc(apiIds); } + public void reduction(List apiIds) { + extApiDefinitionMapper.reduction(apiIds); + } + public void deleteBodyFiles(String apiId) { File file = new File(BODY_FILE_DIR + "/" + apiId); FileUtil.deleteContents(file); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java index ffbdb54759..24d2c3412b 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiScenarioModuleService.java @@ -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 { diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java index 320d5b4664..ba560aeebe 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java @@ -15,4 +15,6 @@ public interface ExtApiDefinitionMapper { int removeToGc(@Param("ids") List ids); + int reduction(@Param("ids") List ids); + } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml index ab5b4dac15..545bc9b71a 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml @@ -255,4 +255,15 @@ #{v} + + + update api_definition + set + status = 'Underway' + where id in + + #{v} + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java index bec0fbde89..fce72e2e7b 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java @@ -16,4 +16,7 @@ public interface ExtApiScenarioMapper { List selectReference(@Param("request") ApiScenarioRequest request); + int removeToGc(@Param("ids") List ids); + + int reduction(@Param("ids") List ids); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml index da36ff4df5..e7c638e10c 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml @@ -88,5 +88,25 @@ + + update api_scenario + set + status = 'Trash' + where id in + + #{v} + + + + + update api_scenario + set + status = 'Underway' + where id in + + #{v} + + + \ No newline at end of file diff --git a/frontend/src/business/components/api/automation/scenario/AddBasisScenario.vue b/frontend/src/business/components/api/automation/scenario/AddBasisScenario.vue index 3b826f7de1..8ef356a85e 100644 --- a/frontend/src/business/components/api/automation/scenario/AddBasisScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/AddBasisScenario.vue @@ -57,74 +57,75 @@ diff --git a/frontend/src/business/components/api/automation/scenario/AddTag.vue b/frontend/src/business/components/api/automation/scenario/AddTag.vue index 4c78077330..4bb4dd12dd 100644 --- a/frontend/src/business/components/api/automation/scenario/AddTag.vue +++ b/frontend/src/business/components/api/automation/scenario/AddTag.vue @@ -9,7 +9,7 @@ - {{$t('commons.save')}} + {{$t('commons.save')}} @@ -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"; diff --git a/frontend/src/business/components/api/automation/scenario/ApiComponent.vue b/frontend/src/business/components/api/automation/scenario/ApiComponent.vue index 6b35d98651..8056137ced 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiComponent.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiComponent.vue @@ -41,7 +41,7 @@

{{$t('api_test.definition.request.req_param')}}

- + diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index 7214df3c1c..16c40b99d9 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -25,9 +25,11 @@ - + @@ -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,7 +267,7 @@ diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 47dc95d5a4..1f728d338d 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -265,6 +265,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(); }); }, diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 9d629a8cba..ea19ba2c39 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -352,6 +352,9 @@ } }, created() { + if (!this.currentScenario.apiScenarioModuleId) { + this.currentScenario.apiScenarioModuleId = ""; + } this.projectId = getCurrentProjectID(); this.operatingElements = ELEMENTS.get("ALL"); this.getMaintainerOptions(); @@ -450,7 +453,7 @@ this.reload(); }, addScenario(arr) { - if (arr.length > 0) { + if (arr && arr.length > 0) { arr.forEach(item => { item.enable === undefined ? item.enable = true : item.enable; this.scenarioDefinition.push(item); @@ -476,7 +479,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 +499,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 +509,8 @@ } }) this.apiListVisible = false; + this.currentRow.cases = []; + this.currentRow.apis = []; this.sort(); this.reload(); }, @@ -708,10 +713,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,7 +862,8 @@ /deep/ .el-step__icon.is-text { border: 1px solid; } - /deep/.el-drawer__header{ + + /deep/ .el-drawer__header { margin-bottom: 0px; } diff --git a/frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue b/frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue index 01c1ab5daa..6ef476bc3a 100644 --- a/frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/ImportApiScenario.vue @@ -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); diff --git a/frontend/src/business/components/api/automation/scenario/api/ApiList.vue b/frontend/src/business/components/api/automation/scenario/api/ApiList.vue index a4102bb1df..9227f02246 100644 --- a/frontend/src/business/components/api/automation/scenario/api/ApiList.vue +++ b/frontend/src/business/components/api/automation/scenario/api/ApiList.vue @@ -193,6 +193,7 @@ this.total = response.data.itemCount; this.tableData = response.data.listObject; }); + this.selectRows = new Set(); }, handleSelect(selection, row) { row.hashTree = []; diff --git a/frontend/src/business/components/api/definition/components/ApiCaseList.vue b/frontend/src/business/components/api/definition/components/ApiCaseList.vue index 7f13288b18..7ca0bb37c6 100644 --- a/frontend/src/business/components/api/definition/components/ApiCaseList.vue +++ b/frontend/src/business/components/api/definition/components/ApiCaseList.vue @@ -335,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) { diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue index 1df8d46921..b886bf9d60 100644 --- a/frontend/src/business/components/api/definition/components/ApiConfig.vue +++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue @@ -77,6 +77,9 @@ } else { this.reqUrl = "/api/definition/create"; } + if (!this.request.hashTree) { + this.request.hashTree = []; + } }, methods: { runTest(data) { diff --git a/frontend/src/business/components/api/definition/components/ApiList.vue b/frontend/src/business/components/api/definition/components/ApiList.vue index 91fe6f53e9..16bc90da52 100644 --- a/frontend/src/business/components/api/definition/components/ApiList.vue +++ b/frontend/src/business/components/api/definition/components/ApiList.vue @@ -230,10 +230,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(); }); @@ -275,6 +273,9 @@ handleTestCase(api) { this.selectApi = api; let request = JSON.parse(api.request); + if (!request.hashTree) { + request.hashTree = []; + } this.selectApi.url = request.path; this.$refs.caseList.open(this.selectApi); }, diff --git a/frontend/src/business/components/api/definition/components/ApiModule.vue b/frontend/src/business/components/api/definition/components/ApiModule.vue index 812732eab1..aea697aa4a 100644 --- a/frontend/src/business/components/api/definition/components/ApiModule.vue +++ b/frontend/src/business/components/api/definition/components/ApiModule.vue @@ -138,6 +138,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; diff --git a/frontend/src/business/components/api/definition/components/request/http/ApiHttpRequestForm.vue b/frontend/src/business/components/api/definition/components/request/http/ApiHttpRequestForm.vue index d89b29472b..279badcc10 100644 --- a/frontend/src/business/components/api/definition/components/request/http/ApiHttpRequestForm.vue +++ b/frontend/src/business/components/api/definition/components/request/http/ApiHttpRequestForm.vue @@ -2,7 +2,7 @@ -
+
@@ -58,23 +58,23 @@
- -
- - - - - - - - - +
+
+ + + + + + + + +
- + +{{$t('api_test.definition.request.pre_script')}}
+{{$t('api_test.definition.request.post_script')}} @@ -116,6 +116,7 @@ return []; } }, + referenced: Boolean, isShowEnable: Boolean, jsonPathList: Array, isReadOnly: { diff --git a/frontend/src/business/components/api/definition/components/request/http/ApiRequestForm.vue b/frontend/src/business/components/api/definition/components/request/http/ApiRequestForm.vue index 3a5af1910c..de97afb499 100644 --- a/frontend/src/business/components/api/definition/components/request/http/ApiRequestForm.vue +++ b/frontend/src/business/components/api/definition/components/request/http/ApiRequestForm.vue @@ -1,6 +1,6 @@ @@ -17,6 +17,10 @@ type: Boolean, default: true }, + referenced: { + type: Boolean, + default: false + }, isReadOnly: { type: Boolean, default: false