fix(用例管理): 导入上千条顺序问题
--bug=1039706 --user=王旭 【用例管理】导入功能用例的顺序和excel不一致 https://www.tapd.cn/55049933/s/1501064
This commit is contained in:
parent
12e3eae473
commit
e7a8fd38ab
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
Loading…
Reference in New Issue