This commit is contained in:
fit2-zhao 2020-12-01 17:49:17 +08:00
commit 184b7ca3f4
27 changed files with 527 additions and 179 deletions

View File

@ -40,11 +40,4 @@ public interface ExtTestPlanTestCaseMapper {
TestPlanCaseDTO get(String testPlanTestCaseId); TestPlanCaseDTO get(String testPlanTestCaseId);
/**
* 获取测试计划下的 TestPlanTestCaseID TestCaseName
* @param request planId 不能为空
* @return List<TestPlanCaseDTO>
*/
List<TestPlanCaseDTO> getTestPlanTestCaseList(@Param("request") QueryTestPlanCaseRequest request);
} }

View File

@ -214,13 +214,6 @@
</if> </if>
</select> </select>
<select id="getTestPlanTestCaseList" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_plan_test_case.id as id, test_case.name
from test_plan_test_case
inner join test_case on test_plan_test_case.case_id = test_case.id
where test_plan_test_case.plan_id = #{request.planId}
</select>
<select id="listTestCaseByProjectIds" resultType="io.metersphere.track.dto.TestPlanCaseDTO"> <select id="listTestCaseByProjectIds" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select distinct * from test_plan_test_case, test_case select distinct * from test_plan_test_case, test_case
where test_plan_test_case.case_id = test_case.id where test_plan_test_case.case_id = test_case.id

View File

@ -19,4 +19,11 @@ public interface ExtTestReviewCaseMapper {
* @return List<TestReviewCaseDTO> * @return List<TestReviewCaseDTO>
*/ */
List<TestReviewCaseDTO> listTestCaseByProjectIds(@Param("ids") List<String> ids); List<TestReviewCaseDTO> listTestCaseByProjectIds(@Param("ids") List<String> ids);
/**
* 获取 TestReviewTestCase 详细信息
* @param id TestReviewTestCase id
* @return TestReviewTestCase 详细信息
*/
TestReviewCaseDTO get(@Param("id") String id);
} }

View File

@ -97,7 +97,10 @@
</sql> </sql>
<select id="list" resultType="io.metersphere.track.dto.TestReviewCaseDTO"> <select id="list" resultType="io.metersphere.track.dto.TestReviewCaseDTO">
select test_case.remark, test_case_review_test_case.*, test_case.*, test_case_node.name as model, project.name as projectName select test_case_review_test_case.id as id, test_case.id as caseId, test_case.name, test_case.priority,
test_case.type, test_case.node_path, test_case.method, test_case.num, test_case_review_test_case.reviewer,
test_case.review_status, test_case_review_test_case.update_time, test_case_node.name as model,
project.name as projectName, test_case_review_test_case.review_id as reviewId
from test_case_review_test_case from test_case_review_test_case
inner join test_case on test_case_review_test_case.case_id = test_case.id inner join test_case on test_case_review_test_case.case_id = test_case.id
left join test_case_node on test_case_node.id=test_case.node_id left join test_case_node on test_case_node.id=test_case.node_id
@ -181,6 +184,15 @@
</if> </if>
</select> </select>
<select id="get" resultType="io.metersphere.track.dto.TestReviewCaseDTO">
select test_case.remark, test_case_review_test_case.*, test_case.*, test_case_node.name as model, project.name as projectName
from test_case_review_test_case
inner join test_case on test_case_review_test_case.case_id = test_case.id
left join test_case_node on test_case_node.id=test_case.node_id
inner join project on project.id = test_case.project_id
where test_case_review_test_case.id = #{id}
</select>
<select id="getStatusByReviewId" resultType="java.lang.String"> <select id="getStatusByReviewId" resultType="java.lang.String">
select review_status select review_status
from test_case from test_case

View File

@ -61,6 +61,10 @@ public class CheckOwnerService {
} }
public void checkPerformanceTestOwner(String testId) { public void checkPerformanceTestOwner(String testId) {
// 关联为其他时
if (StringUtils.equals("other", testId)) {
return;
}
String workspaceId = SessionUtils.getCurrentWorkspaceId(); String workspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestPlanRequest request = new QueryTestPlanRequest(); QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setWorkspaceId(workspaceId); request.setWorkspaceId(workspaceId);

View File

@ -42,7 +42,7 @@ public class FileService {
final List<LoadTestFile> loadTestFiles = loadTestFileMapper.selectByExample(loadTestFileExample); final List<LoadTestFile> loadTestFiles = loadTestFileMapper.selectByExample(loadTestFileExample);
if (CollectionUtils.isEmpty(loadTestFiles)) { if (CollectionUtils.isEmpty(loadTestFiles)) {
return null; return new ArrayList<>();
} }
List<String> fileIds = loadTestFiles.stream().map(LoadTestFile::getFileId).collect(Collectors.toList()); List<String> fileIds = loadTestFiles.stream().map(LoadTestFile::getFileId).collect(Collectors.toList());
FileMetadataExample example = new FileMetadataExample(); FileMetadataExample example = new FileMetadataExample();

View File

@ -44,7 +44,7 @@ public class TestReviewTestCaseController {
} }
@PostMapping("/list/all") @PostMapping("/list/all")
public List<TestReviewCaseDTO> getTestPlanCases(@RequestBody QueryCaseReviewRequest request) { public List<TestReviewCaseDTO> getTestReviewCases(@RequestBody QueryCaseReviewRequest request) {
return testReviewTestCaseService.list(request); return testReviewTestCaseService.list(request);
} }
@ -53,4 +53,17 @@ public class TestReviewTestCaseController {
public void editTestCase(@RequestBody TestCaseReviewTestCase testCaseReviewTestCase) { public void editTestCase(@RequestBody TestCaseReviewTestCase testCaseReviewTestCase) {
testReviewTestCaseService.editTestCase(testCaseReviewTestCase); testReviewTestCaseService.editTestCase(testCaseReviewTestCase);
} }
@GetMapping("/get/{reviewId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public TestReviewCaseDTO get(@PathVariable String reviewId) {
return testReviewTestCaseService.get(reviewId);
}
@PostMapping("/list/ids")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public List<TestReviewCaseDTO> getTestReviewCaseList(@RequestBody QueryCaseReviewRequest request) {
return testReviewTestCaseService.getTestCaseReviewDTOList(request);
}
} }

View File

@ -372,7 +372,7 @@ public class TestCaseNodeService {
public Map<String, String> createNodes(List<String> nodePaths, String projectId) { public Map<String, String> createNodes(List<String> nodePaths, String projectId) {
List<TestCaseNodeDTO> nodeTrees = getNodeTreeByProjectId(projectId); List<TestCaseNodeDTO> nodeTrees = getNodeTreeByProjectId(projectId);
Map<String, String> pathMap = new HashMap<>(); Map<String, String> pathMap = new HashMap<>();
for(String item : nodePaths){ for (String item : nodePaths) {
if (item == null) { if (item == null) {
throw new ExcelException(Translator.get("test_case_module_not_null")); throw new ExcelException(Translator.get("test_case_module_not_null"));
} }
@ -590,6 +590,7 @@ public class TestCaseNodeService {
/** /**
* 测试用例同级模块排序 * 测试用例同级模块排序
*
* @param ids 被拖拽模块相邻的前一个模块 id * @param ids 被拖拽模块相邻的前一个模块 id
* 被拖拽的模块 id * 被拖拽的模块 id
* 被拖拽模块相邻的后一个模块 id * 被拖拽模块相邻的后一个模块 id
@ -635,6 +636,7 @@ public class TestCaseNodeService {
/** /**
* 按照指定排序方式获取同级模块的列表 * 按照指定排序方式获取同级模块的列表
*
* @param projectId 所属项目 id * @param projectId 所属项目 id
* @param level node level * @param level node level
* @param parentId node parent id * @param parentId node parent id
@ -654,6 +656,7 @@ public class TestCaseNodeService {
/** /**
* 刷新同级模块的 pos * 刷新同级模块的 pos
*
* @param projectId project id * @param projectId project id
* @param level node level * @param level node level
* @param parentId node parent id * @param parentId node parent id
@ -675,6 +678,7 @@ public class TestCaseNodeService {
/** /**
* 获得同级模块下一个 pos * 获得同级模块下一个 pos
*
* @param projectId project id * @param projectId project id
* @param level node level * @param level node level
* @param parentId node parent id * @param parentId node parent id
@ -682,7 +686,7 @@ public class TestCaseNodeService {
*/ */
private double getNextLevelPos(String projectId, int level, String parentId) { private double getNextLevelPos(String projectId, int level, String parentId) {
List<TestCaseNode> list = getPos(projectId, level, parentId, "pos desc"); List<TestCaseNode> list = getPos(projectId, level, parentId, "pos desc");
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list) && list.get(0) != null && list.get(0).getPos() != null) {
return list.get(0).getPos() + 65536; return list.get(0).getPos() + 65536;
} else { } else {
return 65536; return 65536;

View File

@ -128,4 +128,13 @@ public class TestReviewTestCaseService {
testCase.setReviewStatus(testCaseReviewTestCase.getStatus()); testCase.setReviewStatus(testCaseReviewTestCase.getStatus());
testCaseMapper.updateByPrimaryKeySelective(testCase); testCaseMapper.updateByPrimaryKeySelective(testCase);
} }
public List<TestReviewCaseDTO> getTestCaseReviewDTOList(QueryCaseReviewRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
return extTestReviewCaseMapper.list(request);
}
public TestReviewCaseDTO get(String reviewId) {
return extTestReviewCaseMapper.get(reviewId);
}
} }

View File

@ -27,7 +27,6 @@
<el-form-item :label="$t('load_test.thread_num')"> <el-form-item :label="$t('load_test.thread_num')">
<el-input-number <el-input-number
:disabled="isReadOnly" :disabled="isReadOnly"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadGroup.threadNumber" v-model="threadGroup.threadNumber"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
:min="resourcePoolResourceLength" :min="resourcePoolResourceLength"
@ -37,7 +36,6 @@
<el-form-item :label="$t('load_test.duration')"> <el-form-item :label="$t('load_test.duration')">
<el-input-number <el-input-number
:disabled="isReadOnly" :disabled="isReadOnly"
:placeholder="$t('load_test.duration')"
v-model="threadGroup.duration" v-model="threadGroup.duration"
:min="1" :min="1"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
@ -49,7 +47,6 @@
&nbsp; &nbsp;
<el-input-number <el-input-number
:disabled="isReadOnly || !threadGroup.rpsLimitEnable" :disabled="isReadOnly || !threadGroup.rpsLimitEnable"
:placeholder="$t('load_test.input_rps_limit')"
v-model="threadGroup.rpsLimit" v-model="threadGroup.rpsLimit"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
:min="1" :min="1"
@ -59,7 +56,6 @@
<el-form-item :label="$t('load_test.ramp_up_time_within')"> <el-form-item :label="$t('load_test.ramp_up_time_within')">
<el-input-number <el-input-number
:disabled="isReadOnly" :disabled="isReadOnly"
placeholder=""
:min="1" :min="1"
:max="threadGroup.duration" :max="threadGroup.duration"
v-model="threadGroup.rampUpTime" v-model="threadGroup.rampUpTime"
@ -69,7 +65,6 @@
<el-form-item :label="$t('load_test.ramp_up_time_minutes')"> <el-form-item :label="$t('load_test.ramp_up_time_minutes')">
<el-input-number <el-input-number
:disabled="isReadOnly" :disabled="isReadOnly"
placeholder=""
:min="1" :min="1"
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)" :max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
v-model="threadGroup.step" v-model="threadGroup.step"
@ -465,7 +460,13 @@ export default {
for (let i = 0; i < this.threadGroups.length; i++) { for (let i = 0; i < this.threadGroups.length; i++) {
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].rpsLimit) { || !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
}
if (this.threadGroups[i].rpsLimitEnable && !this.threadGroups[i].rpsLimit) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty')); this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1'); this.$emit('changeActive', '1');
return false; return false;

View File

@ -10,7 +10,7 @@
<img class="platform" src="../../../../assets/jira.png" alt="Jira"/> <img class="platform" src="../../../../assets/jira.png" alt="Jira"/>
</el-radio> </el-radio>
<el-radio label="Zentao"> <el-radio label="Zentao">
<img class="platform" src="../../../../assets/zentao.jpg" alt="Zentao"/> <img class="zentao_platform" src="../../../../assets/zentao.jpg" alt="Zentao"/>
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
@ -66,7 +66,12 @@ export default {
} }
.platform { .platform {
height: 90px; height: 80px;
vertical-align: middle
}
.zentao_platform {
height: 100px;
vertical-align: middle vertical-align: middle
} }
</style> </style>

View File

@ -2,8 +2,12 @@
<ms-container> <ms-container>
<ms-main-container> <ms-main-container>
<el-alert <el-alert
:title="$t('organization.message.notes')" title="Notice:"
type="info"> type="info"
show-icon>
<template v-slot:default>
{{ $t('organization.message.notes') }}
</template>
</el-alert> </el-alert>
<jenkins-notification :jenkins-receiver-options="jenkinsReceiverOptions"/> <jenkins-notification :jenkins-receiver-options="jenkinsReceiverOptions"/>
<test-plan-task-notification :test-plan-receiver-options="testPlanReceiverOptions"/> <test-plan-task-notification :test-plan-receiver-options="testPlanReceiverOptions"/>

View File

@ -34,7 +34,6 @@ export default {
}, },
...requireContext.keys().map(key => requireContext(key).system), ...requireContext.keys().map(key => requireContext(key).system),
...requireContext.keys().map(key => requireContext(key).license), ...requireContext.keys().map(key => requireContext(key).license),
...requireContext.keys().map(key => requireContext(key).display),
{ {
path: 'organizationpmnmember', path: 'organizationpmnmember',
component: () => import('@/business/components/settings/organization/OrganizationMember'), component: () => import('@/business/components/settings/organization/OrganizationMember'),

View File

@ -11,6 +11,9 @@
<el-tab-pane :label="$t('system_parameter_setting.ldap_setting')" name="ldap"> <el-tab-pane :label="$t('system_parameter_setting.ldap_setting')" name="ldap">
<ldap-setting/> <ldap-setting/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane v-if="hasLicense()" :label="$t('display.title')" name="display">
<ms-display/>
</el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>
</template> </template>
@ -19,17 +22,26 @@
import EmailSetting from "./EmailSetting"; import EmailSetting from "./EmailSetting";
import LdapSetting from "./LdapSetting"; import LdapSetting from "./LdapSetting";
import BaseSetting from "./BaseSetting"; import BaseSetting from "./BaseSetting";
import {hasLicense} from '@/common/js/utils';
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
export default { export default {
name: "SystemParameterSetting", name: "SystemParameterSetting",
components: { components: {
BaseSetting, BaseSetting,
EmailSetting, LdapSetting EmailSetting,
LdapSetting,
"MsDisplay": display.default
}, },
data() { data() {
return { return {
activeName: 'base' activeName: 'base',
} }
},
methods: {
hasLicense,
} }
} }
</script> </script>

View File

@ -1,39 +1,25 @@
<template> <template>
<ms-container> <div>
<ms-test-plan-header-bar>
<ms-aside-container> <template v-slot:info>
<select-menu <select-menu
:data="testPlans" :data="testPlans"
:current-data="currentPlan" :current-data="currentPlan"
:title="$t('test_track.plan_view.plan')" :title="$t('test_track.plan_view.plan')"
@dataChange="changePlan"/> @dataChange="changePlan"/>
<node-tree class="node-tree" </template>
v-loading="result.loading" <template v-slot:menu>
@nodeSelectEvent="nodeChange" <el-menu active-text-color="#6d317c" :default-active="activeIndex"
@refresh="refresh" class="el-menu-demo header-menu" mode="horizontal" @select="handleSelect">
:tree-nodes="treeNodes" <el-menu-item index="functional">功能测试用例</el-menu-item>
:draggable="false" <el-menu-item index="api">接口测试用例</el-menu-item>
ref="nodeTree"/> </el-menu>
</ms-aside-container> </template>
</ms-test-plan-header-bar>
<ms-main-container> <test-plan-functional v-if="activeIndex === 'functional'" :plan-id="planId"/>
<test-plan-test-case-list <test-plan-api v-if="activeIndex === 'api'" :plan-id="planId"/>
class="table-list" </div>
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
@refresh="refresh"
:plan-id="planId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
ref="testPlanTestCaseList"/>
</ms-main-container>
<test-case-relevance
@refresh="refresh"
:plan-id="planId"
ref="testCaseRelevance"/>
</ms-container>
</template> </template>
@ -46,20 +32,23 @@
import MsContainer from "../../../common/components/MsContainer"; import MsContainer from "../../../common/components/MsContainer";
import MsAsideContainer from "../../../common/components/MsAsideContainer"; import MsAsideContainer from "../../../common/components/MsAsideContainer";
import MsMainContainer from "../../../common/components/MsMainContainer"; import MsMainContainer from "../../../common/components/MsMainContainer";
import MsTestPlanHeaderBar from "./comonents/head/TestPlanHeaderBar";
import TestPlanFunctional from "./comonents/functional/TestPlanFunctional";
import TestPlanApi from "./comonents/api/TestPlanApi";
export default { export default {
name: "TestPlanView", name: "TestPlanView",
components: { components: {
TestPlanApi,
TestPlanFunctional,
MsTestPlanHeaderBar,
MsMainContainer, MsMainContainer,
MsAsideContainer, MsContainer, NodeTree, TestPlanTestCaseList, TestCaseRelevance, SelectMenu}, MsAsideContainer, MsContainer, NodeTree, TestPlanTestCaseList, TestCaseRelevance, SelectMenu},
data() { data() {
return { return {
result: {},
testPlans: [], testPlans: [],
currentPlan: {}, currentPlan: {},
selectNodeIds: [], activeIndex: "functional"
selectParentNodes: [],
treeNodes: []
} }
}, },
computed: { computed: {
@ -68,33 +57,11 @@
} }
}, },
mounted() { mounted() {
this.initData(); this.getTestPlans();
this.openTestCaseEdit(this.$route.path);
},
watch: {
'$route'(to, from) {
this.openTestCaseEdit(to.path);
},
planId() {
this.initData();
}
}, },
methods: { methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testCaseRelevance.search();
this.getNodeTreeByPlanId();
},
initData() {
this.getTestPlans();
this.getNodeTreeByPlanId();
},
openTestCaseRelevanceDialog() {
this.$refs.testCaseRelevance.openTestCaseRelevanceDialog();
},
getTestPlans() { getTestPlans() {
this.result = this.$post('/test/plan/list/all', {}, response => { this.$post('/test/plan/list/all', {}, response => {
this.testPlans = response.data; this.testPlans = response.data;
this.testPlans.forEach(plan => { this.testPlans.forEach(plan => {
if (this.planId && plan.id === this.planId) { if (this.planId && plan.id === this.planId) {
@ -103,39 +70,36 @@
}); });
}); });
}, },
nodeChange(nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
// node
this.$refs.testPlanTestCaseList.currentPage = 1;
this.$refs.testPlanTestCaseList.pageSize = 10;
},
changePlan(plan) { changePlan(plan) {
this.currentPlan = plan; this.currentPlan = plan;
this.$router.push('/track/plan/view/' + plan.id); this.$router.push('/track/plan/view/' + plan.id);
}, },
getNodeTreeByPlanId() { handleSelect(key) {
if(this.planId){ this.activeIndex = key;
this.result = this.$get("/case/node/list/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
openTestCaseEdit(path) {
if (path.indexOf("/plan/view/edit") >= 0){
let caseId = this.$route.params.caseId;
this.$get('/test/plan/case/get/' + caseId, response => {
let testCase = response.data;
if (testCase) {
this.$refs.testPlanTestCaseList.handleEdit(testCase);
this.$router.push('/track/plan/view/' + testCase.planId);
}
});
}
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.select-menu {
display: inline-block;
}
.ms-main-container {
height: calc(100vh - 80px - 50px);
}
.ms-aside-container {
height: calc(100vh - 80px - 51px);
margin-top: 1px;
}
.header-menu.el-menu--horizontal > li {
height: 49px;
line-height: 50px;
color: dimgray;
}
</style> </style>

View File

@ -0,0 +1,29 @@
<template>
<ms-container>
<ms-aside-container>
<slot name="aside"></slot>
</ms-aside-container>
<ms-main-container>
<slot name="main"></slot>
</ms-main-container>
<slot></slot>
</ms-container>
</template>
<script>
import MsMainContainer from "../../../../common/components/MsMainContainer";
import MsAsideContainer from "../../../../common/components/MsAsideContainer";
import MsContainer from "../../../../common/components/MsContainer";
export default {
name: "MsTestPlanCommonComponent",
components: {MsContainer, MsAsideContainer, MsMainContainer}
}
</script>
<style scoped>
</style>

View File

@ -472,6 +472,7 @@ export default {
this.getTestCase(this.index); this.getTestCase(this.index);
}, },
getTestCase(index) { getTestCase(index) {
this.testCase = {};
let testCase = this.testCases[index]; let testCase = this.testCases[index];
// id TestPlanTestCase id // id TestPlanTestCase id
this.result = this.$get('/test/plan/case/get/' + testCase.id, response => { this.result = this.$get('/test/plan/case/get/' + testCase.id, response => {
@ -495,11 +496,10 @@ export default {
this.testCase = item; this.testCase = item;
this.getRelatedTest(); this.getRelatedTest();
this.initTest(); this.initTest();
}) this.getIssues(item.caseId);
this.getIssues(testCase.caseId);
this.stepResultChange(); this.stepResultChange();
this.getFileMetaData(testCase); this.getFileMetaData(item);
})
}, },
getFileMetaData(testCase) { getFileMetaData(testCase) {
this.tableData = []; this.tableData = [];

View File

@ -0,0 +1,115 @@
<template>
<ms-test-plan-common-component>
<template v-slot:aside>
<node-tree class="node-tree"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
:tree-nodes="treeNodes"
:draggable="false"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-plan-test-case-list
class="table-list"
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
@refresh="refresh"
:plan-id="planId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
ref="testPlanTestCaseList"/>
</template>
<test-case-relevance
@refresh="refresh"
:plan-id="planId"
ref="testCaseRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import NodeTree from "../../../../common/NodeTree";
import TestPlanTestCaseList from "../TestPlanTestCaseList";
import TestCaseRelevance from "../TestCaseRelevance";
import MsTestPlanCommonComponent from "../TestPlanCommonComponent";
export default {
name: "TestPlanApi",
components: {
MsTestPlanCommonComponent,
TestCaseRelevance,
TestPlanTestCaseList,
NodeTree,
},
data() {
return {
result: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: [],
}
},
props: [
'planId'
],
mounted() {
this.initData();
this.openTestCaseEdit(this.$route.path);
},
watch: {
'$route'(to, from) {
this.openTestCaseEdit(to.path);
},
planId() {
this.initData();
}
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testCaseRelevance.search();
this.getNodeTreeByPlanId();
},
initData() {
this.getNodeTreeByPlanId();
},
openTestCaseRelevanceDialog() {
this.$refs.testCaseRelevance.openTestCaseRelevanceDialog();
},
nodeChange(nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
// node
this.$refs.testPlanTestCaseList.currentPage = 1;
this.$refs.testPlanTestCaseList.pageSize = 10;
},
getNodeTreeByPlanId() {
if (this.planId) {
this.result = this.$get("/case/node/list/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
openTestCaseEdit(path) {
if (path.indexOf("/plan/view/edit") >= 0) {
let caseId = this.$route.params.caseId;
this.$get('/test/plan/case/get/' + caseId, response => {
let testCase = response.data;
if (testCase) {
this.$refs.testPlanTestCaseList.handleEdit(testCase);
this.$router.push('/track/plan/view/' + testCase.planId);
}
});
}
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,115 @@
<template>
<ms-test-plan-common-component>
<template v-slot:aside>
<node-tree class="node-tree"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
:tree-nodes="treeNodes"
:draggable="false"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-plan-test-case-list
class="table-list"
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
@refresh="refresh"
:plan-id="planId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
ref="testPlanTestCaseList"/>
</template>
<test-case-relevance
@refresh="refresh"
:plan-id="planId"
ref="testCaseRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import NodeTree from "../../../../common/NodeTree";
import TestPlanTestCaseList from "../TestPlanTestCaseList";
import TestCaseRelevance from "../TestCaseRelevance";
import MsTestPlanCommonComponent from "../TestPlanCommonComponent";
export default {
name: "TestPlanFunctional",
components: {
MsTestPlanCommonComponent,
TestCaseRelevance,
TestPlanTestCaseList,
NodeTree,
},
data() {
return {
result: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: [],
}
},
props: [
'planId'
],
mounted() {
this.initData();
this.openTestCaseEdit(this.$route.path);
},
watch: {
'$route'(to, from) {
this.openTestCaseEdit(to.path);
},
planId() {
this.initData();
}
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testCaseRelevance.search();
this.getNodeTreeByPlanId();
},
initData() {
this.getNodeTreeByPlanId();
},
openTestCaseRelevanceDialog() {
this.$refs.testCaseRelevance.openTestCaseRelevanceDialog();
},
nodeChange(nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
// node
this.$refs.testPlanTestCaseList.currentPage = 1;
this.$refs.testPlanTestCaseList.pageSize = 10;
},
getNodeTreeByPlanId() {
if (this.planId) {
this.result = this.$get("/case/node/list/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
openTestCaseEdit(path) {
if (path.indexOf("/plan/view/edit") >= 0) {
let caseId = this.$route.params.caseId;
this.$get('/test/plan/case/get/' + caseId, response => {
let testCase = response.data;
if (testCase) {
this.$refs.testPlanTestCaseList.handleEdit(testCase);
this.$router.push('/track/plan/view/' + testCase.planId);
}
});
}
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,51 @@
<template>
<div class="test-plan-header-bar">
<div class="header-info">
<slot name="info"></slot>
</div>
<div class="menu-ul">
<slot name="menu"></slot>
</div>
</div>
</template>
<script>
import SelectMenu from "../../../../common/SelectMenu";
export default {
name: "MsTestPlanHeaderBar",
components: {SelectMenu},
data() {
return {
}
},
props: {
},
methods: {
}
}
</script>
<style scoped>
.test-plan-header-bar {
height: 50px;
background-color: white;
}
.header-info {
height: 50px;
width: 300px;
line-height: 50px;
vertical-align: top;
display: inline-block;
}
.menu-ul {
width: 500px;
display: inline-block;
}
</style>

View File

@ -102,10 +102,11 @@
<el-col class="test-detail" :span="20" :offset="1"> <el-col class="test-detail" :span="20" :offset="1">
<el-tabs v-model="activeTab" type="border-card"> <el-tabs v-model="activeTab" type="border-card">
<el-tab-pane name="detail" :label="$t('test_track.plan_view.test_detail')"> <el-tab-pane name="detail" :label="$t('test_track.plan_view.test_detail')">
<api-test-detail :is-read-only="true" v-if="testCase.type === 'api'" @runTest="testRun" <api-test-detail :is-read-only="true" v-if="testCase.type === 'api'"
:id="testCase.testId" ref="apiTestDetail"/> :id="testCase.testId" ref="apiTestDetail"/>
<performance-test-detail :is-read-only="true" v-if="testCase.type === 'performance'" <performance-test-detail v-if="testCase.type === 'performance'"
@runTest="testRun" :id="testCase.testId" :is-read-only="true"
:id="testCase.testId"
ref="performanceTestDetail"/> ref="performanceTestDetail"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -211,7 +212,6 @@
<test-case-attachment :table-data="tableData" <test-case-attachment :table-data="tableData"
:read-only="false" :read-only="false"
:is-delete="false" :is-delete="false"
@handleDelete="handleDelete"
/> />
</div> </div>
</el-col> </el-col>
@ -338,9 +338,12 @@ export default {
this.getTestCase(this.index); this.getTestCase(this.index);
}, },
getTestCase(index) { getTestCase(index) {
this.testCase = {};
let testCase = this.testCases[index]; let testCase = this.testCases[index];
this.result = this.$get("/test/review/case/get/" + testCase.id, response => {
let item = {}; let item = {};
Object.assign(item, testCase); let data = response.data;
Object.assign(item, data);
item.steps = JSON.parse(item.steps); item.steps = JSON.parse(item.steps);
item.steptResults = []; item.steptResults = [];
for (let i = 0; i < item.steps.length; i++) { for (let i = 0; i < item.steps.length; i++) {
@ -349,9 +352,12 @@ export default {
item.steptResults.push(item.steps[i]); item.steptResults.push(item.steps[i]);
} }
this.testCase = item; this.testCase = item;
this.getRelatedTest();
this.getComments(item); this.getComments(item);
this.initTest(); this.initTest();
this.getFileMetaData(testCase); this.getFileMetaData(data);
})
}, },
getFileMetaData(testCase) { getFileMetaData(testCase) {
this.tableData = []; this.tableData = [];
@ -375,6 +381,7 @@ export default {
}, },
initTest() { initTest() {
this.$nextTick(() => { this.$nextTick(() => {
if (this.testCase.testId && this.testCase.testId !== 'other') {
if (this.testCase.method === 'auto') { if (this.testCase.method === 'auto') {
if (this.$refs.apiTestDetail && this.testCase.type === 'api') { if (this.$refs.apiTestDetail && this.testCase.type === 'api') {
this.$refs.apiTestDetail.init(); this.$refs.apiTestDetail.init();
@ -382,14 +389,8 @@ export default {
this.$refs.performanceTestDetail.init(); this.$refs.performanceTestDetail.init();
} }
} }
}
}); });
},
testRun(reportId) {
this.testCase.reportId = reportId;
this.saveReport(reportId);
},
saveReport(reportId) {
}, },
getComments(testCase) { getComments(testCase) {
let id = ''; let id = '';
@ -403,13 +404,12 @@ export default {
}) })
}, },
initData(testCase) { initData(testCase) {
this.result = this.$post('/test/review/case/list/all', this.searchParam, response => { this.result = this.$post('/test/review/case/list/ids', this.searchParam, response => {
this.testCases = response.data; this.testCases = response.data;
for (let i = 0; i < this.testCases.length; i++) { for (let i = 0; i < this.testCases.length; i++) {
if (this.testCases[i].id === testCase.id) { if (this.testCases[i].id === testCase.id) {
this.index = i; this.index = i;
this.getTestCase(i); this.getTestCase(i);
this.getRelatedTest();
} }
} }
}); });
@ -439,9 +439,6 @@ export default {
}).length > 0; }).length > 0;
} }
}, },
handleDelete() {
}
} }
} }
</script> </script>

View File

@ -11,7 +11,7 @@ import YanProgress from 'yan-progress';
import './permission' // permission control import './permission' // permission control
import i18n from "../i18n/i18n"; import i18n from "../i18n/i18n";
import store from "./store"; import store from "./store";
import {permission, roles} from './permission' import {permission, roles, xpack} from './permission'
import chart from "../common/js/chart"; import chart from "../common/js/chart";
import CalendarHeatmap from "../common/js/calendar-heatmap"; import CalendarHeatmap from "../common/js/calendar-heatmap";
import '../common/css/menu-header.css'; import '../common/css/menu-header.css';
@ -37,6 +37,8 @@ Vue.directive('permission', permission);
// v-roles // v-roles
Vue.directive('roles', roles); Vue.directive('roles', roles);
Vue.directive('xpack', xpack);
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,

View File

@ -1,6 +1,6 @@
import router from './components/common/router/router' import router from './components/common/router/router'
import {TokenKey} from '@/common/js/constants'; import {TokenKey} from '@/common/js/constants';
import {hasRolePermissions, hasRoles} from "@/common/js/utils"; import {hasLicense, hasRolePermissions, hasRoles} from "@/common/js/utils";
import NProgress from 'nprogress' // progress bar import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style import 'nprogress/nprogress.css' // progress bar style
const whiteList = ['/login']; // no redirect whitelist const whiteList = ['/login']; // no redirect whitelist
@ -19,6 +19,20 @@ export const roles = {
} }
}; };
export const xpack = {
inserted(el, binding) {
checkLicense(el, binding);
}
};
function checkLicense(el, binding, type) {
let v = hasLicense()
if (v) {
el.parentNode && el.parentNode.removeChild(el)
}
}
function checkRolePermission(el, binding, type) { function checkRolePermission(el, binding, type) {
const {value} = binding; const {value} = binding;
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {

View File

@ -1,12 +1,12 @@
import { import {
LicenseKey,
REFRESH_SESSION_USER_URL, REFRESH_SESSION_USER_URL,
ROLE_ORG_ADMIN,
ROLE_ADMIN, ROLE_ADMIN,
ROLE_ORG_ADMIN,
ROLE_TEST_MANAGER, ROLE_TEST_MANAGER,
ROLE_TEST_USER, ROLE_TEST_USER,
ROLE_TEST_VIEWER, ROLE_TEST_VIEWER,
TokenKey, TokenKey
LicenseKey
} from "./constants"; } from "./constants";
import axios from "axios"; import axios from "axios";
import {jsPDF} from "jspdf"; import {jsPDF} from "jspdf";
@ -45,6 +45,11 @@ export function hasRolePermission(role) {
return false return false
} }
export function hasLicense() {
let v = localStorage.getItem(LicenseKey);
return v === 'valid';
}
//是否含有对应组织或工作空间的角色 //是否含有对应组织或工作空间的角色
export function hasRolePermissions(...roles) { export function hasRolePermissions(...roles) {
for (let role of roles) { for (let role of roles) {
@ -238,7 +243,7 @@ export function exportPdf(name, canvasList) {
// html页面生成的canvas在pdf中图片的宽高 // html页面生成的canvas在pdf中图片的宽高
let imgWidth = a4Width; let imgWidth = a4Width;
let imgHeight = a4Width/contentWidth * contentHeight; let imgHeight = a4Width / contentWidth * contentHeight;
let pageData = canvas.toDataURL('image/jpeg', 1.0); let pageData = canvas.toDataURL('image/jpeg', 1.0);
@ -251,7 +256,7 @@ export function exportPdf(name, canvasList) {
if (leftHeight > blankHeight) { if (leftHeight > blankHeight) {
//页面偏移 //页面偏移
let position = 0; let position = 0;
while(leftHeight > 0) { while (leftHeight > 0) {
// 本次添加占用的高度 // 本次添加占用的高度
let occupation = a4Height - currentHeight; let occupation = a4Height - currentHeight;
pdf.addImage(pageData, 'JPEG', 0, position + currentHeight, imgWidth, imgHeight); pdf.addImage(pageData, 'JPEG', 0, position + currentHeight, imgWidth, imgHeight);
@ -259,7 +264,7 @@ export function exportPdf(name, canvasList) {
leftHeight -= occupation; leftHeight -= occupation;
position -= occupation; position -= occupation;
//避免添加空白页 //避免添加空白页
if(leftHeight > 0) { if (leftHeight > 0) {
pdf.addPage(); pdf.addPage();
currentHeight = 0; currentHeight = 0;
} }
@ -277,15 +282,15 @@ export function exportPdf(name, canvasList) {
export function windowPrint(id, zoom) { export function windowPrint(id, zoom) {
//根据div标签ID拿到div中的局部内容 //根据div标签ID拿到div中的局部内容
let bdhtml=window.document.body.innerHTML; let bdhtml = window.document.body.innerHTML;
let el = document.getElementById(id); let el = document.getElementById(id);
var jubuData = el.innerHTML; var jubuData = el.innerHTML;
document.getElementsByTagName('body')[0].style.zoom=zoom; document.getElementsByTagName('body')[0].style.zoom = zoom;
//把获取的 局部div内容赋给body标签, 相当于重置了 body里的内容 //把获取的 局部div内容赋给body标签, 相当于重置了 body里的内容
window.document.body.innerHTML= jubuData; window.document.body.innerHTML = jubuData;
//调用打印功能 //调用打印功能
window.print(); window.print();
window.document.body.innerHTML=bdhtml;//重新给页面内容赋值; window.document.body.innerHTML = bdhtml;//重新给页面内容赋值;
return false; return false;
} }

View File

@ -236,7 +236,7 @@ export default {
mail: 'mail', mail: 'mail',
nail_robot: 'Nail robot', nail_robot: 'Nail robot',
enterprise_wechat_robot: 'Enterprise wechat robot', enterprise_wechat_robot: 'Enterprise wechat robot',
notes: 'Note: 1. Nail and create a custom robot in the enterprise group, and then copy the webhook address on our platform;\n' + notes: '1. Nail and create a custom robot in the enterprise group, and then copy the webhook address on our platform;\n' +
'\n' + '\n' +
'2. Robots are selected as swarm robots, and "custom keyword" is selected for security verification: "task notification";\n' + '2. Robots are selected as swarm robots, and "custom keyword" is selected for security verification: "task notification";\n' +
'\n' + '\n' +

View File

@ -237,7 +237,7 @@ export default {
mail: '邮件', mail: '邮件',
nail_robot: '钉钉机器人', nail_robot: '钉钉机器人',
enterprise_wechat_robot: '企业微信机器人', enterprise_wechat_robot: '企业微信机器人',
notes: '注意:1.钉钉和企业群里新建一个自定义机器人,然后复制 webhook 地址在我们平台上;\n' + notes: '1.钉钉和企业群里新建一个自定义机器人,然后复制 webhook 地址在我们平台上;\n' +
' 2.机器人选择为群机器人,安全验证选择“自定义关键词” "任务通知";\n' + ' 2.机器人选择为群机器人,安全验证选择“自定义关键词” "任务通知";\n' +
' 3.选择接收人时必须是你所建的群里包含的人,接收人手机号为必填项且为钉钉企业所使用的手机号,', ' 3.选择接收人时必须是你所建的群里包含的人,接收人手机号为必填项且为钉钉企业所使用的手机号,',
message: '事件,接收人,接收方式为必填项', message: '事件,接收人,接收方式为必填项',

View File

@ -237,7 +237,7 @@ export default {
mail: '郵件', mail: '郵件',
nail_robot: '釘釘機器人', nail_robot: '釘釘機器人',
enterprise_wechat_robot: '企業微信機器人', enterprise_wechat_robot: '企業微信機器人',
notes: '註意: 1.事件,接收方式,接收人為必填項;\n' + notes: '1.事件,接收方式,接收人為必填項;\n' +
' 2.接收方式除郵件外webhook為必填\n' + ' 2.接收方式除郵件外webhook為必填\n' +
' 3.機器人選擇為群機器人,安全驗證選擇“自定義關鍵詞” "任務通知"', ' 3.機器人選擇為群機器人,安全驗證選擇“自定義關鍵詞” "任務通知"',
message: '事件,接收人,接收方式為必填項', message: '事件,接收人,接收方式為必填項',