feat(测试跟踪): 非MS平台集成支持平台状态字段导入导出

--story=1012478 --user=宋昌昌 与JIRA集成,在MS列表中导出缺陷时,希望能够支持导出“平台状态”字段 https://www.tapd.cn/55049933/s/1396115
This commit is contained in:
song-cc-rock 2023-07-21 11:59:06 +08:00 committed by fit2-zhao
parent dd381da0a0
commit 84137c7364
12 changed files with 358 additions and 66 deletions

View File

@ -20,6 +20,7 @@ public enum IssueExportHeadField {
COMMENT("comment", Translator.get("comment"), IssueExcelData::getComment),
RESOURCE("resource", Translator.get("issue_resource"), IssueExcelData::getResourceName),
PLATFORM("platform", Translator.get("issue_platform"), IssueExcelData::getPlatform),
PLATFORM_STATUS("platformStatus", Translator.get("platform_status"), IssueExcelData::getPlatformStatus),
CREATE_TIME("createTime", Translator.get("create_time"), issueExcelData -> issueExcelData.getCreateTime() != null ? DateUtils.getTimeStr(issueExcelData.getCreateTime()) : null);
private String id;

View File

@ -66,6 +66,8 @@ public class IssueExcelData implements Serializable {
private String priority;
@ExcelIgnore
Map<String, Object> customData = new LinkedHashMap<>();
@ExcelIgnore
List<String> tapdUsers = new ArrayList<>();
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request) {
return new ArrayList<>();

View File

@ -4,10 +4,10 @@ import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.request.issues.IssueExportRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import java.util.Locale;
@ -23,7 +23,7 @@ public class IssueExcelDataTw extends IssueExcelData{
@NotBlank(message = "{cannot_be_null}")
@Length(max = 1000)
@ColumnWidth(100)
@ExcelProperty("缺陷内容")
@ExcelProperty("缺陷描述")
private String description;
@Override

View File

@ -1,9 +1,16 @@
package io.metersphere.excel.handler;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import io.metersphere.commons.constants.CustomFieldScene;
import io.metersphere.commons.constants.CustomFieldType;
import io.metersphere.commons.utils.JSON;
@ -12,25 +19,23 @@ import io.metersphere.dto.CustomFieldOptionDTO;
import io.metersphere.excel.constants.IssueExportHeadField;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
/**
* 表头, 单元格后置处理
*/
public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWriteHandler {
public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWriteHandler, CellWriteHandler {
private Sheet sheet;
private Drawing<?> drawingPatriarch;
private Map<String, String> memberMap;
private Map<Integer, String> headCommentIndexMap = new HashMap<>();
private Map<Integer, String> dateFieldIndexMap = new HashMap<>();
public IssueTemplateHeadWriteHandler(Map<String, String> memberMap, List<List<String>> headList, List<CustomFieldDao> customFields) {
this.memberMap = memberMap;
@ -56,7 +61,7 @@ public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWrit
customFieldDao.setType(CustomFieldType.MEMBER.getValue());
} else {
// 自定义字段
List<CustomFieldDao> fields = customFields.stream().filter(field -> StringUtils.equals(field.getName(), head)).collect(Collectors.toList());
List<CustomFieldDao> fields = customFields.stream().filter(field -> StringUtils.equals(field.getName(), head)).toList();
if (fields.size() > 0) {
customFieldDao = fields.get(0);
} else {
@ -79,6 +84,18 @@ public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWrit
}
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
DataFormat dataFormat = workbook.createDataFormat();
for (WriteCellData<?> writeCellData : cellDataList) {
WriteCellStyle writeCellStyle = writeCellData.getOrCreateStyle();
DataFormatData dataFormatData = new DataFormatData();
dataFormatData.setIndex(dataFormat.getFormat("@"));
writeCellStyle.setDataFormatData(dataFormatData);
}
}
private String getCommentByCustomField(CustomFieldDao field) {
String commentText = "";
if (StringUtils.equalsAnyIgnoreCase(field.getType(),
@ -86,6 +103,8 @@ public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWrit
if (StringUtils.equalsAnyIgnoreCase(field.getScene(), CustomFieldScene.ISSUE.name()) &&
StringUtils.equalsAnyIgnoreCase(field.getName(), "状态", "严重程度")) {
commentText = Translator.get("options").concat(JSON.toJSONString(getOptionValues(field)));
} else if (StringUtils.equalsAnyIgnoreCase(field.getName(), Translator.get("platform_status"))) {
commentText = Translator.get("options").concat(JSON.toJSONString(getOptionsLabel(field.getOptions())));
} else {
commentText = Translator.get("options").concat(JSON.toJSONString(getOptionsText(field.getOptions())));
}
@ -159,6 +178,20 @@ public class IssueTemplateHeadWriteHandler implements RowWriteHandler, SheetWrit
return options;
}
private List<String> getOptionsLabel(String optionStr) {
if (StringUtils.isEmpty(optionStr)) {
return Collections.emptyList();
}
List<String> options = new ArrayList<>();
List<Map> optionMapList = JSON.parseArray(optionStr, Map.class);
optionMapList.forEach(optionMap -> {
String optionText = optionMap.get("label").toString();
options.add(optionText);
});
return options;
}
private List<String> getCascadSelect(String optionStr) {
if (StringUtils.isEmpty(optionStr)) {
return Collections.emptyList();

View File

@ -22,6 +22,7 @@ import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator;
import io.metersphere.request.issues.IssueImportRequest;
import io.metersphere.service.IssuesService;
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
@ -40,6 +41,18 @@ import java.util.stream.Collectors;
public class IssueExcelListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* dataClass: EXCEL数据实例class
* request: 导入参数
* isThirdPlatform: 是否是第三方平台
* headMap: excel表头字段集合
* customFields: 自定义字段集合(模板自定义字段 + 平台自定义字段)
* issuesService: 业务类
* memberMap: 成员集合
* platformStatusList: 平台状态列表
* excelHeadFieldMap: excel表头字段字典值
* issueCustomFieldMap: 缺陷自定义字段KeyMap
*/
private Class dataClass;
private IssueImportRequest request;
private Boolean isThirdPlatform;
@ -47,9 +60,8 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
private List<CustomFieldDao> customFields;
private IssuesService issuesService;
private Map<String, String> memberMap;
/**
* excel表头字段字典值
*/
private List<PlatformStatusDTO> platformStatusList;
private List<String> tapdUsers;
private Map<String, String> headFieldTransferDic = new HashMap<>();
private Map<String, List<CustomFieldResourceDTO>> issueCustomFieldMap = new HashMap<>();
@ -61,19 +73,21 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
/**
* insertList: 新增缺陷集合
* updateList: 覆盖缺陷集合
* errList: 校验失败缺陷集合
* errList: 校验失败错误信息集合
*/
protected List<IssueExcelData> insertList = new ArrayList<>();
protected List<IssueExcelData> updateList = new ArrayList<>();
protected List<ExcelErrData<IssueExcelData>> errList = new ArrayList<>();
public IssueExcelListener(IssueImportRequest request, Class clazz, Boolean isThirdPlatform, List<CustomFieldDao> customFields, Map<String, String> memberMap) {
public IssueExcelListener(IssueImportRequest request, Class clazz, Boolean isThirdPlatform, List<CustomFieldDao> customFields, Map<String, String> memberMap, List<PlatformStatusDTO> platformStatusList, List<String> tapdUsers) {
this.request = request;
this.dataClass = clazz;
this.isThirdPlatform = isThirdPlatform;
this.customFields = customFields;
this.issuesService = CommonBeanFactory.getBean(IssuesService.class);
this.memberMap = memberMap;
this.platformStatusList = platformStatusList;
this.tapdUsers = tapdUsers;
}
@Override
@ -85,9 +99,11 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
issueExcelData = this.parseDataToModel(data);
// EXCEL校验, 如果不是第三方模板则需要校验
errMsg = new StringBuilder(!isThirdPlatform ? ExcelValidateHelper.validateEntity(issueExcelData) : StringUtils.EMPTY);
// 校验自定义字段
// 校验自定义字段及平台状态及TAPD处理人
if (StringUtils.isEmpty(errMsg)) {
validateCustomField(issueExcelData, errMsg);
validateAndTransferPlatformStatus(issueExcelData, errMsg);
validateTapdUsers(issueExcelData, errMsg);
}
} catch (Exception e) {
errMsg = new StringBuilder(Translator.get("parse_data_error"));
@ -139,6 +155,9 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
}
/**
* 保存缺陷数据
*/
public void saveData() {
//excel中用例都有错误时就返回只要有用例可用于更新或者插入就不返回
if (!errList.isEmpty()) {
@ -176,6 +195,9 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
issueCustomFieldMap.clear();
}
/**
* headMap转换
*/
private void formatHeadMap() {
for (Integer key : headMap.keySet()) {
String name = headMap.get(key);
@ -185,6 +207,11 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
}
/**
* 校验自定义字段
* @param data excel数据
* @param errMsg 错误信息
*/
public void validateCustomField(IssueExcelData data, StringBuilder errMsg) {
Map<String, List<CustomFieldDao>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(CustomFieldDao::getName));
data.getCustomData().forEach((k, v) -> {
@ -210,6 +237,45 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
});
}
/**
* 校验并转换平台状态
* @param data excel数据
* @param errMsg 错误信息
*/
public void validateAndTransferPlatformStatus(IssueExcelData data, StringBuilder errMsg) {
String platformStatus = data.getPlatformStatus();
if (StringUtils.isNotEmpty(platformStatus) && CollectionUtils.isNotEmpty(platformStatusList)) {
Optional<PlatformStatusDTO> first = platformStatusList.stream().filter(status -> StringUtils.equals(status.getLabel(), platformStatus)).findFirst();
if (first.isPresent()) {
data.setPlatformStatus(first.get().getValue());
} else {
errMsg.append(IssueExportHeadField.PLATFORM_STATUS.getName()).append(Translator.get("options_not_exist")).append(";");
}
}
}
/**
* 校验TAPD处理人
* @param data excel数据
* @param errMsg 错误信息
*/
public void validateTapdUsers(IssueExcelData data, StringBuilder errMsg) {
List<String> tarTapdUsers = data.getTapdUsers();
if (CollectionUtils.isNotEmpty(tarTapdUsers) && CollectionUtils.isNotEmpty(tapdUsers)) {
tarTapdUsers.forEach(tapdUser -> {
Optional<String> first = tapdUsers.stream().filter(user -> StringUtils.equals(user, tapdUser)).findFirst();
if (first.isEmpty()) {
errMsg.append(Translator.get("tapd_user")).append(Translator.get("options_not_exist")).append(";");
}
});
}
}
/**
* 解析表格数据 -> excel数据
* @param rowData 表格数据
* @return excel数据
*/
private IssueExcelData parseDataToModel(Map<Integer, String> rowData) {
IssueExcelData data = new IssueExcelDataFactory().getIssueExcelDataLocal();
for (Map.Entry<Integer, String> headEntry : headMap.entrySet()) {
@ -226,16 +292,28 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
data.setTitle(value);
} else if (StringUtils.equalsAnyIgnoreCase(field, IssueExportHeadField.DESCRIPTION.getId())) {
data.setDescription(value);
} else if (StringUtils.equalsAnyIgnoreCase(field, IssueExportHeadField.PLATFORM_STATUS.getName())) {
data.setPlatformStatus(value);
} else if (StringUtils.equalsAnyIgnoreCase(field, Translator.get("tapd_user"))) {
if (StringUtils.isEmpty(value)) {
data.setTapdUsers(null);
} else if (value.contains(",")) {
data.setTapdUsers(Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toList()));
} else if (value.contains(";")) {
data.setTapdUsers(Arrays.stream(value.split(";")).map(String::trim).collect(Collectors.toList()));
} else {
data.setTapdUsers(List.of(value.trim()));
}
} else {
// 自定义字段
if (StringUtils.isNotEmpty(value) && (value.contains(","))) {
// 逗号分隔
List<String> dataList = Arrays.asList(org.springframework.util.StringUtils.trimAllWhitespace(value).split(","));
List<String> dataList = Arrays.stream(value.split(",")).map(String::trim).toList();
List<String> formatDataList = dataList.stream().map(item -> "\"" + item + "\"").collect(Collectors.toList());
data.getCustomData().put(field, formatDataList);
} else if (StringUtils.isNotEmpty(value) && (value.contains(";"))){
// 分号分隔
List<String> dataList = Arrays.asList(org.springframework.util.StringUtils.trimAllWhitespace(value).split(";"));
List<String> dataList = Arrays.stream(value.split(";")).map(String::trim).toList();
List<String> formatDataList = dataList.stream().map(item -> "\"" + item + "\"").collect(Collectors.toList());
data.getCustomData().put(field, formatDataList);
} else {
@ -246,13 +324,20 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
return data;
}
/**
* excel数据 -> issue请求数据
* @param issueExcelData excel数据
* @return issue请求数据
*/
private IssuesUpdateRequest convertToIssue(IssueExcelData issueExcelData) {
IssuesUpdateRequest issuesUpdateRequest = new IssuesUpdateRequest();
issuesUpdateRequest.setTapdUsers(issueExcelData.getTapdUsers());
issuesUpdateRequest.setWorkspaceId(request.getWorkspaceId());
issuesUpdateRequest.setProjectId(request.getProjectId());
issuesUpdateRequest.setThirdPartPlatform(isThirdPlatform);
issuesUpdateRequest.setDescription(issueExcelData.getDescription());
issuesUpdateRequest.setTitle(issueExcelData.getTitle());
issuesUpdateRequest.setPlatformStatus(issueExcelData.getPlatformStatus());
if (BooleanUtils.isTrue(issueExcelData.getAddFlag())) {
issuesUpdateRequest.setCreator(SessionUtils.getUserId());
} else {
@ -285,10 +370,21 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
}
/**
* 缺陷存在
* @param num 缺陷ID
* @param projectId 项目ID
* @return issues
*/
private Issues checkIssueExist(Integer num, String projectId) {
return issuesService.checkIssueExist(num, projectId);
}
/**
* 构建自定义字段
* @param issueExcelData excel对象
* @param issuesUpdateRequest 缺陷请求对象
*/
private void buildFields(IssueExcelData issueExcelData, IssuesUpdateRequest issuesUpdateRequest) {
if (MapUtils.isEmpty(issueExcelData.getCustomData())) {
return;
@ -368,14 +464,9 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
String parseStr = parseCascadingOptionText(customFieldDao.getOptions(), v.toString());
customFieldItemDTO.setValue(parseStr);
customFieldResourceDTO.setValue(parseStr);
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.DATE.getValue())) {
Date vdate = DateUtils.parseDate(v.toString(), "yyyy/MM/dd");
v = DateUtils.format(vdate, "yyyy-MM-dd");
customFieldItemDTO.setValue(v.toString());
customFieldResourceDTO.setValue("\"" + v + "\"");
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.DATETIME.getValue())) {
Date vdate = DateUtils.parseDate(v.toString());
v = DateUtils.format(vdate, "yyyy-MM-dd'T'HH:mm");
Date vDate = DateUtils.parseDate(v.toString(), "yyyy-MM-dd HH:mm:ss");
v = DateUtils.format(vDate, "yyyy-MM-dd'T'HH:mm");
customFieldItemDTO.setValue(v.toString());
customFieldResourceDTO.setValue("\"" + v + "\"");
} else {
@ -406,18 +497,29 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
return issuesService.getIssue(issueId).getPlatformId();
}
/**
* 校验字段是否为下拉框
* @param type 字段类型
* @return boolean
*/
private Boolean isSelect(String type) {
return StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.SELECT.getValue(), CustomFieldType.RADIO.getValue(),
CustomFieldType.MULTIPLE_SELECT.getValue(), CustomFieldType.CHECKBOX.getValue(),
CustomFieldType.CASCADING_SELECT.getValue(), CustomFieldType.MEMBER.getValue(), CustomFieldType.MULTIPLE_MEMBER.getValue());
}
/**
* 校验字段非法格式
* @param type 字段类型
* @param value 字段值
* @return boolean
*/
private Boolean isIllegalFormat(String type, Object value) {
try {
if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.DATE.getValue())) {
DateUtils.parseDate(value.toString(), "yyyy/MM/dd");
DateUtils.parseDate(value.toString(), "yyyy-MM-dd");
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.DATETIME.getValue())) {
DateUtils.parseDate(value.toString());
DateUtils.parseDate(value.toString(), "yyyy-MM-dd HH:mm:ss");
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.INT.getValue())) {
Integer.parseInt(value.toString());
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.FLOAT.getValue())) {
@ -433,6 +535,12 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
}
/**
* 是否选项中包含该值
* @param value
* @param options 选项
* @return boolean
*/
private Boolean isOptionInclude(Object value, String options) {
AtomicReference<Boolean> isInclude = new AtomicReference<>(Boolean.TRUE);
if (value instanceof List) {
@ -443,11 +551,16 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
});
} else {
isInclude.set(StringUtils.contains(options, "\"" + value.toString() + "\""));
isInclude.set(StringUtils.contains(options, value.toString()));
}
return isInclude.get();
}
/**
* 是否包含导出字段
* @param name 导出字段名称
* @return boolean
*/
public Boolean exportFieldsContains(String name) {
for (IssueExportHeadField issueExportHeadField : IssueExportHeadField.values()) {
if (StringUtils.equals(name, issueExportHeadField.getName())) {
@ -457,6 +570,12 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
return Boolean.FALSE;
}
/**
* 解析选择类型字段 (文本 -> )
* @param options 选项
* @param tarVal 文本
* @return
*/
public String parseOptionText(String options, String tarVal) {
if (StringUtils.isEmpty(options)) {
return StringUtils.EMPTY;
@ -475,18 +594,23 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
}
return parseArr.toString();
} else {
tarVal = tarVal + ",";
for (Map option : optionList) {
String text = option.get("text").toString();
String value = option.get("value").toString();
if (StringUtils.containsIgnoreCase(tarVal, text + ",")) {
tarVal = tarVal.replaceAll(text, value);
if (StringUtils.containsIgnoreCase(text, tarVal)) {
return value;
}
}
return tarVal.substring(0, tarVal.length() - 1);
return tarVal;
}
}
/**
* 解析级联类型字段(文本 -> )
* @param cascadingOption 级联选项
* @param tarVal 文本
* @return
*/
public String parseCascadingOptionText(String cascadingOption, String tarVal) {
List<String> values = new ArrayList<>();
if (StringUtils.isEmpty(cascadingOption)) {
@ -510,6 +634,12 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
return values.toString();
}
/**
* 匹配级联层级options
* @param options 级联选项
* @param tarVal 文本
* @return json对象
*/
private JSONObject findJsonOption(JSONArray options, String tarVal) {
if (options.size() == 0) {
return null;

View File

@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Joiner;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtIssueCommentMapper;
@ -54,6 +55,7 @@ import io.metersphere.service.remote.project.TrackIssueTemplateService;
import io.metersphere.service.wapper.TrackProjectService;
import io.metersphere.service.wapper.UserService;
import io.metersphere.utils.DistinctKeyUtil;
import io.metersphere.xpack.track.dto.AttachmentRequest;
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
import io.metersphere.xpack.track.dto.PlatformUser;
import io.metersphere.xpack.track.dto.*;
@ -799,11 +801,6 @@ public class IssuesService {
if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.RICH_TEXT.getValue(), CustomFieldType.TEXTAREA.getValue())) {
fieldDao.setValue(fieldDao.getTextValue());
}
if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATE.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue()) && !StringUtils.equals(fieldDao.getValue(), "null")) {
Date date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd");
String format = DateUtils.format(date, "yyyy/MM/dd");
fieldDao.setValue("\"" + format + "\"");
}
if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.DATETIME.getValue()) && StringUtils.isNotEmpty(fieldDao.getValue()) && !StringUtils.equals(fieldDao.getValue(), "null")) {
Date date = null;
if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() == 18) {
@ -815,7 +812,7 @@ public class IssuesService {
} else {
date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY));
}
String format = DateUtils.format(date, "yyyy/MM/dd HH:mm:ss");
String format = DateUtils.format(date, "yyyy-MM-dd HH:mm:ss");
fieldDao.setValue("\"" + format + "\"");
}
if (StringUtils.equalsAnyIgnoreCase(customField.getType(), CustomFieldType.SELECT.getValue(),
@ -1664,13 +1661,20 @@ public class IssuesService {
public void issueImportTemplate(String projectId, HttpServletResponse response) {
Map<String, String> userMap = baseUserService.getProjectMemberOption(projectId).stream().collect(Collectors.toMap(User::getId, User::getName));
// 获取第三方平台自定义字段
List<CustomFieldDao> pluginCustomFields = getPluginCustomFields(projectId);
// 获取缺陷模板及自定义字段
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(projectId);
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
customFields.addAll(pluginCustomFields);
// TAPD暂时未拆分插件, 插件字段手动获取
if (StringUtils.equals(issueTemplate.getPlatform(), IssuesManagePlatform.Tapd.name())) {
customFields.add(buildTapdUserCustomField(projectId));
}
// 根据自定义字段获取表头
List<List<String>> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, null);
// 导出空模板, heads->表头, headHandler->表头处理
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields());
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, customFields);
new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal())
.exportByCustomWriteHandler(response, heads, null, Translator.get("issue_import_template_name"),
Translator.get("issue_import_template_sheet"), headHandler);
@ -1687,10 +1691,23 @@ public class IssuesService {
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId());
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
customFields.addAll(pluginCustomFields);
// 非Local平台需要解析平台状态字段
List<PlatformStatusDTO> platformStatus = new ArrayList<>();
if (!IssuesManagePlatform.Local.equals(issueTemplate.getPlatform())) {
CustomFieldDao customFieldDao = buildPlatformStatusCustomField(request.getWorkspaceId(), request.getProjectId());
platformStatus = JSON.parseArray(customFieldDao.getOptions(), PlatformStatusDTO.class);
}
// TAPD暂时未拆分插件, 插件字段手动获取
List<String> tapdUsers = new ArrayList<>();
if (StringUtils.equals(issueTemplate.getPlatform(), IssuesManagePlatform.Tapd.name())) {
CustomFieldDao tapdField = buildTapdUserCustomField(request.getProjectId());
List<JSONObject> jsonObjects = JSON.parseArray(tapdField.getOptions(), JSONObject.class);
tapdUsers = jsonObjects.stream().map(item -> item.getString("value")).collect(Collectors.toList());
}
// 获取本地EXCEL数据对象
Class clazz = new IssueExcelDataFactory().getExcelDataByLocal();
// IssueExcelListener读取file内容
IssueExcelListener issueExcelListener = new IssueExcelListener(request, clazz, issueTemplate.getIsThirdTemplate(), customFields, userMap);
IssueExcelListener issueExcelListener = new IssueExcelListener(request, clazz, issueTemplate.getIsThirdTemplate(), customFields, userMap, platformStatus, tapdUsers);
try {
EasyExcelFactory.read(importFile.getInputStream(), issueExcelListener).sheet().doRead();
} catch (IOException e) {
@ -1718,6 +1735,17 @@ public class IssuesService {
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId());
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
customFields.addAll(pluginCustomFields);
// 非Local平台需要展示平台状态字段
List<PlatformStatusDTO> platformStatus = new ArrayList<>();
if (!IssuesManagePlatform.Local.equals(issueTemplate.getPlatform())) {
CustomFieldDao customFieldDao = buildPlatformStatusCustomField(request.getWorkspaceId(), request.getProjectId());
customFields.add(customFieldDao);
platformStatus = JSON.parseArray(customFieldDao.getOptions(), PlatformStatusDTO.class);
}
// TAPD暂时未拆分插件, 插件字段手动获取
if (StringUtils.equals(issueTemplate.getPlatform(), IssuesManagePlatform.Tapd.name())) {
customFields.add(buildTapdUserCustomField(request.getProjectId()));
}
// 根据自定义字段获取表头内容
List<List<String>> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, request);
// 获取导出缺陷列表
@ -1725,9 +1753,9 @@ public class IssuesService {
// 解析issue对象数据->excel对象数据
List<IssueExcelData> excelDataList = parseIssueDataToExcelData(exportIssues);
// 解析excel对象数据->excel列表数据
List<List<Object>> data = parseExcelDataToList(heads, excelDataList);
List<List<Object>> data = parseExcelDataToList(heads, excelDataList, platformStatus);
// 导出EXCEL
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields());
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, customFields);
// heads-> 表头内容, data -> 导出EXCEL列表数据, headHandler -> 表头处理
new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal())
.exportByCustomWriteHandler(response, heads, data, Translator.get("issue_list_export_excel"),
@ -1786,6 +1814,11 @@ public class IssuesService {
List<String> comments = commentDTOList.stream().map(IssueCommentDTO::getDescription).collect(Collectors.toList());
item.setComment(StringUtils.join(comments, ";"));
}
// TAPD平台需展示TAPD处理人
if (IssuesManagePlatform.Tapd.name().equals(item.getPlatform())) {
item.setTapdUsers(getTapdIssueCurrentOwner(item.getId()));
}
});
// 解析自定义字段
buildCustomField(issues, isThirdTemplate, customFields);
@ -1814,7 +1847,7 @@ public class IssuesService {
}
}
private List<List<Object>> parseExcelDataToList(List<List<String>> heads, List<IssueExcelData> excelDataList) {
private List<List<Object>> parseExcelDataToList(List<List<String>> heads, List<IssueExcelData> excelDataList, List<PlatformStatusDTO> platformStatus) {
List<List<Object>> result = new ArrayList<>();
IssueExportHeadField[] exportHeadFields = IssueExportHeadField.values();
//转化excel头
@ -1832,12 +1865,26 @@ public class IssuesService {
boolean isSystemField = false;
for (IssueExportHeadField exportHeadField : exportHeadFields) {
if (StringUtils.equals(head, exportHeadField.getName())) {
rowData.add(exportHeadField.parseExcelDataValue(data));
if (StringUtils.equals(head, IssueExportHeadField.PLATFORM_STATUS.getName())) {
String platformVal = exportHeadField.parseExcelDataValue(data);
Optional<PlatformStatusDTO> first = platformStatus.stream().filter(status -> StringUtils.equals(status.getValue(), platformVal)).findFirst();
if (first.isPresent()) {
rowData.add(first.get().getLabel());
} else {
rowData.add(StringUtils.EMPTY);
}
} else {
rowData.add(exportHeadField.parseExcelDataValue(data));
}
isSystemField = true;
break;
}
}
if (!isSystemField) {
if (StringUtils.equals(head, Translator.get("tapd_user"))) {
rowData.add(Joiner.on(";").join(data.getTapdUsers()));
continue;
}
// 自定义字段
Object value = customData.get(head);
if (value == null || StringUtils.equals(value.toString(), "null")) {
@ -1925,27 +1972,36 @@ public class IssuesService {
}
private String parseOptionValue(String options, String tarVal) {
if (StringUtils.isEmpty(options) || StringUtils.isEmpty(tarVal)) {
if (StringUtils.isEmpty(options) || StringUtils.isEmpty(tarVal) || StringUtils.equalsAny(tarVal, "null", "[]")) {
return StringUtils.EMPTY;
}
List<String> tarVals = new ArrayList<>();
List<String> vals = JSON.parseArray(tarVal, String.class);
List<Map> optionList = JSON.parseArray(options, Map.class);
for (Map option : optionList) {
String text = option.get("text").toString();
String value = option.get("value").toString();
if (StringUtils.containsIgnoreCase(tarVal, value)) {
tarVal = tarVal.replaceAll(value, text);
}
vals.forEach(val -> {
if (StringUtils.equals(val, value)) {
tarVals.add(text);
}
});
}
return tarVal;
return tarVals.toString();
}
public String parseCascadingOptionValue(String cascadingOption, String tarVal) {
List<String> values = new ArrayList<>();
if (StringUtils.isEmpty(cascadingOption)) {
if (StringUtils.isEmpty(cascadingOption) || StringUtils.isEmpty(tarVal) || StringUtils.equalsAny(tarVal, "null", "[]")) {
return StringUtils.EMPTY;
}
JSONArray options = JSONArray.parseArray(cascadingOption);
JSONArray talVals = JSONArray.parseArray(tarVal);
JSONArray talVals = new JSONArray();
if (tarVal.contains("[") || tarVal.contains("]")) {
talVals = JSONArray.parseArray(tarVal);
} else {
talVals = JSONArray.parseArray("[" + tarVal + "]");
}
if (options.size() == 0 || talVals.size() == 0) {
return StringUtils.EMPTY;
}
@ -2086,6 +2142,43 @@ public class IssuesService {
return tapdUsers;
}
public CustomFieldDao buildPlatformStatusCustomField(String workspaceId, String projectId) {
PlatformIssueTypeRequest platformIssueTypeRequest = new PlatformIssueTypeRequest();
platformIssueTypeRequest.setWorkspaceId(workspaceId);
platformIssueTypeRequest.setProjectId(projectId);
List<PlatformStatusDTO> platformStatus = issuesService.getPlatformStatus(platformIssueTypeRequest);
CustomFieldDao customFieldDao = new CustomFieldDao();
customFieldDao.setName(Translator.get("platform_status"));
customFieldDao.setRequired(false);
customFieldDao.setType(CustomFieldType.SELECT.getValue());
customFieldDao.setOptions(JSON.toJSONString(platformStatus));
return customFieldDao;
}
public CustomFieldDao buildTapdUserCustomField(String projectId) {
Project project = projectMapper.selectByPrimaryKey(projectId);
IssuesRequest request = new IssuesRequest();
request.setProjectId(projectId);
request.setWorkspaceId(project.getWorkspaceId());
List<Map<String, String>> tapdUsers = new ArrayList<>();
List<PlatformUser> tapdProjectUsers = getTapdProjectUsers(request);
if (CollectionUtils.isNotEmpty(tapdProjectUsers)) {
tapdProjectUsers.forEach(tapdUser -> {
Map<String, String> user = new HashMap<>();
user.put("text", tapdUser.getUser());
user.put("value", tapdUser.getUser());
tapdUsers.add(user);
});
}
CustomFieldDao customFieldDao = new CustomFieldDao();
customFieldDao.setId(Translator.get("tapd_user"));
customFieldDao.setName(Translator.get("tapd_user"));
customFieldDao.setRequired(false);
customFieldDao.setType(CustomFieldType.MULTIPLE_SELECT.getValue());
customFieldDao.setOptions(JSON.toJSONString(tapdUsers));
return customFieldDao;
}
@MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.ASSOCIATE_ISSUE, content = "#msClass.getIssueLogDetails(#caseId, #refId, #issuesId)", msClass = TestCaseIssueService.class)
public void insertIssueRelateLog(String issuesId, String caseId, String refId, String refType) {
testCaseIssueService.add(issuesId, caseId, refId, refType);

View File

@ -46,8 +46,8 @@ issue_import_template_name=Issue_Template
issue_import_template_sheet=Template
issue_list_export_excel=Issue_Data_Export
issue_list_export_excel_sheet=Data
date_import_cell_format_comment=The date cell format is YYYY/MM/DD (1999/10/01)
datetime_import_cell_format_comment=The date and time cell format is YYYY/MM/DD HH:MM:SS (1999/10/01 10:01:01)
date_import_cell_format_comment=The date cell format is YYYY-MM-DD (1999-10-01)
datetime_import_cell_format_comment=The date and time cell format is YYYY-MM-DD HH:MM:SS (1999-10-01 01:01:01)
int_import_cell_format_comment=cell format: 100001
float_import_cell_format_comment=cell format: 24
multiple_input_import_cell_format_comment=This field has multiple values. Separate multiple values with commas or semicolons
@ -55,17 +55,20 @@ cascading_select_import_cell_format_comment=This cell is a cascade selection. Pl
options_tips=(format{key:value}, please fill in the corresponding value)Option value:
options_key_tips=(format{key:value},please fill in the corresponding key)Option value:
# issue import and issue export
id=Issue ID
title==Title
description=Description
case_count=Case count
comment=Comment
issue_resource=Issue resource
issue_platform=Issue platform
platform_status=Platform status
create_time=CreateTime
can_not_be_null=Can not be null
excel_field_not_exist=Not exist
options_not_exist=Incorrect option value
format_error=Format error
tapd_user=Tapd User
# issue status
new=new
resolved=resolved

View File

@ -23,8 +23,8 @@ issue_import_template_name=缺陷模版
issue_import_template_sheet=模版
issue_list_export_excel=缺陷数据导出
issue_list_export_excel_sheet=数据
date_import_cell_format_comment=日期类型单元格格式为: YYYY/MM/DD (1999/10/01)
datetime_import_cell_format_comment=日期时间类型单元格格式为: YYYY/MM/DD HH:MM:SS (1999/10/01 10:01:01)
date_import_cell_format_comment=日期类型单元格格式为: YYYY-MM-DD (1999-10-01)
datetime_import_cell_format_comment=日期时间类型单元格格式为: YYYY-MM-DD HH:MM:SS (1999-10-01 01:01:01)
int_import_cell_format_comment=整型单元格格式为: 100001
float_import_cell_format_comment=浮点单元格格式为: 24
multiple_input_import_cell_format_comment=该单元格可输入多个值,多个值请用逗号或分号隔开(v1;v2)
@ -32,17 +32,20 @@ cascading_select_import_cell_format_comment=该单元格为级联选择,选择
options_tips=(格式{key:value},请填写对应的value)选项:
options_key_tips=(格式{key:value},请填写对应的key)选项:
# issue import and issue export
id=缺陷ID
title=缺陷标题
description=缺陷描述
case_count=用例数
comment=评论
issue_resource=缺陷来源
issue_platform=缺陷平台
platform_status=平台状态
create_time=创建时间
can_not_be_null=不能为空
excel_field_not_exist=不存在该字段
options_not_exist=选项值有误
format_error=格式有误
tapd_user=Tapd 处理人
# issue status
new=新建
resolved=已解决

View File

@ -23,8 +23,8 @@ issue_import_template_name=缺陷模版
issue_import_template_sheet=模版
issue_list_export_excel=缺陷數據導出
issue_list_export_excel_sheet=數據
date_import_cell_format_comment=日期單元格格式爲: YYYY/MM/DD (1999/10/01)
datetime_import_cell_format_comment=日期時間單元格格式爲: YYYY/MM/DD HH:MM:SS (1999/10/01 10:01:01)
date_import_cell_format_comment=日期單元格格式爲: YYYY-MM-DD (1999-10-01)
datetime_import_cell_format_comment=日期時間單元格格式爲: YYYY-MM-DD HH:MM:SS (1999-10-01 01:01:01)
int_import_cell_format_comment=單元格格式: 100001
float_import_cell_format_comment=單元格格式: 24
multiple_input_import_cell_format_comment=該單元格可輸入多個值,多個值請用逗號或分號隔開(v1;v2)
@ -32,17 +32,20 @@ cascading_select_import_cell_format_comment=該單元格爲級聯選擇,選擇
options_tips=(格式{key:value},請填寫對應value)選項:
options_key_tips=(格式{key:value},請填寫對應key)選項:
# issue import and issue export
id=缺陷ID
title=缺陷標題
description=缺陷描述
case_count=用例數
comment=評論
issue_resource=缺陷來源
issue_platform=缺陷平臺
platform_status=平台狀態
create_time=創建時間
can_not_be_null=不能爲空
excel_field_not_exist=不存在該字段
options_not_exist=選項值有誤
format_error=格式有誤
tapd_user=Tapd 處理人
#issue status
new=新建
resolved=已解決

View File

@ -385,6 +385,6 @@ span.ms-top.el-tag.el-tag--info.el-tag--small.el-tag--light span {
.el-select__tags .el-tag .el-tag__close.el-icon-close::before {
font-size: 24px;
position: relative;
top: 9px;
top: -2px;
}
</style>

View File

@ -50,7 +50,8 @@
<script>
import IssueExportFieldSelectItem from "@/business/issue/components/export/IssueExportFieldSelectItem";
import {getIssuePartTemplateWithProject} from "@/api/issue";
import {enableThirdPartTemplate, getIssuePartTemplateWithProject} from "@/api/issue";
import {getCurrentProjectID} from "@/business/utils/sdk-utils";
export default {
name: "IssueExportFieldSelect",
@ -63,7 +64,7 @@ export default {
{
id: 'id',
key: 'A',
name: 'ID',
name: this.$t("test_track.issue.id"),
enable: true,
disabled: true
},
@ -88,37 +89,37 @@ export default {
id: 'creator',
key: 'E',
name: this.$t("commons.creator"),
enable: true
enable: false
},
{
id: 'caseCount',
key: 'A',
name: this.$t("test_track.home.case_size"),
enable: true
enable: false
},
{
id: 'comment',
key: 'B',
name: this.$t("commons.comment"),
enable: true
enable: false
},
{
id: 'resource',
key: 'C',
name: this.$t("test_track.issue.issue_resource"),
enable: true
enable: false
},
{
id: 'platform',
key: 'D',
name: this.$t("test_track.issue.issue_platform"),
enable: true
enable: false
},
{
id: 'createTime',
key: 'E',
name: this.$t("commons.create_time"),
enable: true
enable: false
},
]
}
@ -131,12 +132,35 @@ export default {
created() {
this.loading = true;
getIssuePartTemplateWithProject((template) => {
if (template.platform && template.platform !== 'Local') {
this.customFields.push({
id: 'platformStatus',
key: '#',
name: this.$t("test_track.issue.platform_status"),
enable: true
});
}
if (template.platform && template.platform === 'Tapd') {
this.customFields.push({
id: this.$t("test_track.issue.tapd_current_owner"),
key: '&',
name: this.$t("test_track.issue.tapd_current_owner"),
enable: true
});
}
template.customFields.forEach(item => {
item.enable = true;
this.customFields.push(item);
});
this.loading = false;
});
enableThirdPartTemplate(getCurrentProjectID())
.then((res) => {
if (res.data) {
//
this.baseFields = this.baseFields.filter(item => (item.id !== 'description' && item.id !== 'title'));
}
});
},
methods: {
getExportParam() {

View File

@ -131,13 +131,13 @@ const TRACK_HEADER = {
{id: 'num', key: '1', label: 'test_track.issue.id'},
{id: 'title', key: '2', label: 'test_track.issue.title'},
{id: 'platformStatus', key: '3', label: 'test_track.issue.platform_status', width: 120},
{id: 'platform', key: '4', label: 'test_track.issue.platform'},
{id: 'platform', key: '4', label: 'test_track.issue.issue_platform'},
{id: 'creatorName', key: '5', label: 'custom_field.issue_creator'},
{id: 'resourceName', key: '6', label: 'test_track.issue.issue_resource'},
{id: 'description', key: '7', label: 'test_track.issue.description'},
{id: 'caseCount', key: '9', label: 'api_test.definition.api_case_number'},
{id: 'createTime', key: '8', label: 'commons.create_time'},
{id: 'updateTime', key: 'a', label: 'commons.update_time'}
{id: 'updateTime', key: '0', label: 'commons.update_time'}
],
//用例评审
TEST_CASE_REVIEW: [