list = new ArrayList<>();
BugStatustics bugStatustics = new BugStatustics();
int index = 1;
- int totalBugSize = 0;
int totalCaseSize = 0;
for (TestPlan plan : plans) {
int planBugSize = getPlanBugSize(plan.getId());
@@ -137,12 +139,11 @@ public class TrackService {
double planPassRage = getPlanPassRage(plan.getId());
testPlanBug.setPassRage(planPassRage + "%");
list.add(testPlanBug);
-
- totalBugSize += planBugSize;
totalCaseSize += planCaseSize;
}
+ int totalBugSize = projectService.getProjectBugSize(projectId);
bugStatustics.setList(list);
float rage =totalCaseSize == 0 ? 0 : (float) totalBugSize * 100 / totalCaseSize;
DecimalFormat df = new DecimalFormat("0.0");
diff --git a/backend/src/main/resources/db/migration/V99__v1.15_release.sql b/backend/src/main/resources/db/migration/V99__v1.15_release.sql
index 67924fc8ae..5f54a9f3cb 100644
--- a/backend/src/main/resources/db/migration/V99__v1.15_release.sql
+++ b/backend/src/main/resources/db/migration/V99__v1.15_release.sql
@@ -33,37 +33,9 @@ alter table api_scenario
alter table api_scenario
add environment_group_id varchar(50) null;
-DELIMITER $$
-DROP PROCEDURE IF EXISTS proc_loop_test$$
-CREATE PROCEDURE proc_loop_test()
-BEGIN
- DECLARE int_val INT DEFAULT 0;
- DECLARE size INT DEFAULT 10;
- DECLARE count_scenario INT DEFAULT 0;
- SELECT COUNT(1) INTO count_scenario FROM api_scenario;
- test_loop :
- LOOP
- IF (int_val > count_scenario / size)
- THEN
- UPDATE api_scenario
- SET environment_json = api_scenario.scenario_definition -> '$.environmentMap'
- WHERE api_scenario.id IN (SELECT id FROM (SELECT id FROM api_scenario LIMIT int_val, size) l);
- #
- LEAVE test_loop;
- END IF;
-
- UPDATE api_scenario
- SET environment_json = api_scenario.scenario_definition -> '$.environmentMap'
- WHERE api_scenario.id IN (SELECT id FROM (SELECT id FROM api_scenario LIMIT int_val, size) l);
-
- SET int_val = int_val + size;
- END LOOP;
-END$$
-
-DELIMITER ;
-CALL proc_loop_test();
-DROP PROCEDURE proc_loop_test;
-
+update api_scenario
+set environment_json = api_scenario.scenario_definition -> '$.environmentMap'
+where api_scenario.environment_json is null;
update api_scenario set environment_type = 'JSON';
diff --git a/backend/src/main/resources/mail/PerformanceApiSuccessNotification.html b/backend/src/main/resources/mail/PerformanceApiSuccessNotification.html
deleted file mode 100644
index 04bca545f6..0000000000
--- a/backend/src/main/resources/mail/PerformanceApiSuccessNotification.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- MeterSphere
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/src/main/resources/mail/PerformanceSuccessNotification.html b/backend/src/main/resources/mail/PerformanceSuccessNotification.html
new file mode 100644
index 0000000000..0293d6e241
--- /dev/null
+++ b/backend/src/main/resources/mail/PerformanceSuccessNotification.html
@@ -0,0 +1,12 @@
+
+
+
+
+ MeterSphere
+
+
+
+ ${operator}执行性能测试成功: ${name}, 报告: ${reportUrl}
+
+
+
\ No newline at end of file
diff --git a/frontend/src/business/components/api/automation/ApiAutomation.vue b/frontend/src/business/components/api/automation/ApiAutomation.vue
index 95ceae2b7f..ba751d4512 100644
--- a/frontend/src/business/components/api/automation/ApiAutomation.vue
+++ b/frontend/src/business/components/api/automation/ApiAutomation.vue
@@ -427,7 +427,7 @@ export default {
}
},
editScenario(row) {
- const index = this.tabs.find(p => p.currentScenario.id === row.id);
+ const index = this.tabs.find(p => p.currentScenario.id === row.id && p.currentScenario.copy === row.copy);
if (!index) {
this.addTab({name: 'edit', currentScenario: row});
} else {
@@ -486,6 +486,7 @@ export default {
/deep/ .el-tabs__header {
margin: 0 0 0px;
}
+
/deep/ .el-table__empty-block {
width: 100%;
min-width: 100%;
diff --git a/frontend/src/business/components/api/automation/api-automation.js b/frontend/src/business/components/api/automation/api-automation.js
index 36a33ee048..47d1bcf4be 100644
--- a/frontend/src/business/components/api/automation/api-automation.js
+++ b/frontend/src/business/components/api/automation/api-automation.js
@@ -99,7 +99,7 @@ export function saveScenario(url, scenario, scenarioDefinition, _this, success)
success(response);
}
}, error => {
- _this.$emit('errorRefresh', {});
+ _this.$emit('errorRefresh', error);
});
}
diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue
index 3287f4dd12..0488f7f838 100644
--- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue
+++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue
@@ -52,7 +52,6 @@
:fields-width="fieldsWidth"
min-width="120px">
-
{{ scope.row.num }}
@@ -66,7 +65,6 @@
:fields-width="fieldsWidth"
min-width="120px">
-
{{ scope.row.customNum }}
@@ -537,14 +535,6 @@ export default {
};
},
created() {
- // if (!hasLicense()) {
- // for (let i = 0; i < this.unTrashButtons.length; i++) {
- // if (this.unTrashButtons[i].handleClick === this.generateGraph) {
- // this.unTrashButtons.splice(i,1);
- // break;
- // }
- // }
- // }
scenario.$on('hide', id => {
this.hideStopBtn(id);
});
diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
index 7bc0484a17..4bf7b8380e 100644
--- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
+++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
@@ -205,12 +205,14 @@
:project-list="projectList"
:env-map="projectEnvMap"
:env-group-id="envGroupId"
+ :environment-type="environmentType"
@remove="remove"
@copyRow="copyRow"
@suggestClick="suggestClick"
@refReload="refReload"
@runScenario="runDebug"
@stopScenario="stop"
+ @setDomain="setDomain"
@openScenario="openScenario"/>
@@ -528,7 +530,7 @@ export default {
currentUser: () => {
return getCurrentUser();
},
- setDomain(flag) {
+ setDomain() {
if (this.projectEnvMap && this.projectEnvMap.size > 0) {
let scenario = {
id: this.currentScenario.id,
@@ -1501,7 +1503,7 @@ export default {
this.initMessageSocket();
}
},
- errorRefresh() {
+ errorRefresh(error) {
this.debug = false;
this.isTop = false;
this.debugLoading = false;
diff --git a/frontend/src/business/components/api/automation/scenario/common/CustomizeReqInfo.vue b/frontend/src/business/components/api/automation/scenario/common/CustomizeReqInfo.vue
index 24259f12a2..b4a45b1bfd 100644
--- a/frontend/src/business/components/api/automation/scenario/common/CustomizeReqInfo.vue
+++ b/frontend/src/business/components/api/automation/scenario/common/CustomizeReqInfo.vue
@@ -9,7 +9,7 @@
-
+
{{ $t('api_test.request.refer_to_environment') }}
@@ -123,6 +123,9 @@ export default {
this.$error(this.$t('api_test.request.url_invalid'), 2000);
}
},
+ setDomain() {
+ this.$emit("setDomain");
+ }
}
}
diff --git a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue
index f92265f499..6fcba534c1 100644
--- a/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue
+++ b/frontend/src/business/components/api/automation/scenario/component/ApiComponent.vue
@@ -52,7 +52,7 @@
- 启用场景环境
+ 启用场景环境
@@ -66,8 +66,7 @@ import MsTcpBasisParameters from "../../../definition/components/request/tcp/Tcp
import MsDubboBasisParameters from "../../../definition/components/request/dubbo/BasisParameters";
import MsApiRequestForm from "../../../definition/components/request/http/ApiHttpRequestForm";
import ApiBaseComponent from "../common/ApiBaseComponent";
-import {getCurrentProjectID, getUUID} from "@/common/js/utils";
-import {getUrl} from "@/business/components/api/automation/scenario/component/urlhelper";
+import {getCurrentProjectID, getUUID, strMapToObj} from "@/common/js/utils";
export default {
name: "ApiScenarioComponent",
@@ -88,7 +87,10 @@ export default {
default: false,
},
currentEnvironmentId: String,
- projectList: Array
+ projectList: Array,
+ environmentType: String,
+ environmentGroupId: String,
+ envMap: Map
},
watch: {
message() {
@@ -99,6 +101,9 @@ export default {
},
},
created() {
+ if(this.scenario.num){
+ this.isShowNum = true;
+ }
if (!this.scenario.projectId) {
this.scenario.projectId = getCurrentProjectID();
}
@@ -128,8 +133,16 @@ export default {
this.scenario.variables = obj.variables;
this.scenario.environmentMap = obj.environmentMap;
this.$emit('refReload');
- } else {
- this.scenario.referenced = "Deleted";
+ }
+ })
+ }
+ else if(this.scenario.id && this.scenario.referenced === 'Copy' && !this.scenario.loaded){
+ this.result = this.$get("/api/automation/getApiScenario/" + this.scenario.id, response => {
+ if (response.data) {
+ if(response.data.num){
+ this.scenario.num = response.data.num;
+ this.isShowNum = true;
+ }
}
})
}
@@ -163,30 +176,31 @@ export default {
this.$emit('stopScenario');
this.reload();
},
- checkEnv() {
+ checkEnv(val) {
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();
+ this.setDomain(val);
});
},
- setDomain() {
- if (this.scenario.environmentEnable) {
- let param = {
- environmentEnable: true,
- id: this.scenario.id,
- definition: JSON.stringify(this.scenario)
- }
- this.$post("/api/automation/setDomain", param, res => {
- if (res.data) {
- let data = JSON.parse(res.data);
- this.scenario.hashTree = data.hashTree;
- }
- })
+ setDomain(val) {
+ let param = {
+ environmentEnable: val,
+ id: this.scenario.id,
+ environmentType: this.environmentType,
+ environmentGroupId: this.environmentGroupId,
+ environmentMap: strMapToObj(this.envMap),
+ definition: JSON.stringify(this.scenario)
}
+ this.$post("/api/automation/setDomain", param, 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) {
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 0a21b8acad..ebd9706b8a 100644
--- a/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue
+++ b/frontend/src/business/components/api/automation/scenario/component/ComponentConfig.vue
@@ -20,7 +20,7 @@
:title="title"
:color="titleColor"
:response="response"
- :environmet-type="environmentType"
+ :environment-type="environmentType"
:environment-group-id="envGroupId"
:background-color="backgroundColor"
:project-list="projectList"
@@ -34,7 +34,7 @@
@copyRow="copyRow"
@refReload="refReload"
@openScenario="openScenario"
-
+ @setDomain="setDomain"
/>
@@ -231,6 +231,9 @@ export default {
},
stopScenario() {
this.$emit('stopScenario');
+ },
+ setDomain() {
+ this.$emit("setDomain");
}
}
}
diff --git a/frontend/src/business/components/api/definition/ApiDefinition.vue b/frontend/src/business/components/api/definition/ApiDefinition.vue
index 5ce39997d1..11463c908b 100644
--- a/frontend/src/business/components/api/definition/ApiDefinition.vue
+++ b/frontend/src/business/components/api/definition/ApiDefinition.vue
@@ -555,28 +555,28 @@ export default {
}
},
closeConfirm(targetName) {
- let tabs = this.apiTabs;
- if(!tabs[1].api) {
- this.handleTabRemove(targetName);
- }
- if (tabs[1].api && this.$store.state.apiMap.size > 0) {
- if (this.$store.state.apiMap.get(tabs[1].api.id).get("responseChange") === true || this.$store.state.apiMap.get(tabs[1].api.id).get("requestChange") === true ||
- this.$store.state.apiMap.get(tabs[1].api.id).get("fromChange") === true) {
- this.$alert("接口[ " + tabs[1].api.name + " ]未保存,是否确认关闭?", '', {
- confirmButtonText: this.$t('commons.confirm'),
- cancelButtonText: this.$t('commons.cancel'),
- callback: (action) => {
- if (action === 'confirm') {
- this.$store.state.apiMap.delete(tabs[1].api.id);
- this.handleTabRemove(targetName);
- }
+ let tab = this.apiTabs;
+ tab.forEach(t => {
+ if (t.name === targetName) {
+ if (t.api && this.$store.state.apiMap.size > 0 && this.$store.state.apiMap.has(t.api.id)) {
+ if (this.$store.state.apiMap.get(t.api.id).get("responseChange") === true || this.$store.state.apiMap.get(t.api.id).get("requestChange") === true ||
+ this.$store.state.apiMap.get(t.api.id).get("fromChange") === true) {
+ this.$alert("接口[ " + t.api.name + " ]未保存,是否确认关闭?", '', {
+ confirmButtonText: this.$t('commons.confirm'),
+ cancelButtonText: this.$t('commons.cancel'),
+ callback: (action) => {
+ if (action === 'confirm') {
+ this.$store.state.apiMap.delete(t.api.id);
+ this.handleTabRemove(targetName);
+ }
+ }
+ });
}
- });
+ } else {
+ this.handleTabRemove(targetName);
+ }
}
- } else{
- this.handleTabRemove(targetName);
- }
-
+ })
},
handleTabRemove(targetName) {
let tabs = this.apiTabs;
diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue
index 476a53aed5..03cb9dc3bb 100644
--- a/frontend/src/business/components/api/definition/components/ApiConfig.vue
+++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue
@@ -267,6 +267,8 @@ export default {
this.$emit('saveApi', data);
});
this.$store.state.apiMap.delete(this.currentApi.id);
+ this.responseCount = 0;
+ this.count = 0
},
handleSave() {
if (this.$refs.httpApi) {
diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
index d47bbdfbd5..2c0b66d697 100644
--- a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
+++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
@@ -72,7 +72,7 @@
-
+
@@ -179,6 +179,7 @@
label: 'name',
},
mockBaseUrl: "",
+ count: 0
}
},
props: {moduleOptions: {}, request: {}, response: {}, basisData: {}, syncTabs: Array, projectId: String},
@@ -232,6 +233,14 @@
}
}
},
+ 'httpForm.tags': {
+ handler(v, v1) {
+ this.count++;
+ if (v && v1 && JSON.stringify(v) !== JSON.stringify(v1) && this.count > 1) {
+ this.apiMapStatus();
+ }
+ }
+ },
syncTabs() {
if (this.basisData && this.syncTabs && this.syncTabs.includes(this.basisData.id)) {
// 标示接口在其他地方更新过,当前页面需要同步
@@ -337,6 +346,8 @@
if (valid) {
this.setParameter();
this.$emit('saveApi', this.httpForm);
+ this.count = 0;
+ this.$store.state.apiMap.delete(this.httpForm.id);
} else {
return false;
}
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 0a27aa4e33..6d05542d39 100644
--- a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue
+++ b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue
@@ -502,9 +502,13 @@ export default {
let url = "/api/definition/report/get/" + apiCase.lastResultId;
this.$get(url, response => {
if (response.data) {
- let data = JSON.parse(response.data.content);
- this.response = data;
- this.resVisible = true;
+ try {
+ let data = JSON.parse(response.data.content);
+ this.response = data;
+ this.resVisible = true;
+ } catch (error) {
+ this.resVisible = true;
+ }
}
});
}
@@ -653,9 +657,9 @@ export default {
} else if (this.selectDataRange != null) {
let selectParamArr = this.selectDataRange.split(":");
if (selectParamArr.length === 2) {
- if(selectParamArr[0] === "single") {
+ if (selectParamArr[0] === "single") {
this.condition.id = selectParamArr[1];
- }else {
+ } else {
this.condition.apiDefinitionId = selectParamArr[1];
}
}
diff --git a/frontend/src/business/components/api/definition/components/response/AssertionResults.vue b/frontend/src/business/components/api/definition/components/response/AssertionResults.vue
index 63c1ddfab4..c7b0ad9b24 100644
--- a/frontend/src/business/components/api/definition/components/response/AssertionResults.vue
+++ b/frontend/src/business/components/api/definition/components/response/AssertionResults.vue
@@ -13,25 +13,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/common/model/JsonData.js b/frontend/src/business/components/common/model/JsonData.js
index 2a4086abca..5d2a638458 100644
--- a/frontend/src/business/components/common/model/JsonData.js
+++ b/frontend/src/business/components/common/model/JsonData.js
@@ -39,6 +39,10 @@ export const Test_Plan_List = [
{id: 'executionTimes', label: i18n.t('commons.execution_times')},
{id: 'passRate', label: i18n.t('commons.pass_rate')},
{id: 'createUser', label: i18n.t('commons.create_user')},
+ {id: 'testPlanTestCaseCount', label: i18n.t('test_track.plan.test_plan_test_case_count')},
+ {id: 'testPlanApiCaseCount', label: i18n.t('test_track.plan.test_plan_api_case_count')},
+ {id: 'testPlanApiScenarioCount', label: i18n.t('test_track.plan.test_plan_api_scenario_count')},
+ {id: 'testPlanLoadCaseCount', label: i18n.t('test_track.plan.test_plan_load_case_count')}
]
//接口定义-api列表
export const Api_List = [
diff --git a/frontend/src/business/components/notice/util.js b/frontend/src/business/components/notice/util.js
index 89f3f06ec6..5294edcb08 100644
--- a/frontend/src/business/components/notice/util.js
+++ b/frontend/src/business/components/notice/util.js
@@ -50,6 +50,15 @@ export function getResource(d) {
switch (d.resourceType) {
case "JENKINS_TASK" :
resourceType = "Jenkins";
+ if (d.operation === 'EXECUTE_SUCCESSFUL') {
+ resourceType = "Jenkins 成功";
+ }
+ if (d.operation === 'EXECUTE_FAILED') {
+ resourceType = "Jenkins 失败";
+ }
+ if (d.operation === 'EXECUTE_COMPLETED') {
+ resourceType = "Jenkins 完成";
+ }
break;
case "TEST_PLAN_TASK" :
resourceType = "测试计划";
@@ -115,7 +124,40 @@ export function getUrl(d) {
let url = "/#";
switch (d.resourceType) {
case "JENKINS_TASK" :
- url += "/track/plan/all";
+ // jenkins 跳转需要特殊处理
+ try {
+ let obj = JSON.parse(d.content);
+ // 接口自动化,性能测试
+ if (obj.reportUrl) {
+ let s = obj.reportUrl.indexOf('#');
+ url += obj.reportUrl.substring(s + 1);
+ }
+ // 接口用例
+ else if (obj.caseStatus) {
+ url += "/api/definition?caseId=" + d.resourceId;
+ }
+ // 测试计划
+ else if (obj.url) {
+ let s = obj.url.indexOf('#');
+ url += obj.url.substring(s + 1);
+ } else {
+ url += "/track/plan/all";
+ }
+ } catch (e) {
+ // jenkins 跳转需要特殊处理
+ if (d.content.indexOf("接口用例") > -1) {
+ url += "/api/definition?caseId=" + d.resourceId;
+ } else if (d.content.indexOf("性能测试") > -1) {
+ url += "/performance/test/edit/" + d.resourceId;
+ } else if (d.content.indexOf("接口测试") > -1) {
+ url += "/api/automation/report/view/" + d.resourceId;
+ } else if (d.content.indexOf("测试计划运行") > -1) {
+ url += "/track/plan/view/" + d.resourceId;
+ } else {
+ url += "/track/plan/all";
+ }
+ }
+
break;
case "TEST_PLAN_TASK" :
url += "/track/plan/view/" + d.resourceId;
@@ -133,7 +175,7 @@ export function getUrl(d) {
url += "/api/automation?resourceId=" + d.resourceId;
break;
case "API_DEFINITION_TASK" :
- if (d.operation.startsWith('CASE_') || d.operation.startsWith('EXECUTE_') ) {
+ if (d.operation.startsWith('CASE_') || d.operation.startsWith('EXECUTE_')) {
url += "/api/definition?caseId=" + d.resourceId;
} else {
url += "/api/definition?resourceId=" + d.resourceId;
diff --git a/frontend/src/business/components/settings/workspace/components/jenkins/JenkinsNotification.vue b/frontend/src/business/components/settings/workspace/components/jenkins/JenkinsNotification.vue
index 0756eb1660..9f51d8e16b 100644
--- a/frontend/src/business/components/settings/workspace/components/jenkins/JenkinsNotification.vue
+++ b/frontend/src/business/components/settings/workspace/components/jenkins/JenkinsNotification.vue
@@ -157,11 +157,11 @@ export default {
'\n' +
'\n' +
'\n' +
- '${operator}所执行的 ${name} ${type}测试运行成功' +
+ '${operator}执行 Jenkins 成功: ${name}' +
'
\n' +
'\n' +
'