fix(测试跟踪): 缺陷同步异步执行

--bug=1025655 --user=宋昌昌 【测试跟踪】项目集成jira平台,缺陷管理中同步缺陷失败 https://www.tapd.cn/55049933/s/1365554
This commit is contained in:
song-cc-rock 2023-04-23 11:28:36 +08:00 committed by jianxing
parent bed9bba9ed
commit ff3fa223b2
5 changed files with 134 additions and 89 deletions

View File

@ -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;
}

View File

@ -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);
} }

View File

@ -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;
} }
/** /**

View File

@ -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;
} }
} }
}); });

View File

@ -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() {