feat(缺陷管理): 补充缺陷管理变更日志历史及通知

This commit is contained in:
song-cc-rock 2024-01-18 15:47:48 +08:00 committed by Craftsman
parent b5a5f6d899
commit 0f24d581b0
32 changed files with 691 additions and 125 deletions

View File

@ -184,6 +184,7 @@ message.execute_fail=执行不通过
message.execute_at=执行被@
message.open=开启
message.close=关闭
message.sync_completed=同步完成
message.create_user=创建人
message.follow_people=关注人
message.operator=操作人

View File

@ -220,6 +220,7 @@ message.execute_fail=Execution failed
message.execute_at=Execution is @
message.open=turn on
message.close=closure
message.sync_completed=Sync completed
message.create_user=Create user
message.follow_people=Follow people
message.operator=Operator

View File

@ -220,6 +220,7 @@ message.execute_fail=执行不通过
message.execute_at=执行被@
message.open=开启
message.close=关闭
message.sync_completed=同步完成
message.create_user=创建人
message.follow_people=关注人
message.operator=操作人

View File

@ -220,6 +220,7 @@ message.execute_fail=執行不通過
message.execute_at=執行被@
message.open=開啟
message.close=關閉
message.sync_completed=同步完成
message.create_user=創建人
message.follow_people=關注人
message.operator=操作人

View File

@ -4,9 +4,13 @@ import io.metersphere.bug.domain.BugComment;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.service.BugCommentService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -20,21 +24,32 @@ public class BugCommentController {
private BugCommentService bugCommentService;
@GetMapping("/get/{bugId}")
@Operation(summary = "缺陷管理-评论-获取评论")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#bugId", resourceType = "bug")
public List<BugCommentDTO> get(@PathVariable String bugId) {
return bugCommentService.getComments(bugId);
}
@PostMapping("/add")
@Operation(summary = "缺陷管理-评论-新增评论")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#request.getBugId()", resourceType = "bug")
public BugComment add(@RequestBody BugCommentEditRequest request) {
return bugCommentService.addComment(request, SessionUtils.getUserId());
}
@PostMapping("/update")
@Operation(summary = "缺陷管理-评论-编辑评论")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#request.getBugId()", resourceType = "bug")
public BugComment update(@RequestBody BugCommentEditRequest request) {
return bugCommentService.updateComment(request, SessionUtils.getUserId());
}
@GetMapping("/delete/{commentId}")
@Operation(summary = "缺陷管理-评论-删除评论")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public void delete(@PathVariable String commentId) {
bugCommentService.deleteComment(commentId);
}

View File

@ -6,9 +6,7 @@ import io.metersphere.bug.constants.BugExportColumns;
import io.metersphere.bug.dto.BugSyncResult;
import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.service.BugService;
import io.metersphere.bug.service.BugStatusService;
import io.metersphere.bug.service.BugSyncService;
import io.metersphere.bug.service.*;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.service.ProjectTemplateService;
@ -16,6 +14,11 @@ import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.metersphere.system.utils.SessionUtils;
@ -51,29 +54,33 @@ public class BugController {
private ProjectTemplateService projectTemplateService;
@GetMapping("/header/custom-field/{projectId}")
@Operation(summary = "缺陷管理-获取表头自定义字段")
@Operation(summary = "缺陷管理-列表-获取表头自定义字段")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<TemplateCustomFieldDTO> getHeaderFields(@PathVariable String projectId) {
return bugService.getHeaderCustomFields(projectId);
}
@GetMapping("/header/status-option/{projectId}")
@Operation(summary = "缺陷管理-获取表头状态选项")
@Operation(summary = "缺陷管理-列表-获取表头状态选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<SelectOption> getHeaderStatusOption(@PathVariable String projectId) {
return bugStatusService.getHeaderStatusOption(projectId);
}
@GetMapping("/header/handler-option/{projectId}")
@Operation(summary = "缺陷管理-获取表头处理人选项")
@Operation(summary = "缺陷管理-列表-获取表头处理人选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<SelectOption> getHeaderHandleOption(@PathVariable String projectId) {
return bugService.getHeaderHandlerOption(projectId);
}
@PostMapping("/page")
@Operation(summary = "缺陷管理-获取缺陷列表")
@Operation(summary = "缺陷管理-列表-分页缺陷列表")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public Pager<List<BugDTO>> page(@Validated @RequestBody BugPageRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
@ -82,104 +89,121 @@ public class BugController {
}
@PostMapping("/add")
@Operation(summary = "缺陷管理-创建缺陷")
@Operation(summary = "缺陷管理-列表-创建缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_ADD)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@Log(type = OperationLogType.ADD, expression = "#msClass.addLog(#request, #files)", msClass = BugLogService.class)
@SendNotice(taskType = NoticeConstants.TaskType.BUG_TASK, event = NoticeConstants.Event.CREATE, target = "#targetClass.getNoticeByRequest(#request)", targetClass = BugNoticeService.class)
public void add(@Validated({Created.class}) @RequestPart(value = "request") BugEditRequest request,
@RequestPart(value = "file", required = false) List<MultipartFile> files) {
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), false);
}
@PostMapping("/update")
@Operation(summary = "缺陷管理-更新缺陷")
@Operation(summary = "缺陷管理-列表-编辑缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@Log(type = OperationLogType.UPDATE, expression = "#msClass.updateLog(#request, #files)", msClass = BugLogService.class)
@SendNotice(taskType = NoticeConstants.TaskType.BUG_TASK, event = NoticeConstants.Event.UPDATE, target = "#targetClass.getNoticeByRequest(#request)", targetClass = BugNoticeService.class)
public void update(@Validated({Updated.class}) @RequestPart(value = "request") BugEditRequest request,
@RequestPart(value = "file", required = false) List<MultipartFile> files) {
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), true);
}
@GetMapping("/delete/{id}")
@Operation(summary = "缺陷管理-删除缺陷")
@Operation(summary = "缺陷管理-列表-删除缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = BugLogService.class)
@SendNotice(taskType = NoticeConstants.TaskType.BUG_TASK, event = NoticeConstants.Event.DELETE, target = "#targetClass.getNoticeById(#id)", targetClass = BugNoticeService.class)
public void delete(@PathVariable String id) {
bugService.delete(id);
bugService.delete(id, SessionUtils.getUserId());
}
@GetMapping("/template/option")
@Operation(summary = "缺陷管理-获取当前项目缺陷模板选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public List<ProjectTemplateOptionDTO> getTemplateOption(@RequestParam(value = "projectId") String projectId) {
return projectTemplateService.getOption(projectId, TemplateScene.BUG.name());
@GetMapping("/sync/{projectId}")
@Operation(summary = "缺陷管理-列表-同步缺陷(开源)")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public void sync(@PathVariable String projectId) {
bugSyncService.syncBugs(projectId, SessionUtils.getUserId());
}
@PostMapping("/template/detail")
@Operation(summary = "缺陷管理-获取模板详情内容")
@PostMapping("/sync/all")
@Operation(summary = "缺陷管理-列表-同步缺陷(全量)")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void syncAll(@RequestBody BugSyncRequest request) {
bugSyncService.syncAllBugs(request, SessionUtils.getUserId());
}
@GetMapping("/sync/check/{projectId}")
@Operation(summary = "缺陷管理-列表-同步状态校验")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public TemplateDTO getTemplateDetail(@RequestBody BugTemplateRequest request) {
return bugService.getTemplate(request.getId(), request.getProjectId(), request.getFromStatusId(), request.getPlatformBugKey());
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public BugSyncResult checkStatus(@PathVariable String projectId) {
return bugSyncService.checkSyncStatus(projectId);
}
@GetMapping("/export/columns/{projectId}")
@Operation(summary = "缺陷管理-列表-获取导出字段配置")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public BugExportColumns getExportColumns(@PathVariable String projectId) {
return bugService.getExportColumns(projectId);
}
@PostMapping("/export")
@Operation(summary = "缺陷管理-列表-批量导出缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public ResponseEntity<byte[]> export(@Validated @RequestBody BugExportRequest request) throws Exception {
return bugService.export(request);
}
@PostMapping("/batch-delete")
@Operation(summary = "缺陷管理-批量删除缺陷")
@Operation(summary = "缺陷管理-列表-批量删除缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchDelete(@Validated @RequestBody BugBatchRequest request) {
request.setUseTrash(false);
bugService.batchDelete(request);
bugService.batchDelete(request, SessionUtils.getUserId());
}
@PostMapping("/batch-update")
@Operation(summary = "缺陷管理-批量编辑缺陷")
@Operation(summary = "缺陷管理-列表-批量编辑缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchUpdate(@Validated @RequestBody BugBatchUpdateRequest request) {
request.setUseTrash(false);
bugService.batchUpdate(request, SessionUtils.getUserId());
}
@GetMapping("/template/option/{projectId}")
@Operation(summary = "缺陷管理-详情-获取当前项目模板选项")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<ProjectTemplateOptionDTO> getTemplateOption(@PathVariable String projectId) {
return projectTemplateService.getOption(projectId, TemplateScene.BUG.name());
}
@PostMapping("/template/detail")
@Operation(summary = "缺陷管理-详情-获取模板详情内容")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public TemplateDTO getTemplateDetail(@RequestBody BugTemplateRequest request) {
return bugService.getTemplate(request.getId(), request.getProjectId(), request.getFromStatusId(), request.getPlatformBugKey());
}
@GetMapping("/follow/{id}")
@Operation(summary = "缺陷管理-关注缺陷")
@Operation(summary = "缺陷管理-详情-关注缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void follow(@PathVariable String id) {
bugService.follow(id, SessionUtils.getUserId());
}
@GetMapping("/unfollow/{id}")
@Operation(summary = "缺陷管理-取消关注缺陷")
@Operation(summary = "缺陷管理-详情-取消关注缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void unfollow(@PathVariable String id) {
bugService.unfollow(id, SessionUtils.getUserId());
}
@GetMapping("/sync/{projectId}")
@Operation(summary = "缺陷管理-同步缺陷(开源)")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void sync(@PathVariable String projectId) {
bugSyncService.syncBugs(projectId);
}
@PostMapping("/sync/all")
@Operation(summary = "缺陷管理-同步缺陷(全量)")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public void syncAll(@RequestBody BugSyncRequest request) {
bugSyncService.syncAllBugs(request);
}
@GetMapping("/sync/check/{projectId}")
@Operation(summary = "缺陷管理-同步状态校验")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_READ)
public BugSyncResult checkStatus(@PathVariable String projectId) {
return bugSyncService.checkSyncStatus(projectId);
}
@GetMapping("/export/columns/{projectId}")
@Operation(summary = "缺陷管理-获取导出字段配置")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
public BugExportColumns getExportColumns(@PathVariable String projectId) {
return bugService.getExportColumns(projectId);
}
@PostMapping("/export")
@Operation(summary = "缺陷管理-批量导出缺陷")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_EXPORT)
public ResponseEntity<byte[]> export(@Validated @RequestBody BugExportRequest request) throws Exception {
return bugService.export(request);
}
}

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.bug.service.BugRelateCaseLogService;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateCaseModuleRequest;
@ -12,6 +13,8 @@ import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
@ -80,6 +83,7 @@ public class BugRelateCaseController {
@GetMapping("/un-relate/{id}")
@Operation(description = "缺陷管理-关联用例-取消关联用例")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@Log(type = OperationLogType.DISASSOCIATE, expression = "#msClass.getRelateLog(#id)", msClass = BugRelateCaseLogService.class)
public void unRelate(@PathVariable String id) {
bugRelateCaseCommonService.unRelate(id);
}

View File

@ -5,8 +5,12 @@ import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.request.BugBatchRequest;
import io.metersphere.bug.dto.request.BugPageRequest;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.service.BugLogService;
import io.metersphere.bug.service.BugService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
import io.swagger.v3.oas.annotations.Operation;
@ -40,6 +44,7 @@ public class BugTrashController {
@GetMapping("/recover/{id}")
@Operation(summary = "回收站-恢复")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@Log(type = OperationLogType.RECOVER, expression = "#msClass.recoverLog(#id)", msClass = BugLogService.class)
public void recover(@PathVariable String id) {
bugService.recover(id);
}
@ -54,6 +59,7 @@ public class BugTrashController {
@PostMapping("/batch-recover")
@Operation(summary = "回收站-批量恢复")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchRecover(@Validated @RequestBody BugBatchRequest request) {
request.setUseTrash(true);
bugService.batchRecover(request);
@ -62,6 +68,7 @@ public class BugTrashController {
@PostMapping("/batch-delete")
@Operation(summary = "回收站-批量彻底删除")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_DELETE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchDelete(@Validated @RequestBody BugBatchRequest request) {
request.setUseTrash(true);
bugService.batchDeleteTrash(request);

View File

@ -1,17 +1,19 @@
package io.metersphere.bug.dto.response;
import io.metersphere.functional.domain.FunctionalCase;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class BugRelateCaseDTO extends FunctionalCase {
public class BugRelateCaseDTO{
@Schema(description = "关联ID")
private String relateId;
@Schema(description = "关联用例名称")
private String relateCaseName;
@Schema(description = "关联类型")
private String relateCaseType;
@ -29,4 +31,10 @@ public class BugRelateCaseDTO extends FunctionalCase {
@Schema(description = "版本名称")
private String versionName;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "版本ID")
private String versionId;
}

View File

@ -7,7 +7,7 @@ public enum BugAttachmentSourceType {
*/
ATTACHMENT,
/**
* MarkDown编辑器
* MD图片
*/
MD;
MD_PIC;
}

View File

@ -50,5 +50,14 @@ public interface ExtBugRelateCaseMapper {
*/
List<BugRelateCaseDTO> list(@Param("request") BugRelatedCasePageRequest request);
/**
* 根据CaseId获取关联的Case
* @param id 用例ID
* @param sourceType 用例类型
* @return 用例关联DTO
*/
BugRelateCaseDTO getRelateCase(@Param("id") String id, @Param("sourceType") String sourceType);
List<BugProviderDTO> getAssociateBugs(@Param("request") AssociateBugPageRequest request, @Param("sort") String sort);
}

View File

@ -46,7 +46,7 @@
</select>
<select id="list" resultType="io.metersphere.bug.dto.response.BugRelateCaseDTO">
select fc.num, fc.name, fc.project_id, fc.version_id, brc.case_type relateCaseType, brc.id relateId,
select fc.num relateId, fc.name relateCaseName, fc.project_id projectId, fc.version_id versionId, brc.case_type relateCaseType,
brc.test_plan_id is not null relatePlanCase, brc.case_id is not null relateCase
from bug_relation_case brc join functional_case fc on (brc.case_id = fc.id or brc.test_plan_case_id = fc.id)
where brc.bug_id = #{request.bugId} and fc.deleted = false
@ -55,6 +55,17 @@
</if>
</select>
<select id="getRelateCase" resultType="io.metersphere.bug.dto.response.BugRelateCaseDTO">
select distinct c.name relateCaseName, c.project_id projectId, c.version_id versionId, brc.case_type relateCaseType, c.num relateId,
brc.test_plan_id is not null relatePlanCase, brc.case_id is not null relateCase
from bug_relation_case brc
<if test="sourceType == 'FUNCTIONAL'">
join functional_case c on (brc.case_id = c.id or brc.test_plan_case_id = c.id)
</if>
<!-- 后续根据SourceType扩展 -->
where c.id = #{id} and c.deleted = false
</select>
<select id="getAssociateBugs" resultType="io.metersphere.dto.BugProviderDTO">
SELECT
brc.id as id,

View File

@ -47,7 +47,7 @@ public class BugCommentService {
BugCommentExample example = new BugCommentExample();
example.createCriteria().andBugIdEqualTo(bugId);
List<BugComment> bugComments = bugCommentMapper.selectByExample(example);
return generateCommentDTOs(bugComments);
return wrapperComments(bugComments);
}
/**
@ -56,7 +56,7 @@ public class BugCommentService {
* @param bugComments 缺陷评论集合
* @return 缺陷评论DTO
*/
private List<BugCommentDTO> generateCommentDTOs(List<BugComment> bugComments) {
private List<BugCommentDTO> wrapperComments(List<BugComment> bugComments) {
if (CollectionUtils.isEmpty(bugComments)) {
return new ArrayList<>();
}
@ -100,7 +100,7 @@ public class BugCommentService {
Map<String, List<BugCommentDTO>> returnMap = new HashMap<>();
for (Map.Entry<String, List<BugComment>> entry : bugCommentByBugId.entrySet()) {
returnMap.put(entry.getKey(), generateCommentDTOs(entry.getValue()));
returnMap.put(entry.getKey(), wrapperComments(entry.getValue()));
}
return returnMap;
}

View File

@ -0,0 +1,124 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugContent;
import io.metersphere.bug.dto.request.BugEditRequest;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.mapper.BugContentMapper;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugLogService {
@Resource
private BugMapper bugMapper;
@Resource
private BugService bugService;
@Resource
private BugContentMapper bugContentMapper;
/**
* 新增缺陷日志
*
* @param request 请求参数
* @param files 文件
* @return 日志
*/
public LogDTO addLog(BugEditRequest request, List<MultipartFile> files) {
LogDTO dto = new LogDTO(request.getProjectId(), null, null, null, OperationLogType.ADD.name(), OperationLogModule.BUG_MANAGEMENT, request.getTitle());
dto.setHistory(true);
dto.setPath("/bug/add");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setModifiedValue(JSON.toJSONBytes(request));
return dto;
}
/**
* 更新缺陷日志
*
* @param request 请求参数
* @param files 文件
* @return 日志
*/
public LogDTO updateLog(BugEditRequest request, List<MultipartFile> files) {
BugDTO history = getOriginalValue(request.getId());
LogDTO dto = new LogDTO(request.getProjectId(), null, request.getId(), null, OperationLogType.UPDATE.name(), OperationLogModule.BUG_MANAGEMENT, request.getTitle());
dto.setHistory(true);
dto.setPath("/bug/update");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setModifiedValue(JSON.toJSONBytes(request));
dto.setOriginalValue(JSON.toJSONBytes(history));
return dto;
}
/**
* 删除缺陷日志
*
* @param id 缺陷ID
* @return 日志
*/
public LogDTO deleteLog(String id) {
Bug bug = bugMapper.selectByPrimaryKey(id);
if (bug != null) {
LogDTO dto = new LogDTO(bug.getProjectId(), null, bug.getId(), null, OperationLogType.DELETE.name(), OperationLogModule.BUG_MANAGEMENT, bug.getTitle());
dto.setPath("/bug/delete");
dto.setMethod(HttpMethodConstants.GET.name());
dto.setOriginalValue(JSON.toJSONBytes(bug));
return dto;
}
return null;
}
/**
* 恢复缺陷日志
*
* @param id 缺陷ID
* @return 日志
*/
public LogDTO recoverLog(String id) {
Bug bug = bugMapper.selectByPrimaryKey(id);
if (bug != null) {
LogDTO dto = new LogDTO(bug.getProjectId(), null, bug.getId(), null, OperationLogType.RECOVER.name(), OperationLogModule.BUG_MANAGEMENT, bug.getTitle());
dto.setPath("/bug/trash/recover");
dto.setMethod(HttpMethodConstants.GET.name());
dto.setOriginalValue(JSON.toJSONBytes(bug));
return dto;
}
return null;
}
/**
* 获取历史缺陷
* @param id 缺陷ID
* @return 缺陷DTO
*/
private BugDTO getOriginalValue(String id) {
// 缺陷基础信息
BugDTO originalBug = new BugDTO();
Bug bug = bugMapper.selectByPrimaryKey(id);
if (bug == null) {
return null;
}
BeanUtils.copyBean(originalBug, bug);
BugContent bugContent = bugContentMapper.selectByPrimaryKey(id);
if (bugContent != null) {
originalBug.setDescription(bugContent.getDescription());
}
// 缺陷自定义字段
return bugService.handleCustomField(List.of(originalBug), originalBug.getProjectId()).get(0);
}
}

View File

@ -0,0 +1,73 @@
package io.metersphere.bug.service;
import io.metersphere.bug.dto.request.BugEditRequest;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.system.dto.BugNoticeDTO;
import io.metersphere.system.dto.sdk.OptionDTO;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
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;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugNoticeService {
public static final String CUSTOM_TITLE = "summary";
public static final String CUSTOM_STATUS = "status";
public static final String CUSTOM_HANDLE_USER = "处理人";
@Resource
private BugService bugService;
@Resource
private BugStatusService bugStatusService;
public BugNoticeDTO getNoticeByRequest(BugEditRequest request) {
// 获取状态选项, 处理人选项
Map<String, String> statusMap = getStatusMap(request.getProjectId());
Map<String, String> handlerMap = getHandleMap(request.getProjectId());
// 构建通知对象
BugNoticeDTO notice = new BugNoticeDTO();
notice.setTitle(request.getTitle());
// 自定义字段解析{name: value}
if (CollectionUtils.isNotEmpty(request.getCustomFields())) {
List<OptionDTO> fields = new ArrayList<>();
request.getCustomFields().forEach(field -> {
if (StringUtils.equals(field.getId(), CUSTOM_TITLE)) {
// TITLE {标题为空时, 从自定义字段中获取标题}
notice.setTitle(field.getValue());
} else if (StringUtils.equals(field.getId(), CUSTOM_STATUS)) {
// 状态 {从自定义字段中获取状态}
notice.setStatus(statusMap.get(field.getValue()));
} else if (StringUtils.equals(field.getName(), CUSTOM_HANDLE_USER)) {
// 处理人 {从自定义字段中获取状态}
notice.setHandleUser(handlerMap.get(field.getValue()));
} else {
// 其他自定义字段
OptionDTO fieldDTO = new OptionDTO();
fieldDTO.setId(field.getName());
fieldDTO.setName(field.getValue());
fields.add(fieldDTO);
}
});
notice.setCustomFields(fields);
}
return notice;
}
private Map<String, String> getStatusMap(String projectId) {
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(projectId);
return statusOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
}
private Map<String, String> getHandleMap(String projectId) {
List<SelectOption> handlerOption = bugService.getHeaderHandlerOption(projectId);
return handlerOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
}
}

View File

@ -139,6 +139,7 @@ public class BugRelateCaseCommonService extends ModuleTreeService {
* @param request 请求参数
*/
public List<BugRelateCaseDTO> page(BugRelatedCasePageRequest request) {
// 目前只查关联的功能用例类型, 后续多个用例类型SQL扩展
List<BugRelateCaseDTO> relateCases = extBugRelateCaseMapper.list(request);
if (CollectionUtils.isEmpty(relateCases)) {
return new ArrayList<>();

View File

@ -0,0 +1,40 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugRelateCaseLogService {
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@Resource
private ExtBugRelateCaseMapper extBugRelateCaseMapper;
/**
* 新增缺陷日志
*
* @param id 取消关联的引用ID
* @return 日志
*/
public LogDTO getRelateLog(String id) {
BugRelationCase bugRelationCase = bugRelationCaseMapper.selectByPrimaryKey(id);
BugRelateCaseDTO relateCase = extBugRelateCaseMapper.getRelateCase(bugRelationCase.getCaseId(), bugRelationCase.getCaseType());
LogDTO dto = new LogDTO(relateCase.getProjectId(), null, null, null, OperationLogType.DISASSOCIATE.name(), OperationLogModule.BUG_MANAGEMENT, relateCase.getRelateCaseName());
dto.setPath("/bug/un-relate");
dto.setMethod(HttpMethodConstants.GET.name());
dto.setModifiedValue(JSON.toJSONBytes(relateCase));
return dto;
}
}

View File

@ -28,6 +28,7 @@ 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.*;
@ -37,17 +38,27 @@ import io.metersphere.sdk.util.*;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.domain.Template;
import io.metersphere.system.domain.TemplateCustomField;
import io.metersphere.system.domain.User;
import io.metersphere.system.dto.BugNoticeDTO;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
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.mapper.UserMapper;
import io.metersphere.system.notice.NoticeModel;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.notice.utils.MessageTemplateUtils;
import io.metersphere.system.service.*;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource;
import jodd.util.StringUtil;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
@ -84,24 +95,32 @@ public class BugService {
@Resource
private BugMapper bugMapper;
@Resource
private UserMapper userMapper;
@Resource
private ExtBugMapper extBugMapper;
@Resource
private BugContentMapper bugContentMapper;
private ProjectMapper projectMapper;
@Resource
private NoticeSendService noticeSendService;
@Resource
private BaseUserMapper baseUserMapper;
@Resource
protected TemplateMapper templateMapper;
@Resource
private BugContentMapper bugContentMapper;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ProjectTemplateService projectTemplateService;
@Resource
private BaseTemplateCustomFieldService baseTemplateCustomFieldService;
private OperationLogService operationLogService;
@Resource
private PlatformPluginService platformPluginService;
@Resource
private ProjectTemplateService projectTemplateService;
@Resource
private UserPlatformAccountService userPlatformAccountService;
@Resource
private BaseTemplateCustomFieldService baseTemplateCustomFieldService;
@Resource
private BugCustomFieldMapper bugCustomFieldMapper;
@Resource
private ExtBugCustomFieldMapper extBugCustomFieldMapper;
@ -136,6 +155,8 @@ public class BugService {
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private BugSyncNoticeService bugSyncNoticeService;
@Resource
private BugStatusService bugStatusService;
@Resource
private ProjectMemberService projectMemberService;
@ -208,7 +229,7 @@ public class BugService {
*
* @param id 缺陷ID
*/
public void delete(String id) {
public void delete(String id, String currentUser) {
Bug bug = checkById(id);
if (StringUtils.equals(bug.getPlatform(), BugPlatform.LOCAL.getName())) {
Bug record = new Bug();
@ -224,6 +245,8 @@ public class BugService {
clearAssociate(id, bug.getProjectId());
bugMapper.deleteByPrimaryKey(id);
}
// 发送通知
sendDeleteNotice(bug, currentUser);
}
/**
@ -284,9 +307,12 @@ public class BugService {
* 批量删除缺陷
* @param request 请求参数
*/
public void batchDelete(BugBatchRequest request) {
public void batchDelete(BugBatchRequest request, String currentUser) {
List<String> batchIds = getBatchIdsByRequest(request);
batchIds.forEach(this::delete);
batchIds.forEach(id -> delete(id, currentUser));
// 批量日志
List<LogDTO> logs = getBatchLogByRequest(batchIds, OperationLogType.DELETE.name(), "/bug/batch-delete", request.getProjectId(), false, false, null);
operationLogService.batchAdd(logs);
}
/**
@ -296,6 +322,9 @@ public class BugService {
public void batchRecover(BugBatchRequest request) {
List<String> batchIds = getBatchIdsByRequest(request);
batchIds.forEach(this::recover);
// 批量日志
List<LogDTO> logs = getBatchLogByRequest(batchIds, OperationLogType.RECOVER.name(), "/bug/batch-recover", request.getProjectId(), false, false, null);
operationLogService.batchAdd(logs);
}
/**
@ -315,6 +344,10 @@ public class BugService {
public void batchUpdate(BugBatchUpdateRequest request, String currentUser) {
List<String> batchIds = getBatchIdsByRequest(request);
// 批量日志{修改之前}
List<LogDTO> logs = getBatchLogByRequest(batchIds, OperationLogType.UPDATE.name(), "/bug/batch-update",
request.getProjectId(), true, request.isAppend(), request.getTags());
operationLogService.batchAdd(logs);
// 目前只做标签的批量编辑
if (request.isAppend()) {
// 标签(追加)
@ -369,13 +402,14 @@ public class BugService {
* 同步平台缺陷(全量)
* @param request 同步请求参数
* @param project 项目
* @param currentUser 当前用户
*/
@Async
public void syncPlatformAllBugs(BugSyncRequest request, Project project) {
public void syncPlatformAllBugs(BugSyncRequest request, Project project, String currentUser) {
try {
XpackBugService bugService = CommonBeanFactory.getBean(XpackBugService.class);
if (bugService != null) {
bugService.syncPlatformBugs(project, request);
bugService.syncPlatformBugs(project, request, currentUser);
}
} catch (Exception e) {
LogUtils.error(e);
@ -393,7 +427,7 @@ public class BugService {
* @param project 项目
*/
@Async
public void syncPlatformBugs(List<Bug> remainBugs, Project project) {
public void syncPlatformBugs(List<Bug> remainBugs, Project project, String currentUser) {
try {
// 分页同步
SubListUtils.dealForSubList(remainBugs, 500, (subBugs) -> doSyncPlatformBugs(subBugs, project));
@ -404,6 +438,8 @@ public class BugService {
} finally {
// 异常或正常结束都得删除当前项目执行同步的Key
bugSyncExtraService.deleteSyncKey(project.getId());
// 发送同步通知
bugSyncNoticeService.sendNotice(remainBugs.size(), currentUser);
}
}
@ -956,7 +992,7 @@ public class BugService {
* @param bugs 缺陷集合
* @return 缺陷DTO集合
*/
private List<BugDTO> handleCustomField(List<BugDTO> bugs, String projectId) {
public List<BugDTO> handleCustomField(List<BugDTO> bugs, String projectId) {
List<String> ids = bugs.stream().map(BugDTO::getId).toList();
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(ids, projectId);
Map<String, List<BugCustomFieldDTO>> customFieldMap = customFields.stream().collect(Collectors.groupingBy(BugCustomFieldDTO::getBugId));
@ -1168,7 +1204,7 @@ public class BugService {
* @param request 批量操作参数
* @return 缺陷集合
*/
private List<String> getBatchIdsByRequest(BugBatchRequest request) {
public List<String> getBatchIdsByRequest(BugBatchRequest request) {
if (request.isSelectAll()) {
// 全选{根据查询条件查询所有数据, 排除取消勾选的数据}
BugPageRequest bugPageRequest = new BugPageRequest();
@ -1272,6 +1308,11 @@ public class BugService {
return headerCustomFields.stream().distinct().toList();
}
/**
* 校验缺陷是否存在并返回
* @param bugId 缺陷ID
* @return 缺陷
*/
private Bug checkById(String bugId) {
Bug bug = bugMapper.selectByPrimaryKey(bugId);
if (bug == null) {
@ -1279,4 +1320,66 @@ public class BugService {
}
return bug;
}
/**
* 根据批量操作参数获取批量日志
* @param batchIds 批量操作ID
* @param operationType 操作类型
* @param path 请求路径
* @param batchUpdate 是否批量更新
* @return 日志集合
*/
private List<LogDTO> getBatchLogByRequest(List<String> batchIds, String operationType, String path, String projectId, boolean batchUpdate,
boolean appendTag, List<String> modifiedTags) {
Project project = projectMapper.selectByPrimaryKey(projectId);
BugExample example = new BugExample();
example.createCriteria().andIdIn(batchIds);
List<Bug> bugs = bugMapper.selectByExample(example);
List<LogDTO> logs = new ArrayList<>();
bugs.forEach(bug -> {
LogDTO log = new LogDTO(bug.getProjectId(), project.getOrganizationId(), bug.getId(), null, operationType, OperationLogModule.BUG_MANAGEMENT, bug.getTitle());
log.setPath(path);
log.setMethod(HttpMethodConstants.POST.name());
if (batchUpdate) {
// 批量更新只记录TAG的变更内容
log.setOriginalValue(JSON.toJSONBytes(bug.getTags()));
log.setModifiedValue(JSON.toJSONBytes(appendTag ? ListUtils.union(bug.getTags(), modifiedTags) : modifiedTags));
} else {
log.setOriginalValue(JSON.toJSONBytes(bug));
}
logs.add(log);
});
return logs;
}
private void sendDeleteNotice(Bug bug, String currentUser) {
List<SelectOption> statusOption = bugStatusService.getHeaderStatusOption(bug.getProjectId());
Map<String, String> statusMap = statusOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
List<SelectOption> handlerOption = getHeaderHandlerOption(bug.getProjectId());
Map<String, String> handlerMap = handlerOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText));
// 缺陷相关内容
BugNoticeDTO notice = new BugNoticeDTO();
notice.setTitle(bug.getTitle());
notice.setStatus(statusMap.get(bug.getStatus()));
notice.setHandleUser(handlerMap.get(bug.getHandleUser()));
List<BugCustomFieldDTO> customFields = extBugCustomFieldMapper.getBugAllCustomFields(List.of(bug.getId()), bug.getProjectId());
List<OptionDTO> fields = customFields.stream().map(field -> {
OptionDTO fieldDTO = new OptionDTO();
fieldDTO.setId(field.getName());
fieldDTO.setName(field.getValue());
return fieldDTO;
}).toList();
notice.setCustomFields(fields);
BeanMap beanMap = new BeanMap(notice);
User user = userMapper.selectByPrimaryKey(currentUser);
Map paramMap = new HashMap<>(beanMap);
paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName());
Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap();
String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap();
String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_TASK_DELETE);
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser)
.context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.DELETE).build();
noticeSendService.send(NoticeConstants.TaskType.BUG_TASK, noticeModel);
}
}

View File

@ -0,0 +1,40 @@
package io.metersphere.bug.service;
import io.metersphere.system.domain.User;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.NoticeModel;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.notice.utils.MessageTemplateUtils;
import io.metersphere.system.service.NoticeSendService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugSyncNoticeService {
@Resource
private UserMapper userMapper;
@Resource
private NoticeSendService noticeSendService;
public void sendNotice(int total, String currentUser) {
User user = userMapper.selectByPrimaryKey(currentUser);
Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap();
String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED);
Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap();
String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED);
// ${OPERATOR}同步了${total}条缺陷
Map paramMap = new HashMap<>();
paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName());
paramMap.put("total", total);
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser)
.context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.SYNC_COMPLETED).build();
noticeSendService.send(NoticeConstants.TaskType.BUG_SYNC_TASK, noticeModel);
}
}

View File

@ -52,8 +52,9 @@ public class BugSyncService {
/**
* XPACK用户 (同步全量缺陷)
* @param request 同步全量参数
* @param currentUser 当前用户
*/
public void syncAllBugs(BugSyncRequest request) {
public void syncAllBugs(BugSyncRequest request, String currentUser) {
try {
// 获取当前项目同步缺陷唯一Key
String syncValue = bugSyncExtraService.getSyncKey(request.getProjectId());
@ -61,7 +62,7 @@ public class BugSyncService {
// 不存在, 设置保证唯一性, 并开始同步
bugSyncExtraService.setSyncKey(request.getProjectId());
Project project = getProjectById(request.getProjectId());
bugService.syncPlatformAllBugs(request, project);
bugService.syncPlatformAllBugs(request, project, currentUser);
}
} catch (Exception e) {
bugSyncExtraService.deleteSyncKey(request.getProjectId());
@ -73,7 +74,7 @@ public class BugSyncService {
* 开源用户 (同步存量缺陷)
* @param projectId 项目ID
*/
public void syncBugs(String projectId) {
public void syncBugs(String projectId, String currentUser) {
try {
String syncValue = bugSyncExtraService.getSyncKey(projectId);
if (StringUtils.isEmpty(syncValue)) {
@ -99,7 +100,7 @@ public class BugSyncService {
Map<String, Template> templateMap = templates.stream().collect(Collectors.toMap(Template::getId, t -> t));
// 非插件默认模板且模板不存在, 无需同步
bugs.removeIf(bug -> !templateMap.containsKey(bug.getTemplateId()) && !StringUtils.equals(bug.getTemplateId(), pluginTemplate.getId()));
bugService.syncPlatformBugs(bugs, project);
bugService.syncPlatformBugs(bugs, project, currentUser);
}
}
} catch (Exception e) {
@ -136,11 +137,11 @@ public class BugSyncService {
public void syncPlatformBugBySchedule() {
ProjectExample example = new ProjectExample();
List<Project> projects = projectMapper.selectByExample(example);
List<String> allProjectIds = projects.stream().map(Project::getId).toList();
List<String> syncProjectIds = projectApplicationService.filterNoLocalPlatform(allProjectIds);
List<String> allProjectIds = projects.stream().map(Project::getId).collect(Collectors.toList());
List<String> syncProjectIds = projectApplicationService.filterNeedSyncProject(allProjectIds);
syncProjectIds.forEach(id -> {
try {
syncBugs(id);
syncBugs(id, "admin");
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
}

View File

@ -17,6 +17,7 @@ public interface XpackBugService {
* 同步当前项目第三方平台缺陷(前台调用)
* @param project 项目
* @param request 同步请求参数
* @param currentUser 当前用户
*/
void syncPlatformBugs(Project project, BugSyncRequest request);
void syncPlatformBugs(Project project, BugSyncRequest request, String currentUser);
}

View File

@ -304,7 +304,7 @@ public class BugControllerTests extends BaseTest {
@Test
@Order(9)
void testGetBugTemplateOption() throws Exception {
MvcResult mvcResult = this.requestGetWithOkAndReturn(BUG_TEMPLATE_OPTION + "?projectId=default-project-for-bug");
MvcResult mvcResult = this.requestGetWithOkAndReturn(BUG_TEMPLATE_OPTION + "/default-project-for-bug");
String sortData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(sortData, ResultHolder.class);
List<ProjectTemplateOptionDTO> templateOptionDTOS = JSON.parseArray(JSON.toJSONString(resultHolder.getData()), ProjectTemplateOptionDTO.class);
@ -557,6 +557,12 @@ public class BugControllerTests extends BaseTest {
// 添加使用Jira默认模板的缺陷
addRequest.setTemplateId("jira");
BugCustomFieldDTO summary = new BugCustomFieldDTO();
summary.setId("summary");
summary.setName("摘要");
summary.setType("input");
summary.setValue("这是一个系统Jira模板创建的缺陷");
addRequest.getCustomFields().add(summary);
MultiValueMap<String, Object> addParam3 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipart(BUG_ADD, addParam3).andExpect(status().is5xxServerError());
@ -584,13 +590,13 @@ public class BugControllerTests extends BaseTest {
List<Bug> remainBugs = bugMapper.selectByExample(example);
Project defaultProject = projectMapper.selectByPrimaryKey("default-project-for-bug");
// 同步第一次
bugService.syncPlatformBugs(remainBugs, defaultProject);
bugService.syncPlatformBugs(remainBugs, defaultProject, "admin");
// 同步第二次
renameLocalFile(updateRequest2.getId()); // 重命名后, 同步时会删除本地文件
bugService.syncPlatformBugs(remainBugs, defaultProject);
bugService.syncPlatformBugs(remainBugs, defaultProject, "admin");
// 同步第三次
deleteLocalFile(updateRequest2.getId()); // 手动删除关联的文件, 重新同步时会下载平台附件
bugService.syncPlatformBugs(remainBugs, defaultProject);
bugService.syncPlatformBugs(remainBugs, defaultProject, "admin");
// 集成配置为空
addRequest.setProjectId("default-project-for-not-integration");
@ -686,9 +692,9 @@ public class BugControllerTests extends BaseTest {
Project project = projectMapper.selectByPrimaryKey("default-project-for-bug");
this.requestPostWithOk(BUG_SYNC_ALL, request);
BugService mockBugService = Mockito.mock(BugService.class);
Mockito.doThrow(new MSException("sync error!")).when(mockBugService).syncPlatformAllBugs(syncRequest, project);
Mockito.doThrow(new MSException("sync error!")).when(mockBugService).syncPlatformAllBugs(syncRequest, project, "admin");
ReflectionTestUtils.setField(bugSyncService, "bugService", mockBugService);
MSException msException = assertThrows(MSException.class, () -> bugSyncService.syncAllBugs(syncRequest));
MSException msException = assertThrows(MSException.class, () -> bugSyncService.syncAllBugs(syncRequest, "admin"));
assertEquals(msException.getMessage(), "sync error!");
}

View File

@ -113,7 +113,7 @@ public class BugRelateCaseControllerTests extends BaseTest {
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= request.getPageSize());
// 返回值中取出第一条数据, 并判断是否包含关键字default
BugRelateCaseDTO bugRelateCaseDTO = JSON.parseArray(JSON.toJSONString(pageData.getList()), BugRelateCaseDTO.class).get(0);
Assertions.assertTrue(StringUtils.contains(bugRelateCaseDTO.getName(), request.getKeyword()));
Assertions.assertTrue(StringUtils.contains(bugRelateCaseDTO.getRelateCaseName(), request.getKeyword()));
}
@Test

View File

@ -14,7 +14,7 @@ public class XpackBugMockServiceImpl implements XpackBugService {
}
@Override
public void syncPlatformBugs(Project project, BugSyncRequest request) {
public void syncPlatformBugs(Project project, BugSyncRequest request, String currentUser) {
}
}

View File

@ -1,6 +1,8 @@
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('bug-trash-project-tmp', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('bug-trash-project', '100000043', '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES

View File

@ -1,20 +1,20 @@
package io.metersphere.project.service;
import io.metersphere.api.domain.ApiScenario;
import io.metersphere.bug.domain.Bug;
import io.metersphere.functional.domain.CaseReview;
import io.metersphere.load.domain.LoadTest;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.project.dto.MessageTemplateFieldDTO;
import io.metersphere.project.dto.MessageTemplateResultDTO;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.CustomFieldExample;
import io.metersphere.system.domain.Schedule;
import io.metersphere.system.dto.BugNoticeDTO;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.mapper.CustomFieldMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.notice.utils.MessageTemplateUtils;
@ -71,8 +71,8 @@ public class NoticeTemplateService {
//TODO获取报告
}
case NoticeConstants.TaskType.BUG_TASK -> {
Field[] allFields = FieldUtils.getAllFields(Bug.class);
addOptionDto(messageTemplateFieldDTOList, allFields, "bug_");
Field[] allFields = FieldUtils.getAllFields(BugNoticeDTO.class);
addOptionDto(messageTemplateFieldDTOList, allFields, null);
addCustomFiled(messageTemplateFieldDTOList, projectId, TemplateScene.BUG.toString());
//TODO获取报告
}

View File

@ -597,27 +597,23 @@ public class ProjectApplicationService {
}
/**
* 过滤掉非Local平台的项目
* 过滤出能够同步的项目
*
* @param projectIds 项目ID集合
* @return 非Local平台的项目
* @return 同步的项目ID集合
*/
public List<String> filterNoLocalPlatform(List<String> projectIds) {
ProjectApplicationExample example = new ProjectApplicationExample();
example.createCriteria().andProjectIdIn(projectIds).andTypeEqualTo(ProjectApplicationType.BUG.BUG_SYNC.name() + "_PLATFORM_KEY");
List<ProjectApplication> allProjectPlatformKeys = projectApplicationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(allProjectPlatformKeys)) {
for (ProjectApplication projectApplication : allProjectPlatformKeys) {
String pluginName = getPluginName(projectApplication.getTypeValue());
if (StringUtils.equals("Local", pluginName)) {
// 本地平台
projectIds.remove(projectApplication.getProjectId());
}
public List<String> filterNeedSyncProject(List<String> projectIds) {
Iterator<String> iterator = projectIds.iterator();
while (iterator.hasNext()) {
String projectId = iterator.next();
ServiceIntegration serviceIntegration = getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
String platformName = getPlatformName(projectId);
if (serviceIntegration == null || StringUtils.equals("Local", platformName)) {
// 项目未配置第三方平台 或者 项目同步配置为Local
iterator.remove();
}
return projectIds;
} else {
return new ArrayList<>();
}
return projectIds;
}
/**

View File

@ -770,10 +770,10 @@ public class ProjectApplicationControllerTests extends BaseTest {
Assertions.assertFalse(projectApplicationService.isPlatformSyncMethodByIncrement("default-project-for-application-not-exist"));
// 过滤Local平台项目
projectApplicationService.filterNoLocalPlatform(new ArrayList<>(List.of("default-project-for-application")));
projectApplicationService.filterNoLocalPlatform(new ArrayList<>(List.of("default-project-for-application-not-exist")));
projectApplicationService.filterNeedSyncProject(new ArrayList<>(new ArrayList<>(List.of("default-project-for-application"))));
projectApplicationService.filterNeedSyncProject(new ArrayList<>(new ArrayList<>(List.of("default-project-for-application-not-exist"))));
// 移除插件测试
basePluginTestService.deleteJiraPlugin();
projectApplicationService.filterNoLocalPlatform(new ArrayList<>(List.of("default-project-for-application")));
projectApplicationService.filterNeedSyncProject(new ArrayList<>(new ArrayList<>(List.of("default-project-for-application"))));
}
}

View File

@ -0,0 +1,47 @@
package io.metersphere.system.dto;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BugNoticeDTO {
@Schema(description ="message.domain.bug_title")
private String title;
@Schema(description ="message.domain.bug_handleUser")
private String handleUser;
@Schema(description ="message.domain.bug_status")
private String status;
@Schema(description = "message.domain.bug_createUser")
private String createUser;
@Schema(description = "message.domain.bug_updateUser")
private String updateUser;
@Schema(description = "message.domain.bug_deleteUser")
private String deleteUser;
@Schema(description = "message.domain.bug_createTime")
private Long createTime;
@Schema(description = "message.domain.bug_updateTime")
private Long updateTime;
@Schema(description = "message.domain.bug_deleteTime")
private Long deleteTime;
@Schema(description = "自定义字段内容")
private List<OptionDTO> customFields;
}

View File

@ -0,0 +1,47 @@
package io.metersphere.system.dto;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BugSyncNoticeDTO {
@Schema(description ="message.domain.bug_title")
private String title;
@Schema(description ="message.domain.bug_handleUser")
private String handleUser;
@Schema(description ="message.domain.bug_status")
private String status;
@Schema(description = "message.domain.bug_createUser")
private String createUser;
@Schema(description = "message.domain.bug_updateUser")
private String updateUser;
@Schema(description = "message.domain.bug_deleteUser")
private String deleteUser;
@Schema(description = "message.domain.bug_createTime")
private Long createTime;
@Schema(description = "message.domain.bug_updateTime")
private Long updateTime;
@Schema(description = "message.domain.bug_deleteTime")
private Long deleteTime;
@Schema(description = "自定义字段内容")
private List<OptionDTO> customFields;
}

View File

@ -194,6 +194,9 @@ public interface NoticeConstants {
@Schema(description = "message.close")
String CLOSE = "CLOSE";
@Schema(description = "message.close")
String SYNC_COMPLETED = "SYNC_COMPLETED";
}
interface RelatedUser {

View File

@ -1,14 +1,14 @@
package io.metersphere.system.notice.utils;
import io.metersphere.api.domain.ApiScenario;
import io.metersphere.bug.domain.Bug;
import io.metersphere.functional.domain.CaseReview;
import io.metersphere.load.domain.LoadTest;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.Schedule;
import io.metersphere.system.dto.BugNoticeDTO;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.ui.domain.UiScenario;
import io.swagger.v3.oas.annotations.media.Schema;
@ -153,7 +153,7 @@ public class MessageTemplateUtils {
allFields = FieldUtils.getAllFields(FunctionalCaseMessageDTO.class);
}
case NoticeConstants.TaskType.BUG_TASK -> {
allFields = FieldUtils.getAllFields(Bug.class);
allFields = FieldUtils.getAllFields(BugNoticeDTO.class);
}
case NoticeConstants.TaskType.UI_SCENARIO_TASK -> {
allFields = FieldUtils.getAllFields(UiScenario.class);