refactor(用例评审): 添加监听事件

This commit is contained in:
guoyuqi 2023-12-14 19:26:33 +08:00 committed by 刘瑞斌
parent 9fab5ebe34
commit 7fae8fd801
21 changed files with 498 additions and 61 deletions

View File

@ -1,6 +1,8 @@
package io.metersphere.listener;
import io.metersphere.api.event.ApiEventSource;
import io.metersphere.functional.event.CaseEventSource;
import io.metersphere.functional.listener.CaseEventListener;
import io.metersphere.plan.listener.ExecEventListener;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.file.FileCenter;
@ -31,22 +33,25 @@ public class AppStartListener implements ApplicationRunner {
// 注册所有监听源
LogUtils.info("初始化接口事件源");
ApiEventSource apiEventSource = CommonBeanFactory.getBean(ApiEventSource.class);
LogUtils.info("初始化用例管理事件源");
CaseEventSource caseEventSource = CommonBeanFactory.getBean(CaseEventSource.class);
LogUtils.info("初始化性能测试事件源");
//LoadEventSource loadEventSource = CommonBeanFactory.getBean(LoadEventSource.class);
//todo: 注册其他事件源
// 创建监听器对象并注册到多个事件源
ExecEventListener listener = CommonBeanFactory.getBean(ExecEventListener.class);
apiEventSource.addListener(listener);
//todo: 注册其他监听器
CaseEventListener caseEventListener = CommonBeanFactory.getBean(CaseEventListener.class);
caseEventSource.addListener(caseEventListener);
//loadEventSource.addListener(listener);
// 触发事件
apiEventSource.fireEvent("API", "Event after removing the listener test.");
//loadEventSource.fireEvent("LOAD","Event after removing the listener.");
caseEventSource.fireEvent("CASE", "Event after removing the listener test.");
// 加载插件
pluginLoadService.loadPlugins();
}

View File

@ -1,5 +1,36 @@
package io.metersphere.sdk.listener;
public record Event(String module, String message) {
import lombok.Data;
import java.util.Map;
@Data
public class Event {
private String module;
private String message;
private Map<String, Object> paramMap;
public Event(String module, String message) {
this.module = module;
this.message = message;
}
public Event(String module, String message, Map<String, Object> paramMap) {
this.module = module;
this.message = message;
this.paramMap = paramMap;
}
public String module() {
return this.module;
}
public String message() {
return this.message;
}
public Map<String, Object> paramMap() {
return this.paramMap;
}
}

View File

@ -1,5 +1,7 @@
package io.metersphere.sdk.listener;
import java.util.Map;
public interface EventSource {
/**
* 注册监听
@ -10,4 +12,9 @@ public interface EventSource {
* 触发事件
*/
void fireEvent(String module, String message);
/**
* 触发事件带有参数
*/
void fireEvent(String module, String message, Map<String, Object> paramMap);
}

View File

@ -3,8 +3,11 @@ package io.metersphere.api.event;
import io.metersphere.sdk.listener.Event;
import io.metersphere.sdk.listener.EventListener;
import io.metersphere.sdk.listener.EventSource;
import io.metersphere.sdk.util.LogUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ApiEventSource implements EventSource {
private EventListener<Event> listener;
@ -19,4 +22,12 @@ public class ApiEventSource implements EventSource {
Event event = new Event("API", message);
listener.onEvent(event);
}
@Override
public void fireEvent(String module, String message, Map<String, Object> paramMap) {
Event event = new Event("API", message, paramMap);
listener.onEvent(event);
LogUtils.info("带有参数的监听事件");
}
}

View File

@ -12,6 +12,8 @@ import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -33,5 +35,7 @@ public class EventSourceTests {
});
// 触发事件
apiEventSource.fireEvent("API", "Event after removing the listener test.");
// 触发事件待参数
apiEventSource.fireEvent("API", "Event after removing the listener test.",new HashMap<>());
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.functional.constants;
public interface CaseEvent {
interface Event {
String ASSOCIATE = "associate";
String DISASSOCIATE = "disassociate";
String BATCH_DISASSOCIATE = "batchDisassociate";
String DELETE_FUNCTIONAL_CASE = "deleteFunctionalCase";
String DELETE_TRASH_FUNCTIONAL_CASE = "deleteTrashFunctionalCase";
String RECOVER_FUNCTIONAL_CASE = "recoverFunctionalCase";
}
interface Param {
String REVIEW_ID = "reviewId";
String CASE_IDS = "caseIds";
String PASS_COUNT = "passCount";
String CASE_COUNT = "caseCount";
}
}

View File

@ -0,0 +1,32 @@
package io.metersphere.functional.event;
import io.metersphere.sdk.listener.Event;
import io.metersphere.sdk.listener.EventListener;
import io.metersphere.sdk.listener.EventSource;
import io.metersphere.sdk.util.LogUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class CaseEventSource implements EventSource {
private EventListener<Event> listener;
@Override
public void addListener(EventListener<Event> listener) {
this.listener = listener;
}
@Override
public void fireEvent(String module, String message) {
Event event = new Event("CASE", message);
listener.onEvent(event);
}
@Override
public void fireEvent(String module, String message, Map<String, Object> paramMap) {
Event event = new Event("CASE", message, paramMap);
listener.onEvent(event);
LogUtils.info("带有参数的监听事件");
}
}

View File

@ -0,0 +1,201 @@
package io.metersphere.functional.listener;
import io.metersphere.functional.constants.CaseEvent;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.CaseReview;
import io.metersphere.functional.domain.CaseReviewFunctionalCase;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseExample;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseUserExample;
import io.metersphere.functional.mapper.CaseReviewFunctionalCaseMapper;
import io.metersphere.functional.mapper.CaseReviewFunctionalCaseUserMapper;
import io.metersphere.functional.mapper.CaseReviewMapper;
import io.metersphere.sdk.listener.Event;
import io.metersphere.sdk.listener.EventListener;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class CaseEventListener implements EventListener<Event> {
@Resource
private CaseReviewMapper caseReviewMapper;
@Resource
private CaseReviewFunctionalCaseUserMapper caseReviewFunctionalCaseUserMapper;
@Resource
private CaseReviewFunctionalCaseMapper caseReviewFunctionalCaseMapper;
@Async
@Override
public void onEvent(Event event) {
LogUtils.info("CaseEventListener: " + event.module() + "" + event.message());
//批量修改用例评审人
//重新提审(批量)/自动重新提审编辑
//批量修改结果
String message = event.message();
switch (message) {
case CaseEvent.Event.ASSOCIATE -> {
updateCaseReviewByAssociate(event);
}
case CaseEvent.Event.DISASSOCIATE -> {
updateCaseReviewByDisAssociate(event);
}
case CaseEvent.Event.BATCH_DISASSOCIATE -> {
updateCaseReviewByBatchDisassociate(event);
}
case CaseEvent.Event.DELETE_FUNCTIONAL_CASE -> {
updateCaseReviewByDeleteFunctionalCase(event);
}
case CaseEvent.Event.DELETE_TRASH_FUNCTIONAL_CASE -> {
updateCaseReviewByDeleteTrashFunctionalCase(event);
}
case CaseEvent.Event.RECOVER_FUNCTIONAL_CASE -> {
updateCaseReviewByRecoverFunctionalCase(event);
}
default -> LogUtils.info("CaseEventListener: " + event.module() + "" + event.message());
}
}
/**
* 功能用例的回收站恢复/批量恢复重新计算用例评审的通过率和用例数
*/
private void updateCaseReviewByRecoverFunctionalCase(Event event) {
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = getCaseReviewFunctionalCases(event);
Map<String, List<CaseReviewFunctionalCase>> reviewIdMap = caseReviewFunctionalCases.stream().collect(Collectors.groupingBy(CaseReviewFunctionalCase::getReviewId));
reviewIdMap.forEach((reviewId, caseReviewFunctionalCaseList) -> {
CaseReview caseReview = caseReviewMapper.selectByPrimaryKey(reviewId);
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
updateCaseReview(reviewId, caseReview.getCaseCount() + caseReviewFunctionalCaseList.size(), passList.size());
});
}
/**
* 功能用例的回收站删除/批量删除重新计算用例评审的通过率和用例数
*/
private void updateCaseReviewByDeleteTrashFunctionalCase(Event event) {
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = getCaseReviewFunctionalCases(event);
if (CollectionUtils.isEmpty(caseReviewFunctionalCases)) {
return;
}
Map<String, List<CaseReviewFunctionalCase>> reviewIdMap = caseReviewFunctionalCases.stream().collect(Collectors.groupingBy(CaseReviewFunctionalCase::getReviewId));
reviewIdMap.forEach((reviewId, caseReviewFunctionalCaseList) -> {
CaseReview caseReview = caseReviewMapper.selectByPrimaryKey(reviewId);
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
updateCaseReview(reviewId, caseReview.getCaseCount() - caseReviewFunctionalCaseList.size(), passList.size());
});
}
/**
* 功能用例的删除/批量删除重新计算用例评审的通过率和用例数
*/
private void updateCaseReviewByDeleteFunctionalCase(Event event) {
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = getCaseReviewFunctionalCases(event);
if (CollectionUtils.isEmpty(caseReviewFunctionalCases)) {
return;
}
Map<String, List<CaseReviewFunctionalCase>> reviewIdMap = caseReviewFunctionalCases.stream().collect(Collectors.groupingBy(CaseReviewFunctionalCase::getReviewId));
reviewIdMap.forEach((reviewId, caseReviewFunctionalCaseList) -> {
CaseReview caseReview = caseReviewMapper.selectByPrimaryKey(reviewId);
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
updateCaseReview(reviewId, caseReview.getCaseCount() - caseReviewFunctionalCaseList.size(), passList.size());
List<String> caseIdList = caseReviewFunctionalCaseList.stream().map(CaseReviewFunctionalCase::getId).toList();
deleteReviewFunctionalCaseUser(caseIdList, reviewId);
});
}
private List<CaseReviewFunctionalCase> getCaseReviewFunctionalCases(Event event) {
Map<String, Object> paramMap = event.paramMap();
Object caseIds = paramMap.get(CaseEvent.Param.CASE_IDS);
List<String> caseIdList = JSON.parseArray(JSON.toJSONString(caseIds), String.class);
CaseReviewFunctionalCaseExample functionalCaseExample = new CaseReviewFunctionalCaseExample();
functionalCaseExample.createCriteria().andCaseIdIn(caseIdList);
return caseReviewFunctionalCaseMapper.selectByExample(functionalCaseExample);
}
/**
* 1.批量取消关联重新计算用例评审的通过率和用例数
* 2.删除用例和用例评审人的关系
*/
private void updateCaseReviewByBatchDisassociate(Event event) {
Map<String, Object> paramMap = event.paramMap();
String reviewId = paramMap.get(CaseEvent.Param.REVIEW_ID).toString();
CaseReview caseReview = caseReviewMapper.selectByPrimaryKey(reviewId);
Integer oldCaseCount = caseReview.getCaseCount();
Object caseIds = paramMap.get(CaseEvent.Param.CASE_IDS);
List<String> caseIdList = JSON.parseArray(JSON.toJSONString(caseIds), String.class);
int passNumber = Integer.parseInt(paramMap.get(CaseEvent.Param.PASS_COUNT).toString());
updateCaseReview(reviewId, oldCaseCount - caseIdList.size(), passNumber);
//删除用例和用例评审人的关系
deleteCaseReviewFunctionalCaseUser(event);
}
/**
* 1.单独取消关联重新计算用例评审的通过率和用例数
* 2.删除用例和用例评审人的关系
*/
private void updateCaseReviewByDisAssociate(Event event) {
updateCaseReviewByAssociate(event);
//删除用例和用例评审人的关系
deleteCaseReviewFunctionalCaseUser(event);
}
/**
* 删除用例和用例评审人的关系
*/
private void deleteCaseReviewFunctionalCaseUser(Event event) {
Map<String, Object> paramMap = event.paramMap();
Object caseIds = paramMap.get(CaseEvent.Param.CASE_IDS);
List<String> caseIdList = JSON.parseArray(JSON.toJSONString(caseIds), String.class);
String reviewId = paramMap.get(CaseEvent.Param.REVIEW_ID).toString();
deleteReviewFunctionalCaseUser(caseIdList, reviewId);
}
private void deleteReviewFunctionalCaseUser(List<String> caseIdList, String reviewId) {
CaseReviewFunctionalCaseUserExample caseReviewFunctionalCaseUserExample = new CaseReviewFunctionalCaseUserExample();
caseReviewFunctionalCaseUserExample.createCriteria().andCaseIdIn(caseIdList).andReviewIdEqualTo(reviewId);
caseReviewFunctionalCaseUserMapper.deleteByExample(caseReviewFunctionalCaseUserExample);
}
/**
* 关联用例单独/批量重新计算用例评审的通过率和用例数
*/
private void updateCaseReviewByAssociate(Event event) {
Map<String, Object> paramMap = event.paramMap();
String reviewId = paramMap.get(CaseEvent.Param.REVIEW_ID).toString();
int caseCount = Integer.parseInt(paramMap.get(CaseEvent.Param.CASE_COUNT).toString());
int passNumber = Integer.parseInt(paramMap.get(CaseEvent.Param.PASS_COUNT).toString());
updateCaseReview(reviewId, caseCount, passNumber);
}
/**
* 重新计算用例评审的通过率和用例数
*/
private void updateCaseReview(String reviewId, int caseCount, int passNumber) {
CaseReview caseReview = new CaseReview();
caseReview.setId(reviewId);
//更新用例数量
caseReview.setCaseCount(caseCount);
//通过率
BigDecimal passCount = BigDecimal.valueOf(passNumber);
BigDecimal totalCount = BigDecimal.valueOf(caseReview.getCaseCount());
if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
caseReview.setPassRate(BigDecimal.ZERO);
} else {
BigDecimal passRate = passCount.divide(totalCount, 2, RoundingMode.HALF_UP);
caseReview.setPassRate(passRate);
}
caseReviewMapper.updateByPrimaryKeySelective(caseReview);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.functional.mapper;
import io.metersphere.functional.domain.CaseReviewFunctionalCase;
import io.metersphere.functional.dto.FunctionalCaseReviewDTO;
import io.metersphere.functional.dto.ReviewFunctionalCaseDTO;
import io.metersphere.functional.request.BaseReviewCaseBatchRequest;
@ -27,4 +28,6 @@ public interface ExtCaseReviewFunctionalCaseMapper {
Long getPos(@Param("reviewId") String reviewId);
List<String> getIds(@Param("request") BaseReviewCaseBatchRequest request, @Param("userId") String userId, @Param("deleted") boolean deleted);
List<CaseReviewFunctionalCase> getList(@Param("reviewId") String reviewId, @Param("reviewIds") List<String> reviewIds, @Param("deleted") boolean deleted);
}

View File

@ -286,4 +286,23 @@
LIMIT 1;
</select>
<select id="getList" resultType="io.metersphere.functional.domain.CaseReviewFunctionalCase">
SELECT
*
FROM
case_review_functional_case crfc
LEFT JOIN functional_case fc ON crfc.case_id = fc.id
WHERE
fc.deleted = #{deleted}
<if test="reviewId != null and reviewId != ''">
and crfc.review_id = #{reviewId}
</if>
<if test="reviewIds != null and reviewIds.size() > 0">
and crfc.review_id in
<foreach collection="reviewIds" item="reviewId" separator="," open="(" close=")">
#{reviewId}
</foreach>
</if>
</select>
</mapper>

View File

@ -1,6 +1,7 @@
package io.metersphere.functional.service;
import io.metersphere.functional.constants.CaseEvent;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.CaseReviewFunctionalCase;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseExample;
@ -12,18 +13,18 @@ import io.metersphere.functional.mapper.ExtCaseReviewFunctionalCaseUserMapper;
import io.metersphere.functional.mapper.ExtFunctionalCaseModuleMapper;
import io.metersphere.functional.request.BaseReviewCaseBatchRequest;
import io.metersphere.functional.request.ReviewFunctionalCasePageRequest;
import io.metersphere.functional.utils.CaseListenerUtils;
import io.metersphere.project.domain.ProjectVersion;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -89,8 +90,8 @@ public class CaseReviewFunctionalCaseService {
list.forEach(item -> {
item.setModuleName(moduleMap.get(item.getModuleId()));
item.setVersionName(versionMap.get(item.getVersionId()));
item.setReviewers(Arrays.asList(userIdMap.get(item.getId())));
item.setReviewNames(Arrays.asList(userNameMap.get(item.getId())));
item.setReviewers(Collections.singletonList(userIdMap.get(item.getId())));
item.setReviewNames(Collections.singletonList(userNameMap.get(item.getId())));
});
}
return list;
@ -106,10 +107,23 @@ public class CaseReviewFunctionalCaseService {
if (CollectionUtils.isNotEmpty(ids)) {
CaseReviewFunctionalCaseExample example = new CaseReviewFunctionalCaseExample();
example.createCriteria().andIdIn(ids);
Map<String, Object> param = getParam(request.getReviewId(), example, ids);
CaseListenerUtils.addListener(param, CaseEvent.Event.BATCH_DISASSOCIATE);
caseReviewFunctionalCaseMapper.deleteByExample(example);
}
}
private Map<String, Object> getParam(String reviewId, CaseReviewFunctionalCaseExample example, List<String> ids) {
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = caseReviewFunctionalCaseMapper.selectByExample(example);
List<String> caseIds = caseReviewFunctionalCases.stream().map(CaseReviewFunctionalCase::getCaseId).toList();
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> !ids.contains(t.getId()) && StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,caseIds);
param.put(CaseEvent.Param.REVIEW_ID, reviewId);
param.put(CaseEvent.Param.PASS_COUNT,passList.size());
return param;
}
public List<String> doSelectIds(BaseReviewCaseBatchRequest request) {
if (request.isSelectAll()) {
List<String> ids = extCaseReviewFunctionalCaseMapper.getIds(request, request.getUserId(), false);
@ -126,9 +140,9 @@ public class CaseReviewFunctionalCaseService {
/**
* 评审详情页面 创建用例并关联
*
* @param caseId
* @param userId
* @param reviewId
* @param caseId 功能用例ID
* @param userId 当前操作人
* @param reviewId 评审id
*/
public void addCaseReviewFunctionalCase(String caseId, String userId, String reviewId) {
CaseReviewFunctionalCase reviewFunctionalCase = new CaseReviewFunctionalCase();

View File

@ -1,6 +1,7 @@
package io.metersphere.functional.service;
import io.metersphere.functional.constants.CaseEvent;
import io.metersphere.functional.constants.CaseReviewStatus;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.*;
@ -10,6 +11,7 @@ import io.metersphere.functional.dto.CaseReviewUserDTO;
import io.metersphere.functional.mapper.*;
import io.metersphere.functional.request.*;
import io.metersphere.functional.result.CaseManagementResultCode;
import io.metersphere.functional.utils.CaseListenerUtils;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.PermissionConstants;
@ -33,8 +35,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -71,8 +73,6 @@ public class CaseReviewService {
@Resource
private DeleteCaseReviewService deleteCaseReviewService;
@Resource
private CaseReviewFunctionalCaseUserMapper caseReviewFunctionalCaseUserMapper;
@Resource
private ExtCaseReviewFunctionalCaseMapper extCaseReviewFunctionalCaseMapper;
@Resource
private CaseReviewModuleService caseReviewModuleService;
@ -187,9 +187,7 @@ public class CaseReviewService {
* @return Map
*/
private Map<String, List<CaseReviewFunctionalCase>> getReviewCaseMap(List<String> reviewIds) {
CaseReviewFunctionalCaseExample caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample();
caseReviewFunctionalCaseExample.createCriteria().andReviewIdIn(reviewIds);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = caseReviewFunctionalCaseMapper.selectByExample(caseReviewFunctionalCaseExample);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = extCaseReviewFunctionalCaseMapper.getList(null, reviewIds, false);
return caseReviewFunctionalCases.stream().collect(Collectors.groupingBy(CaseReviewFunctionalCase::getReviewId));
}
@ -450,9 +448,7 @@ public class CaseReviewService {
if (CollectionUtils.isEmpty(functionalCases)) {
return;
}
CaseReviewFunctionalCaseExample caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample();
caseReviewFunctionalCaseExample.createCriteria().andReviewIdEqualTo(caseReviewId);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = caseReviewFunctionalCaseMapper.selectByExample(caseReviewFunctionalCaseExample);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = extCaseReviewFunctionalCaseMapper.getList(caseReviewId, null, false);
List<String> castIds = caseReviewFunctionalCases.stream().map(CaseReviewFunctionalCase::getCaseId).toList();
List<String> caseRealIds = caseIds.stream().filter(t -> !castIds.contains(t)).toList();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
@ -467,19 +463,20 @@ public class CaseReviewService {
} finally {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
CaseReview caseReview = new CaseReview();
caseReview.setId(caseReviewId);
//更新用例数量
caseReview.setCaseCount(caseReviewExist.getCaseCount() + caseRealIds.size());
//通过率
BigDecimal passCount = BigDecimal.valueOf(passList.size());
BigDecimal totalCount = BigDecimal.valueOf(caseReview.getCaseCount());
BigDecimal passRate = passCount.divide(totalCount, 2, RoundingMode.HALF_UP);
caseReview.setPassRate(passRate);
caseReviewMapper.updateByPrimaryKeySelective(caseReview);
Map<String, Object> param = getParam(caseReviewFunctionalCases, caseReviewExist.getCaseCount() + caseRealIds.size(), caseReviewId);
CaseListenerUtils.addListener(param, CaseEvent.Event.ASSOCIATE);
}
private Map<String, Object> getParam(List<CaseReviewFunctionalCase> caseReviewFunctionalCases, int caseReviewExist, String caseReviewId) {
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_COUNT, caseReviewExist);
param.put(CaseEvent.Param.REVIEW_ID, caseReviewId);
param.put(CaseEvent.Param.PASS_COUNT, passList.size());
return param;
}
public <T> List<String> doSelectIds(T dto, String projectId) {
BaseFunctionalCaseBatchDTO request = (BaseFunctionalCaseBatchDTO) dto;
if (request.isSelectAll()) {
@ -565,30 +562,11 @@ public class CaseReviewService {
CaseReviewFunctionalCaseExample caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample();
caseReviewFunctionalCaseExample.createCriteria().andReviewIdEqualTo(reviewId).andCaseIdEqualTo(caseId);
caseReviewFunctionalCaseMapper.deleteByExample(caseReviewFunctionalCaseExample);
//2.删除用例和用例评审人的关系
CaseReviewFunctionalCaseUserExample caseReviewFunctionalCaseUserExample = new CaseReviewFunctionalCaseUserExample();
caseReviewFunctionalCaseUserExample.createCriteria().andCaseIdEqualTo(caseId).andReviewIdEqualTo(reviewId);
caseReviewFunctionalCaseUserMapper.deleteByExample(caseReviewFunctionalCaseUserExample);
caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample();
caseReviewFunctionalCaseExample.createCriteria().andReviewIdEqualTo(reviewId);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = caseReviewFunctionalCaseMapper.selectByExample(caseReviewFunctionalCaseExample);
List<CaseReviewFunctionalCase> passList = caseReviewFunctionalCases.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getStatus(), FunctionalCaseReviewStatus.PASS.toString())).toList();
CaseReview caseReview = new CaseReview();
caseReview.setId(reviewId);
//更新用例数量
caseReview.setCaseCount(caseReviewFunctionalCases.size());
//通过率
BigDecimal passCount = BigDecimal.valueOf(passList.size());
BigDecimal totalCount = BigDecimal.valueOf(caseReview.getCaseCount());
BigDecimal passRate;
if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
passRate = BigDecimal.ZERO;
} else {
passRate = passCount.divide(totalCount, 2, RoundingMode.HALF_UP);
}
caseReview.setPassRate(passRate);
caseReviewMapper.updateByPrimaryKeySelective(caseReview);
List<CaseReviewFunctionalCase> caseReviewFunctionalCases = extCaseReviewFunctionalCaseMapper.getList(reviewId, null, false);
Map<String, Object> param = getParam(caseReviewFunctionalCases, caseReviewFunctionalCases.size(), reviewId);
param.put(CaseEvent.Param.CASE_IDS, List.of(caseId));
CaseListenerUtils.addListener(param, CaseEvent.Event.DISASSOCIATE);
}
public Map<String, Long> moduleCount(CaseReviewPageRequest request) {

View File

@ -1,5 +1,6 @@
package io.metersphere.functional.service;
import io.metersphere.functional.constants.CaseEvent;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.*;
import io.metersphere.functional.dto.*;
@ -9,6 +10,7 @@ import io.metersphere.functional.mapper.FunctionalCaseFollowerMapper;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.functional.request.*;
import io.metersphere.functional.result.CaseManagementResultCode;
import io.metersphere.functional.utils.CaseListenerUtils;
import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
@ -372,13 +374,21 @@ public class FunctionalCaseService {
}
private void handDeleteFunctionalCase(List<String> ids, Boolean deleteAll, String userId) {
Map<String, Object> param = new HashMap<>();
if (deleteAll) {
//全部删除 进入回收站
List<String> refId = extFunctionalCaseMapper.getRefIds(ids, false);
FunctionalCaseExample functionalCaseExample = new FunctionalCaseExample();
functionalCaseExample.createCriteria().andRefIdIn(refId);
List<FunctionalCase> functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
List<String> caseIds = functionalCases.stream().map(FunctionalCase::getId).toList();
param.put(CaseEvent.Param.CASE_IDS, caseIds);
extFunctionalCaseMapper.batchDelete(refId, userId);
} else {
param.put(CaseEvent.Param.CASE_IDS, ids);
doDelete(ids, userId);
}
CaseListenerUtils.addListener(param, CaseEvent.Event.DELETE_FUNCTIONAL_CASE);
}

View File

@ -1,5 +1,6 @@
package io.metersphere.functional.service;
import io.metersphere.functional.constants.CaseEvent;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseCustomField;
import io.metersphere.functional.domain.FunctionalCaseCustomFieldExample;
@ -8,6 +9,7 @@ import io.metersphere.functional.mapper.ExtFunctionalCaseMapper;
import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.functional.request.FunctionalCaseBatchRequest;
import io.metersphere.functional.utils.CaseListenerUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.CustomField;
@ -15,12 +17,13 @@ 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.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author guoyuqi
@ -55,10 +58,15 @@ public class FunctionalCaseTrashService {
List<String> ids = getIdsByRefId(functionalCase.getRefId());
//检查自定义字段是否还存在不存在删除关联关系与恢复流程没关系可异步执行
delCustomFields(ids);
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,ids);
CaseListenerUtils.addListener(param, CaseEvent.Event.RECOVER_FUNCTIONAL_CASE);
extFunctionalCaseMapper.recoverCase(ids,userId,System.currentTimeMillis());
}
/**
* 检查自定义字段是否还存在不存在删除关联关系与恢复流程没关系可异步执行
*/
public void delCustomFields(List<String> ids) {
doDeleteCustomFields(ids);
}
@ -102,6 +110,9 @@ public class FunctionalCaseTrashService {
return;
}
List<String> ids = getIdsByRefId(functionalCase.getRefId());
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,ids);
CaseListenerUtils.addListener(param, CaseEvent.Event.DELETE_TRASH_FUNCTIONAL_CASE);
deleteFunctionalCaseService.deleteFunctionalCaseResource(ids, functionalCase.getProjectId());
}
@ -134,6 +145,13 @@ public class FunctionalCaseTrashService {
refIds = functionalCases.stream().map(FunctionalCase::getRefId).distinct().toList();
}
if (CollectionUtils.isNotEmpty(refIds)) {
FunctionalCaseExample functionalCaseExample = new FunctionalCaseExample();
functionalCaseExample.createCriteria().andRefIdIn(refIds);
List<FunctionalCase> functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
List<String> ids = functionalCases.stream().map(FunctionalCase::getId).toList();
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,ids);
CaseListenerUtils.addListener(param, CaseEvent.Event.RECOVER_FUNCTIONAL_CASE);
extFunctionalCaseMapper.recoverCaseByRefIds(refIds, userId, System.currentTimeMillis());
delCustomFieldsByRefIds(refIds);
}
@ -175,6 +193,9 @@ public class FunctionalCaseTrashService {
deleteByRefIds(request, refIds);
}else {
//只删除当前选择的数据
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,ids);
CaseListenerUtils.addListener(param, CaseEvent.Event.DELETE_TRASH_FUNCTIONAL_CASE);
deleteFunctionalCaseService.deleteFunctionalCaseResource(ids, request.getProjectId());
}
} else {
@ -189,6 +210,9 @@ public class FunctionalCaseTrashService {
deleteByRefIds(request, refIds);
} else {
//只删除当前选择的数据
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,request.getSelectIds());
CaseListenerUtils.addListener(param, CaseEvent.Event.DELETE_TRASH_FUNCTIONAL_CASE);
deleteFunctionalCaseService.deleteFunctionalCaseResource(request.getSelectIds(), request.getProjectId());
}
}
@ -199,6 +223,9 @@ public class FunctionalCaseTrashService {
functionalCaseExample.createCriteria().andRefIdIn(refIds).andDeletedEqualTo(true);
List<FunctionalCase> functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
List<String> deleteIds = functionalCases.stream().map(FunctionalCase::getId).toList();
Map<String, Object> param = new HashMap<>();
param.put(CaseEvent.Param.CASE_IDS,deleteIds);
CaseListenerUtils.addListener(param, CaseEvent.Event.DELETE_TRASH_FUNCTIONAL_CASE);
deleteFunctionalCaseService.deleteFunctionalCaseResource(deleteIds, request.getProjectId());
}
}

View File

@ -19,7 +19,6 @@ import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
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.springframework.transaction.annotation.Transactional;
@ -51,7 +50,6 @@ public class ReviewFunctionalCaseService {
private ExtCaseReviewHistoryMapper extCaseReviewHistoryMapper;
/**
* 评审功能用例
*
@ -66,7 +64,7 @@ public class ReviewFunctionalCaseService {
String functionalCaseStatus = getFunctionalCaseStatus(request);
extCaseReviewFunctionalCaseMapper.updateStatus(request.getCaseId(), request.getReviewId(), functionalCaseStatus);
//更新用例评审状态(判断所有用例是否结束false进行中true已完成)
boolean completed = updateCaseReviewStatus(request.getCaseId(), request.getReviewId());
boolean completed = updateCaseReviewStatus(request.getReviewId());
//检查是否有@发送@通知
if (StringUtils.isNotBlank(request.getNotifier())) {
List<String> relatedUsers = Arrays.asList(request.getNotifier().split(";"));
@ -120,11 +118,10 @@ public class ReviewFunctionalCaseService {
/**
* 更新用例评审自身的状态
*
* @param caseId 功能用例Id
* @param reviewId 用例评审Id
* @return completed
*/
private boolean updateCaseReviewStatus(String caseId, String reviewId) {
private boolean updateCaseReviewStatus(String reviewId) {
boolean completed = false;
List<String> statusList = new ArrayList<>();
statusList.add(FunctionalCaseReviewStatus.UN_REVIEWED.toString());

View File

@ -0,0 +1,23 @@
package io.metersphere.functional.utils;
import io.metersphere.functional.event.CaseEventSource;
import io.metersphere.functional.listener.CaseEventListener;
import io.metersphere.sdk.util.CommonBeanFactory;
import java.util.Map;
public class CaseListenerUtils {
public static void addListener(Map<String, Object> param, String message) {
CaseEventSource caseEventSource = CommonBeanFactory.getBean(CaseEventSource.class);
CaseEventListener caseEventListener = CommonBeanFactory.getBean(CaseEventListener.class);
if (caseEventSource != null) {
caseEventSource.addListener(event -> {
assert caseEventListener != null;
caseEventListener.onEvent(event);
});
caseEventSource.fireEvent("CASE_MANAGEMENT", message, param);
}
}
}

View File

@ -301,6 +301,8 @@ public class CaseReviewControllerTests extends BaseTest {
List<String> userIdList = caseReviewFunctionalCaseUsers.stream().map(CaseReviewFunctionalCaseUser::getUserId).toList();
Assertions.assertTrue(userIdList.contains("gyq_review_test"));
Assertions.assertTrue(userIdList.contains("gyq_review_test2"));
List<CaseReview> caseReviews2 = getCaseReviews("创建评审更新1");
Assertions.assertTrue( caseReviews.get(0).getCaseCount()<caseReviews2.get(0).getCaseCount());
}
@Test

View File

@ -96,6 +96,7 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
this.requestPostWithOkAndReturn(BATCH_DELETE_URL, request);
request.setSelectIds(new ArrayList<>());
request.setSelectAll(true);
request.setExcludeIds(Arrays.asList("TEST_FUNCTIONAL_CASE_ID_1"));
Map<String, Object> map = new HashMap<>();
map.put("customs", Arrays.asList(new LinkedHashMap() {{
put("id", "TEST_FIELD_ID");

View File

@ -0,0 +1,41 @@
package io.metersphere.functional.controller;
import io.metersphere.functional.event.CaseEventSource;
import io.metersphere.sdk.listener.Event;
import io.metersphere.sdk.listener.EventListener;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.LogUtils;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class EventSourceTests {
@Test
@Order(0)
public void eventSourceTest() throws Exception {
// 注册所有监听源
LogUtils.info("初始化用例管理事件源");
CaseEventSource caseEventSource = CommonBeanFactory.getBean(CaseEventSource.class);
assert caseEventSource != null;
caseEventSource.addListener(new EventListener<Event>() {
@Override
public void onEvent(Event event) {
LogUtils.info("ExecEventListener: " + event.module() + "" + event.message());
}
});
// 触发事件
caseEventSource.fireEvent("CASE", "Event after removing the listener test.");
// 触发事件待参数
caseEventSource.fireEvent("CASE", "Event after removing the listener test.",new HashMap<>());
}
}

View File

@ -196,6 +196,8 @@ public class FunctionalCaseTrashControllerTests extends BaseTest {
Assertions.assertNull(functionalCase4);
FunctionalCaseComment functionalCaseComment = functionalCaseCommentMapper.selectByPrimaryKey("trash_comment_id");
Assertions.assertNull(functionalCaseComment);
this.requestGetWithOk(URL_CASE_DELETE + "Trash_TEST_FUNCTIONAL_CASE_ID_GYQ");
}

View File

@ -63,7 +63,14 @@ VALUES ('Trash_TEST_FUNCTIONAL_CASE_ID_f', 7, 'Trash_TEST_MOUDLE_ID_1', 'project
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_g', 8, 'Trash_TEST_MOUDLE_ID_1', 'project-case-trash-test-2', '100001', 'copy_测试多版本g', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID_f', 'UN_EXECUTED',true, b'0', b'1', '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_GYQ', 100, 'Trash_TEST_MOUDLE_ID', 'project-case-trash-test', '100001', '回收站测信', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'Trash_TEST_FUNCTIONAL_CASE_ID_GYQ', 'UN_EXECUTED', true, b'0', b'0', 'gyq', 'gyq', '', 1698058347559, 1698058347559, NULL);
INSERT INTO case_review_functional_case(id, review_id, case_id, status, create_time, create_user, update_time, pos)
VALUES ('Gyq_Review_Case_Id','Gyq_review_id', 'Trash_TEST_FUNCTIONAL_CASE_ID', 'PASS', 1698058347559, 'admin',1698058347559, 0);
INSERT INTO case_review(id, num, name, module_id, project_id, status, review_pass_rule, pos, start_time, end_time, case_count, pass_rate, tags, description, create_time, create_user, update_time, update_user)
VALUES ('Gyq_review_id', 10001, '用例评审1', 'Trash_TEST_MOUDLE_ID_1', 'project-case-trash-test', 'COMPLETE' , 'SINGLE', 0, 1698058347559, 1698058347559, 1, 100.00, null, null, 1698058347559, 'admin', 1698058347559, 'admin');
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');