diff --git a/backend/app/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__plan_ddl.sql b/backend/app/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__plan_ddl.sql index bda98b50e6..d27b5ba6b2 100644 --- a/backend/app/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__plan_ddl.sql +++ b/backend/app/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__plan_ddl.sql @@ -1,5 +1,56 @@ -- set innodb lock wait timeout SET SESSION innodb_lock_wait_timeout = 7200; +DROP TABLE IF EXISTS test_plan; +CREATE TABLE test_plan( + `id` VARCHAR(50) NOT NULL COMMENT 'ID' , + `project_id` VARCHAR(50) NOT NULL COMMENT '测试计划所属项目' , + `parent_id` VARCHAR(50) NOT NULL COMMENT '测试计划父ID;测试计划要改为树结构。最上层的为root,其余则是父节点ID' , + `name` VARCHAR(255) NOT NULL COMMENT '测试计划名称' , + `status` VARCHAR(20) NOT NULL COMMENT '测试计划状态;进行中/未开始/已完成/已结束/已归档' , + `stage` VARCHAR(30) NOT NULL COMMENT '测试阶段' , + `tags` VARCHAR(500) COMMENT '标签' , + `create_time` BIGINT NOT NULL COMMENT '创建时间' , + `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , + `update_time` BIGINT COMMENT '更新时间' , + `update_user` VARCHAR(50) COMMENT '更新人' , + `planned_start_time` BIGINT COMMENT '计划开始时间' , + `planned_end_time` BIGINT COMMENT '计划结束时间' , + `actual_start_time` BIGINT COMMENT '实际开始时间' , + `actual_end_time` BIGINT COMMENT '实际结束时间' , + `description` VARCHAR(2000) COMMENT '描述' , + PRIMARY KEY (id) +) COMMENT = '测试计划'; + + +CREATE INDEX idx_parent_id ON test_plan(project_id); +CREATE INDEX idx_project_id ON test_plan(project_id); +CREATE INDEX idx_create_user ON test_plan(create_user); +CREATE INDEX idx_status ON test_plan(status); + +DROP TABLE IF EXISTS test_plan_follower; +CREATE TABLE test_plan_follower( + `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID;联合主键' , + `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID;联合主键' , + PRIMARY KEY (test_plan_id,user_id) +) COMMENT = '测试计划关注人'; + +DROP TABLE IF EXISTS test_plan_principal; +CREATE TABLE test_plan_principal( + `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , + `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID' , + PRIMARY KEY (test_plan_id,user_id) +) COMMENT = '测试计划责任人'; + +DROP TABLE IF EXISTS test_plan_config; +CREATE TABLE test_plan_config( + `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , + `run_mode_config` TEXT NOT NULL COMMENT '运行模式' , + `automatic_status_update` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否自定更新功能用例状态' , + `repeat_case` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否允许重复添加用例' , + `pass_threshold` INT(3) NOT NULL DEFAULT 100 COMMENT '测试计划通过阈值;0-100' , + PRIMARY KEY (test_plan_id) +) COMMENT = '测试计划配置'; + -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollow.java b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollower.java similarity index 55% rename from backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollow.java rename to backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollower.java index e90b06e9b4..c208293235 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollow.java +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollower.java @@ -9,15 +9,15 @@ import java.io.Serializable; import lombok.Data; @Data -public class TestPlanFollow implements Serializable { +public class TestPlanFollower implements Serializable { @Schema(title = "测试计划ID;联合主键", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "{test_plan_follow.test_plan_id.not_blank}", groups = {Created.class}) - @Size(min = 1, max = 50, message = "{test_plan_follow.test_plan_id.length_range}", groups = {Created.class, Updated.class}) + @NotBlank(message = "{test_plan_follower.test_plan_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{test_plan_follower.test_plan_id.length_range}", groups = {Created.class, Updated.class}) private String testPlanId; @Schema(title = "用户ID;联合主键", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "{test_plan_follow.user_id.not_blank}", groups = {Created.class}) - @Size(min = 1, max = 50, message = "{test_plan_follow.user_id.length_range}", groups = {Created.class, Updated.class}) + @NotBlank(message = "{test_plan_follower.user_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{test_plan_follower.user_id.length_range}", groups = {Created.class, Updated.class}) private String userId; private static final long serialVersionUID = 1L; diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowExample.java b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowerExample.java similarity index 99% rename from backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowExample.java rename to backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowerExample.java index 7753cfa772..7bb01ac660 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanFollowerExample.java @@ -3,14 +3,14 @@ package io.metersphere.plan.domain; import java.util.ArrayList; import java.util.List; -public class TestPlanFollowExample { +public class TestPlanFollowerExample { protected String orderByClause; protected boolean distinct; protected List oredCriteria; - public TestPlanFollowExample() { + public TestPlanFollowerExample() { oredCriteria = new ArrayList(); } diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.java b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.java deleted file mode 100644 index 160bea989c..0000000000 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.metersphere.plan.mapper; - -import io.metersphere.plan.domain.TestPlanFollow; -import io.metersphere.plan.domain.TestPlanFollowExample; -import java.util.List; -import org.apache.ibatis.annotations.Param; - -public interface TestPlanFollowMapper { - long countByExample(TestPlanFollowExample example); - - int deleteByExample(TestPlanFollowExample example); - - int deleteByPrimaryKey(@Param("testPlanId") String testPlanId, @Param("userId") String userId); - - int insert(TestPlanFollow record); - - int insertSelective(TestPlanFollow record); - - List selectByExample(TestPlanFollowExample example); - - int updateByExampleSelective(@Param("record") TestPlanFollow record, @Param("example") TestPlanFollowExample example); - - int updateByExample(@Param("record") TestPlanFollow record, @Param("example") TestPlanFollowExample example); -} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.java b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.java new file mode 100644 index 0000000000..3099d4ef0b --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.java @@ -0,0 +1,24 @@ +package io.metersphere.plan.mapper; + +import io.metersphere.plan.domain.TestPlanFollower; +import io.metersphere.plan.domain.TestPlanFollowerExample; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface TestPlanFollowerMapper { + long countByExample(TestPlanFollowerExample example); + + int deleteByExample(TestPlanFollowerExample example); + + int deleteByPrimaryKey(@Param("testPlanId") String testPlanId, @Param("userId") String userId); + + int insert(TestPlanFollower record); + + int insertSelective(TestPlanFollower record); + + List selectByExample(TestPlanFollowerExample example); + + int updateByExampleSelective(@Param("record") TestPlanFollower record, @Param("example") TestPlanFollowerExample example); + + int updateByExample(@Param("record") TestPlanFollower record, @Param("example") TestPlanFollowerExample example); +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.xml similarity index 89% rename from backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.xml rename to backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.xml index fe85ab75b8..808700173f 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowMapper.xml +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanFollowerMapper.xml @@ -1,7 +1,7 @@ - - + + @@ -66,13 +66,13 @@ test_plan_id, user_id - select distinct - from test_plan_follow + from test_plan_follower @@ -81,22 +81,22 @@ - delete from test_plan_follow + delete from test_plan_follower where test_plan_id = #{testPlanId,jdbcType=VARCHAR} and user_id = #{userId,jdbcType=VARCHAR} - - delete from test_plan_follow + + delete from test_plan_follower - - insert into test_plan_follow (test_plan_id, user_id) + + insert into test_plan_follower (test_plan_id, user_id) values (#{testPlanId,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR}) - - insert into test_plan_follow + + insert into test_plan_follower test_plan_id, @@ -114,14 +114,14 @@ - + select count(*) from test_plan_follower - update test_plan_follow + update test_plan_follower test_plan_id = #{record.testPlanId,jdbcType=VARCHAR}, @@ -135,7 +135,7 @@ - update test_plan_follow + update test_plan_follower set test_plan_id = #{record.testPlanId,jdbcType=VARCHAR}, user_id = #{record.userId,jdbcType=VARCHAR} diff --git a/backend/framework/domain/src/main/resources/generatorConfig.xml b/backend/framework/domain/src/main/resources/generatorConfig.xml index c8bf0e79b9..17023b740e 100644 --- a/backend/framework/domain/src/main/resources/generatorConfig.xml +++ b/backend/framework/domain/src/main/resources/generatorConfig.xml @@ -53,28 +53,28 @@ - + - + - - -
-
-
-
-
-
+
+ + + + + + diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java index c818c99868..b91bb5113c 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java @@ -1,6 +1,6 @@ package io.metersphere.plan.controller; -import io.metersphere.plan.domain.TestPlan; +import io.metersphere.plan.dto.TestPlanDTO; import io.metersphere.plan.service.TestPlanService; import io.metersphere.validation.groups.Created; import jakarta.annotation.Resource; @@ -17,7 +17,7 @@ public class TestPlanController { private TestPlanService testPlanService; @PostMapping("/add") - public boolean addUser(@Validated({Created.class}) @RequestBody TestPlan testPlan) { + public TestPlanDTO addUser(@Validated({Created.class}) @RequestBody TestPlanDTO testPlan) { return testPlanService.add(testPlan); } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanDTO.java new file mode 100644 index 0000000000..5effad11cd --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanDTO.java @@ -0,0 +1,16 @@ +package io.metersphere.plan.dto; + +import io.metersphere.plan.domain.TestPlan; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class TestPlanDTO extends TestPlan { + @Schema(title = "测试计划责任人", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private List principals; + + @Schema(title = "测试计划关注人") + private List followers; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFollowerService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFollowerService.java new file mode 100644 index 0000000000..28968d7e88 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFollowerService.java @@ -0,0 +1,23 @@ +package io.metersphere.plan.service; + +import io.metersphere.plan.domain.TestPlanFollower; +import io.metersphere.plan.mapper.TestPlanFollowerMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanFollowerService { + + @Resource + TestPlanFollowerMapper testPlanFollowerMapper; + + public void batchSave(List testPlanFollowerList) { + for (TestPlanFollower testPlanFollower : testPlanFollowerList) { + testPlanFollowerMapper.insert(testPlanFollower); + } + } +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanPrincipalService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanPrincipalService.java new file mode 100644 index 0000000000..d6e352bb47 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanPrincipalService.java @@ -0,0 +1,24 @@ +package io.metersphere.plan.service; + +import io.metersphere.plan.domain.TestPlanPrincipal; +import io.metersphere.plan.mapper.TestPlanPrincipalMapper; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotEmpty; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanPrincipalService { + + @Resource + TestPlanPrincipalMapper testPlanPrincipalMapper; + + public void batchSave(@NotEmpty List testPlanPrincipalList) { + for (TestPlanPrincipal testPlanPrincipal : testPlanPrincipalList) { + testPlanPrincipalMapper.insert(testPlanPrincipal); + } + } +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java index 5cb0764593..a662c0cc0a 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -1,14 +1,61 @@ package io.metersphere.plan.service; import io.metersphere.plan.domain.TestPlan; +import io.metersphere.plan.domain.TestPlanFollower; +import io.metersphere.plan.domain.TestPlanPrincipal; +import io.metersphere.plan.dto.TestPlanDTO; +import io.metersphere.plan.mapper.TestPlanMapper; +import io.metersphere.sdk.util.BeanUtils; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + @Service @Transactional(rollbackFor = Exception.class) public class TestPlanService { + @Resource + private TestPlanMapper testPlanMapper; - public boolean add(TestPlan testPlan) { - return testPlan == null; + @Resource + private TestPlanPrincipalService testPlanPrincipalService; + @Resource + private TestPlanFollowerService testPlanFollowerService; + + public TestPlanDTO add(@NotNull TestPlanDTO testPlanDTO) { + TestPlan testPlan = new TestPlan(); + BeanUtils.copyBean(testPlan, testPlanDTO); + testPlan.setId(UUID.randomUUID().toString()); + //todo SongTianyang:暂时没有SessionUtil,创建人先根据前台传值保存 + testPlan.setCreateTime(System.currentTimeMillis()); + testPlanMapper.insert(testPlan); + + if (CollectionUtils.isNotEmpty(testPlanDTO.getFollowers())) { + List testPlanFollowerList = new ArrayList<>(); + for (String follower : testPlanDTO.getFollowers()) { + TestPlanFollower testPlanFollower = new TestPlanFollower(); + testPlanFollower.setTestPlanId(testPlan.getId()); + testPlanFollower.setUserId(follower); + testPlanFollowerList.add(testPlanFollower); + } + testPlanFollowerService.batchSave(testPlanFollowerList); + } + + if (CollectionUtils.isNotEmpty(testPlanDTO.getPrincipals())) { + List testPlanPrincipalList = new ArrayList<>(); + for (String principal : testPlanDTO.getPrincipals()) { + TestPlanPrincipal testPlanPrincipal = new TestPlanPrincipal(); + testPlanPrincipal.setTestPlanId(testPlan.getId()); + testPlanPrincipal.setUserId(principal); + testPlanPrincipalList.add(testPlanPrincipal); + } + testPlanPrincipalService.batchSave(testPlanPrincipalList); + } + return testPlanDTO; } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java index 46900fb2c3..30d1e5f416 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java @@ -1,6 +1,7 @@ package io.metersphere.plan.controller; import io.metersphere.plan.domain.TestPlan; +import io.metersphere.plan.dto.TestPlanDTO; import io.metersphere.sdk.util.JSON; import jakarta.annotation.Resource; import org.junit.jupiter.api.MethodOrderer; @@ -13,6 +14,9 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.util.ArrayList; +import java.util.List; + import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -25,10 +29,8 @@ public class TestPlanControllerTests { @Resource private MockMvc mockMvc; - @Test - @Order(1) - public void testAddUserTrue() throws Exception { - TestPlan testPlan = new TestPlan(); + private TestPlanDTO getSimpleTestPlan() { + TestPlanDTO testPlan = new TestPlanDTO(); testPlan.setId("test"); testPlan.setName("test"); testPlan.setProjectId("1"); @@ -36,7 +38,26 @@ public class TestPlanControllerTests { testPlan.setCreateUser("JianGuo"); testPlan.setStage("Smock"); testPlan.setStatus("PREPARE"); + testPlan.setCreateUser("JianGuo"); + return testPlan; + } + @Test + @Order(1) + public void testAdd1() throws Exception { + TestPlanDTO testPlan = this.getSimpleTestPlan(); + + List followerList = new ArrayList<>(); + followerList.add("JianGuo"); + followerList.add("SongGuoyu"); + followerList.add("SongYingyu"); + followerList.add("SongFanti"); + testPlan.setFollowers(followerList); + + List participantList = new ArrayList<>(); + participantList.add("JianGuo"); + participantList.add("SongGuoyu"); + testPlan.setPrincipals(participantList); mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") .content(JSON.toJSONString(testPlan)) @@ -47,6 +68,54 @@ public class TestPlanControllerTests { @Test @Order(2) + public void testAdd2() throws Exception { + TestPlanDTO testPlan = this.getSimpleTestPlan(); + + List followerList = new ArrayList<>(); + followerList.add("JianGuo"); + followerList.add("SongGuoyu"); + followerList.add("SongYingyu"); + followerList.add("SongFanti"); + testPlan.setFollowers(followerList); + + mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") + .content(JSON.toJSONString(testPlan)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andDo(print()); + } + + @Test + @Order(2) + public void testAdd3() throws Exception { + TestPlanDTO testPlan = this.getSimpleTestPlan(); + + List participantList = new ArrayList<>(); + participantList.add("JianGuo"); + participantList.add("SongGuoyu"); + testPlan.setPrincipals(participantList); + + mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") + .content(JSON.toJSONString(testPlan)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andDo(print()); + } + + @Test + @Order(4) + public void testAdd4() throws Exception { + TestPlanDTO testPlan = this.getSimpleTestPlan(); + + mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") + .content(JSON.toJSONString(testPlan)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andDo(print()); + } + + @Test + @Order(5) public void testAddUserFalse() throws Exception { TestPlan testPlan = new TestPlan(); testPlan.setName("test");