fix(测试计划): 关联用例列表执行人筛选有误

--bug=1044986 --user=宋昌昌 【测试计划】关联用例成功后,执行人字段默认为空 https://www.tapd.cn/55049933/s/1561580
This commit is contained in:
song-cc-rock 2024-08-13 15:59:22 +08:00 committed by 刘瑞斌
parent 62ce36e2ba
commit 86dd1e49f8
18 changed files with 178 additions and 26 deletions

View File

@ -6,23 +6,25 @@ import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
import io.metersphere.plan.service.TestPlanExecuteService;
import io.metersphere.plan.service.TestPlanLogService;
import io.metersphere.plan.service.TestPlanManagementService;
import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.user.UserExtendDTO;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
@RestController
@RequestMapping("/test-plan-execute")
@ -31,9 +33,12 @@ public class TestPlanExecuteController {
@Resource
private TestPlanManagementService testPlanManagementService;
@Resource
private TestPlanExecuteService testPlanExecuteService;
@Resource
private ProjectService projectService;
private static final String NULL_KEY = "-";
@PostMapping("/single")
@Operation(summary = "测试计划单独执行")
@ -61,4 +66,21 @@ public class TestPlanExecuteController {
);
}
@GetMapping("/user-option/{projectId}")
@Operation(summary = "执行人下拉选项(空选项)")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<UserExtendDTO> getMemberOption(@PathVariable String projectId,
@Schema(description = "查询关键字,根据邮箱和用户名查询")
@RequestParam(value = "keyword", required = false) String keyword) {
List<UserExtendDTO> memberOption = projectService.getMemberOption(projectId, keyword);
// 空选项
if (StringUtils.isBlank(keyword) || StringUtils.equals(keyword, NULL_KEY)) {
UserExtendDTO userExtendDTO = new UserExtendDTO();
userExtendDTO.setId(NULL_KEY);
userExtendDTO.setName(NULL_KEY);
memberOption.add(userExtendDTO);
}
return memberOption;
}
}

View File

@ -48,4 +48,7 @@ public class TestPlanApiCaseRequest extends BasePageRequest {
@Size(max = 50, message = "{api_definition.ref_id.length_range}")
private String refId;
@Schema(description = "是否包含空执行人")
private boolean nullExecutorKey;
}

View File

@ -49,4 +49,7 @@ public class TestPlanApiScenarioRequest extends BasePageRequest {
@Schema(description = "查询时排除的ID")
private List<String> excludeIds = new ArrayList<>();
@Schema(description = "是否包含空执行人")
private boolean nullExecutorKey;
}

View File

@ -35,4 +35,7 @@ public class TestPlanCaseRequest extends BasePageRequest implements Serializable
@Schema(description = "计划集id")
private String collectionId;
@Schema(description = "是否包含空执行人")
private boolean nullExecutorKey;
}

View File

@ -495,6 +495,7 @@
</if>
<include refid="apiCaseFilters">
<property name="filter" value="request.filter"/>
<property name="nullExecutorKey" value="request.nullExecutorKey"/>
</include>
<include refid="queryApiCaseVersionCondition">
<property name="versionTable" value="atc"/>
@ -504,7 +505,7 @@
<sql id="apiCaseFilters">
<if test="${filter} != null and ${filter}.size() > 0">
<foreach collection="${filter}.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<if test="(values != null and values.size() > 0) or key == 'executeUserName'">
<choose>
<when test="key == 'priority'">
and atc.priority in
@ -549,9 +550,24 @@
</foreach>
</when>
<!-- 执行人 -->
<when test="key == 'executeUserName'">
and t.execute_user in
<when test="key == 'executeUserName' and values.size() > 0">
and (
t.execute_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
<if test="${nullExecutorKey} == true">
or t.execute_user is null or t.execute_user = ''
</if>
)
</when>
<when test="key == 'executeUserName' and values.size() == 0">
and (
<if test="${nullExecutorKey} == true">
t.execute_user is null or t.execute_user = ''
</if>
<if test="${nullExecutorKey} == false">
1=1
</if>
)
</when>
</choose>
</if>

View File

@ -143,6 +143,7 @@
</if>
<include refid="filters">
<property name="filter" value="request.filter"/>
<property name="nullExecutorKey" value="request.nullExecutorKey"/>
</include>
<if test="request.combine != null and request.combine != ''">
@ -200,9 +201,25 @@
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 执行人 -->
<when test="key == 'executeUserName'">
and test_plan_api_scenario.execute_user in
<!-- 执行人 -->
<when test="key == 'executeUserName' and values.size() > 0">
and (
test_plan_api_scenario.execute_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
<if test="${nullExecutorKey} == true">
or test_plan_api_scenario.execute_user is null or test_plan_api_scenario.execute_user = ''
</if>
)
</when>
<when test="key == 'executeUserName' and values.size() == 0">
and (
<if test="${nullExecutorKey} == true">
test_plan_api_scenario.execute_user is null or test_plan_api_scenario.execute_user = ''
</if>
<if test="${nullExecutorKey} == false">
1=1
</if>
)
</when>
<when test="key=='versionId'">
and api_scenario.version_id in

View File

@ -90,7 +90,7 @@
</select>
<select id="getCasePage" resultType="io.metersphere.plan.dto.response.TestPlanCasePageResponse">
SELECT
select
functional_case.id as caseId,
functional_case.num,
functional_case.NAME,
@ -114,11 +114,11 @@
project.id as projectId,
test_plan_functional_case.test_plan_collection_id as testPlanCollectionId,
test_plan_collection.name as testPlanCollectionName
FROM
from
functional_case
inner join project on functional_case.project_id = project.id
inner join test_plan_functional_case on functional_case.id = test_plan_functional_case.functional_case_id
inner JOIN project_version ON functional_case.version_id = project_version.id
inner join project_version ON functional_case.version_id = project_version.id
inner join test_plan_collection on test_plan_collection.id = test_plan_functional_case.test_plan_collection_id
where functional_case.deleted = #{deleted}
and test_plan_functional_case.test_plan_id = #{request.testPlanId}
@ -160,6 +160,7 @@
</if>
<include refid="filters">
<property name="filter" value="request.filter"/>
<property name="nullExecutorKey" value="request.nullExecutorKey"/>
</include>
<choose>
<when test='request.searchMode == "AND"'>
@ -220,7 +221,7 @@
<sql id="filters">
<if test="${filter} != null and ${filter}.size() > 0">
<foreach collection="${filter}.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<if test="(values != null and values.size() > 0) or key == 'executeUserName'">
<choose>
<!-- 评审状态 -->
<when test="key=='reviewStatus'">
@ -276,9 +277,25 @@
and functional_case.delete_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<when test="key == 'executeUserName'">
and test_plan_functional_case.execute_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
<!-- 执行人 -->
<when test="key == 'executeUserName' and values.size() > 0">
and (
test_plan_functional_case.execute_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
<if test="${nullExecutorKey} == true">
or test_plan_functional_case.execute_user is null or test_plan_functional_case.execute_user = ''
</if>
)
</when>
<when test="key == 'executeUserName' and values.size() == 0">
and (
<if test="${nullExecutorKey} == true">
test_plan_functional_case.execute_user is null or test_plan_functional_case.execute_user = ''
</if>
<if test="${nullExecutorKey} == false">
1=1
</if>
)
</when>
</choose>
</if>

View File

@ -118,7 +118,10 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
public List<TestPlanResourceExecResultDTO> selectDistinctExecResultByProjectId(String projectId) {
private static final String EXECUTOR = "executeUserName";
@Override
public List<TestPlanResourceExecResultDTO> selectDistinctExecResultByProjectId(String projectId) {
return extTestPlanApiCaseMapper.selectDistinctExecResult(projectId);
}
@ -235,6 +238,7 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
* @return
*/
public List<TestPlanApiCasePageResponse> hasRelateApiCaseList(TestPlanApiCaseRequest request, boolean deleted) {
filterCaseRequest(request);
if (CollectionUtils.isEmpty(request.getProtocols())) {
return new ArrayList<>();
}
@ -337,6 +341,7 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
if (CollectionUtils.isEmpty(request.getProtocols())) {
return Collections.emptyMap();
}
filterCaseRequest(request);
switch (request.getTreeType()) {
case TreeTypeEnums.MODULE:
return getModuleCount(request);
@ -700,7 +705,6 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
testPlanApiCase.setCreateTime(System.currentTimeMillis());
testPlanApiCase.setCreateUser(user.getId());
testPlanApiCase.setPos(nextOrder.getAndAdd(DEFAULT_NODE_INTERVAL_POS));
testPlanApiCase.setExecuteUser(apiTestCase.getCreateUser());
testPlanApiCase.setLastExecResult(ExecStatus.PENDING.name());
testPlanApiCaseList.add(testPlanApiCase);
});
@ -885,4 +889,18 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
return count;
}
/**
* 处理执行人为空过滤参数
* @param request 请求参数
*/
protected void filterCaseRequest(TestPlanApiCaseRequest request) {
Map<String, List<String>> filter = request.getFilter();
if (filter != null && filter.containsKey(EXECUTOR)) {
List<String> filterExecutorIds = filter.get(EXECUTOR);
if (CollectionUtils.isNotEmpty(filterExecutorIds)) {
boolean containNullKey = filterExecutorIds.removeIf(id -> StringUtils.equals(id, "-"));
request.setNullExecutorKey(containNullKey);
}
}
}
}

View File

@ -109,6 +109,8 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
@Resource
private TestPlanConfigService testPlanConfigService;
private static final String EXECUTOR = "executeUserName";
@Override
public List<TestPlanResourceExecResultDTO> selectDistinctExecResultByProjectId(String projectId) {
return extTestPlanApiScenarioMapper.selectDistinctExecResult(projectId);
@ -260,7 +262,6 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
testPlanApiScenario.setCreateTime(System.currentTimeMillis());
testPlanApiScenario.setCreateUser(user.getId());
testPlanApiScenario.setPos(nextOrder.getAndAdd(DEFAULT_NODE_INTERVAL_POS));
testPlanApiScenario.setExecuteUser(scenario.getCreateUser());
testPlanApiScenario.setLastExecResult(ExecStatus.PENDING.name());
testPlanApiScenarioList.add(testPlanApiScenario);
});
@ -379,6 +380,7 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
* @return
*/
public List<TestPlanApiScenarioPageResponse> hasRelateApiScenarioList(TestPlanApiScenarioRequest request, boolean deleted) {
filterCaseRequest(request);
List<TestPlanApiScenarioPageResponse> list = extTestPlanApiScenarioMapper.relateApiScenarioList(request, deleted);
buildApiScenarioResponse(list, request.getTestPlanId());
return list;
@ -489,6 +491,7 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
}
public Map<String, Long> moduleCount(TestPlanApiScenarioModuleRequest request) {
filterCaseRequest(request);
switch (request.getTreeType()) {
case TreeTypeEnums.MODULE:
return getModuleCount(request);
@ -742,4 +745,19 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
return testPlanApiScenarios;
}
}
/**
* 处理执行人为空过滤参数
* @param request 请求参数
*/
protected void filterCaseRequest(TestPlanApiScenarioRequest request) {
Map<String, List<String>> filter = request.getFilter();
if (filter != null && filter.containsKey(EXECUTOR)) {
List<String> filterExecutorIds = filter.get(EXECUTOR);
if (CollectionUtils.isNotEmpty(filterExecutorIds)) {
boolean containNullKey = filterExecutorIds.removeIf(id -> StringUtils.equals(id, "-"));
request.setNullExecutorKey(containNullKey);
}
}
}
}

View File

@ -115,10 +115,8 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
private BugStatusService bugStatusService;
@Resource
private TestPlanCollectionMapper testPlanCollectionMapper;
@Resource
private ExtUserMapper extUserMapper;
private static final String CASE_MODULE_COUNT_ALL = "all";
@Resource
private ExtFunctionalCaseModuleMapper extFunctionalCaseModuleMapper;
@Resource
@ -138,6 +136,9 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
@Resource
private TestPlanApiScenarioMapper testPlanApiScenarioMapper;
private static final String CASE_MODULE_COUNT_ALL = "all";
private static final String EXECUTOR = "executeUserName";
@Override
public List<TestPlanResourceExecResultDTO> selectDistinctExecResultByProjectId(String projectId) {
return extTestPlanFunctionalCaseMapper.selectDistinctExecResult(projectId);
@ -253,6 +254,7 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
public List<TestPlanCasePageResponse> getFunctionalCasePage(TestPlanCaseRequest request, boolean deleted) {
filterCaseRequest(request);
List<TestPlanCasePageResponse> functionalCaseLists = extTestPlanFunctionalCaseMapper.getCasePage(request, deleted, request.getSortString());
if (CollectionUtils.isEmpty(functionalCaseLists)) {
return new ArrayList<>();
@ -384,6 +386,7 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
public Map<String, Long> moduleCount(TestPlanCaseModuleRequest request) {
filterCaseRequest(request);
switch (request.getTreeType()) {
case TreeTypeEnums.MODULE:
return getModuleCount(request);
@ -926,7 +929,6 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
testPlanFunctionalCase.setCreateUser(user.getId());
testPlanFunctionalCase.setCreateTime(System.currentTimeMillis());
testPlanFunctionalCase.setPos(nextOrder.getAndAdd(DEFAULT_NODE_INTERVAL_POS));
testPlanFunctionalCase.setExecuteUser(functionalCase.getCreateUser());
testPlanFunctionalCase.setLastExecResult(ExecStatus.PENDING.name());
testPlanFunctionalCaseList.add(testPlanFunctionalCase);
});
@ -981,4 +983,19 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
List<FunctionalCaseTest> functionalCaseTestList = extFunctionalCaseTestMapper.selectApiAndScenarioIdsFromCaseIds(functionalCaseIds);
return functionalCaseTestList.stream().collect(Collectors.groupingBy(FunctionalCaseTest::getCaseId, Collectors.mapping(FunctionalCaseTest::getSourceId, Collectors.toList())));
}
/**
* 处理执行人为空过滤参数
* @param request 请求参数
*/
protected void filterCaseRequest(TestPlanCaseRequest request) {
Map<String, List<String>> filter = request.getFilter();
if (filter != null && filter.containsKey(EXECUTOR)) {
List<String> filterExecutorIds = filter.get(EXECUTOR);
if (CollectionUtils.isNotEmpty(filterExecutorIds)) {
boolean containNullKey = filterExecutorIds.removeIf(id -> StringUtils.equals(id, "-"));
request.setNullExecutorKey(containNullKey);
}
}
}
}

View File

@ -51,6 +51,7 @@ import {
getStatisticalCountUrl,
GetTestPlanCaseListUrl,
GetTestPlanDetailUrl,
GetTestPlanExecutorOptionsUrl,
GetTestPlanListUrl,
GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl,
@ -433,3 +434,7 @@ export function editPlanMinder(data: PlanMinderEditParams) {
export function testPlanAssociateModuleCount(data: TableQueryParams) {
return MSR.post({ url: TestPlanAssociationUrl, data });
}
// 获取执行人下拉选项(空选项)
export function getExecuteUserOption(projectId: string, keyword?: string) {
return MSR.get({ url: `${GetTestPlanExecutorOptionsUrl}/${projectId}`, params: { keyword } });
}

View File

@ -153,3 +153,5 @@ export const GetPlanMinderUrl = '/test-plan/mind/data';
export const EditPlanMinderUrl = '/test-plan/mind/data/edit';
// 获取测试计划-关联用例-接口模块数量
export const TestPlanAssociationUrl = '/test-plan/association/api/case/module/count';
// 获取执行人下拉选项
export const GetTestPlanExecutorOptionsUrl = '/test-plan-execute/user-option';

View File

@ -9,6 +9,8 @@ import {
} from '@/api/modules/setting/organizationAndProject';
import { getOrgUserGroupOption, getSystemUserGroupOption } from '@/api/modules/setting/usergroup';
import { getOrgOptions, getSystemProjectList } from '@/api/modules/system';
import { getExecuteUserOption } from '@/api/modules/test-plan/testPlan';
// eslint-disable-next-line no-shadow
export enum UserRequestTypeEnum {
SYSTEM_USER_GROUP = 'SYSTEM_USER_GROUP',
@ -26,6 +28,7 @@ export enum UserRequestTypeEnum {
PROJECT_USER_GROUP = 'PROJECT_USER_GROUP',
SYSTEM_ORGANIZATION_LIST = 'SYSTEM_ORGANIZATION_LIST',
SYSTEM_PROJECT_LIST = 'SYSTEM_PROJECT_LIST',
EXECUTE_USER = 'EXECUTE_USER',
}
export default function initOptionsFunc(type: string, params: Record<string, any>) {
if (type === UserRequestTypeEnum.SYSTEM_USER_GROUP) {
@ -76,4 +79,8 @@ export default function initOptionsFunc(type: string, params: Record<string, any
// 系统-项目 获取系统下所有的项目
return getSystemProjectList(params.keyword);
}
if (type === UserRequestTypeEnum.EXECUTE_USER) {
// 测试计划-执行人下拉选项
return getExecuteUserOption(params.projectId, params.keyword);
}
}

View File

@ -1,6 +1,7 @@
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { getProjectList } from '@/api/modules/setting/member';
import { getOrgOptions, getSystemProjectList } from '@/api/modules/system';
import { getExecuteUserOption } from '@/api/modules/test-plan/testPlan';
import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
@ -14,6 +15,8 @@ export function initRemoteOptionsFunc(remoteMethod: string, params: Record<strin
return getSystemProjectList(params.keyword);
case FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_PROJECT:
return getProjectList(params.organizationId, params.keyword);
case FilterRemoteMethodsEnum.EXECUTE_USER:
return getExecuteUserOption(params.projectId, params.keyword);
default:
break;
}

View File

@ -27,6 +27,7 @@ export enum FilterRemoteMethodsEnum {
SYSTEM_ORGANIZATION_LIST = 'SYSTEM_ORGANIZATION_LIST', // 组织列表
SYSTEM_PROJECT_LIST = 'SYSTEM_PROJECT_LIST', // 系统下项目
SYSTEM_ORGANIZATION_PROJECT = 'SYSTEM_ORGANIZATION_PROJECT', // 组织下项目
EXECUTE_USER = 'EXECUTE_USER',
}
export default {};

View File

@ -300,7 +300,7 @@
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
remoteMethod: FilterRemoteMethodsEnum.EXECUTE_USER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
},

View File

@ -282,7 +282,7 @@
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
remoteMethod: FilterRemoteMethodsEnum.EXECUTE_USER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
},

View File

@ -356,7 +356,7 @@
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
remoteMethod: FilterRemoteMethodsEnum.EXECUTE_USER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
},