mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-12-02 03:58:33 +08:00
feat(测试跟踪): 缺陷管理支持EXCEL导入导出
--story=1010310 --user=宋昌昌 【测试跟踪】缺陷管理支持通过excel导入导出 https://www.tapd.cn/55049933/s/1295113
This commit is contained in:
parent
ae9b90f18a
commit
d3eb043730
@ -170,6 +170,7 @@ export default {
|
||||
import_update: "Import Update",
|
||||
import_tip1: "The ID is required when the \"Test Case Custom ID\" switch is turned on in the project settings",
|
||||
import_tip2: "ID is required when importing and updating",
|
||||
import_type_require_tips: "Please select the import type!",
|
||||
import_file_tips: "Please upload the file first!",
|
||||
import_refresh_tips: "Content has been updated, please reopen the edit page!",
|
||||
import_format: "Import Format",
|
||||
@ -443,10 +444,11 @@ export default {
|
||||
platform_tip: "Integrated defect management platform in the system setting-workspace-service integration can submit defects to the designated defect management platform",
|
||||
input_title: "Please enter title",
|
||||
id: "Issue ID",
|
||||
title: "Issue Title",
|
||||
description: "Issue Describe",
|
||||
title: "Title",
|
||||
description: "Description",
|
||||
status: "Issue Status",
|
||||
platform: "Platform",
|
||||
issue_platform: "Issue platform",
|
||||
issue_project: "Project",
|
||||
operate: "Operate",
|
||||
close: "Close",
|
||||
@ -474,8 +476,11 @@ export default {
|
||||
sync_bugs: "Synchronization Issue",
|
||||
sync_complete: "Synchronization complete",
|
||||
issue_sync_tip: "The current project is synchronizing defects, please wait!",
|
||||
import_bugs: "Import Issue",
|
||||
export_bugs: "Export Issue",
|
||||
save_before_open_comment: "Please save issue before comment",
|
||||
delete_tip: "Confirm Delete Issue:",
|
||||
batch_delete_tip: "Confirm Batch Delete Issue",
|
||||
check_id_exist: "Check",
|
||||
save_project_first: "Please save the project first",
|
||||
tapd_status_new: "New",
|
||||
@ -486,6 +491,12 @@ export default {
|
||||
tapd_status_closed: "Closed",
|
||||
tapd_status_resolved: "Resolved",
|
||||
please_choose_platform_status: "Please select platform status",
|
||||
import_type: "Import type",
|
||||
import_file_limit_tips: "Only XLS/XLSX files can be uploaded, and the size is not larger than 100 MB",
|
||||
check_select: "Please check issues",
|
||||
export: "Export issue",
|
||||
batch_delete_issue: "Batch delete issues",
|
||||
import_type_tips: "Cover mode :<br>1. If the defect ID already exists, the original defect of the system will be overwritten. <br>2. If the defect ID does not exist or is missing, a new defect is added; <br> Not cover mode :<br>1. If the defect ID already exists, it will not be changed; <br>2. If the defect ID does not exist or is missing, a new defect is added;"
|
||||
},
|
||||
report: {
|
||||
name: "Test Plan Report",
|
||||
|
@ -161,6 +161,7 @@ export default {
|
||||
import_update: "导入更新",
|
||||
import_tip1: "项目设置中“测试用例自定义ID” 开关开启时ID为必填项",
|
||||
import_tip2: "导入更新时ID为必填项",
|
||||
import_type_require_tips: "请选择导入模式!",
|
||||
import_file_tips: "请先上传文件!",
|
||||
import_format: "导入格式",
|
||||
select_import_field: "选择导出字段",
|
||||
@ -451,6 +452,7 @@ export default {
|
||||
status: "缺陷状态",
|
||||
issue_project: "所属项目",
|
||||
platform: "平台",
|
||||
issue_platform: "缺陷平台",
|
||||
operate: "操作",
|
||||
close: "关闭缺陷",
|
||||
delete: "删除缺陷",
|
||||
@ -477,8 +479,11 @@ export default {
|
||||
sync_bugs: "同步缺陷",
|
||||
sync_complete: "同步完成",
|
||||
issue_sync_tip: "当前项目正在同步缺陷, 请稍等!",
|
||||
import_bugs: "导入缺陷",
|
||||
export_bugs: "导出缺陷",
|
||||
save_before_open_comment: "请先保存缺陷再添加评论",
|
||||
delete_tip: "确认删除缺陷:",
|
||||
batch_delete_tip: "确认批量删除缺陷",
|
||||
check_id_exist: "检查",
|
||||
save_project_first: "请先保存项目",
|
||||
tapd_status_new: "新",
|
||||
@ -488,7 +493,13 @@ export default {
|
||||
tapd_status_verified: "已验证",
|
||||
tapd_status_closed: "已关闭",
|
||||
tapd_status_resolved: "已解决",
|
||||
please_choose_platform_status: "请选择平台状态"
|
||||
please_choose_platform_status: "请选择平台状态",
|
||||
import_type: "导入模式",
|
||||
import_file_limit_tips: "只能上传XLS/XLSX文件, 且不超过100M",
|
||||
check_select: "请勾选缺陷",
|
||||
export: "导出缺陷",
|
||||
batch_delete_issue: "批量删除",
|
||||
import_type_tips: "覆盖模式:<br>1.缺陷ID已存在,则覆盖系统原缺陷;<br>2.缺陷ID不存在或为空缺失,则新增缺陷;<br>不覆盖模式:<br>1.缺陷ID已存在,则不作变更;<br>2.缺陷ID不存在或为空缺失,则新增缺陷;"
|
||||
},
|
||||
report: {
|
||||
name: "测试计划报告",
|
||||
|
@ -161,6 +161,7 @@ export default {
|
||||
import_update: "導入更新",
|
||||
import_tip1: "項目設置中“測試用例自定義ID” 開關開啟時ID為必填項",
|
||||
import_tip2: "導入更新時ID為必填項",
|
||||
import_type_require_tips: "請選擇導入模式!",
|
||||
import_file_tips: "請先上傳文件!",
|
||||
import_format: "導入格式",
|
||||
select_import_field: "選擇導出字段",
|
||||
@ -451,6 +452,7 @@ export default {
|
||||
status: "缺陷狀態",
|
||||
issue_project: "所屬項目",
|
||||
platform: "平臺",
|
||||
issue_platform: "缺陷平臺",
|
||||
operate: "操作",
|
||||
close: "關閉缺陷",
|
||||
delete: "刪除缺陷",
|
||||
@ -477,8 +479,11 @@ export default {
|
||||
sync_bugs: "同步缺陷",
|
||||
sync_complete: "同步完成",
|
||||
issue_sync_tip: "當前項目正在同步缺陷, 請稍等!",
|
||||
import_bugs: "導入缺陷",
|
||||
export_bugs: "導出缺陷",
|
||||
save_before_open_comment: "請先保存缺陷再添加評論",
|
||||
delete_tip: "確認刪除缺陷:",
|
||||
batch_delete_tip: "確認批量刪除缺陷",
|
||||
check_id_exist: "檢查",
|
||||
save_project_first: "請先保存項目",
|
||||
tapd_status_new: "新",
|
||||
@ -488,7 +493,13 @@ export default {
|
||||
tapd_status_verified: "已驗證",
|
||||
tapd_status_closed: "已關閉",
|
||||
tapd_status_resolved: "已解決",
|
||||
please_choose_platform_status: "請選擇平臺狀態"
|
||||
please_choose_platform_status: "請選擇平臺狀態",
|
||||
import_type: "導入模式",
|
||||
import_file_limit_tips: "只能上傳XLS/XLSX文件, 且不超過100M",
|
||||
check_select: "請勾選缺陷",
|
||||
export: "導出缺陷",
|
||||
batch_delete_issue: "批量刪除",
|
||||
import_type_tips: "覆蓋模式:<br>1.缺陷ID已存在,則覆蓋系統原缺陷;<br>2.缺陷ID不存在或爲空缺失,則新增缺陷;<br>不覆蓋模式:<br>1.缺陷ID已存在,則不作變更;<br>2.缺陷ID不存在或爲空缺失,則新增缺陷;"
|
||||
},
|
||||
report: {
|
||||
name: "測試計劃報告",
|
||||
|
@ -18,7 +18,8 @@ public enum CustomFieldType {
|
||||
INT("int", false),
|
||||
FLOAT("float", false),
|
||||
MULTIPLE_INPUT("multipleInput", false),
|
||||
RICH_TEXT("richText", false);
|
||||
RICH_TEXT("richText", false),
|
||||
CASCADING_SELECT("cascadingSelect", false);
|
||||
|
||||
private String value;
|
||||
private Boolean hasOption;
|
||||
|
@ -8,5 +8,6 @@ import java.util.List;
|
||||
|
||||
@Data
|
||||
public class IssueTemplateDao extends IssueTemplate {
|
||||
List<CustomFieldDao> customFields;
|
||||
private List<CustomFieldDao> customFields;
|
||||
private Boolean isThirdTemplate;
|
||||
}
|
||||
|
@ -35,4 +35,9 @@ public class IssuesDao extends IssuesWithBLOBs {
|
||||
private String fieldName;
|
||||
private String fieldType;
|
||||
private String fieldValue;
|
||||
|
||||
/**
|
||||
* 导出评论
|
||||
*/
|
||||
private String comment;
|
||||
}
|
||||
|
@ -59,4 +59,9 @@ public class IssuesRequest extends BaseQueryRequest {
|
||||
* 自定义字段ID
|
||||
*/
|
||||
private String customFieldId;
|
||||
|
||||
/**
|
||||
* 缺陷导出勾选ID
|
||||
*/
|
||||
private List<String> exportIds;
|
||||
}
|
||||
|
@ -66,4 +66,8 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs {
|
||||
* 取消关联文件应用ID
|
||||
*/
|
||||
private List<String> unRelateFileMetaIds = new ArrayList<>();
|
||||
|
||||
private List<String> batchDeleteIds;
|
||||
|
||||
private Boolean batchDeleteAll;
|
||||
}
|
||||
|
@ -15,4 +15,11 @@ public interface ExtIssueCommentMapper {
|
||||
*/
|
||||
List<IssueCommentDTO> getComments(@Param("issueId") String issueId);
|
||||
|
||||
/**
|
||||
* 获取多条用例的的评论
|
||||
* @param issueIds
|
||||
* @return
|
||||
*/
|
||||
List<IssueCommentDTO> getCommentsByIssueIds(@Param("issueIds") List<String> issueIds);
|
||||
|
||||
}
|
||||
|
@ -12,4 +12,17 @@
|
||||
order by issue_comment.update_time desc
|
||||
</select>
|
||||
|
||||
<select id="getCommentsByIssueIds" resultType="io.metersphere.dto.IssueCommentDTO"
|
||||
parameterType="java.lang.String">
|
||||
select *, user.name as authorName
|
||||
from issue_comment,
|
||||
user
|
||||
where issue_comment.author = user.id
|
||||
and issue_id in
|
||||
<foreach collection="issueIds" item="id" separator="," open="(" close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
order by issue_comment.update_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
@ -229,7 +229,12 @@
|
||||
#{value}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="!request.selectAll and request.exportIds != null and request.exportIds.size > 0">
|
||||
and issues.id in
|
||||
<foreach collection="request.exportIds" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="request.filters != null and request.filters.size() > 0">
|
||||
<foreach collection="request.filters.entrySet()" index="key" item="values">
|
||||
<if test="values != null and values.size() > 0">
|
||||
|
@ -11,25 +11,29 @@ import io.metersphere.commons.constants.OperLogModule;
|
||||
import io.metersphere.commons.constants.PermissionConstants;
|
||||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.dto.IssuesStatusCountDao;
|
||||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.log.annotation.MsAuditLog;
|
||||
import io.metersphere.notice.annotation.SendNotice;
|
||||
import io.metersphere.xpack.track.dto.*;
|
||||
import io.metersphere.service.issue.domain.jira.JiraIssueType;
|
||||
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
|
||||
|
||||
import io.metersphere.request.issues.IssueExportRequest;
|
||||
import io.metersphere.request.issues.IssueImportRequest;
|
||||
import io.metersphere.request.issues.JiraIssueTypeRequest;
|
||||
import io.metersphere.request.issues.PlatformIssueTypeRequest;
|
||||
import io.metersphere.request.testcase.AuthUserIssueRequest;
|
||||
import io.metersphere.request.testcase.IssuesCountRequest;
|
||||
import io.metersphere.service.BaseCheckPermissionService;
|
||||
import io.metersphere.service.IssuesService;
|
||||
import io.metersphere.service.issue.domain.jira.JiraIssueType;
|
||||
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
|
||||
import io.metersphere.xpack.track.dto.*;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesRequest;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
|
||||
import io.metersphere.service.IssuesService;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@RequestMapping("issues")
|
||||
@ -38,6 +42,8 @@ public class IssuesController {
|
||||
|
||||
@Resource
|
||||
private IssuesService issuesService;
|
||||
@Resource
|
||||
private BaseCheckPermissionService baseCheckPermissionService;
|
||||
|
||||
@PostMapping("/list/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ)
|
||||
@ -115,12 +121,20 @@ public class IssuesController {
|
||||
}
|
||||
|
||||
@GetMapping("/delete/{id}")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ_DELETE)
|
||||
@MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#id)", msClass = IssuesService.class)
|
||||
@SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#targetClass.getIssue(#id)", targetClass = IssuesService.class, event = NoticeConstants.Event.DELETE, subject = "缺陷通知")
|
||||
public void delete(@PathVariable String id) {
|
||||
issuesService.delete(id);
|
||||
}
|
||||
|
||||
@PostMapping("/batchDelete")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ_DELETE)
|
||||
@MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#request)", msClass = IssuesService.class)
|
||||
public void batchDelete(@RequestBody IssuesUpdateRequest request) {
|
||||
issuesService.batchDelete(request);
|
||||
}
|
||||
|
||||
@PostMapping("/tapd/user")
|
||||
public List<PlatformUser> getTapdUsers(@RequestBody IssuesRequest request) {
|
||||
return issuesService.getTapdProjectUsers(request);
|
||||
@ -190,4 +204,23 @@ public class IssuesController {
|
||||
public void checkThirdProjectExist(@RequestBody Project project) {
|
||||
issuesService.checkThirdProjectExist(project);
|
||||
}
|
||||
|
||||
@GetMapping("/import/template/download/{projectId}")
|
||||
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ_CREATE)
|
||||
public void downloadImportTemplate(@PathVariable String projectId, HttpServletResponse response) {
|
||||
issuesService.issueImportTemplate(projectId, response);
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.IMPORT, project = "#request.projectId")
|
||||
public ExcelResponse issueImport(@RequestPart("request") IssueImportRequest request, @RequestPart("file") MultipartFile file) {
|
||||
baseCheckPermissionService.checkProjectOwner(request.getProjectId());
|
||||
return issuesService.issueImport(request, file);
|
||||
}
|
||||
|
||||
@PostMapping("/export")
|
||||
@MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.EXPORT, project = "#exportRequest.projectId")
|
||||
public void exportIssues(@RequestBody IssueExportRequest exportRequest, HttpServletResponse response) {
|
||||
issuesService.issueExport(exportRequest, response);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
package io.metersphere.excel.constants;
|
||||
|
||||
import io.metersphere.commons.utils.DateUtils;
|
||||
import io.metersphere.excel.domain.IssueExcelData;
|
||||
import io.metersphere.i18n.Translator;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author songcc
|
||||
* 导出缺陷HEAD枚举
|
||||
*/
|
||||
|
||||
public enum IssueExportHeadField {
|
||||
|
||||
ID("id", "ID", issueExcelData -> issueExcelData.getNum().toString()), TITLE("title", Translator.get("title"), IssueExcelData::getTitle),
|
||||
CREATOR("creator", Translator.get("create_user"), IssueExcelData::getCreator),
|
||||
DESCRIPTION("description", Translator.get("description"), IssueExcelData::getDescription),
|
||||
CASE_COUNT("caseCount", Translator.get("case_count"), issueExcelData -> String.valueOf(issueExcelData.getCaseCount())),
|
||||
COMMENT("comment", Translator.get("comment"), IssueExcelData::getComment),
|
||||
RESOURCE("resource", Translator.get("issue_resource"), IssueExcelData::getResourceName),
|
||||
PLATFORM("platform", Translator.get("issue_platform"), IssueExcelData::getPlatform),
|
||||
CREATE_TIME("createTime", Translator.get("create_time"), issueExcelData -> issueExcelData.getCreateTime() != null ? DateUtils.getTimeStr(issueExcelData.getCreateTime()) : null);
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private Function<IssueExcelData, String> parseFunc;
|
||||
|
||||
IssueExportHeadField(String id, String name, Function<IssueExcelData, String> parseFunc) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.parseFunc = parseFunc;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String parseExcelDataValue(IssueExcelData excelData) {
|
||||
return parseFunc.apply(excelData);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package io.metersphere.excel.constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public enum IssueImportField {
|
||||
|
||||
/**
|
||||
* TITLE, DESCRIPTION, STATUS, PROCESSOR, PRIORITY
|
||||
*/
|
||||
TITLE("title","缺陷标题", "缺陷標題", "Title"),
|
||||
DESCRIPTION("description","缺陷描述", "缺陷描述", "Description");
|
||||
// STATUS("status","状态", "狀態", "Status", IssueExcelData::getStatus),
|
||||
// PROCESSOR("processor","处理人", "處理人", "Processor", IssueExcelData::getProcessor),
|
||||
// PRIORITY("priority","严重程度", "嚴重程度", "Priority", IssueExcelData::getPriority);
|
||||
|
||||
private Map<Locale, String> fieldLangMap;
|
||||
private String value;
|
||||
|
||||
IssueImportField(String value, String zn, String chineseTw, String us) {
|
||||
this.fieldLangMap = new HashMap<>() {{
|
||||
put(Locale.SIMPLIFIED_CHINESE, zn);
|
||||
put(Locale.TRADITIONAL_CHINESE, chineseTw);
|
||||
put(Locale.US, us);
|
||||
}};
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Map<Locale, String> getFieldLangMap() {
|
||||
return this.fieldLangMap;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean containsHead(String head) {
|
||||
return this.fieldLangMap.containsValue(head);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package io.metersphere.excel.domain;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnore;
|
||||
import io.metersphere.dto.CustomFieldDao;
|
||||
import io.metersphere.excel.constants.IssueExportHeadField;
|
||||
import io.metersphere.excel.constants.IssueImportField;
|
||||
import io.metersphere.request.issues.IssueExportRequest;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
@Data
|
||||
public class IssueExcelData implements Serializable {
|
||||
|
||||
@ExcelIgnore
|
||||
private String id;
|
||||
@ExcelIgnore
|
||||
private Integer num;
|
||||
@ExcelIgnore
|
||||
private String platform;
|
||||
@ExcelIgnore
|
||||
private String creator;
|
||||
@ExcelIgnore
|
||||
private long caseCount;
|
||||
@ExcelIgnore
|
||||
private Long createTime;
|
||||
@ExcelIgnore
|
||||
private Long updateTime;
|
||||
@ExcelIgnore
|
||||
private String reporter;
|
||||
@ExcelIgnore
|
||||
private String projectId;
|
||||
@ExcelIgnore
|
||||
private String platformId;
|
||||
@ExcelIgnore
|
||||
private String platformStatus;
|
||||
@ExcelIgnore
|
||||
private String resourceId;
|
||||
@ExcelIgnore
|
||||
private String resourceName;
|
||||
@ExcelIgnore
|
||||
private String customFields;
|
||||
@ExcelIgnore
|
||||
private String comment;
|
||||
@ExcelIgnore
|
||||
private Boolean addFlag;
|
||||
@ExcelIgnore
|
||||
private String title;
|
||||
@ExcelIgnore
|
||||
private String description;
|
||||
/**
|
||||
* 处理人
|
||||
* 状态
|
||||
* 严重程度
|
||||
*/
|
||||
@ExcelIgnore
|
||||
private String processor;
|
||||
@ExcelIgnore
|
||||
private String status;
|
||||
@ExcelIgnore
|
||||
private String priority;
|
||||
@ExcelIgnore
|
||||
Map<String, Object> customData = new LinkedHashMap<>();
|
||||
|
||||
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request, Locale lang) {
|
||||
List<List<String>> heads = new ArrayList<>();
|
||||
IssueExportHeadField[] exportHeadFields = IssueExportHeadField.values();
|
||||
if (request != null) {
|
||||
List<IssueExportRequest.IssueExportHeader> baseHeaders = request.getExportFields().get("baseHeaders");
|
||||
List<IssueExportRequest.IssueExportHeader> customHeaders = request.getExportFields().get("customHeaders");
|
||||
List<IssueExportRequest.IssueExportHeader> otherHeaders = request.getExportFields().get("otherHeaders");
|
||||
baseHeaders.forEach(baseHeader -> {
|
||||
for (IssueExportHeadField exportHeadField : exportHeadFields) {
|
||||
if (StringUtils.equals(baseHeader.getId(), exportHeadField.getId())) {
|
||||
heads.add(List.of(exportHeadField.getName()));
|
||||
}
|
||||
}
|
||||
});
|
||||
customHeaders.forEach(customHeader -> {
|
||||
heads.add(List.of(customHeader.getName()));
|
||||
});
|
||||
otherHeaders.forEach(otherHeader -> {
|
||||
for (IssueExportHeadField exportHeadField : exportHeadFields) {
|
||||
if (StringUtils.equals(otherHeader.getId(), exportHeadField.getId())) {
|
||||
heads.add(List.of(exportHeadField.getName()));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (!isThirdTemplate) {
|
||||
IssueImportField[] fields = IssueImportField.values();
|
||||
for (IssueImportField field : fields) {
|
||||
heads.add(List.of(field.getFieldLangMap().get(lang)));
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(customFields)) {
|
||||
for (CustomFieldDao dto : customFields) {
|
||||
heads.add(new ArrayList<>() {{
|
||||
add(dto.getName());
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
||||
return heads;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.metersphere.excel.domain;
|
||||
|
||||
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 lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Data
|
||||
public class IssueExcelDataCn extends IssueExcelData{
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ColumnWidth(50)
|
||||
@ExcelProperty("缺陷标题")
|
||||
private String title;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 1000)
|
||||
@ColumnWidth(50)
|
||||
@ExcelProperty("缺陷描述")
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request) {
|
||||
return super.getHead(isThirdTemplate, customFields, request, Locale.SIMPLIFIED_CHINESE);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.metersphere.excel.domain;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class IssueExcelDataFactory implements ExcelDataFactory{
|
||||
|
||||
@Override
|
||||
public Class getExcelDataByLocal() {
|
||||
Locale locale = LocaleContextHolder.getLocale();
|
||||
if (StringUtils.equals(locale.toString(), Locale.US.toString())) {
|
||||
return IssueExcelDataUs.class;
|
||||
} else if (StringUtils.equals(locale.toString(), Locale.TRADITIONAL_CHINESE.toString())) {
|
||||
return IssueExcelDataTw.class;
|
||||
}
|
||||
return IssueExcelDataCn.class;
|
||||
}
|
||||
|
||||
public IssueExcelData getIssueExcelDataLocal(){
|
||||
Locale locale = LocaleContextHolder.getLocale();
|
||||
if (StringUtils.equals(locale.toString(), Locale.US.toString())) {
|
||||
return new IssueExcelDataUs();
|
||||
} else if (StringUtils.equals(locale.toString(), Locale.TRADITIONAL_CHINESE.toString())) {
|
||||
return new IssueExcelDataTw();
|
||||
}
|
||||
return new IssueExcelDataCn();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.metersphere.excel.domain;
|
||||
|
||||
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 lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Data
|
||||
public class IssueExcelDataTw extends IssueExcelData{
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ColumnWidth(100)
|
||||
@ExcelProperty("缺陷標題")
|
||||
private String title;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 1000)
|
||||
@ColumnWidth(100)
|
||||
@ExcelProperty("缺陷内容")
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request) {
|
||||
return super.getHead(isThirdTemplate, customFields, request, Locale.TRADITIONAL_CHINESE);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package io.metersphere.excel.domain;
|
||||
|
||||
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 lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Data
|
||||
public class IssueExcelDataUs extends IssueExcelData{
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ColumnWidth(100)
|
||||
@ExcelProperty("Title")
|
||||
private String title;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 1000)
|
||||
@ColumnWidth(100)
|
||||
@ExcelProperty("Description")
|
||||
private String description;
|
||||
|
||||
|
||||
@Override
|
||||
public List<List<String>> getHead(Boolean isThirdTemplate, List<CustomFieldDao> customFields, IssueExportRequest request) {
|
||||
return super.getHead(isThirdTemplate, customFields, request, Locale.US);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.metersphere.excel.handler;
|
||||
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.DataValidation;
|
||||
import org.apache.poi.ss.usermodel.DataValidationConstraint;
|
||||
import org.apache.poi.ss.usermodel.DataValidationHelper;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class IssueTemplateCellWriteHandler implements SheetWriteHandler {
|
||||
|
||||
private static final Integer ROW_SIZE = 200;
|
||||
|
||||
/**
|
||||
* 下拉框集合
|
||||
*/
|
||||
private Map<Integer, String[]> dropDownIndexMap;
|
||||
|
||||
public IssueTemplateCellWriteHandler(Map<Integer, String[]> dropDownIndexMap) {
|
||||
this.dropDownIndexMap = dropDownIndexMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
||||
Sheet sheet = writeSheetHolder.getSheet();
|
||||
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||
dropDownIndexMap.forEach((celIndex, options) -> {
|
||||
// 区间设置
|
||||
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, ROW_SIZE, celIndex, celIndex);
|
||||
// 下拉内容
|
||||
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
|
||||
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
|
||||
sheet.addValidationData(dataValidation);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
package io.metersphere.excel.handler;
|
||||
|
||||
import com.alibaba.excel.util.BooleanUtils;
|
||||
import com.alibaba.excel.write.handler.RowWriteHandler;
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
|
||||
import io.metersphere.commons.constants.CustomFieldScene;
|
||||
import io.metersphere.commons.constants.CustomFieldType;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.dto.CustomFieldDao;
|
||||
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.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 {
|
||||
|
||||
private Sheet sheet;
|
||||
private Drawing<?> drawingPatriarch;
|
||||
private Map<String, String> memberMap;
|
||||
private Map<Integer, String> headCommentIndexMap = new HashMap<>();
|
||||
|
||||
public IssueTemplateHeadWriteHandler(Map<String, String> memberMap, List<List<String>> headList, List<CustomFieldDao> customFields) {
|
||||
this.memberMap = memberMap;
|
||||
this.initCustomFieldIndexMap(headList, customFields);
|
||||
}
|
||||
|
||||
private void initCustomFieldIndexMap(List<List<String>> headList, List<CustomFieldDao> customFields) {
|
||||
int index = 0;
|
||||
for (List<String> list : headList) {
|
||||
for (String head : list) {
|
||||
CustomFieldDao customFieldDao;
|
||||
if (StringUtils.equalsAnyIgnoreCase(head, IssueExportHeadField.TITLE.getName(), IssueExportHeadField.DESCRIPTION.getName())) {
|
||||
customFieldDao = new CustomFieldDao();
|
||||
customFieldDao.setRequired(Boolean.TRUE);
|
||||
customFieldDao.setType(CustomFieldType.INPUT.getValue());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(head, IssueExportHeadField.ID.getName())) {
|
||||
customFieldDao = new CustomFieldDao();
|
||||
customFieldDao.setRequired(Boolean.FALSE);
|
||||
customFieldDao.setType(CustomFieldType.INT.getValue());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(head, IssueExportHeadField.CREATOR.getName())) {
|
||||
customFieldDao = new CustomFieldDao();
|
||||
customFieldDao.setRequired(Boolean.FALSE);
|
||||
customFieldDao.setType(CustomFieldType.MEMBER.getValue());
|
||||
} else {
|
||||
// 自定义字段
|
||||
List<CustomFieldDao> fields = customFields.stream().filter(field -> StringUtils.equals(field.getName(), head)).collect(Collectors.toList());
|
||||
if (fields.size() > 0) {
|
||||
customFieldDao = fields.get(0);
|
||||
} else {
|
||||
customFieldDao = null;
|
||||
}
|
||||
}
|
||||
headCommentIndexMap.put(index, customFieldDao == null ? StringUtils.EMPTY : getCommentByCustomField(customFieldDao));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRowDispose(RowWriteHandlerContext context) {
|
||||
if (BooleanUtils.isTrue(context.getHead())) {
|
||||
sheet = context.getWriteSheetHolder().getSheet();
|
||||
drawingPatriarch = sheet.createDrawingPatriarch();
|
||||
headCommentIndexMap.forEach(this::setComment);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCommentByCustomField(CustomFieldDao field) {
|
||||
String commentText = "";
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(),
|
||||
CustomFieldType.SELECT.getValue(), CustomFieldType.RADIO.getValue())) {
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getScene(), CustomFieldScene.ISSUE.name()) &&
|
||||
StringUtils.equalsAnyIgnoreCase(field.getName(), "状态", "严重程度")) {
|
||||
commentText = Translator.get("options").concat(JSON.toJSONString(getOptionValues(field)));
|
||||
} else {
|
||||
commentText = Translator.get("options_tips").concat(JSON.toJSONString(getOptionsText(field.getOptions())));
|
||||
}
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.MEMBER.getValue())) {
|
||||
commentText = Translator.get("options").concat(memberMap.keySet().toString());
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.DATE.getValue())) {
|
||||
commentText = Translator.get("date_import_cell_format_comment");
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.DATETIME.getValue())) {
|
||||
commentText = Translator.get("datetime_import_cell_format_comment");
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.INT.getValue())) {
|
||||
commentText = Translator.get("int_import_cell_format_comment");
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.FLOAT.getValue())) {
|
||||
commentText = Translator.get("float_import_cell_format_comment");
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.MULTIPLE_INPUT.getValue())) {
|
||||
commentText = Translator.get("multiple_input_import_cell_format_comment");
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(),
|
||||
CustomFieldType.MULTIPLE_SELECT.getValue(), CustomFieldType.CHECKBOX.getValue())) {
|
||||
commentText = Translator.get("multiple_input_import_cell_format_comment").concat(", " +
|
||||
Translator.get("options_tips").concat(JSON.toJSONString(getOptionsText(field.getOptions()))));
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.MULTIPLE_MEMBER.getValue())) {
|
||||
commentText = Translator.get("multiple_input_import_cell_format_comment").concat(", " +
|
||||
Translator.get("options").concat(memberMap.keySet().toString()));
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(field.getType(), CustomFieldType.CASCADING_SELECT.getValue())) {
|
||||
commentText = Translator.get("multiple_input_import_cell_format_comment").concat(", " +
|
||||
Translator.get("options_tips").concat(JSON.toJSONString(getCascadSelect(field.getOptions()))));
|
||||
}
|
||||
return field.getRequired() ? Translator.get("required").concat("; " + commentText) : commentText;
|
||||
}
|
||||
|
||||
private void setComment(Integer index, String text) {
|
||||
if (index == null || StringUtils.isEmpty(text)) {
|
||||
return;
|
||||
}
|
||||
Comment comment = drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, index, 0, index + 3, 1));
|
||||
comment.setString(new XSSFRichTextString(text));
|
||||
sheet.getRow(0).getCell(1).setCellComment(comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<String> getOptionValues(CustomFieldDao field) {
|
||||
List<CustomFieldOptionDTO> options = JSON.parseArray(field.getOptions(), CustomFieldOptionDTO.class);
|
||||
List<String> values = new ArrayList<>();
|
||||
if (options != null) {
|
||||
for (CustomFieldOptionDTO option : options) {
|
||||
values.add(option.getValue());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<String> getOptionsText(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 -> {
|
||||
StringBuffer option = new StringBuffer("{" + optionMap.get("text") + ":" + optionMap.get("value") + "}");
|
||||
options.add(option.toString());
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private List<String> getCascadSelect(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 -> {
|
||||
StringBuffer option = new StringBuffer();
|
||||
if (optionMap.get("children") != null) {
|
||||
String children = getCascadSelect(JSON.toJSONString(optionMap.get("children"))).toString();
|
||||
option.append("{").append(optionMap.get("text"))
|
||||
.append(":").append(optionMap.get("value")).append(":").append(children).append("}");
|
||||
} else {
|
||||
option.append("{").append(optionMap.get("text"))
|
||||
.append(":").append(optionMap.get("value")).append("}");
|
||||
}
|
||||
options.add(option.toString());
|
||||
});
|
||||
return options;
|
||||
}
|
||||
}
|
@ -0,0 +1,356 @@
|
||||
package io.metersphere.excel.listener;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
import com.alibaba.excel.util.DateUtils;
|
||||
import io.metersphere.base.domain.Issues;
|
||||
import io.metersphere.commons.constants.CustomFieldType;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.dto.CustomFieldDao;
|
||||
import io.metersphere.dto.CustomFieldItemDTO;
|
||||
import io.metersphere.dto.CustomFieldResourceDTO;
|
||||
import io.metersphere.excel.constants.IssueExportHeadField;
|
||||
import io.metersphere.excel.domain.ExcelErrData;
|
||||
import io.metersphere.excel.domain.IssueExcelData;
|
||||
import io.metersphere.excel.domain.IssueExcelDataFactory;
|
||||
import io.metersphere.excel.utils.ExcelImportType;
|
||||
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.request.IssuesUpdateRequest;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 缺陷导入读取
|
||||
* @author songcc
|
||||
*/
|
||||
|
||||
public class IssueExcelListener extends AnalysisEventListener<Map<Integer, String>> {
|
||||
|
||||
private Class dataClass;
|
||||
private IssueImportRequest request;
|
||||
private Boolean isThirdPlatform = false;
|
||||
private Map<Integer, String> headMap;
|
||||
private List<CustomFieldDao> customFields = new ArrayList<>();
|
||||
private IssuesService issuesService;
|
||||
/**
|
||||
* excel表头字段字典值
|
||||
*/
|
||||
private Map<String, String> headFieldTransferDic = new HashMap<>();
|
||||
private Map<String, List<CustomFieldResourceDTO>> issueCustomFieldMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 每超过2000条数据, 则插入数据库
|
||||
*/
|
||||
protected static final int BATCH_THRESHOLD = 2000;
|
||||
|
||||
/**
|
||||
* insertList: 新增缺陷集合
|
||||
* updateList: 覆盖缺陷集合
|
||||
* 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) {
|
||||
this.request = request;
|
||||
this.dataClass = clazz;
|
||||
this.isThirdPlatform = isThirdPlatform;
|
||||
this.customFields = customFields;
|
||||
this.issuesService = CommonBeanFactory.getBean(IssuesService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Map<Integer, String> data, AnalysisContext analysisContext) {
|
||||
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
|
||||
IssueExcelData issueExcelData = null;
|
||||
StringBuilder errMsg;
|
||||
try {
|
||||
issueExcelData = this.parseDataToModel(data);
|
||||
// EXCEL校验, 如果不是第三方模板则需要校验
|
||||
errMsg = new StringBuilder(!isThirdPlatform ? ExcelValidateHelper.validateEntity(issueExcelData) : StringUtils.EMPTY);
|
||||
//自定义校验规则
|
||||
if (StringUtils.isEmpty(errMsg)) {
|
||||
validate(issueExcelData, errMsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errMsg = new StringBuilder(Translator.get("parse_data_error"));
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(errMsg)) {
|
||||
ExcelErrData excelErrData = new ExcelErrData(issueExcelData, rowIndex,
|
||||
Translator.get("number")
|
||||
.concat(StringUtils.SPACE)
|
||||
.concat(String.valueOf(rowIndex + 1)).concat(StringUtils.SPACE)
|
||||
.concat(Translator.get("row"))
|
||||
.concat(Translator.get("error"))
|
||||
.concat(":")
|
||||
.concat(errMsg.toString()));
|
||||
errList.add(excelErrData);
|
||||
} else {
|
||||
if (issueExcelData.getNum() == null) {
|
||||
// ID为空或不存在, 新增
|
||||
issueExcelData.setAddFlag(Boolean.TRUE);
|
||||
insertList.add(issueExcelData);
|
||||
} else {
|
||||
Issues issues = checkIssueExist(issueExcelData.getNum(), request.getProjectId());
|
||||
if (issues == null) {
|
||||
// ID列值不存在, 则新增
|
||||
issueExcelData.setAddFlag(Boolean.TRUE);
|
||||
insertList.add(issueExcelData);
|
||||
} else {
|
||||
// ID存在
|
||||
if (StringUtils.equals(request.getImportType(), ExcelImportType.Update.name())) {
|
||||
// 覆盖模式
|
||||
issueExcelData.setId(issues.getId());
|
||||
issueExcelData.setAddFlag(Boolean.FALSE);
|
||||
updateList.add(issueExcelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (insertList.size() > BATCH_THRESHOLD || updateList.size() > BATCH_THRESHOLD) {
|
||||
saveData();
|
||||
insertList.clear();
|
||||
updateList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveData() {
|
||||
//excel中用例都有错误时就返回,只要有用例可用于更新或者插入就不返回
|
||||
if (!errList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(insertList) && CollectionUtils.isEmpty(updateList)) {
|
||||
MSException.throwException(Translator.get("no_legitimate_issue_tip"));
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(insertList)) {
|
||||
List<IssuesUpdateRequest> issues = insertList.stream().map(item -> this.convertToIssue(item)).collect(Collectors.toList());
|
||||
issuesService.saveImportData(issues);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(updateList)) {
|
||||
List<IssuesUpdateRequest> issues = updateList.stream().map(item -> this.convertToIssue(item)).collect(Collectors.toList());
|
||||
issuesService.updateImportData(issues);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
|
||||
this.headMap = headMap;
|
||||
this.genExcelHeadFieldTransferDic();
|
||||
this.formatHeadMap();
|
||||
super.invokeHeadMap(headMap, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
|
||||
saveData();
|
||||
insertList.clear();
|
||||
updateList.clear();
|
||||
issueCustomFieldMap.clear();
|
||||
}
|
||||
|
||||
private void formatHeadMap() {
|
||||
for (Integer key : headMap.keySet()) {
|
||||
String name = headMap.get(key);
|
||||
if (headFieldTransferDic.containsKey(name)) {
|
||||
headMap.put(key, headFieldTransferDic.get(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validate(IssueExcelData data, StringBuilder errMsg) {
|
||||
// TODO 校验自定义字段的数据是否合法
|
||||
}
|
||||
|
||||
private IssueExcelData parseDataToModel(Map<Integer, String> rowData) {
|
||||
IssueExcelData data = new IssueExcelDataFactory().getIssueExcelDataLocal();
|
||||
for (Map.Entry<Integer, String> headEntry : headMap.entrySet()) {
|
||||
Integer index = headEntry.getKey();
|
||||
String field = headEntry.getValue();
|
||||
if (StringUtils.isBlank(field)) {
|
||||
continue;
|
||||
}
|
||||
String value = StringUtils.isEmpty(rowData.get(index)) ? StringUtils.EMPTY : rowData.get(index);
|
||||
|
||||
if (StringUtils.equalsIgnoreCase(field, IssueExportHeadField.ID.getName())) {
|
||||
data.setNum(StringUtils.isEmpty(value) ? null : Integer.parseInt(value));
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(field, IssueExportHeadField.TITLE.getId())) {
|
||||
data.setTitle(value);
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(field, IssueExportHeadField.DESCRIPTION.getId())) {
|
||||
data.setDescription(value);
|
||||
} else {
|
||||
// 自定义字段
|
||||
if (StringUtils.isNotEmpty(value) && (value.contains(","))) {
|
||||
// 逗号分隔
|
||||
List<String> dataList = Arrays.asList(org.springframework.util.StringUtils.trimAllWhitespace(value).split(","));
|
||||
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> formatDataList = dataList.stream().map(item -> "\"" + item + "\"").collect(Collectors.toList());
|
||||
data.getCustomData().put(field, formatDataList);
|
||||
} else {
|
||||
data.getCustomData().put(field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private IssuesUpdateRequest convertToIssue(IssueExcelData issueExcelData) {
|
||||
IssuesUpdateRequest issuesUpdateRequest = new IssuesUpdateRequest();
|
||||
issuesUpdateRequest.setWorkspaceId(request.getWorkspaceId());
|
||||
issuesUpdateRequest.setProjectId(request.getProjectId());
|
||||
issuesUpdateRequest.setThirdPartPlatform(isThirdPlatform);
|
||||
issuesUpdateRequest.setDescription(issueExcelData.getDescription());
|
||||
issuesUpdateRequest.setTitle(issueExcelData.getTitle());
|
||||
if (BooleanUtils.isTrue(issueExcelData.getAddFlag())) {
|
||||
issuesUpdateRequest.setCreator(SessionUtils.getUserId());
|
||||
} else {
|
||||
issuesUpdateRequest.setPlatformId(getPlatformId(issueExcelData.getId()));
|
||||
issuesUpdateRequest.setId(issueExcelData.getId());
|
||||
}
|
||||
buildFields(issueExcelData, issuesUpdateRequest);
|
||||
return issuesUpdateRequest;
|
||||
}
|
||||
|
||||
public List<ExcelErrData<IssueExcelData>> getErrList() {
|
||||
return this.errList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解ExcelProperty的value和对应field
|
||||
*/
|
||||
public void genExcelHeadFieldTransferDic() {
|
||||
Field[] fields = dataClass.getDeclaredFields();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
Field field = fields[i];
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
if (excelProperty != null) {
|
||||
StringBuilder value = new StringBuilder();
|
||||
for (String v : excelProperty.value()) {
|
||||
value.append(v);
|
||||
}
|
||||
headFieldTransferDic.put(value.toString(), field.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Issues checkIssueExist(Integer num, String projectId) {
|
||||
return issuesService.checkIssueExist(num, projectId);
|
||||
}
|
||||
|
||||
private void buildFields(IssueExcelData issueExcelData, IssuesUpdateRequest issuesUpdateRequest) {
|
||||
if (MapUtils.isEmpty(issueExcelData.getCustomData())) {
|
||||
return;
|
||||
}
|
||||
Boolean addFlag = issueExcelData.getAddFlag();
|
||||
List<CustomFieldResourceDTO> addFields = new ArrayList<>();
|
||||
List<CustomFieldResourceDTO> editFields = new ArrayList<>();
|
||||
List<CustomFieldItemDTO> requestFields = new ArrayList<>();
|
||||
Map<String, List<CustomFieldDao>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(CustomFieldDao::getName));
|
||||
issueExcelData.getCustomData().forEach((k, v) -> {
|
||||
try {
|
||||
List<CustomFieldDao> customFieldDaos = customFieldMap.get(k);
|
||||
if (CollectionUtils.isNotEmpty(customFieldDaos) && customFieldDaos.size() > 0) {
|
||||
CustomFieldDao customFieldDao = customFieldDaos.get(0);
|
||||
String type = customFieldDao.getType();
|
||||
// addfield
|
||||
CustomFieldResourceDTO customFieldResourceDTO = new CustomFieldResourceDTO();
|
||||
customFieldResourceDTO.setFieldId(customFieldDao.getId());
|
||||
// requestfield
|
||||
CustomFieldItemDTO customFieldItemDTO = new CustomFieldItemDTO();
|
||||
BeanUtils.copyBean(customFieldItemDTO, customFieldDao);
|
||||
if (StringUtils.isEmpty(v.toString())) {
|
||||
if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.MULTIPLE_MEMBER.getValue(),
|
||||
CustomFieldType.MULTIPLE_SELECT.getValue(), CustomFieldType.CHECKBOX.getValue(),
|
||||
CustomFieldType.CASCADING_SELECT.getValue())) {
|
||||
customFieldResourceDTO.setValue("[]");
|
||||
customFieldItemDTO.setValue("[]");
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.MULTIPLE_INPUT.getValue())) {
|
||||
customFieldResourceDTO.setValue("[]");
|
||||
customFieldItemDTO.setValue(Collections.emptyList());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.RADIO.getValue(),
|
||||
CustomFieldType.RICH_TEXT.getValue(), CustomFieldType.SELECT.getValue(),
|
||||
CustomFieldType.FLOAT.getValue(), CustomFieldType.DATE.getValue(),
|
||||
CustomFieldType.DATETIME.getValue(), CustomFieldType.INPUT.getValue())) {
|
||||
customFieldResourceDTO.setValue(StringUtils.EMPTY);
|
||||
customFieldItemDTO.setValue(StringUtils.EMPTY);
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.TEXTAREA.getValue())) {
|
||||
customFieldItemDTO.setValue(StringUtils.EMPTY);
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.equalsAnyIgnoreCase(type,
|
||||
CustomFieldType.RICH_TEXT.getValue(), CustomFieldType.TEXTAREA.getValue())) {
|
||||
customFieldResourceDTO.setTextValue(v.toString());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.FLOAT.getValue())) {
|
||||
customFieldResourceDTO.setValue(v.toString());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.MULTIPLE_SELECT.getValue(),
|
||||
CustomFieldType.CHECKBOX.getValue(), CustomFieldType.MULTIPLE_INPUT.getValue(),
|
||||
CustomFieldType.MULTIPLE_MEMBER.getValue(), CustomFieldType.CASCADING_SELECT.getValue())) {
|
||||
if (!v.toString().contains("[")) {
|
||||
v = List.of("\"" + v.toString() + "\"");
|
||||
}
|
||||
customFieldResourceDTO.setValue(v.toString());
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.DATE.getValue())) {
|
||||
Date vdate = DateUtils.parseDate(v.toString(), "yyyy/MM/dd");
|
||||
v = DateUtils.format(vdate, "yyyy-MM-dd");
|
||||
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");
|
||||
customFieldResourceDTO.setValue("\"" + v + "\"");
|
||||
} else {
|
||||
customFieldResourceDTO.setValue("\"" + v + "\"");
|
||||
}
|
||||
}
|
||||
if (addFlag) {
|
||||
addFields.add(customFieldResourceDTO);
|
||||
} else {
|
||||
editFields.add(customFieldResourceDTO);
|
||||
}
|
||||
if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.MULTIPLE_INPUT.getValue())) {
|
||||
customFieldItemDTO.setValue(v);
|
||||
} else if (StringUtils.equalsAnyIgnoreCase(type, CustomFieldType.FLOAT.getValue())) {
|
||||
customFieldItemDTO.setValue(StringUtils.isNotEmpty(v.toString()) ? Float.parseFloat(v.toString()) : StringUtils.EMPTY);
|
||||
} else {
|
||||
customFieldItemDTO.setValue(v.toString());
|
||||
}
|
||||
requestFields.add(customFieldItemDTO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
MSException.throwException(e.getMessage());
|
||||
}
|
||||
});
|
||||
if (addFlag) {
|
||||
issuesUpdateRequest.setAddFields(addFields);
|
||||
} else {
|
||||
issuesUpdateRequest.setEditFields(editFields);
|
||||
}
|
||||
issuesUpdateRequest.setRequestFields(requestFields);
|
||||
}
|
||||
|
||||
private String getPlatformId(String issueId) {
|
||||
return issuesService.getIssue(issueId).getPlatformId();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.metersphere.request.issues;
|
||||
|
||||
import io.metersphere.request.OrderRequest;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author songcc
|
||||
* 缺陷导出参数
|
||||
*/
|
||||
@Data
|
||||
public class IssueExportRequest {
|
||||
private String projectId;
|
||||
private String workspaceId;
|
||||
private String userId;
|
||||
private Boolean isSelectAll;
|
||||
private List<String> exportIds;
|
||||
private List<OrderRequest> orders;
|
||||
private Map<String, List<IssueExportHeader>> exportFields;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class IssueExportHeader {
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.metersphere.request.issues;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class IssueImportRequest {
|
||||
private String projectId;
|
||||
private String workspaceId;
|
||||
private String userId;
|
||||
private String importType;
|
||||
}
|
@ -1,48 +1,56 @@
|
||||
package io.metersphere.service;
|
||||
|
||||
import com.alibaba.excel.EasyExcelFactory;
|
||||
import com.alibaba.excel.util.DateUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtIssueCommentMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtIssuesMapper;
|
||||
import io.metersphere.commons.constants.FileAssociationType;
|
||||
import io.metersphere.commons.constants.IssueRefType;
|
||||
import io.metersphere.commons.constants.IssuesManagePlatform;
|
||||
import io.metersphere.commons.constants.IssuesStatus;
|
||||
import io.metersphere.commons.constants.*;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.*;
|
||||
import io.metersphere.plan.service.TestPlanTestCaseService;
|
||||
import io.metersphere.utils.DistinctKeyUtil;
|
||||
import io.metersphere.xpack.track.dto.AttachmentSyncType;
|
||||
import io.metersphere.xpack.track.dto.*;
|
||||
import io.metersphere.constants.AttachmentType;
|
||||
import io.metersphere.constants.SystemCustomField;
|
||||
import io.metersphere.dto.CustomFieldDao;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.excel.constants.IssueExportHeadField;
|
||||
import io.metersphere.excel.domain.ExcelErrData;
|
||||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.excel.domain.IssueExcelData;
|
||||
import io.metersphere.excel.domain.IssueExcelDataFactory;
|
||||
import io.metersphere.excel.handler.IssueTemplateHeadWriteHandler;
|
||||
import io.metersphere.excel.listener.IssueExcelListener;
|
||||
import io.metersphere.excel.utils.EasyExcelExporter;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.log.utils.ReflexObjectUtil;
|
||||
import io.metersphere.log.vo.DetailColumn;
|
||||
import io.metersphere.log.vo.OperatingLogDetails;
|
||||
import io.metersphere.log.vo.track.TestPlanReference;
|
||||
import io.metersphere.dto.*;
|
||||
import io.metersphere.plan.dto.PlanReportIssueDTO;
|
||||
import io.metersphere.plan.dto.TestCaseReportStatusResultDTO;
|
||||
import io.metersphere.plan.dto.TestPlanSimpleReportDTO;
|
||||
import io.metersphere.plan.service.TestPlanService;
|
||||
import io.metersphere.plan.service.TestPlanTestCaseService;
|
||||
import io.metersphere.plan.utils.TestPlanStatusCalculator;
|
||||
import io.metersphere.request.IntegrationRequest;
|
||||
import io.metersphere.request.attachment.AttachmentRequest;
|
||||
import io.metersphere.request.issues.IssueExportRequest;
|
||||
import io.metersphere.request.issues.IssueImportRequest;
|
||||
import io.metersphere.request.issues.JiraIssueTypeRequest;
|
||||
import io.metersphere.request.issues.PlatformIssueTypeRequest;
|
||||
import io.metersphere.request.testcase.AuthUserIssueRequest;
|
||||
import io.metersphere.request.testcase.IssuesCountRequest;
|
||||
import io.metersphere.service.issue.domain.jira.JiraIssueType;
|
||||
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
|
||||
import io.metersphere.request.attachment.AttachmentRequest;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesRequest;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
|
||||
import io.metersphere.service.issue.platform.*;
|
||||
import io.metersphere.service.remote.project.TrackCustomFieldTemplateService;
|
||||
import io.metersphere.service.remote.project.TrackIssueTemplateService;
|
||||
import io.metersphere.service.wapper.TrackProjectService;
|
||||
import io.metersphere.utils.DistinctKeyUtil;
|
||||
import io.metersphere.xpack.track.dto.*;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesRequest;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
|
||||
import io.metersphere.xpack.track.issue.IssuesPlatform;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
@ -59,7 +67,9 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -74,6 +84,8 @@ public class IssuesService {
|
||||
@Resource
|
||||
private TrackProjectService trackProjectService;
|
||||
@Resource
|
||||
private BaseUserService baseUserService;
|
||||
@Resource
|
||||
private BaseProjectService baseProjectService;
|
||||
@Resource
|
||||
private TestPlanService testPlanService;
|
||||
@ -117,6 +129,8 @@ public class IssuesService {
|
||||
SqlSessionFactory sqlSessionFactory;
|
||||
@Resource
|
||||
private FileMetadataMapper fileMetadataMapper;
|
||||
@Resource
|
||||
private ExtIssueCommentMapper extIssueCommentMapper;
|
||||
|
||||
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
|
||||
|
||||
@ -474,6 +488,24 @@ public class IssuesService {
|
||||
attachmentService.deleteAttachment(request);
|
||||
}
|
||||
|
||||
public void batchDelete(IssuesUpdateRequest request) {
|
||||
if (request.getBatchDeleteAll()) {
|
||||
IssuesRequest issuesRequest = new IssuesRequest();
|
||||
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
|
||||
List<IssuesDao> issuesDaos = listByWorkspaceId(issuesRequest);
|
||||
if (CollectionUtils.isNotEmpty(issuesDaos)) {
|
||||
issuesDaos.forEach(issuesDao -> {
|
||||
delete(issuesDao.getId());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (CollectionUtils.isNotEmpty(request.getBatchDeleteIds())) {
|
||||
request.getBatchDeleteIds().forEach(id -> delete(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ZentaoBuild> getZentaoBuilds(IssuesRequest request) {
|
||||
try {
|
||||
ZentaoPlatform platform = (ZentaoPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), request);
|
||||
@ -491,7 +523,7 @@ public class IssuesService {
|
||||
request.getOrders().forEach(order -> {
|
||||
if (StringUtils.isNotEmpty(order.getName()) && order.getName().startsWith("custom")) {
|
||||
request.setIsCustomSorted(true);
|
||||
request.setCustomFieldId(order.getName().substring(order.getName().indexOf("-") + 1));
|
||||
request.setCustomFieldId(order.getName().replace("custom_", StringUtils.EMPTY));
|
||||
order.setPrefix("cfi");
|
||||
order.setName("value");
|
||||
}
|
||||
@ -550,6 +582,62 @@ public class IssuesService {
|
||||
data.setFields(fields);
|
||||
}
|
||||
|
||||
private void buildCustomField(List<IssuesDao> data, Boolean isThirdTemplate, List<CustomFieldDao> customFields) {
|
||||
if (CollectionUtils.isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, List<CustomFieldDao>> fieldMap =
|
||||
customFieldIssuesService.getMapByResourceIds(data.stream().map(IssuesDao::getId).collect(Collectors.toList()));
|
||||
try {
|
||||
Map<String, CustomField> fieldMaps = new HashMap<>();
|
||||
if (isThirdTemplate) {
|
||||
fieldMaps = customFields.stream().collect(Collectors.toMap(CustomFieldDao::getId, field -> (CustomField) field));
|
||||
} else {
|
||||
List<CustomFieldDao> customfields = fieldMap.get(data.get(0).getId());
|
||||
if (CollectionUtils.isNotEmpty(customfields) && customfields.size() > 0) {
|
||||
List<String> ids = customfields.stream().map(CustomFieldDao::getId).collect(Collectors.toList());
|
||||
List<CustomField> issueFields = baseCustomFieldService.getFieldByIds(ids);
|
||||
fieldMaps = issueFields.stream().collect(Collectors.toMap(CustomField::getId, field -> field));
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<CustomFieldDao>> entry : fieldMap.entrySet()) {
|
||||
for (CustomFieldDao fieldDao : entry.getValue()) {
|
||||
CustomField customField = fieldMaps.get(fieldDao.getId());
|
||||
if (customField != null) {
|
||||
fieldDao.setName(customField.getName());
|
||||
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())) {
|
||||
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())) {
|
||||
Date date = null;
|
||||
if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() == 18) {
|
||||
date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd'T'HH:mm");
|
||||
} else if (fieldDao.getValue().contains("T") && fieldDao.getValue().length() == 21) {
|
||||
date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY), "yyyy-MM-dd'T'HH:mm:ss");
|
||||
} else {
|
||||
date = DateUtils.parseDate(fieldDao.getValue().replaceAll("\"", StringUtils.EMPTY));
|
||||
}
|
||||
String format = DateUtils.format(date, "yyyy/MM/dd HH:mm:ss");
|
||||
fieldDao.setValue("\"" + format + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.forEach(i -> i.setFields(fieldMap.get(i.getId())));
|
||||
} catch (Exception e) {
|
||||
MSException.throwException(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void handleJiraIssueMdUrl(String workPlaceId, String projectId, List<IssuesDao> issues) {
|
||||
issues.forEach(issue -> {
|
||||
if (StringUtils.isNotEmpty(issue.getDescription()) && issue.getDescription().contains("platform=Jira&")) {
|
||||
@ -576,6 +664,13 @@ public class IssuesService {
|
||||
"platform=Jira&project_id=" + projectId + "&workspace_id=" + workspaceId + "&");
|
||||
}
|
||||
|
||||
private Map<String, List<IssueCommentDTO>> getCommentMap(List<IssuesDao> issues) {
|
||||
List<String> issueIds = issues.stream().map(IssuesDao::getId).collect(Collectors.toList());
|
||||
List<IssueCommentDTO> comments = extIssueCommentMapper.getCommentsByIssueIds(issueIds);
|
||||
Map<String, List<IssueCommentDTO>> commentMap = comments.stream().collect(Collectors.groupingBy(IssueCommentDTO::getIssueId));
|
||||
return commentMap;
|
||||
}
|
||||
|
||||
private Map<String, String> getPlanMap(List<IssuesDao> issues) {
|
||||
List<String> resourceIds = issues.stream().map(IssuesDao::getResourceId)
|
||||
.filter(Objects::nonNull)
|
||||
@ -1097,6 +1192,179 @@ 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));
|
||||
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(projectId);
|
||||
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
|
||||
List<List<String>> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, null);
|
||||
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields());
|
||||
new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal())
|
||||
.exportByCustomWriteHandler(response, heads, null, Translator.get("issue_import_template_name"),
|
||||
Translator.get("issue_import_template_sheet"), headHandler);
|
||||
}
|
||||
|
||||
public ExcelResponse issueImport(IssueImportRequest request, MultipartFile importFile) {
|
||||
if (importFile == null) {
|
||||
MSException.throwException(Translator.get("upload_fail"));
|
||||
}
|
||||
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId());
|
||||
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
|
||||
Class clazz = new IssueExcelDataFactory().getExcelDataByLocal();
|
||||
IssueExcelListener issueExcelListener = new IssueExcelListener(request, clazz, issueTemplate.getIsThirdTemplate(), customFields);
|
||||
try {
|
||||
EasyExcelFactory.read(importFile.getInputStream(), issueExcelListener).sheet().doRead();
|
||||
} catch (IOException e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
List<ExcelErrData<IssueExcelData>> errList = issueExcelListener.getErrList();
|
||||
ExcelResponse excelResponse = new ExcelResponse();
|
||||
if (CollectionUtils.isNotEmpty(errList)) {
|
||||
excelResponse.setErrList(errList);
|
||||
excelResponse.setSuccess(Boolean.FALSE);
|
||||
} else {
|
||||
excelResponse.setSuccess(Boolean.TRUE);
|
||||
}
|
||||
return excelResponse;
|
||||
}
|
||||
|
||||
public void issueExport(IssueExportRequest request, HttpServletResponse response) {
|
||||
Map<String, String> userMap = baseUserService.getProjectMemberOption(request.getProjectId()).stream().collect(Collectors.toMap(User::getId, User::getName));
|
||||
IssueTemplateDao issueTemplate = getIssueTemplateByProjectId(request.getProjectId());
|
||||
List<CustomFieldDao> customFields = Optional.ofNullable(issueTemplate.getCustomFields()).orElse(new ArrayList<>());
|
||||
List<List<String>> heads = new IssueExcelDataFactory().getIssueExcelDataLocal().getHead(issueTemplate.getIsThirdTemplate(), customFields, request);
|
||||
List<IssuesDao> exportIssues = getExportIssues(request, issueTemplate.getIsThirdTemplate(), customFields);
|
||||
List<IssueExcelData> excelDataList = parseIssueDataToExcelData(exportIssues);
|
||||
List<List<Object>> data = parseExcelDataToList(heads, excelDataList);
|
||||
IssueTemplateHeadWriteHandler headHandler = new IssueTemplateHeadWriteHandler(userMap, heads, issueTemplate.getCustomFields());
|
||||
new EasyExcelExporter(new IssueExcelDataFactory().getExcelDataByLocal())
|
||||
.exportByCustomWriteHandler(response, heads, data, Translator.get("issue_list_export_excel"),
|
||||
Translator.get("issue_list_export_excel_sheet"), headHandler);
|
||||
}
|
||||
|
||||
public List<IssuesDao> getExportIssues(IssueExportRequest exportRequest, Boolean isThirdTemplate, List<CustomFieldDao> customFields) {
|
||||
IssuesRequest request = new IssuesRequest();
|
||||
request.setProjectId(exportRequest.getProjectId());
|
||||
request.setWorkspaceId(exportRequest.getWorkspaceId());
|
||||
request.setSelectAll(exportRequest.getIsSelectAll());
|
||||
request.setExportIds(exportRequest.getExportIds());
|
||||
request.setOrders(exportRequest.getOrders());
|
||||
request.setOrders(ServiceUtils.getDefaultOrderByField(request.getOrders(), "create_time"));
|
||||
request.getOrders().forEach(order -> {
|
||||
if (StringUtils.isNotEmpty(order.getName()) && order.getName().startsWith("custom")) {
|
||||
request.setIsCustomSorted(true);
|
||||
request.setCustomFieldId(order.getName().replace("custom_", StringUtils.EMPTY));
|
||||
order.setPrefix("cfi");
|
||||
order.setName("value");
|
||||
}
|
||||
});
|
||||
ServiceUtils.setBaseQueryRequestCustomMultipleFields(request);
|
||||
List<IssuesDao> issues = extIssuesMapper.getIssues(request);
|
||||
|
||||
Map<String, Set<String>> caseSetMap = getCaseSetMap(issues);
|
||||
Map<String, User> userMap = getUserMap(issues);
|
||||
Map<String, String> planMap = getPlanMap(issues);
|
||||
Map<String, List<IssueCommentDTO>> commentMap = getCommentMap(issues);
|
||||
|
||||
issues.forEach(item -> {
|
||||
User createUser = userMap.get(item.getCreator());
|
||||
if (createUser != null) {
|
||||
item.setCreatorName(createUser.getName());
|
||||
}
|
||||
String resourceName = planMap.get(item.getResourceId());
|
||||
if (StringUtils.isNotBlank(resourceName)) {
|
||||
item.setResourceName(resourceName);
|
||||
}
|
||||
|
||||
Set<String> caseIdSet = caseSetMap.get(item.getId());
|
||||
if (caseIdSet == null) {
|
||||
caseIdSet = new HashSet<>();
|
||||
}
|
||||
item.setCaseIds(new ArrayList<>(caseIdSet));
|
||||
item.setCaseCount(caseIdSet.size());
|
||||
List<IssueCommentDTO> commentDTOList = commentMap.get(item.getId());
|
||||
if (CollectionUtils.isNotEmpty(commentDTOList) && commentDTOList.size() > 0) {
|
||||
List<String> comments = commentDTOList.stream().map(IssueCommentDTO::getDescription).collect(Collectors.toList());
|
||||
item.setComment(StringUtils.join(comments, ";"));
|
||||
}
|
||||
});
|
||||
buildCustomField(issues, isThirdTemplate, customFields);
|
||||
return issues;
|
||||
}
|
||||
|
||||
private List<IssueExcelData> parseIssueDataToExcelData(List<IssuesDao> exportIssues) {
|
||||
List<IssueExcelData> excelDataList = new ArrayList<>();
|
||||
for (int i = 0; i < exportIssues.size(); i++) {
|
||||
IssuesDao issuesDao = exportIssues.get(i);
|
||||
IssueExcelData excelData = new IssueExcelData();
|
||||
BeanUtils.copyBean(excelData, issuesDao);
|
||||
buildCustomData(issuesDao, excelData);
|
||||
excelDataList.add(excelData);
|
||||
}
|
||||
return excelDataList;
|
||||
}
|
||||
|
||||
private void buildCustomData(IssuesDao issuesDao, IssueExcelData excelData) {
|
||||
if (CollectionUtils.isNotEmpty(issuesDao.getFields())) {
|
||||
Map<String, Object> customData = new LinkedHashMap<>();
|
||||
issuesDao.getFields().forEach(field -> {
|
||||
customData.put(field.getName(), field.getValue());
|
||||
});
|
||||
excelData.setCustomData(customData);
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<Object>> parseExcelDataToList(List<List<String>> heads, List<IssueExcelData> excelDataList) {
|
||||
List<List<Object>> result = new ArrayList<>();
|
||||
IssueExportHeadField[] exportHeadFields = IssueExportHeadField.values();
|
||||
//转化excel头
|
||||
List<String> headList = new ArrayList<>();
|
||||
for (List<String> list : heads) {
|
||||
for (String head : list) {
|
||||
headList.add(head);
|
||||
}
|
||||
}
|
||||
|
||||
for (IssueExcelData data : excelDataList) {
|
||||
List<Object> rowData = new ArrayList<>();
|
||||
Map<String, Object> customData = data.getCustomData();
|
||||
for (String head : headList) {
|
||||
boolean isSystemField = false;
|
||||
for (IssueExportHeadField exportHeadField : exportHeadFields) {
|
||||
if (StringUtils.equals(head, exportHeadField.getName())) {
|
||||
rowData.add(exportHeadField.parseExcelDataValue(data));
|
||||
isSystemField = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isSystemField) {
|
||||
// 自定义字段
|
||||
Object value = customData.get(head);
|
||||
if (value == null || StringUtils.equals(value.toString(), "null")) {
|
||||
value = StringUtils.EMPTY;
|
||||
}
|
||||
rowData.add(parseCustomFieldValue(value.toString()));
|
||||
}
|
||||
}
|
||||
result.add(rowData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private IssueTemplateDao getIssueTemplateByProjectId(String projectId) {
|
||||
IssueTemplateDao issueTemplateDao = new IssueTemplateDao();
|
||||
Project project = baseProjectService.getProjectById(projectId);
|
||||
if (StringUtils.equals(project.getPlatform(), IssuesManagePlatform.Jira.name()) && project.getThirdPartTemplate()) {
|
||||
// 第三方Jira平台
|
||||
issueTemplateDao = getThirdPartTemplate(project.getId());
|
||||
issueTemplateDao.setIsThirdTemplate(Boolean.TRUE);
|
||||
} else {
|
||||
issueTemplateDao = trackIssueTemplateService.getTemplate(projectId);
|
||||
issueTemplateDao.setIsThirdTemplate(Boolean.FALSE);
|
||||
}
|
||||
return issueTemplateDao;
|
||||
}
|
||||
|
||||
private void doCheckThirdProjectExist(AbstractIssuePlatform platform, String relateId) {
|
||||
if (StringUtils.isBlank(relateId)) {
|
||||
MSException.throwException(Translator.get("issue_project_not_exist"));
|
||||
@ -1134,4 +1402,36 @@ public class IssuesService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String parseCustomFieldValue(String value) {
|
||||
if (value.contains(",")) {
|
||||
value = value.replaceAll(",", ";");
|
||||
}
|
||||
if (value.contains("\"")) {
|
||||
value = value.replaceAll("\"", StringUtils.EMPTY);
|
||||
}
|
||||
if (value.contains("[") || value.contains("]")) {
|
||||
value = value.replaceAll("]", StringUtils.EMPTY).replaceAll("\\[", StringUtils.EMPTY);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public Issues checkIssueExist(Integer num, String projectId) {
|
||||
IssuesExample example = new IssuesExample();
|
||||
example.createCriteria().andNumEqualTo(num).andProjectIdEqualTo(projectId);
|
||||
List<Issues> issues = issuesMapper.selectByExample(example);
|
||||
return CollectionUtils.isNotEmpty(issues) && issues.size() > 0 ? issues.get(0) : null;
|
||||
}
|
||||
|
||||
public void saveImportData(List<IssuesUpdateRequest> issues) {
|
||||
issues.forEach(issue -> {
|
||||
addIssues(issue, null);
|
||||
});
|
||||
}
|
||||
|
||||
public void updateImportData(List<IssuesUpdateRequest> issues) {
|
||||
issues.forEach(issue -> {
|
||||
updateIssues(issue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,24 @@ issues_attachment_upload_not_found=Unable to upload attachment, no associated is
|
||||
# issue template copy
|
||||
target_issue_template_not_checked=Cannot copy, target project not checked
|
||||
source_issue_template_is_empty=Copy error, source project is empty
|
||||
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)
|
||||
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
|
||||
options_tips=(format{key:value}, please fill in the corresponding value)Option value:
|
||||
# issue export
|
||||
title==Title
|
||||
description=Description
|
||||
case_count=Case count
|
||||
comment=Comment
|
||||
issue_resource=Issue resource
|
||||
issue_platform=Issue platform
|
||||
create_time=CreateTime
|
||||
#project
|
||||
project_name_is_null=Project name cannot be null
|
||||
project_name_already_exists=The project name already exists
|
||||
@ -175,6 +193,7 @@ custom_field_int_tip=[%s] must be integer
|
||||
custom_field_member_tip=[%s] must be current project member
|
||||
custom_field_select_tip=[%s] must be %s
|
||||
no_legitimate_case_tip=Import fails without legitimate use cases!
|
||||
no_legitimate_issue_tip=Import fails without legitimate issues!
|
||||
zentao_test_type_error=invalid Zentao request
|
||||
|
||||
test_case_status_prepare=Prepare
|
||||
|
@ -18,6 +18,24 @@ issues_attachment_upload_not_found=无法上传附件,未找到相关联缺陷
|
||||
# issue template copy
|
||||
target_issue_template_not_checked=无法复制,未选中目标项目
|
||||
source_issue_template_is_empty=复制错误,源项目为空
|
||||
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)
|
||||
int_import_cell_format_comment=整型单元格格式为: 100001
|
||||
float_import_cell_format_comment=浮点单元格格式为: 24
|
||||
multiple_input_import_cell_format_comment=该单元格可输入多个值,多个值请用逗号或分号隔开(v1;v2)
|
||||
options_tips=(格式{key:value},请填写对应的value)选项:
|
||||
# issue export
|
||||
title=缺陷标题
|
||||
description=缺陷描述
|
||||
case_count=用例数
|
||||
comment=评论
|
||||
issue_resource=缺陷来源
|
||||
issue_platform=缺陷平台
|
||||
create_time=创建时间
|
||||
#project
|
||||
project_name_is_null=项目名称不能为空
|
||||
project_name_already_exists=项目名称已存在
|
||||
@ -152,6 +170,7 @@ custom_field_int_tip=[%s]必须为整型
|
||||
custom_field_member_tip=[%s]必须当前项目成员
|
||||
custom_field_select_tip=[%s]必须为%s
|
||||
no_legitimate_case_tip=导入失败,没有合法用例!
|
||||
no_legitimate_issue_tip=导入失败,没有合法缺陷!
|
||||
|
||||
test_case_status_prepare=未开始
|
||||
test_case_status_running=进行中
|
||||
|
@ -18,6 +18,24 @@ issues_attachment_upload_not_found=無法上傳附件,未找到相關缺陷:
|
||||
# issue template copy
|
||||
target_issue_template_not_checked=無法複製,未選中目標項目
|
||||
source_issue_template_is_empty=複製錯誤,源項目為空
|
||||
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)
|
||||
int_import_cell_format_comment=單元格格式: 100001
|
||||
float_import_cell_format_comment=單元格格式: 24
|
||||
multiple_input_import_cell_format_comment=該單元格可輸入多個值,多個值請用逗號或分號隔開(v1;v2)
|
||||
options_tips=(格式{key:value},請填寫對應value)選項:
|
||||
# issue export
|
||||
title=缺陷標題
|
||||
description=缺陷描述
|
||||
case_count=用例數
|
||||
comment=評論
|
||||
issue_resource=缺陷來源
|
||||
issue_platform=缺陷平臺
|
||||
create_time=創建時間
|
||||
#project
|
||||
project_name_is_null=項目名稱不能為空
|
||||
project_name_already_exists=項目名稱已存在
|
||||
@ -152,6 +170,7 @@ custom_field_int_tip=[%s]必須為整型
|
||||
custom_field_member_tip=[%s]必須當前項目成員
|
||||
custom_field_select_tip=[%s]必須為%s
|
||||
no_legitimate_case_tip=導入失敗,沒有合法用例!
|
||||
no_legitimate_issue_tip=導入失敗,沒有合法缺陷!
|
||||
|
||||
|
||||
test_case_status_prepare=未開始
|
||||
|
@ -24,6 +24,10 @@ export function deleteIssue(id) {
|
||||
return get(BASE_URL + `delete/${id}`);
|
||||
}
|
||||
|
||||
export function batchDeleteIssue(param) {
|
||||
return post(BASE_URL + `batchDelete`, param);
|
||||
}
|
||||
|
||||
export function issueStatusChange(param) {
|
||||
return post(BASE_URL + 'change/status', param);
|
||||
}
|
||||
|
@ -1,30 +1,28 @@
|
||||
<template>
|
||||
<ms-container>
|
||||
<ms-main-container>
|
||||
|
||||
<el-card class="table-card">
|
||||
<template v-slot:header>
|
||||
<ms-table-header :create-permission="['PROJECT_TRACK_ISSUE:READ+CREATE']" :condition.sync="page.condition" @search="search" @create="handleCreate"
|
||||
:create-tip="$t('test_track.issue.create_issue')"
|
||||
:tip="$t('commons.search_by_name_or_id')">
|
||||
<template v-slot:button>
|
||||
<el-tooltip v-if="isThirdPart" :content="$t('test_track.issue.update_third_party_bugs')">
|
||||
<ms-table-button icon="el-icon-refresh" v-if="true"
|
||||
:content="$t('test_track.issue.sync_bugs')" @click="syncIssues"/>
|
||||
</el-tooltip>
|
||||
<ms-table-button icon="el-icon-refresh" :content="$t('test_track.issue.sync_bugs')" v-if="isThirdPart && hasPermission('PROJECT_TRACK_ISSUE:READ+CREATE')" @click="syncIssues"/>
|
||||
<ms-table-button icon="el-icon-upload2" :content="$t('commons.import')" v-if="hasPermission('PROJECT_TRACK_ISSUE:READ+CREATE')" @click="handleImport"/>
|
||||
<ms-table-button icon="el-icon-download" :content="$t('commons.export')" v-if="hasPermission('PROJECT_TRACK_ISSUE:READ')" @click="handleExport"/>
|
||||
</template>
|
||||
</ms-table-header>
|
||||
</template>
|
||||
|
||||
<ms-table
|
||||
v-loading="page.result.loading || loading"
|
||||
row-key="id"
|
||||
:data="page.data"
|
||||
:enableSelection="false"
|
||||
:condition="page.condition"
|
||||
:total="page.total"
|
||||
:page-size.sync="page.pageSize"
|
||||
:operators="operators"
|
||||
:show-select-all="false"
|
||||
:batch-operators="batchButtons"
|
||||
:screen-height="screenHeight"
|
||||
:remember-order="true"
|
||||
:fields.sync="fields"
|
||||
@ -33,139 +31,141 @@
|
||||
@filter="search"
|
||||
@order="getIssues"
|
||||
@handlePageChange="getIssues"
|
||||
ref="table"
|
||||
>
|
||||
<span v-for="(item) in fields" :key="item.key">
|
||||
<ms-table-column width="1">
|
||||
</ms-table-column>
|
||||
<ms-table-column
|
||||
:label="$t('test_track.issue.id')"
|
||||
prop="num"
|
||||
:field="item"
|
||||
sortable
|
||||
min-width="100"
|
||||
:fields-width="fieldsWidth">
|
||||
</ms-table-column>
|
||||
ref="table">
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="$t('test_track.issue.title')"
|
||||
sortable
|
||||
min-width="110"
|
||||
prop="title">
|
||||
</ms-table-column>
|
||||
<span v-for="(item) in fields" :key="item.key">
|
||||
<ms-table-column width="1">
|
||||
</ms-table-column>
|
||||
<ms-table-column
|
||||
:label="$t('test_track.issue.id')"
|
||||
prop="num"
|
||||
:field="item"
|
||||
sortable
|
||||
min-width="100"
|
||||
:fields-width="fieldsWidth">
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:filters="platformFilters"
|
||||
:label="$t('test_track.issue.platform')"
|
||||
min-width="80"
|
||||
prop="platform">
|
||||
</ms-table-column>
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="$t('test_track.issue.title')"
|
||||
sortable
|
||||
min-width="110"
|
||||
prop="title">
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
sortable
|
||||
min-width="110"
|
||||
:label="$t('test_track.issue.platform_status') "
|
||||
prop="platformStatus">
|
||||
<template v-slot="scope">
|
||||
<span v-if="scope.row.platform ==='Zentao'">{{ scope.row.platformStatus ? issueStatusMap[scope.row.platformStatus] : '--'}}</span>
|
||||
<span v-else-if="scope.row.platform ==='Tapd'">{{ scope.row.platformStatus ? tapdIssueStatusMap[scope.row.platformStatus] : '--'}}</span>
|
||||
<span v-else>{{ scope.row.platformStatus ? scope.row.platformStatus : '--'}}</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:filters="platformFilters"
|
||||
:label="$t('test_track.issue.platform')"
|
||||
min-width="80"
|
||||
prop="platform">
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
column-key="creator"
|
||||
:filters="creatorFilters"
|
||||
sortable
|
||||
min-width="100px"
|
||||
:label="$t('custom_field.issue_creator')"
|
||||
prop="creatorName">
|
||||
</ms-table-column>
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
sortable
|
||||
min-width="110"
|
||||
:label="$t('test_track.issue.platform_status') "
|
||||
prop="platformStatus">
|
||||
<template v-slot="scope">
|
||||
<span v-if="scope.row.platform ==='Zentao'">{{ scope.row.platformStatus ? issueStatusMap[scope.row.platformStatus] : '--'}}</span>
|
||||
<span v-else-if="scope.row.platform ==='Tapd'">{{ scope.row.platformStatus ? tapdIssueStatusMap[scope.row.platformStatus] : '--'}}</span>
|
||||
<span v-else>{{ scope.row.platformStatus ? scope.row.platformStatus : '--'}}</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="$t('test_track.issue.issue_resource')"
|
||||
prop="resourceName">
|
||||
<template v-slot="scope">
|
||||
<el-link v-if="scope.row.resourceName" @click="$router.push('/track/plan/view/' + scope.row.resourceId)">
|
||||
{{ scope.row.resourceName }}
|
||||
</el-link>
|
||||
<span v-else>
|
||||
--
|
||||
</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
<ms-table-column prop="createTime"
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="$t('commons.create_time')"
|
||||
sortable
|
||||
min-width="180px">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.createTime | datetimeFormat }}</span>
|
||||
</template>
|
||||
</ms-table-column >
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
column-key="creator"
|
||||
:filters="creatorFilters"
|
||||
sortable
|
||||
min-width="100px"
|
||||
:label="$t('custom_field.issue_creator')"
|
||||
prop="creatorName">
|
||||
</ms-table-column>
|
||||
|
||||
<issue-description-table-item :fields-width="fieldsWidth" :field="item"/>
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="$t('test_track.issue.issue_resource')"
|
||||
prop="resourceName">
|
||||
<template v-slot="scope">
|
||||
<el-link v-if="scope.row.resourceName" @click="$router.push('/track/plan/view/' + scope.row.resourceId)">
|
||||
{{ scope.row.resourceName }}
|
||||
</el-link>
|
||||
<span v-else>
|
||||
--
|
||||
</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="item.label"
|
||||
prop="caseCount">
|
||||
<template v-slot="scope">
|
||||
<router-link :to="scope.row.caseCount > 0 ? {name: 'testCase', params: { projectId: 'all', ids: scope.row.caseIds }} : {}">
|
||||
{{scope.row.caseCount}}
|
||||
</router-link>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column v-for="field in issueTemplate.customFields" :key="field.id"
|
||||
:filters="field.name === '状态'? i18nCustomStatus(getCustomFieldFilter(field)) : getCustomFieldFilter(field)"
|
||||
sortable="custom"
|
||||
<ms-table-column prop="createTime"
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
min-width="120"
|
||||
:label="field.system ? $t(systemNameMap[field.name]) :field.name"
|
||||
:column-key="generateColumnKey(field)"
|
||||
:prop="field.name">
|
||||
<template v-slot="scope">
|
||||
<span v-if="field.name === '状态'">
|
||||
{{getCustomFieldValue(scope.row, field, issueStatusMap[scope.row.status])}}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'richText'">
|
||||
<el-popover
|
||||
placement="right"
|
||||
width="500"
|
||||
trigger="hover"
|
||||
popper-class="issues-popover">
|
||||
<ms-mark-down-text prop="value" :data="{value: getCustomFieldValue(scope.row, field)}" :disabled="true"/>
|
||||
<el-button slot="reference" type="text">{{ $t('test_track.issue.preview') }}</el-button>
|
||||
</el-popover>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{getCustomFieldValue(scope.row, field)}}
|
||||
</span>
|
||||
:label="$t('commons.create_time')"
|
||||
sortable
|
||||
min-width="180px">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.createTime | datetimeFormat }}</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
</ms-table-column >
|
||||
|
||||
</span>
|
||||
<issue-description-table-item :fields-width="fieldsWidth" :field="item"/>
|
||||
|
||||
<ms-table-column
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
:label="item.label"
|
||||
prop="caseCount">
|
||||
<template v-slot="scope">
|
||||
<router-link :to="scope.row.caseCount > 0 ? {name: 'testCase', params: { projectId: 'all', ids: scope.row.caseIds }} : {}">
|
||||
{{scope.row.caseCount}}
|
||||
</router-link>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
<ms-table-column v-for="field in issueTemplate.customFields" :key="field.id"
|
||||
:filters="field.name === '状态'? i18nCustomStatus(getCustomFieldFilter(field)) : getCustomFieldFilter(field)"
|
||||
sortable="custom"
|
||||
:field="item"
|
||||
:fields-width="fieldsWidth"
|
||||
min-width="200"
|
||||
:label="field.system ? $t(systemNameMap[field.name]) :field.name"
|
||||
:column-key="generateColumnKey(field)"
|
||||
:prop="field.name">
|
||||
<template v-slot="scope">
|
||||
<span v-if="field.name === '状态'">
|
||||
{{getCustomFieldValue(scope.row, field, issueStatusMap[scope.row.status])}}
|
||||
</span>
|
||||
<span v-else-if="field.type === 'richText'">
|
||||
<el-popover
|
||||
placement="right"
|
||||
width="500"
|
||||
trigger="hover"
|
||||
popper-class="issues-popover">
|
||||
<ms-mark-down-text prop="value" :data="{value: getCustomFieldValue(scope.row, field)}" :disabled="true"/>
|
||||
<el-button slot="reference" type="text">{{ $t('test_track.issue.preview') }}</el-button>
|
||||
</el-popover>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{getCustomFieldValue(scope.row, field)}}
|
||||
</span>
|
||||
</template>
|
||||
</ms-table-column>
|
||||
|
||||
</span>
|
||||
</ms-table>
|
||||
|
||||
<ms-table-pagination :change="getIssues" :current-page.sync="page.currentPage" :page-size.sync="page.pageSize"
|
||||
:total="page.total"/>
|
||||
|
||||
<issue-edit @refresh="getIssues" ref="issueEdit"/>
|
||||
<issue-sync-select @syncConfirm="syncConfirm" ref="issueSyncSelect" />
|
||||
<issue-import @refresh="getIssues" ref="issueImport"/>
|
||||
<issue-export @export="exportIssue" ref="issueExport"/>
|
||||
</el-card>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
@ -187,13 +187,15 @@ import MsTableHeader from "metersphere-frontend/src/components/MsTableHeader";
|
||||
import IssueDescriptionTableItem from "@/business/issue/IssueDescriptionTableItem";
|
||||
import IssueEdit from "@/business/issue/IssueEdit";
|
||||
import IssueSyncSelect from "@/business/issue/IssueSyncSelect";
|
||||
import IssueImport from "@/business/issue/components/import/IssueImport";
|
||||
import IssueExport from "@/business/issue/components/export/IssueExport";
|
||||
import {
|
||||
checkSyncIssues,
|
||||
getIssuePartTemplateWithProject,
|
||||
getIssues,
|
||||
syncIssues,
|
||||
deleteIssue,
|
||||
getIssuesById
|
||||
getIssuesById, batchDeleteIssue
|
||||
} from "@/api/issue";
|
||||
import {
|
||||
getCustomFieldValue,
|
||||
@ -202,7 +204,8 @@ import {
|
||||
} from "metersphere-frontend/src/utils/tableUtils";
|
||||
import MsContainer from "metersphere-frontend/src/components/MsContainer";
|
||||
import MsMainContainer from "metersphere-frontend/src/components/MsMainContainer";
|
||||
import {getCurrentProjectID, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
import {getCurrentProjectID, getCurrentWorkspaceId, getCurrentUserId} from "metersphere-frontend/src/utils/token";
|
||||
import {hasPermission} from "metersphere-frontend/src/utils/permission";
|
||||
import {getProjectMember, getProjectMemberUserFilter} from "@/api/user";
|
||||
import {LOCAL} from "metersphere-frontend/src/utils/constants";
|
||||
import {TEST_TRACK_ISSUE_LIST} from "metersphere-frontend/src/components/search/search-components";
|
||||
@ -221,6 +224,8 @@ export default {
|
||||
IssueEdit,
|
||||
IssueDescriptionTableItem,
|
||||
IssueSyncSelect,
|
||||
IssueImport,
|
||||
IssueExport,
|
||||
MsTableHeader,
|
||||
MsTablePagination, MsTableButton, MsTableOperators, MsTableColumn, MsTable
|
||||
},
|
||||
@ -250,6 +255,13 @@ export default {
|
||||
permissions: ['PROJECT_TRACK_ISSUE:READ+DELETE']
|
||||
}
|
||||
],
|
||||
batchButtons: [
|
||||
{
|
||||
name: this.$t('test_track.issue.batch_delete_issue'),
|
||||
handleClick: this.handleBatchDelete,
|
||||
permissions: ['PROJECT_TRACK_ISSUE:READ+DELETE']
|
||||
}
|
||||
],
|
||||
issueTemplate: {},
|
||||
members: [],
|
||||
userFilter: [],
|
||||
@ -308,7 +320,11 @@ export default {
|
||||
this.editParam();
|
||||
},
|
||||
methods: {
|
||||
generateColumnKey,
|
||||
generateColumnKey(field){
|
||||
let columnKey = generateColumnKey(field);
|
||||
return "custom_" + columnKey.substr(columnKey.indexOf("-") + 1);
|
||||
},
|
||||
hasPermission,
|
||||
tableDoLayout() {
|
||||
if (this.$refs.table) this.$refs.table.doLayout();
|
||||
},
|
||||
@ -404,6 +420,28 @@ export default {
|
||||
this.getIssues();
|
||||
})
|
||||
},
|
||||
handleBatchDelete() {
|
||||
this.$alert(this.$t('test_track.issue.batch_delete_tip') + " ?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleBatchDelete();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleBatchDelete() {
|
||||
let selectIds = this.$refs.table.selectIds;
|
||||
if (selectIds.length == 0) {
|
||||
this.$warning(this.$t("test_track.issue.check_select"));
|
||||
return;
|
||||
}
|
||||
batchDeleteIssue({"batchDeleteIds" : selectIds, "batchDeleteAll" : this.page.condition.selectAll})
|
||||
.then(() => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.getIssues();
|
||||
})
|
||||
},
|
||||
btnDisable(row) {
|
||||
if (this.issueTemplate.platform !== row.platform) {
|
||||
return true;
|
||||
@ -413,6 +451,29 @@ export default {
|
||||
syncIssues() {
|
||||
this.$refs.issueSyncSelect.open();
|
||||
},
|
||||
handleImport() {
|
||||
this.$refs.issueImport.open();
|
||||
},
|
||||
handleExport() {
|
||||
let exportIds = this.$refs.table.selectIds;
|
||||
if (exportIds.length == 0) {
|
||||
this.$warning(this.$t("test_track.issue.check_select"));
|
||||
return;
|
||||
}
|
||||
this.$refs.issueExport.open();
|
||||
},
|
||||
exportIssue(data) {
|
||||
let param = {
|
||||
"projectId": getCurrentProjectID(),
|
||||
"workspaceId": getCurrentWorkspaceId(),
|
||||
"userId": getCurrentUserId(),
|
||||
"isSelectAll": this.page.condition.selectAll,
|
||||
"exportIds": this.$refs.table.selectIds,
|
||||
"exportFields": data,
|
||||
"orders": getLastTableSortField(this.tableHeaderKey)
|
||||
}
|
||||
this.$fileDownloadPost("/issues/export", param);
|
||||
},
|
||||
syncConfirm(data) {
|
||||
this.loading = true;
|
||||
let param = {
|
||||
@ -453,4 +514,8 @@ export default {
|
||||
.el-table {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<el-dialog class="issue-export"
|
||||
v-loading="loading"
|
||||
:title="$t('test_track.issue.export')"
|
||||
:visible.sync="dialogVisible"
|
||||
@close="close">
|
||||
<issue-export-field-select ref="issueExportFieldSelect"/>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" @click="dialogVisible = false">{{ $t('commons.cancel') }}</el-button>
|
||||
<el-button type="primary" size="mini" @click="exportIssue">{{ $t('commons.export') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ElUploadList from "element-ui/packages/upload/src/upload-list";
|
||||
import MsTableButton from 'metersphere-frontend/src/components/MsTableButton';
|
||||
import {listenGoBack, removeGoBackListener} from "metersphere-frontend/src/utils";
|
||||
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token"
|
||||
import IssueExportFieldSelect from "@/business/issue/components/export/IssueExportFieldSelect";
|
||||
|
||||
export default {
|
||||
name: "IssueExport",
|
||||
components: {IssueExportFieldSelect, ElUploadList, MsTableButton},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
projectId: "",
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
listenGoBack(this.close);
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.dialogVisible = true;
|
||||
this.loading = false;
|
||||
},
|
||||
close() {
|
||||
removeGoBackListener(this.close);
|
||||
this.dialogVisible = false;
|
||||
this.loading = false;
|
||||
},
|
||||
exportIssue() {
|
||||
let param = null;
|
||||
if (this.$refs.issueExportFieldSelect) {
|
||||
param = this.$refs.issueExportFieldSelect.getExportParam();
|
||||
}
|
||||
this.close();
|
||||
this.$emit('export', param);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.issue-export :deep(.el-dialog) {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.issue-export :deep(.el-dialog .el-dialog__title) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.issue-export :deep(.el-dialog .el-dialog__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row v-for="rowIndex in fieldRowCount" :key="rowIndex">
|
||||
<span v-for="(item, index) in fields"
|
||||
:key="item.id">
|
||||
<el-col :span="6"
|
||||
v-if="Math.floor(index / colCountEachRow) === rowIndex - 1">
|
||||
<el-checkbox
|
||||
v-model="item.enable"
|
||||
:disabled="item.disabled"
|
||||
@change="change">
|
||||
{{ item.name }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</span>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "IssueExportFieldList",
|
||||
props: ['fields'],
|
||||
data() {
|
||||
return {
|
||||
colCountEachRow: 4
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fieldRowCount() {
|
||||
if (!this.fields) {
|
||||
return 0;
|
||||
}
|
||||
return Math.ceil(this.fields.length / this.colCountEachRow);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(value) {
|
||||
this.$emit('enableChange', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="export-title"
|
||||
@click="showSelect = !showSelect">
|
||||
<span>
|
||||
{{ $t('test_track.case.import.select_import_field') }}
|
||||
</span>
|
||||
<i class="el-icon-arrow-down"
|
||||
v-if="showSelect"/>
|
||||
<i class="el-icon-arrow-left"
|
||||
v-if="!showSelect"/>
|
||||
</div>
|
||||
|
||||
<el-divider/>
|
||||
|
||||
<div v-show="showSelect">
|
||||
<el-checkbox class="select-all-checkbox"
|
||||
v-model="selectAll" @change="handleSelectAllChange">
|
||||
{{ $t('test_track.case.import.select_import_all_field') }}
|
||||
</el-checkbox>
|
||||
|
||||
<issue-export-field-select-item
|
||||
type="EXPORT_BASE_FIELD"
|
||||
:title="$t('test_track.case.import.base_field')"
|
||||
:fields="baseFields"
|
||||
@selectAllChange="handleItemSelectAllChange"
|
||||
ref="baseSelectItem"/>
|
||||
|
||||
<issue-export-field-select-item
|
||||
type="EXPORT_CUSTOM_FIELD"
|
||||
:title="$t('test_track.case.import.custom_field')"
|
||||
:fields="customFields"
|
||||
@selectAllChange="handleItemSelectAllChange"
|
||||
ref="customSelectItem"/>
|
||||
|
||||
<issue-export-field-select-item
|
||||
type="EXPORT_OTHER_FIELD"
|
||||
:title="$t('test_track.case.import.other_field')"
|
||||
:fields="otherFields"
|
||||
@selectAllChange="handleItemSelectAllChange"
|
||||
ref="otherSelectItem"/>
|
||||
|
||||
<div class="other-field-tip">
|
||||
{{ $t('test_track.case.import.other_field_tip') }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IssueExportFieldSelectItem from "@/business/issue/components/export/IssueExportFieldSelectItem";
|
||||
import {getIssuePartTemplateWithProject} from "@/api/issue";
|
||||
|
||||
export default {
|
||||
name: "IssueExportFieldSelect",
|
||||
components: {IssueExportFieldSelectItem},
|
||||
data() {
|
||||
return {
|
||||
selectAll: false,
|
||||
showSelect: true,
|
||||
baseFields: [
|
||||
{
|
||||
id: 'id',
|
||||
key: 'A',
|
||||
name: 'ID',
|
||||
enable: true,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
key: 'B',
|
||||
name: this.$t("test_track.issue.title"),
|
||||
enable: true,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
key: 'D',
|
||||
name: this.$t("test_track.issue.description"),
|
||||
enable: true
|
||||
}
|
||||
],
|
||||
loading: false,
|
||||
customFields: [],
|
||||
otherFields: [
|
||||
{
|
||||
id: 'creator',
|
||||
key: 'E',
|
||||
name: this.$t("commons.creator"),
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 'caseCount',
|
||||
key: 'A',
|
||||
name: this.$t("test_track.home.case_size"),
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 'comment',
|
||||
key: 'B',
|
||||
name: this.$t("commons.comment"),
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 'resource',
|
||||
key: 'C',
|
||||
name: this.$t("test_track.issue.issue_resource"),
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 'platform',
|
||||
key: 'D',
|
||||
name: this.$t("test_track.issue.issue_platform"),
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 'createTime',
|
||||
key: 'E',
|
||||
name: this.$t("commons.create_time"),
|
||||
enable: true
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectItems() {
|
||||
return [this.$refs.baseSelectItem, this.$refs.customSelectItem, this.$refs.otherSelectItem];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loading = true;
|
||||
getIssuePartTemplateWithProject((template) => {
|
||||
template.customFields.forEach(item => {
|
||||
item.enable = true;
|
||||
this.customFields.push(item);
|
||||
});
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getExportParam() {
|
||||
return {
|
||||
baseHeaders: this.selectItems[0].getExportParam(),
|
||||
customHeaders: this.selectItems[1].getExportParam(),
|
||||
otherHeaders: this.selectItems[2].getExportParam(),
|
||||
}
|
||||
},
|
||||
handleSelectAllChange() {
|
||||
this.selectItems.forEach(item => {
|
||||
item.selectAllChange(this.selectAll);
|
||||
});
|
||||
},
|
||||
handleItemSelectAllChange() {
|
||||
let isSelectAll = true;
|
||||
this.selectItems.forEach(item => {
|
||||
if (!item.selectAll) {
|
||||
isSelectAll = false;
|
||||
}
|
||||
});
|
||||
this.selectAll = isSelectAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.export-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 20px 5px 15px 0px;
|
||||
}
|
||||
|
||||
.export-title span:first-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.select-all-checkbox {
|
||||
margin-top: 10px
|
||||
}
|
||||
|
||||
.export-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.other-field-tip {
|
||||
margin-top: 30px;
|
||||
font-size: 10px;
|
||||
color: #9ea0a3;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field-title" v-if="fields && fields.length > 0">
|
||||
<span>{{ title }}</span>
|
||||
<el-checkbox
|
||||
v-model="selectAll"
|
||||
@change="selectAllChange"/>
|
||||
</div>
|
||||
<issue-export-field-list
|
||||
:fields="fields"
|
||||
@enableChange="enableChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import IssueExportFieldList from "@/business/issue/components/export/IssueExportFieldList";
|
||||
export default {
|
||||
name: "IssueExportFieldSelectItem",
|
||||
components: {IssueExportFieldList},
|
||||
props: {
|
||||
fields: Array,
|
||||
title: String,
|
||||
type: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectAll: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectAll() {
|
||||
this.$emit('selectAllChange', this.selectAll);
|
||||
},
|
||||
fields() {
|
||||
this.checkEnable();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.checkEnable();
|
||||
},
|
||||
methods: {
|
||||
getExportParam() {
|
||||
return this.fields.filter(item => item.enable);
|
||||
},
|
||||
enableChange(enable) {
|
||||
this.persistenceValues();
|
||||
if (enable) {
|
||||
for (let head of this.fields) {
|
||||
if (!head.enable) {
|
||||
// 单个启用,如果有未启用的则返回
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果全部都开启了就启用总开关,或者一个关闭了则禁用总开关
|
||||
this.selectAll = enable;
|
||||
},
|
||||
persistenceValues() {
|
||||
// 将勾选情况保存在浏览器
|
||||
let enableKeys = this.fields.filter(i => i.enable)
|
||||
.map(i => i.key);
|
||||
localStorage.setItem(this.type, JSON.stringify(enableKeys));
|
||||
},
|
||||
selectAllChange(value) {
|
||||
this.selectAll = value;
|
||||
this.fields.forEach(i => {
|
||||
if (!i.disabled) {
|
||||
i.enable = value;
|
||||
}
|
||||
});
|
||||
this.persistenceValues();
|
||||
},
|
||||
checkEnable() {
|
||||
// 获取保存在浏览器的选项值
|
||||
let enableKeys = localStorage.getItem(this.type);
|
||||
if (enableKeys) {
|
||||
enableKeys = JSON.parse(enableKeys);
|
||||
}
|
||||
|
||||
let isSelectAll = true;
|
||||
for (let field of this.fields) {
|
||||
if (enableKeys) {
|
||||
if (enableKeys.indexOf(field.key) > -1) {
|
||||
field.enable = true;
|
||||
} else {
|
||||
field.enable = false;
|
||||
}
|
||||
}
|
||||
if (!field.enable) {
|
||||
isSelectAll = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.selectAll = isSelectAll;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.field-title {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.field-title span:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog :visible="visible" v-loading="loading" :title="$t('test_track.issue.import_bugs')" @close="cancel" width="35%">
|
||||
<div>
|
||||
<el-row>
|
||||
<span style="color: red">*</span> {{ $t('test_track.issue.import_type') }}
|
||||
<el-select v-model="importType" :placeholder="$t('commons.please_select')" size="mini" class="issue-import-type" clearable>
|
||||
<el-option
|
||||
v-for="item in importOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-tooltip effect="dark" style="margin-left: 10px" placement="right">
|
||||
<div slot="content" v-html="$t('test_track.issue.import_type_tips')"></div>
|
||||
<i class="el-icon-info"></i>
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-upload
|
||||
class="issue-upload" drag action="alert"
|
||||
:limit="1"
|
||||
:file-list="uploadFiles"
|
||||
:http-request="handleUpload"
|
||||
:on-remove="handleRemove" accept=".xls, .xlsx">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
{{ $t('test_track.issue.import_file_limit_tips') }}
|
||||
<el-link type="primary" class="download-template" @click="downloadIssueImportTemplate">
|
||||
{{ $t('test_track.case.import.download_template') }}
|
||||
</el-link>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<ul>
|
||||
<li v-for="errFile in errList" :key="errFile.rowNum">
|
||||
{{ errFile.errMsg }}
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
|
||||
<el-row style="text-align: right; margin-top: 40px">
|
||||
<el-button size="mini" @click="cancel">{{ $t('commons.cancel') }}</el-button>
|
||||
<el-button type="primary" size="mini" @click="save">
|
||||
{{ $t('commons.save') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getCurrentProjectID, getCurrentUserId, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
|
||||
export default {
|
||||
name: "IssueImport",
|
||||
props: ['tabName', 'name'],
|
||||
data() {
|
||||
return {
|
||||
visible:false,
|
||||
loading: false,
|
||||
importType: "",
|
||||
importOptions: [{value: "Update", label: this.$t('commons.cover')}, {value: "Create", label: this.$t('commons.not_cover')}],
|
||||
uploadFiles: [],
|
||||
errList: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
computed: {
|
||||
projectId() {
|
||||
return getCurrentProjectID();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true;
|
||||
},
|
||||
cancel() {
|
||||
this.visible = false;
|
||||
this.importType = "";
|
||||
this.uploadFiles = [];
|
||||
},
|
||||
handleUpload(file) {
|
||||
this.uploadFiles.push(file.file);
|
||||
},
|
||||
handleRemove(file) {
|
||||
let fileName = file.name ? file.name : file.file.name
|
||||
for (let i = 0; i < this.uploadFiles.length; i++) {
|
||||
let uploadFileName = this.uploadFiles[i].name ? this.uploadFiles[i].name : this.uploadFiles[i].file.name;
|
||||
if (fileName === uploadFileName) {
|
||||
this.uploadFiles.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
downloadIssueImportTemplate() {
|
||||
let uri = '/issues/import/template/download/';
|
||||
this.$fileDownload(uri + getCurrentProjectID());
|
||||
},
|
||||
save() {
|
||||
let param = {
|
||||
workspaceId: getCurrentWorkspaceId(),
|
||||
projectId: getCurrentProjectID(),
|
||||
userId: getCurrentUserId(),
|
||||
importType: this.importType
|
||||
};
|
||||
if (this.importType == '') {
|
||||
this.$warning(this.$t('test_track.case.import.import_type_require_tips'))
|
||||
return;
|
||||
}
|
||||
if (this.uploadFiles.length == 0) {
|
||||
this.$warning(this.$t('test_track.case.import.import_file_tips'));
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.$fileUpload('/issues/import', this.uploadFiles[0], param)
|
||||
.then(response => {
|
||||
this.loading = false;
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
this.cancel();
|
||||
this.$emit("refresh");
|
||||
} else {
|
||||
this.errList = res.errList;
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.issue-import-type {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.issue-upload {
|
||||
margin-left: 75px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.download-template {
|
||||
margin-left: 220px;
|
||||
top: -15px;
|
||||
font-size: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user