fix(测试跟踪): 缺陷同步异步执行
--bug=1025655 --user=宋昌昌 【测试跟踪】项目集成jira平台,缺陷管理中同步缺陷失败 https://www.tapd.cn/55049933/s/1365554
This commit is contained in:
parent
bed9bba9ed
commit
ff3fa223b2
|
@ -0,0 +1,15 @@
|
||||||
|
package io.metersphere.base.domain;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class IssueSyncCheckResult implements Serializable {
|
||||||
|
|
||||||
|
private Boolean syncComplete;
|
||||||
|
|
||||||
|
private String syncResult;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package io.metersphere.controller;
|
||||||
|
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
|
import io.metersphere.base.domain.IssueSyncCheckResult;
|
||||||
import io.metersphere.base.domain.Issues;
|
import io.metersphere.base.domain.Issues;
|
||||||
import io.metersphere.base.domain.IssuesWithBLOBs;
|
import io.metersphere.base.domain.IssuesWithBLOBs;
|
||||||
import io.metersphere.base.domain.Project;
|
import io.metersphere.base.domain.Project;
|
||||||
|
@ -157,17 +158,17 @@ public class IssuesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/sync/{projectId}")
|
@GetMapping("/sync/{projectId}")
|
||||||
public boolean syncThirdPartyIssues(@PathVariable String projectId) {
|
public void syncThirdPartyIssues(@PathVariable String projectId) {
|
||||||
return issuesService.syncThirdPartyIssues(projectId);
|
issuesService.syncThirdPartyIssues(projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/sync/all")
|
@PostMapping("/sync/all")
|
||||||
public boolean syncThirdPartyAllIssues(@RequestBody IssueSyncRequest request) {
|
public void syncThirdPartyAllIssues(@RequestBody IssueSyncRequest request) {
|
||||||
return issuesService.syncThirdPartyAllIssues(request);
|
issuesService.syncThirdPartyAllIssues(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/sync/check/{projectId}")
|
@GetMapping("/sync/check/{projectId}")
|
||||||
public boolean checkSync(@PathVariable String projectId) {
|
public IssueSyncCheckResult checkSync(@PathVariable String projectId) {
|
||||||
return issuesService.checkSync(projectId);
|
return issuesService.checkSync(projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ public class IssuesService {
|
||||||
private IssuesService issuesService;
|
private IssuesService issuesService;
|
||||||
|
|
||||||
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
|
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
|
||||||
|
private static final String SYNC_THIRD_PARTY_ISSUES_ERROR_KEY = "ISSUE:SYNC:ERROR";
|
||||||
|
|
||||||
public void testAuth(String workspaceId, String platform) {
|
public void testAuth(String workspaceId, String platform) {
|
||||||
IssuesRequest issuesRequest = new IssuesRequest();
|
IssuesRequest issuesRequest = new IssuesRequest();
|
||||||
|
@ -899,12 +900,20 @@ public class IssuesService {
|
||||||
LogUtil.info("测试计划-测试用例同步缺陷信息结束");
|
LogUtil.info("测试计划-测试用例同步缺陷信息结束");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkSync(String projectId) {
|
public IssueSyncCheckResult checkSync(String projectId) {
|
||||||
|
IssueSyncCheckResult issueSyncCheckResult = IssueSyncCheckResult.builder().syncComplete(Boolean.FALSE).syncResult(StringUtils.EMPTY).build();
|
||||||
String syncValue = getSyncKey(projectId);
|
String syncValue = getSyncKey(projectId);
|
||||||
if (StringUtils.isNotEmpty(syncValue)) {
|
if (StringUtils.isNotEmpty(syncValue)) {
|
||||||
return false;
|
return issueSyncCheckResult;
|
||||||
}
|
}
|
||||||
return true;
|
issueSyncCheckResult.setSyncComplete(Boolean.TRUE);
|
||||||
|
String syncMsg = getSyncErrorMsg(projectId);
|
||||||
|
issueSyncCheckResult.setSyncResult(syncMsg);
|
||||||
|
if (StringUtils.isNotEmpty(syncMsg)) {
|
||||||
|
// 清空同步异常信息
|
||||||
|
deleteSyncErrorMsg(projectId);
|
||||||
|
}
|
||||||
|
return issueSyncCheckResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSyncKey(String projectId) {
|
public String getSyncKey(String projectId) {
|
||||||
|
@ -920,45 +929,56 @@ public class IssuesService {
|
||||||
stringRedisTemplate.delete(SYNC_THIRD_PARTY_ISSUES_KEY + ":" + projectId);
|
stringRedisTemplate.delete(SYNC_THIRD_PARTY_ISSUES_KEY + ":" + projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSyncErrorMsg(String projectId, String errorMsg) {
|
||||||
|
stringRedisTemplate.opsForValue().set(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId, errorMsg, 30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSyncErrorMsg(String projectId) {
|
||||||
|
return stringRedisTemplate.opsForValue().get(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSyncErrorMsg(String projectId) {
|
||||||
|
stringRedisTemplate.delete(SYNC_THIRD_PARTY_ISSUES_ERROR_KEY + ":" + projectId);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public boolean syncThirdPartyIssues(String projectId) {
|
public void syncThirdPartyIssues(String projectId) {
|
||||||
if (StringUtils.isNotBlank(projectId)) {
|
if (StringUtils.isNotBlank(projectId)) {
|
||||||
String syncValue = getSyncKey(projectId);
|
String syncValue = getSyncKey(projectId);
|
||||||
if (StringUtils.isNotEmpty(syncValue)) {
|
if (StringUtils.isEmpty(syncValue)) {
|
||||||
return false;
|
setSyncKey(projectId);
|
||||||
}
|
Project project = baseProjectService.getProjectById(projectId);
|
||||||
|
List<IssuesDao> issues = extIssuesMapper.getIssueForSync(projectId, project.getPlatform());
|
||||||
|
|
||||||
setSyncKey(projectId);
|
if (CollectionUtils.isEmpty(issues)) {
|
||||||
|
deleteSyncKey(projectId);
|
||||||
Project project = baseProjectService.getProjectById(projectId);
|
|
||||||
List<IssuesDao> issues = extIssuesMapper.getIssueForSync(projectId, project.getPlatform());
|
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(issues)) {
|
|
||||||
deleteSyncKey(projectId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
IssuesRequest issuesRequest = new IssuesRequest();
|
|
||||||
issuesRequest.setProjectId(projectId);
|
|
||||||
issuesRequest.setWorkspaceId(project.getWorkspaceId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
issuesRequest.setDefaultCustomFields(getDefaultCustomField(project));
|
|
||||||
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
|
|
||||||
// 分批处理
|
|
||||||
SubListUtil.dealForSubList(issues, 500, (subIssue) ->
|
|
||||||
syncPluginThirdPartyIssues(subIssue, project, issuesRequest.getDefaultCustomFields()));
|
|
||||||
} else {
|
} else {
|
||||||
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issuesRequest);
|
new Thread(() -> {
|
||||||
syncThirdPartyIssues(platform::syncIssues, project, issues);
|
IssuesRequest issuesRequest = new IssuesRequest();
|
||||||
|
issuesRequest.setProjectId(projectId);
|
||||||
|
issuesRequest.setWorkspaceId(project.getWorkspaceId());
|
||||||
|
try {
|
||||||
|
issuesRequest.setDefaultCustomFields(getDefaultCustomField(project));
|
||||||
|
if (PlatformPluginService.isPluginPlatform(project.getPlatform())) {
|
||||||
|
// 分批处理
|
||||||
|
SubListUtil.dealForSubList(issues, 500, (subIssue) ->
|
||||||
|
syncPluginThirdPartyIssues(subIssue, project, issuesRequest.getDefaultCustomFields()));
|
||||||
|
} else {
|
||||||
|
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issuesRequest);
|
||||||
|
syncThirdPartyIssues(platform::syncIssues, project, issues);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.error(e);
|
||||||
|
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
|
||||||
|
setSyncErrorMsg(projectId, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
// 异常或正常结束都得删除当前项目执行同步的Key
|
||||||
|
deleteSyncKey(projectId);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
deleteSyncKey(projectId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDefaultCustomField(Project project) {
|
private String getDefaultCustomField(Project project) {
|
||||||
|
@ -1918,39 +1938,36 @@ public class IssuesService {
|
||||||
&& platformPluginService.isThirdPartTemplateSupport(project.getPlatform());
|
&& platformPluginService.isThirdPartTemplateSupport(project.getPlatform());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean syncThirdPartyAllIssues(IssueSyncRequest syncRequest) {
|
public void syncThirdPartyAllIssues(IssueSyncRequest syncRequest) {
|
||||||
syncRequest.setProjectId(syncRequest.getProjectId());
|
syncRequest.setProjectId(syncRequest.getProjectId());
|
||||||
XpackIssueService xpackIssueService = CommonBeanFactory.getBean(XpackIssueService.class);
|
XpackIssueService xpackIssueService = CommonBeanFactory.getBean(XpackIssueService.class);
|
||||||
if (StringUtils.isNotBlank(syncRequest.getProjectId())) {
|
if (StringUtils.isNotBlank(syncRequest.getProjectId())) {
|
||||||
// 获取当前项目执行同步缺陷Key
|
// 获取当前项目执行同步缺陷Key
|
||||||
String syncValue = getSyncKey(syncRequest.getProjectId());
|
String syncValue = getSyncKey(syncRequest.getProjectId());
|
||||||
// 存在即正在同步中
|
if (StringUtils.isEmpty(syncValue)) {
|
||||||
if (StringUtils.isNotEmpty(syncValue)) {
|
// 同步Key不存在, 设置保证唯一性, 并开始同步
|
||||||
return false;
|
setSyncKey(syncRequest.getProjectId());
|
||||||
}
|
new Thread(() -> {
|
||||||
// 不存在则设置Key, 设置过期时间, 执行完成后delete掉
|
try {
|
||||||
setSyncKey(syncRequest.getProjectId());
|
Project project = baseProjectService.getProjectById(syncRequest.getProjectId());
|
||||||
|
if (!isThirdPartTemplate(project)) {
|
||||||
try {
|
syncRequest.setDefaultCustomFields(getDefaultCustomFields(syncRequest.getProjectId()));
|
||||||
Project project = baseProjectService.getProjectById(syncRequest.getProjectId());
|
}
|
||||||
|
xpackIssueService.syncThirdPartyIssues(project, syncRequest);
|
||||||
if (!isThirdPartTemplate(project)) {
|
if (platformPluginService.isPluginPlatform(project.getPlatform())) {
|
||||||
syncRequest.setDefaultCustomFields(getDefaultCustomFields(syncRequest.getProjectId()));
|
syncAllPluginIssueAttachment(project, syncRequest);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
xpackIssueService.syncThirdPartyIssues(project, syncRequest);
|
LogUtil.error(e);
|
||||||
|
// 同步缺陷异常, 当前同步错误信息 -> Redis(check接口获取)
|
||||||
if (platformPluginService.isPluginPlatform(project.getPlatform())) {
|
setSyncErrorMsg(syncRequest.getProjectId(), e.getMessage());
|
||||||
syncAllPluginIssueAttachment(project, syncRequest);
|
} finally {
|
||||||
}
|
// 异常或正常结束都得删除当前项目执行同步的Key
|
||||||
} catch (Exception e) {
|
deleteSyncKey(syncRequest.getProjectId());
|
||||||
LogUtil.error(e);
|
}
|
||||||
MSException.throwException(e);
|
}).start();
|
||||||
} finally {
|
|
||||||
deleteSyncKey(syncRequest.getProjectId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -184,22 +184,21 @@ export function syncIssues() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 轮询同步状态
|
// 轮询同步状态
|
||||||
export function checkSyncIssues(loading, isNotFirst) {
|
export function checkSyncIssues(loading, isNotFirst, callback) {
|
||||||
let url = 'issues/sync/check/' + getCurrentProjectID() + "?stamp=" + getUUID();
|
let url = 'issues/sync/check/' + getCurrentProjectID() + "?stamp=" + getUUID();
|
||||||
return get(url)
|
return get(url)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data === false) {
|
if (response.data.syncComplete === false) {
|
||||||
if (loading === true) {
|
if (loading === true) {
|
||||||
if (!isNotFirst) {
|
if (!isNotFirst) {
|
||||||
// 第一次才提示
|
// 第一次才提示
|
||||||
$warning(i18n.t('test_track.issue.issue_sync_tip'));
|
$warning(i18n.t('test_track.issue.issue_sync_tip'), false);
|
||||||
}
|
}
|
||||||
setTimeout(() => checkSyncIssues(loading, true), 1000);
|
setTimeout(() => checkSyncIssues(loading, true, callback), 1000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loading === true) {
|
if (loading === true) {
|
||||||
$success(i18n.t('test_track.issue.sync_complete'));
|
callback(response.data);
|
||||||
loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,11 +12,13 @@
|
||||||
<span v-if="isThirdPart && hasPermission('PROJECT_TRACK_ISSUE:READ+CREATE')">
|
<span v-if="isThirdPart && hasPermission('PROJECT_TRACK_ISSUE:READ+CREATE')">
|
||||||
<ms-table-button
|
<ms-table-button
|
||||||
v-if="hasLicense"
|
v-if="hasLicense"
|
||||||
|
:disabled="syncDisable"
|
||||||
icon="el-icon-refresh"
|
icon="el-icon-refresh"
|
||||||
:content="$t('test_track.issue.sync_bugs')"
|
:content="$t('test_track.issue.sync_bugs')"
|
||||||
@click="syncAllIssues"/>
|
@click="syncAllIssues"/>
|
||||||
<ms-table-button
|
<ms-table-button
|
||||||
v-if="!hasLicense"
|
v-if="!hasLicense"
|
||||||
|
:disabled="syncDisable"
|
||||||
icon="el-icon-refresh"
|
icon="el-icon-refresh"
|
||||||
:content="$t('test_track.issue.sync_bugs')"
|
:content="$t('test_track.issue.sync_bugs')"
|
||||||
@click="syncIssues"/>
|
@click="syncIssues"/>
|
||||||
|
@ -248,6 +250,7 @@ export default {
|
||||||
platformStatus: [],
|
platformStatus: [],
|
||||||
platformStatusMap: new Map(),
|
platformStatusMap: new Map(),
|
||||||
hasLicense: false,
|
hasLicense: false,
|
||||||
|
syncDisable: false,
|
||||||
columns: {
|
columns: {
|
||||||
num: {
|
num: {
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
@ -589,38 +592,48 @@ export default {
|
||||||
},
|
},
|
||||||
syncConfirm(data) {
|
syncConfirm(data) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.syncDisable = true;
|
||||||
let param = {
|
let param = {
|
||||||
"projectId": getCurrentProjectID(),
|
"projectId": getCurrentProjectID(),
|
||||||
"createTime": data.createTime.getTime(),
|
"createTime": data.createTime.getTime(),
|
||||||
"pre": data.preValue
|
"pre": data.preValue
|
||||||
}
|
}
|
||||||
syncAllIssues(param)
|
syncAllIssues(param)
|
||||||
.then((response) => {
|
.then(() => {
|
||||||
if (response.data === false) {
|
checkSyncIssues(this.loading, false, (errorData) => {
|
||||||
checkSyncIssues(this.loading);
|
this.loading = false;
|
||||||
} else {
|
this.syncDisable = false;
|
||||||
this.$success(this.$t('test_track.issue.sync_complete'));
|
if (errorData.syncResult && errorData.syncResult !== '') {
|
||||||
|
this.$error(errorData.syncResult, false);
|
||||||
this.getIssues();
|
} else {
|
||||||
}
|
this.$success(this.$t('test_track.issue.sync_complete'), false);
|
||||||
|
this.getIssues();
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
this.syncDisable = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
syncIssues() {
|
syncIssues() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
this.syncDisable = false;
|
||||||
syncIssues()
|
syncIssues()
|
||||||
.then((response) => {
|
.then(() => {
|
||||||
if (response.data === false) {
|
checkSyncIssues(this.loading, false, (errorData) => {
|
||||||
checkSyncIssues(this.loading);
|
|
||||||
} else {
|
|
||||||
this.$success(this.$t('test_track.issue.sync_complete'));
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.getIssues();
|
this.syncDisable = false;
|
||||||
}
|
if (errorData.syncResult && errorData.syncResult !== '') {
|
||||||
|
this.$error(errorData.syncResult, false);
|
||||||
|
} else {
|
||||||
|
this.$success(this.$t('test_track.issue.sync_complete'), false);
|
||||||
|
this.getIssues();
|
||||||
|
}
|
||||||
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.syncDisable = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
editParam() {
|
editParam() {
|
||||||
|
|
Loading…
Reference in New Issue