From c25795e4627a5e14ec50de0942d67edd0e2081d6 Mon Sep 17 00:00:00 2001 From: song-cc-rock Date: Thu, 14 Jul 2022 18:34:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=B7=9F=E8=B8=AA):=20?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E5=8A=9F=E8=83=BD=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1006991 --user=宋昌昌 【测试跟踪】功能用例&缺陷增加附件功能支持视频文件(1.20分支同步上) https://www.tapd.cn/55049933/s/1202348 --- .../io/metersphere/service/FileService.java | 17 +- .../track/controller/IssuesController.java | 24 ++- .../track/controller/TestCaseController.java | 31 ++- .../track/issue/IssuesPlatform.java | 5 +- .../metersphere/track/issue/JiraPlatform.java | 79 +++----- .../track/issue/LocalPlatform.java | 5 +- .../metersphere/track/issue/TapdPlatform.java | 5 +- .../track/issue/ZentaoPlatform.java | 5 +- .../track/service/IssuesService.java | 104 +++------- .../track/service/TestCaseIssueService.java | 1 + .../track/service/TestCaseService.java | 97 +++++----- .../resources/i18n/messages_en_US.properties | 3 + .../resources/i18n/messages_zh_CN.properties | 3 + .../resources/i18n/messages_zh_TW.properties | 3 + .../case/components/TestCaseAttachment.vue | 25 +-- .../track/case/components/TestCaseEdit.vue | 18 -- .../case/components/TestCaseEditOtherInfo.vue | 156 +++++++++------ .../track/issue/IssueEditDetail.vue | 180 +++++++++++------- .../track/issue/TestCaseIssueList.vue | 6 + frontend/src/common/js/utils.js | 10 + frontend/src/i18n/en-US.js | 5 +- frontend/src/i18n/zh-CN.js | 5 +- frontend/src/i18n/zh-TW.js | 5 +- 23 files changed, 423 insertions(+), 369 deletions(-) diff --git a/backend/src/main/java/io/metersphere/service/FileService.java b/backend/src/main/java/io/metersphere/service/FileService.java index 20ed439b95..007213083a 100644 --- a/backend/src/main/java/io/metersphere/service/FileService.java +++ b/backend/src/main/java/io/metersphere/service/FileService.java @@ -126,7 +126,7 @@ public class FileService { final FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata(); fileAttachmentMetadata.setId(UUID.randomUUID().toString()); fileAttachmentMetadata.setName(file.getOriginalFilename()); - fileAttachmentMetadata.setType(getFileType(fileAttachmentMetadata.getName()).name()); + fileAttachmentMetadata.setType(getFileTypeWithoutEnum(fileAttachmentMetadata.getName())); fileAttachmentMetadata.setSize(file.getSize()); fileAttachmentMetadata.setCreateTime(System.currentTimeMillis()); fileAttachmentMetadata.setUpdateTime(System.currentTimeMillis()); @@ -154,7 +154,7 @@ public class FileService { final FileAttachmentMetadata fileAttachmentMetadata = new FileAttachmentMetadata(); fileAttachmentMetadata.setId(UUID.randomUUID().toString()); fileAttachmentMetadata.setName(attachmentName); - fileAttachmentMetadata.setType(getFileType(attachmentName).name()); + fileAttachmentMetadata.setType(getFileTypeWithoutEnum(attachmentName)); fileAttachmentMetadata.setSize(Integer.valueOf(bytes.length).longValue()); fileAttachmentMetadata.setCreateTime(System.currentTimeMillis()); fileAttachmentMetadata.setUpdateTime(System.currentTimeMillis()); @@ -332,6 +332,15 @@ public class FileService { return FileType.valueOf(type.toUpperCase()); } + private String getFileTypeWithoutEnum(String filename) { + if (StringUtils.isEmpty(filename)) { + return ""; + } + int s = filename.lastIndexOf(".") + 1; + String type = filename.substring(s); + return type.toUpperCase(); + } + public List getFileMetadataByCaseId(String caseId) { TestCaseFileExample testCaseFileExample = new TestCaseFileExample(); testCaseFileExample.createCriteria().andCaseIdEqualTo(caseId); @@ -377,7 +386,9 @@ public class FileService { return fileAttachmentMetadataMapper.selectByExample(example); } - + public FileAttachmentMetadata getFileAttachmentMetadataByFileId(String fileId) { + return fileAttachmentMetadataMapper.selectByPrimaryKey(fileId); + } public void deleteFileById(String fileId) { deleteFileByIds(Collections.singletonList(fileId)); diff --git a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java index af5d2c13db..62b516b94f 100644 --- a/backend/src/main/java/io/metersphere/track/controller/IssuesController.java +++ b/backend/src/main/java/io/metersphere/track/controller/IssuesController.java @@ -27,6 +27,7 @@ import io.metersphere.track.request.testcase.AuthUserIssueRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.service.IssuesService; +import io.metersphere.track.service.TestCaseService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -64,22 +65,33 @@ public class IssuesController { return PageUtils.setPageInfo(page, issuesService.relateList(request)); } - @PostMapping(value = "/add", consumes = {"multipart/form-data"}) + @PostMapping(value = "/add") @RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ_CREATE) @MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.CREATE, content = "#msClass.getLogDetails(#issuesRequest)", msClass = IssuesService.class) @SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#issuesRequest", event = NoticeConstants.Event.CREATE, subject = "缺陷通知") - public IssuesWithBLOBs addIssues(@RequestPart(value = "request") IssuesUpdateRequest issuesRequest, @RequestPart(value = "file", required = false) List files) { - return issuesService.addIssuesRefactor(issuesRequest, files); + public IssuesWithBLOBs addIssues(@RequestPart(value = "request") IssuesUpdateRequest issuesRequest) { + return issuesService.addIssues(issuesRequest); } - @PostMapping(value = "/update", consumes = {"multipart/form-data"}) + @PostMapping(value = "/update") @RequiresPermissions(PermissionConstants.PROJECT_TRACK_ISSUE_READ_EDIT) @MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.UPDATE, beforeEvent = "#msClass.getLogDetails(#issuesRequest.id)", content = "#msClass.getLogDetails(#issuesRequest.id)", msClass = IssuesService.class) @SendNotice(taskType = NoticeConstants.TaskType.DEFECT_TASK, target = "#issuesRequest", event = NoticeConstants.Event.UPDATE, subject = "缺陷通知") - public void updateIssues(@RequestPart(value = "request") IssuesUpdateRequest issuesRequest, @RequestPart(value = "file", required = false) List files) { - issuesService.updateIssuesRefactor(issuesRequest, files); + public void updateIssues(@RequestPart(value = "request") IssuesUpdateRequest issuesRequest) { + issuesService.updateIssues(issuesRequest); + } + + @PostMapping(value = "/attachment/upload", consumes = {"multipart/form-data"}) + @MsAuditLog(module = OperLogModule.TRACK_BUG, type = OperLogConstants.IMPORT, beforeEvent = "#msClass.getLogDetails(#issuesRequest.id)", content = "#msClass.getLogDetails(#request.id)", msClass = TestCaseService.class) + public void uploadAttachment(@RequestPart("request") IssuesUpdateRequest issuesRequest, @RequestPart(value = "file", required = false) MultipartFile file) { + issuesService.uploadAttachment(issuesRequest, file); + } + + @GetMapping("/attachment/delete/{fileId}") + public void deleteAttachment(@PathVariable String fileId) { + issuesService.deleteAttachment(fileId); } @GetMapping("/file/attachmentMetadata/{issueId}") diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java index 3bae9c2646..27d8087ef8 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java @@ -385,15 +385,6 @@ public class TestCaseController { .body(bytes); } - @PostMapping("/attachment/download") - public ResponseEntity attachmentDownload(@RequestBody FileOperationRequest fileOperationRequest) { - byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") - .body(bytes); - } - @GetMapping("/file/preview/{fileId}") public ResponseEntity preview(@PathVariable String fileId) { byte[] bytes = fileService.loadFileAsBytes(fileId); @@ -403,8 +394,14 @@ public class TestCaseController { .body(bytes); } + @PostMapping(value = "/attachment/upload", consumes = {"multipart/form-data"}) + @MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.IMPORT, beforeEvent = "#msClass.getLogDetails(#request.id)", title = "#request.name", content = "#msClass.getLogDetails(#request.id)", msClass = TestCaseService.class) + public void uploadAttachment(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file", required = false) MultipartFile file) { + testCaseService.uploadAttachment(request, file); + } + @GetMapping("/attachment/preview/{fileId}") - public ResponseEntity attachmentPreview(@PathVariable String fileId) { + public ResponseEntity previewAttachment(@PathVariable String fileId) { byte[] bytes = fileService.getAttachmentBytes(fileId); return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/octet-stream")) @@ -412,6 +409,20 @@ public class TestCaseController { .body(bytes); } + @PostMapping("/attachment/download") + public ResponseEntity downloadAttachment(@RequestBody FileOperationRequest fileOperationRequest) { + byte[] bytes = fileService.getAttachmentBytes(fileOperationRequest.getId()); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileOperationRequest.getName(), StandardCharsets.UTF_8) + "\"") + .body(bytes); + } + + @GetMapping("/attachment/delete/{fileId}") + public void deleteAttachment(@PathVariable String fileId) { + testCaseService.deleteAttachment(fileId); + } + @PostMapping("/save") @MsAuditLog(module = OperLogModule.TRACK_TEST_CASE, type = OperLogConstants.CREATE, title = "#testCaseWithBLOBs.name", content = "#msClass.getLogDetails(#testCaseWithBLOBs.id)", msClass = TestCaseService.class) public TestCaseWithBLOBs saveTestCase(@RequestBody EditTestCaseRequest request) { diff --git a/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java index 4aee7446ac..57b1d84dd7 100644 --- a/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/IssuesPlatform.java @@ -11,7 +11,6 @@ import io.metersphere.track.request.testcase.EditTestCaseRequest; import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import org.springframework.http.ResponseEntity; -import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -32,13 +31,13 @@ public interface IssuesPlatform { * * @param issuesRequest issueRequest */ - IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List files); + IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest); /** * 更新缺陷 * @param request */ - void updateIssue(IssuesUpdateRequest request, List files); + void updateIssue(IssuesUpdateRequest request); /** * 删除缺陷平台缺陷 diff --git a/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java b/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java index 5f95f0fcb9..27b3b5d5f4 100644 --- a/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/JiraPlatform.java @@ -10,7 +10,6 @@ import io.metersphere.commons.constants.IssuesManagePlatform; import io.metersphere.commons.constants.IssuesStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.LogUtil; import io.metersphere.dto.CustomFieldDao; import io.metersphere.dto.CustomFieldItemDTO; @@ -31,10 +30,8 @@ import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.ResponseEntity; import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.multipart.MultipartFile; import java.io.File; -import java.io.IOException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -227,7 +224,7 @@ public class JiraPlatform extends AbstractIssuePlatform { } @Override - public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List files) { + public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) { setUserConfig(); Project project = getProject(); @@ -239,20 +236,6 @@ public class JiraPlatform extends AbstractIssuePlatform { // 上传附件 imageFiles.forEach(img -> jiraClientV2.uploadAttachment(result.getKey(), img)); - if (files != null) { - files.forEach(multipartFile -> { - try { - File file = new File(FileUtils.ATTACHMENT_TMP_DIR + "/" + multipartFile.getOriginalFilename()); - org.apache.commons.io.FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file); - jiraClientV2.uploadAttachment(result.getKey(), file); - if (file.exists()) { - file.delete(); - } - } catch (IOException e) { - LogUtil.error(e); - } - }); - } String status = getStatus(issues.getFields()); issuesRequest.setPlatformStatus(status); @@ -266,6 +249,21 @@ public class JiraPlatform extends AbstractIssuePlatform { // 用例与第三方缺陷平台中的缺陷关联 handleTestCaseIssues(issuesRequest); + // 如果是复制新增, 同步附件到第三方平台 + if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) { + IssueFileExample example = new IssueFileExample(); + example.createCriteria().andIssueIdEqualTo(issuesRequest.getCopyIssueId()); + List issueFiles = issueFileMapper.selectByExample(example); + if (issueFiles != null) { + issueFiles.forEach(issueFile -> { + FileAttachmentMetadata fileAttachmentMetadata = fileService.getFileAttachmentMetadataByFileId(issueFile.getFileId()); + // 同步第三方平台附件 + File file = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName()); + jiraClientV2.uploadAttachment(result.getKey(), file); + }); + } + } + return res; } @@ -479,7 +477,7 @@ public class JiraPlatform extends AbstractIssuePlatform { } @Override - public void updateIssue(IssuesUpdateRequest request, List files) { + public void updateIssue(IssuesUpdateRequest request) { setUserConfig(); Project project = getProject(); List imageFiles = getImageFiles(request); @@ -487,54 +485,40 @@ public class JiraPlatform extends AbstractIssuePlatform { JSONObject param = buildUpdateParam(request, getIssueType(project.getIssueConfig()), project.getJiraKey()); jiraClientV2.updateIssue(request.getPlatformId(), JSONObject.toJSONString(param)); - List updatedFiles = request.getUpdatedFileList(); - List updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList()); - List originFiles = fileService.getFileAttachmentMetadataByIssueId(request.getId()); - List deleteAttachmentNames = new ArrayList(); - originFiles.forEach(originFile -> { - if (!updatedFileIds.contains(originFile.getId())) { - deleteAttachmentNames.add(originFile.getName()); - } - }); + List newFiles = fileService.getFileAttachmentMetadataByIssueId(request.getId()); + List newFileNames = newFiles.stream().map(FileAttachmentMetadata::getName).collect(Collectors.toList()); Set attachmentNames = new HashSet<>(); - // 更新附件 + // 同步Jira平台附件 JiraIssue jiraIssue = jiraClientV2.getIssues(request.getPlatformId()); JSONObject fields = jiraIssue.getFields(); JSONArray attachments = fields.getJSONArray("attachment"); + // 删除旧附件,若缺陷描述中不存在且附件上传的删除列表中存在则删除 if (!attachments.isEmpty() && attachments.size() > 0) { - // 删除旧附件,若缺陷描述中不存在且附件上传的删除列表中存在则删除 for (int i = 0; i < attachments.size(); i++) { JSONObject attachment = attachments.getJSONObject(i); String filename = attachment.getString("filename"); attachmentNames.add(filename); - if (!request.getDescription().contains(filename) && deleteAttachmentNames.contains(filename)) { + if (!request.getDescription().contains(filename) && !newFileNames.contains(filename)) { String fileId = attachment.getString("id"); jiraClientV2.deleteAttachment(fileId); } } } - - // 上传新附件 + //上传新附件 + if (CollectionUtils.isNotEmpty(newFiles)) { + newFiles.forEach(file -> { + if (!attachmentNames.contains(file.getName())) { + File newFile = new File(file.getFilePath() + "/" + file.getName()); + jiraClientV2.uploadAttachment(request.getPlatformId(), newFile); + } + }); + } imageFiles.forEach(img -> { if (!attachmentNames.contains(img.getName())) { // 旧附件没有才上传新附件 jiraClientV2.uploadAttachment(request.getPlatformId(), img); } }); - if (files != null) { - files.forEach(multipartFile -> { - try { - File file = new File(FileUtils.ATTACHMENT_TMP_DIR + "/" + multipartFile.getOriginalFilename()); - org.apache.commons.io.FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file); - jiraClientV2.uploadAttachment(request.getPlatformId(), file); - if (file.exists()) { - file.delete(); - } - } catch (IOException e) { - LogUtil.error(e); - } - }); - } if (request.getTransitions() != null) { try { @@ -548,7 +532,6 @@ public class JiraPlatform extends AbstractIssuePlatform { LogUtil.error(e); } } - handleIssueUpdate(request); } diff --git a/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java b/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java index c517ffdcad..fe16dd1b1e 100644 --- a/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/LocalPlatform.java @@ -13,7 +13,6 @@ import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.request.testcase.TestCaseBatchRequest; import org.apache.commons.lang3.StringUtils; -import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.UUID; @@ -42,7 +41,7 @@ public class LocalPlatform extends LocalAbstractPlatform { } @Override - public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List files) { + public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) { String issueStatus = "new"; if (StringUtils.isNotBlank(issuesRequest.getCustomFields())) { List customFields = issuesRequest.getRequestFields(); @@ -76,7 +75,7 @@ public class LocalPlatform extends LocalAbstractPlatform { } @Override - public void updateIssue(IssuesUpdateRequest request, List files) { + public void updateIssue(IssuesUpdateRequest request) { handleIssueUpdate(request); } } diff --git a/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java b/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java index e1e017cc56..376513346b 100644 --- a/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/TapdPlatform.java @@ -30,7 +30,6 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.multipart.MultipartFile; import java.util.*; import java.util.stream.Collectors; @@ -91,7 +90,7 @@ public class TapdPlatform extends AbstractIssuePlatform { } @Override - public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List files) { + public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) { MultiValueMap param = buildUpdateParam(issuesRequest); TapdBug bug = tapdClient.addIssue(param); @@ -112,7 +111,7 @@ public class TapdPlatform extends AbstractIssuePlatform { } @Override - public void updateIssue(IssuesUpdateRequest request, List files) { + public void updateIssue(IssuesUpdateRequest request) { MultiValueMap param = buildUpdateParam(request); param.add("id", request.getPlatformId()); handleIssueUpdate(request); diff --git a/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java b/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java index 29f6228c43..b950a3b708 100644 --- a/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java +++ b/backend/src/main/java/io/metersphere/track/issue/ZentaoPlatform.java @@ -37,7 +37,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import org.springframework.web.multipart.MultipartFile; import java.net.URLDecoder; import java.net.URLEncoder; @@ -198,7 +197,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform { } @Override - public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest, List files) { + public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) { setUserConfig(); MultiValueMap param = buildUpdateParam(issuesRequest); @@ -229,7 +228,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform { } @Override - public void updateIssue(IssuesUpdateRequest request, List files) { + public void updateIssue(IssuesUpdateRequest request) { setUserConfig(); MultiValueMap param = buildUpdateParam(request); if (request.getTransitions() != null) { diff --git a/backend/src/main/java/io/metersphere/track/service/IssuesService.java b/backend/src/main/java/io/metersphere/track/service/IssuesService.java index b1466b6916..95d9ff8843 100644 --- a/backend/src/main/java/io/metersphere/track/service/IssuesService.java +++ b/backend/src/main/java/io/metersphere/track/service/IssuesService.java @@ -6,7 +6,6 @@ import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.metersphere.base.domain.*; -import io.metersphere.base.domain.ext.CustomFieldResource; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtIssuesMapper; import io.metersphere.commons.constants.AttachmentType; @@ -36,7 +35,6 @@ import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.request.testcase.TestCaseBatchRequest; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.StringRedisTemplate; @@ -108,7 +106,7 @@ public class IssuesService { List platformList = getAddPlatforms(issuesRequest); IssuesWithBLOBs issues = null; for (AbstractIssuePlatform platform : platformList) { - issues = platform.addIssue(issuesRequest, null); + issues = platform.addIssue(issuesRequest); } if (issuesRequest.getIsPlanEdit()) { issuesRequest.getAddResourceIds().forEach(l -> { @@ -116,39 +114,9 @@ public class IssuesService { }); } saveFollows(issuesRequest.getId(), issuesRequest.getFollows()); - List addFields = issuesRequest.getAddFields(); - addFields.addAll(issuesRequest.getEditFields()); - customFieldIssuesService.addFields(issuesRequest.getId(), addFields); - return issues; - } - - - public void updateIssues(IssuesUpdateRequest issuesRequest) { - issuesRequest.getId(); - List platformList = getUpdatePlatforms(issuesRequest); - platformList.forEach(platform -> { - platform.updateIssue(issuesRequest, null); - }); + customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields()); - customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); - // todo 缺陷更新事件? - } - - public IssuesWithBLOBs addIssuesRefactor(IssuesUpdateRequest issuesRequest, List files) { - List platformList = getAddPlatforms(issuesRequest); - IssuesWithBLOBs issues = null; - for (AbstractIssuePlatform platform : platformList) { - issues = platform.addIssue(issuesRequest, files); - } - if (issuesRequest.getIsPlanEdit()) { - issuesRequest.getAddResourceIds().forEach(l -> { - testCaseIssueService.updateIssuesCount(l); - }); - } - saveFollows(issuesRequest.getId(), issuesRequest.getFollows()); - customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); - - // copy的附件 + // copy附件 if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) { final String addIssueId = issues.getId(); IssueFileExample example = new IssueFileExample(); @@ -156,6 +124,7 @@ public class IssuesService { List issueFiles = issueFileMapper.selectByExample(example); if (issueFiles != null) { issueFiles.forEach(issueFile -> { + // 同步MS附件 FileAttachmentMetadata fileAttachmentMetadata = fileService.copyAttachment(issueFile.getFileId(), AttachmentType.ISSUE.type(), addIssueId); IssueFile newIssueFile = new IssueFile(); newIssueFile.setIssueId(addIssueId); @@ -164,57 +133,40 @@ public class IssuesService { }); } } - - // 新的附件 - if (files != null) { - files.forEach(file -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.ISSUE.type(), issuesRequest.getId()); - IssueFile issueFile = new IssueFile(); - issueFile.setIssueId(issuesRequest.getId()); - issueFile.setFileId(fileAttachmentMetadata.getId()); - issueFileMapper.insert(issueFile); - }); - } return issues; } - - public void updateIssuesRefactor(IssuesUpdateRequest issuesRequest, List files) { - issuesRequest.getId(); + public void updateIssues(IssuesUpdateRequest issuesRequest) { List platformList = getUpdatePlatforms(issuesRequest); platformList.forEach(platform -> { - platform.updateIssue(issuesRequest, files); + platform.updateIssue(issuesRequest); }); customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields()); customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields()); - // todo 缺陷更新事件? + } - // 删除原来的文件 - List updatedFiles = issuesRequest.getUpdatedFileList(); - List originFiles = fileService.getFileAttachmentMetadataByIssueId(issuesRequest.getId()); - List updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList()); - List originFileIds = originFiles.stream().map(FileAttachmentMetadata::getId).collect(Collectors.toList()); - // 获得差异的附件ID - List deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds); - // 删除差异附件记录, 目录下附件文件 - fileService.deleteAttachment(deleteFileIds); - fileService.deleteFileAttachmentByIds(deleteFileIds); - //删除用例文件关联记录 - if (!CollectionUtils.isEmpty(deleteFileIds)) { - IssueFileExample issueFileExample = new IssueFileExample(); - issueFileExample.createCriteria().andFileIdIn(deleteFileIds); - issueFileMapper.deleteByExample(issueFileExample); + public void uploadAttachment(IssuesUpdateRequest request, MultipartFile file) { + IssuesWithBLOBs issuesWithBLOBs = issuesMapper.selectByPrimaryKey(request.getId()); + if (issuesWithBLOBs == null) { + MSException.throwException(Translator.get("issues_attachment_upload_not_found") + request.getId()); } + FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.ISSUE.type(), request.getId()); + IssueFile issueFile = new IssueFile(); + issueFile.setIssueId(request.getId()); + issueFile.setFileId(fileAttachmentMetadata.getId()); + issueFileMapper.insert(issueFile); + } - // 保存新的附件 - if (files != null) { - files.forEach(file -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.ISSUE.type(), issuesRequest.getId()); - IssueFile issueFile = new IssueFile(); - issueFile.setIssueId(issuesRequest.getId()); - issueFile.setFileId(fileAttachmentMetadata.getId()); - issueFileMapper.insert(issueFile); - }); + public void deleteAttachment(String id) { + // 删除附件记录, 目录下附件文件 + if (StringUtils.isNotEmpty(id)) { + List ids = Arrays.asList(id); + fileService.deleteAttachment(ids); + fileService.deleteFileAttachmentByIds(ids); + //删除缺陷文件关联记录 + IssueFileExample issueFileExample = new IssueFileExample(); + issueFileExample.createCriteria().andFileIdIn(ids); + issueFileMapper.deleteByExample(issueFileExample); } } @@ -417,6 +369,7 @@ public class IssuesService { AbstractIssuePlatform platform = IssueFactory.createPlatform(issuesWithBLOBs.getPlatform(), issuesRequest); platform.deleteIssue(id); // 删除缺陷对应的附件 + fileService.deleteAttachment(AttachmentType.ISSUE.type(), id); IssueFileExample issueFileExample = new IssueFileExample(); issueFileExample.createCriteria().andIssueIdEqualTo(id); List issueFiles = issueFileMapper.selectByExample(issueFileExample); @@ -427,7 +380,6 @@ public class IssuesService { fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); } issueFileMapper.deleteByExample(issueFileExample); - fileService.deleteAttachment(AttachmentType.ISSUE.type(), id); } public IssuesWithBLOBs get(String id) { diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseIssueService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseIssueService.java index 3bdfdfeaa2..b731c540b4 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseIssueService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseIssueService.java @@ -50,6 +50,7 @@ public class TestCaseIssueService { List caseIds = getCaseIdsByIssuesId(request.getIssuesId()); List list = testCaseService.getTestCaseByIds(caseIds); testCaseService.addProjectName(list); + testCaseService.addVersionName(list); return list; } diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index 90d17407b9..b2f6f20276 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -618,9 +618,11 @@ public class TestCaseService { testCaseFileExample.createCriteria().andCaseIdEqualTo(testCaseId); List testCaseFiles = testCaseFileMapper.selectByExample(testCaseFileExample); List fileIds = testCaseFiles.stream().map(TestCaseFile::getFileId).collect(Collectors.toList()); - FileAttachmentMetadataExample fileAttachmentMetadataExample = new FileAttachmentMetadataExample(); - fileAttachmentMetadataExample.createCriteria().andIdIn(fileIds); - fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); + if (CollectionUtils.isNotEmpty(fileIds)) { + FileAttachmentMetadataExample fileAttachmentMetadataExample = new FileAttachmentMetadataExample(); + fileAttachmentMetadataExample.createCriteria().andIdIn(fileIds); + fileAttachmentMetadataMapper.deleteByExample(fileAttachmentMetadataExample); + } testCaseFileMapper.deleteByExample(testCaseFileExample); fileService.deleteAttachment(AttachmentType.TEST_CASE.type(), testCaseId); return testCaseMapper.deleteByPrimaryKey(testCaseId); @@ -867,6 +869,7 @@ public class TestCaseService { public List getTestCaseByNotInIssue(QueryTestCaseRequest request) { List list = extTestCaseMapper.getTestCaseByNotInIssue(request); addProjectName(list); + addVersionName(list); return list; } @@ -885,6 +888,24 @@ public class TestCaseService { }); } + public void addVersionName(List list) { + List versionIds = list.stream().map(TestCase::getVersionId).collect(Collectors.toList()); + List versions = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(versionIds)) { + ProjectVersionExample example = new ProjectVersionExample(); + example.createCriteria().andIdIn(versionIds); + versions = projectVersionMapper.selectByExample(example); + } + Map projectVersionMap = versions.stream() + .collect(Collectors.toMap(ProjectVersion::getId, ProjectVersion::getName)); + list.forEach(item -> { + String versionName = projectVersionMap.get(item.getVersionId()); + if (StringUtils.isNotBlank(versionName)) { + item.setVersionName(versionName); + } + }); + } + public List getReviewCase(QueryTestCaseRequest request) { setDefaultOrder(request); request.getOrders().forEach(order -> { @@ -1964,7 +1985,6 @@ public class TestCaseService { public TestCase refactorSave(EditTestCaseRequest request, List files) { final TestCaseWithBLOBs testCaseWithBLOBs = addTestCase(request); - // 复制用例时复制对应附件数据 if (StringUtils.isNotEmpty(request.getCopyCaseId())) { TestCaseFileExample example = new TestCaseFileExample(); @@ -1980,18 +2000,6 @@ public class TestCaseService { }); } } - - // 新的附件 - if (files != null) { - files.forEach(file -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.TEST_CASE.type(), request.getId()); - TestCaseFile testCaseFile = new TestCaseFile(); - testCaseFile.setFileId(fileAttachmentMetadata.getId()); - testCaseFile.setCaseId(request.getId()); - testCaseFileMapper.insert(testCaseFile); - }); - } - return testCaseWithBLOBs; } @@ -2039,41 +2047,36 @@ public class TestCaseService { if (testCaseWithBLOBs == null) { MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId()); } - - if (BooleanUtils.isTrue(request.isHandleAttachment())) { - // 新选择了一个文件,删除原来的文件 - List updatedFiles = request.getUpdatedFileList(); - List originFiles = fileService.getFileAttachmentMetadataByCaseId(request.getId()); - List updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList()); - List originFileIds = originFiles.stream().map(FileAttachmentMetadata::getId).collect(Collectors.toList()); - // 获得差异的附件ID - List deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds); - // 删除差异附件记录, 目录下附件文件 - fileService.deleteAttachment(deleteFileIds); - fileService.deleteFileAttachmentByIds(deleteFileIds); - //删除用例文件关联记录 - if (!CollectionUtils.isEmpty(deleteFileIds)) { - TestCaseFileExample testCaseFileExample = new TestCaseFileExample(); - testCaseFileExample.createCriteria().andFileIdIn(deleteFileIds); - testCaseFileMapper.deleteByExample(testCaseFileExample); - } - - // 保存新的附件 - if (files != null) { - files.forEach(file -> { - FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.TEST_CASE.type(), request.getId()); - TestCaseFile testCaseFile = new TestCaseFile(); - testCaseFile.setFileId(fileAttachmentMetadata.getId()); - testCaseFile.setCaseId(request.getId()); - testCaseFileMapper.insert(testCaseFile); - }); - } - } - this.setNode(request); return editTestCase(request); } + public void uploadAttachment(EditTestCaseRequest request, MultipartFile file) { + TestCaseWithBLOBs testCaseWithBLOBs = testCaseMapper.selectByPrimaryKey(request.getId()); + if (testCaseWithBLOBs == null) { + MSException.throwException(Translator.get("test_case_attachment_upload_not_found") + request.getId()); + } + + FileAttachmentMetadata fileAttachmentMetadata = fileService.saveAttachment(file, AttachmentType.TEST_CASE.type(), request.getId()); + TestCaseFile testCaseFile = new TestCaseFile(); + testCaseFile.setFileId(fileAttachmentMetadata.getId()); + testCaseFile.setCaseId(request.getId()); + testCaseFileMapper.insert(testCaseFile); + } + + public void deleteAttachment(String id) { + // 删除附件记录, 目录下附件文件 + if (StringUtils.isNotEmpty(id)) { + List ids = Arrays.asList(id); + fileService.deleteAttachment(ids); + fileService.deleteFileAttachmentByIds(ids); + //删除用例文件关联记录 + TestCaseFileExample testCaseFileExample = new TestCaseFileExample(); + testCaseFileExample.createCriteria().andFileIdIn(ids); + testCaseFileMapper.deleteByExample(testCaseFileExample); + } + } + public String editTestCase(EditTestCaseRequest request, List files) { String testCaseId = testPlanTestCaseMapper.selectByPrimaryKey(request.getId()).getCaseId(); request.setId(testCaseId); diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 4a5c64a697..790567f497 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -34,6 +34,9 @@ not_authorized=not authorized. login_fail=Login fail user_apikey_limit=Can have up to 5 api keys please_logout_current_user=Please logout current user first +#attachment upload +test_case_attachment_upload_not_found=Unable to upload attachment, no associated test case found +issues_attachment_upload_not_found=Unable to upload attachment, no associated issue found #load test edit_load_test_not_found=Cannot edit test, test not found= run_load_test_not_found=Cannot run test, test not found= diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 853d4630f3..470812ad2d 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -34,6 +34,9 @@ user_expires=用户过期 not_authorized=未经授权 user_apikey_limit=最多能有5个Api key please_logout_current_user=请先登出当前用户 +#attachment upload +test_case_attachment_upload_not_found=无法上传附件,未找到相关联用例: +issues_attachment_upload_not_found=无法上传附件,未找到相关联缺陷: #load test edit_load_test_not_found=无法编辑测试,未找到测试: run_load_test_not_found=无法运行测试,未找到测试: diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 14996c86ac..3aef7b604f 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -34,6 +34,9 @@ user_expires=用戶過期 not_authorized=未經授權 user_apikey_limit=最多能有5個Api key please_logout_current_user=請先登出當前用戶 +#attachment upload +test_case_attachment_upload_not_found=無法上傳附件,未找到相關用例: +issues_attachment_upload_not_found=無法上傳附件,未找到相關缺陷: #load test edit_load_test_not_found=無法編輯測試,未找到測試: run_load_test_not_found=無法運行測試,未找到測試: diff --git a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue index 7406b82fca..617b785647 100644 --- a/frontend/src/business/components/track/case/components/TestCaseAttachment.vue +++ b/frontend/src/business/components/track/case/components/TestCaseAttachment.vue @@ -9,12 +9,12 @@ @@ -37,7 +37,7 @@ :width="70" :label="$t('commons.status')"> @@ -152,11 +153,11 @@ export default { }, }, filters: { - formatProgressPercentage(percentage) { - if (isNaN(percentage)) { - return percentage + formatStatus(status) { + if (isNaN(status)) { + return status === 'success' ? '完成' : '失败' } - return Math.floor(percentage * 100 / 100) + "%"; + return Math.floor(status * 100 / 100) + "%"; } } } diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue index ca2c4f257d..bb7e38da3d 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue @@ -829,24 +829,6 @@ export default { }, getOption(param) { let formData = new FormData(); - if (this.$refs.otherInfo && this.$refs.otherInfo.uploadList) { - this.$refs.otherInfo.uploadList.forEach(f => { - formData.append("file", f); - }); - } - - if (this.$refs.otherInfo && this.$refs.otherInfo.fileList) { - if (param.isCopy) { - // 如果是copy,则把文件的ID传到后台进行文件复制 - param.fileIds = this.$refs.otherInfo.fileList.map(f => f.id); - } - param.updatedFileList = this.$refs.otherInfo.fileList; - } else { - param.fileIds = []; - param.updatedFileList = []; - } - param.handleAttachment = this.isClickAttachmentTab; - let requestJson = JSON.stringify(param, function (key, value) { return key === "file" ? undefined : value }); diff --git a/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue b/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue index e00bed6414..2b70d49f99 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEditOtherInfo.vue @@ -57,17 +57,19 @@ - {{$t('test_track.case.add_attachment')}} + :on-success="handleSuccess" + :on-error="handleError" + :disabled="(readOnly && isTestPlanEdit) || type === 'add' || isCopy"> + {{$t('test_track.case.add_attachment')}} {{ $t('test_track.case.upload_tip') }} @@ -130,8 +132,10 @@ import TabPaneCount from "@/business/components/track/plan/view/comonents/report import {getRelationshipCountCase} from "@/network/testCase"; import TestCaseComment from "@/business/components/track/case/components/TestCaseComment"; import ReviewCommentItem from "@/business/components/track/review/commom/ReviewCommentItem"; -import {byteToSize} from "@/common/js/utils"; +import {byteToSize, getTypeByFileName, hasLicense} from "@/common/js/utils"; import {TokenKey} from "@/common/js/constants"; +import axios from "axios"; +import {validateAndSetLicense} from "@/business/permission"; export default { name: "TestCaseEditOtherInfo", @@ -151,7 +155,6 @@ export default { return { result: {}, tabActiveName: "remark", - uploadList: [], fileList: [], tableData: [], demandOptions: [], @@ -163,7 +166,8 @@ export default { //lazy: true, //lazyLoad:this.lazyLoad }, - intervalMap: new Map() + intervalMap: new Map(), + cancelFileToken: [], }; }, computed: { @@ -227,12 +231,11 @@ export default { this.tabActiveName = "remark"; }, fileValidator(file) { - /// todo: 是否需要对文件内容和大小做限制 - return file.size > 0; + return file.size < 500 * 1024 * 1024; }, beforeUpload(file) { if (!this.fileValidator(file)) { - /// todo: 显示错误信息 + this.$error(this.$t('load_test.file_size_out_of_bounds') + file.name); return false; } @@ -240,22 +243,84 @@ export default { this.$error(this.$t('load_test.delete_file') + ', name: ' + file.name); return false; } - + }, + handleUpload(e) { + // 表格生成上传文件数据 + let file = e.file; let user = JSON.parse(localStorage.getItem(TokenKey)); this.tableData.push({ name: file.name, size: byteToSize(file.size), updateTime: new Date().getTime(), - percentage: 0, + progress: 0, status: 0, - creator: user.name + creator: user.name, + type: getTypeByFileName(file.name) }); - this.handleProcess(file); - return true; + // 上传文件 + this.uploadFile(e, (param) => { + this.showProgress(e.file, param) + }) }, - handleUpload(uploadResources) { - this.uploadList.push(uploadResources.file); + async uploadFile(param, progressCallback) { + let file = param.file; + let progress = 0; + let formData = new FormData(); + let requestJson = JSON.stringify({"id": this.caseId}); + formData.append("file", file); + formData.append('request', new Blob([requestJson], { + type: "application/json" + })); + + let CancelToken = axios.CancelToken + let self = this; + axios({ + headers: { 'Content-Type': 'application/json;charset=UTF-8' }, + method: 'post', + url: '/test/case/attachment/upload', + data: formData, + cancelToken: new CancelToken(function executor(c) { + self.cancelFileToken.push({"name": file.name, "cancelFunc": c}); + }), + onUploadProgress: progressEvent => { // 获取文件上传进度 + progress = (progressEvent.loaded / progressEvent.total * 100) | 0 + progressCallback({ progress, status: progress }) + } + }).then(response => { // 成功回调 + progress = 100; + param.onSuccess(response); + progressCallback({progress, status: 'success'}); + }).catch(error => { // 失败回调 + progress = 100; + progressCallback({progress, status: 'error'}); + }) + }, + showProgress(file, params) { + const { progress, status } = params + const arr = [...this.tableData].map(item => { + if (item.name === file.name) { + item.progress = progress + item.status = status + } + return item + }) + this.tableData = [...arr] + }, + handleExceed(files, fileList) { + this.$error(this.$t('load_test.file_size_limit')); + }, + handleSuccess(response, file, fileList) { + let readyFiles = fileList.filter(item => item.status === 'ready') + if (readyFiles.length === 0 ) { + this.getFileMetaData(); + } + }, + handleError(err, file, fileList) { + let readyFiles = fileList.filter(item => item.status === 'ready') + if (readyFiles.length === 0 ) { + this.getFileMetaData(); + } }, handleDownload(file) { let data = { @@ -297,52 +362,27 @@ export default { } }); }, - handleCancel(file, index) { - this.fileList.splice(index, 1); - let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name); - if (i > -1) { - this.uploadList.splice(i, 1); - } - let cancelFile = this.tableData.filter(f => f.name === file.name)[0]; - clearInterval(this.intervalMap.get(cancelFile.name)); - cancelFile.percentage = 100; - cancelFile.status = this.$t('notice.result.EXECUTE_FAILED'); - }, _handleDelete(file, index) { this.fileList.splice(index, 1); this.tableData.splice(index, 1); - let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name); - if (i > -1) { - this.uploadList.splice(i, 1); - } + this.$get('/test/case/attachment/delete/' + file.id, () => { + this.$success(this.$t('commons.delete_success')); + this.getFileMetaData(); + }); }, - handleExceed() { - this.$error(this.$t('load_test.file_size_limit')); - }, - handleProcess(file) { - let currentUploadFile = this.tableData.filter(f => f.name === file.name)[0]; - const interval = setInterval(() => { - let randomNum = Math.floor(Math.random() * 10); - if (currentUploadFile.percentage + randomNum > 100) { - clearInterval(interval) - currentUploadFile.percentage = 100; - currentUploadFile.status = this.$t('notice.result.EXECUTE_COMPLETED') - return - } - currentUploadFile.percentage += randomNum; - currentUploadFile.status += randomNum; - }, file.size > 1024 * 1024 ? 200 : 100) - this.intervalMap.set(currentUploadFile.name, interval); + handleCancel(file, index) { + this.fileList.splice(index, 1); + let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0]; + cancelToken.cancelFunc(); + let cancelFile = this.tableData.filter(f => f.name === file.name)[0]; + cancelFile.progress = 100; + cancelFile.status = 'error'; }, getFileMetaData(id) { this.$emit("update:isClickAttachmentTab", true); // 保存用例后传入用例id,刷新文件列表,可以预览和下载 - if (this.uploadList && this.uploadList.length > 0 && !id) { - return; - } this.fileList = []; this.tableData = []; - this.uploadList = []; let testCaseId; if (this.isCopy) { testCaseId = this.copyCaseId @@ -361,8 +401,8 @@ export default { this.tableData = JSON.parse(JSON.stringify(files)); this.tableData.map(f => { f.size = byteToSize(f.size); - f.status = this.$t('notice.result.EXECUTE_COMPLETED'); - f.percentage = 100 + f.status = 'success'; + f.progress = 100 }); }); } diff --git a/frontend/src/business/components/track/issue/IssueEditDetail.vue b/frontend/src/business/components/track/issue/IssueEditDetail.vue index 2a6f03727a..47067c654c 100644 --- a/frontend/src/business/components/track/issue/IssueEditDetail.vue +++ b/frontend/src/business/components/track/issue/IssueEditDetail.vue @@ -107,16 +107,19 @@ - {{$t('test_track.case.add_attachment')}} + :on-success="handleSuccess" + :on-error="handleError" + :disabled="type === 'add' || type === 'copy'"> + {{$t('test_track.case.add_attachment')}} {{ $t('test_track.case.upload_tip') }} @@ -197,7 +200,7 @@ import { getCurrentProjectID, getCurrentUser, getCurrentUserId, - getCurrentWorkspaceId, + getCurrentWorkspaceId, getTypeByFileName, hasLicense, } from "@/common/js/utils"; import {enableThirdPartTemplate, getIssuePartTemplateWithProject, getPlatformTransitions} from "@/network/Issue"; import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem"; @@ -207,6 +210,7 @@ import ReviewCommentItem from "@/business/components/track/review/commom/ReviewC import {TokenKey} from "@/common/js/constants"; import {Message} from "element-ui"; import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment"; +import axios from "axios"; const {getIssuesById} = require("@/network/Issue"); @@ -306,12 +310,11 @@ export default { comments: [], richTextDefaultOpen: 'preview', tabActiveName: 'relateTestCase', - uploadList: [], fileList: [], tableData: [], readOnly: false, isDelete: true, - intervalMap: new Map() + cancelFileToken: [], }; }, props: { @@ -337,6 +340,15 @@ export default { return enableThirdPartTemplate(this.currentProject); }, }, + watch: { + tabActiveName() { + if (this.tabActiveName === 'attachment') { + if (this.type === 'edit' && this.issueId) { + this.getFileMetaData(this.issueId); + } + } + }, + }, methods: { resetForm() { this.form = { @@ -356,12 +368,14 @@ export default { } }, open(data, type) { + this.tabActiveName = 'relateTestCase' this.showFollow = false; this.result.loading = true; this.type = type; this.richTextDefaultOpen = this.type === 'edit' ? 'preview' : 'edit'; if (this.$refs.testCaseIssueList) { this.$refs.testCaseIssueList.clear(); + this.$refs.testCaseIssueList.isXpack = hasLicense(); } this.$nextTick(() => { getIssuePartTemplateWithProject((template, project) => { @@ -440,7 +454,6 @@ export default { initEdit(data) { this.tableData = []; this.fileList = []; - this.uploadList = []; if (data) { Object.assign(this.form, data); if (!(data.options instanceof Array)) { @@ -564,21 +577,6 @@ export default { }, getOption(param) { let formData = new FormData(); - - if (this.uploadList) { - this.uploadList.forEach(f => { - formData.append("file", f); - }); - } - - if (this.fileList) { - // 如果是copy,则把文件的ID传到后台进行文件复制 TODO - param.updatedFileList = this.fileList; - } else { - param.fileIds = []; - param.updatedFileList = []; - } - let requestJson = JSON.stringify(param, function (key, value) { return key === "file" ? undefined : value }); @@ -627,12 +625,11 @@ export default { } }, fileValidator(file) { - /// todo: 是否需要对文件内容和大小做限制 - return file.size > 0; + return file.size < 500 * 1024 * 1024; }, beforeUpload(file) { if (!this.fileValidator(file)) { - /// todo: 显示错误信息 + this.$error(this.$t('load_test.file_size_out_of_bounds') + file.name); return false; } @@ -640,22 +637,84 @@ export default { this.$error(this.$t('load_test.delete_file') + ', name: ' + file.name); return false; } - + }, + handleUpload(e) { + // 表格生成上传文件数据 + let file = e.file; let user = JSON.parse(localStorage.getItem(TokenKey)); this.tableData.push({ name: file.name, size: byteToSize(file.size), updateTime: new Date().getTime(), - percentage: 0, + progress: 0, status: 0, - creator: user.name + creator: user.name, + type: getTypeByFileName(file.name) }); - this.handleProcess(file); - return true; + // 上传文件 + this.uploadFile(e, (param) => { + this.showProgress(e.file, param) + }) }, - handleUpload(uploadResources) { - this.uploadList.push(uploadResources.file); + async uploadFile(param, progressCallback) { + let file = param.file; + let progress = 0; + let formData = new FormData(); + let requestJson = JSON.stringify({"id": this.issueId}); + formData.append("file", file); + formData.append('request', new Blob([requestJson], { + type: "application/json" + })); + + let CancelToken = axios.CancelToken + let self = this; + axios({ + headers: { 'Content-Type': 'application/json;charset=UTF-8' }, + method: 'post', + url: '/issues/attachment/upload', + data: formData, + cancelToken: new CancelToken(function executor(c) { + self.cancelFileToken.push({"name": file.name, "cancelFunc": c}); + }), + onUploadProgress: progressEvent => { // 获取文件上传进度 + progress = (progressEvent.loaded / progressEvent.total * 100) | 0 + progressCallback({ progress, status: progress }) + } + }).then(response => { // 成功回调 + progress = 100; + param.onSuccess(response); + progressCallback({progress, status: 'success'}); + }).catch(({error}) => { // 失败回调 + progress = 100; + progressCallback({progress, status: 'error'}); + }) + }, + showProgress(file, params) { + const { progress, status } = params + const arr = [...this.tableData].map(item => { + if (item.name === file.name) { + item.progress = progress + item.status = status + } + return item + }) + this.tableData = [...arr] + }, + handleExceed(files, fileList) { + this.$error(this.$t('load_test.file_size_limit')); + }, + handleSuccess(response, file, fileList) { + let readyFiles = fileList.filter(item => item.status === 'success') + if (readyFiles.length === fileList.length ) { + this.getFileMetaData(this.issueId); + } + }, + handleError(err, file, fileList) { + let readyFiles = fileList.filter(item => item.status === 'success') + if (readyFiles.length === fileList.length ) { + this.getFileMetaData(this.issueId); + } }, handleDownload(file) { let data = { @@ -697,51 +756,26 @@ export default { } }); }, - handleCancel(file, index) { - this.fileList.splice(index, 1); - let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name); - if (i > -1) { - this.uploadList.splice(i, 1); - } - let cancelFile = this.tableData.filter(f => f.name === file.name)[0]; - clearInterval(this.intervalMap.get(cancelFile.name)); - cancelFile.percentage = 100; - cancelFile.status = this.$t('notice.result.EXECUTE_FAILED'); - }, _handleDelete(file, index) { this.fileList.splice(index, 1); this.tableData.splice(index, 1); - let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name); - if (i > -1) { - this.uploadList.splice(i, 1); - } + this.$get('/issues/attachment/delete/' + file.id, () => { + this.$success(this.$t('commons.delete_success')); + this.getFileMetaData(this.issueId); + }); }, - handleExceed() { - this.$error(this.$t('load_test.file_size_limit')); - }, - handleProcess(file) { - let currentUploadFile = this.tableData.filter(f => f.name === file.name)[0]; - const interval = setInterval(() => { - let randomNum = Math.floor(Math.random() * 10); - if (currentUploadFile.percentage + randomNum > 100) { - clearInterval(interval) - currentUploadFile.percentage = 100; - currentUploadFile.status = this.$t('notice.result.EXECUTE_COMPLETED') - return - } - currentUploadFile.percentage += randomNum; - currentUploadFile.status += randomNum; - }, file.size > 1024 * 1024 ? 200 : 100) - this.intervalMap.set(currentUploadFile.name, interval); + handleCancel(file, index) { + this.fileList.splice(index, 1); + let cancelToken = this.cancelFileToken.filter(f => f.name === file.name)[0]; + cancelToken.cancelFunc(); + let cancelFile = this.tableData.filter(f => f.name === file.name)[0]; + cancelFile.progress = 100; + cancelFile.status = 'error'; }, getFileMetaData(id) { // 保存用例后传入用例id,刷新文件列表,可以预览和下载 - if (this.uploadList && this.uploadList.length > 0 && !id) { - return; - } this.fileList = []; this.tableData = []; - this.uploadList = []; if (id) { this.result = this.$get("issues/file/attachmentMetadata/" + id, response => { let files = response.data; @@ -753,8 +787,8 @@ export default { this.tableData = JSON.parse(JSON.stringify(files)); this.tableData.map(f => { f.size = byteToSize(f.size); - f.status = this.$t('notice.result.EXECUTE_COMPLETED'); - f.percentage = 100 + f.status = 'success'; + f.progress = 100 }); }); } diff --git a/frontend/src/business/components/track/issue/TestCaseIssueList.vue b/frontend/src/business/components/track/issue/TestCaseIssueList.vue index 0f56902c9f..fb3dbb767c 100644 --- a/frontend/src/business/components/track/issue/TestCaseIssueList.vue +++ b/frontend/src/business/components/track/issue/TestCaseIssueList.vue @@ -47,6 +47,10 @@ prop="projectName"> + +