feat(测试计划): 已关联用例查询接口

This commit is contained in:
WangXu10 2024-05-10 19:23:12 +08:00 committed by 刘瑞斌
parent d3194932cc
commit 090d5bcb93
23 changed files with 694 additions and 125 deletions

View File

@ -15,9 +15,6 @@ public class TestPlanFunctionalCase implements Serializable {
@Size(min = 1, max = 50, message = "{test_plan_functional_case.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "num")
private Long num;
@Schema(description = "测试计划ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_functional_case.test_plan_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{test_plan_functional_case.test_plan_id.length_range}", groups = {Created.class, Updated.class})
@ -51,7 +48,6 @@ public class TestPlanFunctionalCase implements Serializable {
public enum Column {
id("id", "id", "VARCHAR", false),
num("num", "num", "BIGINT", false),
testPlanId("test_plan_id", "testPlanId", "VARCHAR", false),
functionalCaseId("functional_case_id", "functionalCaseId", "VARCHAR", false),
createTime("create_time", "createTime", "BIGINT", false),

View File

@ -174,66 +174,6 @@ public class TestPlanFunctionalCaseExample {
return (Criteria) this;
}
public Criteria andNumIsNull() {
addCriterion("num is null");
return (Criteria) this;
}
public Criteria andNumIsNotNull() {
addCriterion("num is not null");
return (Criteria) this;
}
public Criteria andNumEqualTo(Long value) {
addCriterion("num =", value, "num");
return (Criteria) this;
}
public Criteria andNumNotEqualTo(Long value) {
addCriterion("num <>", value, "num");
return (Criteria) this;
}
public Criteria andNumGreaterThan(Long value) {
addCriterion("num >", value, "num");
return (Criteria) this;
}
public Criteria andNumGreaterThanOrEqualTo(Long value) {
addCriterion("num >=", value, "num");
return (Criteria) this;
}
public Criteria andNumLessThan(Long value) {
addCriterion("num <", value, "num");
return (Criteria) this;
}
public Criteria andNumLessThanOrEqualTo(Long value) {
addCriterion("num <=", value, "num");
return (Criteria) this;
}
public Criteria andNumIn(List<Long> values) {
addCriterion("num in", values, "num");
return (Criteria) this;
}
public Criteria andNumNotIn(List<Long> values) {
addCriterion("num not in", values, "num");
return (Criteria) this;
}
public Criteria andNumBetween(Long value1, Long value2) {
addCriterion("num between", value1, value2, "num");
return (Criteria) this;
}
public Criteria andNumNotBetween(Long value1, Long value2) {
addCriterion("num not between", value1, value2, "num");
return (Criteria) this;
}
public Criteria andTestPlanIdIsNull() {
addCriterion("test_plan_id is null");
return (Criteria) this;

View File

@ -3,7 +3,6 @@
<mapper namespace="io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper">
<resultMap id="BaseResultMap" type="io.metersphere.plan.domain.TestPlanFunctionalCase">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="num" jdbcType="BIGINT" property="num" />
<result column="test_plan_id" jdbcType="VARCHAR" property="testPlanId" />
<result column="functional_case_id" jdbcType="VARCHAR" property="functionalCaseId" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
@ -72,8 +71,8 @@
</where>
</sql>
<sql id="Base_Column_List">
id, num, test_plan_id, functional_case_id, create_time, create_user, execute_user,
last_exec_time, last_exec_result, pos
id, test_plan_id, functional_case_id, create_time, create_user, execute_user, last_exec_time,
last_exec_result, pos
</sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanFunctionalCaseExample" resultMap="BaseResultMap">
select
@ -106,14 +105,14 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanFunctionalCase">
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)
values (#{id,jdbcType=VARCHAR}, #{num,jdbcType=BIGINT}, #{testPlanId,jdbcType=VARCHAR},
#{functionalCaseId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR},
#{executeUser,jdbcType=VARCHAR}, #{lastExecTime,jdbcType=BIGINT}, #{lastExecResult,jdbcType=VARCHAR},
#{pos,jdbcType=BIGINT})
insert into test_plan_functional_case (id, test_plan_id, functional_case_id,
create_time, create_user, execute_user,
last_exec_time, last_exec_result, pos
)
values (#{id,jdbcType=VARCHAR}, #{testPlanId,jdbcType=VARCHAR}, #{functionalCaseId,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR}, #{executeUser,jdbcType=VARCHAR},
#{lastExecTime,jdbcType=BIGINT}, #{lastExecResult,jdbcType=VARCHAR}, #{pos,jdbcType=BIGINT}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanFunctionalCase">
insert into test_plan_functional_case
@ -121,9 +120,6 @@
<if test="id != null">
id,
</if>
<if test="num != null">
num,
</if>
<if test="testPlanId != null">
test_plan_id,
</if>
@ -153,9 +149,6 @@
<if test="id != null">
#{id,jdbcType=VARCHAR},
</if>
<if test="num != null">
#{num,jdbcType=BIGINT},
</if>
<if test="testPlanId != null">
#{testPlanId,jdbcType=VARCHAR},
</if>
@ -194,9 +187,6 @@
<if test="record.id != null">
id = #{record.id,jdbcType=VARCHAR},
</if>
<if test="record.num != null">
num = #{record.num,jdbcType=BIGINT},
</if>
<if test="record.testPlanId != null">
test_plan_id = #{record.testPlanId,jdbcType=VARCHAR},
</if>
@ -229,7 +219,6 @@
<update id="updateByExample" parameterType="map">
update test_plan_functional_case
set id = #{record.id,jdbcType=VARCHAR},
num = #{record.num,jdbcType=BIGINT},
test_plan_id = #{record.testPlanId,jdbcType=VARCHAR},
functional_case_id = #{record.functionalCaseId,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
@ -245,9 +234,6 @@
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.plan.domain.TestPlanFunctionalCase">
update test_plan_functional_case
<set>
<if test="num != null">
num = #{num,jdbcType=BIGINT},
</if>
<if test="testPlanId != null">
test_plan_id = #{testPlanId,jdbcType=VARCHAR},
</if>
@ -277,8 +263,7 @@
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.plan.domain.TestPlanFunctionalCase">
update test_plan_functional_case
set num = #{num,jdbcType=BIGINT},
test_plan_id = #{testPlanId,jdbcType=VARCHAR},
set test_plan_id = #{testPlanId,jdbcType=VARCHAR},
functional_case_id = #{functionalCaseId,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
create_user = #{createUser,jdbcType=VARCHAR},
@ -290,14 +275,14 @@
</update>
<insert id="batchInsert" parameterType="map">
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)
(id, test_plan_id, functional_case_id, create_time, create_user, execute_user, last_exec_time,
last_exec_result, pos)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.num,jdbcType=BIGINT}, #{item.testPlanId,jdbcType=VARCHAR},
#{item.functionalCaseId,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR}, #{item.executeUser,jdbcType=VARCHAR}, #{item.lastExecTime,jdbcType=BIGINT},
#{item.lastExecResult,jdbcType=VARCHAR}, #{item.pos,jdbcType=BIGINT})
(#{item.id,jdbcType=VARCHAR}, #{item.testPlanId,jdbcType=VARCHAR}, #{item.functionalCaseId,jdbcType=VARCHAR},
#{item.createTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR}, #{item.executeUser,jdbcType=VARCHAR},
#{item.lastExecTime,jdbcType=BIGINT}, #{item.lastExecResult,jdbcType=VARCHAR},
#{item.pos,jdbcType=BIGINT})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -313,9 +298,6 @@
<if test="'id'.toString() == column.value">
#{item.id,jdbcType=VARCHAR}
</if>
<if test="'num'.toString() == column.value">
#{item.num,jdbcType=BIGINT}
</if>
<if test="'test_plan_id'.toString() == column.value">
#{item.testPlanId,jdbcType=VARCHAR}
</if>

View File

@ -40,6 +40,8 @@ CREATE TABLE IF NOT EXISTS test_plan_allocation
CREATE INDEX idx_test_plan_id ON test_plan_allocation(test_plan_id);
ALTER TABLE test_plan_functional_case DROP COLUMN num;
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -0,0 +1,31 @@
package io.metersphere.bug.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author wx
*/
@Data
public class CaseRelateBugDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "缺陷的id")
private String bugId;
@Schema(description = "中间表id")
private String id;
@Schema(description = "缺陷名称")
private String title;
@Schema(description = "类型")
private String type;
@Schema(description = "用例id")
private String caseId;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.dto.CaseRelateBugDTO;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
@ -19,6 +20,7 @@ public interface ExtBugRelateCaseMapper {
/**
* 获取缺陷关联的用例模块树
*
* @param request 请求参数
* @param caseTable 关联用例表
* @param moduleTable 关联用例模块表
@ -28,6 +30,7 @@ public interface ExtBugRelateCaseMapper {
/**
* 获取缺陷关联的用例模块树数量
*
* @param request 请求参数
* @param deleted 是否删除状态
* @param caseTable 关联用例表
@ -53,6 +56,7 @@ public interface ExtBugRelateCaseMapper {
/**
* 根据CaseId获取关联的Case
*
* @param id 用例ID
* @param sourceType 用例类型
* @return 用例关联DTO
@ -61,6 +65,7 @@ public interface ExtBugRelateCaseMapper {
/**
* 获取关联的缺陷
*
* @param request 关联请求参数
* @param sort 排序
* @return 缺陷集合
@ -69,8 +74,11 @@ public interface ExtBugRelateCaseMapper {
/**
* 获取关联的Case数量
*
* @param caseId 用例ID
* @return 关联数量
*/
long countByCaseId(String caseId);
List<CaseRelateBugDTO> getBugCountByIds(@Param("ids") List<String> ids);
}

View File

@ -225,4 +225,23 @@
</if>
</if>
</sql>
<select id="getBugCountByIds" resultType="io.metersphere.bug.dto.CaseRelateBugDTO">
SELECT
bug.id as bugId,
bug.title as title,
bug_relation_case.case_type as caseType,
bug_relation_case.case_id as caseId,
bug_relation_case.id
FROM
bug_relation_case
INNER JOIN bug ON bug.id = bug_relation_case.bug_id
WHERE
bug_relation_case.case_id IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
ORDER BY
bug.pos DESC
</select>
</mapper>

View File

@ -15,6 +15,6 @@ INSERT INTO project(id, num, organization_id, name, description, create_time, up
UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'admin', null, false, null, true, null);
INSERT INTO test_plan_functional_case(id,num, test_plan_id, functional_case_id, create_time, create_user, last_exec_time, last_exec_result, pos)
VALUES ('associate_case_plan_gyq_one', 1,'test_plan_associate_case_gyq_one', 'gyq_associate_function_case', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'SUCCESS', 200),
('associate_case_plan_gyq_two', 2,'test_plan_associate_case_gyq_two', 'gyq_associate_function_case', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'SUCCESS', 200);
INSERT INTO test_plan_functional_case(id, test_plan_id, functional_case_id, create_time, create_user, last_exec_time, last_exec_result, pos)
VALUES ('associate_case_plan_gyq_one','test_plan_associate_case_gyq_one', 'gyq_associate_function_case', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'SUCCESS', 200),
('associate_case_plan_gyq_two','test_plan_associate_case_gyq_two', 'gyq_associate_function_case', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'SUCCESS', 200);

View File

@ -36,6 +36,11 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-case-management</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-bug-management</artifactId>

View File

@ -1,9 +1,13 @@
package io.metersphere.plan.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
import io.metersphere.plan.dto.response.TestPlanResourceSortResponse;
import io.metersphere.plan.service.TestPlanFunctionalCaseService;
import io.metersphere.plan.service.TestPlanManagementService;
@ -11,6 +15,8 @@ import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -23,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
@RestController
@Tag(name = "测试计划功能用例")
@ -52,4 +59,14 @@ public class TestPlanFunctionalCaseController {
return testPlanFunctionalCaseService.sortNode(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/functional/case/sort", HttpMethodConstants.POST.name()));
}
@PostMapping("/page")
@Operation(summary = "测试计划-已关联功能用例分页查询")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public Pager<List<TestPlanCasePageResponse>> page(@Validated @RequestBody TestPlanCaseRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize());
return PageUtils.setPageInfo(page, testPlanFunctionalCaseService.getFunctionalCasePage(request, false));
}
}

View File

@ -0,0 +1,32 @@
package io.metersphere.plan.dto.request;
import io.metersphere.system.dto.sdk.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanCaseRequest extends BasePageRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.project_id.not_blank}")
private String projectId;
@Schema(description = "版本id")
private String versionId;
@Schema(description = "版本来源")
private String refId;
@Schema(description = "模块id")
private List<String> moduleIds;
}

View File

@ -0,0 +1,94 @@
package io.metersphere.plan.dto.response;
import io.metersphere.bug.dto.CaseRelateBugDTO;
import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanCasePageResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "ID")
private String id;
@Schema(description = "业务ID")
private Long num;
@Schema(description = "模块ID")
private String moduleId;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "模板ID")
private String templateId;
@Schema(description = "名称")
private String name;
@Schema(description = "评审状态:未评审/评审中/通过/不通过/重新提审")
private String reviewStatus;
@Schema(description = "版本ID")
private String versionId;
@Schema(description = "指向初始版本ID")
private String refId;
@Schema(description = "创建人")
private String createUser;
@Schema(description = "更新人")
private String updateUser;
@Schema(description = "创建时间")
private Long createTime;
@Schema(description = "更新时间")
private Long updateTime;
@Schema(description = "标签JSON)")
private List<String> tags;
@Schema(description = "自定义字段集合")
private List<FunctionalCaseCustomFieldDTO> customFields;
@Schema(description = "版本名称")
private String versionName;
@Schema(description = "创建人名称")
private String createUserName;
@Schema(description = "执行结果")
private String lastExecResult;
@Schema(description = "最后执行时间")
private Long lastExecTime;
@Schema(description = "执行人")
private String executeUser;
@Schema(description = "执行人名称")
private String executeUserName;
@Schema(description = "缺陷数量")
private int bugCount;
@Schema(description = "关联的缺陷数据")
private List<CaseRelateBugDTO> bugList;
@Schema(description = "用例的id")
private String caseId;
@Schema(description = "测试计划id")
private String testPlanId;
}

View File

@ -2,6 +2,8 @@ package io.metersphere.plan.mapper;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.ResourceSelectParam;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param;
@ -20,4 +22,6 @@ public interface ExtTestPlanFunctionalCaseMapper {
AssociationNode selectDragInfoById(String id);
AssociationNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
List<TestPlanCasePageResponse> getCasePage(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted, @Param("sort") String sort);
}

View File

@ -67,4 +67,310 @@
</if>
LIMIT 1
</select>
<select id="getCasePage" resultType="io.metersphere.plan.dto.response.TestPlanCasePageResponse">
SELECT
functional_case.id as caseId,
functional_case.num,
functional_case.NAME,
functional_case.module_id as moduleId,
functional_case.version_id as versionId,
project_version.name as versionName,
functional_case.case_edit_type as caseEditType,
functional_case.create_user as createUser,
functional_case.create_time as createTime,
functional_case.update_user as updateUser,
functional_case.update_time as updateTime,
functional_case.review_status as reviewStatus,
functional_case.last_execute_result as lastExecuteResult,
functional_case.tags,
test_plan_functional_case.execute_user as executeUser,
test_plan_functional_case.last_exec_time as lastExecTime,
test_plan_functional_case.last_exec_result as lastExecResult,
test_plan_functional_case.test_plan_id as testPlanId,
test_plan_functional_case.id
FROM
functional_case
LEFT JOIN project_version ON functional_case.version_id = project_version.id
left join test_plan_functional_case on functional_case.id = test_plan_functional_case.functional_case_id
where functional_case.deleted = #{deleted}
and functional_case.project_id = #{request.projectId}
<include refid="queryWhereCondition"/>
order by
<if test="sort != null and sort != ''">
functional_case.${sort}
</if>
<if test="sort == null or sort == ''">
functional_case.pos desc
</if>
</select>
<sql id="queryWhereCondition">
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and functional_case.module_id in
<foreach collection="request.moduleIds" item="moduleId" separator="," open="(" close=")">
#{moduleId}
</foreach>
</if>
<if test="request.keyword != null and request.keyword != ''">
and (
functional_case.name like concat('%', #{request.keyword},'%')
or functional_case.num like concat('%', #{request.keyword},'%')
or functional_case.tags like concat('%', #{request.keyword},'%')
)
</if>
<include refid="filters">
<property name="filter" value="request.filter"/>
</include>
<choose>
<when test='request.searchMode == "AND"'>
AND <include refid="queryCombine"/>
</when>
<when test='request.searchMode == "OR"'>
and (
<include refid="queryCombine"/>
)
</when>
</choose>
<include refid="queryVersionCondition">
<property name="versionTable" value="functional_case"/>
</include>
</sql>
<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">
<choose>
<!-- 评审状态 -->
<when test="key=='reviewStatus'">
and functional_case.review_status in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 执行结果 -->
<when test="key=='lastExecuteResult'">
and functional_case.last_execute_result in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 版本 -->
<when test="key=='version_id'">
and functional_case.version_id in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 自定义单选 -->
<when test="key.startsWith('custom_single')">
and functional_case.id in (
select field_id from functional_case_custom_field where concat('custom_single-',field_id) =
#{key}
and trim(both '"' from `value`) in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
)
</when>
<!-- 自定义多选 -->
<when test="key.startsWith('custom_multiple')">
and functional_case.id in (
select field_id from functional_case_custom_field where concat('custom_multiple-',field_id) =
#{key}
and JSON_CONTAINS(`value`, json_array(#{value}))
</when>
<!-- 用例等级 -->
<when test="key=='caseLevel'">
and functional_case.id in (
select case_id from functional_case_custom_field where `value` in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
)
</when>
<!-- 更新人 -->
<when test="key=='updateUserName'">
and functional_case.update_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 创建人 -->
<when test="key=='createUserName'">
and functional_case.create_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
<!-- 删除人 -->
<when test="key=='deleteUserName'">
and functional_case.delete_user in
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
</when>
</choose>
</if>
</foreach>
</if>
</sql>
<sql id="queryCombine">
<if test="request.combine != null">
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="searchMode" value="request.searchMode"/>
</include>
</if>
1=1
</sql>
<sql id="combine">
<!-- 名称 -->
<if test='${condition}.name != null'>
functional_case.name
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.name"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- id -->
<if test='${condition}.id != null'>
functional_case.num
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.id"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 所属模块 -->
<if test='${condition}.moduleId != null'>
functional_case.moduleId
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.moduleId"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 创建人 -->
<if test='${condition}.createUser != null'>
functional_case.create_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.createUser"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 创建时间 -->
<if test='${condition}.createTime != null'>
functional_case.create_time
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.createTime"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 更新人 -->
<if test='${condition}.updateUser != null'>
functional_case.update_user
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.updateUser"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 更新时间 -->
<if test='${condition}.updateTime != null'>
functional_case.update_time
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.updateTime"/>
</include>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 标签 -->
<if test='${condition}.tags != null'>
<include refid="queryTag">
<property name="searchMode" value="${searchMode}"/>
<property name="combineTag" value="${condition}.tags"/>
</include>
</if>
<if test="${condition}.customs != null and ${condition}.customs.size() > 0">
<foreach collection="${condition}.customs" item="custom" separator="" open="" close="">
functional_case.id ${custom.operator} (
select case_id from functional_case_custom_field where field_id = #{custom.id}
<choose>
<when test="custom.type == 'List'">
and JSON_CONTAINS(`value`, json_array(#{custom.value}))
</when>
<when test="custom.type == 'date' or custom.type == 'datetime'">
and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</when>
<otherwise>
and trim(both '"' from `value`)
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/>
</include>
</otherwise>
</choose>
)
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</foreach>
</if>
</sql>
<sql id="queryVersionCondition">
<choose>
<when test="request.versionId != null and request.versionId != ''">
and ${versionTable}.version_id = #{request.versionId}
</when>
<when test="request.refId != null and request.refId != ''">
and ${versionTable}.ref_id = #{request.refId}
</when>
<otherwise>
and ${versionTable}.latest = 1
</otherwise>
</choose>
</sql>
<sql id="queryType">
<choose>
<when test='${searchMode} == "AND"'>
AND
</when>
<when test='${searchMode} == "OR"'>
OR
</when>
</choose>
</sql>
<sql id="queryTag">
<!-- 不包含 -->
<if test='${combineTag}.value.size() > 0 and ${combineTag}.operator == "not like"'>
(
functional_case.tags is null or functional_case.tags = '[]' or
<foreach collection="${combineTag}.value" item="tag" separator="and" open="(" close=")">
!JSON_CONTAINS(functional_case.tags, JSON_ARRAY(#{tag}))
</foreach>
)
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!-- 包含 -->
<if test='${combineTag}.value.size() > 0 and ${combineTag}.operator == "like"'>
<foreach collection="${combineTag}.value" item="tag" separator="or" open="(" close=")">
JSON_CONTAINS(functional_case.tags, JSON_ARRAY(#{tag}))
</foreach>
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
<!---->
<if test='${combineTag}.operator == "is null"'>
(functional_case.tags is null or functional_case.tags = '[]')
<include refid="queryType">
<property name="searchMode" value="${searchMode}"/>
</include>
</if>
</sql>
</mapper>

View File

@ -1,5 +1,9 @@
package io.metersphere.plan.service;
import io.metersphere.bug.dto.CaseRelateBugDTO;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO;
import io.metersphere.functional.service.FunctionalCaseService;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanFunctionalCase;
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
@ -8,18 +12,19 @@ import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
import io.metersphere.plan.dto.response.TestPlanResourceSortResponse;
import io.metersphere.plan.mapper.ExtTestPlanFunctionalCaseMapper;
import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
@ -34,8 +39,10 @@ import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@Transactional(rollbackFor = Exception.class)
@ -50,6 +57,12 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
private TestPlanResourceLogService testPlanResourceLogService;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private FunctionalCaseService functionalCaseService;
@Resource
private UserLoginService userLoginService;
@Resource
private ExtBugRelateCaseMapper bugRelateCaseMapper;
@Override
public int deleteBatchByTestPlanId(List<String> testPlanIdList) {
@ -102,7 +115,6 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
for (int i = 0; i < associationIdList.size(); i++) {
TestPlanFunctionalCase testPlanFunctionalCase = new TestPlanFunctionalCase();
testPlanFunctionalCase.setId(IDGenerator.nextStr());
testPlanFunctionalCase.setNum(NumGenerator.nextNum(associationParam.getTestPlanNum() + "_" + associationParam.getProjectId(), ApplicationNumScope.TEST_PLAN_FUNCTION_CASE));
testPlanFunctionalCase.setTestPlanId(associationParam.getTestPlanId());
testPlanFunctionalCase.setFunctionalCaseId(associationIdList.get(i));
testPlanFunctionalCase.setPos(pox);
@ -161,7 +173,6 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
TestPlanFunctionalCase functionalCase = new TestPlanFunctionalCase();
functionalCase.setTestPlanId(testPlan.getId());
functionalCase.setId(IDGenerator.nextStr());
functionalCase.setNum(NumGenerator.nextNum(testPlan.getNum() + "_" + testPlan.getProjectId(), ApplicationNumScope.TEST_PLAN_FUNCTION_CASE));
functionalCase.setCreateTime(System.currentTimeMillis());
functionalCase.setCreateUser(testPlan.getCreateUser());
functionalCase.setFunctionalCaseId(item.getFunctionalCaseId());
@ -170,4 +181,46 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
pos.updateAndGet(v -> v + ServiceUtils.POS_STEP);
});
}
public List<TestPlanCasePageResponse> getFunctionalCasePage(TestPlanCaseRequest request, boolean deleted) {
List<TestPlanCasePageResponse> functionalCaseLists = extTestPlanFunctionalCaseMapper.getCasePage(request, deleted, request.getSortString());
if (CollectionUtils.isEmpty(functionalCaseLists)) {
return new ArrayList<>();
}
//处理自定义字段值
return handleCustomFields(functionalCaseLists, request.getProjectId());
}
private List<TestPlanCasePageResponse> handleCustomFields(List<TestPlanCasePageResponse> functionalCaseLists, String projectId) {
List<String> ids = functionalCaseLists.stream().map(TestPlanCasePageResponse::getCaseId).collect(Collectors.toList());
Map<String, List<FunctionalCaseCustomFieldDTO>> collect = functionalCaseService.getCaseCustomFiledMap(ids, projectId);
Set<String> userIds = extractUserIds(functionalCaseLists);
Map<String, List<CaseRelateBugDTO>> bugListMap = getBugData(ids);
Map<String, String> userMap = userLoginService.getUserNameMap(new ArrayList<>(userIds));
functionalCaseLists.forEach(testPlanCasePageResponse -> {
testPlanCasePageResponse.setCustomFields(collect.get(testPlanCasePageResponse.getCaseId()));
testPlanCasePageResponse.setCreateUserName(userMap.get(testPlanCasePageResponse.getCreateUser()));
testPlanCasePageResponse.setExecuteUserName(userMap.get(testPlanCasePageResponse.getExecuteUser()));
if (bugListMap.containsKey(testPlanCasePageResponse.getCaseId())) {
List<CaseRelateBugDTO> bugDTOList = bugListMap.get(testPlanCasePageResponse.getCaseId());
testPlanCasePageResponse.setBugList(bugDTOList);
testPlanCasePageResponse.setBugCount(bugDTOList.size());
}
});
return functionalCaseLists;
}
private Map<String, List<CaseRelateBugDTO>> getBugData(List<String> ids) {
List<CaseRelateBugDTO> bugList = bugRelateCaseMapper.getBugCountByIds(ids);
return bugList.stream().collect(Collectors.groupingBy(CaseRelateBugDTO::getCaseId));
}
public Set<String> extractUserIds(List<TestPlanCasePageResponse> list) {
return list.stream()
.flatMap(testPlanCasePageResponse -> Stream.of(testPlanCasePageResponse.getUpdateUser(), testPlanCasePageResponse.getCreateUser(), testPlanCasePageResponse.getExecuteUser()))
.collect(Collectors.toSet());
}
}

View File

@ -33,7 +33,7 @@ import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanModuleService extends ModuleTreeService implements CleanupProjectResourceService {
public class TestPlanModuleService extends ModuleTreeService {
@Resource
private TestPlanModuleMapper testPlanModuleMapper;
@Resource
@ -210,14 +210,6 @@ public class TestPlanModuleService extends ModuleTreeService implements CleanupP
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
@Override
public void deleteResources(String projectId) {
List<String> fileModuleIdList = extTestPlanModuleMapper.selectIdsByProjectId(projectId);
if (CollectionUtils.isNotEmpty(fileModuleIdList)) {
this.deleteModule(fileModuleIdList, projectId, "SCHEDULE", "none", "none");
}
}
public String getNameById(String id) {
return extTestPlanModuleMapper.selectNameById(id);
}

View File

@ -454,6 +454,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
TestPlanDetailResponse response = new TestPlanDetailResponse();
String moduleName = getModuleName(testPlan.getModuleId());
//计划组只有几个参数
response.setId(testPlan.getId());
response.setName(testPlan.getName());
response.setTags(testPlan.getTags());
response.setModuleId(testPlan.getModuleId());

View File

@ -19,7 +19,7 @@ import org.springframework.context.annotation.ComponentScan;
MinioProperties.class
})
@ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.plan", "io.metersphere.system", "io.metersphere.project", "io.metersphere.bug"})
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.plan", "io.metersphere.system", "io.metersphere.project", "io.metersphere.bug","io.metersphere.functional"})
public class TestPlanApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,21 @@
package io.metersphere.plan.config;
import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.provider.BaseAssociateScenarioProvider;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
@TestConfiguration
public class CaseTestConfiguration {
@MockBean
BaseAssociateApiProvider provider;
@MockBean
BaseAssociateScenarioProvider scenarioProvider;
@MockBean
BaseAssociateBugProvider baseAssociateBugProvider;
}

View File

@ -0,0 +1,41 @@
package io.metersphere.plan.controller;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class TestPlanCaseControllerTests extends BaseTest {
public static final String FUNCTIONAL_CASE_LIST_URL = "/test-plan/functional/case/page";
@Test
@Order(1)
@Sql(scripts = {"/dml/init_test_plan_case_relate_bug.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void testGetFunctionalCaseList() throws Exception {
TestPlanCaseRequest request = new TestPlanCaseRequest();
request.setProjectId("123");
request.setCurrent(1);
request.setPageSize(10);
this.requestPost(FUNCTIONAL_CASE_LIST_URL, request);
request.setSort(new HashMap<>() {{
put("createTime", "desc");
}});
MvcResult mvcResult = this.requestPostWithOkAndReturn(FUNCTIONAL_CASE_LIST_URL, request);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Assertions.assertNotNull(resultHolder);
}
}

View File

@ -1871,7 +1871,7 @@ public class TestPlanTests extends BaseTest {
testPlanModuleService.deleteModule(new ArrayList<>(), project.getId(), null, null, null);
//service层判断测试删除项目
testPlanModuleService.deleteResources(project.getId());
// testPlanModuleService.deleteResources(project.getId());
//判断权限
this.requestGetPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ_DELETE, (String.format(URL_GET_MODULE_DELETE, IDGenerator.nextNum())));

View File

@ -0,0 +1,25 @@
INSERT INTO `bug_relation_case`(`id`, `case_id`, `bug_id`, `case_type`, `test_plan_id`, `test_plan_case_id`, `create_user`, `create_time`, `update_time`)
VALUES ('relate_1', 'fc_1', 'bug_1', 'FUNCTIONAL', 'test-plan-id-for-bug', 'test-plan-bug-case-id', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO `bug_relation_case`(`id`, `case_id`, `bug_id`, `case_type`, `test_plan_id`, `test_plan_case_id`, `create_user`, `create_time`, `update_time`)
VALUES ('relate_2', 'fc_2', 'bug_1', 'FUNCTIONAL', 'test-plan-id-for-bug', 'test-plan-bug-case-id', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted, pos)
VALUES ('bug_1', 100001, 'oasis', 'admin', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, '100001100001', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0, 5000);
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`)
VALUES
('plan_1', 20000, '123', 'wx_test_plan_id_3', '1', '测试关联', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`)
VALUES
('relate_case_1', 'plan_1', 'fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1),
('relate_case_2', 'plan_1', 'fc_2', 1714980158000, 'admin', NULL, NULL, NULL, 1);
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
('fc_1', 1, '1', '123', '100001', '111', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
('fc_2', 2, '1', '123', '100001', '222', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
INSERT INTO project_version(id, project_id, name, description, status, latest, publish_time, start_time, end_time, create_time, create_user)
values ('v3.0.0','123','v3', null, 'open', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin');

View File

@ -9,8 +9,8 @@ VALUES
('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`)
VALUES ('wx_tpfc_1', 5000, 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1);
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`)
VALUES ('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1);
INSERT INTO `test_plan_module`(`id`, `project_id`, `name`, `parent_id`, `pos`, `create_time`, `update_time`, `create_user`, `update_user`)