refactor(功能用例): 关联需求支持父子层级&&自定义字段搜索条件参数修改

This commit is contained in:
song-cc-rock 2024-01-27 15:18:02 +08:00 committed by Craftsman
parent 8cf01882d3
commit 8e3bf3d087
16 changed files with 319 additions and 289 deletions

View File

@ -35,6 +35,10 @@ public class PlatformDemandDTO {
* 需求地址
*/
private String demandUrl;
/**
* 子需求集合
*/
private List<Demand> children;
/**
* 自定义字段
*/

View File

@ -2,6 +2,7 @@ package io.metersphere.plugin.platform.dto.request;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
@ -20,7 +21,7 @@ public class DemandPageRequest {
/**
* 筛选条件
*/
private Map<String, Object> filter;
private Map<String, List<String>> filter;
/**
* 开始页码

View File

@ -28,12 +28,15 @@ public class BugProviderDTO implements Serializable {
@Schema(description = "处理人")
private String handleUser;
@Schema(description = "处理人")
@Schema(description = "处理人")
private String handleUserName;
@Schema(description = "缺陷状态")
private String status;
@Schema(description = "缺陷状态名称")
private String statusName;
@Schema(description = "标签")
private List<String> tags;

View File

@ -24,6 +24,10 @@ public class AssociateBugPageRequest extends BaseProviderCondition {
@NotBlank(message = "{functional_case.id.not_blank}")
private String caseId;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.project_id.not_blank}")
private String projectId;
@Min(value = 1, message = "当前页码必须大于0")
@Schema(description = "当前页码")

View File

@ -50,6 +50,8 @@ public class BugController {
@Resource
private BugService bugService;
@Resource
private BugCommonService bugCommonService;
@Resource
private BugSyncService bugSyncService;
@Resource
private BugStatusService bugStatusService;
@ -77,7 +79,7 @@ public class BugController {
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<SelectOption> getHeaderHandleOption(@PathVariable String projectId) {
return bugService.getHeaderHandlerOption(projectId);
return bugCommonService.getHeaderHandlerOption(projectId);
}
@PostMapping("/page")

View File

@ -5,8 +5,11 @@ import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugMapper;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.bug.service.BugCommonService;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.bug.service.BugStatusService;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateBugPageRequest;
import io.metersphere.request.AssociateBugRequest;
@ -24,6 +27,11 @@ import java.util.List;
@Service
public class AssociateBugProvider implements BaseAssociateBugProvider {
@Resource
private BugCommonService bugCommonService;
@Resource
private BugStatusService bugStatusService;
@Resource
private ExtBugMapper extBugMapper;
@Resource
@ -36,8 +44,8 @@ public class AssociateBugProvider implements BaseAssociateBugProvider {
@Override
public List<BugProviderDTO> getBugList(String sourceType, String sourceName, String bugColumnName, BugPageProviderRequest bugPageProviderRequest) {
return extBugMapper.listByProviderRequest(sourceType, sourceName, bugColumnName, bugPageProviderRequest, false);
//TODO 需要转义状态和处理人属性
List<BugProviderDTO> associateBugs = extBugMapper.listByProviderRequest(sourceType, sourceName, bugColumnName, bugPageProviderRequest, false);
return buildAssociateBugs(associateBugs, bugPageProviderRequest.getProjectId());
}
@Override
@ -78,14 +86,33 @@ public class AssociateBugProvider implements BaseAssociateBugProvider {
@Override
public List<BugProviderDTO> hasAssociateBugPage(AssociateBugPageRequest request) {
List<BugProviderDTO> associateBugs = extBugRelateCaseMapper.getAssociateBugs(request, request.getSortString());
//TODO 需要转义状态和处理人属性
associateBugs.stream().forEach(item -> {
associateBugs.forEach(item -> {
if (StringUtils.isNotBlank(item.getTestPlanName())) {
item.setSource(Translator.get("test_plan_relate"));
} else {
item.setSource(Translator.get("direct_related"));
}
});
return buildAssociateBugs(associateBugs, request.getProjectId());
}
/**
* 关联缺陷列表数据处理
* @param associateBugs 关联缺陷
* @param projectId 项目ID
* @return 关联缺陷列表
*/
private List<BugProviderDTO> buildAssociateBugs(List<BugProviderDTO> associateBugs, String projectId) {
List<SelectOption> headerHandlerOption = bugCommonService.getHeaderHandlerOption(projectId);
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(projectId);
associateBugs.forEach(item -> {
headerHandlerOption.stream().filter(option -> StringUtils.equals(option.getValue(), item.getHandleUser())).findFirst().ifPresent(option -> {
item.setHandleUserName(option.getText());
});
statusOption.stream().filter(option -> StringUtils.equals(option.getValue(), item.getStatus())).findFirst().ifPresent(option -> {
item.setStatusName(option.getText());
});
});
return associateBugs;
}
}

View File

@ -12,8 +12,10 @@ import io.metersphere.bug.enums.BugAttachmentSourceType;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.plugin.platform.dto.PlatformAttachment;
import io.metersphere.plugin.platform.dto.request.SyncAttachmentToPlatformRequest;
import io.metersphere.plugin.platform.enums.SyncAttachmentType;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.domain.FileAssociationExample;
import io.metersphere.project.domain.FileMetadata;
@ -27,6 +29,7 @@ import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.service.FileAssociationService;
import io.metersphere.project.service.FileMetadataService;
import io.metersphere.project.service.FileService;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.exception.MSException;
@ -42,10 +45,10 @@ import jakarta.annotation.Resource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.unit.DataSize;
@ -55,10 +58,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -74,14 +74,13 @@ public class BugAttachmentService {
@Resource
private FileMetadataService fileMetadataService;
@Resource
@Lazy
private BugSyncExtraService bugSyncExtraService;
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileAssociationService fileAssociationService;
@Resource
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
@Resource
private ProjectApplicationService projectApplicationService;
@Value("50MB")
private DataSize maxFileSize;
@ -161,7 +160,7 @@ public class BugAttachmentService {
// 同步至第三方(异步调用)
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
}
}
@ -187,7 +186,7 @@ public class BugAttachmentService {
}
// 同步至第三方(异步调用)
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
}
}
@ -254,7 +253,7 @@ public class BugAttachmentService {
List<SyncAttachmentToPlatformRequest> syncLinkFiles = uploadLinkFile(bug.getId(), bug.getPlatformBugId(), request.getProjectId(), tempFileDir, List.of(upgradeFileId), currentUser, bug.getPlatform(), true);
List<SyncAttachmentToPlatformRequest> platformAttachments = Stream.concat(syncUnlinkFiles.stream(), syncLinkFiles.stream()).toList();
if (!StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
bugSyncExtraService.syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
syncAttachmentToPlatform(platformAttachments, request.getProjectId(), tempFileDir);
}
return upgradeFileId;
}
@ -286,6 +285,137 @@ public class BugAttachmentService {
return fileId;
}
/**
* 同步附件到平台
* @param platformAttachments 平台附件参数
* @param projectId 项目ID
* @param tmpFilePath 临时文件路径
*/
@Async
public void syncAttachmentToPlatform(List<SyncAttachmentToPlatformRequest> platformAttachments, String projectId, File tmpFilePath) {
// 平台缺陷需同步附件
Platform platform = projectApplicationService.getPlatform(projectId, true);
platformAttachments.forEach(platform::syncAttachmentToPlatform);
tmpFilePath.deleteOnExit();
}
/**
* 同步平台附件到MS
* @param platform 平台对象
* @param attachmentMap 平台附件缺陷集合
* @param projectId 项目ID
*/
@Async
public void syncAttachmentToMs(Platform platform, Map<String, List<PlatformAttachment>> attachmentMap, String projectId) {
for (String bugId : attachmentMap.keySet()) {
List<PlatformAttachment> syncAttachments = attachmentMap.get(bugId);
// 获取所有MS附件
Set<String> platformAttachmentSet = new HashSet<>();
List<BugFileDTO> allBugFiles = getAllBugFiles(bugId);
Set<String> attachmentsNameSet = allBugFiles.stream().map(BugFileDTO::getFileName).collect(Collectors.toSet());
for (PlatformAttachment syncAttachment : syncAttachments) {
String fileName = syncAttachment.getFileName();
String fileKey = syncAttachment.getFileKey();
platformAttachmentSet.add(fileName);
if (!attachmentsNameSet.contains(fileName)) {
saveSyncAttachmentToMs(platform, bugId, fileName, fileKey, projectId);
}
}
// 删除Jira中不存在的附件
deleteSyncAttachmentFromMs(platformAttachmentSet, allBugFiles, bugId, projectId);
}
}
/**
* 保存同步附件到MS
* @param platform 平台对象
* @param bugId 缺陷ID
* @param fileName 附件名称
* @param fileKey 附件唯一Key
* @param projectId 项目ID
*/
public void saveSyncAttachmentToMs(Platform platform, String bugId, String fileName, String fileKey, String projectId) {
try {
platform.getAttachmentContent(fileKey, (in) -> {
if (in == null) {
return;
}
String fileId = IDGenerator.nextStr();
byte[] bytes;
try {
// upload platform attachment to minio
bytes = in.readAllBytes();
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(projectId, bugId, fileId, fileName));
} catch (Exception e) {
throw new MSException(e.getMessage());
}
// save bug attachment relation
BugLocalAttachment localAttachment = new BugLocalAttachment();
localAttachment.setId(IDGenerator.nextStr());
localAttachment.setBugId(bugId);
localAttachment.setFileId(fileId);
localAttachment.setFileName(fileName);
localAttachment.setSize((long) bytes.length);
localAttachment.setCreateTime(System.currentTimeMillis());
localAttachment.setCreateUser("admin");
localAttachment.setSource(BugAttachmentSourceType.ATTACHMENT.name());
bugLocalAttachmentMapper.insert(localAttachment);
});
} catch (Exception e) {
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}
/**
* 删除MS中不存在的平台附件
* @param platformAttachmentSet 已处理的平台附件集合
* @param allMsAttachments 所有MS附件集合
* @param bugId 缺陷ID
* @param projectId 项目ID
*/
public void deleteSyncAttachmentFromMs(Set<String> platformAttachmentSet, List<BugFileDTO> allMsAttachments, String bugId, String projectId) {
try {
// 删除MS中不存在的平台附件
if (!CollectionUtils.isEmpty(allMsAttachments)) {
List<BugFileDTO> deleteMsAttachments = allMsAttachments.stream()
.filter(msAttachment -> !platformAttachmentSet.contains(msAttachment.getFileName()))
.toList();
List<String> unLinkIds = new ArrayList<>();
List<String> deleteLocalIds = new ArrayList<>();
deleteMsAttachments.forEach(deleteMsFile -> {
if (deleteMsFile.getAssociated()) {
unLinkIds.add(deleteMsFile.getRefId());
} else {
deleteLocalIds.add(deleteMsFile.getRefId());
}
});
if (!CollectionUtils.isEmpty(unLinkIds)) {
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andIdIn(unLinkIds);
fileAssociationMapper.deleteByExample(example);
}
if (!CollectionUtils.isEmpty(deleteLocalIds)) {
Map<String, BugFileDTO> localFileMap = deleteMsAttachments.stream().collect(Collectors.toMap(BugFileDTO::getRefId, f -> f));
deleteLocalIds.forEach(deleteLocalId -> {
try {
BugFileDTO bugFileDTO = localFileMap.get(deleteLocalId);
FileCenter.getDefaultRepository().delete(buildBugFileRequest(projectId, bugId, bugFileDTO.getFileId(), bugFileDTO.getFileName()));
} catch (Exception e) {
throw new MSException(e.getMessage());
}
});
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
example.createCriteria().andIdIn(deleteLocalIds);
bugLocalAttachmentMapper.deleteByExample(example);
}
}
} catch (Exception e) {
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}
/**
* 获取本地文件字节流

View File

@ -0,0 +1,103 @@
package io.metersphere.bug.service;
import io.metersphere.bug.dto.BugTemplateInjectField;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.enums.BugTemplateCustomField;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.platform.dto.request.GetOptionRequest;
import io.metersphere.plugin.platform.spi.AbstractPlatformPlugin;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.dto.ProjectUserDTO;
import io.metersphere.project.request.ProjectMemberRequest;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.project.service.ProjectMemberService;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.service.PlatformPluginService;
import io.metersphere.system.service.PluginLoadService;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugCommonService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
private ProjectMemberService projectMemberService;
@Resource
private PlatformPluginService platformPluginService;
@Resource
private ProjectApplicationService projectApplicationService;
/**
* 获取表头处理人选项
* @param projectId 项目ID
* @return 处理人选项集合
*/
public List<SelectOption> getHeaderHandlerOption(String projectId) {
String platformName = projectApplicationService.getPlatformName(projectId);
// 需要校验服务集成是否开启
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// Local处理人
return getLocalHandlerOption(projectId);
} else {
// 第三方平台(Local处理人 && 平台处理人)
List<SelectOption> localHandlerOption = getLocalHandlerOption(projectId);
// 获取插件中自定义的注入字段(处理人)
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
List<SelectOption> platformHandlerOption = new ArrayList<>();
List<BugTemplateInjectField> platformInjectFields = getPlatformInjectFields(projectId);
for (BugTemplateInjectField injectField : platformInjectFields) {
if (StringUtils.equals(injectField.getKey(), BugTemplateCustomField.HANDLE_USER.getId())) {
GetOptionRequest request = new GetOptionRequest();
request.setOptionMethod(injectField.getOptionMethod());
request.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(projectId));
platformHandlerOption = platform.getFormOptions(request);
}
}
return ListUtils.union(localHandlerOption, platformHandlerOption);
}
}
/**
* 项目成员选项(处理人)
* @param projectId 项目ID
* @return 处理人选项集合
*/
public List<SelectOption> getLocalHandlerOption(String projectId) {
ProjectMemberRequest request = new ProjectMemberRequest();
request.setProjectId(projectId);
List<ProjectUserDTO> projectMembers = projectMemberService.listMember(request);
return projectMembers.stream().map(user -> {
SelectOption option = new SelectOption();
option.setText(user.getName());
option.setValue(user.getId());
return option;
}).toList();
}
/**
* 获取平台注入的字段
* @param projectId 项目ID
* @return 注入的字段集合
*/
public List<BugTemplateInjectField> getPlatformInjectFields(String projectId) {
// 获取插件中自定义的注入字段(处理人)
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getMsPluginManager().getPlugin(serviceIntegration.getPluginId()).getPlugin();
Object scriptContent = pluginLoadService.getPluginScriptContent(serviceIntegration.getPluginId(), platformPlugin.getProjectBugTemplateInjectField());
String injectFields = JSON.toJSONString(((Map<?, ?>) scriptContent).get("injectFields"));
return JSON.parseArray(injectFields, BugTemplateInjectField.class);
}
}

View File

@ -37,7 +37,7 @@ public class BugNoticeService {
@Resource
private UserMapper userMapper;
@Resource
private BugService bugService;
private BugCommonService bugCommonService;
@Resource
private BugStatusService bugStatusService;
@Resource
@ -133,7 +133,7 @@ public class BugNoticeService {
* @return 处理人集合
*/
private Map<String, String> getHandleMap(String projectId) {
List<SelectOption> handlerOption = bugService.getHeaderHandlerOption(projectId);
List<SelectOption> handlerOption = bugCommonService.getHeaderHandlerOption(projectId);
return handlerOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
}
}

View File

@ -18,16 +18,13 @@ import io.metersphere.plugin.platform.dto.reponse.PlatformBugUpdateDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformCustomFieldItemDTO;
import io.metersphere.plugin.platform.dto.request.*;
import io.metersphere.plugin.platform.enums.SyncAttachmentType;
import io.metersphere.plugin.platform.spi.AbstractPlatformPlugin;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.domain.*;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.dto.ProjectUserDTO;
import io.metersphere.project.dto.filemanagement.FileLogRecord;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.request.ProjectMemberRequest;
import io.metersphere.project.service.*;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.exception.MSException;
@ -47,7 +44,10 @@ import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import io.metersphere.system.mapper.BaseUserMapper;
import io.metersphere.system.mapper.TemplateMapper;
import io.metersphere.system.service.*;
import io.metersphere.system.service.BaseTemplateCustomFieldService;
import io.metersphere.system.service.BaseTemplateService;
import io.metersphere.system.service.PlatformPluginService;
import io.metersphere.system.service.UserPlatformAccountService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.ServiceUtils;
@ -62,7 +62,6 @@ import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@ -114,9 +113,10 @@ public class BugService {
@Resource
private BaseTemplateCustomFieldService baseTemplateCustomFieldService;
@Resource
@Lazy
private BugNoticeService bugNoticeService;
@Resource
private BugCommonService bugCommonService;
@Resource
private BugCustomFieldMapper bugCustomFieldMapper;
@Resource
private ExtBugCustomFieldMapper extBugCustomFieldMapper;
@ -147,16 +147,12 @@ public class BugService {
@Resource
private ProjectApplicationService projectApplicationService;
@Resource
private PluginLoadService pluginLoadService;
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private BugSyncNoticeService bugSyncNoticeService;
@Resource
private BugStatusService bugStatusService;
@Resource
private ProjectMemberService projectMemberService;
@Resource
private BugAttachmentService bugAttachmentService;
public static final Long INTERVAL_POS = 5000L;
@ -615,7 +611,7 @@ public class BugService {
// 同步附件至MS
if (MapUtils.isNotEmpty(syncBugResult.getAttachmentMap())) {
bugSyncExtraService.syncAttachmentToMs(platform, syncBugResult.getAttachmentMap(), project.getId());
bugAttachmentService.syncAttachmentToMs(platform, syncBugResult.getAttachmentMap(), project.getId());
}
sqlSession.commit();
@ -650,7 +646,7 @@ public class BugService {
// 状态选项获取时, 获取平台校验了服务集成配置, 所以此处不需要再次校验
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
List<BugTemplateInjectField> injectFieldList = getPlatformInjectFields(projectId);
List<BugTemplateInjectField> injectFieldList = bugCommonService.getPlatformInjectFields(projectId);
for (BugTemplateInjectField injectField : injectFieldList) {
TemplateCustomFieldDTO templateCustomFieldDTO = new TemplateCustomFieldDTO();
BeanUtils.copyBean(templateCustomFieldDTO, injectField);
@ -681,7 +677,7 @@ public class BugService {
handleUserField.setFieldName(BugTemplateCustomField.HANDLE_USER.getName());
handleUserField.setFieldKey(BugTemplateCustomField.HANDLE_USER.getId());
handleUserField.setType(CustomFieldType.SELECT.getType());
List<SelectOption> localHandlerOption = getLocalHandlerOption(projectId);
List<SelectOption> localHandlerOption = bugCommonService.getLocalHandlerOption(projectId);
handleUserOption = localHandlerOption.stream().map(user -> {
CustomFieldOption option = new CustomFieldOption();
option.setText(user.getText());
@ -915,7 +911,7 @@ public class BugService {
// 同步至第三方(异步调用)
if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName()) && CollectionUtils.isNotEmpty(allSyncAttachments)) {
bugSyncExtraService.syncAttachmentToPlatform(allSyncAttachments, request.getProjectId(), tempFileDir);
bugAttachmentService.syncAttachmentToPlatform(allSyncAttachments, request.getProjectId(), tempFileDir);
}
}
@ -1284,7 +1280,7 @@ public class BugService {
handleCustomField(bugs, request.getProjectId());
bugs = buildExtraInfo(bugs);
// 表头处理人选项
List<SelectOption> handleUserOption = getHeaderHandlerOption(request.getProjectId());
List<SelectOption> handleUserOption = bugCommonService.getHeaderHandlerOption(request.getProjectId());
Map<String, String> handleUserMap = handleUserOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
// 表头状态选项
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(request.getProjectId());
@ -1367,69 +1363,6 @@ public class BugService {
}
}
/**
* 获取表头处理人选项
* @param projectId 项目ID
* @return 处理人选项集合
*/
public List<SelectOption> getHeaderHandlerOption(String projectId) {
String platformName = projectApplicationService.getPlatformName(projectId);
// 需要校验服务集成是否开启
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// Local处理人
return getLocalHandlerOption(projectId);
} else {
// 第三方平台(Local处理人 && 平台处理人)
List<SelectOption> localHandlerOption = getLocalHandlerOption(projectId);
// 获取插件中自定义的注入字段(处理人)
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
List<SelectOption> platformHandlerOption = new ArrayList<>();
List<BugTemplateInjectField> platformInjectFields = getPlatformInjectFields(projectId);
for (BugTemplateInjectField injectField : platformInjectFields) {
if (StringUtils.equals(injectField.getKey(), BugTemplateCustomField.HANDLE_USER.getId())) {
GetOptionRequest request = new GetOptionRequest();
request.setOptionMethod(injectField.getOptionMethod());
request.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(projectId));
platformHandlerOption = platform.getFormOptions(request);
}
}
return ListUtils.union(localHandlerOption, platformHandlerOption);
}
}
/**
* 项目成员选项(处理人)
* @param projectId 项目ID
* @return 处理人选项集合
*/
private List<SelectOption> getLocalHandlerOption(String projectId) {
ProjectMemberRequest request = new ProjectMemberRequest();
request.setProjectId(projectId);
List<ProjectUserDTO> projectMembers = projectMemberService.listMember(request);
return projectMembers.stream().map(user -> {
SelectOption option = new SelectOption();
option.setText(user.getName());
option.setValue(user.getId());
return option;
}).toList();
}
/**
* 获取平台注入的字段
* @param projectId 项目ID
* @return 注入的字段集合
*/
private List<BugTemplateInjectField> getPlatformInjectFields(String projectId) {
// 获取插件中自定义的注入字段(处理人)
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getMsPluginManager().getPlugin(serviceIntegration.getPluginId()).getPlugin();
Object scriptContent = pluginLoadService.getPluginScriptContent(serviceIntegration.getPluginId(), platformPlugin.getProjectBugTemplateInjectField());
String injectFields = JSON.toJSONString(((Map<?, ?>) scriptContent).get("injectFields"));
return JSON.parseArray(injectFields, BugTemplateInjectField.class);
}
/**
* 获取表头自定义字段
* @param projectId 项目ID
@ -1441,7 +1374,7 @@ public class BugService {
List<Template> templates = projectTemplateService.getTemplates(projectId, TemplateScene.BUG.name());
templates.forEach(template -> headerCustomFields.addAll(baseTemplateService.getTemplateDTO(template).getCustomFields()));
// 填充自定义字段成员类型的选项值
List<SelectOption> memberOption = getHeaderHandlerOption(projectId);
List<SelectOption> memberOption = bugCommonService.getHeaderHandlerOption(projectId);
List<CustomFieldOption> memberCustomOption = memberOption.stream().map(option -> {
CustomFieldOption customFieldOption = new CustomFieldOption();
customFieldOption.setValue(option.getValue());

View File

@ -1,35 +1,12 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugLocalAttachment;
import io.metersphere.bug.domain.BugLocalAttachmentExample;
import io.metersphere.bug.dto.response.BugFileDTO;
import io.metersphere.bug.enums.BugAttachmentSourceType;
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
import io.metersphere.plugin.platform.dto.PlatformAttachment;
import io.metersphere.plugin.platform.dto.request.SyncAttachmentToPlatformRequest;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.domain.FileAssociationExample;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.util.*;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -37,14 +14,6 @@ public class BugSyncExtraService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private ProjectApplicationService projectApplicationService;
@Resource
private BugAttachmentService bugAttachmentService;
@Resource
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
@Resource
private FileAssociationMapper fileAssociationMapper;
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
private static final String SYNC_THIRD_PARTY_ISSUES_ERROR_KEY = "ISSUE:SYNC:ERROR";
@ -96,150 +65,4 @@ public class BugSyncExtraService {
public void deleteSyncErrorMsg(String projectId) {
stringRedisTemplate.delete(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId);
}
/**
* 同步附件到平台
* @param platformAttachments 平台附件参数
* @param projectId 项目ID
* @param tmpFilePath 临时文件路径
*/
@Async
public void syncAttachmentToPlatform(List<SyncAttachmentToPlatformRequest> platformAttachments, String projectId, File tmpFilePath) {
// 平台缺陷需同步附件
Platform platform = projectApplicationService.getPlatform(projectId, true);
platformAttachments.forEach(platform::syncAttachmentToPlatform);
tmpFilePath.deleteOnExit();
}
/**
* 同步平台附件到MS
* @param platform 平台对象
* @param attachmentMap 平台附件缺陷集合
* @param projectId 项目ID
*/
@Async
public void syncAttachmentToMs(Platform platform, Map<String, List<PlatformAttachment>> attachmentMap, String projectId) {
for (String bugId : attachmentMap.keySet()) {
List<PlatformAttachment> syncAttachments = attachmentMap.get(bugId);
// 获取所有MS附件
Set<String> platformAttachmentSet = new HashSet<>();
List<BugFileDTO> allBugFiles = bugAttachmentService.getAllBugFiles(bugId);
Set<String> attachmentsNameSet = allBugFiles.stream().map(BugFileDTO::getFileName).collect(Collectors.toSet());
for (PlatformAttachment syncAttachment : syncAttachments) {
String fileName = syncAttachment.getFileName();
String fileKey = syncAttachment.getFileKey();
platformAttachmentSet.add(fileName);
if (!attachmentsNameSet.contains(fileName)) {
saveSyncAttachmentToMs(platform, bugId, fileName, fileKey, projectId);
}
}
// 删除Jira中不存在的附件
deleteSyncAttachmentFromMs(platformAttachmentSet, allBugFiles, bugId, projectId);
}
}
/**
* 保存同步附件到MS
* @param platform 平台对象
* @param bugId 缺陷ID
* @param fileName 附件名称
* @param fileKey 附件唯一Key
* @param projectId 项目ID
*/
public void saveSyncAttachmentToMs(Platform platform, String bugId, String fileName, String fileKey, String projectId) {
try {
platform.getAttachmentContent(fileKey, (in) -> {
if (in == null) {
return;
}
byte[] bytes;
try {
// upload platform attachment to minio
bytes = in.readAllBytes();
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(projectId, bugId, fileName));
} catch (Exception e) {
throw new MSException(e.getMessage());
}
// save bug attachment relation
BugLocalAttachment localAttachment = new BugLocalAttachment();
localAttachment.setId(IDGenerator.nextStr());
localAttachment.setBugId(bugId);
localAttachment.setFileId(IDGenerator.nextStr());
localAttachment.setFileName(fileName);
localAttachment.setSize((long) bytes.length);
localAttachment.setCreateTime(System.currentTimeMillis());
localAttachment.setCreateUser("admin");
localAttachment.setSource(BugAttachmentSourceType.ATTACHMENT.name());
bugLocalAttachmentMapper.insert(localAttachment);
});
} catch (Exception e) {
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}
/**
* 删除MS中不存在的平台附件
* @param platformAttachmentSet 已处理的平台附件集合
* @param allMsAttachments 所有MS附件集合
* @param bugId 缺陷ID
* @param projectId 项目ID
*/
public void deleteSyncAttachmentFromMs(Set<String> platformAttachmentSet, List<BugFileDTO> allMsAttachments, String bugId, String projectId) {
try {
// 删除MS中不存在的平台附件
if (!CollectionUtils.isEmpty(allMsAttachments)) {
List<BugFileDTO> deleteMsAttachments = allMsAttachments.stream()
.filter(msAttachment -> !platformAttachmentSet.contains(msAttachment.getFileName()))
.toList();
List<String> unLinkIds = new ArrayList<>();
List<String> deleteLocalIds = new ArrayList<>();
deleteMsAttachments.forEach(deleteMsFile -> {
if (deleteMsFile.getAssociated()) {
unLinkIds.add(deleteMsFile.getRefId());
} else {
deleteLocalIds.add(deleteMsFile.getRefId());
}
});
if (!CollectionUtils.isEmpty(unLinkIds)) {
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andIdIn(unLinkIds);
fileAssociationMapper.deleteByExample(example);
}
if (!CollectionUtils.isEmpty(deleteLocalIds)) {
Map<String, BugFileDTO> localFileMap = deleteMsAttachments.stream().collect(Collectors.toMap(BugFileDTO::getRefId, f -> f));
deleteLocalIds.forEach(deleteLocalId -> {
try {
BugFileDTO bugFileDTO = localFileMap.get(deleteLocalId);
FileCenter.getDefaultRepository().delete(buildBugFileRequest(projectId, bugId, bugFileDTO.getFileName()));
} catch (Exception e) {
throw new MSException(e.getMessage());
}
});
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
example.createCriteria().andIdIn(deleteLocalIds);
bugLocalAttachmentMapper.deleteByExample(example);
}
}
} catch (Exception e) {
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}
/**
* 构建文件库请求参数
* @param projectId 项目ID
* @param resourceId 资源ID(缺陷ID)
* @param fileName 文件名
* @return 构建文件库请求对象
*/
private FileRequest buildBugFileRequest(String projectId, String resourceId, String fileName) {
FileRequest fileRequest = new FileRequest();
fileRequest.setFolder(DefaultRepositoryDir.getBugDir(projectId, resourceId));
fileRequest.setFileName(StringUtils.isEmpty(fileName) ? null : fileName);
fileRequest.setStorage(StorageType.MINIO.name());
return fileRequest;
}
}

View File

@ -85,6 +85,7 @@ public class AssociateBugProviderTests extends BaseTest {
request.setCurrent(1);
request.setPageSize(10);
request.setCaseId("123");
request.setProjectId("project_wx_associate_test");
associateBugProvider.hasAssociateBugPage(request);
request.setCaseId("wx_2");
associateBugProvider.hasAssociateBugPage(request);

View File

@ -119,12 +119,12 @@ public class BugAttachmentControllerTests extends BaseTest {
request.setSelectAll(false);
request.setSelectIds(List.of("not-exist-file-id"));
MultiValueMap<String, Object> paramMap4 = getDefaultMultiPartParam(request, null);
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap4);
this.requestMultipart(BUG_ATTACHMENT_UPLOAD, paramMap4).andExpect(status().is5xxServerError());
request.setSelectIds(List.of(unRelatedFiles.get(0).getId()));
MultiValueMap<String, Object> paramMap5 = getDefaultMultiPartParam(request, null);
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap5);
this.requestMultipart(BUG_ATTACHMENT_UPLOAD, paramMap5).andExpect(status().is5xxServerError());
MultiValueMap<String, Object> paramMap6 = getDefaultMultiPartParam(request, file);
this.requestMultipartWithOk(BUG_ATTACHMENT_UPLOAD, paramMap6);
this.requestMultipart(BUG_ATTACHMENT_UPLOAD, paramMap6).andExpect(status().is5xxServerError());
}
@Test
@ -195,7 +195,7 @@ public class BugAttachmentControllerTests extends BaseTest {
try {
request.setBugId("default-bug-id-tapd");
request.setRefId(file.getRefId());
this.requestPostWithOk(BUG_ATTACHMENT_UPDATE, request);
this.requestPost(BUG_ATTACHMENT_UPDATE, request).andExpect(status().is5xxServerError());
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -227,7 +227,7 @@ public class BugAttachmentControllerTests extends BaseTest {
request.setRefId(file.getRefId());
request.setAssociated(file.getAssociated());
try {
this.requestPostWithOk(BUG_ATTACHMENT_DELETE, request);
this.requestPost(BUG_ATTACHMENT_DELETE, request).andExpect(status().is5xxServerError());
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@ -37,8 +37,6 @@ public class BugSyncExtraServiceTests extends BaseTest {
@MockBean
MinioRepository minioMock;
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private BugAttachmentService bugAttachmentService;
@Test
@ -54,12 +52,12 @@ public class BugSyncExtraServiceTests extends BaseTest {
// Mock minio delete exception
Mockito.doThrow(new MSException("delete minio error!")).when(minioMock).delete(Mockito.any());
MSException deleteException = assertThrows(MSException.class, () ->
bugSyncExtraService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B", "sync-extra-file-local-A.txt"),
bugAttachmentService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B", "sync-extra-file-local-A.txt"),
allBugFile, "bug-for-sync-extra", "project-for-sync-extra"));
assertEquals(deleteException.getMessage(), "delete minio error!");
// Reset minio mock
Mockito.reset(minioMock);
bugSyncExtraService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B"),
bugAttachmentService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B"),
allBugFile, "bug-for-sync-extra", "project-for-sync-extra");
// Mock null input stream and exception input stream
@ -76,9 +74,9 @@ public class BugSyncExtraServiceTests extends BaseTest {
return null;
}).when(platform).getAttachmentContent(Mockito.anyString(), Mockito.any());
// called twice for cover test
bugSyncExtraService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-1", "project-for-sync-extra");
bugAttachmentService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-1", "project-for-sync-extra");
MSException msException = assertThrows(MSException.class, () ->
bugSyncExtraService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-2", "project-for-sync-extra"));
bugAttachmentService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-2", "project-for-sync-extra"));
assertEquals(msException.getMessage(), "read bytes exception occurred!");
}
}

View File

@ -218,7 +218,7 @@ public class FunctionalCaseDemandService {
public PluginPager<PlatformDemandDTO> pageDemand(FunctionalThirdDemandPageRequest request) {
DemandPageRequest demandPageRequest = new DemandPageRequest();
demandPageRequest.setQuery(request.getKeyword());
//demandPageRequest.setFilter(request.getFilter());
demandPageRequest.setFilter(request.getFilter());
demandPageRequest.setStartPage(request.getCurrent());
demandPageRequest.setPageSize(request.getPageSize());
demandPageRequest.setProjectConfig(projectApplicationService.getProjectDemandThirdPartConfig(request.getProjectId()));

View File

@ -509,6 +509,7 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
request.setCurrent(1);
request.setPageSize(10);
request.setCaseId("wx_2");
request.setProjectId("project_wx_associate_test");
List<BugProviderDTO> list = new ArrayList<>();
BugProviderDTO bugProviderDTO = new BugProviderDTO();
bugProviderDTO.setId("123");