feat(用例管理): 拖拽排序&变更历史
This commit is contained in:
parent
c940a86423
commit
ed760e9b2c
|
@ -0,0 +1,11 @@
|
|||
package io.metersphere.functional.constants;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
|
||||
public enum HistoryLogEnum {
|
||||
IMPORT,
|
||||
EDIT,
|
||||
ROLLBACK
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.metersphere.functional.constants;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
|
||||
public enum MoveTypeEnum {
|
||||
BEFORE,
|
||||
AFTER,
|
||||
APPEND
|
||||
}
|
|
@ -125,7 +125,7 @@ public class FunctionalCaseController {
|
|||
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
||||
public Pager<List<FunctionalCasePageDTO>> getFunctionalCasePage(@Validated @RequestBody FunctionalCasePageRequest request) {
|
||||
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
||||
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
|
||||
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "pos desc");
|
||||
return PageUtils.setPageInfo(page, functionalCaseService.getFunctionalCasePage(request, false));
|
||||
}
|
||||
|
||||
|
@ -181,4 +181,12 @@ public class FunctionalCaseController {
|
|||
functionalCaseService.batchEditFunctionalCase(request, userId);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("edit/pos")
|
||||
@Operation(summary = "用例管理-功能用例-拖拽排序")
|
||||
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_UPDATE)
|
||||
public void editPos(@Validated @RequestBody PosRequest request) {
|
||||
functionalCaseService.editPos(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,4 +54,8 @@ public interface ExtFunctionalCaseMapper {
|
|||
|
||||
long caseCount(@Param("request") FunctionalCasePageRequest request, @Param("deleted") boolean deleted);
|
||||
|
||||
Long getPrePos(@Param("projectId") String projectId, @Param("basePos") Long basePos);
|
||||
|
||||
Long getLastPos(@Param("projectId") String projectId, @Param("basePos") Long basePos);
|
||||
|
||||
}
|
||||
|
|
|
@ -547,4 +547,22 @@
|
|||
</when>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
|
||||
<select id="getPrePos" resultType="java.lang.Long">
|
||||
select `pos` from functional_case where project_id = #{projectId}
|
||||
<if test="basePos != null">
|
||||
and `pos` < #{basePos}
|
||||
</if>
|
||||
order by `pos` desc limit 1;
|
||||
</select>
|
||||
|
||||
|
||||
<select id="getLastPos" resultType="java.lang.Long">
|
||||
select `pos` from functional_case where project_id = #{projectId}
|
||||
<if test="basePos != null">
|
||||
and `pos` > #{basePos}
|
||||
</if>
|
||||
order by `pos` desc limit 1;
|
||||
</select>
|
||||
</mapper>
|
|
@ -0,0 +1,36 @@
|
|||
package io.metersphere.functional.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author wx
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PosRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "项目id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{case_review.project_id.not_blank}")
|
||||
private String projectId;
|
||||
|
||||
@Schema(description = "目标用例id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{case_review.name.not_blank}")
|
||||
private String targetId;
|
||||
|
||||
@Schema(description = "移动类型", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"BEFORE", "AFTER", "APPEND"})
|
||||
@NotBlank(message = "{case_review.module_id.not_blank}")
|
||||
private String moveMode;
|
||||
|
||||
@Schema(description = "移动用例id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{case_review.module_id.not_blank}")
|
||||
private String moveId;
|
||||
}
|
|
@ -3,5 +3,5 @@ package io.metersphere.functional.service;
|
|||
import java.util.List;
|
||||
|
||||
public interface FunctionalCaseHistoryService {
|
||||
void saveHistoryLog(List<String> caseIds);
|
||||
void saveHistoryLog(List<String> caseIds, String type, String userId);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.functional.service;
|
||||
|
||||
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
|
||||
import io.metersphere.functional.constants.HistoryLogEnum;
|
||||
import io.metersphere.functional.domain.*;
|
||||
import io.metersphere.functional.dto.*;
|
||||
import io.metersphere.functional.mapper.ExtFunctionalCaseMapper;
|
||||
|
@ -9,6 +10,7 @@ import io.metersphere.functional.mapper.FunctionalCaseFollowerMapper;
|
|||
import io.metersphere.functional.mapper.FunctionalCaseMapper;
|
||||
import io.metersphere.functional.request.*;
|
||||
import io.metersphere.functional.result.CaseManagementResultCode;
|
||||
import io.metersphere.functional.util.ServiceUtils;
|
||||
import io.metersphere.project.domain.FileAssociation;
|
||||
import io.metersphere.project.dto.ModuleCountDTO;
|
||||
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
|
||||
|
@ -46,7 +48,6 @@ import java.util.stream.Collectors;
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public class FunctionalCaseService {
|
||||
|
||||
public static final int ORDER_STEP = 5000;
|
||||
|
||||
@Resource
|
||||
private ExtFunctionalCaseMapper extFunctionalCaseMapper;
|
||||
|
@ -100,15 +101,15 @@ public class FunctionalCaseService {
|
|||
functionalCaseAttachmentService.association(request.getRelateFileMetaIds(), caseId, userId, ADD_FUNCTIONAL_CASE_FILE_LOG_URL, request.getProjectId());
|
||||
}
|
||||
|
||||
saveHistory(Collections.singletonList(caseId));
|
||||
saveHistory(Collections.singletonList(caseId), HistoryLogEnum.EDIT.name(), userId);
|
||||
|
||||
return functionalCase;
|
||||
}
|
||||
|
||||
private void saveHistory(List<String> ids) {
|
||||
private void saveHistory(List<String> ids, String type, String userId) {
|
||||
FunctionalCaseHistoryService functionalCaseHistoryService = CommonBeanFactory.getBean(FunctionalCaseHistoryService.class);
|
||||
if (functionalCaseHistoryService != null) {
|
||||
functionalCaseHistoryService.saveHistoryLog(ids);
|
||||
functionalCaseHistoryService.saveHistoryLog(ids, type, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +161,7 @@ public class FunctionalCaseService {
|
|||
|
||||
public Long getNextOrder(String projectId) {
|
||||
Long pos = extFunctionalCaseMapper.getPos(projectId);
|
||||
return (pos == null ? 0 : pos) + ORDER_STEP;
|
||||
return (pos == null ? 0 : pos) + ServiceUtils.POS_STEP;
|
||||
}
|
||||
|
||||
public long getNextNum(String projectId) {
|
||||
|
@ -281,7 +282,7 @@ public class FunctionalCaseService {
|
|||
functionalCaseAttachmentService.association(request.getRelateFileMetaIds(), request.getId(), userId, UPDATE_FUNCTIONAL_CASE_FILE_LOG_URL, request.getProjectId());
|
||||
}
|
||||
|
||||
saveHistory(Collections.singletonList(request.getId()));
|
||||
saveHistory(Collections.singletonList(request.getId()), HistoryLogEnum.EDIT.name(), userId);
|
||||
|
||||
return functionalCase;
|
||||
|
||||
|
@ -490,7 +491,7 @@ public class FunctionalCaseService {
|
|||
functional.setNum(getNextNum(request.getProjectId()));
|
||||
functional.setName(getCopyName(functionalCase.getName()));
|
||||
functional.setReviewStatus(FunctionalCaseReviewStatus.UN_REVIEWED.name());
|
||||
functional.setPos(nextOrder + ORDER_STEP);
|
||||
functional.setPos(nextOrder + ServiceUtils.POS_STEP);
|
||||
functional.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name());
|
||||
functional.setCreateUser(userId);
|
||||
functional.setCreateTime(System.currentTimeMillis());
|
||||
|
@ -564,7 +565,7 @@ public class FunctionalCaseService {
|
|||
handleTags(request, userId, ids);
|
||||
//自定义字段处理
|
||||
handleCustomFields(request, userId, ids);
|
||||
saveHistory(ids);
|
||||
saveHistory(ids, HistoryLogEnum.EDIT.name(), userId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -630,4 +631,13 @@ public class FunctionalCaseService {
|
|||
return moduleCountMap;
|
||||
|
||||
}
|
||||
|
||||
public void editPos(PosRequest request) {
|
||||
ServiceUtils.updateOrderField(request,
|
||||
FunctionalCase.class,
|
||||
functionalCaseMapper::selectByPrimaryKey,
|
||||
extFunctionalCaseMapper::getPrePos,
|
||||
extFunctionalCaseMapper::getLastPos,
|
||||
functionalCaseMapper::updateByPrimaryKeySelective);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package io.metersphere.functional.util;
|
||||
|
||||
import io.metersphere.functional.constants.MoveTypeEnum;
|
||||
import io.metersphere.functional.request.PosRequest;
|
||||
import io.metersphere.sdk.exception.MSException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ServiceUtils {
|
||||
|
||||
public static final int POS_STEP = 5000;
|
||||
|
||||
public static <T> void updateOrderField(PosRequest request, Class<T> clazz,
|
||||
Function<String, T> selectByPrimaryKeyFunc,
|
||||
BiFunction<String, Long, Long> getPreOrderFunc,
|
||||
BiFunction<String, Long, Long> getLastOrderFunc,
|
||||
Consumer<T> updateByPrimaryKeySelectiveFuc) {
|
||||
Long pos;
|
||||
Long lastOrPrePos;
|
||||
try {
|
||||
Method getPos = clazz.getMethod("getPos");
|
||||
Method setId = clazz.getMethod("setId", String.class);
|
||||
Method setPos = clazz.getMethod("setPos", Long.class);
|
||||
|
||||
// 获取移动的参考对象
|
||||
T target = selectByPrimaryKeyFunc.apply(request.getTargetId());
|
||||
|
||||
if (target == null) {
|
||||
// 如果参考对象被删除,则不处理
|
||||
return;
|
||||
}
|
||||
|
||||
Long targetOrder = (Long) getPos.invoke(target);
|
||||
|
||||
if (request.getMoveMode().equals(MoveTypeEnum.AFTER.name())) {
|
||||
// 追加到参考对象的之后
|
||||
pos = targetOrder - ServiceUtils.POS_STEP;
|
||||
// ,因为是降序排,则查找比目标 order 小的一个order
|
||||
lastOrPrePos = getPreOrderFunc.apply(request.getProjectId(), targetOrder);
|
||||
} else {
|
||||
// 追加到前面
|
||||
pos = targetOrder + ServiceUtils.POS_STEP;
|
||||
// 因为是降序排,则查找比目标 order 更大的一个order
|
||||
lastOrPrePos = getLastOrderFunc.apply(request.getProjectId(), targetOrder);
|
||||
}
|
||||
if (lastOrPrePos != null) {
|
||||
// 如果不是第一个或最后一个则取中间值
|
||||
pos = (targetOrder + lastOrPrePos) / 2;
|
||||
}
|
||||
|
||||
// 更新order值
|
||||
T updateObj = (T) clazz.getDeclaredConstructor().newInstance();
|
||||
setId.invoke(updateObj, request.getMoveId());
|
||||
setPos.invoke(updateObj, pos);
|
||||
updateByPrimaryKeySelectiveFuc.accept(updateObj);
|
||||
} catch (Exception e) {
|
||||
throw new MSException("更新 order 字段失败");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ public class FunctionalCaseControllerTests extends BaseTest {
|
|||
public static final String FUNCTIONAL_CASE_BATCH_COPY_URL = "/functional/case/batch/copy";
|
||||
public static final String FUNCTIONAL_CASE_VERSION_URL = "/functional/case/version/";
|
||||
public static final String FUNCTIONAL_CASE_BATCH_EDIT_URL = "/functional/case/batch/edit";
|
||||
public static final String FUNCTIONAL_CASE_POS_URL = "/functional/case/edit/pos";
|
||||
|
||||
@Resource
|
||||
private NotificationMapper notificationMapper;
|
||||
|
@ -388,4 +389,20 @@ public class FunctionalCaseControllerTests extends BaseTest {
|
|||
this.requestPostWithOkAndReturn(FUNCTIONAL_CASE_BATCH_EDIT_URL, request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@Order(18)
|
||||
public void testPos() throws Exception {
|
||||
PosRequest posRequest = new PosRequest();
|
||||
posRequest.setProjectId(DEFAULT_PROJECT_ID);
|
||||
posRequest.setTargetId("TEST_FUNCTIONAL_CASE_ID_2");
|
||||
posRequest.setMoveId("TEST_FUNCTIONAL_CASE_ID_6");
|
||||
posRequest.setMoveMode("AFTER");
|
||||
this.requestPostWithOkAndReturn(FUNCTIONAL_CASE_POS_URL, posRequest);
|
||||
|
||||
posRequest.setMoveMode("BEFORE");
|
||||
this.requestPostWithOkAndReturn(FUNCTIONAL_CASE_POS_URL, posRequest);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,22 +8,22 @@ INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, r
|
|||
VALUES ('TEST_FUNCTIONAL_CASE_ID', 1, 'TEST_MODULE_ID', '100001100001', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_1', 2, 'TEST_MODULE_ID', '100001100001', '100001', '测试多版本', 'UN_REVIEWED', '["测试标签_1"]', 'STEP', 0, 'v1.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_1', 2, 'TEST_MODULE_ID', '100001100001', '100001', '测试多版本', 'UN_REVIEWED', '["测试标签_1"]', 'STEP', 5000, 'v1.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_2', 3, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v2.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_2', 3, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 15000, 'v2.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_3', 3, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_3', 3, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 25000, 'v3.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_4', 4, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_4', 4, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 40000, 'v3.0.0', 'TEST_REF_ID', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_5', 5, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_5', 5, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 45000, 'v3.0.0', 'TEST_REF_ID_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_6', 6, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
VALUES ('TEST_FUNCTIONAL_CASE_ID_6', 6, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_测试多版本', 'UN_REVIEWED', NULL, 'STEP', 55000, 'v3.0.0', 'TEST_REF_ID_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
||||
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 ('TEST_FUNCTIONAL_CASE_ID_7', 7, 'TEST_MODULE_ID', '100001100001', '100001', 'copy_long_name_11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111', 'UN_REVIEWED', NULL, 'STEP', 0, 'v3.0.0', 'TEST_REF_ID_2', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.system.config.interceptor;
|
||||
|
||||
import io.metersphere.functional.domain.FunctionalCaseHistory;
|
||||
import io.metersphere.sdk.util.CompressUtils;
|
||||
import io.metersphere.system.utils.MybatisInterceptorConfig;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class FunctionalCaseInterceptor {
|
||||
@Bean
|
||||
public List<MybatisInterceptorConfig> functionalCaseCompressConfigs() {
|
||||
List<MybatisInterceptorConfig> configList = new ArrayList<>();
|
||||
|
||||
configList.add(new MybatisInterceptorConfig(FunctionalCaseHistory.class, "content", CompressUtils.class, "zip", "unzip"));
|
||||
|
||||
|
||||
return configList;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue