diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiDelimitController.java b/backend/src/main/java/io/metersphere/api/controller/ApiDelimitController.java index 388287b78a..01ee2abcdd 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiDelimitController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiDelimitController.java @@ -49,6 +49,11 @@ public class ApiDelimitController { apiDelimitService.delete(id); } + @PostMapping("/deleteBatch") + public void deleteBatch(@RequestBody List ids) { + apiDelimitService.deleteBatch(ids); + } + @GetMapping("/get/{id}") public ApiDelimit get(@PathVariable String id) { return apiDelimitService.get(id); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDelimitService.java b/backend/src/main/java/io/metersphere/api/service/ApiDelimitService.java index 81018dd940..afe2489d8b 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDelimitService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDelimitService.java @@ -6,6 +6,7 @@ import io.metersphere.api.dto.delimit.ApiDelimitRequest; import io.metersphere.api.dto.delimit.ApiDelimitResult; import io.metersphere.api.dto.delimit.SaveApiDelimitRequest; import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.ApiDelimitExecResultMapper; import io.metersphere.base.mapper.ApiDelimitMapper; import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.commons.constants.APITestStatus; @@ -43,6 +44,8 @@ public class ApiDelimitService { private FileService fileService; @Resource private ApiTestCaseService apiTestCaseService; + @Resource + private ApiDelimitExecResultMapper apiDelimitExecResultMapper; private static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; @@ -134,11 +137,19 @@ public class ApiDelimitService { public void delete(String apiId) { apiTestCaseService.checkIsRelateTest(apiId); deleteFileByTestId(apiId); - //apiReportService.deleteByTestId(apiId); + apiDelimitExecResultMapper.deleteByResourceId(apiId); apiDelimitMapper.deleteByPrimaryKey(apiId); deleteBodyFiles(apiId); } + public void deleteBatch(List apiIds) { + // 简单处理后续优化 + apiIds.forEach(item -> { + delete(item); + }); + } + + public void deleteBodyFiles(String apiId) { File file = new File(BODY_FILE_DIR + "/" + apiId); FileUtil.deleteContents(file); @@ -149,9 +160,9 @@ public class ApiDelimitService { private void checkNameExist(SaveApiDelimitRequest request) { ApiDelimitExample example = new ApiDelimitExample(); - example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId()); + example.createCriteria().andUrlEqualTo(request.getUrl()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId()); if (apiDelimitMapper.countByExample(example) > 0) { - MSException.throwException(Translator.get("load_test_already_exists")); + MSException.throwException(Translator.get("api_delimit_url_not_repeating")); } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java index 5aa8a23a93..e88ce9f9c3 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java @@ -207,7 +207,7 @@ public class ApiModuleService { if (level > 8) { MSException.throwException(Translator.get("node_deep_limit")); } - if (rootNode.getId().equals("rootID")) { + if (rootNode.getId().equals("root")) { rootPath = ""; } ApiModule apiDelimitNode = new ApiModule(); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index ca203227d5..7065116f11 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -5,6 +5,7 @@ import io.metersphere.api.dto.delimit.ApiTestCaseRequest; import io.metersphere.api.dto.delimit.ApiTestCaseResult; import io.metersphere.api.dto.delimit.SaveApiTestCaseRequest; import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.ApiDelimitExecResultMapper; import io.metersphere.base.mapper.ApiTestCaseMapper; import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.commons.exception.MSException; @@ -38,6 +39,8 @@ public class ApiTestCaseService { private ApiTestFileMapper apiTestFileMapper; @Resource private FileService fileService; + @Resource + private ApiDelimitExecResultMapper apiDelimitExecResultMapper; private static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; @@ -110,7 +113,7 @@ public class ApiTestCaseService { public void delete(String testId) { deleteFileByTestId(testId); - //apiReportService.deleteByTestId(testId); + apiDelimitExecResultMapper.deleteByResourceId(testId); apiTestCaseMapper.deleteByPrimaryKey(testId); deleteBodyFiles(testId); } diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExample.java b/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExample.java index 8b3f36fc9a..4ec93fa69b 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExample.java @@ -264,6 +264,11 @@ public class ApiDelimitExample { return (Criteria) this; } + public Criteria andUrlEqualTo(String value) { + addCriterion("url =", value, "url"); + return (Criteria) this; + } + public Criteria andNameNotEqualTo(String value) { addCriterion("name <>", value, "name"); return (Criteria) this; diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExecResult.java b/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExecResult.java new file mode 100644 index 0000000000..e25cb328c8 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/ApiDelimitExecResult.java @@ -0,0 +1,18 @@ +package io.metersphere.base.domain; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class ApiDelimitExecResult implements Serializable { + private String id; + private String resourceId; + private String name; + private String content; + private String status; + private String userId; + private String startTime; + private String endTime; + +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.java new file mode 100644 index 0000000000..d47ffb6156 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.java @@ -0,0 +1,10 @@ +package io.metersphere.base.mapper; + +import io.metersphere.base.domain.ApiDelimitExecResult; + +public interface ApiDelimitExecResultMapper { + + int deleteByResourceId(String id); + + int insert(ApiDelimitExecResult record); +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.xml new file mode 100644 index 0000000000..a44a7ab56b --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiDelimitExecResultMapper.xml @@ -0,0 +1,16 @@ + + + + + + delete from api_delimit_exec_result where resource_id = #{id,jdbcType=VARCHAR} + + + + insert into api_delimit_exec_result + (id, resource_id,name,content, status, user_id, start_time, end_time) + values + (#{id,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}, #{status,jdbcType=VARCHAR}, + #{startTime,jdbcType=BIGINT}, #{endTime,jdbcType=BIGINT}) + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ApiTestCaseMapper.xml index beca00f27f..938b08ffa4 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ApiTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiTestCaseMapper.xml @@ -260,7 +260,7 @@ request = #{request,jdbcType=LONGVARCHAR}, - request = #{response,jdbcType=LONGVARCHAR}, + response = #{response,jdbcType=LONGVARCHAR}, diff --git a/backend/src/main/resources/db/migration/V40_api_delimit.sql b/backend/src/main/resources/db/migration/V40_api_delimit.sql deleted file mode 100644 index df34f15dda..0000000000 --- a/backend/src/main/resources/db/migration/V40_api_delimit.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `api_module` ( - `id` varchar(50) NOT NULL COMMENT 'Test case node ID', - `project_id` varchar(50) NOT NULL COMMENT 'Project ID this node belongs to', - `name` varchar(64) NOT NULL COMMENT 'Node name', - `protocol` varchar(64) NOT NULL COMMENT 'Node protocol', - `parent_id` varchar(50) DEFAULT NULL COMMENT 'Parent node ID', - `level` int(10) DEFAULT 1 COMMENT 'Node level', - `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', - `update_time` bigint(13) NOT NULL COMMENT 'Update timestamp', - PRIMARY KEY (`id`) -) - ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4; \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index ea9fe501cc..5811628e19 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -169,6 +169,7 @@ test_plan_notification=Test plan notification task_defect_notification=Task defect notification task_notification=Jenkins Task notification task_notification_=Timing task result notification +api_delimit_url_not_repeating=The interface request address already exists diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 7745c3acd2..831db2baa1 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -169,4 +169,5 @@ upload_content_is_null=导入内容为空 test_plan_notification=测试计划通知 task_defect_notification=缺陷任务通知 task_notification=jenkins任务通知 -task_notification_=定时任务结果通知 \ No newline at end of file +task_notification_=定时任务结果通知 +api_delimit_url_not_repeating=接口请求地址已经存在 \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 32411f615d..f20e6f6a77 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -171,4 +171,4 @@ test_plan_notification=測試計畫通知 task_defect_notification=缺陷任務通知 task_notification=jenkins任務通知 task_notification_=定時任務通知 - +api_delimit_url_not_repeating=接口請求地址已經存在 diff --git a/frontend/src/business/components/api/delimit/ApiDelimit.vue b/frontend/src/business/components/api/delimit/ApiDelimit.vue index 567b27e0ec..390084be98 100644 --- a/frontend/src/business/components/api/delimit/ApiDelimit.vue +++ b/frontend/src/business/components/api/delimit/ApiDelimit.vue @@ -2,8 +2,8 @@ - + @@ -16,12 +16,11 @@ {{$t('api_test.delimit.request.close_all_label')}} - - - + + @@ -32,20 +31,23 @@ :current-module="currentModule" @editApi="editApi" @handleCase="handleCase" - ref="apiList"> - + ref="apiList"/> +
+ :moduleOptions="moduleOptions" ref="apiConfig"/>
+ +
- +
+ +
- - +
@@ -83,21 +85,16 @@ comments: {}, data() { return { - projectId: null, isHide: true, - editableTabsValue: '1', - tabIndex: 1, - result: {}, - currentPage: 1, - pageSize: 5, - total: 0, + apiDefaultTab: 'default', currentProject: null, currentModule: null, currentApi: {}, moduleOptions: {}, - editableTabs: [{ + runTestData: {}, + apiTabs: [{ title: this.$t('api_test.delimit.api_title'), - name: '1', + name: 'default', type: "list", closable: false }], @@ -106,21 +103,17 @@ methods: { handleCommand(e) { if (e === "add") { - this.currentApi = { - status: "Underway", - path: "GET", - userId: getCurrentUser().id, - }; + this.currentApi = {status: "Underway", path: "GET", userId: getCurrentUser().id}; this.handleTabsEdit(this.$t('api_test.delimit.request.title'), e); } else if (e === "test") { this.handleTabsEdit(this.$t("commons.api"), e); } else if (e === "closeAll") { - let tabs = this.editableTabs[0]; - this.editableTabs = []; - this.editableTabsValue = tabs.name; - this.editableTabs.push(tabs); + let tabs = this.apiTabs[0]; + this.apiTabs = []; + this.apiDefaultTab = tabs.name; + this.apiTabs.push(tabs); } else { this.handleTabsEdit(this.$t('api_test.delimit.request.fast_debug'), "debug"); @@ -128,8 +121,8 @@ }, handleTabsEdit(targetName, action) { if (action === 'remove') { - let tabs = this.editableTabs; - let activeName = this.editableTabsValue; + let tabs = this.apiTabs; + let activeName = this.apiDefaultTab; if (activeName === targetName) { tabs.forEach((tab, index) => { if (tab.name === targetName) { @@ -140,23 +133,22 @@ } }); } - this.editableTabsValue = activeName; - this.editableTabs = tabs.filter(tab => tab.name !== targetName); + this.apiDefaultTab = activeName; + this.apiTabs = tabs.filter(tab => tab.name !== targetName); } else { if (targetName === undefined || targetName === null) { targetName = this.$t('api_test.delimit.request.title'); } let newTabName = getUUID().substring(0, 8); - this.editableTabs.push({ + this.apiTabs.push({ title: targetName, name: newTabName, closable: true, type: action }); - this.editableTabsValue = newTabName; + this.apiDefaultTab = newTabName; } }, - editApi(row) { this.currentApi = row; this.handleTabsEdit(row.name, "add"); @@ -172,24 +164,25 @@ this.currentModule = data; }, refresh(data) { - this.$refs.apiList[0].initTableData(data); + this.$refs.apiList[0].initApiTable(data); }, - saveAs(data) { - this.handleCommand("add"); - }, - runTest(data) { - this.handleCommand("test"); - }, - saveApi(data) { - for (let index in this.editableTabs) { - let tab = this.editableTabs[index]; - if (tab.name === this.editableTabsValue) { + setTabTitle(data) { + for (let index in this.apiTabs) { + let tab = this.apiTabs[index]; + if (tab.name === this.apiDefaultTab) { tab.title = data.name; break; } } + this.runTestData = data; + }, + runTest(data) { + this.setTabTitle(data); + this.handleCommand("test"); + }, + saveApi(data) { + this.setTabTitle(data); this.$refs.apiList[0].initTableData(data); - }, initTree(data) { this.moduleOptions = data; diff --git a/frontend/src/business/components/api/delimit/components/ApiCaseList.vue b/frontend/src/business/components/api/delimit/components/ApiCaseList.vue index 61d6de00f8..9c549f49bd 100644 --- a/frontend/src/business/components/api/delimit/components/ApiCaseList.vue +++ b/frontend/src/business/components/api/delimit/components/ApiCaseList.vue @@ -27,7 +27,7 @@
- @@ -72,16 +72,15 @@ - + -
- + @@ -100,8 +99,7 @@ @click="active(item)"/> - {{item.type!= 'create'? item.name : ''}} - + {{item.type!= 'create' ? item.name:''}}
{{item.createTime | timestampFormatDate }} {{item.createUser}} 创建 {{item.updateTime | timestampFormatDate }} {{item.updateUser}} 更新 @@ -128,8 +126,7 @@
- + {{$t('commons.save')}} @@ -153,6 +150,7 @@ import {downloadFile, getUUID} from "@/common/js/utils"; import {parseEnvironment} from "../model/EnvironmentModel"; import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig"; + import {PRIORITY} from "../model/JsonData"; export default { name: 'ApiCaseList', @@ -173,18 +171,12 @@ grades: [], environments: [], envValue: {}, - grdValue: "", - value: "", - isReadOnly: false, name: "", - priority: [ - {name: 'P0', id: 'P0'}, - {name: 'P1', id: 'P1'}, - {name: 'P2', id: 'P2'}, - {name: 'P3', id: 'P3'} - ], + priorityValue: "", + isReadOnly: false, + selectedEvent: Object, + priority: PRIORITY, apiCaseList: [], - debugReportId: '' } }, @@ -197,16 +189,10 @@ this.getEnvironments(); } }, + created() { + this.getApiTest(); + }, methods: { - getRequest(request) { - if (request === undefined || request === null) { - this.test = new Test(); - this.test.request.environment = this.envValue; - return this.test.request; - } else { - return new RequestFactory(JSON.parse(request)); - } - }, getResult(data) { if (data === 'PASS') { return '执行结果:通过'; @@ -249,9 +235,6 @@ test: test, }; this.apiCaseList.push(obj); - }, - runDebug() { - }, active(item) { item.active = !item.active; @@ -281,9 +264,9 @@ let condition = {}; condition.projectId = this.api.projectId; condition.apiDelimitId = this.api.id; - condition.priority = this.grdValue; + condition.priority = this.priorityValue; condition.name = this.name; - this.result = this.$post("/api/testcase/list", condition, response => { + this.$post("/api/testcase/list", condition, response => { for (let index in response.data) { let test = response.data[index]; test.test = new Test({request: new RequestFactory(JSON.parse(test.request))}); @@ -308,7 +291,6 @@ row.projectId = this.api.projectId; row.apiDelimitId = this.api.id; row.request = row.test.request; - let jmx = row.test.toJMX(); let blob = new Blob([jmx.xml], {type: "application/octet-stream"}); let file = new File([blob], jmx.name); @@ -323,7 +305,7 @@ }, getEnvironments() { if (this.currentProject) { - this.result = this.$get('/api/environment/list/' + this.currentProject.id, response => { + this.$get('/api/environment/list/' + this.currentProject.id, response => { this.environments = response.data; this.environments.forEach(environment => { parseEnvironment(environment); @@ -363,6 +345,23 @@ }, environmentConfigClose() { this.getEnvironments(); + }, + selectTestCase(item, $event) { + if (item.type === "create") { + return; + } + if ($event.currentTarget.className.indexOf('is-selected') > 0) { + $event.currentTarget.className = "el-card is-always-shadow"; + this.$emit('selectTestCase', null); + } else { + if (this.selectedEvent.currentTarget != undefined) { + this.selectedEvent.currentTarget.className = "el-card is-always-shadow"; + } + this.selectedEvent.currentTarget = $event.currentTarget; + $event.currentTarget.className = "el-card is-always-shadow is-selected"; + this.$emit('selectTestCase', item); + } + } } } @@ -413,4 +412,8 @@ .icon.is-active { transform: rotate(90deg); } + + .is-selected { + background: #EFF7FF; + } diff --git a/frontend/src/business/components/api/delimit/components/ApiConfig.vue b/frontend/src/business/components/api/delimit/components/ApiConfig.vue index 3ac35def9d..96d334e3f6 100644 --- a/frontend/src/business/components/api/delimit/components/ApiConfig.vue +++ b/frontend/src/business/components/api/delimit/components/ApiConfig.vue @@ -1,6 +1,7 @@