feat(缺陷管理): 缺陷同步功能开源

--story=1015332 --user=宋昌昌 企业版功能转开源版功能 https://www.tapd.cn/55049933/s/1540450
This commit is contained in:
song-cc-rock 2024-07-01 14:37:49 +08:00 committed by Craftsman
parent 0a62bc7c80
commit f87bfd1c1a
9 changed files with 403 additions and 113 deletions

View File

@ -143,7 +143,7 @@ public class BugController {
} }
@GetMapping("/sync/{projectId}") @GetMapping("/sync/{projectId}")
@Operation(summary = "缺陷管理-列表-同步缺陷(开源)") @Operation(summary = "缺陷管理-列表-同步存量缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#projectId", resourceType = "project") @CheckOwner(resourceId = "#projectId", resourceType = "project")
public void sync(@PathVariable String projectId) { public void sync(@PathVariable String projectId) {
@ -151,11 +151,11 @@ public class BugController {
} }
@PostMapping("/sync/all") @PostMapping("/sync/all")
@Operation(summary = "缺陷管理-列表-同步缺陷(企业)") @Operation(summary = "缺陷管理-列表-同步全量缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void syncAll(@RequestBody BugSyncRequest request) { public void syncAll(@RequestBody BugSyncRequest request) {
bugSyncService.syncAllBugs(request, SessionUtils.getUserId(), Objects.requireNonNull(SessionUtils.getUser()).getLanguage()); bugSyncService.syncAllBugs(request, SessionUtils.getUserId(), Objects.requireNonNull(SessionUtils.getUser()).getLanguage(), Translator.get("sync_mode.manual"));
} }
@GetMapping("/sync/check/{projectId}") @GetMapping("/sync/check/{projectId}")

View File

@ -0,0 +1,59 @@
package io.metersphere.bug.dto;
import io.metersphere.bug.domain.Bug;
import io.metersphere.plugin.platform.dto.response.PlatformBugDTO;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.domain.Project;
import io.metersphere.system.domain.Template;
import io.metersphere.system.domain.TemplateCustomField;
import io.metersphere.system.dto.sdk.TemplateDTO;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
@Builder
public class BugSyncSaveModel {
/**
* 平台缺陷
*/
private PlatformBugDTO platformBug;
/**
* MS缺陷
*/
private Bug msBug;
/**
* MS默认模板
*/
private TemplateDTO msDefaultTemplate;
/**
* 插件默认模板
*/
private Template pluginDefaultTemplate;
/**
* 模板字段映射
*/
private Map<String, List<TemplateCustomField>> templateFieldMap;
/**
* 所属平台
*/
private Platform platform;
/**
* 平台名称
*/
private String platformName;
/**
* 所属项目
*/
private Project project;
}

View File

@ -3,6 +3,7 @@ package io.metersphere.bug.service;
import io.metersphere.bug.constants.BugExportColumns; import io.metersphere.bug.constants.BugExportColumns;
import io.metersphere.bug.domain.*; import io.metersphere.bug.domain.*;
import io.metersphere.bug.dto.BugExportHeaderModel; import io.metersphere.bug.dto.BugExportHeaderModel;
import io.metersphere.bug.dto.BugSyncSaveModel;
import io.metersphere.bug.dto.BugTemplateInjectField; import io.metersphere.bug.dto.BugTemplateInjectField;
import io.metersphere.bug.dto.request.*; import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.*; import io.metersphere.bug.dto.response.*;
@ -11,6 +12,7 @@ import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.enums.BugTemplateCustomField; import io.metersphere.bug.enums.BugTemplateCustomField;
import io.metersphere.bug.mapper.*; import io.metersphere.bug.mapper.*;
import io.metersphere.bug.utils.ExportUtils; import io.metersphere.bug.utils.ExportUtils;
import io.metersphere.plugin.platform.dto.PlatformAttachment;
import io.metersphere.plugin.platform.dto.SelectOption; import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.platform.dto.SyncBugResult; import io.metersphere.plugin.platform.dto.SyncBugResult;
import io.metersphere.plugin.platform.dto.request.*; import io.metersphere.plugin.platform.dto.request.*;
@ -76,6 +78,10 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -532,22 +538,8 @@ public class BugService {
* @param currentUser 当前用户 * @param currentUser 当前用户
*/ */
@Async @Async
public void syncPlatformAllBugs(BugSyncRequest request, Project project, String currentUser, String language) { public void syncPlatformAllBugs(BugSyncRequest request, Project project, String currentUser, String language, String triggerMode) {
try { doSyncAllPlatformBugs(project, request, currentUser, language, triggerMode);
XpackBugService bugService = CommonBeanFactory.getBean(XpackBugService.class);
if (bugService != null) {
bugService.syncPlatformBugs(project, request, currentUser, language, Translator.get("sync_mode.manual"));
}
} catch (Exception e) {
LogUtils.error(e);
// 异常或正常结束都得删除当前项目执行同步的唯一Key
bugSyncExtraService.deleteSyncKey(request.getProjectId());
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
bugSyncExtraService.setSyncErrorMsg(request.getProjectId(), e.getMessage());
} finally {
// 异常或正常结束都得删除当前项目执行同步的唯一Key
bugSyncExtraService.deleteSyncKey(request.getProjectId());
}
} }
/** /**
@ -562,13 +554,13 @@ public class BugService {
// 分页同步 // 分页同步
SubListUtils.dealForSubList(remainBugs, 100, (subBugs) -> doSyncPlatformBugs(subBugs, project)); SubListUtils.dealForSubList(remainBugs, 100, (subBugs) -> doSyncPlatformBugs(subBugs, project));
} catch (Exception e) { } catch (Exception e) {
LogUtils.error("Synchronization bugs exception occurred :" + e.getMessage()); LogUtils.error("Sync bugs exception occurred: " + e.getMessage());
// 异常或正常结束都得删除当前项目执行同步的Key // 异常或正常结束都得删除当前项目执行同步的Key
bugSyncExtraService.deleteSyncKey(project.getId()); bugSyncExtraService.deleteSyncKey(project.getId());
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取) // 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
bugSyncExtraService.setSyncErrorMsg(project.getId(), e.getMessage()); bugSyncExtraService.setSyncErrorMsg(project.getId(), e.getMessage());
} finally { } finally {
LogUtils.info("Synchronization bugs end......"); LogUtils.info("Sync bugs end");
// 异常或正常结束都得删除当前项目执行同步的Key // 异常或正常结束都得删除当前项目执行同步的Key
bugSyncExtraService.deleteSyncKey(project.getId()); bugSyncExtraService.deleteSyncKey(project.getId());
// 发送同步通知 // 发送同步通知
@ -582,7 +574,6 @@ public class BugService {
* @param project 项目 * @param project 项目
* @param syncRequest 同步请求参数 * @param syncRequest 同步请求参数
*/ */
@SuppressWarnings("unused")
public void execSyncAll(Project project, SyncAllBugRequest syncRequest) { public void execSyncAll(Project project, SyncAllBugRequest syncRequest) {
syncRequest.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(project.getId())); syncRequest.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(project.getId()));
// 获取平台 // 获取平台
@ -592,7 +583,7 @@ public class BugService {
} }
/** /**
* 同步平台缺陷处理 * 处理平台存量缺陷
* *
* @param subBugs 同步的分页缺陷 * @param subBugs 同步的分页缺陷
* @param project 项目 * @param project 项目
@ -682,6 +673,158 @@ public class BugService {
} }
} }
/**
* 处理平台全量缺陷
* @param project 项目
* @param request 同步请求参数
* @param currentUser 当前用户
* @param language 语言
* @param triggerMode 触发方式
*/
private void doSyncAllPlatformBugs(Project project, BugSyncRequest request, String currentUser, String language, String triggerMode) {
// 批量操作
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
// 同步条数
AtomicInteger syncCount = new AtomicInteger();
// 缺陷POS
AtomicLong atomicPos = new AtomicLong(getNextPos(project.getId()));
// 同步缺陷ID集合(由于是分页同步)
AtomicReference<List<String>> allSyncIds = new AtomicReference<>(new ArrayList<>());
try {
BugMapper batchBugMapper = sqlSession.getMapper(BugMapper.class);
BugContentMapper batchBugContentMapper = sqlSession.getMapper(BugContentMapper.class);
// 获取项目所属平台
String platformName = projectApplicationService.getPlatformName(project.getId());
// 获取项目所属平台配置
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(project.getId(), true);
if (serviceIntegration == null) {
// 项目未配置第三方平台
throw new MSException(Translator.get("third_party_not_config"));
}
// 获取配置平台, 插入平台缺陷
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
boolean isIncrement = projectApplicationService.isPlatformSyncMethodByIncrement(project.getId());
// 获取当前平台下满足同步条件的原始缺陷
BugExample bugExample = new BugExample();
BugExample.Criteria criteria = bugExample.createCriteria();
criteria.andProjectIdEqualTo(project.getId()).andPlatformEqualTo(platformName);
if (request.getPre() != null) {
if (request.getPre()) {
criteria.andCreateTimeLessThan(request.getCreateTime());
} else {
criteria.andCreateTimeGreaterThan(request.getCreateTime());
}
}
List<Bug> originalBugs = batchBugMapper.selectByExample(bugExample);
Map<String, Bug> msOriginalBugMap = originalBugs.stream().collect(Collectors.toMap(Bug::getPlatformBugId, bug -> bug));
// 获取原始缺陷的模板字段信息(同步更新缺陷使用)
List<String> templateIds = originalBugs.stream().map(Bug::getTemplateId).distinct().toList();
List<TemplateCustomField> templateCustomsFields = baseTemplateCustomFieldService.getByTemplateIds(templateIds);
Map<String, List<TemplateCustomField>> templateFieldMap = templateCustomsFields.stream().collect(Collectors.groupingBy(TemplateCustomField::getTemplateId));
// 获取当前项目MS默认模板(同步新增缺陷使用)
TemplateDTO msDefaultTemplate = new TemplateDTO();
// 平台默认模板
Template pluginDefaultTemplate = projectTemplateService.getPluginBugTemplate(project.getId());
List<ProjectTemplateOptionDTO> templateOption = projectTemplateService.getOption(project.getId(), TemplateScene.BUG.name());
ProjectTemplateOptionDTO defaultProjectTemplate = templateOption.stream().filter(ProjectTemplateOptionDTO::getEnableDefault).toList().get(0);
if (isPluginDefaultTemplate(defaultProjectTemplate.getId(), pluginDefaultTemplate)) {
BeanUtils.copyBean(msDefaultTemplate, pluginDefaultTemplate);
} else {
// MS默认模板
msDefaultTemplate = projectTemplateService.getTemplateDTOById(defaultProjectTemplate.getId(), project.getId(), TemplateScene.BUG.name());
}
TemplateDTO defaultTemplate = msDefaultTemplate;
Consumer<SyncPostParamRequest> syncPostProcessFunc = (param) -> {
// 准备参数
List<PlatformBugDTO> needSyncBugs = param.getNeedSyncBugs();
Map<String, List<PlatformAttachment>> attachmentMap = param.getAttachmentMap();
// 比对MS原始缺陷, 筛选出同步缺陷中需要作为新增的缺陷, 以及需要作为更新的缺陷
List<PlatformBugDTO> syncToAddBugList = needSyncBugs.stream().filter(syncBug -> !msOriginalBugMap.containsKey(syncBug.getPlatformBugId())).collect(Collectors.toList());
List<PlatformBugDTO> syncToUpdateBugList = needSyncBugs.stream().filter(syncBug -> msOriginalBugMap.containsKey(syncBug.getPlatformBugId())).collect(Collectors.toList());
// 聚合每次同步的ID集合
allSyncIds.set(ListUtils.union(needSyncBugs.stream().map(PlatformBugDTO::getPlatformBugId).toList(), allSyncIds.get()));
// 处理缺陷
Map<String, List<PlatformAttachment>> handleAttachmentMap = new HashMap<>(16);
if (CollectionUtils.isNotEmpty(syncToAddBugList) || CollectionUtils.isNotEmpty(syncToUpdateBugList)) {
List<PlatformBugDTO> combinaList;
if (isIncrement) {
// 增量同步
if (CollectionUtils.isNotEmpty(syncToUpdateBugList)) {
combinaList = new ArrayList<>(syncToUpdateBugList);
} else {
combinaList = new ArrayList<>();
}
} else {
// 全量同步
if (CollectionUtils.isNotEmpty(syncToAddBugList)) {
combinaList = new ArrayList<>(syncToAddBugList);
combinaList.addAll(syncToUpdateBugList);
} else {
combinaList = new ArrayList<>(syncToUpdateBugList);
combinaList.addAll(syncToAddBugList);
}
}
// 同时解析附件
BugSyncSaveModel saveModel = BugSyncSaveModel.builder().platformName(platformName).project(project)
.msDefaultTemplate(defaultTemplate).pluginDefaultTemplate(pluginDefaultTemplate).platform(platform).templateFieldMap(templateFieldMap).build();
for (PlatformBugDTO platformBug : combinaList) {
List<PlatformAttachment> bugAttachments = new ArrayList<>();
if (attachmentMap.containsKey(platformBug.getId())) {
bugAttachments = attachmentMap.get(platformBug.getId());
}
Bug bug = msOriginalBugMap.get(platformBug.getPlatformBugId());
saveModel.setMsBug(bug);
saveModel.setPlatformBug(platformBug);
handleSaveBug(saveModel, atomicPos, batchBugMapper, batchBugContentMapper);
handleAttachmentMap.put(platformBug.getId(), bugAttachments);
}
// 设置同步条数
syncCount.addAndGet(combinaList.size());
}
// 附件处理
if (MapUtils.isNotEmpty(handleAttachmentMap)) {
bugAttachmentService.syncAttachmentToMs(platform, handleAttachmentMap, project.getId());
}
sqlSession.commit();
};
SyncAllBugRequest syncAllBugRequest = new SyncAllBugRequest();
syncAllBugRequest.setPre(request.getPre());
syncAllBugRequest.setCreateTime(request.getCreateTime());
syncAllBugRequest.setSyncPostProcessFunc(syncPostProcessFunc);
execSyncAll(project, syncAllBugRequest);
// 删除缺陷在后置方法处理完再执行
List<String> syncToDeleteIds = originalBugs.stream().filter(msOriginalBug -> !allSyncIds.get().contains(msOriginalBug.getPlatformBugId())).map(Bug::getId).toList();
if (CollectionUtils.isNotEmpty(syncToDeleteIds)) {
syncCount.addAndGet(syncToDeleteIds.size());
// 删除缺陷(单独处理)
BugExample example = new BugExample();
example.createCriteria().andIdIn(syncToDeleteIds);
bugMapper.deleteByExample(example);
bugCommonService.clearAssociateResource(project.getId(), syncToDeleteIds);
}
} catch (Exception e) {
LogUtils.error("Sync bugs exception occurred: " + e.getMessage());
// 异常或正常结束都得删除当前项目执行同步的唯一Key
bugSyncExtraService.deleteSyncKey(request.getProjectId());
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
bugSyncExtraService.setSyncErrorMsg(request.getProjectId(), e.getMessage());
} finally {
LogUtils.info("Sync bugs end");
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
// 异常或正常结束都得删除当前项目执行同步的唯一Key
bugSyncExtraService.deleteSyncKey(project.getId());
// 发送同步成功通知
bugSyncNoticeService.sendNotice(syncCount.get(), currentUser, language, triggerMode, project.getId());
}
}
/** /**
* 注入平台模板缺陷字段 * 注入平台模板缺陷字段
* *
@ -1241,6 +1384,16 @@ public class BugService {
return pluginTemplate != null && StringUtils.equals(pluginTemplate.getId(), templateId); return pluginTemplate != null && StringUtils.equals(pluginTemplate.getId(), templateId);
} }
/**
* 是否插件默认模板
* @param templateId 模板ID
* @param pluginTemplate 插件模板
* @return 是否插件默认模板
*/
private boolean isPluginDefaultTemplate(String templateId, Template pluginTemplate) {
return pluginTemplate != null && StringUtils.equals(pluginTemplate.getId(), templateId);
}
/** /**
* 封装缺陷其他字段 * 封装缺陷其他字段
* *
@ -1756,4 +1909,163 @@ public class BugService {
return option; return option;
}).toList(); }).toList();
} }
/**
*
* @param atomicPos 位置
* @param batchBugMapper 批量操作缺陷
* @param batchBugContentMapper 批量操作缺陷内容
*/
private void handleSaveBug(BugSyncSaveModel saveModel, AtomicLong atomicPos, BugMapper batchBugMapper, BugContentMapper batchBugContentMapper) {
try {
Map<String, String> needSyncApiFieldMap = new HashMap<>(12);
PlatformBugDTO platformBug = saveModel.getPlatformBug();
Bug originalBug = saveModel.getMsBug();
// 设置缺陷基础信息
if (originalBug == null) {
// 新增
platformBug.setId(IDGenerator.nextStr());
platformBug.setNum(Long.valueOf(NumGenerator.nextNum(saveModel.getProject().getId(), ApplicationNumScope.BUG_MANAGEMENT)).intValue());
platformBug.setProjectId(saveModel.getProject().getId());
platformBug.setTemplateId(saveModel.getMsDefaultTemplate().getId());
platformBug.setPlatform(saveModel.getPlatformName());
platformBug.setPlatformDefaultTemplate(isPluginDefaultTemplate(platformBug.getTemplateId(), saveModel.getPluginDefaultTemplate()));
platformBug.setDeleteUser(platformBug.getCreateUser());
platformBug.setDeleteTime(platformBug.getCreateTime());
platformBug.setDeleted(false);
platformBug.setPos(atomicPos.getAndAdd(INTERVAL_POS));
// 非平台默认模板时, 设置需要处理的字段
if (!platformBug.getPlatformDefaultTemplate()) {
List<TemplateCustomFieldDTO> defaultTemplateCustomFields = saveModel.getMsDefaultTemplate().getCustomFields();
needSyncApiFieldMap = defaultTemplateCustomFields.stream().filter(field -> StringUtils.isNotBlank(field.getApiFieldId()))
.collect(Collectors.toMap(TemplateCustomFieldDTO::getApiFieldId, TemplateCustomFieldDTO::getFieldId));
}
} else {
// 更新
platformBug.setId(originalBug.getId());
if (!StringUtils.equals(originalBug.getHandleUser(), platformBug.getHandleUser())) {
platformBug.setHandleUsers(originalBug.getHandleUsers() + "," + platformBug.getHandleUsers());
} else {
platformBug.setHandleUser(originalBug.getHandleUser());
platformBug.setHandleUsers(originalBug.getHandleUsers());
}
platformBug.setProjectId(originalBug.getProjectId());
platformBug.setTemplateId(originalBug.getTemplateId());
platformBug.setPlatform(originalBug.getPlatform());
platformBug.setCreateUser(null);
platformBug.setPlatformDefaultTemplate(isPluginDefaultTemplate(platformBug.getTemplateId(), saveModel.getPluginDefaultTemplate()));
// 非平台默认模板时, 设置需要处理的字段
if (!platformBug.getPlatformDefaultTemplate()) {
List<TemplateCustomField> templateCustomFields = saveModel.getTemplateFieldMap().get(platformBug.getTemplateId());
needSyncApiFieldMap = templateCustomFields.stream().filter(field -> StringUtils.isNotBlank(field.getApiFieldId()))
.collect(Collectors.toMap(TemplateCustomField::getApiFieldId, TemplateCustomField::getFieldId));
}
}
Bug bug = new Bug();
BeanUtils.copyBean(bug, platformBug);
// 如果缺陷需要同步第三方的富文本文件
List<BugLocalAttachment> richTextAttachments = new ArrayList<>();
if (MapUtils.isNotEmpty(platformBug.getRichTextImageMap())) {
Map<String, String> richTextImageMap = platformBug.getRichTextImageMap();
// 同步第三方的富文本文件
try {
Platform platform = saveModel.getPlatform();
richTextImageMap.keySet().forEach(key -> platform.getAttachmentContent(key, (in) -> {
if (in == null) {
return;
}
String fileId = IDGenerator.nextStr();
// 第三方同步的文件名加上平台前缀, 防止同名
String fileName = saveModel.getPlatformName() + "-" + richTextImageMap.get(key);
byte[] bytes;
try {
// 获取第三方平台附件流, 并上传至Minio, 默认不压缩
bytes = in.readAllBytes();
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(platformBug.getProjectId(), platformBug.getId(), fileId, fileName));
} catch (Exception e) {
throw new MSException(e.getMessage());
}
// 保存缺陷附件关系
BugLocalAttachment localAttachment = new BugLocalAttachment();
localAttachment.setId(IDGenerator.nextStr());
localAttachment.setBugId(platformBug.getId());
localAttachment.setFileId(fileId);
localAttachment.setFileName(fileName);
localAttachment.setSize((long) bytes.length);
localAttachment.setCreateTime(System.currentTimeMillis());
localAttachment.setCreateUser("admin");
localAttachment.setSource(BugAttachmentSourceType.RICH_TEXT.name());
richTextAttachments.add(localAttachment);
// 替换富文本中的临时URL, 注意: 第三方的图片附件暂未存储在压缩目录, 因此不支持压缩访问
if (StringUtils.contains(platformBug.getDescription(), "alt=\"" + key + "\"")) {
platformBug.setDescription(platformBug.getDescription()
.replace("alt=\"" + key + "\"", "src=\"/bug/attachment/preview/md/" + platformBug.getProjectId() + "/" + fileId + "/false\""));
if (platformBug.getPlatformDefaultTemplate()) {
// 来自富文本自定义字段
PlatformCustomFieldItemDTO descriptionField = platformBug.getCustomFieldList().stream().filter(field -> StringUtils.equals(field.getCustomData(), "description")).toList().get(0);
descriptionField.setValue(platformBug.getDescription());
}
} else {
// 来自富文本自定义字段
PlatformCustomFieldItemDTO richTextField = platformBug.getCustomFieldList().stream().filter(field -> StringUtils.equals(field.getType(), PlatformCustomFieldType.RICH_TEXT.name())
&& field.getValue() != null && StringUtils.contains(field.getValue().toString(), "alt=\"" + key + "\"")).toList().get(0);
richTextField.setValue(richTextField.getValue().toString().replace("alt=\"" + key + "\"", "src=\"/bug/attachment/preview/md/" + platformBug.getProjectId() + "/" + fileId + "/false\""));
}
}));
} catch (Exception e) {
LogUtils.error("sync platform bug rich text image error : " + e.getMessage());
}
}
BugContent bugContent = new BugContent();
bugContent.setBugId(platformBug.getId());
bugContent.setDescription(platformBug.getDescription());
// 设置缺陷自定义字段参数
BugEditRequest customEditRequest = new BugEditRequest();
customEditRequest.setId(platformBug.getId());
customEditRequest.setProjectId(platformBug.getProjectId());
List<PlatformCustomFieldItemDTO> platformCustomFields = platformBug.getCustomFieldList();
// 过滤出需要同步的自定义字段{默认模板时, 需要同步所有字段; 非默认模板时, 需要同步模板中映射的字段}
final Map<String, String> needSyncApiFieldFilterMap = needSyncApiFieldMap;
if (platformBug.getPlatformDefaultTemplate()) {
// 平台默认模板创建的缺陷
List<BugCustomFieldDTO> bugCustomFieldDTOList = platformCustomFields.stream()
.map(platformField -> {
BugCustomFieldDTO bugCustomFieldDTO = new BugCustomFieldDTO();
bugCustomFieldDTO.setId(platformField.getId());
bugCustomFieldDTO.setValue(platformField.getValue() == null ? null : platformField.getValue().toString());
return bugCustomFieldDTO;
}).collect(Collectors.toList());
customEditRequest.setCustomFields(bugCustomFieldDTOList);
} else {
// 非平台默认模板创建的缺陷(使用模板API映射字段)
List<BugCustomFieldDTO> bugCustomFieldDTOList = platformCustomFields.stream()
.filter(field -> needSyncApiFieldFilterMap.containsKey(field.getId()))
.map(platformField -> {
BugCustomFieldDTO bugCustomFieldDTO = new BugCustomFieldDTO();
bugCustomFieldDTO.setId(needSyncApiFieldFilterMap.get(platformField.getId()));
bugCustomFieldDTO.setValue(platformField.getValue() == null ? null : platformField.getValue().toString());
return bugCustomFieldDTO;
}).collect(Collectors.toList());
customEditRequest.setCustomFields(bugCustomFieldDTOList);
}
// 保存缺陷
if (originalBug == null) {
// 新增
batchBugMapper.insertSelective(bug);
batchBugContentMapper.insertSelective(bugContent);
handleAndSaveCustomFields(customEditRequest, false, null);
} else {
// 更新
batchBugMapper.updateByPrimaryKeySelective(bug);
batchBugContentMapper.updateByPrimaryKeyWithBLOBs(bugContent);
handleAndSaveCustomFields(customEditRequest, true, null);
}
if (CollectionUtils.isNotEmpty(richTextAttachments)) {
extBugLocalAttachmentMapper.batchInsert(richTextAttachments);
}
} catch (Exception e) {
LogUtils.error(e);
}
}
} }

View File

@ -12,7 +12,6 @@ import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.project.service.ProjectTemplateService; import io.metersphere.project.service.ProjectTemplateService;
import io.metersphere.sdk.constants.TemplateScene; import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.Template; import io.metersphere.system.domain.Template;
import io.metersphere.system.domain.TemplateExample; import io.metersphere.system.domain.TemplateExample;
@ -51,11 +50,11 @@ public class BugSyncService {
private ProjectApplicationService projectApplicationService; private ProjectApplicationService projectApplicationService;
/** /**
* XPACK用户 (同步全量缺陷) * 同步全量缺陷
* @param request 同步全量参数 * @param request 同步全量参数
* @param currentUser 当前用户 * @param currentUser 当前用户
*/ */
public void syncAllBugs(BugSyncRequest request, String currentUser, String language) { public void syncAllBugs(BugSyncRequest request, String currentUser, String language, String triggerMode) {
try { try {
// 获取当前项目同步缺陷唯一Key // 获取当前项目同步缺陷唯一Key
String syncValue = bugSyncExtraService.getSyncKey(request.getProjectId()); String syncValue = bugSyncExtraService.getSyncKey(request.getProjectId());
@ -63,7 +62,7 @@ public class BugSyncService {
// 不存在, 设置保证唯一性, 并开始同步 // 不存在, 设置保证唯一性, 并开始同步
bugSyncExtraService.setSyncKey(request.getProjectId()); bugSyncExtraService.setSyncKey(request.getProjectId());
Project project = getProjectById(request.getProjectId()); Project project = getProjectById(request.getProjectId());
bugService.syncPlatformAllBugs(request, project, currentUser, language); bugService.syncPlatformAllBugs(request, project, currentUser, language, triggerMode);
} }
} catch (Exception e) { } catch (Exception e) {
bugSyncExtraService.deleteSyncKey(request.getProjectId()); bugSyncExtraService.deleteSyncKey(request.getProjectId());
@ -72,7 +71,7 @@ public class BugSyncService {
} }
/** /**
* 开源用户 (同步存量缺陷) * 同步存量缺陷
* @param projectId 项目ID * @param projectId 项目ID
*/ */
public void syncBugs(String projectId, String currentUser, String language, String triggerMode) { public void syncBugs(String projectId, String currentUser, String language, String triggerMode) {
@ -147,25 +146,9 @@ public class BugSyncService {
* @param scheduleUser 任务触发用户 * @param scheduleUser 任务触发用户
*/ */
public void syncPlatformAllBugBySchedule(String projectId, String scheduleUser) { public void syncPlatformAllBugBySchedule(String projectId, String scheduleUser) {
try {
String syncValue = bugSyncExtraService.getSyncKey(projectId);
if (StringUtils.isEmpty(syncValue)) {
// 不存在, 设置保证唯一性, 并开始同步
bugSyncExtraService.setSyncKey(projectId);
XpackBugService bugService = CommonBeanFactory.getBean(XpackBugService.class);
if (bugService != null) {
Project project = getProjectById(projectId);
BugSyncRequest syncRequest = new BugSyncRequest(); BugSyncRequest syncRequest = new BugSyncRequest();
syncRequest.setProjectId(projectId); syncRequest.setProjectId(projectId);
bugService.syncPlatformBugs(project, syncRequest, scheduleUser, Locale.SIMPLIFIED_CHINESE.getLanguage(), Translator.get("sync_mode.auto")); syncAllBugs(syncRequest, scheduleUser, Locale.SIMPLIFIED_CHINESE.getLanguage(), Translator.get("sync_mode.auto"));
}
}
} catch (Exception e) {
// 异常或正常结束都得删除当前项目执行同步的唯一Key
bugSyncExtraService.deleteSyncKey(projectId);
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
bugSyncExtraService.setSyncErrorMsg(projectId, e.getMessage());
}
} }
/** /**

View File

@ -1,21 +0,0 @@
package io.metersphere.bug.service;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.project.domain.Project;
/**
* 缺陷相关企业版功能接口
*/
public interface XpackBugService {
/**
* 同步当前项目第三方平台缺陷(前台调用, 支持全量及同步条件区间)
* @param project 项目
* @param request 同步请求参数
* @param currentUser 当前用户
* @param language 语言环境
* @param triggerMode 同步触发方式
*/
void syncPlatformBugs(Project project, BugSyncRequest request, String currentUser, String language, String triggerMode);
}

View File

@ -24,7 +24,6 @@ import io.metersphere.project.service.FileService;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.StorageType; import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.FileAssociationSourceUtil; import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
@ -45,14 +44,12 @@ import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -63,8 +60,6 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN; import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -731,13 +726,7 @@ public class BugControllerTests extends BaseTest {
bugSyncExtraService.setSyncKey("default-project-for-bug"); bugSyncExtraService.setSyncKey("default-project-for-bug");
this.requestPostWithOk(BUG_SYNC_ALL, request); this.requestPostWithOk(BUG_SYNC_ALL, request);
bugSyncExtraService.deleteSyncKey("default-project-for-bug"); bugSyncExtraService.deleteSyncKey("default-project-for-bug");
Project project = projectMapper.selectByPrimaryKey("default-project-for-bug");
this.requestPostWithOk(BUG_SYNC_ALL, request); this.requestPostWithOk(BUG_SYNC_ALL, request);
BugService mockBugService = Mockito.mock(BugService.class);
Mockito.doThrow(new MSException("sync error!")).when(mockBugService).syncPlatformAllBugs(syncRequest, project, "admin", Locale.SIMPLIFIED_CHINESE.getLanguage());
ReflectionTestUtils.setField(bugSyncService, "bugService", mockBugService);
MSException msException = assertThrows(MSException.class, () -> bugSyncService.syncAllBugs(syncRequest, "admin", Locale.SIMPLIFIED_CHINESE.getLanguage()));
assertEquals(msException.getMessage(), "sync error!");
} }
/** /**

View File

@ -1,15 +0,0 @@
package io.metersphere.bug.mock;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.bug.service.XpackBugService;
import io.metersphere.project.domain.Project;
import org.springframework.stereotype.Service;
@Service
public class XpackBugMockServiceImpl implements XpackBugService {
@Override
public void syncPlatformBugs(Project project, BugSyncRequest request, String currentUser, String language, String triggerMode) {
}
}

View File

@ -181,7 +181,6 @@
getPlatform, getPlatform,
getSyncStatus, getSyncStatus,
syncBugEnterprise, syncBugEnterprise,
syncBugOpenSource,
} from '@/api/modules/bug-management'; } from '@/api/modules/bug-management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
@ -579,23 +578,7 @@
}); });
// //
const handleSync = async () => { const handleSync = async () => {
if (isXpack.value) {
//
syncVisible.value = true; syncVisible.value = true;
} else {
try {
//
await syncBugOpenSource(appStore.currentProjectId);
Message.warning(t('bugManagement.synchronizing'));
isComplete.value = false;
isShowCompleteMsg.value = true;
//
resume();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
}; };
const handleSyncModalOk = async () => { const handleSyncModalOk = async () => {
try { try {

View File

@ -52,7 +52,7 @@
</a-tooltip> </a-tooltip>
</div> </div>
</a-radio> </a-radio>
<a-radio v-xpack value="full"> <a-radio value="full">
<div class="flex flex-row items-center gap-[4px]"> <div class="flex flex-row items-center gap-[4px]">
<span class="text-[var(--color-text-1)]">{{ t('project.menu.fullSync') }}</span> <span class="text-[var(--color-text-1)]">{{ t('project.menu.fullSync') }}</span>
<a-tooltip :content="t('project.menu.fullSyncTip')" position="top"> <a-tooltip :content="t('project.menu.fullSyncTip')" position="top">