fix: 解决冲突

This commit is contained in:
chenjianxing 2021-05-21 12:55:17 +08:00 committed by jianxing
parent 73d6dcd9ca
commit 4e3b2166d0
28 changed files with 518 additions and 70 deletions

View File

@ -21,5 +21,7 @@ public class CustomFieldTemplate implements Serializable {
private String customData; private String customData;
private Boolean isThirdPart;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -643,6 +643,66 @@ public class CustomFieldTemplateExample {
addCriterion("custom_data not between", value1, value2, "customData"); addCriterion("custom_data not between", value1, value2, "customData");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andIsThirdPartIsNull() {
addCriterion("is_third_part is null");
return (Criteria) this;
}
public Criteria andIsThirdPartIsNotNull() {
addCriterion("is_third_part is not null");
return (Criteria) this;
}
public Criteria andIsThirdPartEqualTo(Boolean value) {
addCriterion("is_third_part =", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartNotEqualTo(Boolean value) {
addCriterion("is_third_part <>", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartGreaterThan(Boolean value) {
addCriterion("is_third_part >", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartGreaterThanOrEqualTo(Boolean value) {
addCriterion("is_third_part >=", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartLessThan(Boolean value) {
addCriterion("is_third_part <", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartLessThanOrEqualTo(Boolean value) {
addCriterion("is_third_part <=", value, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartIn(List<Boolean> values) {
addCriterion("is_third_part in", values, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartNotIn(List<Boolean> values) {
addCriterion("is_third_part not in", values, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartBetween(Boolean value1, Boolean value2) {
addCriterion("is_third_part between", value1, value2, "isThirdPart");
return (Criteria) this;
}
public Criteria andIsThirdPartNotBetween(Boolean value1, Boolean value2) {
addCriterion("is_third_part not between", value1, value2, "isThirdPart");
return (Criteria) this;
}
} }
public static class Criteria extends GeneratedCriteria { public static class Criteria extends GeneratedCriteria {

View File

@ -10,6 +10,7 @@
<result column="order" jdbcType="INTEGER" property="order" /> <result column="order" jdbcType="INTEGER" property="order" />
<result column="default_value" jdbcType="VARCHAR" property="defaultValue" /> <result column="default_value" jdbcType="VARCHAR" property="defaultValue" />
<result column="custom_data" jdbcType="VARCHAR" property="customData" /> <result column="custom_data" jdbcType="VARCHAR" property="customData" />
<result column="is_third_part" jdbcType="BIT" property="isThirdPart" />
</resultMap> </resultMap>
<sql id="Example_Where_Clause"> <sql id="Example_Where_Clause">
<where> <where>
@ -70,7 +71,8 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, field_id, template_id, scene, required, `order`, default_value, custom_data id, field_id, template_id, scene, required, `order`, default_value, custom_data,
is_third_part
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.CustomFieldTemplateExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.base.domain.CustomFieldTemplateExample" resultMap="BaseResultMap">
select select
@ -105,10 +107,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.CustomFieldTemplate"> <insert id="insert" parameterType="io.metersphere.base.domain.CustomFieldTemplate">
insert into custom_field_template (id, field_id, template_id, insert into custom_field_template (id, field_id, template_id,
scene, required, `order`, scene, required, `order`,
default_value, custom_data) default_value, custom_data, is_third_part
)
values (#{id,jdbcType=VARCHAR}, #{fieldId,jdbcType=VARCHAR}, #{templateId,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{fieldId,jdbcType=VARCHAR}, #{templateId,jdbcType=VARCHAR},
#{scene,jdbcType=VARCHAR}, #{required,jdbcType=BIT}, #{order,jdbcType=INTEGER}, #{scene,jdbcType=VARCHAR}, #{required,jdbcType=BIT}, #{order,jdbcType=INTEGER},
#{defaultValue,jdbcType=VARCHAR}, #{customData,jdbcType=VARCHAR}) #{defaultValue,jdbcType=VARCHAR}, #{customData,jdbcType=VARCHAR}, #{isThirdPart,jdbcType=BIT}
)
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.CustomFieldTemplate"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.CustomFieldTemplate">
insert into custom_field_template insert into custom_field_template
@ -137,6 +141,9 @@
<if test="customData != null"> <if test="customData != null">
custom_data, custom_data,
</if> </if>
<if test="isThirdPart != null">
is_third_part,
</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null"> <if test="id != null">
@ -163,6 +170,9 @@
<if test="customData != null"> <if test="customData != null">
#{customData,jdbcType=VARCHAR}, #{customData,jdbcType=VARCHAR},
</if> </if>
<if test="isThirdPart != null">
#{isThirdPart,jdbcType=BIT},
</if>
</trim> </trim>
</insert> </insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.CustomFieldTemplateExample" resultType="java.lang.Long"> <select id="countByExample" parameterType="io.metersphere.base.domain.CustomFieldTemplateExample" resultType="java.lang.Long">
@ -198,6 +208,9 @@
<if test="record.customData != null"> <if test="record.customData != null">
custom_data = #{record.customData,jdbcType=VARCHAR}, custom_data = #{record.customData,jdbcType=VARCHAR},
</if> </if>
<if test="record.isThirdPart != null">
is_third_part = #{record.isThirdPart,jdbcType=BIT},
</if>
</set> </set>
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
@ -212,7 +225,8 @@
required = #{record.required,jdbcType=BIT}, required = #{record.required,jdbcType=BIT},
`order` = #{record.order,jdbcType=INTEGER}, `order` = #{record.order,jdbcType=INTEGER},
default_value = #{record.defaultValue,jdbcType=VARCHAR}, default_value = #{record.defaultValue,jdbcType=VARCHAR},
custom_data = #{record.customData,jdbcType=VARCHAR} custom_data = #{record.customData,jdbcType=VARCHAR},
is_third_part = #{record.isThirdPart,jdbcType=BIT}
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
</if> </if>
@ -241,6 +255,9 @@
<if test="customData != null"> <if test="customData != null">
custom_data = #{customData,jdbcType=VARCHAR}, custom_data = #{customData,jdbcType=VARCHAR},
</if> </if>
<if test="isThirdPart != null">
is_third_part = #{isThirdPart,jdbcType=BIT},
</if>
</set> </set>
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
@ -252,7 +269,8 @@
required = #{required,jdbcType=BIT}, required = #{required,jdbcType=BIT},
`order` = #{order,jdbcType=INTEGER}, `order` = #{order,jdbcType=INTEGER},
default_value = #{defaultValue,jdbcType=VARCHAR}, default_value = #{defaultValue,jdbcType=VARCHAR},
custom_data = #{customData,jdbcType=VARCHAR} custom_data = #{customData,jdbcType=VARCHAR},
is_third_part = #{isThirdPart,jdbcType=BIT}
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
</mapper> </mapper>

View File

@ -10,7 +10,7 @@
<select id="list" resultType="io.metersphere.dto.CustomFieldTemplateDao"> <select id="list" resultType="io.metersphere.dto.CustomFieldTemplateDao">
select select
field_id, template_id, required, default_value, custom_data, field_id, template_id, required, default_value, custom_data,
cft.id as id, cft.id as id, is_third_part,
cf.name as name, cf.type as type, cf.remark as remark, cf.`system` as system, cf.options as options cf.name as name, cf.type as type, cf.remark as remark, cf.`system` as system, cf.options as options
from custom_field_template cft from custom_field_template cft
inner join custom_field cf inner join custom_field cf

View File

@ -12,4 +12,6 @@ public class CustomFieldDao extends CustomField {
private String defaultValue; private String defaultValue;
private String customData; private String customData;
private Boolean isThirdPart;
} }

View File

@ -8,4 +8,5 @@ public class CustomFieldItemDTO {
private String name; private String name;
private String value; private String value;
private String customData; private String customData;
private Boolean isThirdPart;
} }

View File

@ -16,4 +16,6 @@ public class CustomFieldTemplateDao extends CustomFieldTemplate {
private String options; private String options;
private Boolean system; private Boolean system;
private Boolean isThirdPart;
} }

View File

@ -168,6 +168,7 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
issues.setPlatform(issuesRequest.getPlatform()); issues.setPlatform(issuesRequest.getPlatform());
issues.setProjectId(issuesRequest.getProjectId()); issues.setProjectId(issuesRequest.getProjectId());
issues.setCustomFields(issuesRequest.getCustomFields()); issues.setCustomFields(issuesRequest.getCustomFields());
issues.setCreator(issuesRequest.getCreator());
issues.setCreateTime(System.currentTimeMillis()); issues.setCreateTime(System.currentTimeMillis());
issues.setUpdateTime(System.currentTimeMillis()); issues.setUpdateTime(System.currentTimeMillis());
issuesMapper.insert(issues); issuesMapper.insert(issues);

View File

@ -9,8 +9,10 @@ import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils; import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.CustomFieldItemDTO;
import io.metersphere.track.dto.DemandDTO; import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser; import io.metersphere.track.issue.client.JiraClient;
import io.metersphere.track.issue.domain.*;
import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest; import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -40,6 +42,15 @@ public class JiraPlatform extends AbstractIssuePlatform {
super(issuesRequest); super(issuesRequest);
} }
public JiraConfig getConfig() {
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
if (StringUtils.isNotBlank(config)) {
return JSONObject.parseObject(config, JiraConfig.class);
} else {
return null;
}
}
@Override @Override
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) { public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
List<IssuesDao> list = new ArrayList<>(); List<IssuesDao> list = new ArrayList<>();
@ -51,15 +62,11 @@ public class JiraPlatform extends AbstractIssuePlatform {
} else { } else {
issues = extIssuesMapper.getIssuesByCaseId(issuesRequest); issues = extIssuesMapper.getIssuesByCaseId(issuesRequest);
} }
JiraConfig config = getConfig();
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString()); JiraClient.setConfig(config);
JSONObject object = JSON.parseObject(config);
HttpHeaders headers = getAuthHeader(object);
String url = object.getString("url");
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList()); List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> { issuesIds.forEach(issuesId -> {
IssuesDao dto = getJiraIssues(headers, url, issuesId); IssuesDao dto = parseIssue(JiraClient.getIssues(issuesId));
if (StringUtils.isBlank(dto.getId())) { if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联 // 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample(); TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
@ -78,6 +85,38 @@ public class JiraPlatform extends AbstractIssuePlatform {
return list; return list;
} }
public IssuesDao parseIssue(JiraIssue jiraIssue) {
String lastmodify = "";
String status = "";
JSONObject fields = jiraIssue.getFields();
JSONObject statusObj = (JSONObject) fields.get("status");
JSONObject assignee = (JSONObject) fields.get("assignee");
if (statusObj != null) {
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
status = statusCategory.getString("key");
}
String description = fields.getString("description");
Parser parser = Parser.builder().build();
Node document = parser.parse(description);
HtmlRenderer renderer = HtmlRenderer.builder().build();
description = renderer.render(document);
if (assignee != null) {
lastmodify = assignee.getString("displayName");
}
IssuesDao issues = new IssuesDao();
issues.setId(jiraIssue.getKey());
issues.setTitle(fields.getString("summary"));
issues.setCreateTime(fields.getLong("created"));
issues.setLastmodify(lastmodify);
issues.setDescription(description);
issues.setStatus(status);
issues.setPlatform(IssuesManagePlatform.Jira.toString());
return issues;
}
public HttpHeaders getAuthHeader(JSONObject object) { public HttpHeaders getAuthHeader(JSONObject object) {
if (object == null) { if (object == null) {
MSException.throwException("tapd config is null"); MSException.throwException("tapd config is null");
@ -164,26 +203,18 @@ public class JiraPlatform extends AbstractIssuePlatform {
@Override @Override
public void addIssue(IssuesUpdateRequest issuesRequest) { public void addIssue(IssuesUpdateRequest issuesRequest) {
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
issuesRequest.setPlatform(IssuesManagePlatform.Jira.toString()); issuesRequest.setPlatform(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
if (object == null) { JiraConfig config = getConfig();
if (config == null) {
MSException.throwException("jira config is null"); MSException.throwException("jira config is null");
} }
if (StringUtils.isBlank(config.getIssuetype())) {
String account = object.getString("account");
String password = object.getString("password");
String url = object.getString("url");
String issuetype = object.getString("issuetype");
if (StringUtils.isBlank(issuetype)) {
MSException.throwException("Jira 问题类型为空"); MSException.throwException("Jira 问题类型为空");
} }
String auth = EncryptUtils.base64Encoding(account + ":" + password);
String jiraKey = getProjectId(issuesRequest.getProjectId()); String jiraKey = getProjectId(issuesRequest.getProjectId());
if (StringUtils.isBlank(jiraKey)) { if (StringUtils.isBlank(jiraKey)) {
MSException.throwException("未关联Jira 项目Key"); MSException.throwException("未关联Jira 项目Key");
} }
@ -198,30 +229,47 @@ public class JiraPlatform extends AbstractIssuePlatform {
String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false)); String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
desc = desc.replace("&nbsp;", ""); desc = desc.replace("&nbsp;", "");
String json = "{\n" + JSONObject addJiraIssueParam = new JSONObject();
" \"fields\":{\n" + JSONObject fields = new JSONObject();
" \"project\":{\n" + JSONObject project = new JSONObject();
" \"key\":\"" + jiraKey + "\"\n" +
" },\n" +
" \"summary\":\"" + issuesRequest.getTitle() + "\",\n" +
" \"description\": " + JSON.toJSONString(desc) + ",\n" +
" \"issuetype\":{\n" +
" \"name\":\"" + issuetype + "\"\n" +
" }\n" +
" }\n" +
"}";
String result = addJiraIssue(url, auth, json);
JSONObject jsonObject = JSON.parseObject(result); fields.put("project", project);
String id = jsonObject.getString("key"); project.put("key", jiraKey);
issuesRequest.setId(id); JSONObject issuetype = new JSONObject();
issuetype.put("name", config.getIssuetype());
fields.put("summary", issuesRequest.getTitle());
fields.put("description", new JiraIssueDescription(desc));
fields.put("issuetype", issuetype);
addJiraIssueParam.put("fields", fields);
List<CustomFieldItemDTO> customFields = getCustomFields(issuesRequest.getCustomFields());
JiraClient.setConfig(config);
// List<JiraField> jiraFields = JiraClient.getFields();
// Map<String, Boolean> isCustomMap = jiraFields.stream().
// collect(Collectors.toMap(JiraField::getId, JiraField::isCustom));
customFields.forEach(item -> {
if (StringUtils.isNotBlank(item.getCustomData()) && item.getIsThirdPart() != null && item.getIsThirdPart()) {
// if (isCustomMap.get(item.getCustomData())) {
// fields.put(item.getCustomData(), item.getValue());
// } else {
JSONObject param = new JSONObject();
param.put("id", item.getValue());
fields.put(item.getCustomData(), param);
// }
}
});
JiraAddIssueResponse result = JiraClient.addIssue(JSONObject.toJSONString(addJiraIssueParam));
issuesRequest.setId(result.getKey());
// 用例与第三方缺陷平台中的缺陷关联 // 用例与第三方缺陷平台中的缺陷关联
handleTestCaseIssues(issuesRequest); handleTestCaseIssues(issuesRequest);
// 插入缺陷表 // 插入缺陷表
insertIssuesWithoutContext(id, issuesRequest); insertIssuesWithoutContext(result.getKey(), issuesRequest);
} }
@Override @Override

View File

@ -0,0 +1,68 @@
package io.metersphere.track.issue.client;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public abstract class BaseClient {
protected static RestTemplate restTemplate;
static {
try {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate = new RestTemplate(requestFactory);
} catch (Exception e) {
LogUtil.error(e);
}
}
protected static HttpHeaders getBasicHttpHeaders(String userName, String passWd) {
String authKey = EncryptUtils.base64Encoding(userName + ":" + passWd);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + authKey);
return headers;
}
protected static String getResult(ResponseEntity<String> response) {
int statusCodeValue = response.getStatusCodeValue();
LogUtil.info("responseCode: " + statusCodeValue);
if(statusCodeValue >= 400){
MSException.throwException(response.getBody());
}
LogUtil.info("result: " + response.getBody());
return response.getBody();
}
protected static Object getResultForList(Class clazz, ResponseEntity<String> response) {
return Arrays.asList(JSONArray.parseArray(getResult(response), clazz).toArray());
}
protected static Object getResultForObject(Class clazz,ResponseEntity<String> response) {
return JSONObject.parseObject(getResult(response), clazz);
}
}

View File

@ -0,0 +1,70 @@
package io.metersphere.track.issue.client;
import io.metersphere.commons.exception.MSException;
import io.metersphere.track.issue.domain.JiraAddIssueResponse;
import io.metersphere.track.issue.domain.JiraConfig;
import io.metersphere.track.issue.domain.JiraField;
import io.metersphere.track.issue.domain.JiraIssue;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.util.MultiValueMap;
import java.util.List;
public class JiraClient extends BaseClient {
private static String ENDPOINT;
private static String PREFIX = "/rest/api/3";
private static String USER_NAME;
private static String PASSWD;
public static List<JiraField> getFields() {
String url = getBaseUrl() + "/field";
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, getAuthHttpEntity(), String.class);
return (List<JiraField>) getResultForList(JiraField.class, response);
}
public static JiraIssue getIssues(String issuesId) {
HttpEntity<MultiValueMap> requestEntity = getAuthHttpEntity();
ResponseEntity<String> responseEntity;
responseEntity = restTemplate.exchange(ENDPOINT + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class);
return (JiraIssue) getResultForObject(JiraIssue.class, responseEntity);
}
public static JiraAddIssueResponse addIssue(String body) {
HttpHeaders headers = getAuthHeader();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> entity = restTemplate.exchange(getBaseUrl() + "/issue", HttpMethod.POST, requestEntity, String.class);
return (JiraAddIssueResponse) getResultForObject(JiraAddIssueResponse.class, entity);
}
public static void setConfig(JiraConfig config) {
if (config == null) {
MSException.throwException("config is null");
}
String url = config.getUrl();
if (StringUtils.isNotBlank(url) && url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
ENDPOINT = url;
USER_NAME = config.getAccount();
PASSWD = config.getPassword();
}
private static HttpEntity<MultiValueMap> getAuthHttpEntity() {
return new HttpEntity<>(getAuthHeader());
}
private static HttpHeaders getAuthHeader() {
return getBasicHttpHeaders(USER_NAME, PASSWD);
}
private static String getBaseUrl() {
return ENDPOINT + PREFIX;
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.track.issue.domain;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class JiraAddIssueResponse {
private String id;
private String key;
private String self;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.track.issue.domain;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class JiraConfig {
private String account;
private String password;
private String url;
private String issuetype;
private String storytype;
}

View File

@ -0,0 +1,28 @@
package io.metersphere.track.issue.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class JiraField {
private String id;
private String key;
private String name;
private boolean custom;
// private boolean orderable;
// private boolean navigable;
// private boolean searchable;
// private List<String> clauseNames;
// private Schema schema;
//
// @Getter
// @Setter
// public class Schema {
// private String type;
// private String system;
// }
}

View File

@ -0,0 +1,15 @@
package io.metersphere.track.issue.domain;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class JiraIssue {
private String expand;
private String id;
private String self;
private String key;
private JSONObject fields;
}

View File

@ -0,0 +1,52 @@
package io.metersphere.track.issue.domain;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class JiraIssueDescription {
private String type;
private int version;
private List<Content> content;
public JiraIssueDescription(String text) {
List<Content> list = new ArrayList<>();
Content content = new Content(text);
list.add(content);
this.type = "doc";
this.version = 1;
this.content = list;
}
@Data
@NoArgsConstructor
public class Content {
private String type;
private List<ContentInfo> content;
public Content(String text) {
List<ContentInfo> list = new ArrayList<>();
ContentInfo content = new ContentInfo(text);
list.add(content);
this.type = "paragraph";
this.content = list;
}
}
@Data
@NoArgsConstructor
public class ContentInfo {
private String text;
private String type;
public ContentInfo(String text) {
this.text = text;
this.type = "text";
}
}
}

View File

@ -78,6 +78,7 @@
<table tableName="test_plan"/> <table tableName="test_plan"/>
<table tableName="test_case_test"/>--> <table tableName="test_case_test"/>-->
<table tableName="api_test_environment"></table> <table tableName="api_test_environment"></table>
<table tableName="custom_field_template"></table>
<!-- <table tableName="custom_field"></table>--> <!-- <table tableName="custom_field"></table>-->
<!-- <table tableName="test_case"></table>--> <!-- <table tableName="test_case"></table>-->
<!-- <table tableName="test_case"></table>--> <!-- <table tableName="test_case"></table>-->

View File

@ -3,9 +3,10 @@
<div> <div>
<slot name="header"> <slot name="header">
<el-link class="add-text" :underline="false" :disabled="disable" @click="add"> <el-link class="add-text" :underline="false" :disabled="disable" @click="add">
<i class="el-icon-plus">添加选项</i> <i class="el-icon-plus">{{$t('custom_field.add_option')}}</i>
</el-link> </el-link>
</slot> </slot>
<ms-instructions-icon size="13" v-if="isKv" :content="$t('选项值用于对接Jira等平台提交缺陷时对应字段的属性值')"/>
</div> </div>
<draggable :list="data" handle=".handle" class="list-group"> <draggable :list="data" handle=".handle" class="list-group">
@ -16,17 +17,33 @@
<el-input size="mini" type="text" <el-input size="mini" type="text"
class="text-item" class="text-item"
v-if="editIndex === idx" :placeholder="$t('custom_field.field_text')"
@blur="handleEdit(element)" v-if="editIndex === idx && isKv"
v-model="element.value"/> @blur="handleTextEdit(element)"
<span class="text-item" v-else> v-model="element.text"/>
<span class="text-item" v-else-if="isKv">
<span v-if="element.system"> <span v-if="element.system">
{{$t(element.text)}} ({{$t(element.text)}})
</span> </span>
<span v-else> <span v-else>
{{element.text}} {{element.text}}
</span> </span>
</span> </span>
<el-input size="mini" type="value"
class="text-item"
:placeholder="$t('custom_field.field_value')"
v-if="editIndex === idx"
@blur="handleValueEdit(element)"
v-model="element.value"/>
<span class="text-item" v-else>
<span v-if="element.system">
{{$t(element.value)}}
</span>
<span v-else>
{{ (element.value && isKv ? '(' : '') + element.value + (element.value && isKv ? ')' : '')}}
</span>
</span>
<i class="operator-icon" v-for="(item, index) in operators" <i class="operator-icon" v-for="(item, index) in operators"
:key="index" :key="index"
:class="item.icon" :class="item.icon"
@ -41,9 +58,11 @@
<script> <script>
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import MsInstructionsIcon from "@/business/components/common/components/MsInstructionsIcon";
export default { export default {
name: "MsSingleHandleDrag", name: "MsSingleHandleDrag",
components: { components: {
MsInstructionsIcon,
draggable draggable
}, },
data() { data() {
@ -53,6 +72,7 @@ export default {
}, },
props: { props: {
disable: Boolean, disable: Boolean,
isKv: Boolean,
data: { data: {
type: Array, type: Array,
default() { default() {
@ -99,9 +119,17 @@ export default {
this.data.push(item); this.data.push(item);
this.editIndex = this.data.length - 1; this.editIndex = this.data.length - 1;
}, },
handleEdit(element) { handleTextEdit(element) {
this.editIndex = -1; if (!this.isKv) {
element.text = element.value; element.text = element.value;
this.editIndex = -1;
}
},
handleValueEdit(element) {
this.editIndex = -1;
if (!this.isKv) {
element.text = element.value;
}
}, },
isSystem(element) { isSystem(element) {
if (element.system) { if (element.system) {
@ -145,4 +173,8 @@ export default {
font-size: 13px; font-size: 13px;
font-weight: bold; font-weight: bold;
} }
.instructions-icon {
margin-left: 5px;
}
</style> </style>

View File

@ -44,6 +44,7 @@
:label="$t('custom_field.field_option')" :label="$t('custom_field.field_option')"
prop="options" :label-width="labelWidth"> prop="options" :label-width="labelWidth">
<ms-single-handle-drag <ms-single-handle-drag
:is-kv="form.scene === 'ISSUE'"
:disable="form.name === '用例等级'" :disable="form.name === '用例等级'"
:data="form.options"/> :data="form.options"/>
</el-form-item> </el-form-item>

View File

@ -57,6 +57,16 @@
</template> </template>
</ms-table-column> </ms-table-column>
<ms-table-column
v-if="platform !== 'metersphere'"
:label="platform + $t('custom_field.field')"
width="100"
prop="thirdPart">
<template v-slot="scope">
<el-checkbox v-model="scope.row.isThirdPart"/>
</template>
</ms-table-column>
<ms-table-column <ms-table-column
:label="$t('commons.remark')" :label="$t('commons.remark')"
prop="remark"> prop="remark">
@ -97,6 +107,7 @@ export default {
}, },
}, },
scene: String, scene: String,
platform: String,
templateContainIds: Set templateContainIds: Set
}, },
watch: { watch: {
@ -128,6 +139,7 @@ export default {
} }
item.fieldId = item.id; item.fieldId = item.id;
item.id = null; item.id = null;
item.isThirdPart = false;
item.options = JSON.parse(item.options); item.options = JSON.parse(item.options);
if (item.type === 'checkbox') { if (item.type === 'checkbox') {
item.defaultValue = []; item.defaultValue = [];

View File

@ -167,16 +167,7 @@ export default {
this.visible = true; this.visible = true;
}, },
save() { save() {
if (this.condition.selectAll) {
if (this.scene) {
// this.result = this.$post('custom/field/list/ids' + this.currentPage + '/' + this.pageSize,
// this.condition, (response) => {
// this.$emit('save', response.data);
// });
}
} else {
this.$emit('save', this.$refs.table.selectIds); this.$emit('save', this.$refs.table.selectIds);
}
this.visible = false; this.visible = false;
}, },
} }

View File

@ -41,6 +41,7 @@
<custom-field-form-list <custom-field-form-list
:table-data="relateFields" :table-data="relateFields"
:scene="scene" :scene="scene"
:platform="form.platform"
:template-contain-ids="templateContainIds" :template-contain-ids="templateContainIds"
:custom-field-ids="form.customFieldIds" :custom-field-ids="form.customFieldIds"
ref="customFieldFormList" ref="customFieldFormList"

View File

@ -140,7 +140,7 @@ export default {
for (let i = 0; i < this.tableData.length; i++) { for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i]) { if (this.tableData[i]) {
if (this.tableData[i].platform !== 'Local') { if (this.tableData[i].platform !== 'Local') {
this.$post("issues/get/platform/issue", this.tableData[i]).then(response => { this.result = this.$post("issues/get/platform/issue", this.tableData[i]).then(response => {
let issues = response.data.data; let issues = response.data.data;
if (issues) { if (issues) {
this.$set(this.tableData[i], "title", issues.title ? issues.title : "--"); this.$set(this.tableData[i], "title", issues.title ? issues.title : "--");

View File

@ -102,11 +102,13 @@ textarea {
/* 表格拖拽表头调整宽度 --> */ /* 表格拖拽表头调整宽度 --> */
/* <-- 表格 input 编辑效果 */ /* <-- 表格 input 编辑效果 */
.table-edit-input .el-textarea__inner { .table-edit-input .el-textarea__inner,
.table-edit-input .el-input__inner {
border-style: hidden; border-style: hidden;
} }
.table-edit-input.is-disabled .el-textarea__inner { .table-edit-input.is-disabled .el-textarea__inner,
.table-edit-input.is-disabled .el-input__inner {
background-color: white; background-color: white;
color: #606266; color: #606266;
} }
@ -116,7 +118,8 @@ textarea {
border-radius: 5px; border-radius: 5px;
} }
.table-edit-input .el-textarea__inner:focus { .table-edit-input .el-textarea__inner:focus,
.table-edit-input .el-input__inner:focus {
border: 1px solid #409EFF; border: 1px solid #409EFF;
} }

View File

@ -93,6 +93,7 @@ export function buildCustomFields(data, param, template) {
hasField = true; hasField = true;
customFields[index].name = item.name; customFields[index].name = item.name;
customFields[index].value = item.defaultValue; customFields[index].value = item.defaultValue;
customFields[index].isThirdPart = item.isThirdPart;
break; break;
} }
} }
@ -101,7 +102,8 @@ export function buildCustomFields(data, param, template) {
id: item.id, id: item.id,
name: item.name, name: item.name,
value: item.defaultValue, value: item.defaultValue,
customData: item.customData customData: item.customData,
isThirdPart: item.isThirdPart
}; };
customFields.push(customField); customFields.push(customField);
} }

View File

@ -279,6 +279,7 @@ export default {
} }
}, },
custom_field: { custom_field: {
add_option: 'Add Option',
case_status: 'Case Status', case_status: 'Case Status',
case_maintainer: 'Maintainer', case_maintainer: 'Maintainer',
case_priority: 'Case Priority', case_priority: 'Case Priority',
@ -292,9 +293,12 @@ export default {
scene: 'Use Scene', scene: 'Use Scene',
attribute_type: 'Attribute Type', attribute_type: 'Attribute Type',
field_name: 'Field Name', field_name: 'Field Name',
field: 'Field',
field_remark: 'Field Remark', field_remark: 'Field Remark',
field_type: 'Field Type', field_type: 'Field Type',
field_option: 'Options', field_option: 'Options',
field_text: 'Field Text',
field_value: 'Field Value',
add_field: 'Add Field', add_field: 'Add Field',
api_field_name: 'API Field Name', api_field_name: 'API Field Name',
template_setting: 'Template Setting', template_setting: 'Template Setting',
@ -386,7 +390,7 @@ export default {
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"', use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)', use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
use_tip_zentao: 'The account password is a Zentao account with corresponding permissions, and the account needs to have super model calling interface permissions', use_tip_zentao: 'The account password is a Zentao account with corresponding permissions, and the account needs to have super model calling interface permissions',
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the MeterSphere project', use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key and issue template in the MeterSphere project',
link_the_project_now: 'Link the project now', link_the_project_now: 'Link the project now',
cancel_edit: 'Cancel edit', cancel_edit: 'Cancel edit',
cancel_integration: 'Cancel integration', cancel_integration: 'Cancel integration',

View File

@ -280,6 +280,7 @@ export default {
} }
}, },
custom_field: { custom_field: {
add_option: '添加选项',
case_status: '用例状态', case_status: '用例状态',
case_maintainer: '责任人', case_maintainer: '责任人',
case_priority: '用例等级', case_priority: '用例等级',
@ -293,9 +294,12 @@ export default {
scene: '使用场景', scene: '使用场景',
attribute_type: '属性类型', attribute_type: '属性类型',
field_name: '字段名', field_name: '字段名',
field: '字段',
field_remark: '字段备注', field_remark: '字段备注',
field_type: '字段类型', field_type: '字段类型',
field_option: '选项值', field_option: '选项值',
field_text: '选项内容',
field_value: '选项值',
add_field: '添加字段', add_field: '添加字段',
api_field_name: 'API字段名', api_field_name: 'API字段名',
template_setting: '模板设置', template_setting: '模板设置',
@ -384,7 +388,7 @@ export default {
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询', use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)', use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
use_tip_zentao: '账号密码为具有相应权限的Zentao账号账号需要具有 超级model调用接口权限', use_tip_zentao: '账号密码为具有相应权限的Zentao账号账号需要具有 超级model调用接口权限',
use_tip_two: '保存 Basic Auth 账号信息后,需要在 MeterSphere 项目中手动关联 ID/key', use_tip_two: '保存 Basic Auth 账号信息后,需要在 MeterSphere 项目中手动关联 ID/key 和缺陷模板',
link_the_project_now: '马上关联项目', link_the_project_now: '马上关联项目',
cancel_edit: '取消编辑', cancel_edit: '取消编辑',
cancel_integration: '取消集成', cancel_integration: '取消集成',

View File

@ -280,6 +280,7 @@ export default {
} }
}, },
custom_field: { custom_field: {
add_option: '添加選項',
case_status: '用例狀態', case_status: '用例狀態',
case_maintainer: '責任人', case_maintainer: '責任人',
case_priority: '用例等級', case_priority: '用例等級',
@ -293,9 +294,12 @@ export default {
scene: '使用場景', scene: '使用場景',
attribute_type: '屬性類型', attribute_type: '屬性類型',
field_name: '字段名', field_name: '字段名',
field: '字段名',
field_remark: '字段備註', field_remark: '字段備註',
field_type: '字段類型', field_type: '字段類型',
field_option: '選項值', field_option: '選項值',
field_text: '選項內容',
field_value: '選項值',
add_field: '添加字段', add_field: '添加字段',
api_field_name: 'API字段名', api_field_name: 'API字段名',
template_setting: '模板設置', template_setting: '模板設置',
@ -384,7 +388,7 @@ export default {
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢', use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)', use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
use_tip_zentao: '賬號密碼為具有相應權限的Zentao賬號賬號需要具有 超級model調用接口權限', use_tip_zentao: '賬號密碼為具有相應權限的Zentao賬號賬號需要具有 超級model調用接口權限',
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 MeterSphere 項目中手動關聯 ID/key', use_tip_two: '保存 Basic Auth 賬號信息後,需要在 MeterSphere 項目中手動關聯 ID/key 和缺陷模板',
link_the_project_now: '馬上關聯項目', link_the_project_now: '馬上關聯項目',
cancel_edit: '取消編輯', cancel_edit: '取消編輯',
cancel_integration: '取消集成', cancel_integration: '取消集成',