feat(测试跟踪): 用例评审

This commit is contained in:
shiziyuan9527 2020-09-19 23:52:09 +08:00
parent b9579d61a0
commit ef33833794
9 changed files with 209 additions and 28 deletions

View File

@ -0,0 +1,27 @@
package io.metersphere.track.controller;
import io.metersphere.base.domain.TestCaseComment;
import io.metersphere.track.request.testreview.SaveCommentRequest;
import io.metersphere.track.service.TestCaseCommentService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("/test/case/comment")
@RestController
public class TestCaseCommentController {
@Resource
TestCaseCommentService testCaseCommentService;
@PostMapping("/save")
public void saveComment(@RequestBody SaveCommentRequest request) {
testCaseCommentService.saveComment(request);
}
@GetMapping("/list/{caseId}")
public List<TestCaseComment> getComments(@PathVariable String caseId) {
return testCaseCommentService.getComments(caseId);
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.track.request.testreview;
import io.metersphere.base.domain.TestCaseComment;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SaveCommentRequest extends TestCaseComment {
}

View File

@ -0,0 +1,49 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.TestCaseComment;
import io.metersphere.base.domain.TestCaseCommentExample;
import io.metersphere.base.domain.User;
import io.metersphere.base.mapper.TestCaseCommentMapper;
import io.metersphere.base.mapper.UserMapper;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.request.testreview.SaveCommentRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestCaseCommentService {
@Resource
TestCaseCommentMapper testCaseCommentMapper;
@Resource
UserMapper userMapper;
public void saveComment(SaveCommentRequest request) {
TestCaseComment testCaseComment = new TestCaseComment();
testCaseComment.setId(UUID.randomUUID().toString());
testCaseComment.setAuthor(SessionUtils.getUser().getId());
testCaseComment.setCaseId(request.getCaseId());
testCaseComment.setCreateTime(System.currentTimeMillis());
testCaseComment.setUpdateTime(System.currentTimeMillis());
testCaseComment.setDescription(request.getDescription());
testCaseCommentMapper.insert(testCaseComment);
}
public List<TestCaseComment> getComments(String caseId) {
TestCaseCommentExample testCaseCommentExample = new TestCaseCommentExample();
testCaseCommentExample.setOrderByClause("update_time desc");
testCaseCommentExample.createCriteria().andCaseIdEqualTo(caseId);
List<TestCaseComment> testCaseComments = testCaseCommentMapper.selectByExampleWithBLOBs(testCaseCommentExample);
testCaseComments.forEach(testCaseComment -> {
String authorId = testCaseComment.getAuthor();
User user = userMapper.selectByPrimaryKey(authorId);
testCaseComment.setAuthor(user.getName());
});
return testCaseComments;
}
}

View File

@ -2,7 +2,9 @@ package io.metersphere.track.service;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseReviewTestCaseMapper; import io.metersphere.base.mapper.TestCaseReviewTestCaseMapper;
import io.metersphere.base.mapper.TestCaseReviewUsersMapper;
import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper; import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.controller.request.member.QueryMemberRequest;
@ -28,6 +30,8 @@ public class TestReviewTestCaseService {
UserService userService; UserService userService;
@Resource @Resource
TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper; TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper;
@Resource
TestCaseReviewUsersMapper testCaseReviewUsersMapper;
public List<TestReviewCaseDTO> list(QueryCaseReviewRequest request) { public List<TestReviewCaseDTO> list(QueryCaseReviewRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -37,11 +41,33 @@ public class TestReviewTestCaseService {
Map<String, String> userMap = userService.getMemberList(queryMemberRequest) Map<String, String> userMap = userService.getMemberList(queryMemberRequest)
.stream().collect(Collectors.toMap(User::getId, User::getName)); .stream().collect(Collectors.toMap(User::getId, User::getName));
list.forEach(item -> { list.forEach(item -> {
item.setReviewerName(userMap.get(item.getReviewer())); String reviewId = item.getReviewId();
List<String> userIds = getReviewUserIds(reviewId);
item.setReviewerName(getReviewName(userIds, userMap));
}); });
return list; return list;
} }
private List<String> getReviewUserIds(String reviewId) {
TestCaseReviewUsersExample testCaseReviewUsersExample = new TestCaseReviewUsersExample();
testCaseReviewUsersExample.createCriteria().andReviewIdEqualTo(reviewId);
List<TestCaseReviewUsers> testCaseReviewUsers = testCaseReviewUsersMapper.selectByExample(testCaseReviewUsersExample);
return testCaseReviewUsers.stream().map(TestCaseReviewUsers::getUserId).collect(Collectors.toList());
}
private String getReviewName(List<String> userIds, Map userMap) {
StringBuilder stringBuilder = new StringBuilder();
String name = "";
if (userIds.size() > 0) {
for (String id : userIds) {
stringBuilder.append(userMap.get(id)).append("");
}
name = stringBuilder.toString().substring(0, stringBuilder.length() - 1);
}
return name;
}
public int deleteTestCase(String id) { public int deleteTestCase(String id) {
return testCaseReviewTestCaseMapper.deleteByPrimaryKey(id); return testCaseReviewTestCaseMapper.deleteByPrimaryKey(id);
} }
@ -53,6 +79,15 @@ public class TestReviewTestCaseService {
} }
public void editTestCase(TestCaseReviewTestCase testCaseReviewTestCase) { public void editTestCase(TestCaseReviewTestCase testCaseReviewTestCase) {
String currentUserId = SessionUtils.getUser().getId();
String reviewId = testCaseReviewTestCase.getReviewId();
TestCaseReviewUsersExample testCaseReviewUsersExample = new TestCaseReviewUsersExample();
testCaseReviewUsersExample.createCriteria().andReviewIdEqualTo(reviewId);
List<TestCaseReviewUsers> testCaseReviewUsers = testCaseReviewUsersMapper.selectByExample(testCaseReviewUsersExample);
List<String> reviewIds = testCaseReviewUsers.stream().map(TestCaseReviewUsers::getUserId).collect(Collectors.toList());
if (!reviewIds.contains(currentUserId)) {
MSException.throwException("非此用例的评审人员!");
}
testCaseReviewTestCase.setStatus(testCaseReviewTestCase.getStatus()); testCaseReviewTestCase.setStatus(testCaseReviewTestCase.getStatus());
testCaseReviewTestCase.setReviewer(SessionUtils.getUser().getId()); testCaseReviewTestCase.setReviewer(SessionUtils.getUser().getId());
testCaseReviewTestCase.setUpdateTime(System.currentTimeMillis()); testCaseReviewTestCase.setUpdateTime(System.currentTimeMillis());

View File

@ -1,20 +1,28 @@
<template> <template>
<div> <div v-loading="result.loading">
<div class="main"> <div style="height: 60vh;overflow-y: scroll">
<review-comment-item/> <review-comment-item v-for="(comment,index) in comments" :key="index" :comment="comment"/>
<div v-if="comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 20px;color: #8a8b8d;">
<span style="font-size: 20px; color: #8a8b8d;">
暂无评论
</span>
</i>
</div>
</div> </div>
<div> <div>
<el-input <el-input
type="textarea" type="textarea"
placeholder="发表评论" placeholder="发表评论Ctrl+Enter发送"
v-model="textarea" v-model="textarea"
maxlength="60" maxlength="60"
show-word-limit show-word-limit
resize="none" resize="none"
:autosize="{ minRows: 4, maxRows: 4}" :autosize="{ minRows: 4, maxRows: 4}"
@keyup.ctrl.enter.native="sendComment"
> >
</el-input> </el-input>
<el-button type="primary" size="mini" class="send-btn">发送</el-button> <el-button type="primary" size="mini" class="send-btn" @click="sendComment">发送</el-button>
</div> </div>
</div> </div>
</template> </template>
@ -25,19 +33,37 @@ import ReviewCommentItem from "./ReviewCommentItem";
export default { export default {
name: "ReviewComment", name: "ReviewComment",
components: {ReviewCommentItem}, components: {ReviewCommentItem},
props: {
caseId: String,
comments: Array
},
data() { data() {
return { return {
commentData: [], result: {},
textarea: '' textarea: '',
} }
},
methods: {
sendComment() {
let comment = {};
comment.caseId = this.caseId;
comment.description = this.textarea;
if (!this.textarea) {
this.$warning("评论内容不能为空!");
return;
}
this.$post('/test/case/comment/save', comment, () => {
this.$success("评论成功!");
this.$emit('getComments');
this.textarea = '';
});
},
} }
} }
</script> </script>
<style scoped> <style scoped>
.main {
height: 60vh;
}
.send-btn { .send-btn {
float: right; float: right;
margin-top: 5px; margin-top: 5px;

View File

@ -1,36 +1,51 @@
<template> <template>
<div style="height: 60px;"> <div class="main">
<div class="comment-left"> <div class="comment-left">
<span> <span>
<i class="el-icon-user-solid review-comment-user"/> <i class="el-icon-user-solid review-comment-user"/>
</span> </span>
</div> </div>
<div class="comment-right"> <div class="comment-right">
张三 <span style="color: #8a8b8d">9月8日</span><br/> <span style="font-size: 16px;">{{comment.author}}</span>
<span>优先级为2</span> <span style="color: #8a8b8d; margin-left: 8px; font-size: 12px">
{{comment.createTime | timestampFormatDate}}
</span>
<br/>
<div style="word-wrap:break-word; word-break:break-all;">{{comment.description}}</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "ReviewCommentItem" name: "ReviewCommentItem",
props: {
comment: Object
},
data() {
return {}
}
} }
</script> </script>
<style scoped> <style scoped>
.main {
overflow-y: scroll
}
.review-comment-user { .review-comment-user {
font-size: 19px; font-size: 19px;
} }
.comment-left { .comment-left {
float: left; float: left;
width: 10%; width: 10%;
height: 100%; height: 100%;
} }
.comment-right { .comment-right {
float: left; float: left;
width: 90%; width: 90%;
height: 100%;
padding: 0; padding: 0;
line-height: 25px; line-height: 25px;
} }

View File

@ -187,8 +187,8 @@ export default {
} }
}); });
}, },
intoReview() { intoReview(row) {
this.$router.push('/track/review/view/' + row.id);
}, },
testCaseReviewCreate() { testCaseReviewCreate() {
this.$emit('openCaseReviewEditDialog'); this.$emit('openCaseReviewEditDialog');

View File

@ -132,7 +132,7 @@
:default-sort="{prop: 'num', order: 'ascending'}" :default-sort="{prop: 'num', order: 'ascending'}"
highlight-current-row> highlight-current-row>
<el-table-column :label="$t('test_track.case.number')" prop="num" <el-table-column :label="$t('test_track.case.number')" prop="num"
min-width="5%"></el-table-column> min-width="5%"/>
<el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="21%"> <el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="21%">
<template v-slot:default="scope"> <template v-slot:default="scope">
@ -216,7 +216,7 @@
<el-card> <el-card>
<el-tabs class="system-setting" v-model="activeName"> <el-tabs class="system-setting" v-model="activeName">
<el-tab-pane label="评论" name="comment"> <el-tab-pane label="评论" name="comment">
<review-comment/> <review-comment :comments="comments" :case-id="testCase.caseId" @getComments="getComments"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>
@ -263,7 +263,8 @@ export default {
activeTab: 'detail', activeTab: 'detail',
isFailure: true, isFailure: true,
users: [], users: [],
activeName: 'comment' activeName: 'comment',
comments: []
}; };
}, },
props: { props: {
@ -290,8 +291,8 @@ export default {
saveCase(status) { saveCase(status) {
let param = {}; let param = {};
param.id = this.testCase.id; param.id = this.testCase.id;
param.reviewId = this.testCase.reviewId;
param.status = status; param.status = status;
this.$post('/test/review/case/edit', param, () => { this.$post('/test/review/case/edit', param, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.updateTestCases(param); this.updateTestCases(param);
@ -334,6 +335,7 @@ export default {
this.activeTab = 'detail'; this.activeTab = 'detail';
listenGoBack(this.handleClose); listenGoBack(this.handleClose);
this.initData(testCase); this.initData(testCase);
this.getComments(testCase);
}, },
initTest() { initTest() {
this.$nextTick(() => { this.$nextTick(() => {
@ -360,6 +362,17 @@ export default {
saveReport(reportId) { saveReport(reportId) {
this.$post('/test/plan/case/edit', {id: this.testCase.id, reportId: reportId}); this.$post('/test/plan/case/edit', {id: this.testCase.id, reportId: reportId});
}, },
getComments(testCase) {
let id = '';
if (testCase) {
id = testCase.caseId;
} else {
id = this.testCase.caseId;
}
this.result = this.$get('/test/case/comment/list/' + id, res => {
this.comments = res.data;
})
},
initData(testCase) { initData(testCase) {
this.result = this.$post('/test/review/case/list/all', this.searchParam, response => { this.result = this.$post('/test/review/case/list/all', this.searchParam, response => {
this.testCases = response.data; this.testCases = response.data;
@ -374,7 +387,6 @@ export default {
}, },
getRelatedTest() { getRelatedTest() {
if (this.testCase.method == 'auto' && this.testCase.testId && this.testCase.testId != 'other') { if (this.testCase.method == 'auto' && this.testCase.testId && this.testCase.testId != 'other') {
console.log(this.testCase.type)
this.$get('/' + this.testCase.type + '/get/' + this.testCase.testId, response => { this.$get('/' + this.testCase.type + '/get/' + this.testCase.testId, response => {
let data = response.data; let data = response.data;
if (data) { if (data) {

View File

@ -37,7 +37,7 @@
type="selection"/> type="selection"/>
<el-table-column width="40" :resizable="false" align="center"> <el-table-column width="40" :resizable="false" align="center">
<template v-slot:default="scope"> <template v-slot:default="scope">
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/> <!-- <show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/>-->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -128,7 +128,9 @@
<el-table-column <el-table-column
prop="reviewerName" prop="reviewerName"
:label="$t('test_track.plan_view.executor')"> label="评审人"
show-overflow-tooltip
>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -231,7 +233,7 @@ export default {
pageSize: 10, pageSize: 10,
total: 0, total: 0,
selectRows: new Set(), selectRows: new Set(),
testPlan: {}, testReview: {},
isReadOnly: false, isReadOnly: false,
isTestManagerOrTestUser: false, isTestManagerOrTestUser: false,
priorityFilters: [ priorityFilters: [
@ -466,7 +468,7 @@ export default {
getTestReviewById() { getTestReviewById() {
if (this.reviewId) { if (this.reviewId) {
this.$post('/test/case/review/get/' + this.reviewId, {}, response => { this.$post('/test/case/review/get/' + this.reviewId, {}, response => {
this.testPlan = response.data; this.testReview = response.data;
this.refreshTestReviewRecent(); this.refreshTestReviewRecent();
}); });
} }
@ -506,7 +508,11 @@ export default {
}); });
}, },
startReview() { startReview() {
if (this.tableData.length !== 0) {
this.$refs.testReviewTestCaseEdit.openTestCaseEdit(this.tableData[0]);
} else {
this.$warning("没有关联的评审!");
}
} }
} }
} }