feat(测试跟踪): 用例及缺陷附件支持文件管理

This commit is contained in:
song-cc-rock 2022-09-17 13:21:28 +08:00 committed by 刘瑞斌
parent d359bbf66f
commit c0870f06e5
32 changed files with 924 additions and 97 deletions

View File

@ -27,6 +27,7 @@
<xmlbeans.version>5.1.0</xmlbeans.version> <xmlbeans.version>5.1.0</xmlbeans.version>
<poi.version>5.1.0</poi.version> <poi.version>5.1.0</poi.version>
<jgit.version>6.2.0.202206071550-r</jgit.version> <jgit.version>6.2.0.202206071550-r</jgit.version>
<commons-fileupload.version>1.3</commons-fileupload.version>
</properties> </properties>
<dependencies> <dependencies>
@ -152,6 +153,17 @@
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
</dependency> </dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
<exclusions>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>

View File

@ -12,5 +12,7 @@ public class AttachmentModuleRelation implements Serializable {
private String attachmentId; private String attachmentId;
private String fileMetadataRefId;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -313,6 +313,76 @@ public class AttachmentModuleRelationExample {
addCriterion("attachment_id not between", value1, value2, "attachmentId"); addCriterion("attachment_id not between", value1, value2, "attachmentId");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andFileMetadataRefIdIsNull() {
addCriterion("file_metadata_ref_id is null");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdIsNotNull() {
addCriterion("file_metadata_ref_id is not null");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdEqualTo(String value) {
addCriterion("file_metadata_ref_id =", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdNotEqualTo(String value) {
addCriterion("file_metadata_ref_id <>", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdGreaterThan(String value) {
addCriterion("file_metadata_ref_id >", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdGreaterThanOrEqualTo(String value) {
addCriterion("file_metadata_ref_id >=", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdLessThan(String value) {
addCriterion("file_metadata_ref_id <", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdLessThanOrEqualTo(String value) {
addCriterion("file_metadata_ref_id <=", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdLike(String value) {
addCriterion("file_metadata_ref_id like", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdNotLike(String value) {
addCriterion("file_metadata_ref_id not like", value, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdIn(List<String> values) {
addCriterion("file_metadata_ref_id in", values, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdNotIn(List<String> values) {
addCriterion("file_metadata_ref_id not in", values, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdBetween(String value1, String value2) {
addCriterion("file_metadata_ref_id between", value1, value2, "fileMetadataRefId");
return (Criteria) this;
}
public Criteria andFileMetadataRefIdNotBetween(String value1, String value2) {
addCriterion("file_metadata_ref_id not between", value1, value2, "fileMetadataRefId");
return (Criteria) this;
}
} }
public static class Criteria extends GeneratedCriteria { public static class Criteria extends GeneratedCriteria {

View File

@ -1,8 +1,9 @@
package io.metersphere.base.domain; package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
@Data @Data
public class FileAttachmentMetadata implements Serializable { public class FileAttachmentMetadata implements Serializable {
private String id; private String id;
@ -21,5 +22,9 @@ public class FileAttachmentMetadata implements Serializable {
private String filePath; private String filePath;
private Boolean isLocal;
private Boolean isRelatedDeleted;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -5,6 +5,7 @@
<result column="relation_id" jdbcType="VARCHAR" property="relationId" /> <result column="relation_id" jdbcType="VARCHAR" property="relationId" />
<result column="relation_type" jdbcType="VARCHAR" property="relationType" /> <result column="relation_type" jdbcType="VARCHAR" property="relationType" />
<result column="attachment_id" jdbcType="VARCHAR" property="attachmentId" /> <result column="attachment_id" jdbcType="VARCHAR" property="attachmentId" />
<result column="file_metadata_ref_id" jdbcType="VARCHAR" property="fileMetadataRefId" />
</resultMap> </resultMap>
<sql id="Example_Where_Clause"> <sql id="Example_Where_Clause">
<where> <where>
@ -65,7 +66,7 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
relation_id, relation_type, attachment_id relation_id, relation_type, attachment_id, file_metadata_ref_id
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultMap="BaseResultMap">
select select
@ -88,10 +89,10 @@
</if> </if>
</delete> </delete>
<insert id="insert" parameterType="io.metersphere.base.domain.AttachmentModuleRelation"> <insert id="insert" parameterType="io.metersphere.base.domain.AttachmentModuleRelation">
insert into attachment_module_relation (relation_id, relation_type, attachment_id insert into attachment_module_relation (relation_id, relation_type, attachment_id,
) file_metadata_ref_id)
values (#{relationId,jdbcType=VARCHAR}, #{relationType,jdbcType=VARCHAR}, #{attachmentId,jdbcType=VARCHAR} values (#{relationId,jdbcType=VARCHAR}, #{relationType,jdbcType=VARCHAR}, #{attachmentId,jdbcType=VARCHAR},
) #{fileMetadataRefId,jdbcType=VARCHAR})
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.AttachmentModuleRelation"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.AttachmentModuleRelation">
insert into attachment_module_relation insert into attachment_module_relation
@ -105,6 +106,9 @@
<if test="attachmentId != null"> <if test="attachmentId != null">
attachment_id, attachment_id,
</if> </if>
<if test="fileMetadataRefId != null">
file_metadata_ref_id,
</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="relationId != null"> <if test="relationId != null">
@ -116,6 +120,9 @@
<if test="attachmentId != null"> <if test="attachmentId != null">
#{attachmentId,jdbcType=VARCHAR}, #{attachmentId,jdbcType=VARCHAR},
</if> </if>
<if test="fileMetadataRefId != null">
#{fileMetadataRefId,jdbcType=VARCHAR},
</if>
</trim> </trim>
</insert> </insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultType="java.lang.Long"> <select id="countByExample" parameterType="io.metersphere.base.domain.AttachmentModuleRelationExample" resultType="java.lang.Long">
@ -136,6 +143,9 @@
<if test="record.attachmentId != null"> <if test="record.attachmentId != null">
attachment_id = #{record.attachmentId,jdbcType=VARCHAR}, attachment_id = #{record.attachmentId,jdbcType=VARCHAR},
</if> </if>
<if test="record.fileMetadataRefId != null">
file_metadata_ref_id = #{record.fileMetadataRefId,jdbcType=VARCHAR},
</if>
</set> </set>
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
@ -145,7 +155,8 @@
update attachment_module_relation update attachment_module_relation
set relation_id = #{record.relationId,jdbcType=VARCHAR}, set relation_id = #{record.relationId,jdbcType=VARCHAR},
relation_type = #{record.relationType,jdbcType=VARCHAR}, relation_type = #{record.relationType,jdbcType=VARCHAR},
attachment_id = #{record.attachmentId,jdbcType=VARCHAR} attachment_id = #{record.attachmentId,jdbcType=VARCHAR},
file_metadata_ref_id = #{record.fileMetadataRefId,jdbcType=VARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
</if> </if>

View File

@ -3,10 +3,10 @@
<mapper namespace="io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper"> <mapper namespace="io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper">
<insert id="batchInsert" parameterType="java.util.List"> <insert id="batchInsert" parameterType="java.util.List">
INSERT INTO INSERT INTO
attachment_module_relation (relation_id, relation_type, attachment_id) attachment_module_relation (relation_id, relation_type, attachment_id, file_metadata_ref_id)
VALUES VALUES
<foreach collection="attachmentModuleRelations" item="relation" separator="," > <foreach collection="attachmentModuleRelations" item="relation" separator="," >
(#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId}) (#{relation.relationId}, #{relation.relationType}, #{relation.attachmentId}, #{relation.fileMetadataRefId})
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum FileAssociationType { public enum FileAssociationType {
API, CASE, SCENARIO, UI, ENVIRONMENT API, CASE, SCENARIO, UI, ENVIRONMENT, TEST_CASE, ISSUE
} }

View File

@ -0,0 +1,8 @@
package io.metersphere.metadata.vo;
import lombok.Data;
@Data
public class AttachmentDumpRequest extends DumpFileRequest{
private String attachmentId;
}

View File

@ -8,6 +8,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*; import io.metersphere.commons.utils.*;
import io.metersphere.performance.request.QueryProjectFileRequest; import io.metersphere.performance.request.QueryProjectFileRequest;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import io.metersphere.xmind.utils.FileUtil;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -50,6 +51,12 @@ public class FileService {
return FileUtils.fileToByte(attachmentFile); return FileUtils.fileToByte(attachmentFile);
} }
public MultipartFile getAttachmentMultipartFile(String id) {
FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(id);
File attachmentFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName());
return FileUtil.fileToMultipartFile(attachmentFile);
}
public FileContent getFileContent(String fileId) { public FileContent getFileContent(String fileId) {
return fileContentMapper.selectByPrimaryKey(fileId); return fileContentMapper.selectByPrimaryKey(fileId);
} }

View File

@ -1,6 +1,8 @@
package io.metersphere.track.controller; package io.metersphere.track.controller;
import io.metersphere.base.domain.FileAttachmentMetadata; import io.metersphere.base.domain.FileAttachmentMetadata;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.metadata.vo.AttachmentDumpRequest;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.attachment.AttachmentRequest;
import io.metersphere.track.request.testplan.FileOperationRequest; import io.metersphere.track.request.testplan.FileOperationRequest;
@ -14,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -27,15 +30,23 @@ public class AttachmentController {
private FileService fileService; private FileService fileService;
@Resource @Resource
private AttachmentService attachmentService; private AttachmentService attachmentService;
@Resource
private FileMetadataService fileMetadataService;
@PostMapping(value = "/upload", consumes = {"multipart/form-data"}) @PostMapping(value = "/upload", consumes = {"multipart/form-data"})
public void uploadAttachment(@RequestPart("request") AttachmentRequest request, @RequestPart(value = "file", required = false) MultipartFile file) { public void uploadAttachment(@RequestPart("request") AttachmentRequest request, @RequestPart(value = "file", required = false) MultipartFile file) {
attachmentService.uploadAttachment(request, file); attachmentService.uploadAttachment(request, file);
} }
@GetMapping("/preview/{fileId}") @GetMapping("/preview/{fileId}/{isLocal}")
public ResponseEntity<byte[]> previewAttachment(@PathVariable String fileId) { public ResponseEntity<byte[]> previewAttachment(@PathVariable String fileId, @PathVariable Boolean isLocal) {
byte[] bytes = fileService.getAttachmentBytes(fileId); byte[] bytes;
if (isLocal) {
bytes = fileService.getAttachmentBytes(fileId);
} else {
String refId = attachmentService.getRefIdByAttachmentId(fileId);
bytes = fileMetadataService.loadFileAsBytes(refId);
}
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream")) .contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileId + "\"") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileId + "\"")
@ -44,7 +55,13 @@ public class AttachmentController {
@PostMapping("/download") @PostMapping("/download")
public ResponseEntity<byte[]> downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) { public ResponseEntity<byte[]> downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) {
byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); byte[] bytes;
if (fileOperationRequest.getIsLocal()) {
bytes = fileService.getAttachmentBytes(fileOperationRequest.getId());
} else {
String refId = attachmentService.getRefIdByAttachmentId(fileOperationRequest.getId());
bytes = fileMetadataService.loadFileAsBytes(refId);
}
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream")) .contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"")
@ -61,4 +78,22 @@ public class AttachmentController {
public List<FileAttachmentMetadata> listMetadata(@RequestBody AttachmentRequest request) { public List<FileAttachmentMetadata> listMetadata(@RequestBody AttachmentRequest request) {
return attachmentService.listMetadata(request); return attachmentService.listMetadata(request);
} }
@PostMapping("/metadata/relate")
public void relate(@RequestBody AttachmentRequest request) {
attachmentService.relate(request);
}
@PostMapping("/metadata/unrelated")
public void unrelated(@RequestBody AttachmentRequest request) {
attachmentService.unrelated(request);
}
@PostMapping(value = "/metadata/dump")
public void dumpFile(@RequestBody AttachmentDumpRequest request) {
List<MultipartFile> files = new ArrayList<>();
MultipartFile file = fileService.getAttachmentMultipartFile(request.getAttachmentId());
files.add(file);
fileMetadataService.dumpFile(request, files);
}
} }

View File

@ -1,6 +1,5 @@
package io.metersphere.track.issue; package io.metersphere.track.issue;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;

View File

@ -2,6 +2,8 @@ package io.metersphere.track.request.attachment;
import lombok.Data; import lombok.Data;
import java.util.List;
/** /**
* @author songcc * @author songcc
@ -14,4 +16,6 @@ public class AttachmentRequest {
private String belongId; private String belongId;
private String copyBelongId; private String copyBelongId;
private List<String> metadataRefIds;
} }

View File

@ -28,6 +28,10 @@ public class EditTestCaseRequest extends TestCaseWithBLOBs {
private String copyCaseId; private String copyCaseId;
// 是否处理附件文件 // 是否处理附件文件
private boolean handleAttachment = true; private boolean handleAttachment = true;
// 关联文件管理引用ID
private List<String> relateFileMetaIds = new ArrayList<>();
// 取消关联文件应用ID
private List<String> unRelateFileMetaIds = new ArrayList<>();
/** /**
* 创建新版本时 是否连带复制其他信息的配置类 * 创建新版本时 是否连带复制其他信息的配置类

View File

@ -8,6 +8,7 @@ import io.metersphere.track.dto.PlatformStatusDTO;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Getter @Getter
@ -53,4 +54,8 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs {
* 复制缺陷时原始缺陷ID * 复制缺陷时原始缺陷ID
*/ */
private String copyIssueId; private String copyIssueId;
// 关联文件管理引用ID
private List<String> relateFileMetaIds = new ArrayList<>();
// 取消关联文件应用ID
private List<String> unRelateFileMetaIds = new ArrayList<>();
} }

View File

@ -8,4 +8,5 @@ import lombok.Setter;
public class FileOperationRequest { public class FileOperationRequest {
private String id; private String id;
private String name; private String name;
private Boolean isLocal;
} }

View File

@ -5,24 +5,31 @@ import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper; import io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper;
import io.metersphere.commons.constants.AttachmentSyncType; import io.metersphere.commons.constants.AttachmentSyncType;
import io.metersphere.commons.constants.AttachmentType; import io.metersphere.commons.constants.AttachmentType;
import io.metersphere.commons.constants.FileAssociationType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.track.issue.IssueFactory; import io.metersphere.track.issue.IssueFactory;
import io.metersphere.track.request.attachment.AttachmentRequest; import io.metersphere.track.request.attachment.AttachmentRequest;
import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -48,6 +55,14 @@ public class AttachmentService {
private AttachmentModuleRelationMapper attachmentModuleRelationMapper; private AttachmentModuleRelationMapper attachmentModuleRelationMapper;
@Resource @Resource
private ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper; private ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileMetadataService fileMetadataService;
@Resource
SqlSessionFactory sqlSessionFactory;
public void uploadAttachment(AttachmentRequest request, MultipartFile file) { public void uploadAttachment(AttachmentRequest request, MultipartFile file) {
// 附件上传的前置校验 // 附件上传的前置校验
@ -129,25 +144,162 @@ public class AttachmentService {
example.createCriteria().andRelationIdEqualTo(request.getCopyBelongId()).andRelationTypeEqualTo(request.getBelongType()); example.createCriteria().andRelationIdEqualTo(request.getCopyBelongId()).andRelationTypeEqualTo(request.getBelongType());
List<AttachmentModuleRelation> attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example); List<AttachmentModuleRelation> attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(attachmentModuleRelations)) { if (CollectionUtils.isNotEmpty(attachmentModuleRelations)) {
attachmentModuleRelations.forEach(attachmentModuleRelation -> { // 本地附件
FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(attachmentModuleRelation.getAttachmentId(), request.getBelongType(), request.getBelongId()); List<String> localAttachments = attachmentModuleRelations.stream()
.filter(relation -> StringUtils.isEmpty(relation.getFileMetadataRefId()))
.map(AttachmentModuleRelation::getAttachmentId)
.filter(StringUtils::isNotEmpty).collect(Collectors.toList());
localAttachments.forEach(localAttachmentId -> {
FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(localAttachmentId, request.getBelongType(), request.getBelongId());
AttachmentModuleRelation record = new AttachmentModuleRelation(); AttachmentModuleRelation record = new AttachmentModuleRelation();
record.setRelationId(request.getBelongId()); record.setRelationId(request.getBelongId());
record.setRelationType(request.getBelongType()); record.setRelationType(request.getBelongType());
record.setAttachmentId(fileAttachmentMetadata.getId()); record.setAttachmentId(fileAttachmentMetadata.getId());
attachmentModuleRelationMapper.insert(record); attachmentModuleRelationMapper.insert(record);
}); });
// 文件管理关联附件
List<AttachmentModuleRelation> refAttachments = attachmentModuleRelations.stream()
.filter(relation -> StringUtils.isNotEmpty(relation.getFileMetadataRefId())).collect(Collectors.toList());
refAttachments.forEach(refAttachment -> {
refAttachment.setRelationId(request.getBelongId());
attachmentModuleRelationMapper.insert(refAttachment);
// 缺陷类型的附件, 关联时需单独同步第三方平台
if (AttachmentType.ISSUE.type().equals(request.getBelongType())) {
String metadataRefId = getRefIdByAttachmentId(refAttachment.getAttachmentId());
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(metadataRefId);
IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId());
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
}
});
} }
} }
public List<FileAttachmentMetadata> listMetadata(AttachmentRequest request) { public List<FileAttachmentMetadata> listMetadata(AttachmentRequest request) {
List<String> attachmentIds = getAttachmentIdsByParam(request); List<FileAttachmentMetadata> attachments = new ArrayList<FileAttachmentMetadata>();
if (CollectionUtils.isEmpty(attachmentIds)) { AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
return new ArrayList<>(); example.createCriteria().andRelationIdEqualTo(request.getBelongId()).andRelationTypeEqualTo(request.getBelongType());
List<AttachmentModuleRelation> attachmentModuleRelations = attachmentModuleRelationMapper.selectByExample(example);
Map<String, String> relationMap = attachmentModuleRelations.stream()
.collect(Collectors.toMap(AttachmentModuleRelation::getAttachmentId,
relation -> relation.getFileMetadataRefId() == null ? "" : relation.getFileMetadataRefId()));
List<String> attachmentIds = attachmentModuleRelations.stream().map(AttachmentModuleRelation::getAttachmentId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(attachmentIds)) {
FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample();
fileExample.createCriteria().andIdIn(attachmentIds);
List<FileAttachmentMetadata> fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByExample(fileExample);
fileAttachmentMetadata.forEach(file -> {
String fileRefId = relationMap.get(file.getId());
if (StringUtils.isEmpty(fileRefId)) {
file.setIsLocal(Boolean.TRUE);
file.setIsRelatedDeleted(Boolean.FALSE);
} else {
file.setIsLocal(Boolean.FALSE);
FileAssociation fileAssociation = fileAssociationMapper.selectByPrimaryKey(fileRefId);
if (fileAssociation != null) {
// 关联文件信息同步
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(fileAssociation.getFileMetadataId());
file.setIsRelatedDeleted(Boolean.FALSE);
file.setName(fileMetadata.getName());
file.setSize(fileMetadata.getSize());
file.setCreator(fileMetadata.getCreateUser());
file.setCreateTime(fileMetadata.getCreateTime());
} else {
file.setIsRelatedDeleted(Boolean.TRUE);
}
}
});
attachments.addAll(fileAttachmentMetadata);
} }
FileAttachmentMetadataExample fileExample = new FileAttachmentMetadataExample(); return attachments;
fileExample.createCriteria().andIdIn(attachmentIds); }
return fileAttachmentMetadataMapper.selectByExample(fileExample);
public void relate(AttachmentRequest request) {
// 批量关联文件管理
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class);
AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class);
FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class);
if (CollectionUtils.isNotEmpty(request.getMetadataRefIds())) {
request.getMetadataRefIds().forEach(metadataRefId -> {
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(metadataRefId);
FileAssociation fileAssociation = new FileAssociation();
fileAssociation.setId(UUID.randomUUID().toString());
fileAssociation.setFileMetadataId(metadataRefId);
fileAssociation.setFileType(fileMetadata.getType());
if (AttachmentType.TEST_CASE.type().equals(request.getBelongType())) {
fileAssociation.setType(FileAssociationType.TEST_CASE.name());
} else {
fileAssociation.setType(FileAssociationType.ISSUE.name());
}
fileAssociation.setProjectId(fileMetadata.getProjectId());
fileAssociation.setSourceItemId(metadataRefId);
fileAssociation.setSourceId(request.getBelongId());
associationBatchMapper.insert(fileAssociation);
AttachmentModuleRelation record = new AttachmentModuleRelation();
record.setRelationId(request.getBelongId());
record.setRelationType(request.getBelongType());
record.setFileMetadataRefId(fileAssociation.getId());
record.setAttachmentId(UUID.randomUUID().toString());
attachmentModuleRelationBatchMapper.insert(record);
FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata();
BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata);
fileAttachmentMetadata.setId(record.getAttachmentId());
fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser());
fileAttachmentMetadata.setFilePath(fileMetadata.getPath());
fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata);
// 缺陷类型的附件, 关联时需单独同步第三方平台
if (AttachmentType.ISSUE.type().equals(request.getBelongType())) {
IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId());
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
}
});
sqlSession.flushStatements();
if (sqlSession != null && sqlSessionFactory != null) {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
}
public void unrelated(AttachmentRequest request) {
// 缺陷类型的附件, 取消关联时同步第三方平台
if (AttachmentType.ISSUE.type().equals(request.getBelongType())) {
IssuesWithBLOBs issues = issuesMapper.selectByPrimaryKey(request.getBelongId());
request.getMetadataRefIds().forEach(metadataRefId -> {
FileAttachmentMetadata fileAttachmentMetadata = fileAttachmentMetadataMapper.selectByPrimaryKey(metadataRefId);
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File deleteFile = new File(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileAttachmentMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE);
});
}
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andRelationIdEqualTo(request.getBelongId())
.andRelationTypeEqualTo(request.getBelongType())
.andAttachmentIdIn(request.getMetadataRefIds());
FileAttachmentMetadataExample exampleAttachment = new FileAttachmentMetadataExample();
exampleAttachment.createCriteria().andIdIn(request.getMetadataRefIds());
fileAttachmentMetadataMapper.deleteByExample(exampleAttachment);
attachmentModuleRelationMapper.deleteByExample(example);
} }
public List<String> getAttachmentIdsByParam(AttachmentRequest request) { public List<String> getAttachmentIdsByParam(AttachmentRequest request) {
@ -159,6 +311,15 @@ public class AttachmentService {
return attachmentIds; return attachmentIds;
} }
public String getRefIdByAttachmentId(String attachmentId) {
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andAttachmentIdEqualTo(attachmentId);
List<AttachmentModuleRelation> relations = attachmentModuleRelationMapper.selectByExample(example);
String associationId = relations.get(0).getFileMetadataRefId();
FileAssociation fileAssociation = fileAssociationMapper.selectByPrimaryKey(associationId);
return fileAssociation.getFileMetadataId();
}
public void initAttachment() { public void initAttachment() {
List<AttachmentModuleRelation> attachmentModuleRelations = new ArrayList<>(); List<AttachmentModuleRelation> attachmentModuleRelations = new ArrayList<>();
List<IssueFile> issueFiles = issueFileMapper.selectByExample(new IssueFileExample()); List<IssueFile> issueFiles = issueFileMapper.selectByExample(new IssueFileExample());
@ -183,4 +344,9 @@ public class AttachmentService {
} }
extAttachmentModuleRelationMapper.batchInsert(attachmentModuleRelations); extAttachmentModuleRelationMapper.batchInsert(attachmentModuleRelations);
} }
public File downloadMetadataFile(String fileMetadataRefId, String filename) {
byte[] refFileBytes = fileMetadataService.loadFileAsBytes(fileMetadataRefId);
return FileUtils.byteToFile(refFileBytes, FileUtils.ATTACHMENT_TMP_DIR, filename);
}
} }

View File

@ -37,6 +37,10 @@ import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -44,6 +48,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -91,11 +96,15 @@ public class IssuesService {
@Resource @Resource
IssueFileMapper issueFileMapper; IssueFileMapper issueFileMapper;
@Resource @Resource
SqlSessionFactory sqlSessionFactory;
@Resource
private AttachmentService attachmentService; private AttachmentService attachmentService;
@Resource @Resource
private CustomFieldService customFieldService; private CustomFieldService customFieldService;
@Resource @Resource
private ProjectMapper projectMapper; private ProjectMapper projectMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC"; private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
@ -129,9 +138,9 @@ public class IssuesService {
attachmentRequest.setBelongType(AttachmentType.ISSUE.type()); attachmentRequest.setBelongType(AttachmentType.ISSUE.type());
attachmentService.copyAttachment(attachmentRequest); attachmentService.copyAttachment(attachmentRequest);
} else { } else {
final String issueId = issues.getId();
// 新增, 需保存并同步所有待上传的附件 // 新增, 需保存并同步所有待上传的附件
if (CollectionUtils.isNotEmpty(files)) { if (CollectionUtils.isNotEmpty(files)) {
final String issueId = issues.getId();
files.forEach(file -> { files.forEach(file -> {
AttachmentRequest attachmentRequest = new AttachmentRequest(); AttachmentRequest attachmentRequest = new AttachmentRequest();
attachmentRequest.setBelongId(issueId); attachmentRequest.setBelongId(issueId);
@ -139,6 +148,48 @@ public class IssuesService {
attachmentService.uploadAttachment(attachmentRequest, file); attachmentService.uploadAttachment(attachmentRequest, file);
}); });
} }
// 处理待关联的文件附件, 生成关联记录, 并同步至第三方平台
if (CollectionUtils.isNotEmpty(issuesRequest.getRelateFileMetaIds())) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class);
AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class);
FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class);
issuesRequest.getRelateFileMetaIds().forEach(filemetaId -> {
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(filemetaId);
FileAssociation fileAssociation = new FileAssociation();
fileAssociation.setId(UUID.randomUUID().toString());
fileAssociation.setFileMetadataId(filemetaId);
fileAssociation.setFileType(fileMetadata.getType());
fileAssociation.setType(FileAssociationType.ISSUE.name());
fileAssociation.setProjectId(fileMetadata.getProjectId());
fileAssociation.setSourceItemId(filemetaId);
fileAssociation.setSourceId(issueId);
associationBatchMapper.insert(fileAssociation);
AttachmentModuleRelation relation = new AttachmentModuleRelation();
relation.setRelationId(issueId);
relation.setRelationType(AttachmentType.ISSUE.type());
relation.setFileMetadataRefId(fileAssociation.getId());
relation.setAttachmentId(UUID.randomUUID().toString());
attachmentModuleRelationBatchMapper.insert(relation);
FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata();
BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata);
fileAttachmentMetadata.setId(relation.getAttachmentId());
fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser());
fileAttachmentMetadata.setFilePath(fileMetadata.getPath());
fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata);
// 下载文件管理文件, 同步到第三方平台
File refFile = attachmentService.downloadMetadataFile(filemetaId, fileMetadata.getName());
IssuesRequest addIssueRequest = new IssuesRequest();
addIssueRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
Objects.requireNonNull(IssueFactory.createPlatform(issuesRequest.getPlatform(), addIssueRequest))
.syncIssuesAttachment(issuesRequest, refFile, AttachmentSyncType.UPLOAD);
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
});
sqlSession.flushStatements();
if (sqlSession != null && sqlSessionFactory != null) {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
} }
return getIssue(issues.getId()); return getIssue(issues.getId());
} }

View File

@ -19,6 +19,7 @@ import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.domain.ext.CustomFieldResource; import io.metersphere.base.domain.ext.CustomFieldResource;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtAttachmentModuleRelationMapper;
import io.metersphere.base.mapper.ext.ExtIssuesMapper; import io.metersphere.base.mapper.ext.ExtIssuesMapper;
import io.metersphere.base.mapper.ext.ExtProjectVersionMapper; import io.metersphere.base.mapper.ext.ExtProjectVersionMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper; import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
@ -135,6 +136,8 @@ public class TestCaseService {
@Resource @Resource
AttachmentModuleRelationMapper attachmentModuleRelationMapper; AttachmentModuleRelationMapper attachmentModuleRelationMapper;
@Resource @Resource
ExtAttachmentModuleRelationMapper extAttachmentModuleRelationMapper;
@Resource
private LoadTestMapper loadTestMapper; private LoadTestMapper loadTestMapper;
@Resource @Resource
private ApiScenarioMapper apiScenarioMapper; private ApiScenarioMapper apiScenarioMapper;
@ -2101,6 +2104,41 @@ public class TestCaseService {
attachmentService.uploadAttachment(attachmentRequest, file); attachmentService.uploadAttachment(attachmentRequest, file);
}); });
} }
// 同步待关联的文件附件, 生成关联记录
if (CollectionUtils.isNotEmpty(request.getRelateFileMetaIds())) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
FileAssociationMapper associationBatchMapper = sqlSession.getMapper(FileAssociationMapper.class);
AttachmentModuleRelationMapper attachmentModuleRelationBatchMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class);
FileAttachmentMetadataMapper fileAttachmentMetadataBatchMapper = sqlSession.getMapper(FileAttachmentMetadataMapper.class);
request.getRelateFileMetaIds().forEach(filemetaId -> {
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(filemetaId);
FileAssociation fileAssociation = new FileAssociation();
fileAssociation.setId(UUID.randomUUID().toString());
fileAssociation.setFileMetadataId(filemetaId);
fileAssociation.setFileType(fileMetadata.getType());
fileAssociation.setType(FileAssociationType.TEST_CASE.name());
fileAssociation.setProjectId(fileMetadata.getProjectId());
fileAssociation.setSourceItemId(filemetaId);
fileAssociation.setSourceId(testCaseWithBLOBs.getId());
associationBatchMapper.insert(fileAssociation);
AttachmentModuleRelation record = new AttachmentModuleRelation();
record.setRelationId(testCaseWithBLOBs.getId());
record.setRelationType(AttachmentType.TEST_CASE.type());
record.setFileMetadataRefId(fileAssociation.getId());
record.setAttachmentId(UUID.randomUUID().toString());
attachmentModuleRelationBatchMapper.insert(record);
FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata();
BeanUtils.copyBean(fileAttachmentMetadata, fileMetadata);
fileAttachmentMetadata.setId(record.getAttachmentId());
fileAttachmentMetadata.setCreator(fileMetadata.getCreateUser());
fileAttachmentMetadata.setFilePath(fileMetadata.getPath());
fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata);
});
sqlSession.flushStatements();
if (sqlSession != null && sqlSessionFactory != null) {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
} }
return testCaseWithBLOBs; return testCaseWithBLOBs;
} }

View File

@ -1,12 +1,13 @@
package io.metersphere.xmind.utils; package io.metersphere.xmind.utils;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.File; import java.io.*;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/** /**
* 工具类 * 工具类
@ -47,6 +48,23 @@ public class FileUtil {
return null; return null;
} }
/**
* File MultipartFile
* @param file
* @return
*/
public static MultipartFile fileToMultipartFile(File file) {
DiskFileItem item = new DiskFileItem("file", MediaType.MULTIPART_FORM_DATA_VALUE, true,
file.getName(), (int)file.length(), file.getParentFile());
try {
OutputStream os = item.getOutputStream();
os.write(FileUtils.fileToByte(file));
} catch (IOException e) {
e.printStackTrace();
}
return new CommonsMultipartFile(item);
}
public static boolean deleteDir(File dir) { public static boolean deleteDir(File dir) {
if (dir.isDirectory()) { if (dir.isDirectory()) {
String[] children = dir.list(); String[] children = dir.list();

View File

@ -151,3 +151,8 @@ ALTER TABLE `file_metadata` ADD INDEX `INDEX_REF_ID`(`ref_id`);
ALTER TABLE `ui_scenario` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`; ALTER TABLE `ui_scenario` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`;
ALTER TABLE `ui_scenario_module` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`; ALTER TABLE `ui_scenario_module` ADD COLUMN scenario_type VARCHAR(100) NOT NULL DEFAULT 'scenario' COMMENT 'Scenario type' AFTER `level`;
-- 功能用例, 缺陷管理文件管理关联字段
ALTER TABLE `attachment_module_relation` MODIFY COLUMN attachment_id VARCHAR(50) NULL;
ALTER TABLE `attachment_module_relation` ADD COLUMN file_metadata_ref_id VARCHAR(50) DEFAULT NULL COMMENT 'FILE ASSOCIATION ID';

View File

@ -2,13 +2,22 @@
<div> <div>
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col> <el-col>
<el-table class="basic-config" :data="tableData"> <el-table class="basic-config" :data="tableData" :row-class-name="handleIsRelated">
<el-table-column <el-table-column
prop="name" prop="name"
:label="$t('load_test.file_name')"> :label="$t('load_test.file_name')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-tooltip class="item" effect="dark" :content="scope.row.name" placement="top"> <el-tooltip class="item" effect="dark" :content="scope.row.name" placement="top">
<el-progress <el-progress
class="row-delete-name"
type="line"
v-if="!scope.row.isLocal && scope.row.isRelatedDeleted"
:stroke-width="40"
:text-inside="true"
:format="clearPercentage(scope.row)">
</el-progress>
<el-progress
v-else
:color="scope.row.progress >= 100 ? '' : uploadProgressColor" :color="scope.row.progress >= 100 ? '' : uploadProgressColor"
type="line" type="line"
:format="clearPercentage(scope.row)" :format="clearPercentage(scope.row)"
@ -37,7 +46,7 @@
:width="70" :width="70"
:label="$t('commons.status')"> :label="$t('commons.status')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span :class="scope.row.status === 'success' ? 'green' : scope.row.status === 'error' ? 'red' : scope.row.status === 'toUpload' ? 'yellow' : ''">{{ scope.row.status | formatStatus}}</span> <span :class="scope.row.status === 'expired' ? 'lightgrey' : scope.row.status === 'success' ? 'green' : scope.row.status === 'error' ? 'red' : scope.row.status === 'toUpload' || 'toRelate' ? 'yellow' : ''">{{ formatStatus(scope.row.status) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -46,19 +55,27 @@
:label="$t('group.operator')"> :label="$t('group.operator')">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
:width="140" :width="180"
:label="$t('commons.operating')"> :label="$t('commons.operating')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-button @click="preview(scope.row)" :disabled="!scope.row.id" type="primary" <el-button @click="preview(scope.row)" type="primary" :disabled="!scope.row.id || scope.row.status === 'toRelate' || scope.row.isRelatedDeleted"
v-if="scope.row.progress === 100 && isPreview(scope.row)" v-if="scope.row.progress === 100 && isPreview(scope.row)"
icon="el-icon-view" size="mini" circle/> icon="el-icon-view" size="mini" circle/>
<el-button @click="handleDownload(scope.row)" type="primary" :disabled="!scope.row.id" <el-button @click="handleDownload(scope.row)" type="primary" :disabled="!scope.row.id || scope.row.status === 'toRelate' || scope.row.isRelatedDeleted"
v-if="scope.row.progress === 100" v-if="scope.row.progress === 100"
icon="el-icon-download" size="mini" circle/> icon="el-icon-download" size="mini" circle/>
<el-button :disabled="isCopy || !scope.row.id"
@click="handleUpload(scope.row)" type="primary"
v-if="scope.row.progress === 100 && scope.row.isLocal"
icon="el-icon-upload" size="mini" circle/>
<el-button :disabled="readOnly || !isDelete || isCopy || (!scope.row.id && scope.row.status !== 'toUpload')" <el-button :disabled="readOnly || !isDelete || isCopy || (!scope.row.id && scope.row.status !== 'toUpload')"
@click="handleDelete(scope.row, scope.$index)" type="danger" @click="handleDelete(scope.row, scope.$index)" type="danger"
v-if="scope.row.progress === 100" v-if="scope.row.progress === 100 && scope.row.isLocal"
icon="el-icon-delete" size="mini" icon="el-icon-delete" size="mini" circle/>
<el-button :disabled="readOnly || !isDelete || isCopy || (!scope.row.id && scope.row.status !== 'toRelate')"
@click="handleUnRelate(scope.row, scope.$index)" type="danger"
v-if="scope.row.progress === 100 && !scope.row.isLocal"
icon="el-icon-unlock" size="mini"
circle/> circle/>
<el-button :disabled="readOnly || !isDelete" @click="handleCancel(scope.row, scope.$index)" type="danger" <el-button :disabled="readOnly || !isDelete" @click="handleCancel(scope.row, scope.$index)" type="danger"
v-if="scope.row.progress < 100" v-if="scope.row.progress < 100"
@ -100,10 +117,14 @@ export default {
data() { data() {
return { return {
uploadProgressColor: '#d4f6d4', uploadProgressColor: '#d4f6d4',
uploadSuccessColor: '#FFFFFF' uploadSuccessColor: '#FFFFFF',
that: null,
} }
}, },
methods: { methods: {
handleIsRelated(row, rowIndex) {
return row.row.isRelatedDeleted ? 'delete-row' : '';
},
clearPercentage(row) { clearPercentage(row) {
return () => { return () => {
return row.name; return row.name;
@ -120,19 +141,35 @@ export default {
this.$fileDownloadPost('/attachment/download', { this.$fileDownloadPost('/attachment/download', {
name: file.name, name: file.name,
id: file.id, id: file.id,
isLocal: file.isLocal
}); });
}, },
handleUpload(file) {
this.$emit("handleDump", file);
},
handleDelete(file, index) { handleDelete(file, index) {
this.$emit("handleDelete", file, index); this.$emit("handleDelete", file, index);
}, },
handleUnRelate(file, index) {
this.$emit("handleUnRelate", file, index);
},
handleCancel(file, index) { handleCancel(file, index) {
this.$emit("handleCancel", file, index); this.$emit("handleCancel", file, index);
}, },
},
filters: {
formatStatus(status) { formatStatus(status) {
if (isNaN(status)) { if (isNaN(status)) {
return status === 'success' ? '完成' : status === 'toUpload' ? '待上传' : '失败' switch (status) {
case 'success':
return this.$t('commons.file_upload_status.success');
case 'toUpload':
return this.$t('commons.file_upload_status.to_upload');
case 'toRelate':
return this.$t('commons.file_upload_status.to_relate');
case 'expired':
return this.$t('commons.file_upload_status.expired');
default:
return this.$t('commons.file_upload_status.error');
}
} }
return Math.floor(status * 100 / 100) + "%"; return Math.floor(status * 100 / 100) + "%";
} }
@ -145,6 +182,10 @@ export default {
color: black; color: black;
} }
::v-deep .el-progress.row-delete-name .el-progress-bar__innerText {
color: lightgrey!important;
}
::v-deep .el-progress-bar__outer, ::v-deep .el-progress-bar__outer,
::v-deep .el-progress-bar__inner { ::v-deep .el-progress-bar__inner {
border-radius: inherit ; border-radius: inherit ;
@ -169,4 +210,14 @@ export default {
.yellow { .yellow {
color: #E6A23C; color: #E6A23C;
} }
.lightgrey {
color: lightgrey;
}
</style>
<style>
.el-table .delete-row {
color: lightgrey;
}
</style> </style>

View File

@ -742,6 +742,9 @@ export default {
if (this.validate(param)) { if (this.validate(param)) {
let option = this.getOption(param); let option = this.getOption(param);
this.result = this.$request(option, (response) => { this.result = this.$request(option, (response) => {
//
this.currentTestCaseInfo.isCopy = false;
this.$refs.otherInfo.getFileMetaData(response.data.id);
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.path = "/test/case/edit"; this.path = "/test/case/edit";
// this.operationType = "edit" // this.operationType = "edit"
@ -767,8 +770,6 @@ export default {
if (callback) { if (callback) {
callback(this); callback(this);
} }
//
// //
if (hasLicense()) { if (hasLicense()) {
this.getVersionHistory(); this.getVersionHistory();
@ -810,6 +811,12 @@ export default {
if (this.selectedOtherInfo) { if (this.selectedOtherInfo) {
param.otherInfoConfig = this.selectedOtherInfo; param.otherInfoConfig = this.selectedOtherInfo;
} }
if (this.$refs.otherInfo.relateFiles.length > 0) {
param.relateFileMetaIds = this.$refs.otherInfo.relateFiles;
}
if (this.$refs.otherInfo.unRelateFiles.length > 0) {
param.unRelateFileMetaIds = this.$refs.otherInfo.unRelateFiles;
}
return param; return param;
}, },
parseOldFields(param) { parseOldFields(param) {

View File

@ -60,23 +60,33 @@
<el-tab-pane :label="$t('test_track.case.attachment')" name="attachment"> <el-tab-pane :label="$t('test_track.case.attachment')" name="attachment">
<el-row> <el-row>
<el-col :span="22"> <el-col :span="22" style="margin-bottom: 10px;">
<el-upload <div class="upload-default" @click.stop>
multiple <el-popover placement="right" trigger="hover">
:limit="8" <div>
action="" <el-upload
:auto-upload="true" multiple
:file-list="fileList" :limit="8"
:show-file-list="false" action=""
:before-upload="beforeUpload" :auto-upload="true"
:http-request="handleUpload" :file-list="fileList"
:on-exceed="handleExceed" :show-file-list="false"
:on-success="handleSuccess" :before-upload="beforeUpload"
:on-error="handleError" :http-request="handleUpload"
:disabled="readOnly || isCopy"> :on-exceed="handleExceed"
<el-button :disabled="readOnly || isCopy" type="primary" size="mini">{{$t('test_track.case.add_attachment')}}</el-button> :on-success="handleSuccess"
:on-error="handleError"
:disabled="readOnly || isCopy">
<el-button :disabled="readOnly || isCopy" type="text">{{$t('permission.project_file.local_upload')}}</el-button>
</el-upload>
</div>
<el-button type="text" :disabled="readOnly || isCopy" @click="associationFile">{{ $t('permission.project_file.associated_files') }}</el-button>
<i class="el-icon-plus" slot="reference"/>
</el-popover>
</div>
<div :class="readOnly ? 'testplan-local-upload-tip' : 'not-testplan-local-upload-tip'">
<span slot="tip" class="el-upload__tip"> {{ $t('test_track.case.upload_tip') }} </span> <span slot="tip" class="el-upload__tip"> {{ $t('test_track.case.upload_tip') }} </span>
</el-upload> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
@ -86,6 +96,8 @@
:is-copy="isCopy" :is-copy="isCopy"
:is-delete="!isTestPlan" :is-delete="!isTestPlan"
@handleDelete="handleDelete" @handleDelete="handleDelete"
@handleUnRelate="handleUnRelate"
@handleDump="handleDump"
@handleCancel="handleCancel"/> @handleCancel="handleCancel"/>
</el-col> </el-col>
</el-row> </el-row>
@ -121,6 +133,8 @@
</el-col> </el-col>
</el-row> </el-row>
</el-tab-pane> </el-tab-pane>
<ms-file-metadata-list ref="metadataList" @checkRows="checkRows"/>
<ms-file-batch-move ref="module" @setModuleId="setModuleId"/>
</el-tabs> </el-tabs>
</template> </template>
@ -137,10 +151,12 @@ import TabPaneCount from "@/business/components/track/plan/view/comonents/report
import {getRelationshipCountCase} from "@/network/testCase"; import {getRelationshipCountCase} from "@/network/testCase";
import TestCaseComment from "@/business/components/track/case/components/TestCaseComment"; import TestCaseComment from "@/business/components/track/case/components/TestCaseComment";
import ReviewCommentItem from "@/business/components/track/review/commom/ReviewCommentItem"; import ReviewCommentItem from "@/business/components/track/review/commom/ReviewCommentItem";
import {byteToSize, getTypeByFileName, hasLicense} from "@/common/js/utils"; import {byteToSize, getCurrentProjectID, getTypeByFileName, getUUID, hasLicense} from "@/common/js/utils";
import {TokenKey} from "@/common/js/constants"; import {TokenKey} from "@/common/js/constants";
import axios from "axios"; import axios from "axios";
import {validateAndSetLicense} from "@/business/permission"; import {validateAndSetLicense} from "@/business/permission";
import MsFileMetadataList from "@/business/components/project/menu/file/quote/QuoteFileList";
import MsFileBatchMove from "@/business/components/project/menu/file/module/FileBatchMove";
export default { export default {
name: "TestCaseEditOtherInfo", name: "TestCaseEditOtherInfo",
@ -150,6 +166,8 @@ export default {
TestCaseTestRelate, TestCaseTestRelate,
TestCaseComment, TestCaseComment,
ReviewCommentItem, ReviewCommentItem,
MsFileMetadataList,
MsFileBatchMove,
FormRichTextItem, TestCaseIssueRelate, TestCaseAttachment, MsRichText, TestCaseRichText FormRichTextItem, TestCaseIssueRelate, TestCaseAttachment, MsRichText, TestCaseRichText
}, },
props: ['form', 'labelWidth', 'caseId', 'readOnly', 'projectId', 'isTestPlan', 'planId', 'versionEnable', 'isCopy', 'copyCaseId', props: ['form', 'labelWidth', 'caseId', 'readOnly', 'projectId', 'isTestPlan', 'planId', 'versionEnable', 'isCopy', 'copyCaseId',
@ -174,7 +192,10 @@ export default {
}, },
intervalMap: new Map(), intervalMap: new Map(),
cancelFileToken: [], cancelFileToken: [],
uploadFiles: [] uploadFiles: [],
relateFiles: [],
unRelateFiles: [],
dumpFile: {},
}; };
}, },
computed: { computed: {
@ -262,7 +283,8 @@ export default {
progress: this.type === 'add' ? 100 : 0, progress: this.type === 'add' ? 100 : 0,
status: this.type === 'add' ? 'toUpload' : 0, status: this.type === 'add' ? 'toUpload' : 0,
creator: user.name, creator: user.name,
type: getTypeByFileName(file.name) type: getTypeByFileName(file.name),
isLocal: true
}); });
if (this.type === 'add') { if (this.type === 'add') {
@ -365,7 +387,8 @@ export default {
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
this.tableData.splice(index, 1); this.tableData.splice(index, 1);
if (this.type === 'add') { if (this.type === 'add') {
this.uploadFiles.splice(index, 1); let delIndex = this.uploadFiles.findIndex(uploadFile => uploadFile.name === file.name)
this.uploadFiles.splice(delIndex, 1);
} else { } else {
this.$get('/attachment/delete/testcase/' + file.id , response => { this.$get('/attachment/delete/testcase/' + file.id , response => {
this.$success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
@ -373,6 +396,36 @@ export default {
}); });
} }
}, },
handleUnRelate(file, index) {
//
this.$alert(this.$t('load_test.unrelated_file_confirm') + file.name + "?", '', {
confirmButtonText: this.$t('commons.confirm'),
dangerouslyUseHTMLString: true,
callback: (action) => {
if (action === 'confirm') {
let unRelateFileIndex = this.tableData.findIndex(f => f.name === file.name);
this.tableData.splice(unRelateFileIndex, 1);
if (file.status === 'toRelate') {
// ,
let unRelateId = this.relateFiles.findIndex(f => f === file.id);
this.relateFiles.splice(unRelateId, 1);
} else {
//
this.unRelateFiles.push(file.id);
let data = {'belongType': 'testcase', 'belongId': this.caseId, 'metadataRefIds': this.unRelateFiles};
this.$post('/attachment/metadata/unrelated', data, response => {
this.$success(this.$t('commons.unrelated_success'));
this.getFileMetaData();
});
}
}
}
});
},
handleDump(file) {
this.$refs.module.init();
this.dumpFile = file;
},
handleCancel(file, index) { handleCancel(file, index) {
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0]; let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0];
@ -382,13 +435,17 @@ export default {
cancelFile.status = 'error'; cancelFile.status = 'error';
}, },
getFileMetaData(id) { getFileMetaData(id) {
if (this.type === 'edit') {
this.relateFiles = [];
this.unRelateFiles = [];
}
this.$emit("update:isClickAttachmentTab", true); this.$emit("update:isClickAttachmentTab", true);
// id // id
this.fileList = []; this.fileList = [];
this.tableData = []; this.tableData = [];
let testCaseId; let testCaseId;
if (this.isCopy) { if (this.isCopy) {
testCaseId = this.copyCaseId testCaseId = id ? id : this.copyCaseId
} else { } else {
testCaseId = id ? id : this.caseId; testCaseId = id ? id : this.caseId;
} }
@ -404,12 +461,61 @@ export default {
this.tableData = JSON.parse(JSON.stringify(files)); this.tableData = JSON.parse(JSON.stringify(files));
this.tableData.map(f => { this.tableData.map(f => {
f.size = byteToSize(f.size); f.size = byteToSize(f.size);
f.status = 'success'; f.status = f.isRelatedDeleted ? 'expired' : 'success';
f.progress = 100 f.progress = 100
}); });
}); });
} }
}, },
associationFile() {
this.$refs.metadataList.open();
},
checkRows(rows) {
let repeatRecord = false;
for (let row of rows) {
let rowIndex = this.tableData.findIndex(item => item.name === row.name);
if (rowIndex >= 0) {
this.$error(this.$t('load_test.exist_related_file') + ": " + row.name);
repeatRecord = true;
break;
}
}
if (!repeatRecord) {
if (this.type === 'add') {
//
rows.forEach(row => {
this.relateFiles.push(row.id);
this.tableData.push({
id: row.id,
name: row.name,
size: byteToSize(row.size),
updateTime: row.createTime,
progress: 100,
status: 'toRelate',
creator: row.createUser,
type: row.type,
isLocal: false,
});
})
} else {
//
let metadataRefIds = [];
rows.forEach(row => metadataRefIds.push(row.id));
let data = {'belongType': 'testcase', 'belongId': this.caseId, 'metadataRefIds': metadataRefIds};
this.$post('/attachment/metadata/relate', data, response => {
this.$success(this.$t('commons.relate_success'));
this.getFileMetaData();
});
}
}
},
setModuleId(moduleId) {
let data = {id: getUUID(), resourceId: getCurrentProjectID(), moduleId: moduleId,
projectId: getCurrentProjectID(), fileName: this.dumpFile.name, attachmentId: this.dumpFile.id};
this.$post("/attachment/metadata/dump", data, (response) => {
this.$success(this.$t("organization.integration.successful_operation"));
});
},
getRelatedTest() { getRelatedTest() {
this.$refs.relateTest.initTable(); this.$refs.relateTest.initTable();
}, },
@ -510,4 +616,45 @@ export default {
.demandInput { .demandInput {
width: 200px; width: 200px;
} }
.el-icon-plus {
font-size: 16px;
}
.upload-default {
background-color: #fbfdff;
border: 1px dashed #c0ccda;
border-radius: 6px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 40px;
height: 30px;
line-height: 32px;
vertical-align: top;
text-align: center;
cursor: pointer;
display: inline-block;
}
.upload-default i {
color: #8c939d;
}
.upload-default:hover {
border: 1px dashed #783887;
}
.testplan-local-upload-tip {
display: inline-block;
position: relative;
left: 25px;
top: -5px;
}
.not-testplan-local-upload-tip {
display: inline-block;
position: relative;
left: 25px;
top: 8px;
}
</style> </style>

View File

@ -1,10 +1,10 @@
<template> <template>
<el-dialog :visible.sync="dialogVisible" width="80%" :destroy-on-close="true" :before-close="close" :append-to-body="true"> <el-dialog :visible.sync="dialogVisible" width="80%" :destroy-on-close="true" :before-close="close" :append-to-body="true">
<div> <div>
<img :src="'/attachment/preview/' + file.id" :alt="$t('test_track.case.img_loading_fail')" style="width: 100%;height: 100%;" <img :src="'/attachment/preview/' + file.id + '/' + file.isLocal" :alt="$t('test_track.case.img_loading_fail')" style="width: 100%;height: 100%;"
v-if="file.type === 'JPG' || file.type === 'JPEG' || file.type === 'PNG'"> v-if="file.type === 'JPG' || file.type === 'JPEG' || file.type === 'PNG'">
<div v-if="file.type === 'PDF'"> <div v-if="file.type === 'PDF'">
<test-case-pdf :file-id="file.id"/> <test-case-pdf :file-id="file.id" :is-local="file.isLocal"/>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>

View File

@ -10,7 +10,8 @@ export default {
name: "TestCasePdf", name: "TestCasePdf",
components: {pdf}, components: {pdf},
props: { props: {
fileId: String fileId: String,
isLocal: Boolean
}, },
data() { data() {
return { return {
@ -21,7 +22,7 @@ export default {
}, },
mounted() { mounted() {
this.loading = true; this.loading = true;
this.loadingTask = pdf.createLoadingTask("/attachment/preview/" + this.fileId); this.loadingTask = pdf.createLoadingTask("/attachment/preview/" + this.fileId + "/" + this.isLocal);
this.loadingTask.promise.then(pdf => { this.loadingTask.promise.then(pdf => {
this.numPages = pdf.numPages this.numPages = pdf.numPages
this.loading = false; this.loading = false;

View File

@ -102,23 +102,33 @@
<el-tab-pane :label="$t('test_track.case.attachment')" name="attachment"> <el-tab-pane :label="$t('test_track.case.attachment')" name="attachment">
<el-row> <el-row>
<el-col :span="22"> <el-col :span="22" style="margin-bottom: 10px;">
<el-upload <div class="upload-default" @click.stop>
multiple <el-popover placement="right" trigger="hover">
:limit="8" <div>
action="" <el-upload
:auto-upload="true" multiple
:file-list="fileList" :limit="8"
:show-file-list="false" action=""
:before-upload="beforeUpload" :auto-upload="true"
:http-request="handleUpload" :file-list="fileList"
:on-exceed="handleExceed" :show-file-list="false"
:on-success="handleSuccess" :before-upload="beforeUpload"
:on-error="handleError" :http-request="handleUpload"
:disabled="type === 'copy'"> :on-exceed="handleExceed"
<el-button type="primary" :disabled="type === 'copy'" size="mini">{{$t('test_track.case.add_attachment')}}</el-button> :on-success="handleSuccess"
:on-error="handleError"
:disabled="readOnly || type === 'copy'">
<el-button :disabled="readOnly || type === 'copy'" type="text">{{$t('permission.project_file.local_upload')}}</el-button>
</el-upload>
</div>
<el-button type="text" :disabled="readOnly || type === 'copy'" @click="associationFile">{{ $t('permission.project_file.associated_files') }}</el-button>
<i class="el-icon-plus" slot="reference"/>
</el-popover>
</div>
<div class="local-upload-tips">
<span slot="tip" class="el-upload__tip"> {{ $t('test_track.case.upload_tip') }} </span> <span slot="tip" class="el-upload__tip"> {{ $t('test_track.case.upload_tip') }} </span>
</el-upload> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-row style="margin-top: 10px"> <el-row style="margin-top: 10px">
@ -128,7 +138,9 @@
:is-delete="isDelete" :is-delete="isDelete"
:is-copy="type === 'copy'" :is-copy="type === 'copy'"
@handleDelete="handleDelete" @handleDelete="handleDelete"
@handleCancel="handleCancel"/> @handleCancel="handleCancel"
@handleUnRelate="handleUnRelate"
@handleDump="handleDump"/>
</el-col> </el-col>
</el-row> </el-row>
</el-tab-pane> </el-tab-pane>
@ -170,9 +182,10 @@
<issue-comment :issues-id="form.id" <issue-comment :issues-id="form.id"
@getComments="getComments" @getComments="getComments"
ref="issueComment"/> ref="issueComment"/>
<ms-file-metadata-list ref="metadataList" @checkRows="checkRows"/>
<ms-file-batch-move ref="module" @setModuleId="setModuleId"/>
</el-form> </el-form>
</el-scrollbar> </el-scrollbar>
</el-main> </el-main>
</template> </template>
@ -193,7 +206,7 @@ import {
getCurrentProjectID, getCurrentProjectID,
getCurrentUser, getCurrentUser,
getCurrentUserId, getCurrentUserId,
getCurrentWorkspaceId, getTypeByFileName, hasLicense, getCurrentWorkspaceId, getTypeByFileName, getUUID, hasLicense,
} from "@/common/js/utils"; } from "@/common/js/utils";
import {enableThirdPartTemplate, getIssuePartTemplateWithProject, getPlatformTransitions} from "@/network/Issue"; import {enableThirdPartTemplate, getIssuePartTemplateWithProject, getPlatformTransitions} from "@/network/Issue";
import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem"; import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem";
@ -204,6 +217,8 @@ import {TokenKey} from "@/common/js/constants";
import {Message} from "element-ui"; import {Message} from "element-ui";
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment"; import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
import axios from "axios"; import axios from "axios";
import MsFileMetadataList from "@/business/components/project/menu/file/quote/QuoteFileList";
import MsFileBatchMove from "@/business/components/project/menu/file/module/FileBatchMove";
const {getIssuesById} = require("@/network/Issue"); const {getIssuesById} = require("@/network/Issue");
@ -222,7 +237,9 @@ export default {
MsMarkDownText, MsMarkDownText,
IssueComment, IssueComment,
ReviewCommentItem, ReviewCommentItem,
TestCaseAttachment TestCaseAttachment,
MsFileMetadataList,
MsFileBatchMove,
}, },
data() { data() {
return { return {
@ -308,7 +325,10 @@ export default {
readOnly: false, readOnly: false,
isDelete: true, isDelete: true,
cancelFileToken: [], cancelFileToken: [],
uploadFiles: [] uploadFiles: [],
relateFiles: [],
unRelateFiles: [],
dumpFile: {},
}; };
}, },
props: { props: {
@ -362,6 +382,7 @@ export default {
} }
}, },
open(data, type) { open(data, type) {
this.uploadFiles = [];
this.tabActiveName = 'relateTestCase' this.tabActiveName = 'relateTestCase'
this.showFollow = false; this.showFollow = false;
this.result.loading = true; this.result.loading = true;
@ -546,8 +567,10 @@ export default {
} }
param.withoutTestCaseIssue = this.isMinder; param.withoutTestCaseIssue = this.isMinder;
param.thirdPartPlatform = this.enableThirdPartTemplate; param.thirdPartPlatform = this.enableThirdPartTemplate;
if (this.relateFiles.length > 0) {
param.relateFileMetaIds = this.relateFiles;
}
return param; return param;
}, },
_save() { _save() {
@ -651,7 +674,8 @@ export default {
progress: this.type === 'add' || this.isCaseEdit? 100 : 0, progress: this.type === 'add' || this.isCaseEdit? 100 : 0,
status: this.type === 'add' || this.isCaseEdit? 'toUpload' : 0, status: this.type === 'add' || this.isCaseEdit? 'toUpload' : 0,
creator: user.name, creator: user.name,
type: getTypeByFileName(file.name) type: getTypeByFileName(file.name),
isLocal: true
}); });
if (this.type === 'add' || this.isCaseEdit) { if (this.type === 'add' || this.isCaseEdit) {
@ -753,7 +777,8 @@ export default {
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
this.tableData.splice(index, 1); this.tableData.splice(index, 1);
if (this.type === 'add' || this.isCaseEdit) { if (this.type === 'add' || this.isCaseEdit) {
this.uploadFiles.splice(index, 1); let delIndex = this.uploadFiles.findIndex(uploadFile => uploadFile.name === file.name)
this.uploadFiles.splice(delIndex, 1);
} else { } else {
this.$get('/attachment/delete/issue/' + file.id , response => { this.$get('/attachment/delete/issue/' + file.id , response => {
this.$success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
@ -761,6 +786,38 @@ export default {
}); });
} }
}, },
handleUnRelate(file, index) {
//
this.$alert(this.$t('load_test.unrelated_file_confirm') + file.name + "?", '', {
confirmButtonText: this.$t('commons.confirm'),
dangerouslyUseHTMLString: true,
callback: (action) => {
if (action === 'confirm') {
let unRelateFileIndex = this.tableData.findIndex(f => f.name === file.name);
this.tableData.splice(unRelateFileIndex, 1);
if (file.status === 'toRelate') {
// ,
let unRelateId = this.relateFiles.findIndex(f => f === file.id);
this.relateFiles.splice(unRelateId, 1);
} else {
//
this.unRelateFiles.push(file.id);
let data = {'belongType': 'issue', 'belongId': this.issueId, 'metadataRefIds': this.unRelateFiles};
this.result.loading = true;
this.$post('/attachment/metadata/unrelated', data, response => {
this.$success(this.$t('commons.unrelated_success'));
this.result.loading = false;
this.getFileMetaData(this.issueId);
});
}
}
}
});
},
handleDump(file) {
this.$refs.module.init();
this.dumpFile = file;
},
handleCancel(file, index) { handleCancel(file, index) {
this.fileList.splice(index, 1); this.fileList.splice(index, 1);
let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0]; let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0];
@ -769,7 +826,63 @@ export default {
cancelFile.progress = 100; cancelFile.progress = 100;
cancelFile.status = 'error'; cancelFile.status = 'error';
}, },
associationFile() {
this.$refs.metadataList.open();
},
checkRows(rows) {
let repeatRecord = false;
for (let row of rows) {
let rowIndex = this.tableData.findIndex(item => item.name === row.name);
if (rowIndex >= 0) {
this.$error(this.$t('load_test.exist_related_file') + ": " + row.name);
repeatRecord = true;
break;
}
}
if (!repeatRecord) {
if (this.type === 'add') {
//
rows.forEach(row => {
this.relateFiles.push(row.id);
this.tableData.push({
id: row.id,
name: row.name,
size: byteToSize(row.size),
updateTime: row.createTime,
progress: 100,
status: 'toRelate',
creator: row.createUser,
type: row.type,
isLocal: false,
});
})
} else {
//
let metadataRefIds = [];
rows.forEach(row => metadataRefIds.push(row.id));
let data = {'belongType': 'issue', 'belongId': this.issueId, 'metadataRefIds': metadataRefIds};
this.result.loading = true;
this.$post('/attachment/metadata/relate', data, response => {
this.$success(this.$t('commons.relate_success'));
this.result.loading = false;
this.getFileMetaData(this.issueId);
});
}
}
},
setModuleId(moduleId) {
let data = {id: getUUID(), resourceId: getCurrentProjectID(), moduleId: moduleId,
projectId: getCurrentProjectID(), fileName: this.dumpFile.name, attachmentId: this.dumpFile.id};
this.$post("/attachment/metadata/dump", data, (response) => {
this.$success(this.$t("organization.integration.successful_operation"));
});
},
getFileMetaData(id) { getFileMetaData(id) {
if (this.type === 'edit') {
this.uploadFiles = [];
this.relateFiles = [];
this.unRelateFiles = [];
}
// id // id
this.fileList = []; this.fileList = [];
this.tableData = []; this.tableData = [];
@ -838,4 +951,38 @@ export default {
font-size: xx-small; font-size: xx-small;
border-radius: 50%; border-radius: 50%;
} }
.el-icon-plus {
font-size: 16px;
}
.upload-default {
background-color: #fbfdff;
border: 1px dashed #c0ccda;
border-radius: 6px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 40px;
height: 30px;
line-height: 32px;
vertical-align: top;
text-align: center;
cursor: pointer;
display: inline-block;
}
.upload-default i {
color: #8c939d;
}
.upload-default:hover {
border: 1px dashed #783887;
}
.local-upload-tips {
display: inline-block;
position: relative;
left: 25px;
top: 8px;
}
</style> </style>

View File

@ -72,6 +72,8 @@ export default {
warning_module_add: "Tree modules are up to 8 levels deep", warning_module_add: "Tree modules are up to 8 levels deep",
send_success: 'Send successfully', send_success: 'Send successfully',
delete_success: 'Deleted successfully', delete_success: 'Deleted successfully',
relate_success: 'Related successfully',
unrelated_success: 'Unrelated successfully',
modify_success: 'Modify Success', modify_success: 'Modify Success',
copy_success: 'Copy Success', copy_success: 'Copy Success',
delete_cancel: 'Deleted Cancel', delete_cancel: 'Deleted Cancel',
@ -188,6 +190,13 @@ export default {
month: "Month", month: "Month",
year: "Year" year: "Year"
}, },
file_upload_status: {
success: 'Success',
to_upload: 'To upload',
to_relate: 'To be associated',
expired: 'Expired',
error: 'Error'
},
test_unit: 'tests', test_unit: 'tests',
remove: 'Remove', remove: 'Remove',
next_level: "Next level", next_level: "Next level",
@ -1098,6 +1107,7 @@ export default {
related_file_not_found: "No related test file found!", related_file_not_found: "No related test file found!",
delete_file_when_uploading: 'The current operation may interrupt the file being uploaded!', delete_file_when_uploading: 'The current operation may interrupt the file being uploaded!',
delete_file_confirm: 'Confirm delete file:', delete_file_confirm: 'Confirm delete file:',
unrelated_file_confirm: 'Confirm unrelated file: ',
file_size_out_of_bounds: "File size out of bounds, file name: ", file_size_out_of_bounds: "File size out of bounds, file name: ",
file_size_limit: "The number of files exceeds the limit", file_size_limit: "The number of files exceeds the limit",
delete_file: "The file already exists, please delete the file with the same name first!", delete_file: "The file already exists, please delete the file with the same name first!",
@ -1160,6 +1170,7 @@ export default {
report_type: 'Report type', report_type: 'Report type',
upload_jmx: 'Upload JMX', upload_jmx: 'Upload JMX',
exist_jmx: 'Existed Files', exist_jmx: 'Existed Files',
exist_related_file: 'Existed Relate Files',
other_resource: 'Resource Files', other_resource: 'Resource Files',
upload_file: 'Upload Files', upload_file: 'Upload Files',
load_exist_file: 'Load Project Files', load_exist_file: 'Load Project Files',

View File

@ -137,7 +137,7 @@ export default {
cancel_relevance_project: "Disassociating the project will also cancel the associated test cases under the project", cancel_relevance_project: "Disassociating the project will also cancel the associated test cases under the project",
img_loading_fail: "Image failed to load", img_loading_fail: "Image failed to load",
pdf_loading_fail: "PDF loading failed", pdf_loading_fail: "PDF loading failed",
upload_tip: "file size limit[0-500MB]", upload_tip: "Local upload file size is limited to [0-500MB]",
add_attachment: "Add", add_attachment: "Add",
attachment: "Attachment", attachment: "Attachment",
upload_time: "Upload Time", upload_time: "Upload Time",

View File

@ -128,7 +128,7 @@ export default {
cancel_relevance_project: "取消项目关联会同时取消该项目下已关联的测试用例", cancel_relevance_project: "取消项目关联会同时取消该项目下已关联的测试用例",
img_loading_fail: "图片加载失败", img_loading_fail: "图片加载失败",
pdf_loading_fail: "PDF加载失败", pdf_loading_fail: "PDF加载失败",
upload_tip: "文件大小限制[0-500MB]", upload_tip: "本地上传, 文件大小限制[0-500MB]",
add_attachment: "添加", add_attachment: "添加",
attachment: "附件", attachment: "附件",
upload_time: "上传时间", upload_time: "上传时间",

View File

@ -128,7 +128,7 @@ export default {
cancel_relevance_project: "取消項目關聯會同時取消該項目下已關聯的測試用例", cancel_relevance_project: "取消項目關聯會同時取消該項目下已關聯的測試用例",
img_loading_fail: "圖片加載失敗", img_loading_fail: "圖片加載失敗",
pdf_loading_fail: "PDF加載失敗", pdf_loading_fail: "PDF加載失敗",
upload_tip: "文件大小限制[0-500MB]", upload_tip: "本地上傳, 文件大小限制[0-500MB]",
add_attachment: "添加", add_attachment: "添加",
attachment: "附件", attachment: "附件",
upload_time: "上傳時間", upload_time: "上傳時間",

View File

@ -71,6 +71,8 @@ export default {
warning_module_add: "模块树深度最大为8层", warning_module_add: "模块树深度最大为8层",
send_success: '发送成功', send_success: '发送成功',
delete_success: '删除成功', delete_success: '删除成功',
relate_success: '关联成功',
unrelated_success: '取消关联成功',
copy_success: '复制成功', copy_success: '复制成功',
modify_success: '修改成功', modify_success: '修改成功',
delete_cancel: '已取消删除', delete_cancel: '已取消删除',
@ -182,6 +184,13 @@ export default {
month: "月", month: "月",
year: "年" year: "年"
}, },
file_upload_status: {
success: '完成',
to_upload: '待上传',
to_relate: '待关联',
expired: '已失效',
error: '失败'
},
test_unit: '测试', test_unit: '测试',
system_parameter_setting: '系统参数设置', system_parameter_setting: '系统参数设置',
connection_successful: '连接成功', connection_successful: '连接成功',
@ -1106,6 +1115,7 @@ export default {
related_file_not_found: "未找到关联的测试文件!", related_file_not_found: "未找到关联的测试文件!",
delete_file_when_uploading: '当前操作可能会中断正在上传的文件!', delete_file_when_uploading: '当前操作可能会中断正在上传的文件!',
delete_file_confirm: '确认删除文件: ', delete_file_confirm: '确认删除文件: ',
unrelated_file_confirm: '确认取消关联: ',
file_size_limit: "文件个数超出限制!", file_size_limit: "文件个数超出限制!",
file_size_out_of_bounds: "文件大小超出范围, 文件名称: ", file_size_out_of_bounds: "文件大小超出范围, 文件名称: ",
delete_file: "文件已存在,请先删除同名文件!", delete_file: "文件已存在,请先删除同名文件!",
@ -1171,6 +1181,7 @@ export default {
report_type: "报告类型", report_type: "报告类型",
upload_jmx: '上传 JMX 文件', upload_jmx: '上传 JMX 文件',
exist_jmx: '已存在的文件', exist_jmx: '已存在的文件',
exist_related_file: '已存在的关联文件',
other_resource: '资源文件', other_resource: '资源文件',
upload_file: '上传新文件', upload_file: '上传新文件',
load_exist_file: '加载文件', load_exist_file: '加载文件',

View File

@ -70,6 +70,8 @@ export default {
save_success: '保存成功', save_success: '保存成功',
send_success: '發送成功', send_success: '發送成功',
delete_success: '刪除成功', delete_success: '刪除成功',
relate_success: '關聯成功',
unrelated_success: '取消關聯成功',
copy_success: '復製成功', copy_success: '復製成功',
warning_module_add: "模塊樹深度最大為8層", warning_module_add: "模塊樹深度最大為8層",
modify_success: '修改成功', modify_success: '修改成功',
@ -182,6 +184,13 @@ export default {
month: "月", month: "月",
year: "年" year: "年"
}, },
file_upload_status: {
success: '完成',
to_upload: '待上傳',
to_relate: '待關聯',
expired: '已失效',
error: '失敗'
},
test_unit: '測試', test_unit: '測試',
system_parameter_setting: '系統參數設置', system_parameter_setting: '系統參數設置',
connection_successful: '連接成功', connection_successful: '連接成功',
@ -1102,6 +1111,7 @@ export default {
related_file_not_found: "未找到關聯的測試文件!", related_file_not_found: "未找到關聯的測試文件!",
delete_file_when_uploading: '當前操作可能會中斷正在上傳的文件!', delete_file_when_uploading: '當前操作可能會中斷正在上傳的文件!',
delete_file_confirm: '確認刪除文件: ', delete_file_confirm: '確認刪除文件: ',
unrelated_file_confirm: '確認取消關聯: ',
file_size_limit: "文件個數超出限製!", file_size_limit: "文件個數超出限製!",
file_size_out_of_bounds: "文件大小超出範圍, 文件名称: ", file_size_out_of_bounds: "文件大小超出範圍, 文件名称: ",
delete_file: "文件已存在,請先刪除同名文件!", delete_file: "文件已存在,請先刪除同名文件!",
@ -1167,6 +1177,7 @@ export default {
report_type: "报告类型", report_type: "报告类型",
upload_jmx: '上傳 JMX 文件', upload_jmx: '上傳 JMX 文件',
exist_jmx: '已存在的文件', exist_jmx: '已存在的文件',
exist_related_file: '已存在的關聯文件',
other_resource: '資源文件', other_resource: '資源文件',
upload_file: '上傳新文件', upload_file: '上傳新文件',
load_exist_file: '加載文件', load_exist_file: '加載文件',