diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.java new file mode 100644 index 0000000000..071827642b --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.java @@ -0,0 +1,14 @@ +package io.metersphere.base.mapper.ext; + +import io.metersphere.track.dto.TestCaseReviewDTO; +import io.metersphere.track.request.testreview.QueryCaseReviewRequest; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtTestCaseReviewMapper { + + List list(@Param("request") QueryCaseReviewRequest params); + + List listByWorkspaceId(@Param("workspaceId") String workspaceId); +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml new file mode 100644 index 0000000000..7c1d2986ce --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/constants/TestCaseReviewStatus.java b/backend/src/main/java/io/metersphere/commons/constants/TestCaseReviewStatus.java new file mode 100644 index 0000000000..2903628417 --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/constants/TestCaseReviewStatus.java @@ -0,0 +1,5 @@ +package io.metersphere.commons.constants; + +public enum TestCaseReviewStatus { + Prepare, Underway, Completed +} diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java new file mode 100644 index 0000000000..211c63a4f9 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java @@ -0,0 +1,73 @@ +package io.metersphere.track.controller; + +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.metersphere.base.domain.Project; +import io.metersphere.base.domain.TestCaseReview; +import io.metersphere.base.domain.User; +import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.commons.utils.PageUtils; +import io.metersphere.commons.utils.Pager; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.track.dto.TestCaseReviewDTO; +import io.metersphere.track.request.testreview.QueryCaseReviewRequest; +import io.metersphere.track.request.testreview.SaveTestCaseReviewRequest; +import io.metersphere.track.service.TestCaseReviewService; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import java.util.List; + +@RequestMapping("/test/case/review") +@RestController +public class TestCaseReviewController { + + @Resource + TestCaseReviewService testCaseReviewService; + + @PostMapping("/list/{goPage}/{pageSize}") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryCaseReviewRequest request) { + Page page = PageHelper.startPage(goPage, pageSize, true); + return PageUtils.setPageInfo(page, testCaseReviewService.listCaseReview(request)); + } + + @PostMapping("/save") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public void saveCaseReview(@RequestBody SaveTestCaseReviewRequest reviewRequest) { + testCaseReviewService.saveTestCaseReview(reviewRequest); + } + + @PostMapping("/project") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public List getProjectByReviewId(@RequestBody TestCaseReview request) { + return testCaseReviewService.getProjectByReviewId(request); + } + + @PostMapping("/reviewer") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public List getUserByReviewId(@RequestBody TestCaseReview request) { + return testCaseReviewService.getUserByReviewId(request); + } + + @GetMapping("/recent/{count}") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public List recentTestPlans(@PathVariable int count) { + String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); + PageHelper.startPage(1, count, true); + return testCaseReviewService.recent(currentWorkspaceId); + } + + @PostMapping("/edit") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public void editCaseReview(@RequestBody TestCaseReview testCaseReview) { + testCaseReviewService.editCaseReview(testCaseReview); + } + + @GetMapping("/delete/{reviewId}") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public void deleteCaseReview(@PathVariable String reviewId) { + testCaseReviewService.deleteCaseReview(reviewId); + } +} diff --git a/backend/src/main/java/io/metersphere/track/dto/TestCaseReviewDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestCaseReviewDTO.java new file mode 100644 index 0000000000..5e85f82850 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/dto/TestCaseReviewDTO.java @@ -0,0 +1,13 @@ +package io.metersphere.track.dto; + +import io.metersphere.base.domain.TestCaseReview; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TestCaseReviewDTO extends TestCaseReview { + + private String projectName; + private String reviewerName; +} diff --git a/backend/src/main/java/io/metersphere/track/request/testreview/QueryCaseReviewRequest.java b/backend/src/main/java/io/metersphere/track/request/testreview/QueryCaseReviewRequest.java new file mode 100644 index 0000000000..d93f0c54c7 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/request/testreview/QueryCaseReviewRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.track.request.testreview; + +import io.metersphere.base.domain.TestCaseReview; +import io.metersphere.controller.request.OrderRequest; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class QueryCaseReviewRequest extends TestCaseReview { + private List orders; +} diff --git a/backend/src/main/java/io/metersphere/track/request/testreview/SaveTestCaseReviewRequest.java b/backend/src/main/java/io/metersphere/track/request/testreview/SaveTestCaseReviewRequest.java new file mode 100644 index 0000000000..32efe54542 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/request/testreview/SaveTestCaseReviewRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.track.request.testreview; + +import io.metersphere.base.domain.TestCaseReview; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class SaveTestCaseReviewRequest extends TestCaseReview { + private List projectIds; + private List userIds; +} diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java new file mode 100644 index 0000000000..acf0e76f53 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java @@ -0,0 +1,150 @@ +package io.metersphere.track.service; + +import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.*; +import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper; +import io.metersphere.commons.constants.TestCaseReviewStatus; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.ServiceUtils; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.track.dto.TestCaseReviewDTO; +import io.metersphere.track.request.testreview.QueryCaseReviewRequest; +import io.metersphere.track.request.testreview.SaveTestCaseReviewRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.annotation.Resource; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestCaseReviewService { + + @Resource + private TestCaseReviewProjectMapper testCaseReviewProjectMapper; + @Resource + private TestCaseReviewUsersMapper testCaseReviewUsersMapper; + @Resource + private TestCaseReviewMapper testCaseReviewMapper; + @Resource + private ExtTestCaseReviewMapper extTestCaseReviewMapper; + @Resource + private ProjectMapper projectMapper; + @Resource + private UserMapper userMapper; + + public void saveTestCaseReview(SaveTestCaseReviewRequest reviewRequest) { + checkCaseReviewExist(reviewRequest); + + String reviewId = UUID.randomUUID().toString(); + List projectIds = reviewRequest.getProjectIds(); + List userIds = reviewRequest.getUserIds(); + + projectIds.forEach(projectId -> { + TestCaseReviewProject testCaseReviewProject = new TestCaseReviewProject(); + testCaseReviewProject.setProjectId(projectId); + testCaseReviewProject.setReviewId(reviewId); + testCaseReviewProjectMapper.insertSelective(testCaseReviewProject); + }); + + userIds.forEach(userId -> { + TestCaseReviewUsers testCaseReviewUsers = new TestCaseReviewUsers(); + testCaseReviewUsers.setReviewId(reviewId); + testCaseReviewUsers.setUserId(userId); + testCaseReviewUsersMapper.insert(testCaseReviewUsers); + }); + + reviewRequest.setId(reviewId); + reviewRequest.setCreateTime(System.currentTimeMillis()); + reviewRequest.setUpdateTime(System.currentTimeMillis()); + reviewRequest.setCreator(SessionUtils.getUser().getId()); + reviewRequest.setStatus(TestCaseReviewStatus.Prepare.name()); + testCaseReviewMapper.insert(reviewRequest); + } + + public List listCaseReview(QueryCaseReviewRequest request) { + request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); + return extTestCaseReviewMapper.list(request); + } + + public List getProjectByReviewId(TestCaseReview request) { + String reviewId = request.getId(); + + TestCaseReviewProjectExample testCaseReviewProjectExample = new TestCaseReviewProjectExample(); + testCaseReviewProjectExample.createCriteria().andReviewIdEqualTo(reviewId); + List testCaseReviewProject = testCaseReviewProjectMapper.selectByExample(testCaseReviewProjectExample); + + List projectIds = testCaseReviewProject + .stream() + .map(TestCaseReviewProject::getProjectId) + .collect(Collectors.toList()); + + ProjectExample projectExample = new ProjectExample(); + projectExample.createCriteria().andIdIn(projectIds); + return projectMapper.selectByExample(projectExample); + } + + public List getUserByReviewId(TestCaseReview request) { + String reviewId = request.getId(); + + TestCaseReviewUsersExample testCaseReviewUsersExample = new TestCaseReviewUsersExample(); + testCaseReviewUsersExample.createCriteria().andReviewIdEqualTo(reviewId); + List testCaseReviewUsers = testCaseReviewUsersMapper.selectByExample(testCaseReviewUsersExample); + + List userIds = testCaseReviewUsers + .stream() + .map(TestCaseReviewUsers::getUserId) + .collect(Collectors.toList()); + + UserExample userExample = new UserExample(); + userExample.createCriteria().andIdIn(userIds); + return userMapper.selectByExample(userExample); + } + + public List recent(String currentWorkspaceId) { + return extTestCaseReviewMapper.listByWorkspaceId(currentWorkspaceId); + } + + public void editCaseReview(TestCaseReview testCaseReview) { + testCaseReview.setUpdateTime(System.currentTimeMillis()); + checkCaseReviewExist(testCaseReview); + testCaseReviewMapper.updateByPrimaryKeySelective(testCaseReview); + } + + private void checkCaseReviewExist(TestCaseReview testCaseReview) { + if (testCaseReview.getName() != null) { + TestCaseReviewExample example = new TestCaseReviewExample(); + TestCaseReviewExample.Criteria criteria = example + .createCriteria() + .andNameEqualTo(testCaseReview.getName()); + + if (StringUtils.isNotBlank(testCaseReview.getId())) { + criteria.andIdNotEqualTo(testCaseReview.getId()); + } + + if (testCaseReviewMapper.selectByExample(example).size() > 0) { + MSException.throwException("评审名称已存在"); + } + } + } + + public void deleteCaseReview(String reviewId) { + deleteCaseReviewProject(reviewId); + deleteCaseReviewUsers(reviewId); + testCaseReviewMapper.deleteByPrimaryKey(reviewId); + } + + private void deleteCaseReviewProject(String reviewId) { + TestCaseReviewProjectExample testCaseReviewProjectExample = new TestCaseReviewProjectExample(); + testCaseReviewProjectExample.createCriteria().andReviewIdEqualTo(reviewId); + testCaseReviewProjectMapper.deleteByExample(testCaseReviewProjectExample); + } + + private void deleteCaseReviewUsers(String reviewId) { + TestCaseReviewUsersExample testCaseReviewUsersExample = new TestCaseReviewUsersExample(); + testCaseReviewUsersExample.createCriteria().andReviewIdEqualTo(reviewId); + testCaseReviewUsersMapper.deleteByExample(testCaseReviewUsersExample); + } +} diff --git a/frontend/src/business/components/common/components/MsTipButton.vue b/frontend/src/business/components/common/components/MsTipButton.vue index dfcc4003dc..d5b6030155 100644 --- a/frontend/src/business/components/common/components/MsTipButton.vue +++ b/frontend/src/business/components/common/components/MsTipButton.vue @@ -2,6 +2,7 @@ - + @@ -30,6 +30,16 @@ :title="$t('test_track.case.create_case')"/> + + + + + + + + + @@ -41,7 +51,7 @@ - + @@ -61,6 +71,7 @@ export default { testPlanViewPath: '', isRouterAlive: true, testCaseEditPath: '', + testCaseReviewEditPath: '', testCaseProjectPath: '', isProjectActivation: true, projectRecent: { @@ -82,6 +93,15 @@ export default { router: function (item) { } }, + reviewRecent: { + title: "最近的评审", + url: "/test/case/review/recent/5", + index: function (item) { + return '/test/case/review/' + item.id; + }, + router: function (item) { + } + }, planRecent: { title: this.$t('test_track.recent_plan'), url: "/test/plan/recent/5", diff --git a/frontend/src/business/components/track/review/TestCaseReview.vue b/frontend/src/business/components/track/review/TestCaseReview.vue new file mode 100644 index 0000000000..80f9c5df92 --- /dev/null +++ b/frontend/src/business/components/track/review/TestCaseReview.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue new file mode 100644 index 0000000000..7dfc0298b8 --- /dev/null +++ b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue @@ -0,0 +1,245 @@ + + + + + + diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewList.vue b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue new file mode 100644 index 0000000000..cf0afb8cc1 --- /dev/null +++ b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/frontend/src/business/components/track/router.js b/frontend/src/business/components/track/router.js index 759af75bd8..336a3fc76a 100644 --- a/frontend/src/business/components/track/router.js +++ b/frontend/src/business/components/track/router.js @@ -4,6 +4,7 @@ const TestTrack = () => import(/* webpackChunkName: "track" */ '@/business/compo 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 TestCaseReview = () => import(/* webpackChunkName: "track" */ '@/business/components/track/review/TestCaseReview') const TestPlanView = () => import(/* webpackChunkName: "track" */ '@/business/components/track/plan/view/TestPlanView') export default { @@ -53,6 +54,11 @@ export default { path: "project/:type", name: "trackProject", component: MsProject - } + }, + { + path: "review/:type", + name: "testCaseReview", + component: TestCaseReview + }, ] }