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.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
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 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.request = request;
excelDataClass = clazz;
@ -93,6 +96,7 @@ public class FunctionalCaseImportEventListener extends AnalysisEventListener<Map
moduleTree = CommonBeanFactory.getBean(FunctionalCaseModuleService.class).getTree(request.getProjectId());
functionalCaseService = CommonBeanFactory.getBean(FunctionalCaseService.class);
customFieldValidatorMap = CustomFieldValidatorFactory.getValidatorMap();
lastPos = new AtomicLong(pos);
this.user = user;
}
@ -201,7 +205,7 @@ public class FunctionalCaseImportEventListener extends AnalysisEventListener<Map
*/
private void saveData() {
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)) {

View File

@ -27,4 +27,7 @@ public class FunctionalCaseImportRequest implements Serializable {
@Schema(description = "是否覆盖原用例", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.cover.not_blank}")
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.TemplateDTO;
import io.metersphere.system.excel.utils.EasyExcelExporter;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
@ -47,6 +48,9 @@ public class FunctionalCaseFileService {
@Resource
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
@Resource
private FunctionalCaseService functionalCaseService;
/**
* 下载excel导入模板
@ -242,6 +246,8 @@ public class FunctionalCaseFileService {
if (StringUtils.isEmpty(request.getVersionId())) {
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();
//获取当前项目默认模板的自定义字段
@ -250,7 +256,7 @@ public class FunctionalCaseFileService {
// 预处理查询合并单元格信息
EasyExcel.read(file.getInputStream(), null, new FunctionalCasePretreatmentListener(mergeInfoSet))
.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();
response.setErrorMessages(eventListener.getErrList());
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.UserMapper;
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.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
@ -66,6 +64,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -1023,7 +1022,7 @@ public class FunctionalCaseService {
* @param pathMap 模块路径
* @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());
//模块路径
@ -1035,18 +1034,18 @@ public class FunctionalCaseService {
FunctionalCaseMapper caseMapper = sqlSession.getMapper(FunctionalCaseMapper.class);
FunctionalCaseBlobMapper caseBlobMapper = sqlSession.getMapper(FunctionalCaseBlobMapper.class);
FunctionalCaseCustomFieldMapper customFieldMapper = sqlSession.getMapper(FunctionalCaseCustomFieldMapper.class);
Long nextOrder = getNextOrder(request.getProjectId());
List<FunctionalCaseDTO> noticeList = new ArrayList<>();
List<FunctionalCaseHistoryLogDTO> historyLogDTOS = new ArrayList<>();
List<LogDTO> logDTOS = new ArrayList<>();
List<String> caseIds = new ArrayList<>();
for (int i = list.size() - 1; i > -1; i--) {
parseInsertDataToModule(list.get(i), request, user.getId(), caseModulePathMap, defaultTemplateDTO, nextOrder, caseMapper, caseBlobMapper, customFieldMapper, customFieldsMap, caseIds, historyLogDTOS);
nextOrder += ServiceUtils.POS_STEP;
Long pos = lastPos.get();
for (int i = 0; i < list.size(); i++) {
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);
}
lastPos.set(pos);
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);

View File

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

View File

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

View File

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

View File

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