feat(缺陷管理): 第三方平台同步功能&&批量日志校验检查

This commit is contained in:
song-cc-rock 2023-12-12 19:21:45 +08:00 committed by 刘瑞斌
parent 7490ec669a
commit f9ffd26e67
115 changed files with 2901 additions and 868 deletions

View File

@ -1,15 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class DemandDTO {
protected String id;
protected String name;
protected String platform;
protected List<? extends DemandDTO> children;
}

View File

@ -1,41 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MsBugDTO {
private String id;
private String title;
private String status;
private Long createTime;
private Long updateTime;
private String reporter;
private String lastmodify;
private String platform;
private String projectId;
private String creator;
private String resourceId;
private Integer num;
private String platformStatus;
private String platformId;
private String description;
private String customFields;
}

View File

@ -1,14 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class PlatformBugDTO extends MsBugDTO {
private List<PlatformCustomFieldItemDTO> customFieldList;
private List<PlatformAttachment> attachments = new ArrayList<>();
}

View File

@ -1,11 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class PlatformStatusDTO {
protected String value;
protected String label;
}

View File

@ -1,17 +1,15 @@
package io.metersphere.plugin.platform.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
@Getter
@Setter
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class SelectOption {
public SelectOption(String text, String value) {
this.text = text;
this.value = value;
}
private String text;
private String value;

View File

@ -1,31 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@Setter
@Getter
public class SyncAllBugRequest extends SyncBugRequest {
/**
* 项目设置的配置项
*/
private String projectConfig;
/**
* 缺陷模板所关联的自定义字段
*/
private String defaultCustomFields;
/**
* 需要同步的缺陷列表
*/
private List<PlatformBugDTO> bugs;
private boolean pre;
private Long createTime;
private Consumer<Map> handleSyncFunc;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.plugin.platform.dto;
import io.metersphere.plugin.platform.dto.reponse.MsSyncBugDTO;
import lombok.Getter;
import lombok.Setter;
@ -11,7 +12,7 @@ import java.util.Map;
@Setter
@Getter
public class SyncAllBugResult extends SyncBugResult {
private List<MsBugDTO> updateBugs = new ArrayList<>();
private List<MsSyncBugDTO> updateBugs = new ArrayList<>();
private Map<String, List<PlatformAttachment>> attachmentMap = new HashMap<>();
/**
* 保存当前查询到的缺陷的平台ID

View File

@ -1,23 +0,0 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class SyncBugRequest {
/**
* 项目设置的配置项
*/
private String projectConfig;
/**
* 缺陷模板所关联的自定义字段
*/
private String defaultCustomFields;
/**
* 需要同步的缺陷列表
*/
private List<PlatformBugDTO> bugs;
}

View File

@ -1,18 +1,30 @@
package io.metersphere.plugin.platform.dto;
import lombok.Getter;
import lombok.Setter;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugDTO;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Setter
@Getter
@Data
public class SyncBugResult {
private List<MsBugDTO> updateBug= new ArrayList<>();
private List<MsBugDTO> addBug = new ArrayList<>();
private Map<String, List<PlatformAttachment>> attachmentMap = new HashMap<>();
/**
* 同步新增的缺陷
*/
private List<PlatformBugDTO> addBug = new ArrayList<>();
/**
* 同步更新的缺陷
*/
private List<PlatformBugDTO> updateBug = new ArrayList<>();
/**
* 同步失败需删除的ID
*/
private List<String> deleteBugIds = new ArrayList<>();
/**
* 需同步的平台附件集合(统一处理) {key: bugId, value: attachmentList}
*/
private Map<String, List<PlatformAttachment>> attachmentMap = new HashMap<>();
}

View File

@ -0,0 +1,15 @@
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
@Data
public class DemandDTO {
/**
* ID
*/
protected String id;
/**
* 名称
*/
protected String name;
}

View File

@ -0,0 +1,97 @@
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
@Data
public class MsSyncBugDTO {
/**
* 缺陷ID
*/
private String id;
/**
* 缺陷业务ID
*/
private Integer num;
/**
* 缺陷标题
*/
private String title;
/**
* 缺陷状态
*/
private String status;
/**
* 缺陷处理人
*/
private String handleUser;
/**
* 缺陷处理人集合
*/
private String handleUsers;
/**
* 创建人
*/
private String createUser;
/**
* 创建时间
*/
private Long createTime;
/**
* 更新人
*/
private String updateUser;
/**
* 更新时间
*/
private Long updateTime;
/**
* 删除人
*/
private String deleteUser;
/**
* 删除时间
*/
private Long deleteTime;
/**
* 是否删除
*/
private Boolean deleted;
/**
* 所属项目
*/
private String projectId;
/**
* 所属平台
*/
private String platform;
/**
* 第三方平台缺陷ID
*/
private String platformBugId;
/**
* 模板ID
*/
private String templateId;
/**
* 缺陷描述
*/
private String description;
}

View File

@ -0,0 +1,23 @@
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
import java.util.List;
@Data
public class PlatformBugDTO extends MsSyncBugDTO {
/**
* 自定义字段集合(双向同步需要)
*/
private List<PlatformCustomFieldItemDTO> customFieldList;
/**
* 是否平台默认模板(默认模板时, 第三方自定义字段默认都需同步)
*/
private Boolean platformDefaultTemplate;
/**
* 缺陷同步所需处理的平台自定义字段ID(同步第三方平台到MS时需要, 非默认模板时使用)
*/
private List<String> needSyncCustomFields;
}

View File

@ -0,0 +1,28 @@
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
@Data
public class PlatformBugUpdateDTO {
/**
* 平台缺陷唯一标识
*/
private String platformBugKey;
/**
* 平台缺陷标题
*/
private String platformTitle;
/**
* 平台缺陷处理人
*/
private String platformHandleUser;
/**
* 平台缺陷状态
*/
private String platformStatus;
/**
* 平台描述
*/
private String platformDescription;
}

View File

@ -1,11 +1,11 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
import java.io.Serializable;
@Data
public class PlatformCustomFieldDTO implements Serializable {
public class PlatformCustomFieldDTO implements Serializable{
private String id;
private String name;

View File

@ -1,4 +1,4 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -11,4 +11,6 @@ public class PlatformCustomFieldItemDTO extends PlatformCustomFieldDTO {
private String customData;
private Boolean required;
private String defaultValue;
private Boolean supportSearch;
private String searchMethod;
}

View File

@ -0,0 +1,9 @@
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Data;
@Data
public class PlatformStatusDTO {
protected String id;
protected String name;
}

View File

@ -1,4 +1,4 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.reponse;
import lombok.Getter;
import lombok.Setter;

View File

@ -0,0 +1,27 @@
package io.metersphere.plugin.platform.dto.request;
import lombok.Data;
@Data
public class DemandPageRequest {
/**
* 项目配置信息
*/
private String projectConfig;
/**
* 需求分页查询关键字
*/
private String query;
/**
* 开始页码
*/
private int startPage;
/**
* 每页条数
*/
private int pageSize;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.plugin.platform.dto.request;
import lombok.Data;
import java.util.List;
@Data
public class DemandRelateQueryRequest {
/**
* 项目配置信息
*/
private String projectConfig;
/**
* 关联的需求ID集合
*/
private List<String> relateDemandIds;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import io.metersphere.plugin.platform.dto.reponse.TestCaseDemandDTO;
import lombok.Getter;
import lombok.Setter;

View File

@ -1,4 +1,4 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import lombok.Getter;
import lombok.Setter;
@ -14,4 +14,8 @@ public class GetOptionRequest {
* 对应插件中获取选项的方法名
*/
private String optionMethod;
/**
* 输入的查询关键字
*/
private String query;
}

View File

@ -1,5 +1,7 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformStatusDTO;
import lombok.Getter;
import lombok.Setter;
@ -10,15 +12,15 @@ import java.util.Set;
public class PlatformBugUpdateRequest extends PlatformBugDTO {
/**
* 用户信息的第三方平台的配置项
* 服务集成配置的用户及平台信息
*/
private String userPlatformUserConfig;
private String userPlatformConfig;
/**
* 项目设置的配置项
* 项目配置信息
*/
private String projectConfig;
/**
* 缺陷关联的附件集合
* 缺陷关联的附件集合
*/
private Set<String> msAttachmentNames;
/**

View File

@ -1,4 +1,4 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import lombok.Getter;
import lombok.Setter;

View File

@ -1,4 +1,4 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import lombok.Getter;
import lombok.Setter;

View File

@ -0,0 +1,24 @@
package io.metersphere.plugin.platform.dto.request;
import lombok.Data;
import java.util.function.Consumer;
@Data
public class SyncAllBugRequest extends SyncBugRequest {
/**
* 创建时间前后
*/
private boolean pre;
/**
* 条件: 缺陷创建时间
*/
private Long createTime;
/**
* 同步后置方法
*/
private Consumer<SyncPostParamRequest> syncPostProcessFunc;
}

View File

@ -1,10 +1,14 @@
package io.metersphere.plugin.platform.dto;
package io.metersphere.plugin.platform.dto.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.File;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SyncAttachmentToPlatformRequest {
/**
* 平台资源Key(需求或缺陷ID)

View File

@ -0,0 +1,19 @@
package io.metersphere.plugin.platform.dto.request;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugDTO;
import lombok.Data;
import java.util.List;
@Data
public class SyncBugRequest {
/**
* 项目设置的配置项
*/
private String projectConfig;
/**
* 需要同步的平台缺陷列表
*/
private List<PlatformBugDTO> bugs;
}

View File

@ -0,0 +1,22 @@
package io.metersphere.plugin.platform.dto.request;
import io.metersphere.plugin.platform.dto.PlatformAttachment;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugDTO;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class SyncPostParamRequest {
/**
* 需要同步的缺陷集合
*/
List<PlatformBugDTO> needSyncBugs;
/**
* 需要处理的缺陷集合
*/
Map<String, List<PlatformAttachment>> attachmentMap;
}

View File

@ -0,0 +1,77 @@
package io.metersphere.plugin.platform.enums;
import lombok.Getter;
@Getter
public enum PlatformCustomFieldType {
/**
* 输入框
*/
INPUT(false, "input"),
/**
* 文本框
*/
TEXTAREA(false, "textarea"),
/**
* 单选下拉框框
*/
SELECT(true, "select"),
/**
* 多选下拉框框
*/
MULTIPLE_SELECT(true, "multipleSelect"),
/**
* 单选框
*/
RADIO(true, "radio"),
/**
* 复选框
*/
CHECKBOX(true, "checkbox"),
/**
* 单选成员
*/
MEMBER(true, "member"),
/**
* 多选成员
*/
MULTIPLE_MEMBER(true, "multipleMember"),
/**
* 日期
*/
DATE(false, "date"),
/**
* 日期时间
*/
DATETIME(false, "datetime"),
/**
* 整型
*/
INT(false, "int"),
/**
* 浮点型
*/
FLOAT(false, "float"),
/**
* 多值输入框标签输入框
*/
MULTIPLE_INPUT(false, "multipleInput"),
/**
* 级联选择
*/
CASCADE_SELECT(true, "cascadingSelect"),
/**
* 富文本
*/
RICH_TEXT(false, "richText");
private final Boolean hasOption;
private final String type;
PlatformCustomFieldType(Boolean hasOption, String type) {
this.hasOption = hasOption;
this.type = type;
}
}

View File

@ -1,10 +1,10 @@
package io.metersphere.plugin.platform.spi;
import io.metersphere.plugin.platform.dto.PlatformRequest;
import io.metersphere.plugin.platform.dto.PluginOptionsRequest;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.sdk.util.PluginUtils;
import io.metersphere.plugin.platform.dto.request.PlatformRequest;
import io.metersphere.plugin.platform.dto.request.PluginOptionsRequest;
import io.metersphere.plugin.sdk.util.MSPluginException;
import io.metersphere.plugin.sdk.util.PluginUtils;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.InvocationTargetException;

View File

@ -8,6 +8,7 @@ public abstract class AbstractPlatformPlugin extends AbstractMsPlugin {
private static final String DEFAULT_ACCOUNT_SCRIPT_ID = "account";
private static final String PROJECT_BUG_SCRIPT_ID = "project_bug";
private static final String PROJECT_DEMAND_SCRIPT_ID = "project_demand";
private static final String PROJECT_BUG_TEMPLATE_INJECT_FIELD = "inject_field";
/**
* 返回插件的描述信息
@ -52,4 +53,8 @@ public abstract class AbstractPlatformPlugin extends AbstractMsPlugin {
public String getProjectDemandScriptId() {
return PROJECT_DEMAND_SCRIPT_ID;
}
public String getProjectBugTemplateInjectField() {
return PROJECT_BUG_TEMPLATE_INJECT_FIELD;
}
}

View File

@ -2,9 +2,9 @@ package io.metersphere.plugin.platform.spi;
import io.metersphere.plugin.platform.utils.EnvProxySelector;
import io.metersphere.plugin.platform.utils.PluginCodingUtils;
import io.metersphere.plugin.sdk.util.PluginUtils;
import io.metersphere.plugin.sdk.util.MSPluginException;
import io.metersphere.plugin.sdk.util.PluginLogUtils;
import io.metersphere.plugin.sdk.util.PluginUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
@ -24,7 +24,7 @@ import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
public abstract class BaseClient {
@ -86,11 +86,11 @@ public abstract class BaseClient {
return response.getBody();
}
protected Object getResultForList(Class clazz, ResponseEntity<String> response) {
return Arrays.asList(PluginUtils.parseArray(getResult(response), clazz).toArray());
protected <T> List<T> getResultForList(Class<T> clazz, ResponseEntity<String> response) {
return PluginUtils.parseArray(getResult(response), clazz);
}
protected Object getResultForObject(Class clazz, ResponseEntity<String> response) {
protected <T> T getResultForObject(Class<T> clazz, ResponseEntity<String> response) {
return PluginUtils.parseObject(getResult(response), clazz);
}

View File

@ -1,14 +1,17 @@
package io.metersphere.plugin.platform.spi;
import io.metersphere.plugin.platform.dto.PlatformBugUpdateRequest;
import io.metersphere.plugin.platform.dto.PlatformCustomFieldItemDTO;
import io.metersphere.plugin.platform.dto.PluginOptionsRequest;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.platform.dto.PlatformStatusDTO;
import io.metersphere.plugin.platform.dto.SyncAttachmentToPlatformRequest;
import io.metersphere.plugin.platform.dto.SyncBugResult;
import io.metersphere.plugin.platform.dto.reponse.DemandDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformBugUpdateDTO;
import io.metersphere.plugin.platform.dto.reponse.PlatformCustomFieldItemDTO;
import io.metersphere.plugin.platform.dto.request.*;
import io.metersphere.plugin.platform.utils.PluginPager;
import org.pf4j.ExtensionPoint;
import java.io.InputStream;
import java.util.List;
import java.util.function.Consumer;
/**
* 平台对接相关业务接口
@ -30,42 +33,72 @@ public interface Platform extends ExtensionPoint {
void validateProjectConfig(String projectConfig);
/**
* 平台是否支持第三方模板
* 平台是否支持第三方默认模板
*
* @return 是否支持第三方模板
* @return True时会在MS平台展示第三方的默认模板
*/
boolean isThirdPartTemplateSupport();
boolean isSupportDefaultTemplate();
/**
* 获取第三方平台缺陷的自定义字段
* 需要 PluginMetaInfo isThirdPartTemplateSupport 返回 true
* 获取第三方平台模板的自定义字段(isSupportDefaultTemplate为true时才会调用)
*
* @param projectConfig 项目配置信息
* @return 平台自定义字段集合
*/
List<PlatformCustomFieldItemDTO> getThirdPartCustomField(String projectConfig);
List<PlatformCustomFieldItemDTO> getDefaultTemplateCustomField(String projectConfig);
/**
* 获取第三方联级下拉options
* @param optionsRequest
* @return
* @param optionsRequest 插件请求参数
* @return 选项集合
*/
List<SelectOption> getPluginOptions(PluginOptionsRequest optionsRequest);
/**
* 获取第三方平台表单下拉选项
* @param optionsRequest 选项请求参数
* @return 选项集合
*/
List<SelectOption> getFormOptions(GetOptionRequest optionsRequest);
/**
* 获取第三方平台缺陷状态选项
* @param projectConfig 项目配置信息
* @param issueKey 缺陷ID
* @return 缺陷平台状态
*/
List<SelectOption> getStatusTransitions(String projectConfig, String issueKey);
/**
* 获取第三方平台需求
* @param request 需求分页查询参数
* @return 需求分页数据
*/
PluginPager<List<DemandDTO>> pageDemand(DemandPageRequest request);
/**
* 获取第三方平台已关联的需求集合
* @param request 查询请求参数
* @return 需求数据集合
*/
List<DemandDTO> getDemands(DemandRelateQueryRequest request);
/**
* 新增平台缺陷
*
* @param request 平台缺陷参数
* @return 平台缺陷ID
* @return 平台缺陷
*/
String addBug(PlatformBugUpdateRequest request);
PlatformBugUpdateDTO addBug(PlatformBugUpdateRequest request);
/**
* 修改平台缺陷
*
* @param request 平台缺陷参数
* @return 平台缺陷
*/
void updateBug(PlatformBugUpdateRequest request);
PlatformBugUpdateDTO updateBug(PlatformBugUpdateRequest request);
/**
* 删除平台缺陷
@ -75,28 +108,37 @@ public interface Platform extends ExtensionPoint {
void deleteBug(String platformBugId);
/**
* 平台是否支持附件同步
* 平台是否支持附件API
*
* @return 是否支持附件同步
*/
boolean isAttachmentUploadSupport();
boolean isSupportAttachment();
/**
* 同步MS附件至第三方平台(isAttachmentUploadSupport返回true时执行同步附件的逻辑)
* 同步MS附件至第三方平台(isSupportAttachment为true时执行同步附件的逻辑)
*
* @param request 同步附件参数
*/
void syncAttachmentToPlatform(SyncAttachmentToPlatformRequest request);
/**
* 同步部分缺陷
* 同步存量缺陷
*
* @param request 同步缺陷参数
* @return 同步缺陷结果
*/
void syncPartIssues();
SyncBugResult syncBugs(SyncBugRequest request);
/**
* 同步全量缺陷
*/
void syncAllIssues();
void syncAllBugs(SyncAllBugRequest request);
List<PlatformStatusDTO> getStatusList();
/**
* 获取附件输入流并做相应处理
* 同步缺陷中同步附件时会调用
* @param fileKey 文件关键字
* @param inputStreamHandler 获取响应的输入流后做对应处理
*/
void getAttachmentContent(String fileKey, Consumer<InputStream> inputStreamHandler);
}

View File

@ -0,0 +1,22 @@
package io.metersphere.plugin.platform.utils;
import lombok.Data;
@Data
public class PluginPager<T> {
private T list;
private long total;
private long pageSize;
private long current;
public PluginPager() {
}
public PluginPager(T list, long total, long pageSize, long current) {
this.list = list;
this.total = total;
this.pageSize = pageSize;
this.current = current;
}
}

View File

@ -15,6 +15,8 @@ bug.project_id.not_blank=项目ID不能为空
bug.project_id.length_range=项目ID长度必须在1-50之间
bug.template_id.not_blank=模板ID不能为空
bug.template_id.length_range=模板ID长度必须在1-50之间
bug.platform_id.not_blank=平台缺陷ID不能为空
bug.platform_id.length_range=平台缺陷ID长度必须在1-50之间
bug.platform.not_blank=缺陷平台不能为空
bug.platform.length_range=缺陷平台长度必须在1-50之间
bug.status.not_blank=平台状态不能为空
@ -96,6 +98,13 @@ bug_comment_not_exist=缺陷评论不存在
bug_relate_case_not_found=未查询到关联的用例
bug_relate_case_type_unknown=关联的用例类型未知, 无法查看
bug_relate_case_permission_error=无权限查看, 请联系管理员
bug_status_can_not_be_empty=缺陷状态不能为空
handle_user_can_not_be_empty=缺陷处理人不能为空
bug.title=缺项名称
bug.handle_user=处理人
bug.status=状态
bug.tag=标签
bug.description=缺陷内容
# bug export
bug.system_columns.not_empty=系统字段不能为空
bug.export.system.columns.name=缺陷名称

View File

@ -15,6 +15,8 @@ bug.project_id.not_blank=projectId cannot be empty
bug.project_id.length_range=projectId length must be between 1-50
bug.template_id.not_blank=templateId cannot be empty
bug.template_id.length_range=templateId length must be between 1-50
bug.platform_id.not_blank=platformId cannot be empty
bug.platform_id.length_range=platformId length must be between 1-50
bug.platform.not_blank=platform cannot be empty
bug.platform.length_range=platform length must be between 1-50
bug.status.not_blank=status cannot be empty
@ -96,6 +98,13 @@ bug_comment_not_exist=Bug comment does not exist
bug_relate_case_not_found=Bug related case not found
bug_relate_case_type_unknown=Bug related case type unknown
bug_relate_case_permission_error=No permission to show the case
bug_status_can_not_be_empty=Status cannot be empty
handle_user_can_not_be_empty=Handle user cannot be empty
bug.title=Title
bug.handle_user=Assignee
bug.status=Status
bug.tag=Tag
bug.description=Description
# bug export
bug.system_columns.not_empty=System columns cannot be empty
bug.export.system.columns.name=Name

View File

@ -15,6 +15,8 @@ bug.project_id.not_blank=项目ID不能为空
bug.project_id.length_range=项目ID长度必须在1-50之间
bug.template_id.not_blank=模板ID不能为空
bug.template_id.length_range=模板ID长度必须在1-50之间
bug.platform_id.not_blank=平台缺陷ID不能为空
bug.platform_id.length_range=平台缺陷ID长度必须在1-50之间
bug.platform.not_blank=缺陷平台不能为空
bug.platform.length_range=缺陷平台长度必须在1-50之间
bug.status.not_blank=平台状态不能为空
@ -96,6 +98,13 @@ bug_comment_not_exist=缺陷评论不存在
bug_relate_case_not_found=未查询到关联的用例
bug_relate_case_type_unknown=关联的用例类型未知, 无法查看
bug_relate_case_permission_error=无用例查看权限, 请联系管理员
bug_status_can_not_be_empty=缺陷状态不能为空
handle_user_can_not_be_empty=缺陷处理人不能为空
bug.title=缺项名称
bug.handle_user=处理人
bug.status=状态
bug.tag=标签
bug.description=缺陷内容
# bug export
bug.system_columns.not_empty=系统字段不能为空
bug.export.system.columns.name=缺陷名称

View File

@ -15,6 +15,8 @@ bug.project_id.not_blank=项目ID不能為空
bug.project_id.length_range=项目ID長度必須在1-50之間
bug.template_id.not_blank=模板ID不能為空
bug.template_id.length_range=模板ID長度必須在1-50之間
bug.platform_id.not_blank=平台缺陷ID不能為空
bug.platform_id.length_range=平台缺陷ID長度必須在1-50之間
bug.platform.not_blank=缺陷平台不能為空
bug.platform.length_range=缺陷平台長度必須在1-50之間
bug.status.not_blank=平台状态不能為空
@ -96,6 +98,14 @@ bug_comment_not_exist=缺陷評論不存在
bug_relate_case_not_found=未查詢到關聯的用例
bug_relate_case_type_unknown=關聯的用例類型未知, 無法查看
bug_relate_case_permission_error=無權限查看, 請聯繫管理員
bug_status_can_not_be_empty=缺陷狀態不能為空
handle_user_can_not_be_empty=缺陷處理人不能為空
bug.title=缺項名稱
bug.handle_user=處理人
bug.status=狀態
bug.tag=標籤
bug.description=缺陷內容
# bug export
bug.system_columns.not_empty=系統字段不能為空
bug.export.system.columns.name=缺陷名稱

View File

@ -459,6 +459,7 @@ set_default_template=设置默认模板
project_template_enable=开启项目模板
# 状态流
status=状态
status_flow.name=状态流
status_item.bug_new=新建
status_item.bug_in_process=处理中
@ -488,4 +489,3 @@ priority_is_null=用例等级不能为空
apikey_has_expired=ApiKey 已过期
user_key.id.not_blank=ApiKey ID不能为空
expire_time_not_null=过期时间不能为空

View File

@ -469,6 +469,7 @@ node.name.repeat=Name repeat
project.cannot.match.parent=Project can not match parent
# 状态流
status=Status
status_flow.name=Status Flow
status_item.bug_new=NEW
status_item.bug_in_process=IN PROCESS

View File

@ -465,6 +465,7 @@ api_test_environment_not_exist=环境不存在
mock_environment_not_delete=Mock环境不允许删除
# 状态流
status=状态
status_flow.name=状态流
status_item.bug_new=新建
status_item.bug_in_process=处理中

View File

@ -465,6 +465,7 @@ api_test_environment_not_exist=環境不存在
mock_environment_not_delete=Mock 環境不允許刪除
# 状态流
status=狀態
status_flow.name=狀態流
status_item.bug_new=新建
status_item.bug_in_process=處理中

View File

@ -0,0 +1,43 @@
package io.metersphere.bug;
import io.metersphere.bug.service.BugSyncService;
import io.metersphere.bug.service.XpackBugService;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.dto.sdk.LicenseDTO;
import io.metersphere.system.sechedule.BaseScheduleJob;
import io.metersphere.system.service.LicenseService;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
/**
* 缺陷同步定时任务
*/
public class BugSyncJob extends BaseScheduleJob {
private final LicenseService licenseService;
private final XpackBugService xpackBugService;
private final BugSyncService bugSyncService;
public BugSyncJob() {
licenseService = CommonBeanFactory.getBean(LicenseService.class);
xpackBugService = CommonBeanFactory.getBean(XpackBugService.class);
bugSyncService = CommonBeanFactory.getBean(BugSyncService.class);
}
@Override
protected void businessExecute(JobExecutionContext context) {
LicenseDTO licenseDTO = licenseService.validate();
if (licenseDTO != null && licenseDTO.getLicense() != null
&& StringUtils.equals(licenseDTO.getStatus(), "valid")) {
LogUtils.info("sync all bug");
xpackBugService.syncPlatformBugsBySchedule();
} else {
LogUtils.info("sync remain bug");
bugSyncService.syncPlatformBugBySchedule();
}
LogUtils.info("sync bug end");
}
}

View File

@ -1,6 +1,6 @@
package io.metersphere.bug.constants;
import io.metersphere.bug.dto.BugCustomFieldDTO;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.sdk.util.Translator;
import lombok.Data;

View File

@ -1,8 +1,8 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.domain.BugComment;
import io.metersphere.bug.dto.BugCommentDTO;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.service.BugCommentService;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.tags.Tag;

View File

@ -3,9 +3,10 @@ package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.constants.BugExportColumns;
import io.metersphere.bug.dto.BugDTO;
import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.service.BugService;
import io.metersphere.bug.service.BugSyncService;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.service.ProjectTemplateService;
import io.metersphere.sdk.constants.PermissionConstants;
@ -39,6 +40,8 @@ public class BugController {
@Resource
private BugService bugService;
@Resource
private BugSyncService bugSyncService;
@Resource
private ProjectTemplateService projectTemplateService;
@PostMapping("/page")
@ -56,7 +59,7 @@ public class BugController {
@RequiresPermissions(PermissionConstants.BUG_ADD)
public void add(@Validated({Created.class}) @RequestPart(value = "request") BugEditRequest request,
@RequestPart(value = "file", required = false) List<MultipartFile> files) {
bugService.add(request, files, SessionUtils.getUserId());
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), false);
}
@PostMapping("/update")
@ -64,7 +67,7 @@ public class BugController {
@RequiresPermissions(PermissionConstants.BUG_UPDATE)
public void update(@Validated({Updated.class}) @RequestPart(value = "request") BugEditRequest request,
@RequestPart(value = "file", required = false) List<MultipartFile> files) {
bugService.update(request, files, SessionUtils.getUserId());
bugService.addOrUpdate(request, files, SessionUtils.getUserId(), true);
}
@GetMapping("/delete/{id}")
@ -81,11 +84,11 @@ public class BugController {
return projectTemplateService.getOption(projectId, TemplateScene.BUG.name());
}
@GetMapping("/template/{id}")
@Operation(summary = "缺陷管理-获取模板内容")
@PostMapping("/template/detail")
@Operation(summary = "缺陷管理-获取模板详情内容")
@RequiresPermissions(PermissionConstants.BUG_READ)
public TemplateDTO getTemplateField(@PathVariable String id, @RequestParam(value = "projectId") String projectId) {
return bugService.getTemplate(id, projectId);
public TemplateDTO getTemplateDetail(@RequestBody BugTemplateRequest request) {
return bugService.getTemplate(request.getId(), request.getProjectId(), request.getFromStatusId(), request.getPlatformBugKey());
}
@PostMapping("/batch-delete")
@ -116,6 +119,20 @@ public class BugController {
bugService.unfollow(id, SessionUtils.getUserId());
}
@GetMapping("/sync/{projectId}")
@Operation(summary = "缺陷管理-同步缺陷(开源)")
@RequiresPermissions(PermissionConstants.BUG_UPDATE)
public void sync(@PathVariable String projectId) {
bugSyncService.syncBugs(projectId);
}
@PostMapping("/sync/all")
@Operation(summary = "缺陷管理-同步缺陷(全量)")
@RequiresPermissions(PermissionConstants.BUG_UPDATE)
public void syncAll(@RequestBody BugSyncRequest request) {
bugSyncService.syncAllBugs(request);
}
@GetMapping("/export/columns/{projectId}")
@Operation(summary = "缺陷管理-获取导出字段配置")
@RequiresPermissions(PermissionConstants.BUG_EXPORT)

View File

@ -2,8 +2,8 @@ package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.BugRelateCaseDTO;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.utils.PageUtils;

View File

@ -1,14 +0,0 @@
package io.metersphere.bug.dto;
import io.metersphere.system.domain.User;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class BugCommentUserInfo extends User {
@Schema(description = "用户头像")
private String createUserAvatar;
}

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto.request;
package io.metersphere.bug.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,7 +1,9 @@
package io.metersphere.bug.dto;
import io.metersphere.bug.domain.BugContent;
import io.metersphere.bug.dto.request.BugExportColumn;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.sdk.util.DateUtils;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;

View File

@ -1,24 +0,0 @@
package io.metersphere.bug.dto;
import lombok.Data;
import java.util.List;
@Data
public class BugHistoryContentDTO extends BugDTO {
/**
* 缺陷自定义字段
*/
private List<BugCustomFieldDTO> customFields;
/**
* 缺陷内容
*/
private String description;
/**
* 附件名称集合
*/
private List<String> attachmentNames;
}

View File

@ -1,10 +0,0 @@
package io.metersphere.bug.dto;
import io.metersphere.system.dto.sdk.OptionDTO;
import lombok.Data;
@Data
public class BugStatusOptionDTO extends OptionDTO {
// 状态流转信息
}

View File

@ -0,0 +1,14 @@
package io.metersphere.bug.dto;
import lombok.Data;
@Data
public class BugTemplateInjectField {
private String id;
private String name;
private String type;
private String defaultValue;
private Boolean required;
private Boolean supportSearch;
private String optionMethod;
}

View File

@ -1,22 +1,14 @@
package io.metersphere.bug.dto.request;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class BugBatchRequest extends BaseCondition {
public class BugBatchRequest extends TableBatchProcessDTO {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "是否全选", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean selectAll;
@Schema(description = "缺陷ID勾选集合")
private List<String> includeBugIds;
}

View File

@ -1,6 +1,5 @@
package io.metersphere.bug.dto.request;
import io.metersphere.bug.dto.BugCustomFieldDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -9,15 +8,9 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = false)
public class BugBatchUpdateRequest extends BugBatchRequest{
@Schema(description = "处理人")
private String handleUser;
@Schema(description = "标签")
private String tag;
@Schema(description = "自定义字段")
private BugCustomFieldDTO customField;
@Schema(description = "是否追加", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean append;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.bug.dto.request;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
@ -9,7 +10,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
import java.util.Map;
/**
* @author song-cc-rock
@ -23,16 +23,10 @@ public class BugEditRequest {
@Size(min = 1, max = 50, message = "{bug.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "缺陷标题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{bug.title.not_blank}", groups = {Created.class, Updated.class})
@Schema(description = "缺陷标题")
@Size(min = 1, max = 300, message = "{bug.title.length_range}", groups = {Created.class, Updated.class})
private String title;
@Schema(description = "处理人", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{bug.assign_user.not_blank}", groups = {Created.class, Updated.class})
@Size(min = 1, max = 50, message = "{bug.assign_user.length_range}", groups = {Created.class, Updated.class})
private String handleUser;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{bug.project_id.not_blank}", groups = {Created.class, Updated.class})
@Size(min = 1, max = 50, message = "{bug.project_id.length_range}", groups = {Created.class, Updated.class})
@ -43,11 +37,6 @@ public class BugEditRequest {
@Size(min = 1, max = 50, message = "{bug.template_id.length_range}", groups = {Created.class, Updated.class})
private String templateId;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{bug.status.not_blank}", groups = {Created.class, Updated.class})
@Size(min = 1, max = 50, message = "{bug.status.length_range}", groups = {Created.class, Updated.class})
private String status;
@Schema(description = "标签")
private String tag;
@ -55,14 +44,14 @@ public class BugEditRequest {
private String description;
@Schema(description = "自定义字段集合")
private Map<String, String> customFieldMap;
private List<BugCustomFieldDTO> customFields;
@Schema(description = "删除的本地附件集合")
@Schema(description = "删除的本地附件集合, 文件ID")
private List<String> deleteLocalFileIds;
@Schema(description = "取消关联附件关系ID集合")
@Schema(description = "取消关联附件关系ID集合, 关联关系ID")
private List<String> unLinkRefIds;
@Schema(description = "关联附件集合")
@Schema(description = "关联附件集合, 文件ID")
private List<String> linkFileIds;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.bug.dto.request;
import io.metersphere.bug.dto.BugExportColumn;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;

View File

@ -0,0 +1,17 @@
package io.metersphere.bug.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class BugSyncRequest {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "创建时间前或后", requiredMode = Schema.RequiredMode.REQUIRED)
private boolean pre;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private Long createTime;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.bug.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class BugTemplateRequest {
@Schema(description = "模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String projectId;
@Schema(description = "缺陷当前状态ID")
private String fromStatusId;
@Schema(description = "缺陷第三方平台Key")
private String platformBugKey;
}

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.metersphere.bug.domain.BugComment;
import io.metersphere.system.dto.CommentUserInfo;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.metersphere.system.domain.CustomField;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.metersphere.bug.domain.Bug;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@ -0,0 +1,34 @@
package io.metersphere.bug.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class BugFileDTO {
@Schema(description = "关系ID")
private String refId;
@Schema(description = "文件ID")
private String fileId;
@Schema(description = "文件名称")
private String fileName;
@Schema(description = "文件类型")
private String fileType;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "创建时间")
private Long createTime;
@Schema(description = "创建人")
private String createUser;
@Schema(description = "是否关联")
private Boolean associated;
}

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.metersphere.functional.domain.FunctionalCase;
import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -1,4 +1,4 @@
package io.metersphere.bug.dto;
package io.metersphere.bug.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@ -0,0 +1,27 @@
package io.metersphere.bug.enums;
import io.metersphere.sdk.util.Translator;
import lombok.Getter;
@Getter
public enum BugTemplateCustomField {
/**
* 处理人
*/
HANDLE_USER("handleUser", Translator.get("bug.handle_user")),
/**
* 状态
*/
STATUS("status", Translator.get("bug.status"));
private final String id;
private final String name;
BugTemplateCustomField(String id, String name) {
this.id = id;
this.name = name;
}
}

View File

@ -1,7 +1,7 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.domain.BugCustomField;
import io.metersphere.bug.dto.BugCustomFieldDTO;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -9,12 +9,20 @@ import java.util.List;
public interface ExtBugCustomFieldMapper {
/**
* 获取缺陷自定义字段值
* 获取缺陷存在的自定义字段值
* @param bugIds 缺陷集合
* @param projectId 项目ID
* @return 缺陷自定义字段值
*/
List<BugCustomFieldDTO> getBugCustomFields(@Param("ids") List<String> bugIds, @Param("projectId") String projectId);
List<BugCustomFieldDTO> getBugExistCustomFields(@Param("ids") List<String> bugIds, @Param("projectId") String projectId);
/**
* 获取缺陷所有自定义字段映射值
* @param bugIds 缺陷集合
* @param projectId 项目ID
* @return 缺陷自定义字段值
*/
List<BugCustomFieldDTO> getBugAllCustomFields(@Param("ids") List<String> bugIds, @Param("projectId") String projectId);
/**
* 批量插入缺陷自定义字段值

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.bug.mapper.ExtBugCustomFieldMapper">
<select id="getBugCustomFields" resultType="io.metersphere.bug.dto.BugCustomFieldDTO">
<select id="getBugExistCustomFields" resultType="io.metersphere.bug.dto.response.BugCustomFieldDTO">
select cf.*, bcf.value, bcf.bug_id from bug_custom_field bcf join custom_field cf on bcf.field_id = cf.id
where cf.scene = 'BUG' and cf.scope_type = 'PROJECT' and scope_id = #{projectId}
and bug_id in
@ -10,6 +10,15 @@
</foreach>
</select>
<select id="getBugAllCustomFields" resultType="io.metersphere.bug.dto.response.BugCustomFieldDTO">
select cf.name, bcf.value, bcf.bug_id, bcf.field_id as id from bug_custom_field bcf left join custom_field cf on bcf.field_id = cf.id
and cf.scene = 'BUG' and cf.scope_type = 'PROJECT' and scope_id = #{projectId}
where bug_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<insert id="batchInsert" parameterType="io.metersphere.bug.domain.BugCustomField">
insert into bug_custom_field (bug_id, field_id, value) values
<foreach collection="list" item="field" separator=",">

View File

@ -1,9 +1,9 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.dto.BugDTO;
import io.metersphere.bug.dto.BugTagEditDTO;
import io.metersphere.bug.dto.request.BugBatchUpdateRequest;
import io.metersphere.bug.dto.request.BugPageRequest;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.dto.response.BugTagEditDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -18,7 +18,22 @@ public interface ExtBugMapper {
*/
List<BugDTO> list(@Param("request") BugPageRequest request);
/**
* 缺陷列表查询
*
* @param request 请求查询参数
* @return 缺陷列表
*/
List<String> getIdsByPageRequest(@Param("request") BugPageRequest request);
/**
* 根据ID列表查询缺陷
*
* @param ids 缺陷ID集合
* @return 缺陷列表
*/
List<BugDTO> listByIds(@Param("ids") List<String> ids);
/**
* 获取缺陷业务ID
*

View File

@ -1,13 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.bug.mapper.ExtBugMapper">
<select id="list" resultType="io.metersphere.bug.dto.BugDTO">
<select id="list" resultType="io.metersphere.bug.dto.response.BugDTO">
select b.id, b.num, b.title, b.handle_user, b.create_user, b.create_time, b.update_time, b.delete_time, b.delete_user,
b.project_id, b.template_id, b.platform, b.status, bc.description from bug b left join bug_content bc on b.id = bc.bug_id
<include refid="queryWhereCondition"/>
</select>
<select id="listByIds" resultType="io.metersphere.bug.dto.BugDTO">
<select id="getIdsByPageRequest" resultType="java.lang.String">
select b.id from bug b left join bug_content bc on b.id = bc.bug_id
<include refid="queryWhereCondition"/>
</select>
<select id="listByIds" resultType="io.metersphere.bug.dto.response.BugDTO">
select b.id,
b.num,
b.title,
@ -33,7 +38,7 @@
select max(num) from bug where project_id = #{projectId}
</select>
<select id="getBugTagList" resultType="io.metersphere.bug.dto.BugTagEditDTO">
<select id="getBugTagList" resultType="io.metersphere.bug.dto.response.BugTagEditDTO">
select id as bugId, tag from bug where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
@ -43,10 +48,6 @@
<update id="batchUpdate" parameterType="io.metersphere.bug.dto.request.BugBatchUpdateRequest">
update bug
<set>
<if test="request.handleUser != null and request.handleUser != ''">
handle_user = #{request.handleUser},
handle_users = handle_users + concat(',', #{request.handleUser}),
</if>
<if test="request.tag != null and request.tag != ''">
tag = #{request.tag},
</if>

View File

@ -1,8 +1,8 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.dto.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.BugRelateCaseDTO;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.bug.mapper.ExtBugRelateCaseMapper">
<select id="countRelationCases" resultType="io.metersphere.bug.dto.BugRelateCaseCountDTO">
<select id="countRelationCases" resultType="io.metersphere.bug.dto.response.BugRelateCaseCountDTO">
select count(id) as relationCaseCount, bug_id as bugId from bug_relation_case brc
where brc.bug_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
@ -10,11 +10,11 @@
group by brc.bug_id
</select>
<select id="list" resultType="io.metersphere.bug.dto.BugRelateCaseDTO">
<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,
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}
where brc.bug_id = #{request.bugId} and fc.deleted = false
<if test="request.keyword != null and request.keyword != ''">
and fc.name like concat('%', #{request.keyword}, '%')
</if>

View File

@ -0,0 +1,84 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugLocalAttachment;
import io.metersphere.bug.domain.BugLocalAttachmentExample;
import io.metersphere.bug.dto.response.BugFileDTO;
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.domain.FileAssociationExample;
import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.domain.FileMetadataExample;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class BugAttachmentService {
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
/**
* 查询缺陷的附件集合
* @param bugId 缺陷ID
* @return 缺陷附件关系集合
*/
public List<BugFileDTO> getAllBugFiles(String bugId) {
List<BugFileDTO> bugFiles = new ArrayList<>();
BugLocalAttachmentExample localAttachmentExample = new BugLocalAttachmentExample();
localAttachmentExample.createCriteria().andBugIdEqualTo(bugId);
List<BugLocalAttachment> bugLocalAttachments = bugLocalAttachmentMapper.selectByExample(localAttachmentExample);
if (!CollectionUtils.isEmpty(bugLocalAttachments)) {
bugLocalAttachments.forEach(localFile -> {
BugFileDTO localFileDTO = BugFileDTO.builder().refId(localFile.getId()).fileId(localFile.getFileId()).fileName(localFile.getFileName()).fileType(getLocalFileType(localFile.getFileName()))
.fileSize(localFile.getSize()).createTime(localFile.getCreateTime()).createUser(localFile.getCreateUser()).associated(false).build();
bugFiles.add(localFileDTO);
});
}
FileAssociationExample associationExample = new FileAssociationExample();
associationExample.createCriteria().andSourceIdEqualTo(bugId).andSourceTypeEqualTo(FileAssociationSourceUtil.SOURCE_TYPE_BUG);
List<FileAssociation> fileAssociations = fileAssociationMapper.selectByExample(associationExample);
if (!CollectionUtils.isEmpty(fileAssociations)) {
List<String> associateFileIds = fileAssociations.stream().map(FileAssociation::getFileId).toList();
FileMetadataExample metadataExample = new FileMetadataExample();
metadataExample.createCriteria().andIdIn(associateFileIds);
List<FileMetadata> fileMetadataList = fileMetadataMapper.selectByExample(metadataExample);
Map<String, FileMetadata> fileMetadataMap = fileMetadataList.stream().collect(Collectors.toMap(FileMetadata::getId, v -> v));
fileAssociations.forEach(associatedFile -> {
FileMetadata associatedFileMetadata = fileMetadataMap.get(associatedFile.getFileId());
BugFileDTO associatedFileDTO = BugFileDTO.builder().refId(associatedFile.getId()).fileId(associatedFile.getFileId()).fileName(associatedFileMetadata.getName() + "." + associatedFileMetadata.getType())
.fileType(associatedFileMetadata.getType()).fileSize(associatedFileMetadata.getSize()).createTime(associatedFileMetadata.getCreateTime())
.createUser(associatedFileMetadata.getCreateUser()).associated(true).build();
bugFiles.add(associatedFileDTO);
});
}
return bugFiles;
}
/**
* 获取本地文件类型
* @param fileName 文件名
* @return 文件类型
*/
private String getLocalFileType(String fileName) {
int i = fileName.lastIndexOf(".");
if (i > 0) {
return fileName.substring(i);
} else {
return StringUtils.EMPTY;
}
}
}

View File

@ -1,9 +1,9 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.dto.BugCommentNoticeDTO;
import io.metersphere.bug.dto.BugCustomFieldDTO;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentNoticeDTO;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.bug.mapper.ExtBugCustomFieldMapper;
import io.metersphere.sdk.util.BeanUtils;
@ -82,7 +82,7 @@ public class BugCommentNoticeService {
* @param bugCommentNoticeDTO 缺陷通知参数
*/
private void setCustomField(BugCommentNoticeDTO bugCommentNoticeDTO) {
List<BugCustomFieldDTO> bugCustomFields = extBugCustomFieldMapper.getBugCustomFields(List.of(bugCommentNoticeDTO.getId()), bugCommentNoticeDTO.getProjectId());
List<BugCustomFieldDTO> bugCustomFields = extBugCustomFieldMapper.getBugExistCustomFields(List.of(bugCommentNoticeDTO.getId()), bugCommentNoticeDTO.getProjectId());
if (CollectionUtils.isNotEmpty(bugCustomFields)) {
List<OptionDTO> fields = bugCustomFields.stream().map(field -> new OptionDTO(field.getName(), field.getValue())).toList();
bugCommentNoticeDTO.setFields(fields);

View File

@ -3,9 +3,9 @@ package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugComment;
import io.metersphere.bug.domain.BugCommentExample;
import io.metersphere.bug.dto.BugCommentDTO;
import io.metersphere.bug.dto.BugCommentNoticeDTO;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.dto.response.BugCommentNoticeDTO;
import io.metersphere.bug.mapper.BugCommentMapper;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.sdk.exception.MSException;

View File

@ -4,10 +4,10 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import io.metersphere.bug.domain.BugContent;
import io.metersphere.bug.domain.BugContentExample;
import io.metersphere.bug.dto.BugCommentDTO;
import io.metersphere.bug.dto.BugDTO;
import io.metersphere.bug.dto.BugExportColumn;
import io.metersphere.bug.dto.BugExportExcelModel;
import io.metersphere.bug.dto.request.BugExportColumn;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.mapper.BugContentMapper;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;

View File

@ -1,29 +0,0 @@
//package io.metersphere.bug.service;
//
//import io.metersphere.bug.mapper.BugAttachmentMapper;
//import io.metersphere.bug.mapper.BugMapper;
//import jakarta.annotation.Resource;
//import org.springframework.stereotype.Service;
//
//@Service
//public class BugHistoryService {
//
// @Resource
// private BugMapper bugMapper;
// @Resource
// private BugAttachmentMapper bugAttachmentMapper;
//
// public void save(String bugId) {
// /*
// * 变更历史内容: BUG信息(基础字段, 自定义字段, 缺陷内容大字段, 附件)
// */
// }
//
// public void compare() {
// // 对比变更历史
// }
//
// public void rollback() {
// // 回退变更历史
// }
//}

View File

@ -1,8 +1,8 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.dto.BugRelateCaseDTO;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.project.domain.Project;

View File

@ -1,66 +1,55 @@
//package io.metersphere.bug.service;
//
//import io.metersphere.bug.dto.BugStatusOptionDTO;
//import io.metersphere.bug.enums.BugPlatform;
//import io.metersphere.plugin.platform.spi.Platform;
//import io.metersphere.project.service.ProjectApplicationService;
//import io.metersphere.project.service.ProjectTemplateService;
//import io.metersphere.sdk.exception.MSException;
//import io.metersphere.sdk.util.Translator;
//import io.metersphere.system.domain.ServiceIntegration;
//import io.metersphere.system.service.PlatformPluginService;
//import jakarta.annotation.Resource;
//import org.apache.commons.lang3.StringUtils;
//import org.springframework.stereotype.Service;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.List;
//
//@Service
//@Transactional(rollbackFor = Exception.class)
//public class BugStatusService {
//
// @Resource
// private ProjectApplicationService projectApplicationService;
// @Resource
// private ProjectTemplateService projectTemplateService;
// @Resource
// private PlatformPluginService platformPluginService;
//
// /**
// * 获取状态下拉选项
// * @param projectId 项目ID
// * @return 选项集合
// */
// public List<BugStatusOptionDTO> getProjectStatusOption(String projectId) {
// // TODO: 获取状态下拉选项 {Local平台: 直接取状态流中的选项, 第三方平台: 取第三方插件平台的状态和状态流中的选项}
// String platformName = projectApplicationService.getPlatformName(projectId);
// if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// return getProjectStatusItemOption(projectId);
// } else {
// // 状态流 && 第三方平台状态流
// ServiceIntegration serviceIntegration = projectTemplateService.getServiceIntegration(projectId);
// if (serviceIntegration == null) {
// // 项目未配置第三方平台
// throw new MSException(Translator.get("third_party_not_config"));
// }
// // 获取配置平台, 获取第三方平台状态流
// Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
// new String(serviceIntegration.getConfiguration()));
//// List<PlatformStatusDTO> statusList = platform.getStatusList();
// // 获取项目状态流
//// List<BugStatusOptionDTO> projectStatusItemOption = getProjectStatusItemOption(projectId);
// }
// return null;
// }
//
// /**
// * 获取项目状态流选项
// * @param projectId 项目ID
// * @return 选项集合
// */
// public List<BugStatusOptionDTO> getProjectStatusItemOption(String projectId) {
// // TODO: 获取项目状态流选项
// return null;
// }
//}
package io.metersphere.bug.service;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.system.service.BaseStatusFlowSettingService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class BugStatusService {
@Resource
private ProjectApplicationService projectApplicationService;
@Resource
private BaseStatusFlowSettingService baseStatusFlowSettingService;
/**
* 获取缺陷下一批状态流转选项
* @param projectId 项目ID
* @param fromStatusId 当前状态选项值ID
* @param platformBugKey 平台缺陷Key
* @return 选项集合
*/
public List<SelectOption> getToStatusItemOption(String projectId, String fromStatusId, String platformBugKey) {
String platformName = projectApplicationService.getPlatformName(projectId);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// Local状态流
return getToStatusItemOptionOnLocal(projectId, fromStatusId);
} else {
// 第三方平台状态流
// 获取配置平台, 获取第三方平台状态流
Platform platform = projectApplicationService.getPlatform(projectId, true);
String projectConfig = projectApplicationService.getProjectBugThirdPartConfig(projectId);
return platform.getStatusTransitions(projectConfig, platformBugKey);
}
}
/**
* 获取缺陷下一批状态流转选项
* @param projectId 项目ID
* @param fromStatusId 当前状态选项值ID
* @return 选项集合
*/
public List<SelectOption> getToStatusItemOptionOnLocal(String projectId, String fromStatusId) {
return baseStatusFlowSettingService.getStatusTransitions(projectId, TemplateScene.BUG.name(), fromStatusId);
}
}

View File

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

View File

@ -0,0 +1,135 @@
package io.metersphere.bug.service;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugExample;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.bug.enums.BugPlatform;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.project.service.ProjectTemplateService;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.domain.Template;
import io.metersphere.system.domain.TemplateExample;
import io.metersphere.system.mapper.TemplateMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author song-cc-rock
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class BugSyncService {
@Resource
private BugMapper bugMapper;
@Resource
private BugService bugService;
@Resource
private ProjectMapper projectMapper;
@Resource
private TemplateMapper templateMapper;
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private ProjectTemplateService projectTemplateService;
@Resource
private ProjectApplicationService projectApplicationService;
/**
* XPACK用户 (同步全量缺陷)
* @param request 同步全量参数
*/
public void syncAllBugs(BugSyncRequest request) {
try {
// 获取当前项目同步缺陷唯一Key
String syncValue = bugSyncExtraService.getSyncKey(request.getProjectId());
if (StringUtils.isEmpty(syncValue)) {
// 不存在, 设置保证唯一性, 并开始同步
bugSyncExtraService.setSyncKey(request.getProjectId());
Project project = getProjectById(request.getProjectId());
bugService.syncPlatformAllBugs(request, project);
}
} catch (Exception e) {
bugSyncExtraService.deleteSyncKey(request.getProjectId());
throw new MSException(e);
}
}
/**
* 开源用户 (同步存量缺陷)
* @param projectId 项目ID
*/
public void syncBugs(String projectId) {
try {
String syncValue = bugSyncExtraService.getSyncKey(projectId);
if (StringUtils.isEmpty(syncValue)) {
bugSyncExtraService.setSyncKey(projectId);
Project project = getProjectById(projectId);
String platformName = projectApplicationService.getPlatformName(projectId);
if (StringUtils.equals(platformName, BugPlatform.LOCAL.getName())) {
// 当前项目为本地平台, 不需要同步第三方
bugSyncExtraService.deleteSyncKey(projectId);
}
// 查询存量的平台缺陷
BugExample example = new BugExample();
example.createCriteria().andProjectIdEqualTo(projectId).andPlatformEqualTo(platformName).andDeletedEqualTo(false);
List<Bug> bugs = bugMapper.selectByExample(example);
if (CollectionUtils.isEmpty(bugs)) {
bugSyncExtraService.deleteSyncKey(projectId);
} else {
Template pluginTemplate = projectTemplateService.getPluginBugTemplate(projectId);
List<String> templateIds = bugs.stream().map(Bug::getTemplateId).toList();
TemplateExample templateExample = new TemplateExample();
templateExample.createCriteria().andScopeIdEqualTo(projectId).andSceneEqualTo(TemplateScene.BUG.name()).andIdIn(templateIds);
List<Template> templates = templateMapper.selectByExample(templateExample);
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);
}
}
} catch (Exception e) {
bugSyncExtraService.deleteSyncKey(projectId);
throw new MSException(e);
}
}
/**
* 定时任务同步缺陷(存量)
*/
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);
syncProjectIds.forEach(id -> {
try {
syncBugs(id);
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
}
});
}
/**
* 根据项目ID获取项目信息
* @param projectId 项目ID
* @return 项目信息
*/
private Project getProjectById(String projectId) {
return projectMapper.selectByPrimaryKey(projectId);
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.bug.service;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.project.domain.Project;
/**
* 缺陷Xpack功能接口 (全量同步)
*/
public interface XpackBugService {
/**
* 同步系统下所有第三方平台项目缺陷(定时任务)
*/
void syncPlatformBugsBySchedule();
/**
* 同步当前项目第三方平台缺陷(前台调用)
* @param project 项目
* @param request 同步请求参数
*/
void syncPlatformBugs(Project project, BugSyncRequest request);
}

View File

@ -1,7 +1,7 @@
package io.metersphere.bug.utils;
import io.metersphere.bug.dto.BugDTO;
import io.metersphere.bug.dto.request.BugExportColumn;
import io.metersphere.bug.dto.BugExportColumn;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.sdk.util.CompressUtils;
import org.apache.commons.io.FileUtils;

View File

@ -1,8 +1,8 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.domain.BugComment;
import io.metersphere.bug.dto.BugCommentDTO;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.mapper.BugCommentMapper;
import io.metersphere.project.domain.Notification;
import io.metersphere.project.domain.NotificationExample;

View File

@ -1,21 +1,51 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.dto.BugCustomFieldDTO;
import io.metersphere.bug.dto.BugDTO;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugExample;
import io.metersphere.bug.domain.BugLocalAttachment;
import io.metersphere.bug.domain.BugLocalAttachmentExample;
import io.metersphere.bug.dto.BugExportColumn;
import io.metersphere.bug.dto.request.*;
import io.metersphere.bug.dto.response.BugCustomFieldDTO;
import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.mapper.BugLocalAttachmentMapper;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.bug.service.BugService;
import io.metersphere.bug.service.BugSyncExtraService;
import io.metersphere.bug.service.BugSyncService;
import io.metersphere.bug.utils.CustomFieldUtils;
import io.metersphere.plugin.platform.dto.request.SyncAllBugRequest;
import io.metersphere.project.domain.*;
import io.metersphere.project.dto.ProjectTemplateOptionDTO;
import io.metersphere.project.mapper.FileAssociationMapper;
import io.metersphere.project.mapper.FileMetadataMapper;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.project.service.FileService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.CustomFieldExample;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.mapper.CustomFieldMapper;
import io.metersphere.system.mapper.ServiceIntegrationMapper;
import io.metersphere.system.service.PluginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
@ -23,9 +53,11 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -38,17 +70,56 @@ public class BugControllerTests extends BaseTest {
public static final String BUG_UPDATE = "/bug/update";
public static final String BUG_DELETE = "/bug/delete";
public static final String BUG_TEMPLATE_OPTION = "/bug/template/option";
public static final String BUG_TEMPLATE_DETAIL = "/bug/template";
public static final String BUG_TEMPLATE_DETAIL = "/bug/template/detail";
public static final String BUG_BATCH_DELETE = "/bug/batch-delete";
public static final String BUG_BATCH_UPDATE = "/bug/batch-update";
public static final String BUG_FOLLOW = "/bug/follow";
public static final String BUG_UN_FOLLOW = "/bug/unfollow";
public static final String BUG_SYNC = "/bug/sync";
public static final String BUG_SYNC_ALL = "/bug/sync/all";
public static final String BUG_EXPORT_COLUMNS = "/bug/export/columns/%s";
public static final String BUG_EXPORT = "/bug/export";
@Resource
private PluginService pluginService;
@Resource
private BugMapper bugMapper;
@Resource
private BugLocalAttachmentMapper bugLocalAttachmentMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private CustomFieldMapper customFieldMapper;
@Resource
private FileAssociationMapper fileAssociationMapper;
@Resource
private FileService fileService;
@Resource
private BugSyncService bugSyncService;
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private BugService bugService;
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
@Resource
private ProjectApplicationMapper projectApplicationMapper;
@Test
@Order(0)
@Sql(scripts = {"/dml/init_bug.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void prepareData() throws Exception {
// 插入准备的文件
FileRequest fileRequest = new FileRequest();
fileRequest.setFolder(DefaultRepositoryDir.getFileManagementDir("default-project-for-bug"));
fileRequest.setFileName("default-bug-file-id-1");
fileRequest.setStorage(StorageType.MINIO.name());
fileService.upload(getMockFile(), fileRequest);
}
@Test
@Order(1)
void testBugPageSuccess() throws Exception {
BugPageRequest bugRequest = new BugPageRequest();
bugRequest.setCurrent(1);
@ -89,7 +160,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(1)
@Order(2)
void testBugPageEmptySuccess() throws Exception {
BugPageRequest bugPageRequest = new BugPageRequest();
bugPageRequest.setCurrent(1);
@ -161,7 +232,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(2)
@Order(3)
void testBugPageError() throws Exception {
// 页码有误
BugPageRequest bugPageRequest = new BugPageRequest();
@ -176,7 +247,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(3)
@Order(4)
void testAddBugSuccess() throws Exception {
BugEditRequest request = buildRequest(false);
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
@ -186,7 +257,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(4)
@Order(5)
void testAddBugError() throws Exception {
BugEditRequest request = buildRequest(false);
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
@ -195,30 +266,27 @@ public class BugControllerTests extends BaseTest {
request.setProjectId(null);
MultiValueMap<String, Object> paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().isBadRequest());
// 标题为空
request.setProjectId("default-project-for-bug");
request.setTitle(null);
paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().isBadRequest());
// 处理人为空
request.setTitle("default-bug-title");
request.setHandleUser(null);
List<BugCustomFieldDTO> noHandleUser = request.getCustomFields().stream().filter(field -> !StringUtils.equals(field.getId(), "handleUser")).toList();
request.setCustomFields(noHandleUser);
paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().isBadRequest());
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().is5xxServerError());
// 模板为空
request.setHandleUser("admin");
request.setTemplateId(null);
paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().isBadRequest());
// 状态为空
request.setTemplateId("default-bug-template");
request.setStatus(null);
List<BugCustomFieldDTO> noStatus = request.getCustomFields().stream().filter(field -> !StringUtils.equals(field.getId(), "status")).toList();
request.setCustomFields(noStatus);
paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().isBadRequest());
this.requestMultipart(BUG_ADD, paramMap).andExpect(status().is5xxServerError());
}
@Test
@Order(5)
@Order(6)
void testUpdateBugSuccess() throws Exception {
BugEditRequest request = buildRequest(true);
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
@ -228,12 +296,14 @@ public class BugControllerTests extends BaseTest {
// 第二次更新, no-file
MultiValueMap<String, Object> noFileParamMap = new LinkedMultiValueMap<>();
request.setLinkFileIds(null);
request.setUnLinkRefIds(null);
request.setDeleteLocalFileIds(null);
noFileParamMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn(BUG_UPDATE, noFileParamMap);
}
@Test
@Order(6)
@Order(7)
void testUpdateBugError() throws Exception {
BugEditRequest request = buildRequest(true);
request.setId("default-bug-id-not-exist");
@ -244,18 +314,22 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(7)
@Order(8)
void testUpdateBugWithEmptyField() throws Exception {
BugEditRequest request = buildRequest(true);
request.setCustomFieldMap(null);
List<BugCustomFieldDTO> statusAndHandleUser = request.getCustomFields().stream().filter(field -> StringUtils.equalsAny(field.getId(), "status", "handleUser")).toList();
request.setCustomFields(statusAndHandleUser);
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
File file = new File(filePath);
request.setLinkFileIds(null);
request.setUnLinkRefIds(null);
request.setDeleteLocalFileIds(null);
MultiValueMap<String, Object> paramMap = getDefaultMultiPartParam(request, file);
this.requestMultipartWithOkAndReturn(BUG_UPDATE, paramMap);
}
@Test
@Order(8)
@Order(9)
void testGetBugTemplateOption() throws Exception {
MvcResult mvcResult = this.requestGetWithOkAndReturn(BUG_TEMPLATE_OPTION + "?projectId=default-project-for-bug");
String sortData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
@ -266,82 +340,71 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(9)
@Order(10)
void testGetBugTemplateDetailSuccess() throws Exception {
MvcResult mvcResult = this.requestGetWithOkAndReturn(BUG_TEMPLATE_DETAIL + "/default-bug-template-id" + "?projectId=default-project-for-bug");
BugTemplateRequest request = new BugTemplateRequest();
request.setId("default-bug-template-id");
request.setProjectId("default-project-for-bug");
MvcResult mvcResult = this.requestPostWithOkAndReturn(BUG_TEMPLATE_DETAIL, request);
String sortData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(sortData, ResultHolder.class);
TemplateDTO templateDTO = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), TemplateDTO.class);
Assertions.assertNotNull(templateDTO);
Assertions.assertEquals("default-bug-template-id", templateDTO.getId());
// 获取默认模板
MvcResult defaultResult = this.requestGetWithOkAndReturn(BUG_TEMPLATE_DETAIL + "/default-bug-template-id-not-exist" + "?projectId=default-project-for-bug");
String defaultData = defaultResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder defaultResultHolder = JSON.parseObject(defaultData, ResultHolder.class);
TemplateDTO defaultTemplate = JSON.parseObject(JSON.toJSONString(defaultResultHolder.getData()), TemplateDTO.class);
Assertions.assertNotNull(defaultTemplate);
Assertions.assertEquals("default-bug-template-id", defaultTemplate.getId());
// 编辑时的模板详情
// 覆盖状态流
request.setFromStatusId("1");
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
request.setFromStatusId("2");
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
request.setProjectId("no-status-project");
request.setId("no-status-template");
request.setFromStatusId(null);
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
}
@Test
@Order(10)
@Order(11)
void testBatchUpdateBugSuccess() throws Exception {
BugBatchUpdateRequest request = new BugBatchUpdateRequest();
request.setProjectId("default-project-for-bug");
// 全选, 编辑所有数据
request.setSelectAll(true);
request.setSelectIds(List.of("test"));
// TAG追加
request.setTag(JSON.toJSONString(List.of("TAG", "TEST_TAG")));
request.setAppend(true);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// TAG覆盖
request.setExcludeIds(List.of("default-bug-id-tapd1"));
request.setTag(JSON.toJSONString(List.of("A", "B")));
request.setAppend(false);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// TAG追加
request.setTag(JSON.toJSONString(List.of("C", "D")));
request.setAppend(true);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// 处理人修改
request.setTag(null);
request.setHandleUser("default-admin");
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// 自定义字段追加
BugCustomFieldDTO field = new BugCustomFieldDTO();
field.setId("test_field");
field.setValue(JSON.toJSONString(List.of("test1")));
request.setCustomField(field);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// 自定义字段覆盖
request.setAppend(false);
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
// 勾选部分
request.setSelectAll(false);
request.setIncludeBugIds(List.of("default-bug-id"));
request.setSelectIds(List.of("default-bug-id"));
this.requestPost(BUG_BATCH_UPDATE, request, status().isOk());
}
@Test
@Order(11)
@Order(12)
void testBatchUpdateEmptyBugSuccess() throws Exception {
BugBatchUpdateRequest request = new BugBatchUpdateRequest();
request.setProjectId("default-project-for-bug");
request.setCombine(buildRequestCombine());
request.setProjectId("default-project-for-bug-no-data");
// 全选, 空数据
request.setSelectAll(true);
request.setSelectIds(List.of("test"));
request.setTag(JSON.toJSONString(List.of("TAG", "TEST_TAG")));
request.setAppend(true);
this.requestPost(BUG_BATCH_UPDATE, request, status().is5xxServerError());
// 取消全选, 空数据
request.setSelectAll(false);
request.setIncludeBugIds(List.of("not-exist-bug-id"));
this.requestPost(BUG_BATCH_UPDATE, request, status().is5xxServerError());
request.setSelectAll(false);
request.setIncludeBugIds(null);
request.setSelectIds(null);
this.requestPost(BUG_BATCH_UPDATE, request, status().is5xxServerError());
}
@Test
@Order(12)
@Order(13)
void testExportColumns() throws Exception {
this.requestGetWithOkAndReturn(String.format(BUG_EXPORT_COLUMNS, "default-project-for-bug"));
//校验权限
@ -349,7 +412,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(13)
@Order(14)
void testExportBugs() throws Exception {
BugExportRequest request = new BugExportRequest();
request.setProjectId("default-project-for-bug");
@ -379,13 +442,13 @@ public class BugControllerTests extends BaseTest {
// 勾选部分
request.setSelectAll(false);
request.setIncludeBugIds(List.of("default-bug-id-single"));
request.setSelectIds(List.of("default-bug-id-single"));
result = this.requestPostDownloadFile(BUG_EXPORT, null, request);
bytes = result.getResponse().getContentAsByteArray();
Assertions.assertTrue(bytes.length > 0);
//不存在的ID
request.setIncludeBugIds(List.of(IDGenerator.nextStr()));
request.setSelectIds(List.of(IDGenerator.nextStr()));
this.requestPost(BUG_EXPORT, request).andExpect(status().is5xxServerError());
//没有数据
@ -402,11 +465,13 @@ public class BugControllerTests extends BaseTest {
request.setExportColumns(exportColumns);
this.requestPostPermissionTest(PermissionConstants.BUG_EXPORT, BUG_EXPORT, request);
}
@Test
@Order(90)
void testDeleteBugSuccess() throws Exception {
// Local
this.requestGet(BUG_DELETE + "/default-bug-id", status().isOk());
// 非Local缺陷
// Tapd
this.requestGet(BUG_DELETE + "/default-bug-id-tapd1", status().isOk());
}
@ -418,20 +483,6 @@ public class BugControllerTests extends BaseTest {
@Test
@Order(92)
void testBatchDeleteEmptyBugSuccess() throws Exception {
BugBatchRequest request = new BugBatchRequest();
request.setProjectId("default-project-for-bug");
request.setCombine(buildRequestCombine());
// 全选, 空数据
request.setSelectAll(true);
this.requestPost(BUG_BATCH_DELETE, request, status().isOk());
// 勾选部分, 空数据
request.setSelectAll(false);
this.requestPost(BUG_BATCH_DELETE, request, status().is5xxServerError());
}
@Test
@Order(93)
void testFollowBug() throws Exception {
// 关注的缺陷存在
this.requestGet(BUG_FOLLOW + "/default-bug-id-single", status().isOk());
@ -440,7 +491,7 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(94)
@Order(93)
void testUnFollowBug() throws Exception {
// 取消关注的缺陷存在
this.requestGet(BUG_UN_FOLLOW + "/default-bug-id-single", status().isOk());
@ -449,26 +500,169 @@ public class BugControllerTests extends BaseTest {
}
@Test
@Order(95)
@Order(94)
void testBatchDeleteBugSuccess() throws Exception {
BugBatchRequest request = new BugBatchRequest();
request.setProjectId("default-project-for-bug");
// 全选, 删除所有
request.setSelectAll(true);
this.requestPost(BUG_BATCH_DELETE, request, status().isOk());
// 非Local的缺陷删除
request.setProjectId("default-project-for-bug-no-local");
this.requestPost(BUG_BATCH_DELETE, request, status().isOk());
// 勾选部分
request.setSelectAll(false);
request.setIncludeBugIds(List.of("default-bug-id-single"));
this.requestPost(BUG_BATCH_DELETE, request, status().isOk());
request.setSelectIds(List.of("test"));
request.setExcludeIds(List.of("default-bug-id-jira-delete"));
this.requestPost(BUG_BATCH_DELETE, request, status().is5xxServerError());
}
@Test
@Order(95)
void coverUtilsTests() {
CustomFieldUtils.appendToMultipleCustomField(null, "test");
}
@Test
@Order(96)
void coverUtilsTest() throws Exception {
CustomFieldUtils.appendToMultipleCustomField(null, "test");
void coverPlatformTemplateTests() throws Exception{
// 覆盖同步缺陷(Local)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-not-integration");
// 上传Jira插件
addJiraPlugin();
// 获取Jira默认模板
BugTemplateRequest request = new BugTemplateRequest();
request.setId("jira");
request.setProjectId("default-project-for-bug");
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
// 获取MS默认模板
request.setId("default-bug-template-not-exist");
request.setProjectId("default-project-for-bug");
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
// 关闭集成
ServiceIntegration record = new ServiceIntegration();
record.setId("621103810617344");
record.setEnable(false);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
record.setEnable(true);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
// 关闭同步
ProjectApplicationExample example = new ProjectApplicationExample();
example.createCriteria().andProjectIdEqualTo("default-project-for-bug").andTypeEqualTo("BUG_SYNC_PLATFORM_KEY");
projectApplicationMapper.deleteByExample(example);
this.requestPostWithOk(BUG_TEMPLATE_DETAIL, request);
ProjectApplication projectApplication = new ProjectApplication();
projectApplication.setProjectId("default-project-for-bug");
projectApplication.setType("BUG_SYNC_PLATFORM_KEY");
projectApplication.setTypeValue("jira");
projectApplicationMapper.insert(projectApplication);
}
@Test
@Order(96)
void coverPlatformBugSyncTests() throws Exception {
// 添加一条需要同步删除的缺陷
BugEditRequest deleteRequest = buildJiraBugRequest(false);
MultiValueMap<String, Object> deleteParam = getDefaultMultiPartParam(deleteRequest, null);
this.requestMultipartWithOkAndReturn(BUG_ADD, deleteParam);
Bug record = new Bug();
record.setId(getAddJiraBug().getId());
record.setPlatformBugId("Tapd-XXX");
bugMapper.updateByPrimaryKeySelective(record);
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 添加Jira缺陷
BugEditRequest addRequest = buildJiraBugRequest(false);
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/test.xlsx")).getPath();
File file = new File(filePath);
MultiValueMap<String, Object> addParam = getDefaultMultiPartParam(addRequest, file);
this.requestMultipartWithOkAndReturn(BUG_ADD, addParam);
// 添加没有附件的Jira缺陷
addRequest.setLinkFileIds(null);
MultiValueMap<String, Object> addParam2 = getDefaultMultiPartParam(addRequest, null);
this.requestMultipartWithOkAndReturn(BUG_ADD, addParam2);
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 更新Jira缺陷
BugEditRequest updateRequest = buildJiraBugRequest(true);
updateRequest.setUnLinkRefIds(List.of(getAddJiraAssociateFile().getId()));
updateRequest.setDeleteLocalFileIds(List.of(getAddJiraLocalFile().getFileId()));
MultiValueMap<String, Object> updateParma = getDefaultMultiPartParam(updateRequest, null);
this.requestMultipartWithOkAndReturn(BUG_UPDATE, updateParma);
// 删除Jira缺陷
this.requestGet(BUG_DELETE + "/" + updateRequest.getId(), status().isOk());
// 更新没有附件的缺陷
BugEditRequest updateRequest2 = buildJiraBugRequest(true);
updateRequest2.setLinkFileIds(List.of("default-bug-file-id-1"));
MultiValueMap<String, Object> updateParam2 = getDefaultMultiPartParam(updateRequest2, file);
this.requestMultipartWithOkAndReturn(BUG_UPDATE, updateParam2);
// 同步第一次
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 同步第二次
renameLocalFile(updateRequest2.getId()); // 重命名后, 同步时会删除本地文件
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 同步第三次
deleteLocalFile(updateRequest2.getId()); // 手动删除关联的文件, 重新同步时会下载平台附件
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 删除唯一条平台缺陷
BugBatchRequest batchRequest = new BugBatchRequest();
batchRequest.setProjectId("default-project-for-bug");
batchRequest.setSelectAll(true);
batchRequest.setSelectIds(List.of("test"));
batchRequest.setExcludeIds(List.of("default-bug-id-tapd1"));
this.requestPost(BUG_BATCH_DELETE, batchRequest, status().isOk());
// 同步Jira存量缺陷(存量数据为空)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
// 集成配置为空
addRequest.setProjectId("default-project-for-not-integration");
MultiValueMap<String, Object> notIntegrationParam = getDefaultMultiPartParam(addRequest, file);
this.requestMultipart(BUG_ADD, notIntegrationParam).andExpect(status().is5xxServerError());
// 同步全量缺陷
BugSyncRequest request = new BugSyncRequest();
request.setProjectId("default-project-for-bug");
request.setPre(true);
request.setCreateTime(1702021500000L);
this.requestPostWithOk(BUG_SYNC_ALL, request);
// 执行同步全部
Project project = new Project();
project.setId("default-project-for-bug");
SyncAllBugRequest syncAllBugRequest = new SyncAllBugRequest();
syncAllBugRequest.setPre(true);
syncAllBugRequest.setCreateTime(1702021500000L);
// 同步后置方法处理为空, 覆盖主工程代码即可
syncAllBugRequest.setSyncPostProcessFunc((param) -> {});
bugService.execSyncAll(project, syncAllBugRequest);
}
@Test
@Order(97)
void coverSyncScheduleTests() {
// 定时同步存量缺陷
bugSyncService.syncPlatformBugBySchedule();
// 异常信息
bugSyncExtraService.setSyncErrorMsg("default-project-for-bug", "sync error!");
String syncErrorMsg = bugSyncExtraService.getSyncErrorMsg("default-project-for-bug");
Assertions.assertEquals(syncErrorMsg, "sync error!");
bugSyncExtraService.deleteSyncErrorMsg("default-project-for-bug");
}
@Test
@Order(98)
void coverBugTests() {
BugCustomFieldDTO field = new BugCustomFieldDTO();
field.setId("test_field");
field.setName("test");
field.setValue("test");
field.setType("multipleSelect");
bugService.transferCustomToPlatformField(null, List.of(field), true);
// 添加没有配置自定义映射字段的Jira缺陷
removeApiFieldTmp();
bugService.transferCustomToPlatformField("default-bug-template-id-not-exist", List.of(field), false);
rollBackApiField();
}
/**
@ -508,20 +702,199 @@ public class BugControllerTests extends BaseTest {
request.setProjectId("default-project-for-bug");
request.setTitle("default-bug-title");
request.setDescription("default-bug-description");
request.setHandleUser("admin");
request.setTemplateId("default-bug-template");
request.setStatus("prepare");
request.setLinkFileIds(List.of("default-bug-file-id-1"));
Map<String, String> customFieldMap = new HashMap<>();
customFieldMap.put("custom-field", "oasis");
customFieldMap.put("test_field", JSON.toJSONString(List.of("test")));
if (isUpdate) {
request.setId("default-bug-id");
request.setUnLinkRefIds(List.of("default-bug-file-id-1"));
request.setUnLinkRefIds(List.of("default-file-association-id"));
request.setDeleteLocalFileIds(List.of("default-bug-file-id"));
request.setLinkFileIds(List.of("default-bug-file-id-2"));
}
request.setCustomFieldMap(customFieldMap);
BugCustomFieldDTO fieldDTO1 = new BugCustomFieldDTO();
fieldDTO1.setId("custom-field");
fieldDTO1.setName("oasis");
BugCustomFieldDTO fieldDTO2 = new BugCustomFieldDTO();
fieldDTO2.setId("test_field");
fieldDTO2.setName(JSON.toJSONString(List.of("test")));
BugCustomFieldDTO handleUserField = new BugCustomFieldDTO();
handleUserField.setId("handleUser");
handleUserField.setName("处理人");
handleUserField.setValue("admin");
BugCustomFieldDTO statusField = new BugCustomFieldDTO();
statusField.setId("status");
statusField.setName("状态");
statusField.setValue("1");
request.setCustomFields(List.of(fieldDTO1, fieldDTO2, handleUserField, statusField));
return request;
}
/**
* 生成添加Jira缺陷的请求参数
* @param isUpdate 是否更新
* @return 缺陷编辑请求参数
*/
private BugEditRequest buildJiraBugRequest(boolean isUpdate) {
BugEditRequest request = new BugEditRequest();
request.setProjectId("default-project-for-bug");
request.setTitle("这是一个系统Jira模板创建的缺陷");
request.setDescription("这是一段缺陷内容!!!!");
request.setTemplateId("default-bug-template-id");
BugCustomFieldDTO customFieldDTO1 = new BugCustomFieldDTO();
customFieldDTO1.setId("custom-field-1");
customFieldDTO1.setName("开发团队");
customFieldDTO1.setValue("10068");
customFieldDTO1.setType("SELECT");
BugCustomFieldDTO customFieldDTO2 = new BugCustomFieldDTO();
customFieldDTO2.setId("custom-field-2");
customFieldDTO2.setName("同名验证");
customFieldDTO2.setValue("10062");
customFieldDTO2.setType("SELECT");
request.setCustomFields(new ArrayList<>(List.of(customFieldDTO1, customFieldDTO2)));
if (!isUpdate) {
// 新增
request.setLinkFileIds(List.of("default-bug-file-id-1"));
BugCustomFieldDTO handleUserField = new BugCustomFieldDTO();
handleUserField.setId("assignee");
handleUserField.setName("处理人");
handleUserField.setType("select");
handleUserField.setValue("60f68f7952162b0068de6a2d");
BugCustomFieldDTO statusField = new BugCustomFieldDTO();
statusField.setId("status");
statusField.setName("状态");
statusField.setType("select");
statusField.setValue("10003");
request.getCustomFields().addAll(List.of(handleUserField, statusField));
}
if (isUpdate) {
// 更新
Bug addJiraBug = getAddJiraBug();
request.setId(addJiraBug.getId());
request.setLinkFileIds(List.of("default-bug-file-id-3"));
BugCustomFieldDTO summaryField = new BugCustomFieldDTO();
summaryField.setId("summary");
summaryField.setName("摘要");
summaryField.setType("input");
summaryField.setValue("这是一段summary内容!!!!");
BugCustomFieldDTO descriptionField = new BugCustomFieldDTO();
descriptionField.setId("description");
descriptionField.setName("描述");
descriptionField.setType("richText");
descriptionField.setValue("这是一段描述内容!!!!");
BugCustomFieldDTO statusField = new BugCustomFieldDTO();
statusField.setId("status");
statusField.setName("状态");
statusField.setType("select");
statusField.setValue("31");
request.getCustomFields().addAll(List.of(summaryField, descriptionField, statusField));
}
return request;
}
/**
* 添加Jira插件供测试使用
* @throws Exception 异常
*/
public void addJiraPlugin() throws Exception {
PluginUpdateRequest request = new PluginUpdateRequest();
File jiraTestFile = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/metersphere-jira-test.jar")).getPath());
FileInputStream inputStream = new FileInputStream(jiraTestFile);
MockMultipartFile mockMultipartFile = new MockMultipartFile(jiraTestFile.getName(), jiraTestFile.getName(), "jar", inputStream);
request.setName("测试插件");
request.setGlobal(true);
request.setEnable(true);
request.setCreateUser(ADMIN.name());
pluginService.add(request, mockMultipartFile);
}
/**
* 获取添加的Jira缺陷
* @return 缺陷
*/
private Bug getAddJiraBug() {
BugExample example = new BugExample();
example.createCriteria().andTitleEqualTo("这是一个系统Jira模板创建的缺陷");
return bugMapper.selectByExample(example).get(0);
}
/**
* 获取创建Jira缺陷时的本地文件
* @return 本地附件
*/
private BugLocalAttachment getAddJiraLocalFile() {
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
example.createCriteria().andBugIdEqualTo(getAddJiraBug().getId());
return bugLocalAttachmentMapper.selectByExample(example).get(0);
}
/**
* 获取创建Jira缺陷时的关联文件
* @return 关联文件
*/
private FileAssociation getAddJiraAssociateFile() {
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andSourceIdEqualTo(getAddJiraBug().getId()).andSourceTypeEqualTo(FileAssociationSourceUtil.SOURCE_TYPE_BUG);
return fileAssociationMapper.selectByExample(example).get(0);
}
/**
* 获取File上传
* @return multipartFile
*/
private MockMultipartFile getMockFile() {
return new MockMultipartFile(
"test-file.xlsx",
"test-file.xlsx",
MediaType.APPLICATION_OCTET_STREAM_VALUE,
"Hello, World!".getBytes());
}
/**
* 重命名本地文件
*/
private void renameLocalFile(String bugId) {
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
example.createCriteria().andBugIdEqualTo(bugId);
BugLocalAttachment record = new BugLocalAttachment();
record.setFileName("test1");
bugLocalAttachmentMapper.updateByExampleSelective(record, example);
FileMetadata associatedFile = new FileMetadata();
associatedFile.setId("default-bug-file-id-1");
associatedFile.setName("test1");
fileMetadataMapper.updateByPrimaryKeySelective(associatedFile);
}
/**
* 临时移除API字段
*/
private void removeApiFieldTmp() {
CustomFieldExample example = new CustomFieldExample();
example.createCriteria().andIdIn(List.of("custom-field-1", "custom-field-2"));
CustomField record = new CustomField();
record.setScopeId("default-project-for-bug-tmp");
customFieldMapper.updateByExampleSelective(record, example);
}
/**
* 恢复API字段
*/
private void rollBackApiField() {
CustomFieldExample example = new CustomFieldExample();
example.createCriteria().andIdIn(List.of("custom-field-1", "custom-field-2"));
CustomField record = new CustomField();
record.setScopeId("default-project-for-bug");
customFieldMapper.updateByExampleSelective(record, example);
}
/**
* 删除本地文件
*/
private void deleteLocalFile(String bugId) {
BugLocalAttachmentExample localExample = new BugLocalAttachmentExample();
localExample.createCriteria().andBugIdEqualTo(bugId);
bugLocalAttachmentMapper.deleteByExample(localExample);
FileAssociationExample example = new FileAssociationExample();
example.createCriteria().andSourceIdEqualTo(bugId).andSourceTypeEqualTo(FileAssociationSourceUtil.SOURCE_TYPE_BUG);
fileAssociationMapper.deleteByExample(example);
}
}

View File

@ -1,7 +1,7 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.dto.BugRelateCaseDTO;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.sdk.constants.UserRoleType;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;

View File

@ -2,24 +2,34 @@ INSERT INTO project (id, num, organization_id, name, description, create_user, u
('default-project-for-bug-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
('default-project-for-bug', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
('default-project-for-bug', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('no-status-project', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUES
(UUID(), 'admin', 'project_admin', 'default-project-for-bug', '100001', UNIX_TIMESTAMP() * 1000, 'admin');
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, tag, platform_bug_id, deleted) VALUES
('default-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('default-bug-id-tapd1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'default-bug-template-id', 'Tapd', 'open', 'default-tag', null, 0),
('default-bug-id-tapd2', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-no-local', 'default-bug-template-id', 'Tapd', 'open', 'default-tag', null, 0),
('default-bug-id-single', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-single', 'default-bug-template-id', 'Tapd', 'open', 'default-tag', null, 0);
('default-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd2', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-no-local', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-single', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-single', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-jira', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-single', 'default-bug-template-id', 'Jira', 'open', '["default-tag"]', 'TES-TEST', 0);
INSERT INTO bug_custom_field (bug_id, field_id, value) VALUE ('default-bug-id', 'test_field', '["default", "default-1"]');
INSERT INTO custom_field (id, name, scene, type, remark, internal, scope_type, create_time, update_time, create_user, scope_id) VALUE
('test_field', '测试字段', 'BUG', 'MULTIPLE_SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'default-project-for-bug'),
('custom-field', '测试字段', 'BUG', 'SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'default-project-for-bug');
('custom-field-1', '测试字段1', 'BUG', 'SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'default-project-for-bug'),
('custom-field-2', '测试字段2', 'BUG', 'SELECT', '', 0, 'PROJECT', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'default-project-for-bug');
INSERT INTO template (id, name, remark, internal, update_time, create_time, create_user, scope_type, scope_id, enable_third_part, scene) VALUES
('bug-template-id', 'bug-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', 'default-project-for-bug', 0, 'BUG'),
('default-bug-template-id', 'bug-default-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', 'default-project-for-bug', 0, 'BUG');
('default-bug-template-id', 'bug-default-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', 'default-project-for-bug', 0, 'BUG'),
('no-status-template', 'no-status-template', '', 0, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'PROJECT', 'no-status-project', 0, 'BUG');
INSERT INTO template_custom_field(`id`, `field_id`, `template_id`, `required`, `system_field`, `pos`, `api_field_id`, `default_value`) VALUES
('100581234408685570', 'custom-field-1', 'default-bug-template-id', false, false, 0, 'customfield_10097', NULL),
('100581234408685571', 'custom-field-2', 'default-bug-template-id', false, false, 0, 'customfield_10098', NULL);
INSERT INTO bug_relation_case (id, case_id, bug_id, case_type, test_plan_id, test_plan_case_id, create_user, create_time, update_time) VALUE
(UUID_SHORT(), UUID_SHORT(), 'default-bug-id', 'functional', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
@ -31,4 +41,27 @@ INSERT INTO file_metadata (id, name, type, size, create_time, update_time, proje
('default-bug-file-id-2', 'test-file', 'xlsx', 100, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'MINIO', 'admin', 'admin',
'["default-tag"]', 'test-file', null, 'test-file', 1, 'default-bug-id', 1);
INSERT INTO project_application (project_id, type, type_value) VALUES ('default-project-for-bug', 'BUG_DEFAULT_TEMPLATE', 'default-bug-template-id');
INSERT INTO bug_local_attachment (id, bug_id, file_id, file_name, size, create_user, create_time) VALUES
('default-local-attachment-id', 'default-bug-id', 'default-bug-file-id', 'test-file-1', 100, 'admin', UNIX_TIMESTAMP() * 1000);
INSERT INTO file_association (id, source_type, source_id, file_id, file_ref_id, file_version, create_user, create_time, update_user, update_time) VALUES
('default-file-association-id', 'BUG', 'default-bug-id', 'default-bug-file-id-1', 'default-bug-file-id-1', '1', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000);
INSERT INTO project_application (project_id, type, type_value) VALUES
('default-project-for-bug', 'BUG_DEFAULT_TEMPLATE', 'default-bug-template-id'),
('default-project-for-bug', 'BUG_SYNC_BUG_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraBugTypeId":"10009"}'),
('default-project-for-bug', 'BUG_SYNC_CRON_EXPRESSION', '0 0 0/1 * * ?'),
('default-project-for-bug', 'BUG_SYNC_MECHANISM', 'increment'),
('default-project-for-bug', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-not-integration', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-bug', 'BUG_SYNC_SYNC_ENABLE', 'true'),
('default-project-for-bug', 'CASE_ENABLE', 'false'),
('default-project-for-bug', 'CASE_RELATED_DEMAND_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraDemandTypeId":"10007"}'),
('default-project-for-bug', 'CASE_RELATED_PLATFORM_KEY', 'jira');
INSERT INTO service_integration(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES
('621103810617344', 'jira', true, 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '100001');

View File

@ -4,15 +4,13 @@ import io.metersphere.plugin.platform.spi.AbstractPlatformPlugin;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.plugin.sdk.spi.MsPlugin;
import io.metersphere.project.domain.FakeErrorExample;
import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.project.domain.ProjectApplicationExample;
import io.metersphere.project.dto.ModuleDTO;
import io.metersphere.project.job.BugSyncJob;
import io.metersphere.project.job.CleanUpReportJob;
import io.metersphere.project.mapper.ExtProjectMapper;
import io.metersphere.project.mapper.ExtProjectUserRoleMapper;
import io.metersphere.project.mapper.FakeErrorMapper;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.project.mapper.*;
import io.metersphere.project.request.ProjectApplicationRequest;
import io.metersphere.project.utils.ModuleSortUtils;
import io.metersphere.sdk.constants.OperationLogConstants;
@ -20,6 +18,7 @@ import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.constants.ScheduleType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.*;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.log.constants.OperationLogModule;
@ -77,6 +76,8 @@ public class ProjectApplicationService {
@Resource
private ServiceIntegrationService serviceIntegrationService;
@Resource
private ProjectMapper projectMapper;
/**
* 更新配置信息
@ -550,7 +551,7 @@ public class ProjectApplicationService {
example.createCriteria().andProjectIdEqualTo(projectId).andTypeLike(ProjectApplicationType.BUG.BUG_SYNC.name() + "_" + ProjectApplicationType.PLATFORM_BUG_CONFIG.BUG_PLATFORM_CONFIG.name());
List<ProjectApplication> list = projectApplicationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0).getTypeValue();
return list.get(0).getTypeValue().replaceAll("\\\\", "");
}
return null;
}
@ -567,7 +568,7 @@ public class ProjectApplicationService {
example.createCriteria().andProjectIdEqualTo(projectId).andTypeLike(ProjectApplicationType.CASE_RELATED_CONFIG.CASE_RELATED.name() + "_" + ProjectApplicationType.PLATFORM_DEMAND_CONFIG.DEMAND_PLATFORM_CONFIG.name());
List<ProjectApplication> list = projectApplicationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0).getTypeValue();
return list.get(0).getTypeValue().replaceAll("\\\\", "");
}
return null;
}
@ -590,11 +591,120 @@ public class ProjectApplicationService {
example.createCriteria().andProjectIdEqualTo(projectId).andTypeEqualTo(ProjectApplicationType.BUG.BUG_SYNC.name() + "_PLATFORM_KEY");
List<ProjectApplication> list = projectApplicationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(list)) {
PluginWrapper pluginWrapper = pluginLoadService.getPluginWrapper(list.get(0).getTypeValue());
MsPlugin plugin = (MsPlugin) pluginWrapper.getPlugin();
return plugin.getName();
return getPluginName(list.get(0).getTypeValue());
} else {
return "local";
return "Local";
}
}
/**
* 过滤掉非Local平台的项目
* @param projectIds 项目ID集合
* @return 非Local平台的项目
*/
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());
}
}
return projectIds;
} else {
return new ArrayList<>();
}
}
/**
* 获取项目同步机制
*
* @param projectId 项目ID
* @return 项目所属平台
*/
public boolean isPlatformSyncMethodByIncrement(String projectId) {
ProjectApplicationExample example = new ProjectApplicationExample();
example.createCriteria().andProjectIdEqualTo(projectId).andTypeEqualTo(ProjectApplicationType.BUG.BUG_SYNC.name() + "_MECHANISM");
List<ProjectApplication> list = projectApplicationMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(list)) {
return StringUtils.equals(list.get(0).getTypeValue(), "increment");
} else {
return false;
}
}
/**
* 查询插件具体的服务集成信息(缺陷PLATFORM_KEY, 需求PLATFORM_KEY)
* @param projectId 项目ID
* @param isSync 是否缺陷同步配置
* @return 插件服务集成信息
*/
public ServiceIntegration getPlatformServiceIntegrationWithSyncOrDemand(String projectId, boolean isSync) {
// 是否开启项目配置第三方平台
ProjectApplication platformEnableConfig;
ProjectApplication platformKeyConfig;
if (isSync) {
platformEnableConfig = getByType(projectId, ProjectApplicationType.BUG.BUG_SYNC.name() + "_" + ProjectApplicationType.BUG_SYNC_CONFIG.SYNC_ENABLE.name());
platformKeyConfig = getByType(projectId, ProjectApplicationType.BUG.BUG_SYNC.name() + "_PLATFORM_KEY");
} else {
platformEnableConfig = getByType(projectId, ProjectApplicationType.CASE_RELATED_CONFIG.CASE_ENABLE.name());
platformKeyConfig = getByType(projectId, ProjectApplicationType.CASE_RELATED_CONFIG.CASE_RELATED.name() + "_PLATFORM_KEY");
}
boolean isEnable = platformEnableConfig != null && Boolean.parseBoolean(platformEnableConfig.getTypeValue()) && platformKeyConfig != null;
if (!isEnable) {
return null;
}
Project project = projectMapper.selectByPrimaryKey(projectId);
// 查询组织下有权限的插件
Set<String> orgPluginIds = platformPluginService.getOrgEnabledPlatformPlugins(project.getOrganizationId())
.stream()
.map(Plugin::getId)
.collect(Collectors.toSet());
// 查询服务集成中启用并且支持第三方模板的插件
return serviceIntegrationService.getServiceIntegrationByOrgId(project.getOrganizationId())
.stream()
.filter(serviceIntegration -> {
return serviceIntegration.getEnable() // 服务集成开启
&& orgPluginIds.contains(serviceIntegration.getPluginId())
&& StringUtils.equals(serviceIntegration.getPluginId(), platformKeyConfig.getTypeValue()); // 该服务集成对应的插件有权限
})
.findFirst()
.orElse(null);
}
/**
* 获取项目同步配置或需求配置的所属平台
* @param projectId 项目ID
* @param isSync 是否同步
* @return 平台
*/
public Platform getPlatform(String projectId, boolean isSync) {
// 第三方平台状态流
ServiceIntegration serviceIntegration = getPlatformServiceIntegrationWithSyncOrDemand(projectId, isSync);
if (serviceIntegration == null) {
// 项目未配置第三方平台
throw new MSException(Translator.get("third_party_not_config"));
}
return platformPluginService.getPlatform(serviceIntegration.getPluginId(), serviceIntegration.getOrganizationId(),
new String(serviceIntegration.getConfiguration()));
}
private String getPluginName(String platformKey) {
PluginWrapper pluginWrapper = pluginLoadService.getPluginWrapper(platformKey);
if (pluginWrapper == null) {
// 插件未找到
return "Local";
}
MsPlugin plugin = (MsPlugin) pluginWrapper.getPlugin();
if (plugin == null) {
// 插件未找到
return "Local";
}
return plugin.getName();
}
}

View File

@ -1,15 +1,14 @@
package io.metersphere.project.service;
import io.metersphere.sdk.constants.TemplateScopeType;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.domain.StatusItem;
import io.metersphere.system.dto.StatusItemDTO;
import io.metersphere.system.dto.sdk.request.StatusDefinitionUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusFlowUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusItemAddRequest;
import io.metersphere.system.dto.sdk.request.StatusItemUpdateRequest;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.domain.StatusItem;
import io.metersphere.system.dto.StatusItemDTO;
import io.metersphere.system.service.BaseStatusFlowSettingService;
import io.metersphere.system.service.OrganizationService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -104,6 +103,7 @@ public class ProjectStatusFlowSettingService extends BaseStatusFlowSettingServic
* 更新状态流配置
* @param request
*/
@Override
public void updateStatusFlow(StatusFlowUpdateRequest request) {
StatusItem fromStatusItem = baseStatusItemService.getWithCheck(request.getFromId());
StatusItem toStatusItem = baseStatusItemService.getWithCheck(request.getToId());

View File

@ -16,8 +16,8 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.*;
import io.metersphere.system.dto.ProjectDTO;
import io.metersphere.system.dto.sdk.TemplateDTO;
import io.metersphere.system.mapper.CustomFieldOptionMapper;
import io.metersphere.system.dto.sdk.request.TemplateUpdateRequest;
import io.metersphere.system.mapper.CustomFieldOptionMapper;
import io.metersphere.system.service.BaseTemplateService;
import io.metersphere.system.service.PlatformPluginService;
import io.metersphere.system.service.PluginLoadService;
@ -227,13 +227,13 @@ public class ProjectTemplateService extends BaseTemplateService {
* @return
*/
public Template getPluginBugTemplate(String projectId) {
ServiceIntegration serviceIntegration = getServiceIntegration(projectId);
ServiceIntegration serviceIntegration = projectApplicationService.getPlatformServiceIntegrationWithSyncOrDemand(projectId, true);
if (serviceIntegration == null) {
return null;
}
Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(),
serviceIntegration.getOrganizationId(), new String(serviceIntegration.getConfiguration()));
if (platform != null && platform.isThirdPartTemplateSupport()) {
if (platform != null && platform.isSupportDefaultTemplate()) {
return getPluginBugTemplate(projectId, serviceIntegration.getPluginId()); // 该插件支持第三方平台模板
}
return null;
@ -254,37 +254,6 @@ public class ProjectTemplateService extends BaseTemplateService {
return template;
}
/**
* 如果项目下配置了第三方平台信息
* 获取对应的服务集成信息
*
* @param projectId
* @return
*/
public ServiceIntegration getServiceIntegration(String projectId) {
// 判断项目是否开启集成缺陷
ProjectApplication syncEnableConfig = projectApplicationService.getByType(projectId, ProjectApplicationType.BUG_SYNC_CONFIG.SYNC_ENABLE.name());
boolean isSyncEnable = syncEnableConfig != null && Boolean.parseBoolean(syncEnableConfig.getTypeValue());
if (!isSyncEnable) {
return null;
}
ProjectDTO project = projectService.getProjectById(projectId);
// 查询组织下有权限的插件
Set<String> orgPluginIds = platformPluginService.getOrgEnabledPlatformPlugins(project.getOrganizationId())
.stream()
.map(Plugin::getId)
.collect(Collectors.toSet());
// 查询服务集成中启用并且支持第三方模板的插件
return serviceIntegrationService.getServiceIntegrationByOrgId(project.getOrganizationId())
.stream()
.filter(serviceIntegration -> {
return serviceIntegration.getEnable() // 服务集成开启
&& orgPluginIds.contains(serviceIntegration.getPluginId()); // 该服务集成对应的插件有权限
})
.findFirst()
.orElse(null);
}
/**
* 获取模板以及字段
* @param id

View File

@ -7,11 +7,14 @@ import io.metersphere.project.request.ProjectApplicationRequest;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.base.BasePluginTestService;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.dto.request.ServiceIntegrationUpdateRequest;
import io.metersphere.system.mapper.ServiceIntegrationMapper;
import io.metersphere.system.service.PluginService;
import jakarta.annotation.Resource;
import lombok.Getter;
@ -22,17 +25,16 @@ import org.mockserver.model.Header;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@ -46,7 +48,11 @@ public class ProjectApplicationControllerTests extends BaseTest {
private static Plugin plugin;
@Resource
private PluginService pluginService;
@Resource
private BasePluginTestService basePluginTestService;
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
@Resource
private ProjectApplicationService projectApplicationService;
@ -374,7 +380,7 @@ public class ProjectApplicationControllerTests extends BaseTest {
@Test
@Order(26)
public void testGetPlatformInfo() throws Exception {
plugin = addPlugin();
plugin = basePluginTestService.addJiraPlugin();
MvcResult mvcResult = this.requestGetWithOkAndReturn(GET_PLATFORM_INFO_URL + "/" + plugin.getId());
// 获取返回值
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
@ -467,23 +473,6 @@ public class ProjectApplicationControllerTests extends BaseTest {
* ==========缺陷管理 end==========
*/
public Plugin addPlugin() throws Exception {
PluginUpdateRequest request = new PluginUpdateRequest();
File jarFile = new File(
this.getClass().getClassLoader().getResource("file/metersphere-jira-plugin-3.x.jar")
.getPath()
);
FileInputStream inputStream = new FileInputStream(jarFile);
MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
request.setName("测试插件1");
request.setGlobal(true);
request.setEnable(true);
request.setCreateUser(ADMIN.name());
return pluginService.add(request, mockMultipartFile);
}
private ProjectApplicationRequest getRequest(String type) {
ProjectApplicationRequest request = new ProjectApplicationRequest();
request.setProjectId(PROJECT_ID);
@ -704,4 +693,56 @@ public class ProjectApplicationControllerTests extends BaseTest {
projectApplicationService.getPlatformName(PROJECT_ID);
}
@Test
@Order(99)
void coverPlatformTest() {
// 没有配置平台的项目
try {
projectApplicationService.getPlatform("default-project-for-application-not-exist", true);
} catch (Exception e) {
Assertions.assertEquals(e.getMessage(), Translator.get("third_party_not_config"));
}
try {
// 获取需求配置
projectApplicationService.getPlatform("default-project-for-application", false);
} catch (Exception e) {
Assertions.assertEquals(e.getMessage(), Translator.get("third_party_not_config"));
}
ServiceIntegration record = new ServiceIntegration();
try {
// 关闭集成
record.setId("621103810617345");
record.setEnable(false);
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
projectApplicationService.getPlatform("default-project-for-application", true);
} catch (Exception e) {
Assertions.assertEquals(e.getMessage(), Translator.get("third_party_not_config"));
}
try {
// 集成Tapd
record.setEnable(true);
record.setPluginId("Tapd");
serviceIntegrationMapper.updateByPrimaryKeySelective(record);
projectApplicationService.getPlatform("default-project-for-application", true);
} catch (Exception e) {
Assertions.assertEquals(e.getMessage(), Translator.get("third_party_not_config"));
}
// 获取缺陷同步配置平台
projectApplicationService.getPlatform("default-project-for-application", true);
// 获取同步机制
Assertions.assertTrue(projectApplicationService.isPlatformSyncMethodByIncrement("default-project-for-application"));
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")));
// 移除插件测试
basePluginTestService.deleteJiraPlugin();
projectApplicationService.filterNoLocalPlatform(new ArrayList<>(List.of("default-project-for-application")));
}
}

View File

@ -10,7 +10,6 @@ import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.utils.Pager;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
@ -133,8 +132,6 @@ public class ProjectMemberControllerTests extends BaseTest {
request.setUserIds(List.of("default-project-member-user-1", "default-project-member-user-del"));
request.setRoleIds(List.of("project_admin", "project_admin_x", "project_member"));
this.requestPost(ADD_MEMBER, request, status().isOk());
// 日志
checkLog("default-project-member-user-1", OperationLogType.ADD);
// 权限校验
request.setProjectId(DEFAULT_PROJECT_ID);
requestPostPermissionTest(PermissionConstants.PROJECT_USER_ADD, ADD_MEMBER, request);
@ -172,8 +169,6 @@ public class ProjectMemberControllerTests extends BaseTest {
// 存在的用户组
request.setRoleIds(List.of("project_admin", "project_member"));
this.requestPost(UPDATE_MEMBER, request, status().isOk());
// 日志
checkLog("default-project-member-user-1", OperationLogType.UPDATE);
// 权限校验
request.setProjectId(DEFAULT_PROJECT_ID);
requestPostPermissionTest(PermissionConstants.PROJECT_USER_UPDATE, UPDATE_MEMBER, request);
@ -193,8 +188,6 @@ public class ProjectMemberControllerTests extends BaseTest {
@Order(11)
public void testRemoveMemberSuccess() throws Exception {
this.requestGet(REMOVE_MEMBER + "/default-project-member-test/default-project-member-user-1", status().isOk());
// 日志
checkLog("default-project-member-user-1", OperationLogType.DELETE);
// 权限校验
requestGetPermissionTest(PermissionConstants.PROJECT_USER_DELETE, REMOVE_MEMBER + "/" + DEFAULT_PROJECT_ID + "/default-project-member-user-1");
}
@ -213,8 +206,6 @@ public class ProjectMemberControllerTests extends BaseTest {
request.setUserIds(List.of("default-project-member-user-1", "default-project-member-user-2"));
request.setRoleIds(List.of("project_admin", "project_member"));
this.requestPost(ADD_ROLE, request, status().isOk());
// 日志
checkLog("default-project-member-user-2", OperationLogType.UPDATE);
// 权限校验
request.setProjectId(DEFAULT_PROJECT_ID);
requestPostPermissionTest(PermissionConstants.PROJECT_USER_UPDATE, ADD_ROLE, request);
@ -237,8 +228,6 @@ public class ProjectMemberControllerTests extends BaseTest {
request.setProjectId("default-project-member-test");
request.setUserIds(List.of("default-project-member-user-1", "default-project-member-user-2"));
this.requestPost(BATCH_REMOVE_MEMBER, request, status().isOk());
// 日志
checkLog("default-project-member-user-1", OperationLogType.DELETE);
// 权限校验
request.setProjectId(DEFAULT_PROJECT_ID);
requestPostPermissionTest(PermissionConstants.PROJECT_USER_DELETE, BATCH_REMOVE_MEMBER, request);

View File

@ -1,5 +1,5 @@
-- 模拟数据
INSERT INTO `service_integration`(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES ('1', 'jira', b'1', '1111', '100002');
INSERT INTO `service_integration`(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES ('1', 'jira', b'1', 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '100002');
-- 模拟用户
@ -10,5 +10,22 @@ replace INTO user(id, name, email, password, create_time, update_time, language,
replace INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUES (UUID_SHORT(), 'wx-test', 'project_admin', '100001100001', '100001', 1684747668375, 'admin');
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('default-project-for-application', null, '100002', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
-- 集成信息
INSERT INTO project_application (project_id, type, type_value) VALUES
('default-project-for-application', 'BUG_DEFAULT_TEMPLATE', 'default-bug-template-id'),
('default-project-for-application', 'BUG_SYNC_BUG_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraBugTypeId":"10009"}'),
('default-project-for-application', 'BUG_SYNC_CRON_EXPRESSION', '0 0 0/1 * * ?'),
('default-project-for-application', 'BUG_SYNC_MECHANISM', 'increment'),
('default-project-for-application', 'BUG_SYNC_PLATFORM_KEY', 'jira'),
('default-project-for-application', 'BUG_SYNC_SYNC_ENABLE', 'true'),
('default-project-for-application', 'CASE_ENABLE', 'false'),
('default-project-for-application', 'CASE_RELATED_DEMAND_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraDemandTypeId":"10007"}'),
('default-project-for-application', 'CASE_RELATED_PLATFORM_KEY', 'jira');
INSERT INTO service_integration(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES
('621103810617345', 'jira', true, 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '100002');

View File

@ -8,3 +8,6 @@ VALUES ('test_template_id_2', 'functional_default', '', b'0', 1696992836000, 169
INSERT INTO template_custom_field(id, field_id, template_id, required, pos, api_field_id, default_value) VALUES ('100555929702891957', '100555929702891955', 'test_template_id_2', b'1', 0, NULL, NULL);
INSERT INTO custom_field(id, name, scene, type, remark, internal, scope_type, create_time, update_time, create_user, ref_id, enable_option_key, scope_id) VALUES ('100555929702891955', '测试自定义字段', 'FUNCTIONAL', 'SELECT', '', b'0', 'ORGANIZATION', 1698810592000, 1698810592000, 'admin', NULL, b'0', '100001');
INSERT INTO project_application (project_id, type, type_value) VALUES
('100001100001', 'BUG_SYNC_PLATFORM_KEY', 'jira');

Some files were not shown because too many files have changed in this diff Show More