feat(测试跟踪): 同步缺陷支持时间范围内同步
--story=1010094 --user=宋昌昌 同步缺陷支持同步指定时间范围内的缺陷 https://www.tapd.cn/55049933/s/1269234
This commit is contained in:
parent
50af3dd418
commit
15abe5a6d1
|
@ -613,6 +613,7 @@ const message = {
|
|||
system_template: 'System Template',
|
||||
option_check: 'Please add option values',
|
||||
option_value_check: 'Please fill in the full option values',
|
||||
sync_issue_tips: 'Note: The system will automatically synchronize at 00:00:00 every day',
|
||||
},
|
||||
workspace: {
|
||||
id: 'Workspace ID',
|
||||
|
|
|
@ -619,6 +619,7 @@ const message = {
|
|||
system_template: '系统模板',
|
||||
option_check: '请添加选项值',
|
||||
option_value_check: '请填写完整选项值',
|
||||
sync_issue_tips: '注: 系统在每天00:00:00会自动同步一次',
|
||||
},
|
||||
workspace: {
|
||||
id: '工作空间ID',
|
||||
|
|
|
@ -616,6 +616,7 @@ const message = {
|
|||
system_template: '系統模板',
|
||||
option_check: '請添加選項值',
|
||||
option_value_check: '請填寫完整選項值',
|
||||
sync_issue_tips: '注:系統在每天00:00:00會自動同步一次',
|
||||
},
|
||||
workspace: {
|
||||
id: '工作空間ID',
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
package io.metersphere.xpack.track.controller;
|
||||
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.xpack.track.dto.IssueSyncRequest;
|
||||
import io.metersphere.xpack.track.service.XpackIssueService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/xpack/issue")
|
||||
public class XpackIssueController {
|
||||
|
||||
@GetMapping("/sync/{projectId}")
|
||||
public boolean getPlatformIssue(@PathVariable String projectId) {
|
||||
@PostMapping("/sync")
|
||||
public boolean getPlatformIssue(@RequestBody IssueSyncRequest request) {
|
||||
XpackIssueService xpackIssueService = CommonBeanFactory.getBean(XpackIssueService.class);
|
||||
return xpackIssueService.syncThirdPartyIssues(projectId);
|
||||
return xpackIssueService.syncThirdPartyIssues(request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package io.metersphere.xpack.track.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author songcc
|
||||
*/
|
||||
@Data
|
||||
public class IssueSyncRequest {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private String projectId;
|
||||
|
||||
/**
|
||||
* 缺陷创建时间
|
||||
*/
|
||||
private Long createTime;
|
||||
|
||||
/**
|
||||
* TRUE: 创建时间之前
|
||||
*/
|
||||
private boolean pre;
|
||||
}
|
|
@ -43,6 +43,7 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs {
|
|||
* azure devops bug同步fields
|
||||
*/
|
||||
private String devopsFields;
|
||||
private String azureDevopsCreateTime;
|
||||
|
||||
private PlatformStatusDTO transitions;
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ public interface IssuesPlatform {
|
|||
* 同步缺陷全量的缺陷
|
||||
* @param project
|
||||
*/
|
||||
void syncAllIssues(Project project);
|
||||
void syncAllIssues(Project project, IssueSyncRequest syncRequest);
|
||||
|
||||
/**
|
||||
* 获取第三方平台缺陷模板
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package io.metersphere.xpack.track.service;
|
||||
|
||||
import io.metersphere.xpack.track.dto.IssueSyncRequest;
|
||||
|
||||
public interface XpackIssueService {
|
||||
|
||||
boolean syncThirdPartyIssues(String projectId);
|
||||
boolean syncThirdPartyIssues(IssueSyncRequest request);
|
||||
|
||||
void syncThirdPartyIssues();
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
group by issues.id
|
||||
</select>
|
||||
<select id="getIssueForSync" resultType="io.metersphere.xpack.track.dto.IssuesDao">
|
||||
select id,platform, platform_id
|
||||
select id, platform, platform_id, create_time
|
||||
from issues
|
||||
where project_id = #{projectId} and platform = #{platform} and (platform_status != 'delete' or platform_status is null);
|
||||
</select>
|
||||
|
|
|
@ -136,9 +136,9 @@ public class IssuesController {
|
|||
return issuesService.getZentaoBuilds(request);
|
||||
}
|
||||
|
||||
@GetMapping("/sync/{projectId}")
|
||||
public boolean getPlatformIssue(@PathVariable String projectId) {
|
||||
return issuesService.syncThirdPartyIssues(projectId);
|
||||
@PostMapping("/sync")
|
||||
public boolean getPlatformIssue(@RequestBody IssueSyncRequest request) {
|
||||
return issuesService.syncThirdPartyIssues(request);
|
||||
}
|
||||
|
||||
@GetMapping("/sync/check/{projectId}")
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.metersphere.job.schedule;
|
||||
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.sechedule.MsScheduleJob;
|
||||
import io.metersphere.service.IssuesService;
|
||||
import io.metersphere.xpack.license.dto.LicenseDTO;
|
||||
import io.metersphere.xpack.license.service.LicenseService;
|
||||
import io.metersphere.xpack.track.service.XpackIssueService;
|
||||
import org.quartz.JobExecutionContext;
|
||||
|
||||
/**
|
||||
* @author songcc
|
||||
*/
|
||||
public class IssueSyncJob extends MsScheduleJob {
|
||||
|
||||
private LicenseService licenseService;
|
||||
private XpackIssueService xpackIssueService;
|
||||
private IssuesService issuesService;
|
||||
|
||||
public IssueSyncJob() {
|
||||
licenseService = CommonBeanFactory.getBean(LicenseService.class);
|
||||
xpackIssueService = CommonBeanFactory.getBean(XpackIssueService.class);
|
||||
issuesService = CommonBeanFactory.getBean(IssuesService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void businessExecute(JobExecutionContext context) {
|
||||
LicenseDTO licenseDTO = licenseService.validate();
|
||||
if (licenseDTO != null && licenseDTO.getLicense() != null) {
|
||||
xpackIssueService.syncThirdPartyIssues();
|
||||
} else {
|
||||
issuesService.syncThirdPartyIssues();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ import io.metersphere.commons.utils.*;
|
|||
import io.metersphere.plan.service.TestPlanTestCaseService;
|
||||
import io.metersphere.utils.DistinctKeyUtil;
|
||||
import io.metersphere.xpack.track.dto.AttachmentSyncType;
|
||||
import io.metersphere.xpack.track.dto.*;
|
||||
import io.metersphere.constants.AttachmentType;
|
||||
import io.metersphere.constants.SystemCustomField;
|
||||
import io.metersphere.dto.CustomFieldDao;
|
||||
import io.metersphere.xpack.track.dto.IssueTemplateDao;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.log.utils.ReflexObjectUtil;
|
||||
import io.metersphere.log.vo.DetailColumn;
|
||||
|
@ -34,19 +34,15 @@ import io.metersphere.request.issues.JiraIssueTypeRequest;
|
|||
import io.metersphere.request.issues.PlatformIssueTypeRequest;
|
||||
import io.metersphere.request.testcase.AuthUserIssueRequest;
|
||||
import io.metersphere.request.testcase.IssuesCountRequest;
|
||||
import io.metersphere.xpack.track.dto.PlatformUser;
|
||||
import io.metersphere.service.issue.domain.jira.JiraIssueType;
|
||||
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
|
||||
import io.metersphere.request.attachment.AttachmentRequest;
|
||||
import io.metersphere.xpack.track.dto.DemandDTO;
|
||||
import io.metersphere.xpack.track.dto.IssuesDao;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesRequest;
|
||||
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
|
||||
import io.metersphere.service.issue.platform.*;
|
||||
import io.metersphere.service.remote.project.TrackCustomFieldTemplateService;
|
||||
import io.metersphere.service.remote.project.TrackIssueTemplateService;
|
||||
import io.metersphere.service.wapper.TrackProjectService;
|
||||
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
|
||||
import io.metersphere.xpack.track.issue.IssuesPlatform;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
|
@ -655,7 +651,9 @@ public class IssuesService {
|
|||
List<String> projectIds = trackProjectService.getThirdPartProjectIds();
|
||||
projectIds.forEach(id -> {
|
||||
try {
|
||||
syncThirdPartyIssues(id);
|
||||
IssueSyncRequest request = new IssueSyncRequest();
|
||||
request.setProjectId(id);
|
||||
syncThirdPartyIssues(request);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
|
@ -698,29 +696,32 @@ public class IssuesService {
|
|||
stringRedisTemplate.delete(SYNC_THIRD_PARTY_ISSUES_KEY + ":" + projectId);
|
||||
}
|
||||
|
||||
public boolean syncThirdPartyIssues(String projectId) {
|
||||
if (StringUtils.isNotBlank(projectId)) {
|
||||
String syncValue = getSyncKey(projectId);
|
||||
public boolean syncThirdPartyIssues(IssueSyncRequest syncRequest) {
|
||||
if (StringUtils.isNotBlank(syncRequest.getProjectId())) {
|
||||
String syncValue = getSyncKey(syncRequest.getProjectId());
|
||||
if (StringUtils.isNotEmpty(syncValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setSyncKey(projectId);
|
||||
setSyncKey(syncRequest.getProjectId());
|
||||
|
||||
Project project = baseProjectService.getProjectById(projectId);
|
||||
List<IssuesDao> issues = extIssuesMapper.getIssueForSync(projectId, project.getPlatform());
|
||||
Project project = baseProjectService.getProjectById(syncRequest.getProjectId());
|
||||
List<IssuesDao> issues = extIssuesMapper.getIssueForSync(syncRequest.getProjectId(), project.getPlatform());
|
||||
if (syncRequest.getCreateTime() != null) {
|
||||
issues = filterSyncIssuesByCreated(issues, syncRequest);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(issues)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IssuesRequest issuesRequest = new IssuesRequest();
|
||||
issuesRequest.setProjectId(projectId);
|
||||
issuesRequest.setProjectId(syncRequest.getProjectId());
|
||||
issuesRequest.setWorkspaceId(project.getWorkspaceId());
|
||||
|
||||
try {
|
||||
if (!trackProjectService.isThirdPartTemplate(project)) {
|
||||
String defaultCustomFields = getDefaultCustomFields(projectId);
|
||||
String defaultCustomFields = getDefaultCustomFields(syncRequest.getProjectId());
|
||||
issuesRequest.setDefaultCustomFields(defaultCustomFields);
|
||||
}
|
||||
IssuesPlatform platform = IssueFactory.createPlatform(project.getPlatform(), issuesRequest);
|
||||
|
@ -728,7 +729,7 @@ public class IssuesService {
|
|||
} catch (Exception e) {
|
||||
throw e;
|
||||
} finally {
|
||||
deleteSyncKey(projectId);
|
||||
deleteSyncKey(syncRequest.getProjectId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -817,7 +818,6 @@ public class IssuesService {
|
|||
|
||||
public void calculatePlanReport(String planId, TestPlanSimpleReportDTO report) {
|
||||
List<PlanReportIssueDTO> planReportIssueDTOS = extIssuesMapper.selectForPlanReport(planId);
|
||||
planReportIssueDTOS = DistinctKeyUtil.distinctByKey(planReportIssueDTOS, PlanReportIssueDTO::getId);
|
||||
TestPlanFunctionResultReportDTO functionResult = report.getFunctionResult();
|
||||
List<TestCaseReportStatusResultDTO> statusResult = new ArrayList<>();
|
||||
Map<String, TestCaseReportStatusResultDTO> statusResultMap = new HashMap<>();
|
||||
|
@ -1087,4 +1087,15 @@ public class IssuesService {
|
|||
MSException.throwException(Translator.get("issue_project_not_exist"));
|
||||
}
|
||||
}
|
||||
|
||||
private List<IssuesDao> filterSyncIssuesByCreated(List<IssuesDao> issues, IssueSyncRequest syncRequest) {
|
||||
List<IssuesDao> filterIssues = issues.stream().filter(issue -> {
|
||||
if (syncRequest.isPre()) {
|
||||
return issue.getCreateTime() <= syncRequest.getCreateTime();
|
||||
} else {
|
||||
return issue.getCreateTime() >= syncRequest.getCreateTime();
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
return filterIssues;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import io.metersphere.commons.constants.IssuesStatus;
|
|||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.*;
|
||||
import io.metersphere.dto.CustomFieldItemDTO;
|
||||
import io.metersphere.xpack.track.dto.IssueSyncRequest;
|
||||
import io.metersphere.xpack.track.dto.IssueTemplateDao;
|
||||
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
|
@ -486,7 +487,7 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void syncAllIssues(Project project) {}
|
||||
public void syncAllIssues(Project project, IssueSyncRequest syncRequest) {}
|
||||
|
||||
@Override
|
||||
public IssueTemplateDao getThirdPartTemplate() {return null;}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- 同步缺陷定时器修改
|
||||
update schedule set value = '0 0 0 * * ?', job = 'io.metersphere.job.schedule.IssueSyncJob' where id = '7a23d4db-9909-438d-9e36-58e432c8c4ae'
|
|
@ -132,33 +132,33 @@ export function getRelateIssues(page) {
|
|||
});
|
||||
}
|
||||
|
||||
export function syncIssues() {
|
||||
let url = 'issues/sync/';
|
||||
export function syncIssues(param) {
|
||||
let url = 'issues/sync';
|
||||
if (hasLicense()) {
|
||||
url = 'xpack/issue/sync/';
|
||||
url = 'xpack/issue/sync';
|
||||
}
|
||||
// 浏览器默认策略,请求同一个url,可能导致 stalled 时间过长,加个uuid防止请求阻塞
|
||||
url = url + getCurrentProjectID() + "?stamp=" + getUUID();
|
||||
return get(url);
|
||||
url = url + "?stamp=" + getUUID();
|
||||
return post(url, param);
|
||||
}
|
||||
|
||||
// 轮询同步状态
|
||||
export function checkSyncIssues(result, isNotFirst) {
|
||||
export function checkSyncIssues(loading, isNotFirst) {
|
||||
let url = 'issues/sync/check/' + getCurrentProjectID() + "?stamp=" + getUUID();
|
||||
return get(url)
|
||||
.then((response) => {
|
||||
if (response.data === false) {
|
||||
if (result.loading === true) {
|
||||
if (loading === true) {
|
||||
if (!isNotFirst) {
|
||||
// 第一次才提示
|
||||
$warning(i18n.t('test_track.issue.issue_sync_tip'));
|
||||
}
|
||||
setTimeout(() => checkSyncIssues(result, true), 1000);
|
||||
setTimeout(() => checkSyncIssues(loading, true), 1000);
|
||||
}
|
||||
} else {
|
||||
if (result.loading === true) {
|
||||
if (loading === true) {
|
||||
$success(i18n.t('test_track.issue.sync_complete'));
|
||||
result.loading = false;
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<ms-table
|
||||
v-loading="page.result.loading"
|
||||
v-loading="page.result.loading || loading"
|
||||
:data="page.data"
|
||||
:enableSelection="false"
|
||||
:condition="page.condition"
|
||||
|
@ -165,6 +165,7 @@
|
|||
:total="page.total"/>
|
||||
|
||||
<issue-edit @refresh="getIssues" ref="issueEdit"/>
|
||||
<issue-sync-select @syncConfirm="syncConfirm" ref="issueSyncSelect" />
|
||||
</el-card>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
|
@ -185,6 +186,7 @@ import {
|
|||
import MsTableHeader from "metersphere-frontend/src/components/MsTableHeader";
|
||||
import IssueDescriptionTableItem from "@/business/issue/IssueDescriptionTableItem";
|
||||
import IssueEdit from "@/business/issue/IssueEdit";
|
||||
import IssueSyncSelect from "@/business/issue/IssueSyncSelect";
|
||||
import {
|
||||
checkSyncIssues,
|
||||
getIssuePartTemplateWithProject,
|
||||
|
@ -218,6 +220,7 @@ export default {
|
|||
MsContainer,
|
||||
IssueEdit,
|
||||
IssueDescriptionTableItem,
|
||||
IssueSyncSelect,
|
||||
MsTableHeader,
|
||||
MsTablePagination, MsTableButton, MsTableOperators, MsTableColumn, MsTable
|
||||
},
|
||||
|
@ -251,6 +254,7 @@ export default {
|
|||
userFilter: [],
|
||||
isThirdPart: false,
|
||||
creatorFilters: [],
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -406,14 +410,22 @@ export default {
|
|||
return false;
|
||||
},
|
||||
syncIssues() {
|
||||
this.page.result.loading = true;
|
||||
syncIssues()
|
||||
this.$refs.issueSyncSelect.open();
|
||||
},
|
||||
syncConfirm(data) {
|
||||
this.loading = true;
|
||||
let param = {
|
||||
"projectId": getCurrentProjectID(),
|
||||
"createTime": data.createTime.getTime(),
|
||||
"pre": data.preValue
|
||||
}
|
||||
syncIssues(param)
|
||||
.then((response) => {
|
||||
if (response.data === false) {
|
||||
checkSyncIssues(this.page.result);
|
||||
checkSyncIssues(this.loading);
|
||||
} else {
|
||||
this.$success(this.$t('test_track.issue.sync_complete'));
|
||||
this.page.result.loading = false;
|
||||
this.loading = false;
|
||||
this.getIssues();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<el-dialog :visible="visible" :title="$t('test_track.issue.sync_bugs')" @close="cancel">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="300px" :inline="true" size="small">
|
||||
<el-form-item prop="typeValue">
|
||||
<el-select v-model="form.typeValue">
|
||||
<el-option
|
||||
v-for="item in form.timeTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="preValue">
|
||||
<el-select v-model="form.preValue">
|
||||
<el-option
|
||||
v-for="item in form.preOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="form.createTime"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item class="tips-item">
|
||||
<span class="tips">{{ $t('custom_field.sync_issue_tips') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item class="btn-group">
|
||||
<el-button size="small" @click="cancel">{{ $t('commons.cancel') }}</el-button>
|
||||
<el-button type="primary" size="small" @click="save">{{ $t('commons.confirm') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "IssueSyncSelect",
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
form: {
|
||||
timeTypeOptions: [{"value": "createTime", "label": "创建时间"}],
|
||||
typeValue: "createTime",
|
||||
preOptions: [{"value": false, "label": "大于等于"}, {"value": true, "label": "小于等于"}],
|
||||
preValue: false,
|
||||
createTime: ""
|
||||
},
|
||||
rules: {
|
||||
createTime: [
|
||||
{ type: 'date', required: true, message: "请选择日期时间", trigger: 'change'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true;
|
||||
},
|
||||
save() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.visible = false;
|
||||
this.$emit('syncConfirm', this.form);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.visible = false;
|
||||
this.$refs.form.resetFields();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tips{
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-form-item.tips-item.el-form-item--small {
|
||||
position: relative;
|
||||
top: -20px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.el-form-item.btn-group.el-form-item--small {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 25px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue