feat: 集成Jira

This commit is contained in:
shiziyuan9527 2020-08-17 22:01:15 +08:00
parent 60ed8a0535
commit 240ac7ab26
3 changed files with 225 additions and 18 deletions

View File

@ -10,24 +10,26 @@ import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.EncryptUtils; import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.RestTemplateUtils; import io.metersphere.commons.utils.RestTemplateUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.ResultHolder; import io.metersphere.controller.ResultHolder;
import io.metersphere.controller.request.IntegrationRequest; import io.metersphere.controller.request.IntegrationRequest;
import io.metersphere.track.request.testcase.IssuesRequest; import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.service.TestCaseService; import io.metersphere.track.service.TestCaseService;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -48,10 +50,42 @@ public class IssuesService {
private ExtIssuesMapper extIssuesMapper; private ExtIssuesMapper extIssuesMapper;
public void testAuth() { public void testAuth(String platform) {
String url = "https://api.tapd.cn/quickstart/testauth"; if (StringUtils.equals(platform, IssuesManagePlatform.Tapd.toString())) {
ResultHolder call = call(url);
System.out.println(call.getData()); try {
String tapdConfig = platformConfig(IssuesManagePlatform.Tapd.toString());
JSONObject object = JSON.parseObject(tapdConfig);
String account = object.getString("account");
String password = object.getString("password");
HttpHeaders headers = auth(account, password);
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange("https://api.tapd.cn/quickstart/testauth", HttpMethod.GET, requestEntity, String.class);
} catch (Exception e) {
System.out.println(e);
LogUtil.error(e.getMessage(), e);
MSException.throwException("验证失败!");
}
} else {
try {
String config = platformConfig(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
String account = object.getString("account");
String password = object.getString("password");
String url = object.getString("url");
HttpHeaders headers = auth(account, password);
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange(url + "rest/api/2/issue/createmeta", HttpMethod.GET, requestEntity, String.class);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException("验证失败!");
}
}
} }
private ResultHolder call(String url) { private ResultHolder call(String url) {
@ -61,7 +95,7 @@ public class IssuesService {
private ResultHolder call(String url, HttpMethod httpMethod, Object params) { private ResultHolder call(String url, HttpMethod httpMethod, Object params) {
String responseJson; String responseJson;
String config = tapdConfig(); String config = platformConfig(IssuesManagePlatform.Tapd.toString());
JSONObject object = JSON.parseObject(config); JSONObject object = JSON.parseObject(config);
if (object == null) { if (object == null) {
@ -71,7 +105,7 @@ public class IssuesService {
String account = object.getString("account"); String account = object.getString("account");
String password = object.getString("password"); String password = object.getString("password");
HttpHeaders header = tapdAuth(account, password); HttpHeaders header = auth(account, password);
if (httpMethod.equals(HttpMethod.GET)) { if (httpMethod.equals(HttpMethod.GET)) {
responseJson = RestTemplateUtils.get(url, header); responseJson = RestTemplateUtils.get(url, header);
@ -88,7 +122,7 @@ public class IssuesService {
} }
private String tapdConfig() { private String platformConfig(String platform) {
SessionUser user = SessionUtils.getUser(); SessionUser user = SessionUtils.getUser();
String orgId = user.getLastOrganizationId(); String orgId = user.getLastOrganizationId();
@ -97,13 +131,13 @@ public class IssuesService {
MSException.throwException("organization id is null"); MSException.throwException("organization id is null");
} }
request.setOrgId(orgId); request.setOrgId(orgId);
request.setPlatform(IssuesManagePlatform.Tapd.toString()); request.setPlatform(platform);
ServiceIntegration integration = integrationService.get(request); ServiceIntegration integration = integrationService.get(request);
return integration.getConfiguration(); return integration.getConfiguration();
} }
private HttpHeaders tapdAuth(String apiUser, String password) { private HttpHeaders auth(String apiUser, String password) {
String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password); String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password);
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + authKey); headers.add("Authorization", "Basic " + authKey);
@ -118,10 +152,10 @@ public class IssuesService {
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString()); boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
String tapdId = getTapdProjectId(issuesRequest.getTestCaseId()); String tapdId = getTapdProjectId(issuesRequest.getTestCaseId());
String jiraId = ""; String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId());
if (tapd || jira) { if (tapd || jira) {
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraId)) { if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey)) {
MSException.throwException("集成了缺陷管理平台,但未进行项目关联!"); MSException.throwException("集成了缺陷管理平台,但未进行项目关联!");
} }
} }
@ -134,7 +168,9 @@ public class IssuesService {
} }
if (jira) { if (jira) {
// addJiraIssues(issuesRequest); if (StringUtils.isNotBlank(jiraKey)) {
addJiraIssues(issuesRequest);
}
} }
if (!tapd && !jira) { if (!tapd && !jira) {
@ -177,6 +213,72 @@ public class IssuesService {
issuesMapper.insert(issues); issuesMapper.insert(issues);
} }
public void addJiraIssues(IssuesRequest issuesRequest) {
String config = platformConfig(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
if (object == null) {
MSException.throwException("jira config is null");
}
String account = object.getString("account");
String password = object.getString("password");
String url = object.getString("url");
String auth = EncryptUtils.base64Encoding(account + ":" + password);
String testCaseId = issuesRequest.getTestCaseId();
String jiraKey = getJiraProjectKey(testCaseId);
if (StringUtils.isBlank(jiraKey)) {
MSException.throwException("未关联Jira 项目Key");
}
String json = "{\n" +
" \"fields\":{\n" +
" \"project\":{\n" +
" \"key\":\"" + jiraKey + "\"\n" +
" },\n" +
" \"summary\":\"" + issuesRequest.getTitle() + "\",\n" +
" \"description\":\"" + issuesRequest.getContent() + "\",\n" +
" \"issuetype\":{\n" +
" \"id\":\"10009\",\n" +
" \"name\":\"Defect\"\n" +
" }\n" +
" }\n" +
"}";
String result = addJiraIssue(url, auth, json);
JSONObject jsonObject = JSON.parseObject(result);
String id = jsonObject.getString("id");
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
// 插入缺陷表
Issues issues = new Issues();
issues.setId(id);
issues.setPlatform(IssuesManagePlatform.Jira.toString());
issuesMapper.insert(issues);
}
private String addJiraIssue(String url, String auth, String json) {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Authorization", "Basic " + auth);
requestHeaders.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
//HttpEntity
HttpEntity<String> requestEntity = new HttpEntity<>(json, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
//post
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "/rest/api/2/issue", HttpMethod.POST, requestEntity, String.class);
return responseEntity.getBody();
}
public void addLocalIssues(IssuesRequest request) { public void addLocalIssues(IssuesRequest request) {
SessionUser user = SessionUtils.getUser(); SessionUser user = SessionUtils.getUser();
String id = UUID.randomUUID().toString(); String id = UUID.randomUUID().toString();
@ -209,6 +311,39 @@ public class IssuesService {
return jsonObject.getObject("Bug", Issues.class); return jsonObject.getObject("Bug", Issues.class);
} }
public Issues getJiraIssues(HttpHeaders headers, String url, String issuesId) {
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
//post
ResponseEntity<String> responseEntity = null;
Issues issues = new Issues();
try {
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
JSONObject fields = (JSONObject) obj.get("fields");
JSONObject statusObj = (JSONObject) fields.get("status");
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
String id = obj.getString("id");
String title = fields.getString("summary");
String description = fields.getString("description");
String status = statusCategory.getString("key");
issues.setId(id);
issues.setTitle(title);
issues.setDescription(description);
issues.setStatus(status);
issues.setPlatform(IssuesManagePlatform.Jira.toString());
} catch (Exception e) {
return new Issues();
}
return issues;
}
public List<Issues> getIssues(String caseId) { public List<Issues> getIssues(String caseId) {
List<Issues> list = new ArrayList<>(); List<Issues> list = new ArrayList<>();
SessionUser user = SessionUtils.getUser(); SessionUser user = SessionUtils.getUser();
@ -227,7 +362,10 @@ public class IssuesService {
} }
if (jira) { if (jira) {
// getJiraIssues(caseId); String jiraKey = getJiraProjectKey(caseId);
if (StringUtils.isNotBlank(jiraKey)) {
list.addAll(getJiraIssues(caseId));
}
} }
list.addAll(getLocalIssues(caseId)); list.addAll(getLocalIssues(caseId));
@ -253,6 +391,7 @@ public class IssuesService {
.andTestCaseIdEqualTo(caseId) .andTestCaseIdEqualTo(caseId)
.andIssuesIdEqualTo(issuesId); .andIssuesIdEqualTo(issuesId);
testCaseIssuesMapper.deleteByExample(issuesExample); testCaseIssuesMapper.deleteByExample(issuesExample);
issuesMapper.deleteByPrimaryKey(issuesId);
} else { } else {
dto.setPlatform(IssuesManagePlatform.Tapd.toString()); dto.setPlatform(IssuesManagePlatform.Tapd.toString());
// 缺陷状态为 关闭则不显示 // 缺陷状态为 关闭则不显示
@ -264,6 +403,47 @@ public class IssuesService {
return list; return list;
} }
public List<Issues> getJiraIssues(String caseId) {
List<Issues> list = new ArrayList<>();
String config = platformConfig(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
if (object == null) {
MSException.throwException("tapd config is null");
}
String account = object.getString("account");
String password = object.getString("password");
String url = object.getString("url");
HttpHeaders headers = auth(account, password);
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(caseId);
List<Issues> issues = extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Jira.toString());
List<String> issuesIds = issues.stream().map(issue -> issue.getId()).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getJiraIssues(headers, url, issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
issuesExample.createCriteria()
.andTestCaseIdEqualTo(caseId)
.andIssuesIdEqualTo(issuesId);
testCaseIssuesMapper.deleteByExample(issuesExample);
issuesMapper.deleteByPrimaryKey(issuesId);
} else {
// 缺陷状态为 完成则不显示
if (!StringUtils.equals("done", dto.getStatus())) {
list.add(dto);
}
}
});
return list;
}
public List<Issues> getLocalIssues(String caseId) { public List<Issues> getLocalIssues(String caseId) {
return extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Local.toString()); return extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Local.toString());
} }
@ -274,6 +454,12 @@ public class IssuesService {
return project.getTapdId(); return project.getTapdId();
} }
public String getJiraProjectKey(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getJiraKey();
}
/** /**
* 是否关联平台 * 是否关联平台
*/ */

View File

@ -24,4 +24,10 @@ public class TestCaseIssuesController {
public List<Issues> getIssues(@PathVariable String id) { public List<Issues> getIssues(@PathVariable String id) {
return issuesService.getIssues(id); return issuesService.getIssues(id);
} }
@GetMapping("/auth/{platform}")
public void testAuth(@PathVariable String platform) {
issuesService.testAuth(platform);
}
} }

View File

@ -19,10 +19,15 @@
<el-input v-model="form.password" auto-complete="new-password" <el-input v-model="form.password" auto-complete="new-password"
:placeholder="$t('organization.input_api_password')" show-password/> :placeholder="$t('organization.input_api_password')" show-password/>
</el-form-item> </el-form-item>
<el-form-item label="JIRA 地址" prop="url" v-if="platform === 'Jira'">
<el-input v-model="form.url" placeholder="请输入Jira地址"/>
</el-form-item>
</el-form> </el-form>
</div> </div>
<div style="margin-left: 100px"> <div style="margin-left: 100px">
<el-button type="primary" size="small" :disabled="!show" @click="testConnection">{{$t('ldap.test_connect')}}
</el-button>
<el-button v-if="showEdit" size="small" @click="edit">{{$t('commons.edit')}}</el-button> <el-button v-if="showEdit" size="small" @click="edit">{{$t('commons.edit')}}</el-button>
<el-button type="primary" v-if="showSave" size="small" @click="save('form')">{{$t('commons.save')}}</el-button> <el-button type="primary" v-if="showSave" size="small" @click="save('form')">{{$t('commons.save')}}</el-button>
<el-button v-if="showCancel" size="small" @click="cancelEdit">取消编辑</el-button> <el-button v-if="showCancel" size="small" @click="cancelEdit">取消编辑</el-button>
@ -72,7 +77,8 @@
], ],
rules: { rules: {
account: {required: true, message: this.$t('organization.input_api_account'), trigger: ['change', 'blur']}, account: {required: true, message: this.$t('organization.input_api_account'), trigger: ['change', 'blur']},
password: {required: true, message: this.$t('organization.input_api_password'), trigger: ['change', 'blur']} password: {required: true, message: this.$t('organization.input_api_password'), trigger: ['change', 'blur']},
url: {required: true, message: '请输入url', trigger: ['change', 'blur']}
}, },
} }
}, },
@ -91,6 +97,7 @@
let config = JSON.parse(data.configuration); let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account); this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password); this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
} else { } else {
this.clear(); this.clear();
} }
@ -138,7 +145,8 @@
let param = {}; let param = {};
let auth = { let auth = {
account: this.form.account, account: this.form.account,
password: this.form.password password: this.form.password,
url: this.form.url
}; };
param.organizationId = getCurrentUser().lastOrganizationId; param.organizationId = getCurrentUser().lastOrganizationId;
param.platform = this.platform; param.platform = this.platform;
@ -172,6 +180,7 @@
let config = JSON.parse(data.configuration); let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account); this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password); this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
} else { } else {
this.clear(); this.clear();
} }
@ -180,9 +189,15 @@
clear() { clear() {
this.$set(this.form, 'account', ''); this.$set(this.form, 'account', '');
this.$set(this.form, 'password', ''); this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.form.clearValidate(); this.$refs.form.clearValidate();
}); });
},
testConnection() {
this.$get("issues/auth/" + this.platform, () => {
this.$success("验证通过!");
});
} }
} }
} }