feat(测试计划): 批量编辑标签

This commit is contained in:
WangXu10 2024-05-15 15:37:00 +08:00 committed by Craftsman
parent 10b97d1b51
commit cd70685204
11 changed files with 347 additions and 24 deletions

View File

@ -460,6 +460,7 @@ message.domain.test_plan_plannedEndTime=计划结束时间
message.domain.test_plan_actualStartTime=实际开始时间 message.domain.test_plan_actualStartTime=实际开始时间
message.domain.test_plan_actualEndTime=实际结束时间 message.domain.test_plan_actualEndTime=实际结束时间
message.domain.test_plan_num=编号 message.domain.test_plan_num=编号
message.domain.test_plan_name=名称
message.domain.test_plan_type=类型 message.domain.test_plan_type=类型
# 用例评审 # 用例评审
message.domain.case_review_name=名称 message.domain.case_review_name=名称

View File

@ -461,6 +461,7 @@ message.domain.test_plan_plannedEndTime=計劃結束時間
message.domain.test_plan_actualStartTime=實際開始時間 message.domain.test_plan_actualStartTime=實際開始時間
message.domain.test_plan_actualEndTime=實際結束時間 message.domain.test_plan_actualEndTime=實際結束時間
message.domain.test_plan_num=編號 message.domain.test_plan_num=編號
message.domain.test_plan_name=名稱
message.domain.test_plan_type=類型 message.domain.test_plan_type=類型
# 用例評審 # 用例評審
message.domain.case_review_name=名稱 message.domain.case_review_name=名稱

View File

@ -180,4 +180,15 @@ public class TestPlanController {
testPlanManagementService.checkModuleIsOpen(request.getTestPlanId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN_FUNCTIONAL_CASE)); testPlanManagementService.checkModuleIsOpen(request.getTestPlanId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN_FUNCTIONAL_CASE));
testPlanService.association(request); testPlanService.association(request);
} }
@PostMapping("/batch-edit")
@Operation(summary = "测试计划-批量编辑")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@Log(type = OperationLogType.UPDATE, expression = "#msClass.batchEditLog(#request)", msClass = TestPlanLogService.class)
public void batchEdit(@Validated @RequestBody TestPlanBatchEditRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchEdit(request, SessionUtils.getUserId());
}
} }

View File

@ -0,0 +1,59 @@
package io.metersphere.plan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author wx
*/
@Data
@NoArgsConstructor
public class TestPlanDTO {
@Schema(description ="message.domain.test_plan_num")
private String num;
@Schema(description ="message.domain.test_plan_name")
private String name;
@Schema(description ="message.domain.test_plan_status")
private String status;
@Schema(description ="message.domain.test_plan_type")
private String type;
@Schema(description ="message.domain.test_plan_tags")
private List<String> tags;
@Schema(description = "message.domain.test_plan_createTime")
private Long createTime;
@Schema(description = "message.domain.test_plan_createUser")
private String createUser;
@Schema(description = "message.domain.test_plan_updateTime")
private Long updateTime;
@Schema(description = "message.domain.test_plan_updateUser")
private String updateUser;
@Schema(description = "message.domain.test_plan_plannedStartTime")
private Long plannedStartTime;
@Schema(description = "message.domain.test_plan_plannedEndTime")
private Long plannedEndTime;
@Schema(description = "message.domain.test_plan_actualStartTime")
private Long actualStartTime;
@Schema(description = "message.domain.test_plan_actualEndTime")
private Long actualEndTime;
@Schema(description = "message.domain.test_plan_description")
private Long description;
}

View File

@ -0,0 +1,20 @@
package io.metersphere.plan.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanBatchEditRequest extends TestPlanBatchProcessRequest {
@Schema(description = "是否追加")
private boolean append;
@Schema(description = "标签")
private List<String> tags;
}

View File

@ -32,4 +32,8 @@ public interface ExtTestPlanMapper {
void batchUpdateStatus(@Param("status") String status, @Param("userId") String userId, @Param("updateTime") Long updateTime, @Param("ids") List<String> ids); void batchUpdateStatus(@Param("status") String status, @Param("userId") String userId, @Param("updateTime") Long updateTime, @Param("ids") List<String> ids);
void batchMove(@Param("ids") List<String> ids, @Param("moduleId") String moduleId, @Param("userId") String userId, @Param("updateTime") long updateTime); void batchMove(@Param("ids") List<String> ids, @Param("moduleId") String moduleId, @Param("userId") String userId, @Param("updateTime") long updateTime);
List<TestPlan> getTagsByIds(@Param("ids") List<String> ids);
void batchUpdate(@Param("testPlan") TestPlan testPlan, @Param("ids") List<String> ids);
} }

View File

@ -378,4 +378,32 @@
</foreach> </foreach>
</update> </update>
<select id="getTagsByIds" resultType="io.metersphere.plan.domain.TestPlan">
select id, tags from test_plan
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<update id="batchUpdate">
update test_plan
<set>
<if test="testPlan.tags != null and testPlan.tags != ''">
tags = #{testPlan.tags,typeHandler=io.metersphere.handler.ListTypeHandler},
</if>
<if test="testPlan.updateUser != null and testPlan.updateUser != ''">
update_user = #{testPlan.updateUser},
</if>
<if test="testPlan.updateTime != null">
update_time = #{testPlan.updateTime},
</if>
</set>
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
and project_id = #{testPlan.projectId}
</update>
</mapper> </mapper>

View File

@ -1,6 +1,8 @@
package io.metersphere.plan.service; package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanExample;
import io.metersphere.plan.dto.request.TestPlanBatchEditRequest;
import io.metersphere.plan.dto.request.TestPlanCopyRequest; import io.metersphere.plan.dto.request.TestPlanCopyRequest;
import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.project.domain.Project; import io.metersphere.project.domain.Project;
@ -15,12 +17,15 @@ import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO; import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService; import io.metersphere.system.log.service.OperationLogService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -37,6 +42,7 @@ public class TestPlanLogService {
/** /**
* 新增计划日志 * 新增计划日志
*
* @param module 模块 * @param module 模块
* @param operator 操作人 * @param operator 操作人
* @param requestUrl 请求路径 * @param requestUrl 请求路径
@ -61,6 +67,7 @@ public class TestPlanLogService {
/** /**
* 修改计划日志 * 修改计划日志
*
* @param oldTestPlan 旧计划 * @param oldTestPlan 旧计划
* @param newTestPlan 新计划 * @param newTestPlan 新计划
* @param projectId 项目ID * @param projectId 项目ID
@ -88,6 +95,7 @@ public class TestPlanLogService {
/** /**
* 删除计划日志 * 删除计划日志
*
* @param deleteTestPlan 删除计划 * @param deleteTestPlan 删除计划
* @param operator 操作人 * @param operator 操作人
* @param requestUrl 请求URL * @param requestUrl 请求URL
@ -157,6 +165,7 @@ public class TestPlanLogService {
/** /**
* 保存批量日志 * 保存批量日志
*
* @param plans 计划 * @param plans 计划
* @param operator 操作人 * @param operator 操作人
* @param requestUrl 请求URL * @param requestUrl 请求URL
@ -187,6 +196,7 @@ public class TestPlanLogService {
/** /**
* 生成计划操作日志内容 * 生成计划操作日志内容
*
* @param testPlan 测试计划 * @param testPlan 测试计划
* @param type 类型 * @param type 类型
* @return 日志内容 * @return 日志内容
@ -200,4 +210,39 @@ public class TestPlanLogService {
} }
return content.toString(); return content.toString();
} }
/**
* 批量编辑
*
* @param request
* @return
*/
public List<LogDTO> batchEditLog(TestPlanBatchEditRequest request) {
// 测试计划不支持全选所有页
List<String> ids = request.getSelectIds();
List<LogDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andProjectIdEqualTo(request.getProjectId()).andIdIn(ids);
List<TestPlan> testPlans = testPlanMapper.selectByExample(example);
Map<String, TestPlan> collect = testPlans.stream().collect(Collectors.toMap(TestPlan::getId, testPlan -> testPlan));
ids.forEach(id -> {
TestPlan testPlan = collect.get(id);
LogDTO dto = new LogDTO(
testPlan.getProjectId(),
null,
testPlan.getId(),
null,
OperationLogType.UPDATE.name(),
OperationLogModule.TEST_PLAN,
testPlan.getName());
dto.setPath("/test-plan/batch-edit");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(testPlan));
dtoList.add(dto);
});
}
return dtoList;
}
} }

View File

@ -3,24 +3,28 @@ package io.metersphere.plan.service;
import io.metersphere.functional.domain.FunctionalCase; import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.mapper.FunctionalCaseMapper; import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanExample;
import io.metersphere.plan.dto.TestPlanDTO;
import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.NoticeModel; import io.metersphere.system.notice.NoticeModel;
import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.notice.utils.MessageTemplateUtils; import io.metersphere.system.notice.utils.MessageTemplateUtils;
import io.metersphere.system.service.CommonNoticeSendService;
import io.metersphere.system.service.NoticeSendService; import io.metersphere.system.service.NoticeSendService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.beanutils.BeanMap; import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap; import java.util.*;
import java.util.List; import java.util.stream.Collectors;
import java.util.Locale;
import java.util.Map;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -34,6 +38,8 @@ public class TestPlanSendNoticeService {
private TestPlanMapper testPlanMapper; private TestPlanMapper testPlanMapper;
@Resource @Resource
private NoticeSendService noticeSendService; private NoticeSendService noticeSendService;
@Resource
private CommonNoticeSendService commonNoticeSendService;
public void sendNoticeCase(List<String> relatedUsers, String userId, String caseId, String task, String event, String testPlanId) { public void sendNoticeCase(List<String> relatedUsers, String userId, String caseId, String task, String event, String testPlanId) {
FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(caseId); FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(caseId);
@ -71,4 +77,45 @@ public class TestPlanSendNoticeService {
} }
LocaleContextHolder.setLocale(locale); LocaleContextHolder.setLocale(locale);
} }
public void batchSendNotice(String projectId, List<String> ids, User user, String event) {
int amount = 100;//每次读取的条数
long roundTimes = Double.valueOf(Math.ceil((double) ids.size() / amount)).longValue();//循环的次数
for (int i = 0; i < (int) roundTimes; i++) {
int fromIndex = (i * amount);
int toIndex = ((i + 1) * amount);
if (i == roundTimes - 1 || toIndex > ids.size()) {//最后一次遍历
toIndex = ids.size();
}
List<String> subList = ids.subList(fromIndex, toIndex);
List<TestPlanDTO> testPlanDTOS = handleBatchNotice(projectId, subList);
List<Map> resources = new ArrayList<>();
resources.addAll(JSON.parseArray(JSON.toJSONString(testPlanDTOS), Map.class));
commonNoticeSendService.sendNotice(NoticeConstants.TaskType.TEST_PLAN_TASK, event, resources, user, projectId);
}
}
private List<TestPlanDTO> handleBatchNotice(String projectId, List<String> ids) {
List<TestPlanDTO> dtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ids)) {
Map<String, TestPlan> testPlanMap = getTestPlanInfo(projectId, ids);
ids.forEach(id -> {
if (testPlanMap.containsKey(id)) {
TestPlan testPlan = testPlanMap.get(id);
TestPlanDTO testPlanDTO = new TestPlanDTO();
BeanUtils.copyBean(testPlanDTO, testPlan);
dtoList.add(testPlanDTO);
}
});
}
return dtoList;
}
private Map<String, TestPlan> getTestPlanInfo(String projectId, List<String> ids) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andProjectIdEqualTo(projectId).andIdIn(ids);
List<TestPlan> testPlans = testPlanMapper.selectByExample(example);
return testPlans.stream().collect(Collectors.toMap(TestPlan::getId, testPlan -> testPlan));
}
} }

View File

@ -13,9 +13,12 @@ import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.ScheduleExample; import io.metersphere.system.domain.ScheduleExample;
import io.metersphere.system.domain.User;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.ScheduleMapper; import io.metersphere.system.mapper.ScheduleMapper;
import io.metersphere.system.mapper.TestPlanModuleMapper; import io.metersphere.system.mapper.TestPlanModuleMapper;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.BatchProcessUtils; import io.metersphere.system.utils.BatchProcessUtils;
@ -24,6 +27,10 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -59,7 +66,13 @@ public class TestPlanService extends TestPlanBaseUtilsService {
private TestPlanCaseService testPlanCaseService; private TestPlanCaseService testPlanCaseService;
@Resource @Resource
private ScheduleMapper scheduleMapper; private ScheduleMapper scheduleMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
private UserMapper userMapper;
@Resource
private TestPlanSendNoticeService testPlanSendNoticeService;
private static final int MAX_TAG_SIZE = 10;
/** /**
* 创建测试计划 * 创建测试计划
@ -157,6 +170,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
/** /**
* 计划组删除的相关逻辑(待定) * 计划组删除的相关逻辑(待定)
*
* @param testPlanGroupIds 计划组ID集合 * @param testPlanGroupIds 计划组ID集合
*/ */
private void deleteGroupByList(List<String> testPlanGroupIds) { private void deleteGroupByList(List<String> testPlanGroupIds) {
@ -531,4 +545,79 @@ public class TestPlanService extends TestPlanBaseUtilsService {
} }
} }
} }
/**
* 批量编辑
*
* @param request
* @param userId
*/
public void batchEdit(TestPlanBatchEditRequest request, String userId) {
// 目前计划的批量操作不支持全选所有页
List<String> ids = request.getSelectIds();
if (CollectionUtils.isNotEmpty(ids)) {
User user = userMapper.selectByPrimaryKey(userId);
handleTags(request, userId, ids);
testPlanSendNoticeService.batchSendNotice(request.getProjectId(), ids, user, NoticeConstants.Event.UPDATE);
}
}
/**
* 处理标签
*
* @param request
* @param userId
* @param ids
*/
private void handleTags(TestPlanBatchEditRequest request, String userId, List<String> ids) {
if (CollectionUtils.isNotEmpty(request.getTags())) {
if (request.isAppend()) {
//追加标签
List<TestPlan> planList = extTestPlanMapper.getTagsByIds(ids);
Map<String, TestPlan> collect = planList.stream().collect(Collectors.toMap(TestPlan::getId, v -> v));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestPlanMapper testPlanMapper = sqlSession.getMapper(TestPlanMapper.class);
ids.forEach(id -> {
TestPlan testPlan = new TestPlan();
if (CollectionUtils.isNotEmpty(collect.get(id).getTags())) {
List<String> tags = collect.get(id).getTags();
tags.addAll(request.getTags());
checkTagsLength(tags);
List<String> newTags = tags.stream().distinct().collect(Collectors.toList());
testPlan.setTags(newTags);
} else {
testPlan.setTags(request.getTags());
}
testPlan.setId(id);
testPlan.setUpdateTime(System.currentTimeMillis());
testPlan.setUpdateUser(userId);
testPlanMapper.updateByPrimaryKeySelective(testPlan);
});
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
} else {
//替换标签
TestPlan testPlan = new TestPlan();
testPlan.setTags(request.getTags());
testPlan.setProjectId(request.getProjectId());
testPlan.setUpdateTime(System.currentTimeMillis());
testPlan.setUpdateUser(userId);
extTestPlanMapper.batchUpdate(testPlan, ids);
}
}
}
/**
* 校验追加标签长度
*
* @param tags
*/
private void checkTagsLength(List<String> tags) {
if (tags.size() > MAX_TAG_SIZE) {
throw new MSException(Translator.getWithArgs("tags_length_large_than", String.valueOf(MAX_TAG_SIZE)));
}
}
} }

View File

@ -118,6 +118,7 @@ public class TestPlanTests extends BaseTest {
private static final String URL_TEST_PLAN_BATCH_COPY = "/test-plan/batch-copy"; private static final String URL_TEST_PLAN_BATCH_COPY = "/test-plan/batch-copy";
private static final String URL_TEST_PLAN_BATCH_MOVE = "/test-plan/batch-move"; private static final String URL_TEST_PLAN_BATCH_MOVE = "/test-plan/batch-move";
private static final String URL_TEST_PLAN_BATCH_ARCHIVED = "/test-plan/batch-archived"; private static final String URL_TEST_PLAN_BATCH_ARCHIVED = "/test-plan/batch-archived";
private static final String URL_TEST_PLAN_BATCH_EDIT = "/test-plan/batch-edit";
private static String groupTestPlanId7 = null; private static String groupTestPlanId7 = null;
private static String groupTestPlanId15 = null; private static String groupTestPlanId15 = null;
@ -1849,4 +1850,21 @@ public class TestPlanTests extends BaseTest {
this.requestPostWithOk(URL_POST_TEST_PLAN_STATISTICS, List.of("wx_test_plan_id_7")); this.requestPostWithOk(URL_POST_TEST_PLAN_STATISTICS, List.of("wx_test_plan_id_7"));
} }
@Test
@Order(307)
public void testBatchEditTestPlan() throws Exception {
TestPlanBatchEditRequest request = new TestPlanBatchEditRequest();
request.setTags(Arrays.asList("tag1", "tag2"));
request.setAppend(true);
request.setType("ALL");
request.setProjectId("123");
request.setSelectIds(Arrays.asList("wx_test_plan_id_1"));
this.requestPostWithOk(URL_TEST_PLAN_BATCH_EDIT, request);
request.setAppend(false);
request.setTags(Arrays.asList("tag3", "tag4"));
request.setSelectIds(Arrays.asList("wx_test_plan_id_1"));
this.requestPostWithOk(URL_TEST_PLAN_BATCH_EDIT, request);
}
} }