diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java index c5c926a817..2db47c78f3 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java @@ -1,6 +1,5 @@ package io.metersphere.api.service.definition; -import io.metersphere.sdk.constants.ApiFileResourceType; import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.ApiFile; @@ -24,6 +23,7 @@ import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.project.dto.environment.EnvironmentInfoDTO; import io.metersphere.project.service.EnvironmentService; import io.metersphere.project.service.ProjectService; +import io.metersphere.sdk.constants.ApiFileResourceType; import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.domain.Environment; @@ -362,7 +362,7 @@ public class ApiDefinitionMockService { List environments = environmentMapper.selectByExample(environmentExample); if (CollectionUtils.isNotEmpty(environments)) { EnvironmentInfoDTO environmentInfoDTO = environmentService.get(environments.getFirst().getId()); - return StringUtils.join(environmentInfoDTO.getConfig().getHttpConfig().getFirst().getUrl(), "/", apiDefinition.getNum(), apiDefinition.getPath()); + return StringUtils.join(environmentInfoDTO.getConfig().getHttpConfig().getFirst().getUrl(), "/", apiDefinition.getNum(), "/", apiDefinitionMock.getExpectNum(), apiDefinition.getPath()); } return null; diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java index 1e77a12398..7df07818f2 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/dto/request/BugEditRequest.java @@ -58,6 +58,12 @@ public class BugEditRequest implements Serializable { @Schema(description = "用例ID") private String caseId; + @Schema(description = "测试计划ID") + private String testPlanId; + + @Schema(description = "测试计划管理的用例ID") + private String testPlanCaseId; + @Schema(description = "复制的附件") private List copyFiles; diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java index 75b79642d4..d7cd579661 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java @@ -183,11 +183,11 @@ public class BugService { /** * 创建或编辑缺陷 * - * @param request 缺陷请求参数 - * @param files 附件集合 - * @param currentUser 当前用户 + * @param request 缺陷请求参数 + * @param files 附件集合 + * @param currentUser 当前用户 * @param currentOrgId 当前组织ID - * @param isUpdate 是否更新 + * @param isUpdate 是否更新 * @return 缺陷 */ public Bug addOrUpdate(BugEditRequest request, List files, String currentUser, String currentOrgId, boolean isUpdate) { @@ -205,25 +205,25 @@ public class BugService { String platformName = projectApplicationService.getPlatformName(request.getProjectId()); PlatformBugUpdateDTO platformBug = null; if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) { - // 平台缺陷, 需同步新增 - ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(request.getProjectId(), 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())); - PlatformBugUpdateRequest platformRequest = buildPlatformBugRequest(request); - platformRequest.setUserPlatformConfig(JSON.toJSONString(userPlatformAccountService.getPluginUserPlatformConfig(serviceIntegration.getPluginId(), currentOrgId, currentUser))); - platformRequest.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(request.getProjectId())); - if (isUpdate) { - Bug bug = bugMapper.selectByPrimaryKey(request.getId()); - platformRequest.setPlatformBugId(bug.getPlatformBugId()); - platformBug = platform.updateBug(platformRequest); - } else { - platformBug = platform.addBug(platformRequest); - } + // 平台缺陷, 需同步新增 + ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(request.getProjectId(), 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())); + PlatformBugUpdateRequest platformRequest = buildPlatformBugRequest(request); + platformRequest.setUserPlatformConfig(JSON.toJSONString(userPlatformAccountService.getPluginUserPlatformConfig(serviceIntegration.getPluginId(), currentOrgId, currentUser))); + platformRequest.setProjectConfig(projectApplicationService.getProjectBugThirdPartConfig(request.getProjectId())); + if (isUpdate) { + Bug bug = bugMapper.selectByPrimaryKey(request.getId()); + platformRequest.setPlatformBugId(bug.getPlatformBugId()); + platformBug = platform.updateBug(platformRequest); + } else { + platformBug = platform.addBug(platformRequest); + } } // 处理基础字段 Bug bug = handleAndSaveBug(request, currentUser, platformName, platformBug); @@ -241,6 +241,7 @@ public class BugService { /** * 获取缺陷详情 + * * @param id 缺陷ID * @return 缺陷详情 */ @@ -335,6 +336,7 @@ public class BugService { /** * 恢复缺陷 + * * @param id 缺陷ID */ public void recover(String id) { @@ -350,6 +352,7 @@ public class BugService { /** * 彻底删除缺陷 + * * @param id 缺陷ID */ public void deleteTrash(String id) { @@ -363,8 +366,8 @@ public class BugService { /** * 获取缺陷模板详情 * - * @param templateId 模板ID - * @param projectId 项目ID + * @param templateId 模板ID + * @param projectId 项目ID * @param platformBugKey 平台缺陷key * @return 模板详情 */ @@ -389,6 +392,7 @@ public class BugService { /** * 批量删除缺陷 + * * @param request 请求参数 */ public void batchDelete(BugBatchRequest request, String currentUser) { @@ -401,6 +405,7 @@ public class BugService { /** * 批量恢复缺陷 + * * @param request 请求参数 */ public void batchRecover(BugBatchRequest request, String currentUser) { @@ -413,6 +418,7 @@ public class BugService { /** * 批量彻底删除缺陷 + * * @param request 请求参数 */ public void batchDeleteTrash(BugBatchRequest request) { @@ -422,7 +428,8 @@ public class BugService { /** * 批量编辑缺陷 - * @param request 请求参数 + * + * @param request 请求参数 * @param currentUser 当前用户 */ public void batchUpdate(BugBatchUpdateRequest request, String currentUser) { @@ -454,7 +461,7 @@ public class BugService { }); sqlSession.flushStatements(); SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); - } else { + } else { // 标签(覆盖) request.setUpdateUser(currentUser); request.setUpdateTime(System.currentTimeMillis()); @@ -464,6 +471,7 @@ public class BugService { /** * 拖拽缺陷位置 + * * @param request 请求参数 */ public void editPos(PosRequest request) { @@ -477,7 +485,8 @@ public class BugService { /** * 关注缺陷 - * @param id 缺陷ID + * + * @param id 缺陷ID * @param currentUser 当前用户 */ public void follow(String id, String currentUser) { @@ -490,7 +499,8 @@ public class BugService { /** * 取消关注缺陷 - * @param id 缺陷ID + * + * @param id 缺陷ID * @param currentUser 当前用户 */ public void unfollow(String id, String currentUser) { @@ -502,6 +512,7 @@ public class BugService { /** * 获取表头列选项 + * * @param projectId 项目ID * @return 表头列选项 */ @@ -515,8 +526,9 @@ public class BugService { /** * 同步平台缺陷(全量) - * @param request 同步请求参数 - * @param project 项目 + * + * @param request 同步请求参数 + * @param project 项目 * @param currentUser 当前用户 */ @Async @@ -540,8 +552,9 @@ public class BugService { /** * 同步平台缺陷(存量) + * * @param remainBugs 存量缺陷 - * @param project 项目 + * @param project 项目 */ @Async public void syncPlatformBugs(List remainBugs, Project project, String currentUser, String language, String triggerMode) { @@ -565,7 +578,8 @@ public class BugService { /** * 执行同步全量缺陷(xpack调用) - * @param project 项目 + * + * @param project 项目 * @param syncRequest 同步请求参数 */ @SuppressWarnings("unused") @@ -579,6 +593,7 @@ public class BugService { /** * 同步平台缺陷处理 + * * @param subBugs 同步的分页缺陷 * @param project 项目 */ @@ -669,9 +684,10 @@ public class BugService { /** * 注入平台模板缺陷字段 - * @param templateDTO 模板 - * @param projectId 项目ID - * @param fromStatusId 起始状态ID + * + * @param templateDTO 模板 + * @param projectId 项目ID + * @param fromStatusId 起始状态ID * @param platformBugKey 平台缺陷key * @return 模板 */ @@ -737,13 +753,14 @@ public class BugService { /** * 注入模板状态字段 - * @param templateDTO 模板 - * @param projectId 项目ID - * @param fromStatusId 起始状态ID + * + * @param templateDTO 模板 + * @param projectId 项目ID + * @param fromStatusId 起始状态ID * @param platformBugKey 平台缺陷key * @return 模板 */ - public TemplateDTO attachTemplateStatusField(TemplateDTO templateDTO , String projectId, String fromStatusId, String platformBugKey) { + public TemplateDTO attachTemplateStatusField(TemplateDTO templateDTO, String projectId, String fromStatusId, String platformBugKey) { if (templateDTO == null) { return null; } @@ -771,8 +788,8 @@ public class BugService { /** * 处理保存缺陷基础信息 * - * @param request 请求参数 - * @param currentUser 当前用户ID + * @param request 请求参数 + * @param currentUser 当前用户ID * @param platformName 第三方平台名称 */ private Bug handleAndSaveBug(BugEditRequest request, String currentUser, String platformName, PlatformBugUpdateDTO platformBug) { @@ -859,6 +876,7 @@ public class BugService { /** * 校验缺陷是否存在 + * * @param id 缺陷ID * @return 缺陷 */ @@ -874,6 +892,7 @@ public class BugService { /** * 校验缺陷是否存在并返回 + * * @param id 缺陷ID * @return 缺陷 */ @@ -951,8 +970,9 @@ public class BugService { /** * 处理保存附件信息 + * * @param request 请求参数 - * @param files 上传附件集合 + * @param files 上传附件集合 */ private void handleAndSaveAttachments(BugEditRequest request, List files, String currentUser, String platformName, PlatformBugUpdateDTO platformBug) { /* @@ -975,9 +995,10 @@ public class BugService { /** * 移除缺陷附件 - * @param request 请求参数 - * @param platformBug 平台缺陷 - * @param currentUser 当前用户 + * + * @param request 请求参数 + * @param platformBug 平台缺陷 + * @param currentUser 当前用户 * @param platformName 平台名称 * @return 同步删除附件集合 */ @@ -1030,10 +1051,11 @@ public class BugService { /** * 上传缺陷附件 - * @param request 请求参数 - * @param files 上传的文件集合 - * @param platformBug 平台缺陷 - * @param currentUser 当前用户 + * + * @param request 请求参数 + * @param files 上传的文件集合 + * @param platformBug 平台缺陷 + * @param currentUser 当前用户 * @param platformName 平台名称 * @return 同步删除附件集合 */ @@ -1091,7 +1113,7 @@ public class BugService { fileService.upload(file, fileRequest); // 同步新上传的附件至平台 if (!StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) { - File uploadTmpFile = new File(FilenameUtils.normalize(LocalRepositoryDir.getBugTmpDir() + File.separator + file.getOriginalFilename())); + File uploadTmpFile = new File(FilenameUtils.normalize(LocalRepositoryDir.getBugTmpDir() + File.separator + file.getOriginalFilename())); FileUtils.writeByteArrayToFile(uploadTmpFile, file.getBytes()); uploadPlatformAttachments.add(new SyncAttachmentToPlatformRequest(platformBug.getPlatformBugKey(), uploadTmpFile, SyncAttachmentType.UPLOAD.syncOperateType())); } @@ -1131,8 +1153,9 @@ public class BugService { /** * 处理富文本临时文件 - * @param request 请求参数 - * @param bugId 缺陷ID + * + * @param request 请求参数 + * @param bugId 缺陷ID * @param currentUser 当前用户 */ private void handleRichTextTmpFile(BugEditRequest request, String bugId, String currentUser) { @@ -1141,9 +1164,10 @@ public class BugService { /** * 处理并保存缺陷用例关联关系 - * @param request 请求参数 - * @param isUpdate 是否更新 - * @param bug 缺陷 + * + * @param request 请求参数 + * @param isUpdate 是否更新 + * @param bug 缺陷 * @param currentUser 当前用户 */ private void handleAndSaveCaseRelation(BugEditRequest request, boolean isUpdate, Bug bug, String currentUser) { @@ -1157,55 +1181,59 @@ public class BugService { bugRelationCase.setCreateUser(currentUser); bugRelationCase.setCreateTime(System.currentTimeMillis()); bugRelationCase.setUpdateTime(System.currentTimeMillis()); + bugRelationCase.setTestPlanId(request.getTestPlanId()); + bugRelationCase.setTestPlanCaseId(request.getTestPlanCaseId()); bugRelationCaseMapper.insertSelective(bugRelationCase); } } - /** - * 封装缺陷平台请求参数 - * @param request 缺陷请求参数 - */ - private PlatformBugUpdateRequest buildPlatformBugRequest(BugEditRequest request) { - PlatformBugUpdateRequest platformRequest = new PlatformBugUpdateRequest(); - /* - * 处理平台自定义字段 - * 参数中模板非平台默认模板, 则为系统自定义模板, 只需处理配置API映射的字段 - */ - TemplateDTO pluginDefaultTemplate = getPluginBugDefaultTemplate(request.getProjectId(), false); - // 参数模板为插件默认模板, 处理所有自定义字段, 无需过滤API映射 - boolean noApiFilter = pluginDefaultTemplate != null && StringUtils.equals(pluginDefaultTemplate.getId(), request.getTemplateId()); - platformRequest.setCustomFieldList(transferCustomToPlatformField(request.getTemplateId(), request.getCustomFields(), noApiFilter)); - // TITLE, DESCRIPTION 传到平台插件处理 - platformRequest.setTitle(request.getTitle()); - platformRequest.setDescription(request.getDescription()); - if (CollectionUtils.isNotEmpty(request.getRichTextTmpFileIds())) { - request.getRichTextTmpFileIds().forEach(tmpFileId -> { - // 目前只支持富文本图片临时文件的下载, 并同步至第三方平台 (后续支持富文本其他类型文件) - FileRequest downloadRequest = buildTmpImageFileRequest(tmpFileId); - try { - byte[] tmpBytes = fileService.download(downloadRequest); - File uploadTmpFile = new File(FilenameUtils.normalize(LocalRepositoryDir.getBugTmpDir() + File.separator + tmpFileId + File.separator + downloadRequest.getFileName())); - FileUtils.writeByteArrayToFile(uploadTmpFile, tmpBytes); - platformRequest.getRichFileMap().put(tmpFileId, uploadTmpFile); - } catch (Exception e) { - LogUtils.info("缺陷富文本临时图片文件下载失败, 文件ID: " + tmpFileId); - } - }); - } - platformRequest.setBaseUrl(systemParameterService.getBaseInfo().getUrl()); - return platformRequest; - } + /** + * 封装缺陷平台请求参数 + * + * @param request 缺陷请求参数 + */ + private PlatformBugUpdateRequest buildPlatformBugRequest(BugEditRequest request) { + PlatformBugUpdateRequest platformRequest = new PlatformBugUpdateRequest(); + /* + * 处理平台自定义字段 + * 参数中模板非平台默认模板, 则为系统自定义模板, 只需处理配置API映射的字段 + */ + TemplateDTO pluginDefaultTemplate = getPluginBugDefaultTemplate(request.getProjectId(), false); + // 参数模板为插件默认模板, 处理所有自定义字段, 无需过滤API映射 + boolean noApiFilter = pluginDefaultTemplate != null && StringUtils.equals(pluginDefaultTemplate.getId(), request.getTemplateId()); + platformRequest.setCustomFieldList(transferCustomToPlatformField(request.getTemplateId(), request.getCustomFields(), noApiFilter)); + // TITLE, DESCRIPTION 传到平台插件处理 + platformRequest.setTitle(request.getTitle()); + platformRequest.setDescription(request.getDescription()); + if (CollectionUtils.isNotEmpty(request.getRichTextTmpFileIds())) { + request.getRichTextTmpFileIds().forEach(tmpFileId -> { + // 目前只支持富文本图片临时文件的下载, 并同步至第三方平台 (后续支持富文本其他类型文件) + FileRequest downloadRequest = buildTmpImageFileRequest(tmpFileId); + try { + byte[] tmpBytes = fileService.download(downloadRequest); + File uploadTmpFile = new File(FilenameUtils.normalize(LocalRepositoryDir.getBugTmpDir() + File.separator + tmpFileId + File.separator + downloadRequest.getFileName())); + FileUtils.writeByteArrayToFile(uploadTmpFile, tmpBytes); + platformRequest.getRichFileMap().put(tmpFileId, uploadTmpFile); + } catch (Exception e) { + LogUtils.info("缺陷富文本临时图片文件下载失败, 文件ID: " + tmpFileId); + } + }); + } + platformRequest.setBaseUrl(systemParameterService.getBaseInfo().getUrl()); + return platformRequest; + } /** * 是否插件默认模板 + * * @param templateId 模板ID - * @param projectId 项目ID + * @param projectId 项目ID * @return 是否插件默认模板 */ - private boolean isPluginDefaultTemplate(String templateId, String projectId) { - Template pluginTemplate = projectTemplateService.getPluginBugTemplate(projectId); - return pluginTemplate != null && StringUtils.equals(pluginTemplate.getId(), templateId); - } + private boolean isPluginDefaultTemplate(String templateId, String projectId) { + Template pluginTemplate = projectTemplateService.getPluginBugTemplate(projectId); + return pluginTemplate != null && StringUtils.equals(pluginTemplate.getId(), templateId); + } /** * 封装缺陷其他字段 @@ -1264,8 +1292,9 @@ public class BugService { /** * 处理同步缺陷中的富文本图片 + * * @param updateBug 同步更新的缺陷 - * @param platform 平台对象 + * @param platform 平台对象 */ private void syncRichTextToMs(PlatformBugDTO updateBug, Platform platform) { if (MapUtils.isNotEmpty(updateBug.getRichTextImageMap())) { @@ -1319,9 +1348,10 @@ public class BugService { /** * 自定义字段转换为平台字段 - * @param templateId 模板ID + * + * @param templateId 模板ID * @param customFields 自定义字段集合 - * @param noApiFilter 是否不过滤API映射的字段 + * @param noApiFilter 是否不过滤API映射的字段 * @return 平台字段集合 */ public List transferCustomToPlatformField(String templateId, List customFields, boolean noApiFilter) { @@ -1358,57 +1388,56 @@ public class BugService { } } - /** - * 获取第三方平台默认模板 - * - * @param projectId 项目ID - * @return 第三方平台默认模板 - */ - private TemplateDTO getPluginBugDefaultTemplate(String projectId, boolean setPluginTemplateField) { - // 在获取插件模板之前, 已经获取过平台集成信息, 这里不再判空 - ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true); - TemplateDTO template = new TemplateDTO(); - Template pluginTemplate = projectTemplateService.getPluginBugTemplate(projectId); - if (pluginTemplate == null) { - return null; - } - BeanUtils.copyBean(template, pluginTemplate); - if (setPluginTemplateField) { - Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(), - new String(serviceIntegration.getConfiguration())); - String projectConfig = projectApplicationService.getProjectBugThirdPartConfig(projectId); - List platformCustomFields = new ArrayList<>(); - try { - platformCustomFields = platform.getDefaultTemplateCustomField(projectConfig); - } catch (Exception e) { - LogUtils.error("获取平台默认模板字段失败: " + e.getMessage()); - } - if (CollectionUtils.isNotEmpty(platformCustomFields)) { - List customFields = platformCustomFields.stream().map(platformCustomField -> { - TemplateCustomFieldDTO customField = new TemplateCustomFieldDTO(); - BeanUtils.copyBean(customField, platformCustomField); - customField.setFieldId(platformCustomField.getId()); - customField.setFieldName(platformCustomField.getName()); - customField.setPlatformOptionJson(platformCustomField.getOptions()); - customField.setPlatformPlaceHolder(platformCustomField.getPlaceHolder()); - customField.setPlatformSystemField(platformCustomField.getSystemField()); - return customField; - }).collect(Collectors.toList()); - template.setCustomFields(customFields); - } - } - // 平台插件中获取的默认模板 - template.setPlatformDefault(true); - return template; - } + /** + * 获取第三方平台默认模板 + * + * @param projectId 项目ID + * @return 第三方平台默认模板 + */ + private TemplateDTO getPluginBugDefaultTemplate(String projectId, boolean setPluginTemplateField) { + // 在获取插件模板之前, 已经获取过平台集成信息, 这里不再判空 + ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true); + TemplateDTO template = new TemplateDTO(); + Template pluginTemplate = projectTemplateService.getPluginBugTemplate(projectId); + if (pluginTemplate == null) { + return null; + } + BeanUtils.copyBean(template, pluginTemplate); + if (setPluginTemplateField) { + Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(), + new String(serviceIntegration.getConfiguration())); + String projectConfig = projectApplicationService.getProjectBugThirdPartConfig(projectId); + List platformCustomFields = new ArrayList<>(); + try { + platformCustomFields = platform.getDefaultTemplateCustomField(projectConfig); + } catch (Exception e) { + LogUtils.error("获取平台默认模板字段失败: " + e.getMessage()); + } + if (CollectionUtils.isNotEmpty(platformCustomFields)) { + List customFields = platformCustomFields.stream().map(platformCustomField -> { + TemplateCustomFieldDTO customField = new TemplateCustomFieldDTO(); + BeanUtils.copyBean(customField, platformCustomField); + customField.setFieldId(platformCustomField.getId()); + customField.setFieldName(platformCustomField.getName()); + customField.setPlatformOptionJson(platformCustomField.getOptions()); + customField.setPlatformPlaceHolder(platformCustomField.getPlaceHolder()); + customField.setPlatformSystemField(platformCustomField.getSystemField()); + return customField; + }).collect(Collectors.toList()); + template.setCustomFields(customFields); + } + } + // 平台插件中获取的默认模板 + template.setPlatformDefault(true); + return template; + } /** - * - * @param operator 操作人 + * @param operator 操作人 * @param projectId 项目ID * @return 文件操作日志记录 */ - private FileLogRecord createFileLogRecord(String operator, String projectId){ + private FileLogRecord createFileLogRecord(String operator, String projectId) { return FileLogRecord.builder() .logModule(OperationLogModule.BUG_MANAGEMENT_INDEX) .operator(operator) @@ -1418,10 +1447,11 @@ public class BugService { /** * 构建缺陷文件请求 - * @param projectId 项目ID + * + * @param projectId 项目ID * @param resourceId 资源ID - * @param fileId 文件ID - * @param fileName 文件名称 + * @param fileId 文件ID + * @param fileName 文件名称 * @return 文件请求对象 */ public FileRequest buildBugFileRequest(String projectId, String resourceId, String fileId, String fileName) { @@ -1434,6 +1464,7 @@ public class BugService { /** * 构建临时图片文件请求 + * * @param fileId 文件ID * @return 文件请求对象 */ @@ -1447,6 +1478,7 @@ public class BugService { /** * 导出缺陷 + * * @param request 导出请求参数 * @return 导出对象 * @throws Exception 异常 @@ -1481,12 +1513,13 @@ public class BugService { String zipName = "MeterSphere_bug_" + URLEncoder.encode(project.getName(), StandardCharsets.UTF_8) + ".zip"; return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + zipName + "\";" + "filename*=utf-8''"+ zipName) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + zipName + "\";" + "filename*=utf-8''" + zipName) .body(bytes); } /** * 获取导出列 + * * @param projectId 项目ID * @return 缺陷导出列 */ @@ -1500,6 +1533,7 @@ public class BugService { /** * 获取批量导出的缺陷集合 + * * @param request 批量操作参数 * @return 缺陷集合 */ @@ -1531,6 +1565,7 @@ public class BugService { /** * 获取批量操作的缺陷ID集合 + * * @param request 批量操作参数 * @return 缺陷集合 */ @@ -1565,6 +1600,7 @@ public class BugService { /** * 获取表头自定义字段 + * * @param projectId 项目ID * @return 自定义字段集合 */ @@ -1598,6 +1634,7 @@ public class BugService { /** * 校验缺陷是否存在并返回 + * * @param bugId 缺陷ID * @return 缺陷 */ @@ -1611,11 +1648,12 @@ public class BugService { /** * 根据批量操作参数获取批量日志 - * @param batchIds 批量操作ID + * + * @param batchIds 批量操作ID * @param operationType 操作类型 - * @param module 操作对象 - * @param path 请求路径 - * @param batchUpdate 是否批量更新 + * @param module 操作对象 + * @param path 请求路径 + * @param batchUpdate 是否批量更新 * @return 日志集合 */ private List getBatchLogByRequest(List batchIds, String operationType, String module, String path, String projectId, boolean batchUpdate, @@ -1644,6 +1682,7 @@ public class BugService { /** * 获取下一个位置 + * * @param projectId 项目ID * @return 位置 */ @@ -1654,6 +1693,7 @@ public class BugService { /** * distinct by key + * * @param function distinct function * @return predicate */ @@ -1664,6 +1704,7 @@ public class BugService { /** * 校验TAG长度 + * * @param tags 标签集合 */ private void checkTagLength(List tags) { @@ -1674,9 +1715,10 @@ public class BugService { /** * 构建缺陷本地附件 - * @param bugId 缺陷ID - * @param fileName 文件名称 - * @param size 文件大小 + * + * @param bugId 缺陷ID + * @param fileName 文件名称 + * @param size 文件大小 * @param currentUser 当前用户 * @return 本地附件 */ @@ -1695,6 +1737,7 @@ public class BugService { /** * 获取当前项目下成员选项 + * * @param projectId 项目ID * @return 选项集合 */ diff --git a/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java b/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java index b8da0bc2fc..2549eba346 100644 --- a/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java +++ b/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java @@ -67,7 +67,7 @@ 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; -@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BugControllerTests extends BaseTest { @@ -244,6 +244,13 @@ public class BugControllerTests extends BaseTest { paramMap = new LinkedMultiValueMap<>(); paramMap.add("request", JSON.toJSONString(request)); this.requestMultipartWithOkAndReturn(BUG_ADD, paramMap); + + request.setCaseId("test-case-1"); + request.setTestPlanId("test-plan-1"); + request.setTestPlanCaseId("test-plan-case-1"); + paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + this.requestMultipartWithOkAndReturn(BUG_ADD, paramMap); } @Test @@ -529,7 +536,7 @@ public class BugControllerTests extends BaseTest { @Test @Order(95) - void coverPlatformTemplateTests() throws Exception{ + void coverPlatformTemplateTests() throws Exception { // 覆盖同步缺陷(Local) this.requestGetWithOk(BUG_SYNC + "/default-project-for-not-integration"); @@ -667,7 +674,8 @@ public class BugControllerTests extends BaseTest { syncAllBugRequest.setPre(true); syncAllBugRequest.setCreateTime(1702021500000L); // 同步后置方法处理为空, 覆盖主工程代码即可 - syncAllBugRequest.setSyncPostProcessFunc((param) -> {}); + syncAllBugRequest.setSyncPostProcessFunc((param) -> { + }); bugService.execSyncAll(project, syncAllBugRequest); } @@ -734,6 +742,7 @@ public class BugControllerTests extends BaseTest { /** * 生成请求过滤参数 + * * @return filter param */ private Map> buildRequestFilter() { @@ -744,6 +753,7 @@ public class BugControllerTests extends BaseTest { /** * 生成高级搜索参数 + * * @return combine param */ private Map buildRequestCombine() { @@ -761,6 +771,7 @@ public class BugControllerTests extends BaseTest { /** * 生成请求参数 + * * @param isUpdate 是否更新操作 * @return 请求参数 */ @@ -797,6 +808,7 @@ public class BugControllerTests extends BaseTest { /** * 生成添加Jira缺陷的请求参数 + * * @param isUpdate 是否更新 * @return 缺陷编辑请求参数 */ @@ -860,6 +872,7 @@ public class BugControllerTests extends BaseTest { /** * 添加Jira插件,供测试使用 + * * @throws Exception 异常 */ public void addJiraPlugin() throws Exception { @@ -876,6 +889,7 @@ public class BugControllerTests extends BaseTest { /** * 获取添加的Jira缺陷 + * * @return 缺陷 */ private Bug getAddJiraBug() { @@ -886,6 +900,7 @@ public class BugControllerTests extends BaseTest { /** * 获取创建Jira缺陷时的本地文件 + * * @return 本地附件 */ private BugLocalAttachment getAddJiraLocalFile() { @@ -896,6 +911,7 @@ public class BugControllerTests extends BaseTest { /** * 获取创建Jira缺陷时的关联文件 + * * @return 关联文件 */ private FileAssociation getAddJiraAssociateFile() { @@ -906,6 +922,7 @@ public class BugControllerTests extends BaseTest { /** * 获取File上传 + * * @return multipartFile */ private MockMultipartFile getMockFile() { @@ -969,7 +986,7 @@ public class BugControllerTests extends BaseTest { * 获取默认的 MultiValue 参数 * * @param param 参数 - * @param file 文件 + * @param file 文件 * @return 文件参数 */ protected MultiValueMap getMultiPartParam(Object param, File file) { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java index 49e089d222..75c2734632 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java @@ -2,19 +2,24 @@ package io.metersphere.plan.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.dto.BugProviderDTO; import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; import io.metersphere.plan.dto.request.ResourceSortRequest; +import io.metersphere.plan.dto.request.TestPlanCaseAssociateBugRequest; import io.metersphere.plan.dto.request.TestPlanCaseRequest; import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanCasePageResponse; import io.metersphere.plan.dto.response.TestPlanResourceSortResponse; import io.metersphere.plan.service.TestPlanFunctionalCaseService; import io.metersphere.plan.service.TestPlanManagementService; +import io.metersphere.request.BugPageProviderRequest; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.system.dto.LogInsertModule; 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; @@ -76,6 +81,7 @@ public class TestPlanFunctionalCaseController { public Map moduleCount(@Validated @RequestBody TestPlanCaseRequest request) { return testPlanFunctionalCaseService.moduleCount(request); } + @PostMapping("/batch/disassociate") @Operation(summary = "测试计划-计划详情-列表-批量取消关联用例") @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_ASSOCIATION) @@ -85,4 +91,26 @@ public class TestPlanFunctionalCaseController { return testPlanFunctionalCaseService.disassociate(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/functional/case/association", HttpMethodConstants.POST.name())); } + @PostMapping("/associate/bug/page") + @Operation(summary = "测试计划-计划详情-功能用例-获取缺陷列表") + @CheckOwner(resourceId = "#request.getProjectId", resourceType = "project") + public Pager> associateBugList(@Validated @RequestBody BugPageProviderRequest request) { + Page page = PageHelper.startPage(request.getCurrent(), request.getPageSize()); + return PageUtils.setPageInfo(page, testPlanFunctionalCaseService.bugPage(request)); + } + + @PostMapping("/associate/bug") + @Operation(summary = "测试计划-计划详情-功能用例-关联其他用例-关联缺陷") + @CheckOwner(resourceId = "#request.getTestPlanCaseId()", resourceType = "test_plan_functional_case") + public void associateBug(@Validated @RequestBody TestPlanCaseAssociateBugRequest request) { + testPlanFunctionalCaseService.associateBug(request, SessionUtils.getUserId()); + } + + @GetMapping("/disassociate/bug/{id}") + @Operation(summary = "用例管理-功能用例-关联其他用例-取消关联缺陷") + @Log(type = OperationLogType.DISASSOCIATE, expression = "#msClass.disassociateBugLog(#id)", msClass = TestPlanFunctionalCaseService.class) + @CheckOwner(resourceId = "#id", resourceType = "bug_relation_case") + public void disassociateBug(@PathVariable String id) { + testPlanFunctionalCaseService.disassociateBug(id); + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseAssociateBugRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseAssociateBugRequest.java new file mode 100644 index 0000000000..183124e748 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseAssociateBugRequest.java @@ -0,0 +1,19 @@ +package io.metersphere.plan.dto.request; + +import io.metersphere.request.AssociateBugRequest; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class TestPlanCaseAssociateBugRequest extends AssociateBugRequest { + @Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{functional_case.id.not_blank}") + private String testPlanId; + + @Schema(description = "测试计划关联用例的id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{functional_case.id.not_blank}") + private String testPlanCaseId; + + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java index da11f11e81..18e5e9c11b 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java @@ -1,7 +1,12 @@ package io.metersphere.plan.service; +import io.metersphere.bug.domain.Bug; +import io.metersphere.bug.domain.BugRelationCase; import io.metersphere.bug.dto.CaseRelateBugDTO; +import io.metersphere.bug.mapper.BugMapper; +import io.metersphere.bug.mapper.BugRelationCaseMapper; import io.metersphere.bug.mapper.ExtBugRelateCaseMapper; +import io.metersphere.dto.BugProviderDTO; import io.metersphere.functional.domain.FunctionalCaseModule; import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; @@ -17,6 +22,7 @@ import io.metersphere.plan.dto.ResourceLogInsertModule; import io.metersphere.plan.dto.TestPlanResourceAssociationParam; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; import io.metersphere.plan.dto.request.ResourceSortRequest; +import io.metersphere.plan.dto.request.TestPlanCaseAssociateBugRequest; import io.metersphere.plan.dto.request.TestPlanCaseRequest; import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanCasePageResponse; @@ -27,13 +33,24 @@ import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper; import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.project.domain.Project; import io.metersphere.project.dto.ModuleCountDTO; +import io.metersphere.provider.BaseAssociateBugProvider; +import io.metersphere.request.BugPageProviderRequest; +import io.metersphere.sdk.constants.CaseType; +import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.TestPlanResourceConstants; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.SubListUtils; import io.metersphere.sdk.util.Translator; import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.log.aspect.OperationLogAspect; +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.service.UserLoginService; +import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; @@ -74,6 +91,12 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { private ExtTestPlanModuleMapper extTestPlanModuleMapper; @Resource private FunctionalCaseModuleService functionalCaseModuleService; + @Resource + private BaseAssociateBugProvider baseAssociateBugProvider; + @Resource + private BugRelationCaseMapper bugRelationCaseMapper; + @Resource + private BugMapper bugMapper; private static final String CASE_MODULE_COUNT_ALL = "all"; @Override @@ -266,4 +289,56 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { return request.getSelectIds(); } } + + public List bugPage(BugPageProviderRequest request) { + return baseAssociateBugProvider.getBugList("bug_relation_case", "test_plan_case_id", "bug_id", request); + } + + public void associateBug(TestPlanCaseAssociateBugRequest request, String userId) { + List ids = baseAssociateBugProvider.getSelectBugs(request, false); + if (CollectionUtils.isNotEmpty(ids)) { + SubListUtils.dealForSubList(ids, 100, subList -> { + List list = new ArrayList<>(); + subList.forEach(id -> { + BugRelationCase bugRelationCase = new BugRelationCase(); + bugRelationCase.setId(IDGenerator.nextStr()); + bugRelationCase.setBugId(id); + bugRelationCase.setCaseId(request.getCaseId()); + bugRelationCase.setCaseType(CaseType.FUNCTIONAL_CASE.getKey()); + bugRelationCase.setCreateUser(userId); + bugRelationCase.setCreateTime(System.currentTimeMillis()); + bugRelationCase.setUpdateTime(System.currentTimeMillis()); + bugRelationCase.setTestPlanCaseId(request.getTestPlanCaseId()); + bugRelationCase.setTestPlanId(request.getTestPlanId()); + list.add(bugRelationCase); + }); + bugRelationCaseMapper.batchInsert(list); + }); + + } + } + + public void disassociateBug(String id) { + baseAssociateBugProvider.disassociateBug(id); + } + + public LogDTO disassociateBugLog(String id) { + BugRelationCase bugRelationCase = bugRelationCaseMapper.selectByPrimaryKey(id); + if (bugRelationCase != null) { + Bug bug = bugMapper.selectByPrimaryKey(bugRelationCase.getBugId()); + LogDTO dto = new LogDTO( + null, + null, + bugRelationCase.getBugId(), + null, + OperationLogType.DISASSOCIATE.name(), + OperationLogModule.TEST_PLAN, + bug.getTitle() + "缺陷"); + dto.setPath(OperationLogAspect.getPath()); + dto.setMethod(HttpMethodConstants.GET.name()); + dto.setOriginalValue(JSON.toJSONBytes(bugRelationCase)); + return dto; + } + return null; + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java index 463f3b6096..924a573fec 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java @@ -1,15 +1,23 @@ package io.metersphere.plan.controller; +import io.metersphere.bug.domain.BugRelationCase; +import io.metersphere.bug.domain.BugRelationCaseExample; +import io.metersphere.bug.mapper.BugRelationCaseMapper; +import io.metersphere.dto.BugProviderDTO; import io.metersphere.plan.domain.TestPlanFunctionalCase; import io.metersphere.plan.domain.TestPlanFunctionalCaseExample; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; +import io.metersphere.plan.dto.request.TestPlanCaseAssociateBugRequest; import io.metersphere.plan.dto.request.TestPlanCaseRequest; import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper; +import io.metersphere.provider.BaseAssociateBugProvider; +import io.metersphere.request.BugPageProviderRequest; import io.metersphere.sdk.util.JSON; import io.metersphere.system.base.BaseTest; import io.metersphere.system.controller.handler.ResultHolder; import jakarta.annotation.Resource; import org.junit.jupiter.api.*; +import org.mockito.Mockito; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.jdbc.Sql; @@ -17,6 +25,7 @@ import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MvcResult; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -33,6 +42,11 @@ public class TestPlanCaseControllerTests extends BaseTest { @Resource private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper; + @Resource + BaseAssociateBugProvider baseAssociateBugProvider; + @Resource + BugRelationCaseMapper bugRelationCaseMapper; + @Test @Order(1) @@ -80,7 +94,6 @@ public class TestPlanCaseControllerTests extends BaseTest { } - @Test @Order(4) public void disassociateBatch() throws Exception { @@ -92,13 +105,53 @@ public class TestPlanCaseControllerTests extends BaseTest { TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample(); testPlanFunctionalCaseExample.createCriteria().andTestPlanIdEqualTo("gyq_disassociate_plan_1"); List testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample); - Assertions.assertEquals(1,testPlanFunctionalCases.size()); + Assertions.assertEquals(1, testPlanFunctionalCases.size()); request = new BasePlanCaseBatchRequest(); request.setTestPlanId("gyq_disassociate_plan_1"); request.setSelectAll(false); request.setSelectIds(List.of("gyq_disassociate_case_2")); this.requestPostWithOk(FUNCTIONAL_CASE_DISASSOCIATE_URL, request); testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample); - Assertions.assertEquals(0,testPlanFunctionalCases.size()); + Assertions.assertEquals(0, testPlanFunctionalCases.size()); } + + @Test + @Order(5) + public void getAssociateBugList() throws Exception { + BugPageProviderRequest request = new BugPageProviderRequest(); + request.setSourceId("test_plan_case_id"); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setCurrent(1); + request.setPageSize(10); + BugProviderDTO bugProviderDTO = new BugProviderDTO(); + bugProviderDTO.setName("第二个"); + List operations = new ArrayList<>(); + operations.add(bugProviderDTO); + Mockito.when(baseAssociateBugProvider.getBugList("bug_relation_case", "test_plan_case_id", "bug_id", request)).thenReturn(operations); + this.requestPostWithOkAndReturn("/test-plan/functional/case/associate/bug/page", request); + } + + @Test + @Order(9) + public void testAssociateBugs() throws Exception { + TestPlanCaseAssociateBugRequest request = new TestPlanCaseAssociateBugRequest(); + request.setCaseId("fc_1"); + request.setTestPlanCaseId("relate_case_1"); + request.setTestPlanId("plan_1"); + request.setProjectId(DEFAULT_PROJECT_ID); + List ids = new ArrayList<>(); + ids.add("bug_1"); + Mockito.when(baseAssociateBugProvider.getSelectBugs(request, false)).thenReturn(ids); + this.requestPostWithOkAndReturn("/test-plan/functional/case/associate/bug", request); + } + + @Test + @Order(10) + public void testDisassociateBug() throws Exception { + BugRelationCaseExample bugRelationCaseExample = new BugRelationCaseExample(); + bugRelationCaseExample.createCriteria().andTestPlanCaseIdEqualTo("relate_case_1").andTestPlanIdEqualTo("plan_1"); + List bugRelationCases = bugRelationCaseMapper.selectByExample(bugRelationCaseExample); + this.requestGetWithOk("/test-plan/functional/case/disassociate/bug/" + bugRelationCases.get(0).getId()); + } + }