feat(测试跟踪): 缺陷管理插件化

This commit is contained in:
chenjianxing 2022-11-18 10:54:15 +08:00 committed by jianxing
parent b887a660c4
commit bed338813b
24 changed files with 638 additions and 318 deletions

View File

@ -23,7 +23,7 @@
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-security.version>5.7.5</spring-security.version>
<dubbo.version>2.7.15</dubbo.version>
<platform-plugin-sdk.version>main</platform-plugin-sdk.version>
<platform-plugin-sdk.version>1.0.0</platform-plugin-sdk.version>
<flyway.version>7.15.0</flyway.version>
<shiro.version>1.10.0</shiro.version>
<mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version>

View File

@ -1,7 +1,7 @@
package io.metersphere.controller.remote;
import io.metersphere.remote.service.PlatformPluginService;
import io.metersphere.remote.service.SystemSettingService;
import io.metersphere.service.remote.SystemSettingService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

View File

@ -1,12 +0,0 @@
package io.metersphere.remote.service;
import io.metersphere.commons.constants.MicroServiceName;
import io.metersphere.service.RemoteService;
import org.springframework.stereotype.Service;
@Service
public class SystemSettingService extends RemoteService {
public SystemSettingService() {
super(MicroServiceName.SYSTEM_SETTING);
}
}

View File

@ -49,7 +49,6 @@ import draggable from 'vuedraggable';
import TemplateComponentEditHeader
from "./ext/TemplateComponentEditHeader";
import MsFormDivider from "metersphere-frontend/src/components/MsFormDivider";
import {ISSUE_PLATFORM_OPTION} from "metersphere-frontend/src/utils/table-constants";
import CustomFieldFormList from "./CustomFieldFormList";
import CustomFieldRelateList from "./CustomFieldRelateList";
import FieldTemplateEdit from "./FieldTemplateEdit";
@ -90,10 +89,10 @@ export default {
url: '',
};
},
props: {
platformOption: Array
},
computed: {
platformOption() {
return ISSUE_PLATFORM_OPTION;
},
isSystem() {
return this.form.system;
}

View File

@ -76,7 +76,7 @@
:total="total"/>
<issue-template-copy ref="templateCopy" @refresh="initTableData"/>
<issue-template-edit ref="templateEdit" @refresh="initTableData"/>
<issue-template-edit :platform-option="platformFilters" ref="templateEdit" @refresh="initTableData"/>
<ms-delete-confirm :title="$t('commons.template_delete')" @delete="_handleDelete" ref="deleteConfirm"/>
</el-card>
</template>
@ -95,6 +95,7 @@ import IssueTemplateEdit from "./IssueTemplateEdit";
import {deleteIssueFieldTemplateById, getIssueFieldTemplatePages} from "../../../api/template";
import MsDeleteConfirm from "metersphere-frontend/src/components/MsDeleteConfirm";
import IssueTemplateCopy from "./IssueTemplateCopy";
import {getPlatformOption} from "@/api/platform-plugin";
export default {
name: "IssuesTemplateList",
@ -102,7 +103,7 @@ export default {
IssueTemplateEdit,
IssueTemplateCopy,
MsTableHeader,
MsTablePagination, MsTableButton, MsTableOperators, MsTableColumn, MsTable , MsDeleteConfirm
MsTablePagination, MsTableButton, MsTableOperators, MsTableColumn, MsTable, MsDeleteConfirm
},
data() {
return {
@ -113,9 +114,9 @@ export default {
currentPage: 1,
result: {},
loading: false,
platformOptions: [],
issuePlatformMap: {
Local: 'Metersphere',
Jira: 'JIRA',
Tapd: 'Tapd',
Zentao: '禅道',
AzureDevops: 'Azure Devops',
@ -135,6 +136,10 @@ export default {
};
},
created() {
getPlatformOption()
.then((r) => {
this.platformOptions = r.data;
});
this.initTableData();
},
computed: {
@ -142,7 +147,12 @@ export default {
return ISSUE_TEMPLATE_LIST;
},
platformFilters() {
return ISSUE_PLATFORM_OPTION;
this.platformOptions.forEach(item => {
this.issuePlatformMap[item.value] = item.text;
});
let options = [...ISSUE_PLATFORM_OPTION];
options.push(...this.platformOptions);
return options;
},
tableHeight() {
return document.documentElement.clientHeight - 200;

View File

@ -26,6 +26,12 @@
<artifactId>metersphere-platform-plugin-sdk</artifactId>
<groupId>io.metersphere</groupId>
<version>${platform-plugin-sdk.version}</version>
<exclusions>
<exclusion>
<groupId>io.metersphere</groupId>
<artifactId>domain</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

View File

@ -1,6 +1,6 @@
package io.metersphere.controller;
import io.metersphere.domain.SelectOption;
import io.metersphere.platform.domain.SelectOption;
import io.metersphere.dto.PlatformProjectOptionRequest;
import io.metersphere.service.PlatformPluginService;
import org.springframework.web.bind.annotation.*;

View File

@ -1,7 +1,7 @@
package io.metersphere.service;
import io.metersphere.api.Platform;
import io.metersphere.api.PluginMetaInfo;
import io.metersphere.platform.api.Platform;
import io.metersphere.platform.api.PluginMetaInfo;
import io.metersphere.base.domain.PluginWithBLOBs;
import io.metersphere.base.domain.ServiceIntegration;
import io.metersphere.base.mapper.PluginMapper;
@ -11,11 +11,9 @@ import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.domain.GetOptionRequest;
import io.metersphere.domain.PlatformRequest;
import io.metersphere.domain.SelectOption;
import io.metersphere.platform.domain.*;
import io.metersphere.dto.PlatformProjectOptionRequest;
import io.metersphere.loader.PlatformPluginManager;
import io.metersphere.platform.loader.PlatformPluginManager;
import io.metersphere.request.IntegrationRequest;
import io.metersphere.utils.PluginManagerUtil;
import org.apache.commons.lang3.StringUtils;

View File

@ -1,6 +1,6 @@
package io.metersphere.service.plugin;
import im.metersphere.storage.StorageStrategy;
import im.metersphere.plugin.storage.StorageStrategy;
import io.metersphere.commons.constants.StorageConstants;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.metadata.service.FileManagerService;

View File

@ -1,6 +1,6 @@
package io.metersphere.utils;
import im.metersphere.loader.PluginManager;
import im.metersphere.plugin.loader.PluginManager;
import io.metersphere.base.domain.PluginWithBLOBs;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;

View File

@ -17,13 +17,11 @@ import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.notice.annotation.SendNotice;
import io.metersphere.request.issues.IssueExportRequest;
import io.metersphere.request.issues.IssueImportRequest;
import io.metersphere.request.issues.JiraIssueTypeRequest;
import io.metersphere.request.issues.PlatformIssueTypeRequest;
import io.metersphere.request.testcase.AuthUserIssueRequest;
import io.metersphere.request.testcase.IssuesCountRequest;
import io.metersphere.service.BaseCheckPermissionService;
import io.metersphere.service.IssuesService;
import io.metersphere.service.issue.domain.jira.JiraIssueType;
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
import io.metersphere.xpack.track.dto.*;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
@ -186,14 +184,14 @@ public class IssuesController {
return issuesService.getThirdPartTemplate(projectId);
}
@PostMapping("/jira/issuetype")
public List<JiraIssueType> getJiraIssueType(@RequestBody JiraIssueTypeRequest request) {
return issuesService.getIssueTypes(request);
@GetMapping("/demand/list/{projectId}")
public List getDemandList(@PathVariable String projectId) {
return issuesService.getDemandList(projectId);
}
@GetMapping("/demand/list/{projectId}")
public List<DemandDTO> getDemandList(@PathVariable String projectId) {
return issuesService.getDemandList(projectId);
@GetMapping("/third/part/template/enable/{projectId}")
public boolean thirdPartTemplateEnable(@PathVariable String projectId) {
return issuesService.thirdPartTemplateEnable(projectId);
}
@PostMapping("/platform/transitions")

View File

@ -295,14 +295,14 @@ public class IssueExcelListener extends AnalysisEventListener<Map<Integer, Strin
Map<String, List<CustomFieldDao>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(CustomFieldDao::getName));
issueExcelData.getCustomData().forEach((k, v) -> {
try {
List<CustomFieldDao> customFieldDaos = customFieldMap.get(k);
if (CollectionUtils.isNotEmpty(customFieldDaos) && customFieldDaos.size() > 0) {
CustomFieldDao customFieldDao = customFieldDaos.get(0);
List<CustomFieldDao> customFieldDaoList = customFieldMap.get(k);
if (CollectionUtils.isNotEmpty(customFieldDaoList) && customFieldDaoList.size() > 0) {
CustomFieldDao customFieldDao = customFieldDaoList.get(0);
String type = customFieldDao.getType();
// addfield
// add field
CustomFieldResourceDTO customFieldResourceDTO = new CustomFieldResourceDTO();
customFieldResourceDTO.setFieldId(customFieldDao.getId());
// requestfield
// request field
CustomFieldItemDTO customFieldItemDTO = new CustomFieldItemDTO();
BeanUtils.copyBean(customFieldItemDTO, customFieldDao);
if (StringUtils.isEmpty(v.toString())) {

View File

@ -8,16 +8,17 @@ 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.xpack.track.dto.AttachmentSyncType;
import io.metersphere.constants.AttachmentType;
import io.metersphere.i18n.Translator;
import io.metersphere.metadata.service.FileMetadataService;
import io.metersphere.metadata.utils.MetadataUtils;
import io.metersphere.platform.domain.SyncIssuesAttachmentRequest;
import io.metersphere.request.attachment.AttachmentRequest;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import io.metersphere.service.issue.platform.IssueFactory;
import io.metersphere.xmind.utils.FileUtil;
import io.metersphere.xpack.track.dto.AttachmentSyncType;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
@ -35,7 +36,7 @@ import javax.annotation.Resource;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author songcc
@ -68,6 +69,8 @@ public class AttachmentService {
private BaseUserService baseUserService;
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
PlatformPluginService platformPluginService;
public void uploadAttachment(AttachmentRequest request, MultipartFile file) {
// 附件上传的前置校验
@ -98,11 +101,16 @@ public class AttachmentService {
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File uploadFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, uploadFile, AttachmentSyncType.UPLOAD);
if (PlatformPluginService.isPluginPlatform(issues.getPlatform())) {
syncIssuesAttachment(issues, uploadFile, AttachmentSyncType.UPLOAD);
} else {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, uploadFile, AttachmentSyncType.UPLOAD);
}
}
}
@ -119,11 +127,16 @@ public class AttachmentService {
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File deleteFile = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE);
if (PlatformPluginService.isPluginPlatform(issues.getPlatform())) {
syncIssuesAttachment(issues, deleteFile, AttachmentSyncType.DELETE);
} else {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE);
}
}
}
@ -178,17 +191,31 @@ public class AttachmentService {
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
if (PlatformPluginService.isPluginPlatform(issues.getPlatform())) {
syncIssuesAttachment(issues, refFile, AttachmentSyncType.UPLOAD);
} else {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
}
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
}
});
}
}
public void syncIssuesAttachment(IssuesWithBLOBs issues, File refFile, AttachmentSyncType attachmentSyncType) {
SyncIssuesAttachmentRequest attachmentRequest = new SyncIssuesAttachmentRequest();
attachmentRequest.setPlatformId(issues.getPlatformId());
attachmentRequest.setFile(refFile);
attachmentRequest.setSyncType(attachmentSyncType.syncOperateType());
platformPluginService.getPlatform(issues.getPlatform())
.syncIssuesAttachment(attachmentRequest);
}
public List<FileAttachmentMetadata> listMetadata(AttachmentRequest request) {
List<FileAttachmentMetadata> attachments = new ArrayList<FileAttachmentMetadata>();
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
@ -276,12 +303,17 @@ public class AttachmentService {
IssuesUpdateRequest updateRequest = new IssuesUpdateRequest();
updateRequest.setPlatformId(issues.getPlatformId());
File refFile = downloadMetadataFile(metadataRefId, fileMetadata.getName());
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
if (PlatformPluginService.isPluginPlatform(issues.getPlatform())) {
syncIssuesAttachment(issues, refFile, AttachmentSyncType.UPLOAD);
} else {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, refFile, AttachmentSyncType.UPLOAD);
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
}
}
});
sqlSession.flushStatements();
@ -301,11 +333,16 @@ public class AttachmentService {
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());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE);
if (PlatformPluginService.isPluginPlatform(issues.getPlatform())) {
syncIssuesAttachment(issues, deleteFile, AttachmentSyncType.UPLOAD);
} else {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(issues.getPlatform(), issuesRequest))
.syncIssuesAttachment(updateRequest, deleteFile, AttachmentSyncType.DELETE);
}
});
}
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();

View File

@ -4,6 +4,7 @@ import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.util.DateUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.platform.api.Platform;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtIssueCommentMapper;
@ -13,6 +14,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.constants.AttachmentType;
import io.metersphere.constants.SystemCustomField;
import io.metersphere.platform.domain.*;
import io.metersphere.dto.*;
import io.metersphere.excel.constants.IssueExportHeadField;
import io.metersphere.excel.domain.ExcelErrData;
@ -37,17 +39,18 @@ import io.metersphere.request.IntegrationRequest;
import io.metersphere.request.attachment.AttachmentRequest;
import io.metersphere.request.issues.IssueExportRequest;
import io.metersphere.request.issues.IssueImportRequest;
import io.metersphere.request.issues.JiraIssueTypeRequest;
import io.metersphere.request.issues.PlatformIssueTypeRequest;
import io.metersphere.request.testcase.AuthUserIssueRequest;
import io.metersphere.request.testcase.IssuesCountRequest;
import io.metersphere.service.issue.domain.jira.JiraIssueType;
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
import io.metersphere.service.issue.platform.*;
import io.metersphere.service.remote.project.TrackCustomFieldTemplateService;
import io.metersphere.service.remote.project.TrackIssueTemplateService;
import io.metersphere.service.wapper.TrackProjectService;
import io.metersphere.service.wapper.UserService;
import io.metersphere.utils.DistinctKeyUtil;
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
import io.metersphere.xpack.track.dto.PlatformUser;
import io.metersphere.xpack.track.dto.*;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
@ -124,6 +127,8 @@ public class IssuesService {
@Resource
private AttachmentService attachmentService;
@Resource
private AttachmentModuleRelationMapper attachmentModuleRelationMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@ -131,6 +136,10 @@ public class IssuesService {
private FileMetadataMapper fileMetadataMapper;
@Resource
private ExtIssueCommentMapper extIssueCommentMapper;
@Resource
private PlatformPluginService platformPluginService;
@Resource
private UserService userService;
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
@ -143,19 +152,56 @@ public class IssuesService {
public IssuesWithBLOBs addIssues(IssuesUpdateRequest issuesRequest, List<MultipartFile> files) {
List<IssuesPlatform> platformList = getAddPlatforms(issuesRequest);
Project project = baseProjectService.getProjectById(issuesRequest.getProjectId());
IssuesWithBLOBs issues = null;
for (IssuesPlatform platform : platformList) {
issues = platform.addIssue(issuesRequest);
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
PlatformIssuesUpdateRequest platformIssuesUpdateRequest =
JSON.parseObject(JSON.toJSONString(issuesRequest), PlatformIssuesUpdateRequest.class);
List<PlatformCustomFieldItemDTO> customFieldItemDTOS =
JSON.parseArray(JSON.toJSONString(issuesRequest.getRequestFields()), PlatformCustomFieldItemDTO.class);
platformIssuesUpdateRequest.setCustomFieldList(customFieldItemDTOS); // todo 全部插件化后去掉
platformIssuesUpdateRequest.setUserPlatformUserConfig(userService.getCurrentPlatformInfoStr(SessionUtils.getCurrentWorkspaceId()));
platformIssuesUpdateRequest.setProjectConfig(PlatformPluginService.getCompatibleProjectConfig(project));
issues = platformPluginService.getPlatform(project.getPlatform())
.addIssue(platformIssuesUpdateRequest);
insertIssues(issues);
issuesRequest.setId(issues.getId());
// 用例与第三方缺陷平台中的缺陷关联
handleTestCaseIssues(issuesRequest);
// 如果是复制新增, 同步MS附件到Jira
if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) {
AttachmentRequest attachmentRequest = new AttachmentRequest();
attachmentRequest.setBelongId(issuesRequest.getCopyIssueId());
attachmentRequest.setBelongType(AttachmentType.ISSUE.type());
List<String> attachmentIds = attachmentService.getAttachmentIdsByParam(attachmentRequest);
if (CollectionUtils.isNotEmpty(attachmentIds)) {
for (String attachmentId : attachmentIds) {
FileAttachmentMetadata fileAttachmentMetadata = attachmentService.getFileAttachmentMetadataByFileId(attachmentId);
File file = new File(fileAttachmentMetadata.getFilePath() + "/" + fileAttachmentMetadata.getName());
attachmentService.syncIssuesAttachment(issues, file, AttachmentSyncType.UPLOAD);
}
}
}
} else {
List<IssuesPlatform> platformList = getAddPlatforms(issuesRequest);
for (IssuesPlatform platform : platformList) {
issues = platform.addIssue(issuesRequest);
}
}
if (issuesRequest.getIsPlanEdit()) {
issuesRequest.getAddResourceIds().forEach(l -> {
testCaseIssueService.updateIssuesCount(l);
});
}
saveFollows(issuesRequest.getId(), issuesRequest.getFollows());
customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields());
customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields());
String issuesId = issues.getId();
saveFollows(issuesId, issuesRequest.getFollows());
customFieldIssuesService.addFields(issuesId, issuesRequest.getAddFields());
customFieldIssuesService.editFields(issuesId, issuesRequest.getEditFields());
if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) {
final String platformId = issues.getPlatformId();
// 复制新增, 同步缺陷的MS附件
@ -215,11 +261,15 @@ public class IssuesService {
fileAttachmentMetadataBatchMapper.insert(fileAttachmentMetadata);
// 下载文件管理文件, 同步到第三方平台
File refFile = attachmentService.downloadMetadataFile(filemetaId, fileMetadata.getName());
IssuesRequest addIssueRequest = new IssuesRequest();
addIssueRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
addIssueRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(platform, addIssueRequest))
.syncIssuesAttachment(issuesRequest, refFile, AttachmentSyncType.UPLOAD);
if (PlatformPluginService.isPluginPlatform(platform)) {
attachmentService.syncIssuesAttachment(issuesRequest, refFile, AttachmentSyncType.UPLOAD);
} else {
IssuesRequest addIssueRequest = new IssuesRequest();
addIssueRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
addIssueRequest.setProjectId(SessionUtils.getCurrentProjectId());
Objects.requireNonNull(IssueFactory.createPlatform(platform, addIssueRequest))
.syncIssuesAttachment(issuesRequest, refFile, AttachmentSyncType.UPLOAD);
}
FileUtils.deleteFile(FileUtils.ATTACHMENT_TMP_DIR + File.separator + fileMetadata.getName());
});
sqlSession.flushStatements();
@ -231,11 +281,90 @@ public class IssuesService {
return getIssue(issues.getId());
}
protected IssuesWithBLOBs insertIssues(IssuesWithBLOBs issues) {
if (StringUtils.isBlank(issues.getId())) {
issues.setId(UUID.randomUUID().toString());
}
issues.setCreateTime(System.currentTimeMillis());
issues.setUpdateTime(System.currentTimeMillis());
issues.setNum(getNextNum(issues.getProjectId()));
issues.setCreator(SessionUtils.getUserId());
issuesMapper.insert(issues);
return issues;
}
protected int getNextNum(String projectId) {
Issues issue = extIssuesMapper.getNextNum(projectId);
if (issue == null || issue.getNum() == null) {
return 100001;
} else {
return Optional.of(issue.getNum() + 1).orElse(100001);
}
}
protected void handleTestCaseIssues(IssuesUpdateRequest issuesRequest) {
String issuesId = issuesRequest.getId();
List<String> deleteCaseIds = issuesRequest.getDeleteResourceIds();
if (!org.springframework.util.CollectionUtils.isEmpty(deleteCaseIds)) {
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andResourceIdIn(deleteCaseIds);
// 测试计划的用例 deleteCaseIds 是空的 不会进到这里
example.or(example.createCriteria().andRefIdIn(deleteCaseIds));
testCaseIssuesMapper.deleteByExample(example);
}
List<String> addCaseIds = issuesRequest.getAddResourceIds();
TestCaseIssueService testCaseIssueService = CommonBeanFactory.getBean(TestCaseIssueService.class);
if (!org.springframework.util.CollectionUtils.isEmpty(addCaseIds)) {
if (issuesRequest.getIsPlanEdit()) {
addCaseIds.forEach(caseId -> {
testCaseIssueService.add(issuesId, caseId, issuesRequest.getRefId(), IssueRefType.PLAN_FUNCTIONAL.name());
testCaseIssueService.updateIssuesCount(caseId);
});
} else {
addCaseIds.forEach(caseId -> testCaseIssueService.add(issuesId, caseId, null, IssueRefType.FUNCTIONAL.name()));
}
}
}
public IssuesWithBLOBs updateIssues(IssuesUpdateRequest issuesRequest) {
List<IssuesPlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.updateIssue(issuesRequest);
});
PlatformIssuesUpdateRequest platformIssuesUpdateRequest = JSON.parseObject(JSON.toJSONString(issuesRequest), PlatformIssuesUpdateRequest.class);
Project project = baseProjectService.getProjectById(issuesRequest.getProjectId());
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
Platform platform = platformPluginService.getPlatform(project.getPlatform());
if (platform.isAttachmentUploadSupport()) {
AttachmentRequest attachmentRequest = new AttachmentRequest();
attachmentRequest.setBelongId(issuesRequest.getId());
attachmentRequest.setBelongType(AttachmentType.ISSUE.type());
List<FileAttachmentMetadata> fileAttachmentMetadata = attachmentService.listMetadata(attachmentRequest);
Set<String> msAttachmentNames = fileAttachmentMetadata.stream()
.map(FileAttachmentMetadata::getName)
.collect(Collectors.toSet());
// 获得缺陷MS附件名称
platformIssuesUpdateRequest.setMsAttachmentNames(msAttachmentNames);
}
List<PlatformCustomFieldItemDTO> customFieldItemDTOS = JSON.parseArray(JSON.toJSONString(issuesRequest.getRequestFields()), PlatformCustomFieldItemDTO.class);
platformIssuesUpdateRequest.setCustomFieldList(customFieldItemDTOS); // todo 全部插件化后去掉
platformIssuesUpdateRequest.setUserPlatformUserConfig(userService.getCurrentPlatformInfoStr(SessionUtils.getCurrentWorkspaceId()));
platformIssuesUpdateRequest.setProjectConfig(PlatformPluginService.getCompatibleProjectConfig(project));
IssuesWithBLOBs issue = platformPluginService.getPlatform(project.getPlatform())
.updateIssue(platformIssuesUpdateRequest);
issue.setUpdateTime(System.currentTimeMillis());
issuesMapper.updateByPrimaryKeySelective(issue);
handleTestCaseIssues(issuesRequest);
} else {
List<IssuesPlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.updateIssue(issuesRequest);
});
}
customFieldIssuesService.editFields(issuesRequest.getId(), issuesRequest.getEditFields());
customFieldIssuesService.addFields(issuesRequest.getId(), issuesRequest.getAddFields());
@ -377,7 +506,7 @@ public class IssuesService {
if (jira) {
String jiraKey = project.getJiraKey();
if (StringUtils.isNotBlank(jiraKey) && StringUtils.equals(project.getPlatform(), IssuesManagePlatform.Jira.toString())) {
if (StringUtils.isNotBlank(jiraKey) && PlatformPluginService.isPluginPlatform(project.getPlatform())) {
platforms.add(IssuesManagePlatform.Jira.name());
}
}
@ -479,8 +608,15 @@ public class IssuesService {
Project project = baseProjectService.getProjectById(projectId);
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(project.getWorkspaceId());
IssuesPlatform platform = IssueFactory.createPlatform(issuesWithBLOBs.getPlatform(), issuesRequest);
platform.deleteIssue(id);
if (PlatformPluginService.isPluginPlatform(issuesWithBLOBs.getPlatform())) {
platformPluginService.getPlatform(issuesWithBLOBs.getPlatform())
.deleteIssue(issuesWithBLOBs.getPlatformId());
deleteIssue(id);
} else {
IssuesPlatform platform = IssueFactory.createPlatform(issuesWithBLOBs.getPlatform(), issuesRequest);
platform.deleteIssue(id);
}
// 删除缺陷对应的附件
AttachmentRequest request = new AttachmentRequest();
request.setBelongId(id);
@ -750,13 +886,6 @@ public class IssuesService {
return issueMap;
}
public Map<String, IssuesPlatform> getPlatformMap(IssuesRequest request) {
Project project = baseProjectService.getProjectById(request.getProjectId());
List<String> platforms = getPlatforms(project);
platforms.add(IssuesManagePlatform.Local.toString());
return IssueFactory.createPlatformsForMap(platforms, request);
}
public void syncThirdPartyIssues() {
List<String> projectIds = trackProjectService.getThirdPartProjectIds();
projectIds.forEach(id -> {
@ -834,8 +963,14 @@ public class IssuesService {
String defaultCustomFields = getDefaultCustomFields(syncRequest.getProjectId());
issuesRequest.setDefaultCustomFields(defaultCustomFields);
}
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issuesRequest);
syncThirdPartyIssues(platform::syncIssues, project, issues);
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
// 分批处理
SubListUtil.dealForSubList(issues, 500, (subIssue) ->
syncPluginThirdPartyIssues(subIssue, project));
} else {
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issuesRequest);
syncThirdPartyIssues(platform::syncIssues, project, issues);
}
} catch (Exception e) {
throw e;
} finally {
@ -845,6 +980,118 @@ public class IssuesService {
return true;
}
public void syncPluginThirdPartyIssues(List<IssuesDao> issues, Project project) {
List<PlatformIssuesDTO> platformIssues = JSON.parseArray(JSON.toJSONString(issues), PlatformIssuesDTO.class);
platformIssues.stream().forEach(item -> {
// 给缺陷添加自定义字段
List<PlatformCustomFieldItemDTO> platformCustomFieldList = extIssuesMapper.getIssueCustomField(item.getId()).stream()
.map(field -> {
PlatformCustomFieldItemDTO platformCustomFieldItemDTO = new PlatformCustomFieldItemDTO();
BeanUtils.copyBean(platformCustomFieldItemDTO, field);
return platformCustomFieldItemDTO;
})
.collect(Collectors.toList());
item.setCustomFieldList(platformCustomFieldList);
});
SyncIssuesRequest request = new SyncIssuesRequest();
request.setIssues(platformIssues);
request.setProjectConfig(PlatformPluginService.getCompatibleProjectConfig(project));
Platform platform = platformPluginService.getPlatform(project.getPlatform());
// 获取需要变更的缺陷
SyncIssuesResult syncIssuesResult = platform.syncIssues(request);
List<IssuesWithBLOBs> updateIssues = syncIssuesResult.getUpdateIssues();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
IssuesMapper issueBatchMapper = sqlSession.getMapper(IssuesMapper.class);
AttachmentModuleRelationMapper batchAttachmentModuleRelationMapper = sqlSession.getMapper(AttachmentModuleRelationMapper.class);
// 批量更新
updateIssues.stream()
.map(item -> {
IssuesWithBLOBs issuesWithBLOBs = new IssuesWithBLOBs();
BeanUtils.copyBean(issuesWithBLOBs, item);
return issuesWithBLOBs;
})
.forEach(issueBatchMapper::updateByPrimaryKeySelective);
// 批量删除
syncIssuesResult.getDeleteIssuesIds()
.stream()
.forEach(issueBatchMapper::deleteByPrimaryKey);
try {
// 同步附件
syncPluginIssueAttachment(platform, syncIssuesResult, batchAttachmentModuleRelationMapper);
} catch (Exception e) {
LogUtil.error(e);
}
HashMap<String, List<CustomFieldResourceDTO>> customFieldMap = new HashMap<>();
updateIssues.forEach(item -> {
List<CustomFieldResourceDTO> customFieldResource = baseCustomFieldService.getCustomFieldResourceDTO(item.getCustomFields());
customFieldMap.put(item.getId(), customFieldResource);
});
// 修改自定义字段
customFieldIssuesService.batchEditFields(customFieldMap);
}
private void syncPluginIssueAttachment(Platform platform, SyncIssuesResult syncIssuesResult, AttachmentModuleRelationMapper batchAttachmentModuleRelationMapper) {
Map<String, List<PlatformAttachment>> attachmentMap = syncIssuesResult.getAttachmentMap();
if (MapUtils.isNotEmpty(attachmentMap)) {
for (String issueId : attachmentMap.keySet()) {
// 查询我们平台的附件
Set<String> jiraAttachmentSet = new HashSet<>();
AttachmentRequest attachmentRequest = new AttachmentRequest();
attachmentRequest.setBelongType(AttachmentType.ISSUE.type());
attachmentRequest.setBelongId(issueId);
List<FileAttachmentMetadata> allMsAttachments = attachmentService.listMetadata(attachmentRequest);
Set<String> attachmentsNameSet = allMsAttachments.stream()
.map(FileAttachmentMetadata::getName)
.collect(Collectors.toSet());
List<PlatformAttachment> syncAttachments = attachmentMap.get(issueId);
for (PlatformAttachment syncAttachment : syncAttachments) {
jiraAttachmentSet.add(syncAttachment.getFileName());
if (!attachmentsNameSet.contains(syncAttachment.getFileName())) {
try {
byte[] content = platform.getAttachmentContent(syncAttachment.getFileKey());
if (content == null) {
continue;
}
FileAttachmentMetadata fileAttachmentMetadata = attachmentService
.saveAttachmentByBytes(content, AttachmentType.ISSUE.type(), issueId, syncAttachment.getFileName());
AttachmentModuleRelation attachmentModuleRelation = new AttachmentModuleRelation();
attachmentModuleRelation.setAttachmentId(fileAttachmentMetadata.getId());
attachmentModuleRelation.setRelationId(issueId);
attachmentModuleRelation.setRelationType(AttachmentType.ISSUE.type());
batchAttachmentModuleRelationMapper.insert(attachmentModuleRelation);
} catch (Exception e) {
LogUtil.error(e);
}
}
}
// 删除Jira中不存在的附件
if (CollectionUtils.isNotEmpty(allMsAttachments)) {
List<FileAttachmentMetadata> deleteMsAttachments = allMsAttachments.stream()
.filter(msAttachment -> !jiraAttachmentSet.contains(msAttachment.getName()))
.collect(Collectors.toList());
deleteMsAttachments.forEach(fileAttachmentMetadata -> {
List<String> ids = List.of(fileAttachmentMetadata.getId());
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andAttachmentIdIn(ids).andRelationTypeEqualTo(AttachmentType.ISSUE.type());
// 删除MS附件及关联数据
attachmentService.deleteAttachmentByIds(ids);
attachmentService.deleteFileAttachmentByIds(ids);
batchAttachmentModuleRelationMapper.deleteByExample(example);
});
}
}
}
}
/**
* 获取默认的自定义字段的取值同步之后更新成第三方平台的值
@ -967,6 +1214,7 @@ public class IssuesService {
/**
* 获取缺陷状态的自定义字段替换
*
* @param planIssues
* @param planId
*/
@ -1063,38 +1311,17 @@ public class IssuesService {
return issuesMapper.selectByExampleWithBLOBs(example);
}
public List<IssuesDao> getPlatformIssueByIds(List<String> platformIds, String projectId) {
// todo 是否保留
List<IssuesDao> issues = extIssuesMapper.getPlatformIssueByIds(platformIds, projectId);
if (CollectionUtils.isEmpty(issues)) {
return issues;
}
List<String> issueIds = issues.stream().map(IssuesDao::getId).collect(Collectors.toList());
List<IssuesDao> issuesList = extIssuesMapper.getIssueCustomFields(issueIds);
Map<String, List<CustomFieldItemDTO>> map = new HashMap<>();
issuesList.forEach(f -> {
CustomFieldItemDTO dto = new CustomFieldItemDTO();
dto.setId(f.getFieldId());
dto.setName(f.getFieldName());
dto.setType(f.getFieldType());
dto.setValue(f.getFieldValue());
dto.setCustomData(f.getCustomData());
List<CustomFieldItemDTO> list = Optional.ofNullable(map.get(f.getId())).orElse(new ArrayList<>());
map.put(f.getId(), list);
list.add(dto);
});
issues.forEach(i -> i.setCustomFieldList(map.getOrDefault(i.getId(), new ArrayList<>())));
return issues;
}
public IssueTemplateDao getThirdPartTemplate(String projectId) {
IssueTemplateDao issueTemplateDao = new IssueTemplateDao();
if (StringUtils.isNotBlank(projectId)) {
Project project = baseProjectService.getProjectById(projectId);
return IssueFactory.createPlatform(IssuesManagePlatform.Jira.toString(), getDefaultIssueRequest(projectId, project.getWorkspaceId()))
.getThirdPartTemplate();
List<PlatformCustomFieldItemDTO> thirdPartCustomField = platformPluginService.getPlatform(project.getPlatform(), project.getWorkspaceId())
.getThirdPartCustomField(PlatformPluginService.getCompatibleProjectConfig(project));
List<CustomFieldDao> customFieldDaoList = JSON.parseArray(JSON.toJSONString(thirdPartCustomField), CustomFieldDao.class);
issueTemplateDao.setCustomFields(customFieldDaoList);
issueTemplateDao.setPlatform(project.getPlatform());
}
return new IssueTemplateDao();
return issueTemplateDao;
}
public IssuesRequest getDefaultIssueRequest(String projectId, String workspaceId) {
@ -1104,24 +1331,20 @@ public class IssuesService {
return issuesRequest;
}
public List<JiraIssueType> getIssueTypes(JiraIssueTypeRequest request) {
IssuesRequest issuesRequest = getDefaultIssueRequest(request.getProjectId(), request.getWorkspaceId());
JiraPlatform platform = (JiraPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Jira.toString(), issuesRequest);
if (StringUtils.isNotBlank(request.getJiraKey())) {
return platform.getIssueTypes(request.getJiraKey());
} else {
return new ArrayList<>();
}
}
public List<DemandDTO> getDemandList(String projectId) {
public List getDemandList(String projectId) {
Project project = baseProjectService.getProjectById(projectId);
String workspaceId = project.getWorkspaceId();
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setWorkspaceId(workspaceId);
issueRequest.setProjectId(projectId);
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issueRequest);
return platform.getDemandList(projectId);
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
return platformPluginService.getPlatform(project.getPlatform())
.getDemands(PlatformPluginService.getCompatibleProjectConfig(project));
} else {
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setWorkspaceId(workspaceId);
issueRequest.setProjectId(projectId);
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issueRequest);
return platform.getDemandList(projectId);
}
}
public List<IssuesDao> listByWorkspaceId(IssuesRequest request) {
@ -1134,47 +1357,36 @@ public class IssuesService {
if (!StringUtils.isBlank(request.getPlatformKey())) {
Project project = baseProjectService.getProjectById(request.getProjectId());
List<String> platforms = getPlatforms(project);
if (CollectionUtils.isEmpty(platforms)) {
return platformStatusDTOS;
}
String platform = project.getPlatform();
if (PlatformPluginService.isPluginPlatform(platform)) {
return platformPluginService.getPlatform(platform)
.getStatusList(request.getPlatformKey())
.stream().map(item -> {
PlatformStatusDTO platformStatusDTO = new PlatformStatusDTO();
platformStatusDTO.setLabel(item.getLabel());
platformStatusDTO.setValue(item.getValue());
return platformStatusDTO;
})
.collect(Collectors.toList());
} else {
List<String> platforms = getPlatforms(project);
if (CollectionUtils.isEmpty(platforms)) {
return platformStatusDTOS;
}
IssuesRequest issuesRequest = getDefaultIssueRequest(request.getProjectId(), request.getWorkspaceId());
Map<String, IssuesPlatform> platformMap = IssueFactory.createPlatformsForMap(platforms, issuesRequest);
try {
if (platformMap.size() > 1) {
MSException.throwException(Translator.get("project_reference_multiple_plateform"));
}
Optional<IssuesPlatform> platformOptional = platformMap.values().stream().findFirst();
if (platformOptional.isPresent()) {
platformStatusDTOS = platformOptional.get().getTransitions(request.getPlatformKey());
}
} catch (Exception e) {
LogUtil.error(e);
IssuesRequest issuesRequest = getDefaultIssueRequest(request.getProjectId(), request.getWorkspaceId());
return IssueFactory.createPlatform(platform, issuesRequest).getTransitions(request.getPlatformKey());
}
}
return platformStatusDTOS;
}
public void deleteIssueAttachments(String issueId) {
attachmentService.deleteAttachment(AttachmentType.ISSUE.type(), issueId);
IssueFileExample example = new IssueFileExample();
example.createCriteria().andIssueIdEqualTo(issueId);
List<IssueFile> issueFiles = issueFileMapper.selectByExample(example);
if (issueFiles.size() == 0) {
return;
}
List<String> ids = issueFiles.stream().map(IssueFile::getFileId).collect(Collectors.toList());
attachmentService.deleteFileAttachmentByIds(ids);
issueFileMapper.deleteByExample(example);
}
public boolean isThirdPartTemplate(Project project) {
return project.getThirdPartTemplate() != null && project.getThirdPartTemplate() && project.getPlatform().equals(IssuesManagePlatform.Jira.name());
return project.getThirdPartTemplate() != null
&& project.getThirdPartTemplate()
&& PlatformPluginService.isPluginPlatform(project.getPlatform());
}
public void checkThirdProjectExist(Project project) {
IssuesRequest issuesRequest = new IssuesRequest();
if (StringUtils.isBlank(project.getId())) {
@ -1373,9 +1585,10 @@ public class IssuesService {
}
private IssueTemplateDao getIssueTemplateByProjectId(String projectId) {
IssueTemplateDao issueTemplateDao = new IssueTemplateDao();
IssueTemplateDao issueTemplateDao;
Project project = baseProjectService.getProjectById(projectId);
if (StringUtils.equals(project.getPlatform(), IssuesManagePlatform.Jira.name()) && project.getThirdPartTemplate()) {
if (PlatformPluginService.isPluginPlatform(project.getPlatform())
&& project.getThirdPartTemplate()) {
// 第三方Jira平台
issueTemplateDao = getThirdPartTemplate(project.getId());
issueTemplateDao.setIsThirdTemplate(Boolean.TRUE);
@ -1407,7 +1620,7 @@ public class IssuesService {
return filterIssues;
}
private void uploadAzureCopyAttachment(AttachmentRequest attachmentRequest, String platform, String platformId) {
private void uploadAzureCopyAttachment(AttachmentRequest attachmentRequest, String platform, String platformId) {
List<String> attachmentIds = attachmentService.getAttachmentIdsByParam(attachmentRequest);
if (CollectionUtils.isNotEmpty(attachmentIds)) {
attachmentIds.forEach(attachmentId -> {
@ -1478,4 +1691,10 @@ public class IssuesService {
}
}
}
public boolean thirdPartTemplateEnable(String projectId) {
Project project = baseProjectService.getProjectById(projectId);
return BooleanUtils.isTrue(project.getThirdPartTemplate())
&& platformPluginService.isThirdPartTemplateSupport(project.getPlatform());
}
}

View File

@ -1,15 +1,26 @@
package io.metersphere.service;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.utils.JSON;
import io.metersphere.platform.api.Platform;
import io.metersphere.platform.api.PluginMetaInfo;
import io.metersphere.base.domain.PluginWithBLOBs;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.ServiceIntegration;
import io.metersphere.commons.constants.PluginScenario;
import io.metersphere.loader.PlatformPluginManager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.platform.domain.PlatformRequest;
import io.metersphere.platform.loader.PlatformPluginManager;
import io.metersphere.request.IntegrationRequest;
import io.metersphere.utils.PluginManagerUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
@ -19,6 +30,8 @@ public class PlatformPluginService {
private BasePluginService basePluginService;
@Resource
private BaseIntegrationService baseIntegrationService;
@Resource
private BaseProjectService baseProjectService;
private PlatformPluginManager pluginManager;
@ -46,4 +59,48 @@ public class PlatformPluginService {
public void unloadPlugin(String pluginId) {
pluginManager.deletePlugin(pluginId);
}
public boolean isThirdPartTemplateSupport(String platform) {
if (StringUtils.isBlank(platform)) {
return false;
}
PluginMetaInfo pluginMetaInfo = pluginManager.getPluginMetaInfoByKey(platform);
return pluginMetaInfo.isThirdPartTemplateSupport();
}
public Platform getPlatform(String platformKey, String workspaceId) {
IntegrationRequest integrationRequest = new IntegrationRequest();
integrationRequest.setPlatform(platformKey);
integrationRequest.setWorkspaceId(StringUtils.isBlank(workspaceId) ? SessionUtils.getCurrentWorkspaceId() : workspaceId);
ServiceIntegration serviceIntegration = baseIntegrationService.get(integrationRequest);
PlatformRequest pluginRequest = new PlatformRequest();
pluginRequest.setIntegrationConfig(serviceIntegration.getConfiguration());
return pluginManager.getPlatformByKey(platformKey, pluginRequest);
}
public Platform getPlatform(String platformKey) {
return this.getPlatform(platformKey, null);
}
public static String getCompatibleProjectConfig(Project project) {
String issueConfig = project.getIssueConfig();
Map map = JSON.parseMap(issueConfig);
map.put("jiraKey", project.getJiraKey());
map.put("tapdId", project.getTapdId());
map.put("azureDevopsId", project.getAzureDevopsId());
map.put("zentaoId", project.getZentaoId());
map.put("thirdPartTemplate", project.getThirdPartTemplate());
return JSON.toJSONString(map);
}
public static boolean isPluginPlatform(String platform) {
if (StringUtils.equalsAnyIgnoreCase(platform,
IssuesManagePlatform.Tapd.name(), IssuesManagePlatform.AzureDevops.name(),
IssuesManagePlatform.Zentao.name(), IssuesManagePlatform.Local.name())) {
return false;
}
return true;
}
}

View File

@ -11,19 +11,16 @@ import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.dto.CustomFieldItemDTO;
import io.metersphere.xpack.track.dto.IssueSyncRequest;
import io.metersphere.xpack.track.dto.IssueTemplateDao;
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
import io.metersphere.dto.UserDTO;
import io.metersphere.request.IntegrationRequest;
import io.metersphere.xpack.track.dto.EditTestCaseRequest;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import io.metersphere.service.*;
import io.metersphere.xpack.track.issue.IssuesPlatform;
import io.metersphere.service.issue.domain.ProjectIssueConfig;
import io.metersphere.service.wapper.TrackProjectService;
import io.metersphere.service.wapper.UserService;
import io.metersphere.xpack.track.dto.*;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import io.metersphere.xpack.track.issue.IssuesPlatform;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@ -654,4 +651,8 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
return null;
}
@Override
public List<IssuesDao> getIssue(IssuesRequest request) {
return null;
}
}

View File

@ -16,8 +16,6 @@ public class IssueFactory {
public static IssuesPlatform createPlatform(String platform, IssuesRequest addIssueRequest) {
if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) {
return new TapdPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) {
return new JiraPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Zentao.toString(), platform)) {
return new ZentaoPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.AzureDevops.toString(), platform)) {
@ -46,15 +44,4 @@ public class IssueFactory {
});
return platforms;
}
public static Map<String, IssuesPlatform> createPlatformsForMap(List<String> types, IssuesRequest addIssueRequest) {
Map<String, IssuesPlatform> platformMap = new HashMap<>();
types.forEach(type -> {
IssuesPlatform abstractIssuePlatform = createPlatform(type, addIssueRequest);
if (abstractIssuePlatform != null) {
platformMap.put(type, abstractIssuePlatform);
}
});
return platformMap;
}
}

View File

@ -2,19 +2,17 @@ package io.metersphere.service.issue.platform;
import io.metersphere.base.domain.IssuesWithBLOBs;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.xpack.track.dto.AttachmentSyncType;
import io.metersphere.dto.CustomFieldItemDTO;
import io.metersphere.request.testcase.TestCaseBatchRequest;
import io.metersphere.xpack.track.dto.AttachmentSyncType;
import io.metersphere.xpack.track.dto.DemandDTO;
import io.metersphere.xpack.track.dto.IssuesDao;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
import io.metersphere.request.testcase.TestCaseBatchRequest;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
@ -29,16 +27,6 @@ public class LocalPlatform extends LocalAbstractPlatform {
super(issuesRequest);
}
@Override
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
String projectId = issuesRequest.getProjectId();
issuesRequest.setPlatform(IssuesManagePlatform.Local.toString());
if (StringUtils.isNotBlank(projectId)) {
return extIssuesMapper.getIssues(issuesRequest);
}
return extIssuesMapper.getIssuesByCaseId(issuesRequest);
}
@Override
public List<DemandDTO> getDemandList(String projectId) {
return null;

View File

@ -1,6 +1,6 @@
package io.metersphere.service.plugin;
import im.metersphere.storage.StorageStrategy;
import im.metersphere.plugin.storage.StorageStrategy;
import io.metersphere.commons.constants.StorageConstants;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.metadata.service.FileManagerService;

View File

@ -1,10 +1,9 @@
package io.metersphere.service.wapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.service.PlatformPluginService;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.service.issue.platform.IssueFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
@ -20,6 +19,8 @@ public class IssueProxyResourceService {
@Resource
private RestTemplate restTemplate;
@Resource
private PlatformPluginService platformPluginService;
/**
* http 代理
@ -37,8 +38,9 @@ public class IssueProxyResourceService {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setProjectId(projectId);
issuesRequest.setWorkspaceId(workspaceId);
return IssueFactory.createPlatform(platform, issuesRequest)
return platformPluginService.getPlatform(platform)
.proxyForGet(url, byte[].class);
}
return restTemplate.exchange(url, HttpMethod.GET, null, byte[].class);
}

View File

@ -34,4 +34,9 @@ public class UserService {
}
return JSON.parseObject(JSON.toJSONString(platformInfo), UserDTO.PlatformInfo.class);
}
public String getCurrentPlatformInfoStr(String workspaceId) {
UserDTO.PlatformInfo currentPlatformInfo = getCurrentPlatformInfo(workspaceId);
return currentPlatformInfo == null ? null : JSON.toJSONString(currentPlatformInfo);
}
}

View File

@ -1,6 +1,6 @@
package io.metersphere.utils;
import im.metersphere.loader.PluginManager;
import im.metersphere.plugin.loader.PluginManager;
import io.metersphere.base.domain.PluginWithBLOBs;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;

View File

@ -4,7 +4,7 @@ import {getUUID} from "metersphere-frontend/src/utils";
import {getCurrentProjectID, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {getCurrentProject} from "./project";
import {JIRA, LOCAL} from "metersphere-frontend/src/utils/constants";
import {LOCAL} from "metersphere-frontend/src/utils/constants";
import {getIssueTemplate} from "./custom-field-template";
import {$success, $warning} from "metersphere-frontend/src/plugins/message";
import i18n from "../i18n";
@ -212,8 +212,8 @@ export function getPlatformTransitions(param) {
return post('/issues/platform/transitions', param);
}
export function enableThirdPartTemplate(currentProject) {
return currentProject && currentProject.thirdPartTemplate && currentProject.platform === JIRA;
export function enableThirdPartTemplate(projectId) {
return get(BASE_URL + '/third/part/template/enable/' + projectId);
}
export function buildIssues(page) {
@ -230,18 +230,21 @@ export function buildIssues(page) {
export function getIssuePartTemplateWithProject(callback) {
getCurrentProject().then((response) => {
let currentProject = response.data;
if (enableThirdPartTemplate(currentProject)) {
getIssueThirdPartTemplate()
.then((template) => {
if (callback)
callback(template, currentProject);
});
} else {
getIssueTemplate()
.then((template) => {
if (callback)
callback(template, currentProject);
});
}
enableThirdPartTemplate(currentProject.id)
.then((r) => {
if (r.data) {
getIssueThirdPartTemplate()
.then((template) => {
if (callback)
callback(template, currentProject);
});
} else {
getIssueTemplate()
.then((template) => {
if (callback)
callback(template, currentProject);
});
}
});
});
}

View File

@ -6,25 +6,33 @@
<ms-form-divider :title="$t('test_track.plan_view.base_info')"/>
<el-form-item v-if="!enableThirdPartTemplate" :label="$t('commons.title')" prop="title">
<el-row>
<el-col :span="22">
<el-col :span="22">
<el-input v-model="form.title" autocomplete="off" class="top-input-class"></el-input>
</el-col>
<el-col :span="2">
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; margin-left: 15px;cursor: pointer;position: relative;top: 5px" @click="saveFollow" />
<el-col :span="2">
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off"
style="color: #783987; font-size: 25px; margin-left: 15px;cursor: pointer;position: relative;top: 5px"
@click="saveFollow"/>
</el-tooltip>
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="showFollow" >
<i class="el-icon-star-on" style="color: #783987; font-size: 28px; margin-left: 15px; cursor: pointer;position: relative;top: 5px" @click="saveFollow" />
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="showFollow">
<i class="el-icon-star-on"
style="color: #783987; font-size: 28px; margin-left: 15px; cursor: pointer;position: relative;top: 5px"
@click="saveFollow"/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<div v-else style="text-align: right; margin-bottom: 5px">
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off" style="color: #783987; font-size: 25px; margin-left: 15px;cursor: pointer;position: relative;top: 5px" @click="saveFollow" />
<el-tooltip :content="$t('commons.follow')" placement="bottom" effect="dark" v-if="!showFollow">
<i class="el-icon-star-off"
style="color: #783987; font-size: 25px; margin-left: 15px;cursor: pointer;position: relative;top: 5px"
@click="saveFollow"/>
</el-tooltip>
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="showFollow" >
<i class="el-icon-star-on" style="color: #783987; font-size: 28px; margin-left: 15px; cursor: pointer;position: relative;top: 5px" @click="saveFollow" />
<el-tooltip :content="$t('commons.cancel')" placement="bottom" effect="dark" v-if="showFollow">
<i class="el-icon-star-on"
style="color: #783987; font-size: 28px; margin-left: 15px; cursor: pointer;position: relative;top: 5px"
@click="saveFollow"/>
</el-tooltip>
</div>
@ -118,10 +126,14 @@
: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-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>
<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>
@ -198,7 +210,12 @@ import CustomFiledComponent from "metersphere-frontend/src/components/template/C
import TestCaseIssueList from "@/business/issue/TestCaseIssueList";
import IssueEditDetail from "@/business/issue/IssueEditDetail";
import {byteToSize, getTypeByFileName, getUUID} from "metersphere-frontend/src/utils";
import {getCurrentProjectID, getCurrentUser, getCurrentWorkspaceId, getCurrentUserId} from "metersphere-frontend/src/utils/token"
import {
getCurrentProjectID,
getCurrentUser,
getCurrentWorkspaceId,
getCurrentUserId
} from "metersphere-frontend/src/utils/token"
import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {
enableThirdPartTemplate,
@ -250,7 +267,7 @@ export default {
data() {
return {
type: null,
issueId:'',
issueId: '',
result: {
loading: false
},
@ -275,8 +292,8 @@ export default {
description: '',
creator: null,
remark: null,
tapdUsers:[],
zentaoBuilds:[],
tapdUsers: [],
zentaoBuilds: [],
zentaoAssigned: '',
platformStatus: null,
copyIssueId: ''
@ -335,6 +352,7 @@ export default {
relateFiles: [],
unRelateFiles: [],
dumpFile: {},
enableThirdPartTemplate: false
};
},
props: {
@ -356,9 +374,6 @@ export default {
projectId() {
return getCurrentProjectID();
},
enableThirdPartTemplate() {
return enableThirdPartTemplate(this.currentProject);
},
},
watch: {
tabActiveName() {
@ -376,8 +391,8 @@ export default {
description: '',
creator: null,
remark: null,
tapdUsers:[],
zentaoBuilds:[],
tapdUsers: [],
zentaoBuilds: [],
zentaoAssigned: '',
platformStatus: null
};
@ -403,6 +418,11 @@ export default {
this.currentProject = project;
this.init(template, data);
this.getDataInfoAsync(data);
enableThirdPartTemplate(this.currentProject.id)
.then(r => {
this.enableThirdPartTemplate = r.data;
});
});
});
},
@ -460,21 +480,21 @@ export default {
if (platform === 'Zentao') {
this.hasZentaoId = true;
getZentaoBuilds(data)
.then((response) => {
if (response.data) {
this.Builds = response.data;
}
getZentaoUser(data)
.then((response) => {
this.zentaoUsers = response.data;
if (response.data) {
this.Builds = response.data;
}
getZentaoUser(data)
.then((response) => {
this.zentaoUsers = response.data;
})
})
})
} else if (platform === 'Tapd') {
this.hasTapdId = true;
getTapdUser(data)
.then((response) => {
this.tapdUsers = response.data;
})
.then((response) => {
this.tapdUsers = response.data;
})
}
},
initEdit(data) {
@ -627,30 +647,30 @@ export default {
}
};
},
saveFollow(){
if(!this.form.follows){
saveFollow() {
if (!this.form.follows) {
this.form.follows = [];
}
if(this.showFollow){
if (this.showFollow) {
this.showFollow = false;
for (let i = 0; i < this.form.follows.length; i++) {
if(this.form.follows[i]===this.currentUser().id){
this.form.follows.splice(i,1)
if (this.form.follows[i] === this.currentUser().id) {
this.form.follows.splice(i, 1)
break;
}
}
if(this.url === "issues/update"){
if (this.url === "issues/update") {
saveFollow(this.issueId, this.form.follows).then(() => {
this.$success(this.$t('commons.cancel_follow_success'));
})
}
}else {
} else {
this.showFollow = true;
if(!this.form.follows){
if (!this.form.follows) {
this.form.follows = [];
}
this.form.follows.push(this.currentUser().id)
if(this.url === "issues/update"){
if (this.url === "issues/update") {
saveFollow(this.issueId, this.form.follows).then(() => {
this.$success(this.$t('commons.follow_success'));
})
@ -679,8 +699,8 @@ export default {
name: file.name,
size: byteToSize(file.size),
updateTime: new Date().getTime(),
progress: this.type === 'add' || this.isCaseEdit? 100 : 0,
status: this.type === 'add' || this.isCaseEdit? 'toUpload' : 0,
progress: this.type === 'add' || this.isCaseEdit ? 100 : 0,
status: this.type === 'add' || this.isCaseEdit ? 'toUpload' : 0,
creator: user.name,
type: getTypeByFileName(file.name),
isLocal: true
@ -702,27 +722,27 @@ export default {
let CancelToken = axios.CancelToken
let self = this;
uploadIssueAttachment(file, data, CancelToken, self.cancelFileToken, progressCallback)
.then(response => { //
progress = 100;
param.onSuccess(response);
progressCallback({progress, status: 'success'});
self.cancelFileToken.forEach((token, index, array)=>{
if(token.name == file.name){
array.splice(token,1)
}
})
}).catch(({error}) => { //
.then(response => { //
progress = 100;
param.onSuccess(response);
progressCallback({progress, status: 'success'});
self.cancelFileToken.forEach((token, index, array) => {
if (token.name == file.name) {
array.splice(token, 1)
}
})
}).catch(({error}) => { //
progress = 100;
progressCallback({progress, status: 'error'});
self.cancelFileToken.forEach((token, index, array)=>{
if(token.name == file.name){
array.splice(token,1)
self.cancelFileToken.forEach((token, index, array) => {
if (token.name == file.name) {
array.splice(token, 1)
}
})
});
},
showProgress(file, params) {
const { progress, status } = params
const {progress, status} = params
const arr = [...this.tableData].map(item => {
if (item.name === file.name) {
item.progress = progress
@ -737,18 +757,18 @@ export default {
},
handleSuccess(response, file, fileList) {
let readyFiles = fileList.filter(item => item.status === 'success')
if (readyFiles.length === fileList.length ) {
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 ) {
if (readyFiles.length === fileList.length) {
this.getFileMetaData(this.issueId);
}
},
handleDelete(file, index) {
this.$alert((this.cancelFileToken.length > 0 ? this.$t('load_test.delete_file_when_uploading') + '<br/>': "") + this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
this.$alert((this.cancelFileToken.length > 0 ? this.$t('load_test.delete_file_when_uploading') + '<br/>' : "") + this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
confirmButtonText: this.$t('commons.confirm'),
dangerouslyUseHTMLString: true,
callback: (action) => {
@ -772,10 +792,10 @@ export default {
this.uploadFiles.splice(delIndex, 1);
} else {
deleteIssueAttachment(file.id)
.then(() => {
this.$success(this.$t('commons.delete_success'));
this.getFileMetaData(this.issueId);
});
.then(() => {
this.$success(this.$t('commons.delete_success'));
this.getFileMetaData(this.issueId);
});
}
},
handleUnRelate(file, index) {
@ -797,11 +817,11 @@ export default {
let data = {'belongType': 'issue', 'belongId': this.issueId, 'metadataRefIds': this.unRelateFiles};
this.result.loading = true;
unrelatedAttachment(data)
.then(() => {
this.$success(this.$t('commons.unrelated_success'));
this.result.loading = false;
this.getFileMetaData(this.issueId);
})
.then(() => {
this.$success(this.$t('commons.unrelated_success'));
this.result.loading = false;
this.getFileMetaData(this.issueId);
})
}
}
}
@ -827,7 +847,7 @@ export default {
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);
this.$error(this.$t('load_test.exist_related_file') + ": " + row.name);
repeatRecord = true;
break;
}
@ -856,21 +876,23 @@ export default {
let data = {'belongType': 'issue', 'belongId': this.issueId, 'metadataRefIds': metadataRefIds};
this.result.loading = true;
relatedAttachment(data)
.then(() => {
this.$success(this.$t('commons.relate_success'));
this.result.loading = false;
this.getFileMetaData(this.issueId);
});
.then(() => {
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};
let data = {
id: getUUID(), resourceId: getCurrentProjectID(), moduleId: moduleId,
projectId: getCurrentProjectID(), fileName: this.dumpFile.name, attachmentId: this.dumpFile.id
};
dumpAttachment(data)
.then(() => {
this.$success(this.$t("organization.integration.successful_operation"));
});
.then(() => {
this.$success(this.$t("organization.integration.successful_operation"));
});
},
getFileMetaData(id) {
if (this.type === 'edit') {
@ -926,7 +948,7 @@ export default {
margin-left: 20px;
}
.top-input-class{
.top-input-class {
width: 100%;
}