This commit is contained in:
q4speed 2020-05-29 14:57:32 +08:00
commit b116516af1
27 changed files with 858 additions and 99 deletions

View File

@ -15,6 +15,7 @@
and w.id = #{proRequest.workspaceId} and w.id = #{proRequest.workspaceId}
</if> </if>
</where> </where>
order by p.update_time desc
</select> </select>
<select id="getProjectIdByWorkspaceId" resultType="java.lang.String"> <select id="getProjectIdByWorkspaceId" resultType="java.lang.String">
select id select id

View File

@ -57,7 +57,9 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; "); stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; ");
} }
if (testCaseNames.contains(data.getName())) { if (testCaseNames.contains(data.getName())) {
stringBuilder.append(Translator.get("test_case_already_exists") + "" + data.getName() + "; "); stringBuilder.append(Translator.get("test_case_already_exists_excel") + "" + data.getName() + "; ");
} else {
testCaseNames.add(data.getName());
} }
return stringBuilder.toString(); return stringBuilder.toString();
} }

View File

@ -125,9 +125,21 @@ public class ProjectService {
public void updateProject(Project project) { public void updateProject(Project project) {
project.setCreateTime(null); project.setCreateTime(null);
project.setUpdateTime(System.currentTimeMillis()); project.setUpdateTime(System.currentTimeMillis());
checkProjectExist(project);
projectMapper.updateByPrimaryKeySelective(project); projectMapper.updateByPrimaryKeySelective(project);
} }
private void checkProjectExist (Project project) {
ProjectExample example = new ProjectExample();
example.createCriteria()
.andNameEqualTo(project.getName())
.andWorkspaceIdEqualTo(SessionUtils.getCurrentWorkspaceId())
.andIdNotEqualTo(project.getId());
if (projectMapper.selectByExample(example).size() > 0) {
MSException.throwException(Translator.get("project_name_already_exists"));
}
}
public List<Project> listAll() { public List<Project> listAll() {
return projectMapper.selectByExample(null); return projectMapper.selectByExample(null);
} }

View File

@ -15,4 +15,5 @@ public class TestPlanCaseDTO extends TestCaseWithBLOBs {
private String planName; private String planName;
private String caseId; private String caseId;
private String issues; private String issues;
private String reportId;
} }

View File

@ -92,9 +92,21 @@ public class TestCaseService {
public int editTestCase(TestCaseWithBLOBs testCase) { public int editTestCase(TestCaseWithBLOBs testCase) {
testCase.setUpdateTime(System.currentTimeMillis()); testCase.setUpdateTime(System.currentTimeMillis());
checkTestCaseExist(testCase);
return testCaseMapper.updateByPrimaryKeySelective(testCase); return testCaseMapper.updateByPrimaryKeySelective(testCase);
} }
private void checkTestCaseExist (TestCaseWithBLOBs testCase) {
TestCaseExample example = new TestCaseExample();
example.createCriteria()
.andNameEqualTo(testCase.getName())
.andProjectIdEqualTo(testCase.getProjectId())
.andIdNotEqualTo(testCase.getId());
if (testCaseMapper.selectByExample(example).size() > 0) {
MSException.throwException(Translator.get("test_case_already_exists"));
}
}
public int deleteTestCase(String testCaseId) { public int deleteTestCase(String testCaseId) {
TestPlanTestCaseExample example = new TestPlanTestCaseExample(); TestPlanTestCaseExample example = new TestPlanTestCaseExample();
example.createCriteria().andCaseIdEqualTo(testCaseId); example.createCriteria().andCaseIdEqualTo(testCaseId);

View File

@ -90,9 +90,21 @@ public class TestPlanService {
public int editTestPlan(TestPlan testPlan) { public int editTestPlan(TestPlan testPlan) {
testPlan.setUpdateTime(System.currentTimeMillis()); testPlan.setUpdateTime(System.currentTimeMillis());
checkTestPlanExist(testPlan);
return testPlanMapper.updateByPrimaryKeySelective(testPlan); return testPlanMapper.updateByPrimaryKeySelective(testPlan);
} }
private void checkTestPlanExist (TestPlan testPlan) {
TestPlanExample example = new TestPlanExample();
example.createCriteria()
.andNameEqualTo(testPlan.getName())
.andWorkspaceIdEqualTo(SessionUtils.getCurrentWorkspaceId())
.andIdNotEqualTo(testPlan.getId());
if (testPlanMapper.selectByExample(example).size() > 0) {
MSException.throwException(Translator.get("plan_name_already_exists"));
}
}
public int deleteTestPlan(String planId) { public int deleteTestPlan(String planId) {
deleteTestCaseByPlanId(planId); deleteTestCaseByPlanId(planId);
return testPlanMapper.deleteByPrimaryKey(planId); return testPlanMapper.deleteByPrimaryKey(planId);

View File

@ -94,3 +94,4 @@ options=options
please_input_workspace_member=Please input workspace merber please_input_workspace_member=Please input workspace merber
test_case_report_template_repeat=The workspace has the same name template test_case_report_template_repeat=The workspace has the same name template
plan_name_already_exists=Test plan name already exists plan_name_already_exists=Test plan name already exists
test_case_already_exists_excel=There are duplicate test cases in the import file

View File

@ -94,3 +94,4 @@ options=选项
please_input_workspace_member=请填写该工作空间相关人员 please_input_workspace_member=请填写该工作空间相关人员
test_case_report_template_repeat=同一工作空间下不能存在同名模版 test_case_report_template_repeat=同一工作空间下不能存在同名模版
plan_name_already_exists=测试计划名称已存在 plan_name_already_exists=测试计划名称已存在
test_case_already_exists_excel=导入文件中存在重复用例

View File

@ -93,4 +93,5 @@ module_created_automatically=若無該模塊將自動創建
options=選項 options=選項
please_input_workspace_member=請填寫該工作空間相關人員 please_input_workspace_member=請填寫該工作空間相關人員
test_case_report_template_repeat=同壹工作空間下不能存在同名模版 test_case_report_template_repeat=同壹工作空間下不能存在同名模版
plan_name_already_exists=測試計劃名稱已存在 plan_name_already_exists=測試計劃名稱已存在
test_case_already_exists_excel=導入文件中存在重復用例

View File

@ -37,16 +37,17 @@
<el-tabs v-model="active" type="border-card" :stretch="true"> <el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_overview')"> <el-tab-pane :label="$t('report.test_overview')">
<ms-report-test-overview :id="reportId" :status="status"/> <!-- <ms-report-test-overview :id="reportId" :status="status"/>-->
<ms-report-test-overview :report="report"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')"> <el-tab-pane :label="$t('report.test_request_statistics')">
<ms-report-request-statistics :id="reportId" :status="status"/> <ms-report-request-statistics :report="report"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('report.test_error_log')"> <el-tab-pane :label="$t('report.test_error_log')">
<ms-report-error-log :id="reportId" :status="status"/> <ms-report-error-log :report="report"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('report.test_log_details')"> <el-tab-pane :label="$t('report.test_log_details')">
<ms-report-log-details :id="reportId" :status="status"/> <ms-report-log-details :report="report"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -89,6 +90,7 @@
minutes: '0', minutes: '0',
seconds: '0', seconds: '0',
title: 'Logging', title: 'Logging',
report: {}
} }
}, },
methods: { methods: {
@ -108,8 +110,9 @@
}, },
initReportTimeInfo() { initReportTimeInfo() {
if (this.reportId) { if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId, res => { this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
let data = res.data; .then(res => {
let data = res.data.data;
if (data) { if (data) {
this.startTime = data.startTime; this.startTime = data.startTime;
this.endTime = data.endTime; this.endTime = data.endTime;
@ -117,55 +120,90 @@
this.minutes = Math.floor(duration / 60); this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60; this.seconds = duration % 60;
} }
}).catch(() => {
this.clearData();
}) })
} }
}, },
}, checkReportStatus(status) {
mounted() { switch (status) {
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
this.status = data.status;
switch (data.status) {
case 'Error': case 'Error':
this.$warning(this.$t('report.generation_error')); this.$warning(this.$t('report.generation_error'));
break; break;
case 'Starting': case 'Starting':
this.$warning(this.$t('report.start_status'));
break;
case 'Reporting': case 'Reporting':
this.$info(this.$t('report.being_generated')); this.$info(this.$t('report.being_generated'));
break; break;
case 'Running': case 'Running':
this.$warning(this.$t('report.run_status'));
break; break;
case 'Completed':
default: default:
break; break;
} }
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
}
},
created() {
this.reportId = this.$route.path.split('/')[4];
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.initReportTimeInfo();
}
}) })
this.initBreadcrumb(); this.initBreadcrumb();
this.initReportTimeInfo();
}, },
watch: { watch: {
'$route'(to) { '$route'(to) {
let reportId = to.path.split('/')[4]; if (to.name === "perReportView") {
if (reportId) { let reportId = to.path.split('/')[4];
this.$get("/performance/report/test/pro/info/" + reportId, response => { this.reportId = reportId;
let data = response.data; if (reportId) {
if (data) { this.$get("/performance/report/test/pro/info/" + reportId, response => {
this.reportName = data.name; let data = response.data;
this.testName = data.testName; if (data) {
this.projectName = data.projectName; this.status = data.status;
} this.reportName = data.name;
}); this.testName = data.testName;
this.result = this.$get("/performance/report/content/report_time/" + this.reportId, res => { this.projectName = data.projectName;
let data = res.data;
if (data) { this.$set(this.report, "id", reportId);
this.startTime = data.startTime; this.$set(this.report, "status", data.status);
this.endTime = data.endTime;
let duration = data.duration; this.checkReportStatus(data.status);
this.minutes = Math.floor(duration / 60); if (this.status === "Completed") {
this.seconds = duration % 60; this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
} let data = res.data.data;
}) if (data) {
window.location.reload(); this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
} else {
this.clearData();
}
}
});
}
} }
} }
} }

View File

@ -131,27 +131,40 @@
data() { data() {
return { return {
tableData: [], tableData: [],
errorTop5: [] errorTop5: [],
id: ''
} }
}, },
methods: { methods: {
initTableData() { initTableData() {
this.$get("/performance/report/content/errors/" + this.id, res => { this.$get("/performance/report/content/errors/" + this.id).then(res => {
this.tableData = res.data; this.tableData = res.data.data;
}).catch(() => {
this.tableData = [];
}) })
this.$get("/performance/report/content/errors_top5/" + this.id, res => { this.$get("/performance/report/content/errors_top5/" + this.id).then(res => {
this.errorTop5 = res.data; this.errorTop5 = res.data.data;
}).catch(() => {
this.errorTop5 = [];
}) })
} }
}, },
watch: { watch: {
status() { report: {
if ("Completed" === this.status) { handler(val){
this.initTableData() let status = val.status;
} this.id = val.id;
if (status === "Completed") {
this.initTableData();
} else {
this.tableData = [];
this.errorTop5 = [];
}
},
deep:true
} }
}, },
props: ['id','status'] props: ['report']
} }
</script> </script>

View File

@ -12,17 +12,19 @@
<script> <script>
export default { export default {
name: "LogDetails", name: "LogDetails",
props: ['id', 'status'],
data() { data() {
return { return {
logContent: null, logContent: null,
result: {}, result: {},
id: ''
} }
}, },
methods: { methods: {
initTableData() { initTableData() {
this.result = this.$get("/performance/report/log/" + this.id, res => { this.result = this.$get("/performance/report/log/" + this.id).then(res => {
this.logContent = res.data; this.logContent = res.data.data;
}).catch(() => {
this.logContent = null;
}) })
}, },
downloadLogFile(item) { downloadLogFile(item) {
@ -50,12 +52,20 @@
} }
}, },
watch: { watch: {
status() { report: {
if ("Completed" === this.status) { handler(val){
this.initTableData() let status = val.status;
} this.id = val.id;
if (status === "Completed") {
this.initTableData();
} else {
this.logContent = null;
}
},
deep:true
} }
}, },
props: ['report']
} }
</script> </script>

View File

@ -95,13 +95,16 @@
name: "RequestStatistics", name: "RequestStatistics",
data() { data() {
return { return {
tableData: [] tableData: [],
id: ''
} }
}, },
methods: { methods: {
initTableData() { initTableData() {
this.$get("/performance/report/content/" + this.id, res => { this.$get("/performance/report/content/" + this.id).then(res => {
this.tableData = res.data; this.tableData = res.data.data;
}).catch(() => {
this.tableData = [];
}) })
}, },
getSummaries(param) { getSummaries(param) {
@ -154,13 +157,20 @@
} }
}, },
watch: { watch: {
status() { report: {
if ("Completed" === this.status) { handler(val){
this.initTableData() let status = val.status;
} this.id = val.id;
if (status === "Completed") {
this.initTableData();
} else {
this.tableData = [];
}
},
deep:true
} }
}, },
props: ['id', 'status'] props: ['report']
} }
</script> </script>

View File

@ -80,22 +80,31 @@
responseTime90: "0", responseTime90: "0",
avgBandwidth: "0", avgBandwidth: "0",
loadOption: {}, loadOption: {},
resOption: {} resOption: {},
id: ''
} }
}, },
methods: { methods: {
initTableData() { initTableData() {
this.$get("/performance/report/content/testoverview/" + this.id, res => { this.$get("/performance/report/content/testoverview/" + this.id).then(res => {
let data = res.data; let data = res.data.data;
this.maxUsers = data.maxUsers; this.maxUsers = data.maxUsers;
this.avgThroughput = data.avgThroughput; this.avgThroughput = data.avgThroughput;
this.errors = data.errors; this.errors = data.errors;
this.avgResponseTime = data.avgResponseTime; this.avgResponseTime = data.avgResponseTime;
this.responseTime90 = data.responseTime90; this.responseTime90 = data.responseTime90;
this.avgBandwidth = data.avgBandwidth; this.avgBandwidth = data.avgBandwidth;
}).catch(() => {
this.maxUsers = '0';
this.avgThroughput = '0';
this.errors = '0';
this.avgResponseTime = '0';
this.responseTime90 = '0';
this.avgBandwidth = '0';
this.$warning(this.$t('report.generation_error'));
}) })
this.$get("/performance/report/content/load_chart/" + this.id, res => { this.$get("/performance/report/content/load_chart/" + this.id).then(res => {
let data = res.data; let data = res.data.data;
let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis); let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis);
let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2); let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
@ -166,9 +175,11 @@
setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'}) setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'})
}) })
this.loadOption = this.generateOption(loadOption, data, setting); this.loadOption = this.generateOption(loadOption, data, setting);
}).catch(() => {
this.loadOption = {};
}) })
this.$get("/performance/report/content/res_chart/" + this.id, res => { this.$get("/performance/report/content/res_chart/" + this.id).then(res => {
let data = res.data; let data = res.data.data;
let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis); let yAxisList = data.filter(m => m.yAxis2 === -1).map(m => m.yAxis);
let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2); let yAxis2List = data.filter(m => m.yAxis === -1).map(m => m.yAxis2);
let yAxisListMax = this._getChartMax(yAxisList); let yAxisListMax = this._getChartMax(yAxisList);
@ -246,6 +257,8 @@
}) })
this.resOption = this.generateOption(resOption, data, setting); this.resOption = this.generateOption(resOption, data, setting);
}).catch(() => {
this.resOption = {};
}) })
}, },
generateOption(option, data, setting) { generateOption(option, data, setting) {
@ -310,13 +323,27 @@
} }
}, },
watch: { watch: {
status() { report: {
if ("Completed" === this.status) { handler(val){
this.initTableData() let status = val.status;
} this.id = val.id;
if (status === "Completed") {
this.initTableData();
} else {
this.maxUsers = '0';
this.avgThroughput = '0';
this.errors = '0';
this.avgResponseTime = '0';
this.responseTime90 = '0';
this.avgBandwidth = '0';
this.loadOption = {};
this.resOption = {};
}
},
deep:true
} }
}, },
props: ['id', 'status'] props: ['report']
} }
</script> </script>

View File

@ -37,10 +37,10 @@
<el-dialog :title="title" :visible.sync="createVisible"> <el-dialog :title="title" :visible.sync="createVisible">
<el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input> <el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.description')"> <el-form-item :label="$t('commons.description')" prop="description">
<el-input type="textarea" v-model="form.description"></el-input> <el-input type="textarea" v-model="form.description"></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -87,8 +87,17 @@
rules: { rules: {
name: [ name: [
{required: true, message: this.$t('project.input_name'), trigger: 'blur'}, {required: true, message: this.$t('project.input_name'), trigger: 'blur'},
{min: 2, max: 50, message: this.$t('commons.input_limit', [2, 50]), trigger: 'blur'} {min: 2, max: 25, message: this.$t('commons.input_limit', [2, 25]), trigger: 'blur'},
] {
required: true,
pattern: /^(?!-)(?!.*?-$)[a-zA-Z0-9\u4e00-\u9fa5-]+$/,
message: this.$t('project.special_characters_are_not_supported'),
trigger: 'blur'
}
],
description: [
{max: 50, message: this.$t('commons.input_limit', [0, 50]), trigger: 'blur'}
],
}, },
} }
}, },

View File

@ -218,7 +218,7 @@
rule: { rule: {
name: [ name: [
{required: true, message: this.$t('organization.input_name'), trigger: 'blur'}, {required: true, message: this.$t('organization.input_name'), trigger: 'blur'},
{min: 2, max: 20, message: this.$t('commons.input_limit', [2, 20]), trigger: 'blur'}, {min: 2, max: 25, message: this.$t('commons.input_limit', [2, 25]), trigger: 'blur'},
{ {
required: true, required: true,
pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/, pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/,

View File

@ -447,7 +447,7 @@
rules: { rules: {
name: [ name: [
{required: true, message: this.$t('workspace.input_name'), trigger: 'blur'}, {required: true, message: this.$t('workspace.input_name'), trigger: 'blur'},
{min: 2, max: 20, message: this.$t('commons.input_limit', [2, 20]), trigger: 'blur'}, {min: 2, max: 25, message: this.$t('commons.input_limit', [2, 25]), trigger: 'blur'},
{ {
required: true, required: true,
pattern: /^(?!-)(?!.*?-$)[a-zA-Z0-9\u4e00-\u9fa5-]+$/, pattern: /^(?!-)(?!.*?-$)[a-zA-Z0-9\u4e00-\u9fa5-]+$/,

View File

@ -289,7 +289,7 @@
open(testCase) { open(testCase) {
this.resetForm(); this.resetForm();
this.operationType = 'add'; this.operationType = 'add';
if(testCase){ if (testCase) {
// //
this.operationType = 'edit'; this.operationType = 'edit';
// //

View File

@ -89,12 +89,12 @@
<el-col class="test-detail" :span="20" :offset="1"> <el-col class="test-detail" :span="20" :offset="1">
<el-tabs type="border-card"> <el-tabs type="border-card">
<el-tab-pane :label="$t('test_track.plan_view.test_detail')"> <el-tab-pane :label="$t('test_track.plan_view.test_detail')">
<ms-api-test-config v-if="testCase.type == 'api'"/> <api-test-detail v-if="testCase.type == 'api'" @runTest="apiTestRun" :id="testCase.testId" ref="apiTestDetail"/>
<edit-performance-test-plan v-if="testCase.type == 'performance'"/> <performance-test-detail v-if="testCase.type == 'performance'" :id="testCase.testId" ref="performanceTestDetail"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('test_track.plan_view.test_result')"> <el-tab-pane :label="$t('test_track.plan_view.test_result')">
<ms-api-report-view v-if="testCase.type == 'api'"/> <api-test-result :report-id="testCase.reportId" v-if=" testCase.type == 'api'" ref="apiTestResult"/>
<performance-report-view v-if="testCase.type == 'performance'"/> <performance-test-result :report-id="testCase.reportId" v-if="testCase.type == 'performance'"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
@ -199,16 +199,19 @@
<script> <script>
import TestPlanTestCaseStatusButton from '../../common/TestPlanTestCaseStatusButton'; import TestPlanTestCaseStatusButton from '../../common/TestPlanTestCaseStatusButton';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import MsApiTestConfig from "../../../../api/test/ApiTestConfig"; import ApiTestDetail from "./test/ApiTestDetail";
import MsApiReportView from "../../../../api/report/ApiReportView"; import ApiTestResult from "./test/ApiTestResult";
import EditPerformanceTestPlan from "../../../../performance/test/EditPerformanceTestPlan"; import PerformanceTestDetail from "./test/PerformanceTestDetail";
import PerformanceReportView from "../../../../performance/report/PerformanceReportView"; import PerformanceTestResult from "./test/PerformanceTestResult";
export default { export default {
name: "TestPlanTestCaseEdit", name: "TestPlanTestCaseEdit",
components: { components: {
PerformanceReportView, PerformanceTestResult,
EditPerformanceTestPlan, MsApiReportView, MsApiTestConfig, TestPlanTestCaseStatusButton}, PerformanceTestDetail,
ApiTestResult,
ApiTestDetail,
TestPlanTestCaseStatusButton},
data() { data() {
return { return {
result: {}, result: {},
@ -298,13 +301,24 @@
this.showDialog = true; this.showDialog = true;
this.initData(testCase); this.initData(testCase);
}, },
updateTestCases(testCase) { initTest() {
this.testCases.forEach(item => { this.$nextTick(() => {
if (testCase.id === item.id) { if (this.testCase.method == 'auto') {
Object.assign(item, testCase); if (this.$refs.apiTestDetail && this.testCase.type == 'api') {
} this.$refs.apiTestDetail.init();
} else if(this.testCase.type == 'performance') {
this.$refs.performanceTestDetail.init();
}
}
}); });
}, },
apiTestRun(reportId) {
this.testCase.reportId = reportId;
this.saveReport(reportId);
},
saveReport(reportId) {
this.$post('/test/plan/case/edit', {id: this.testCase.id, reportId: reportId});
},
initData(testCase) { initData(testCase) {
this.result = this.$post('/test/plan/case/list/all', this.searchParam, response => { this.result = this.$post('/test/plan/case/list/all', this.searchParam, response => {
this.testCases = response.data; this.testCases = response.data;
@ -313,6 +327,7 @@
this.index = i; this.index = i;
this.getTestCase(i); this.getTestCase(i);
this.getRelatedTest(); this.getRelatedTest();
this.initTest();
} }
} }
}); });

View File

@ -0,0 +1,101 @@
<template>
<el-card>
<el-container class="test-container">
<el-header>
<el-row type="flex" align="middle">
<el-input :disabled="true" class="test-name" v-model="test.name" maxlength="60" :placeholder="$t('api_test.input_name')"
show-word-limit>
<el-select :disabled="true" class="test-project" v-model="project.name" slot="prepend"
:placeholder="$t('api_test.select_project')">
</el-select>
</el-input>
<el-button type="primary" plain @click="runTest">
{{$t('api_test.run')}}
</el-button>
</el-row>
</el-header>
<ms-api-scenario-config :scenarios="test.scenarioDefinition" ref="config"/>
</el-container>
</el-card>
</template>
<script>
import {Test} from "../../../../../api/test/model/ScenarioModel"
import MsApiScenarioConfig from "../../../../../api/test/components/ApiScenarioConfig";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
export default {
name: "ApiTestDetail",
components: {MsMainContainer, MsContainer, MsApiScenarioConfig},
props: ["id"],
data() {
return {
result: {},
test: new Test(),
project: {}
}
},
methods: {
init() {
this.project = {};
if (this.id) {
this.getTest(this.id);
} else {
this.test = new Test();
if (this.$refs.config) {
this.$refs.config.reset();
}
}
},
getTest(id) {
this.result = this.$get("/api/get/" + id, response => {
if (response.data) {
let item = response.data;
this.test = new Test({
id: item.id,
projectId: item.projectId,
name: item.name,
status: item.status,
scenarioDefinition: JSON.parse(item.scenarioDefinition),
});
this.getProject(item.projectId);
this.$refs.config.reset();
}
});
},
getProject(projectId) {
this.$get("/project/get/" + projectId, response => {
this.project = response.data;
});
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id}, (response) => {
this.$success(this.$t('api_test.running'));
this.$emit('runTest', response.data)
});
}
}
}
</script>
<style scoped>
.test-container {
height: calc(100vh - 150px);
min-height: 600px;
padding: 15px;
}
.test-name {
width: 600px;
margin-left: -20px;
margin-right: 20px;
}
.test-project {
min-width: 150px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<ms-container>
<ms-main-container>
<span v-if="!reportId">尚未执行</span>
<el-card v-if="reportId">
<section class="report-container" v-loading="loading">
<header class="report-header">
<span>{{report.projectName}} / </span>
<span class="time">{{report.createTime | timestampFormatDate}}</span>
</header>
<main>
<ms-metric-chart v-if="content" :content="content"/>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_report.total')" name="total">
<ms-scenario-results :scenarios="content.scenarios"/>
</el-tab-pane>
<el-tab-pane name="fail">
<template slot="label">
<span class="fail">{{$t('api_report.fail')}}</span>
</template>
<ms-scenario-results :scenarios="fails"/>
</el-tab-pane>
</el-tabs>
</main>
</section>
</el-card>
</ms-main-container>
</ms-container>
</template>
<script>
import MsScenarioResult from "../../../../../api/report/components/ScenarioResult";
import MsMetricChart from "../../../../../api/report/components/MetricChart";
import MsScenarioResults from "../../../../../api/report/components/ScenarioResults";
import MsRequestResult from "../../../../../api/report/components/RequestResult";
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
export default {
name: "ApiTestResult",
components: {MsMainContainer, MsContainer, MsRequestResult, MsScenarioResults, MsMetricChart, MsScenarioResult},
data() {
return {
activeName: "total",
content: {},
report: {},
loading: true,
fails: [],
}
},
props:['reportId'],
watch: {
reportId() {
this.init();
}
},
mounted() {
this.init();
},
methods: {
init() {
this.loading = true;
this.report = {};
this.content = {};
this.fails = [];
this.getReport();
},
getReport() {
if (this.reportId) {
let url = "/api/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
if (this.report.status == 'Completed') {
this.content = JSON.parse(this.report.content);
this.getFails();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
});
}
},
getFails() {
this.fails = [];
this.content.scenarios.forEach((scenario) => {
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
});
}
});
}
}
}
</script>
<style>
.report-container .el-tabs__header {
margin-bottom: 1px;
}
</style>
<style scoped>
.report-container {
height: calc(100vh - 150px);
min-height: 600px;
overflow-y: auto;
}
.report-header {
font-size: 15px;
}
.report-header a {
text-decoration: none;
}
.report-header .time {
color: #909399;
margin-left: 10px;
}
.report-container .fail {
color: #F56C6C;
}
.report-container .is-active .fail {
color: inherit;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<ms-container>
<ms-main-container>
<el-card>
<!--<el-card v-loading="result.loading">-->
<el-row>
<el-col :span="10">
<el-input :disabled="true" :placeholder="$t('load_test.input_name')" v-model="test.name" class="input-with-select">
<template v-slot:prepend>
<el-select :disabled="true" v-model="project.name" :placeholder="$t('load_test.select_project')" slot="prepend">
</el-select>
</template>
</el-input>
</el-col>
<el-col :span="12" :offset="2">
<el-button type="primary" plain @click="runTest">执行</el-button>
</el-col>
</el-row>
<el-tabs class="test-config" v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('load_test.basic_config')">
<performance-basic-config :test-plan="test" ref="basicConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')">
<performance-pressure-config :test-plan="test" ref="pressureConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
<performance-advanced-config ref="advancedConfig"/>
</el-tab-pane>
</el-tabs>
</el-card>
</ms-main-container>
</ms-container>
</template>
<script>
import MsContainer from "../../../../../common/components/MsContainer";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import PerformanceBasicConfig from "../../../../../performance/test/components/PerformanceBasicConfig";
import PerformancePressureConfig from "../../../../../performance/test/components/PerformancePressureConfig";
import PerformanceAdvancedConfig from "../../../../../performance/test/components/PerformanceAdvancedConfig";
export default {
name: "PerformanceTestDetail",
components: {
PerformanceAdvancedConfig,
PerformancePressureConfig,
PerformanceBasicConfig,
MsMainContainer,
MsContainer
},
data() {
return {
result: {},
test: {},
savePath: "/performance/save",
editPath: "/performance/edit",
runPath: "/performance/run",
project: {},
projectId: '',
active: '0',
tabs: [{
title: this.$t('load_test.basic_config'),
id: '0',
component: 'PerformanceBasicConfig'
}, {
title: this.$t('load_test.pressure_config'),
id: '1',
component: 'PerformancePressureConfig'
}, {
title: this.$t('load_test.advanced_config'),
id: '2',
component: 'PerformanceAdvancedConfig'
}]
}
},
props: ['id'],
methods: {
init() {
this.getTest();
},
getProject(projectId) {
this.$get("/project/get/" + projectId, response => {
this.project = response.data;
});
},
getTest() {
if (this.id) {
this.result = this.$get('/performance/get/' + this.id, response => {
this.test = response.data;
this.getProject(this.test.projectId);
});
}
},
runTest() {
this.result = this.$post(this.runPath, {id: this.test.id}, () => {
this.$success(this.$t('load_test.is_running'));
});
}
}
}
</script>
<style scoped>
.test-config {
margin-top: 15px;
text-align: center;
}
.el-select {
min-width: 130px;
}
.edit-test-container .input-with-select .el-input-group__prepend {
background-color: #fff;
}
.advanced-config {
height: calc(100vh - 280px);
overflow: auto;
}
</style>

View File

@ -0,0 +1,223 @@
<template>
<ms-container>
<ms-main-container>
<el-card v-loading="result.loading">
<el-row>
<el-col :span="16">
<el-row>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/performance/test/' + this.projectId }">{{projectName}}
</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/performance/test/edit/' + this.testId }">{{testName}}
</el-breadcrumb-item>
<el-breadcrumb-item>{{reportName}}</el-breadcrumb-item>
</el-breadcrumb>
</el-row>
<el-row class="ms-report-view-btns">
<el-button type="primary" plain size="mini">{{$t('report.test_stop_now')}}</el-button>
<el-button type="success" plain size="mini">{{$t('report.test_execute_again')}}</el-button>
<el-button type="info" plain size="mini">{{$t('report.export')}}</el-button>
<el-button type="warning" plain size="mini">{{$t('report.compare')}}</el-button>
</el-row>
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
{{$t('report.test_duration', [this.minutes, this.seconds])}}
</span>
<span class="ms-report-time-desc">
{{$t('report.test_start_time')}}{{startTime}}
</span>
<span class="ms-report-time-desc">
{{$t('report.test_end_time')}}{{endTime}}
</span>
</el-col>
</el-row>
<el-divider></el-divider>
<el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_overview')">
<test-overview :report="report"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')">
<request-statistics :report="report"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_error_log')">
<error-log :report="report"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_log_details')">
<log-details :report="report"/>
</el-tab-pane>
</el-tabs>
</el-card>
</ms-main-container>
</ms-container>
</template>
<script>
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import MsContainer from "../../../../../common/components/MsContainer";
import LogDetails from "../../../../../performance/report/components/LogDetails";
import ErrorLog from "../../../../../performance/report/components/ErrorLog";
import RequestStatistics from "../../../../../performance/report/components/RequestStatistics";
import TestOverview from "../../../../../performance/report/components/TestOverview";
export default {
name: "PerformanceTestResult",
components: {
TestOverview,
RequestStatistics,
ErrorLog,
LogDetails,
MsContainer,
MsMainContainer
},
data() {
return {
result: {},
active: '0',
status: '',
reportName: '',
testId: '',
testName: '',
projectId: '',
projectName: '',
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0',
title: 'Logging',
report: {}
}
},
props: ['reportId'],
methods: {
initBreadcrumb() {
if (this.reportId) {
this.result = this.$get("/performance/report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if (data) {
this.reportName = data.name;
this.testId = data.testId;
this.testName = data.testName;
this.projectId = data.projectId;
this.projectName = data.projectName;
}
})
}
},
initReportTimeInfo() {
if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
}
},
checkReportStatus(status) {
switch (status) {
case 'Error':
this.$warning(this.$t('report.generation_error'));
break;
case 'Starting':
this.$warning("测试处于开始状态,请稍后查看报告!");
break;
case 'Reporting':
this.$info(this.$t('report.being_generated'));
break;
case 'Running':
this.$warning("测试处于运行状态,请稍后查看报告!");
break;
case 'Completed':
default:
break;
}
},
clearData() {
this.startTime = '0';
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
}
},
created() {
this.result = this.$get("/performance/report/" + this.reportId, res => {
let data = res.data;
this.status = data.status;
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.initReportTimeInfo();
}
});
this.initBreadcrumb();
},
watch: {
'$route'(to) {
if (to.name === "perReportView") {
let reportId = to.path.split('/')[4];
this.reportId = reportId;
if (reportId) {
this.$get("/performance/report/test/pro/info/" + reportId, response => {
let data = response.data;
if (data) {
this.status = data.status;
this.reportName = data.name;
this.testName = data.testName;
this.projectName = data.projectName;
this.$set(this.report, "id", reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId).then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
} else {
this.clearData();
}
}
});
}
}
}
}
}
</script>
<style scoped>
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
</style>

View File

@ -18,6 +18,8 @@ export default {
callback: () => { callback: () => {
window.location.href = "/login" window.location.href = "/login"
} }
}).then(r => {
}); });
}; };

View File

@ -114,7 +114,7 @@ export default {
'input_name': 'Please enter a organization name', 'input_name': 'Please enter a organization name',
'select_organization': 'Please select organization', 'select_organization': 'Please select organization',
'search_by_name': 'Search by name', 'search_by_name': 'Search by name',
'special_characters_are_not_supported': 'Special characters are not supported', 'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'none': 'None Organization', 'none': 'None Organization',
'select': 'Select Organization', 'select': 'Select Organization',
}, },
@ -129,6 +129,7 @@ export default {
'input_name': 'Please enter a workspace name', 'input_name': 'Please enter a workspace name',
'owning_workspace': 'Owning Workspace', 'owning_workspace': 'Owning Workspace',
'please_choose_workspace': 'Please select Workspace', 'please_choose_workspace': 'Please select Workspace',
'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
}, },
member: { member: {
'create': 'Create', 'create': 'Create',
@ -191,6 +192,8 @@ export default {
'generation_error': 'Report generation error, cannot be viewed!', 'generation_error': 'Report generation error, cannot be viewed!',
'being_generated': 'Report is being generated...', 'being_generated': 'Report is being generated...',
'delete_confirm': 'Confirm delete: ', 'delete_confirm': 'Confirm delete: ',
'start_status': 'The test is starting, please check the report later!',
'run_status': 'The test is running, please check the report later',
}, },
load_test: { load_test: {
'operating': 'Operating', 'operating': 'Operating',

View File

@ -113,7 +113,7 @@ export default {
'input_name': '请输入组织名称', 'input_name': '请输入组织名称',
'select_organization': '请选择组织', 'select_organization': '请选择组织',
'search_by_name': '根据名称搜索', 'search_by_name': '根据名称搜索',
'special_characters_are_not_supported': '不支持特殊字符', 'special_characters_are_not_supported': '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
'none': '无组织', 'none': '无组织',
'select': '选择组织', 'select': '选择组织',
}, },
@ -127,6 +127,7 @@ export default {
'input_name': '请输入项目名称', 'input_name': '请输入项目名称',
'owning_workspace': '所属工作空间', 'owning_workspace': '所属工作空间',
'please_choose_workspace': '请选择工作空间', 'please_choose_workspace': '请选择工作空间',
'special_characters_are_not_supported': '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
}, },
member: { member: {
'create': '添加成员', 'create': '添加成员',
@ -189,6 +190,8 @@ export default {
'generation_error': '报告生成错误,无法查看!', 'generation_error': '报告生成错误,无法查看!',
'being_generated': '报告正在生成中...', 'being_generated': '报告正在生成中...',
'delete_confirm': '确认删除报告: ', 'delete_confirm': '确认删除报告: ',
'start_status': '测试处于开始状态,请稍后查看报告!',
'run_status': '测试处于运行状态,请稍后查看报告!',
}, },
load_test: { load_test: {
'operating': '操作', 'operating': '操作',

View File

@ -113,7 +113,7 @@ export default {
'input_name': '請輸入組織名稱', 'input_name': '請輸入組織名稱',
'select_organization': '請選擇組織', 'select_organization': '請選擇組織',
'search_by_name': '根據名稱搜索', 'search_by_name': '根據名稱搜索',
'special_characters_are_not_supported': '不支持特殊字符', 'special_characters_are_not_supported': 'Incorrect format (special characters are not supported and cannot end with \'-\')',
'none': '無組織', 'none': '無組織',
'select': '選擇組織', 'select': '選擇組織',
}, },
@ -127,6 +127,7 @@ export default {
'input_name': '請輸入項目名稱', 'input_name': '請輸入項目名稱',
'owning_workspace': '所屬工作空間', 'owning_workspace': '所屬工作空間',
'please_choose_workspace': '請選擇工作空間', 'please_choose_workspace': '請選擇工作空間',
'special_characters_are_not_supported': '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
}, },
member: { member: {
'create': '添加成員', 'create': '添加成員',
@ -190,6 +191,8 @@ export default {
'generation_error': '報告生成錯誤,無法查看!', 'generation_error': '報告生成錯誤,無法查看!',
'being_generated': '報告正在生成中...', 'being_generated': '報告正在生成中...',
'delete_confirm': '確認刪除報告: ', 'delete_confirm': '確認刪除報告: ',
'start_status': '測試處於開始狀態,請稍後查看報告!',
'run_status': '測試處於運行狀態,請稍後查看報告!',
}, },
load_test: { load_test: {
'operating': '操作', 'operating': '操作',