From c13baa2f70a9797e676cf3dc7ff8d210c4e13338 Mon Sep 17 00:00:00 2001 From: q4speed Date: Tue, 13 Oct 2020 11:17:35 +0800 Subject: [PATCH 01/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E4=BF=AE=E5=A4=8DTCP=E9=85=8D=E7=BD=AE=E8=A2=AB=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E9=85=8D=E7=BD=AE=E8=A6=86=E7=9B=96=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/api/test/model/ScenarioModel.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 80dbafb4da..e1c8d79763 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -1052,10 +1052,21 @@ class JMXTCPRequest { obj.set(scenario.environment.config.tcpConfig, true); return obj; } - obj.set(scenario.tcpConfig, true); + + this.copy(this, scenario.tcpConfig); return obj; } + + copy(target, source) { + for (let key in source) { + if (source.hasOwnProperty(key)) { + if (source[key] !== undefined && !target[key]) { + target[key] = source[key]; + } + } + } + } } class JMeterTestPlan extends Element { From 971bc350bddfc1d69aa98e4270d80bc02aebe312 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Tue, 13 Oct 2020 14:08:16 +0800 Subject: [PATCH 02/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E6=97=A0=E6=B3=95=E5=AF=BC=E5=85=A5=E5=86=85=E7=BD=AEpytho?= =?UTF-8?q?n=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 9 +++++++ .../metersphere/api/jmeter/JMeterService.java | 8 ++---- .../listener/AppStartListener.java | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index a1772fb09f..3c1b52b42e 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -490,6 +490,15 @@ src/main/resources/jmeter/lib/ext ApacheJMeter_functions.jar + + org.python + jython-standalone + 2.7.0 + jar + true + src/main/resources/jmeter/lib/ext + jython-standalone.jar + ${project.build.directory}/wars false diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 45e177624d..9ddcd092b5 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -11,7 +11,7 @@ import org.apache.jmeter.save.SaveService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; -import org.python.core.Options; + import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -34,10 +34,6 @@ public class JMeterService { JMeterUtils.setJMeterHome(JMETER_HOME); JMeterUtils.setLocale(LocaleContextHolder.getLocale()); - - //解决无法加载 PyScriptEngineFactory - Options.importSite = false; - try { Object scriptWrapper = SaveService.loadElement(is); HashTree testPlan = getHashTree(scriptWrapper); @@ -51,7 +47,7 @@ public class JMeterService { } } - private String getJmeterHome() { + public String getJmeterHome() { String home = getClass().getResource("/").getPath() + "jmeter"; try { File file = new File(home); diff --git a/backend/src/main/java/io/metersphere/listener/AppStartListener.java b/backend/src/main/java/io/metersphere/listener/AppStartListener.java index e2068d3768..6f25d0fd66 100644 --- a/backend/src/main/java/io/metersphere/listener/AppStartListener.java +++ b/backend/src/main/java/io/metersphere/listener/AppStartListener.java @@ -1,6 +1,10 @@ package io.metersphere.listener; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.service.ScheduleService; +import org.python.core.Options; +import org.python.util.PythonInterpreter; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @@ -12,12 +16,16 @@ public class AppStartListener implements ApplicationListener Date: Tue, 13 Oct 2020 15:47:51 +0800 Subject: [PATCH 03/31] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/common/js/ajax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/common/js/ajax.js b/frontend/src/common/js/ajax.js index 8d2e1edc4b..b7dc6bff95 100644 --- a/frontend/src/common/js/ajax.js +++ b/frontend/src/common/js/ajax.js @@ -61,7 +61,7 @@ export default { window.console.error(error.response || error.message); if (error.response && error.response.data) { if (error.response.headers["authentication-status"] !== "invalid") { - Message.error({message: error.response.data.message, showClose: true}); + Message.error({message: error.response.data.message || error.response.data, showClose: true}); } } else { Message.error({message: error.message, showClose: true}); From 9118cb340406bfddd0455394b15b102b20cf56b7 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 13 Oct 2020 16:09:54 +0800 Subject: [PATCH 04/31] =?UTF-8?q?refactor(=E7=B3=BB=E7=BB=9F=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE):=20=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=BE=93=E5=85=A5=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../settings/system/Organization.vue | 6 +++--- .../settings/system/SystemWorkspace.vue | 10 +++++----- .../components/settings/system/User.vue | 20 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/src/business/components/settings/system/Organization.vue b/frontend/src/business/components/settings/system/Organization.vue index ab8cfc75c5..abf1d5dbda 100644 --- a/frontend/src/business/components/settings/system/Organization.vue +++ b/frontend/src/business/components/settings/system/Organization.vue @@ -101,7 +101,7 @@ - - - - - - - - - + - + - + - + - + - + - + - + - + - + Date: Tue, 13 Oct 2020 16:10:18 +0800 Subject: [PATCH 05/31] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=BE=93=E5=85=A5=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/business/components/api/test/ApiTestConfig.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/business/components/api/test/ApiTestConfig.vue b/frontend/src/business/components/api/test/ApiTestConfig.vue index 9c19a6666e..bde0fd0817 100644 --- a/frontend/src/business/components/api/test/ApiTestConfig.vue +++ b/frontend/src/business/components/api/test/ApiTestConfig.vue @@ -8,7 +8,7 @@ - From 1d23ce38a917c8a508b1161f226e910ffbc0ac86 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 13 Oct 2020 16:10:36 +0800 Subject: [PATCH 06/31] =?UTF-8?q?refactor(=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=BE=93=E5=85=A5=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/performance/test/EditPerformanceTestPlan.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/business/components/performance/test/EditPerformanceTestPlan.vue b/frontend/src/business/components/performance/test/EditPerformanceTestPlan.vue index 8bfd028b70..29096c205c 100644 --- a/frontend/src/business/components/performance/test/EditPerformanceTestPlan.vue +++ b/frontend/src/business/components/performance/test/EditPerformanceTestPlan.vue @@ -9,7 +9,7 @@ maxlength="30" show-word-limit > From 782f32ce8e0f8f1722b2c9acbc8a7f358b892f23 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Tue, 13 Oct 2020 17:44:52 +0800 Subject: [PATCH 11/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E4=B8=80=E9=94=AE=E6=89=A7=E8=A1=8C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/APITestController.java | 5 +++ .../api/service/APITestService.java | 18 +++++++- .../components/api/test/OneClickOperation.vue | 44 ++++--------------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java index 3eb82dddba..8df2169b88 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -73,6 +73,11 @@ public class APITestController { apiTestService.create(request, file, bodyFiles); } + @PostMapping(value = "/create/merge", consumes = {"multipart/form-data"}) + public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List selectIds) { + apiTestService.mergeCreate(request, file, selectIds); + } + @PostMapping(value = "/update", consumes = {"multipart/form-data"}) public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { checkownerService.checkApiTestOwner(request.getId()); diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index 44dd35677b..cc9238c205 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -83,15 +83,19 @@ public class APITestService { } public void create(SaveAPITestRequest request, MultipartFile file, List bodyFiles) { + List bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); + ApiTest test = createTest(request, file); + createBodyFiles(test, bodyUploadIds, bodyFiles); + } + private ApiTest createTest(SaveAPITestRequest request, MultipartFile file) { if (file == null) { throw new IllegalArgumentException(Translator.get("file_cannot_be_null")); } checkQuota(); - List bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); request.setBodyUploadIds(null); ApiTest test = createTest(request); - createBodyFiles(test, bodyUploadIds, bodyFiles); saveFile(test.getId(), file); + return test; } public void update(SaveAPITestRequest request, MultipartFile file, List bodyFiles) { @@ -108,6 +112,9 @@ public class APITestService { } private void createBodyFiles(ApiTest test, List bodyUploadIds, List bodyFiles) { + if (bodyUploadIds.size() <= 0) { + return; + } String dir = BODY_FILE_DIR + "/" + test.getId(); File testDir = new File(dir); if (!testDir.exists()) { @@ -436,4 +443,11 @@ public class APITestService { quotaService.checkAPITestQuota(); } } + + public void mergeCreate(SaveAPITestRequest request, MultipartFile file, List selectIds) { + ApiTest test = createTest(request, file); + selectIds.forEach(sourceId -> { + copyBodyFiles(test.getId(), sourceId); + }); + } } diff --git a/frontend/src/business/components/api/test/OneClickOperation.vue b/frontend/src/business/components/api/test/OneClickOperation.vue index 579dea0d8c..d2f5ecf3f8 100644 --- a/frontend/src/business/components/api/test/OneClickOperation.vue +++ b/frontend/src/business/components/api/test/OneClickOperation.vue @@ -156,37 +156,11 @@ }, save(callback) { this.change = false; - let url = "/api/create"; - let bodyFiles = this.getBodyUploadFiles(); - this.result = this.$request(this.getOptions(url, bodyFiles), () => { + let url = "/api/create/merge"; + this.result = this.$request(this.getOptions(url, this.selectIds), () => { if (callback) callback(); }); }, - getBodyUploadFiles() { - let bodyUploadFiles = []; - this.test.bodyUploadIds = []; - this.test.scenarioDefinition.forEach(scenario => { - scenario.requests.forEach(request => { - if (request.body) { - request.body.kvs.forEach(param => { - if (param.files) { - param.files.forEach(item => { - if (item.file) { - let fileId = getUUID().substring(0, 8); - item.name = item.file.name; - item.id = fileId; - this.test.bodyUploadIds.push(fileId); - bodyUploadFiles.push(item.file); - // item.file = undefined; - } - }); - } - }); - } - }); - }); - return bodyUploadFiles; - }, runTest() { this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => { this.$success(this.$t('api_test.running')); @@ -196,16 +170,14 @@ this.test = "" }); }, - getOptions(url, bodyFiles) { + getOptions(url, selectIds) { let formData = new FormData(); - if (bodyFiles) { - bodyFiles.forEach(f => { - formData.append("files", f); - }) - } - let requestJson = JSON.stringify(this.test); - formData.append('request', new Blob([requestJson], { + formData.append('request', new Blob([JSON.stringify(this.test)], { + type: "application/json" + })); + + formData.append('selectIds', new Blob([JSON.stringify(Array.from(selectIds))], { type: "application/json" })); From 7b69e810ef2ff6259fb54db1afae849aa0319673 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Tue, 13 Oct 2020 17:55:08 +0800 Subject: [PATCH 12/31] =?UTF-8?q?refactor(=E6=B5=8B=E8=AF=95=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA):=20=E9=87=8D=E6=9E=84=E7=BC=BA=E9=99=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=83=A8=E5=88=86=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TestCaseIssuesController.java | 4 +- .../track/issue/AbstractIssuePlatform.java | 77 +++ .../metersphere/track/issue/IssueFactory.java | 32 ++ .../track/issue/IssuesPlatform.java | 39 ++ .../io/metersphere/track/issue/JiraIssue.java | 264 ++++++++++ .../metersphere/track/issue/LocalIssue.java | 72 +++ .../metersphere/track/issue/PlatformUser.java | 12 + .../io/metersphere/track/issue/TapdIssue.java | 200 ++++++++ .../track/service/IssuesService.java | 469 ++---------------- 9 files changed, 729 insertions(+), 440 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/IssueFactory.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/JiraIssue.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/LocalIssue.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/PlatformUser.java create mode 100644 backend/src/main/java/io/metersphere/track/issue/TapdIssue.java diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseIssuesController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseIssuesController.java index c276a6fcef..18357447da 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseIssuesController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseIssuesController.java @@ -1,7 +1,7 @@ package io.metersphere.track.controller; import io.metersphere.base.domain.Issues; -import io.metersphere.track.domain.TapdUser; +import io.metersphere.track.issue.PlatformUser; import io.metersphere.track.service.IssuesService; import io.metersphere.track.request.testcase.IssuesRequest; import org.springframework.web.bind.annotation.*; @@ -42,7 +42,7 @@ public class TestCaseIssuesController { } @GetMapping("/tapd/user/{caseId}") - public List getTapdUsers(@PathVariable String caseId) { + public List getPlatformUsers(@PathVariable String caseId) { return issuesService.getTapdProjectUsers(caseId); } diff --git a/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java b/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java new file mode 100644 index 0000000000..a5e8dd6bf1 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/AbstractIssuePlatform.java @@ -0,0 +1,77 @@ +package io.metersphere.track.issue; + +import io.metersphere.base.domain.ServiceIntegration; +import io.metersphere.base.mapper.IssuesMapper; +import io.metersphere.base.mapper.TestCaseIssuesMapper; +import io.metersphere.base.mapper.ext.ExtIssuesMapper; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.user.SessionUser; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.EncryptUtils; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.controller.request.IntegrationRequest; +import io.metersphere.service.IntegrationService; +import io.metersphere.service.ProjectService; +import io.metersphere.track.request.testcase.IssuesRequest; +import io.metersphere.track.service.TestCaseService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpHeaders; + +public abstract class AbstractIssuePlatform implements IssuesPlatform { + + protected IntegrationService integrationService; + protected TestCaseIssuesMapper testCaseIssuesMapper; + protected ProjectService projectService; + protected TestCaseService testCaseService; + protected IssuesMapper issuesMapper; + protected ExtIssuesMapper extIssuesMapper; + + protected String testCaseId; + + public AbstractIssuePlatform(IssuesRequest issuesRequest) { + this.integrationService = CommonBeanFactory.getBean(IntegrationService.class); + this.testCaseIssuesMapper = CommonBeanFactory.getBean(TestCaseIssuesMapper.class); + this.projectService = CommonBeanFactory.getBean(ProjectService.class); + this.testCaseService = CommonBeanFactory.getBean(TestCaseService.class); + this.issuesMapper = CommonBeanFactory.getBean(IssuesMapper.class); + this.extIssuesMapper = CommonBeanFactory.getBean(ExtIssuesMapper.class); + this.testCaseId = issuesRequest.getTestCaseId(); + } + + protected String getPlatformConfig(String platform) { + SessionUser user = SessionUtils.getUser(); + String orgId = user.getLastOrganizationId(); + + IntegrationRequest request = new IntegrationRequest(); + if (StringUtils.isBlank(orgId)) { + MSException.throwException("organization id is null"); + } + request.setOrgId(orgId); + request.setPlatform(platform); + + ServiceIntegration integration = integrationService.get(request); + return integration.getConfiguration(); + } + + protected HttpHeaders auth(String apiUser, String password) { + String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Basic " + authKey); + return headers; + } + + /** + * 获取平台与项目相关的属性 + * @return + */ + abstract String getProjectId(); + + protected boolean isIntegratedPlatform(String orgId, String platform) { + IntegrationRequest request = new IntegrationRequest(); + request.setPlatform(platform); + request.setOrgId(orgId); + ServiceIntegration integration = integrationService.get(request); + return StringUtils.isNotBlank(integration.getId()); + } + +} diff --git a/backend/src/main/java/io/metersphere/track/issue/IssueFactory.java b/backend/src/main/java/io/metersphere/track/issue/IssueFactory.java new file mode 100644 index 0000000000..3592094890 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/IssueFactory.java @@ -0,0 +1,32 @@ +package io.metersphere.track.issue; + +import io.metersphere.commons.constants.IssuesManagePlatform; +import io.metersphere.track.request.testcase.IssuesRequest; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class IssueFactory { + public static AbstractIssuePlatform createPlatform(String platform, IssuesRequest addIssueRequest) { + if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) { + return new TapdIssue(addIssueRequest); + } else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) { + return new JiraIssue(addIssueRequest); + } else if (StringUtils.equals("LOCAL", platform)) { + return new LocalIssue(addIssueRequest); + } + return null; + } + + public static List createPlatforms(List types, IssuesRequest addIssueRequest) { + List platforms = new ArrayList<>(); + types.forEach(type -> { + AbstractIssuePlatform abstractIssuePlatform = createPlatform(type, addIssueRequest); + if (abstractIssuePlatform != null) { + platforms.add(abstractIssuePlatform); + } + }); + return platforms; + } +} diff --git a/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java new file mode 100644 index 0000000000..a80d225733 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java @@ -0,0 +1,39 @@ +package io.metersphere.track.issue; + +import io.metersphere.base.domain.Issues; +import io.metersphere.track.request.testcase.IssuesRequest; + +import java.util.List; + +public interface IssuesPlatform { + + /** + * 获取平台相关联的缺陷 + * @return + */ + List getIssue(); + + /** + * 添加缺陷到缺陷平台 + * @param issuesRequest + */ + void addIssue(IssuesRequest issuesRequest); + + /** + * 删除缺陷平台缺陷 + * @param id + */ + void deleteIssue(String id); + + /** + * 测试缺陷平台连通性 + * @param + */ + void testAuth(); + + /** + * 获取缺陷平台项目下的相关人员 + * @return + */ + List getPlatformUser(); +} diff --git a/backend/src/main/java/io/metersphere/track/issue/JiraIssue.java b/backend/src/main/java/io/metersphere/track/issue/JiraIssue.java new file mode 100644 index 0000000000..40a1277f90 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/JiraIssue.java @@ -0,0 +1,264 @@ +package io.metersphere.track.issue; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.base.domain.*; +import io.metersphere.commons.constants.IssuesManagePlatform; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.EncryptUtils; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.track.request.testcase.IssuesRequest; +import org.apache.commons.lang3.StringUtils; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.safety.Whitelist; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class JiraIssue extends AbstractIssuePlatform { + + + public JiraIssue(IssuesRequest issuesRequest) { + super(issuesRequest); + } + + @Override + public List getIssue() { + List list = new ArrayList<>(); + + String config = getPlatformConfig(IssuesManagePlatform.Jira.toString()); + JSONObject object = JSON.parseObject(config); + + if (object == null) { + MSException.throwException("tapd config is null"); + } + + String account = object.getString("account"); + String password = object.getString("password"); + String url = object.getString("url"); + HttpHeaders headers = auth(account, password); + + TestCaseIssuesExample example = new TestCaseIssuesExample(); + example.createCriteria().andTestCaseIdEqualTo(testCaseId); + + List issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Jira.toString()); + + List issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList()); + issuesIds.forEach(issuesId -> { + Issues dto = getJiraIssues(headers, url, issuesId); + if (StringUtils.isBlank(dto.getId())) { + // 缺陷不存在,解除用例和缺陷的关联 + TestCaseIssuesExample issuesExample = new TestCaseIssuesExample(); + issuesExample.createCriteria() + .andTestCaseIdEqualTo(testCaseId) + .andIssuesIdEqualTo(issuesId); + testCaseIssuesMapper.deleteByExample(issuesExample); + issuesMapper.deleteByPrimaryKey(issuesId); + } else { + // 缺陷状态为 完成,则不显示 + if (!StringUtils.equals("done", dto.getStatus())) { + list.add(dto); + } + } + }); + return list; + } + + @Override + public void addIssue(IssuesRequest issuesRequest) { + String config = getPlatformConfig(IssuesManagePlatform.Jira.toString()); + JSONObject object = JSON.parseObject(config); + + if (object == null) { + MSException.throwException("jira config is null"); + } + + String account = object.getString("account"); + String password = object.getString("password"); + String url = object.getString("url"); + String issuetype = object.getString("issuetype"); + if (StringUtils.isBlank(issuetype)) { + MSException.throwException("Jira 问题类型为空"); + } + String auth = EncryptUtils.base64Encoding(account + ":" + password); + + String testCaseId = issuesRequest.getTestCaseId(); + String jiraKey = getProjectId(); + + + if (StringUtils.isBlank(jiraKey)) { + MSException.throwException("未关联Jira 项目Key"); + } + + String content = issuesRequest.getContent(); + + Document document = Jsoup.parse(content); + document.outputSettings(new Document.OutputSettings().prettyPrint(false)); + document.select("br").append("\\n"); + document.select("p").prepend("\\n\\n"); + String s = document.html().replaceAll("\\\\n", "\n"); + String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false)); + desc = desc.replace(" ", ""); + + String json = "{\n" + + " \"fields\":{\n" + + " \"project\":{\n" + + " \"key\":\"" + jiraKey + "\"\n" + + " },\n" + + " \"summary\":\"" + issuesRequest.getTitle() + "\",\n" + + " \"description\": " + JSON.toJSONString(desc) + ",\n" + + " \"issuetype\":{\n" + + " \"name\":\"" + issuetype + "\"\n" + + " }\n" + + " }\n" + + "}"; + + String result = addJiraIssue(url, auth, json); + + JSONObject jsonObject = JSON.parseObject(result); + String id = jsonObject.getString("key"); + + // 用例与第三方缺陷平台中的缺陷关联 + TestCaseIssues testCaseIssues = new TestCaseIssues(); + testCaseIssues.setId(UUID.randomUUID().toString()); + testCaseIssues.setIssuesId(id); + testCaseIssues.setTestCaseId(testCaseId); + testCaseIssuesMapper.insert(testCaseIssues); + + // 插入缺陷表 + Issues issues = new Issues(); + issues.setId(id); + issues.setPlatform(IssuesManagePlatform.Jira.toString()); + issuesMapper.insert(issues); + } + + private String addJiraIssue(String url, String auth, String json) { + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.add("Authorization", "Basic " + auth); + requestHeaders.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); + //HttpEntity + HttpEntity requestEntity = new HttpEntity<>(json, requestHeaders); + RestTemplate restTemplate = new RestTemplate(); + //post + ResponseEntity responseEntity = null; + try { + responseEntity = restTemplate.exchange(url + "/rest/api/2/issue", HttpMethod.POST, requestEntity, String.class); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("调用Jira接口创建缺陷失败"); + } + + return responseEntity.getBody(); + } + + @Override + public void deleteIssue(String id) { + + } + + @Override + public void testAuth() { + try { + String config = getPlatformConfig(IssuesManagePlatform.Jira.toString()); + JSONObject object = JSON.parseObject(config); + String account = object.getString("account"); + String password = object.getString("password"); + String url = object.getString("url"); + HttpHeaders headers = auth(account, password); + HttpEntity requestEntity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.exchange(url + "rest/api/2/issue/createmeta", HttpMethod.GET, requestEntity, String.class); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("验证失败!"); + } + } + + @Override + public List getPlatformUser() { + return null; + } + + @Override + String getProjectId() { + TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId); + Project project = projectService.getProjectById(testCase.getProjectId()); + return project.getJiraKey(); + } + + private Issues getJiraIssues(HttpHeaders headers, String url, String issuesId) { + HttpEntity requestEntity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + //post + ResponseEntity responseEntity; + Issues issues = new Issues(); + try { + responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class); + String body = responseEntity.getBody(); + + JSONObject obj = JSONObject.parseObject(body); + LogUtil.info(obj); + + String lastmodify = ""; + String status = ""; + + JSONObject fields = (JSONObject) obj.get("fields"); + JSONObject statusObj = (JSONObject) fields.get("status"); + JSONObject assignee = (JSONObject) fields.get("assignee"); + + if (statusObj != null) { + JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory"); + status = statusCategory.getString("key"); + } + + String id = obj.getString("key"); + String title = fields.getString("summary"); + String description = fields.getString("description"); + + Parser parser = Parser.builder().build(); + Node document = parser.parse(description); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + description = renderer.render(document); + + Long createTime = fields.getLong("created"); + + if (assignee != null) { + lastmodify = assignee.getString("displayName"); + } + + issues.setId(id); + issues.setTitle(title); + issues.setCreateTime(createTime); + issues.setLastmodify(lastmodify); + issues.setDescription(description); + issues.setStatus(status); + issues.setPlatform(IssuesManagePlatform.Jira.toString()); + } catch (HttpClientErrorException.NotFound e) { + LogUtil.error(e.getStackTrace(), e); + return new Issues(); + } catch (HttpClientErrorException.Unauthorized e) { + LogUtil.error(e.getStackTrace(), e); + MSException.throwException("获取Jira缺陷失败,检查Jira配置信息"); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("调用Jira接口获取缺陷失败"); + } + + return issues; + + } + +} diff --git a/backend/src/main/java/io/metersphere/track/issue/LocalIssue.java b/backend/src/main/java/io/metersphere/track/issue/LocalIssue.java new file mode 100644 index 0000000000..b75946b726 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/LocalIssue.java @@ -0,0 +1,72 @@ +package io.metersphere.track.issue; + +import io.metersphere.base.domain.Issues; +import io.metersphere.base.domain.TestCaseIssues; +import io.metersphere.commons.constants.IssuesManagePlatform; +import io.metersphere.commons.user.SessionUser; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.track.request.testcase.IssuesRequest; + +import java.util.List; +import java.util.UUID; + +public class LocalIssue extends AbstractIssuePlatform { + + public LocalIssue(IssuesRequest issuesRequest) { + super(issuesRequest); + } + + @Override + public List getIssue() { + return extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Local.toString()); + } + + @Override + public void addIssue(IssuesRequest issuesRequest) { + SessionUser user = SessionUtils.getUser(); + String id = UUID.randomUUID().toString(); + Issues issues = new Issues(); + issues.setId(id); + issues.setStatus("new"); + issues.setReporter(user.getId()); + issues.setTitle(issuesRequest.getTitle()); + issues.setDescription(issuesRequest.getContent()); + issues.setCreateTime(System.currentTimeMillis()); + issues.setUpdateTime(System.currentTimeMillis()); + issues.setPlatform(IssuesManagePlatform.Local.toString()); + issuesMapper.insert(issues); + + TestCaseIssues testCaseIssues = new TestCaseIssues(); + testCaseIssues.setId(UUID.randomUUID().toString()); + testCaseIssues.setIssuesId(id); + testCaseIssues.setTestCaseId(issuesRequest.getTestCaseId()); + testCaseIssuesMapper.insert(testCaseIssues); + } + + @Override + public void deleteIssue(String id) { + issuesMapper.deleteByPrimaryKey(id); + } + + @Override + public void testAuth() { + + } + + @Override + public List getPlatformUser() { + return null; + } + + @Override + String getProjectId() { + return null; + } + + public void closeIssue(String issueId) { + Issues issues = new Issues(); + issues.setId(issueId); + issues.setStatus("closed"); + issuesMapper.updateByPrimaryKeySelective(issues); + } +} diff --git a/backend/src/main/java/io/metersphere/track/issue/PlatformUser.java b/backend/src/main/java/io/metersphere/track/issue/PlatformUser.java new file mode 100644 index 0000000000..ac913343ae --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/PlatformUser.java @@ -0,0 +1,12 @@ +package io.metersphere.track.issue; + +import lombok.Data; + +import java.util.List; + +@Data +public class PlatformUser { + private List roleId; + private String name; + private String user; +} diff --git a/backend/src/main/java/io/metersphere/track/issue/TapdIssue.java b/backend/src/main/java/io/metersphere/track/issue/TapdIssue.java new file mode 100644 index 0000000000..3df46f4291 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/issue/TapdIssue.java @@ -0,0 +1,200 @@ +package io.metersphere.track.issue; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.base.domain.*; +import io.metersphere.commons.constants.IssuesManagePlatform; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.commons.utils.RestTemplateUtils; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.controller.ResultHolder; +import io.metersphere.track.request.testcase.IssuesRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class TapdIssue extends AbstractIssuePlatform { + + + public TapdIssue(IssuesRequest issueRequest) { + super(issueRequest); + } + + @Override + public List getIssue() { + List list = new ArrayList<>(); + String tapdId = getProjectId(); + + TestCaseIssuesExample example = new TestCaseIssuesExample(); + example.createCriteria().andTestCaseIdEqualTo(testCaseId); + + List issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Tapd.toString()); + + List issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList()); + issuesIds.forEach(issuesId -> { + Issues dto = getTapdIssues(tapdId, issuesId); + if (StringUtils.isBlank(dto.getId())) { + // 缺陷不存在,解除用例和缺陷的关联 + TestCaseIssuesExample issuesExample = new TestCaseIssuesExample(); + issuesExample.createCriteria() + .andTestCaseIdEqualTo(testCaseId) + .andIssuesIdEqualTo(issuesId); + testCaseIssuesMapper.deleteByExample(issuesExample); + issuesMapper.deleteByPrimaryKey(issuesId); + } else { + dto.setPlatform(IssuesManagePlatform.Tapd.toString()); + // 缺陷状态为 关闭,则不显示 + if (!StringUtils.equals("closed", dto.getStatus())) { + list.add(dto); + } + } + }); + return list; + } + + private Issues getTapdIssues(String projectId, String issuesId) { + String url = "https://api.tapd.cn/bugs?workspace_id=" + projectId + "&id=" + issuesId; + ResultHolder call = call(url); + String listJson = JSON.toJSONString(call.getData()); + if (StringUtils.equals(Boolean.FALSE.toString(), listJson)) { + return new Issues(); + } + JSONObject jsonObject = JSONObject.parseObject(listJson); + JSONObject bug = jsonObject.getJSONObject("Bug"); + Long created = bug.getLong("created"); + Issues issues = jsonObject.getObject("Bug", Issues.class); + issues.setCreateTime(created); + return issues; + } + + @Override + public void addIssue(IssuesRequest issuesRequest) { + String url = "https://api.tapd.cn/bugs"; + String testCaseId = issuesRequest.getTestCaseId(); + String tapdId = getProjectId(); + + if (StringUtils.isBlank(tapdId)) { + MSException.throwException("未关联Tapd 项目ID"); + } + + List PlatformUsers = issuesRequest.getTapdUsers(); + String usersStr = String.join(";", PlatformUsers); + + String username = SessionUtils.getUser().getName(); + + MultiValueMap paramMap = new LinkedMultiValueMap<>(); + paramMap.add("title", issuesRequest.getTitle()); + paramMap.add("workspace_id", tapdId); + paramMap.add("description", issuesRequest.getContent()); + paramMap.add("reporter", username); + paramMap.add("current_owner", usersStr); + + ResultHolder result = call(url, HttpMethod.POST, paramMap); + + String listJson = JSON.toJSONString(result.getData()); + JSONObject jsonObject = JSONObject.parseObject(listJson); + String issuesId = jsonObject.getObject("Bug", Issues.class).getId(); + + // 用例与第三方缺陷平台中的缺陷关联 + TestCaseIssues testCaseIssues = new TestCaseIssues(); + testCaseIssues.setId(UUID.randomUUID().toString()); + testCaseIssues.setIssuesId(issuesId); + testCaseIssues.setTestCaseId(testCaseId); + testCaseIssuesMapper.insert(testCaseIssues); + + // 插入缺陷表 + Issues issues = new Issues(); + issues.setId(issuesId); + issues.setPlatform(IssuesManagePlatform.Tapd.toString()); + issuesMapper.insert(issues); + } + + @Override + public void deleteIssue(String id) {} + + @Override + public void testAuth() { + try { + String tapdConfig = getPlatformConfig(IssuesManagePlatform.Tapd.toString()); + JSONObject object = JSON.parseObject(tapdConfig); + String account = object.getString("account"); + String password = object.getString("password"); + HttpHeaders headers = auth(account, password); + HttpEntity requestEntity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.exchange("https://api.tapd.cn/quickstart/testauth", HttpMethod.GET, requestEntity, String.class); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("验证失败!"); + } + } + + @Override + public List getPlatformUser() { + List users = new ArrayList<>(); + String projectId = getProjectId(); + String url = "https://api.tapd.cn/workspaces/users?workspace_id=" + projectId; + ResultHolder call = call(url); + String listJson = JSON.toJSONString(call.getData()); + JSONArray jsonArray = JSON.parseArray(listJson); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject o = jsonArray.getJSONObject(i); + PlatformUser user = o.getObject("UserWorkspace", PlatformUser.class); + users.add(user); + } + return users; + } + + @Override + String getProjectId() { + TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId); + Project project = projectService.getProjectById(testCase.getProjectId()); + return project.getTapdId(); + } + + private ResultHolder call(String url) { + return call(url, HttpMethod.GET, null); + } + + private ResultHolder call(String url, HttpMethod httpMethod, Object params) { + String responseJson; + + String config = getPlatformConfig(IssuesManagePlatform.Tapd.toString()); + JSONObject object = JSON.parseObject(config); + + if (object == null) { + MSException.throwException("tapd config is null"); + } + + String account = object.getString("account"); + String password = object.getString("password"); + + HttpHeaders header = auth(account, password); + + if (httpMethod.equals(HttpMethod.GET)) { + responseJson = RestTemplateUtils.get(url, header); + } else { + responseJson = RestTemplateUtils.post(url, params, header); + } + + ResultHolder result = JSON.parseObject(responseJson, ResultHolder.class); + + if (!result.isSuccess()) { + MSException.throwException(result.getMessage()); + } + return JSON.parseObject(responseJson, ResultHolder.class); + + } + +} diff --git a/backend/src/main/java/io/metersphere/track/service/IssuesService.java b/backend/src/main/java/io/metersphere/track/service/IssuesService.java index f07e697471..8030b09b74 100644 --- a/backend/src/main/java/io/metersphere/track/service/IssuesService.java +++ b/backend/src/main/java/io/metersphere/track/service/IssuesService.java @@ -1,45 +1,23 @@ package io.metersphere.track.service; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.IssuesMapper; -import io.metersphere.base.mapper.TestCaseIssuesMapper; -import io.metersphere.base.mapper.ext.ExtIssuesMapper; import io.metersphere.commons.constants.IssuesManagePlatform; -import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; -import io.metersphere.commons.utils.EncryptUtils; -import io.metersphere.commons.utils.LogUtil; -import io.metersphere.commons.utils.RestTemplateUtils; import io.metersphere.commons.utils.SessionUtils; -import io.metersphere.controller.ResultHolder; import io.metersphere.controller.request.IntegrationRequest; import io.metersphere.service.IntegrationService; import io.metersphere.service.ProjectService; -import io.metersphere.track.domain.TapdUser; +import io.metersphere.track.issue.AbstractIssuePlatform; +import io.metersphere.track.issue.IssueFactory; +import io.metersphere.track.issue.PlatformUser; import io.metersphere.track.request.testcase.IssuesRequest; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.safety.Whitelist; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.apache.commons.lang3.StringUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; -import org.commonmark.node.*; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; + import javax.annotation.Resource; import java.util.*; -import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) @@ -48,108 +26,16 @@ public class IssuesService { @Resource private IntegrationService integrationService; @Resource - private TestCaseIssuesMapper testCaseIssuesMapper; - @Resource private ProjectService projectService; @Resource private TestCaseService testCaseService; @Resource private IssuesMapper issuesMapper; - @Resource - private ExtIssuesMapper extIssuesMapper; public void testAuth(String platform) { - if (StringUtils.equals(platform, IssuesManagePlatform.Tapd.toString())) { - - try { - String tapdConfig = platformConfig(IssuesManagePlatform.Tapd.toString()); - JSONObject object = JSON.parseObject(tapdConfig); - String account = object.getString("account"); - String password = object.getString("password"); - HttpHeaders headers = auth(account, password); - HttpEntity requestEntity = new HttpEntity<>(headers); - RestTemplate restTemplate = new RestTemplate(); - restTemplate.exchange("https://api.tapd.cn/quickstart/testauth", HttpMethod.GET, requestEntity, String.class); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException("验证失败!"); - } - - } else { - - try { - String config = platformConfig(IssuesManagePlatform.Jira.toString()); - JSONObject object = JSON.parseObject(config); - String account = object.getString("account"); - String password = object.getString("password"); - String url = object.getString("url"); - HttpHeaders headers = auth(account, password); - HttpEntity requestEntity = new HttpEntity<>(headers); - RestTemplate restTemplate = new RestTemplate(); - restTemplate.exchange(url + "rest/api/2/issue/createmeta", HttpMethod.GET, requestEntity, String.class); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException("验证失败!"); - } - } - - } - - private ResultHolder call(String url) { - return call(url, HttpMethod.GET, null); - } - - private ResultHolder call(String url, HttpMethod httpMethod, Object params) { - String responseJson; - - String config = platformConfig(IssuesManagePlatform.Tapd.toString()); - JSONObject object = JSON.parseObject(config); - - if (object == null) { - MSException.throwException("tapd config is null"); - } - - String account = object.getString("account"); - String password = object.getString("password"); - - HttpHeaders header = auth(account, password); - - if (httpMethod.equals(HttpMethod.GET)) { - responseJson = RestTemplateUtils.get(url, header); - } else { - responseJson = RestTemplateUtils.post(url, params, header); - } - - ResultHolder result = JSON.parseObject(responseJson, ResultHolder.class); - - if (!result.isSuccess()) { - MSException.throwException(result.getMessage()); - } - return JSON.parseObject(responseJson, ResultHolder.class); - - } - - private String platformConfig(String platform) { - SessionUser user = SessionUtils.getUser(); - String orgId = user.getLastOrganizationId(); - - IntegrationRequest request = new IntegrationRequest(); - if (StringUtils.isBlank(orgId)) { - MSException.throwException("organization id is null"); - } - request.setOrgId(orgId); - request.setPlatform(platform); - - ServiceIntegration integration = integrationService.get(request); - return integration.getConfiguration(); - } - - private HttpHeaders auth(String apiUser, String password) { - String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password); - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Basic " + authKey); - return headers; + AbstractIssuePlatform abstractPlatform = IssueFactory.createPlatform(platform, new IssuesRequest()); + abstractPlatform.testAuth(); } public void addIssues(IssuesRequest issuesRequest) { @@ -162,247 +48,29 @@ public class IssuesService { String tapdId = getTapdProjectId(issuesRequest.getTestCaseId()); String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId()); + List platforms = new ArrayList<>(); + if (tapd) { // 是否关联了项目 if (StringUtils.isNotBlank(tapdId)) { - addTapdIssues(issuesRequest); + platforms.add(IssuesManagePlatform.Tapd.name()); } } if (jira) { if (StringUtils.isNotBlank(jiraKey)) { - addJiraIssues(issuesRequest); + platforms.add(IssuesManagePlatform.Jira.name()); } } if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey)) { - addLocalIssues(issuesRequest); + platforms.add("LOCAL"); } - } - - public void addTapdIssues(IssuesRequest issuesRequest) { - String url = "https://api.tapd.cn/bugs"; - String testCaseId = issuesRequest.getTestCaseId(); - String tapdId = getTapdProjectId(testCaseId); - - if (StringUtils.isBlank(tapdId)) { - MSException.throwException("未关联Tapd 项目ID"); - } - - List tapdUsers = issuesRequest.getTapdUsers(); - String usersStr = String.join(";", tapdUsers); - - String username = SessionUtils.getUser().getName(); - - MultiValueMap paramMap = new LinkedMultiValueMap<>(); - paramMap.add("title", issuesRequest.getTitle()); - paramMap.add("workspace_id", tapdId); - paramMap.add("description", issuesRequest.getContent()); - paramMap.add("reporter", username); - paramMap.add("current_owner", usersStr); - - ResultHolder result = call(url, HttpMethod.POST, paramMap); - - String listJson = JSON.toJSONString(result.getData()); - JSONObject jsonObject = JSONObject.parseObject(listJson); - String issuesId = jsonObject.getObject("Bug", Issues.class).getId(); - - // 用例与第三方缺陷平台中的缺陷关联 - TestCaseIssues testCaseIssues = new TestCaseIssues(); - testCaseIssues.setId(UUID.randomUUID().toString()); - testCaseIssues.setIssuesId(issuesId); - testCaseIssues.setTestCaseId(testCaseId); - testCaseIssuesMapper.insert(testCaseIssues); - - // 插入缺陷表 - Issues issues = new Issues(); - issues.setId(issuesId); - issues.setPlatform(IssuesManagePlatform.Tapd.toString()); - issuesMapper.insert(issues); - } - - public void addJiraIssues(IssuesRequest issuesRequest) { - String config = platformConfig(IssuesManagePlatform.Jira.toString()); - JSONObject object = JSON.parseObject(config); - - if (object == null) { - MSException.throwException("jira config is null"); - } - - String account = object.getString("account"); - String password = object.getString("password"); - String url = object.getString("url"); - String issuetype = object.getString("issuetype"); - if (StringUtils.isBlank(issuetype)) { - MSException.throwException("Jira 问题类型为空"); - } - String auth = EncryptUtils.base64Encoding(account + ":" + password); - - String testCaseId = issuesRequest.getTestCaseId(); - String jiraKey = getJiraProjectKey(testCaseId); - - - if (StringUtils.isBlank(jiraKey)) { - MSException.throwException("未关联Jira 项目Key"); - } - - String content = issuesRequest.getContent(); - - Document document = Jsoup.parse(content); - document.outputSettings(new Document.OutputSettings().prettyPrint(false)); - document.select("br").append("\\n"); - document.select("p").prepend("\\n\\n"); - String s = document.html().replaceAll("\\\\n", "\n"); - String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false)); - desc = desc.replace(" ", ""); - - String json = "{\n" + - " \"fields\":{\n" + - " \"project\":{\n" + - " \"key\":\"" + jiraKey + "\"\n" + - " },\n" + - " \"summary\":\"" + issuesRequest.getTitle() + "\",\n" + - " \"description\": " + JSON.toJSONString(desc) + ",\n" + - " \"issuetype\":{\n" + - " \"name\":\"" + issuetype + "\"\n" + - " }\n" + - " }\n" + - "}"; - - String result = addJiraIssue(url, auth, json); - - JSONObject jsonObject = JSON.parseObject(result); - String id = jsonObject.getString("key"); - - // 用例与第三方缺陷平台中的缺陷关联 - TestCaseIssues testCaseIssues = new TestCaseIssues(); - testCaseIssues.setId(UUID.randomUUID().toString()); - testCaseIssues.setIssuesId(id); - testCaseIssues.setTestCaseId(testCaseId); - testCaseIssuesMapper.insert(testCaseIssues); - - // 插入缺陷表 - Issues issues = new Issues(); - issues.setId(id); - issues.setPlatform(IssuesManagePlatform.Jira.toString()); - issuesMapper.insert(issues); - } - - private String addJiraIssue(String url, String auth, String json) { - HttpHeaders requestHeaders = new HttpHeaders(); - requestHeaders.add("Authorization", "Basic " + auth); - requestHeaders.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); - //HttpEntity - HttpEntity requestEntity = new HttpEntity<>(json, requestHeaders); - RestTemplate restTemplate = new RestTemplate(); - //post - ResponseEntity responseEntity = null; - try { - responseEntity = restTemplate.exchange(url + "/rest/api/2/issue", HttpMethod.POST, requestEntity, String.class); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException("调用Jira接口创建缺陷失败"); - } - - return responseEntity.getBody(); - } - - public void addLocalIssues(IssuesRequest request) { - SessionUser user = SessionUtils.getUser(); - String id = UUID.randomUUID().toString(); - Issues issues = new Issues(); - issues.setId(id); - issues.setStatus("new"); - issues.setReporter(user.getId()); - issues.setTitle(request.getTitle()); - issues.setDescription(request.getContent()); - issues.setCreateTime(System.currentTimeMillis()); - issues.setUpdateTime(System.currentTimeMillis()); - issues.setPlatform(IssuesManagePlatform.Local.toString()); - issuesMapper.insert(issues); - - TestCaseIssues testCaseIssues = new TestCaseIssues(); - testCaseIssues.setId(UUID.randomUUID().toString()); - testCaseIssues.setIssuesId(id); - testCaseIssues.setTestCaseId(request.getTestCaseId()); - testCaseIssuesMapper.insert(testCaseIssues); - } - - public Issues getTapdIssues(String projectId, String issuesId) { - String url = "https://api.tapd.cn/bugs?workspace_id=" + projectId + "&id=" + issuesId; - ResultHolder call = call(url); - String listJson = JSON.toJSONString(call.getData()); - if (StringUtils.equals(Boolean.FALSE.toString(), listJson)) { - return new Issues(); - } - JSONObject jsonObject = JSONObject.parseObject(listJson); - JSONObject bug = jsonObject.getJSONObject("Bug"); - Long created = bug.getLong("created"); - Issues issues = jsonObject.getObject("Bug", Issues.class); - issues.setCreateTime(created); - return issues; - } - - public Issues getJiraIssues(HttpHeaders headers, String url, String issuesId) { - HttpEntity requestEntity = new HttpEntity<>(headers); - RestTemplate restTemplate = new RestTemplate(); - //post - ResponseEntity responseEntity; - Issues issues = new Issues(); - try { - responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class); - String body = responseEntity.getBody(); - - JSONObject obj = JSONObject.parseObject(body); - LogUtil.info(obj); - - String lastmodify = ""; - String status = ""; - - JSONObject fields = (JSONObject) obj.get("fields"); - JSONObject statusObj = (JSONObject) fields.get("status"); - JSONObject assignee = (JSONObject) fields.get("assignee"); - - if (statusObj != null) { - JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory"); - status = statusCategory.getString("key"); - } - - String id = obj.getString("key"); - String title = fields.getString("summary"); - String description = fields.getString("description"); - - Parser parser = Parser.builder().build(); - Node document = parser.parse(description); - HtmlRenderer renderer = HtmlRenderer.builder().build(); - description = renderer.render(document); - - Long createTime = fields.getLong("created"); - - if (assignee != null) { - lastmodify = assignee.getString("displayName"); - } - - issues.setId(id); - issues.setTitle(title); - issues.setCreateTime(createTime); - issues.setLastmodify(lastmodify); - issues.setDescription(description); - issues.setStatus(status); - issues.setPlatform(IssuesManagePlatform.Jira.toString()); - } catch (HttpClientErrorException.NotFound e) { - LogUtil.error(e.getStackTrace(), e); - return new Issues(); - } catch (HttpClientErrorException.Unauthorized e) { - LogUtil.error(e.getStackTrace(), e); - MSException.throwException("获取Jira缺陷失败,检查Jira配置信息"); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException("调用Jira接口获取缺陷失败"); - } - - return issues; + List platformList = IssueFactory.createPlatforms(platforms, issuesRequest); + platformList.forEach(platform -> { + platform.addIssue(issuesRequest); + }); } @@ -414,11 +82,12 @@ public class IssuesService { boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString()); boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString()); + List platforms = new ArrayList<>(); if (tapd) { // 是否关联了项目 String tapdId = getTapdProjectId(caseId); if (StringUtils.isNotBlank(tapdId)) { - list.addAll(getTapdIssues(caseId)); + platforms.add(IssuesManagePlatform.Tapd.name()); } } @@ -426,90 +95,22 @@ public class IssuesService { if (jira) { String jiraKey = getJiraProjectKey(caseId); if (StringUtils.isNotBlank(jiraKey)) { - list.addAll(getJiraIssues(caseId)); + platforms.add(IssuesManagePlatform.Jira.name()); } } - list.addAll(getLocalIssues(caseId)); - return list; - } - - public List getTapdIssues(String caseId) { - List list = new ArrayList<>(); - String tapdId = getTapdProjectId(caseId); - - TestCaseIssuesExample example = new TestCaseIssuesExample(); - example.createCriteria().andTestCaseIdEqualTo(caseId); - - List issues = extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Tapd.toString()); - - List issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList()); - issuesIds.forEach(issuesId -> { - Issues dto = getTapdIssues(tapdId, issuesId); - if (StringUtils.isBlank(dto.getId())) { - // 缺陷不存在,解除用例和缺陷的关联 - TestCaseIssuesExample issuesExample = new TestCaseIssuesExample(); - issuesExample.createCriteria() - .andTestCaseIdEqualTo(caseId) - .andIssuesIdEqualTo(issuesId); - testCaseIssuesMapper.deleteByExample(issuesExample); - issuesMapper.deleteByPrimaryKey(issuesId); - } else { - dto.setPlatform(IssuesManagePlatform.Tapd.toString()); - // 缺陷状态为 关闭,则不显示 - if (!StringUtils.equals("closed", dto.getStatus())) { - list.add(dto); - } - } + platforms.add("LOCAL"); + IssuesRequest issueRequest = new IssuesRequest(); + issueRequest.setTestCaseId(caseId); + List platformList = IssueFactory.createPlatforms(platforms, issueRequest); + platformList.forEach(platform -> { + List issue = platform.getIssue(); + list.addAll(issue); }); + return list; } - public List getJiraIssues(String caseId) { - List list = new ArrayList<>(); - - String config = platformConfig(IssuesManagePlatform.Jira.toString()); - JSONObject object = JSON.parseObject(config); - - if (object == null) { - MSException.throwException("tapd config is null"); - } - - String account = object.getString("account"); - String password = object.getString("password"); - String url = object.getString("url"); - HttpHeaders headers = auth(account, password); - - TestCaseIssuesExample example = new TestCaseIssuesExample(); - example.createCriteria().andTestCaseIdEqualTo(caseId); - - List issues = extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Jira.toString()); - - List issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList()); - issuesIds.forEach(issuesId -> { - Issues dto = getJiraIssues(headers, url, issuesId); - if (StringUtils.isBlank(dto.getId())) { - // 缺陷不存在,解除用例和缺陷的关联 - TestCaseIssuesExample issuesExample = new TestCaseIssuesExample(); - issuesExample.createCriteria() - .andTestCaseIdEqualTo(caseId) - .andIssuesIdEqualTo(issuesId); - testCaseIssuesMapper.deleteByExample(issuesExample); - issuesMapper.deleteByPrimaryKey(issuesId); - } else { - // 缺陷状态为 完成,则不显示 - if (!StringUtils.equals("done", dto.getStatus())) { - list.add(dto); - } - } - }); - return list; - } - - public List getLocalIssues(String caseId) { - return extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Local.toString()); - } - public String getTapdProjectId(String testCaseId) { TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId); Project project = projectService.getProjectById(testCase.getProjectId()); @@ -540,19 +141,11 @@ public class IssuesService { issuesMapper.updateByPrimaryKeySelective(issues); } - public List getTapdProjectUsers(String caseId) { - List users = new ArrayList<>(); - String projectId = getTapdProjectId(caseId); - String url = "https://api.tapd.cn/workspaces/users?workspace_id=" + projectId; - ResultHolder call = call(url); - String listJson = JSON.toJSONString(call.getData()); - JSONArray jsonArray = JSON.parseArray(listJson); - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject o = jsonArray.getJSONObject(i); - TapdUser user = o.getObject("UserWorkspace", TapdUser.class); - users.add(user); - } - return users; + public List getTapdProjectUsers(String caseId) { + IssuesRequest issueRequest = new IssuesRequest(); + issueRequest.setTestCaseId(caseId); + AbstractIssuePlatform platform = IssueFactory.createPlatform(IssuesManagePlatform.Tapd.name(), issueRequest); + return platform.getPlatformUser(); } public void deleteIssue(String id) { From 08b973c03e3b41c5609b6c64d9128427a85c27db Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 10:55:52 +0800 Subject: [PATCH 13/31] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20POST=20=E6=94=AF=E6=8C=81=20multipart/form-data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/scenario/request/HttpRequest.java | 2 + .../controller/TestController.java | 9 ++++ backend/src/main/java/io/metersphere/xpack | 2 +- .../components/request/ApiHttpRequestForm.vue | 48 +++++++++++-------- .../business/components/api/test/model/JMX.js | 1 + .../api/test/model/ScenarioModel.js | 3 +- frontend/src/business/components/xpack | 2 +- frontend/src/i18n/en-US.js | 1 + frontend/src/i18n/zh-CN.js | 1 + frontend/src/i18n/zh-TW.js | 1 + 10 files changed, 46 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/request/HttpRequest.java b/backend/src/main/java/io/metersphere/api/dto/scenario/request/HttpRequest.java index f43fcc41c7..74b497d07c 100644 --- a/backend/src/main/java/io/metersphere/api/dto/scenario/request/HttpRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/request/HttpRequest.java @@ -35,4 +35,6 @@ public class HttpRequest extends Request { private Long responseTimeout; @JSONField(ordinal = 16) private Boolean followRedirects; + @JSONField(ordinal = 17) + private Boolean doMultipartPost; } diff --git a/backend/src/main/java/io/metersphere/controller/TestController.java b/backend/src/main/java/io/metersphere/controller/TestController.java index c3b7c033af..9783d98572 100644 --- a/backend/src/main/java/io/metersphere/controller/TestController.java +++ b/backend/src/main/java/io/metersphere/controller/TestController.java @@ -25,6 +25,15 @@ public class TestController { return jsonObject; } + @PostMapping(value = "/multipart", consumes = {"multipart/form-data"}) + public Object testMultipart(@RequestPart(value = "id") String id, @RequestPart(value = "user") User user, @RequestParam(value = "name") String name) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", id); + jsonObject.put("user", user.getName()); + jsonObject.put("name", name); + return jsonObject; + } + @GetMapping(value = "/{str}") public Object getString(@PathVariable String str) throws InterruptedException { if (StringUtils.equals("error", str)) { diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index ee74568be0..cf6b065263 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit ee74568be0beba46da19616f5832e83f9164c688 +Subproject commit cf6b06526324326a563d933e07118fac014a63b4 diff --git a/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue b/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue index 4999ba3842..3e0e43b21a 100644 --- a/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue +++ b/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue @@ -39,6 +39,7 @@ :active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange"> + {{$t('api_test.request.do_multipart_post')}} diff --git a/frontend/src/business/components/api/test/model/JMX.js b/frontend/src/business/components/api/test/model/JMX.js index 0d02ecff8c..6c57e47486 100644 --- a/frontend/src/business/components/api/test/model/JMX.js +++ b/frontend/src/business/components/api/test/model/JMX.js @@ -337,6 +337,7 @@ export class HTTPSamplerProxy extends DefaultTestElement { } this.boolProp("HTTPSampler.use_keepalive", options.keepalive, true); + this.boolProp("HTTPSampler.DO_MULTIPART_POST", options.doMultipartPost, false); } } diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index e1c8d79763..a10d6e3c56 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -346,6 +346,7 @@ export class HttpRequest extends Request { this.environment = options.environment; this.useEnvironment = options.useEnvironment; this.debugReport = undefined; + this.doMultipartPost = options.doMultipartPost; this.connectTimeout = options.connectTimeout || 60 * 1000; this.responseTimeout = options.responseTimeout; this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects; @@ -987,7 +988,7 @@ class JMXHttpRequest { this.connectTimeout = request.connectTimeout; this.responseTimeout = request.responseTimeout; this.followRedirects = request.followRedirects; - + this.doMultipartPost = request.doMultipartPost; } } diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index cc38137a69..06d935cd1d 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit cc38137a69a0f20fadece9c0f9f50a9468c4ace9 +Subproject commit 06d935cd1d22ab36f09763745c2aff8ad3fb08c1 diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 9986e5cf4a..0fa27d7b0d 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -504,6 +504,7 @@ export default { connect_timeout: "Connect Timeout", response_timeout: "Response Timeout", follow_redirects: "Follow Redirects", + do_multipart_post: "Use multipart/form-data for POST", body_upload_limit_size: "The file size does not exceed 500 MB", condition: "condition", condition_variable: "Variable, e.g: ${var}", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 3ca1f88a10..6ca3aa5277 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -505,6 +505,7 @@ export default { connect_timeout: "连接超时", response_timeout: "响应超时", follow_redirects: "跟随重定向", + do_multipart_post: "对 POST 使用 multipart/form-data", body_upload_limit_size: "上传文件大小不能超过 500 MB!", condition: "条件", condition_variable: "变量,例如: ${var}", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index f2ca497ec1..0619038219 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -505,6 +505,7 @@ export default { connect_timeout: "連接超時", response_timeout: "響應超時", follow_redirects: "跟隨重定向", + do_multipart_post: "對 POST 使用 multipart/form-data", body_upload_limit_size: "上傳文件大小不能超過 500 MB!", condition: "條件", condition_variable: "變量,例如: ${var}", From 70c513099a4fef80e7dd0499b2ae622bee56722a Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 14 Oct 2020 11:00:16 +0800 Subject: [PATCH 14/31] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=20show-mor?= =?UTF-8?q?e-btn=20=E7=9A=84=E8=83=8C=E6=99=AF=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/components/track/case/components/ShowMoreBtn.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/business/components/track/case/components/ShowMoreBtn.vue b/frontend/src/business/components/track/case/components/ShowMoreBtn.vue index e7e6022138..3a4a8b3b01 100644 --- a/frontend/src/business/components/track/case/components/ShowMoreBtn.vue +++ b/frontend/src/business/components/track/case/components/ShowMoreBtn.vue @@ -45,7 +45,6 @@ width: 20px; height: 25px; line-height: 25px; - background-color: #FFF; } .show-more-btn-title { From 578dfa4b8886cc1a046694ad3c8b9d7f3df845f9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 14 Oct 2020 11:16:14 +0800 Subject: [PATCH 15/31] =?UTF-8?q?style(=E6=B5=8B=E8=AF=95=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA):=20=E8=A7=84=E8=8C=83=E4=B8=8B=E6=8B=89=E6=A1=86?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E7=BB=9F=E4=B8=80=E7=BB=84=E4=BB=B6=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track/plan/view/comonents/TestPlanTestCaseEdit.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseEdit.vue b/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseEdit.vue index 070cd778b2..1ad742ec0a 100644 --- a/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseEdit.vue +++ b/frontend/src/business/components/track/plan/view/comonents/TestPlanTestCaseEdit.vue @@ -231,7 +231,7 @@ filterable style="width: 20%" :placeholder="$t('test_track.issue.please_choose_current_owner')" - collapse-tags> + collapse-tags size="small"> From a02cb2cc465fd72bf6c786920515bf6b110825e6 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 14 Oct 2020 11:44:12 +0800 Subject: [PATCH 16/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E5=AF=BC=E5=85=A5=E7=94=A8=E4=BE=8B=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E4=BF=9D=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/io/metersphere/xmind/XmindCaseParser.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java index 0ee85a5814..db912696b3 100644 --- a/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java +++ b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java @@ -248,6 +248,10 @@ public class XmindCaseParser { String nodePath = data.getNodePath(); StringBuilder stringBuilder = new StringBuilder(); + if (data.getName().length() > 50) { + stringBuilder.append(data.getName() + ":" + Translator.get("test_case") + Translator.get("test_track.length_less_than") + "50 ;"); + } + if (!StringUtils.isEmpty(nodePath)) { String[] nodes = nodePath.split("/"); if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) { @@ -259,7 +263,7 @@ public class XmindCaseParser { stringBuilder.append(Translator.get("module_not_null") + "; "); break; } else if (nodes[i].trim().length() > 30) { - stringBuilder.append(nodes[i].trim() + ":" + Translator.get("test_track.length_less_than") + "30 ;"); + stringBuilder.append(nodes[i].trim() + ":" + Translator.get("module") + Translator.get("test_track.length_less_than") + "30 ;"); break; } } From d58990bb160ee876ca0030d02e291181262fe010 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 12:40:50 +0800 Subject: [PATCH 17/31] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E4=B8=8D=E5=90=8C=E8=AF=B7=E6=B1=82=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E5=8C=BA=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/components/request/ApiRequestConfig.vue | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/src/business/components/api/test/components/request/ApiRequestConfig.vue b/frontend/src/business/components/api/test/components/request/ApiRequestConfig.vue index f192c009cc..76d44cf4d2 100644 --- a/frontend/src/business/components/api/test/components/request/ApiRequestConfig.vue +++ b/frontend/src/business/components/api/test/components/request/ApiRequestConfig.vue @@ -9,7 +9,7 @@
{{ request.showType() }}
-
+
{{ request.showMethod() }}
@@ -85,7 +85,13 @@ export default { selected: 0, visible: false, types: RequestFactory.TYPES, - type: "" + type: "", + methodColorMap: new Map([ + ['GET', "#61AFFE"], ['POST', '#49CC90'], ['PUT', '#fca130'], + ['PATCH', '#E2EE11'], ['DELETE', '#f93e3d'], ['OPTIONS', '#0EF5DA'], + ['HEAD', '#8E58E7'], ['CONNECT', '#90AFAE'], + ['DUBBO', '#C36EEF'],['SQL', '#0AEAD4'],['TCP', '#0A52DF'], + ]) } }, @@ -156,6 +162,11 @@ export default { break; } }, + getColor(enable, method) { + if (enable) { + return this.methodColorMap.get(method); + } + }, select(request) { if (!request) { return; From 0c3b154433c05e986b7f060e05ae1175f698a353 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 13:08:46 +0800 Subject: [PATCH 18/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20=E7=8E=AF=E5=A2=83=E4=B8=AD=E5=8F=98=E9=87=8F=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E5=A4=AA=E9=95=BF=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/test/components/ApiScenarioVariables.vue | 6 +++++- .../components/api/test/components/ApiVariableInput.vue | 8 ++++++-- .../components/environment/EnvironmentCommonConfig.vue | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/business/components/api/test/components/ApiScenarioVariables.vue b/frontend/src/business/components/api/test/components/ApiScenarioVariables.vue index 236b3b2555..3500c4145f 100644 --- a/frontend/src/business/components/api/test/components/ApiScenarioVariables.vue +++ b/frontend/src/business/components/api/test/components/ApiScenarioVariables.vue @@ -11,7 +11,7 @@ - @@ -45,6 +45,10 @@ type: Boolean, default: true }, + showCopy: { + type: Boolean, + default: true + }, }, data() { return { diff --git a/frontend/src/business/components/api/test/components/ApiVariableInput.vue b/frontend/src/business/components/api/test/components/ApiVariableInput.vue index 7d95810ed6..0cbd17338f 100644 --- a/frontend/src/business/components/api/test/components/ApiVariableInput.vue +++ b/frontend/src/business/components/api/test/components/ApiVariableInput.vue @@ -2,8 +2,8 @@
-
{{variable}}
- +
{{variable}}
+
@@ -25,6 +25,10 @@ type: Boolean, default: true }, + showCopy: { + type: Boolean, + default: true + }, }, data() { diff --git a/frontend/src/business/components/api/test/components/environment/EnvironmentCommonConfig.vue b/frontend/src/business/components/api/test/components/environment/EnvironmentCommonConfig.vue index 2dd5c442c4..3803db4ea2 100644 --- a/frontend/src/business/components/api/test/components/environment/EnvironmentCommonConfig.vue +++ b/frontend/src/business/components/api/test/components/environment/EnvironmentCommonConfig.vue @@ -3,7 +3,7 @@ {{$t('api_test.environment.globalVariable')}} - + From cccb100ed045d4b77ad9fca143dace1673c82030 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 14:37:49 +0800 Subject: [PATCH 19/31] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20sql=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/scenario/request/SqlRequest.java | 5 +++++ .../api/test/components/request/ApiSqlRequestForm.vue | 8 +++++++- .../components/api/test/model/ScenarioModel.js | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/request/SqlRequest.java b/backend/src/main/java/io/metersphere/api/dto/scenario/request/SqlRequest.java index d48c75ac3a..b116326049 100644 --- a/backend/src/main/java/io/metersphere/api/dto/scenario/request/SqlRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/request/SqlRequest.java @@ -2,9 +2,12 @@ package io.metersphere.api.dto.scenario.request; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONType; +import io.metersphere.api.dto.scenario.KeyValue; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + @EqualsAndHashCode(callSuper = true) @Data @JSONType(typeName = RequestType.SQL) @@ -25,4 +28,6 @@ public class SqlRequest extends Request { private String resultVariable; @JSONField(ordinal = 14) private String variableNames; + @JSONField(ordinal = 15) + private List variables; } diff --git a/frontend/src/business/components/api/test/components/request/ApiSqlRequestForm.vue b/frontend/src/business/components/api/test/components/request/ApiSqlRequestForm.vue index 92212cfffa..7282fe9d53 100644 --- a/frontend/src/business/components/api/test/components/request/ApiSqlRequestForm.vue +++ b/frontend/src/business/components/api/test/components/request/ApiSqlRequestForm.vue @@ -41,6 +41,10 @@ {{$t('api_test.request.debug')}} + + +
@@ -74,10 +78,12 @@ import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService"; import MsJsr233Processor from "../processor/Jsr233Processor"; import MsCodeEdit from "../../../../common/components/MsCodeEdit"; + import MsApiScenarioVariables from "../ApiScenarioVariables"; export default { name: "MsApiSqlRequestForm", components: { + MsApiScenarioVariables, MsCodeEdit, MsJsr233Processor, MsDubboConsumerService, @@ -96,7 +102,7 @@ data() { return { - activeName: "sql", + activeName: "variables", databaseConfigsOptions: [], rules: { name: [ diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index a10d6e3c56..e98c96223f 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -477,6 +477,7 @@ export class SqlRequest extends Request { this.useEnvironment = options.useEnvironment; this.resultVariable = options.resultVariable; this.variableNames = options.variableNames; + this.variables = options.variables || []; this.debugReport = undefined; this.dataSource = options.dataSource; this.query = options.query; @@ -1127,6 +1128,7 @@ class JMXGenerator { } else if (request instanceof SqlRequest) { request.dataSource = scenario.databaseConfigMap.get(request.dataSource); sampler = new JDBCSampler(request.name || "", request); + this.addRequestVariables(sampler, request); } else if (request instanceof TCPRequest) { sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario)); } @@ -1188,6 +1190,14 @@ class JMXGenerator { } } + addRequestVariables(httpSamplerProxy, request) { + let name = request.name + " Variables"; + let variables = this.filterKV(request.variables); + if (variables && variables.length > 0) { + httpSamplerProxy.put(new Arguments(name, variables)); + } + } + addScenarioCookieManager(threadGroup, scenario) { if (scenario.enableCookieShare) { threadGroup.put(new CookieManager(scenario.name)); From 79d9926e53daac7252122c074ab022f2bcdcdbb1 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 15:06:54 +0800 Subject: [PATCH 20/31] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20sql=E8=87=AA=E5=AE=9A=E4=B9=89=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/business/components/api/test/model/ScenarioModel.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index e98c96223f..099dabbebb 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -477,14 +477,14 @@ export class SqlRequest extends Request { this.useEnvironment = options.useEnvironment; this.resultVariable = options.resultVariable; this.variableNames = options.variableNames; - this.variables = options.variables || []; + this.variables = []; this.debugReport = undefined; this.dataSource = options.dataSource; this.query = options.query; // this.queryType = options.queryType; this.queryTimeout = options.queryTimeout || 60000; - this.sets({args: KeyValue, attachmentArgs: KeyValue}, options); + this.sets({args: KeyValue, attachmentArgs: KeyValue, variables: KeyValue}, options); } isValid() { @@ -1117,7 +1117,6 @@ class JMXGenerator { if (request.enable) { if (!request.isValid()) return; let sampler; - if (request instanceof DubboRequest) { sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig)); } else if (request instanceof HttpRequest) { From b52359c0978ec6514dc03b419f871034ac956b05 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 14 Oct 2020 15:09:13 +0800 Subject: [PATCH 21/31] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20readme=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 85c7bd0b8a..8dbea3c9e7 100755 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ v1.1.0 是 v1.0.0 之后的功能版本。 - + @@ -92,9 +92,22 @@ v1.1.0 是 v1.0.0 之后的功能版本。 + + + + - + + + + + + + + + + @@ -110,8 +123,17 @@ v1.1.0 是 v1.0.0 之后的功能版本。 - - + + + + + + + + + + + @@ -123,19 +145,56 @@ v1.1.0 是 v1.0.0 之后的功能版本。 + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + @@ -150,6 +209,16 @@ v1.1.0 是 v1.0.0 之后的功能版本。 + + + + + + + + + + @@ -164,27 +233,37 @@ v1.1.0 是 v1.0.0 之后的功能版本。 - - + + - + + + + + + + + - + + + +
测试跟踪测试跟踪 项目管理 多项目支持,测试用例、测试计划与项目关联
快速导入用例到系统
测试用例评审基于已有用例发起评审
测试计划跟踪在线更新评审结果
支持多人在线添加评审评论
灵活的评审人分配形式
测试计划跟踪 基于已有用例发起测试计划
与平台中的接口测试、性能测试功能结合,自动更新关联用例的结果
接口测试测试脚本记录测试用例关联的缺陷
缺陷记录支持关联到 Jira/TAPD 平台
测试报告支持分享、导出
接口测试测试脚本 在线编辑接口测试内容
支持多接口的场景化测试
测试场景复用
测试场景支持引用已有环境信息
测试环境信息管理
通过浏览器插件快速录制测试脚本
测试报告测试执行后自动生成测试报告支持前后置 BeanShell/Python 脚本
上传并引用自定义 Jar 包
多协议支持,支持 HTTP、Dubbo、SQL、TCP 类型请求
支持等待时间、条件判断等逻辑控制功能
测试执行内置定时任务支持
通过 Jenkins 插件触发测试执行
多个接口测试一键合并执行
一键创建性能测试
测试报告测试执行后自动生成动态实时测试报告
测试报告导出
性能测试测试脚本通过邮件、IM 工具等通知执行结果
性能测试测试脚本 完全兼容 JMeter 脚本
通过浏览器插件快速录制测试脚本
多协议支持
测试执行内置定时任务支持
通过 Jenkins 插件触发测试执行
测试报告 测试执行后自动生成测试报告查看测试日志详情
系统管理租户管理系统管理用户租户管理 支持多级租户体系
支持多种租户角色
测试资源管理LDAP 认证对接
测试资源管理 性能测试资源池管理
消息通知配置IM 工具通知(如企业微信、钉钉)
邮件通知配置
集成与扩展集成与扩展 完善的 API 列表
支持对接 Jenkins 等持续集成工具
支持对接 Jira/TAPD 等缺陷管理工具
From 16506bee0aca4b8ba51bf2a0ef9ed92d0d8543c9 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 14 Oct 2020 16:39:39 +0800 Subject: [PATCH 22/31] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95?= =?UTF-8?q?):=20=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=80=A7=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/ApiDatabaseController.java | 25 +++++++++++++++++++ .../api/service/APIDatabaseService.java | 23 +++++++++++++++++ .../request/database/DatabaseConfig.vue | 5 ++++ .../request/database/DatabaseFrom.vue | 9 ++++++- 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/io/metersphere/api/controller/ApiDatabaseController.java create mode 100644 backend/src/main/java/io/metersphere/api/service/APIDatabaseService.java diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiDatabaseController.java b/backend/src/main/java/io/metersphere/api/controller/ApiDatabaseController.java new file mode 100644 index 0000000000..f3b316e703 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/controller/ApiDatabaseController.java @@ -0,0 +1,25 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.dto.scenario.DatabaseConfig; +import io.metersphere.api.service.APIDatabaseService; +import io.metersphere.commons.constants.RoleConstants; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@RestController +@RequestMapping(value = "/api/database") +@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR) +public class ApiDatabaseController { + + @Resource + APIDatabaseService apiDatabaseService; + + @PostMapping("/validate") + public void validate(@RequestBody DatabaseConfig databaseConfig) { + apiDatabaseService.validate(databaseConfig); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/service/APIDatabaseService.java b/backend/src/main/java/io/metersphere/api/service/APIDatabaseService.java new file mode 100644 index 0000000000..939c27ff99 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/service/APIDatabaseService.java @@ -0,0 +1,23 @@ +package io.metersphere.api.service; + +import io.metersphere.api.dto.scenario.DatabaseConfig; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.LogUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.sql.DriverManager; + +@Service +@Transactional(rollbackFor = Exception.class) +public class APIDatabaseService { + + public void validate(DatabaseConfig databaseConfig) { + try { + DriverManager.getConnection(databaseConfig.getDbUrl(), databaseConfig.getUsername(), databaseConfig.getPassword()); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } +} diff --git a/frontend/src/business/components/api/test/components/request/database/DatabaseConfig.vue b/frontend/src/business/components/api/test/components/request/database/DatabaseConfig.vue index 1dc77469b2..7542ed13df 100644 --- a/frontend/src/business/components/api/test/components/request/database/DatabaseConfig.vue +++ b/frontend/src/business/components/api/test/components/request/database/DatabaseConfig.vue @@ -27,6 +27,11 @@ currentConfig: new DatabaseConfig() } }, + watch: { + configs() { + this.currentConfig = new DatabaseConfig(); + } + }, methods: { saveConfig(config) { for (let item of this.configs) { diff --git a/frontend/src/business/components/api/test/components/request/database/DatabaseFrom.vue b/frontend/src/business/components/api/test/components/request/database/DatabaseFrom.vue index 89919ab28b..93c14e1258 100644 --- a/frontend/src/business/components/api/test/components/request/database/DatabaseFrom.vue +++ b/frontend/src/business/components/api/test/components/request/database/DatabaseFrom.vue @@ -1,5 +1,5 @@