refactor(测试跟踪): 测试计划,用例评审 两处关联测试用例一次加载全部优化

This commit is contained in:
fit2-zhao 2020-10-15 18:08:41 +08:00
parent 92122e0546
commit b4af198897
12 changed files with 253 additions and 104 deletions

View File

@ -20,8 +20,13 @@ public interface ExtTestCaseMapper {
TestCase getMaxNumByProjectId(@Param("projectId") String projectId);
List<TestCase> getTestCaseByNotInPlan(@Param("request") QueryTestCaseRequest request);
List<TestCase> getTestCaseByNotInReview(@Param("request") QueryTestCaseRequest request);
/**
* 检查某工作空间下是否有某用例
*
* @param caseId
* @param workspaceId
* @return TestCase ID

View File

@ -97,6 +97,113 @@
</if>
</sql>
<select id="getTestCaseByNotInReview" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status
from test_case
<where>
<if test="request.combine != null">
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="name" value="request.name"/>
</include>
</if>
and test_case.id not in (select case_id from test_case_review_test_case where review_id =#{request.reviewId})
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.projectId != null">
AND test_case.project_id = #{request.projectId}
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
AND test_case.node_id IN
<foreach collection="request.nodeIds" open="(" close=")" separator="," item="nodeId">
#{nodeId}
</foreach>
</if>
<if test="request.filters != null and request.filters.size() > 0">
<foreach collection="request.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='priority'">
and test_case.priority in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='status'">
and test_case.review_status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<otherwise>
and test_case.type in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</otherwise>
</choose>
</if>
</foreach>
</if>
</where>
ORDER BY test_case.update_time DESC
</select>
<select id="getTestCaseByNotInPlan" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status
from test_case
<where>
<if test="request.combine != null">
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="name" value="request.name"/>
</include>
</if>
and test_case.id not in (select case_id from test_plan_test_case where plan_id =#{request.planId})
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.projectId != null">
AND test_case.project_id = #{request.projectId}
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
AND test_case.node_id IN
<foreach collection="request.nodeIds" open="(" close=")" separator="," item="nodeId">
#{nodeId}
</foreach>
</if>
<if test="request.filters != null and request.filters.size() > 0">
<foreach collection="request.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='priority'">
and test_case.priority in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='status'">
and test_case.review_status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<otherwise>
and test_case.type in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</otherwise>
</choose>
</if>
</foreach>
</if>
</where>
ORDER BY test_case.update_time DESC
</select>
<select id="getTestCaseNames" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status

View File

@ -72,9 +72,10 @@ public class TestCaseController {
return testCaseService.getTestCaseByNodeId(nodeIds);
}
@PostMapping("/name")
public List<TestCase> getTestCaseNames(@RequestBody QueryTestCaseRequest request) {
return testCaseService.getTestCaseNames(request);
@PostMapping("/name/{goPage}/{pageSize}")
public Pager<List<TestCase>> getTestCaseNames(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page,testCaseService.getTestCaseNames(request));
}
@PostMapping("/reviews/case/{goPage}/{pageSize}")

View File

@ -10,5 +10,6 @@ import java.util.List;
@Setter
public class PlanCaseRelevanceRequest {
private String planId;
private String projectId;
private List<String> testCaseIds = new ArrayList<>();
}

View File

@ -10,5 +10,6 @@ import java.util.List;
@Setter
public class ReviewRelevanceRequest {
private String reviewId;
private String projectId;
private List<String> testCaseIds = new ArrayList<>();
}

View File

@ -3,6 +3,7 @@ package io.metersphere.track.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper;
import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper;
import io.metersphere.commons.constants.TestCaseReviewStatus;
@ -20,6 +21,7 @@ import io.metersphere.service.UserService;
import io.metersphere.track.dto.TestCaseReviewDTO;
import io.metersphere.track.dto.TestReviewCaseDTO;
import io.metersphere.track.dto.TestReviewDTOWithMetric;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testreview.QueryCaseReviewRequest;
import io.metersphere.track.request.testreview.QueryTestReviewRequest;
import io.metersphere.track.request.testreview.ReviewRelevanceRequest;
@ -32,6 +34,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
@ -66,7 +69,8 @@ public class TestCaseReviewService {
TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper;
@Resource
MailService mailService;
@Resource
ExtTestCaseMapper extTestCaseMapper;
public void saveTestCaseReview(SaveTestCaseReviewRequest reviewRequest) {
checkCaseReviewExist(reviewRequest);
@ -300,6 +304,16 @@ public class TestCaseReviewService {
if (testCaseIds.isEmpty()) {
return;
}
// 如果是关联全部指令则从新查询未关联的案例
if (testCaseIds.get(0).equals("all")) {
QueryTestCaseRequest req = new QueryTestCaseRequest();
req.setReviewId(request.getReviewId());
req.setProjectId(request.getProjectId());
List<TestCase> testCases = extTestCaseMapper.getTestCaseByNotInReview(req);
if (!testCases.isEmpty()) {
testCaseIds = testCases.stream().map(testCase -> testCase.getId()).collect(Collectors.toList());
}
}
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseReviewTestCaseMapper batchMapper = sqlSession.getMapper(TestCaseReviewTestCaseMapper.class);

View File

@ -190,46 +190,24 @@ public class TestCaseService {
* @return
*/
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
if (StringUtils.isNotBlank(request.getPlanId())) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getPlanId());
// request 传入要查询的 projectId 切换的项目ID
}
List<TestCase> testCaseNames = extTestCaseMapper.getTestCaseNames(request);
if (StringUtils.isNotBlank(request.getPlanId())) {
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(request.getPlanId());
List<String> relevanceIds = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample).stream()
.map(TestPlanTestCase::getCaseId)
.collect(Collectors.toList());
return testCaseNames.stream()
.filter(testcase -> !relevanceIds.contains(testcase.getId()))
.collect(Collectors.toList());
}
return testCaseNames;
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
return extTestCaseMapper.getTestCaseByNotInPlan(request);
}
public List<TestCase> getReviewCase(QueryTestCaseRequest request) {
List<TestCase> testCases = extTestCaseMapper.getTestCaseNames(request);
if (StringUtils.isNotBlank(request.getReviewId())) {
TestCaseReviewTestCaseExample testCaseReviewTestCaseExample = new TestCaseReviewTestCaseExample();
testCaseReviewTestCaseExample.createCriteria().andReviewIdEqualTo(request.getReviewId());
List<String> relevanceIds = testCaseReviewTestCaseMapper.selectByExample(testCaseReviewTestCaseExample).stream()
.map(TestCaseReviewTestCase::getCaseId)
.collect(Collectors.toList());
return testCases.stream()
.filter(testcase -> !relevanceIds.contains(testcase.getId()))
.collect(Collectors.toList());
}
return testCases;
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
// 对模板导入的测试用例排序
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
return extTestCaseMapper.getTestCaseByNotInReview(request);
}

View File

@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.commons.constants.TestPlanStatus;
@ -24,6 +25,7 @@ import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
@ -78,6 +80,8 @@ public class TestPlanService {
TestPlanProjectService testPlanProjectService;
@Resource
ProjectMapper projectMapper;
@Resource
ExtTestCaseMapper extTestCaseMapper;
public void addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -207,6 +211,16 @@ public class TestPlanService {
return;
}
// 如果是关联全部指令则从新查询未关联的案例
if (testCaseIds.get(0).equals("all")) {
QueryTestCaseRequest req = new QueryTestCaseRequest();
req.setPlanId(request.getPlanId());
req.setProjectId(request.getProjectId());
List<TestCase> testCases = extTestCaseMapper.getTestCaseByNotInPlan(req);
if (!testCases.isEmpty()) {
testCaseIds = testCases.stream().map(testCase -> testCase.getId()).collect(Collectors.toList());
}
}
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andIdIn(testCaseIds);

View File

@ -83,6 +83,7 @@
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testCaseRelevance.search();
this.getNodeTreeByPlanId();
},
initData() {

View File

@ -11,7 +11,9 @@
<el-container class="main-content">
<el-aside class="tree-aside" width="250px">
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName : $t('test_track.switch_project') }}</el-link>
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
$t('test_track.switch_project') }}
</el-link>
<node-tree class="node-tree"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
@ -21,11 +23,13 @@
<el-container>
<el-main class="case-content">
<ms-table-header :condition.sync="condition" @search="getCaseNames" title="" :show-create="false"/>
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
<el-table
:data="testCases"
@filter-change="filter"
row-key="id"
@mouseleave.passive="leave"
v-el-table-infinite-scroll="loadData"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="50vh"
@ -63,7 +67,9 @@
</template>
</el-table-column>
</el-table>
<div style="text-align: center"> {{testCases.length}} </div>
<div v-if="!endStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
<div style="text-align: center"> {{total}} </div>
</el-main>
</el-container>
</el-container>
@ -91,6 +97,7 @@
import MsTableHeader from "../../../../common/components/MsTableHeader";
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import SwitchProject from "../../../case/components/SwitchProject";
import elTableInfiniteScroll from 'el-table-infinite-scroll';
export default {
name: "TestCaseRelevance",
@ -104,6 +111,9 @@
MsTableHeader,
SwitchProject
},
directives: {
'el-table-infinite-scroll': elTableInfiniteScroll
},
data() {
return {
result: {},
@ -117,6 +127,10 @@
projectId: '',
projectName: '',
projects: [],
pageSize: 50,
currentPage: 1,
total: 0,
endStatus: true,
condition: {
components: TEST_CASE_CONFIGS
},
@ -140,12 +154,15 @@
},
watch: {
planId() {
this.initData();
this.condition.planId = this.planId;
},
selectNodeIds() {
this.getCaseNames();
if (this.dialogFormVisible) {
this.search();
}
},
projectId() {
this.condition.projectId = this.projectId;
this.getProjectNode();
}
},
@ -155,13 +172,17 @@
methods: {
openTestCaseRelevanceDialog() {
this.getProject();
this.initData();
this.dialogFormVisible = true;
},
saveCaseRelevance() {
let param = {};
param.planId = this.planId;
param.testCaseIds = [...this.selectIds];
param.projectId = this.projectId;
//
if (this.testCases.length === param.testCaseIds.length) {
param.testCaseIds = ['all'];
}
this.result = this.$post('/test/plan/relevance', param, () => {
this.selectIds.clear();
this.$success(this.$t('commons.save_success'));
@ -169,25 +190,34 @@
this.$emit('refresh');
});
},
getCaseNames() {
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
search() {
this.currentPage = 1;
this.testCases = [];
this.getTestCases();
},
getTestCases() {
if (this.planId) {
// param.planId = this.planId;
this.condition.planId = this.planId;
}
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
// param.nodeIds = this.selectNodeIds;
this.condition.nodeIds = this.selectNodeIds;
} else {
this.condition.nodeIds = [];
}
if (this.projectId) {
this.condition.projectId = this.projectId;
this.result = this.$post('/test/case/name', this.condition, response => {
this.testCases = response.data;
this.testCases.forEach(item => {
this.result = this.$post(this.buildPagePath('/test/case/name'), this.condition, response => {
let data = response.data;
this.total = data.itemCount;
let tableData = data.listObject;
tableData.forEach(item => {
item.checked = false;
});
this.testCases = this.testCases.concat(tableData);
this.endStatus = tableData.length === 50 && this.testCases.length < this.total;
});
}
@ -198,7 +228,6 @@
this.selectIds.add(item.id);
});
} else {
// this.selectIds.clear();
this.testCases.forEach(item => {
if (this.selectIds.has(item.id)) {
this.selectIds.delete(item.id);
@ -217,32 +246,37 @@
this.selectNodeIds = nodeIds;
this.selectNodeNames = nodeNames;
},
initData() {
this.getCaseNames();
this.getAllNodeTreeByPlanId();
},
refresh() {
this.close();
},
loadData() {
if (this.dialogFormVisible) {
if (this.endStatus) {
this.currentPage += 1;
this.getTestCases();
}
}
},
getAllNodeTreeByPlanId() {
if (this.planId) {
let param = {
testPlanId: this.planId,
projectId: this.projectId
};
this.result = this.$post("/case/node/list/all/plan", param , response => {
this.result = this.$post("/case/node/list/all/plan", param, response => {
this.treeNodes = response.data;
});
}
},
close() {
this.endStatus = false;
this.selectIds.clear();
this.selectNodeIds = [];
this.selectNodeNames = [];
},
filter(filters) {
_filter(filters, this.condition);
this.initData();
this.search();
},
toggleSelection(rows) {
rows.forEach(row => {
@ -256,7 +290,7 @@
},
getProject() {
if (this.planId) {
this.$post("/test/plan/project/", {planId: this.planId},res => {
this.$post("/test/plan/project/", {planId: this.planId}, res => {
let data = res.data;
if (data) {
this.projects = data;
@ -267,7 +301,7 @@
}
},
switchProject() {
this.$refs.switchProject.open({id: this.planId, url: '/test/plan/project/',type: 'plan'});
this.$refs.switchProject.open({id: this.planId, url: '/test/plan/project/', type: 'plan'});
},
getProjectNode(projectId) {
const index = this.projects.findIndex(project => project.id === projectId);
@ -278,9 +312,9 @@
this.projectId = projectId;
}
this.result = this.$post("/case/node/list/all/plan",
{testPlanId: this.planId, projectId: this.projectId} , response => {
this.treeNodes = response.data;
});
{testPlanId: this.planId, projectId: this.projectId}, response => {
this.treeNodes = response.data;
});
this.selectNodeIds = [];
}

View File

@ -88,6 +88,7 @@ export default {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testReviewRelevance.search();
this.getNodeTreeByReviewId();
},
initData() {

View File

@ -2,9 +2,7 @@
<div>
<el-dialog :title="$t('test_track.review_view.relevance_case')"
:visible.sync="dialogFormVisible"
@close="close"
<el-dialog :title="$t('test_track.review_view.relevance_case')" :visible.sync="dialogFormVisible" @close="close"
width="60%" v-loading="result.loading"
:close-on-click-modal="false"
top="50px">
@ -14,30 +12,21 @@
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
$t('test_track.switch_project') }}
</el-link>
<node-tree class="node-tree"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
:tree-nodes="treeNodes"
<node-tree class="node-tree" @nodeSelectEvent="nodeChange" @refresh="refresh" :tree-nodes="treeNodes"
ref="nodeTree"/>
</el-aside>
<el-container>
<el-main class="case-content">
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
<el-table
:data="testReviews"
v-el-table-infinite-scroll="loadData"
class="infinite-list"
@filter-change="filter"
row-key="id"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="50vh"
ref="table">
<el-table-column
type="selection"/>
<el-table :data="testReviews" @mouseleave.passive="leave" v-el-table-infinite-scroll="loadData"
@filter-change="filter" row-key="id"
@select-all="handleSelectAll"
@select="handleSelectionChange"
height="50vh"
ref="table">
<el-table-column type="selection"/>
<el-table-column
prop="name"
:label="$t('test_track.case.name')"
@ -46,6 +35,7 @@
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column
prop="priority"
:filters="priorityFilters"
@ -56,6 +46,7 @@
<priority-table-item :value="scope.row.priority"/>
</template>
</el-table-column>
<el-table-column
prop="type"
:filters="typeFilters"
@ -66,6 +57,7 @@
<type-table-item :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column
:filters="statusFilters"
column-key="status"
@ -75,7 +67,9 @@
<status-table-item :value="scope.row.reviewStatus"/>
</template>
</el-table-column>
</el-table>
<div v-if="!endStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
<div style="text-align: center"> {{total}} </div>
</el-main>
</el-container>
@ -129,7 +123,6 @@
dialogFormVisible: false,
isCheckAll: false,
testReviews: [],
tableData: [],
selectIds: new Set(),
treeNodes: [],
selectNodeIds: [],
@ -169,12 +162,15 @@
},
watch: {
reviewId() {
this.initData();
this.condition.reviewId = this.reviewId;
},
selectNodeIds() {
this.search();
if (this.dialogFormVisible) {
this.search();
}
},
projectId() {
this.condition.projectId = this.projectId;
this.getProjectNode();
}
},
@ -184,13 +180,17 @@
methods: {
openTestReviewRelevanceDialog() {
this.getProject();
this.initData();
this.dialogFormVisible = true;
},
saveReviewRelevance() {
let param = {};
param.reviewId = this.reviewId;
param.testCaseIds = [...this.selectIds];
param.projectId = this.projectId;
//
if (this.testReviews.length === param.testCaseIds.length) {
param.testCaseIds = ['all'];
}
this.result = this.$post('/test/case/review/relevance', param, () => {
this.selectIds.clear();
this.$success(this.$t('commons.save_success'));
@ -210,18 +210,18 @@
} else {
this.condition.nodeIds = [];
}
if (this.projectId) {
this.condition.projectId = this.projectId;
this.result = this.$post(this.buildPagePath('/test/case/reviews/case'), this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.endStatus = this.tableData.length === 50;
this.tableData.forEach(item => {
let tableData = data.listObject;
tableData.forEach(item => {
item.checked = false;
});
this.testReviews = this.testReviews.concat(this.tableData);
this.testReviews = this.testReviews.concat(tableData);
this.endStatus = tableData.length === 50 && this.testReviews.length < this.total;
});
}
@ -251,11 +251,6 @@
this.selectNodeIds = nodeIds;
this.selectNodeNames = nodeNames;
},
initData() {
// this.testReviews=[];
// this.getReviews();
// this.getAllNodeTreeByPlanId();
},
refresh() {
this.close();
},
@ -275,7 +270,6 @@
this.selectIds.clear();
this.selectNodeIds = [];
this.selectNodeNames = [];
this.tableData = [];
},
filter(filters) {
_filter(filters, this.condition);
@ -308,11 +302,9 @@
},
loadData() {
if (this.dialogFormVisible) {
if (this.endStatus === true) {
if (this.endStatus) {
this.currentPage += 1;
this.getReviews();
} else {
this.$message.warning(this.$t('test_track.review_view.last_page'));
}
}
},