From 701b9751f8deb0702249b09e87d6182315d981a2 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 23 Sep 2021 10:43:21 +0800 Subject: [PATCH 01/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89)?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E7=94=A8=E4=BE=8B=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=9D=83=E9=99=90=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=A8=E4=BE=8B=E5=88=97=E8=A1=A8=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E9=97=AE=E9=A2=98=20--bug=3D1006841=20--user=3D?= =?UTF-8?q?=E8=B5=B5=E5=8B=87=20=E3=80=90=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=91=E6=8E=A5=E5=8F=A3case=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=AD=9B=E9=80=89=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=B8=AD=20=E7=9A=84=E5=A4=B1=E8=B4=A5=20https://www.tapd.cn/5?= =?UTF-8?q?5049933/s/1050076?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ApiDefinitionExecResultService.java | 1 + .../definition/components/case/ApiCaseItem.vue | 4 +++- .../definition/components/case/ApiCaseList.vue | 1 + .../components/list/ApiCaseSimpleList.vue | 17 ----------------- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java index ba4b181169..2f237226a8 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java @@ -205,6 +205,7 @@ public class ApiDefinitionExecResultService { // 更新用例最后执行结果 caseWithBLOBs.setLastResultId(resourceId); caseWithBLOBs.setStatus(status); + caseWithBLOBs.setUpdateTime(System.currentTimeMillis()); apiTestCaseMapper.updateByPrimaryKey(caseWithBLOBs); return caseWithBLOBs.getName(); } diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue index 33406de5b8..9877262207 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue @@ -24,7 +24,7 @@ @@ -178,6 +178,7 @@ const esbDefinitionResponse = (requireComponent != null && requireComponent.keys import {API_METHOD_COLOUR} from "../../model/JsonData"; import MsChangeHistory from "../../../../history/ChangeHistory"; import {TYPE_TO_C} from "@/business/components/api/automation/scenario/Setting"; +import {hasPermission} from '@/common/js/utils'; export default { name: "ApiCaseItem", @@ -282,6 +283,7 @@ export default { } }, methods: { + hasPermission, openHis(row) { this.$refs.changeHistory.open(row.id); }, diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue index d71e1ed2bd..5c78456dad 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue @@ -258,6 +258,7 @@ export default { this.runResult = {testId: getUUID()}; this.$refs.apiCaseItem.runLoading = false; this.$success(this.$t('organization.integration.successful_operation')); + this.$store.state.currentApiCase = {refresh: true}; this.getApiTest(); }, errorRefresh() { diff --git a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue index 2fd7e7f8f1..1e7f12716f 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue @@ -585,23 +585,6 @@ export default { isNext = true; } }); - this.$nextTick(function () { - - handleRowDrop(this.tableData, (param) => { - param.groupId = this.condition.projectId; - editApiTestCaseOrder(param); - }); - - if (this.$refs.caseTable) { - this.$refs.caseTable.doLayout(); - this.$refs.caseTable.checkTableRowIsSelect(); - } - }); - if (isNext) { - setTimeout(() => { - this.initTable(); - }, 5000); - } }); } }, From 7e9c1e6d0111e4979efff8e72d04237f712386e6 Mon Sep 17 00:00:00 2001 From: metersphere-bot <78466014+metersphere-bot@users.noreply.github.com> Date: Thu, 23 Sep 2021 11:12:05 +0800 Subject: [PATCH 02/74] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E8=8F=9C=E5=8D=95=E4=BC=98=E5=8C=96=20(#6379)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: shiziyuan9527 --- .../settings/project/function/ScriptNavMenu.vue | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue index 1d8fcb6b70..1eddf640d6 100644 --- a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue +++ b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue @@ -16,7 +16,7 @@
-
+
{{child.title}}
@@ -197,10 +197,17 @@ export default { } .func-link { - color: #935aa1; margin-left: 18px; } +.func-div >>> .func-link { + color: #935aa1; +} + +.func-div >>> .func-link:hover { + color: #935aa1; +} + .link-type { font-weight: bold; font-size: 14px; From 7b5eba6057cba9028ef09e2ff3acd74bc118e4ab Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Thu, 23 Sep 2021 11:10:45 +0800 Subject: [PATCH 03/74] =?UTF-8?q?fix:=20=E6=B5=8B=E8=AF=95=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E5=9C=BA=E6=99=AF=E6=8A=A5=E5=91=8A=E6=9F=B1=E7=8A=B6?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E9=A2=98=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/ApiScenarioCharResult.vue | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/frontend/src/business/components/track/plan/view/comonents/report/detail/component/ApiScenarioCharResult.vue b/frontend/src/business/components/track/plan/view/comonents/report/detail/component/ApiScenarioCharResult.vue index 020d04870b..273aa07531 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/detail/component/ApiScenarioCharResult.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/detail/component/ApiScenarioCharResult.vue @@ -18,6 +18,22 @@ export default { return { visible: false, options: { + title: { + text: '场景用例数', + subtext: '55', + textAlign:'center', + y: 'center', + padding: 40, + itemGap: 5, + textStyle: { + lineHeight: 30, + fontSize: 16, + }, + subtextStyle: { + height: 30, + fontSize: 18, + } + }, tooltip: { trigger: 'axis', axisPointer: { @@ -38,20 +54,8 @@ export default { data: [], axisLabel: { formatter: function (value) { + return ''; }, - margin: 20, - rich: { - name: { - lineHeight: 30, - fontSize: 16, - align: 'center' - }, - count: { - height: 40, - fontSize: 20, - align: 'center', - }, - } } }, series: [ @@ -110,10 +114,8 @@ export default { this.options.series[0].data = this.data; this.options.series[0].label.formatter = formatterFuc; - let name = this.name; - this.options.yAxis.axisLabel.formatter = function (value) { - return '{name|' + name + '}\n' + '{count| ' + dataCount + '}'; - }; + this.options.title.text = this.name; + this.options.title.subtext = dataCount; this.options.legend.data = this.data.map(i => i.name); this.options.yAxis.data = this.data.map(i => i.name); From ef82a08aa0f910b31deecea91ed968e45840b702 Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Thu, 23 Sep 2021 11:03:04 +0800 Subject: [PATCH 04/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95)?= =?UTF-8?q?:=20#1006858=20=E3=80=90=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95-?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8=E5=8C=96=E3=80=91=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E8=84=9A=E6=9C=AC=E5=BF=85=E9=A1=BB=E5=89=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E5=90=8E=E7=BD=AE=E4=B8=80=E8=B5=B7=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=90=A6=E5=88=99=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【【接口测试-接口自动化】全局脚本必须前置和后置一起使用否则失效】https://www.tapd.cn/55049933/bugtrace/bugs/view?bug_id=1155049933001006858 --- .../api/dto/definition/request/sampler/MsHTTPSamplerProxy.java | 2 +- backend/src/main/java/io/metersphere/xpack | 2 +- frontend/src/business/components/xpack | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java index 773e0f0da8..a53fef875e 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -305,7 +305,7 @@ public class MsHTTPSamplerProxy extends MsTestElement { } preProcessor.toHashTree(httpSamplerTree, preProcessor.getHashTree(), config); } - if (postProcessor != null && StringUtils.isNotEmpty(preProcessor.getScript())) { + if (postProcessor != null && StringUtils.isNotEmpty(postProcessor.getScript())) { if (postProcessor.getEnvironmentId() == null) { if (this.getEnvironmentId() == null) { postProcessor.setEnvironmentId(useEnvironment); diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index 8f10fac361..ae9b3772ee 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit 8f10fac36134bfe4d9520051fdf1cfb9b44f67e4 +Subproject commit ae9b3772eebbfd3ce389c4d5c028eca8ef9687b8 diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index 183d61c236..17d46328fd 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit 183d61c23620452e3fe0ba56574adf5ce45207a7 +Subproject commit 17d46328fd6b0f723f91ea44e55da9a839e3da04 From 965596d35b09b3f3d30fa607bfeb04361d35df31 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Thu, 23 Sep 2021 11:36:39 +0800 Subject: [PATCH 05/74] =?UTF-8?q?refactor:=20=E5=AD=97=E4=BD=93=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/automation/scenario/EditApiScenario.vue | 4 ++-- .../settings/project/function/ScriptNavMenu.vue | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 0fb813de1f..e2480e86bf 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -132,10 +132,10 @@ :{{ getVariableSize() }} - 共享cookie + 共享cookie - {{ $t('commons.failure_continues') }} + {{ $t('commons.failure_continues') }} diff --git a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue index 1eddf640d6..d8c792ab24 100644 --- a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue +++ b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue @@ -1,7 +1,7 @@ From fbe43422fd118e84278838527f228a1d11130e97 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 26 Sep 2021 16:16:29 +0800 Subject: [PATCH 41/74] =?UTF-8?q?refactor:=20=E6=B6=88=E6=81=AF=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=A8=A1=E7=89=88=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mail/track/TestPlanDelete.html | 2 +- .../api/automation/schedule/ScheduleNotification.vue | 2 +- .../organization/components/ScheduleTaskNotification.vue | 2 +- .../components/api/ApiAutomationNotification.vue | 4 ++-- .../components/api/ApiDefinitionNotification.vue | 4 ++-- .../organization/components/api/ApiHomeNotification.vue | 4 ++-- .../components/api/ApiReportNotification.vue | 4 ++-- .../components/jenkins/JenkinsNotification.vue | 2 +- .../performance/PerformanceReportNotification.vue | 4 ++-- .../performance/PerformanceTestNotification.vue | 4 ++-- .../components/track/DefectTaskNotification.vue | 4 ++-- .../components/track/TestCaseNotification.vue | 4 ++-- .../components/track/TestPlanTaskNotification.vue | 8 +++----- .../components/track/TestReviewNotification.vue | 9 +++------ .../components/track/TrackHomeNotification.vue | 4 ++-- .../components/track/TrackReportNotification.vue | 4 ++-- 16 files changed, 30 insertions(+), 35 deletions(-) diff --git a/backend/src/main/resources/mail/track/TestPlanDelete.html b/backend/src/main/resources/mail/track/TestPlanDelete.html index 3529bd3122..dd3144763c 100644 --- a/backend/src/main/resources/mail/track/TestPlanDelete.html +++ b/backend/src/main/resources/mail/track/TestPlanDelete.html @@ -6,7 +6,7 @@
-

${creator}创建的:
+

${operator}创建的:
${name}
计划开始时间是:${start}
计划结束时间为:${end}
diff --git a/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue index 93d49fc6ac..5c4745508f 100644 --- a/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue +++ b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue @@ -183,7 +183,7 @@ export default { '\n' + '', robotTitle: - "测试【任务通知】:'${name} ${type}测试运行${status}\n" + + "测试'${name} ${type}测试运行${status}\n" + "执行人:${executor}" + "\n" + "负责人:${principal}" + "\n" + "测试环境为:${executionEnvironment}\n" + diff --git a/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue b/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue index 93ddb8ddd6..0c0ee9eed0 100644 --- a/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue +++ b/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue @@ -182,7 +182,7 @@ export default { '\n' + '', robotTitle: - "测试【任务通知】:'${name} ${type}测试运行${status}," + "\n" + + "测试'${name} ${type}测试运行${status}," + "\n" + "执行人:${executor}" + "\n" + "负责人:${principal}" + "\n" + "测试环境为:${executionEnvironment}" + "\n" + diff --git a/frontend/src/business/components/settings/organization/components/api/ApiAutomationNotification.vue b/frontend/src/business/components/settings/organization/components/api/ApiAutomationNotification.vue index 4a49656407..2bf68b00b9 100644 --- a/frontend/src/business/components/settings/organization/components/api/ApiAutomationNotification.vue +++ b/frontend/src/business/components/settings/organization/components/api/ApiAutomationNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "

\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}创建了接口自动化${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}创建了接口自动化${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/api/ApiDefinitionNotification.vue b/frontend/src/business/components/settings/organization/components/api/ApiDefinitionNotification.vue index 76ac94c389..d9cd38cc5e 100644 --- a/frontend/src/business/components/settings/organization/components/api/ApiDefinitionNotification.vue +++ b/frontend/src/business/components/settings/organization/components/api/ApiDefinitionNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}创建了接口定义${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}创建了接口定义${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/api/ApiHomeNotification.vue b/frontend/src/business/components/settings/organization/components/api/ApiHomeNotification.vue index da49d238ad..5366721461 100644 --- a/frontend/src/business/components/settings/organization/components/api/ApiHomeNotification.vue +++ b/frontend/src/business/components/settings/organization/components/api/ApiHomeNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}关闭了定时任务

\n" + + "

${operator}关闭了定时任务

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}发起了一个缺陷:${issuesName},请跟进", + robotTitle: "${operator}发起了一个缺陷:${issuesName},请跟进", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/api/ApiReportNotification.vue b/frontend/src/business/components/settings/organization/components/api/ApiReportNotification.vue index 5723464228..eab5664d21 100644 --- a/frontend/src/business/components/settings/organization/components/api/ApiReportNotification.vue +++ b/frontend/src/business/components/settings/organization/components/api/ApiReportNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}删除了测试报告${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}删除了测试报告${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue b/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue index fee156b78b..78b109f5b9 100644 --- a/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue +++ b/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue @@ -198,7 +198,7 @@ export default { '\n' + '', robotTitle: - "测试【任务通知】:'${executor}所执行的 ${name} ${type}测试运行${status}\n" + + "测试'${executor}所执行的 ${name} ${type}测试运行${status}\n" + "测试环境为:${executionEnvironment}\n" + "执行时间:${executionTime}\n" + "请点击下面链接进入测试报告页面\n" + diff --git a/frontend/src/business/components/settings/organization/components/performance/PerformanceReportNotification.vue b/frontend/src/business/components/settings/organization/components/performance/PerformanceReportNotification.vue index f3d2075a52..f63ca7d47b 100644 --- a/frontend/src/business/components/settings/organization/components/performance/PerformanceReportNotification.vue +++ b/frontend/src/business/components/settings/organization/components/performance/PerformanceReportNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}删除了测试报告${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}删除了测试报告${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/performance/PerformanceTestNotification.vue b/frontend/src/business/components/settings/organization/components/performance/PerformanceTestNotification.vue index 0a5cbdca7a..56da0b02f7 100644 --- a/frontend/src/business/components/settings/organization/components/performance/PerformanceTestNotification.vue +++ b/frontend/src/business/components/settings/organization/components/performance/PerformanceTestNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}创建了性能测试${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}创建了性能测试${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/track/DefectTaskNotification.vue b/frontend/src/business/components/settings/organization/components/track/DefectTaskNotification.vue index 2a06d16450..2785bb3e63 100644 --- a/frontend/src/business/components/settings/organization/components/track/DefectTaskNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/DefectTaskNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}发起了一个缺陷:${issuesName},请跟进

\n" + + "

${operator}发起了一个缺陷:${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}发起了一个缺陷:${issuesName},请跟进", + robotTitle: "${operator}发起了一个缺陷:${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/track/TestCaseNotification.vue b/frontend/src/business/components/settings/organization/components/track/TestCaseNotification.vue index ad2f30e41d..d9e7f35d79 100644 --- a/frontend/src/business/components/settings/organization/components/track/TestCaseNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/TestCaseNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}创建了测试用例

\n" + + "

${operator}创建了测试用例:${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}创建了测试用例", + robotTitle: "${operator}创建了测试用例:${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/track/TestPlanTaskNotification.vue b/frontend/src/business/components/settings/organization/components/track/TestPlanTaskNotification.vue index 0c749f8b88..9dc4002aba 100644 --- a/frontend/src/business/components/settings/organization/components/track/TestPlanTaskNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/TestPlanTaskNotification.vue @@ -175,10 +175,8 @@ export default { "\n" + "\n" + "
\n" + - "

${creator} 创建的:
\n" + - " ${testPlanName}
\n" + - " 计划开始时间是:${start}
\n" + - " 计划结束时间为:${end}
\n" + + "

${operator} 创建测试计划: ${name}
\n" + + "
\n" + " 请跟进!
\n" + " 点击下面链接进入测试计划页面

\n" + " ${url}/#/track/plan/all\n" + @@ -186,7 +184,7 @@ export default { "\n" + "", robotTitle: - " 【任务通知】:${creator} 创建的:${testPlanName}计划开始时间是:${start}计划结束时间是:${end}请跟进!/ ${status}!" + + "${operator} 创建的:${name} " + "点击下面链接进入测试计划页面${url}/#/track/plan/all", testCasePlanTask: [{ taskType: "testPlanTask", diff --git a/frontend/src/business/components/settings/organization/components/track/TestReviewNotification.vue b/frontend/src/business/components/settings/organization/components/track/TestReviewNotification.vue index da9427346a..2f71eaa953 100644 --- a/frontend/src/business/components/settings/organization/components/track/TestReviewNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/TestReviewNotification.vue @@ -175,18 +175,15 @@ export default { "\n" + "\n" + "
\n" + - "

${creator} 创建的:
\n" + - " ${reviewName}待开始
\n" + - " 计划开始时间是:${start}
\n" + - " 计划结束时间为:${end}
\n" + + "

${operator} 创建的:
\n" + + " ${name}待开始
\n" + " 请跟进!/${status}
\n" + " 点击下面链接进入评审页面进行审核

\n" + " ${url}/#/track/review/view/${id}\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator} 创建的:${reviewName}待开始,计划开始时间是:${start}," + - "计划结束时间是:${end}请跟进!/ ${status}!点击下面链接进入测试评审页面${url}/#/track/review/view/${id}", + robotTitle: "${operator} 创建的:${name} 请跟进!/ ${status}!点击下面链接进入测试评审页面${url}/#/track/review/view/${id}", reviewTask: [{ taskType: "reviewTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/track/TrackHomeNotification.vue b/frontend/src/business/components/settings/organization/components/track/TrackHomeNotification.vue index f55581c0e8..ca834601b5 100644 --- a/frontend/src/business/components/settings/organization/components/track/TrackHomeNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/TrackHomeNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${operator}关闭了定时任务

\n" + + "

${operator}关闭了定时任务: ${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${operator}发起了一个缺陷:${name},请跟进", + robotTitle: "${operator}关闭了定时任务: ${name}", defectTask: [{ taskType: "defectTask", event: "", diff --git a/frontend/src/business/components/settings/organization/components/track/TrackReportNotification.vue b/frontend/src/business/components/settings/organization/components/track/TrackReportNotification.vue index e16c38eff1..4987b9131a 100644 --- a/frontend/src/business/components/settings/organization/components/track/TrackReportNotification.vue +++ b/frontend/src/business/components/settings/organization/components/track/TrackReportNotification.vue @@ -176,11 +176,11 @@ export default { "\n" + "\n" + "
\n" + - "

${creator}关闭了定时任务

\n" + + "

${operator}删除了测试报告${name}

\n" + "
\n" + "\n" + "", - robotTitle: "【任务通知】:${creator}发起了一个缺陷:${issuesName},请跟进", + robotTitle: "${operator}删除了测试报告${name}", defectTask: [{ taskType: "defectTask", event: "", From 861cb5e80b3174d245e8a05712e640753fd96602 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Sun, 26 Sep 2021 15:51:37 +0800 Subject: [PATCH 42/74] =?UTF-8?q?fix:=20--bug=3D1006896=20--user=3D?= =?UTF-8?q?=E9=99=88=E5=BB=BA=E6=98=9F=20=E3=80=90github#6406=E3=80=91?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E7=AD=89=E7=BA=A7=E6=B2=A1=E6=9C=89=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E7=94=A8=E6=88=B7=E5=AE=9A=E4=B9=89=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=9A=84=E5=80=BC=20https://www.tapd.cn/55049933/s/1051937?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/track/case/components/TestCaseEdit.vue | 6 +++--- .../src/business/components/track/issue/IssueEditDetail.vue | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue index 90a5e20e18..4369008c61 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue @@ -383,7 +383,7 @@ } if (this.type === 'add') { //设置自定义熟悉默认值 - parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, buildTestCaseOldFields(this.form)); + parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules); this.form.name = this.testCaseTemplate.caseName; this.form.stepDescription = this.testCaseTemplate.stepDescription; this.form.expectedResult = this.testCaseTemplate.expectedResult; @@ -504,7 +504,7 @@ this.form.maintainer = user.id; this.form.tags = []; this.getSelectOptions(); - parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, buildTestCaseOldFields(this.form)); + parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules); this.reload(); } }, @@ -580,7 +580,7 @@ } this.form.module = testCase.nodeId; //设置自定义熟悉默认值 - parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, buildTestCaseOldFields(this.form)); + parseCustomField(this.form, this.testCaseTemplate, this.customFieldForm, this.customFieldRules, testCase ? buildTestCaseOldFields(this.form) : null); this.setDefaultValue(); // 重新渲染,显示自定义字段的必填校验 this.reloadForm(); diff --git a/frontend/src/business/components/track/issue/IssueEditDetail.vue b/frontend/src/business/components/track/issue/IssueEditDetail.vue index be4796b50d..09321a4f72 100644 --- a/frontend/src/business/components/track/issue/IssueEditDetail.vue +++ b/frontend/src/business/components/track/issue/IssueEditDetail.vue @@ -72,7 +72,7 @@ import CustomFieldFormList from "@/business/components/settings/workspace/templa import CustomFieldRelateList from "@/business/components/settings/workspace/template/CustomFieldRelateList"; import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem"; import {SYSTEM_FIELD_NAME_MAP} from "@/common/js/table-constants"; -import {buildCustomFields, getTemplate, parseCustomField} from "@/common/js/custom_field"; +import {buildCustomFields, parseCustomField} from "@/common/js/custom_field"; import CustomFiledComponent from "@/business/components/settings/workspace/template/CustomFiledComponent"; import TestCaseIssueList from "@/business/components/track/issue/TestCaseIssueList"; import IssueEditDetail from "@/business/components/track/issue/IssueEditDetail"; @@ -201,7 +201,7 @@ export default { this.form.creator = getCurrentUserId(); } } - parseCustomField(this.form, this.issueTemplate, this.customFieldForm, this.customFieldRules, null); + parseCustomField(this.form, this.issueTemplate, this.customFieldForm, this.customFieldRules); this.$nextTick(() => { if (this.$refs.testCaseIssueList) { this.$refs.testCaseIssueList.initTableData(); From bd3fecb72ed29e489bda068806f9fd13d746a196 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Sun, 26 Sep 2021 17:03:10 +0800 Subject: [PATCH 43/74] =?UTF-8?q?refactor:=20--bug=3D1006704=20--user=3D?= =?UTF-8?q?=E9=99=88=E5=BB=BA=E6=98=9F=20=E3=80=90=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=B7=9F=E8=B8=AA=E3=80=91=E6=89=A7=E8=A1=8C=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=AE=A1=E5=88=92=EF=BC=8C=E7=82=B9=E5=87=BB=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=B6=E6=98=BE=E7=A4=BA=E9=94=99=E4=B9=B1=20https://www.tap?= =?UTF-8?q?d.cn/55049933/s/1051967?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/track/case/components/FormRichTextItem.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/business/components/track/case/components/FormRichTextItem.vue b/frontend/src/business/components/track/case/components/FormRichTextItem.vue index 9eaa89747a..e31baaccca 100644 --- a/frontend/src/business/components/track/case/components/FormRichTextItem.vue +++ b/frontend/src/business/components/track/case/components/FormRichTextItem.vue @@ -80,7 +80,12 @@ export default { let el = document.getElementById(this.id); if (el) { el.addEventListener('click', () => { - this.defaultOpen = null; + let imagePreview = el.getElementsByClassName('v-note-img-wrapper'); + if (imagePreview.length > 0) { // 图片预览的时候不切换到编辑模式 + this.defaultOpen = 'preview'; + } else { + this.defaultOpen = null; + } }); let input = el.getElementsByClassName('auto-textarea-input'); input[0].addEventListener('blur', () => { From 6504298c637be091f47ff387ca54354805a773dc Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Sun, 26 Sep 2021 14:31:01 +0800 Subject: [PATCH 44/74] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B)?= =?UTF-8?q?:=20=E5=85=B3=E8=81=94=E8=AE=A1=E5=88=92=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9F=A5=E8=AF=A2=E9=97=AE=E9=A2=98#1006717?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1006717 --user=lyh 【测试跟踪】测试用例关联接口的高级搜索,根据创建人搜索没生效 https://www.tapd.cn/55049933/s/1051748 --- .../main/java/io/metersphere/base/mapper/TestCaseTestMapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/base/mapper/TestCaseTestMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/TestCaseTestMapper.xml index 58c095e1b4..ee7ee42e10 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/TestCaseTestMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/TestCaseTestMapper.xml @@ -144,7 +144,7 @@ from api_test_case atc left join test_case_test tct on atc.id = tct.test_id inner join api_definition ad on ad.id = atc.api_definition_id - where tct.test_id is NULL and ad.status != 'Trash' + where tct.test_id is NULL and atc.status != 'Trash' and ad.protocol = #{request.protocol} From be6390d3bf8f6d350b5a7ea663c118bf0a20a0d1 Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Sun, 26 Sep 2021 16:31:06 +0800 Subject: [PATCH 45/74] =?UTF-8?q?feat(=20=E6=8A=A5=E8=A1=A8=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1):=20=E6=8A=A5=E8=A1=A8=E7=BB=9F=E8=AE=A1=E4=BB=8Expac?= =?UTF-8?q?k=E6=8C=AA=E5=88=B0=E5=BC=80=E6=BA=90=E7=89=88=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=8A=A0=E4=BF=9D=E5=AD=98=E3=80=81=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E6=9D=83=E9=99=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 报表统计从xpack挪到开源版,并增加保存、导出的权限。 --- .../mapper/ext/ExtTestAnalysisMapper.java | 14 + .../base/mapper/ext/ExtTestAnalysisMapper.xml | 100 +++ .../mapper/ext/ExtTestCaseCountMapper.java | 65 ++ .../mapper/ext/ExtTestCaseCountMapper.xml | 219 +++++ .../controller/HistoryReportController.java | 56 ++ .../controller/TestAnalysisController.java | 24 + .../controller/TestCaseCountController.java | 34 + .../reportstatistics/dto/PieChartDTO.java | 27 + .../dto/ReportStatisticsSaveRequest.java | 14 + .../dto/ReportStatisticsType.java | 5 + .../dto/TestAnalysisChartDTO.java | 29 + .../dto/TestAnalysisChartRequest.java | 21 + .../dto/TestAnalysisChartResult.java | 12 + .../dto/TestAnalysisResult.java | 22 + .../dto/TestAnalysisTableDTO.java | 29 + .../dto/TestCaseCountChartResult.java | 15 + .../dto/TestCaseCountRequest.java | 79 ++ .../dto/TestCaseCountResponse.java | 24 + .../dto/TestCaseCountSummary.java | 22 + .../dto/TestCaseCountTableDTO.java | 34 + .../reportstatistics/dto/charts/Legend.java | 20 + .../reportstatistics/dto/charts/PieData.java | 20 + .../reportstatistics/dto/charts/Series.java | 20 + .../reportstatistics/dto/charts/Title.java | 14 + .../reportstatistics/dto/charts/XAxis.java | 17 + .../reportstatistics/dto/charts/YAxis.java | 14 + .../service/ReportStatisticsService.java | 77 ++ .../service/TestAnalysisService.java | 171 ++++ .../service/TestCaseCountService.java | 775 ++++++++++++++++++ backend/src/main/java/io/metersphere/xpack | 2 +- backend/src/main/resources/permission.json | 12 + .../components/common/router/router.js | 10 +- .../common/select-tree/SelectTree.vue | 26 +- .../reportstatistics/ReportAnalysis.vue | 101 +++ .../reportstatistics/ReportCard.vue | 138 ++++ .../base/HistoryReportData.vue | 86 ++ .../reportstatistics/base/ReportHeader.vue | 115 +++ .../base/compose/HistoryReportDataCard.vue | 86 ++ .../components/reportstatistics/router.js | 16 + .../testCaseCount/TestCaseCountContainer.vue | 217 +++++ .../chart/TestCaseCountChart.vue | 284 +++++++ .../filter/TestCaseCountFilter.vue | 401 +++++++++ .../table/TestCaseCountTable.vue | 113 +++ .../track/TestAnalysisContainer.vue | 168 ++++ .../track/chart/TestAnalysisChart.vue | 143 ++++ .../track/filter/TestAnalysisFilter.vue | 225 +++++ .../track/table/TestAnalysisTable.vue | 77 ++ 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 + 51 files changed, 4189 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.xml create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.xml create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/controller/HistoryReportController.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/controller/TestAnalysisController.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/controller/TestCaseCountController.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/PieChartDTO.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsSaveRequest.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsType.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartDTO.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartRequest.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartResult.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisResult.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisTableDTO.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountChartResult.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountRequest.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountResponse.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountSummary.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountTableDTO.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Legend.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/PieData.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Series.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Title.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/XAxis.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/dto/charts/YAxis.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/service/ReportStatisticsService.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/service/TestAnalysisService.java create mode 100644 backend/src/main/java/io/metersphere/reportstatistics/service/TestCaseCountService.java create mode 100644 frontend/src/business/components/reportstatistics/ReportAnalysis.vue create mode 100644 frontend/src/business/components/reportstatistics/ReportCard.vue create mode 100644 frontend/src/business/components/reportstatistics/base/HistoryReportData.vue create mode 100644 frontend/src/business/components/reportstatistics/base/ReportHeader.vue create mode 100644 frontend/src/business/components/reportstatistics/base/compose/HistoryReportDataCard.vue create mode 100644 frontend/src/business/components/reportstatistics/router.js create mode 100644 frontend/src/business/components/reportstatistics/testCaseCount/TestCaseCountContainer.vue create mode 100644 frontend/src/business/components/reportstatistics/testCaseCount/chart/TestCaseCountChart.vue create mode 100644 frontend/src/business/components/reportstatistics/testCaseCount/filter/TestCaseCountFilter.vue create mode 100644 frontend/src/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable.vue create mode 100644 frontend/src/business/components/reportstatistics/track/TestAnalysisContainer.vue create mode 100644 frontend/src/business/components/reportstatistics/track/chart/TestAnalysisChart.vue create mode 100644 frontend/src/business/components/reportstatistics/track/filter/TestAnalysisFilter.vue create mode 100644 frontend/src/business/components/reportstatistics/track/table/TestAnalysisTable.vue diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.java new file mode 100644 index 0000000000..87e088bccd --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.java @@ -0,0 +1,14 @@ +package io.metersphere.base.mapper.ext; + + +import io.metersphere.reportstatistics.dto.TestAnalysisChartRequest; +import io.metersphere.reportstatistics.dto.TestAnalysisChartResult; + +import java.util.List; + +public interface ExtTestAnalysisMapper { + + List getCraeteCaseReport(TestAnalysisChartRequest request); + + List getUpdateCaseReport(TestAnalysisChartRequest request); +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.xml new file mode 100644 index 0000000000..c4c9e36489 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestAnalysisMapper.xml @@ -0,0 +1,100 @@ + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.java new file mode 100644 index 0000000000..060f2735ee --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.java @@ -0,0 +1,65 @@ +package io.metersphere.base.mapper.ext; + +import io.metersphere.reportstatistics.dto.TestCaseCountChartResult; +import io.metersphere.reportstatistics.dto.TestCaseCountRequest; + +import java.util.List; + +public interface ExtTestCaseCountMapper { + + /** + * 创建人 维护人 用例类型 用例状态 用例等级 + * + * create_user + * maintainer + * '功能用例' + * status + * priority + * + * @ request + * @return + */ + List getFunctionCaseCount(TestCaseCountRequest request); + + /** + * 创建人 维护人 用例类型 用例状态 用例等级 + * + * create_user_id + * ----不知道 + * '接口用例' + * status + * ----不知道 + * + * @param request + * @return + */ + List getApiCaseCount(TestCaseCountRequest request); + + /** + * 创建人 维护人 用例类型 用例状态 用例等级 + * + * create_user + * principal + * '场景用例' + * status + * level + * + * @param request + * @return + */ + List getScenarioCaseCount(TestCaseCountRequest request); + + /** + * 创建人 维护人 用例类型 用例状态 用例等级 + * + * create_user + * follow_people + * '性能用例' + * status + * 不知道 + * + * @param request + * @return + */ + List getLoadCaseCount(TestCaseCountRequest request); +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.xml new file mode 100644 index 0000000000..33ea3c3bf3 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseCountMapper.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/reportstatistics/controller/HistoryReportController.java b/backend/src/main/java/io/metersphere/reportstatistics/controller/HistoryReportController.java new file mode 100644 index 0000000000..ce62705bdb --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/controller/HistoryReportController.java @@ -0,0 +1,56 @@ +package io.metersphere.reportstatistics.controller; + +import com.alibaba.fastjson.JSONArray; +import io.metersphere.base.domain.ReportStatistics; +import io.metersphere.base.domain.ReportStatisticsWithBLOBs; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.reportstatistics.dto.ReportStatisticsSaveRequest; +import io.metersphere.reportstatistics.service.ReportStatisticsService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author song.tianyang + * @Date 2021/9/14 2:58 下午 + */ +@RestController +@RequestMapping(value = "/history/report") +public class HistoryReportController { + + @Resource + private ReportStatisticsService reportStatisticsService; + + @PostMapping("/selectByParams") + public List selectByParams(@RequestBody ReportStatisticsSaveRequest request) { + List returnList = reportStatisticsService.selectByProjectIdAndReportType(request.getProjectId(),request.getReportType()); + LogUtil.info("报表查询结果:"+JSONArray.toJSONString(returnList)); + return returnList; + } + + @PostMapping("/saveReport") + public ReportStatisticsWithBLOBs saveReport(@RequestBody ReportStatisticsSaveRequest request){ + ReportStatisticsWithBLOBs returnData = reportStatisticsService.saveByRequest(request); + return returnData; + } + + @PostMapping("/updateReport") + public ReportStatisticsWithBLOBs updateReport(@RequestBody ReportStatisticsSaveRequest request){ + ReportStatisticsWithBLOBs returnData = reportStatisticsService.updateByRequest(request); + return returnData; + } + + @PostMapping("/deleteByParam") + public int deleteById(@RequestBody ReportStatisticsSaveRequest request) { + return reportStatisticsService.deleteById(request.getId()); + } + + @PostMapping("/selectById") + public ReportStatisticsWithBLOBs selectById(@RequestBody ReportStatisticsSaveRequest request) { + return reportStatisticsService.selectById(request.getId()); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/controller/TestAnalysisController.java b/backend/src/main/java/io/metersphere/reportstatistics/controller/TestAnalysisController.java new file mode 100644 index 0000000000..f7746b183b --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/controller/TestAnalysisController.java @@ -0,0 +1,24 @@ +package io.metersphere.reportstatistics.controller; + +import io.metersphere.reportstatistics.dto.TestAnalysisChartRequest; +import io.metersphere.reportstatistics.dto.TestAnalysisResult; +import io.metersphere.reportstatistics.service.TestAnalysisService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@RestController +@RequestMapping(value = "/report/test/analysis") +public class TestAnalysisController { + + @Resource + TestAnalysisService testAnalysisService; + + @PostMapping("/getReport") + public TestAnalysisResult getReport(@RequestBody TestAnalysisChartRequest request) { + return testAnalysisService.getReport(request); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/controller/TestCaseCountController.java b/backend/src/main/java/io/metersphere/reportstatistics/controller/TestCaseCountController.java new file mode 100644 index 0000000000..d42d0d6b15 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/controller/TestCaseCountController.java @@ -0,0 +1,34 @@ +package io.metersphere.reportstatistics.controller; + +import io.metersphere.reportstatistics.dto.TestCaseCountRequest; +import io.metersphere.reportstatistics.dto.TestCaseCountResponse; +import io.metersphere.reportstatistics.service.TestCaseCountService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping(value = "/report/test/case/count") +public class TestCaseCountController { + + @Resource + TestCaseCountService testCaseCountService; + + @PostMapping("/initDatas") + public Map>> initDatas(@RequestBody TestCaseCountRequest request) { + Map>> returnMap = testCaseCountService.getSelectFilterDatas(request.getProjectId()); + + return returnMap; + } + + @PostMapping("/getReport") + public TestCaseCountResponse getReport(@RequestBody TestCaseCountRequest request) { + TestCaseCountResponse response = testCaseCountService.getReport(request); + return response; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/PieChartDTO.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/PieChartDTO.java new file mode 100644 index 0000000000..c5ed790f2c --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/PieChartDTO.java @@ -0,0 +1,27 @@ +package io.metersphere.reportstatistics.dto; + +import com.alibaba.fastjson.JSONObject; +import io.metersphere.reportstatistics.dto.charts.Series; +import io.metersphere.reportstatistics.dto.charts.Title; +import io.metersphere.reportstatistics.dto.charts.XAxis; +import io.metersphere.reportstatistics.dto.charts.YAxis; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PieChartDTO { + private JSONObject dataset; + private JSONObject tooltip; + private XAxis xAxis; + private YAxis yAxis; + private List series; + private List title; + private int width; + + public PieChartDTO() { + tooltip = new JSONObject(); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsSaveRequest.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsSaveRequest.java new file mode 100644 index 0000000000..a206aa2fdb --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsSaveRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.reportstatistics.dto; + +import io.metersphere.base.domain.ReportStatisticsWithBLOBs; +import lombok.Getter; +import lombok.Setter; + +/** + * @author song.tianyang + * @Date 2021/9/14 4:51 下午 + */ +@Getter +@Setter +public class ReportStatisticsSaveRequest extends ReportStatisticsWithBLOBs { +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsType.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsType.java new file mode 100644 index 0000000000..34dfbd8c9e --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/ReportStatisticsType.java @@ -0,0 +1,5 @@ +package io.metersphere.reportstatistics.dto; + +public enum ReportStatisticsType { + TEST_CASE_COUNT,TEST_CASE_ANALYSIS +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartDTO.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartDTO.java new file mode 100644 index 0000000000..8e10195daa --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartDTO.java @@ -0,0 +1,29 @@ +package io.metersphere.reportstatistics.dto; + +import io.metersphere.reportstatistics.dto.charts.Legend; +import io.metersphere.reportstatistics.dto.charts.Series; +import io.metersphere.reportstatistics.dto.charts.XAxis; +import io.metersphere.reportstatistics.dto.charts.YAxis; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class TestAnalysisChartDTO { + private Legend legend; + private XAxis xAxis; + private YAxis yAxis; + private List<Series> series; + + public TestAnalysisChartDTO() { + } + + public TestAnalysisChartDTO(Legend legend, XAxis xAxis, YAxis yAxis, List<Series> series) { + this.legend = legend; + this.xAxis = xAxis; + this.yAxis = yAxis; + this.series = series; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartRequest.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartRequest.java new file mode 100644 index 0000000000..3e3c84b9ce --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartRequest.java @@ -0,0 +1,21 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class TestAnalysisChartRequest { + private boolean createCase; + private boolean updateCase; + private String order; + private List<Long> times; + private String startTime; + private String endTime; + private List<String> prioritys; + private List<String> projects; + private List<String> modules; + private List<String> users; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartResult.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartResult.java new file mode 100644 index 0000000000..34bd70c583 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisChartResult.java @@ -0,0 +1,12 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TestAnalysisChartResult { + private String dateStr; + private String countNum; + +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisResult.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisResult.java new file mode 100644 index 0000000000..e6e682ad79 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisResult.java @@ -0,0 +1,22 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class TestAnalysisResult { + private TestAnalysisChartDTO chartDTO; + private List<TestAnalysisTableDTO> tableDTOs; + + public TestAnalysisResult() { + + } + + public TestAnalysisResult(TestAnalysisChartDTO chartDTO, List<TestAnalysisTableDTO> tableDTOs) { + this.chartDTO = chartDTO; + this.tableDTOs = tableDTOs; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisTableDTO.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisTableDTO.java new file mode 100644 index 0000000000..7f7a62d273 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestAnalysisTableDTO.java @@ -0,0 +1,29 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +public class TestAnalysisTableDTO { + private String id; + private String name; + private String createCount; + private String updateCount; + private List<TestAnalysisTableDTO> children; + + public TestAnalysisTableDTO() { + + } + + public TestAnalysisTableDTO(String name, String createCount, String updateCount, List<TestAnalysisTableDTO> children) { + this.id = UUID.randomUUID().toString(); + this.name = name; + this.createCount = createCount; + this.updateCount = updateCount; + this.children = children; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountChartResult.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountChartResult.java new file mode 100644 index 0000000000..4c023d1428 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountChartResult.java @@ -0,0 +1,15 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TestCaseCountChartResult { + private String groupName; + private long countNum; + + public String getCountNumStr(){ + return String.valueOf(countNum); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountRequest.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountRequest.java new file mode 100644 index 0000000000..227e2c0c1d --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountRequest.java @@ -0,0 +1,79 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Getter +@Setter +public class TestCaseCountRequest { + //x轴字段 + private String xaxis; + //y轴字段 + private List<String> yaxis; + + //搜索条件 + private String projectId; + private String timeType; + private TimeFilter timeFilter; + private List<Long> times; + private String order; + + //起始时间 + private long startTime = 0; + //结束时间 + private long endTime = 0; + + //其余条件 + private String filterType; + private List<Map<String,Object>> filters; + + /** + * 功能用例、接口用例、场景用例、性能用例的分组字段 + */ + private String testCaseGroupColumn; + private String apiCaseGroupColumn; + private String scenarioCaseGroupColumn; + private String loadCaseGroupColumn; + + /** + * filter整理后的查询数据 + * @return + */ + private Map<String,List<String>> filterSearchList; + private Map<String,List<String>> apiFilterSearchList; + private Map<String,List<String>> loadFilterSearchList; + + public int getTimeRange(){ + if(timeFilter != null){ + return timeFilter.getTimeRange(); + }else { + return 0; + } + } + + public String getTimeRangeUnit(){ + if(timeFilter != null){ + return timeFilter.getTimeRangeUnit(); + }else { + return null; + } + } + + public void setFilterSearchList(String key,List<String> values){ + if(this.filterSearchList == null){ + this.filterSearchList = new HashMap<>(); + } + filterSearchList.put(key,values); + } +} + +@Getter +@Setter +class TimeFilter{ + private int timeRange; + private String timeRangeUnit; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountResponse.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountResponse.java new file mode 100644 index 0000000000..70bfeaccab --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountResponse.java @@ -0,0 +1,24 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class TestCaseCountResponse { + private TestAnalysisChartDTO barChartDTO; + private PieChartDTO pieChartDTO; + private List<TestCaseCountTableDTO> tableDTOs; + + public TestCaseCountResponse() { + + } + + public TestCaseCountResponse(TestAnalysisChartDTO chartDTO, PieChartDTO pieChartDTO, List<TestCaseCountTableDTO> tableDTOs) { + this.pieChartDTO = pieChartDTO; + this.barChartDTO = chartDTO; + this.tableDTOs = tableDTOs; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountSummary.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountSummary.java new file mode 100644 index 0000000000..29f6042e28 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountSummary.java @@ -0,0 +1,22 @@ +package io.metersphere.reportstatistics.dto; + +/** + * @author song.tianyang + * @Date 2021/9/8 5:36 下午 + */ +public class TestCaseCountSummary { + public String groupName; + + public long testCaseCount = 0; + public long apiCaseCount = 0; + public long scenarioCaseCount = 0; + public long loadCaseCount = 0; + + public TestCaseCountSummary(String groupName) { + this.groupName = groupName; + } + + public long getAllCount() { + return this.testCaseCount + this.apiCaseCount + this.scenarioCaseCount + this.loadCaseCount; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountTableDTO.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountTableDTO.java new file mode 100644 index 0000000000..2ce5c86a2b --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/TestCaseCountTableDTO.java @@ -0,0 +1,34 @@ +package io.metersphere.reportstatistics.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +public class TestCaseCountTableDTO { + private String id; + private String name; + private String allCount; + private String testCaseCount; + private String apiCaseCount; + private String scenarioCaseCount; + private String loadCaseCount; + + private List<TestCaseCountTableDTO> children; + + public TestCaseCountTableDTO(String name, long testCaseCount, long apiCaseCount, long scenarioCaseCount, long loadCaseCount) { + this.id = UUID.randomUUID().toString(); + this.name = name; + this.testCaseCount = String.valueOf(testCaseCount); + this.apiCaseCount = String.valueOf(apiCaseCount); + this.scenarioCaseCount = String.valueOf(scenarioCaseCount); + this.loadCaseCount = String.valueOf(loadCaseCount); + this.allCount = String.valueOf(testCaseCount+apiCaseCount+scenarioCaseCount+loadCaseCount); + + children = new ArrayList<>(); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Legend.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Legend.java new file mode 100644 index 0000000000..5ac9f6545e --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Legend.java @@ -0,0 +1,20 @@ +package io.metersphere.reportstatistics.dto.charts; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Getter +@Setter +public class Legend { + private final String x = "center"; + private final String y = "bottom"; + private final String type = "scroll"; + private final List<Integer> padding = Arrays.asList(0, 40, 0, 0); + private Map<String, Boolean> selected; + private List<String> data; + +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/PieData.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/PieData.java new file mode 100644 index 0000000000..3222b624e5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/PieData.java @@ -0,0 +1,20 @@ +package io.metersphere.reportstatistics.dto.charts; + +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PieData { + private String name; + private long value; + private JSONObject itemStyle; + + public void setColor(String color){ + if(itemStyle == null){ + itemStyle = new JSONObject(); + } + itemStyle.put("color",color); + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Series.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Series.java new file mode 100644 index 0000000000..823e066a05 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Series.java @@ -0,0 +1,20 @@ +package io.metersphere.reportstatistics.dto.charts; + +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class Series { + private String name; + private List<Object> data; + private String color = "#783887"; + private String type = "line"; + private String radius = "50"; + private String stack; + private JSONObject encode; + private List<String> center; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Title.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Title.java new file mode 100644 index 0000000000..f79d210710 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/Title.java @@ -0,0 +1,14 @@ +package io.metersphere.reportstatistics.dto.charts; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Title { + private String name; + private String subtext; + private String left; + private String top = "75%"; + private String textAlign = "center"; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/XAxis.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/XAxis.java new file mode 100644 index 0000000000..543a3488ac --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/XAxis.java @@ -0,0 +1,17 @@ +package io.metersphere.reportstatistics.dto.charts; + +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Getter +@Setter +public class XAxis { + private final String type = "category"; + private List<String> data; + private String name; + private Map<String,Integer> axisLabel = new HashMap<String,Integer>(){ {this.put("interval",0);this.put("rotate",30);}}; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/YAxis.java b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/YAxis.java new file mode 100644 index 0000000000..30b70d9b31 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/dto/charts/YAxis.java @@ -0,0 +1,14 @@ +package io.metersphere.reportstatistics.dto.charts; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class YAxis { + private String type; + private List<String> data; + private String name; +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/service/ReportStatisticsService.java b/backend/src/main/java/io/metersphere/reportstatistics/service/ReportStatisticsService.java new file mode 100644 index 0000000000..f2e1e71c38 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/service/ReportStatisticsService.java @@ -0,0 +1,77 @@ +package io.metersphere.reportstatistics.service; + +import io.metersphere.base.domain.ReportStatistics; +import io.metersphere.base.domain.ReportStatisticsExample; +import io.metersphere.base.domain.ReportStatisticsWithBLOBs; +import io.metersphere.base.mapper.ReportStatisticsMapper; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.reportstatistics.dto.ReportStatisticsSaveRequest; +import io.metersphere.reportstatistics.dto.ReportStatisticsType; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.UUID; + +/** + * @author song.tianyang + * @Date 2021/9/14 4:50 下午 + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class ReportStatisticsService { + @Resource + private ReportStatisticsMapper reportStatisticsMapper; + + public ReportStatisticsWithBLOBs saveByRequest(ReportStatisticsSaveRequest request) { + ReportStatisticsWithBLOBs model = new ReportStatisticsWithBLOBs(); + model.setId(UUID.randomUUID().toString()); + String name = "用例分析报表"; + if(StringUtils.equalsIgnoreCase(ReportStatisticsType.TEST_CASE_COUNT.name(),request.getReportType())){ + name = "用例统计报表"; + model.setReportType(ReportStatisticsType.TEST_CASE_COUNT.name()); + }else { + model.setReportType(ReportStatisticsType.TEST_CASE_ANALYSIS.name()); + } + model.setName(name); + model.setDataOption(request.getDataOption()); + model.setSelectOption(request.getSelectOption()); + model.setCreateTime(System.currentTimeMillis()); + model.setUpdateTime(System.currentTimeMillis()); + model.setProjectId(request.getProjectId()); + String userId = SessionUtils.getUserId(); + model.setCreateUser(userId); + model.setUpdateUser(userId); + + reportStatisticsMapper.insert(model); + + return model; + } + + public int deleteById(String id) { + return reportStatisticsMapper.deleteByPrimaryKey(id); + } + + public List<ReportStatistics> selectByProjectIdAndReportType(String projectId, String reportType) { + ReportStatisticsExample example = new ReportStatisticsExample(); + example.createCriteria().andProjectIdEqualTo(projectId).andReportTypeEqualTo(reportType); + example.setOrderByClause("create_time DESC"); + return reportStatisticsMapper.selectByExample(example); + } + + public ReportStatisticsWithBLOBs selectById(String id) { + return reportStatisticsMapper.selectByPrimaryKey(id); + } + + public ReportStatisticsWithBLOBs updateByRequest(ReportStatisticsSaveRequest request) { + ReportStatisticsWithBLOBs updateModel = new ReportStatisticsWithBLOBs(); + updateModel.setId(request.getId()); + updateModel.setName(request.getName()); + updateModel.setUpdateTime(request.getUpdateTime()); + updateModel.setUpdateUser(SessionUtils.getUserId()); + reportStatisticsMapper.updateByPrimaryKeySelective(updateModel); + return updateModel; + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/service/TestAnalysisService.java b/backend/src/main/java/io/metersphere/reportstatistics/service/TestAnalysisService.java new file mode 100644 index 0000000000..aebd939897 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/service/TestAnalysisService.java @@ -0,0 +1,171 @@ +package io.metersphere.reportstatistics.service; + +import io.metersphere.base.mapper.ext.ExtTestAnalysisMapper; +import io.metersphere.commons.utils.DateUtils; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.controller.request.ProjectRequest; +import io.metersphere.dto.ProjectDTO; +import io.metersphere.reportstatistics.dto.*; +import io.metersphere.reportstatistics.dto.charts.Legend; +import io.metersphere.reportstatistics.dto.charts.Series; +import io.metersphere.reportstatistics.dto.charts.XAxis; +import io.metersphere.reportstatistics.dto.charts.YAxis; +import io.metersphere.service.ProjectService; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestAnalysisService { + @Resource + private ExtTestAnalysisMapper extTestAnalysisMapper; + @Resource + private ProjectService projectService; + + private final String ADD = "新增用例"; + private final String UPDATE = "修改用例"; + + public TestAnalysisResult getReport(TestAnalysisChartRequest request) { + if (CollectionUtils.isEmpty(request.getTimes())) { + // 最近七天 + request.setTimes(Arrays.asList(System.currentTimeMillis() - 7 * 24 * 3600 * 1000L, System.currentTimeMillis())); + } + request.setStartTime(DateUtils.getDataStr(request.getTimes().get(0))); + request.setEndTime(DateUtils.getDataStr(request.getTimes().get(1))); + if (CollectionUtils.isEmpty(request.getProjects())) { + // 获取当前组织空间下所有项目 + String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); + ProjectRequest projectRequest = new ProjectRequest(); + projectRequest.setWorkspaceId(currentWorkspaceId); + List<ProjectDTO> projectDTOS = projectService.getProjectList(projectRequest); + if (CollectionUtils.isNotEmpty(projectDTOS)) { + request.setProjects(projectDTOS.stream().map(ProjectDTO::getId).collect(Collectors.toList())); + } else { + request.setProjects(new LinkedList<String>(){{this.add(UUID.randomUUID().toString());}}); + } + } + TestAnalysisChartDTO dto = new TestAnalysisChartDTO(); + List<TestAnalysisTableDTO> dtos = new LinkedList<>(); + + List<Series> seriesList = new LinkedList<>(); + XAxis xAxis = new XAxis(); + + if (CollectionUtils.isEmpty(request.getUsers())) { + // 组织charts格式数据 + Legend legend = new Legend(); + formatLegend(legend, null, request); + dto.setLegend(legend); + List<TestAnalysisChartResult> createResults = extTestAnalysisMapper.getCraeteCaseReport(request); + // 获取修改的用例统计报表 + List<TestAnalysisChartResult> updateResults = extTestAnalysisMapper.getUpdateCaseReport(request); + formatXaxisSeries(xAxis, seriesList, "", dto, createResults, updateResults); + formatTable(dtos, createResults, updateResults); + } else { + List<String> users = request.getUsers(); + Legend legend = new Legend(); + formatLegend(legend, users, request); + dto.setLegend(legend); + + // 按用户展示 + boolean isFlag = true; + for (String item : users) { + request.setUsers(Arrays.asList(item)); + List<TestAnalysisChartResult> createResults = extTestAnalysisMapper.getCraeteCaseReport(request); + // 获取修改的用例统计报表 + List<TestAnalysisChartResult> updateResults = extTestAnalysisMapper.getUpdateCaseReport(request); + formatXaxisSeries(xAxis, seriesList, item + "-", dto, createResults, updateResults); + + // 初始化列表总量,按天统计总量 + if (isFlag) { + formatTable(dtos, createResults, updateResults); + isFlag = false; + } + // 增加子项 + for (int j = 0; j < dtos.size(); j++) { + TestAnalysisTableDTO childItem = new TestAnalysisTableDTO(item, createResults.get(j).getCountNum(), updateResults.get(j).getCountNum(), null); + dtos.get(j).getChildren().add(childItem); + } + } + } + // 每行总计 + dtos.forEach(item -> { + if (CollectionUtils.isNotEmpty(item.getChildren())) { + // table 总和计算 + List<Integer> collect = item.getChildren().stream().map(childItem -> Integer.valueOf(childItem.getCreateCount())).collect(Collectors.toList()); + // reduce求和 + Optional<Integer> createCount = collect.stream().reduce(Integer::sum); + List<Integer> upCollect = item.getChildren().stream().map(childItem -> Integer.valueOf(childItem.getUpdateCount())).collect(Collectors.toList()); + // reduce求和 + Optional<Integer> updateCount = upCollect.stream().reduce(Integer::sum); + item.setCreateCount(createCount.get().toString()); + item.setUpdateCount(updateCount.get().toString()); + } + }); + // table 总和计算 + List<Integer> collect = dtos.stream().map(item -> Integer.valueOf(item.getCreateCount())).collect(Collectors.toList()); + // reduce求和 + Optional<Integer> createCount = collect.stream().reduce(Integer::sum); + List<Integer> upCollect = dtos.stream().map(item -> Integer.valueOf(item.getUpdateCount())).collect(Collectors.toList()); + // reduce求和 + Optional<Integer> updateCount = upCollect.stream().reduce(Integer::sum); + dtos.add(new TestAnalysisTableDTO("总计", createCount.get().toString(), updateCount.get().toString(), new LinkedList<>())); + + TestAnalysisResult testAnalysisResult = new TestAnalysisResult(); + testAnalysisResult.setChartDTO(dto); + testAnalysisResult.setTableDTOs(dtos); + return testAnalysisResult; + } + + private void formatXaxisSeries(XAxis xAxis, List<Series> seriesList, String name, TestAnalysisChartDTO dto, List<TestAnalysisChartResult> createResults, List<TestAnalysisChartResult> updateResults) { + if (CollectionUtils.isNotEmpty(createResults)) { + xAxis.setData(createResults.stream().map(TestAnalysisChartResult::getDateStr).collect(Collectors.toList())); + Series series = new Series(); + series.setName(name + ADD); + series.setData(createResults.stream().map(TestAnalysisChartResult::getCountNum).collect(Collectors.toList())); + seriesList.add(series); + } + if (CollectionUtils.isNotEmpty(updateResults)) { + xAxis.setData(updateResults.stream().map(TestAnalysisChartResult::getDateStr).collect(Collectors.toList())); + Series series = new Series(); + series.setName(name + UPDATE); + series.setColor("#B8741A"); + series.setData(updateResults.stream().map(TestAnalysisChartResult::getCountNum).collect(Collectors.toList())); + seriesList.add(series); + } + dto.setXAxis(xAxis); + dto.setYAxis(new YAxis()); + dto.setSeries(seriesList); + } + + private void formatLegend(Legend legend, List<String> datas, TestAnalysisChartRequest request) { + Map<String, Boolean> selected = new LinkedHashMap<>(); + List<String> list = new LinkedList<>(); + if (CollectionUtils.isEmpty(datas)) { + selected.put(ADD, request.isCreateCase()); + selected.put(UPDATE, request.isUpdateCase()); + list.add(ADD); + list.add(UPDATE); + } else { + datas.forEach(item -> { + selected.put(item + "-" + ADD, request.isCreateCase()); + selected.put(item + "-" + UPDATE, request.isUpdateCase()); + list.add(item + "-" + ADD); + list.add(item + "-" + UPDATE); + }); + } + legend.setSelected(selected); + legend.setData(list); + } + + private void formatTable(List<TestAnalysisTableDTO> dtos, List<TestAnalysisChartResult> createResults, List<TestAnalysisChartResult> updateResults) { + for (int i = 0; i < createResults.size(); i++) { + TestAnalysisTableDTO dto = new TestAnalysisTableDTO(createResults.get(i).getDateStr(), createResults.get(i).getCountNum(), updateResults.get(i).getCountNum(), new LinkedList<>()); + dtos.add(dto); + } + } +} diff --git a/backend/src/main/java/io/metersphere/reportstatistics/service/TestCaseCountService.java b/backend/src/main/java/io/metersphere/reportstatistics/service/TestCaseCountService.java new file mode 100644 index 0000000000..1f438af677 --- /dev/null +++ b/backend/src/main/java/io/metersphere/reportstatistics/service/TestCaseCountService.java @@ -0,0 +1,775 @@ +package io.metersphere.reportstatistics.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.base.domain.CustomField; +import io.metersphere.base.domain.User; +import io.metersphere.base.mapper.ext.ExtTestCaseCountMapper; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.DateUtils; +import io.metersphere.controller.request.member.QueryMemberRequest; +import io.metersphere.dto.TestCaseTemplateDao; +import io.metersphere.i18n.Translator; +import io.metersphere.reportstatistics.dto.*; +import io.metersphere.reportstatistics.dto.charts.*; +import io.metersphere.service.TestCaseTemplateService; +import io.metersphere.service.UserService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestCaseCountService { + @Resource + private ExtTestCaseCountMapper extTestCaseCountMapper; + @Resource + UserService userService; + + public TestCaseCountResponse getReport(TestCaseCountRequest request) { + request.setFilterType(request.getFilterType().toUpperCase(Locale.ROOT)); + + TestAnalysisChartDTO dto = new TestAnalysisChartDTO(); + PieChartDTO pieChartDTO = new PieChartDTO(); + List<TestCaseCountTableDTO> dtos = new LinkedList<>(); + + List<Series> seriesList = new LinkedList<>(); + XAxis xAxis = new XAxis(); + xAxis.setAxisLabel(new HashMap<String,Integer>(){ {this.put("interval",0);this.put("rotate",0);}}); + + // 组织charts格式数据 + Legend legend = new Legend(); + formatLegend(legend, request.getYaxis(), request); + dto.setLegend(legend); + //根据X轴(分组计算字段)来整理不同表对应的字段 注意:x轴维护人/查询条件有维护人时 不查接口和性能; x轴为用例等级/查询条件有用例等级的,不查性能 + + boolean yAxisSelectTestCase = false; + boolean yAxisSelectApi = false; + boolean yAxisSelectScenarioCase = false; + boolean yAxisSelectLoad = false; + + boolean selectApi = true; + boolean selectLoad = true; + + boolean parseUser = false; + boolean parseStatus = false; + + switch (request.getXaxis()) { + case "creator": + request.setTestCaseGroupColumn("create_user"); + request.setApiCaseGroupColumn("create_user_id"); + request.setScenarioCaseGroupColumn("create_user"); + request.setLoadCaseGroupColumn("create_user"); + parseUser = true; + break; + case "maintainer": + request.setTestCaseGroupColumn("maintainer"); + request.setApiCaseGroupColumn("'无维护人'"); + request.setScenarioCaseGroupColumn("principal"); + request.setLoadCaseGroupColumn("'无维护人'"); + selectApi = false; + selectLoad = false; + parseUser = true; + break; + case "casetype": + Map<String, String> caseDescMap = this.getCaseDescMap(); + request.setTestCaseGroupColumn("'" + caseDescMap.get("testCaseDesc") + "'"); + request.setApiCaseGroupColumn("'" + caseDescMap.get("apiCaseDesc") + "'"); + request.setScenarioCaseGroupColumn("'" + caseDescMap.get("scenarioCaseDesc") + "'"); + request.setLoadCaseGroupColumn("'" + caseDescMap.get("loadCaseDesc") + "'"); + break; + case "casestatus": + request.setTestCaseGroupColumn("status"); + request.setApiCaseGroupColumn("status"); + request.setScenarioCaseGroupColumn("status"); + request.setLoadCaseGroupColumn("status"); + selectApi = false; + parseStatus = true; + break; + case "caselevel": + request.setTestCaseGroupColumn("priority"); + request.setApiCaseGroupColumn("priority"); + request.setScenarioCaseGroupColumn("level"); + request.setLoadCaseGroupColumn("'无用例等级'"); + selectLoad = false; + break; + default: + return new TestCaseCountResponse(); + } + + //计算时间 + if (StringUtils.equalsIgnoreCase(request.getTimeType(), "dynamicTime")) { + int dateCountType = 0; + if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "day")) { + dateCountType = Calendar.DAY_OF_MONTH; + } else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "month")) { + dateCountType = Calendar.MONTH; + } else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "year")) { + dateCountType = Calendar.YEAR; + } + + if (dateCountType != 0 && request.getTimeRange() != 0) { + long startTime = DateUtils.dateSum(new Date(), (0 - request.getTimeRange()), dateCountType).getTime(); + request.setStartTime(startTime); + } + + } else if (StringUtils.equalsIgnoreCase(request.getTimeType(), "fixedTime")) { + if (CollectionUtils.isNotEmpty(request.getTimes()) && request.getTimes().size() == 2) { + request.setStartTime(request.getTimes().get(0)); + request.setEndTime(request.getTimes().get(1)); + } + } + + //计算更多属性 + if (CollectionUtils.isNotEmpty(request.getFilters())) { + for (Map<String, Object> filterMap : request.getFilters()) { + String filterType = String.valueOf(filterMap.get("type")); + + if (StringUtils.equalsAnyIgnoreCase(filterType, "casetype", "caselevel", "creator", "maintainer")) { + Object valueObj = filterMap.get("values"); + if (valueObj instanceof List) { + List<String> searchList = (List) valueObj; + if(!searchList.isEmpty()){ + request.setFilterSearchList(filterType, searchList); + } + } + + if (StringUtils.equalsIgnoreCase(filterType, "caselevel")) { + selectLoad = false; + }else if (StringUtils.equalsIgnoreCase(filterType, "maintainer")) { + selectApi = false; + selectLoad = false; + } + }else if(StringUtils.equalsAnyIgnoreCase(filterType, "casestatus")){ + List<String> searchList = new ArrayList<>(); + Object valueObj = filterMap.get("values"); + if (valueObj instanceof List) { + for (String statusStr : (List<String>) valueObj) { + searchList.add(statusStr.toUpperCase(Locale.ROOT)); + } + } + //如果包含Running + if(searchList.contains("RUNNING")){ + if(!searchList.contains("Starting")){ + searchList.add("STARTING"); + } + if(!searchList.contains("Underway")){ + searchList.add("UNDERWAY"); + } + } + + if(searchList.contains("FINISHED")){ + if(!searchList.contains("Completed")){ + searchList.add("Completed"); + } + } + + if(!searchList.isEmpty()){ + request.setFilterSearchList(filterType, searchList); + } + selectApi = false; + } + } + } + + //获取测试用例、接口用例、场景用例、性能用例的统计 + List<TestCaseCountChartResult> functionCaseCountResult = new ArrayList<>(); + List<TestCaseCountChartResult> apiCaseCountResult = new ArrayList<>(); + List<TestCaseCountChartResult> scenarioCaseCount = new ArrayList<>(); + List<TestCaseCountChartResult> loadCaseCount = new ArrayList<>(); + + List<String> moreOptionsAboutCaseType = new ArrayList<>(); + if (StringUtils.equalsIgnoreCase(request.getFilterType(), "And") && MapUtils.isNotEmpty(request.getFilterSearchList())) { + if (request.getFilterSearchList().containsKey("maintainer")) { + selectApi = false; + } + if (request.getFilterSearchList().containsKey("caselevel")) { + selectLoad = false; + } + + if (request.getFilterSearchList().containsKey("casetype")) { + //如果"且"查询,同时针对案例类型做过筛选,那么则分开批量查询 + List<String> selectCaseTypeList = request.getFilterSearchList().get("casetype"); + request.getFilterSearchList().remove("casetype"); + if (CollectionUtils.isNotEmpty(selectCaseTypeList)) { + moreOptionsAboutCaseType.addAll(selectCaseTypeList); + } + } + } + //没有选择的话默认搜索条件是所有类型的案例 + if(moreOptionsAboutCaseType.isEmpty()){ + moreOptionsAboutCaseType.add("testCase"); + moreOptionsAboutCaseType.add("apiCase"); + moreOptionsAboutCaseType.add("scenarioCase"); + moreOptionsAboutCaseType.add("loadCase"); + } + + //解析Y轴,判断要查询的案例类型 + if(CollectionUtils.isNotEmpty(request.getYaxis())){ + + for (String selectType:request.getYaxis()) { + if(moreOptionsAboutCaseType.contains(selectType)){ + if (StringUtils.equalsIgnoreCase(selectType, "testCase")) { + yAxisSelectTestCase = true; + } else if (StringUtils.equalsIgnoreCase(selectType, "apiCase")) { + if(selectApi){ + yAxisSelectApi = true; + } + } else if (StringUtils.equalsIgnoreCase(selectType, "scenarioCase")) { + yAxisSelectScenarioCase = true; + } else if (StringUtils.equalsIgnoreCase(selectType, "loadCase")) { + if(selectLoad){ + yAxisSelectLoad = true; + } + } + } + } + } + + + if(yAxisSelectTestCase){ + functionCaseCountResult = extTestCaseCountMapper.getFunctionCaseCount(request); + } + if (yAxisSelectApi) { + Map<String,List<String>> apiCaseFilterList = new HashMap<>(); + if(MapUtils.isNotEmpty(request.getFilterSearchList())){ + for (Map.Entry<String,List<String>> entry : request.getFilterSearchList().entrySet()) { + String type = entry.getKey(); + if(!StringUtils.equalsAnyIgnoreCase(type,"maintainer","casestatus")){ + apiCaseFilterList.put(entry.getKey(),entry.getValue()); + } + } + } + request.setApiFilterSearchList(apiCaseFilterList); + apiCaseCountResult = extTestCaseCountMapper.getApiCaseCount(request); + } + if(yAxisSelectScenarioCase){ + scenarioCaseCount = extTestCaseCountMapper.getScenarioCaseCount(request); + } + if (yAxisSelectLoad) { + Map<String,List<String>> loadCaseFilterMap = new HashMap<>(); + if(MapUtils.isNotEmpty(request.getFilterSearchList())){ + for (Map.Entry<String,List<String>> entry : request.getFilterSearchList().entrySet()) { + String type = entry.getKey(); + if(!StringUtils.equalsAnyIgnoreCase(type,"maintainer","caselevel")){ + loadCaseFilterMap.put(entry.getKey(),entry.getValue()); + } + } + } + request.setLoadFilterSearchList(loadCaseFilterMap); + loadCaseCount = extTestCaseCountMapper.getLoadCaseCount(request); + } + + Map<String, TestCaseCountSummary> summaryMap = this.summaryCountResult(parseUser, parseStatus,request.getProjectId(),request.getOrder(), + functionCaseCountResult, apiCaseCountResult, scenarioCaseCount, loadCaseCount); + + formatXaxisSeries(xAxis, seriesList, dto, summaryMap); + formatTable(dtos, summaryMap); + + formatPieChart(pieChartDTO, request.getXaxis(), summaryMap,yAxisSelectTestCase,yAxisSelectApi,yAxisSelectScenarioCase,yAxisSelectLoad); + + TestCaseCountResponse testCaseCountResult = new TestCaseCountResponse(); + testCaseCountResult.setBarChartDTO(dto); + testCaseCountResult.setTableDTOs(dtos); + testCaseCountResult.setPieChartDTO(pieChartDTO); + return testCaseCountResult; + } + + private void formatPieChart(PieChartDTO pieChartDTO, String groupName, Map<String, TestCaseCountSummary> summaryMap, + boolean selectTestCase, boolean selectApi, boolean selectScenarioCase, boolean selectLoad) { + JSONArray titleArray = new JSONArray(); + titleArray.add("type"); + titleArray.add("count"); + titleArray.add(groupName); + + List<Series> seriesArr = new ArrayList<>(); + List<Title> titles = new ArrayList<>(); + + int leftPx = 200; + Map<String, String> caseDescMap = this.getCaseDescMap(); + for (TestCaseCountSummary summary : summaryMap.values()) { + String leftPxStr = String.valueOf(leftPx); + + List<Object> dataList = new ArrayList<>(); + + if(selectTestCase && summary.testCaseCount > 0){ + PieData pieData = new PieData(); + pieData.setName(caseDescMap.get("testCaseDesc")); + pieData.setValue(summary.testCaseCount); + pieData.setColor("#F38F1F"); + dataList.add(pieData); + } + + if(selectApi && summary.apiCaseCount > 0){ + PieData apicasePieData = new PieData(); + apicasePieData.setName(caseDescMap.get("apiCaseDesc")); + apicasePieData.setValue(summary.apiCaseCount); + apicasePieData.setColor("#6FD999"); + dataList.add(apicasePieData); + } + + if(selectScenarioCase && summary.scenarioCaseCount > 0){ + PieData scenarioPieData = new PieData(); + scenarioPieData.setName(caseDescMap.get("scenarioCaseDesc")); + scenarioPieData.setValue(summary.scenarioCaseCount); + scenarioPieData.setColor("#2884F3"); + dataList.add(scenarioPieData); + } + + if(selectLoad && summary.loadCaseCount > 0){ + PieData loadCasePieData = new PieData(); + loadCasePieData.setName(caseDescMap.get("loadCaseDesc")); + loadCasePieData.setValue(summary.loadCaseCount); + loadCasePieData.setColor("#F45E53"); + dataList.add(loadCasePieData); + } + + Series series = new Series(); + series.setType("pie"); + series.setRadius("50"); + series.setEncode(new JSONObject() {{ + this.put("itemName", "groupname"); + this.put("value", summary.groupName); + }}); + seriesArr.add(series); + + series.setData(dataList); + series.setCenter(new ArrayList<String>() {{ + this.add(leftPxStr); + this.add("50%"); + }}); + + Title title = new Title(); + title.setSubtext(summary.groupName); + title.setLeft(leftPxStr); + titles.add(title); + + leftPx = leftPx + 350; + } + + pieChartDTO.setSeries(seriesArr); + pieChartDTO.setTitle(titles); + pieChartDTO.setWidth(leftPx); + } + + private Map<String, TestCaseCountSummary> summaryCountResult(boolean parseGroupNameToUserName, boolean parseGrouNameToCaseStatus, String projectId, String order, + List<TestCaseCountChartResult> functionCaseCountResult, List<TestCaseCountChartResult> apiCaseCountResult, List<TestCaseCountChartResult> scenarioCaseCount, List<TestCaseCountChartResult> loadCaseCount) { + Map<String, TestCaseCountSummary> summaryMap = new LinkedHashMap<>(); + + //groupName 解析对象 + Map<String, String> groupNameParseMap = new HashMap<>(); + if (parseGroupNameToUserName) { + groupNameParseMap.putAll(this.getUserIdMap()); + } + if (parseGrouNameToCaseStatus) { + groupNameParseMap.putAll(this.getCaseStatusMap(projectId)); + } + + if (CollectionUtils.isNotEmpty(functionCaseCountResult)) { + for (TestCaseCountChartResult result : functionCaseCountResult) { + if(result.getGroupName() == null){ + result.setGroupName(groupNameParseMap.get("running")); + }else { + if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) { + result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT))); + } + } + + String groupName = result.getGroupName(); + if (StringUtils.isNotEmpty(groupName)) { + TestCaseCountSummary summary = summaryMap.get(groupName); + if (summary == null) { + summary = new TestCaseCountSummary(groupName); + } + summary.testCaseCount = result.getCountNum(); + summaryMap.put(groupName, summary); + } + } + } + + if (CollectionUtils.isNotEmpty(apiCaseCountResult)) { + for (TestCaseCountChartResult result : apiCaseCountResult) { + if(result.getGroupName() == null){ + result.setGroupName(groupNameParseMap.get("running")); + }else { + if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) { + result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT))); + } + } + String groupName = result.getGroupName(); + if (StringUtils.isNotEmpty(groupName)) { + TestCaseCountSummary summary = summaryMap.get(groupName); + if (summary == null) { + summary = new TestCaseCountSummary(groupName); + } + summary.apiCaseCount = result.getCountNum(); + summaryMap.put(groupName, summary); + } + } + } + + if (CollectionUtils.isNotEmpty(scenarioCaseCount)) { + for (TestCaseCountChartResult result : scenarioCaseCount) { + if(result.getGroupName() == null){ + result.setGroupName(groupNameParseMap.get("running")); + }else { + if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) { + result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT))); + } + } + String groupName = result.getGroupName(); + if (StringUtils.isNotEmpty(groupName)) { + TestCaseCountSummary summary = summaryMap.get(groupName); + if (summary == null) { + summary = new TestCaseCountSummary(groupName); + } + summary.scenarioCaseCount = result.getCountNum(); + summaryMap.put(groupName, summary); + } + } + } + + if (CollectionUtils.isNotEmpty(loadCaseCount)) { + for (TestCaseCountChartResult result : loadCaseCount) { + if(result.getGroupName() == null){ + result.setGroupName(groupNameParseMap.get("running")); + }else { + if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) { + result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT))); + } + } + String groupName = result.getGroupName(); + if (StringUtils.isNotEmpty(groupName)) { + TestCaseCountSummary summary = summaryMap.get(groupName); + if (summary == null) { + summary = new TestCaseCountSummary(groupName); + } + summary.loadCaseCount = result.getCountNum(); + summaryMap.put(groupName, summary); + } + } + } + + Map<String, TestCaseCountSummary> returmMap = new LinkedHashMap<>(); + + if(StringUtils.equalsIgnoreCase(order,"desc")){ + TreeMap<Long,List<TestCaseCountSummary>> treeMap = new TreeMap<>(); + for (TestCaseCountSummary model : summaryMap.values()) { + if(treeMap.containsKey(model.getAllCount())){ + treeMap.get(model.getAllCount()).add(model); + }else { + List<TestCaseCountSummary> list = new ArrayList<>(); + list.add(model); + treeMap.put(model.getAllCount(),list); + } + } + ArrayList<TestCaseCountSummary> sortedList = new ArrayList<>(); + for (List<TestCaseCountSummary> list : treeMap.values()) { + sortedList.addAll(list); + } + + for (int i = sortedList.size(); i > 0; i --) { + TestCaseCountSummary model = sortedList.get(i-1); + returmMap.put(model.groupName,model); + } + }else if(StringUtils.equalsIgnoreCase(order,"asc")){ + TreeMap<Long,List<TestCaseCountSummary>> treeMap = new TreeMap<>(); + for (TestCaseCountSummary model : summaryMap.values()) { + if(treeMap.containsKey(model.getAllCount())){ + treeMap.get(model.getAllCount()).add(model); + }else { + List<TestCaseCountSummary> list = new ArrayList<>(); + list.add(model); + treeMap.put(model.getAllCount(),list); + } + } + for (List<TestCaseCountSummary> list : treeMap.values()) { + for (TestCaseCountSummary model : list ) { + returmMap.put(model.groupName,model); + } + } + }else { + returmMap = summaryMap; + } + + + return returmMap; + } + + private Map<String, String> getUserIdMap() { + List<User> userList = userService.getUserList(); + Map<String, String> userIdMap = new HashMap<>(); + for (User model : userList) { + userIdMap.put(model.getId(), model.getId() + "\n(" + model.getName() + ")"); + } + return userIdMap; + } + + private Map<String, String> getCaseStatusMap(String projectId) { + + Map<String, String> caseStatusMap = new HashMap<>(); + + TestCaseTemplateService testCaseTemplateService = CommonBeanFactory.getBean(TestCaseTemplateService.class); + TestCaseTemplateDao testCaseTemplate = testCaseTemplateService.getTemplate(projectId); + + caseStatusMap.put("prepare", Translator.get("test_case_status_prepare")); + caseStatusMap.put("error", Translator.get("test_case_status_error")); + caseStatusMap.put("success", Translator.get("test_case_status_success")); + caseStatusMap.put("trash", Translator.get("test_case_status_trash")); + caseStatusMap.put("underway", Translator.get("test_case_status_running")); + caseStatusMap.put("starting", Translator.get("test_case_status_running")); + caseStatusMap.put("saved", Translator.get("test_case_status_saved")); + caseStatusMap.put("running", Translator.get("test_case_status_running")); + caseStatusMap.put("finished", Translator.get("test_case_status_finished")); + caseStatusMap.put("completed", Translator.get("test_case_status_finished")); + + if (testCaseTemplate != null && CollectionUtils.isNotEmpty(testCaseTemplate.getCustomFields())) { + for (CustomField customField : testCaseTemplate.getCustomFields()) { + if (StringUtils.equals(customField.getName(), "用例状态")) { + JSONArray optionsArr = JSONArray.parseArray(customField.getOptions()); + for (int i = 0; i < optionsArr.size(); i++) { + JSONObject jsonObject = optionsArr.getJSONObject(i); + if (jsonObject.containsKey("value") && jsonObject.containsKey("text") && + !StringUtils.equalsAnyIgnoreCase(jsonObject.getString("value"), "Prepare", "Error", "Success", "Trash", "Underway", "Starting", "Saved")) { + caseStatusMap.put(jsonObject.getString("value"), jsonObject.getString("text")); + } + } + } + } + } + + return caseStatusMap; + } + + + private void formatXaxisSeries(XAxis xAxis, List<Series> seriesList, TestAnalysisChartDTO dto, + Map<String, TestCaseCountSummary> summaryMap) { + List<String> xAxisDataList = new ArrayList<>(); + + List<Object> testCaseCountList = new ArrayList<>(); + List<Object> apiCaseCountList = new ArrayList<>(); + List<Object> scenarioCaseCountList = new ArrayList<>(); + List<Object> loadCaseCountList = new ArrayList<>(); + for (TestCaseCountSummary summary : summaryMap.values()) { + xAxisDataList.add(summary.groupName); + testCaseCountList.add(String.valueOf(summary.testCaseCount)); + apiCaseCountList.add(String.valueOf(summary.apiCaseCount)); + scenarioCaseCountList.add(String.valueOf(summary.scenarioCaseCount)); + loadCaseCountList.add(String.valueOf(summary.loadCaseCount)); + } + xAxis.setData(xAxisDataList); + + Map<String, String> caseDescMap = this.getCaseDescMap(); + + Series tetcaseSeries = new Series(); + tetcaseSeries.setName(caseDescMap.get("testCaseDesc")); + tetcaseSeries.setColor("#F38F1F"); + tetcaseSeries.setRadius("20%"); + tetcaseSeries.setType("bar"); + tetcaseSeries.setStack("total"); + tetcaseSeries.setData(testCaseCountList); + seriesList.add(tetcaseSeries); + + Series apiSeries = new Series(); + apiSeries.setName(caseDescMap.get("apiCaseDesc")); + apiSeries.setColor("#6FD999"); + apiSeries.setType("bar"); + apiSeries.setStack("total"); + apiSeries.setData(apiCaseCountList); + seriesList.add(apiSeries); + + Series scenarioSeries = new Series(); + scenarioSeries.setName(caseDescMap.get("scenarioCaseDesc")); + scenarioSeries.setColor("#2884F3"); + scenarioSeries.setType("bar"); + scenarioSeries.setStack("total"); + scenarioSeries.setData(scenarioCaseCountList); + seriesList.add(scenarioSeries); + + Series loadSeries = new Series(); + loadSeries.setName(caseDescMap.get("loadCaseDesc")); + loadSeries.setColor("#F45E53"); + loadSeries.setType("bar"); + loadSeries.setStack("total"); + loadSeries.setData(loadCaseCountList); + seriesList.add(loadSeries); + + dto.setXAxis(xAxis); + dto.setYAxis(new YAxis()); + dto.setSeries(seriesList); + } + + private void formatLegend(Legend legend, List<String> datas, TestCaseCountRequest yrequest) { + Map<String, Boolean> selected = new LinkedHashMap<>(); + List<String> list = new LinkedList<>(); + legend.setSelected(selected); + legend.setData(datas); + } + + private void formatTable(List<TestCaseCountTableDTO> dtos, Map<String, TestCaseCountSummary> summaryMap) { + for (TestCaseCountSummary summary : summaryMap.values()) { + TestCaseCountTableDTO dto = new TestCaseCountTableDTO(summary.groupName, summary.testCaseCount, summary.apiCaseCount, summary.scenarioCaseCount, summary.loadCaseCount); + dtos.add(dto); + } + } + + private Map<String, String> getCaseDescMap() { + Map<String, String> map = new HashMap<>(); + map.put("testCaseDesc", Translator.get("test_case")); + map.put("apiCaseDesc", Translator.get("api_case")); + map.put("scenarioCaseDesc", Translator.get("scenario_case")); + map.put("loadCaseDesc", Translator.get("performance_case")); + return map; + } + + public Map<String, List<Map<String, String>>> getSelectFilterDatas(String projectId) { + Map<String, List<Map<String, String>>> returnMap = new HashMap<>(); + + //组装用户 + QueryMemberRequest memberRequest = new QueryMemberRequest(); + memberRequest.setProjectId(projectId); + List<User> userList = userService.getUserList(); + + List<Map<String, String>> returnUserList = new ArrayList<>(); + for (User user : userList) { + Map<String, String> map = new HashMap<>(); + map.put("id", user.getId()); + map.put("label", user.getId() + "(" + user.getName() + ")"); + returnUserList.add(map); + } + + //组装用例等级和用例状态 + TestCaseTemplateService testCaseTemplateService = CommonBeanFactory.getBean(TestCaseTemplateService.class); + TestCaseTemplateDao testCaseTemplate = testCaseTemplateService.getTemplate(projectId); + + List<Map<String, String>> caseLevelList = new ArrayList<>(); + List<Map<String, String>> caseStatusList = new ArrayList<>(); + Map<String, String> statusMap1 = new HashMap<>(); + statusMap1.put("id", "Prepare"); + statusMap1.put("label", Translator.get("test_case_status_prepare")); + +// Map<String, String> statusMap2 = new HashMap<>(); +// statusMap2.put("id", "Error"); +// statusMap2.put("label", Translator.get("test_case_status_error")); +// +// Map<String, String> statusMap3 = new HashMap<>(); +// statusMap3.put("id", "Success"); +// statusMap3.put("label", Translator.get("test_case_status_success")); + +// Map<String, String> statusMap4 = new HashMap<>(); +// statusMap4.put("id", "Trash"); +// statusMap4.put("label", Translator.get("test_case_status_trash")); + +// Map<String, String> statusMap5 = new HashMap<>(); +// statusMap5.put("id", "Underway"); +// statusMap5.put("label", Translator.get("test_case_status_running")); + +// Map<String, String> statusMap6 = new HashMap<>(); +// statusMap6.put("id", "Starting"); +// statusMap6.put("label", Translator.get("test_case_status_running")); + + Map<String, String> statusMap7 = new HashMap<>(); + statusMap7.put("id", "Saved"); + statusMap7.put("label", Translator.get("test_case_status_saved")); + + Map<String, String> statusMap8 = new HashMap<>(); + statusMap8.put("id", "Running"); + statusMap8.put("label", Translator.get("test_case_status_running")); + + Map<String, String> statusMap9 = new HashMap<>(); + statusMap9.put("id", "Finished"); + statusMap9.put("label", Translator.get("test_case_status_finished")); + + caseStatusList.add(statusMap1); +// caseStatusList.add(statusMap2); +// caseStatusList.add(statusMap3); +// caseStatusList.add(statusMap4); +// caseStatusList.add(statusMap5); +// caseStatusList.add(statusMap6); + caseStatusList.add(statusMap7); + caseStatusList.add(statusMap8); + caseStatusList.add(statusMap9); + + Map<String, String> levelMap1 = new HashMap<>(); + levelMap1.put("id", "P0"); + levelMap1.put("label", "P0"); + Map<String, String> levelMap2 = new HashMap<>(); + levelMap2.put("id", "P1"); + levelMap2.put("label", "P1"); + Map<String, String> levelMap3 = new HashMap<>(); + levelMap3.put("id", "P2"); + levelMap3.put("label", "P2"); + Map<String, String> levelMap4 = new HashMap<>(); + levelMap4.put("id", "P3"); + levelMap4.put("label", "P3"); + caseLevelList.add(levelMap1); + caseLevelList.add(levelMap2); + caseLevelList.add(levelMap3); + caseLevelList.add(levelMap4); + + + if (testCaseTemplate != null && CollectionUtils.isNotEmpty(testCaseTemplate.getCustomFields())) { + for (CustomField customField : testCaseTemplate.getCustomFields()) { + if (StringUtils.equals(customField.getName(), "用例状态")) { + JSONArray optionsArr = JSONArray.parseArray(customField.getOptions()); + for (int i = 0; i < optionsArr.size(); i++) { + JSONObject jsonObject = optionsArr.getJSONObject(i); + if (jsonObject.containsKey("value") && jsonObject.containsKey("text")) { + String value = jsonObject.getString("value"); + if(!StringUtils.equalsAnyIgnoreCase(value, "Prepare", "Error", "Success", "Trash", "Underway", "Starting", "Saved", + "Completed","test_track.case.status_finished")){ + Map<String, String> statusMap = new HashMap<>(); + statusMap.put("id", jsonObject.getString("value")); + statusMap.put("label", jsonObject.getString("text")); + caseStatusList.add(statusMap); + } + } + } + } else if (StringUtils.equals(customField.getName(), "用例等级")) { + JSONArray optionsArr = JSONArray.parseArray(customField.getOptions()); + for (int i = 0; i < optionsArr.size(); i++) { + JSONObject jsonObject = optionsArr.getJSONObject(i); + if (jsonObject.containsKey("value") && jsonObject.containsKey("text") && + !StringUtils.equalsAnyIgnoreCase(jsonObject.getString("value"), "P0", "P1", "P2", "P3")) { + Map<String, String> levelMap = new HashMap<>(); + levelMap.put("id", jsonObject.getString("value")); + levelMap.put("label", jsonObject.getString("text")); + caseLevelList.add(levelMap); + } + } + } + } + } + Map<String, String> caseDescMap = this.getCaseDescMap(); + // 组装用例类型 + List<Map<String, String>> caseTypeList = new ArrayList<>(); + Map<String, String> caseTypeMap1 = new HashMap<>(); + caseTypeMap1.put("id", "testCase"); + caseTypeMap1.put("label", caseDescMap.get("testCaseDesc")); + Map<String, String> caseTypeMap2 = new HashMap<>(); + caseTypeMap2.put("id", "apiCase"); + caseTypeMap2.put("label", caseDescMap.get("apiCaseDesc")); + Map<String, String> caseTypeMap3 = new HashMap<>(); + caseTypeMap3.put("id", "scenarioCase"); + caseTypeMap3.put("label", caseDescMap.get("scenarioCaseDesc")); + Map<String, String> caseTypeMap4 = new HashMap<>(); + caseTypeMap4.put("id", "loadCase"); + caseTypeMap4.put("label", caseDescMap.get("loadCaseDesc")); + caseTypeList.add(caseTypeMap1); + caseTypeList.add(caseTypeMap2); + caseTypeList.add(caseTypeMap3); + caseTypeList.add(caseTypeMap4); + + returnMap.put("casetype", caseTypeList); + returnMap.put("caselevel", caseLevelList); + returnMap.put("casestatus", caseStatusList); + returnMap.put("creator", returnUserList); + returnMap.put("maintainer", returnUserList); + return returnMap; + } +} diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index bc8f05fdd7..6edae7aeeb 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit bc8f05fdd7bfa0a13438d409394a2d150187f88b +Subproject commit 6edae7aeeb9d5ade65d64a115d00af96e4dc56d6 diff --git a/backend/src/main/resources/permission.json b/backend/src/main/resources/permission.json index c51c2f4e80..73024e68cd 100644 --- a/backend/src/main/resources/permission.json +++ b/backend/src/main/resources/permission.json @@ -816,6 +816,18 @@ "name": "导出", "resourceId": "PROJECT_REPORT_ANALYSIS", "license": true + }, + { + "id": "PROJECT_REPORT_ANALYSIS:READ+UPDATE", + "name": "保存", + "resourceId": "PROJECT_REPORT_ANALYSIS", + "license": true + }, + { + "id": "PROJECT_REPORT_ANALYSIS:READ+CREATE", + "name": "另存为", + "resourceId": "PROJECT_REPORT_ANALYSIS", + "license": true } ], "resource": [ diff --git a/frontend/src/business/components/common/router/router.js b/frontend/src/business/components/common/router/router.js index 312338f335..d3ff0889c9 100644 --- a/frontend/src/business/components/common/router/router.js +++ b/frontend/src/business/components/common/router/router.js @@ -5,11 +5,12 @@ import Setting from "@/business/components/settings/router"; import API from "@/business/components/api/router"; import Performance from "@/business/components/performance/router"; import Track from "@/business/components/track/router"; +import ReportStatistics from "@/business/components/reportstatistics/router"; import {getCurrentUserId} from "@/common/js/utils"; -const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/); -const Report = requireContext.keys().map(key => requireContext(key).report); -const ReportObj = Report && Report != null && Report.length > 0 && Report[0] != undefined ? Report : [{path: "/sidebar"}]; +// const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/); +// const Report = requireContext.keys().map(key => requireContext(key).report); +// const ReportObj = Report && Report != null && Report.length > 0 && Report[0] != undefined ? Report : [{path: "/sidebar"}]; Vue.use(VueRouter); @@ -26,7 +27,8 @@ const router = new VueRouter({ API, Performance, Track, - ...ReportObj + ReportStatistics, + // ...ReportStatistics ] }); diff --git a/frontend/src/business/components/common/select-tree/SelectTree.vue b/frontend/src/business/components/common/select-tree/SelectTree.vue index 73f42f9a05..5d61c9a383 100644 --- a/frontend/src/business/components/common/select-tree/SelectTree.vue +++ b/frontend/src/business/components/common/select-tree/SelectTree.vue @@ -290,11 +290,14 @@ export default { this.$refs.tree.setCheckedKeys(thisKeys); this.returnDataKeys = thisKeys; let t = []; + this.options = []; thisKeys.map((item) => {//设置option选项 let node = this.$refs.tree.getNode(item); // 所有被选中的节点对应的node - t.push(node.data); - this.options.push({label: node.label, value: node.key}); - return {label: node.label, value: node.key}; + if(node){ + t.push(node.data); + this.options.push({label: node.label, value: node.key}); + return {label: node.label, value: node.key}; + } }); this.returnDatas = t; this.popoverHide() @@ -398,6 +401,23 @@ export default { }, deep: true }, + defaultKey:{ + handler:function(){ + this.init(); + if(this.data && this.data.length > 0){ + this.$refs.tree.setCheckedKeys(this.defaultKey); + } + }, + deep:true + }, + data:{ + handler:function(){ + if(this.defaultKey && this.defaultKey.length > 0){ + this.$refs.tree.setCheckedKeys(this.defaultKey); + } + }, + deep:true + }, filterText(val) { this.$nextTick(() => { this.$refs.tree.filter(val); diff --git a/frontend/src/business/components/reportstatistics/ReportAnalysis.vue b/frontend/src/business/components/reportstatistics/ReportAnalysis.vue new file mode 100644 index 0000000000..c2cb0a8f98 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/ReportAnalysis.vue @@ -0,0 +1,101 @@ +<template> + <div> + <el-row type="flex"> + <p class="tip"> + <span class="ms-span">{{$t('commons.report_statistics.name')}}</span> + <el-select v-model="reportType" class="ms-col-type" size="mini" style="width: 120px"> + <el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in reportTypes"/> + </el-select> + </p> + </el-row> + <transition> + <keep-alive> + <report-card @openCard="openCard"/> + </keep-alive> + </transition> + + <!-- 测试用例趋势页面 --> + <ms-drawer :visible="testCaseTrendDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" :is-show-close="false" style="overflow: hidden"> + <template v-slot:header> + <report-header :title="$t('commons.report_statistics.test_case_analysis')" :history-report-id="historyReportId" + @closePage="close" @saveReport="saveReport" @selectAndSaveReport="selectAndSaveReport"/> + </template> + <test-analysis-container @initHistoryReportId="initHistoryReportId" ref="testAnalysisContainer"/> + </ms-drawer> + + <!-- 测试用例分析页面 --> + <ms-drawer :visible="testCaseCountDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" :is-show-close="false" style="overflow: hidden"> + <template v-slot:header> + <report-header :title="$t('commons.report_statistics.test_case_count')" :history-report-id="historyReportId" + @closePage="close" @saveReport="saveReport" @selectAndSaveReport="selectAndSaveReport"/> + </template> + <test-case-count-container @initHistoryReportId="initHistoryReportId" ref="testCaseCountContainer"/> + </ms-drawer> + </div> +</template> + +<script> + import ReportCard from "./ReportCard"; + import TestAnalysisContainer from "./track/TestAnalysisContainer"; + import MsDrawer from "@/business/components/common/components/MsDrawer"; + import ReportHeader from './base/ReportHeader'; + import TestCaseCountContainer from "./testCaseCount/TestCaseCountContainer"; + + export default { + name: "ReportAnalysis", + components: {ReportCard, TestAnalysisContainer, MsDrawer, ReportHeader, TestCaseCountContainer}, + data() { + return { + reportType: "track", + testCaseTrendDrawer: false, + testCaseCountDrawer: false, + historyReportId:"", + reportTypes: [{id: 'track', name: this.$t('test_track.test_track')}], + } + }, + methods: { + openCard(type) { + if(type === 'trackTestCase'){ + this.testCaseTrendDrawer = true; + }else if(type === 'countTestCase'){ + this.testCaseCountDrawer = true; + } + }, + close() { + this.testCaseTrendDrawer = false; + this.testCaseCountDrawer = false; + }, + saveReport(){ + if(this.testCaseTrendDrawer){ + this.$refs.testAnalysisContainer.saveReport(); + }else if(this.testCaseCountDrawer){ + this.$refs.testCaseCountContainer.saveReport(); + } + }, + selectAndSaveReport(){ + if(this.testCaseTrendDrawer){ + this.$refs.testAnalysisContainer.selectAndSaveReport(); + }else if(this.testCaseCountDrawer){ + this.$refs.testCaseCountContainer.selectAndSaveReport(); + } + }, + initHistoryReportId(reportId){ + this.historyReportId = reportId; + }, + }, + } +</script> + +<style scoped> + .ms-span { + margin: 10px 10px 0px + } + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 10px 20px 0px; + } +</style> diff --git a/frontend/src/business/components/reportstatistics/ReportCard.vue b/frontend/src/business/components/reportstatistics/ReportCard.vue new file mode 100644 index 0000000000..648ad41b63 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/ReportCard.vue @@ -0,0 +1,138 @@ +<template> + <div class="ms-content"> + <el-row> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col" @click.native="openCard('trackTestCase')"> + <img src="../../../assets/track.jpg" class="image"> + <div style="padding: 10px;"> + <span>{{$t('commons.report_statistics.test_case_analysis')}}</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col" @click.native="openCard('countTestCase')"> + <img src="../../../assets/track.jpg" class="image"> + <div style="padding: 10px;"> + <span>{{$t('commons.report_statistics.test_case_count')}}</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_count_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col"> + <img src="../../../assets/other.png" class="image"> + <div style="padding: 10px;"> + <span>预留模块敬请期待</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col"> + <img src="../../../assets/other.png" class="image"> + <div style="padding: 10px;"> + <span>预留模块敬请期待</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col"> + <img src="../../../assets/other.png" class="image"> + <div style="padding: 10px;"> + <span>预留模块敬请期待</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + <el-col :span="4"> + <el-card :body-style="{ padding: '0px' }" class="ms-col"> + <img src="../../../assets/other.png" class="image"> + <div style="padding: 10px;"> + <span>预留模块敬请期待</span> + <div class="bottom clearfix"> + <time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time> + </div> + </div> + </el-card> + </el-col> + </el-row> + </div> +</template> + +<script> + import {hasPermission} from "@/common/js/utils"; + + export default { + name: "ReportCard", + components: {}, + data() { + return {} + }, + methods: { + openCard(type) { + if (!hasPermission('PROJECT_REPORT_ANALYSIS:READ')) { + this.$warning("无查看权限!"); + return; + } + this.$emit('openCard', type); + } + }, + } +</script> + +<style scoped> + .time { + font-size: 13px; + color: #999; + } + + .bottom { + margin-top: 13px; + line-height: 12px; + } + + .button { + padding: 0; + float: right; + } + + .image { + width: 100%; + display: block; + } + + .clearfix:before, + .clearfix:after { + display: table; + content: ""; + } + + .clearfix:after { + clear: both + } + + .ms-col { + margin: 5px; + } + + .ms-content { + padding: 15px 10px 15px 15px; + } + + .ms-col:hover { + cursor: pointer; + border-color: #783887; + } +</style> diff --git a/frontend/src/business/components/reportstatistics/base/HistoryReportData.vue b/frontend/src/business/components/reportstatistics/base/HistoryReportData.vue new file mode 100644 index 0000000000..5922f31499 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/base/HistoryReportData.vue @@ -0,0 +1,86 @@ +<template> + <div> + <el-tabs v-model="activeName"> + <el-tab-pane :label="$t('commons.report_statistics.report_data.all_report')" name="allReport"> + <history-report-data-card :report-data="allReportData" :show-options-button="false" @deleteReport="deleteReport" @selectReport="selectReport"/> + </el-tab-pane> + <el-tab-pane :label="$t('commons.report_statistics.report_data.my_report')" name="myReport"> + <history-report-data-card :report-data="myReportData" :show-options-button="true" @deleteReport="deleteReport" @selectReport="selectReport"/> + </el-tab-pane> + </el-tabs> + </div> +</template> + +<script> + import {getCurrentProjectID,getCurrentUserId} from "@/common/js/utils"; + import HistoryReportDataCard from "./compose/HistoryReportDataCard"; + export default { + name: "HistoryReportData", + components: {HistoryReportDataCard}, + data() { + return { + activeName: 'allReport', + allReportData: [], + myReportData: [], + } + }, + props:{ + reportType:String + }, + created(){ + this.initReportData(); + }, + watch :{ + activeName(){ + this.initReportData(); + } + }, + methods: { + initReportData(){ + let projectId = getCurrentProjectID(); + let userId = getCurrentUserId(); + this.allReportData = []; + this.myReportData = []; + + let paramsObj = { + projectId:getCurrentProjectID(), + reportType:this.reportType, + }; + this.$post('/history/report/selectByParams',paramsObj, response => { + let allData = response.data; + allData.forEach(item => { + if(item){ + this.allReportData.push(item); + if(item.createUser === userId){ + this.myReportData.push(item); + } + } + }); + }); + }, + deleteReport(deleteId){ + let paramObj = { + id:deleteId + } + this.$post('/history/report/deleteByParam',paramObj, response => { + this.initReportData(); + }); + this.$emit("removeHistoryReportId"); + }, + selectReport(id){ + this.$emit("selectReport",id); + } + }, +} +</script> + +<style scoped> + +.historyCard{ + border: 0px; +} +/deep/ .el-card__header{ + border: 0px; +} + +</style> diff --git a/frontend/src/business/components/reportstatistics/base/ReportHeader.vue b/frontend/src/business/components/reportstatistics/base/ReportHeader.vue new file mode 100644 index 0000000000..c16290229d --- /dev/null +++ b/frontend/src/business/components/reportstatistics/base/ReportHeader.vue @@ -0,0 +1,115 @@ +<template> + <div class="ms-header"> + <el-row> + <div class="ms-div">{{title}}</div> + <div class="ms-header-right"> + <el-button type="primary" v-if="isSaveAsButtonShow" size="mini" @click="handleSaveAs" :disabled="readOnly">{{ $t('commons.save_as') }}<i class="el-icon-files el-icon--right"></i></el-button> + <el-button type="primary" v-if="isSaveButtonShow" size="mini" @click="handleSave" :disabled="readOnly">{{ $t('commons.save') }}<i class="el-icon-files el-icon--right"></i></el-button> + <el-button type="" size="mini" @click="handleExport" :disabled="readOnly">{{ $t('report.export') }}<i class="el-icon-download el-icon--right"></i></el-button> + <span class="ms-span">|</span> + <i class="el-icon-close report-alt-ico" @click="close"/> + </div> + </el-row> + </div> +</template> + +<script> +import {exportPdf, hasPermission} from "@/common/js/utils"; + import html2canvas from 'html2canvas'; + + export default { + name: "ReportHeader", + components: {}, + data() { + return {} + }, + props:{ + title:String, + historyReportId:String, + }, + created() { + }, + computed: { + readOnly() { + return !hasPermission('PROJECT_REPORT_ANALYSIS:READ+EXPORT'); + }, + isSaveAsButtonShow(){ + if(!this.historyReportId || this.historyReportId === null || this.historyReportId === ''){ + return false; + }else { + if(hasPermission('PROJECT_REPORT_ANALYSIS:READ+CREATE')){ + return true; + }else { + return false; + } + } + }, + isSaveButtonShow(){ + if(hasPermission('PROJECT_REPORT_ANALYSIS:READ+UPDATE')){ + return true; + }else { + return false; + } + } + }, + methods: { + handleExport() { + let name = this.title; + this.$nextTick(function () { + setTimeout(() => { + html2canvas(document.getElementById('reportAnalysis'), { + scale: 2 + }).then(function (canvas) { + exportPdf(name, [canvas]); + }); + }, 1000); + }); + }, + handleSave(){ + this.$emit("saveReport"); + }, + handleSaveAs(){ + this.$emit("selectAndSaveReport"); + }, + close() { + this.$emit('closePage'); + }, + }, + } +</script> + +<style scoped> + .ms-header { + border-bottom: 1px solid #E6E6E6; + background-color: #FFF; + } + + .ms-div { + float: left; + margin-left: 20px; + margin-top: 12px; + } + + .ms-span { + margin: 0px 10px 10px; + } + + .ms-header-right { + float: right; + /*width: 320px;*/ + margin-bottom: 10px; + margin-top: 10px; + margin-right: 20px; + } + + .report-alt-ico { + font-size: 17px; + top: auto; + } + + .report-alt-ico:hover { + color: black; + cursor: pointer; + font-size: 18px; + } +</style> diff --git a/frontend/src/business/components/reportstatistics/base/compose/HistoryReportDataCard.vue b/frontend/src/business/components/reportstatistics/base/compose/HistoryReportDataCard.vue new file mode 100644 index 0000000000..737f2d643f --- /dev/null +++ b/frontend/src/business/components/reportstatistics/base/compose/HistoryReportDataCard.vue @@ -0,0 +1,86 @@ +<template> + <div v-if="loadIsOver" v-loading="loading"> + <el-card class="historyCard" v-for="item in reportData" :key="item.id"> + <div slot="header"> + <li style="color:var(--count_number); font-size: 18px"> + <el-input v-if="item.isEdit === 'edit'" size="mini" @blur="updateReport(item)" v-model="item.name"/> + <el-link v-if="item.isEdit !== 'edit'" type="info" @click="selectReport(item.id)" target="_blank" style="color:#303133; font-size: 14px"> + {{ item.name }} + </el-link> + <el-button v-if="showOptionsButton && item.isEdit !== 'edit'" style="float: right; padding: 3px 0; border: 0px;margin-left: 5px" icon="el-icon-delete" size="mini" @click="deleteReport(item.id)"></el-button> + <el-button v-if="showOptionsButton && item.isEdit !== 'edit'" style="float: right; padding: 3px 0; border: 0px" icon="el-icon-edit" size="mini" @click="renameReport(item)"></el-button> + </li> + </div> + <div class="text item"> + <span>{{ item.createTime | timestampFormatDate }}</span> + </div> + </el-card> + </div> +</template> +<script> + +export default { + name: "HistoryReportDataCard", + components: {}, + data() { + return { + loadIsOver: true, + loading: false, + } + }, + props:{ + reportData:Array, + showOptionsButton:Boolean + }, + watch:{ + reportData:{ + handler:function (){ + this.loading = false; + }, + deep:true + } + }, + methods: { + reload(){ + this.loadIsOver = false; + this.$nextTick(() => { + this.loadIsOver = true; + }) + }, + deleteReport(id){ + this.loading = true; + this.$emit("deleteReport",id); + }, + renameReport(item){ + item.isEdit = 'edit'; + this.reload(); + }, + selectReport(id){ + this.$emit("selectReport",id); + }, + updateReport(item){ + let obj = { + name: item.name, + id: item.id + }; + this.$post("/history/report/updateReport", obj, response => { + }); + item.isEdit = ""; + this.reload(); + } + }, +} +</script> + +<style scoped> + +.historyCard{ + border: 0px; +} +/deep/ .el-card__header{ + border: 0px; + padding-bottom: 0px; + padding-top: 5px; +} + +</style> diff --git a/frontend/src/business/components/reportstatistics/router.js b/frontend/src/business/components/reportstatistics/router.js new file mode 100644 index 0000000000..825771c740 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/router.js @@ -0,0 +1,16 @@ +const reportForm = () => import('./ReportAnalysis'); + +export default { + path: "/report", + name: "report", + redirect: "/report/home", + components: { + content: reportForm + }, + children: [ + { + path: 'home', + name: 'reportHome', + }, + ] +} diff --git a/frontend/src/business/components/reportstatistics/testCaseCount/TestCaseCountContainer.vue b/frontend/src/business/components/reportstatistics/testCaseCount/TestCaseCountContainer.vue new file mode 100644 index 0000000000..fb5c3cf686 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/testCaseCount/TestCaseCountContainer.vue @@ -0,0 +1,217 @@ +<template> + <div> + + <el-container v-loading="loading" id="reportAnalysis" style="overflow: scroll"> + <el-container class="ms-row"> + <el-aside v-if="!isHide" :width="!isHide ?'235px':'0px'" :style="{ 'max-height': h-50 + 'px', 'margin-left': '5px'}" > + <history-report-data report-type="TEST_CASE_COUNT" + @selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId" + ref="historyReport"/> + </el-aside> + <el-main class="ms-main" style="padding: 0px 5px 0px"> + <div> + <test-case-count-chart @hidePage="hidePage" @orderCharts="orderCharts" ref="analysisChart" + :chart-width="chartWidth" :load-option="loadOption" :pie-option="pieOption"/> + </div> + <div class="ms-row" v-if="!isHide"> + <test-case-count-table :group-name="getGroupNameStr(options.xaxis)" :show-coloums="options.yaxis" :tableData="tableData"/> + </div> + </el-main> + <el-aside v-if="!isHide" style="height: 100%" :width="!isHide ?'485px':'0px'"> + <test-case-count-filter @filterCharts="filterCharts" ref="countFilter"/> + </el-aside> + </el-container> + </el-container> + </div> +</template> + +<script> +import TestCaseCountChart from "./chart/TestCaseCountChart"; +import TestCaseCountTable from "@/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable"; +import TestCaseCountFilter from "./filter/TestCaseCountFilter"; +import {exportPdf,getCurrentProjectID} from "@/common/js/utils"; +import html2canvas from 'html2canvas'; +import HistoryReportData from "../base/HistoryReportData"; + +export default { + name: "TestCaseCountContainer", + components: {TestCaseCountChart, TestCaseCountTable, TestCaseCountFilter, HistoryReportData}, + data() { + return { + isHide: false, + loading: false, + options: {}, + chartWidth: 0, + tableHeight: 300, + loadOption: { + legend: {}, + xAxis: {}, + yAxis: {}, + label: {}, + tooltip: {}, + series: [] + }, + pieOption: { + legend: {}, + label: {}, + tooltip: {}, + series: [], + title: [], + }, + + tableData: [], + h: document.documentElement.clientHeight - 40, + }; + }, + methods: { + handleExport() { + let name = this.$t('commons.report_statistics.test_case_analysis'); + this.$nextTick(function () { + setTimeout(() => { + html2canvas(document.getElementById('reportAnalysis'), { + scale: 2 + }).then(function (canvas) { + exportPdf(name, [canvas]); + }); + }, 1000); + }); + }, + hidePage(isHide) { + this.isHide = isHide; + }, + close() { + this.$emit('closePage'); + }, + init(opt) { + this.loading = true; + this.options = opt; + this.$post(' /report/test/case/count/getReport', opt, response => { + let data = response.data.barChartDTO; + let pieData = response.data.pieChartDTO; + let selectTableData = response.data.tableDTOs; + this.initPic(data,pieData,selectTableData); + + },error => { + this.loading = false; + }); + }, + initPic(barData,pieData,selectTableData){ + this.loading = true; + if (barData) { + this.loadOption.legend = barData.legend; + this.loadOption.xAxis = barData.xaxis; + this.loadOption.xaxis = barData.xaxis; + this.loadOption.series = barData.series; + this.loadOption.grid = { + bottom: '75px',//距离下边距 + }; + this.loadOption.series.forEach(item => { + item.type = this.$refs.analysisChart.chartType; + }); + } + if (pieData) { + this.pieOption.legend = pieData.legend; + this.pieOption.series = pieData.series; + this.pieOption.title = pieData.title; + this.pieOption.grid = { + bottom: '75px',//距离下边距 + }; + if (pieData.width) { + this.pieOption.width = pieData.width; + this.chartWidth = pieData.width; + } + this.pieOption.series.forEach(item => { + item.type = this.$refs.analysisChart.chartType; + }); + } + if (selectTableData) { + this.tableData = selectTableData; + } + this.loading = false; + this.$refs.analysisChart.reload(); + }, + filterCharts(opt) { + this.init(opt); + }, + orderCharts(order) { + this.options.order = order; + this.filterCharts(this.options); + }, + saveReport() { + let obj = {}; + obj.projectId = getCurrentProjectID(); + obj.selectOption = JSON.stringify(this.options); + let dataOptionObj = { + loadOption: this.loadOption, + pieOption: this.pieOption, + tableData: this.tableData, + }; + obj.dataOption = JSON.stringify(dataOptionObj); + obj.reportType = 'TEST_CASE_COUNT'; + this.$post("/history/report/saveReport", obj, response => { + this.$alert(this.$t('commons.save_success')); + this.$refs.historyReport.initReportData(); + }); + }, + selectReport(selectId){ + if(selectId){ + this.loading = true; + let paramObj = { + id:selectId + } + this.$post('/history/report/selectById',paramObj, response => { + let reportData = response.data; + if(reportData){ + if(reportData.dataOption){ + let dataOptionObj = JSON.parse(reportData.dataOption); + this.initPic(dataOptionObj.loadOption,dataOptionObj.pieOption,dataOptionObj.tableData); + } + if(reportData.selectOption){ + let selectOptionObj = JSON.parse(reportData.selectOption); + this.$refs.countFilter.initSelectOption(selectOptionObj); + } + + this.loading = false; + } + }, (error) => { + this.loading = false; + }); + this.$emit('initHistoryReportId',selectId); + } + }, + removeHistoryReportId(){ + this.$emit('initHistoryReportId',""); + }, + getGroupNameStr(groupName){ + if(groupName === 'creator') { + return this.$t('commons.report_statistics.report_filter.select_options.creator'); + }else if(groupName === 'maintainer'){ + return this.$t('commons.report_statistics.report_filter.select_options.maintainer'); + }else if(groupName === 'casetype'){ + return this.$t('commons.report_statistics.report_filter.select_options.case_type'); + }else if(groupName === 'casestatus'){ + return this.$t('commons.report_statistics.report_filter.select_options.case_status'); + }else if(groupName === 'caselevel'){ + return this.$t('commons.report_statistics.report_filter.select_options.case_level'); + }else { + return ""; + } + }, + selectAndSaveReport(){ + let opt = this.$refs.countFilter.getOption(); + this.options = opt; + this.saveReport(); + } + }, +}; +</script> + +<style scoped> +.ms-row { + padding-top: 5px; +} + +/deep/ .el-main { + padding: 0px 20px 0px; +} +</style> diff --git a/frontend/src/business/components/reportstatistics/testCaseCount/chart/TestCaseCountChart.vue b/frontend/src/business/components/reportstatistics/testCaseCount/chart/TestCaseCountChart.vue new file mode 100644 index 0000000000..5ada182cfa --- /dev/null +++ b/frontend/src/business/components/reportstatistics/testCaseCount/chart/TestCaseCountChart.vue @@ -0,0 +1,284 @@ +<template> + <div v-loading="loading"> + <el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer"> + <el-row class="ms-row"> + <p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.chart')}} </p> + <div class="ms-test-chart-header"> + <el-dropdown @command="exportCommand" :hide-on-click="false"> + <span class="el-dropdown-link"> + {{ $t('commons.export') }}<i class="el-icon-arrow-down el-icon--right"></i> + </span> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item command="jpg">JPG</el-dropdown-item> + <el-dropdown-item command="png">PNG</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + <span style="margin: 0px 10px 10px">|</span> + <el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption"> + <el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/> + </el-select> + <span style="margin: 0px 10px 10px">|</span> + <el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts"> + <el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/> + </el-select> + <span style="margin: 0px 10px 10px">|</span> + <font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/> + <font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" size="lg" @click="unFullScreen"/> + </div> + </el-row> + <el-row style="overflow: auto"> + <ms-chart ref="chart1" v-if="!loading" :options="dataOption" :style="{width: chartWidthNumber+'px', height: (h-50) + 'px'}" class="chart-config" :autoresize="true" id="picChart"/> + </el-row> + </el-card> + </div> +</template> + +<script> + import echarts from "echarts"; + import MsChart from "@/business/components/common/chart/MsChart"; + + export default { + name: "TestCaseCountChart", + components: {MsChart}, + props: { + loadOption: {}, + pieOption: {}, + chartWidth:Number, + }, + data() { + return { + dataOption:{}, + x: 0, + y: 0, + w: document.documentElement.clientWidth - 760, + h: document.documentElement.clientHeight * 0.5 , + chartWidthNumber:document.documentElement.clientWidth - 760, + isFullScreen: false, + originalW: 100, + originalH: 100, + showFullScreen: { + type: Boolean, + default() { + return true; + } + }, + // 头部部分 + chartType: "bar", + charts: [ + {id: 'bar', name: this.$t('commons.report_statistics.bar')}, + {id: 'pie', name: this.$t('commons.report_statistics.pie')} + ], + order: "", + orders: [{id: '', name: '默认排序'},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}], + loading: false, + options: {}, + pieItemOption:{ + dataset: [{ + source: [ + ['Product', 'Sales', 'Price', 'Year'], + ['Cake', 123, 32, 2011], + ['Cereal', 231, 14, 2011], + ['Tofu', 235, 5, 2011], + ['Dumpling', 341, 25, 2011], + ['Biscuit', 122, 29, 2011], + ['Cake', 143, 30, 2012], + ['Cereal', 201, 19, 2012], + ['Tofu', 255, 7, 2012], + ['Dumpling', 241, 27, 2012], + ['Biscuit', 102, 34, 2012], + ['Cake', 153, 28, 2013], + ['Cereal', 181, 21, 2013], + ['Tofu', 395, 4, 2013], + ['Dumpling', 281, 31, 2013], + ['Biscuit', 92, 39, 2013], + ['Cake', 223, 29, 2014], + ['Cereal', 211, 17, 2014], + ['Tofu', 345, 3, 2014], + ['Dumpling', 211, 35, 2014], + ['Biscuit', 72, 24, 2014], + ], + }, { + transform: { + type: 'filter', + config: { dimension: 'Year', value: 2011 } + }, + }, { + transform: { + type: 'filter', + config: { dimension: 'Year', value: 2012 } + } + }, { + transform: { + type: 'filter', + config: { dimension: 'Year', value: 2013 } + } + }], + series: [{ + type: 'pie', radius: 50, center: ['50%', '25%'], + datasetIndex: 1 + }, { + type: 'pie', radius: 50, center: ['50%', '50%'], + datasetIndex: 2 + }, { + type: 'pie', radius: 50, center: ['50%', '75%'], + datasetIndex: 3 + }], + media: [{ + query: { minAspectRatio: 1 }, + option: { + series: [ + { center: ['25%', '50%'] }, + { center: ['50%', '50%'] }, + { center: ['75%', '50%'] } + ] + } + }, { + option: { + series: [ + { center: ['50%', '25%'] }, + { center: ['50%', '50%'] }, + { center: ['50%', '75%'] } + ] + } + }] + }, + } + }, + created() { + this.dataOption = this.loadOption; + }, + watch:{ + chartWidth(){ + this.countChartWidth(); + }, + chartType(){ + this.countChartWidth(); + } + }, + methods: { + countChartWidth(){ + if(this.chartWidth === 0 || this.chartType === 'bar'){ + this.chartWidthNumber = this.w; + }else { + this.chartWidthNumber = this.chartWidth; + } + }, + orderCharts() { + this.$emit('orderCharts', this.order); + }, + generateOption() { + if(this.chartType === 'pie'){ + this.dataOption = this.pieOption; + }else { + this.dataOption = this.loadOption; + } + this.dataOption.series.forEach(item => { + item.type = this.chartType; + }) + this.reload(); + }, + reload() { + this.loading = true + this.$nextTick(() => { + this.loading = false + }) + }, + fullScreen() { + this.originalW = this.w; + this.originalH = this.h; + this.w = document.body.clientWidth - 50; + this.h = document.body.clientHeight; + this.isFullScreen = true; + this.$emit('hidePage', true); + }, + unFullScreen() { + this.w = this.originalW; + this.h = this.originalH; + this.isFullScreen = false; + this.$emit('hidePage', false); + }, + exportCommand(command){ + let fileName = 'report_pic.'+command; + if (document.getElementById('picChart')) { + let chartsCanvas = document.getElementById('picChart').querySelectorAll('canvas')[0] + let mime = 'image/png'; + if(command === 'jpg'){ + mime = 'image/jpg'; + } + if (chartsCanvas) { + // toDataURL()是canvas对象的一种方法,用于将canvas对象转换为base64位编码 + let imageUrl = chartsCanvas && chartsCanvas.toDataURL(mime) + if (navigator.userAgent.indexOf('Trident') > -1) { + // IE11 + let arr = imageUrl.split(',') + // atob() 函数对已经使用base64编码编码的数据字符串进行解码 + let bstr = atob(arr[1]) + let bstrLen = bstr.length + // Uint8Array, 开辟 8 位无符号整数值的类型化数组。内容将初始化为 0 + let u8arr = new Uint8Array(bstrLen) + while (bstrLen--) { + // charCodeAt() 方法可返回指定位置的字符的 Unicode 编码 + u8arr[bstrLen] = bstr.charCodeAt(bstrLen) + } + // msSaveOrOpenBlob 方法允许用户在客户端上保存文件,方法如同从 Internet 下载文件,这是此类文件保存到“下载”文件夹的原因 + window.navigator.msSaveOrOpenBlob(new Blob([u8arr], {type: mime}), fileName ); + } else { + // 其他浏览器 + let $a = document.createElement('a') + $a.setAttribute('href', imageUrl) + $a.setAttribute('download', fileName) + $a.click() + } + } + } + }, + }, + } +</script> + +<style scoped> + + .ms-test-chart-header { + z-index: 999; + width: 380px; + float: right; + margin-right: 10px; + } + + .report-alt-ico { + font-size: 15px; + margin: 0px 10px 0px; + color: #8c939d; + } + + .report-alt-ico:hover { + color: black; + cursor: pointer; + font-size: 18px; + } + + /deep/ .echarts { + height: calc(100vh / 1.95); + } + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 20px 0px; + } + + .ms-row { + padding-top: 10px; + } + + .chart-config { + width: 100%; + } + + /deep/ .el-card__body { + padding: 0px; + } + +</style> diff --git a/frontend/src/business/components/reportstatistics/testCaseCount/filter/TestCaseCountFilter.vue b/frontend/src/business/components/reportstatistics/testCaseCount/filter/TestCaseCountFilter.vue new file mode 100644 index 0000000000..cf43fdbce6 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/testCaseCount/filter/TestCaseCountFilter.vue @@ -0,0 +1,401 @@ +<template> + <div v-loading="loading"> + <el-card :style="{height: h + 'px'}" class="ms-card"> + <el-row style="padding-top: 10px"> + <p class="tip"><span style="margin-left: 5px"></span> {{ $t('commons.report_statistics.options') }}</p> + </el-row> + <el-row class="ms-row"> + <p>{{ $t('commons.report_statistics.report_filter.xaxis') }}</p> + <el-select class="ms-http-select" size="small" v-model="option.xaxis" style="width: 100%"> + <el-option v-for="item in xAxisOptions" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </el-row> + <el-row class="ms-row"> + <p>{{ $t('commons.report_statistics.report_filter.yaxis') }}</p> + <el-select class="ms-http-select" size="small" v-model="option.yaxis" multiple style="width: 100%"> + <el-option v-for="item in yAxisOptions" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </el-row> + <el-row class="ms-row"> + <p>{{ $t('commons.create_time')}}</p> + <div style="width: 25%;float: left"> + <el-select class="ms-http-select" size="small" v-model="option.timeType" > + <el-option v-for="item in timeTypeOptions" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </div> + <div v-if="option.timeType === 'fixedTime'" style="width: 70%;margin-left: 20px;float: left"> + <el-date-picker + size="small" + v-model="option.times" + type="datetimerange" + value-format="timestamp" + :range-separator="$t('api_monitor.to')" + :start-placeholder="$t('commons.date.start_date')" + :end-placeholder="$t('commons.date.end_date')" + :picker-options="datePickerOptions" + style="margin-left: 10px;width: 100%"> + </el-date-picker> + </div> + <div v-if="option.timeType === 'dynamicTime'" style="width: 70%;margin-left: 20px;float: left"> + <span style="width: 20%">{{ $t('commons.report_statistics.report_filter.recently') }}</span> + <el-select class="ms-http-select" size="small" v-model="option.timeFilter.timeRange" style="width: 30%;margin-left: 10px;width: 40%"> + <el-option v-for="item in timeRangeNumberMax" :key="item" :label="item" :value="item"/> + </el-select> + <el-select class="ms-http-select" size="small" v-model="option.timeFilter.timeRangeUnit" + @change="timeRangeUnitChange" + style="width: 30%;margin-left: 10px;width: 40%"> + <el-option v-for="item in timeRangeUnitOptions" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </div> + </el-row> + <el-row class="ms-row" style="margin-left: 0px;margin-right: 0px; margin-top: 20px"> + <el-collapse v-model="collapseActiveNames"> + <el-collapse-item :title="$t('commons.report_statistics.report_filter.more_options')" name="1"> + <el-container> + <el-aside width="73px" style="overflow: hidden"> + <div v-if="option.filters.length > 1" style="height: 100%" id="moreOptionTypeDiv"> + <div class="top-line-box" :style="{ height:lineDivHeight+'px'}"> + </div> + <div> + <el-select class="ms-http-select" size="small" v-model="option.filterType" style="width: 70px"> + <el-option v-for="item in filterTypes" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </div> + <div class="bottom-line-box" :style="{ height:lineDivHeight+'px'}"> + </div> + </div> + </el-aside> + <el-main v-if="optionLoad" style="padding: 0px"> + <el-row v-for="filterItem in option.filters" :key="filterItem.id" style="margin-bottom: 5px"> + <el-col :span="24"> + <el-select style="width: 100px" class="ms-http-select" size="small" v-model="filterItem.type"> + <el-option v-for="item in getFilterOptionKey(filterItem.type)" :key="item.type" :label="item.name" :value="item.type"/> + </el-select> + <span style="margin-left:10px;margin-right:10px">{{ $t('commons.report_statistics.report_filter.belone') }}</span> + + <el-select style="width:173px" :collapse-tags="true" class="ms-http-select" size="small" multiple filterable v-model="filterItem.values" v-if="getFilterOptions(filterItem.type).length > 0"> + <el-option v-for="itemOption in getFilterOptions(filterItem.type)" :key="itemOption.id" :label="itemOption.label" :value="itemOption.id"/> + </el-select> + <el-input style="width:173px" v-model="filterItem.value" size="small" v-else ></el-input> + <el-button @click="addFilterOptions(filterItem.type)" + @keydown.enter.native.prevent + type="primary" + icon="el-icon-plus" + circle + style="color:white;padding: 0px 0.1px;width: 20px;height: 20px;margin-left:5px;" + size="mini"/> + <el-button @click="removeFilterOptions(filterItem.type)" + @keydown.enter.native.prevent + type="danger" + icon="el-icon-minus" + circle + style="color:white;padding: 0px 0.1px;width: 20px;height: 20px;margin-left:5px;" + size="mini"/> + </el-col> + </el-row> + </el-main> + </el-container> + </el-collapse-item> + </el-collapse> + </el-row> + + + <el-row type="flex"> + <el-col style="height: 100%" :span="4" > + + </el-col> + <el-col :span="20"> + + </el-col> + </el-row> + <el-row align="middle"> + <el-button style="margin-left: 200px;margin-top: 20px" type="primary" size="mini" @click="init">{{ $t('commons.confirm') }}</el-button> + </el-row> + </el-card> + </div> +</template> + +<script> +import {getCurrentProjectID, getUUID} from "@/common/js/utils"; +import MsSelectTree from "@/business/components/common/select-tree/SelectTree"; + +export default { + name: "TestAnalysisTable", + components: {MsSelectTree}, + data() { + return { + collapseActiveNames: "", + option: { + xaxis: "creator", + yaxis: ["testCase","apiCase","scenarioCase","loadCase"], + timeType: "dynamicTime", + projectId: getCurrentProjectID(), + filterType: "And", + timeFilter:{ + timeRange: 7, + timeRangeUnit: "day", + }, + times: [new Date().getTime() - 6 * 24 * 3600 * 1000, new Date().getTime()], + filters:[ + { + type:"", + name:"", + compType:"input", + isShow:false, + }, + ], + }, + h: document.documentElement.clientHeight + 80, + lineDivHeight: 0, + disabled: false, + loading: false, + optionLoad: true, + result: {}, + items: [], + modules: [], + xAxisOptions: [ + {id: 'creator', label: this.$t('commons.report_statistics.report_filter.select_options.creator')}, + {id: 'maintainer', label: this.$t('commons.report_statistics.report_filter.select_options.maintainer')}, + {id: 'casetype', label: this.$t('commons.report_statistics.report_filter.select_options.case_type')}, + {id: 'casestatus', label: this.$t('commons.report_statistics.report_filter.select_options.case_status')}, + {id: 'caselevel', label: this.$t('commons.report_statistics.report_filter.select_options.case_level')}, + ], + yAxisOptions: [ + {id: 'testCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.functional')}, + {id: 'apiCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.api')}, + {id: 'scenarioCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.scene')}, + {id: 'loadCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.load')}, + ], + filterTypes: [ + {id: 'And', label: 'And'}, + {id: 'Or', label: 'Or'}, + ], + timeTypeOptions: [ + {id: 'fixedTime', label: this.$t('commons.report_statistics.report_filter.time_options.fixed_time')}, + {id: 'dynamicTime', label: this.$t('commons.report_statistics.report_filter.time_options.dynamic_time')}, + ], + timeRangeNumberMax: 31, + timeRangeUnitOptions: [ + {id: 'day', label: this.$t('commons.report_statistics.report_filter.time_options.day')}, + {id: 'month', label: this.$t('commons.report_statistics.report_filter.time_options.month')}, + {id: 'year', label: this.$t('commons.report_statistics.report_filter.time_options.year')}, + ], + priorityFilters: [ + {id: 'P0', label: 'P0'}, + {id: 'P1', label: 'P1'}, + {id: 'P2', label: 'P2'}, + {id: 'P3', label: 'P3'} + ], + moduleObj: { + id: 'id', + label: 'name', + }, + moreOptionsSelectorKeys:[ + { + type:"casetype", + name:this.$t('commons.report_statistics.report_filter.select_options.case_type'), + }, + { + type:"creator", + name:this.$t('commons.report_statistics.report_filter.select_options.creator'), + }, + { + type:"maintainer", + name:this.$t('commons.report_statistics.report_filter.select_options.maintainer'), + }, + { + type:"casestatus", + name:this.$t('commons.report_statistics.report_filter.select_options.case_status'), + }, + { + type:"caselevel", + name:this.$t('commons.report_statistics.report_filter.select_options.case_level'), + }, + ], + moreOptionsSelectorValues: { + id: 'id', + label: 'label', + }, + datePickerOptions: { + disabledDate: (time) => { + let nowDate = new Date(); + let oneDay = 1000 * 60 * 60 * 24; + let oneYearLater = new Date(nowDate.getTime() + (oneDay * 365)); + return time.getTime() > nowDate || time.getTime() > oneYearLater;//注意是||不是&& + } + }, + }; + }, + created() { + this.init(); + this.initMoreOptionsSelectorValues(); + }, + computed: { + }, + watch: { + option: { + handler: function () { + this.$nextTick(() => { + this.lineDivHeight = 0; + // let elem = document.getElementById("moreOptionTypeDiv"); + if(this.option.filters.length > 1){ + let countPageHeight = (this.option.filters.length)* 32 + (this.option.filters.length-1)*5; + if(countPageHeight > 32){ + this.lineDivHeight = (countPageHeight-32)/2-11; + } + } + }); + }, + deep: true + } + }, + methods: { + initSelectOption(opt){ + this.loading = true; + this.option = opt; + this.$nextTick(() => { + this.loading = false; + }); + }, + addFilterOptions: function (type){ + this.optionLoad = false; + let otherOptionKeys = this.getFilterOptionKey(""); + if(otherOptionKeys.length > 0 && this.option.filters.length < 5) { + let addOptions = { + type: "", + id: getUUID(), + name: "", + compType: "selector", + isShow: false, + itemOptions: this.priorityFilters, + }; + this.option.filters.push(addOptions); + } else { + this.$alert(this.$t('commons.report_statistics.alert.cannot_add_more_options')); + } + this.$nextTick(() => { + this.optionLoad = true; + }); + }, + getFilterOptions(type){ + let optionArray = []; + if(this.moreOptionsSelectorValues && this.moreOptionsSelectorValues[type]){ + optionArray = this.moreOptionsSelectorValues[type]; + } + return optionArray; + }, + getFilterOptionKey(type){ + let optionArray = []; + for(let i = 0; i < this.moreOptionsSelectorKeys.length; i++){ + let keyObj = this.moreOptionsSelectorKeys[i]; + let inOptions = false; + if(keyObj.type !== type){ + for(let j = 0; j < this.option.filters.length; j ++){ + if(keyObj.type === this.option.filters[j].type){ + inOptions = true; + } + } + } + if(!inOptions){ + optionArray.push(keyObj); + } + } + return optionArray; + }, + removeFilterOptions: function (type){ + let removeOptionsIndex = -1; + for(let index = 0; index < this.option.filters.length; index ++){ + let item = this.option.filters[index]; + if(item.type === type){ + removeOptionsIndex = index; + } + } + if(removeOptionsIndex >= 0){ + this.option.filters.splice(removeOptionsIndex,1); + } + if(this.option.filters.length === 0){ + let addOptions = { + type: "", + id: getUUID(), + name: "", + compType: "selector", + isShow: false, + itemOptions: this.priorityFilters, + }; + this.option.filters.push(addOptions); + } + }, + init: function () { + this.$emit('filterCharts', this.option); + }, + onTimeChange() { + if (this.option.times[1] > new Date().getTime()) { + this.$alert(this.$t('commons.report_statistics.alert.end_time_cannot_over_than_start_time')); + } + }, + initMoreOptionsSelectorValues() { + let selectParam = { + projectId:getCurrentProjectID() + }; + this.$post('/report/test/case/count/initDatas',selectParam, response => { + this.moreOptionsSelectorValues = response.data; + }); + }, + timeRangeUnitChange(val){ + if(val === 'day'){ + this.timeRangeNumberMax = 31; + }else if(val === 'month'){ + this.timeRangeNumberMax = 12; + }else { + this.timeRangeNumberMax = 1; + } + this.option.timeFilter.timeRange = 1; + }, + getOption(){ + return this.option; + } + }, +}; +</script> + +<style scoped> + +.tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 10px 0px; +} + +.ms-row { + margin: 0px 10px 0px; +} + +.ms-card { + width: 480px; +} + +.top-line-box{ + border-top: 1px solid; + border-left: 1px solid; + margin-left: 32px; + margin-top: 10px; + border-top-left-radius: 10px; +} + +.bottom-line-box{ + border-bottom: 1px solid; + border-left: 1px solid; + margin-left: 32px; + border-bottom-left-radius: 10px; +} + +/deep/ .el-select__tags-text { + display: inline-block; + max-width: 50px; + overflow: hidden; + text-overflow:ellipsis; + white-space: nowrap; +} +</style> diff --git a/frontend/src/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable.vue b/frontend/src/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable.vue new file mode 100644 index 0000000000..833ec2da74 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable.vue @@ -0,0 +1,113 @@ +<template> + <div v-loading="loading" class="ms-div"> + <el-card :style="{ width: w+'px'}"> + <el-row style="padding-top: 10px"> + <p class="tip"><span style="margin-left: 5px"></span>{{$t('commons.report_statistics.excel')}} </p> + </el-row> + <el-row> + <el-table + :data="tableData" + :max-height="tableHeight" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + row-key="id" + border + class="ms-table"> + <el-table-column + prop="name" + :label="groupName"> + </el-table-column> + <el-table-column + prop="allCount" + label="总计"> + </el-table-column> + <el-table-column + prop="testCaseCount" + :label="$t('api_test.home_page.failed_case_list.table_value.case_type.functional')" + v-if="isShowColumn('testCase')" + > + </el-table-column> + <el-table-column + prop="apiCaseCount" + :label="$t('api_test.home_page.failed_case_list.table_value.case_type.api')" + v-if="isShowColumn('apiCase')" + > + </el-table-column> + <el-table-column + prop="scenarioCaseCount" + :label="$t('api_test.home_page.failed_case_list.table_value.case_type.scene')" + v-if="isShowColumn('scenarioCase')" + > + </el-table-column> + <el-table-column + prop="loadCaseCount" + :label="$t('api_test.home_page.failed_case_list.table_value.case_type.load')" + v-if="isShowColumn('loadCase')" + > + </el-table-column> + </el-table> + </el-row> + </el-card> + </div> +</template> + +<script> + export default { + name: "TestAnalysisTable", + components: {}, + props: { + tableData: Array, + groupName: String, + showColoums: Array, + }, + data() { + return { + tableHeight : "100px", + w: document.documentElement.clientWidth - 760, + loading: false, + } + }, + created() { + this.getTableHeight(); + }, + methods: { + isShowColumn(type){ + if(this.showColoums){ + return this.showColoums.findIndex(item => item=== type) >= 0; + }else { + return false; + } + + }, + getTableHeight(){ + let countNumber = document.documentElement.clientHeight * 0.4 /1 - 140; + countNumber = Math.ceil(countNumber); + this.tableHeight = countNumber + 'px'; + } + }, + } +</script> + +<style scoped> + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 20px 0px; + } + + .ms-div { + margin-bottom: 20px; + } + + .ms-table { + width: 95%; + margin: 20px; + } + + /deep/ .el-card__body { + padding: 0px; + } + +</style> diff --git a/frontend/src/business/components/reportstatistics/track/TestAnalysisContainer.vue b/frontend/src/business/components/reportstatistics/track/TestAnalysisContainer.vue new file mode 100644 index 0000000000..a68a6ae9ee --- /dev/null +++ b/frontend/src/business/components/reportstatistics/track/TestAnalysisContainer.vue @@ -0,0 +1,168 @@ +<template> + <div :style="{ height: h + 'px'}"> + <el-container v-loading="loading" id="reportAnalysis" style="overflow: scroll"> + <el-container class="ms-row"> + <el-aside :width="!isHide ?'235px':'0px'" style="margin-left: 5px; max-height: 843px"> + <history-report-data report-type="TEST_CASE_ANALYSIS" + @selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId" + ref="historyReport"/> + </el-aside> + <el-main class="ms-main"> + <div> + <test-analysis-chart @hidePage="hidePage" @orderCharts="orderCharts" ref="analysisChart" :load-option="loadOption"/> + </div> + <div class="ms-row" v-if="!isHide"> + <test-analysis-table :tableData="tableData"/> + </div> + </el-main> + <el-aside :width="!isHide ?'485px':'0px'"> + <test-analysis-filter @filterCharts="filterCharts" ref="analysisFilter"/> + </el-aside> + </el-container> + </el-container> + </div> +</template> + +<script> + import TestAnalysisChart from "./chart/TestAnalysisChart"; + import TestAnalysisTable from "./table/TestAnalysisTable"; + import TestAnalysisFilter from "./filter/TestAnalysisFilter"; + import {exportPdf, getCurrentProjectID} from "@/common/js/utils"; + import html2canvas from 'html2canvas'; + import HistoryReportData from "../base/HistoryReportData"; + + export default { + name: "TestAnalysisContainer", + components: {TestAnalysisChart, TestAnalysisTable, TestAnalysisFilter, HistoryReportData}, + data() { + return { + isHide: false, + loading: false, + options: {}, + loadOption: { + legend: {}, + xAxis: {}, + yAxis: {}, + label: {}, + tooltip: {}, + series: [] + }, + tableData: [], + h: document.documentElement.clientHeight - 40, + } + }, + methods: { + handleExport() { + let name = this.$t('commons.report_statistics.test_case_analysis'); + this.$nextTick(function () { + setTimeout(() => { + html2canvas(document.getElementById('reportAnalysis'), { + scale: 2 + }).then(function (canvas) { + exportPdf(name, [canvas]); + }); + }, 1000); + }); + }, + hidePage(isHide) { + this.isHide = isHide; + }, + close() { + this.$emit('closePage'); + }, + init(opt) { + this.loading = true; + this.options = opt; + this.$post(' /report/test/analysis/getReport', opt, response => { + let data = response.data.chartDTO; + let tableDTOs = response.data.tableDTOs; + this.initPic(data,tableDTOs); + }); + }, + filterCharts(opt) { + this.init(opt); + }, + orderCharts(order) { + this.options.order = order; + this.filterCharts(this.options); + }, + saveReport() { + let obj = {}; + obj.projectId = getCurrentProjectID(); + obj.selectOption = JSON.stringify(this.options); + let dataOptionObj = { + loadOption: this.loadOption, + pieOption: this.pieOption, + tableData: this.tableData, + }; + obj.dataOption = JSON.stringify(dataOptionObj); + obj.reportType = 'TEST_CASE_ANALYSIS'; + this.$post("/history/report/saveReport", obj, response => { + this.$alert(this.$t('commons.save_success')); + this.$refs.historyReport.initReportData(); + }); + }, + initPic(loadOption,tableData){ + this.loading = true; + if (loadOption) { + this.loadOption.legend = loadOption.legend; + this.loadOption.xAxis = loadOption.xAxis; + this.loadOption.series = loadOption.series; + this.loadOption.grid = { + bottom: '75px',//距离下边距 + } + this.loadOption.series.forEach(item => { + item.type = this.$refs.analysisChart.chartType; + }) + } + if (tableData) { + this.tableData = tableData; + } + this.loading = false; + }, + selectReport(selectId){ + if(selectId){ + this.loading = true; + let paramObj = { + id:selectId + } + this.$post('/history/report/selectById',paramObj, response => { + let reportData = response.data; + if(reportData){ + if(reportData.dataOption){ + let dataOptionObj = JSON.parse(reportData.dataOption); + this.initPic(dataOptionObj.loadOption,dataOptionObj.pieOption,dataOptionObj.tableData); + } + if(reportData.selectOption){ + let selectOptionObj = JSON.parse(reportData.selectOption); + this.$refs.analysisFilter.initSelectOption(selectOptionObj); + } + } + this.loading = false; + }, (error) => { + this.loading = false; + }); + this.$emit('initHistoryReportId',selectId); + } + }, + removeHistoryReportId(){ + this.$emit('initHistoryReportId',""); + }, + selectAndSaveReport(){ + let opt = this.$refs.analysisFilter.getOption(); + this.options = opt; + this.saveReport(); + } + }, + } +</script> + +<style scoped> + .ms-row { + padding-top: 10px; + } + + /deep/ .el-main { + padding: 0px 20px 0px; + } +</style> diff --git a/frontend/src/business/components/reportstatistics/track/chart/TestAnalysisChart.vue b/frontend/src/business/components/reportstatistics/track/chart/TestAnalysisChart.vue new file mode 100644 index 0000000000..cc88b0abc2 --- /dev/null +++ b/frontend/src/business/components/reportstatistics/track/chart/TestAnalysisChart.vue @@ -0,0 +1,143 @@ +<template> + <div v-loading="loading"> + <el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer"> + <el-row class="ms-row"> + <p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.chart')}} </p> + <div class="ms-test-chart-header"> + <el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption"> + <el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/> + </el-select> + <span style="margin: 0px 10px 10px">|</span> + <el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts"> + <el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/> + </el-select> + <span style="margin: 0px 10px 10px">|</span> + <font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/> + <font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" size="lg" @click="unFullScreen"/> + </div> + </el-row> + <el-row> + <ms-chart ref="chart1" :options="loadOption" class="chart-config" :autoresize="true"/> + </el-row> + </el-card> + </div> +</template> + +<script> + // 这个引用不能删除,删除后图例不显示 + import echarts from "echarts"; + import MsChart from "@/business/components/common/chart/MsChart"; + + export default { + name: "TestAnalysisChart", + components: {MsChart}, + props: { + loadOption: {}, + }, + data() { + return { + x: 0, + y: 0, + w: document.documentElement.clientWidth - 760, + h: document.documentElement.clientHeight / 1.7, + isFullScreen: false, + originalW: 100, + originalH: 100, + showFullScreen: { + type: Boolean, + default() { + return true; + } + }, + // 头部部分 + chartType: "line", + charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}], + order: "", + orders: [{id: '', name: '默认排序'},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}], + loading: false, + options: {}, + } + }, + methods: { + orderCharts() { + this.$emit('orderCharts', this.order); + }, + generateOption() { + this.loadOption.series.forEach(item => { + item.type = this.chartType; + }) + this.reload(); + }, + reload() { + this.loading = true + this.$nextTick(() => { + this.loading = false + }) + }, + fullScreen() { + this.originalW = this.w; + this.originalH = this.h; + this.w = document.body.clientWidth - 50; + this.h = document.body.clientHeight; + this.isFullScreen = true; + this.$emit('hidePage', true); + }, + unFullScreen() { + this.w = this.originalW; + this.h = this.originalH; + this.isFullScreen = false; + this.$emit('hidePage', false); + }, + getOptions(){ + return this.loadOption; + } + }, + } +</script> + +<style scoped> + + .ms-test-chart-header { + z-index: 999; + width: 320px; + float: right; + margin-right: 10px; + } + + .report-alt-ico { + font-size: 15px; + margin: 0px 10px 0px; + color: #8c939d; + } + + .report-alt-ico:hover { + color: black; + cursor: pointer; + font-size: 18px; + } + + /deep/ .echarts { + height: calc(100vh / 1.95); + } + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 20px 0px; + } + + .ms-row { + padding-top: 10px; + } + + .chart-config { + width: 100%; + } + + /deep/ .el-card__body { + padding: 0px; + } + +</style> diff --git a/frontend/src/business/components/reportstatistics/track/filter/TestAnalysisFilter.vue b/frontend/src/business/components/reportstatistics/track/filter/TestAnalysisFilter.vue new file mode 100644 index 0000000000..5cb3e70a8e --- /dev/null +++ b/frontend/src/business/components/reportstatistics/track/filter/TestAnalysisFilter.vue @@ -0,0 +1,225 @@ +<template> + <div v-loading="loading"> + <el-card :style="{height: h + 'px'}" class="ms-card"> + <el-row style="padding-top: 10px"> + <p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.options')}}</p> + </el-row> + <el-row class="ms-row"> + <p>{{$t('commons.report_statistics.type')}}</p> + <el-checkbox v-model="option.createCase">{{$t('commons.report_statistics.add_case')}}</el-checkbox> + <el-checkbox v-model="option.updateCase">{{$t('commons.report_statistics.change_case')}}</el-checkbox> + </el-row> + <el-row class="ms-row"> + <p> {{$t('api_monitor.date')}}</p> + <el-date-picker + size="small" + v-model="option.times" + type="datetimerange" + value-format="timestamp" + range-separator="至" + start-placeholder="开始日期" + end-placeholder="结束日期" + :picker-options="datePickerOptions" + style="width: 100%"> + </el-date-picker> + </el-row> + <el-row class="ms-row"> + <p>{{$t('commons.project')}}</p> + <ms-select-tree size="small" :data="items" :default-key="projectDefaultKey" @getValue="setProjects" :obj="obj" clearable checkStrictly multiple ref="projectSelector"/> + </el-row> + <el-row class="ms-row"> + <p>{{$t('test_track.module.module')}}</p> + <ms-select-tree size="small" :data="modules" :default-key="moduleDefaultKey" :disabled="disabled" @getValue="setModules" :obj="moduleObj" clearable checkStrictly multiple ref="moduleSelector"/> + </el-row> + <el-row class="ms-row"> + <p>{{$t('api_test.automation.case_level')}}</p> + <el-select class="ms-http-select" size="small" v-model="option.prioritys" multiple style="width: 100%"> + <el-option v-for="item in priorityFilters" :key="item.id" :label="item.label" :value="item.id"/> + </el-select> + </el-row> + <el-row class="ms-row"> + <p>{{$t('test_track.case.maintainer')}}</p> + <ms-select-tree size="small" :data="maintainerOptions" :default-key="userDefaultKey" @getValue="setUsers" :obj="moduleObj" clearable checkStrictly multiple ref="userSelector"/> + </el-row> + + </el-card> + </div> +</template> + +<script> + import {getCurrentProjectID} from "@/common/js/utils"; + import MsSelectTree from "@/business/components/common/select-tree/SelectTree"; + + export default { + name: "TestAnalysisTable", + components: {MsSelectTree}, + data() { + return { + option: {createCase: true, updateCase: true, projects: [], times: [new Date().getTime() - 6 * 24 * 3600 * 1000, new Date().getTime()]}, + h: document.documentElement.clientHeight + 80, + disabled: false, + loading: false, + result: {}, + items: [], + projectDefaultKey:[], + moduleDefaultKey:[], + userDefaultKey:[], + modules: [], + maintainerOptions: [], + priorityFilters: [ + {id: 'P0', label: 'P0'}, + {id: 'P1', label: 'P1'}, + {id: 'P2', label: 'P2'}, + {id: 'P3', label: 'P3'} + ], + syncReport: true, + moduleObj: { + id: 'id', + label: 'name', + }, + obj: { + id: 'id', + label: 'label', + }, + datePickerOptions: { + disabledDate: (time) => { + let nowDate = new Date(); + let oneDay = 1000 * 60 * 60 * 24; + let oneYearLater = new Date(nowDate.getTime() + (oneDay * 365)); + return time.getTime() > nowDate || time.getTime() > oneYearLater;//注意是||不是&& + } + }, + } + }, + created() { + this.init(); + this.initUsers(); + }, + watch: { + option: { + handler: function () { + if(this.syncReport){ + this.$emit('filterCharts', this.option); + } + }, + deep: true + } + }, + methods: { + initSelectOption(opt){ + if(opt){ + this.syncReport = false; + this.loading = true; + this.option = opt; + if(opt.projects){ + this.projectDefaultKey = opt.projects; + }else { + this.projectDefaultKey = []; + } + if(opt.modules && this.projectDefaultKey.length === 1){ + this.moduleDefaultKey = opt.modules; + }else { + this.moduleDefaultKey = []; + } + if(opt.users){ + this.userDefaultKey = opt.users; + }else { + this.userDefaultKey = []; + } + this.$nextTick(() => { + this.loading = false; + this.syncReport = true; + }); + } + }, + init: function () { + this.result = this.$get("/project/listAll", response => { + let projects = response.data; + if (projects) { + this.items = []; + projects.forEach(item => { + let data = {id: item.id, label: item.name}; + this.items.push(data); + }) + } + }) + }, + onTimeChange() { + if (this.option.times[1] > new Date().getTime()) { + this.$error("结束时间不能超过当前时间"); + } + }, + initModule() { + this.result = this.$get("/case/node/list/" + this.option.projects[0], response => { + this.modules = response.data; + this.$refs.moduleSelector.setKeys(this.moduleDefaultKey); + }) + }, + initUsers() { + this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => { + this.maintainerOptions = response.data; + }); + }, + setProjects(key, data) {//获取子组件值 + if(!key || key === ""){ + key = []; + } + this.option.projects = key; + this.modules = []; + if (key && key.length > 1) { + this.moduleDefaultKey = []; + this.disabled = true; + } else { + this.disabled = false; + } + if (this.option.projects && this.option.projects.length == 1) { + this.initModule(); + } + if(this.syncReport){ + this.$emit('filterCharts', this.option); + } + }, + setModules(key, data) {//获取子组件值 + if(!key || key === ""){ + key = []; + } + this.option.modules = key; + if(this.syncReport){ + this.$emit('filterCharts', this.option); + } + }, + setUsers(key, data) {//获取子组件值 + if(!key || key === ""){ + key = []; + } + this.option.users = key; + if(this.syncReport){ + this.$emit('filterCharts', this.option); + } + }, + getOption(){ + return this.option; + } + }, + } +</script> + +<style scoped> + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 10px 0px; + } + + .ms-row { + margin: 0px 10px 0px; + } + + .ms-card { + width: 480px; + } + +</style> diff --git a/frontend/src/business/components/reportstatistics/track/table/TestAnalysisTable.vue b/frontend/src/business/components/reportstatistics/track/table/TestAnalysisTable.vue new file mode 100644 index 0000000000..78613a4f3a --- /dev/null +++ b/frontend/src/business/components/reportstatistics/track/table/TestAnalysisTable.vue @@ -0,0 +1,77 @@ +<template> + <div v-loading="loading" class="ms-div"> + <el-card :style="{ width: w+'px'}"> + <el-row style="padding-top: 10px"> + <p class="tip"><span style="margin-left: 5px"></span>{{$t('commons.report_statistics.excel')}} </p> + </el-row> + <el-row> + <el-table + :data="tableData" + :height="h" + :tree-props="{children: 'children', hasChildren: 'hasChildren'}" + row-key="id" + border + class="ms-table"> + <el-table-column + prop="name" + :label="$t('api_monitor.date')" + sortable> + </el-table-column> + <el-table-column + prop="createCount" + :label="$t('commons.report_statistics.add_case')" + sortable> + </el-table-column> + <el-table-column + prop="updateCount" + sortable + :label="$t('commons.report_statistics.change_case')"> + </el-table-column> + </el-table> + </el-row> + </el-card> + </div> +</template> + +<script> + export default { + name: "TestAnalysisTable", + components: {}, + props: { + tableData: Array, + }, + data() { + return { + w: document.documentElement.clientWidth - 760, + h: document.body.clientHeight / 2.3 - 20, + loading: false, + } + }, + methods: {}, + } +</script> + +<style scoped> + + .tip { + float: left; + font-size: 14px; + border-radius: 2px; + border-left: 2px solid #783887; + margin: 0px 20px 0px; + } + + .ms-div { + margin-bottom: 20px; + } + + .ms-table { + width: 95%; + margin: 20px; + } + + /deep/ .el-card__body { + padding: 0px; + } + +</style> diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index d99efd7e4f..e0da1d3ef6 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit d99efd7e4f70846553444065fab8159e65035525 +Subproject commit e0da1d3ef611b0901e42520f6d68a04cabec4c08 diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index e0e6684b0c..e03bc3969e 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -43,6 +43,7 @@ export default { annotation: 'Annotation', clear: 'Clear', save: 'Save', + save_as: 'Save as', update: 'Update', save_success: 'Saved successfully', delete_success: 'Deleted successfully', diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 7ad15e8b90..92ce7bcaf8 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -43,6 +43,7 @@ export default { annotation: '注释', clear: '清空', save: '保存', + save_as: '另存为', update: '更新', save_success: '保存成功', delete_success: '删除成功', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 83ec904f64..522716d107 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -43,6 +43,7 @@ export default { annotation: '註釋', clear: '清空', save: '保存', + save_as: '另存為', update: '更新', save_success: '保存成功', delete_success: '刪除成功', From ac546e04c12b8d6943548fca30637e86cc048278 Mon Sep 17 00:00:00 2001 From: song-tianyang <tianyang.song@fit2cloud.com> Date: Sun, 26 Sep 2021 18:08:15 +0800 Subject: [PATCH 46/74] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E5=AE=9A?= =?UTF-8?q?=E4=B9=89):=20=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E7=BC=96=E8=BE=91=E6=A1=88=E4=BE=8B=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E7=8A=B6=E6=80=81=E6=94=B9=E4=B8=BA=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接口定义列表编辑案例时的默认状态改为直接展开 --- .../components/api/definition/components/case/ApiCaseList.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue index 5c78456dad..e5bf2e2d75 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue @@ -303,6 +303,10 @@ export default { const index = this.runData.findIndex(d => d.name === apiCase.id); if (index !== -1) { apiCase.active = true; + }else { + if(this.condition.id && this.condition.id != ""){ + apiCase.active = true; + } } }); this.apiCaseList = data; From 7f0bd6085838bb049545705d04e67e85c94110e5 Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Sun, 26 Sep 2021 19:10:59 +0800 Subject: [PATCH 47/74] =?UTF-8?q?fix=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=5F=E8=AF=B7=E6=B1=82=E4=BD=93=E4=B8=BAform-data=5F=E7=9A=84jso?= =?UTF-8?q?n=E5=8F=82=E6=95=B0=E7=9A=84=E5=8F=AF=E4=BB=A5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8F=82=E6=95=B0--story=3D1003216=20--user=3D?= =?UTF-8?q?=E7=8E=8B=E5=AD=9D=E5=88=9A=20=E8=AF=B7=E6=B1=82=E4=BD=93?= =?UTF-8?q?=E4=B8=BAform-data=20=E7=9A=84json=E5=8F=82=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=A2=9E=E5=8A=A0=E5=8F=82=E6=95=B0=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E5=A1=AB=E5=86=99=20https://www.tapd.cn/55049933/s/10?= =?UTF-8?q?51283?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/definition/components/ApiVariable.vue | 24 +++- .../definition/components/ApiVariableJson.vue | 108 ++++++++++++++++++ .../definition/components/body/ApiBody.vue | 40 +++++-- 3 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 frontend/src/business/components/api/definition/components/ApiVariableJson.vue diff --git a/frontend/src/business/components/api/definition/components/ApiVariable.vue b/frontend/src/business/components/api/definition/components/ApiVariable.vue index 02e24adaac..13f2d3b02e 100644 --- a/frontend/src/business/components/api/definition/components/ApiVariable.vue +++ b/frontend/src/business/components/api/definition/components/ApiVariable.vue @@ -24,6 +24,7 @@ @change="typeChange(item)"> <el-option value="text"/> <el-option value="file"/> + <el-option value="json"/> </el-select> </template> </el-input> @@ -90,6 +91,7 @@ <ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario" :parameters="parameters" :current-item="currentItem"/> + <ms-api-variable-json ref="variableJson" @callback="callback"/> <api-variable-setting ref="apiVariableSetting"/> @@ -101,6 +103,7 @@ import {KeyValue, Scenario} from "../model/ApiTestModel"; import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants"; import MsApiVariableAdvance from "./ApiVariableAdvance"; + import MsApiVariableJson from "./ApiVariableJson"; import MsApiBodyFileUpload from "./body/ApiBodyFileUpload"; import {REQUIRED} from "../model/JsonData"; import Vue from 'vue'; @@ -108,7 +111,7 @@ export default { name: "MsApiVariable", - components: {ApiVariableSetting, MsApiBodyFileUpload, MsApiVariableAdvance}, + components: {ApiVariableSetting, MsApiBodyFileUpload, MsApiVariableAdvance , MsApiVariableJson}, props: { keyPlaceholder: String, valuePlaceholder: String, @@ -140,7 +143,7 @@ {name: this.$t('commons.selector.not_required'), id: false} ], isSelectAll: true, - isActive: true + isActive: true, } }, watch: { @@ -237,14 +240,22 @@ return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1); }, advanced(item) { - this.$refs.variableAdvance.open(); - this.currentItem = item; + if (item.type === 'json'){ + this.$refs.variableJson.open(item); + this.currentItem = item; + }else { + this.$refs.variableAdvance.open(); + this.currentItem = item; + } + }, typeChange(item) { if (item.type === 'file') { item.contentType = 'application/octet-stream'; - } else { + } else if (item.type === 'text'){ item.contentType = 'text/plain'; + } else { + item.contentType = 'application/json' } this.reload(); }, @@ -266,6 +277,9 @@ }, openApiVariableSetting(item) { this.$refs.apiVariableSetting.open(item); + }, + callback(item){ + this.currentItem.value=item; } }, created() { diff --git a/frontend/src/business/components/api/definition/components/ApiVariableJson.vue b/frontend/src/business/components/api/definition/components/ApiVariableJson.vue new file mode 100644 index 0000000000..c78ee428c5 --- /dev/null +++ b/frontend/src/business/components/api/definition/components/ApiVariableJson.vue @@ -0,0 +1,108 @@ +<template> + + <el-dialog + :visible.sync="dialogVisible" destroy-on-close @close="close"> + <div style="padding: 10px"> + <el-switch active-text="JSON-SCHEMA" v-model="item.jsonType" @change="formatChange" active-value="JSON-SCHEMA"/> + </div> + <div v-if="codeEditActive"> + <ms-json-code-edit + v-if="item.jsonType==='JSON-SCHEMA'" + :body="item" + ref="jsonCodeEdit"/> + <ms-code-edit + v-else + :read-only="isReadOnly" + :data.sync="item.value" + :mode="'json'" + height="400px" + ref="codeEdit"/> + </div> + </el-dialog> +</template> + +<script> + +import MsCodeEdit from "../../../common/components/MsCodeEdit"; +import Convert from "@/business/components/common/json-schema/convert/convert"; +import MsJsonCodeEdit from "../../../common/json-schema/JsonSchemaEditor"; + + +export default { + name: "MsApiVariableJson", + components: {MsJsonCodeEdit, MsCodeEdit}, + props: { + isReadOnly: { + type: Boolean, + default: false + }, + }, + data() { + return { + dialogVisible: false, + jsonSchema: "JSON", + codeEditActive: true, + item: {} + } + }, + watch: { + 'item.value'() { + if (this.item.jsonType !== 'JSON-SCHEMA' && this.item.value) { + try { + const MsConvert = new Convert(); + let data = MsConvert.format(JSON.parse(this.item.value)); + if (this.item.jsonSchema) { + this.item.jsonSchema = this.deepAssign(this.item.jsonSchema, data); + } + } catch (ex) { + this.item.jsonSchema = ""; + } + } + } + }, + + methods: { + open(item) { + this.item.value = item.value; + this.dialogVisible = true; + this.reloadCodeEdit(); + }, + reloadCodeEdit() { + this.codeEditActive = false; + this.$nextTick(() => { + this.codeEditActive = true; + }); + }, + formatChange() { + const MsConvert = new Convert(); + if (this.item.jsonType === 'JSON-SCHEMA') { + if (this.item.value && !this.item.jsonSchema) { + this.item.jsonSchema = MsConvert.format(JSON.parse(this.item.value)); + } + } else { + if (this.item.jsonSchema) { + MsConvert.schemaToJsonStr(this.item.jsonSchema, (result) => { + this.$set(this.item, 'value', result); + this.$emit('callback', result); + }); + } + } + }, + saveAdvanced() { + this.dialogVisible = false; + if (this.item.jsonType === 'JSON-SCHEMA') { + this.item.jsonType = 'JSON'; + this.formatChange(); + } else { + this.$emit('callback', this.item.value); + } + this.item = {}; + this.reloadCodeEdit(); + }, + close(){ + this.saveAdvanced(); + } + }, + +} +</script> diff --git a/frontend/src/business/components/api/definition/components/body/ApiBody.vue b/frontend/src/business/components/api/definition/components/body/ApiBody.vue index 2d86e275bf..bd00d8731c 100644 --- a/frontend/src/business/components/api/definition/components/body/ApiBody.vue +++ b/frontend/src/business/components/api/definition/components/body/ApiBody.vue @@ -208,6 +208,7 @@ export default { }, formatChange() { const MsConvert = new Convert(); + if (this.body.format === 'JSON-SCHEMA') { if (this.body.raw && !this.body.jsonSchema) { this.body.jsonSchema = MsConvert.format(JSON.parse(this.body.raw)); @@ -266,24 +267,48 @@ export default { batchAdd() { this.$refs.batchAddParameter.open(); }, + format(array, obj) { + if (array) { + let isAdd = true; + for (let i in array) { + let item = array[i]; + if (item.name === obj.name) { + item.value = obj.value; + isAdd = false; + } + } + if (isAdd) { + this.body.kvs.unshift(obj); + } + } + }, batchSave(data) { if (data) { let params = data.split("\n"); let keyValues = []; params.forEach(item => { - let line = item.split(/,|,/); + let line = []; + line[0] = item.substring(0,item.indexOf(":")); + line[1] = item.substring(item.indexOf(":")+1,item.length); let required = false; - if (line[1] === '必填' || line[1] === 'Required' || line[1] === 'true') { - required = true; - } - keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"})); + keyValues.unshift(new KeyValue({ + name: line[0], + required: required, + value: line[1], + description: line[2], + type: "text", + valid: false, + file: false, + encode: true, + enable: true, + contentType: "text/plain" + })); }) keyValues.forEach(item => { - this.body.kvs.unshift(item); + this.format(this.body.kvs, item); }) } }, - }, created() { if (!this.body.type) { @@ -296,6 +321,7 @@ export default { } }); } + } } </script> From 0f794b08022097d918c6ce4c06984a98e69d07a4 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 <yuhao.li@fit2cloud.com> Date: Sun, 26 Sep 2021 18:17:15 +0800 Subject: [PATCH 48/74] =?UTF-8?q?fix:=20=E4=BB=A3=E7=A0=81=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E9=94=99=E8=AF=AF#1006923?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1006923 --user=lyh 【github#1598】脚本的代码模版错误 https://www.tapd.cn/55049933/s/1052016 --- .../settings/project/function/ScriptNavMenu.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue index d8c792ab24..7c0f48c6ab 100644 --- a/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue +++ b/frontend/src/business/components/settings/project/function/ScriptNavMenu.vue @@ -154,6 +154,14 @@ export default { return; } } else { + // todo 优化 + if (this.language !== 'beanshell' && this.language !== 'groovy') { + if (obj.title === this.$t('api_test.request.processor.code_add_report_length') || + obj.title === this.$t('api_test.request.processor.code_hide_report_length')) { + this.$warning("无对应的 "+ this.language +" 代码模版!"); + return; + } + } code = obj.value; } this.handleCodeTemplate(code); From e60dc78e944730454011c78e96f4156546be0f71 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Mon, 27 Sep 2021 10:29:41 +0800 Subject: [PATCH 49/74] =?UTF-8?q?fix:=20--bug=3D1006700=20--user=3D?= =?UTF-8?q?=E9=99=88=E5=BB=BA=E6=98=9F=20=E3=80=90=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8E=92=E5=BA=8F=E3=80=91-?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=B8=8D=E6=98=AF10=E4=B8=AA/=E9=A1=B5?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E6=8E=92=E5=BA=8F=E5=85=89=E6=A0=87=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=BC=82=E5=B8=B8=20https://www.tapd.cn/55049933/s/10?= =?UTF-8?q?52120?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/components/table/MsTable.vue | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/business/components/common/components/table/MsTable.vue b/frontend/src/business/components/common/components/table/MsTable.vue index f3d3cb99b3..7c340f2250 100644 --- a/frontend/src/business/components/common/components/table/MsTable.vue +++ b/frontend/src/business/components/common/components/table/MsTable.vue @@ -473,4 +473,16 @@ export default { .ms-icon-more:first-child { margin-right: -5px; } + +.ms-table >>> .el-table__body tr.hover-row.current-row>td, +.ms-table >>> .el-table__body tr.hover-row.el-table__row--striped.current-row>td, +.ms-table >>> .el-table__body tr.hover-row.el-table__row--striped>td, +.ms-table >>> .el-table__body tr.hover-row>td { + background-color: #ffffff; +} +/* 解决拖拽排序后hover阴影错乱问题 */ +.ms-table >>> .el-table__body tr:hover>td + { + background-color: #F5F7FA!important; +} </style> From cb9bb38b3a437ebab87d53eb9b74e776a3fcefdb Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Mon, 27 Sep 2021 12:02:42 +0800 Subject: [PATCH 50/74] =?UTF-8?q?fix:=20=E5=9C=BA=E6=99=AF=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E5=88=A0=E9=99=A4=E5=90=8E=E5=88=97=E8=A1=A8=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E7=BB=93=E6=9E=9C=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/automation/report/ApiReportDetail.vue | 15 +++++++++++---- .../api/automation/scenario/ApiScenarioList.vue | 2 +- .../comonents/api/TestPlanApiScenarioList.vue | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue index 822f783874..c935d0da25 100644 --- a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue +++ b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue @@ -288,16 +288,23 @@ export default { this.buildReport(); } else if (this.isShare) { getShareScenarioReport(this.shareId, this.reportId, (data) => { - this.report = data || {}; - this.buildReport(); + this.handleGetScenarioReport(data); }); } else { getScenarioReport(this.reportId, (data) => { - this.report = data || {}; - this.buildReport(); + this.handleGetScenarioReport(data); }); } }, + handleGetScenarioReport(data) { + if (data) { + this.report = data; + this.buildReport(); + } else { + this.$emit('invisible'); + this.$warning('报告已删除'); + } + }, buildReport() { if (this.report) { if (this.isNotRunning) { diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index a1e57259e1..77c85da331 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -220,7 +220,7 @@ <el-drawer :visible.sync="showReportVisible" :destroy-on-close="true" direction="ltr" :withHeader="true" :modal="false" size="90%"> - <ms-api-report-detail @refresh="search" :infoDb="infoDb" :report-id="showReportId" :currentProjectId="projectId"/> + <ms-api-report-detail @invisible="showReportVisible = false" @refresh="search" :infoDb="infoDb" :report-id="showReportId" :currentProjectId="projectId"/> </el-drawer> <!--测试计划--> <el-drawer :visible.sync="planVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" diff --git a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue index 57014d4f70..06875572a4 100644 --- a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue @@ -157,7 +157,7 @@ <!-- 执行结果 --> <el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="true" :modal="false" size="90%"> - <ms-api-report-detail @refresh="search" :infoDb="infoDb" :report-id="reportId" :currentProjectId="projectId"/> + <ms-api-report-detail @invisible="runVisible = false" @refresh="search" :infoDb="infoDb" :report-id="reportId" :currentProjectId="projectId"/> </el-drawer> </div> </el-card> From 54039fe059258020136295aa6e39e0b405e2e630 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Mon, 27 Sep 2021 13:51:28 +0800 Subject: [PATCH 51/74] =?UTF-8?q?fix:=20--bug=3D1003290=20--user=3D?= =?UTF-8?q?=E9=99=88=E5=BB=BA=E6=98=9F=20=E8=84=91=E5=9B=BE=E7=81=AB?= =?UTF-8?q?=E7=8B=90=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98=20https:?= =?UTF-8?q?//www.tapd.cn/55049933/s/1052296?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/business/components/track/common/minder/minderUtils.js | 1 + frontend/src/common/js/tableUtils.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/business/components/track/common/minder/minderUtils.js b/frontend/src/business/components/track/common/minder/minderUtils.js index be8beefc2f..2ad04ad496 100644 --- a/frontend/src/business/components/track/common/minder/minderUtils.js +++ b/frontend/src/business/components/track/common/minder/minderUtils.js @@ -71,6 +71,7 @@ export function loadNode(node, param, getCaseFuc, setParamCallback, getExtraNode } } data.loaded = true; + window.minder.execCommand('expand'); } /** diff --git a/frontend/src/common/js/tableUtils.js b/frontend/src/common/js/tableUtils.js index 8312bfeb75..8cf4ce2892 100644 --- a/frontend/src/common/js/tableUtils.js +++ b/frontend/src/common/js/tableUtils.js @@ -529,6 +529,9 @@ export function getCustomFieldBatchEditOption(customFields, typeArr, valueArr, m export function handleRowDrop(data, callback) { setTimeout(() => { const tbody = document.querySelector('.el-table__body-wrapper tbody'); + if (!tbody) { + return; + } const dropBars = tbody.getElementsByClassName('table-row-drop-bar'); // 每次调用生成一个class From 5a28e6d6d3c591d87db6e3565a025f31c1892775 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Mon, 27 Sep 2021 14:48:43 +0800 Subject: [PATCH 52/74] =?UTF-8?q?fix:=20=E8=84=91=E5=9B=BE=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E4=BF=9D=E5=AD=98=E5=90=8E=E6=89=93=E4=B8=8A=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E6=A0=87=E7=AD=BE=E4=BF=9D=E5=AD=98=E6=8A=A5=E9=94=99?= =?UTF-8?q?=20#6548?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track/common/minder/TestCaseMinder.vue | 15 ++++++++++++--- .../components/track/common/minder/minderUtils.js | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/business/components/track/common/minder/TestCaseMinder.vue b/frontend/src/business/components/track/common/minder/TestCaseMinder.vue index fc7bdaaa12..d71be71365 100644 --- a/frontend/src/business/components/track/common/minder/TestCaseMinder.vue +++ b/frontend/src/business/components/track/common/minder/TestCaseMinder.vue @@ -129,7 +129,7 @@ name: "TestCaseMinder", }, save(data) { let saveCases = []; - let deleteCases = []; + let deleteCases = []; // 包含测试用例和临时节点 let saveExtraNode = {}; this.buildSaveCase(data.root, saveCases, deleteCases, saveExtraNode, undefined); @@ -169,7 +169,7 @@ name: "TestCaseMinder", buildSaveCase(root, saveCases, deleteCases, saveExtraNode, parent) { let data = root.data; if (data.resource && data.resource.indexOf(this.$t('api_test.definition.request.case')) > -1) { - this._buildSaveCase(root, saveCases, parent); + this._buildSaveCase(root, saveCases, deleteCases, parent); } else { let deleteChild = data.deleteChild; if (deleteChild && deleteChild.length > 0 @@ -200,11 +200,20 @@ name: "TestCaseMinder", } } }, - _buildSaveCase(node, saveCases, parent) { + _buildSaveCase(node, saveCases, deleteCases, parent) { let data = node.data; if (!data.text) { return; } + + if (data.isExtraNode) { + // 如果是临时节点,打上了用例标签,则删除临时节点并新建用例节点 + let deleteData = {}; + Object.assign(deleteData, data); + deleteCases.push(deleteData); + data.id = ""; + } + let isChange = false; let testCase = { id: data.id, diff --git a/frontend/src/business/components/track/common/minder/minderUtils.js b/frontend/src/business/components/track/common/minder/minderUtils.js index 2ad04ad496..35a1ca1644 100644 --- a/frontend/src/business/components/track/common/minder/minderUtils.js +++ b/frontend/src/business/components/track/common/minder/minderUtils.js @@ -271,6 +271,7 @@ export function appendExtraNodes(parent, nodes) { } function _appendExtraNodes(parent, data) { + data.isExtraNode = true; let node = appendChildNode(parent, data, true); if (data.children && data.children.length > 0) { data.children.forEach(child => { From dd10ae60a6ce6eec696494824374e8ef9fb5f37c Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Mon, 27 Sep 2021 11:57:27 +0800 Subject: [PATCH 53/74] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96)=20=E5=9C=BA=E6=99=AF=E5=A2=9E=E5=8A=A0=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=90=AF?= =?UTF-8?q?=E7=94=A8=E5=9C=BA=E6=99=AF=E7=8E=AF=E5=A2=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiAutomationController.java | 6 ++ .../dto/definition/request/ElementUtil.java | 29 +++++++- .../dto/definition/request/MsScenario.java | 30 ++++++-- .../api/service/ApiAutomationService.java | 17 ++++- .../automation/scenario/EditApiScenario.vue | 47 ++++++++---- .../scenario/common/ApiBaseComponent.vue | 3 + .../component/ApiScenarioComponent.vue | 71 ++++++++++++++++++- .../scenario/component/ComponentConfig.vue | 8 ++- .../common/select-tree/SelectTree.vue | 2 +- 9 files changed, 189 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index 3a90c6976d..e1ad2d3100 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -353,5 +353,11 @@ public class ApiAutomationController { return apiAutomationService.exportJmx(request); } + @PostMapping(value = "/checkScenarioEnv") + public boolean checkScenarioEnv(@RequestBody ApiScenarioWithBLOBs request) { + return apiAutomationService.checkScenarioEnv(request); + } + + } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java index 15bd6362e0..af2a0a5676 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java @@ -304,7 +304,28 @@ public class ElementUtil { try { for (int i = 0; i < hashTree.size(); i++) { JSONObject element = hashTree.getJSONObject(i); - if (element != null && element.get("type").toString().equals("HTTPSamplerProxy")) { + boolean isScenarioEnv = false; + ParameterConfig config = new ParameterConfig(); + if (element != null && element.get("type").toString().equals("scenario")) { + MsScenario scenario = JSONObject.toJavaObject(element, MsScenario.class); + if (scenario.isEnvironmentEnable()) { + isScenarioEnv = true; + Map<String, EnvironmentConfig> envConfig = new HashMap<>(16); + Map<String, String> environmentMap = (Map<String, String>) element.get("environmentMap"); + if (environmentMap != null && !environmentMap.isEmpty()) { + environmentMap.keySet().forEach(projectId -> { + ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class); + ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentMap.get(projectId)); + if (environment != null && environment.getConfig() != null) { + EnvironmentConfig env = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class); + env.setApiEnvironmentid(environment.getId()); + envConfig.put(projectId, env); + } + }); + config.setConfig(envConfig); + } + } + } else if (element != null && element.get("type").toString().equals("HTTPSamplerProxy")) { MsHTTPSamplerProxy httpSamplerProxy = JSON.toJavaObject(element, MsHTTPSamplerProxy.class); if (httpSamplerProxy != null && (!httpSamplerProxy.isCustomizeReq() || (httpSamplerProxy.isCustomizeReq() && httpSamplerProxy.getIsRefEnvironment()))) { @@ -320,7 +341,11 @@ public class ElementUtil { } if (element.containsKey("hashTree")) { JSONArray elementJSONArray = element.getJSONArray("hashTree"); - dataSetDomain(elementJSONArray, msParameter); + if (isScenarioEnv) { + dataSetDomain(elementJSONArray, config); + } else { + dataSetDomain(elementJSONArray, msParameter); + } } } } catch (Exception e) { diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java index dad8a24edd..48fae7b315 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java @@ -64,9 +64,12 @@ public class MsScenario extends MsTestElement { @JSONField(ordinal = 27) private Map<String, String> environmentMap; - @JSONField(ordinal = 24) + @JSONField(ordinal = 28) private Boolean onSampleError; + @JSONField(ordinal = 29) + private boolean environmentEnable; + private static final String BODY_FILE_DIR = FileUtils.BODY_FILE_DIR; public MsScenario() { @@ -152,8 +155,7 @@ public class MsScenario extends MsTestElement { } } else { Map<String, EnvironmentConfig> map = config.getConfig(); - for (EnvironmentConfig evnConfig : - map.values()) { + for (EnvironmentConfig evnConfig : map.values()) { if (evnConfig.getHttpConfig() != null) { this.setMockEnvironment(evnConfig.getHttpConfig().isMock()); } @@ -174,12 +176,32 @@ public class MsScenario extends MsTestElement { //setHeader(tree, this.headers); config.setHeaders(this.headers); } + ParameterConfig newConfig = new ParameterConfig(); + if (this.isEnvironmentEnable() && this.environmentMap != null && !this.environmentMap.isEmpty()) { + environmentMap.keySet().forEach(projectId -> { + ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class); + ApiTestEnvironmentWithBLOBs environment = environmentService.get(this.environmentMap.get(projectId)); + if (environment != null && environment.getConfig() != null) { + EnvironmentConfig env = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class); + env.setApiEnvironmentid(environment.getId()); + envConfig.put(projectId, env); + if (StringUtils.equals(environment.getName(), MockConfigStaticData.MOCK_EVN_NAME)) { + this.setMockEnvironment(true); + } + } + }); + newConfig.setConfig(envConfig); + } if (CollectionUtils.isNotEmpty(hashTree)) { for (MsTestElement el : hashTree) { // 给所有孩子加一个父亲标志 el.setParent(this); el.setMockEnvironment(this.isMockEnvironment()); - el.toHashTree(tree, el.getHashTree(), config); + if (this.isEnvironmentEnable()) { + el.toHashTree(tree, el.getHashTree(), newConfig); + } else { + el.toHashTree(tree, el.getHashTree(), config); + } } } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index e393c69eb1..c239140e80 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -714,6 +714,10 @@ public class ApiAutomationService { } if (StringUtils.equals(tr.getType(), "scenario")) { + MsScenario scenario = (MsScenario) tr; + if (scenario.isEnvironmentEnable()) { + continue; + } env.getProjectIds().add(tr.getProjectId()); } if (CollectionUtils.isNotEmpty(tr.getHashTree())) { @@ -786,6 +790,10 @@ public class ApiAutomationService { } } if (StringUtils.equals(tr.getType(), "scenario")) { + MsScenario scenario = (MsScenario) tr; + if (scenario.isEnvironmentEnable()) { + continue; + } env.getProjectIds().add(tr.getProjectId()); } if (CollectionUtils.isNotEmpty(tr.getHashTree())) { @@ -1952,14 +1960,14 @@ public class ApiAutomationService { hashTree.set(i, object); } } else { - ApiScenarioWithBLOBs bloBs = this.getDto(object.getString("id")); + ApiScenarioWithBLOBs bloBs = apiScenarioMapper.selectByPrimaryKey(object.getString("id")); if (bloBs != null) { object = JSON.parseObject(bloBs.getScenarioDefinition()); hashTree.set(i, object); } } } else if ("scenario".equals(object.getString("type"))) { - ApiScenarioWithBLOBs bloBs = this.getDto(object.getString("id")); + ApiScenarioWithBLOBs bloBs = apiScenarioMapper.selectByPrimaryKey(object.getString("id")); if (bloBs != null) { object = JSON.parseObject(bloBs.getScenarioDefinition()); hashTree.set(i, object); @@ -2610,4 +2618,9 @@ public class ApiAutomationService { extApiScenarioMapper::getLastOrder, apiScenarioMapper::updateByPrimaryKeySelective); } + + public boolean checkScenarioEnv(ApiScenarioWithBLOBs request) { + return this.checkScenarioEnv(request, null); + } + } diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index fe145701d0..54967468f7 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -214,6 +214,8 @@ @copyRow="copyRow" @suggestClick="suggestClick" @refReload="refReload" + @runScenario="runDebug" + @stopScenario="stop" @openScenario="openScenario"/> </span> </el-tree> @@ -454,6 +456,7 @@ export default { stepFilter: new STEP, plugins: [], clearMessage: "", + runScenario: undefined, } }, created() { @@ -571,6 +574,7 @@ export default { this.debugLoading = false; } }); + this.runScenario = undefined; }, clearDebug() { this.reqError = 0; @@ -655,7 +659,9 @@ export default { this.message = getUUID(); if (data.end) { this.removeReport(); + this.runScenario = undefined; this.debugLoading = false; + this.message = "stop"; this.stopDebug = "stop"; } } @@ -775,7 +781,11 @@ export default { this.reqTotalTime = endTime - startTime + 100; } this.debugResult = resMap; - this.sort(); + if (this.runScenario && this.runScenario.hashTree) { + this.sort(this.runScenario.hashTree); + } else { + this.sort(); + } this.reloadDebug = getUUID(); }, removeReport() { @@ -1127,7 +1137,7 @@ export default { this.showHideTree = true }); }, - runDebug() { + runDebug(runScenario) { if (this.scenarioDefinition.length < 1) { return; } @@ -1149,22 +1159,32 @@ export default { this.clearMessage = getUUID().substring(0, 8); return; } + let scenario = undefined; + if (runScenario && runScenario.type === 'scenario') { + scenario = runScenario; + this.runScenario = runScenario; + } //调试时不再保存 this.debugData = { - id: this.currentScenario.id, - name: this.currentScenario.name, + id: scenario ? scenario.id : this.currentScenario.id, + name: scenario ? scenario.name : this.currentScenario.name, type: "scenario", - variables: this.currentScenario.variables, + variables: scenario ? scenario.variables : this.currentScenario.variables, referenced: 'Created', - enableCookieShare: this.enableCookieShare, - headers: this.currentScenario.headers, - environmentMap: this.projectEnvMap, - hashTree: this.scenarioDefinition, - onSampleError: this.onSampleError, + enableCookieShare: scenario ? scenario.enableCookieShare : this.enableCookieShare, + headers: scenario ? scenario.headers : this.currentScenario.headers, + environmentMap: scenario && scenario.environmentEnable ? scenario.environmentMap : this.projectEnvMap, + hashTree: scenario ? scenario.hashTree : this.scenarioDefinition, + onSampleError: scenario ? scenario.onSampleError : this.onSampleError, }; + if (scenario && scenario.environmentEnable) { + this.debugData.environmentEnable = scenario.environmentEnable; + this.debugLoading = false; + }else{ + this.debugLoading = true; + } this.reportId = getUUID().substring(0, 8); this.debug = true; - this.debugLoading = true; }) }) } else { @@ -1219,8 +1239,7 @@ export default { } return true; } else if (dropType === "inner" && dropNode.data.referenced !== 'REF' && dropNode.data.referenced !== 'Deleted' - && (this.stepFilter.get(dropNode.data.type) && this.stepFilter.get(dropNode.data.type).indexOf(draggingNode.data.type) != -1) - && !draggingNode.data.disabled) { + && (this.stepFilter.get(dropNode.data.type) && this.stepFilter.get(dropNode.data.type).indexOf(draggingNode.data.type) != -1)) { return true; } return false; @@ -1412,6 +1431,8 @@ export default { this.debugLoading = false; this.debugVisible = false; this.loading = false; + this.runScenario = undefined; + this.message = "stop"; this.clearMessage = getUUID().substring(0, 8); }, showScenarioParameters() { diff --git a/frontend/src/business/components/api/automation/scenario/common/ApiBaseComponent.vue b/frontend/src/business/components/api/automation/scenario/common/ApiBaseComponent.vue index b1cebd82ff..cc7f6f1384 100644 --- a/frontend/src/business/components/api/automation/scenario/common/ApiBaseComponent.vue +++ b/frontend/src/business/components/api/automation/scenario/common/ApiBaseComponent.vue @@ -26,6 +26,9 @@ </el-tooltip> </span> </slot> + + <slot name="scenarioEnable"/> + </span> <div class="header-right" @click.stop> diff --git a/frontend/src/business/components/api/automation/scenario/component/ApiScenarioComponent.vue b/frontend/src/business/components/api/automation/scenario/component/ApiScenarioComponent.vue index 831c4448a9..f00496b2c9 100644 --- a/frontend/src/business/components/api/automation/scenario/component/ApiScenarioComponent.vue +++ b/frontend/src/business/components/api/automation/scenario/component/ApiScenarioComponent.vue @@ -19,7 +19,6 @@ <el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='Deleted'" type="danger">{{ $t('api_test.automation.reference_deleted') }}</el-tag> <el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='Copy'">{{ $t('commons.copy') }}</el-tag> <el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='REF'">{{ $t('api_test.scenario.reference') }}</el-tag> - <span class="ms-tag">{{ getProjectName(scenario.projectId) }}</span> </template> <template v-slot:debugStepCode> @@ -31,6 +30,23 @@ {{ getCode() }} </span> </template> + <template v-slot:scenarioEnable> + <el-tooltip content="启用场景环境:当前步骤使用场景原始环境配置运行" placement="top"> + <el-checkbox v-model="scenario.environmentEnable" @change="checkEnv">启用场景环境</el-checkbox> + </el-tooltip> + </template> + <template v-slot:button> + <el-tooltip :content="$t('api_test.run')" placement="top" v-if="!scenario.run"> + <el-button :disabled="!scenario.enable" @click="run" icon="el-icon-video-play" style="padding: 5px" class="ms-btn" size="mini" circle/> + </el-tooltip> + <el-tooltip :content="$t('report.stop_btn')" placement="top" :enterable="false" v-else> + <el-button :disabled="!scenario.enable" @click.once="stop" size="mini" style="color:white;padding: 0 0.1px;width: 24px;height: 24px;" class="stop-btn" circle> + <div style="transform: scale(0.66)"> + <span style="margin-left: -4.5px;font-weight: bold;">STOP</span> + </div> + </el-button> + </el-tooltip> + </template> </api-base-component> </template> @@ -66,6 +82,9 @@ export default { }, watch: { message() { + if (this.message === "stop") { + this.scenario.run = false; + } this.reload(); }, }, @@ -121,6 +140,35 @@ export default { }, }, methods: { + run() { + this.scenario.run = true; + this.$emit('runScenario', this.scenario); + }, + stop() { + this.scenario.run = false; + this.$emit('stopScenario'); + this.reload(); + }, + checkEnv() { + this.$post("/api/automation/checkScenarioEnv", {scenarioDefinition: JSON.stringify(this.scenario), projectId: this.projectId}, res => { + if (this.scenario.environmentEnable && !res.data) { + this.scenario.environmentEnable = false; + this.$warning("当前场景没有环境,需要先设置自身环境"); + return; + } + this.setDomain(); + }); + }, + setDomain() { + if (this.scenario.environmentEnable) { + this.$post("/api/automation/setDomain", {definition: JSON.stringify(this.scenario)}, res => { + if (res.data) { + let data = JSON.parse(res.data); + this.scenario.hashTree = data.hashTree; + } + }) + } + }, getCode() { if (this.node && this.node.data.code && this.node.data.debug) { if (this.node.data.code && this.node.data.code === 'error') { @@ -210,10 +258,30 @@ export default { color: #F56C6C; } +.ms-test-running { + color: #6D317C; +} + .ms-req-success { color: #67C23A; } +.ms-btn { + background-color: #409EFF; + color: white; +} + +.ms-btn-flot { + margin: 20px; + float: right; +} + +.stop-btn { + background-color: #E62424; + border-color: #EE6161; + color: white; +} + .ms-step-debug-code { display: inline-block; margin: 0 5px; @@ -224,6 +292,7 @@ export default { white-space: nowrap; width: 60px; } + .ms-test-running { color: #6D317C; } diff --git a/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue b/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue index 3f13b85e96..45f4c0de9a 100644 --- a/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue +++ b/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue @@ -4,7 +4,7 @@ <component v-bind:is="component" :isMax="isMax" :show-btn="showBtn" :expandedNode="expandedNode" :scenario="scenario" :controller="scenario" :timer="scenario" :assertions="scenario" :extract="scenario" :jsr223-processor="scenario" :request="scenario" :currentScenario="currentScenario" :currentEnvironmentId="currentEnvironmentId" :node="node" :draggable="draggable" :title="title" :color="titleColor" :background-color="backgroundColor" @suggestClick="suggestClick(node)" :response="response" - @remove="remove" @copyRow="copyRow" @refReload="refReload" @openScenario="openScenario" :project-list="projectList" :env-map="envMap" :message="message"/> + @remove="remove" @runScenario="runScenario" @stopScenario="stopScenario" @copyRow="copyRow" @refReload="refReload" @openScenario="openScenario" :project-list="projectList" :env-map="envMap" :message="message"/> </keep-alive> </div> </template> @@ -184,6 +184,12 @@ export default { }, refReload(data, node) { this.$emit('refReload', data, node); + }, + runScenario(scenario) { + this.$emit('runScenario', scenario); + }, + stopScenario(){ + this.$emit('stopScenario'); } } } diff --git a/frontend/src/business/components/common/select-tree/SelectTree.vue b/frontend/src/business/components/common/select-tree/SelectTree.vue index 5d61c9a383..87fcaddea6 100644 --- a/frontend/src/business/components/common/select-tree/SelectTree.vue +++ b/frontend/src/business/components/common/select-tree/SelectTree.vue @@ -412,7 +412,7 @@ export default { }, data:{ handler:function(){ - if(this.defaultKey && this.defaultKey.length > 0){ + if(this.defaultKey && this.defaultKey.length > 0 && this.defaultKey instanceof Array){ this.$refs.tree.setCheckedKeys(this.defaultKey); } }, From 0c692cd7949d00d741beb1bd0d82404c20760b33 Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Mon, 27 Sep 2021 16:31:47 +0800 Subject: [PATCH 54/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89)?= =?UTF-8?q?=20=E7=94=A8=E4=BE=8B=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86=20--bug=3D1006958=20--user=3D=E8=B5=B5?= =?UTF-8?q?=E5=8B=87=20=E6=B7=BB=E5=8A=A0=E4=BA=86=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=9A=84=E6=8E=A5=E5=8F=A3=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=97=B6=E6=8A=A5=E9=94=99=20https://www.tap?= =?UTF-8?q?d.cn/55049933/s/1052429?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/automation/scenario/ApiScenarioList.vue | 3 +++ .../components/api/automation/scenario/DebugRun.vue | 3 +++ .../api/automation/scenario/EditApiScenario.vue | 6 ++++++ .../api/definition/components/EditCompleteContainer.vue | 3 +++ .../components/api/definition/components/Run.vue | 3 +++ .../api/definition/components/basis/AddBasisApi.vue | 3 +++ .../api/definition/components/case/ApiCaseItem.vue | 6 ++++++ .../api/definition/components/case/ApiCaseList.vue | 9 +++------ .../api/definition/components/debug/DebugDubboPage.vue | 3 +++ .../api/definition/components/debug/DebugHttpPage.vue | 3 +++ .../api/definition/components/list/ApiCaseSimpleList.vue | 3 +++ .../definition/components/reference/ApiExtendBtns.vue | 3 +++ .../definition/components/runtest/RunTestHTTPPage.vue | 3 +++ .../components/settings/project/function/FunctionRun.vue | 3 +++ 14 files changed, 48 insertions(+), 6 deletions(-) diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index 77c85da331..bd064f08be 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -877,6 +877,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/automation/scenario/DebugRun.vue b/frontend/src/business/components/api/automation/scenario/DebugRun.vue index 0bc97a02ce..1c0a55fe8e 100644 --- a/frontend/src/business/components/api/automation/scenario/DebugRun.vue +++ b/frontend/src/business/components/api/automation/scenario/DebugRun.vue @@ -39,6 +39,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 54967468f7..0ed80407e4 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -967,6 +967,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (!stepArray[i].projectId) { // 如果自身没有ID并且场景有ID则赋值场景ID,否则赋值当前项目ID stepArray[i].projectId = scenarioProjectId ? scenarioProjectId : this.projectId; @@ -1378,6 +1381,9 @@ export default { if (!hashTree[i].clazzName) { hashTree[i].clazzName = TYPE_TO_C.get(hashTree[i].type); } + if (hashTree[i] && hashTree[i].authManager && !hashTree[i].authManager.clazzName) { + hashTree[i].authManager.clazzName = TYPE_TO_C.get(hashTree[i].authManager.type); + } if (hashTree[i].hashTree && hashTree[i].hashTree.length > 0) { this.formatData(hashTree[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/EditCompleteContainer.vue b/frontend/src/business/components/api/definition/components/EditCompleteContainer.vue index 39ae9777c0..edf9163e46 100644 --- a/frontend/src/business/components/api/definition/components/EditCompleteContainer.vue +++ b/frontend/src/business/components/api/definition/components/EditCompleteContainer.vue @@ -183,6 +183,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/Run.vue b/frontend/src/business/components/api/definition/components/Run.vue index 3866d4c9bf..88b08e7a60 100644 --- a/frontend/src/business/components/api/definition/components/Run.vue +++ b/frontend/src/business/components/api/definition/components/Run.vue @@ -64,6 +64,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/basis/AddBasisApi.vue b/frontend/src/business/components/api/definition/components/basis/AddBasisApi.vue index 6abaf3d750..14791020af 100644 --- a/frontend/src/business/components/api/definition/components/basis/AddBasisApi.vue +++ b/frontend/src/business/components/api/definition/components/basis/AddBasisApi.vue @@ -108,6 +108,9 @@ if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.compatibleHistory(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue index ea1c637457..de4714ff10 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue @@ -397,6 +397,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } @@ -438,6 +441,9 @@ export default { } if (tmp.request) { tmp.request.clazzName = TYPE_TO_C.get(tmp.request.type); + if (tmp.request.authManager) { + tmp.request.authManager.clazzName = TYPE_TO_C.get(tmp.request.authManager.type); + } this.sort(tmp.request.hashTree); } this.result = this.$fileUpload(url, null, bodyFiles, tmp, (response) => { diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue index e5bf2e2d75..28c2798eee 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseList.vue @@ -58,22 +58,19 @@ <script> import ApiCaseHeader from "./ApiCaseHeader"; -import ApiCaseItem from "./ApiCaseItem"; -import MsRun from "../Run"; import {getCurrentProjectID, getUUID} from "@/common/js/utils"; import MsDrawer from "../../../../common/components/MsDrawer"; import {CASE_ORDER, CASE_PRIORITY, DUBBO_METHOD, REQ_METHOD, SQL_METHOD, TCP_METHOD} from "../../model/JsonData"; import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components"; -import MsBatchEdit from "../basis/BatchEdit"; export default { name: 'ApiCaseList', components: { MsDrawer, - MsRun, + MsRun: () => import("../Run"), ApiCaseHeader, - ApiCaseItem, - MsBatchEdit, + ApiCaseItem: () => import("./ApiCaseItem"), + MsBatchEdit: () => import("../basis/BatchEdit"), MsTaskCenter: () => import("../../../../task/TaskCenter"), }, props: { diff --git a/frontend/src/business/components/api/definition/components/debug/DebugDubboPage.vue b/frontend/src/business/components/api/definition/components/debug/DebugDubboPage.vue index 076e6fee38..5c829bde94 100644 --- a/frontend/src/business/components/api/definition/components/debug/DebugDubboPage.vue +++ b/frontend/src/business/components/api/definition/components/debug/DebugDubboPage.vue @@ -165,6 +165,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.compatibleHistory(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/debug/DebugHttpPage.vue b/frontend/src/business/components/api/definition/components/debug/DebugHttpPage.vue index 7432d482c1..15640afd12 100644 --- a/frontend/src/business/components/api/definition/components/debug/DebugHttpPage.vue +++ b/frontend/src/business/components/api/definition/components/debug/DebugHttpPage.vue @@ -217,6 +217,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.compatibleHistory(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue index 58b6209f07..4ffe2b90ad 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue @@ -1040,6 +1040,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sortHashTree(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/reference/ApiExtendBtns.vue b/frontend/src/business/components/api/definition/components/reference/ApiExtendBtns.vue index f772c9183c..b0a98faffa 100644 --- a/frontend/src/business/components/api/definition/components/reference/ApiExtendBtns.vue +++ b/frontend/src/business/components/api/definition/components/reference/ApiExtendBtns.vue @@ -53,6 +53,9 @@ if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sortHashTree(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/api/definition/components/runtest/RunTestHTTPPage.vue b/frontend/src/business/components/api/definition/components/runtest/RunTestHTTPPage.vue index 75531c0f23..30419f9688 100644 --- a/frontend/src/business/components/api/definition/components/runtest/RunTestHTTPPage.vue +++ b/frontend/src/business/components/api/definition/components/runtest/RunTestHTTPPage.vue @@ -225,6 +225,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.compatibleHistory(stepArray[i].hashTree); } diff --git a/frontend/src/business/components/settings/project/function/FunctionRun.vue b/frontend/src/business/components/settings/project/function/FunctionRun.vue index cc0fb097ea..db9f87dd7d 100644 --- a/frontend/src/business/components/settings/project/function/FunctionRun.vue +++ b/frontend/src/business/components/settings/project/function/FunctionRun.vue @@ -63,6 +63,9 @@ export default { if (!stepArray[i].clazzName) { stepArray[i].clazzName = TYPE_TO_C.get(stepArray[i].type); } + if (stepArray[i] && stepArray[i].authManager && !stepArray[i].authManager.clazzName) { + stepArray[i].authManager.clazzName = TYPE_TO_C.get(stepArray[i].authManager.type); + } if (stepArray[i].hashTree && stepArray[i].hashTree.length > 0) { this.sort(stepArray[i].hashTree); } From e459e87039ef636ef1aa3e458e0c722fd217036f Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Mon, 27 Sep 2021 18:03:36 +0800 Subject: [PATCH 55/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89/?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=B7=9F=E8=B8=AA)=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89=E6=89=B9=E9=87=8F=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=EF=BC=9B=E4=BF=AE=E5=A4=8D=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E9=97=AE=E9=A2=98=20--bug=3D1006802=20--user?= =?UTF-8?q?=3D=E8=B5=B5=E5=8B=87=20=E3=80=90=E4=BB=BB=E5=8A=A1=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E3=80=91=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=EF=BC=8C?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E4=B8=B2=E8=A1=8C=E6=89=A7=E8=A1=8C=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E6=B5=8B=E8=AF=95=EF=BC=8C=E6=89=A7=E8=A1=8C=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=82=B9=E5=87=BB=E4=BB=BB=E5=8A=A1=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E7=A4=BA=E6=8A=A5=E5=91=8A=E4=B8=8D=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=20https://www.tapd.cn/55049933/s/1052563?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/definition/RunCaseRequest.java | 6 ++ .../dto/definition/request/ElementUtil.java | 8 +++ .../api/service/ApiTestCaseService.java | 55 ++++++++++--------- .../base/mapper/ext/ExtApiTestCaseMapper.xml | 5 +- .../TestPlanLoadCaseController.java | 10 +++- .../components/list/ApiCaseBatchRun.vue | 1 + .../components/list/ApiCaseSimpleList.vue | 2 +- .../comonents/load/TestPlanLoadCaseList.vue | 2 +- 8 files changed, 58 insertions(+), 31 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/RunCaseRequest.java b/backend/src/main/java/io/metersphere/api/dto/definition/RunCaseRequest.java index 6ab3648046..01d5383c61 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/RunCaseRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/RunCaseRequest.java @@ -1,5 +1,7 @@ package io.metersphere.api.dto.definition; +import io.metersphere.base.domain.ApiDefinitionExecResult; +import io.metersphere.base.domain.ApiTestCaseWithBLOBs; import lombok.Getter; import lombok.Setter; @@ -16,4 +18,8 @@ public class RunCaseRequest { private String environmentId; private String testPlanId; + + private ApiTestCaseWithBLOBs bloBs; + + private ApiDefinitionExecResult report; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java index af2a0a5676..ee2d1720a5 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java @@ -279,6 +279,14 @@ public class ElementUtil { element.fluentPut("tcpPreProcessor", tcpPreProcessor); } } + } else if (element != null && element.getString("type").equals("HTTPSamplerProxy")) { + if (element.getString("authManager") != null) { + JSONObject authManager = JSON.parseObject(element.getString("authManager")); + if (authManager != null && authManager.get("clazzName") == null) { + authManager.fluentPut("clazzName", clazzMap.get(authManager.getString("type"))); + element.fluentPut("authManager", authManager); + } + } } if (element != null && element.get("clazzName") == null && clazzMap.containsKey(element.getString("type"))) { element.fluentPut("clazzName", clazzMap.get(element.getString("type"))); 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 7938a45274..2404211d5e 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -703,50 +703,53 @@ public class ApiTestCaseService { public void batchRun(ApiCaseBatchRequest request) { ServiceUtils.getSelectAllIds(request, request.getCondition(), (query) -> extApiTestCaseMapper.selectIdsByQuery((ApiTestCaseRequest) query)); - Map<String, ApiDefinitionExecResult> executeQueue = new HashMap<>(); + List<RunCaseRequest> executeQueue = new LinkedList<>(); SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); ApiDefinitionExecResultMapper batchMapper = sqlSession.getMapper(ApiDefinitionExecResultMapper.class); - for (String testCaseId : request.getIds()) { - ApiDefinitionExecResult report = addResult(testCaseId, APITestStatus.Running.name()); - ApiTestCaseWithBLOBs caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testCaseId); - if (caseWithBLOBs != null) { - report.setName(caseWithBLOBs.getName()); - caseWithBLOBs.setLastResultId(report.getId()); - caseWithBLOBs.setUpdateTime(System.currentTimeMillis()); - caseWithBLOBs.setStatus(APITestStatus.Running.name()); - apiTestCaseMapper.updateByPrimaryKey(caseWithBLOBs); - } - batchMapper.insert(report); - executeQueue.put(testCaseId, report); - } - sqlSession.flushStatements(); - for (String caseId : executeQueue.keySet()) { + ApiTestCaseExample example = new ApiTestCaseExample(); + example.createCriteria().andIdIn(request.getIds()); + List<ApiTestCaseWithBLOBs> list = apiTestCaseMapper.selectByExampleWithBLOBs(example); + + ApiTestCaseMapper sqlSessionMapper = sqlSession.getMapper(ApiTestCaseMapper.class); + for (ApiTestCaseWithBLOBs caseWithBLOBs : list) { + ApiDefinitionExecResult report = addResult(caseWithBLOBs.getId(), APITestStatus.Running.name()); + report.setName(caseWithBLOBs.getName()); + caseWithBLOBs.setLastResultId(report.getId()); + caseWithBLOBs.setUpdateTime(System.currentTimeMillis()); + caseWithBLOBs.setStatus(APITestStatus.Running.name()); + sqlSessionMapper.updateByPrimaryKey(caseWithBLOBs); + + // 执行对象 RunCaseRequest runCaseRequest = new RunCaseRequest(); runCaseRequest.setRunMode(ApiRunMode.DEFINITION.name()); - runCaseRequest.setCaseId(caseId); - runCaseRequest.setReportId(executeQueue.get(caseId).getId()); + runCaseRequest.setCaseId(caseWithBLOBs.getId()); + runCaseRequest.setReportId(report.getId()); runCaseRequest.setEnvironmentId(request.getEnvironmentId()); + runCaseRequest.setBloBs(caseWithBLOBs); + runCaseRequest.setReport(report); + + batchMapper.insert(report); + executeQueue.add(runCaseRequest); + } + sqlSession.flushStatements(); + for (RunCaseRequest runCaseRequest : executeQueue) { run(runCaseRequest); - MessageCache.batchTestCases.put(executeQueue.get(caseId).getId(), executeQueue.get(caseId)); + MessageCache.batchTestCases.put(runCaseRequest.getReportId(), runCaseRequest.getReport()); } } public String run(RunCaseRequest request) { - ApiTestCaseWithBLOBs testCaseWithBLOBs = null; + ApiTestCaseWithBLOBs testCaseWithBLOBs = request.getBloBs(); if (StringUtils.equals(request.getRunMode(), ApiRunMode.JENKINS_API_PLAN.name())) { testCaseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(request.getReportId()); request.setCaseId(request.getReportId()); - } else { - testCaseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(request.getCaseId()); + //通过测试计划id查询环境 + request.setReportId(request.getTestPlanId()); } if (StringUtils.equals(request.getRunMode(), ApiRunMode.JENKINS.name())) { request.setReportId(request.getEnvironmentId()); } - if (StringUtils.equals(request.getRunMode(), ApiRunMode.JENKINS_API_PLAN.name())) { - //通过测试计划id查询环境 - request.setReportId(request.getTestPlanId()); - } // 多态JSON普通转换会丢失内容,需要通过 ObjectMapper 获取 if (testCaseWithBLOBs != null && StringUtils.isNotEmpty(testCaseWithBLOBs.getRequest())) { try { diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestCaseMapper.xml index a71e567983..80f278a383 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiTestCaseMapper.xml @@ -559,6 +559,9 @@ #{nodeId} </foreach> </if> + <if test="request.protocol != null and request.protocol !=''"> + and a.protocol = #{request.protocol} + </if> <if test="request.combine != null"> <include refid="combine"> <property name="condition" value="request.combine"/> @@ -571,7 +574,7 @@ <select id="selectIdsByQuery" resultType="java.lang.String"> SELECT t1.id FROM api_test_case t1 - <if test="request.moduleIds != null and request.moduleIds.size() > 0"> + <if test="(request.moduleIds != null and request.moduleIds.size() > 0 ) or (request.protocol!=null and request.protocol!='' )"> inner join api_definition a on t1.api_definition_id = a.id </if> <include refid="queryWhereCondition"/> diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanLoadCaseController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanLoadCaseController.java index ad53a54dd5..5402d34c4e 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanLoadCaseController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanLoadCaseController.java @@ -5,6 +5,7 @@ import com.github.pagehelper.PageHelper; import io.metersphere.base.domain.LoadTest; import io.metersphere.base.domain.TestPlanLoadCase; import io.metersphere.commons.constants.OperLogConstants; +import io.metersphere.commons.constants.TriggerMode; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; import io.metersphere.controller.request.ResetOrderRequest; @@ -66,6 +67,11 @@ public class TestPlanLoadCaseController { @PostMapping("/run/batch") @MsAuditLog(module = "track_test_plan", type = OperLogConstants.EXECUTE, content = "#msClass.getRunLogDetails(#request.requests)", msClass = TestPlanLoadCaseService.class) public void runBatch(@RequestBody RunBatchTestPlanRequest request) { + if (request.getRequests() != null) { + for (RunTestPlanRequest req : request.getRequests()) { + req.setTriggerMode(TriggerMode.BATCH.name()); + } + } testPlanLoadCaseService.runBatch(request); } @@ -81,13 +87,13 @@ public class TestPlanLoadCaseController { } @PostMapping("/update") - @MsAuditLog(module = "track_test_plan", type = OperLogConstants.UPDATE, content = "#msClass.getLogDetails(#testPlanLoadCase.id)", msClass = TestPlanLoadCaseService.class) + @MsAuditLog(module = "track_test_plan", type = OperLogConstants.UPDATE, content = "#msClass.getLogDetails(#testPlanLoadCase.id)", msClass = TestPlanLoadCaseService.class) public void update(@RequestBody TestPlanLoadCase testPlanLoadCase) { testPlanLoadCaseService.update(testPlanLoadCase); } @PostMapping("/update/api") - @MsAuditLog(module = "track_test_plan", type = OperLogConstants.UPDATE, content = "#msClass.getLogDetails(#testPlanLoadCase.id)", msClass = TestPlanLoadCaseService.class) + @MsAuditLog(module = "track_test_plan", type = OperLogConstants.UPDATE, content = "#msClass.getLogDetails(#testPlanLoadCase.id)", msClass = TestPlanLoadCaseService.class) public void updateByApi(@RequestBody TestPlanLoadCase testPlanLoadCase) { testPlanLoadCaseService.updateByApi(testPlanLoadCase); } diff --git a/frontend/src/business/components/api/definition/components/list/ApiCaseBatchRun.vue b/frontend/src/business/components/api/definition/components/list/ApiCaseBatchRun.vue index bc7990e83d..33a486403b 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiCaseBatchRun.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiCaseBatchRun.vue @@ -36,6 +36,7 @@ export default { }, save() { this.$emit('batchRun', this.environment); + this.close(); } } } diff --git a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue index 4ffe2b90ad..6149969d0f 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue @@ -517,7 +517,7 @@ export default { obj.ids = Array.from(this.selectRows).map(row => row.id); obj.environmentId = environment.id; obj.condition = this.condition; - + obj.condition.status = ""; this.$post('/api/testcase/batch/run', obj, () => { this.condition.ids = []; this.$refs.batchRun.close(); diff --git a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue index 7dbe07aa01..9ae014b118 100644 --- a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue @@ -267,7 +267,7 @@ export default { testPlanLoadId: loadCase.id, userId: getCurrentUserId(), projectId: getCurrentProjectID(), - triggerMode: 'MANUAL' + triggerMode: 'BATCH' }); }); let obj = {config: config, requests: runArr, userId: getCurrentUser().id}; From a96a6e93f82915f963dd71e43a8d0f795ea77298 Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Mon, 27 Sep 2021 18:51:37 +0800 Subject: [PATCH 56/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89)?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=201.8=20=E5=89=8D=E5=8D=95=E6=9D=A1?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E8=AE=A4=E8=AF=81=E6=95=B0=E6=8D=AE=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/definition/request/ElementUtil.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java index ee2d1720a5..9a0a3cbb00 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java @@ -302,6 +302,23 @@ public class ElementUtil { if (element != null && element.get("clazzName") == null && clazzMap.containsKey(element.getString("type"))) { element.fluentPut("clazzName", clazzMap.get(element.getString("type"))); } + if (element != null && element.get("clazzName") == null && element.getString("type").equals("TCPSampler")) { + if (element.getString("tcpPreProcessor") != null) { + JSONObject tcpPreProcessor = JSON.parseObject(element.getString("tcpPreProcessor")); + if (tcpPreProcessor != null && tcpPreProcessor.get("clazzName") == null) { + tcpPreProcessor.fluentPut("clazzName", clazzMap.get(tcpPreProcessor.getString("type"))); + element.fluentPut("tcpPreProcessor", tcpPreProcessor); + } + } + } else if (element != null && element.getString("type").equals("HTTPSamplerProxy")) { + if (element.getString("authManager") != null) { + JSONObject authManager = JSON.parseObject(element.getString("authManager")); + if (authManager != null && authManager.get("clazzName") == null) { + authManager.fluentPut("clazzName", clazzMap.get(authManager.getString("type"))); + element.fluentPut("authManager", authManager); + } + } + } if (element != null && element.containsKey("hashTree")) { JSONArray elementJSONArray = element.getJSONArray("hashTree"); dataFormatting(elementJSONArray); From a3c7dbde7fc844392bdae3176325683ac1eee2d7 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Tue, 28 Sep 2021 10:13:33 +0800 Subject: [PATCH 57/74] =?UTF-8?q?fix:=20=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E6=94=AF=E6=8C=81=E5=9C=A8=E8=84=91=E5=9B=BE=E4=B8=AD?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../testcase/TestCaseMinderEditRequest.java | 9 ++++- .../track/service/TestCaseService.java | 15 ++++++- .../track/common/minder/TestCaseMinder.vue | 40 +++++++++++++++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/io/metersphere/track/request/testcase/TestCaseMinderEditRequest.java b/backend/src/main/java/io/metersphere/track/request/testcase/TestCaseMinderEditRequest.java index ad6cd78cc7..3661a13f0a 100644 --- a/backend/src/main/java/io/metersphere/track/request/testcase/TestCaseMinderEditRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testcase/TestCaseMinderEditRequest.java @@ -11,5 +11,12 @@ import java.util.List; public class TestCaseMinderEditRequest { private String projectId; private List<String> ids; - List<TestCaseWithBLOBs> data; + List<TestCaseMinderEditItem> data; + + @Getter + @Setter + public static class TestCaseMinderEditItem extends TestCaseWithBLOBs { + private String targetId; + private String moveMode; + } } diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index 8f9ffc0cb2..421dad94c5 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -1427,7 +1427,7 @@ public class TestCaseService { } public void minderEdit(TestCaseMinderEditRequest request) { - List<TestCaseWithBLOBs> data = request.getData(); + List<TestCaseMinderEditRequest.TestCaseMinderEditItem> data = request.getData(); if (CollectionUtils.isNotEmpty(data)) { List<String> editIds = data.stream() .filter(t -> StringUtils.isNotBlank(t.getId()) && t.getId().length() > 20) @@ -1451,12 +1451,14 @@ public class TestCaseService { item.setId(UUID.randomUUID().toString()); item.setMaintainer(SessionUtils.getUserId()); addTestCase(item); + changeOrder(item, request.getProjectId()); } else { TestCaseWithBLOBs dbCase = finalTestCaseMap.get(item.getId()); if (editCustomFieldsPriority(dbCase, item.getPriority())) { item.setCustomFields(dbCase.getCustomFields()); } editTestCase(item); + changeOrder(item, request.getProjectId()); } }); } @@ -1468,6 +1470,17 @@ public class TestCaseService { } } + private void changeOrder(TestCaseMinderEditRequest.TestCaseMinderEditItem item, String projectId) { + if (StringUtils.isNotBlank(item.getTargetId())) { + ResetOrderRequest resetOrderRequest = new ResetOrderRequest(); + resetOrderRequest.setGroupId(projectId); + resetOrderRequest.setMoveId(item.getId()); + resetOrderRequest.setTargetId(item.getTargetId()); + resetOrderRequest.setMoveMode(item.getMoveMode()); + updateOrder(resetOrderRequest); + } + } + /** * 脑图编辑之后修改用例等级,同时修改自定义字段的用例等级 * diff --git a/frontend/src/business/components/track/common/minder/TestCaseMinder.vue b/frontend/src/business/components/track/common/minder/TestCaseMinder.vue index d71be71365..fb05d4b72e 100644 --- a/frontend/src/business/components/track/common/minder/TestCaseMinder.vue +++ b/frontend/src/business/components/track/common/minder/TestCaseMinder.vue @@ -131,7 +131,7 @@ name: "TestCaseMinder", let saveCases = []; let deleteCases = []; // 包含测试用例和临时节点 let saveExtraNode = {}; - this.buildSaveCase(data.root, saveCases, deleteCases, saveExtraNode, undefined); + this.buildSaveCase(data.root, saveCases, deleteCases, saveExtraNode); let param = { projectId: this.projectId, @@ -166,10 +166,10 @@ name: "TestCaseMinder", this.setIsChange(false); }); }, - buildSaveCase(root, saveCases, deleteCases, saveExtraNode, parent) { + buildSaveCase(root, saveCases, deleteCases, saveExtraNode, parent, preNode, nextNode) { let data = root.data; if (data.resource && data.resource.indexOf(this.$t('api_test.definition.request.case')) > -1) { - this._buildSaveCase(root, saveCases, deleteCases, parent); + this._buildSaveCase(root, saveCases, deleteCases, parent, preNode, nextNode); } else { let deleteChild = data.deleteChild; if (deleteChild && deleteChild.length > 0 @@ -194,13 +194,22 @@ name: "TestCaseMinder", throw new Error(tip); } if (root.children) { - root.children.forEach((childNode) => { - this.buildSaveCase(childNode, saveCases, deleteCases, saveExtraNode, root.data); - }); + for (let i = 0; i < root.children.length; i++) { + let childNode = root.children[i]; + let preNode = null; + let nextNode = null; + if (i != 0) { + preNode = root.children[i - 1]; + } + if (i + 1 < root.children.length) { + nextNode = root.children[i + 1]; + } + this.buildSaveCase(childNode, saveCases, deleteCases, saveExtraNode, root.data, preNode, nextNode); + } } } }, - _buildSaveCase(node, saveCases, deleteCases, parent) { + _buildSaveCase(node, saveCases, deleteCases, parent, preNode, nextNode) { let data = node.data; if (!data.text) { return; @@ -264,7 +273,24 @@ name: "TestCaseMinder", }) } testCase.steps = JSON.stringify(steps); + if (isChange) { + + testCase.targetId = null; // 排序处理 + if (preNode) { + let preId = preNode.data.id; + if (preId && preId.length > 15) { + testCase.targetId = preId; + testCase.moveMode = 'AFTER'; + } + } else if (nextNode) { + let nextId = nextNode.data.id; + if (nextId && nextId.length > 15) { + testCase.targetId = nextId; + testCase.moveMode = 'BEFORE'; + } + } + saveCases.push(testCase); } if (testCase.nodeId !== 'root' && testCase.nodeId.length < 15) { From cded9b1ad45dfe9d445d742cf41800c3c91ac468 Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Tue, 28 Sep 2021 10:14:44 +0800 Subject: [PATCH 58/74] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96)=20=E6=89=A7=E8=A1=8C=E5=BE=AE=E8=B0=83=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=89=B9=E6=AE=8A=E6=95=B0=E6=8D=AE=E4=BF=9D=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/definition/request/ElementUtil.java | 59 ++++++++----------- .../automation/scenario/EditApiScenario.vue | 2 + .../scenario/maximize/MaximizeScenario.vue | 8 +++ 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java index 9a0a3cbb00..48067aefb2 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/ElementUtil.java @@ -268,26 +268,33 @@ public class ElementUtil { } }; + private static void formatSampler(JSONObject element) { + if (element == null || StringUtils.isEmpty(element.getString("type"))) { + return; + } + if (element.get("clazzName") == null && element.getString("type").equals("TCPSampler")) { + if (element.getString("tcpPreProcessor") != null) { + JSONObject tcpPreProcessor = JSON.parseObject(element.getString("tcpPreProcessor")); + if (tcpPreProcessor != null && tcpPreProcessor.get("clazzName") == null) { + tcpPreProcessor.fluentPut("clazzName", clazzMap.get(tcpPreProcessor.getString("type"))); + element.fluentPut("tcpPreProcessor", tcpPreProcessor); + } + } + } else if (element.getString("type").equals("HTTPSamplerProxy")) { + if (element.getString("authManager") != null) { + JSONObject authManager = JSON.parseObject(element.getString("authManager")); + if (authManager != null && authManager.get("clazzName") == null) { + authManager.fluentPut("clazzName", clazzMap.get(authManager.getString("type"))); + element.fluentPut("authManager", authManager); + } + } + } + } + public static void dataFormatting(JSONArray hashTree) { for (int i = 0; i < hashTree.size(); i++) { JSONObject element = hashTree.getJSONObject(i); - if (element != null && element.get("clazzName") == null && element.getString("type").equals("TCPSampler")) { - if (element.getString("tcpPreProcessor") != null) { - JSONObject tcpPreProcessor = JSON.parseObject(element.getString("tcpPreProcessor")); - if (tcpPreProcessor != null && tcpPreProcessor.get("clazzName") == null) { - tcpPreProcessor.fluentPut("clazzName", clazzMap.get(tcpPreProcessor.getString("type"))); - element.fluentPut("tcpPreProcessor", tcpPreProcessor); - } - } - } else if (element != null && element.getString("type").equals("HTTPSamplerProxy")) { - if (element.getString("authManager") != null) { - JSONObject authManager = JSON.parseObject(element.getString("authManager")); - if (authManager != null && authManager.get("clazzName") == null) { - authManager.fluentPut("clazzName", clazzMap.get(authManager.getString("type"))); - element.fluentPut("authManager", authManager); - } - } - } + formatSampler(element); if (element != null && element.get("clazzName") == null && clazzMap.containsKey(element.getString("type"))) { element.fluentPut("clazzName", clazzMap.get(element.getString("type"))); } @@ -302,23 +309,7 @@ public class ElementUtil { if (element != null && element.get("clazzName") == null && clazzMap.containsKey(element.getString("type"))) { element.fluentPut("clazzName", clazzMap.get(element.getString("type"))); } - if (element != null && element.get("clazzName") == null && element.getString("type").equals("TCPSampler")) { - if (element.getString("tcpPreProcessor") != null) { - JSONObject tcpPreProcessor = JSON.parseObject(element.getString("tcpPreProcessor")); - if (tcpPreProcessor != null && tcpPreProcessor.get("clazzName") == null) { - tcpPreProcessor.fluentPut("clazzName", clazzMap.get(tcpPreProcessor.getString("type"))); - element.fluentPut("tcpPreProcessor", tcpPreProcessor); - } - } - } else if (element != null && element.getString("type").equals("HTTPSamplerProxy")) { - if (element.getString("authManager") != null) { - JSONObject authManager = JSON.parseObject(element.getString("authManager")); - if (authManager != null && authManager.get("clazzName") == null) { - authManager.fluentPut("clazzName", clazzMap.get(authManager.getString("type"))); - element.fluentPut("authManager", authManager); - } - } - } + formatSampler(element); if (element != null && element.containsKey("hashTree")) { JSONArray elementJSONArray = element.getJSONArray("hashTree"); dataFormatting(elementJSONArray); diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index 0ed80407e4..a62b58134a 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -315,6 +315,8 @@ :stepReEnable="stepEnable" :message="message" @openScenario="openScenario" + @runScenario="runDebug" + @stopScenario="stop" ref="maximizeScenario"/> </ms-drawer> <ms-change-history ref="changeHistory"/> diff --git a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue index 9770a70ec9..33eb551c9d 100644 --- a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue @@ -48,6 +48,8 @@ :env-map="projectEnvMap" :message="message" @remove="remove" @copyRow="copyRow" + @runScenario="runScenario" + @stopScenario="stopScenario" @suggestClick="suggestClick" @refReload="refReload" @openScenario="openScenario"/> </span> @@ -874,6 +876,12 @@ export default { disableAll() { this.stepEnable = false; this.stepNode(); + }, + runScenario(scenario) { + this.$emit('runScenario', scenario); + }, + stopScenario(){ + this.$emit('stopScenario'); } } } From a1abd2fd768e11eb0e4f81974467c4c1309a2a55 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Tue, 28 Sep 2021 10:46:05 +0800 Subject: [PATCH 59/74] =?UTF-8?q?fix:=20=E7=BC=96=E8=BE=91=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E8=AE=A1=E5=88=92=E6=97=B6=EF=BC=8C=E8=8B=A5=E8=AF=A5?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E7=A9=BA=E9=97=B4=E5=AD=98=E5=9C=A8=E5=90=8C?= =?UTF-8?q?=E5=90=8D=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=E4=BC=9A=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/metersphere/track/service/TestPlanService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index d63005b1de..e14d34ed49 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -348,7 +348,7 @@ public class TestPlanService { TestPlanExample example = new TestPlanExample(); example.createCriteria() .andNameEqualTo(testPlan.getName()) - .andWorkspaceIdEqualTo(SessionUtils.getCurrentWorkspaceId()) + .andProjectIdEqualTo(testPlan.getProjectId()) .andIdNotEqualTo(testPlan.getId()); if (testPlanMapper.selectByExample(example).size() > 0) { MSException.throwException(Translator.get("plan_name_already_exists")); From 0a47d283a2b1601c9fa79d9ef958c1601043ff6e Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Tue, 28 Sep 2021 10:26:45 +0800 Subject: [PATCH 60/74] =?UTF-8?q?feat=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=5F=E8=AF=B7=E6=B1=82=E4=BD=93=E4=B8=BAform-data=5F=E7=9A=84jso?= =?UTF-8?q?n=E5=8F=82=E6=95=B0=E7=9A=84=E5=8F=AF=E4=BB=A5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8F=82=E6=95=B0=E7=9A=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/definition/components/ApiVariable.vue | 425 +++++++++--------- 1 file changed, 213 insertions(+), 212 deletions(-) diff --git a/frontend/src/business/components/api/definition/components/ApiVariable.vue b/frontend/src/business/components/api/definition/components/ApiVariable.vue index 13f2d3b02e..0a34327de3 100644 --- a/frontend/src/business/components/api/definition/components/ApiVariable.vue +++ b/frontend/src/business/components/api/definition/components/ApiVariable.vue @@ -10,7 +10,7 @@ <el-row type="flex" :gutter="20" justify="space-between" align="middle"> <el-col class="kv-checkbox" v-if="isShowEnable"> <el-checkbox v-if="!isDisable(index)" v-model="item.enable" - :disabled="isReadOnly"/> + :disabled="isReadOnly"/> </el-col> <span style="margin-left: 10px" v-else></span> <i class="el-icon-top" style="cursor:pointer" @click="moveTop(index)"/> @@ -100,247 +100,248 @@ </template> <script> - import {KeyValue, Scenario} from "../model/ApiTestModel"; - import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants"; - import MsApiVariableAdvance from "./ApiVariableAdvance"; - import MsApiVariableJson from "./ApiVariableJson"; - import MsApiBodyFileUpload from "./body/ApiBodyFileUpload"; - import {REQUIRED} from "../model/JsonData"; - import Vue from 'vue'; - import ApiVariableSetting from "@/business/components/api/definition/components/ApiVariableSetting"; +import {KeyValue, Scenario} from "../model/ApiTestModel"; +import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants"; +import MsApiVariableAdvance from "./ApiVariableAdvance"; +import MsApiVariableJson from "./ApiVariableJson"; +import MsApiBodyFileUpload from "./body/ApiBodyFileUpload"; +import {REQUIRED} from "../model/JsonData"; +import Vue from 'vue'; +import ApiVariableSetting from "@/business/components/api/definition/components/ApiVariableSetting"; - export default { - name: "MsApiVariable", - components: {ApiVariableSetting, MsApiBodyFileUpload, MsApiVariableAdvance , MsApiVariableJson}, - props: { - keyPlaceholder: String, - valuePlaceholder: String, - description: String, - parameters: Array, - rest: Array, - environment: Object, - scenario: Scenario, - type: { - type: String, - default: '' - }, - isReadOnly: { - type: Boolean, - default: false - }, - isShowEnable: { - type: Boolean, - default: true - }, - suggestions: Array, - withMorSetting: Boolean +export default { + name: "MsApiVariable", + components: {ApiVariableSetting, MsApiBodyFileUpload, MsApiVariableAdvance, MsApiVariableJson}, + props: { + keyPlaceholder: String, + valuePlaceholder: String, + description: String, + parameters: Array, + rest: Array, + environment: Object, + scenario: Scenario, + type: { + type: String, + default: '' }, - data() { - return { - currentItem: null, - requireds: [ - {name: this.$t('commons.selector.required'), id: true}, - {name: this.$t('commons.selector.not_required'), id: false} - ], - isSelectAll: true, - isActive: true, + isReadOnly: { + type: Boolean, + default: false + }, + isShowEnable: { + type: Boolean, + default: true + }, + suggestions: Array, + withMorSetting: Boolean + }, + data() { + return { + currentItem: null, + requireds: [ + {name: this.$t('commons.selector.required'), id: true}, + {name: this.$t('commons.selector.not_required'), id: false} + ], + isSelectAll: true, + isActive: true, + } + }, + watch: { + isSelectAll: function (to, from) { + if (from == false && to == true) { + this.selectAll(); + } else if (from == true && to == false) { + this.invertSelect(); } }, - watch: { - isSelectAll: function(to, from) { - if(from == false && to == true) { - this.selectAll(); - } else if(from == true && to == false) { - this.invertSelect(); - } - }, + }, + computed: { + keyText() { + return this.keyPlaceholder || this.$t("api_test.key"); }, - computed: { - keyText() { - return this.keyPlaceholder || this.$t("api_test.key"); - }, - valueText() { - return this.valuePlaceholder || this.$t("api_test.value"); + valueText() { + return this.valuePlaceholder || this.$t("api_test.value"); + } + }, + methods: { + moveBottom(index) { + if (this.parameters.length < 2 || index === this.parameters.length - 2) { + return; } + let thisRow = this.parameters[index]; + let nextRow = this.parameters[index + 1]; + Vue.set(this.parameters, index + 1, thisRow); + Vue.set(this.parameters, index, nextRow) }, - methods: { - moveBottom(index) { - if (this.parameters.length < 2 || index === this.parameters.length - 2) { - return; - } - let thisRow = this.parameters[index]; - let nextRow = this.parameters[index + 1]; - Vue.set(this.parameters, index + 1, thisRow); - Vue.set(this.parameters, index, nextRow) - }, - moveTop(index) { - if (index === 0) { - return; - } - let thisRow = this.parameters[index]; - let lastRow = this.parameters[index - 1]; - Vue.set(this.parameters, index - 1, thisRow); - Vue.set(this.parameters, index, lastRow) + moveTop(index) { + if (index === 0) { + return; + } + let thisRow = this.parameters[index]; + let lastRow = this.parameters[index - 1]; + Vue.set(this.parameters, index - 1, thisRow); + Vue.set(this.parameters, index, lastRow) - }, - remove: function (index) { - // 移除整行输入控件及内容 - this.parameters.splice(index, 1); - this.$emit('change', this.parameters); - }, - change: function () { - let isNeedCreate = true; - let removeIndex = -1; - this.parameters.forEach((item, index) => { - if (!item.name && !item.value) { - // 多余的空行 - if (index !== this.parameters.length - 1) { - removeIndex = index; - } - // 没有空行,需要创建空行 - isNeedCreate = false; + }, + remove: function (index) { + // 移除整行输入控件及内容 + this.parameters.splice(index, 1); + this.$emit('change', this.parameters); + }, + change: function () { + let isNeedCreate = true; + let removeIndex = -1; + this.parameters.forEach((item, index) => { + if (!item.name && !item.value) { + // 多余的空行 + if (index !== this.parameters.length - 1) { + removeIndex = index; } - }); - if (isNeedCreate) { - this.parameters.push(new KeyValue({ - type: 'text', - enable: true, - uuid: this.uuid(), - contentType: 'text/plain' - })); + // 没有空行,需要创建空行 + isNeedCreate = false; } - this.$emit('change', this.parameters); - // TODO 检查key重复 - }, - isDisable: function (index) { - return this.parameters.length - 1 == index; - }, - querySearch(queryString, cb) { - let suggestions = this.suggestions; - let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions; - cb(results); - }, - createFilter(queryString) { - return (restaurant) => { - return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0); - }; - }, - funcSearch(queryString, cb) { - let funcs = MOCKJS_FUNC.concat(JMETER_FUNC); - let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs; - // 调用 callback 返回建议列表的数据 - cb(results); - }, - funcFilter(queryString) { - return (func) => { - return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1); - }; - }, - uuid: function () { - return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1); - }, - advanced(item) { - if (item.type === 'json'){ - this.$refs.variableJson.open(item); - this.currentItem = item; - }else { - this.$refs.variableAdvance.open(); - this.currentItem = item; - } - - }, - typeChange(item) { - if (item.type === 'file') { - item.contentType = 'application/octet-stream'; - } else if (item.type === 'text'){ - item.contentType = 'text/plain'; - } else { - item.contentType = 'application/json' - } - this.reload(); - }, - selectAll() { - this.parameters.forEach(item => { - item.enable = true; - }); - }, - invertSelect() { - this.parameters.forEach(item => { - item.enable = false; - }); - }, - reload() { - this.isActive = false; - this.$nextTick(() => { - this.isActive = true; - }); - }, - openApiVariableSetting(item) { - this.$refs.apiVariableSetting.open(item); - }, - callback(item){ - this.currentItem.value=item; - } - }, - created() { - if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) { + }); + if (isNeedCreate) { this.parameters.push(new KeyValue({ type: 'text', enable: true, - required: true, uuid: this.uuid(), contentType: 'text/plain' })); } + this.$emit('change', this.parameters); + // TODO 检查key重复 + }, + isDisable: function (index) { + return this.parameters.length - 1 == index; + }, + querySearch(queryString, cb) { + let suggestions = this.suggestions; + let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions; + cb(results); + }, + createFilter(queryString) { + return (restaurant) => { + return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0); + }; + }, + funcSearch(queryString, cb) { + let funcs = MOCKJS_FUNC.concat(JMETER_FUNC); + let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs; + // 调用 callback 返回建议列表的数据 + cb(results); + }, + funcFilter(queryString) { + return (func) => { + return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1); + }; + }, + uuid: function () { + return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1); + }, + advanced(item) { + if (item.type === 'json') { + this.$refs.variableJson.open(item); + this.currentItem = item; + } else { + this.$refs.variableAdvance.open(); + this.currentItem = item; + } + + }, + typeChange(item) { + if (item.type === 'file') { + item.contentType = 'application/octet-stream'; + } else if (item.type === 'text') { + item.contentType = 'text/plain'; + } else { + item.contentType = 'application/json' + } + this.reload(); + }, + selectAll() { + this.parameters.forEach(item => { + item.enable = true; + }); + }, + invertSelect() { + this.parameters.forEach(item => { + item.enable = false; + }); + }, + reload() { + this.isActive = false; + this.$nextTick(() => { + this.isActive = true; + }); + }, + openApiVariableSetting(item) { + this.$refs.apiVariableSetting.open(item); + }, + callback(item) { + this.currentItem.value = item; + this.currentItem = null; + } + }, + created() { + if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) { + this.parameters.push(new KeyValue({ + type: 'text', + enable: true, + required: true, + uuid: this.uuid(), + contentType: 'text/plain' + })); } } +} </script> <style scoped> - .kv-description { - font-size: 13px; - } +.kv-description { + font-size: 13px; +} - .kv-row { - margin-top: 10px; - } +.kv-row { + margin-top: 10px; +} - .kv-delete { - width: 60px; - } +.kv-delete { + width: 60px; +} - .kv-select { - width: 50%; - } +.kv-select { + width: 50%; +} - .el-autocomplete { - width: 100%; - } +.el-autocomplete { + width: 100%; +} - .kv-checkbox { - width: 20px; - margin-right: 10px; - } +.kv-checkbox { + width: 20px; + margin-right: 10px; +} - .advanced-item-value >>> .el-dialog__body { - padding: 15px 25px; - } +.advanced-item-value >>> .el-dialog__body { + padding: 15px 25px; +} - .el-row { - margin-bottom: 5px; - } +.el-row { + margin-bottom: 5px; +} - .kv-type { - width: 70px; - } +.kv-type { + width: 70px; +} - .pointer { - cursor: pointer; - color: #1E90FF; - } +.pointer { + cursor: pointer; + color: #1E90FF; +} - .kv-setting { - width: 40px; - padding: 0px !important; - } +.kv-setting { + width: 40px; + padding: 0px !important; +} </style> From ff1407d24f40f361ae01640b10b8c8f137b690c0 Mon Sep 17 00:00:00 2001 From: song-tianyang <tianyang.song@fit2cloud.com> Date: Tue, 28 Sep 2021 10:52:04 +0800 Subject: [PATCH 61/74] =?UTF-8?q?refactor(mock=E6=B5=8B=E8=AF=95=EF=BC=8C?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E7=AE=A1=E7=90=86=EF=BC=8C=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8):=20#1006965=20#1006966=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E7=9A=84json=E6=A0=BC=E5=BC=8F=E6=94=B9?= =?UTF-8?q?=E4=B8=BAjson-shcema=E5=B1=95=E7=A4=BA=E3=80=81=E5=BC=80?= =?UTF-8?q?=E6=BA=90=E7=89=88=E6=9C=AC=E7=9C=8B=E4=B8=8D=E5=88=B0=E6=8A=A5?= =?UTF-8?q?=E8=A1=A8=E8=8F=9C=E5=8D=95=E3=80=81=E5=AF=BC=E5=87=BA=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=97=B6=E6=B2=A1=E6=9C=89mock=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E7=9A=84=E9=97=AE=E9=A2=98=E5=8F=8Amock?= =?UTF-8?q?=E6=B5=8B=E8=AF=95json=E5=8F=82=E6=95=B0=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E5=BA=A6=E6=9C=89=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【【github#6183】mock接口填不同参数,没有按设置的期望返回结果,返回】https://www.tapd.cn/55049933/bugtrace/bugs/view?bug_id=1155049933001006966;【【接口测试】导出接口定义没有导出mock设置】https://www.tapd.cn/55049933/bugtrace/bugs/view?bug_id=1155049933001006965 --- .../api/dto/definition/MsApiExportResult.java | 2 + .../definition/parse/ApiDefinitionImport.java | 4 + .../dto/mockconfig/MockConfigImportDTO.java | 15 + .../api/service/ApiDefinitionService.java | 37 +- .../api/service/MockConfigService.java | 86 +++ .../mapper/ext/ExtMockExpectConfigMapper.java | 5 + .../mapper/ext/ExtMockExpectConfigMapper.xml | 16 +- .../commons/utils/JsonStructUtils.java | 70 +- .../components/document/ApiDocumentAnchor.vue | 396 ++++++---- .../components/document/ApiDocumentItem.vue | 717 ------------------ .../components/list/ApiDocumentsPage.vue | 1 - .../components/common/head/HeaderTopMenus.vue | 10 +- .../common/json-schema/JsonSchemaEditor.vue | 19 +- .../common/select-tree/SelectTree.vue | 8 +- 14 files changed, 457 insertions(+), 929 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/dto/mockconfig/MockConfigImportDTO.java delete mode 100644 frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/MsApiExportResult.java b/backend/src/main/java/io/metersphere/api/dto/definition/MsApiExportResult.java index 0b839273b6..07684fe4e6 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/MsApiExportResult.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/MsApiExportResult.java @@ -1,5 +1,6 @@ package io.metersphere.api.dto.definition; +import io.metersphere.api.dto.mockconfig.MockConfigImportDTO; import io.metersphere.base.domain.ApiDefinitionWithBLOBs; import io.metersphere.base.domain.ApiTestCaseWithBLOBs; @@ -17,4 +18,5 @@ public class MsApiExportResult extends ApiExportResult { private String version; private List<ApiDefinitionWithBLOBs> data; private List<ApiTestCaseWithBLOBs> cases; + private List<MockConfigImportDTO> mocks; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImport.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImport.java index ab80415bb1..bf17ae66f8 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImport.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImport.java @@ -1,6 +1,7 @@ package io.metersphere.api.dto.definition.parse; import io.metersphere.api.dto.definition.parse.ms.NodeTree; +import io.metersphere.api.dto.mockconfig.MockConfigImportDTO; import io.metersphere.base.domain.ApiDefinitionWithBLOBs; import io.metersphere.base.domain.ApiTestCaseWithBLOBs; import io.metersphere.base.domain.EsbApiParamsWithBLOBs; @@ -21,5 +22,8 @@ public class ApiDefinitionImport { //ESB文件导入的附属数据类 private Map<String,EsbApiParamsWithBLOBs> esbApiParamsMap; + //Mock数据相关 + private List<MockConfigImportDTO> mocks; + private List<NodeTree> nodeTree; } diff --git a/backend/src/main/java/io/metersphere/api/dto/mockconfig/MockConfigImportDTO.java b/backend/src/main/java/io/metersphere/api/dto/mockconfig/MockConfigImportDTO.java new file mode 100644 index 0000000000..a3478bff5f --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/mockconfig/MockConfigImportDTO.java @@ -0,0 +1,15 @@ +package io.metersphere.api.dto.mockconfig; + +import io.metersphere.base.domain.MockExpectConfigWithBLOBs; +import lombok.Getter; +import lombok.Setter; + +/** + * @author song.tianyang + * @Date 2021/9/27 5:54 下午 + */ +@Getter +@Setter +public class MockConfigImportDTO extends MockExpectConfigWithBLOBs { + public String apiId; +} diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index 10351697e9..4517f33043 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -14,6 +14,7 @@ import io.metersphere.api.dto.definition.parse.Swagger3Parser; import io.metersphere.api.dto.definition.request.ParameterConfig; import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler; +import io.metersphere.api.dto.mockconfig.MockConfigImportDTO; import io.metersphere.api.dto.scenario.Body; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.dto.scenario.request.RequestType; @@ -210,6 +211,8 @@ public class ApiDefinitionService { extApiDefinitionExecResultMapper.deleteByResourceId(apiId); apiDefinitionMapper.deleteByPrimaryKey(apiId); esbApiParamService.deleteByResourceId(apiId); + MockConfigService mockConfigService = CommonBeanFactory.getBean(MockConfigService.class); + mockConfigService.deleteMockConfigByApiId(apiId); FileUtils.deleteBodyFiles(apiId); } @@ -219,6 +222,10 @@ public class ApiDefinitionService { esbApiParamService.deleteByResourceIdIn(apiIds); apiDefinitionMapper.deleteByExample(example); apiTestCaseService.deleteBatchByDefinitionId(apiIds); + MockConfigService mockConfigService = CommonBeanFactory.getBean(MockConfigService.class); + for (String apiId : apiIds) { + mockConfigService.deleteMockConfigByApiId(apiId); + } } public void removeToGc(List<String> apiIds) { @@ -466,7 +473,7 @@ public class ApiDefinitionService { } private ApiDefinition importCreate(ApiDefinitionWithBLOBs apiDefinition, ApiDefinitionMapper batchMapper, - ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List<ApiTestCaseWithBLOBs> cases, + ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List<ApiTestCaseWithBLOBs> cases, List<MockConfigImportDTO> mocks, Boolean repeatable) { SaveApiDefinitionRequest saveReq = new SaveApiDefinitionRequest(); BeanUtils.copyBean(saveReq, apiDefinition); @@ -493,7 +500,7 @@ public class ApiDefinitionService { sameRequest = getSameRequestById(apiDefinition.getId(), apiTestImportRequest.getProjectId()); } if (StringUtils.equals("fullCoverage", apiTestImportRequest.getModeId())) { - _importCreate(sameRequest, batchMapper, apiDefinition, apiTestCaseMapper, apiTestImportRequest, cases); + _importCreate(sameRequest, batchMapper, apiDefinition, apiTestCaseMapper, apiTestImportRequest, cases, mocks); } else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) { if (CollectionUtils.isEmpty(sameRequest)) { //postman 可能含有前置脚本,接口定义去掉脚本 @@ -507,7 +514,7 @@ public class ApiDefinitionService { importApiCase(apiDefinition, apiTestImportRequest); } } else { - _importCreate(sameRequest, batchMapper, apiDefinition, apiTestCaseMapper, apiTestImportRequest, cases); + _importCreate(sameRequest, batchMapper, apiDefinition, apiTestCaseMapper, apiTestImportRequest, cases, mocks); } return apiDefinition; @@ -534,12 +541,13 @@ public class ApiDefinitionService { } private void _importCreate(List<ApiDefinition> sameRequest, ApiDefinitionMapper batchMapper, ApiDefinitionWithBLOBs apiDefinition, - ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List<ApiTestCaseWithBLOBs> cases) { + ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List<ApiTestCaseWithBLOBs> cases ,List<MockConfigImportDTO> mocks) { String originId = apiDefinition.getId(); if (CollectionUtils.isEmpty(sameRequest)) { apiDefinition.setId(UUID.randomUUID().toString()); apiDefinition.setOrder(getImportNextOrder(apiTestImportRequest.getProjectId())); reSetImportCasesApiId(cases, originId, apiDefinition.getId()); + reSetImportMocksApiId(mocks, originId, apiDefinition.getId()); if (StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(), RequestType.HTTP)) { batchMapper.insert(apiDefinition); String request = setImportHashTree(apiDefinition); @@ -595,6 +603,16 @@ public class ApiDefinitionService { } } + private void reSetImportMocksApiId(List<MockConfigImportDTO> mocks, String originId, String newId) { + if (CollectionUtils.isNotEmpty(mocks)) { + mocks.forEach(item -> { + if (StringUtils.equals(item.getApiId(), originId)) { + item.setApiId(newId); + } + }); + } + } + private String setImportHashTree(ApiDefinitionWithBLOBs apiDefinition) { String request = apiDefinition.getRequest(); MsHTTPSamplerProxy msHTTPSamplerProxy = JSONObject.parseObject(request, MsHTTPSamplerProxy.class); @@ -936,14 +954,14 @@ public class ApiDefinitionService { if (apiImport.getEsbApiParamsMap() != null) { String apiId = item.getId(); EsbApiParamsWithBLOBs model = apiImport.getEsbApiParamsMap().get(apiId); - importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases(), project.getRepeatable()); + importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases(), apiImport.getMocks(), project.getRepeatable()); if (model != null) { apiImport.getEsbApiParamsMap().remove(apiId); model.setResourceId(item.getId()); apiImport.getEsbApiParamsMap().put(item.getId(), model); } } else { - importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases(), project.getRepeatable()); + importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases(), apiImport.getMocks(), project.getRepeatable()); } if (i % 300 == 0) { sqlSession.flushStatements(); @@ -967,6 +985,11 @@ public class ApiDefinitionService { } } + if (!CollectionUtils.isEmpty(apiImport.getMocks())) { + MockConfigService mockConfigService = CommonBeanFactory.getBean(MockConfigService.class); + mockConfigService.importMock(apiImport, sqlSession, request); + } + if (!CollectionUtils.isEmpty(apiImport.getCases())) { importMsCase(apiImport, sqlSession, request); } @@ -1253,9 +1276,11 @@ public class ApiDefinitionService { example.createCriteria().andIdIn(request.getIds()); if (StringUtils.equals(type, "MS")) { // 导出为 Metersphere 格式 + MockConfigService mockConfigService = CommonBeanFactory.getBean(MockConfigService.class); apiExportResult = new MsApiExportResult(); ((MsApiExportResult) apiExportResult).setData(apiDefinitionMapper.selectByExampleWithBLOBs(example)); ((MsApiExportResult) apiExportResult).setCases(apiTestCaseService.selectCasesBydApiIds(request.getIds())); + ((MsApiExportResult) apiExportResult).setMocks(mockConfigService.selectMockExpectConfigByApiIdIn(request.getIds())); ((MsApiExportResult) apiExportResult).setProjectName(request.getProjectId()); ((MsApiExportResult) apiExportResult).setProtocol(request.getProtocol()); ((MsApiExportResult) apiExportResult).setProjectId(request.getProjectId()); diff --git a/backend/src/main/java/io/metersphere/api/service/MockConfigService.java b/backend/src/main/java/io/metersphere/api/service/MockConfigService.java index f3f3104045..f3b8ca06ca 100644 --- a/backend/src/main/java/io/metersphere/api/service/MockConfigService.java +++ b/backend/src/main/java/io/metersphere/api/service/MockConfigService.java @@ -4,9 +4,12 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONValidator; +import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.automation.EsbDataStruct; import io.metersphere.api.dto.automation.TcpTreeTableDataStruct; import io.metersphere.api.dto.automation.parse.TcpTreeTableDataParser; +import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; +import io.metersphere.api.dto.mockconfig.MockConfigImportDTO; import io.metersphere.api.dto.mockconfig.MockConfigRequest; import io.metersphere.api.dto.mockconfig.MockExpectConfigRequest; import io.metersphere.api.dto.mockconfig.response.JsonSchemaReturnObj; @@ -23,6 +26,7 @@ import io.metersphere.jmeter.utils.ScriptEngineUtils; import io.metersphere.i18n.Translator; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.SqlSession; import org.json.XML; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -65,6 +69,28 @@ public class MockConfigService { return this.assemblyMockConfingResponse(configList); } + public List<MockExpectConfigWithBLOBs> selectMockExpectConfigByApiId(String apiId){ + return extMockExpectConfigMapper.selectByApiId(apiId); + } + + public List<MockConfigImportDTO> selectMockExpectConfigByApiIdIn(List<String> apiIds){ + if(CollectionUtils.isNotEmpty(apiIds)){ + List<MockConfigImportDTO> returnDTO = new ArrayList<>(); + for (String apiId : apiIds) { + List<MockExpectConfigWithBLOBs> mockExpectConfigWithBLOBsList = extMockExpectConfigMapper.selectByApiId(apiId); + for (MockExpectConfigWithBLOBs model : mockExpectConfigWithBLOBsList) { + MockConfigImportDTO dto = new MockConfigImportDTO(); + BeanUtils.copyBean(dto, model); + dto.setApiId(apiId); + returnDTO.add(dto); + } + } + return returnDTO; + }else { + return new ArrayList<>(); + } + } + private MockConfigResponse assemblyMockConfingResponse(List<MockConfig> configList) { if (!configList.isEmpty()) { MockConfig config = configList.get(0); @@ -610,6 +636,19 @@ public class MockConfigService { mockExpectConfigMapper.deleteByPrimaryKey(id); } + public void deleteMockConfigByApiId(String apiId){ + MockConfigExample configExample = new MockConfigExample(); + configExample.createCriteria().andApiIdEqualTo(apiId); + List<MockConfig> mockConfigList = mockConfigMapper.selectByExample(configExample); + MockExpectConfigExample example = new MockExpectConfigExample(); + for (MockConfig mockConfig : mockConfigList) { + example.clear(); + example.createCriteria().andMockConfigIdEqualTo(mockConfig.getId()); + mockExpectConfigMapper.deleteByExample(example); + } + mockConfigMapper.deleteByExample(configExample); + } + public JSONObject getGetParamMap(String urlParams, ApiDefinitionWithBLOBs api, HttpServletRequest request) { JSONObject paramMap = this.getSendRestParamMapByIdAndUrl(api, urlParams); Enumeration<String> paramNameItor = request.getParameterNames(); @@ -1187,4 +1226,51 @@ public class MockConfigService { } return isJson; } + + public void importMock(ApiDefinitionImport apiImport, SqlSession sqlSession, ApiTestImportRequest request) { + if(CollectionUtils.isNotEmpty(apiImport.getMocks())){ + Map<String,List<MockExpectConfigWithBLOBs>> saveMap = new HashMap<>(); + for (MockConfigImportDTO dto : apiImport.getMocks()) { + String apiId = dto.getApiId();//de33108c-26e2-4d4f-826a-a5f8e017d2f4 + if(saveMap.containsKey(apiId)){ + saveMap.get(apiId).add(dto); + }else { + List<MockExpectConfigWithBLOBs> list = new ArrayList<>(); + list.add(dto); + saveMap.put(apiId,list); + } + } + + for (Map.Entry<String,List<MockExpectConfigWithBLOBs>> entry : saveMap.entrySet()) { + String apiId = entry.getKey(); + this.deleteMockConfigByApiId(apiId); + + List<MockExpectConfigWithBLOBs> list = entry.getValue(); + + String mockId = UUID.randomUUID().toString(); + MockConfig config = new MockConfig(); + config.setProjectId(request.getProjectId()); + config.setId(mockId); + config.setCreateUserId(SessionUtils.getUserId()); + config.setCreateTime(System.currentTimeMillis()); + config.setUpdateTime(System.currentTimeMillis()); + config.setApiId(apiId); + mockConfigMapper.insert(config); + + int batchCount = 0; + for (MockExpectConfigWithBLOBs mockExpect : list) { + mockExpect.setId(UUID.randomUUID().toString()); + mockExpect.setMockConfigId(mockId); + mockExpect.setCreateTime(System.currentTimeMillis()); + mockExpect.setUpdateTime(System.currentTimeMillis()); + mockExpect.setCreateUserId(SessionUtils.getUserId()); + mockExpectConfigMapper.insert(mockExpect); + } + if (batchCount % 300 == 0) { + sqlSession.flushStatements(); + } + } + + } + } } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.java index 18d2a08f40..4240df4316 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.java @@ -1,10 +1,15 @@ package io.metersphere.base.mapper.ext; import io.metersphere.base.domain.MockExpectConfigWithBLOBs; +import org.apache.ibatis.annotations.Param; import java.util.List; public interface ExtMockExpectConfigMapper { List<MockExpectConfigWithBLOBs> selectByProjectIdAndStatusIsOpen(String projectId); + + List<MockExpectConfigWithBLOBs> selectByApiId(String apiId); + + List<MockExpectConfigWithBLOBs> selectByApiIdIn(@Param("values") List<String> apiIds); } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.xml index 84cfc506f4..a74d8284b0 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtMockExpectConfigMapper.xml @@ -6,5 +6,19 @@ SELECT * FROM mock_expect_config WHERE status = 'true' AND mock_config_id IN (SELECT id FROM mock_config WHERE api_path IS NULL AND project_id = #{0} ) </select> - + <select id="selectByApiId" resultType="io.metersphere.base.domain.MockExpectConfigWithBLOBs"> + SELECT * FROM mock_expect_config WHERE mock_config_id IN + ( + SELECT id FROM mock_config WHERE api_id = #{0} + ) + </select> + <select id="selectByApiIdIn" resultType="io.metersphere.base.domain.MockExpectConfigWithBLOBs"> + SELECT * FROM mock_expect_config WHERE mock_config_id IN + ( + SELECT id FROM mock_config WHERE api_id IN + <foreach collection="values" item="value" separator="," open="(" close=")"> + #{value} + </foreach> + ) + </select> </mapper> \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/utils/JsonStructUtils.java b/backend/src/main/java/io/metersphere/commons/utils/JsonStructUtils.java index 496ab6c3ec..214a98fb48 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/JsonStructUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/JsonStructUtils.java @@ -10,6 +10,7 @@ import java.util.Set; /** * JSON数据结构相关的工具类 + * * @author song.tianyang * @Date 2021/8/16 3:50 下午 */ @@ -26,14 +27,18 @@ public class JsonStructUtils { if (sourceObj == null && matchObj == null) { return true; } else if (sourceObj != null && matchObj != null) { - boolean isMatch = false; + boolean lastMatchResultIsTrue = false; + boolean hasNotMatchResult = false; try { Set<String> matchKeys = matchObj.keySet(); for (String key : matchKeys) { if (sourceObj.containsKey(key)) { Object sourceObjItem = sourceObj.get(key); Object matchObjItem = matchObj.get(key); - isMatch = checkObjCompliance(sourceObjItem, matchObjItem); + lastMatchResultIsTrue = checkObjCompliance(sourceObjItem, matchObjItem); + if (!lastMatchResultIsTrue) { + hasNotMatchResult = true; + } } else { return false; } @@ -41,7 +46,7 @@ public class JsonStructUtils { } catch (Exception e) { e.printStackTrace(); } - return isMatch; + return lastMatchResultIsTrue && !hasNotMatchResult; } else { return false; } @@ -52,9 +57,9 @@ public class JsonStructUtils { return true; } else if (sourceArray != null && matchArray != null && sourceArray.size() > matchArray.size()) { try { - for (int i = 0; i < matchArray.size(); i ++) { + for (int i = 0; i < matchArray.size(); i++) { Object obj = matchArray.get(i); - if(!sourceArray.contains(obj)){ + if (!sourceArray.contains(obj)) { return false; } } @@ -70,13 +75,13 @@ public class JsonStructUtils { public static boolean checkJsonArrayContainsObj(JSONArray sourceArray, JSONObject matchObj) { if (sourceArray == null && matchObj == null) { return true; - } else if (sourceArray != null && matchObj != null ) { + } else if (sourceArray != null && matchObj != null) { try { - for (int i = 0; i < sourceArray.size(); i ++) { + for (int i = 0; i < sourceArray.size(); i++) { Object obj = sourceArray.get(i); - if(obj instanceof JSONObject){ - boolean isMatch = checkJsonObjCompliance((JSONObject) obj,matchObj); - if(isMatch){ + if (obj instanceof JSONObject) { + boolean isMatch = checkJsonObjCompliance((JSONObject) obj, matchObj); + if (isMatch) { return isMatch; } } @@ -92,6 +97,7 @@ public class JsonStructUtils { /** * 检查一个JSON对象的数据集合是否包含另一个对象(包含) + * * @param sourceArray * @param matchObj * @return @@ -103,14 +109,14 @@ public class JsonStructUtils { boolean isMatch = false; try { Set<String> matchKeys = matchObj.keySet(); - for(int sourceIndex = 0;sourceIndex < sourceArray.size();sourceIndex ++){ + for (int sourceIndex = 0; sourceIndex < sourceArray.size(); sourceIndex++) { JSONObject sourceObj = sourceArray.getJSONObject(sourceIndex); for (String key : matchKeys) { if (sourceObj.containsKey(key)) { Object sourceObjItem = sourceObj.get(key); Object matchObjItem = matchObj.get(key); isMatch = checkObjCompliance(sourceObjItem, matchObjItem); - if(!isMatch){ + if (!isMatch) { break; } } else { @@ -119,7 +125,7 @@ public class JsonStructUtils { } } - if(isMatch){ + if (isMatch) { break; } } @@ -173,12 +179,12 @@ public class JsonStructUtils { public static void deepParseKeyByJsonObject(JSONObject jsonObject, List<String> keyList) { for (String key : jsonObject.keySet()) { Object obj = jsonObject.get(key); - if(obj instanceof JSONArray) { + if (obj instanceof JSONArray) { deepParseKeyByJsonArray((JSONArray) obj, keyList); - }else if(obj instanceof JSONObject){ - deepParseKeyByJsonObject((JSONObject) obj,keyList); - }else { - if(!keyList.contains(key)){ + } else if (obj instanceof JSONObject) { + deepParseKeyByJsonObject((JSONObject) obj, keyList); + } else { + if (!keyList.contains(key)) { keyList.add(key); } } @@ -186,10 +192,10 @@ public class JsonStructUtils { } public static void deepParseKeyByJsonArray(JSONArray jsonArray, List<String> keyList) { - for (int i = 0; i < jsonArray.size(); i ++) { + for (int i = 0; i < jsonArray.size(); i++) { Object itemObj = jsonArray.get(i); - if(itemObj instanceof JSONObject){ - deepParseKeyByJsonObject((JSONObject)itemObj,keyList); + if (itemObj instanceof JSONObject) { + deepParseKeyByJsonObject((JSONObject) itemObj, keyList); } } } @@ -204,31 +210,31 @@ public class JsonStructUtils { JSONValidator matchValidator = JSONValidator.from(matchJson); String sourceType = sourceValidator.getType().name(); String matchType = matchValidator.getType().name(); - if(StringUtils.equalsIgnoreCase(sourceType,"array")&&StringUtils.equalsIgnoreCase(matchType,"array")){ + if (StringUtils.equalsIgnoreCase(sourceType, "array") && StringUtils.equalsIgnoreCase(matchType, "array")) { isSourceJsonIsArray = true; isMatchJsonIsArray = true; - }else if(StringUtils.equalsIgnoreCase(sourceType,"array")){ + } else if (StringUtils.equalsIgnoreCase(sourceType, "array")) { isSourceJsonIsArray = true; - }else if(StringUtils.equalsIgnoreCase(matchType,"array")){ + } else if (StringUtils.equalsIgnoreCase(matchType, "array")) { isMatchJsonIsArray = true; } - if(isSourceJsonIsArray && isMatchJsonIsArray){ + if (isSourceJsonIsArray && isMatchJsonIsArray) { JSONArray sourceArr = JSONArray.parseArray(sourceJson); JSONArray compArr = JSONArray.parseArray(matchJson); - isMatch = checkJsonArrayCompliance(sourceArr,compArr); - }else if(isSourceJsonIsArray && !isMatchJsonIsArray){ + isMatch = checkJsonArrayCompliance(sourceArr, compArr); + } else if (isSourceJsonIsArray && !isMatchJsonIsArray) { JSONArray sourceArr = JSONArray.parseArray(sourceJson); JSONObject compObj = JSONObject.parseObject(matchJson); - isMatch = checkJsonArrayContainsObj(sourceArr,compObj); - }else if(!isSourceJsonIsArray && !isMatchJsonIsArray){ + isMatch = checkJsonArrayContainsObj(sourceArr, compObj); + } else if (!isSourceJsonIsArray && !isMatchJsonIsArray) { JSONObject sourceObj = JSONObject.parseObject(sourceJson); JSONObject compObj = JSONObject.parseObject(matchJson); - isMatch = checkJsonObjCompliance(sourceObj,compObj); - }else { + isMatch = checkJsonObjCompliance(sourceObj, compObj); + } else { isMatch = false; } - }catch (Exception e){ + } catch (Exception e) { } return isMatch; diff --git a/frontend/src/business/components/api/definition/components/document/ApiDocumentAnchor.vue b/frontend/src/business/components/api/definition/components/document/ApiDocumentAnchor.vue index 3b66277fbb..ac581cb2dd 100644 --- a/frontend/src/business/components/api/definition/components/document/ApiDocumentAnchor.vue +++ b/frontend/src/business/components/api/definition/components/document/ApiDocumentAnchor.vue @@ -3,28 +3,40 @@ <el-container v-loading="isLoading"> <el-main style="padding-top: 0px;padding-bottom: 0px"> <el-row v-if="sharePage" style="margin-top: 10px"> - <el-select size="small" :placeholder="$t('api_test.definition.document.order')" v-model="apiSearch.orderCondition" style="float: right;width: 180px;margin-right: 5px" + <el-select size="small" :placeholder="$t('api_test.definition.document.order')" + v-model="apiSearch.orderCondition" style="float: right;width: 180px;margin-right: 5px" class="ms-api-header-select" @change="initApiDocSimpleList" clearable> - <el-option key="createTimeDesc" :label="$t('api_test.definition.document.create_time_sort')" value="createTimeDesc" /> - <el-option key="editTimeAsc" :label="$t('api_test.definition.document.edit_time_positive_sequence')" value="editTimeAsc"/> - <el-option key="editTimeDesc" :label="$t('api_test.definition.document.edit_time_Reverse_order')" value="editTimeDesc"/> + <el-option key="createTimeDesc" :label="$t('api_test.definition.document.create_time_sort')" + value="createTimeDesc"/> + <el-option key="editTimeAsc" :label="$t('api_test.definition.document.edit_time_positive_sequence')" + value="editTimeAsc"/> + <el-option key="editTimeDesc" :label="$t('api_test.definition.document.edit_time_Reverse_order')" + value="editTimeDesc"/> </el-select> - <el-select size="small" :placeholder="$t('api_test.definition.document.request_method')" v-model="apiSearch.type" style="float: right;width: 180px;margin-right: 5px" + <el-select size="small" :placeholder="$t('api_test.definition.document.request_method')" + v-model="apiSearch.type" style="float: right;width: 180px;margin-right: 5px" class="ms-api-header-select" @change="initApiDocSimpleList" clearable> <el-option key="ALL" :label="$t('api_test.definition.document.data_set.all')" value="ALL"/> <el-option key="GET" :label="'GET '+$t('api_test.definition.document.request_interface')" value="GET"/> <el-option key="POST" :label="'POST '+$t('api_test.definition.document.request_interface')" value="POST"/> <el-option key="PUT" :label="'PUT '+$t('api_test.definition.document.request_interface')" value="PUT"/> - <el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" value="DELETE"/> - <el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" value="PATCH"/> - <el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" value="OPTIONS"/> + <el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" + value="DELETE"/> + <el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" + value="PATCH"/> + <el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" + value="OPTIONS"/> <el-option key="HEAD" :label="'HEAD '+$t('api_test.definition.document.request_interface')" value="HEAD"/> - <el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" value="CONNECT"/> + <el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" + value="CONNECT"/> </el-select> - <el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" style="float: right;width: 180px;margin-right: 5px" size="small" + <el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" + style="float: right;width: 180px;margin-right: 5px" size="small" @keyup.enter.native="initApiDocSimpleList()" v-model="apiSearch.name"/> - <api-document-batch-share v-xpack v-if="showXpackCompnent" @shareApiDocument="shareApiDocument" :project-id="projectId" :share-url="batchShareUrl" style="float: right;margin: 6px;font-size: 17px"/> + <api-document-batch-share v-xpack v-if="showXpackCompnent" @shareApiDocument="shareApiDocument" + :project-id="projectId" :share-url="batchShareUrl" + style="float: right;margin: 6px;font-size: 17px"/> </el-row> <el-row v-else style="margin-top: 0px;position: fixed;float: right;margin-right: 0px;margin-left: 400px;top: 135px; right: 90px;"> @@ -46,19 +58,26 @@ <el-option key="GET" :label="'GET '+$t('api_test.definition.document.request_interface')" value="GET"/> <el-option key="POST" :label="'POST '+$t('api_test.definition.document.request_interface')" value="POST"/> <el-option key="PUT" :label="'PUT '+$t('api_test.definition.document.request_interface')" value="PUT"/> - <el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" value="DELETE"/> - <el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" value="PATCH"/> - <el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" value="OPTIONS"/> + <el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" + value="DELETE"/> + <el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" + value="PATCH"/> + <el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" + value="OPTIONS"/> <el-option key="HEAD" :label="'HEAD '+$t('api_test.definition.document.request_interface')" value="HEAD"/> - <el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" value="CONNECT"/> + <el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" + value="CONNECT"/> </el-select> - <el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" style="float: right;width: 180px;margin-right: 5px" size="small" + <el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" + style="float: right;width: 180px;margin-right: 5px" size="small" @keyup.enter.native="initApiDocSimpleList()" v-model="apiSearch.name"/> - <api-document-batch-share v-xpack v-if="showXpackCompnent" @shareApiDocument="shareApiDocument" :project-id="projectId" :share-url="batchShareUrl" style="float: right;margin: 6px;font-size: 17px"/> + <api-document-batch-share v-xpack v-if="showXpackCompnent" @shareApiDocument="shareApiDocument" + :project-id="projectId" :share-url="batchShareUrl" + style="float: right;margin: 6px;font-size: 17px"/> </el-row> <el-divider></el-divider> - <div ref="apiDocInfoDiv" @scroll="handleScroll" > + <div ref="apiDocInfoDiv" @scroll="handleScroll"> <div v-for="(apiInfo) in apiShowArray" :key="apiInfo.id" ref="apiDocInfoDivItem"> <div style="font-size: 17px"> <el-popover @@ -66,10 +85,11 @@ placement="right" width="260" @show="shareApiDocument('false')"> - <p>{{shareUrl}}</p> + <p>{{ shareUrl }}</p> <div style="text-align: right; margin: 0"> <el-button type="primary" size="mini" - v-clipboard:copy="shareUrl">{{ $t("commons.copy") }}</el-button> + v-clipboard:copy="shareUrl">{{ $t("commons.copy") }} + </el-button> </div> <i class="el-icon-share" slot="reference" style="margin-right: 10px;cursor: pointer"></i> </el-popover> @@ -131,10 +151,6 @@ :label="$t('api_test.definition.document.table_coloum.name')" min-width="120px" show-overflow-tooltip/> -<!-- <el-table-column prop="isEnable"--> -<!-- :label="$t('api_test.definition.document.table_coloum.is_required')"--> -<!-- min-width="80px"--> -<!-- show-overflow-tooltip/>--> <el-table-column prop="required" :label="$t('api_test.definition.document.table_coloum.is_required')" :formatter="formatBoolean" @@ -187,12 +203,11 @@ show-overflow-tooltip/> </el-table> <div v-else-if="apiInfo.requestBodyParamType == 'JSON-SCHEMA'" style="margin-left: 10px"> - <ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/> + <ms-json-code-edit :show-preview="false" :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/> + </div> + <div v-else-if="formatRowDataToJsonSchema(apiInfo,'request') " style="margin-left: 10px"> + <ms-json-code-edit :show-preview="false" :body="apiInfo.requestJsonSchema" ref="jsonCodeEdit"/> </div> -<!-- <div v-else-if="apiInfo.requestBodyParamType == 'XML'" style="margin-left: 10px">--> -<!-- <ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/>--> -<!-- <editor v-model="formatData" :lang="mode" @init="editorInit" :theme="theme" :height="height"/>--> -<!-- </div>--> <div v-else class="showDataDiv"> <br/> <p style="margin: 0px 20px;" @@ -274,7 +289,10 @@ show-overflow-tooltip/> </el-table> <div v-else-if="apiInfo.responseBodyParamType == 'JSON-SCHEMA'" style="margin-left: 10px"> - <ms-json-code-edit :body="apiInfo.jsonSchemaResponseBody" ref="jsonCodeEdit"/> + <ms-json-code-edit :show-preview="false" :body="apiInfo.jsonSchemaResponseBody" ref="jsonCodeEdit"/> + </div> + <div v-else-if="formatRowDataToJsonSchema(apiInfo,'response') " style="margin-left: 10px"> + <ms-json-code-edit :show-preview="false" :body="apiInfo.responseJsonSchema" ref="jsonCodeEdit"/> </div> <div v-else class="showDataDiv"> <br/> @@ -305,7 +323,7 @@ </el-main> <!-- 右侧列表 --> <el-aside width="200px" style="margin-top: 30px;"> - <div ref="apiDocList" > + <div ref="apiDocList"> <el-steps style="height: 40%" direction="vertical" :active="apiStepIndex"> <el-step v-for="(apiInfo) in apiInfoArray" :key="apiInfo.id" @click.native="clickStep(apiInfo.id)"> <el-link slot="title">{{ apiInfo.name }}</el-link> @@ -326,9 +344,10 @@ import {calculate} from "@/business/components/api/definition/model/ApiTestModel import MsJsonCodeEdit from "@/business/components/common/json-schema/JsonSchemaEditor"; import Api from "@/business/components/api/router"; import {generateApiDocumentShareInfo} from "@/network/share"; +import Convert from "@/business/components/common/json-schema/convert/convert"; const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/); -const apiDocumentBatchShare = (requireComponent!=null&&requireComponent.keys().length) > 0 ? requireComponent("./share/ApiDocumentBatchShare.vue") : {}; +const apiDocumentBatchShare = (requireComponent != null && requireComponent.keys().length) > 0 ? requireComponent("./share/ApiDocumentBatchShare.vue") : {}; export default { name: "ApiDocumentAnchor", @@ -341,21 +360,21 @@ export default { data() { return { isLoading: false, - shareUrl:"", - batchShareUrl:"", + shareUrl: "", + batchShareUrl: "", apiStepIndex: 0, - showXpackCompnent:false, + showXpackCompnent: false, apiInfoArray: [], modes: ['text', 'json', 'xml', 'html'], formParamTypes: ['form-data', 'x-www-from-urlencoded', 'BINARY'], mockVariableFuncs: [], - apiSearch:{ - name:"", - type:"ALL", - orderCondition:"createTimeDesc", + apiSearch: { + name: "", + type: "ALL", + orderCondition: "createTimeDesc", }, apiInfoBaseObj: { - selectedFlag:false, + selectedFlag: false, method: "无", uri: "无", name: "无", @@ -365,9 +384,9 @@ export default { requestBodyParamType: "无", requestBodyFormData: '[]', requestBodyStrutureData: "", - sharePopoverVisible:false, + sharePopoverVisible: false, jsonSchemaBody: {}, - JsonSchemaResponseBody:{}, + JsonSchemaResponseBody: {}, responseHead: "无", responseBody: "", responseBodyParamType: "无", @@ -377,19 +396,19 @@ export default { }, methodColorMap: new Map(API_METHOD_COLOUR), clientHeight: '',//浏览器高度, - maxCompnentSize : 5, //浏览器最多渲染的api信息体数量 - apiShowArray:[],//浏览器要渲染的api信息集合 + maxCompnentSize: 5, //浏览器最多渲染的api信息体数量 + apiShowArray: [],//浏览器要渲染的api信息集合 needAsyncSelect: false, //是否需要异步查询api详细数据做展现。只有本次要展示的数据总量大于maxCompnentSize时为true currentApiIndexInApiShowArray: 0,//当前主要展示的api信息在apiShowArray的索引 - clickStepFlag:false, - } + clickStepFlag: false, + }; }, props: { projectId: String, documentId: String, moduleIds: Array, - sharePage:Boolean, - pageHeaderHeight:Number, + sharePage: Boolean, + pageHeaderHeight: Number, trashEnable: { type: Boolean, default: false, @@ -402,10 +421,10 @@ export default { window.onresize = function () { this.clientHeight = `${document.documentElement.clientHeight}`; this.changeFixed(this.clientHeight); - } + }; }, created: function () { - if(requireComponent!=null && JSON.stringify(apiDocumentBatchShare) != '{}'){ + if (requireComponent != null && JSON.stringify(apiDocumentBatchShare) != '{}') { this.showXpackCompnent = true; } this.initApiDocSimpleList(); @@ -415,7 +434,7 @@ export default { this.clientHeight = `${document.documentElement.clientHeight}`; this.changeFixed(this.clientHeight); }; - window.addEventListener('scroll',that.handleScroll); + window.addEventListener('scroll', that.handleScroll); }, mounted() { let that = this; @@ -424,10 +443,9 @@ export default { that.changeFixed(that.clientHeight); }; // 监听滚动事件,然后用handleScroll这个方法进行相应的处理 - window.addEventListener('scroll',this.handleScroll); - }, - computed: { + window.addEventListener('scroll', this.handleScroll); }, + computed: {}, watch: { moduleIds() { this.initApiDocSimpleList(); @@ -440,18 +458,39 @@ export default { }, }, methods: { + formatRowDataToJsonSchema(api, jsonType) { + if (jsonType === 'request' && api.requestBodyStrutureData) { + try { + JSON.parse(api.requestBodyStrutureData); + api.requestJsonSchema = {'raw': api.requestBodyStrutureData}; + return true; + } catch (e) { + return false; + } + } else if (jsonType === 'response' && api.responseBodyStrutureData) { + try { + JSON.parse(api.responseBodyStrutureData); + api.responseJsonSchema = {'raw': api.responseBodyStrutureData}; + return true; + } catch (e) { + return false; + } + } else { + return false; + } + }, formatRowData(dataType, data) { var returnData = data; if (data) { - returnData = "<xmp>"+returnData+"</xmp>"; + returnData = "<xmp>" + returnData + "</xmp>"; } return returnData; }, changeFixed(clientHeight) { if (this.$refs.apiDocInfoDiv) { let countPageHeight = 210; - if(this.pageHeaderHeight!=0 && this.pageHeaderHeight != null){ - countPageHeight = this.pageHeaderHeight + if (this.pageHeaderHeight != 0 && this.pageHeaderHeight != null) { + countPageHeight = this.pageHeaderHeight; } this.$refs.apiDocInfoDiv.style.height = clientHeight - countPageHeight + 'px'; @@ -461,7 +500,7 @@ export default { }, initApiDocSimpleList() { //首先跳转到第一个节点(为了让滚动条变为0,防止重新加载后滚动条位置出现动乱导致页面混乱) - if(this.apiInfoArray.length > 0){ + if (this.apiInfoArray.length > 0) { this.clickStep(this.apiInfoArray[0].id); } this.apiInfoArray = []; @@ -487,21 +526,21 @@ export default { this.apiInfoArray = response.data; this.apiStepIndex = 0; if (this.apiInfoArray.length > 0) { - this.checkApiInfoNode(this.apiStepIndex,true); + this.checkApiInfoNode(this.apiStepIndex, true); } - if(response.data.length > this.maxCompnentSize){ + if (response.data.length > this.maxCompnentSize) { this.needAsyncSelect = true; - }else{ + } else { this.needAsyncSelect = false; } }); }, - shareApiDocument(isBatchShare){ + shareApiDocument(isBatchShare) { this.shareUrl = ""; this.batchShareUrl = ""; let shareIdArr = []; let shareType = "Single"; - if(isBatchShare == 'true'){ + if (isBatchShare == 'true') { this.apiInfoArray.forEach(f => { if (!f.id) { return; @@ -509,7 +548,7 @@ export default { shareIdArr.push(f.id); }); shareType = "Batch"; - }else{ + } else { shareIdArr.push(this.apiInfoArray[this.apiStepIndex].id); } let genShareInfoParam = {}; @@ -518,49 +557,49 @@ export default { generateApiDocumentShareInfo(genShareInfoParam, (data) => { let thisHost = window.location.host; - if(shareType == "Batch"){ + if (shareType == "Batch") { this.batchShareUrl = thisHost + "/document" + data.shareUrl; - }else{ + } else { this.shareUrl = thisHost + "/document" + data.shareUrl; } }); }, - selectApiInfo(index,apiId,needUpdateShowArray) { + selectApiInfo(index, apiId, needUpdateShowArray) { let simpleInfoUrl = "/share/info/selectApiInfoById/" + apiId; this.$get(simpleInfoUrl, response => { let returnData = response.data; - this.$set(this.apiInfoArray,index,returnData); - if(needUpdateShowArray){ + this.$set(this.apiInfoArray, index, returnData); + if (needUpdateShowArray) { let showApiIndex = -1; - for(let i = 0;i< this.apiShowArray.length;i++){ - if(this.apiShowArray[i].id === apiId){ + for (let i = 0; i < this.apiShowArray.length; i++) { + if (this.apiShowArray[i].id === apiId) { showApiIndex = i; } } - if(showApiIndex > -1){ - this.$set(this.apiShowArray,showApiIndex,returnData); + if (showApiIndex > -1) { + this.$set(this.apiShowArray, showApiIndex, returnData); } } }); }, //itemIndex,afterNodeIndex,beforeNodeIndex 三个是回调参数,用于重新构建showArray的数据 isRedirectScroll:是否调用跳转函数 - selectApiInfoBatch(indexArr,apiIdArr,itemIndex,afterNodeIndex,beforeNodeIndex,isRedirectScroll) { - if(indexArr.length != apiIdArr.length){ + selectApiInfoBatch(indexArr, apiIdArr, itemIndex, afterNodeIndex, beforeNodeIndex, isRedirectScroll) { + if (indexArr.length != apiIdArr.length) { this.isLoading = false; this.clickStepFlag = false; return; - }else { + } else { let params = {}; params.apiIdList = apiIdArr; this.$post("/share/info/selectApiInfoByParam", params, response => { let returnDatas = response.data; - for(let dataIndex = 0; dataIndex < returnDatas.length;dataIndex ++){ + for (let dataIndex = 0; dataIndex < returnDatas.length; dataIndex++) { let index = indexArr[dataIndex]; let data = returnDatas[dataIndex]; - this.$set(this.apiInfoArray,index,data); + this.$set(this.apiInfoArray, index, data); } - this.updateShowArray(itemIndex,afterNodeIndex,beforeNodeIndex); - if(isRedirectScroll){ + this.updateShowArray(itemIndex, afterNodeIndex, beforeNodeIndex); + if (isRedirectScroll) { this.redirectScroll(); } }); @@ -577,17 +616,17 @@ export default { } } //检查数据 - this.checkApiInfoNode(this.apiStepIndex,true); + this.checkApiInfoNode(this.apiStepIndex, true); }, getColor(enable, method) { return this.methodColorMap.get(method); }, formatBoolean(row, column, cellValue) { - var ret = '' //你想在页面展示的值 + var ret = ''; //你想在页面展示的值 if (cellValue) { - ret = "是" //根据自己的需求设定 + ret = "是"; //根据自己的需求设定 } else { - ret = "否" + ret = "否"; } return ret; }, @@ -614,7 +653,7 @@ export default { for (var key in previewData) { // showDataObj.set(key,previewData[key]); let value = previewData[key]; - if(typeof(value)=='string'){ + if (typeof (value) == 'string') { if (value.indexOf("@") >= 0) { value = this.showPreview(value); } @@ -650,7 +689,7 @@ export default { return itemValue; }, onCopySuccess: function (e) { - if(this.apiStepIndex < this.apiInfoArray.length){ + if (this.apiStepIndex < this.apiInfoArray.length) { this.apiInfoArray[this.apiStepIndex].sharePopoverVisible = false; } this.$message({ @@ -659,146 +698,146 @@ export default { }); }, onCopyError: function (e) { - if(this.apiStepIndex < this.apiInfoArray.length){ + if (this.apiStepIndex < this.apiInfoArray.length) { this.apiInfoArray[this.apiStepIndex].sharePopoverVisible = false; } this.$message.error(this.$t('api_report.error')); }, - handleScroll(){ - if(!this.clickStepFlag && this.$refs.apiDocInfoDiv){ + handleScroll() { + if (!this.clickStepFlag && this.$refs.apiDocInfoDiv) { //apiDocInfoDiv的总高度,是(每个item的高度+20)数量 let apiDocDivScrollTop = 0; - if(this.$refs.apiDocInfoDiv&&this.$refs.apiDocInfoDiv.scrollTop){ + if (this.$refs.apiDocInfoDiv && this.$refs.apiDocInfoDiv.scrollTop) { apiDocDivScrollTop = this.$refs.apiDocInfoDiv.scrollTop; } let apiDocDivClientTop = this.$refs.apiDocInfoDiv.clientHeight; - let scrolledHeigh = apiDocDivScrollTop+apiDocDivClientTop; + let scrolledHeigh = apiDocDivScrollTop + apiDocDivClientTop; let lastIndex = 0; for (let index = 0; index < this.apiShowArray.length; index++) { //判断移动到了第几个元素. 公式: 移动过的高度+页面显示高度-第index子元素的高度(含20px)>0 的 index最大值 - if(scrolledHeigh>0){ + if (scrolledHeigh > 0) { lastIndex = index; - let itemHeight = this.$refs.apiDocInfoDivItem[index].offsetHeight+10; + let itemHeight = this.$refs.apiDocInfoDivItem[index].offsetHeight + 10; scrolledHeigh = scrolledHeigh - itemHeight; - }else{ + } else { break; } } - if(lastIndex < this.currentApiIndexInApiShowArray){ + if (lastIndex < this.currentApiIndexInApiShowArray) { //上移 // if(this.needAsyncSelect){ - //进行判断:是否还需要为apiShowArray 增加数据。 由于在当前数据前后最多展现2条数据, - //可得: apiStepIndex-1- 2 < apiInfoArray,需要添加数据 - let dataIndex = this.apiStepIndex -3; - if(dataIndex >= 0){ - let apiInfo = this.apiInfoArray[dataIndex]; - let haveData = false; - //检查showArray是否存在这条数据。不存在才加入 - this.apiShowArray.forEach(api => { - if(api.id === apiInfo.id){ - haveData = true; - } - }); - if(!haveData){ - this.apiShowArray.unshift(apiInfo); - if(!apiInfo.selectedFlag){ - this.selectApiInfo(dataIndex,this.apiInfoArray[dataIndex].id,true); - } - }else { - this.currentApiIndexInApiShowArray--; + //进行判断:是否还需要为apiShowArray 增加数据。 由于在当前数据前后最多展现2条数据, + //可得: apiStepIndex-1- 2 < apiInfoArray,需要添加数据 + let dataIndex = this.apiStepIndex - 3; + if (dataIndex >= 0) { + let apiInfo = this.apiInfoArray[dataIndex]; + let haveData = false; + //检查showArray是否存在这条数据。不存在才加入 + this.apiShowArray.forEach(api => { + if (api.id === apiInfo.id) { + haveData = true; } - }else{ + }); + if (!haveData) { + this.apiShowArray.unshift(apiInfo); + if (!apiInfo.selectedFlag) { + this.selectApiInfo(dataIndex, this.apiInfoArray[dataIndex].id, true); + } + } else { this.currentApiIndexInApiShowArray--; } + } else { + this.currentApiIndexInApiShowArray--; + } - if(this.apiShowArray.length > (this.currentApiIndexInApiShowArray+3)){ - this.apiShowArray.pop(); - } + if (this.apiShowArray.length > (this.currentApiIndexInApiShowArray + 3)) { + this.apiShowArray.pop(); + } // } - this.apiStepIndex --; - }else if(lastIndex > this.currentApiIndexInApiShowArray){ + this.apiStepIndex--; + } else if (lastIndex > this.currentApiIndexInApiShowArray) { //下滚 //进行判断:是否还需要为apiShowArray 增加数据。 由于在当前数据前后最多展现2条数据, //可得: apiStepIndex+1+ 2 < apiInfoArray,需要添加数据 - let dataIndex = this.apiStepIndex +3; - if(dataIndex < this.apiInfoArray.length){ + let dataIndex = this.apiStepIndex + 3; + if (dataIndex < this.apiInfoArray.length) { let apiInfo = this.apiInfoArray[dataIndex]; let haveData = false; //检查showArray是否存在这条数据。不存在才加入 this.apiShowArray.forEach(api => { - if(api.id === apiInfo.id){ + if (api.id === apiInfo.id) { haveData = true; } }); - if(!haveData){ + if (!haveData) { this.apiShowArray.push(apiInfo); - if(!apiInfo.selectedFlag){ - this.selectApiInfo(dataIndex,this.apiInfoArray[dataIndex].id,true); + if (!apiInfo.selectedFlag) { + this.selectApiInfo(dataIndex, this.apiInfoArray[dataIndex].id, true); } } } - if(this.apiShowArray.length <= this.maxCompnentSize){ + if (this.apiShowArray.length <= this.maxCompnentSize) { //判断currentApiIndexInApiShowArray 是否需要添加,以及是否需要删除第一个元素 this.currentApiIndexInApiShowArray++; - }else{ + } else { this.apiShowArray.shift(); - let itemHeight = this.$refs.apiDocInfoDivItem[0].offsetHeight+10; - if(this.$refs.apiDocInfoDiv&&this.$refs.apiDocInfoDiv.scrollTop){ - this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivScrollTop-itemHeight); + let itemHeight = this.$refs.apiDocInfoDivItem[0].offsetHeight + 10; + if (this.$refs.apiDocInfoDiv && this.$refs.apiDocInfoDiv.scrollTop) { + this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivScrollTop - itemHeight); } } - this.apiStepIndex ++; + this.apiStepIndex++; } } this.clickStepFlag = false; }, - redirectScroll(){ - if(!this.$refs.apiDocInfoDiv){ + redirectScroll() { + if (!this.$refs.apiDocInfoDiv) { return; } //滚动条跳转:将滚动条下拉到显示对应对api接口的位置 let apiDocDivClientTop = 0; let itemHeightCount = 0; - if(this.currentApiIndexInApiShowArray > 0){ - for (let i = 0; i <= this.currentApiIndexInApiShowArray-1; i++) { - let itemHeight = this.$refs.apiDocInfoDivItem[i].offsetHeight+10; - itemHeightCount+=itemHeight; + if (this.currentApiIndexInApiShowArray > 0) { + for (let i = 0; i <= this.currentApiIndexInApiShowArray - 1; i++) { + let itemHeight = this.$refs.apiDocInfoDivItem[i].offsetHeight + 10; + itemHeightCount += itemHeight; } } this.clickStepFlag = true; let scrollTopIndex = this.$refs.apiDocInfoDiv.scrollTop; - if(this.$refs.apiDocInfoDiv&&this.$refs.apiDocInfoDiv.scrollTop){ - this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivClientTop+itemHeightCount); - }else if(scrollTopIndex === 0){ - this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivClientTop+itemHeightCount); + if (this.$refs.apiDocInfoDiv && this.$refs.apiDocInfoDiv.scrollTop) { + this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivClientTop + itemHeightCount); + } else if (scrollTopIndex === 0) { + this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivClientTop + itemHeightCount); } this.isLoading = false; }, //检查要展示的api信息节点,和上下个2个及以内的范围内数据有没有查询过。并赋值为showArray //isRedirectScroll 最后是否调用跳转函数 - checkApiInfoNode(itemIndex,isRedirectScroll){ - let beforeNodeIndex = itemIndex<2?0:(itemIndex-2); - let afterNodeIndex = (itemIndex+2)<this.apiInfoArray.length?(itemIndex+2):this.apiInfoArray.length; + checkApiInfoNode(itemIndex, isRedirectScroll) { + let beforeNodeIndex = itemIndex < 2 ? 0 : (itemIndex - 2); + let afterNodeIndex = (itemIndex + 2) < this.apiInfoArray.length ? (itemIndex + 2) : this.apiInfoArray.length; this.apiShowArray = []; let selectIndexArr = []; let selectApiId = []; //查当前节点前两个 - for(let afterIndex = beforeNodeIndex;afterIndex <itemIndex;afterIndex++){ + for (let afterIndex = beforeNodeIndex; afterIndex < itemIndex; afterIndex++) { let apiInfo = this.apiInfoArray[afterIndex]; - if(apiInfo==null){ + if (apiInfo == null) { continue; } - if(apiInfo == null || !apiInfo.selectedFlag){ + if (apiInfo == null || !apiInfo.selectedFlag) { let apiId = apiInfo.id; - if(!apiInfo.isSearching) { + if (!apiInfo.isSearching) { apiInfo.isSearching = true; selectIndexArr.push(afterIndex); selectApiId.push(apiId); @@ -808,14 +847,14 @@ export default { } this.currentApiIndexInApiShowArray = this.apiShowArray.length; //查当前节点以及后三个 - for(let beforeIndex = itemIndex;beforeIndex <= afterNodeIndex;beforeIndex++){ + for (let beforeIndex = itemIndex; beforeIndex <= afterNodeIndex; beforeIndex++) { let apiInfo = this.apiInfoArray[beforeIndex]; - if(apiInfo==null){ + if (apiInfo == null) { continue; } - if(apiInfo == null || !apiInfo.selectedFlag){ + if (apiInfo == null || !apiInfo.selectedFlag) { let apiId = apiInfo.id; - if(!apiInfo.isSearching){ + if (!apiInfo.isSearching) { apiInfo.isSearching = true; selectIndexArr.push(beforeIndex); selectApiId.push(apiId); @@ -823,10 +862,10 @@ export default { } this.apiShowArray.push(apiInfo); } - if(selectIndexArr.length>0){ - this.selectApiInfoBatch(selectIndexArr,selectApiId,itemIndex,afterNodeIndex,beforeNodeIndex,isRedirectScroll); - }else{ - if(isRedirectScroll){ + if (selectIndexArr.length > 0) { + this.selectApiInfoBatch(selectIndexArr, selectApiId, itemIndex, afterNodeIndex, beforeNodeIndex, isRedirectScroll); + } else { + if (isRedirectScroll) { //进行跳转 this.$nextTick(() => { this.redirectScroll(); @@ -835,28 +874,28 @@ export default { } }, //该方法只用于批量查询后的函数处理。 因为查询完成,数据更新,重新为apiShowArray赋值 - updateShowArray(itemIndex,afterNodeIndex,beforeNodeIndex){ + updateShowArray(itemIndex, afterNodeIndex, beforeNodeIndex) { this.apiShowArray = []; //查当前节点前两个 - for(let afterIndex = beforeNodeIndex;afterIndex <itemIndex;afterIndex++){ + for (let afterIndex = beforeNodeIndex; afterIndex < itemIndex; afterIndex++) { let apiInfo = this.apiInfoArray[afterIndex]; - if(apiInfo==null){ + if (apiInfo == null) { continue; } this.apiShowArray.push(apiInfo); } this.currentApiIndexInApiShowArray = this.apiShowArray.length; //查当前节点以及后三个 - for(let beforeIndex = itemIndex;beforeIndex <= afterNodeIndex;beforeIndex++){ + for (let beforeIndex = itemIndex; beforeIndex <= afterNodeIndex; beforeIndex++) { let apiInfo = this.apiInfoArray[beforeIndex]; - if(apiInfo==null){ + if (apiInfo == null) { continue; } this.apiShowArray.push(apiInfo); } } }, -} +}; </script> <style scoped> @@ -898,6 +937,11 @@ export default { /* 步骤条中,已经完成后的节点样式和里面a标签的样式 */ + +/deep/ .el-step { + flex-basis: 40px !important; +} + /deep/ .el-step__head.is-finish { color: #C0C4CC; border-color: #C0C4CC; @@ -910,15 +954,38 @@ export default { /* 步骤条中,当前节点样式和当前a标签的样式 */ +/deep/ .el-step__head { + width: 20px; +} + /deep/ .el-step__head.is-process { color: #783887; border-color: #783887; + width: 20px; } -/deep/ .el-step__title.is-process /deep/ .el-link.el-link--default { +/deep/ .el-step__title.is-process .el-link.el-link--default.is-underline { color: #783887; } +/deep/ .el-link--inner { + font-size: 12px; +} + +/deep/ .el-step__icon-inner { + font-size: 12px; +} + +/deep/ .el-step.is-vertical .el-step__line { + left: 9px; +} + + +/deep/ .el-step__icon { + width: 20px; + height: 20px; +} + .document-table { margin: 10px 10px; width: auto; @@ -942,6 +1009,7 @@ export default { background-color: #FAFAFA; border-right: 0px solid #EBEEF5 } + .el-divider--horizontal { margin: 12px 0; } diff --git a/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue b/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue deleted file mode 100644 index d60b718e8d..0000000000 --- a/frontend/src/business/components/api/definition/components/document/ApiDocumentItem.vue +++ /dev/null @@ -1,717 +0,0 @@ -<template> - <div> - <el-container> - <el-main style="padding-top: 0px;padding-bottom: 0px"> - <el-row style="margin-top: 10px"> - <el-select size="small" :placeholder="$t('api_test.definition.document.order')" v-model="apiSearch.orderCondition" style="float: right;width: 180px;margin-right: 5px" - class="ms-api-header-select" @change="initApiDocSimpleList" clearable> - <el-option key="createTimeDesc" :label="$t('api_test.definition.document.create_time_sort')" value="createTimeDesc" /> - <el-option key="editTimeAsc" :label="$t('api_test.definition.document.edit_time_positive_sequence')" value="editTimeAsc"/> - <el-option key="editTimeDesc" :label="$t('api_test.definition.document.edit_time_Reverse_order')" value="editTimeDesc"/> - </el-select> - - <el-select size="small" :placeholder="$t('api_test.definition.document.request_method')" v-model="apiSearch.type" style="float: right;width: 180px;margin-right: 5px" - class="ms-api-header-select" @change="initApiDocSimpleList" clearable> - <el-option key="ALL" :label="$t('api_test.definition.document.data_set.all')" value="ALL"/> - <el-option key="GET" :label="'GET '+$t('api_test.definition.document.request_interface')" value="GET"/> - <el-option key="POST" :label="'POST '+$t('api_test.definition.document.request_interface')" value="POST"/> - <el-option key="PUT" :label="'PUT '+$t('api_test.definition.document.request_interface')" value="PUT"/> - <el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" value="DELETE"/> - <el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" value="PATCH"/> - <el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" value="OPTIONS"/> - <el-option key="HEAD" :label="'HEAD '+$t('api_test.definition.document.request_interface')" value="HEAD"/> - <el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" value="CONNECT"/> - </el-select> - <el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" style="float: right;width: 180px;margin-right: 5px" size="small" - @keyup.enter.native="initApiDocSimpleList()" v-model="apiSearch.name"/> - <api-document-batch-share v-xpack v-if="showXpackCompnent" @shareApiDocument="shareApiDocument" :project-id="projectId" :share-url="batchShareUrl" style="float: right;margin: 6px;font-size: 17px"/> - <!-- <api-document-batch-share v-xpack v-if="showXpackCompnent"/>--> - </el-row> - <el-divider></el-divider> - <div ref="apiDocInfoDiv" @scroll="handleScroll" > - <div v-for="(apiInfo) in apiInfoArray" :key="apiInfo.id" ref="apiDocInfoDivItem"> - <div style="font-size: 17px"> - <el-popover - v-if="projectId" - placement="right" - width="260" - @show="shareApiDocument('false')"> - <p>{{shareUrl}}</p> - <div style="text-align: right; margin: 0"> - <el-button type="primary" size="mini" - v-clipboard:copy="shareUrl">{{ $t("commons.copy") }}</el-button> - </div> - <i class="el-icon-share" slot="reference" style="margin-right: 10px;cursor: pointer"></i> - </el-popover> - {{ apiInfo.name }} - <span class="apiStatusTag"> - <api-status :value="apiInfo.status"/> - </span> - </div> - <!--api请求信息--> - <el-row class="apiInfoRow"> - <div class="tip"> - {{ $t('api_test.definition.document.request_info') }} - </div> - </el-row> - <el-row class="apiInfoRow"> - <div class="simpleFontClass"> - <el-tag size="medium" - :style="{'background-color': getColor(true,apiInfo.method), border: getColor(true,apiInfo.method),borderRadius:'0px', marginRight:'20px',color:'white'}"> - {{ apiInfo.method }} - </el-tag> - {{ apiInfo.uri }} - </div> - </el-row> - <!--api请求头--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.request_head') }}: - <div v-if="getJsonArr(apiInfo.requestHead).length==0"> - <div class="simpleFontClass" style="margin-top: 10px"> - {{ $t('api_test.definition.document.data_set.none') }} - </div> - </div> - <div v-else> - <el-table border :show-header="false" - :data="getJsonArr(apiInfo.requestHead)" row-key="name" class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.value')" - show-overflow-tooltip/> - </el-table> - </div> - </div> - </el-row> - <!--URL参数--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - URL{{ $t('api_test.definition.document.request_param') }}: - <div v-if="getJsonArr(apiInfo.urlParams).length==0"> - <div class="simpleFontClass" style="margin-top: 10px"> - {{ $t('api_test.definition.document.data_set.none') }} - </div> - </div> - <div v-else> - <el-table border - :data="getJsonArr(apiInfo.urlParams)" row-key="name" class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="isEnable" - :label="$t('api_test.definition.document.table_coloum.is_required')" - min-width="80px" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.value')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="description" - :label="$t('api_test.definition.document.table_coloum.desc')" - min-width="280px" - show-overflow-tooltip/> - </el-table> - </div> - </div> - </el-row> - <!--api请求体 以及表格--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.request_body') }} - </div> - <div class="smallFontClass"> - {{ $t('api_test.definition.document.table_coloum.type') }}:{{ apiInfo.requestBodyParamType }} - </div> - <div> - <el-table border v-if="formParamTypes.includes(apiInfo.requestBodyParamType)" - :data="getJsonArr(apiInfo.requestBodyFormData)" row-key="name" - class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="contentType" - :label="$t('api_test.definition.document.table_coloum.type')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="description" - :label="$t('api_test.definition.document.table_coloum.desc')" - min-width="280px" - show-overflow-tooltip/> - <el-table-column prop="required" - :label="$t('api_test.definition.document.table_coloum.is_required')" - :formatter="formatBoolean" - min-width="80px" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.default_value')" - min-width="120px" - show-overflow-tooltip/> - </el-table> - <div v-else-if="apiInfo.requestBodyParamType == 'JSON-SCHEMA'" style="margin-left: 10px"> - <ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/> - </div> - <div v-else class="showDataDiv"> - <br/> - <p style="margin: 0px 20px;" - v-html="formatRowData(apiInfo.requestBodyParamType,apiInfo.requestBodyStrutureData)"> - </p> - <br/> - </div> - </div> - </el-row> - <!--范例展示--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.example_presentation') }} - </div> - <div class="showDataDiv"> - <br/> - <p style="margin: 0px 20px;" - v-html="genPreviewData(apiInfo.requestPreviewData)"> - </p> - <br/> - </div> - </el-row> - <!--响应信息--> - <el-row class="apiInfoRow"> - <div class="tip"> - {{ $t('api_test.definition.document.response_info') }} - </div> - </el-row> - <el-row class="apiInfoRow"> - - </el-row> - <!--响应头--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.response_head') }}: - <el-table border :show-header="false" - :data="getJsonArr(apiInfo.responseHead)" row-key="name" class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.value')" - show-overflow-tooltip/> - </el-table> - </div> - </el-row> - <!--响应体--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.response_body') }} - </div> - <div class="smallFontClass"> - {{ $t('api_test.definition.document.table_coloum.type') }}:{{ apiInfo.responseBodyParamType }} - </div> - <div> - <el-table border v-if="formParamTypes.includes(apiInfo.responseBodyParamType)" - :data="getJsonArr(apiInfo.responseBodyFormData)" row-key="id" - class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="contentType" - :label="$t('api_test.definition.document.table_coloum.type')" - min-width="120px" - show-overflow-tooltip/> - <el-table-column prop="description" - :label="$t('api_test.definition.document.table_coloum.desc')" - min-width="280px" - show-overflow-tooltip/> - <el-table-column prop="required" - :label="$t('api_test.definition.document.table_coloum.is_required')" - :formatter="formatBoolean" - min-width="80px" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.default_value')" - min-width="120px" - show-overflow-tooltip/> - </el-table> - <div v-else class="showDataDiv"> - <br/> - <p style="margin: 0px 20px;" - v-html="formatRowData(apiInfo.responseBodyParamType,apiInfo.responseBodyStrutureData)"> - </p> - <br/> - </div> - </div> - </el-row> - <!--响应状态码--> - <el-row class="apiInfoRow"> - <div class="blackFontClass"> - {{ $t('api_test.definition.document.response_code') }}: - <el-table border :show-header="false" - :data="getJsonArr(apiInfo.responseCode)" row-key="name" class="test-content document-table"> - <el-table-column prop="name" - :label="$t('api_test.definition.document.table_coloum.name')" - show-overflow-tooltip/> - <el-table-column prop="value" - :label="$t('api_test.definition.document.table_coloum.value')" - show-overflow-tooltip/> - </el-table> - </div> - </el-row> - </div> - </div> - </el-main> - <!-- 右侧列表 --> - <el-aside width="200px" style="margin-top: 70px;"> - <div ref="apiDocList" > - <el-steps style="height: 40%" direction="vertical" :active="apiStepIndex"> - <el-step v-for="(apiInfo) in apiInfoArray" :key="apiInfo.id" @click.native="clickStep(apiInfo.id)"> - <el-link slot="title">{{ apiInfo.name }}</el-link> - </el-step> - </el-steps> - </div> - </el-aside> - </el-container> - </div> -</template> - -<script> -import {API_METHOD_COLOUR} from "@/business/components/api/definition/model/JsonData"; -import MsCodeEdit from "@/business/components/common/components/MsCodeEdit"; -import {formatJson,} from "@/common/js/format-utils"; -import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus"; -import {calculate} from "@/business/components/api/definition/model/ApiTestModel"; -import MsJsonCodeEdit from "@/business/components/common/json-schema/JsonSchemaEditor"; -import Api from "@/business/components/api/router"; - -const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/); -const apiDocumentBatchShare = (requireComponent!=null&&requireComponent.keys().length) > 0 ? requireComponent("./share/ApiDocumentBatchShare.vue") : {}; - -export default { - name: "ApiDocumentItem", - components: { - Api, - MsJsonCodeEdit, - ApiStatus, MsCodeEdit, - "ApiDocumentBatchShare": apiDocumentBatchShare.default - }, - data() { - return { - shareUrl:"", - batchShareUrl:"", - apiStepIndex: 0, - showXpackCompnent:false, - apiInfoArray: [], - modes: ['text', 'json', 'xml', 'html'], - formParamTypes: ['form-data', 'x-www-from-urlencoded', 'BINARY'], - mockVariableFuncs: [], - apiSearch:{ - name:"", - type:"ALL", - orderCondition:"createTimeDesc", - }, - apiInfoBaseObj: { - selectedFlag:false, - method: "无", - uri: "无", - name: "无", - id: "", - requestHead: "无", - urlParams: "无", - requestBodyParamType: "无", - requestBodyFormData: '[]', - requestBodyStrutureData: "", - sharePopoverVisible:false, - jsonSchemaBody: {}, - responseHead: "无", - responseBody: "", - responseBodyParamType: "无", - responseBodyFormData: "无", - responseBodyStrutureData: "无", - responseCode: "无", - }, - methodColorMap: new Map(API_METHOD_COLOUR), - clientHeight: '',//坚挺浏览器高度 - } - }, - props: { - projectId: String, - documentId: String, - moduleIds: Array, - pageHeaderHeight:Number, - }, - activated() { - this.initApiDocSimpleList(); - this.clientHeight = `${document.documentElement.clientHeight}`;//获取浏览器可视区域高度 - let that = this; - window.onresize = function () { - this.clientHeight = `${document.documentElement.clientHeight}`; - this.changeFixed(this.clientHeight); - } - }, - created: function () { - if(requireComponent!=null && JSON.stringify(apiDocumentBatchShare) != '{}'){ - this.showXpackCompnent = true; - } - this.initApiDocSimpleList(); - this.clientHeight = `${document.documentElement.clientHeight}`;//获取浏览器可视区域高度 - let that = this; - window.onresize = function () { - this.clientHeight = `${document.documentElement.clientHeight}`; - this.changeFixed(this.clientHeight); - }; - window.addEventListener('scroll',that.handleScroll); - }, - mounted() { - let that = this; - window.onresize = function () { - that.clientHeight = `${document.documentElement.clientHeight}`; - that.changeFixed(that.clientHeight); - }; - // 监听滚动事件,然后用handleScroll这个方法进行相应的处理 - window.addEventListener('scroll',this.handleScroll); - }, - computed: { - }, - watch: { - moduleIds() { - this.initApiDocSimpleList(); - }, - clientHeight() { //如果clientHeight 发生改变,这个函数就会运行 - this.changeFixed(this.clientHeight); - } - }, - methods: { - formatRowData(dataType, data) { - var returnData = data; - if (data) { - returnData = data.replace(/\n/g, '<br>'); - } - return returnData; - }, - changeFixed(clientHeight) { - if (this.$refs.apiDocInfoDiv) { - let countPageHeight = 350; - if(this.pageHeaderHeight!=0 && this.pageHeaderHeight != null){ - countPageHeight = this.pageHeaderHeight - } - - this.$refs.apiDocInfoDiv.style.height = clientHeight - countPageHeight + 'px'; - this.$refs.apiDocInfoDiv.style.overflow = 'auto'; - this.$refs.apiDocList.style.height = clientHeight - countPageHeight + 'px'; - } - }, - initApiDocSimpleList() { - let simpleRequest = this.apiSearch; - if (this.projectId != null && this.projectId != "") { - simpleRequest.projectId = this.projectId; - } - if (this.documentId != null && this.documentId != "") { - simpleRequest.shareId = this.documentId; - } - if (this.moduleIds.length > 0) { - simpleRequest.moduleIds = this.moduleIds; - } - - let simpleInfoUrl = "/share/info/selectApiSimpleInfo"; - this.apiInfoArray = []; - this.$post(simpleInfoUrl, simpleRequest, response => { - this.apiInfoArray = response.data; - this.apiStepIndex = 0; - if (this.apiInfoArray.length > 0) { - this.checkApiInfoNode(this.apiStepIndex); - } - }); - }, - shareApiDocument(isBatchShare){ - let thisHost = window.location.host; - this.shareUrl = ""; - this.batchShareUrl = ""; - let shareIdArr = []; - let shareType = "Single"; - if(isBatchShare == 'true'){ - this.apiInfoArray.forEach(f => { - if (!f.id) { - return; - } - shareIdArr.push(f.id); - }); - shareType = "Batch"; - }else{ - shareIdArr.push(this.apiInfoArray[this.apiStepIndex].id); - } - let genShareInfoParam = {}; - genShareInfoParam.shareApiIdList = shareIdArr; - genShareInfoParam.shareType = shareType; - - this.$post("/share/info/generateApiDocumentShareInfo", genShareInfoParam, res => { - if(shareType == "Batch"){ - this.batchShareUrl = thisHost+"/document"+res.data.shareUrl; - }else{ - this.shareUrl = thisHost+"/document"+res.data.shareUrl; - } - }, (error) => { - }); - }, - selectApiInfo(index,apiId) { - let simpleInfoUrl = "/share/info/selectApiInfoById/" + apiId; - this.$get(simpleInfoUrl, response => { - let returnData = response.data; - this.$set(this.apiInfoArray,index,returnData); - }); - }, - clickStep(apiId) { - for (let index = 0; index < this.apiInfoArray.length; index++) { - if (apiId == this.apiInfoArray[index].id) { - this.apiStepIndex = index; - break; - } - } - //检查数据 - this.checkApiInfoNode(this.apiStepIndex); - //进行跳转 - this.redirectScroll(this.apiStepIndex); - }, - stepClick(stepIndex) { - this.apiStepIndex = stepIndex; - }, - getColor(enable, method) { - return this.methodColorMap.get(method); - }, - formatBoolean(row, column, cellValue) { - var ret = '' //你想在页面展示的值 - if (cellValue) { - ret = "是" //根据自己的需求设定 - } else { - ret = "否" - } - return ret; - }, - getJsonArr(jsonString) { - let returnJsonArr = []; - if (jsonString == '无' || jsonString == null) { - return returnJsonArr; - } - - let jsonArr = JSON.parse(jsonString); - //遍历,把必填项空的数据去掉 - for (var index = 0; index < jsonArr.length; index++) { - var item = jsonArr[index]; - if (item.name != "" && item.name != null) { - returnJsonArr.push(item); - } - } - return returnJsonArr; - }, - //构建预览数据 - genPreviewData(previewData) { - if (previewData != null && previewData != '') { - let showDataObj = {}; - for (var key in previewData) { - // showDataObj.set(key,previewData[key]); - let value = previewData[key]; - if(typeof(value)=='string'){ - if (value.indexOf("@") >= 0) { - value = this.showPreview(value); - } - } - showDataObj[key] = value; - } - showDataObj = JSON.stringify(showDataObj); - previewData = formatJson(showDataObj); - } - return previewData; - }, - showPreview(itemValue) { - // 找到变量本身 - if (!itemValue) { - return; - } - let index = itemValue.indexOf("|"); - if (index > -1) { - itemValue = itemValue.substring(0, index).trim(); - } - - this.mockVariableFuncs.forEach(f => { - if (!f.name) { - return; - } - itemValue += "|" + f.name; - if (f.params) { - itemValue += ":" + f.params.map(p => p.value).join(","); - } - }); - - itemValue = calculate(itemValue); - return itemValue; - }, - onCopySuccess: function (e) { - if(this.apiStepIndex < this.apiInfoArray.length){ - this.apiInfoArray[this.apiStepIndex].sharePopoverVisible = false; - } - this.$message({ - message: this.$t('commons.copy_success'), - type: 'success' - }); - }, - onCopyError: function (e) { - if(this.apiStepIndex < this.apiInfoArray.length){ - this.apiInfoArray[this.apiStepIndex].sharePopoverVisible = false; - } - this.$message.error(this.$t('api_report.error')); - }, - handleScroll(){ - if(!this.$refs.apiDocInfoDiv){ - return; - } - //apiDocInfoDiv的总高度,是(每个item的高度+20)数量 - let apiDocDivScrollTop = this.$refs.apiDocInfoDiv.scrollTop; - let apiDocDivClientTop = this.$refs.apiDocInfoDiv.clientHeight; - - let scrolledHeigh = apiDocDivScrollTop+apiDocDivClientTop; - let lastIndex = 0; - for (let index = 0; index < this.apiInfoArray.length; index++) { - //判断移动到了第几个元素. 公式: 移动过的高度+页面显示高度-第index子元素的高度(含20px)>0 的 index最大值 - if(scrolledHeigh>0){ - lastIndex = index; - let itemHeight = this.$refs.apiDocInfoDivItem[index].offsetHeight+20; - scrolledHeigh = scrolledHeigh - itemHeight; - }else{ - break; - } - } - this.apiStepIndex = lastIndex; - //检查上下文 3个以内的节点有没有查询出来 - this.checkApiInfoNode(this.apiStepIndex); - }, - redirectScroll(itemIndex){ - //滚动条跳转:将滚动条下拉到显示对应对api接口的位置 - // let apiDocDivClientTop = this.$refs.apiDocInfoDiv.clientHeight; - let apiDocDivClientTop = 0; - let itemHeightCount = 0; - for (let i = 0; i <= itemIndex-1; i++) { - let itemHeight = this.$refs.apiDocInfoDivItem[i].offsetHeight+20; - itemHeightCount+=itemHeight; - } - if(this.$refs.apiDocInfoDiv){ - this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivClientTop+itemHeightCount); - } - }, - checkApiInfoNode(itemIndex){ - //检查要展示的api信息节点,和上下个3个及以内的范围内数据有没有查询过 - let beforeNodeIndex = itemIndex<3?0:(itemIndex-3); - let afterNodeIndex = (itemIndex+3)<this.apiInfoArray.length?(itemIndex+3):this.apiInfoArray.length; - - for(let beforeIndex = itemIndex;beforeIndex < afterNodeIndex;beforeIndex++){ - let apiInfo = this.apiInfoArray[beforeIndex]; - if(apiInfo==null){ - continue; - } - if(apiInfo == null || !apiInfo.selectedFlag){ - let apiId = apiInfo.id; - if(!apiInfo.isSearching){ - apiInfo.isSearching = true; - this.selectApiInfo(beforeIndex,apiId); - } - } - } - - for(let afterIndex = beforeNodeIndex;afterIndex <itemIndex;afterIndex++){ - let apiInfo = this.apiInfoArray[afterIndex]; - if(apiInfo==null){ - continue; - } - if(apiInfo == null || !apiInfo.selectedFlag){ - let apiId = apiInfo.id; - if(!apiInfo.isSearching) { - apiInfo.isSearching = true; - this.selectApiInfo(afterIndex,apiId); - } - } - } - } - }, -} -</script> - -<style scoped> -.simpleFontClass { - font-weight: normal; - font-size: 14px; - margin-left: 10px; -} - -.blackFontClass { - font-weight: bold; - font-size: 14px; -} - -.smallFontClass { - font-size: 13px; - margin: 20px 10px; -} - -.apiInfoRow { - margin: 20px 10px; -} - -.apiStatusTag { - margin: 20px 5px; -} - -.showDataDiv { - background-color: #F5F7F9; - margin: 20px 10px; -} - -/* -步骤条中,已经完成后的节点样式和里面a标签的样式 -*/ -/deep/ .el-step__head.is-finish { - color: #C0C4CC; - border-color: #C0C4CC; -} - -/deep/ .el-step__title.is-finish /deep/ .el-link.el-link--default { - color: #C0C4CC; -} - -/* -步骤条中,当前节点样式和当前a标签的样式 -*/ -/deep/ .el-step__head.is-process { - color: #783887; - border-color: #783887; -} - -/deep/ .el-step__title.is-process /deep/ .el-link.el-link--default { - color: #783887; -} - -.document-table { - margin: 20px 10px; - width: auto; -} - -.document-table /deep/ .el-table__row { - font-size: 12px; - font-weight: initial; -} - -.document-table /deep/ .has-gutter { - font-size: 12px; - color: #404040; -} - -.document-table /deep/ td { - border-right: 0px solid #EBEEF5 -} - -.document-table /deep/ th { - background-color: #FAFAFA; - border-right: 0px solid #EBEEF5 -} -.el-divider--horizontal { - margin: 12px 0; -} -</style> diff --git a/frontend/src/business/components/api/definition/components/list/ApiDocumentsPage.vue b/frontend/src/business/components/api/definition/components/list/ApiDocumentsPage.vue index 1f36c31078..7d2e532109 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiDocumentsPage.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiDocumentsPage.vue @@ -1,6 +1,5 @@ <template> <div> -<!-- <api-document-item :project-id="projectId" :module-ids="moduleIds"/>--> <api-document-anchor :is-share-page="isSharePage" :trash-enable="trashEnable" :project-id="projectId" :module-ids="moduleIds"></api-document-anchor> </div> </template> diff --git a/frontend/src/business/components/common/head/HeaderTopMenus.vue b/frontend/src/business/components/common/head/HeaderTopMenus.vue index 6c90a03261..5bb43af740 100644 --- a/frontend/src/business/components/common/head/HeaderTopMenus.vue +++ b/frontend/src/business/components/common/head/HeaderTopMenus.vue @@ -24,7 +24,7 @@ </el-menu-item> <el-menu-item index="/report" onselectstart="return false" v-permission="['PROJECT_TRACK_CASE:READ','PROJECT_TRACK_PLAN:READ','PROJECT_TRACK_REVIEW:READ']" - v-if="isReport && check('reportStat')"> + > {{ $t('commons.report_statistics.title') }} </el-menu-item> @@ -40,9 +40,9 @@ import {mapGetters} from "vuex"; import {hasLicense} from "@/common/js/utils"; import {MODULE_CHANGE, ModuleEvent} from "@/business/components/common/head/ListEvent"; -const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/); -const report = requireContext.keys().map(key => requireContext(key).report); -const isReport = report && report != null && report.length > 0 && report[0] != undefined ? true : false; +// const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/); +// const report = requireContext.keys().map(key => requireContext(key).report); +// const isReport = report && report != null && report.length > 0 && report[0] != undefined ? true : false; const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/); const module = requireComponent.keys().length > 0 ? requireComponent("./module/Module.vue") : {}; @@ -52,7 +52,7 @@ export default { data() { return { activeIndex: '/', - isReport: isReport, + isReport: true, modules: {}, menuKey: 0, }; diff --git a/frontend/src/business/components/common/json-schema/JsonSchemaEditor.vue b/frontend/src/business/components/common/json-schema/JsonSchemaEditor.vue index 78dbd180ad..94c5298e9a 100644 --- a/frontend/src/business/components/common/json-schema/JsonSchemaEditor.vue +++ b/frontend/src/business/components/common/json-schema/JsonSchemaEditor.vue @@ -7,7 +7,7 @@ <json-schema-editor class="schema" :value="schema" lang="zh_CN" custom/> </div> </el-tab-pane> - <el-tab-pane :label="$t('schema.preview')" name="preview"> + <el-tab-pane v-if="showPreview" :label="$t('schema.preview')" name="preview"> <div style="min-height: 200px"> <pre>{{this.preview}}</pre> </div> @@ -29,6 +29,10 @@ components: {MsImportJson}, props: { body: {}, + showPreview: { + type: Boolean, + default: true + }, }, created() { if (!this.body.jsonSchema && this.body.raw && this.checkIsJson(this.body.raw)) { @@ -46,6 +50,19 @@ this.body.jsonSchema = this.schema.root; }, deep: true + }, + body: { + handler(newValue, oldValue) { + if (!this.body.jsonSchema && this.body.raw && this.checkIsJson(this.body.raw)) { + let obj = {"root": MsConvert.format(JSON.parse(this.body.raw))} + this.schema = obj; + } + else if (this.body.jsonSchema) { + this.schema = {"root": this.body.jsonSchema}; + } + this.body.jsonSchema = this.schema.root; + }, + deep: true } }, data() { diff --git a/frontend/src/business/components/common/select-tree/SelectTree.vue b/frontend/src/business/components/common/select-tree/SelectTree.vue index 87fcaddea6..4937d27f38 100644 --- a/frontend/src/business/components/common/select-tree/SelectTree.vue +++ b/frontend/src/business/components/common/select-tree/SelectTree.vue @@ -405,7 +405,9 @@ export default { handler:function(){ this.init(); if(this.data && this.data.length > 0){ - this.$refs.tree.setCheckedKeys(this.defaultKey); + if(this.defaultKey instanceof Array){ + this.$refs.tree.setCheckedKeys(this.defaultKey); + } } }, deep:true @@ -413,7 +415,9 @@ export default { data:{ handler:function(){ if(this.defaultKey && this.defaultKey.length > 0 && this.defaultKey instanceof Array){ - this.$refs.tree.setCheckedKeys(this.defaultKey); + if(this.defaultKey instanceof Array){ + this.$refs.tree.setCheckedKeys(this.defaultKey); + } } }, deep:true From ebec57f2e53420e2ee035e161e3ffff7ac80746f Mon Sep 17 00:00:00 2001 From: shiziyuan9527 <yuhao.li@fit2cloud.com> Date: Mon, 27 Sep 2021 16:52:44 +0800 Subject: [PATCH 62/74] =?UTF-8?q?fix:=20=E7=BC=BA=E9=99=B7=E6=95=B0?= =?UTF-8?q?=E4=B8=BA0=E7=9A=84=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E4=B8=8D=E7=BB=9F=E8=AE=A1#1006130?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1006130 --user=lyh 【测试跟踪-首页】遗留缺陷统计不对 https://www.tapd.cn/55049933/s/1052500 --- .../java/io/metersphere/track/service/TrackService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/track/service/TrackService.java b/backend/src/main/java/io/metersphere/track/service/TrackService.java index a2aae3ce11..f9df6fdcc5 100644 --- a/backend/src/main/java/io/metersphere/track/service/TrackService.java +++ b/backend/src/main/java/io/metersphere/track/service/TrackService.java @@ -118,6 +118,12 @@ public class TrackService { int totalBugSize = 0; int totalCaseSize = 0; for (TestPlan plan : plans) { + int planBugSize = getPlanBugSize(plan.getId()); + // bug为0不记录 + if (planBugSize == 0) { + continue; + } + TestPlanBugCount testPlanBug = new TestPlanBugCount(); testPlanBug.setIndex(index++); testPlanBug.setPlanName(plan.getName()); @@ -127,7 +133,6 @@ public class TrackService { int planCaseSize = getPlanCaseSize(plan.getId()); testPlanBug.setCaseSize(planCaseSize); - int planBugSize = getPlanBugSize(plan.getId()); testPlanBug.setBugSize(planBugSize); double planPassRage = getPlanPassRage(plan.getId()); testPlanBug.setPassRage(planPassRage + "%"); From 71948d659ab8ad6ba90b33d6ce60178f4aea45f1 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 <yuhao.li@fit2cloud.com> Date: Tue, 28 Sep 2021 10:46:56 +0800 Subject: [PATCH 63/74] =?UTF-8?q?fix:=20=E9=A6=96=E9=A1=B5=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E7=BB=B4=E6=8A=A4=E4=BA=BA=E6=98=BE=E7=A4=BA=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE#1006427?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1006427 --user=lyh 【github#5902】测试跟踪》首页》用例维护人 显示不正确 https://www.tapd.cn/55049933/s/1052569 --- .../excel/listener/TestCaseNoModelDataListener.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/main/java/io/metersphere/excel/listener/TestCaseNoModelDataListener.java b/backend/src/main/java/io/metersphere/excel/listener/TestCaseNoModelDataListener.java index 0b706e3215..343ebbf855 100644 --- a/backend/src/main/java/io/metersphere/excel/listener/TestCaseNoModelDataListener.java +++ b/backend/src/main/java/io/metersphere/excel/listener/TestCaseNoModelDataListener.java @@ -416,6 +416,9 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ String customFieldsJson = this.getCustomFieldsJson(data); testCase.setCustomFields(customFieldsJson); + if (StringUtils.isNotBlank(data.getMaintainer())) { + testCase.setMaintainer(data.getMaintainer()); + } if (StringUtils.isNotBlank(data.getStepModel()) && StringUtils.equals(data.getStepModel(), TestCaseConstants.StepModel.TEXT.name())) { @@ -466,6 +469,9 @@ public class TestCaseNoModelDataListener extends AnalysisEventListener<Map<Integ String customFieldsJson = this.getCustomFieldsJson(data); testCase.setCustomFields(customFieldsJson); + if (StringUtils.isNotBlank(data.getMaintainer())) { + testCase.setMaintainer(data.getMaintainer()); + } //将标签设置为前端可解析的格式 String modifiedTags = modifyTagPattern(data); From 2b9bd90e25afdcb78caf93725eecbee189710937 Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Tue, 28 Sep 2021 10:56:30 +0800 Subject: [PATCH 64/74] =?UTF-8?q?style(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automation/scenario/maximize/MaximizeScenario.vue | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue index 33eb551c9d..187e6504a9 100644 --- a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue @@ -934,16 +934,6 @@ export default { z-index: 5; } -/deep/ .el-tree-node__content { - height: 100%; - margin-top: 8px; - vertical-align: center; -} - -/deep/ .el-card__body { - padding: 6px 10px; -} - /deep/ .el-drawer__body { overflow: auto; } From d5df2c39a4a604c4a11a1dc01d96133d0ce3de69 Mon Sep 17 00:00:00 2001 From: "Captain.B" <bin@fit2cloud.com> Date: Tue, 28 Sep 2021 11:31:04 +0800 Subject: [PATCH 65/74] =?UTF-8?q?fix(=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5)?= =?UTF-8?q?:=20=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8F=98=E9=87=8F=E4=B8=8D=E8=83=BD=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/mail/ApiFailedNotification.html | 2 +- .../src/main/resources/mail/ApiSuccessfulNotification.html | 2 +- .../api/automation/schedule/ScheduleNotification.vue | 4 ++-- .../organization/components/ScheduleTaskNotification.vue | 4 ++-- .../organization/components/jenkins/JenkinsNotification.vue | 4 ++-- frontend/src/business/components/xpack | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/resources/mail/ApiFailedNotification.html b/backend/src/main/resources/mail/ApiFailedNotification.html index 79f468ffbc..179c816b4c 100644 --- a/backend/src/main/resources/mail/ApiFailedNotification.html +++ b/backend/src/main/resources/mail/ApiFailedNotification.html @@ -13,7 +13,7 @@ <div style="margin-left: 100px"> <p>${name} 接口测试运行失败<br/> - <p>执行人:${executor}</p> + <p>执行人:${operator}</p> <p>执行环境:${executionEnvironment}</p> <p>执行时间:${executionTime}</p> 请点击下面链接进入测试报告页面【接口定义暂无报告路径】<br/> diff --git a/backend/src/main/resources/mail/ApiSuccessfulNotification.html b/backend/src/main/resources/mail/ApiSuccessfulNotification.html index 7ae2c9cded..9ecdcc11a4 100644 --- a/backend/src/main/resources/mail/ApiSuccessfulNotification.html +++ b/backend/src/main/resources/mail/ApiSuccessfulNotification.html @@ -11,7 +11,7 @@ </div> <div style="margin-left: 100px"> <p>${name} 接口测试运行成功<br/> - <p>执行人:${executor}</p> + <p>执行人:${operator}</p> <p>执行环境:${executionEnvironment}</p> <p>执行时间:${executionTime}</p> 请点击下面链接进入测试报告页面【接口定义暂无报告路径】<br/> diff --git a/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue index 5c4745508f..3db82c8d81 100644 --- a/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue +++ b/frontend/src/business/components/api/automation/schedule/ScheduleNotification.vue @@ -169,7 +169,7 @@ export default { ' </div>\n' + ' <div style="margin-left: 100px">\n' + ' <p>${name} 接口测试运行失败<br/>\n' + - ' <p>执行人:${executor}</p>' + + ' <p>执行人:${operator}</p>' + ' <p>负责人:${principal}</p>' + ' <p>执行环境:${executionEnvironment}</p>' + ' <p>执行时间:${executionTime}</p>' + @@ -184,7 +184,7 @@ export default { '</html>', robotTitle: "测试'${name} ${type}测试运行${status}\n" + - "执行人:${executor}" + "\n" + + "执行人:${operator}" + "\n" + "负责人:${principal}" + "\n" + "测试环境为:${executionEnvironment}\n" + "执行时间:${executionTime}\n" + diff --git a/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue b/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue index 0c0ee9eed0..5836d34116 100644 --- a/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue +++ b/frontend/src/business/components/settings/organization/components/ScheduleTaskNotification.vue @@ -168,7 +168,7 @@ export default { ' </div>\n' + ' <div style="margin-left: 100px">\n' + ' <p>${name} 接口测试运行失败/成功<br/>\n' + - ' <p>执行人:${executor}</p>' + + ' <p>执行人:${operator}</p>' + ' <p>负责人:${principal}</p>' + ' <p>执行环境:${executionEnvironment}</p>' + ' <p>执行时间:${executionTime}</p>' + @@ -183,7 +183,7 @@ export default { '</html>', robotTitle: "测试'${name} ${type}测试运行${status}," + "\n" + - "执行人:${executor}" + "\n" + + "执行人:${operator}" + "\n" + "负责人:${principal}" + "\n" + "测试环境为:${executionEnvironment}" + "\n" + "执行时间:${executionTime}" + "\n" + diff --git a/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue b/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue index 78b109f5b9..fc04426d19 100644 --- a/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue +++ b/frontend/src/business/components/settings/organization/components/jenkins/JenkinsNotification.vue @@ -185,7 +185,7 @@ export default { ' </div>\n' + ' <div style="margin-left: 100px">\n' + ' <p>${name} 接口测试运行失败/成功<br/>\n' + - ' <p>执行人:${executor}</p>' + + ' <p>执行人:${operator}</p>' + ' <p>执行环境:${executionEnvironment}</p>' + ' <p>执行时间:${executionTime}</p>' + ' 请点击下面链接进入测试报告页面</p>\n' + @@ -198,7 +198,7 @@ export default { '</body>\n' + '</html>', robotTitle: - "测试'${executor}所执行的 ${name} ${type}测试运行${status}\n" + + "测试'${operator}所执行的 ${name} ${type}测试运行${status}\n" + "测试环境为:${executionEnvironment}\n" + "执行时间:${executionTime}\n" + "请点击下面链接进入测试报告页面\n" + diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index e0da1d3ef6..71e705f48d 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit e0da1d3ef611b0901e42520f6d68a04cabec4c08 +Subproject commit 71e705f48da10eb37135b189582ca0c917a2ffb3 From 1786f96afc8d1e3d432929c2b98945ce6c34f6ae Mon Sep 17 00:00:00 2001 From: song-tianyang <tianyang.song@fit2cloud.com> Date: Tue, 28 Sep 2021 14:56:42 +0800 Subject: [PATCH 66/74] =?UTF-8?q?refactor(=E6=8A=A5=E8=A1=A8=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=BC=80=E6=BA=90=E7=89=88=E6=9D=83=E9=99=90=E4=B8=AD?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E7=BB=9F=E8=AE=A1=E6=9D=83=E9=99=90=E4=B8=8D?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=AE=A1=E7=90=86=E5=91=98=E3=80=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=88=90=E5=91=98=E9=BB=98=E8=AE=A4=E8=B5=8B=E4=BA=88=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=92=8C=E5=8F=A6=E5=AD=98=E4=B8=BA=E7=9A=84=E6=9D=83?= =?UTF-8?q?=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改开源版权限中报表统计权限不展示的问题,项目管理员、项目成员默认赋予保存和另存为的权限 --- .../db/migration/V96__v1.13.1__release.sql | 15 +++++++++++++++ backend/src/main/resources/permission.json | 11 +++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/backend/src/main/resources/db/migration/V96__v1.13.1__release.sql b/backend/src/main/resources/db/migration/V96__v1.13.1__release.sql index 9937736e26..b9d9837c3b 100644 --- a/backend/src/main/resources/db/migration/V96__v1.13.1__release.sql +++ b/backend/src/main/resources/db/migration/V96__v1.13.1__release.sql @@ -16,3 +16,18 @@ alter table test_plan modify principal varchar(50) null comment 'Plan principal' ALTER TABLE test_case_review_test_case ADD `order` bigint(20) NOT NULL COMMENT '自定义排序,间隔5000'; + + +-- 报表默认权限插入 + +-- 项目管理员 +insert into user_group_permission (id, group_id, permission_id, module_id) +values (UUID(), 'project_admin', 'PROJECT_REPORT_ANALYSIS:READ+UPDATE', 'PROJECT_REPORT_ANALYSIS'); +insert into user_group_permission (id, group_id, permission_id, module_id) +values (UUID(), 'project_admin', 'PROJECT_REPORT_ANALYSIS:READ+CREATE', 'PROJECT_REPORT_ANALYSIS'); + +-- 项目成员 +insert into user_group_permission (id, group_id, permission_id, module_id) +values (UUID(), 'project_member', 'PROJECT_REPORT_ANALYSIS:READ+UPDATE', 'PROJECT_REPORT_ANALYSIS'); +insert into user_group_permission (id, group_id, permission_id, module_id) +values (UUID(), 'project_member', 'PROJECT_REPORT_ANALYSIS:READ+CREATE', 'PROJECT_REPORT_ANALYSIS'); diff --git a/backend/src/main/resources/permission.json b/backend/src/main/resources/permission.json index 73024e68cd..5d8b7e01d1 100644 --- a/backend/src/main/resources/permission.json +++ b/backend/src/main/resources/permission.json @@ -809,25 +809,25 @@ "id": "PROJECT_REPORT_ANALYSIS:READ", "name": "查看", "resourceId": "PROJECT_REPORT_ANALYSIS", - "license": true + "license": false }, { "id": "PROJECT_REPORT_ANALYSIS:READ+EXPORT", "name": "导出", "resourceId": "PROJECT_REPORT_ANALYSIS", - "license": true + "license": false }, { "id": "PROJECT_REPORT_ANALYSIS:READ+UPDATE", "name": "保存", "resourceId": "PROJECT_REPORT_ANALYSIS", - "license": true + "license": false }, { "id": "PROJECT_REPORT_ANALYSIS:READ+CREATE", "name": "另存为", "resourceId": "PROJECT_REPORT_ANALYSIS", - "license": true + "license": false } ], "resource": [ @@ -969,8 +969,7 @@ }, { "id": "PROJECT_REPORT_ANALYSIS", - "name": "报表", - "license": true + "name": "报表" } ] } \ No newline at end of file From 1b745d5c5be8e4673f79e799406d1995260bb3f4 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Tue, 28 Sep 2021 16:50:44 +0800 Subject: [PATCH 67/74] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E6=8B=96=E6=8B=BD=E6=97=B6=E9=AB=98=E4=BA=AE=E9=98=B4?= =?UTF-8?q?=E5=BD=B1=E5=81=9C=E7=95=99=E5=9C=A8=E5=8E=9F=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track/issue/client/ZentaoClient.java | 2 +- .../common/components/table/MsTable.vue | 6 +++++- frontend/src/common/js/tableUtils.js | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/io/metersphere/track/issue/client/ZentaoClient.java b/backend/src/main/java/io/metersphere/track/issue/client/ZentaoClient.java index 2fe4418e17..3ed951714a 100644 --- a/backend/src/main/java/io/metersphere/track/issue/client/ZentaoClient.java +++ b/backend/src/main/java/io/metersphere/track/issue/client/ZentaoClient.java @@ -41,7 +41,7 @@ public abstract class ZentaoClient extends BaseClient { ResponseEntity<String> response = restTemplate.exchange(loginUrl + sessionId, HttpMethod.POST, requestEntity, String.class); getUserResponse = (GetUserResponse) getResultForObject(GetUserResponse.class, response); } catch (Exception e) { - LogUtil.error("get result for object error," + e.getMessage()); + LogUtil.error(e); MSException.throwException(e.getMessage()); } GetUserResponse.User user = getUserResponse.getUser(); diff --git a/frontend/src/business/components/common/components/table/MsTable.vue b/frontend/src/business/components/common/components/table/MsTable.vue index 7c340f2250..af3f453a9c 100644 --- a/frontend/src/business/components/common/components/table/MsTable.vue +++ b/frontend/src/business/components/common/components/table/MsTable.vue @@ -483,6 +483,10 @@ export default { /* 解决拖拽排序后hover阴影错乱问题 */ .ms-table >>> .el-table__body tr:hover>td { - background-color: #F5F7FA!important; + background-color: #F5F7FA; +} + +.disable-hover >>> tr:hover>td{ + background-color: #ffffff !important; } </style> diff --git a/frontend/src/common/js/tableUtils.js b/frontend/src/common/js/tableUtils.js index 8cf4ce2892..fed8f15340 100644 --- a/frontend/src/common/js/tableUtils.js +++ b/frontend/src/common/js/tableUtils.js @@ -534,6 +534,8 @@ export function handleRowDrop(data, callback) { } const dropBars = tbody.getElementsByClassName('table-row-drop-bar'); + const msTable = document.getElementsByClassName('ms-table'); + // 每次调用生成一个class // 避免增删列表数据时,回调函数中的 data 与实际 data 不一致 let dropClass = 'table-row-drop-bar-random' + '_' + getUUID(); @@ -545,6 +547,14 @@ export function handleRowDrop(data, callback) { Sortable.create(tbody, { handle: "." + dropClass, animation: 100, + onStart: function (/**Event*/evt) { + // 解决拖拽时高亮阴影停留在原位置的问题 + if (msTable) { + msTable.forEach(table => { + table.classList.add('disable-hover'); + }); + } + }, onEnd({ newIndex, oldIndex}) { let param = {}; param.moveId = data[oldIndex].id; @@ -569,6 +579,12 @@ export function handleRowDrop(data, callback) { callback(param); } } + + msTable.forEach(table => { + if (msTable) { + table.classList.remove('disable-hover'); + } + }); } }); }, 100); From 7c63b60b97dedc3340e51b3f494acf7f1e882652 Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Tue, 28 Sep 2021 17:12:13 +0800 Subject: [PATCH 68/74] =?UTF-8?q?fix:=20--bug=3D1006969=20--user=3D?= =?UTF-8?q?=E9=99=88=E5=BB=BA=E6=98=9F=20=E3=80=90github#6529=E3=80=91?= =?UTF-8?q?=E5=9C=A8=E5=90=8C=E4=B8=80=E4=B8=AAmeter=20sphere=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E5=B7=A5=E4=BD=9C=E7=A9=BA=E9=97=B4=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E5=AD=98=E5=9C=A8=E5=90=8C=E4=B8=80=E4=B8=AA=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=20https://www.tapd.cn/55049933/s/1052834?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/ApiAutomationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index c239140e80..3d99d21904 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -1860,6 +1860,7 @@ public class ApiAutomationService { List<ApiMethodUrlDTO> useUrl = this.parseUrl(scenarioWithBLOBs); scenarioWithBLOBs.setUseUrl(JSONArray.toJSONString(useUrl)); scenarioWithBLOBs.setOrder(getImportNextOrder(request.getProjectId())); + scenarioWithBLOBs.setId(UUID.randomUUID().toString()); batchMapper.insert(scenarioWithBLOBs); apiScenarioReferenceIdService.saveByApiScenario(scenarioWithBLOBs); } From 391e4224a84dba079a66de637d90649d436cfee4 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 <yuhao.li@fit2cloud.com> Date: Tue, 28 Sep 2021 15:39:34 +0800 Subject: [PATCH 69/74] =?UTF-8?q?fix:=20=E5=9C=BA=E6=99=AF=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=9D=83=E9=99=90=E7=9B=B8=E5=85=B3#1005370?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1005370 --user=lyh 【github#4887】接口场景-越权问题 https://www.tapd.cn/55049933/s/1052754 --- .../metersphere/api/controller/ApiAutomationController.java | 5 +++-- .../components/api/automation/scenario/ApiScenarioList.vue | 2 +- .../components/api/automation/scenario/EditApiScenario.vue | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index e1ad2d3100..2f0b746cda 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -22,6 +22,7 @@ import io.metersphere.task.service.TaskService; import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest; import io.metersphere.track.request.testplan.FileOperationRequest; import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -96,7 +97,7 @@ public class ApiAutomationController { @PostMapping(value = "/create") @MsAuditLog(module = "api_automation", type = OperLogConstants.CREATE, title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = ApiAutomationService.class) - @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_READ_CREATE) + @RequiresPermissions(value={PermissionConstants.PROJECT_API_SCENARIO_READ_CREATE, PermissionConstants.PROJECT_API_SCENARIO_READ_COPY}, logical = Logical.OR) @SendNotice(taskType = NoticeConstants.TaskType.API_AUTOMATION_TASK, event = NoticeConstants.Event.CREATE, mailTemplate = "api/AutomationCreate", subject = "接口自动化通知") public ApiScenario create(@RequestPart("request") SaveApiScenarioRequest request, @RequestPart(value = "bodyFiles", required = false) List<MultipartFile> bodyFiles, @RequestPart(value = "scenarioFiles", required = false) List<MultipartFile> scenarioFiles) { @@ -105,7 +106,7 @@ public class ApiAutomationController { @PostMapping(value = "/update") @MsAuditLog(module = "api_automation", type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#request.id)", title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = ApiAutomationService.class) - @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_READ_EDIT) + @RequiresPermissions(value={PermissionConstants.PROJECT_API_SCENARIO_READ_EDIT, PermissionConstants.PROJECT_API_SCENARIO_READ_COPY}, logical = Logical.OR) @SendNotice(taskType = NoticeConstants.TaskType.API_AUTOMATION_TASK, event = NoticeConstants.Event.UPDATE, mailTemplate = "api/AutomationUpdate", subject = "接口自动化通知") public ApiScenario update(@RequestPart("request") SaveApiScenarioRequest request, @RequestPart(value = "bodyFiles", required = false) List<MultipartFile> bodyFiles, @RequestPart(value = "scenarioFiles", required = false) List<MultipartFile> scenarioFiles) { diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index bd064f08be..990af5ab3e 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -391,7 +391,7 @@ export default { tip: this.$t('api_test.automation.copy'), icon: "el-icon-document-copy", exec: this.copy, - permissions: ['PROJECT_API_SCENARIO:READ+EDIT'] + permissions: ['PROJECT_API_SCENARIO:READ+COPY'] }, { tip: this.$t('commons.delete'), diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index a62b58134a..f43b144a9d 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -8,7 +8,7 @@ <el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="path === '/api/automation/update'">{{ $t('operating_log.change_history') }}</el-link> <el-button id="inputDelay" type="primary" size="small" v-prevent-re-click @click="editScenario" - title="ctrl + s" v-permission="['PROJECT_API_SCENARIO:READ+EDIT']"> + title="ctrl + s" v-permission="['PROJECT_API_SCENARIO:READ+EDIT', 'PROJECT_API_SCENARIO:READ+CREATE', 'PROJECT_API_SCENARIO:READ+COPY']"> {{ $t('commons.save') }} </el-button> </div> @@ -146,7 +146,7 @@ :isReadOnly="scenarioDefinition.length < 1" @showPopover="showPopover" :project-list="projectList" ref="envPopover" class="ms-message-right"/> <el-tooltip v-if="!debugLoading" content="Ctrl + R" placement="top"> - <el-dropdown split-button type="primary" @click="runDebug" class="ms-message-right" size="mini" @command="handleCommand"> + <el-dropdown split-button type="primary" @click="runDebug" class="ms-message-right" size="mini" @command="handleCommand" v-permission="['PROJECT_API_SCENARIO:READ+EDIT', 'PROJECT_API_SCENARIO:READ+CREATE']"> {{ $t('api_test.request.debug') }} <el-dropdown-menu slot="dropdown"> <el-dropdown-item>{{ $t('api_test.automation.generate_report') }}</el-dropdown-item> @@ -223,7 +223,7 @@ </el-col> <!-- 按钮列表 --> <el-col :span="3"> - <div @click="fabClick"> + <div @click="fabClick" v-permission="['PROJECT_API_SCENARIO:READ+EDIT', 'PROJECT_API_SCENARIO:READ+CREATE']"> <vue-fab id="fab" mainBtnColor="#783887" size="small" :global-options="globalOptions" :click-auto-close="false" v-outside-click="outsideClick" ref="refFab"> <fab-item From 05f75c74ca7e7610ade17c64ff6ae538fcdda029 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 <yuhao.li@fit2cloud.com> Date: Tue, 28 Sep 2021 16:25:46 +0800 Subject: [PATCH 70/74] refactor: i18n#1005314 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1005314 --user=lyh 【测试跟踪】国际化问题 https://www.tapd.cn/55049933/s/1052794 --- .../common/components/table/MsCustomTableHeader.vue | 10 ++++++++++ .../src/business/components/track/issue/IssueList.vue | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/business/components/common/components/table/MsCustomTableHeader.vue b/frontend/src/business/components/common/components/table/MsCustomTableHeader.vue index dd734b57ba..0d5b3482a1 100644 --- a/frontend/src/business/components/common/components/table/MsCustomTableHeader.vue +++ b/frontend/src/business/components/common/components/table/MsCustomTableHeader.vue @@ -20,6 +20,7 @@ import MsDialogFooter from "@/business/components/common/components/MsDialogFooter"; import treeTransfer from 'el-tree-transfer' import {getAllFieldWithCustomFields, saveCustomTableHeader} from "@/common/js/tableUtils"; +import {SYSTEM_FIELD_NAME_MAP} from "@/common/js/table-constants"; export default { name: "MsCustomTableHeader", @@ -45,6 +46,12 @@ export default { }, open(items) { items = JSON.parse(JSON.stringify(items)); + items.forEach(it => { + if (it.isCustom) { + // i18n + it.label = SYSTEM_FIELD_NAME_MAP[it.id] ? this.$t(SYSTEM_FIELD_NAME_MAP[it.id]) : it.label; + } + }) let fields = getAllFieldWithCustomFields(this.type, this.customFields); this.selectedKeys = []; this.fromFields = []; @@ -52,6 +59,9 @@ export default { this.selectedFields = items; fields.forEach(field => { if (this.selectedKeys.indexOf(field.key) < 0) { + if (field.isCustom) { + field.label = SYSTEM_FIELD_NAME_MAP[field.id] ? this.$t(SYSTEM_FIELD_NAME_MAP[field.id]) : field.label + } this.fromFields.push(field); } }); diff --git a/frontend/src/business/components/track/issue/IssueList.vue b/frontend/src/business/components/track/issue/IssueList.vue index b6631db62a..fd57441afc 100644 --- a/frontend/src/business/components/track/issue/IssueList.vue +++ b/frontend/src/business/components/track/issue/IssueList.vue @@ -134,7 +134,7 @@ <ms-table-column v-for="field in issueTemplate.customFields" :key="field.id" :field="item" :fields-width="fieldsWidth" - :label="field.name" + :label="field.system ? $t(systemNameMap[field.name]) :field.name" :prop="field.name"> <template v-slot="scope"> <span v-if="field.name === '状态'"> From c591c1df57a7f004a4aa002c70c47e0f68826b5c Mon Sep 17 00:00:00 2001 From: "Captain.B" <bin@fit2cloud.com> Date: Tue, 28 Sep 2021 17:57:37 +0800 Subject: [PATCH 71/74] =?UTF-8?q?fix(=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5)?= =?UTF-8?q?:=20=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8F=98=E9=87=8F=E4=B8=8D=E8=83=BD=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/metersphere/api/service/TestResultService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/api/service/TestResultService.java b/backend/src/main/java/io/metersphere/api/service/TestResultService.java index a87416adf8..68e0cd38f0 100644 --- a/backend/src/main/java/io/metersphere/api/service/TestResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/TestResultService.java @@ -152,7 +152,7 @@ public class TestResultService { } if (reportTask != null) { if (!StringUtils.equals(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), runMode) && !StringUtils.equals(ApiRunMode.JENKINS_SCENARIO_PLAN.name(), runMode) && StringUtils.equals(ReportTriggerMode.API.name(), reportTask.getTriggerMode()) || StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), reportTask.getTriggerMode())) { - sendTask(reportTask, reportUrl, testResult); +// sendTask(reportTask, reportUrl, testResult); } } } catch (Exception e) { From 74c4c556724171d3fc25b52db3238aa8aef995a0 Mon Sep 17 00:00:00 2001 From: "Captain.B" <bin@fit2cloud.com> Date: Tue, 28 Sep 2021 18:54:32 +0800 Subject: [PATCH 72/74] =?UTF-8?q?fix(=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5)?= =?UTF-8?q?:=20=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=8F=98=E9=87=8F=E4=B8=8D=E8=83=BD=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/service/TestResultService.java | 68 +------------------ .../notice/service/NoticeService.java | 2 +- 2 files changed, 2 insertions(+), 68 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/service/TestResultService.java b/backend/src/main/java/io/metersphere/api/service/TestResultService.java index 68e0cd38f0..1d6274f33f 100644 --- a/backend/src/main/java/io/metersphere/api/service/TestResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/TestResultService.java @@ -150,11 +150,7 @@ public class TestResultService { testPlanTestCaseService.updateTestCaseStates(ids, TestPlanTestCaseStatus.Failure.name()); } } - if (reportTask != null) { - if (!StringUtils.equals(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), runMode) && !StringUtils.equals(ApiRunMode.JENKINS_SCENARIO_PLAN.name(), runMode) && StringUtils.equals(ReportTriggerMode.API.name(), reportTask.getTriggerMode()) || StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), reportTask.getTriggerMode())) { -// sendTask(reportTask, reportUrl, testResult); - } - } + } catch (Exception e) { e.printStackTrace(); LogUtil.error(e.getMessage(), e); @@ -193,66 +189,4 @@ public class TestResultService { } } - private static void sendTask(ApiTestReportVariable report, String reportUrl, TestResult testResult) { - if (report == null) { - return; - } - SystemParameterService systemParameterService = CommonBeanFactory.getBean(SystemParameterService.class); - NoticeSendService noticeSendService = CommonBeanFactory.getBean(NoticeSendService.class); - assert systemParameterService != null; - assert noticeSendService != null; - BaseSystemConfigDTO baseSystemConfigDTO = systemParameterService.getBaseInfo(); - String url = baseSystemConfigDTO.getUrl() + "/#/api/report/view/" + report.getId(); - String url2 = baseSystemConfigDTO.getUrl() + "/#/api/automation/report/view/" + report.getId(); - - String successContext = ""; - String failedContext = ""; - String subject = ""; - String event = ""; - if (StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode())) { - successContext = "接口测试 API任务通知:jenkins所执行的" + report.getName() + "'执行成功" + "\n" + "执行环境:" + report.getExecutionEnvironment() + "\n" + "[接口定义暂无报告链接]" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2; - failedContext = "接口测试 API任务通知:jenkins所执行的" + report.getName() + "'执行失败" + "\n" + "执行环境:" + report.getExecutionEnvironment() + "\n" + "[接口定义暂无报告链接]" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2; - subject = Translator.get("task_notification_jenkins"); - } - if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) { - successContext = "接口测试定时任务通知:定时任务所执行的" + report.getName() + "'执行成功" + "\n" + "执行环境:" + report.getExecutionEnvironment() + "\n" + "[接口定义暂无报告链接]" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2; - failedContext = "接口测试定时任务通知:定时任务所执行的" + report.getName() + "'执行失败" + "\n" + "执行环境:" + report.getExecutionEnvironment() + "\n" + "[接口定义暂无报告链接]" + "\n" + "请点击下面链接进入测试报告页面" + "\n" + "(旧版)接口测试路径" + url + "\n" + "(新版)接口测试路径" + url2; - subject = Translator.get("task_notification"); - } - if (StringUtils.equals("Success", report.getStatus())) { - event = NoticeConstants.Event.EXECUTE_SUCCESSFUL; - } - if (StringUtils.equals("success", report.getStatus())) { - event = NoticeConstants.Event.EXECUTE_SUCCESSFUL; - } - if (StringUtils.equals("Error", report.getStatus())) { - event = NoticeConstants.Event.EXECUTE_FAILED; - } - if (StringUtils.equals("error", report.getStatus())) { - event = NoticeConstants.Event.EXECUTE_FAILED; - } - Map<String, Object> paramMap = new HashMap<>(); - paramMap.put("testName", report.getName()); - paramMap.put("id", report.getId()); - paramMap.put("type", "api"); - paramMap.put("url", baseSystemConfigDTO.getUrl()); - paramMap.put("status", report.getStatus()); - paramMap.put("executor", report.getExecutor()); - paramMap.put("executionTime", report.getExecutionTime()); - paramMap.put("executionEnvironment", report.getExecutionEnvironment()); - paramMap.put("principal", report.getPrincipal()); - NoticeModel noticeModel = NoticeModel.builder() - .operator(SessionUtils.getUserId()) - .successContext(successContext) - .successMailTemplate("ApiSuccessfulNotification") - .failedContext(failedContext) - .failedMailTemplate("ApiFailedNotification") - .testId(testResult.getTestId()) - .status(report.getStatus()) - .event(event) - .subject(subject) - .paramMap(paramMap) - .build(); - noticeSendService.send(report.getTriggerMode(), NoticeConstants.TaskType.API_DEFINITION_TASK, noticeModel); - } } diff --git a/backend/src/main/java/io/metersphere/notice/service/NoticeService.java b/backend/src/main/java/io/metersphere/notice/service/NoticeService.java index 231e0fd2ad..3b17088b5a 100644 --- a/backend/src/main/java/io/metersphere/notice/service/NoticeService.java +++ b/backend/src/main/java/io/metersphere/notice/service/NoticeService.java @@ -134,7 +134,7 @@ public class NoticeService { public List<MessageDetail> searchMessageByTypeBySend(String type, String projectId) { try { String orgId = ""; - if (null == SessionUtils.getUser()) { + if (StringUtils.isNotEmpty(projectId)) { Organization organization = extProjectMapper.getOrganizationByProjectId(projectId); orgId = organization.getId(); } else { From 3e1936bc691631f038363691494aa5b85e96b5e6 Mon Sep 17 00:00:00 2001 From: fit2-zhao <yong.zhao@fit2cloud.com> Date: Tue, 28 Sep 2021 18:14:59 +0800 Subject: [PATCH 73/74] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BC=98=E5=8C=96=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E4=B8=B2=E8=A1=8C=E6=89=A7=E8=A1=8C=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E9=A2=91=E7=B9=81=E5=92=8C=E6=95=B0=E6=8D=AE=E5=BA=93=E4=BA=A4?= =?UTF-8?q?=E4=BA=92;=E5=A2=9E=E5=8A=A0get=E8=AF=B7=E6=B1=82=E5=8F=AF?= =?UTF-8?q?=E5=8A=A0=E8=AF=B7=E6=B1=82=E4=BD=93=20--bug=3D1006978=20--user?= =?UTF-8?q?=3D=E8=B5=B5=E5=8B=87=20=E3=80=90github#6559=E3=80=91http?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=EF=BC=8Cget=E4=B8=AD=E5=B8=A6=E6=9C=89body?= =?UTF-8?q?=E7=9A=84=E8=AF=B7=E6=B1=82=E6=89=A7=E8=A1=8C=E5=90=8E=EF=BC=8C?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=9C=8D=E5=8A=A1=E6=94=B6=E5=88=B0=E7=9A=84?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E9=87=8C=E6=B2=A1=E6=9C=89=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=BD=93=20https://www.tapd.cn/55049933/s/1052908?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/sampler/MsHTTPSamplerProxy.java | 19 ++++++------- .../api/jmeter/APIBackendListenerHandler.java | 3 +++ .../metersphere/api/jmeter/MessageCache.java | 3 +++ .../api/service/ApiAutomationService.java | 22 +++++++++++---- .../service/task/SerialScenarioExecTask.java | 27 +++++++++---------- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java index a53fef875e..c64b66b22d 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -1,7 +1,6 @@ package io.metersphere.api.dto.definition.request.sampler; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONType; @@ -225,16 +224,14 @@ public class MsHTTPSamplerProxy extends MsTestElement { setSamplerPath(config, httpConfig, sampler); - // 请求体 - if (!StringUtils.equals(this.getMethod(), "GET")) { - if (this.body != null) { - List<KeyValue> bodyParams = this.body.getBodyParams(sampler, this.getId()); - if (StringUtils.isNotEmpty(this.body.getType()) && "Form Data".equals(this.body.getType())) { - sampler.setDoMultipart(true); - } - if (CollectionUtils.isNotEmpty(bodyParams)) { - sampler.setArguments(httpArguments(bodyParams)); - } + // 请求体处理 + if (this.body != null) { + List<KeyValue> bodyParams = this.body.getBodyParams(sampler, this.getId()); + if (StringUtils.isNotEmpty(this.body.getType()) && "Form Data".equals(this.body.getType())) { + sampler.setDoMultipart(true); + } + if (CollectionUtils.isNotEmpty(bodyParams)) { + sampler.setArguments(httpArguments(bodyParams)); } } diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java index 4b14129810..c5c7c237d7 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java @@ -56,5 +56,8 @@ public class APIBackendListenerHandler { if (!MessageCache.reportCache.containsKey(testId) && resultService.getProcessCache().containsKey(testId)) { resultService.getProcessCache().remove(testId); } + if(StringUtils.isNotEmpty(testId)) { + MessageCache.executionQueue.remove(testId); + } } } diff --git a/backend/src/main/java/io/metersphere/api/jmeter/MessageCache.java b/backend/src/main/java/io/metersphere/api/jmeter/MessageCache.java index 73bec5fcae..47bd821598 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/MessageCache.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/MessageCache.java @@ -20,4 +20,7 @@ public class MessageCache { public static ConcurrentHashMap<String, ApiDefinitionExecResult> batchTestCases = new ConcurrentHashMap<>(); + // 串行执行队列 KEY=报告ID VALUE=开始时间 + public static Map<String, Long> executionQueue = new HashMap<>(); + } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 3d99d21904..1b5821ff6a 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -51,7 +51,9 @@ import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest; import io.metersphere.track.request.testcase.QueryTestPlanRequest; import io.metersphere.track.request.testplan.FileOperationRequest; import io.metersphere.track.service.TestPlanScenarioCaseService; +import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.comparators.FixedOrderComparator; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; @@ -997,11 +999,19 @@ public class ApiAutomationService { // 生成集成报告 String serialReportId = null; - StringBuilder idStr = new StringBuilder(); - ids.forEach(item -> { - idStr.append("\"").append(item).append("\"").append(","); - }); - List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectByIds(idStr.toString().substring(0, idStr.toString().length() - 1), "\"" + StringUtils.join(ids, ",") + "\""); + ApiScenarioExample example = new ApiScenarioExample(); + example.createCriteria().andIdIn(ids); + List<ApiScenarioWithBLOBs> apiScenarios = apiScenarioMapper.selectByExampleWithBLOBs(example); + if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString())) { + if (request.getCondition() == null || !request.getCondition().isSelectAll()) { + // 按照id指定顺序排序 + FixedOrderComparator<String> fixedOrderComparator = new FixedOrderComparator<String>(ids); + fixedOrderComparator.setUnknownObjectBehavior(FixedOrderComparator.UnknownObjectBehavior.BEFORE); + BeanComparator beanComparator = new BeanComparator("id", fixedOrderComparator); + Collections.sort(apiScenarios, beanComparator); + } + } + // 只有一个场景且没有测试步骤,则提示 if (apiScenarios != null && apiScenarios.size() == 1 && (apiScenarios.get(0).getStepTotal() == null || apiScenarios.get(0).getStepTotal() == 0)) { MSException.throwException((apiScenarios.get(0).getName() + "," + Translator.get("automation_exec_info"))); @@ -1162,6 +1172,7 @@ public class ApiAutomationService { MessageCache.terminationOrderDeque.remove(key); break; } + MessageCache.executionQueue.put(key, System.currentTimeMillis()); reportIds.add(key); APIScenarioReportResult report = executeQueue.get(key).getReport(); if (StringUtils.isNotEmpty(serialReportId)) { @@ -1193,6 +1204,7 @@ public class ApiAutomationService { executeEnvParams = hashTreeUtil.mergeParamDataMap(executeEnvParams, envParamsMap); } catch (Exception e) { reportIds.remove(key); + MessageCache.executionQueue.remove(key); LogUtil.error("执行终止:" + e.getMessage()); break; } diff --git a/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java b/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java index 0151101252..8d2825d347 100644 --- a/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java +++ b/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java @@ -34,7 +34,7 @@ public class SerialScenarioExecTask<T> implements Callable<T> { @Override public T call() { try { - if (runModeDataDTO.getReport()!=null && MessageCache.terminationOrderDeque.contains(runModeDataDTO.getReport().getId())) { + if (runModeDataDTO.getReport() != null && MessageCache.terminationOrderDeque.contains(runModeDataDTO.getReport().getId())) { MessageCache.terminationOrderDeque.remove(runModeDataDTO.getReport().getId()); return null; } @@ -43,25 +43,24 @@ public class SerialScenarioExecTask<T> implements Callable<T> { } else { jMeterService.runLocal(runModeDataDTO.getReport().getId(), runModeDataDTO.getHashTree(), TriggerMode.BATCH.name().equals(request.getTriggerMode()) ? TriggerMode.BATCH.name() : request.getReportId(), request.getRunMode()); } - // 轮询查看报告状态,最多200次,防止死循环 - int index = 1; - while (index < 200) { - Thread.sleep(3000); - index++; - report = apiScenarioReportMapper.selectByPrimaryKey(runModeDataDTO.getReport().getId()); - if (report != null && !report.getStatus().equals(APITestStatus.Running.name())) { + while (MessageCache.executionQueue.containsKey(runModeDataDTO.getReport().getId())) { + long currentSecond = (System.currentTimeMillis() - MessageCache.executionQueue.get(runModeDataDTO.getReport().getId())) / 1000 / 60; + // 设置五分钟超时 + if (currentSecond > 5) { + // 执行失败了,恢复报告状态 + report = apiScenarioReportMapper.selectByPrimaryKey(runModeDataDTO.getReport().getId()); + if (report != null) { + report.setStatus(APITestStatus.Error.name()); + apiScenarioReportMapper.updateByPrimaryKey(report); + } break; } - if (runModeDataDTO.getReport()!=null && MessageCache.terminationOrderDeque.contains(runModeDataDTO.getReport().getId())) { + if (runModeDataDTO.getReport() != null && MessageCache.terminationOrderDeque.contains(runModeDataDTO.getReport().getId())) { MessageCache.terminationOrderDeque.remove(runModeDataDTO.getReport().getId()); break; } } - // 执行失败了,恢复报告状态 - if (index == 200 && report != null && report.getStatus().equals(APITestStatus.Running.name())) { - report.setStatus(APITestStatus.Error.name()); - apiScenarioReportMapper.updateByPrimaryKey(report); - } + report = apiScenarioReportMapper.selectByPrimaryKey(runModeDataDTO.getReport().getId()); return (T) report; } catch (Exception ex) { LogUtil.error(ex); From bd290756f6c91276881be7f19c73bb6dfe892e4f Mon Sep 17 00:00:00 2001 From: chenjianxing <jianxing.chen@fit2cloud.com> Date: Tue, 28 Sep 2021 17:46:49 +0800 Subject: [PATCH 74/74] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8E=89=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BC=96=E8=BE=91=E5=99=A8=E4=B8=AD=E7=9A=84=E4=B8=AD?= =?UTF-8?q?=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/common/css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/common/css/main.css b/frontend/src/common/css/main.css index bf2f9d3a22..cde3fe414c 100644 --- a/frontend/src/common/css/main.css +++ b/frontend/src/common/css/main.css @@ -296,3 +296,7 @@ textarea { .ms-full-loading .el-loading-spinner { font-size: 16px; } + +.ace-chrome .ace_print-margin { + width: 0px !important; +}