feat(功能用例): 回收站

This commit is contained in:
guoyuqi 2023-11-07 18:17:55 +08:00 committed by Craftsman
parent 516559506d
commit 61ee9d8ab0
18 changed files with 633 additions and 63 deletions

View File

@ -0,0 +1,41 @@
package io.metersphere.functional.controller;
import io.metersphere.functional.service.FunctionalCaseLogService;
import io.metersphere.functional.service.FunctionalCaseTrashService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "用例管理-功能用例-回收站")
@RestController
@RequestMapping("/functional/case/trash")
public class FunctionalCaseTrashController {
@Resource
private FunctionalCaseTrashService functionalCaseTrashService;
@GetMapping("/recover/{id}")
@Operation(summary = "用例管理-功能用例-回收站-恢复用例")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_DELETE)
@Log(type = OperationLogType.RECOVER, expression = "#msClass.recoverLog(#id)", msClass = FunctionalCaseLogService.class)
public void recoverCase(@PathVariable String id) {
functionalCaseTrashService.recoverCase(id, SessionUtils.getUserId());
}
@GetMapping("/delete/{id}")
@Operation(summary = "用例管理-功能用例-回收站-彻底删除用例")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteTrashCaseLog(#id)", msClass = FunctionalCaseLogService.class)
public void deleteCase(@PathVariable String id) {
functionalCaseTrashService.deleteCase(id);
}
}

View File

@ -18,6 +18,9 @@ public class FunctionalCaseCommentDTO extends FunctionalCaseComment {
@Schema(description = "被回复的人头像")
private String replyUserLogo;
@Schema(description = "评论的人头像")
private String userLogo;
@Schema(description = "该条评论下的所有回复数据")
private List<FunctionalCaseCommentDTO> replies;

View File

@ -28,6 +28,9 @@ public interface ExtFunctionalCaseMapper {
List<FunctionalCasePageDTO> list(@Param("request") FunctionalCasePageRequest request, @Param("deleted") boolean deleted);
void recoverCase(@Param("ids") List<String> ids, @Param("userId") String userId, @Param("time") long time);
List<String> getIds(@Param("request") TableBatchProcessDTO request, @Param("projectId") String projectId, @Param("deleted") boolean deleted);
void batchDelete(@Param("ids") List<String> ids, @Param("userId") String userId);

View File

@ -87,6 +87,19 @@
</select>
<update id="recoverCase">
UPDATE functional_case
SET deleted = false,
update_user = #{userId},
update_time = #{time},
delete_user = null,
delete_time = null
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<sql id="queryWhereCondition">
<if test="request.projectId">
and functional_case.project_id = #{request.projectId}

View File

@ -6,12 +6,22 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author guoyuqi
*/
@Data
public class FunctionalCaseModuleCreateRequest {
public class FunctionalCaseModuleCreateRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{project.id.not_blank}")
private String projectId;
@Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "{file_module.name.not_blank}")
private String name;

View File

@ -5,8 +5,18 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author guoyuqi
*/
@Data
public class FunctionalCaseModuleUpdateRequest {
public class FunctionalCaseModuleUpdateRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{file_module.id.not_blank}")
private String id;

View File

@ -30,7 +30,7 @@ public class DeleteFunctionalCaseService {
public void deleteFunctionalCaseResource(List<String> ids, String projectId) {
//TODO 删除各种关联关系 1.测试用例(接口/场景/ui/性能) 2.关联缺陷(是否需要同步) 3.关联需求(是否需要同步) 4.依赖关系 5.关联评审 6.操作记录 7.关联测试计划 8.评论 9.附件 10.自定义字段 11.用例基本信息(主表附属表) 12...?
//TODO 删除各种关联关系 1.测试用例(接口/场景/ui/性能) 2.关联缺陷(是否需要同步) 3.关联需求(是否需要同步) 4.依赖关系 5.关联评审 6.关联测试计划 7.操作记录 8.评论 9.附件 10.自定义字段 11.用例基本信息(主表附属表) 12...?
//1.刪除用例与其他用例关联关系
FunctionalCaseTestExample caseTestExample = new FunctionalCaseTestExample();
caseTestExample.createCriteria().andCaseIdIn(ids);

View File

@ -26,9 +26,6 @@ import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.apache.commons.collections.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
@ -69,7 +66,7 @@ public class FunctionalCaseCommentService {
if (StringUtils.equals(functionalCaseCommentRequest.getEvent(), NoticeConstants.Event.REPLY)) {
return saveCommentWidthReply(functionalCaseCommentRequest, functionalCaseComment, userId);
} else {
return saveCommentWidthOther(functionalCaseCommentRequest, functionalCaseComment, userId);
return saveCommentOrAt(functionalCaseCommentRequest, functionalCaseComment, userId);
}
}
@ -107,13 +104,15 @@ public class FunctionalCaseCommentService {
* @param functionalCaseComment 被组装的半份数据
* @return FunctionalCaseComment
*/
public FunctionalCaseComment saveCommentWidthOther(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment, String userId) {
public FunctionalCaseComment saveCommentOrAt(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment, String userId) {
if (StringUtils.isNotBlank(functionalCaseCommentRequest.getNotifier())) {
functionalCaseComment.setNotifier(functionalCaseCommentRequest.getNotifier());
}
functionalCaseCommentMapper.insert(functionalCaseComment);
FunctionalCaseDTO functionalCaseDTO = functionalCaseNoticeService.getFunctionalCaseDTO(functionalCaseCommentRequest);
//发送@ 通知人
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTO);
//发送系统设置的评论通知
if (StringUtils.isBlank(functionalCaseCommentRequest.getParentId()) && !StringUtils.equals(functionalCaseCommentRequest.getEvent(),NoticeConstants.Event.COMMENT)) {
functionalCaseCommentRequest.setEvent(NoticeConstants.Event.COMMENT);
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTO);
@ -128,7 +127,7 @@ public class FunctionalCaseCommentService {
* @return FunctionalCaseComment
*/
public FunctionalCaseComment saveCommentWidthReply(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment, String userId) {
setOther(functionalCaseCommentRequest, functionalCaseComment);
setReplyAndNotifier(functionalCaseCommentRequest, functionalCaseComment);
functionalCaseCommentMapper.insert(functionalCaseComment);
FunctionalCaseDTO functionalCaseDTOReply = functionalCaseNoticeService.getFunctionalCaseDTO(functionalCaseCommentRequest);
sendNotice(functionalCaseCommentRequest, userId, functionalCaseDTOReply);
@ -139,12 +138,12 @@ public class FunctionalCaseCommentService {
return functionalCaseComment;
}
private void setOther(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment) {
private void setReplyAndNotifier(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment) {
checkParentId(functionalCaseCommentRequest, functionalCaseComment);
if (StringUtils.isBlank(functionalCaseCommentRequest.getReplyUser())) {
throw new MSException(Translator.get("case_comment.reply_user_is_null"));
}
functionalCaseCommentRequest.setReplyUser(functionalCaseCommentRequest.getReplyUser());
functionalCaseComment.setReplyUser(functionalCaseCommentRequest.getReplyUser());
if (StringUtils.isNotBlank(functionalCaseCommentRequest.getNotifier())) {
functionalCaseComment.setNotifier(functionalCaseCommentRequest.getNotifier());
} else {
@ -236,37 +235,24 @@ public class FunctionalCaseCommentService {
*/
private List<FunctionalCaseCommentDTO> buildData(List<FunctionalCaseComment> functionalCaseComments, Map<String, User> userMap) {
List<FunctionalCaseCommentDTO>list = new ArrayList<>();
List<FunctionalCaseComment> rootList = functionalCaseComments.stream().filter(t -> StringUtils.isBlank(t.getParentId())).toList();
List<FunctionalCaseComment> replyList = functionalCaseComments.stream().filter(t -> StringUtils.isNotBlank(t.getParentId())).toList();
Map<String, List<FunctionalCaseComment>> commentMap = replyList.stream().collect(Collectors.groupingBy(FunctionalCaseComment::getParentId));
for (FunctionalCaseComment functionalCaseComment : rootList) {
for (FunctionalCaseComment functionalCaseComment : functionalCaseComments) {
FunctionalCaseCommentDTO functionalCaseCommentDTO = new FunctionalCaseCommentDTO();
BeanUtils.copyBean(functionalCaseCommentDTO,functionalCaseComment);
functionalCaseCommentDTO.setUserName(userMap.get(functionalCaseComment.getCreateUser()).getName());
List<FunctionalCaseComment> replyComments = commentMap.get(functionalCaseComment.getId());
if (CollectionUtils.isNotEmpty(replyComments)) {
List<FunctionalCaseCommentDTO> replies = getReplies(userMap, functionalCaseComment, replyComments);
functionalCaseCommentDTO.setReplies(replies);
if (StringUtils.isNotBlank(functionalCaseComment.getReplyUser())) {
functionalCaseCommentDTO.setReplyUserName(userMap.get(functionalCaseComment.getReplyUser()).getName());
}
list.add(functionalCaseCommentDTO);
}
return list;
}
private List<FunctionalCaseCommentDTO> getReplies(Map<String, User> userMap, FunctionalCaseComment functionalCaseComment, List<FunctionalCaseComment> replyComments) {
List<FunctionalCaseCommentDTO> replies = new ArrayList<>();
for (FunctionalCaseComment replyComment : replyComments) {
FunctionalCaseCommentDTO functionalCaseCommentDTOReply = new FunctionalCaseCommentDTO();
BeanUtils.copyBean(functionalCaseCommentDTOReply,replyComment);
functionalCaseCommentDTOReply.setUserName(userMap.get(replyComment.getCreateUser()).getName());
if (StringUtils.isBlank(replyComment.getReplyUser())) {
functionalCaseCommentDTOReply.setReplyUserName(userMap.get(functionalCaseComment.getCreateUser()).getName());
} else {
functionalCaseCommentDTOReply.setReplyUserName(userMap.get(replyComment.getReplyUser()).getName());
}
replies.add(functionalCaseCommentDTOReply);
List<FunctionalCaseCommentDTO> rootList = list.stream().filter(t -> StringUtils.isBlank(t.getParentId())).sorted(Comparator.comparing(FunctionalCaseComment::getCreateTime).reversed()).toList();
List<FunctionalCaseCommentDTO> replyList = list.stream().filter(t -> StringUtils.isNotBlank(t.getParentId())).sorted(Comparator.comparing(FunctionalCaseComment::getCreateTime).reversed()).toList();
Map<String, List<FunctionalCaseCommentDTO>> commentMap = replyList.stream().collect(Collectors.groupingBy(FunctionalCaseComment::getParentId));
for (FunctionalCaseCommentDTO functionalCaseComment : rootList) {
List<FunctionalCaseCommentDTO> replyComments = commentMap.get(functionalCaseComment.getId());
functionalCaseComment.setReplies(replyComments);
}
return replies.stream().sorted(Comparator.comparing(FunctionalCaseComment::getCreateTime).reversed()).toList();
return rootList;
}
/**
@ -322,7 +308,7 @@ public class FunctionalCaseCommentService {
}
private FunctionalCaseComment updateCommentWidthNotice(FunctionalCaseCommentRequest functionalCaseCommentRequest, FunctionalCaseComment functionalCaseComment, String userId) {
setOther(functionalCaseCommentRequest, functionalCaseComment);
setReplyAndNotifier(functionalCaseCommentRequest, functionalCaseComment);
functionalCaseCommentMapper.updateByPrimaryKeySelective(functionalCaseComment);
functionalCaseCommentRequest.setEvent(NoticeConstants.Event.AT);
FunctionalCaseDTO functionalCaseDTO = functionalCaseNoticeService.getFunctionalCaseDTO(functionalCaseCommentRequest);

View File

@ -161,4 +161,51 @@ public class FunctionalCaseLogService {
}
return dtoList;
}
/**
* 恢复项目
* @param id 接口请求参数
* @return 日志详情
*/
public LogDTO recoverLog(String id) {
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(id);
if (functionalCase != null) {
LogDTO dto = new LogDTO(
functionalCase.getProjectId(),
"",
id,
functionalCase.getCreateUser(),
OperationLogType.RECOVER.name(),
OperationLogModule.FUNCTIONAL_CASE,
functionalCase.getName());
dto.setOriginalValue(JSON.toJSONBytes(functionalCase));
return dto;
}
return null;
}
/**
* 彻底删除
* @param id 接口请求参数
* @return 日志详情
*/
public LogDTO deleteTrashCaseLog(String id) {
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(id);
if (functionalCase != null) {
LogDTO dto = new LogDTO(
functionalCase.getProjectId(),
null,
functionalCase.getId(),
functionalCase.getCreateUser(),
OperationLogType.DELETE.name(),
OperationLogModule.FUNCTIONAL_CASE,
functionalCase.getName());
dto.setPath("/functional/case/trash/delete");
dto.setMethod(HttpMethodConstants.GET.name());
dto.setOriginalValue(JSON.toJSONBytes(functionalCase));
return dto;
}
return null;
}
}

View File

@ -58,33 +58,29 @@ public class FunctionalCaseNoticeService {
String notifier = functionalCaseCommentRequest.getNotifier();
String replyUser = functionalCaseCommentRequest.getReplyUser();
if (StringUtils.equals(functionalCaseCommentRequest.getEvent(), NoticeConstants.Event.REPLY)) {
if (StringUtils.isNotBlank(replyUser)) {
if (StringUtils.isNotBlank(notifier)) {
List<String> notifierList = Arrays.asList(notifier.split(";"));
if (!notifierList.contains(replyUser)) {
functionalCaseDTO.setRelatedUsers(replyUser);
}
} else {
if (StringUtils.isNotBlank(notifier)) {
List<String> notifierList = Arrays.asList(notifier.split(";"));
if (!notifierList.contains(replyUser)) {
functionalCaseDTO.setRelatedUsers(replyUser);
}
} else {
functionalCaseDTO.setRelatedUsers(replyUser);
}
}
else {
if (StringUtils.isNotBlank(replyUser)) {
if (StringUtils.isNotBlank(replyUser) && StringUtils.isNotBlank(notifier)) {
List<String> notifierList = Arrays.asList(notifier.split(";"));
StringBuilder notifierStr = new StringBuilder();
if (StringUtils.isNotBlank(notifier)) {
List<String> notifierList = Arrays.asList(notifier.split(";"));
if (notifierList.contains(replyUser)) {
for (String notifierId : notifierList) {
if (!StringUtils.equals(notifierId, replyUser)) {
notifierStr.append(notifierId).append(";");
}
if (notifierList.contains(replyUser)) {
for (String notifierId : notifierList) {
if (!StringUtils.equals(notifierId, replyUser)) {
notifierStr.append(notifierId).append(";");
}
} else {
notifierStr = new StringBuilder(notifier);
}
functionalCaseDTO.setRelatedUsers(notifierStr.toString());
} else {
notifierStr = new StringBuilder(notifier);
}
functionalCaseDTO.setRelatedUsers(notifierStr.toString());
} else {
functionalCaseDTO.setRelatedUsers(notifier);
}

View File

@ -0,0 +1,93 @@
package io.metersphere.functional.service;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseCustomField;
import io.metersphere.functional.domain.FunctionalCaseCustomFieldExample;
import io.metersphere.functional.domain.FunctionalCaseExample;
import io.metersphere.functional.mapper.ExtFunctionalCaseMapper;
import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.CustomFieldExample;
import io.metersphere.system.mapper.CustomFieldMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* @author guoyuqi
* 功能用例回收站服务实现类
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class FunctionalCaseTrashService {
@Resource
private FunctionalCaseMapper functionalCaseMapper;
@Resource
private FunctionalCaseCustomFieldMapper functionalCaseCustomFieldMapper;
@Resource
private CustomFieldMapper customFieldMapper;
@Resource
private ExtFunctionalCaseMapper extFunctionalCaseMapper;
@Resource
private DeleteFunctionalCaseService deleteFunctionalCaseService;
/**
* 从回收站恢复用例
* @param id 用例ID
* @param userId 当前操作人
*/
public void recoverCase(String id, String userId) {
//检查并恢复所有版本
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(id);
if (functionalCase == null) {
throw new MSException(Translator.get("case_comment.case_is_null"));
}
List<String> ids = getIdsByRefId(functionalCase.getRefId());
//检查自定义字段是否还存在不存在删除关联关系
FunctionalCaseCustomFieldExample functionalCaseCustomFieldExample = new FunctionalCaseCustomFieldExample();
functionalCaseCustomFieldExample.createCriteria().andCaseIdIn(ids);
List<FunctionalCaseCustomField> functionalCaseCustomFields = functionalCaseCustomFieldMapper.selectByExample(functionalCaseCustomFieldExample);
List<String> filedIds = functionalCaseCustomFields.stream().map(FunctionalCaseCustomField::getFieldId).distinct().toList();
List<CustomField> customFields = new ArrayList<>();
if (CollectionUtils.isNotEmpty(filedIds)) {
CustomFieldExample customFieldExample = new CustomFieldExample();
customFieldExample.createCriteria().andIdIn(filedIds);
customFields = customFieldMapper.selectByExample(customFieldExample);
}
List<String> customFieldIds = customFields.stream().map(CustomField::getId).toList();
List<String> delIds = filedIds.stream().filter(t -> !customFieldIds.contains(t)).toList();
if (CollectionUtils.isNotEmpty(delIds)) {
functionalCaseCustomFieldExample = new FunctionalCaseCustomFieldExample();
functionalCaseCustomFieldExample.createCriteria().andFieldIdIn(delIds);
functionalCaseCustomFieldMapper.deleteByExample(functionalCaseCustomFieldExample);
}
extFunctionalCaseMapper.recoverCase(ids,userId,System.currentTimeMillis());
}
/**
* 从回收站彻底用例
* @param id 用例ID
*/
public void deleteCase(String id) {
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(id);
if (functionalCase == null) {
return;
}
List<String> ids = getIdsByRefId(functionalCase.getRefId());
deleteFunctionalCaseService.deleteFunctionalCaseResource(ids, functionalCase.getProjectId());
}
private List<String> getIdsByRefId(String refId) {
FunctionalCaseExample functionalCaseExample = new FunctionalCaseExample();
functionalCaseExample.createCriteria().andRefIdEqualTo(refId);
List<FunctionalCase> functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
return functionalCases.stream().map(FunctionalCase::getId).toList();
}
}

View File

@ -469,6 +469,8 @@ public class FunctionalCaseCommentControllerTests {
Assertions.assertTrue(StringUtils.equals(functionalCaseComment.getCaseId(), "xiaomeinvGTest"));
Assertions.assertTrue(StringUtils.equals(functionalCaseComment.getNotifier(), "default-project-member-user-guo;default-project-member-user-guo-4;"));
Assertions.assertTrue(StringUtils.equals(functionalCaseComment.getContent(), "评论你好哇"));
Assertions.assertTrue(StringUtils.isBlank(functionalCaseComment.getReplyUser()));
}
@Test

View File

@ -0,0 +1,91 @@
package io.metersphere.functional.controller;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseComment;
import io.metersphere.functional.mapper.FunctionalCaseCommentMapper;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.mapper.CustomFieldMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class FunctionalCaseTrashControllerTests extends BaseTest {
private static final String URL_CASE_RECOVER = "/functional/case/trash/recover/";
private static final String URL_CASE_DELETE = "/functional/case/trash/delete/";
@Resource
private FunctionalCaseMapper functionalCaseMapper;
@Resource
private CustomFieldMapper customFieldMapper;
@Resource
private FunctionalCaseCommentMapper functionalCaseCommentMapper;
@Test
@Order(1)
@Sql(scripts = {"/dml/init_case_trash.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void recoverCaseSuccessWidthCustom() throws Exception {
customFieldMapper.deleteByPrimaryKey("gyq_custom_id2");
this.requestGetWithOk(URL_CASE_RECOVER + "Trash_TEST_FUNCTIONAL_CASE_ID");
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID");
Assertions.assertFalse(functionalCase.getDeleted());
System.out.println(functionalCase.getUpdateUser());
FunctionalCase functionalCaseV = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID_4");
Assertions.assertFalse(functionalCaseV.getDeleted());
}
@Test
@Order(2)
public void recoverCaseSuccessWidthNoCustom() throws Exception {
this.requestGetWithOk(URL_CASE_RECOVER + "Trash_TEST_FUNCTIONAL_CASE_ID_1");
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID_1");
Assertions.assertFalse(functionalCase.getDeleted());
FunctionalCase functionalCase2 = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID_2");
Assertions.assertFalse(functionalCase2.getDeleted());
FunctionalCase functionalCase3 = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID_3");
Assertions.assertFalse(functionalCase3.getDeleted());
}
@Test
@Order(3)
public void recoverCaseFalse() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(URL_CASE_RECOVER + "Trash_TEST_FUNCTIONAL_CASE_ID_del").header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is5xxServerError());
}
@Test
@Order(4)
public void deleteCaseWidthNoExist() throws Exception {
this.requestGetWithOk(URL_CASE_DELETE + "Trash_TEST_FUNCTIONAL_CASE_ID_del");
}
@Test
@Order(5)
public void deleteCaseWidthSuccess() throws Exception {
this.requestGetWithOk(URL_CASE_DELETE + "Trash_TEST_FUNCTIONAL_CASE_ID");
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID");
Assertions.assertNull(functionalCase);
FunctionalCase functionalCase4 = functionalCaseMapper.selectByPrimaryKey("Trash_TEST_FUNCTIONAL_CASE_ID_4");
Assertions.assertNull(functionalCase4);
FunctionalCaseComment functionalCaseComment = functionalCaseCommentMapper.selectByPrimaryKey("trash_comment_id");
Assertions.assertNull(functionalCaseComment);
}
}

View File

@ -0,0 +1,29 @@
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID', 1, 'Trash_TEST_MOUDLE_ID', '100001100001', '100001', '回收站测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID', 'UN_EXECUTED', true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID_1', 2, 'Trash_TEST_MOUDLE_ID', '100001100001', '100001', '测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID_1', 'UN_EXECUTED', true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID_2', 3, 'Trash_TEST_MOUDLE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v2.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID_1', 'UN_EXECUTED', true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID_3', 3, 'Trash_TEST_MOUDLE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID_1', 'UN_EXECUTED', true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID_4', 4, 'Trash_TEST_MOUDLE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID', 'UN_EXECUTED',true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO functional_case_custom_field(case_id, field_id, value) VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID', 'gyq_custom_id1', '22');
INSERT INTO functional_case_custom_field(case_id, field_id, value) VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID', 'gyq_custom_id2', '33');
INSERT INTO custom_field(id, name, scene, `type`, remark, internal, scope_type, create_time, update_time, create_user, scope_id)
VALUES('gyq_custom_id1', 'functional_priority', 'FUNCTIONAL', 'SELECT', '', 1, 'ORGANIZATION', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001');
INSERT INTO custom_field(id, name, scene, `type`, remark, internal, scope_type, create_time, update_time, create_user, scope_id)
VALUES('gyq_custom_id2', 'level', 'FUNCTIONAL', 'SELECT', '', 1, 'ORGANIZATION', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', '100001');
INSERT INTO functional_case_comment(id, case_id, create_user, status, parent_id, resource_id, notifier, content, reply_user, create_time, update_time)
VALUES ('trash_comment_id', 'Trash_TEST_FUNCTIONAL_CASE_ID', 'gyq', null, null, null, 'gyq','你好', null, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);

View File

@ -64,7 +64,7 @@ public class OperationLogAspect {
private final ThreadLocal<String> localProjectId = new ThreadLocal<>();
// 此方法随时补充类型需要在内容变更前执行的类型都可以加入
private final OperationLogType[] beforeMethodNames = new OperationLogType[]{OperationLogType.UPDATE, OperationLogType.DELETE};
private final OperationLogType[] beforeMethodNames = new OperationLogType[]{OperationLogType.UPDATE, OperationLogType.DELETE, OperationLogType.RECOVER};
// 需要后置执行合并内容的
private final OperationLogType[] postMethodNames = new OperationLogType[]{OperationLogType.ADD, OperationLogType.UPDATE};

View File

@ -23,7 +23,6 @@ import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.domain.User;
import io.metersphere.system.domain.UserExample;
import io.metersphere.system.mapper.CustomFieldMapper;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.MessageDetail;
import io.metersphere.system.notice.NoticeModel;
@ -42,8 +41,6 @@ import java.util.stream.Collectors;
public abstract class AbstractNoticeSender implements NoticeSender {
@Resource
private CustomFieldMapper customFieldMapper;
@Resource
private TestPlanFollowerMapper testPlanFollowerMapper;
@Resource
@ -89,7 +86,7 @@ public abstract class AbstractNoticeSender implements NoticeSender {
}
if (CollectionUtils.isNotEmpty(fields)) {
for (Object o : fields) {
String jsonFields = JSON.toJSONString(o);
String jsonFields = JSON.toJSONString(o);
Map<?, ?> jsonObject = JSON.parseObject(jsonFields, Map.class);
String customFieldName = (String) jsonObject.get("id");
Object value = jsonObject.get("name");
@ -145,6 +142,7 @@ public abstract class AbstractNoticeSender implements NoticeSender {
// 去重复
List<String> userIds = toUsers.stream().map(Receiver::getUserId).distinct().toList();
LogUtils.error("userIds: ", JSON.toJSONString(userIds));
List<User> users = getUsers(userIds);
List<String> realUserIds = users.stream().map(User::getId).toList();
return toUsers.stream().filter(t -> realUserIds.contains(t.getUserId())).toList();
@ -153,6 +151,9 @@ public abstract class AbstractNoticeSender implements NoticeSender {
private List<Receiver> handleFollows(MessageDetail messageDetail, NoticeModel noticeModel) {
List<Receiver> receivers = new ArrayList<>();
String id = (String) noticeModel.getParamMap().get("id");
if (StringUtils.isBlank(id)) {
return receivers;
}
String taskType = messageDetail.getTaskType();
switch (taskType) {
case NoticeConstants.TaskType.TEST_PLAN_TASK -> {
@ -218,7 +219,10 @@ public abstract class AbstractNoticeSender implements NoticeSender {
protected List<User> getUsers(List<String> userIds) {
UserExample userExample = new UserExample();
userExample.createCriteria().andIdIn(userIds);
return userMapper.selectByExample(userExample);
if (CollectionUtils.isNotEmpty(userIds)) {
userExample.createCriteria().andIdIn(userIds);
return userMapper.selectByExample(userExample);
}
return new ArrayList<>();
}
}

View File

@ -1,10 +1,19 @@
package io.metersphere.system.controller;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.mapper.ApiScenarioMapper;
import io.metersphere.functional.domain.CaseReview;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseCustomField;
import io.metersphere.functional.domain.FunctionalCaseCustomFieldExample;
import io.metersphere.functional.mapper.CaseReviewMapper;
import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.load.domain.LoadTest;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
@ -46,6 +55,14 @@ public class AfterReturningNoticeSendServiceTests extends BaseTest {
private CustomFieldMapper customFieldMapper;
@Resource
private FunctionalCaseCustomFieldMapper functionalCaseCustomFieldMapper;
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ApiScenarioMapper apiScenarioMapper;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private CaseReviewMapper caseReviewMapper;
private ThreadLocal<String> source = new ThreadLocal<>();
@ -54,7 +71,8 @@ public class AfterReturningNoticeSendServiceTests extends BaseTest {
@Order(1)
@Sql(scripts = {"/dml/init_aspect.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void noticeSuccess() {
String taskType = NoticeConstants.TaskType.FUNCTIONAL_CASE_TASK;
String functionalCaseTask = NoticeConstants.TaskType.FUNCTIONAL_CASE_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey("aspect_gyq_one");
@ -99,7 +117,218 @@ public class AfterReturningNoticeSendServiceTests extends BaseTest {
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(taskType, event,resources, user, "100001100001");
afterReturningNoticeSendService.sendNotice(functionalCaseTask, event,resources, user, "100001100001");
}
}
@Test
@Order(2)
public void ApiNoticeSuccess() {
String apiDefinitionTask = NoticeConstants.TaskType.API_DEFINITION_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
ApiDefinition aspectGyqApiOne = apiDefinitionMapper.selectByPrimaryKey("aspect_gyq_api_one");
String jsonObject = JSON.toJSONString(aspectGyqApiOne);
if (!StringUtils.equals("{}", jsonObject) && !StringUtils.equals("[]", jsonObject)) {
source.set(JSON.toJSONString(aspectGyqApiOne));
}
List<Map> resources = new ArrayList<>();
String v = source.get();
if (StringUtils.isNotBlank(v)) {
// array
if (StringUtils.startsWith(v, "[")) {
resources.addAll(JSON.parseArray(v, Map.class));
}
// map
else {
Map<?, ?> value = JSON.parseObject(v, Map.class);
resources.add(value);
}
}
UserDTO userDTO = new UserDTO();
userDTO.setId(sessionId);
userDTO.setName("admin");
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(apiDefinitionTask, event,resources, user, "100001100001");
}
}
@Test
@Order(3)
public void ApiScenarioNoticeSuccess() {
String apiScenarioTask = NoticeConstants.TaskType.API_SCENARIO_TASK;
String testPlanTask = NoticeConstants.TaskType.TEST_PLAN_TASK;
String caseReviewTask = NoticeConstants.TaskType.CASE_REVIEW_TASK;
String loadTestTask = NoticeConstants.TaskType.LOAD_TEST_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
ApiScenario aspectGyqApiScenarioOne = apiScenarioMapper.selectByPrimaryKey("aspect_gyq_api_scenario_one");
String jsonObject = JSON.toJSONString(aspectGyqApiScenarioOne);
if (!StringUtils.equals("{}", jsonObject) && !StringUtils.equals("[]", jsonObject)) {
source.set(JSON.toJSONString(aspectGyqApiScenarioOne));
}
List<Map> resources = new ArrayList<>();
String v = source.get();
if (StringUtils.isNotBlank(v)) {
// array
if (StringUtils.startsWith(v, "[")) {
resources.addAll(JSON.parseArray(v, Map.class));
}
// map
else {
Map<?, ?> value = JSON.parseObject(v, Map.class);
resources.add(value);
}
}
UserDTO userDTO = new UserDTO();
userDTO.setId(sessionId);
userDTO.setName("admin");
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(apiScenarioTask, event,resources, user, "100001100001");
}
}
@Test
@Order(4)
public void TestPlanTaskNoticeSuccess() {
String testPlanTask = NoticeConstants.TaskType.TEST_PLAN_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
TestPlan aspectGyqTestPlanOne = testPlanMapper.selectByPrimaryKey("aspect_gyq_test_plan_one");
String jsonObject = JSON.toJSONString(aspectGyqTestPlanOne);
if (!StringUtils.equals("{}", jsonObject) && !StringUtils.equals("[]", jsonObject)) {
source.set(JSON.toJSONString(aspectGyqTestPlanOne));
}
List<Map> resources = new ArrayList<>();
String v = source.get();
if (StringUtils.isNotBlank(v)) {
// array
if (StringUtils.startsWith(v, "[")) {
resources.addAll(JSON.parseArray(v, Map.class));
}
// map
else {
Map<?, ?> value = JSON.parseObject(v, Map.class);
resources.add(value);
}
}
UserDTO userDTO = new UserDTO();
userDTO.setId(sessionId);
userDTO.setName("admin");
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(testPlanTask, event,resources, user, "100001100001");
}
}
@Test
@Order(5)
public void CaseReviewTaskNoticeSuccess() {
String caseReviewTask = NoticeConstants.TaskType.CASE_REVIEW_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
CaseReview aspectGyqCaseReviewOne = caseReviewMapper.selectByPrimaryKey("aspect_gyq_case_review_one");
String jsonObject = JSON.toJSONString(aspectGyqCaseReviewOne);
if (!StringUtils.equals("{}", jsonObject) && !StringUtils.equals("[]", jsonObject)) {
source.set(JSON.toJSONString(aspectGyqCaseReviewOne));
}
List<Map> resources = new ArrayList<>();
String v = source.get();
if (StringUtils.isNotBlank(v)) {
// array
if (StringUtils.startsWith(v, "[")) {
resources.addAll(JSON.parseArray(v, Map.class));
}
// map
else {
Map<?, ?> value = JSON.parseObject(v, Map.class);
resources.add(value);
}
}
UserDTO userDTO = new UserDTO();
userDTO.setId(sessionId);
userDTO.setName("admin");
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(caseReviewTask, event,resources, user, "100001100001");
}
}
@Test
@Order(6)
public void LoadTestTaskTaskNoticeSuccess() {
String loadTestTask = NoticeConstants.TaskType.LOAD_TEST_TASK;
List<String>eventList = new ArrayList<>();
getTypeList(eventList);
LoadTest loadTest = new LoadTest();
loadTest.setId("aspect_gyq_load_test_one");
loadTest.setProjectId("100001100001");
loadTest.setName("load_test");
loadTest.setCreateTime(System.currentTimeMillis());
loadTest.setUpdateTime(System.currentTimeMillis());
loadTest.setStatus("Starting");
loadTest.setTestResourcePoolId("test_pool");
loadTest.setNum(10001);
loadTest.setCreateUser("admin");
loadTest.setPos(1L);
loadTest.setVersionId("v1.10");
loadTest.setRefId("aspect_gyq_load_test_one");
loadTest.setLatest(true);
String jsonObject = JSON.toJSONString(loadTest);
if (!StringUtils.equals("{}", jsonObject) && !StringUtils.equals("[]", jsonObject)) {
source.set(JSON.toJSONString(loadTest));
}
List<Map> resources = new ArrayList<>();
String v = source.get();
if (StringUtils.isNotBlank(v)) {
// array
if (StringUtils.startsWith(v, "[")) {
resources.addAll(JSON.parseArray(v, Map.class));
}
// map
else {
Map<?, ?> value = JSON.parseObject(v, Map.class);
resources.add(value);
}
}
UserDTO userDTO = new UserDTO();
userDTO.setId(sessionId);
userDTO.setName("admin");
SessionUser user = SessionUser.fromUser(userDTO, sessionId);
for (String event : eventList) {
afterReturningNoticeSendService.sendNotice(loadTestTask, event,resources, user, "100001100001");
}
}

View File

@ -33,3 +33,16 @@ INSERT INTO custom_field(id, name, scene, type, remark, create_time, update_time
values ('aspect_test_one','aspect_test','FUNCTIONAL', 'INPUT','aspect_test', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin' , 'aspect_test_one', '100001100001',false, false, 'PROJECT');
INSERT INTO functional_case_custom_field(case_id, field_id, value) VALUES ('aspect_gyq_one', 'aspect_test_one', 'hello');
INSERT INTO api_definition(id, name, protocol, method, path, status, num, tags, pos, project_id, module_id, latest, version_id, ref_id, description, create_time, create_user, update_time, update_user, delete_user, delete_time, deleted)
VALUES ('aspect_gyq_api_one', 'api_test','HTTP', 'POST','api/test','test-api-status', 1000001, null, 1, '100001100001' , 'test_module', true, 'v1.10','aspect_gyq_api_one', null, UNIX_TIMESTAMP() * 1000,'admin', UNIX_TIMESTAMP() * 1000,'admin', null,null,false);
INSERT INTO api_scenario(id, name, level, status, principal, last_report_status, last_report_id, num, custom_num, pos, version_id, ref_id, project_id, api_scenario_module_id, description, tags, create_user, create_time, delete_time, delete_user, update_user, update_time)
VALUES ('aspect_gyq_api_scenario_one', 'api_scenario', 'p1', 'test-api-status', 'gyq', null, null,1000001, 1000001, 1,'v1.10', 'aspect_gyq_api_scenario_one','100001100001', 'test_module', null,null,'admin', UNIX_TIMESTAMP() * 1000,null,null,'admin', UNIX_TIMESTAMP() * 1000);
INSERT INTO test_plan(id, project_id, parent_id, name, status, stage, tags, create_time, create_user, update_time, update_user, planned_start_time, planned_end_time, actual_start_time, actual_end_time, description)
VALUES ('aspect_gyq_test_plan_one','100001100001', 'NONE', 'test_plan', 'test-api-status', 'Smock', null, UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 2000, UNIX_TIMESTAMP() * 3000 ,UNIX_TIMESTAMP() * 2000, UNIX_TIMESTAMP() * 3000,null);
INSERT INTO case_review(id, name, status, create_time, update_time, end_time, description, project_id, tags, create_user, review_pass_rule)
VALUES ('aspect_gyq_case_review_one','case_review','test-api-status',UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000,null, '100001100001',null,'admin','single');