fix(用例管理): 导入上千条顺序问题

--bug=1039706 --user=王旭 【用例管理】导入功能用例的顺序和excel不一致 https://www.tapd.cn/55049933/s/1501064
This commit is contained in:
WangXu10 2024-04-18 17:31:32 +08:00 committed by 刘瑞斌
parent 12e3eae473
commit e7a8fd38ab
8 changed files with 37 additions and 19 deletions

View File

@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.Serial; import java.io.Serial;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -84,7 +85,9 @@ public class FunctionalCaseImportEventListener extends AnalysisEventListener<Map
protected static final int TAGS_COUNT = 15; protected static final int TAGS_COUNT = 15;
protected static final int TAG_LENGTH = 64; protected static final int TAG_LENGTH = 64;
public FunctionalCaseImportEventListener(FunctionalCaseImportRequest request, Class clazz, List<TemplateCustomFieldDTO> customFields, Set<ExcelMergeInfo> mergeInfoSet, SessionUser user) { private AtomicLong lastPos;
public FunctionalCaseImportEventListener(FunctionalCaseImportRequest request, Class clazz, List<TemplateCustomFieldDTO> customFields, Set<ExcelMergeInfo> mergeInfoSet, SessionUser user, Long pos) {
this.mergeInfoSet = mergeInfoSet; this.mergeInfoSet = mergeInfoSet;
this.request = request; this.request = request;
excelDataClass = clazz; excelDataClass = clazz;
@ -93,6 +96,7 @@ public class FunctionalCaseImportEventListener extends AnalysisEventListener<Map
moduleTree = CommonBeanFactory.getBean(FunctionalCaseModuleService.class).getTree(request.getProjectId()); moduleTree = CommonBeanFactory.getBean(FunctionalCaseModuleService.class).getTree(request.getProjectId());
functionalCaseService = CommonBeanFactory.getBean(FunctionalCaseService.class); functionalCaseService = CommonBeanFactory.getBean(FunctionalCaseService.class);
customFieldValidatorMap = CustomFieldValidatorFactory.getValidatorMap(); customFieldValidatorMap = CustomFieldValidatorFactory.getValidatorMap();
lastPos = new AtomicLong(pos);
this.user = user; this.user = user;
} }
@ -201,7 +205,7 @@ public class FunctionalCaseImportEventListener extends AnalysisEventListener<Map
*/ */
private void saveData() { private void saveData() {
if (CollectionUtils.isNotEmpty(list)) { if (CollectionUtils.isNotEmpty(list)) {
functionalCaseService.saveImportData(list, request, moduleTree, customFieldsMap, pathMap, user); functionalCaseService.saveImportData(list, request, moduleTree, customFieldsMap, pathMap, user, lastPos);
} }
if (CollectionUtils.isNotEmpty(updateList)) { if (CollectionUtils.isNotEmpty(updateList)) {

View File

@ -27,4 +27,7 @@ public class FunctionalCaseImportRequest implements Serializable {
@Schema(description = "是否覆盖原用例", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "是否覆盖原用例", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.cover.not_blank}") @NotBlank(message = "{functional_case.cover.not_blank}")
private boolean cover; private boolean cover;
@Schema(description = "导入数量")
private String count;
} }

View File

@ -24,6 +24,7 @@ import io.metersphere.system.dto.sdk.SessionUser;
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO; import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
import io.metersphere.system.dto.sdk.TemplateDTO; import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.excel.utils.EasyExcelExporter; import io.metersphere.system.excel.utils.EasyExcelExporter;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -47,6 +48,9 @@ public class FunctionalCaseFileService {
@Resource @Resource
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper; private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
@Resource
private FunctionalCaseService functionalCaseService;
/** /**
* 下载excel导入模板 * 下载excel导入模板
@ -242,6 +246,8 @@ public class FunctionalCaseFileService {
if (StringUtils.isEmpty(request.getVersionId())) { if (StringUtils.isEmpty(request.getVersionId())) {
request.setVersionId(extBaseProjectVersionMapper.getDefaultVersion(request.getProjectId())); request.setVersionId(extBaseProjectVersionMapper.getDefaultVersion(request.getProjectId()));
} }
Long nextPos = functionalCaseService.getNextOrder(request.getProjectId());
Long lasePos = nextPos + (ServiceUtils.POS_STEP * Integer.parseInt(request.getCount()));
//根据本地语言环境选择用哪种数据对象进行存放读取的数据 //根据本地语言环境选择用哪种数据对象进行存放读取的数据
Class clazz = new FunctionalCaseExcelDataFactory().getExcelDataByLocal(); Class clazz = new FunctionalCaseExcelDataFactory().getExcelDataByLocal();
//获取当前项目默认模板的自定义字段 //获取当前项目默认模板的自定义字段
@ -250,7 +256,7 @@ public class FunctionalCaseFileService {
// 预处理查询合并单元格信息 // 预处理查询合并单元格信息
EasyExcel.read(file.getInputStream(), null, new FunctionalCasePretreatmentListener(mergeInfoSet)) EasyExcel.read(file.getInputStream(), null, new FunctionalCasePretreatmentListener(mergeInfoSet))
.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead(); .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
FunctionalCaseImportEventListener eventListener = new FunctionalCaseImportEventListener(request, clazz, customFields, mergeInfoSet, user); FunctionalCaseImportEventListener eventListener = new FunctionalCaseImportEventListener(request, clazz, customFields, mergeInfoSet, user, lasePos);
EasyExcelFactory.read(file.getInputStream(), eventListener).sheet().doRead(); EasyExcelFactory.read(file.getInputStream(), eventListener).sheet().doRead();
response.setErrorMessages(eventListener.getErrList()); response.setErrorMessages(eventListener.getErrList());
response.setSuccessCount(eventListener.getSuccessCount()); response.setSuccessCount(eventListener.getSuccessCount());

View File

@ -46,8 +46,6 @@ import io.metersphere.system.log.service.OperationLogService;
import io.metersphere.system.mapper.OperationHistoryMapper; import io.metersphere.system.mapper.OperationHistoryMapper;
import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.resolver.field.AbstractCustomFieldResolver;
import io.metersphere.system.resolver.field.CustomFieldResolverFactory;
import io.metersphere.system.service.*; import io.metersphere.system.service.*;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
@ -66,6 +64,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -1023,7 +1022,7 @@ public class FunctionalCaseService {
* @param pathMap 模块路径 * @param pathMap 模块路径
* @param user user * @param user user
*/ */
public void saveImportData(List<FunctionalCaseExcelData> list, FunctionalCaseImportRequest request, List<BaseTreeNode> moduleTree, Map<String, TemplateCustomFieldDTO> customFieldsMap, Map<String, String> pathMap, SessionUser user) { public void saveImportData(List<FunctionalCaseExcelData> list, FunctionalCaseImportRequest request, List<BaseTreeNode> moduleTree, Map<String, TemplateCustomFieldDTO> customFieldsMap, Map<String, String> pathMap, SessionUser user, AtomicLong lastPos) {
//默认模板 //默认模板
TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(request.getProjectId(), TemplateScene.FUNCTIONAL.name()); TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(request.getProjectId(), TemplateScene.FUNCTIONAL.name());
//模块路径 //模块路径
@ -1035,18 +1034,18 @@ public class FunctionalCaseService {
FunctionalCaseMapper caseMapper = sqlSession.getMapper(FunctionalCaseMapper.class); FunctionalCaseMapper caseMapper = sqlSession.getMapper(FunctionalCaseMapper.class);
FunctionalCaseBlobMapper caseBlobMapper = sqlSession.getMapper(FunctionalCaseBlobMapper.class); FunctionalCaseBlobMapper caseBlobMapper = sqlSession.getMapper(FunctionalCaseBlobMapper.class);
FunctionalCaseCustomFieldMapper customFieldMapper = sqlSession.getMapper(FunctionalCaseCustomFieldMapper.class); FunctionalCaseCustomFieldMapper customFieldMapper = sqlSession.getMapper(FunctionalCaseCustomFieldMapper.class);
Long nextOrder = getNextOrder(request.getProjectId());
List<FunctionalCaseDTO> noticeList = new ArrayList<>(); List<FunctionalCaseDTO> noticeList = new ArrayList<>();
List<FunctionalCaseHistoryLogDTO> historyLogDTOS = new ArrayList<>(); List<FunctionalCaseHistoryLogDTO> historyLogDTOS = new ArrayList<>();
List<LogDTO> logDTOS = new ArrayList<>(); List<LogDTO> logDTOS = new ArrayList<>();
List<String> caseIds = new ArrayList<>(); List<String> caseIds = new ArrayList<>();
for (int i = list.size() - 1; i > -1; i--) { Long pos = lastPos.get();
parseInsertDataToModule(list.get(i), request, user.getId(), caseModulePathMap, defaultTemplateDTO, nextOrder, caseMapper, caseBlobMapper, customFieldMapper, customFieldsMap, caseIds, historyLogDTOS); for (int i = 0; i < list.size(); i++) {
nextOrder += ServiceUtils.POS_STEP; parseInsertDataToModule(list.get(i), request, user.getId(), caseModulePathMap, defaultTemplateDTO, pos, caseMapper, caseBlobMapper, customFieldMapper, customFieldsMap, caseIds, historyLogDTOS);
pos -= ServiceUtils.POS_STEP;
//通知 //通知
noticeModule(noticeList, list.get(i), request, user.getId(), customFieldsMap); noticeModule(noticeList, list.get(i), request, user.getId(), customFieldsMap);
} }
lastPos.set(pos);
sqlSession.flushStatements(); sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);

View File

@ -675,6 +675,7 @@ public class FunctionalCaseControllerTests extends BaseTest {
FunctionalCaseImportRequest request = new FunctionalCaseImportRequest(); FunctionalCaseImportRequest request = new FunctionalCaseImportRequest();
request.setCover(true); request.setCover(true);
request.setProjectId("100001100001"); request.setProjectId("100001100001");
request.setCount("1");
LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>(); LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request)); paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file); paramMap.add("file", file);

View File

@ -3,9 +3,9 @@
<MsIcon <MsIcon
:type="lastExecuteResultMap[props.executeResult]?.icon || ''" :type="lastExecuteResultMap[props.executeResult]?.icon || ''"
class="mr-1" class="mr-1"
:class="[lastExecuteResultMap[props.executeResult].color]" :style="{ color: lastExecuteResultMap[props.executeResult].color }"
></MsIcon> ></MsIcon>
<span>{{ status?.text || '' }}</span> <span class="text-[var(--color-text-2)]">{{ status?.text || '' }}</span>
</div> </div>
</template> </template>
@ -27,7 +27,7 @@
label: 'UN_EXECUTED', label: 'UN_EXECUTED',
icon: StatusType.UN_EXECUTED, icon: StatusType.UN_EXECUTED,
statusText: 'caseManagement.featureCase.nonExecution', statusText: 'caseManagement.featureCase.nonExecution',
color: 'text-[rgb(var(--warning-6))]', color: 'var(--color-text-brand)',
}, },
PASSED: { PASSED: {
label: 'PASSED', label: 'PASSED',
@ -39,13 +39,13 @@
label: 'SKIPPED', label: 'SKIPPED',
icon: StatusType.SKIPPED, icon: StatusType.SKIPPED,
statusText: 'caseManagement.featureCase.skip', statusText: 'caseManagement.featureCase.skip',
color: 'text-[rgb(var(--link-6))]', color: 'rgb(var(--link-6))',
}, },
BLOCKED: { BLOCKED: {
label: 'BLOCKED', label: 'BLOCKED',
icon: StatusType.BLOCKED, icon: StatusType.BLOCKED,
statusText: 'caseManagement.featureCase.chokeUp', statusText: 'caseManagement.featureCase.chokeUp',
color: 'text-[rgb(var(--warning-6))]', color: 'rgb(var(--warning-6))',
}, },
FAILED: { FAILED: {
label: 'FAILED', label: 'FAILED',

View File

@ -96,7 +96,7 @@
> >
<a-button <a-button
type="text" type="text"
class="arco-btn-text--secondary p-[8px_4px]" class="arco-btn-text--secondary p-[8px_4px] text-[14px] leading-[22px]"
size="mini" size="mini"
@click="executeResultFilterVisible = true" @click="executeResultFilterVisible = true"
> >
@ -167,7 +167,11 @@
</template> </template>
<template #reviewStatusFilter="{ columnConfig }"> <template #reviewStatusFilter="{ columnConfig }">
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden"> <a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true"> <a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px] leading-[22px]"
@click="statusFilterVisible = true"
>
<div class="font-medium"> <div class="font-medium">
{{ t(columnConfig.title as string) }} {{ t(columnConfig.title as string) }}
</div> </div>
@ -209,7 +213,7 @@
@change="() => handleStatusChange(record)" @change="() => handleStatusChange(record)"
> >
<template #label> <template #label>
<span class="text-[var(--color-text-2)]"> <executeResult :execute-result="record.lastExecuteResult" /></span> <executeResult :execute-result="record.lastExecuteResult" />
</template> </template>
<a-option v-for="item of LastExecuteResults" :key="item" :value="item"> <a-option v-for="item of LastExecuteResults" :key="item" :value="item">
<executeResult :execute-result="item" /> <executeResult :execute-result="item" />

View File

@ -388,6 +388,7 @@
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
versionId: '', versionId: '',
cover: isCover.value, cover: isCover.value,
count: validateInfo.value.successCount,
}; };
await importExcelCase({ request: params, fileList: fileList.value.map((item: any) => item.file) }); await importExcelCase({ request: params, fileList: fileList.value.map((item: any) => item.file) });
Message.success(t('caseManagement.featureCase.importSuccess')); Message.success(t('caseManagement.featureCase.importSuccess'));