refactor: 解决冲突

This commit is contained in:
chenjianxing 2020-08-25 13:44:50 +08:00
commit 050a519d54
33 changed files with 1256 additions and 1163 deletions

View File

@ -7,6 +7,7 @@
from test_case_issues, issues
where test_case_issues.issues_id = issues.id
and test_case_issues.test_case_id = #{caseId}
and issues.platform = #{platform};
and issues.platform = #{platform}
order by issues.create_time DESC
</select>
</mapper>

View File

@ -14,6 +14,7 @@ import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("testresourcepool")
@ -56,5 +57,11 @@ public class TestResourcePoolController {
return testResourcePoolService.listValidResourcePools();
}
@GetMapping("list/quota/valid")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public List<TestResourcePool> listValidQuotaResourcePools() {
return testResourcePoolService.listValidQuotaResourcePools();
}
}

View File

@ -34,8 +34,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
@ -44,6 +42,8 @@ import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
@Service
@Transactional(rollbackFor = Exception.class)
public class PerformanceTestService {
@ -131,7 +131,7 @@ public class PerformanceTestService {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
checkQuota(request);
checkQuota(request, true);
final LoadTestWithBLOBs loadTest = saveLoadTest(request);
files.forEach(file -> {
@ -168,6 +168,7 @@ public class PerformanceTestService {
}
public String edit(EditTestPlanRequest request, List<MultipartFile> files) {
checkQuota(request, false);
//
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
if (loadTest == null) {
@ -363,7 +364,7 @@ public class PerformanceTestService {
}
public void copy(SaveTestPlanRequest request) {
checkQuota(request);
checkQuota(request, true);
// copy test
LoadTestWithBLOBs copy = loadTestMapper.selectByPrimaryKey(request.getId());
copy.setId(UUID.randomUUID().toString());
@ -441,10 +442,10 @@ public class PerformanceTestService {
return schedules;
}
private void checkQuota(SaveTestPlanRequest request) {
private void checkQuota(TestPlanRequest request, boolean create) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkLoadTestQuota(request);
quotaService.checkLoadTestQuota(request, create);
}
}
}

View File

@ -1,10 +1,14 @@
package io.metersphere.service;
import io.metersphere.track.request.testplan.SaveTestPlanRequest;
import io.metersphere.track.request.testplan.TestPlanRequest;
import java.util.Set;
public interface QuotaService {
void checkAPITestQuota();
void checkLoadTestQuota(SaveTestPlanRequest request);
void checkLoadTestQuota(TestPlanRequest request, boolean checkPerformance);
Set<String> getQuotaResourcePools();
}

View File

@ -1,5 +1,8 @@
package io.metersphere.service;
import static io.metersphere.commons.constants.ResourceStatusEnum.INVALID;
import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.LoadTestMapper;
@ -7,6 +10,7 @@ import io.metersphere.base.mapper.TestResourceMapper;
import io.metersphere.base.mapper.TestResourcePoolMapper;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.dto.NodeDTO;
@ -21,15 +25,14 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static io.metersphere.commons.constants.ResourceStatusEnum.INVALID;
import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
import javax.annotation.Resource;
/**
* @author dongbin
@ -243,4 +246,19 @@ public class TestResourcePoolService {
return testResourcePoolMapper.selectByExample(example);
}
public List<TestResourcePool> listValidQuotaResourcePools() {
return filterQuota(listValidResourcePools());
}
private List<TestResourcePool> filterQuota(List<TestResourcePool> list) {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
Set<String> pools = quotaService.getQuotaResourcePools();
if (!pools.isEmpty()) {
return list.stream().filter(pool -> pools.contains(pool.getId())).collect(Collectors.toList());
}
}
return list;
}
}

View File

@ -310,7 +310,11 @@ public class IssuesService {
return new Issues();
}
JSONObject jsonObject = JSONObject.parseObject(listJson);
return jsonObject.getObject("Bug", Issues.class);
JSONObject bug = jsonObject.getJSONObject("Bug");
Long created = bug.getLong("created");
Issues issues = jsonObject.getObject("Bug", Issues.class);
issues.setCreateTime(created);
return issues;
}
public Issues getJiraIssues(HttpHeaders headers, String url, String issuesId) {
@ -326,15 +330,23 @@ public class IssuesService {
JSONObject obj = JSONObject.parseObject(body);
JSONObject fields = (JSONObject) obj.get("fields");
JSONObject statusObj = (JSONObject) fields.get("status");
JSONObject assignee = (JSONObject) fields.get("assignee");
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
String id = obj.getString("id");
String title = fields.getString("summary");
String description = fields.getString("description");
String status = statusCategory.getString("key");
Long createTime = fields.getLong("created");
String lastmodify = "";
if (assignee != null) {
lastmodify = assignee.getString("displayName");
}
issues.setId(id);
issues.setTitle(title);
issues.setCreateTime(createTime);
issues.setLastmodify(lastmodify);
issues.setDescription(description);
issues.setStatus(status);
issues.setPlatform(IssuesManagePlatform.Jira.toString());

View File

@ -269,9 +269,17 @@ public class TestPlanService {
List<Issues> issue = issuesService.getIssues(testCase.getCaseId());
if (issue.size() > 0) {
for (Issues i : issue) {
i.setModel(testCase.getModel());
i.setModel(testCase.getNodePath());
String des = i.getDescription().replaceAll("<p>", "").replaceAll("</p>", "");
i.setDescription(des);
if (i.getLastmodify() == null || i.getLastmodify() == "") {
if (i.getReporter() != null || i.getReporter() != "") {
i.setLastmodify(i.getReporter());
}
}
}
issues.addAll(issue);
Collections.sort(issues, Comparator.comparing(Issues::getCreateTime, (t1, t2) -> t2.compareTo(t1)));
}
components.forEach(component -> {

View File

@ -20,7 +20,7 @@ create table if not exists issues
(
id varchar(50) not null
primary key,
title varchar(50) null,
title varchar(64) null,
description text null,
status varchar(50) null,
create_time bigint(13) null,

View File

@ -9,10 +9,10 @@
<span>{{ report.projectName }} / </span>
<router-link :to="path">{{ report.testName }}</router-link>
<span class="time">{{ report.createTime | timestampFormatDate }}</span>
<el-button plain type="primary" size="mini" @click="handleExport(report.name)"
<!--<el-button plain type="primary" size="mini" @click="handleExport(report.name)"
style="margin-left: 1200px">
{{$t('test_track.plan_view.export_report')}}
</el-button>
</el-button>-->
</el-col>
</el-row>
</header>
@ -114,9 +114,7 @@
this.fails = [];
this.totalTime = 0
this.content.scenarios.forEach((scenario) => {
console.log(scenario.responseTime)
this.totalTime = this.totalTime + Number(scenario.responseTime)
console.log(this.totalTime)
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);

View File

@ -0,0 +1,43 @@
import MsProject from "@/business/components/project/MsProject";
export default {
path: "/api",
name: "api",
redirect: "/api/home",
components: {
content: () => import(/* webpackChunkName: "api" */ '@/business/components/api/ApiTest')
},
children: [
{
path: 'home',
name: 'fucHome',
component: () => import(/* webpackChunkName: "api" */ '@/business/components/api/home/ApiTestHome'),
},
{
path: "test/:type",
name: "ApiTestConfig",
component: () => import(/* webpackChunkName: "api" */ '@/business/components/api/test/ApiTestConfig'),
props: (route) => ({id: route.query.id})
},
{
path: "test/list/:projectId",
name: "ApiTestList",
component: () => import(/* webpackChunkName: "api" */ '@/business/components/api/test/ApiTestList'),
},
{
path: "project/:type",
name: "fucProject",
component: MsProject,
},
{
path: "report/list/:testId",
name: "ApiReportList",
component: () => import(/* webpackChunkName: "api" */ '@/business/components/api/report/ApiReportList'),
},
{
path: "report/view/:reportId",
name: "ApiReportView",
component: () => import(/* webpackChunkName: "api" */ '@/business/components/api/report/ApiReportView'),
}
]
}

View File

@ -21,130 +21,130 @@
</template>
<script>
import MsDialogFooter from '../../common/components/MsDialogFooter'
import {Test} from "./model/ScenarioModel";
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog";
import {removeGoBackListener} from "../../../../common/js/utils";
import MsDialogFooter from '../../common/components/MsDialogFooter'
import {Test} from "./model/ScenarioModel";
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog";
export default {
name: "OneClickOperation",
components: {
MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig, MsDialogFooter
export default {
name: "OneClickOperation",
components: {
MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig, MsDialogFooter
},
data() {
return {
oneClickOperationVisible: false,
test: null,
tests: [],
ruleForm: {},
rule: {
testName: [
{required: true, message: this.$t('api_test.input_name'), trigger: 'blur'},
],
}
};
},
props: {
selectIds: {
type: Set
},
data() {
selectNames: {
type: Set
},
selectProjectNames: {
type: Set
}
},
methods: {
openOneClickOperation() {
this.oneClickOperationVisible = true;
},
checkedSaveAndRunTest() {
if (this.selectNames.has(this.ruleForm.testName)) {
this.$warning(this.$t('load_test.already_exists'));
this.oneClickOperationVisible = false;
} else {
if (this.selectProjectNames.size > 1) {
this.$warning(this.$t('load_test.same_project_test'));
this.oneClickOperationVisible = false;
} else {
for (let x of this.selectIds) {
this.getTest(x)
}
}
}
},
getTest(id) {
this.result = this.$get("/api/get/" + id, response => {
if (response.data) {
let item = response.data;
this.tests.push(item);
let test = new Test({
projectId: item.projectId,
name: this.ruleForm.testName,
scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: {},
});
this.test = this.test || test;
if (this.tests.length > 1) {
this.test.scenarioDefinition = this.test.scenarioDefinition.concat(test.scenarioDefinition);
}
if (this.tests.length === this.selectIds.size) {
this.tests = [];
this.saveRunTest();
this.oneClickOperationVisible = false;
}
}
});
},
saveRunTest() {
this.save(() => {
this.$success(this.$t('commons.save_success'));
this.runTest();
})
},
save(callback) {
let url = "/api/create";
this.result = this.$request(this.getOptions(url), () => {
this.create = false;
if (callback) callback();
});
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('api_test.running'));
this.$router.push({
path: '/api/report/view/' + response.data
})
});
},
getOptions(url) {
let formData = new FormData();
let requestJson = JSON.stringify(this.test);
formData.append('request', new Blob([requestJson], {
type: "application/json"
}));
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
formData.append("file", new File([blob], jmx.name));
return {
oneClickOperationVisible: false,
test: null,
tests: [],
ruleForm: {},
rule: {
testName: [
{required: true, message: this.$t('api_test.input_name'), trigger: 'blur'},
],
method: 'POST',
url: url,
data: formData,
headers: {
'Content-Type': undefined
}
};
},
props: {
selectIds: {
type: Set
},
selectNames: {
type: Set
},
selectProjectNames: {
type: Set
}
handleClose() {
this.ruleForm = {}
},
methods: {
openOneClickOperation() {
this.oneClickOperationVisible = true;
},
checkedSaveAndRunTest() {
if (this.selectNames.has(this.testName)) {
this.$warning(this.$t('load_test.already_exists'));
} else {
if (this.selectProjectNames.size > 1) {
this.$warning(this.$t('load_test.same_project_test'));
} else {
for (let x of this.selectIds) {
this.getTest(x)
}
}
}
},
getTest(id) {
this.result = this.$get("/api/get/" + id, response => {
if (response.data) {
let item = response.data;
this.tests.push(item);
let test = new Test({
projectId: item.projectId,
name: this.ruleForm.testName,
scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: {},
});
this.test = this.test || test;
if (this.tests.length > 1) {
this.test.scenarioDefinition = this.test.scenarioDefinition.concat(test.scenarioDefinition);
}
if (this.tests.length === this.selectIds.size) {
this.tests = [];
this.saveRunTest();
this.oneClickOperationVisible = false;
}
}
});
},
saveRunTest() {
this.save(() => {
this.$success(this.$t('commons.save_success'));
this.runTest();
})
},
save(callback) {
let url = "/api/create";
this.result = this.$request(this.getOptions(url), () => {
this.create = false;
if (callback) callback();
});
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('api_test.running'));
this.$router.push({
path: '/api/report/view/' + response.data
})
});
},
getOptions(url) {
let formData = new FormData();
let requestJson = JSON.stringify(this.test);
formData.append('request', new Blob([requestJson], {
type: "application/json"
}));
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
formData.append("file", new File([blob], jmx.name));
return {
method: 'POST',
url: url,
data: formData,
headers: {
'Content-Type': undefined
}
};
},
handleClose() {
this.ruleForm = {}
},
}
}
}
</script>
<style scoped>

View File

@ -52,7 +52,7 @@
<script>
import {KeyValue, Scenario} from "../model/ScenarioModel";
import {MOCKJS_FUNC} from "@/common/js/constants";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
import MsApiVariableAdvance from "@/business/components/api/test/components/ApiVariableAdvance";
import MsApiBodyFileUpload from "./body/ApiBodyFileUpload";
@ -127,7 +127,7 @@ export default {
};
},
funcSearch(queryString, cb) {
let funcs = MOCKJS_FUNC;
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
// callback
cb(results);

View File

@ -17,7 +17,7 @@
<slot name="button"></slot>
</span>
<span>
<ms-table-search-bar :condition.sync="condition" @change="search" class="search-bar"/>
<ms-table-search-bar :condition.sync="condition" @change="search" class="search-bar" :tip="tip"/>
<ms-table-adv-search-bar :condition.sync="condition" @search="search" v-if="isCombine"/>
</span>
</el-row>
@ -65,6 +65,12 @@
isTesterPermission: {
type: Boolean,
default: false
},
tip: {
String,
default() {
return this.$t('commons.search_by_name');
}
}
},
methods: {

View File

@ -4,7 +4,7 @@
class="search"
type="text"
size="small"
:placeholder="$t('commons.search_by_name')"
:placeholder="tip"
prefix-icon="el-icon-search"
@change="search"
maxlength="60"
@ -18,6 +18,12 @@
props: {
condition: {
type: Object
},
tip: {
String,
default() {
return this.$t('commons.search_by_name');
}
}
},
methods: {

View File

@ -1,28 +1,29 @@
<template>
<el-menu-item :index="this.index" @click="changeRoute">
<font-awesome-icon :icon="['fa', 'list-ul']"/>
<span>{{$t('commons.show_all')}}</span>
<span>{{ $t('commons.show_all') }}</span>
</el-menu-item>
</template>
<script>
export default {
name: "MsShowAll",
props: {
index: String
},
methods: {
changeRoute() {
//
export default {
name: "MsShowAll",
props: {
index: String
},
methods: {
changeRoute() {
//
if (this.$route.path === this.index) {
this.$router.replace({path: this.index, query: {type: 'all'}});
window.location.reload();
}
}
}
}
</script>
<style scoped>
svg + span {
padding-left: 5px;
}
svg + span {
padding-left: 5px;
}
</style>

View File

@ -1,27 +1,11 @@
import Vue from "vue";
import VueRouter from 'vue-router'
import RouterSidebar from "./RouterSidebar";
import EditPerformanceTestPlan from "../../performance/test/EditPerformanceTestPlan";
import PerformanceTestPlan from "../../performance/test/PerformanceTestPlan";
import MsProject from "../../project/MsProject";
import PerformanceChart from "../../performance/report/components/PerformanceChart";
import PerformanceTestReport from "../../performance/report/PerformanceTestReport";
import ApiTest from "../../api/ApiTest";
import PerformanceTest from "../../performance/PerformanceTest";
import ApiTestConfig from "../../api/test/ApiTestConfig";
import PerformanceTestHome from "../../performance/home/PerformanceTestHome";
import ApiTestList from "../../api/test/ApiTestList";
import ApiTestHome from "../../api/home/ApiTestHome";
import PerformanceReportView from "../../performance/report/PerformanceReportView";
import ApiReportView from "../../api/report/ApiReportView";
import TrackHome from "../../track/home/TrackHome";
import TestPlan from "../../track/plan/TestPlan";
import TestPlanView from "../../track/plan/view/TestPlanView";
import TestCase from "../../track/case/TestCase";
import TestTrack from "../../track/TestTrack";
import ApiReportList from "../../api/report/ApiReportList";
import axios from "axios";
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";
Vue.use(VueRouter);
@ -35,158 +19,12 @@ const router = new VueRouter({
}
},
Setting,
{
path: "/api",
name: "api",
redirect: "/api/home",
components: {
content: ApiTest
},
children: [
{
path: 'home',
name: 'fucHome',
component: ApiTestHome,
},
{
path: "test/:type",
name: "ApiTestConfig",
component: ApiTestConfig,
props: (route) => ({id: route.query.id})
},
{
path: "test/list/:projectId",
name: "ApiTestList",
component: ApiTestList
},
{
path: "project/:type",
name: "fucProject",
component: MsProject
},
{
path: "report/list/:testId",
name: "ApiReportList",
component: ApiReportList
},
{
path: "report/view/:reportId",
name: "ApiReportView",
component: ApiReportView
}
]
},
{
path: "/performance",
name: "performance",
redirect: "/performance/home",
components: {
content: PerformanceTest
},
children: [
{
path: 'home',
name: 'perHome',
component: PerformanceTestHome,
},
{
path: 'test/create',
name: "createPerTest",
component: EditPerformanceTestPlan,
},
{
path: "test/edit/:testId",
name: "editPerTest",
component: EditPerformanceTestPlan,
props: {
content: (route) => {
return {
...route.params
}
}
}
},
{
path: "test/:projectId",
name: "perPlan",
component: PerformanceTestPlan
},
{
path: "project/:type",
name: "perProject",
component: MsProject
},
{
path: "report/:type",
name: "perReport",
component: PerformanceTestReport
},
{
path: "chart",
name: "perChart",
component: PerformanceChart
},
{
path: "report/view/:reportId",
name: "perReportView",
component: PerformanceReportView
}
]
},
{
path: "/track",
name: "track",
redirect: "/track/home",
components: {
content: TestTrack
},
children: [
{
path: 'home',
name: 'trackHome',
component: TrackHome,
},
{
path: 'case/create',
name: 'testCaseCreate',
component: TestCase,
},
{
path: 'case/:projectId',
name: 'testCase',
component: TestCase,
},
{
path: 'case/edit/:caseId',
name: 'testCaseEdit',
component: TestCase,
},
{
path: "plan/:type",
name: "testPlan",
component: TestPlan
},
{
path: "plan/view/:planId",
name: "planView",
component: TestPlanView
},
{
path: "plan/view/edit/:caseId",
name: "planViewEdit",
component: TestPlanView
},
{
path: "project/:type",
name: "trackProject",
component: MsProject
}
]
}
API,
Performance,
Track,
]
});
router.beforeEach((to, from, next) => {
//解决localStorage清空cookie没失效导致的卡死问题
if (!localStorage.getItem('Admin-Token')) {

View File

@ -22,9 +22,9 @@
@click="rerun(testId)">
{{ $t('report.test_execute_again') }}
</el-button>
<el-button :disabled="isReadOnly" type="info" plain size="mini" @click="exports(reportName)">
<!-- <el-button :disabled="isReadOnly" type="info" plain size="mini" @click="exports(reportName)">
{{$t('report.export')}}
</el-button>
</el-button>-->
<!--
<el-button :disabled="isReadOnly" type="warning" plain size="mini">
{{$t('report.compare')}}

View File

@ -0,0 +1,67 @@
import MsProject from "@/business/components/project/MsProject";
const PerformanceTest = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/PerformanceTest')
const PerformanceTestHome = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/home/PerformanceTestHome')
const EditPerformanceTestPlan = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/test/EditPerformanceTestPlan')
const PerformanceTestPlan = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/test/PerformanceTestPlan')
const PerformanceTestReport = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/report/PerformanceTestReport')
const PerformanceChart = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/report/components/PerformanceChart')
const PerformanceReportView = () => import(/* webpackChunkName: "performance" */ '@/business/components/performance/report/PerformanceReportView')
export default {
path: "/performance",
name: "performance",
redirect: "/performance/home",
components: {
content: PerformanceTest
},
children: [
{
path: 'home',
name: 'perHome',
component: PerformanceTestHome,
},
{
path: 'test/create',
name: "createPerTest",
component: EditPerformanceTestPlan,
},
{
path: "test/edit/:testId",
name: "editPerTest",
component: EditPerformanceTestPlan,
props: {
content: (route) => {
return {
...route.params
}
}
}
},
{
path: "test/:projectId",
name: "perPlan",
component: PerformanceTestPlan
},
{
path: "project/:type",
name: "perProject",
component: MsProject
},
{
path: "report/:type",
name: "perReport",
component: PerformanceTestReport
},
{
path: "chart",
name: "perChart",
component: PerformanceChart
},
{
path: "report/view/:reportId",
name: "perReportView",
component: PerformanceReportView
}
]
}

View File

@ -22,11 +22,12 @@
</el-input>
</el-col>
<el-col :span="12" :offset="2">
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{$t('commons.save')}}</el-button>
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{ $t('commons.save') }}</el-button>
<el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">
{{$t('load_test.save_and_run')}}
{{ $t('load_test.save_and_run') }}
</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{ $t('commons.cancel') }}
</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
:check-open="checkScheduleEdit" :custom-validate="durationValidate"/>
@ -52,272 +53,272 @@
</template>
<script>
import PerformanceBasicConfig from "./components/PerformanceBasicConfig";
import PerformancePressureConfig from "./components/PerformancePressureConfig";
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import PerformanceBasicConfig from "./components/PerformanceBasicConfig";
import PerformancePressureConfig from "./components/PerformancePressureConfig";
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
export default {
name: "EditPerformanceTestPlan",
components: {
MsScheduleConfig,
PerformancePressureConfig,
PerformanceBasicConfig,
PerformanceAdvancedConfig,
MsContainer,
MsMainContainer
},
data() {
return {
result: {},
testPlan: {schedule: {}},
listProjectPath: "/project/listAll",
savePath: "/performance/save",
editPath: "/performance/edit",
runPath: "/performance/run",
projects: [],
active: '0',
testId: '',
isReadOnly: false,
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'
}]
}
},
watch: {
'$route'(to) {
//
if (to.name === 'createPerTest') {
window.location.reload();
return;
}
if (to.name !== 'editPerTest') {
return;
}
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
this.getTest(to.params.testId);
export default {
name: "EditPerformanceTestPlan",
components: {
MsScheduleConfig,
PerformancePressureConfig,
PerformanceBasicConfig,
PerformanceAdvancedConfig,
MsContainer,
MsMainContainer
},
data() {
return {
result: {},
testPlan: {schedule: {}},
listProjectPath: "/project/listAll",
savePath: "/performance/save",
editPath: "/performance/edit",
runPath: "/performance/run",
projects: [],
active: '0',
testId: '',
isReadOnly: false,
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'
}]
}
},
watch: {
'$route'(to) {
//
if (to.name === 'createPerTest') {
window.location.reload();
return;
}
if (to.name !== 'editPerTest') {
return;
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
this.getTest(this.$route.params.testId);
this.listProjects();
this.getTest(to.params.testId);
}
},
created() {
this.isReadOnly = false;
if (!checkoutTestManagerOrTestUser()) {
this.isReadOnly = true;
}
this.getTest(this.$route.params.testId);
this.listProjects();
},
mounted() {
this.importAPITest();
},
methods: {
importAPITest() {
let apiTest = this.$store.state.api.test;
if (apiTest && apiTest.name) {
this.$set(this.testPlan, "projectId", apiTest.projectId);
this.$set(this.testPlan, "name", apiTest.name);
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], apiTest.jmx.name);
this.$refs.basicConfig.beforeUpload(file);
this.$refs.basicConfig.handleUpload({file: file});
this.active = '1';
this.$store.commit("clearTest");
}
},
mounted() {
this.importAPITest();
},
methods: {
importAPITest() {
let apiTest = this.$store.state.api.test;
if (apiTest && apiTest.name) {
this.$set(this.testPlan, "projectId", apiTest.projectId);
this.$set(this.testPlan, "name", apiTest.name);
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], apiTest.jmx.name);
this.$refs.basicConfig.beforeUpload(file);
this.$refs.basicConfig.handleUpload({file: file});
this.active = '1';
this.$store.commit("clearTest");
}
},
getTest(testId) {
if (testId) {
this.testId = testId;
this.result = this.$get('/performance/get/' + testId, response => {
if (response.data) {
this.testPlan = response.data;
if (!this.testPlan.schedule) {
this.testPlan.schedule = {};
}
getTest(testId) {
if (testId) {
this.testId = testId;
this.result = this.$get('/performance/get/' + testId, response => {
if (response.data) {
this.testPlan = response.data;
if (!this.testPlan.schedule) {
this.testPlan.schedule = {};
}
});
}
},
listProjects() {
this.result = this.$get(this.listProjectPath, response => {
this.projects = response.data;
})
},
save() {
if (!this.validTestPlan()) {
return;
}
let options = this.getSaveOption();
this.result = this.$request(options, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.advancedConfig.cancelAllEdit();
this.$router.push({path: '/performance/test/all'})
});
},
saveAndRun() {
if (!this.validTestPlan()) {
return;
}
let options = this.getSaveOption();
this.result = this.$request(options, (response) => {
this.testPlan.id = response.data;
this.$success(this.$t('commons.save_success'));
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
let reportId = response.data;
this.$router.push({path: '/performance/report/view/' + reportId})
})
});
},
getSaveOption() {
let formData = new FormData();
let url = this.testPlan.id ? this.editPath : this.savePath;
if (this.$refs.basicConfig.uploadList.length > 0) {
this.$refs.basicConfig.uploadList.forEach(f => {
formData.append("file", f);
});
}
//
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
//
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
//
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
// filejson
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
return key === "file" ? undefined : value
});
formData.append('request', new Blob([requestJson], {
type: "application/json"
}));
return {
method: 'POST',
url: url,
data: formData,
headers: {
'Content-Type': undefined
}
};
},
cancel() {
});
}
},
listProjects() {
this.result = this.$get(this.listProjectPath, response => {
this.projects = response.data;
})
},
save() {
if (!this.validTestPlan()) {
return;
}
let options = this.getSaveOption();
this.result = this.$request(options, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.advancedConfig.cancelAllEdit();
this.$router.push({path: '/performance/test/all'})
},
validTestPlan() {
if (!this.testPlan.name) {
this.$error(this.$t('load_test.test_name_is_null'));
return false;
}
});
},
saveAndRun() {
if (!this.validTestPlan()) {
return;
}
if (!this.testPlan.projectId) {
this.$error(this.$t('load_test.project_is_null'));
return false;
}
let options = this.getSaveOption();
if (!this.$refs.basicConfig.validConfig()) {
return false;
}
this.result = this.$request(options, (response) => {
this.testPlan.id = response.data;
this.$success(this.$t('commons.save_success'));
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
let reportId = response.data;
this.$router.push({path: '/performance/report/view/' + reportId})
})
});
},
getSaveOption() {
let formData = new FormData();
let url = this.testPlan.id ? this.editPath : this.savePath;
if (!this.$refs.pressureConfig.validConfig()) {
return false;
}
if (!this.$refs.advancedConfig.validConfig()) {
return false;
}
/// todo:
return true;
},
changeTabActive(activeName) {
this.$nextTick(() => {
this.active = activeName;
if (this.$refs.basicConfig.uploadList.length > 0) {
this.$refs.basicConfig.uploadList.forEach(f => {
formData.append("file", f);
});
},
saveCronExpression(cronExpression) {
this.testPlan.schedule.enable = true;
this.testPlan.schedule.value = cronExpression;
this.saveSchedule();
},
saveSchedule() {
this.checkScheduleEdit();
let param = {};
param = this.testPlan.schedule;
param.resourceId = this.testPlan.id;
let url = '/performance/schedule/create';
if (param.id) {
url = '/performance/schedule/update';
}
this.$post(url, param, response => {
this.$success(this.$t('commons.save_success'));
this.getTest(this.testPlan.id);
});
},
checkScheduleEdit() {
if (!this.testPlan.id) {
this.$message(this.$t('api_test.environment.please_save_test'));
return false;
}
return true;
},
durationValidate(intervalTime) {
let duration = this.$refs.pressureConfig.duration * 60 * 1000;
if (intervalTime < duration) {
return {
pass: false,
info: this.$t('load_test.schedule_tip')
}
}
//
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
//
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
//
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
// filejson
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
return key === "file" ? undefined : value
});
formData.append('request', new Blob([requestJson], {
type: "application/json"
}));
return {
method: 'POST',
url: url,
data: formData,
headers: {
'Content-Type': undefined
}
};
},
cancel() {
this.$router.push({path: '/performance/test/all'})
},
validTestPlan() {
if (!this.testPlan.name) {
this.$error(this.$t('load_test.test_name_is_null'));
return false;
}
if (!this.testPlan.projectId) {
this.$error(this.$t('load_test.project_is_null'));
return false;
}
if (!this.$refs.basicConfig.validConfig()) {
return false;
}
if (!this.$refs.pressureConfig.validConfig()) {
return false;
}
if (!this.$refs.advancedConfig.validConfig()) {
return false;
}
/// todo:
return true;
},
changeTabActive(activeName) {
this.$nextTick(() => {
this.active = activeName;
});
},
saveCronExpression(cronExpression) {
this.testPlan.schedule.enable = true;
this.testPlan.schedule.value = cronExpression;
this.saveSchedule();
},
saveSchedule() {
this.checkScheduleEdit();
let param = {};
param = this.testPlan.schedule;
param.resourceId = this.testPlan.id;
let url = '/performance/schedule/create';
if (param.id) {
url = '/performance/schedule/update';
}
this.$post(url, param, response => {
this.$success(this.$t('commons.save_success'));
this.getTest(this.testPlan.id);
});
},
checkScheduleEdit() {
if (!this.testPlan.id) {
this.$message(this.$t('api_test.environment.please_save_test'));
return false;
}
return true;
},
durationValidate(intervalTime) {
let duration = this.$refs.pressureConfig.duration * 60 * 1000;
if (intervalTime < duration) {
return {
pass: true
pass: false,
info: this.$t('load_test.schedule_tip')
}
}
return {
pass: true
}
}
}
}
</script>
<style scoped>
.testplan-config {
margin-top: 15px;
text-align: center;
}
.testplan-config {
margin-top: 15px;
text-align: center;
}
.el-select {
min-width: 130px;
}
.el-select {
min-width: 130px;
}
.edit-testplan-container .input-with-select .el-input-group__prepend {
background-color: #fff;
}
.edit-testplan-container .input-with-select .el-input-group__prepend {
background-color: #fff;
}
.advanced-config {
height: calc(100vh - 280px);
overflow: auto;
}
.advanced-config {
height: calc(100vh - 280px);
overflow: auto;
}
</style>

View File

@ -17,7 +17,7 @@
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
<div>{{ row.description }}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
@ -30,13 +30,13 @@
</template>
<script>
export default {
name: "MsPerformanceTestStatus",
export default {
name: "MsPerformanceTestStatus",
props: {
row: Object
}
props: {
row: Object
}
}
</script>
<style scoped>

View File

@ -2,8 +2,9 @@
<div>
<el-row>
<el-col :span="8">
<h3>{{$t('load_test.params')}}</h3>
<el-button :disabled="readOnly" icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">{{$t('commons.add')}}
<h3>{{ $t('load_test.params') }}</h3>
<el-button :disabled="readOnly" icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">
{{ $t('commons.add') }}
</el-button>
</el-col>
</el-row>
@ -26,7 +27,7 @@
:placeholder="$t('load_test.param_name')"
clearable>
</el-input>
<span>{{row.name}}</span>
<span>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column
@ -60,12 +61,13 @@
v-model="row.value"
:placeholder="$t('load_test.param_value')"
clearable></el-input>
<span>{{row.value}}</span>
<span>{{ row.value }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('load_test.operating')">
<template v-slot:default="{row, $index}">
<ms-table-operator-button :disabled="readOnly" :tip="$t('commons.delete')" icon="el-icon-delete" type="danger"
<ms-table-operator-button :disabled="readOnly" :tip="$t('commons.delete')" icon="el-icon-delete"
type="danger"
@exec="del(row, 'params', $index)"/>
</template>
</el-table-column>
@ -77,10 +79,11 @@
<el-col :span="8">
<el-form :inline="true">
<el-form-item>
<div>{{$t('load_test.connect_timeout')}}</div>
<div>{{ $t('load_test.connect_timeout') }}</div>
</el-form-item>
<el-form-item>
<el-input-number :disabled="readOnly" size="mini" v-model="timeout" :min="10" :max="100000"></el-input-number>
<el-input-number :disabled="readOnly" size="mini" v-model="timeout" :min="10"
:max="100000"></el-input-number>
</el-form-item>
<el-form-item>
ms
@ -92,10 +95,11 @@
<el-col :span="8">
<el-form :inline="true">
<el-form-item>
<div>{{$t('load_test.custom_http_code')}}</div>
<div>{{ $t('load_test.custom_http_code') }}</div>
</el-form-item>
<el-form-item>
<el-input :disabled="readOnly" size="mini" v-model="statusCodeStr" :placeholder="$t('load_test.separated_by_commas')"
<el-input :disabled="readOnly" size="mini" v-model="statusCodeStr"
:placeholder="$t('load_test.separated_by_commas')"
@input="checkStatusCode"></el-input>
</el-form-item>
</el-form>
@ -105,169 +109,169 @@
</template>
<script>
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
export default {
name: "PerformanceAdvancedConfig",
components: {MsTableOperatorButton},
data() {
return {
timeout: 2000,
statusCode: [],
domains: [],
params: [],
statusCodeStr: '',
}
export default {
name: "PerformanceAdvancedConfig",
components: {MsTableOperatorButton},
data() {
return {
timeout: 2000,
statusCode: [],
domains: [],
params: [],
statusCodeStr: '',
}
},
props: {
readOnly: {
type: Boolean,
default: false
},
props: {
readOnly: {
type: Boolean,
default: false
},
testId: String,
},
mounted() {
testId: String,
},
mounted() {
if (this.testId) {
this.getAdvancedConfig();
}
},
watch: {
testId() {
if (this.testId) {
this.getAdvancedConfig();
}
},
watch: {
testId () {
if (this.testId) {
this.getAdvancedConfig();
}
},
methods: {
getAdvancedConfig() {
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
this.timeout = data.timeout || 10;
this.statusCode = data.statusCode || [];
this.statusCodeStr = this.statusCode.join(',');
this.domains = data.domains || [];
this.params = data.params || [];
/*this.domains.forEach(d => d.edit = false);
this.params.forEach(d => d.edit = false);*/
}
});
},
add(dataName) {
if (dataName === 'domains') {
this[dataName].push({
domain: 'fit2cloud.com',
enable: true,
ip: '127.0.0.1',
edit: true,
});
}
if (dataName === 'params') {
this[dataName].push({
name: 'param1',
enable: true,
value: '0',
edit: true,
});
}
},
methods: {
getAdvancedConfig() {
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
this.timeout = data.timeout || 10;
this.statusCode = data.statusCode || [];
this.statusCodeStr = this.statusCode.join(',');
this.domains = data.domains || [];
this.params = data.params || [];
/*this.domains.forEach(d => d.edit = false);
this.params.forEach(d => d.edit = false);*/
}
});
},
add(dataName) {
if (dataName === 'domains') {
this[dataName].push({
domain: 'fit2cloud.com',
enable: true,
ip: '127.0.0.1',
edit: true,
});
edit(row) {
row.edit = !row.edit
},
del(row, dataName, index) {
this[dataName].splice(index, 1);
},
confirmEdit(row) {
row.edit = false;
row.enable = true;
},
groupBy(data, key) {
return data.reduce((p, c) => {
let name = c[key];
if (!p.hasOwnProperty(name)) {
p[name] = 0;
}
if (dataName === 'params') {
this[dataName].push({
name: 'param1',
enable: true,
value: '0',
edit: true,
});
}
},
edit(row) {
row.edit = !row.edit
},
del(row, dataName, index) {
this[dataName].splice(index, 1);
},
confirmEdit(row) {
row.edit = false;
row.enable = true;
},
groupBy(data, key) {
return data.reduce((p, c) => {
let name = c[key];
if (!p.hasOwnProperty(name)) {
p[name] = 0;
}
p[name]++;
return p;
}, {});
},
validConfig() {
let counts = this.groupBy(this.domains, 'domain');
for (let c in counts) {
if (counts[c] > 1) {
this.$error(this.$t('load_test.domain_is_duplicate'));
return false;
}
}
counts = this.groupBy(this.params, 'name');
for (let c in counts) {
if (counts[c] > 1) {
this.$error(this.$t('load_test.param_is_duplicate'));
return false;
}
}
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
this.$error(this.$t('load_test.domain_ip_is_empty'));
p[name]++;
return p;
}, {});
},
validConfig() {
let counts = this.groupBy(this.domains, 'domain');
for (let c in counts) {
if (counts[c] > 1) {
this.$error(this.$t('load_test.domain_is_duplicate'));
return false;
}
if (this.params.filter(d => !d.name || !d.value).length > 0) {
this.$error(this.$t('load_test.param_name_value_is_empty'));
}
counts = this.groupBy(this.params, 'name');
for (let c in counts) {
if (counts[c] > 1) {
this.$error(this.$t('load_test.param_is_duplicate'));
return false;
}
return true;
},
checkStatusCode() {
let license_num = this.statusCodeStr;
license_num = license_num.replace(/[^\d,]/g, ''); // .
this.statusCodeStr = license_num;
},
cancelAllEdit() {
this.domains.forEach(d => d.edit = false);
this.params.forEach(d => d.edit = false);
},
configurations() {
let statusCode = [];
if (this.statusCodeStr) {
statusCode = this.statusCodeStr.split(',');
}
return {
timeout: this.timeout,
statusCode: statusCode,
params: this.params,
domains: this.domains,
};
},
}
}
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
this.$error(this.$t('load_test.domain_ip_is_empty'));
return false;
}
if (this.params.filter(d => !d.name || !d.value).length > 0) {
this.$error(this.$t('load_test.param_name_value_is_empty'));
return false;
}
return true;
},
checkStatusCode() {
let license_num = this.statusCodeStr;
license_num = license_num.replace(/[^\d,]/g, ''); // .
this.statusCodeStr = license_num;
},
cancelAllEdit() {
this.domains.forEach(d => d.edit = false);
this.params.forEach(d => d.edit = false);
},
configurations() {
let statusCode = [];
if (this.statusCodeStr) {
statusCode = this.statusCodeStr.split(',');
}
return {
timeout: this.timeout,
statusCode: statusCode,
params: this.params,
domains: this.domains,
};
},
}
}
</script>
<style scoped>
.el-row {
margin-bottom: 10px;
}
.el-row {
margin-bottom: 10px;
}
.edit-input {
padding-right: 0px;
}
.edit-input {
padding-right: 0px;
}
.tb-edit .el-textarea {
display: none;
}
.tb-edit .el-textarea {
display: none;
}
.tb-edit .current-row .el-textarea {
display: block;
}
.tb-edit .current-row .el-textarea {
display: block;
}
.tb-edit .current-row .el-textarea + span {
display: none;
}
.tb-edit .current-row .el-textarea + span {
display: none;
}
.el-col {
text-align: left;
}
.el-col {
text-align: left;
}
.el-col .el-table {
align: center;
}
.el-col .el-table {
align: center;
}
</style>

View File

@ -15,7 +15,7 @@
<i class="el-icon-upload"/>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<template v-slot:tip>
<div class="el-upload__tip">{{$t('load_test.upload_type')}}</div>
<div class="el-upload__tip">{{ $t('load_test.upload_type') }}</div>
</template>
</el-upload>
@ -42,9 +42,11 @@
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="scope">
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || isReadOnly" type="primary" icon="el-icon-download"
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || isReadOnly" type="primary"
icon="el-icon-download"
size="mini" circle/>
<el-button :disabled="isReadOnly" @click="handleDelete(scope.row, scope.$index)" type="danger" icon="el-icon-delete" size="mini"
<el-button :disabled="isReadOnly" @click="handleDelete(scope.row, scope.$index)" type="danger"
icon="el-icon-delete" size="mini"
circle/>
</template>
</el-table-column>
@ -53,170 +55,170 @@
</template>
<script>
import {Message} from "element-ui";
import {Message} from "element-ui";
export default {
name: "PerformanceBasicConfig",
props: {
testPlan: {
type: Object
},
isReadOnly: {
type: Boolean,
default: false
}
export default {
name: "PerformanceBasicConfig",
props: {
testPlan: {
type: Object
},
data() {
return {
result: {},
getFileMetadataPath: "/performance/file/metadata",
jmxDownloadPath: '/performance/file/download',
jmxDeletePath: '/performance/file/delete',
fileList: [],
tableData: [],
uploadList: [],
};
},
created() {
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
result: {},
getFileMetadataPath: "/performance/file/metadata",
jmxDownloadPath: '/performance/file/download',
jmxDeletePath: '/performance/file/delete',
fileList: [],
tableData: [],
uploadList: [],
};
},
created() {
if (this.testPlan.id) {
this.getFileMetadata(this.testPlan)
}
},
watch: {
testPlan() {
if (this.testPlan.id) {
this.getFileMetadata(this.testPlan)
}
},
watch: {
testPlan() {
if (this.testPlan.id) {
this.getFileMetadata(this.testPlan)
}
},
methods: {
getFileMetadata(testPlan) {
this.fileList = [];
this.tableData = [];
this.uploadList = [];
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
let files = response.data;
if (!files) {
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
return;
}
// deep copy
this.fileList = JSON.parse(JSON.stringify(files));
this.tableData = JSON.parse(JSON.stringify(files));
this.tableData.map(f => {
f.size = f.size + ' Bytes';
});
})
},
beforeUpload(file) {
if (!this.fileValidator(file)) {
/// todo:
return false;
}
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return false;
}
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
this.tableData.push({
name: file.name,
size: file.size + ' Bytes', /// todo: ByteKBMB
type: type.toUpperCase(),
updateTime: file.lastModified,
});
return true;
},
handleUpload(uploadResources) {
this.uploadList.push(uploadResources.file);
},
handleDownload(file) {
let data = {
name: file.name,
id: file.id,
};
let config = {
url: this.jmxDownloadPath,
method: 'post',
data: data,
responseType: 'blob'
};
this.result = this.$request(config).then(response => {
const content = response.data;
const blob = new Blob([content]);
if ("download" in document.createElement("a")) {
// IE
// chrome/firefox
let aTag = document.createElement('a');
aTag.download = file.name;
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(aTag.href)
} else {
// IE10+
navigator.msSaveBlob(blob, this.filename)
}
}).catch(e => {
Message.error({message: e.message, showClose: true});
});
},
handleDelete(file, index) {
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this._handleDelete(file, index);
}
}
});
},
_handleDelete(file, index) {
this.fileList.splice(index, 1);
this.tableData.splice(index, 1);
//
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
if (i > -1) {
this.uploadList.splice(i, 1);
}
},
methods: {
getFileMetadata(testPlan) {
this.fileList = [];
this.tableData = [];
this.uploadList = [];
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
let files = response.data;
if (!files) {
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
return;
}
// deep copy
this.fileList = JSON.parse(JSON.stringify(files));
this.tableData = JSON.parse(JSON.stringify(files));
this.tableData.map(f => {
f.size = f.size + ' Bytes';
});
})
},
beforeUpload(file) {
if (!this.fileValidator(file)) {
/// todo:
return false;
}
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return false;
}
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
this.tableData.push({
name: file.name,
size: file.size + ' Bytes', /// todo: ByteKBMB
type: type.toUpperCase(),
updateTime: file.lastModified,
});
return true;
},
handleUpload(uploadResources) {
this.uploadList.push(uploadResources.file);
},
handleDownload(file) {
let data = {
name: file.name,
id: file.id,
};
let config = {
url: this.jmxDownloadPath,
method: 'post',
data: data,
responseType: 'blob'
};
this.result = this.$request(config).then(response => {
const content = response.data;
const blob = new Blob([content]);
if ("download" in document.createElement("a")) {
// IE
// chrome/firefox
let aTag = document.createElement('a');
aTag.download = file.name;
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(aTag.href)
} else {
// IE10+
navigator.msSaveBlob(blob, this.filename)
}
}).catch(e => {
Message.error({message: e.message, showClose: true});
});
},
handleDelete(file, index) {
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this._handleDelete(file, index);
}
}
});
},
_handleDelete(file, index) {
this.fileList.splice(index, 1);
this.tableData.splice(index, 1);
//
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
if (i > -1) {
this.uploadList.splice(i, 1);
}
},
handleExceed() {
this.$error(this.$t('load_test.file_size_limit'));
},
fileValidator(file) {
/// todo:
return file.size > 0;
},
updatedFileList() {
return this.fileList;//
},
validConfig() {
let newJmxNum = 0, oldJmxNum = 0;
if (this.uploadList.length > 0) {
newJmxNum = this.uploadList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
}
if (this.fileList.length > 0) {
oldJmxNum = this.fileList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
}
if (newJmxNum + oldJmxNum !== 1) {
this.$error(this.$t('load_test.jmx_is_null'));
return false;
}
return true;
}
handleExceed() {
this.$error(this.$t('load_test.file_size_limit'));
},
}
fileValidator(file) {
/// todo:
return file.size > 0;
},
updatedFileList() {
return this.fileList;//
},
validConfig() {
let newJmxNum = 0, oldJmxNum = 0;
if (this.uploadList.length > 0) {
newJmxNum = this.uploadList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
}
if (this.fileList.length > 0) {
oldJmxNum = this.fileList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
}
if (newJmxNum + oldJmxNum !== 1) {
this.$error(this.$t('load_test.jmx_is_null'));
return false;
}
return true;
}
},
}
</script>
<style scoped>
.basic-config {
width: 100%
}
.basic-config {
width: 100%
}
.last-modified {
margin-left: 5px;
}
.last-modified {
margin-left: 5px;
}
</style>

View File

@ -4,7 +4,7 @@
<el-col :span="10">
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{$t('load_test.thread_num')}}</div>
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
@ -18,7 +18,7 @@
</el-form>
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{$t('load_test.duration')}}</div>
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
@ -33,7 +33,7 @@
<el-form :inline="true">
<el-form-item>
<el-form-item>
<div class="config-form-label">{{$t('load_test.rps_limit')}}</div>
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
</el-form-item>
<el-form-item>
<el-switch v-model="rpsLimitEnable"/>
@ -52,7 +52,7 @@
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{$t('load_test.ramp_up_time_within')}}</div>
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
@ -65,7 +65,7 @@
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{$t('load_test.ramp_up_time_minutes')}}</div>
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
@ -78,12 +78,12 @@
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{$t('load_test.ramp_up_time_times')}}</div>
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
</el-form-item>
</el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{$t('load_test.select_resource_pool')}}</div>
<div>{{ $t('load_test.select_resource_pool') }}</div>
</el-form-item>
<el-form-item>
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
@ -98,7 +98,7 @@
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{$t('load_test.pressure_prediction_chart')}}</div>
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
</el-col>
</el-row>
@ -106,263 +106,263 @@
</template>
<script>
import echarts from "echarts";
import echarts from "echarts";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
export default {
name: "PerformancePressureConfig",
props: {
testPlan: {
type: Object
},
testId: {
type: String
},
isReadOnly: {
type: Boolean,
default: false
}
export default {
name: "PerformancePressureConfig",
props: {
testPlan: {
type: Object
},
data() {
return {
result: {},
threadNumber: 10,
duration: 10,
rampUpTime: 10,
step: 10,
rpsLimit: 10,
rpsLimitEnable: false,
orgOptions: {},
resourcePool: null,
resourcePools: [],
}
testId: {
type: String
},
mounted() {
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
result: {},
threadNumber: 10,
duration: 10,
rampUpTime: 10,
step: 10,
rpsLimit: 10,
rpsLimitEnable: false,
orgOptions: {},
resourcePool: null,
resourcePools: [],
}
},
mounted() {
if (this.testId) {
this.getLoadConfig();
} else {
this.calculateChart();
}
this.resourcePool = this.testPlan.testResourcePoolId;
this.getResourcePools();
},
watch: {
testPlan(n) {
this.resourcePool = n.testResourcePoolId;
},
testId() {
if (this.testId) {
this.getLoadConfig();
} else {
this.calculateChart();
}
this.resourcePool = this.testPlan.testResourcePoolId;
this.getResourcePools();
},
watch: {
testPlan(n) {
this.resourcePool = n.testResourcePoolId;
},
testId() {
if (this.testId) {
this.getLoadConfig();
} else {
this.calculateChart();
},
methods: {
getResourcePools() {
this.result = this.$get('/testresourcepool/list/quota/valid', response => {
this.resourcePools = response.data;
// null
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
this.resourcePool = null;
}
},
})
},
methods: {
getResourcePools() {
this.result = this.$get('/testresourcepool/list/all/valid', response => {
this.resourcePools = response.data;
// null
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
this.resourcePool = null;
getLoadConfig() {
if (this.testId) {
this.$get('/performance/get-load-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
}
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
}
})
},
getLoadConfig() {
if (this.testId) {
this.$get('/performance/get-load-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
}
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
}
});
}
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
}
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
}
this.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
}
this.orgOptions.series[0].data.push(threadPeriod);
} else {
this.orgOptions.series[0].data.push(threadPeriod);
}
}
},
validConfig() {
if (!this.resourcePool) {
this.$warning(this.$t('load_test.resource_pool_is_null'));
// Tabname
this.$emit('changeActive', '1');
return false;
}
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
}
return true;
},
convertProperty() {
/// todo4jmeter ConcurrencyThreadGroup plugin
return [
{key: TARGET_LEVEL, value: this.threadNumber},
{key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step},
{key: DURATION, value: this.duration},
{key: RPS_LIMIT, value: this.rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
];
});
}
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
}
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
}
this.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
}
this.orgOptions.series[0].data.push(threadPeriod);
} else {
this.orgOptions.series[0].data.push(threadPeriod);
}
}
},
validConfig() {
if (!this.resourcePool) {
this.$warning(this.$t('load_test.resource_pool_is_null'));
// Tabname
this.$emit('changeActive', '1');
return false;
}
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
}
return true;
},
convertProperty() {
/// todo4jmeter ConcurrencyThreadGroup plugin
return [
{key: TARGET_LEVEL, value: this.threadNumber},
{key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step},
{key: DURATION, value: this.duration},
{key: RPS_LIMIT, value: this.rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
];
}
}
}
</script>
<style scoped>
.pressure-config-container .el-input {
width: 130px;
.pressure-config-container .el-input {
width: 130px;
}
}
.pressure-config-container .config-form-label {
width: 130px;
}
.pressure-config-container .config-form-label {
width: 130px;
}
.pressure-config-container .input-bottom-border input {
border: 0;
border-bottom: 1px solid #DCDFE6;
}
.pressure-config-container .input-bottom-border input {
border: 0;
border-bottom: 1px solid #DCDFE6;
}
.chart-container {
width: 100%;
}
.chart-container {
width: 100%;
}
.el-col .el-form {
margin-top: 15px;
text-align: left;
}
.el-col .el-form {
margin-top: 15px;
text-align: left;
}
.el-col {
margin-top: 15px;
text-align: left;
}
.el-col {
margin-top: 15px;
text-align: left;
}
.title {
margin-left: 60px;
}
.title {
margin-left: 60px;
}
</style>

View File

@ -1,86 +1,72 @@
import Setting from "@/business/components/settings/Setting";
import User from "@/business/components/settings/system/User";
import Organization from "@/business/components/settings/system/Organization";
import OrganizationMember from "@/business/components/settings/organization/OrganizationMember";
import OrganizationWorkspace from "@/business/components/settings/organization/OrganizationWorkspace";
import ServiceIntegration from "@/business/components/settings/organization/ServiceIntegration";
import PersonSetting from "@/business/components/settings/personal/PersonSetting";
import ApiKeys from "@/business/components/settings/personal/ApiKeys";
import Member from "@/business/components/settings/workspace/WorkspaceMember";
import SystemWorkspace from "@/business/components/settings/system/SystemWorkspace";
import TestResourcePool from "@/business/components/settings/system/TestResourcePool";
import SystemParameterSetting from "@/business/components/settings/system/SystemParameterSetting";
import TestCaseReportTemplate from "@/business/components/settings/workspace/TestCaseReportTemplate";
const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/)
export default {
path: "/setting",
name: "Setting",
components: {
content: Setting
content: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/Setting')
},
children: [
{
path: 'user',
component: User,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/User'),
meta: {system: true, title: 'commons.user'}
},
{
path: 'organization',
component: Organization,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/Organization'),
meta: {system: true, title: 'commons.organization'}
},
{
path: 'systemworkspace',
component: SystemWorkspace,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/SystemWorkspace'),
meta: {system: true, title: 'commons.workspace'}
},
{
path: 'testresourcepool',
component: TestResourcePool,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/TestResourcePool'),
meta: {system: true, title: 'commons.test_resource_pool'}
},
{
path: 'systemparametersetting',
component: SystemParameterSetting,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/SystemParameterSetting'),
meta: {system: true, title: 'commons.system_parameter_setting'}
},
...requireContext.keys().map(key => requireContext(key).system),
{
path: 'organizationmember',
component: OrganizationMember,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/organization/OrganizationMember'),
meta: {organization: true, title: 'commons.member'}
},
{
path: 'organizationworkspace',
component: OrganizationWorkspace,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/organization/OrganizationWorkspace'),
meta: {organization: true, title: 'commons.workspace'}
},
{
path: 'serviceintegration',
component: ServiceIntegration,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/organization/ServiceIntegration'),
meta: {organization: true, title: 'organization.service_integration'}
},
{
path: 'member',
component: Member,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/workspace/WorkspaceMember'),
meta: {workspace: true, title: 'commons.member'}
},
{
path: 'testcase/report/template',
name: 'testCaseReportTemplate',
component: TestCaseReportTemplate,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/workspace/TestCaseReportTemplate'),
meta: {workspace: true, title: 'test_track.plan_view.report_template'}
},
{
path: 'personsetting',
component: PersonSetting,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/personal/PersonSetting'),
meta: {person: true, title: 'commons.personal_setting'}
},
{
path: 'apikeys',
component: ApiKeys,
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/personal/ApiKeys'),
meta: {person: true, title: 'commons.api_keys'}
},

View File

@ -4,7 +4,7 @@
<el-card class="card-content" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="initTableData"
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="initTableData" :tip="$t('commons.search_by_name_or_id')"
:create-tip="$t('test_track.case.create')" @create="testCaseCreate">
<template v-slot:title>
<node-breadcrumb class="table-title" :nodes="selectParentNodes" @refresh="refresh"/>

View File

@ -137,6 +137,9 @@
this.getCaseNames();
}
},
updated() {
this.toggleSelection(this.testCases);
},
methods: {
openTestCaseRelevanceDialog() {
this.initData();
@ -178,7 +181,12 @@
this.selectIds.add(item.id);
});
} else {
this.selectIds.clear();
// this.selectIds.clear();
this.testCases.forEach(item => {
if (this.selectIds.has(item.id)) {
this.selectIds.delete(item.id);
}
});
}
},
handleSelectionChange(selection, row) {
@ -215,6 +223,16 @@
_filter(filters, this.condition);
this.initData();
},
toggleSelection(rows) {
rows.forEach(row => {
this.selectIds.forEach(id => {
if (row.id === id) {
// true
this.$refs.table.toggleRowSelection(row, true)
}
})
})
},
}
}
</script>

View File

@ -194,7 +194,7 @@
type="text"
:placeholder="$t('test_track.issue.input_title')"
v-model="testCase.issues.title"
maxlength="100"
maxlength="60"
show-word-limit
/>
<ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig"
@ -208,7 +208,7 @@
<el-col :span="20" :offset="1" class="issues-edit">
<el-table border class="adjust-table" :data="issues" style="width: 100%">
<el-table-column prop="id" :label="$t('test_track.issue.id')" show-overflow-tooltip/>
<el-table-column prop="title" :label="$t('test_track.issue.title')"/>
<el-table-column prop="title" :label="$t('test_track.issue.title')" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('test_track.issue.description')">
<template v-slot:default="scope">
<el-popover
@ -218,9 +218,7 @@
>
<ckeditor :editor="editor" disabled
v-model="scope.row.description"/>
<!-- <span v-html="scope.row.description"/>-->
<!-- <span slot="reference">{{scope.row.description}}</span>-->
<el-button slot="reference" type="text">预览</el-button>
<el-button slot="reference" type="text">{{$t('test_track.issue.preview')}}</el-button>
</el-popover>
</template>
</el-table-column>
@ -398,6 +396,7 @@
}
this.testCase = item;
this.initTest();
this.getIssues(testCase.caseId);
this.stepResultChange();
},
openTestCaseEdit(testCase) {
@ -405,7 +404,6 @@
this.activeTab = 'detail';
listenGoBack(this.handleClose);
this.initData(testCase);
this.getIssues(testCase.caseId);
},
initTest() {
this.$nextTick(() => {
@ -445,7 +443,7 @@
});
},
getRelatedTest() {
if (this.testCase.method == 'auto' && this.testCase.testId) {
if (this.testCase.method == 'auto' && this.testCase.testId && this.testCase.testId != 'other') {
this.$get('/' + this.testCase.type + '/get/' + this.testCase.testId, response => {
let data = response.data;
if (data) {
@ -455,6 +453,8 @@
this.$warning(this.$t("test_track.case.relate_test_not_find"));
}
});
} else if (this.testCase.testId === 'other') {
this.$warning(this.$t("test_track.case.other_relate_test_not_find"));
}
},
issuesChange() {
@ -497,7 +497,10 @@
this.result = this.$post("/issues/add", param, () => {
this.$success(this.$t('commons.save_success'));
this.getIssues(param.testCaseId);
})
});
this.issuesSwitch = false;
this.testCase.issues.title = "";
this.testCase.issues.content = "";
},
getIssues(caseId) {
this.result = this.$get("/issues/get/" + caseId, response => {

View File

@ -113,7 +113,7 @@
trigger="hover">
<el-table border class="adjust-table" :data="scope.row.issuesContent" style="width: 100%">
<!-- <el-table-column prop="id" label="缺陷ID" show-overflow-tooltip/>-->
<el-table-column prop="title" :label="$t('test_track.issue.title')"/>
<el-table-column prop="title" :label="$t('test_track.issue.title')" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('test_track.issue.description')">
<template v-slot:default="scope">
<el-popover
@ -123,9 +123,7 @@
>
<ckeditor :editor="editor" disabled
v-model="scope.row.description"/>
<!-- <span v-html="scope.row.description"/>-->
<!-- <span slot="reference">{{scope.row.description}}</span>-->
<el-button slot="reference" type="text">预览</el-button>
<el-button slot="reference" type="text">{{$t('test_track.issue.preview')}}</el-button>
</el-popover>
</template>
</el-table-column>

View File

@ -32,14 +32,18 @@
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="reporter"
:label="$t('test_track.module.current_owner')"
show-overflow-tooltip>
prop="lastmodify"
:label="$t('test_track.module.current_owner')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="createTime"
:label="$t('test_track.module.creation_time')">
prop="createTime"
:label="$t('test_track.module.creation_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
</template>

View File

@ -0,0 +1,58 @@
import MsProject from "@/business/components/project/MsProject";
const TestTrack = () => import(/* webpackChunkName: "track" */ '@/business/components/track/TestTrack')
const TrackHome = () => import(/* webpackChunkName: "track" */ '@/business/components/track/home/TrackHome')
const TestCase = () => import(/* webpackChunkName: "track" */ '@/business/components/track/case/TestCase')
const TestPlan = () => import(/* webpackChunkName: "track" */ '@/business/components/track/plan/TestPlan')
const TestPlanView = () => import(/* webpackChunkName: "track" */ '@/business/components/track/plan/view/TestPlanView')
export default {
path: "/track",
name: "track",
redirect: "/track/home",
components: {
content: TestTrack
},
children: [
{
path: 'home',
name: 'trackHome',
component: TrackHome,
},
{
path: 'case/create',
name: 'testCaseCreate',
component: TestCase,
},
{
path: 'case/:projectId',
name: 'testCase',
component: TestCase,
},
{
path: 'case/edit/:caseId',
name: 'testCaseEdit',
component: TestCase,
},
{
path: "plan/:type",
name: "testPlan",
component: TestPlan
},
{
path: "plan/view/:planId",
name: "planView",
component: TestPlanView
},
{
path: "plan/view/edit/:caseId",
name: "planViewEdit",
component: TestPlanView
},
{
path: "project/:type",
name: "trackProject",
component: MsProject
}
]
}

View File

@ -60,6 +60,7 @@ export default {
not_filled: 'Not filled',
please_select: 'Please select',
search_by_name: 'Search by name',
search_by_name_or_id: 'Search by name or id',
personal_information: 'Personal Information',
exit_system: 'Exit System',
verification: 'Verification',
@ -593,6 +594,7 @@ export default {
create_module_first: "Please create module first",
relate_test: "Relate test",
relate_test_not_find: 'The associated test does not exist, please check the test case',
other_relate_test_not_find: 'Associated test name, please go to the third party platform to execute',
batch_handle: 'Batch processing (select {0} item)',
batch_update: 'Update the attributes of {0} cases',
select_catalog: 'Please select use case catalog',
@ -729,6 +731,7 @@ export default {
close: "Close",
title_description_required: "Title and description are required",
close_success: "Closed successfully",
preview: "Preview"
}
},
test_resource_pool: {

View File

@ -60,6 +60,7 @@ export default {
not_filled: '未填写',
please_select: '请选择',
search_by_name: '根据名称搜索',
search_by_name_or_id: '根据ID或名称搜索',
personal_information: '个人信息',
exit_system: '退出系统',
verification: '验证',
@ -596,6 +597,7 @@ export default {
create_module_first: "请先新建模块",
relate_test: "关联测试",
relate_test_not_find: '关联的测试不存在,请检查用例',
other_relate_test_not_find: '关联的测试名,请前往第三方平台执行',
batch_handle: '批量处理 (选中{0}项)',
batch_update: '更新{0}个用例的属性',
select_catalog: '请选择用例目录',
@ -732,6 +734,7 @@ export default {
close: "关闭缺陷",
title_description_required: "标题和描述必填",
close_success: "关闭成功",
preview: "预览"
}
},
test_resource_pool: {

View File

@ -60,6 +60,7 @@ export default {
not_filled: '未填寫',
please_select: '請選擇',
search_by_name: '根據名稱搜索',
search_by_name_or_id: '根據ID或名稱搜索',
personal_information: '個人信息',
exit_system: '退出系統',
verification: '驗證',
@ -593,6 +594,7 @@ export default {
create_module_first: "請先新建模塊",
relate_test: "關聯測試",
relate_test_not_find: '關聯的測試不存在,請檢查用例',
other_relate_test_not_find: '關聯的測試名,請前往協力廠商平臺執行',
batch_handle: '批量處理 (選中{0}項)',
batch_update: '更新{0}個用例的屬性',
select_catalog: '請選擇用例目錄',
@ -729,6 +731,7 @@ export default {
close: "關閉缺陷",
title_description_required: "標題和描述必填",
close_success: "關閉成功",
preview: "預覽"
}
},
test_resource_pool: {