feat(测试计划): 补充测试计划批量操作功能

This commit is contained in:
song-cc-rock 2024-05-10 19:24:32 +08:00 committed by Craftsman
parent 73f0404d90
commit 457d709102
17 changed files with 267 additions and 136 deletions

View File

@ -440,6 +440,9 @@ add=添加
delete=删除 delete=删除
update=更新 update=更新
recover=恢复 recover=恢复
copy=复制
move=移动
archive=归档
project_is_not_exist=项目不存在 project_is_not_exist=项目不存在
#permission #permission

View File

@ -448,6 +448,9 @@ add=Add
delete=Delete delete=Delete
update=Update update=Update
recover=Recover recover=Recover
copy=Copy
move=Move
archive=Archive
project_is_not_exist=Project is not exist project_is_not_exist=Project is not exist
#permission #permission

View File

@ -447,6 +447,9 @@ add=添加
delete=删除 delete=删除
update=更新 update=更新
recover=恢复 recover=恢复
copy=复制
move=移动
archive=归档
project_is_not_exist=项目不存在 project_is_not_exist=项目不存在
#permission #permission

View File

@ -445,6 +445,9 @@ add=添加
delete=删除 delete=删除
update=更新 update=更新
recover=恢復 recover=恢復
copy=複製
move=移動
archive=歸檔
project_is_not_exist=項目不存在 project_is_not_exist=項目不存在
#permission #permission

View File

@ -85,12 +85,5 @@ test_plan_principal.user_id.not_blank=用户id不能为空
test_plan_report_content.id.not_blank=测试计划报告内容id不能为空 test_plan_report_content.id.not_blank=测试计划报告内容id不能为空
test_plan_report_content.test_plan_report_id.length_range=测试计划报告id长度过长 test_plan_report_content.test_plan_report_id.length_range=测试计划报告id长度过长
test_plan_report_content.test_plan_report_id.not_blank=测试计划报告id不能为空 test_plan_report_content.test_plan_report_id.not_blank=测试计划报告id不能为空
log.delete.test_plan=删除测试计划 test_plan_group.batch.log={0}测试计划组
log.delete.test_plan_group=删除测试计划组 test_plan.batch.log={0}测试计划
log.test_plan.add=关联了资源
log.test_plan.remove=移除了资源
log.test_plan.move=移动了资源
log.test_plan.update=修改了资源
log.test_plan.functional_case=功能用例
log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口场景

View File

@ -96,9 +96,5 @@ log.test_plan.api_case=Api case
log.test_plan.api_scenario=Api scenario log.test_plan.api_scenario=Api scenario
test_plan.type.not_blank=Test plan type cannot be empty test_plan.type.not_blank=Test plan type cannot be empty
test_plan.group.not_plan=There are no archived plans in the current testing plan group test_plan.group.not_plan=There are no archived plans in the current testing plan group
log.copy.test_plan_group=Copy test plan group test_plan_group.batch.log={0} test plan group
log.copy.test_plan=Copy test plan test_plan.batch.log={0} plan
log.update.test_plan_group=Update test plan group
log.update.test_plan=Update test plan
log.move.test_plan_group=Move test plan group
log.move.test_plan=Move test plan

View File

@ -96,9 +96,5 @@ log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口场景 log.test_plan.api_scenario=接口场景
test_plan.type.not_blank=测试计划类型不能为空 test_plan.type.not_blank=测试计划类型不能为空
test_plan.group.not_plan=当前测试计划组没有可归档计划 test_plan.group.not_plan=当前测试计划组没有可归档计划
log.copy.test_plan_group=复制测试计划组 test_plan_group.batch.log={0}测试计划组
log.copy.test_plan=复制测试计划 test_plan.batch.log={0}测试计划
log.update.test_plan_group=更新测试计划组
log.update.test_plan=更新测试计划
log.move.test_plan_group=移动测试计划组
log.move.test_plan=移动测试计划

View File

@ -96,9 +96,5 @@ log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口場景 log.test_plan.api_scenario=接口場景
test_plan.type.not_blank=測試計劃類型不能為空 test_plan.type.not_blank=測試計劃類型不能為空
test_plan.group.not_plan=當前測試計劃組沒有可歸檔計劃 test_plan.group.not_plan=當前測試計劃組沒有可歸檔計劃
log.copy.test_plan_group=複製測試計劃組 test_plan_group.batch.log={0}測試計劃組
log.copy.test_plan=複製測試計劃 test_plan.batch.log={0}測試計劃
log.update.test_plan_group=更新測試計劃組
log.update.test_plan=更新測試計劃
log.move.test_plan_group=移動測試計劃組
log.move.test_plan=移動測試計劃

View File

@ -6,6 +6,7 @@ import io.metersphere.plan.dto.request.TestPlanBugPageRequest;
import io.metersphere.plan.dto.response.TestPlanBugPageResponse; import io.metersphere.plan.dto.response.TestPlanBugPageResponse;
import io.metersphere.plan.service.TestPlanBugService; import io.metersphere.plan.service.TestPlanBugService;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils; import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager; import io.metersphere.system.utils.Pager;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -32,6 +33,7 @@ public class TestPlanBugController {
@PostMapping("/page") @PostMapping("/page")
@Operation(summary = "缺陷列表-分页查询") @Operation(summary = "缺陷列表-分页查询")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ)
@CheckOwner(resourceId = "#requst.getPlanId()", resourceType = "test_plan")
public Pager<List<TestPlanBugPageResponse>> page(@Validated @RequestBody TestPlanBugPageRequest request) { public Pager<List<TestPlanBugPageResponse>> page(@Validated @RequestBody TestPlanBugPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "b.create_time desc"); StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "b.create_time desc");

View File

@ -85,17 +85,6 @@ public class TestPlanController {
testPlanService.delete(id, SessionUtils.getUserId(), "/test-plan/delete", HttpMethodConstants.GET.name()); testPlanService.delete(id, SessionUtils.getUserId(), "/test-plan/delete", HttpMethodConstants.GET.name());
} }
@PostMapping(value = "/batch-delete")
@Operation(summary = "测试计划-批量删除")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_DELETE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void delete(@Validated @RequestBody TestPlanBatchProcessRequest request) throws Exception {
testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchDelete(request, SessionUtils.getUserId(), "/test-plan/batch-delete", HttpMethodConstants.POST.name());
}
@PostMapping("/edit/follower") @PostMapping("/edit/follower")
@Operation(summary = "测试计划-关注/取消关注") @Operation(summary = "测试计划-关注/取消关注")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@ -116,7 +105,6 @@ public class TestPlanController {
testPlanService.archived(id, userId); testPlanService.archived(id, userId);
} }
@PostMapping("/copy") @PostMapping("/copy")
@Operation(summary = "测试计划-复制测试计划") @Operation(summary = "测试计划-复制测试计划")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@ -134,21 +122,40 @@ public class TestPlanController {
return testPlanService.detail(id); return testPlanService.detail(id);
} }
@PostMapping(value = "/batch-delete")
@Operation(summary = "测试计划-批量删除")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_DELETE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void delete(@Validated @RequestBody TestPlanBatchProcessRequest request) throws Exception {
testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchDelete(request, SessionUtils.getUserId(), "/test-plan/batch-delete", HttpMethodConstants.POST.name());
}
@PostMapping("/batch/copy") @PostMapping("/batch-copy")
@Operation(summary = "测试计划-批量复制测试计划") @Operation(summary = "测试计划-批量复制测试计划")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchCopy(@Validated @RequestBody TestPlanBatchRequest request) { public void batchCopy(@Validated @RequestBody TestPlanBatchRequest request) {
testPlanService.batchCopy(request, SessionUtils.getUserId(), "/test-plan/batch/copy", HttpMethodConstants.POST.name()); testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchCopy(request, SessionUtils.getUserId(), "/test-plan/batch-copy", HttpMethodConstants.POST.name());
} }
@PostMapping("/batch/move") @PostMapping("/batch-move")
@Operation(summary = "测试计划-批量移动测试计划") @Operation(summary = "测试计划-批量移动测试计划")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchMove(@Validated @RequestBody TestPlanBatchRequest request) { public void batchMove(@Validated @RequestBody TestPlanBatchRequest request) {
testPlanService.batchMove(request, SessionUtils.getUserId(), "/test-plan/batch/move", HttpMethodConstants.POST.name()); testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchMove(request, SessionUtils.getUserId(), "/test-plan/batch-move", HttpMethodConstants.POST.name());
}
@PostMapping("/batch-archived")
@Operation(summary = "测试计划-批量归档")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchArchived(@Validated @RequestBody TestPlanBatchRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanService.batchArchived(request, SessionUtils.getUserId());
} }
} }

View File

@ -10,6 +10,8 @@ public class TestPlanBugCaseDTO {
@Schema(description = "用例ID") @Schema(description = "用例ID")
private String id; private String id;
@Schema(description = "用例业务ID")
private String num;
@Schema(description = "缺陷ID") @Schema(description = "缺陷ID")
private String bugId; private String bugId;
@Schema(description = "用例名称") @Schema(description = "用例名称")

View File

@ -24,7 +24,7 @@
</select> </select>
<select id="getBugRelatedCase" resultType="io.metersphere.plan.dto.TestPlanBugCaseDTO"> <select id="getBugRelatedCase" resultType="io.metersphere.plan.dto.TestPlanBugCaseDTO">
select brc.test_plan_case_id as id, brc.bug_id as bugId, fc.name as name select brc.test_plan_case_id as id, fc.num as num, brc.bug_id as bugId, fc.name as name
from bug_relation_case brc from bug_relation_case brc
join functional_case fc on brc.test_plan_case_id = fc.id join functional_case fc on brc.test_plan_case_id = fc.id
# 后续会有其他用例, 根据关联用例类型, 取不同用例表 # 后续会有其他用例, 根据关联用例类型, 取不同用例表

View File

@ -0,0 +1,68 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanExample;
import io.metersphere.plan.dto.request.TestPlanBatchProcessRequest;
import io.metersphere.plan.dto.request.TestPlanBatchRequest;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanBatchArchivedService extends TestPlanBaseUtilsService {
@Resource
private TestPlanMapper testPlanMapper;
public void batchArchived(Map<String, List<TestPlan>> plans, TestPlanBatchRequest request, String userId) {
int affectedGroupPlanCount = batchArchivedGroup(plans, request, userId);
int affectedPlanCount = batchArchivedPlan(plans, request, userId);
if (affectedGroupPlanCount <= 0 && affectedPlanCount <= 0) {
// 暂无可归档的计划
throw new MSException("");
}
}
/**
* 批量移动组
*
* @param plans
*/
private int batchArchivedGroup(Map<String, List<TestPlan>> plans, TestPlanBatchProcessRequest request, String userId) {
//TODO 批量移动计划组
return 0;
}
/**
* 批量移动计划
*
* @param plans 归档测试计划集合
*/
private int batchArchivedPlan(Map<String, List<TestPlan>> plans, TestPlanBatchRequest request, String userId) {
if (plans.containsKey(TestPlanConstants.TEST_PLAN_TYPE_PLAN)) {
List<TestPlan> testPlans = plans.get(TestPlanConstants.TEST_PLAN_TYPE_PLAN);
testPlans.forEach(testPlan -> {
testPlan.setModuleId(request.getModuleId());
validateTestPlan(testPlan);
});
List<String> ids = testPlans.stream().map(TestPlan::getId).collect(Collectors.toList());
TestPlan record = new TestPlan();
record.setStatus(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED);
record.setUpdateUser(userId);
record.setUpdateTime(System.currentTimeMillis());
TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(ids);
return testPlanMapper.updateByExampleSelective(record, example);
} else {
return 0;
}
}
}

View File

@ -35,6 +35,13 @@ public class TestPlanLogService {
@Resource @Resource
private TestPlanMapper testPlanMapper; private TestPlanMapper testPlanMapper;
/**
* 新增计划日志
* @param module 模块
* @param operator 操作人
* @param requestUrl 请求路径
* @param requestMethod 请求方法
*/
public void saveAddLog(TestPlan module, String operator, String requestUrl, String requestMethod) { public void saveAddLog(TestPlan module, String operator, String requestUrl, String requestMethod) {
Project project = projectMapper.selectByPrimaryKey(module.getProjectId()); Project project = projectMapper.selectByPrimaryKey(module.getProjectId());
LogDTO dto = LogDTOBuilder.builder() LogDTO dto = LogDTOBuilder.builder()
@ -45,13 +52,22 @@ public class TestPlanLogService {
.method(requestMethod) .method(requestMethod)
.path(requestUrl) .path(requestUrl)
.sourceId(module.getId()) .sourceId(module.getId())
.content(generateTestPlanSimpleContent(module,"test_plan.test_plan_group","test_plan.test_plan")) .content(module.getName())
.originalValue(JSON.toJSONBytes(module)) .originalValue(JSON.toJSONBytes(module))
.createUser(operator) .createUser(operator)
.build().getLogDTO(); .build().getLogDTO();
operationLogService.add(dto); operationLogService.add(dto);
} }
/**
* 修改计划日志
* @param oldTestPlan 旧计划
* @param newTestPlan 新计划
* @param projectId 项目ID
* @param operator 操作人
* @param requestUrl 请求URL
* @param requestMethod 请求方法
*/
public void saveUpdateLog(TestPlan oldTestPlan, TestPlan newTestPlan, String projectId, String operator, String requestUrl, String requestMethod) { public void saveUpdateLog(TestPlan oldTestPlan, TestPlan newTestPlan, String projectId, String operator, String requestUrl, String requestMethod) {
Project project = projectMapper.selectByPrimaryKey(projectId); Project project = projectMapper.selectByPrimaryKey(projectId);
LogDTO dto = LogDTOBuilder.builder() LogDTO dto = LogDTOBuilder.builder()
@ -62,7 +78,7 @@ public class TestPlanLogService {
.method(requestMethod) .method(requestMethod)
.path(requestUrl) .path(requestUrl)
.sourceId(newTestPlan.getId()) .sourceId(newTestPlan.getId())
.content(generateTestPlanSimpleContent(newTestPlan,"log.update.test_plan_group","log.update.test_plan")) .content(generateTestPlanSimpleContent(newTestPlan, Translator.get("update")))
.originalValue(JSON.toJSONBytes(oldTestPlan)) .originalValue(JSON.toJSONBytes(oldTestPlan))
.modifiedValue(JSON.toJSONBytes(newTestPlan)) .modifiedValue(JSON.toJSONBytes(newTestPlan))
.createUser(operator) .createUser(operator)
@ -70,6 +86,13 @@ public class TestPlanLogService {
operationLogService.add(dto); operationLogService.add(dto);
} }
/**
* 删除计划日志
* @param deleteTestPlan 删除计划
* @param operator 操作人
* @param requestUrl 请求URL
* @param requestMethod 请求方法
*/
public void saveDeleteLog(TestPlan deleteTestPlan, String operator, String requestUrl, String requestMethod) { public void saveDeleteLog(TestPlan deleteTestPlan, String operator, String requestUrl, String requestMethod) {
Project project = projectMapper.selectByPrimaryKey(deleteTestPlan.getProjectId()); Project project = projectMapper.selectByPrimaryKey(deleteTestPlan.getProjectId());
LogDTO dto = LogDTOBuilder.builder() LogDTO dto = LogDTOBuilder.builder()
@ -80,49 +103,18 @@ public class TestPlanLogService {
.method(requestMethod) .method(requestMethod)
.path(requestUrl) .path(requestUrl)
.sourceId(deleteTestPlan.getId()) .sourceId(deleteTestPlan.getId())
.content(generateTestPlanSimpleContent(deleteTestPlan,"log.delete.test_plan_group","log.delete.test_plan")) .content(generateTestPlanSimpleContent(deleteTestPlan, Translator.get("delete")))
.originalValue(JSON.toJSONBytes(deleteTestPlan)) .originalValue(JSON.toJSONBytes(deleteTestPlan))
.createUser(operator) .createUser(operator)
.build().getLogDTO(); .build().getLogDTO();
operationLogService.add(dto); operationLogService.add(dto);
} }
public void saveBatchDeleteLog(List<TestPlan> testPlanList, String operator, String requestUrl, String requestMethod) {
Project project = projectMapper.selectByPrimaryKey(testPlanList.get(0).getProjectId());
List<LogDTO> list = new ArrayList<>();
for (TestPlan testPlan : testPlanList) {
LogDTO dto = LogDTOBuilder.builder()
.projectId(testPlan.getProjectId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.DELETE.name())
.module(logModule)
.method(requestMethod)
.path(requestUrl)
.sourceId(testPlan.getId())
.content(generateTestPlanSimpleContent(testPlan, "log.delete.test_plan_group", "log.delete.test_plan"))
.originalValue(JSON.toJSONBytes(testPlan))
.createUser(operator)
.build().getLogDTO();
list.add(dto);
}
operationLogService.batchAdd(list);
}
private String generateTestPlanSimpleContent(TestPlan testPlan,String groupKey,String planKey) {
StringBuilder content = new StringBuilder();
if (StringUtils.equals(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
content.append(Translator.get(groupKey)).append(StringUtils.SPACE).append(testPlan.getName()).append(StringUtils.SPACE);
} else {
content.append(Translator.get(planKey)).append(StringUtils.SPACE).append(testPlan.getName()).append(StringUtils.SPACE);
}
return content.toString();
}
/** /**
* 归档日志 * 归档日志
* *
* @param id * @param id ID
* @return * @return 日志对象
*/ */
public LogDTO archivedLog(String id) { public LogDTO archivedLog(String id) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id); TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id);
@ -143,8 +135,8 @@ public class TestPlanLogService {
/** /**
* 复制日志 * 复制日志
* *
* @param request * @param request 请求参数
* @return * @return 日志对象
*/ */
public LogDTO copyLog(TestPlanCopyRequest request) { public LogDTO copyLog(TestPlanCopyRequest request) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId()); TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId());
@ -156,27 +148,36 @@ public class TestPlanLogService {
null, null,
OperationLogType.COPY.name(), OperationLogType.COPY.name(),
logModule, logModule,
generateTestPlanSimpleContent(testPlan, "log.copy.test_plan_group", "log.copy.test_plan")); generateTestPlanSimpleContent(testPlan, Translator.get("copy")));
dto.setPath("/test-plan/copy"); dto.setPath("/test-plan/copy");
dto.setMethod(HttpMethodConstants.POST.name()); dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(request)); dto.setOriginalValue(JSON.toJSONBytes(request));
return dto; return dto;
} }
public void saveBatchCopyLog(List<TestPlan> testPlanList, String operator, String requestUrl, String requestMethod) { /**
Project project = projectMapper.selectByPrimaryKey(testPlanList.get(0).getProjectId()); * 保存批量日志
* @param plans 计划
* @param operator 操作人
* @param requestUrl 请求URL
* @param requestMethod 请求方法
* @param requestType 请求类型
* @param typeKey 类型Key
*/
public void saveBatchLog(List<TestPlan> plans, String operator, String requestUrl, String requestMethod, String requestType, String typeKey) {
Project project = projectMapper.selectByPrimaryKey(plans.get(0).getProjectId());
List<LogDTO> list = new ArrayList<>(); List<LogDTO> list = new ArrayList<>();
for (TestPlan testPlan : testPlanList) { for (TestPlan plan : plans) {
LogDTO dto = LogDTOBuilder.builder() LogDTO dto = LogDTOBuilder.builder()
.projectId(testPlan.getProjectId()) .projectId(plan.getProjectId())
.organizationId(project.getOrganizationId()) .organizationId(project.getOrganizationId())
.type(OperationLogType.COPY.name()) .type(requestType)
.module(logModule) .module(logModule)
.method(requestMethod) .method(requestMethod)
.path(requestUrl) .path(requestUrl)
.sourceId(testPlan.getId()) .sourceId(plan.getId())
.content(generateTestPlanSimpleContent(testPlan, "log.copy.test_plan_group", "log.copy.test_plan")) .content(generateTestPlanSimpleContent(plan, Translator.get(typeKey)))
.originalValue(JSON.toJSONBytes(testPlan)) .originalValue(JSON.toJSONBytes(plan))
.createUser(operator) .createUser(operator)
.build().getLogDTO(); .build().getLogDTO();
list.add(dto); list.add(dto);
@ -184,24 +185,19 @@ public class TestPlanLogService {
operationLogService.batchAdd(list); operationLogService.batchAdd(list);
} }
public void saveBatchMoveLog(List<TestPlan> testPlanList, String operator, String requestUrl, String requestMethod) { /**
Project project = projectMapper.selectByPrimaryKey(testPlanList.get(0).getProjectId()); * 生成计划操作日志内容
List<LogDTO> list = new ArrayList<>(); * @param testPlan 测试计划
for (TestPlan testPlan : testPlanList) { * @param type 类型
LogDTO dto = LogDTOBuilder.builder() * @return 日志内容
.projectId(testPlan.getProjectId()) */
.organizationId(project.getOrganizationId()) private String generateTestPlanSimpleContent(TestPlan testPlan, String type) {
.type(OperationLogType.UPDATE.name()) StringBuilder content = new StringBuilder();
.module(logModule) if (StringUtils.equals(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
.method(requestMethod) content.append(Translator.getWithArgs("test_plan_group.batch.log", type)).append(StringUtils.SPACE).append(testPlan.getName()).append(StringUtils.SPACE);
.path(requestUrl) } else {
.sourceId(testPlan.getId()) content.append(Translator.getWithArgs("test_plan.batch.log", type)).append(StringUtils.SPACE).append(testPlan.getName()).append(StringUtils.SPACE);
.content(generateTestPlanSimpleContent(testPlan, "log.move.test_plan_group", "log.move.test_plan"))
.originalValue(JSON.toJSONBytes(testPlan))
.createUser(operator)
.build().getLogDTO();
list.add(dto);
} }
operationLogService.batchAdd(list); return content.toString();
} }
} }

View File

@ -6,18 +6,21 @@ import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.response.TestPlanDetailResponse; import io.metersphere.plan.dto.response.TestPlanDetailResponse;
import io.metersphere.plan.mapper.*; import io.metersphere.plan.mapper.*;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils; 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.log.constants.OperationLogType;
import io.metersphere.system.mapper.TestPlanModuleMapper; import io.metersphere.system.mapper.TestPlanModuleMapper;
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;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
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.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -49,6 +52,8 @@ public class TestPlanService extends TestPlanBaseUtilsService {
private TestPlanBatchCopyService testPlanBatchCopyService; private TestPlanBatchCopyService testPlanBatchCopyService;
@Resource @Resource
private TestPlanBatchMoveService testPlanBatchMoveService; private TestPlanBatchMoveService testPlanBatchMoveService;
@Resource
private TestPlanBatchArchivedService testPlanBatchArchivedService;
/** /**
@ -147,6 +152,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
public void delete(String id, String operator, String requestUrl, String requestMethod) { public void delete(String id, String operator, String requestUrl, String requestMethod) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id); TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id);
if (StringUtils.equals(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { if (StringUtils.equals(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
// 计划组的删除暂时预留
this.deleteGroupByList(Collections.singletonList(testPlan.getId())); this.deleteGroupByList(Collections.singletonList(testPlan.getId()));
} else { } else {
testPlanMapper.deleteByPrimaryKey(id); testPlanMapper.deleteByPrimaryKey(id);
@ -169,16 +175,29 @@ public class TestPlanService extends TestPlanBaseUtilsService {
} }
} }
/**
* 计划组删除的相关逻辑(待定)
* @param testPlanGroupIds 计划组ID集合
*/
private void deleteGroupByList(List<String> testPlanGroupIds) { private void deleteGroupByList(List<String> testPlanGroupIds) {
if (CollectionUtils.isNotEmpty(testPlanGroupIds)) { if (CollectionUtils.isNotEmpty(testPlanGroupIds)) {
BatchProcessUtils.consumerByString(testPlanGroupIds, (deleteGroupIds) -> { BatchProcessUtils.consumerByString(testPlanGroupIds, (deleteGroupIds) -> {
/*
* 计划组删除逻辑{第一版需求: 删除组, 组下的子计划Group置为None}:
* 1. 查询计划组下的全部子计划并删除(级联删除这些子计划的关联资源)
* 2. 删除所有计划组
*/
TestPlanExample testPlanExample = new TestPlanExample(); TestPlanExample testPlanExample = new TestPlanExample();
testPlanExample.createCriteria().andIdIn(deleteGroupIds); testPlanExample.createCriteria().andGroupIdIn(deleteGroupIds);
List<TestPlan> deleteGroupPlans = testPlanMapper.selectByExample(testPlanExample);
List<String> deleteGroupPlanIds = deleteGroupPlans.stream().map(TestPlan::getId).toList();
if (CollectionUtils.isNotEmpty(deleteGroupPlanIds)) {
// 级联删除子计划关联的资源(计划组不存在关联的资源)
this.cascadeDeleteTestPlanIds(deleteGroupPlanIds);
}
testPlanExample.clear();
testPlanExample.createCriteria().andIdIn(ListUtils.union(deleteGroupIds, deleteGroupPlanIds));
testPlanMapper.deleteByExample(testPlanExample); testPlanMapper.deleteByExample(testPlanExample);
//级联删除
this.cascadeDeleteTestPlanIds(deleteGroupIds);
//更新组下的数据
extTestPlanMapper.updateDefaultGroupId(deleteGroupIds);
}); });
} }
} }
@ -187,13 +206,14 @@ public class TestPlanService extends TestPlanBaseUtilsService {
/** /**
* 批量删除测试计划 * 批量删除测试计划
* *
* @param request * @param request 批量请求参数
* @param operator * @param operator 当前登录操作人
* @param requestUrl * @param requestUrl 请求URL
* @param requestMethod * @param requestMethod 请求方法
*/ */
public void batchDelete(TestPlanBatchProcessRequest request, String operator, String requestUrl, String requestMethod) { public void batchDelete(TestPlanBatchProcessRequest request, String operator, String requestUrl, String requestMethod) {
List<String> deleteIdList = getSelectIds(request); // 目前计划的批量操作不支持全选所有页
List<String> deleteIdList = request.getSelectIds();
if (CollectionUtils.isNotEmpty(deleteIdList)) { if (CollectionUtils.isNotEmpty(deleteIdList)) {
List<TestPlan> deleteTestPlanList = extTestPlanMapper.selectBaseInfoByIds(deleteIdList); List<TestPlan> deleteTestPlanList = extTestPlanMapper.selectBaseInfoByIds(deleteIdList);
if (CollectionUtils.isNotEmpty(deleteTestPlanList)) { if (CollectionUtils.isNotEmpty(deleteTestPlanList)) {
@ -206,15 +226,20 @@ public class TestPlanService extends TestPlanBaseUtilsService {
testPlanIdList.add(testPlan.getId()); testPlanIdList.add(testPlan.getId());
} }
} }
this.deleteByList(deleteIdList); this.deleteByList(testPlanIdList);
// 计划组的删除暂时预留
this.deleteGroupByList(testPlanGroupList); this.deleteGroupByList(testPlanGroupList);
//记录日志 //记录日志
testPlanLogService.saveBatchDeleteLog(deleteTestPlanList, operator, requestUrl, requestMethod); testPlanLogService.saveBatchLog(deleteTestPlanList, operator, requestUrl, requestMethod, OperationLogType.DELETE.name(), "delete");
} }
} }
} }
/**
* 级联删除计划关联的资源
* @param testPlanIds 计划ID集合
*/
private void cascadeDeleteTestPlanIds(List<String> testPlanIds) { private void cascadeDeleteTestPlanIds(List<String> testPlanIds) {
//删除当前计划对应的资源 //删除当前计划对应的资源
Map<String, TestPlanResourceService> subTypes = CommonBeanFactory.getBeansOfType(TestPlanResourceService.class); Map<String, TestPlanResourceService> subTypes = CommonBeanFactory.getBeansOfType(TestPlanResourceService.class);
@ -331,6 +356,24 @@ public class TestPlanService extends TestPlanBaseUtilsService {
} }
/**
* 批量归档
* @param request 批量请求参数
* @param currentUser 当前用户
*/
public void batchArchived(TestPlanBatchRequest request, String currentUser) {
List<String> batchArchivedIds = request.getSelectIds();
if (CollectionUtils.isNotEmpty(batchArchivedIds)) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(batchArchivedIds);
List<TestPlan> archivedPlanList = testPlanMapper.selectByExample(example);
Map<String, List<TestPlan>> plans = archivedPlanList.stream().collect(Collectors.groupingBy(TestPlan::getType));
testPlanBatchArchivedService.batchArchived(plans, request, currentUser);
//日志
testPlanLogService.saveBatchLog(archivedPlanList, currentUser, "/test-plan/batch-archived", HttpMethodConstants.POST.name(), OperationLogType.UPDATE.name(), "archive");
}
}
/** /**
* 测试计划组归档 * 测试计划组归档
* *
@ -454,14 +497,14 @@ public class TestPlanService extends TestPlanBaseUtilsService {
/** /**
* 批量复制 计划/计划组 * 批量复制 计划/计划组
* *
* @param request * @param request 批量请求参数
* @param userId * @param userId 当前登录用户
* @param url * @param url 请求URL
* @param method * @param method 请求方法
* @return
*/ */
public void batchCopy(TestPlanBatchRequest request, String userId, String url, String method) { public void batchCopy(TestPlanBatchRequest request, String userId, String url, String method) {
List<String> copyIds = getSelectIds(request); // 目前计划的批量操作不支持全选所有页
List<String> copyIds = request.getSelectIds();
if (CollectionUtils.isNotEmpty(copyIds)) { if (CollectionUtils.isNotEmpty(copyIds)) {
TestPlanExample example = new TestPlanExample(); TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(copyIds); example.createCriteria().andIdIn(copyIds);
@ -470,13 +513,21 @@ public class TestPlanService extends TestPlanBaseUtilsService {
Map<String, List<TestPlan>> plans = copyTestPlanList.stream().collect(Collectors.groupingBy(TestPlan::getType)); Map<String, List<TestPlan>> plans = copyTestPlanList.stream().collect(Collectors.groupingBy(TestPlan::getType));
testPlanBatchCopyService.batchCopy(plans, request, userId); testPlanBatchCopyService.batchCopy(plans, request, userId);
//日志 //日志
testPlanLogService.saveBatchCopyLog(copyTestPlanList, userId, url, method); testPlanLogService.saveBatchLog(copyTestPlanList, userId, url, method, OperationLogType.COPY.name(), "copy");
} }
} }
} }
/**
* 批量移动 (计划/计划组)
* @param request 批量请求参数
* @param userId 当前登录用户
* @param url 请求URL
* @param method 请求方法
*/
public void batchMove(TestPlanBatchRequest request, String userId, String url, String method) { public void batchMove(TestPlanBatchRequest request, String userId, String url, String method) {
List<String> moveIds = getSelectIds(request); // 目前计划的批量操作不支持全选所有页
List<String> moveIds = request.getSelectIds();
if (CollectionUtils.isNotEmpty(moveIds)) { if (CollectionUtils.isNotEmpty(moveIds)) {
TestPlanExample example = new TestPlanExample(); TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(moveIds); example.createCriteria().andIdIn(moveIds);
@ -485,7 +536,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
Map<String, List<TestPlan>> plans = moveTestPlanList.stream().collect(Collectors.groupingBy(TestPlan::getType)); Map<String, List<TestPlan>> plans = moveTestPlanList.stream().collect(Collectors.groupingBy(TestPlan::getType));
testPlanBatchMoveService.batchMove(plans, request, userId); testPlanBatchMoveService.batchMove(plans, request, userId);
//日志 //日志
testPlanLogService.saveBatchMoveLog(moveTestPlanList, userId, url, method); testPlanLogService.saveBatchLog(moveTestPlanList, userId, url, method, OperationLogType.UPDATE.name(), "update");
} }
} }
} }

View File

@ -122,8 +122,9 @@ public class TestPlanTests extends BaseTest {
private static final String URL_TEST_PLAN_ARCHIVED = "/test-plan/archived/%s"; private static final String URL_TEST_PLAN_ARCHIVED = "/test-plan/archived/%s";
private static final String URL_TEST_PLAN_COPY = "/test-plan/copy"; private static final String URL_TEST_PLAN_COPY = "/test-plan/copy";
private static final String URL_TEST_PLAN_DETAIL = "/test-plan/%s"; private static final String URL_TEST_PLAN_DETAIL = "/test-plan/%s";
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 String groupTestPlanId7 = null; private static String groupTestPlanId7 = null;
private static String groupTestPlanId15 = null; private static String groupTestPlanId15 = null;
@ -1884,9 +1885,6 @@ public class TestPlanTests extends BaseTest {
example = new TestPlanModuleExample(); example = new TestPlanModuleExample();
example.createCriteria().andIdEqualTo(id); example.createCriteria().andIdEqualTo(id);
Assertions.assertEquals(testPlanModuleMapper.countByExample(example), 0); Assertions.assertEquals(testPlanModuleMapper.countByExample(example), 0);
// 该模块下已无测试计划
Assertions.assertEquals(testPlanTestService.countByModuleId(id), 0);
} }
@ -2206,4 +2204,17 @@ public class TestPlanTests extends BaseTest {
} }
@Test
@Order(305)
public void testBatchArchived() throws Exception {
TestPlanBatchRequest request = new TestPlanBatchRequest();
request.setProjectId("123");
request.setType("ALL");
request.setModuleId("3");
request.setSelectIds(List.of("wx_test_plan_id_2"));
this.requestPost(URL_TEST_PLAN_BATCH_ARCHIVED, request, status().is5xxServerError());
request.setSelectIds(List.of("wx_test_plan_id_7"));
this.requestPostWithOkAndReturn(URL_TEST_PLAN_BATCH_ARCHIVED, request);
}
} }

View File

@ -5,7 +5,8 @@ VALUES
('wx_test_plan_id_3', 15000, '123', 'NONE', '1', '测试一下组2', 'PREPARED', 'GROUP', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'), ('wx_test_plan_id_3', 15000, '123', 'NONE', '1', '测试一下组2', 'PREPARED', 'GROUP', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('wx_test_plan_id_4', 20000, '123', 'wx_test_plan_id_3', '1', '测试一下计划2', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'), ('wx_test_plan_id_4', 20000, '123', 'wx_test_plan_id_3', '1', '测试一下计划2', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('wx_test_plan_id_5', 25000, '123', 'NONE', '1', '测试一下组3', 'PREPARED', 'GROUP', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'), ('wx_test_plan_id_5', 25000, '123', 'NONE', '1', '测试一下组3', 'PREPARED', 'GROUP', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('wx_test_plan_id_6', 30000, '123', 'wx_test_plan_id_5', '1', '测试组3下计划', 'COMPLETED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'); ('wx_test_plan_id_6', 30000, '123', 'wx_test_plan_id_5', '1', '测试组3下计划', 'COMPLETED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('wx_test_plan_id_7', 30000, '123', 'NONE', '1', '测试组4下计划', 'COMPLETED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
INSERT INTO `test_plan_functional_case`(`id`, `num`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`) INSERT INTO `test_plan_functional_case`(`id`, `num`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`)