feat(用例管理): 拖拽排序&变更历史

This commit is contained in:
WangXu10 2023-11-27 16:33:57 +08:00 committed by f2c-ci-robot[bot]
parent c940a86423
commit ed760e9b2c
12 changed files with 217 additions and 16 deletions

View File

@ -0,0 +1,11 @@
package io.metersphere.functional.constants;
/**
* @author wx
*/
public enum HistoryLogEnum {
IMPORT,
EDIT,
ROLLBACK
}

View File

@ -0,0 +1,11 @@
package io.metersphere.functional.constants;
/**
* @author wx
*/
public enum MoveTypeEnum {
BEFORE,
AFTER,
APPEND
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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` &lt; #{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` &gt; #{basePos}
</if>
order by `pos` desc limit 1;
</select>
</mapper>

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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 字段失败");
}
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}
}