feat(测试跟踪): 禅道插件化改造

This commit is contained in:
chenjianxing 2022-12-09 14:03:30 +08:00 committed by 刘瑞斌
parent 39defe8a3b
commit 90bbdc168a
34 changed files with 125 additions and 1878 deletions

View File

@ -6,6 +6,7 @@ import io.metersphere.base.mapper.PluginMapper;
import io.metersphere.commons.constants.StorageConstants;
import io.metersphere.metadata.service.FileManagerService;
import io.metersphere.metadata.vo.FileRequest;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -28,6 +29,17 @@ public class BasePluginService {
return pluginMapper.selectByExampleWithBLOBs(example);
}
public PluginWithBLOBs get(String pluginId) {
return pluginMapper.selectByPrimaryKey(pluginId);
}
public PluginWithBLOBs getByScripId(String scripId) {
PluginExample example = new PluginExample();
example.createCriteria().andScriptIdEqualTo(scripId);
List<PluginWithBLOBs> plugins = pluginMapper.selectByExampleWithBLOBs(example);
return CollectionUtils.isEmpty(plugins) ? null : plugins.get(0);
}
public InputStream getPluginResource(String pluginId, String resourceName) {
FileRequest request = new FileRequest();
request.setProjectId(DIR_PATH + "/" + pluginId);
@ -38,6 +50,9 @@ public class BasePluginService {
public InputStream getPluginJar(String pluginId) {
PluginWithBLOBs plugin = pluginMapper.selectByPrimaryKey(pluginId);
if (plugin == null) {
return null;
}
return getPluginResource(pluginId, plugin.getSourceName());
}
}

View File

@ -19,8 +19,6 @@ public class IssuesDao extends IssuesWithBLOBs {
private List<String> caseIds;
private String caseId;
private List<String> tapdUsers;
private List<String>zentaoBuilds;
private String zentaoAssigned;
private String refType;
private String refId;
private List<CustomFieldDao> fields;

View File

@ -21,14 +21,6 @@ public class IssuesRequest extends BaseQueryRequest {
* 如果是 PLAN_FUNCTIONAL 则只查询该测试计划用例所关联的缺陷
*/
private String refType;
/**
* zentao bug 处理人
*/
private String zentaoUser;
/**
* zentao bug 影响版本
*/
private List<String> zentaoBuilds;
/**
* issues id

View File

@ -22,15 +22,6 @@ public class IssuesUpdateRequest extends IssuesWithBLOBs {
private List<CustomFieldResourceDTO> addFields;
private List<CustomFieldResourceDTO> editFields;
private List<CustomFieldItemDTO> requestFields;
/**
* zentao bug 处理人
*/
private String zentaoUser;
private String zentaoAssigned;
/**
* zentao bug 影响版本
*/
private List<String> zentaoBuilds;
private boolean thirdPartPlatform;
private List<String> follows;

View File

@ -55,6 +55,9 @@ public class PluginManagerUtil {
* @param pluginManager
*/
public static void loadPlugin(String id, PluginManager pluginManager, InputStream inputStream) {
if (inputStream == null) {
return;
}
if (pluginManager == null) {
pluginManager = new PluginManager();
}

View File

@ -31,15 +31,14 @@
<script>
import TapdSetting from '@/business/workspace/integration/TapdSetting';
import JiraSetting from '@/business/workspace/integration/JiraSetting';
import AzuredevopsSetting from '@/business/workspace/integration/AzureDevopsSetting';
import {AZURE_DEVOPS, TAPD, ZEN_TAO} from "metersphere-frontend/src/utils/constants";
import {AZURE_DEVOPS, TAPD} from "metersphere-frontend/src/utils/constants";
import PlatformConfig from "@/business/workspace/integration/PlatformConfig";
import {generatePlatformResourceUrl, getIntegrationInfo} from "@/api/platform-plugin";
export default {
name: "BugManagement",
components: {PlatformConfig, TapdSetting, JiraSetting, AzuredevopsSetting},
components: {PlatformConfig, TapdSetting, AzuredevopsSetting},
data() {
return {
loading: false,

View File

@ -1,229 +0,0 @@
<template>
<div>
<div style="width: 500px">
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
<el-form :model="form" ref="form" label-width="100px" size="small" :disabled="show" :rules="rules">
<el-form-item :label="$t('organization.integration.account')" prop="account">
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
</el-form-item>
<el-form-item label="Token" prop="password">
<el-input v-model="form.password" auto-complete="new-password" v-if="showInput"
:placeholder="$t('organization.integration.input_api_password')" show-password/>
</el-form-item>
<el-form-item :label="$t('organization.integration.jira_url')" prop="url">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
</el-form-item>
</el-form>
</div>
<bug-manage-btn @save="save"
@init="init"
:edit-permission="['WORKSPACE_SERVICE:READ+EDIT']"
@testConnection="testConnection"
@cancelIntegration="cancelIntegration"
@reloadPassInput="reloadPassInput"
:form="form"
:show.sync="show"
ref="bugBtn"/>
<div class="defect-tip">
<div>{{ $t('organization.integration.use_tip') }}</div>
<div>
1. {{ $t('organization.integration.use_tip_jira') }}
</div>
<div>
2. {{ $t('organization.integration.use_tip_two') }}
<router-link to="/setting/project/all" style="margin-left: 5px;color: #551A8B; text-decoration: underline; cursor: pointer">
{{ $t('organization.integration.link_the_project_now') }}
</router-link>
</div>
<div>
3. {{ $t('organization.integration.use_tip_three') }}
<span style="margin-left: 5px;color: #551A8B; text-decoration: underline; cursor: pointer" @click="resVisible = true">
{{ $t('organization.integration.link_the_info_now') }}
</span>
<el-dialog :close-on-click-modal="false" width="80%"
:visible.sync="resVisible" destroy-on-close @close="closeDialog">
<ms-person-router @closeDialog = "closeDialog"/>
</el-dialog>
</div>
</div>
</div>
</template>
<script>
import BugManageBtn from "./BugManageBtn";
import {getCurrentUser, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
import {JIRA} from "metersphere-frontend/src/utils/constants";
import MsInstructionsIcon from "metersphere-frontend/src/components/MsInstructionsIcon";
import MsPersonRouter from "metersphere-frontend/src/components/personal/PersonRouter";
import {
authServiceIntegration,
delServiceIntegration,
getServiceIntegration,
saveServiceIntegration
} from "../../../api/workspace";
export default {
name: "JiraSetting",
components: {MsInstructionsIcon, BugManageBtn,MsPersonRouter},
created() {
this.init();
},
data() {
return {
show: true,
showInput: true,
resVisible:false,
form: {},
rules: {
account: {
required: true,
message: this.$t('organization.integration.input_api_account'),
trigger: ['change', 'blur']
},
password: {
required: true,
message: this.$t('organization.integration.input_api_password'),
trigger: ['change', 'blur']
},
url: {
required: true,
message: this.$t('organization.integration.input_jira_url'),
trigger: ['change', 'blur']
},
issuetype: {
required: true,
message: this.$t('organization.integration.input_jira_issuetype'),
trigger: ['change', 'blur']
},
storytype: {
required: true,
message: this.$t('organization.integration.input_jira_storytype'),
trigger: ['change', 'blur']
}
},
};
},
methods: {
init() {
const {lastWorkspaceId} = getCurrentUser();
let param = {};
param.platform = JIRA;
param.workspaceId = lastWorkspaceId;
this.$parent.loading = getServiceIntegration(param).then(res => {
let data = res.data;
if (data.configuration) {
let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
this.$set(this.form, 'issuetype', config.issuetype);
this.$set(this.form, 'storytype', config.storytype);
} else {
this.clear();
}
});
},
save() {
this.$refs['form'].validate(valid => {
if (valid) {
let formatUrl = this.form.url.trim();
if (!formatUrl.endsWith('/')) {
formatUrl = formatUrl + '/';
}
let param = {};
let auth = {
account: this.form.account,
password: this.form.password,
url: formatUrl,
issuetype: this.form.issuetype,
storytype: this.form.storytype
};
const {lastWorkspaceId} = getCurrentUser();
param.workspaceId = lastWorkspaceId;
param.platform = JIRA;
param.configuration = JSON.stringify(auth);
this.$parent.loading = saveServiceIntegration(param).then(() => {
this.show = true;
this.$refs.bugBtn.showEdit = true;
this.$refs.bugBtn.showSave = false;
this.$refs.bugBtn.showCancel = false;
this.reloadPassInput();
this.init();
this.$success(this.$t('commons.save_success'));
});
} else {
return false;
}
});
},
clear() {
this.$set(this.form, 'account', '');
this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$set(this.form, 'issuetype', '');
this.$set(this.form, 'storytype', '');
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
});
},
testConnection() {
if (this.form.account && this.form.password) {
this.$parent.loading = authServiceIntegration(getCurrentWorkspaceId(), JIRA).then(() => {
this.$success(this.$t('organization.integration.verified'));
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
return false;
}
},
cancelIntegration() {
if (this.form.account && this.form.password) {
this.$alert(this.$t('organization.integration.cancel_confirm') + JIRA + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
const {lastWorkspaceId} = getCurrentUser();
let param = {};
param.workspaceId = lastWorkspaceId;
param.platform = JIRA;
this.$parent.loading = delServiceIntegration(param).then(() => {
this.$success(this.$t('organization.integration.successful_operation'));
this.init('');
});
}
}
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
}
},
reloadPassInput() {
this.showInput = false;
this.$nextTick(function () {
this.showInput = true;
});
},
closeDialog(){
this.resVisible = false;
}
}
};
</script>
<style scoped>
.defect-tip {
background: #EDEDED;
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.el-input {
width: 80%;
}
</style>

View File

@ -1,235 +0,0 @@
<template>
<div>
<div style="width: 500px">
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
<el-form :model="form" ref="form" label-width="100px" size="small" :disabled="show" :rules="rules">
<el-form-item :label="$t('organization.integration.account')" prop="account">
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
</el-form-item>
<el-form-item :label="$t('organization.integration.password')" prop="password">
<el-input v-model="form.password" auto-complete="new-password" v-if="showInput"
:placeholder="$t('organization.integration.input_api_password')" show-password/>
</el-form-item>
<el-form-item :label="$t('organization.integration.zentao_url')" prop="url">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_zentao_url')"/>
</el-form-item>
<el-form-item :label="$t('organization.integration.zentao_request')" prop="request">
<el-radio v-model="form.request" label="PATH_INFO" size="small" border> PATH_INFO</el-radio>
<el-radio v-model="form.request" label="GET" size="small" border>GET</el-radio>
<ms-instructions-icon effect="light" style="margin-left: -20px;">
{{ $t('organization.integration.zentao_config_tip')}} <br/><br/>
{{ $t('organization.integration.zentao_config_path')}}
</ms-instructions-icon>
</el-form-item>
</el-form>
</div>
<bug-manage-btn @save="save"
@init="init"
:edit-permission="['WORKSPACE_SERVICE:READ+EDIT']"
@testConnection="testConnection"
@cancelIntegration="cancelIntegration"
@reloadPassInput="reloadPassInput"
:form="form"
:show.sync="show"
ref="bugBtn"/>
<div class="defect-tip">
<div>{{ $t('organization.integration.use_tip') }}</div>
<div>
1. {{ $t('organization.integration.use_tip_zentao') }}
</div>
<div>
2. {{ $t('organization.integration.use_tip_two') }}
<router-link to="/setting/project/all" style="margin-left: 5px;color: #551A8B; text-decoration: underline; cursor: pointer">
{{ $t('organization.integration.link_the_project_now') }}
</router-link>
</div>
<div>
3. {{ $t('organization.integration.use_tip_three') }}
<span style="margin-left: 5px;color: #551A8B; text-decoration: underline; cursor: pointer" @click="resVisible = true">
{{ $t('organization.integration.link_the_info_now') }}
</span>
<el-dialog :close-on-click-modal="false" width="80%"
:visible.sync="resVisible" destroy-on-close @close="closeDialog">
<ms-person-router @closeDialog = "closeDialog"/>
</el-dialog>
</div>
</div>
</div>
</template>
<script>
import BugManageBtn from "./BugManageBtn";
import {getCurrentUser, getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
import {ZEN_TAO} from "metersphere-frontend/src/utils/constants";
import MsInstructionsIcon from "metersphere-frontend/src/components/MsInstructionsIcon";
import MsPersonRouter from "metersphere-frontend/src/components/personal/PersonRouter";
import {
authServiceIntegration,
delServiceIntegration,
getServiceIntegration,
saveServiceIntegration
} from "../../../api/workspace";
export default {
name: "ZentaoSetting",
components: {
MsInstructionsIcon,
BugManageBtn,
MsPersonRouter
},
created() {
this.init();
},
data() {
return {
show: true,
showInput: true,
resVisible:false,
form: {},
rules: {
account: {
required: true,
message: this.$t('organization.integration.input_api_account'),
trigger: ['change', 'blur']
},
password: {
required: true,
message: this.$t('organization.integration.input_api_password'),
trigger: ['change', 'blur']
},
url: {
required: true,
message: this.$t('organization.integration.input_zentao_url'),
trigger: ['change', 'blur']
},
request: {
required: true,
message: this.$t('organization.integration.input_zentao_request'),
trigger: ['change', 'blur']
},
},
};
},
methods: {
save() {
this.$refs['form'].validate(valid => {
if (valid) {
let formatUrl = this.form.url.trim();
if (!formatUrl.endsWith('/')) {
formatUrl = formatUrl + '/';
}
const {lastWorkspaceId} = getCurrentUser();
let param = {};
let auth = {
account: this.form.account,
password: this.form.password,
url: formatUrl,
request: this.form.request
};
param.workspaceId = lastWorkspaceId;
param.platform = ZEN_TAO;
param.configuration = JSON.stringify(auth);
this.$parent.loading = saveServiceIntegration(param).then(() => {
this.show = true;
this.$refs.bugBtn.showEdit = true;
this.$refs.bugBtn.showSave = false;
this.$refs.bugBtn.showCancel = false;
this.reloadPassInput();
this.init();
this.$success(this.$t('commons.save_success'));
});
} else {
return false;
}
});
},
init() {
const {lastWorkspaceId} = getCurrentUser();
let param = {};
param.platform = ZEN_TAO;
param.workspaceId = lastWorkspaceId;
this.$parent.loading = getServiceIntegration(param).then(res => {
let data = res.data;
if (data.configuration) {
let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
this.$set(this.form, 'request', config.request ? config.request : 'PATH_INFO');
} else {
this.clear();
}
});
},
clear() {
this.$set(this.form, 'account', '');
this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$set(this.form, 'request', '');
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
});
},
testConnection() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.form.account && this.form.password) {
this.$parent.loading = authServiceIntegration(getCurrentWorkspaceId(), ZEN_TAO).then(() => {
this.$success(this.$t('organization.integration.verified'));
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
return false;
}
} else {
return false;
}
});
},
cancelIntegration() {
if (this.form.account && this.form.password) {
this.$alert(this.$t('organization.integration.cancel_confirm') + ZEN_TAO + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
const {lastWorkspaceId} = getCurrentUser();
let param = {};
param.workspaceId = lastWorkspaceId;
param.platform = ZEN_TAO;
this.$parent.loading = delServiceIntegration(param).then(() => {
this.$success(this.$t('organization.integration.successful_operation'));
this.init('');
});
}
}
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
}
},
reloadPassInput() {
this.showInput = false;
this.$nextTick(function () {
this.showInput = true;
});
},
closeDialog(){
this.resVisible = false;
}
}
};
</script>
<style scoped>
.defect-tip {
background: #EDEDED;
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
</style>

View File

@ -152,7 +152,7 @@
when 'textarea' then cfi.text_value
else cfi.value end as value
from custom_field_issues cfi
join custom_field cf on cf.id = cfi.field_id
left join custom_field cf on cf.id = cfi.field_id
where cfi.resource_id = #{issueId}
</select>
<select id="getPlatformIssueByIds" resultType="io.metersphere.xpack.track.dto.IssuesDao">

View File

@ -11,6 +11,7 @@ import io.metersphere.commons.constants.OperLogModule;
import io.metersphere.commons.constants.PermissionConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.dto.IssuesStatusCountDao;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.log.annotation.MsAuditLog;
@ -24,7 +25,6 @@ import io.metersphere.request.testcase.IssuesCountRequest;
import io.metersphere.service.BaseCheckPermissionService;
import io.metersphere.service.IssuesService;
import io.metersphere.service.PlatformPluginService;
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
import io.metersphere.xpack.track.dto.*;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import io.metersphere.xpack.track.dto.request.IssuesUpdateRequest;
@ -150,16 +150,6 @@ public class IssuesController {
return issuesService.getTapdProjectUsers(request);
}
@PostMapping("/zentao/user")
public List<PlatformUser> getZentaoUsers(@RequestBody IssuesRequest request) {
return issuesService.getZentaoUsers(request);
}
@PostMapping("/zentao/builds")
public List<ZentaoBuild> getZentaoBuilds(@RequestBody IssuesRequest request) {
return issuesService.getZentaoBuilds(request);
}
@GetMapping("/sync/{projectId}")
public boolean syncThirdPartyIssues(@PathVariable String projectId) {
return issuesService.syncThirdPartyIssues(projectId);
@ -200,6 +190,11 @@ public class IssuesController {
return issuesService.getThirdPartTemplate(projectId);
}
@GetMapping("/plugin/custom/fields/{projectId}")
public List<CustomFieldDao> getPluginCustomFields(@PathVariable String projectId) {
return issuesService.getPluginCustomFields(projectId);
}
@GetMapping("/demand/list/{projectId}")
public List getDemandList(@PathVariable String projectId) {
return issuesService.getDemandList(projectId);

View File

@ -0,0 +1,18 @@
package io.metersphere.dto;
import io.metersphere.platform.domain.SelectOption;
import lombok.Data;
import java.util.List;
@Data
public class ThirdPartIssueField {
private String name;
private String type;
private String defaultValue;
private boolean required;
private String optionMethod;
private List<SelectOption> options;
private String label;
private String message;
}

View File

@ -46,7 +46,6 @@ import io.metersphere.request.issues.IssueImportRequest;
import io.metersphere.request.issues.PlatformIssueTypeRequest;
import io.metersphere.request.testcase.AuthUserIssueRequest;
import io.metersphere.request.testcase.IssuesCountRequest;
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
import io.metersphere.service.issue.platform.*;
import io.metersphere.service.remote.project.TrackCustomFieldTemplateService;
import io.metersphere.service.remote.project.TrackIssueTemplateService;
@ -141,6 +140,8 @@ public class IssuesService {
private PlatformPluginService platformPluginService;
@Resource
private UserService userService;
@Resource
private BasePluginService basePluginService;
private static final String SYNC_THIRD_PARTY_ISSUES_KEY = "ISSUE:SYNC";
@ -481,10 +482,6 @@ public class IssuesService {
List<String> tapdUsers = tapdPlatform.getTapdUsers(issuesWithBLOBs.getProjectId(), issuesWithBLOBs.getPlatformId());
issuesWithBLOBs.setTapdUsers(tapdUsers);
}
if (StringUtils.equals(issuesWithBLOBs.getPlatform(), IssuesManagePlatform.Zentao.name())) {
ZentaoPlatform zentaoPlatform = (ZentaoPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), issuesRequest);
zentaoPlatform.getZentaoAssignedAndBuilds(issuesWithBLOBs);
}
buildCustomField(issuesWithBLOBs);
return issuesWithBLOBs;
}
@ -649,18 +646,6 @@ public class IssuesService {
}
}
public List<ZentaoBuild> getZentaoBuilds(IssuesRequest request) {
try {
ZentaoPlatform platform = (ZentaoPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), request);
return platform.getBuilds();
} catch (Exception e) {
LogUtil.error("get zentao builds fail.");
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("zentao_get_project_builds_fail"));
}
return null;
}
public List<IssuesDao> list(IssuesRequest request) {
request.setOrders(ServiceUtils.getDefaultOrderByField(request.getOrders(), "create_time"));
request.getOrders().forEach(order -> {
@ -1148,6 +1133,8 @@ public class IssuesService {
public String getDefaultCustomFields(String projectId) {
IssueTemplateDao template = trackIssueTemplateService.getTemplate(projectId);
List<CustomFieldDao> customFields = trackCustomFieldTemplateService.getCustomFieldByTemplateId(template.getId());
List<CustomFieldDao> pluginCustomFields = getPluginCustomFields(projectId);
customFields.addAll(pluginCustomFields);
return getCustomFieldsValuesString(customFields);
}
@ -1370,6 +1357,47 @@ public class IssuesService {
return issueTemplateDao;
}
public List<CustomFieldDao> getPluginCustomFields(String projectId) {
List<CustomFieldDao> fields = new ArrayList<>();
if (StringUtils.isNotBlank(projectId)) {
Project project = baseProjectService.getProjectById(projectId);
PluginWithBLOBs plugin = basePluginService.getByScripId(project.getPlatform());
if (plugin == null) {
return fields;
}
Map metaData = JSON.parseMap(plugin.getFormScript());
Object issueConfig = metaData.get("issueConfig");
List<ThirdPartIssueField> thirdPartIssueFields = null;
if (issueConfig != null) {
String formItems = JSON.toJSONString(((Map) issueConfig).get("formItems"));
thirdPartIssueFields = JSON.parseArray(formItems, ThirdPartIssueField.class);
}
if (CollectionUtils.isEmpty(thirdPartIssueFields)) {
return fields;
}
char filedKey = 'A';
for (ThirdPartIssueField item : thirdPartIssueFields) {
CustomFieldDao customField = new CustomFieldDao();
BeanUtils.copyBean(customField, item);
customField.setKey(String.valueOf(filedKey++));
customField.setId(item.getName());
customField.setCustomData(item.getName());
customField.setName(item.getLabel());
if (StringUtils.isNotBlank(item.getOptionMethod())) {
Platform platform = platformPluginService.getPlatform(project.getPlatform());
GetOptionRequest request = new GetOptionRequest();
request.setOptionMethod(item.getOptionMethod());
request.setProjectConfig(PlatformPluginService.getCompatibleProjectConfig(project));
customField.setOptions(JSON.toJSONString(platform.getFormOptions(request)));
}
fields.add(customField);
}
}
return fields;
}
public IssuesRequest getDefaultIssueRequest(String projectId, String workspaceId) {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setProjectId(projectId);
@ -1443,9 +1471,6 @@ public class IssuesService {
if (StringUtils.equalsIgnoreCase(project.getPlatform(), IssuesManagePlatform.Tapd.name())) {
TapdPlatform tapd = new TapdPlatform(issuesRequest);
this.doCheckThirdProjectExist(tapd, project.getTapdId());
} else if (StringUtils.equalsIgnoreCase(project.getPlatform(), IssuesManagePlatform.Zentao.name())) {
ZentaoPlatform zentao = new ZentaoPlatform(issuesRequest);
this.doCheckThirdProjectExist(zentao, project.getZentaoId());
}
}

View File

@ -13,6 +13,7 @@ import io.metersphere.commons.constants.PluginScenario;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.platform.domain.PlatformRequest;
import io.metersphere.platform.domain.SelectOption;
import io.metersphere.platform.loader.PlatformPluginManager;
import io.metersphere.request.IntegrationRequest;
import io.metersphere.utils.PluginManagerUtil;
@ -122,8 +123,7 @@ public class PlatformPluginService {
public static boolean isPluginPlatform(String platform) {
if (StringUtils.equalsAnyIgnoreCase(platform,
IssuesManagePlatform.Tapd.name(), IssuesManagePlatform.AzureDevops.name(),
IssuesManagePlatform.Zentao.name(), IssuesManagePlatform.Local.name())) {
IssuesManagePlatform.Tapd.name(), IssuesManagePlatform.AzureDevops.name(), IssuesManagePlatform.Local.name())) {
return false;
}
return true;

View File

@ -1,263 +0,0 @@
package io.metersphere.service.issue.client;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.UnicodeConvertUtils;
import io.metersphere.service.issue.domain.zentao.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
public abstract class ZentaoClient extends BaseClient {
protected String ENDPOINT;
protected String USER_NAME;
protected String PASSWD;
public RequestUrl requestUrl;
protected String url;
public ZentaoClient(String url) {
ENDPOINT = url;
}
public String login() {
GetUserResponse getUserResponse = new GetUserResponse();
String sessionId = "";
try {
sessionId = getSessionId();
String loginUrl = requestUrl.getLogin();
MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
paramMap.add("account", USER_NAME);
paramMap.add("password", PASSWD);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
ResponseEntity<String> response = restTemplate.exchange(loginUrl + sessionId, HttpMethod.POST, requestEntity, String.class);
getUserResponse = (GetUserResponse) getResultForObject(GetUserResponse.class, response);
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e.getMessage());
}
GetUserResponse.User user = getUserResponse.getUser();
if (user == null) {
LogUtil.error(JSON.toJSONString(getUserResponse));
// 登录失败获取的session无效置空session
MSException.throwException("zentao login fail, user null");
}
if (!StringUtils.equals(user.getAccount(), USER_NAME)) {
LogUtil.error("login failinconsistent users");
MSException.throwException("zentao login fail, inconsistent user");
}
return sessionId;
}
public String getSessionId() {
String getSessionUrl = requestUrl.getSessionGet();
ResponseEntity<String> response = restTemplate.exchange(getSessionUrl,
HttpMethod.GET, null, String.class);
GetSessionResponse getSessionResponse = (GetSessionResponse) getResultForObject(GetSessionResponse.class, response);
return JSON.parseObject(getSessionResponse.getData(), GetSessionResponse.Session.class).getSessionID();
}
public AddIssueResponse.Issue addIssue(MultiValueMap<String, Object> paramMap) {
String sessionId = login();
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
ResponseEntity<String> response = null;
try {
String bugCreate = requestUrl.getBugCreate();
response = restTemplate.exchange(bugCreate + sessionId,
HttpMethod.POST, requestEntity, String.class);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
}
AddIssueResponse addIssueResponse = (AddIssueResponse) getResultForObject(AddIssueResponse.class, response);
AddIssueResponse.Issue issue = JSON.parseObject(addIssueResponse.getData(), AddIssueResponse.Issue.class);
if (issue == null) {
MSException.throwException(UnicodeConvertUtils.unicodeToCn(response.getBody()));
}
return issue;
}
public void updateIssue(String id, MultiValueMap<String, Object> paramMap) {
String sessionId = login();
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
try {
restTemplate.exchange(requestUrl.getBugUpdate(),
HttpMethod.POST, requestEntity, String.class, id, sessionId);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
}
}
public void deleteIssue(String id) {
String sessionId = login();
try {
restTemplate.exchange(requestUrl.getBugDelete(),
HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class, id, sessionId);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
}
}
public Map getBugById(String id) {
String sessionId = login();
String bugGet = requestUrl.getBugGet();
ResponseEntity<String> response = restTemplate.exchange(bugGet,
HttpMethod.GET, null, String.class, id, sessionId);
GetIssueResponse getIssueResponse = (GetIssueResponse) getResultForObject(GetIssueResponse.class, response);
if(StringUtils.equalsIgnoreCase(getIssueResponse.getStatus(),"fail")){
GetIssueResponse.Issue issue = new GetIssueResponse.Issue();
issue.setId(id);
issue.setSteps(StringUtils.SPACE);
issue.setTitle(StringUtils.SPACE);
issue.setStatus("closed");
issue.setDeleted("1");
issue.setOpenedBy(StringUtils.SPACE);
getIssueResponse.setData(JSON.toJSONString(issue).toString());
}
return JSON.parseMap(getIssueResponse.getData());
}
public GetCreateMetaDataResponse.MetaData getCreateMetaData(String productID) {
String sessionId = login();
ResponseEntity<String> response = restTemplate.exchange(requestUrl.getCreateMetaData(),
HttpMethod.GET, null, String.class, productID, sessionId);
GetCreateMetaDataResponse getCreateMetaDataResponse = (GetCreateMetaDataResponse) getResultForObject(GetCreateMetaDataResponse.class, response);
return JSON.parseObject(getCreateMetaDataResponse.getData(), GetCreateMetaDataResponse.MetaData.class);
}
public Map getCustomFields(String productID) {
return getCreateMetaData(productID).getCustomFields();
}
public Map<String, Object> getBuildsByCreateMetaData(String projectId) {
return getCreateMetaData(projectId).getBuilds();
}
public Map<String, Object> getBuilds(String projectId) {
String sessionId = login();
ResponseEntity<String> response = restTemplate.exchange(requestUrl.getBuildsGet(),
HttpMethod.GET, null, String.class, projectId, sessionId);
return (Map<String, Object>) JSON.parseMap(response.getBody()).get("data");
}
public Map getBugsByProjectId(String projectId, Integer pageNum, Integer pageSize) {
String sessionId = login();
ResponseEntity<String> response = restTemplate.exchange(requestUrl.getBugList(),
HttpMethod.GET, null, String.class, projectId, 9999999, pageSize, pageNum, sessionId);
try {
return JSON.parseMap(JSON.parseMap(response.getBody()).get("data").toString());
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException("请检查配置信息是否填写正确!");
}
return null;
}
public String getBaseUrl() {
if (ENDPOINT.endsWith("/")) {
return ENDPOINT.substring(0, ENDPOINT.length() - 1);
}
return ENDPOINT;
}
public void setConfig(ZentaoConfig config) {
if (config == null) {
MSException.throwException("config is null");
}
USER_NAME = config.getAccount();
PASSWD = config.getPassword();
ENDPOINT = config.getUrl();
}
public String getReplaceImgUrl(String replaceImgUrl) {
String baseUrl = getBaseUrl();
String[] split = baseUrl.split("/");
String suffix = split[split.length - 1];
if (StringUtils.equals("biz", suffix)) {
suffix = baseUrl;
} else if (!StringUtils.equalsAny(suffix, "zentao", "pro", "zentaopms", "zentaopro", "zentaobiz")) {
suffix = "";
} else {
suffix = "/" + suffix;
}
return String.format(replaceImgUrl, suffix);
}
public boolean checkProjectExist(String relateId) {
String sessionId = login();
ResponseEntity<String> response = restTemplate.exchange(requestUrl.getProductGet(),
HttpMethod.GET, null, String.class, relateId, sessionId);
try {
Object data = JSON.parseMap(response.getBody()).get("data");
if (!StringUtils.equals((String) data, "false")) {
return true;
}
} catch (Exception e) {
LogUtil.error("checkProjectExist error: " + response.getBody());
}
return false;
}
public void uploadAttachment(String objectType, String objectId, File file) {
String sessionId = login();
HttpHeaders authHeader = new HttpHeaders();
authHeader.setContentType(MediaType.parseMediaType("multipart/form-data; charset=UTF-8"));
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
FileSystemResource fileResource = new FileSystemResource(file);
paramMap.add("files", fileResource);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(paramMap, authHeader);
try {
restTemplate.exchange(requestUrl.getFileUpload(), HttpMethod.POST, requestEntity,
String.class, objectId, sessionId);
} catch (Exception e) {
LogUtil.info("upload zentao attachment error");
}
}
public void deleteAttachment(String fileId) {
String sessionId = login();
try {
restTemplate.exchange(requestUrl.getFileDelete(), HttpMethod.GET, null, String.class, fileId, sessionId);
} catch (Exception e) {
LogUtil.info("delete zentao attachment error");
}
}
public byte[] getAttachmentBytes(String fileId) {
String sessionId = login();
ResponseEntity<byte[]> response = restTemplate.exchange(requestUrl.getFileDownload(), HttpMethod.GET,
null, byte[].class, fileId, sessionId);
return response.getBody();
}
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
im.metersphere.plugin.utils.LogUtil.info("zentao proxyForGet: " + path);
String url = this.ENDPOINT + path;
try {
if (!StringUtils.containsAny(new URI(url).getPath(), "/index.php", "/file-read-")) {
// 只允许访问图片
MSException.throwException("illegal path");
}
} catch (URISyntaxException e) {
LogUtil.error(e);
MSException.throwException("illegal path");
}
return restTemplate.exchange(url, HttpMethod.GET, null, responseEntityClazz);
}
}

View File

@ -1,65 +0,0 @@
package io.metersphere.service.issue.client;
import io.metersphere.service.issue.domain.zentao.RequestUrl;
import java.util.regex.Pattern;
public class ZentaoGetClient extends ZentaoClient {
private static final String LOGIN = "/?m=user&f=login&t=json&zentaosid=";
private static final String SESSION_GET="/?m=api&f=getSessionID&t=json";
private static final String BUG_CREATE="&module=bug&methodName=create&t=json&zentaosid=";
private static final String BUG_UPDATE = "&module=bug&methodName=update&params=bugID={0}&t=json&zentaosid={1}";
private static final String BUG_DELETE = "/?m=bug&f=delete&bugID={0}&confirm=yes&t=json&zentaosid={1}";
private static final String BUG_GET="&module=bug&methodName=getById&params=bugID={1}&t=json&zentaosid={2}";
private static final String STORY_GET="&module=story&methodName=getProductStories&params=productID={key}&t=json&zentaosid=";
private static final String USER_GET="&module=user&methodName=getList&t=json&zentaosid=";
private static final String BUILDS_GET="&module=build&methodName=getProductBuildPairs&productID={0}&zentaosid={1}";
private static final String FILE_UPLOAD="&module=file&methodName=saveUpload&params=objectType=bug,objectID={1}&zentaosid={2}";
private static final String FILE_DELETE="/?m=file&f=delete&t=json&fileID={1}&confirm=yes&zentaosid={2}";
private static final String FILE_DOWNLOAD="/?m=file&f=download&t=json&fileID={1}&mouse=click&zentaosid={2}";
private static final String CREATE_META_DATA="?m=bug&f=create&productID={0}&t=json&zentaosid={1}";
private static final String REPLACE_IMG_URL="<img src=\"%s/index.php?m=file&f=read&fileID=$1\"/>";
private static final Pattern IMG_PATTERN = Pattern.compile("m=file&f=read&fileID=(.*?)\"/>");
private static final String PRODUCT_GET = "&module=product&methodName=getById&params=productID={0}&zentaosid={1}";
/**
* 注意 recTotal={1}&recPerPage={2}&pageID={3} 顺序不能调换有点恶心
*/
private static final String BUG_LIST_URL = "/?m=bug&f=browse&productID={0}&branch=&browseType=all&param=0&orderBy=&recTotal={1}&recPerPage={2}&pageID={3}&t=json&zentaosid={4}";
RequestUrl request = new RequestUrl();
public ZentaoGetClient(String url) {
super(url);
}
{
request.setLogin(getNotSuperModelUrl(LOGIN));
request.setSessionGet(getNotSuperModelUrl(SESSION_GET));
request.setBugCreate(getUrl(BUG_CREATE));
request.setBugGet(getUrl(BUG_GET));
request.setStoryGet(getUrl(STORY_GET));
request.setUserGet(getUrl(USER_GET));
request.setBuildsGet(getUrl(BUILDS_GET));
request.setFileUpload(getUrl(FILE_UPLOAD));
request.setReplaceImgUrl(getReplaceImgUrl(REPLACE_IMG_URL));
request.setImgPattern(IMG_PATTERN);
request.setBugUpdate(getUrl(BUG_UPDATE));
request.setBugDelete(getNotSuperModelUrl(BUG_DELETE));
request.setBugList(getNotSuperModelUrl(BUG_LIST_URL));
request.setCreateMetaData(getNotSuperModelUrl(CREATE_META_DATA));
request.setProductGet(getUrl(PRODUCT_GET));
request.setFileDelete(getNotSuperModelUrl(FILE_DELETE));
request.setFileDownload(getNotSuperModelUrl(FILE_DOWNLOAD));
requestUrl = request;
}
private String getUrl(String url) {
return getBaseUrl() + "/?m=api&f=getModel" + url;
}
private String getNotSuperModelUrl(String url) {
return getBaseUrl() + url;
}
}

View File

@ -1,57 +0,0 @@
package io.metersphere.service.issue.client;
import io.metersphere.service.issue.domain.zentao.RequestUrl;
import java.util.regex.Pattern;
public class ZentaoPathInfoClient extends ZentaoClient {
private static final String LOGIN = "/user-login.json?zentaosid=";
private static final String SESSION_GET = "/api-getsessionid.json";
private static final String BUG_CREATE = "/api-getModel-bug-create.json?zentaosid=";
private static final String BUG_UPDATE = "/api-getModel-bug-update-bugID={1}.json?zentaosid={2}";
private static final String BUG_DELETE = "/bug-delete-{1}-yes.json?zentaosid={2}";
private static final String BUG_GET = "/api-getModel-bug-getById-bugID={1}?zentaosid={2}";
private static final String STORY_GET = "/api-getModel-story-getProductStories-productID={key}?zentaosid=";
private static final String USER_GET = "/api-getModel-user-getList?zentaosid=";
private static final String BUILDS_GET = "/api-getModel-build-getProductBuildPairs-productID={0}?zentaosid={1}";
private static final String CREATE_META_DATA="/bug-create-{0}.json?zentaosid={1}";
private static final String FILE_UPLOAD = "/api-getModel-file-saveUpload-objectType=bug,objectID={1}?zentaosid={2}";
private static final String FILE_DELETE = "/file-delete-{1}-.yes.json?zentaosid={2}";
private static final String FILE_DOWNLOAD="/file-download-{1}-.click.json?zentaosid={2}";
private static final String REPLACE_IMG_URL = "<img src=\"%s/file-read-$1\"/>";
private static final Pattern IMG_PATTERN = Pattern.compile("file-read-(.*?)\"/>");
private static final String PRODUCT_GET = "/api-getModel-product-getById-productID={0}?zentaosid={1}";
private static final String BUG_LIST_URL = "/bug-browse-{1}-0-all-0--{2}-{3}-{4}.json?&zentaosid={5}";
public ZentaoPathInfoClient(String url) {
super(url);
}
protected RequestUrl request = new RequestUrl();
{
request.setLogin(getUrl(LOGIN));
request.setSessionGet(getUrl(SESSION_GET));
request.setBugCreate(getUrl(BUG_CREATE));
request.setBugGet(getUrl(BUG_GET));
request.setStoryGet(getUrl(STORY_GET));
request.setUserGet(getUrl(USER_GET));
request.setBuildsGet(getUrl(BUILDS_GET));
request.setFileUpload(getUrl(FILE_UPLOAD));
request.setReplaceImgUrl(getReplaceImgUrl(REPLACE_IMG_URL));
request.setImgPattern(IMG_PATTERN);
request.setBugUpdate(getUrl(BUG_UPDATE));
request.setBugDelete(getUrl(BUG_DELETE));
request.setBugList(getUrl(BUG_LIST_URL));
request.setCreateMetaData(getUrl(CREATE_META_DATA));
request.setProductGet(getUrl(PRODUCT_GET));
request.setFileDelete(getUrl(FILE_DELETE));
request.setFileDownload(getUrl(FILE_DOWNLOAD));
requestUrl = request;
}
protected String getUrl(String url) {
return getBaseUrl() + url;
}
}

View File

@ -1,15 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AddIssueResponse extends ZentaoResponse {
@Getter
@Setter
public static class Issue {
private String status;
private String id;
}
}

View File

@ -1,20 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Getter
@Setter
public class GetCreateMetaDataResponse extends ZentaoResponse {
@Getter
@Setter
public static class MetaData {
private String title;
private Map users;
private Map customFields;
private Map<String, Object> builds;
}
}

View File

@ -1,23 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GetIssueResponse extends ZentaoResponse {
@Getter
@Setter
public static class Issue {
private String id;
private String title;
private String steps;
private String status;
private String openedBy;
// private String openedDate;
private String deleted;
// private String product;
// private String openedBuild;
// private String assignedTo;
}
}

View File

@ -1,18 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GetSessionResponse extends ZentaoResponse {
@Getter
@Setter
public static class Session {
// private String title;
// private String sessionName;
private String sessionID;
// private int rand;
// private String pager;
}
}

View File

@ -1,19 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GetUserResponse {
private String status;
private User user;
private String reason;
@Getter
@Setter
public static class User {
private String id;
private String account;
}
}

View File

@ -1,28 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
import java.util.regex.Pattern;
@Getter
@Setter
public class RequestUrl {
private String login;
private String sessionGet;
private String bugCreate;
private String createMetaData;
private String bugUpdate;
private String bugList;
private String bugDelete;
private String bugGet;
private String storyGet;
private String userGet;
private String buildsGet;
private String fileUpload;
private String fileDelete;
private String fileDownload;
private String replaceImgUrl;
private String productGet;
private Pattern imgPattern;
}

View File

@ -1,16 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Data;
@Data
public class ZentaoBuild {
private String id;
private String name;
public ZentaoBuild(String id, String name) {
this.id = id;
this.name = name;
}
public ZentaoBuild() {}
}

View File

@ -1,14 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ZentaoConfig {
private String account;
private String password;
private String url;
private String requestType;
private String request;
}

View File

@ -1,12 +0,0 @@
package io.metersphere.service.issue.domain.zentao;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ZentaoResponse {
private String status;
private String md5;
private String data;
}

View File

@ -16,8 +16,6 @@ public class IssueFactory {
public static IssuesPlatform createPlatform(String platform, IssuesRequest addIssueRequest) {
if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) {
return new TapdPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Zentao.toString(), platform)) {
return new ZentaoPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.AzureDevops.toString(), platform)) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {

View File

@ -1,18 +0,0 @@
package io.metersphere.service.issue.platform;
import io.metersphere.service.issue.client.ZentaoClient;
import io.metersphere.service.issue.client.ZentaoGetClient;
import io.metersphere.service.issue.client.ZentaoPathInfoClient;
import org.apache.commons.lang3.StringUtils;
public class ZentaoFactory {
public static ZentaoClient getInstance(String url, String type) {
if (StringUtils.equals(type, "PATH_INFO")) {
return new ZentaoPathInfoClient(url);
} else if (StringUtils.equals(type, "GET")) {
return new ZentaoGetClient(url);
}
return new ZentaoPathInfoClient(url);
}
}

View File

@ -1,692 +0,0 @@
package io.metersphere.service.issue.platform;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.constants.ZentaoIssuePlatformStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.xpack.track.dto.AttachmentSyncType;
import io.metersphere.constants.AttachmentType;
import io.metersphere.dto.*;
import io.metersphere.xpack.track.dto.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.client.ZentaoClient;
import io.metersphere.service.issue.client.ZentaoGetClient;
import io.metersphere.xpack.track.dto.PlatformUser;
import io.metersphere.service.issue.domain.zentao.AddIssueResponse;
import io.metersphere.service.issue.domain.zentao.GetIssueResponse;
import io.metersphere.service.issue.domain.zentao.ZentaoBuild;
import io.metersphere.service.issue.domain.zentao.ZentaoConfig;
import io.metersphere.xpack.track.dto.PlatformStatusDTO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class ZentaoPlatform extends AbstractIssuePlatform {
protected final ZentaoClient zentaoClient;
protected final String[] imgArray = {
"bmp", "jpg", "png", "tif", "gif", "jpeg"
};
// xpack 反射调用
public ZentaoClient getZentaoClient() {
return zentaoClient;
}
public ZentaoPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
this.key = IssuesManagePlatform.Zentao.name();
ZentaoConfig zentaoConfig = getConfig();
this.workspaceId = issuesRequest.getWorkspaceId();
this.zentaoClient = ZentaoFactory.getInstance(zentaoConfig.getUrl(), zentaoConfig.getRequest());
this.zentaoClient.setConfig(zentaoConfig);
}
@Override
public String getProjectId(String projectId) {
return getProjectId(projectId, Project::getZentaoId);
}
@Override
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
issuesRequest.setPlatform(key);
List<IssuesDao> issues;
if (StringUtils.isNotBlank(issuesRequest.getProjectId())) {
issues = extIssuesMapper.getIssues(issuesRequest);
} else {
issues = extIssuesMapper.getIssuesByCaseId(issuesRequest);
}
return issues;
}
public IssuesDao getZentaoAssignedAndBuilds(IssuesDao issue) {
Map zentaoIssue = (Map) zentaoClient.getBugById(issue.getPlatformId());
String assignedTo = zentaoIssue.get("assignedTo").toString();
String openedBuild = zentaoIssue.get("openedBuild").toString();
List<String> zentaoBuilds = new ArrayList<>();
if (Strings.isNotBlank(openedBuild)) {
zentaoBuilds = Arrays.asList(openedBuild.split(","));
}
issue.setZentaoAssigned(assignedTo);
issue.setZentaoBuilds(zentaoBuilds);
return issue;
}
@Override
public List<DemandDTO> getDemandList(String projectId) {
//getTestStories
List<DemandDTO> list = new ArrayList<>();
try {
String session = zentaoClient.login();
String key = getProjectId(projectId);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
String storyGet = zentaoClient.requestUrl.getStoryGet();
ResponseEntity<String> responseEntity = restTemplate.exchange(storyGet + session,
HttpMethod.POST, requestEntity, String.class, key);
String body = responseEntity.getBody();
Map obj = JSON.parseMap(body);
LogUtil.info("project story: " + key + obj);
if (obj != null) {
String data = obj.get("data").toString();
if (StringUtils.isBlank(data)) {
return list;
}
// 兼容处理11.5版本格式 [{obj},{obj}]
if (data.charAt(0) == '[') {
List array = (List) obj.get("data");
for (int i = 0; i < array.size(); i++) {
Map o = (Map) array.get(i);
DemandDTO demandDTO = new DemandDTO();
demandDTO.setId(o.get("id").toString());
demandDTO.setName(o.get("title").toString());
demandDTO.setPlatform(key);
list.add(demandDTO);
}
}
// {"5": {"children": {"51": {}}}, "6": {}}
else if (data.startsWith("{\"")) {
Map<String, Map<String, String>> dataMap = JSON.parseMap(data);
Collection<Map<String, String>> values = dataMap.values();
values.forEach(v -> {
Map jsonObject = JSON.parseMap(JSON.toJSONString(v));
DemandDTO demandDTO = new DemandDTO();
demandDTO.setId(jsonObject.get("id").toString());
demandDTO.setName(jsonObject.get("title").toString());
demandDTO.setPlatform(key);
list.add(demandDTO);
if (jsonObject.get("children") != null) {
LinkedHashMap<String, Map<String, String>> children = (LinkedHashMap<String, Map<String, String>>) jsonObject.get("children");
Collection<Map<String, String>> childrenMap = children.values();
childrenMap.forEach(ch -> {
DemandDTO dto = new DemandDTO();
dto.setId(ch.get("id"));
dto.setName(ch.get("title"));
dto.setPlatform(key);
list.add(dto);
});
}
});
}
// 处理格式 {{"id": {obj}},{"id",{obj}}}
else if (data.charAt(0) == '{') {
Map dataObject = (Map) obj.get("data");
String s = JSON.toJSONString(dataObject);
Map<String, Object> map = JSON.parseMap(s);
Collection<Object> values = map.values();
values.forEach(v -> {
Map jsonObject = JSON.parseMap(JSON.toJSONString(v));
DemandDTO demandDTO = new DemandDTO();
demandDTO.setId(jsonObject.get("id").toString());
demandDTO.setName(jsonObject.get("title").toString());
demandDTO.setPlatform(key);
list.add(demandDTO);
});
}
}
} catch (Exception e) {
LogUtil.error("get zentao demand fail " + e.getMessage());
}
return list;
}
public IssuesWithBLOBs getUpdateIssues(Map bug) {
return getUpdateIssues(null, bug);
}
/**
* 更新缺陷数据
*
* @param issue 待更新缺陷数据
* @param bug 平台缺陷数据
* @return
*/
public IssuesWithBLOBs getUpdateIssues(IssuesWithBLOBs issue, Map bug) {
GetIssueResponse.Issue bugObj = JSON.parseObject(JSON.toJSONString(bug), GetIssueResponse.Issue.class);
String description = bugObj.getSteps();
String steps = description;
try {
steps = htmlDesc2MsDesc(zentao2MsDescription(description));
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
if (issue == null) {
issue = new IssuesWithBLOBs();
issue.setCustomFields(defaultCustomFields);
} else {
mergeCustomField(issue, defaultCustomFields);
}
issue.setPlatformStatus(bugObj.getStatus());
if (StringUtils.equals(bugObj.getDeleted(), "1")) {
issue.setPlatformStatus(IssuesStatus.DELETE.toString());
issuesMapper.updateByPrimaryKeySelective(issue);
}
issue.setTitle(bugObj.getTitle());
issue.setDescription(steps);
issue.setReporter(bugObj.getOpenedBy());
issue.setPlatform(key);
try {
String openedDate = bug.get("openedDate").toString();
String lastEditedDate = bug.get("lastEditedDate").toString();
if (StringUtils.isNotBlank(openedDate) && !openedDate.startsWith("0000-00-00"))
issue.setCreateTime(DateUtils.getTime(openedDate).getTime());
if (StringUtils.isNotBlank(lastEditedDate) && !lastEditedDate.startsWith("0000-00-00"))
issue.setUpdateTime(DateUtils.getTime(lastEditedDate).getTime());
} catch (Exception e) {
LogUtil.error("update zentao time" + e.getMessage());
}
if (issue.getUpdateTime() == null) {
issue.setUpdateTime(System.currentTimeMillis());
}
issue.setCustomFields(syncIssueCustomField(issue.getCustomFields(), bug));
return issue;
}
@Override
public IssuesWithBLOBs addIssue(IssuesUpdateRequest issuesRequest) {
setUserConfig();
MultiValueMap<String, Object> param = buildUpdateParam(issuesRequest);
AddIssueResponse.Issue issue = zentaoClient.addIssue(param);
issuesRequest.setPlatformStatus(issue.getStatus());
IssuesWithBLOBs issues = null;
String id = issue.getId();
if (StringUtils.isNotBlank(id)) {
issuesRequest.setPlatformId(id);
issuesRequest.setId(UUID.randomUUID().toString());
IssuesExample issuesExample = new IssuesExample();
issuesExample.createCriteria().andIdEqualTo(id)
.andPlatformEqualTo(key);
if (issuesMapper.selectByExample(issuesExample).size() <= 0) {
// 插入缺陷表
issues = insertIssues(issuesRequest);
}
// 用例与第三方缺陷平台中的缺陷关联
handleTestCaseIssues(issuesRequest);
} else {
MSException.throwException("请确认该Zentao账号是否开启超级model调用接口权限");
}
// 如果是复制新增, 同步MS附件到Zentao
if (StringUtils.isNotEmpty(issuesRequest.getCopyIssueId())) {
AttachmentRequest request = new AttachmentRequest();
request.setBelongId(issuesRequest.getCopyIssueId());
request.setBelongType(AttachmentType.ISSUE.type());
List<String> attachmentIds = attachmentService.getAttachmentIdsByParam(request);
if (CollectionUtils.isNotEmpty(attachmentIds)) {
attachmentIds.forEach(attachmentId -> {
FileAttachmentMetadata fileAttachmentMetadata = attachmentService.getFileAttachmentMetadataByFileId(attachmentId);
File file = new File(fileAttachmentMetadata.getFilePath() + File.separator + fileAttachmentMetadata.getName());
zentaoClient.uploadAttachment("bug", issuesRequest.getPlatformId(), file);
});
}
}
return issues;
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
setUserConfig();
MultiValueMap<String, Object> param = buildUpdateParam(request);
if (request.getTransitions() != null) {
request.setPlatformStatus(request.getTransitions().getValue());
}
handleIssueUpdate(request);
this.handleZentaoBugStatus(param);
zentaoClient.updateIssue(request.getPlatformId(), param);
}
private void handleZentaoBugStatus(MultiValueMap<String, Object> param) {
if (!param.containsKey("status")) {
return;
}
List<Object> status = param.get("status");
if (CollectionUtils.isEmpty(status)) {
return;
}
try {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = (String) status.get(0);
if (StringUtils.equals(str, "resolved")) {
param.add("resolvedDate", format.format(new Date()));
} else if (StringUtils.equals(str, "closed")) {
param.add("closedDate", format.format(new Date()));
if (!param.containsKey("resolution")) {
// 解决方案默认为已解决
param.add("resolution", "fixed");
}
}
} catch (Exception e) {
//
}
}
private MultiValueMap<String, Object> buildUpdateParam(IssuesUpdateRequest issuesRequest) {
issuesRequest.setPlatform(key);
String projectId = getProjectId(issuesRequest.getProjectId());
if (StringUtils.isBlank(projectId)) {
MSException.throwException("未关联禅道项目ID.");
}
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("product", projectId);
paramMap.add("title", issuesRequest.getTitle());
if (issuesRequest.getTransitions() != null) {
paramMap.add("status", issuesRequest.getTransitions().getValue());
}
addCustomFields(issuesRequest, paramMap);
String description = issuesRequest.getDescription();
String zentaoSteps = description;
// transfer description
try {
zentaoSteps = ms2ZentaoDescription(description);
zentaoSteps = zentaoSteps.replaceAll("\\n", "<br/>");
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
LogUtil.info("zentao description transfer: " + zentaoSteps);
paramMap.add("steps", zentaoSteps);
if (!CollectionUtils.isEmpty(issuesRequest.getZentaoBuilds())) {
List<String> builds = issuesRequest.getZentaoBuilds();
builds.forEach(build -> paramMap.add("openedBuild[]", build));
} else {
paramMap.add("openedBuild", "trunk");
}
if (StringUtils.isNotBlank(issuesRequest.getZentaoAssigned())) {
paramMap.add("assignedTo", issuesRequest.getZentaoAssigned());
}
return paramMap;
}
@Override
public void deleteIssue(String id) {
IssuesWithBLOBs issuesWithBLOBs = issuesMapper.selectByPrimaryKey(id);
super.deleteIssue(id);
zentaoClient.deleteIssue(issuesWithBLOBs.getPlatformId());
}
@Override
public void testAuth() {
zentaoClient.login();
}
@Override
public void userAuth(UserDTO.PlatformInfo userInfo) {
setUserConfig(userInfo);
zentaoClient.login();
}
public ZentaoConfig getConfig() {
return getConfig(key, ZentaoConfig.class);
}
public ZentaoConfig setConfig() {
ZentaoConfig config = getConfig();
zentaoClient.setConfig(config);
return config;
}
public ZentaoConfig setUserConfig() {
return setUserConfig(getUserPlatInfo(this.workspaceId));
}
public ZentaoConfig setUserConfig(UserDTO.PlatformInfo userPlatInfo) {
ZentaoConfig zentaoConfig = getConfig();
if (userPlatInfo != null && StringUtils.isNotBlank(userPlatInfo.getZentaoUserName())
&& StringUtils.isNotBlank(userPlatInfo.getZentaoPassword())) {
zentaoConfig.setAccount(userPlatInfo.getZentaoUserName());
zentaoConfig.setPassword(userPlatInfo.getZentaoPassword());
}
zentaoClient.setConfig(zentaoConfig);
return zentaoConfig;
}
@Override
public List<PlatformUser> getPlatformUser() {
String session = zentaoClient.login();
HttpHeaders httpHeaders = new HttpHeaders();
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(httpHeaders);
RestTemplate restTemplate = new RestTemplate();
String getUser = zentaoClient.requestUrl.getUserGet();
ResponseEntity<String> responseEntity = restTemplate.exchange(getUser + session,
HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
Map obj = JSON.parseMap(body);
LogUtil.info("zentao user " + obj);
List data = JSON.parseArray(obj.get("data").toString());
List<PlatformUser> users = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
Map o = (Map) data.get(i);
PlatformUser platformUser = new PlatformUser();
String account = o.get("account").toString();
String username = o.get("realname").toString();
platformUser.setName(username);
platformUser.setUser(account);
users.add(platformUser);
}
return users;
}
@Override
public void syncIssues(Project project, List<IssuesDao> issues) {
HashMap<String, List<CustomFieldResourceDTO>> customFieldMap = new HashMap<>();
issues.forEach(item -> {
IssuesWithBLOBs issue = issuesMapper.selectByPrimaryKey(item.getId());
Map bug = zentaoClient.getBugById(item.getPlatformId());
issue = getUpdateIssues(issue, bug);
customFieldMap.put(item.getId(), baseCustomFieldService.getCustomFieldResourceDTO(issue.getCustomFields()));
issue.setId(item.getId());
issuesMapper.updateByPrimaryKeySelective(issue);
syncZentaoIssueAttachments(issue);
});
customFieldIssuesService.batchEditFields(customFieldMap);
}
public List<ZentaoBuild> getBuilds() {
Map<String, Object> builds = zentaoClient.getBuildsByCreateMetaData(getProjectId(projectId));
if (builds == null || builds.isEmpty()) {
builds = zentaoClient.getBuilds(getProjectId(projectId));
}
List<ZentaoBuild> res = new ArrayList<>();
builds.forEach((k, v) -> {
if (StringUtils.isNotBlank(k)) {
res.add(new ZentaoBuild(k, v.toString()));
}
});
return res;
}
private String uploadFile(FileSystemResource resource) {
String id = "";
String session = zentaoClient.login();
HttpHeaders httpHeaders = new HttpHeaders();
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("files", resource);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(paramMap, httpHeaders);
RestTemplate restTemplate = new RestTemplate();
try {
String fileUpload = zentaoClient.requestUrl.getFileUpload();
ResponseEntity<String> responseEntity = restTemplate.exchange(fileUpload, HttpMethod.POST, requestEntity,
String.class, null, session);
String body = responseEntity.getBody();
Map obj = JSON.parseMap(body);
Map data = (Map) JSON.parseObject(obj.get("data").toString());
Set<String> set = data.keySet();
if (!set.isEmpty()) {
id = (String) set.toArray()[0];
}
} catch (Exception e) {
LogUtil.error(e, e.getMessage());
}
LogUtil.info("upload file id: " + id);
return id;
}
private String ms2ZentaoDescription(String msDescription) {
String imgUrlRegex = "!\\[.*?]\\(/resource/md/get(.*?\\..*?)\\)";
String zentaoSteps = msDescription.replaceAll(imgUrlRegex, zentaoClient.requestUrl.getReplaceImgUrl());
Matcher matcher = zentaoClient.requestUrl.getImgPattern().matcher(zentaoSteps);
while (matcher.find()) {
// get file name
String originSubUrl = matcher.group(1);
if (originSubUrl.contains("/url?url=") || originSubUrl.contains("/path?")) {
String path = URLDecoder.decode(originSubUrl, StandardCharsets.UTF_8);
String fileName;
if (path.indexOf("fileID") > 0) {
fileName = path.substring(path.indexOf("fileID") + 7);
} else {
fileName = path.substring(path.indexOf("file-read-") + 10);
}
zentaoSteps = zentaoSteps.replaceAll(Pattern.quote(originSubUrl), fileName);
} else {
String fileName = originSubUrl.substring(10);
// get file
ResponseEntity<FileSystemResource> mdImage = resourceService.getMdImage(fileName);
// upload zentao
String id = uploadFile(mdImage.getBody());
// todo delete local file
int index = fileName.lastIndexOf(".");
String suffix = "";
if (index != -1) {
suffix = fileName.substring(index);
}
// replace id
zentaoSteps = zentaoSteps.replaceAll(Pattern.quote(originSubUrl), id + suffix);
}
}
// image link
String netImgRegex = "!\\[(.*?)]\\((http.*?)\\)";
return zentaoSteps.replaceAll(netImgRegex, "<img src=\"$2\" alt=\"$1\"/>");
}
private String zentao2MsDescription(String ztDescription) {
String imgRegex = "<img src.*?/>";
Pattern pattern = Pattern.compile(imgRegex);
Matcher matcher = pattern.matcher(ztDescription);
while (matcher.find()) {
if (StringUtils.isNotEmpty(matcher.group())) {
// img标签内容
String imgPath = matcher.group();
// 解析标签内容为图片超链接格式进行替换
String src = getMatcherResultForImg("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)", imgPath);
String alt = getMatcherResultForImg("alt\\s*=\\s*\"?(.*?)(\"|>|\\s+)", imgPath);
String hyperLinkPath = packageDescriptionByPathAndName(src, alt);
imgPath = transferSpecialCharacter(imgPath);
ztDescription = ztDescription.replaceAll(imgPath, hyperLinkPath);
}
}
return ztDescription;
}
private String packageDescriptionByPathAndName(String path, String name) {
String result = "";
if (StringUtils.isNotEmpty(path)) {
if (!path.startsWith("http")) {
if (path.startsWith("{") && path.endsWith("}")) {
String srcContent = path.substring(1, path.length() - 1);
if (StringUtils.isEmpty(name)) {
name = srcContent;
}
if (Arrays.stream(imgArray).anyMatch(imgType -> StringUtils.equals(imgType, srcContent.substring(srcContent.indexOf('.') + 1)))) {
if (zentaoClient instanceof ZentaoGetClient) {
path = zentaoClient.getBaseUrl() + "/index.php?m=file&f=read&fileID=" + srcContent;
} else {
// 禅道开源版
path = zentaoClient.getBaseUrl() + "/file-read-" + srcContent;
}
} else {
return result;
}
} else {
name = name.replaceAll("&amp;", "&");
path = path.replaceAll("&amp;", "&");
}
StringBuilder stringBuilder = new StringBuilder();
for (String item : path.split("&")) {
// 去掉多余的参数
if (!StringUtils.containsAny(item, "platform", "workspaceId")) {
stringBuilder.append(item);
stringBuilder.append("&");
}
}
path = getProxyPath(stringBuilder.toString());
}
// 图片与描述信息之间需换行否则无法预览图片
result = "\n\n![" + name + "](" + path + ")";
}
return result;
}
private String getMatcherResultForImg(String regex, String targetStr) {
String result = "";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(targetStr);
while (matcher.find()) {
result = matcher.group(1);
}
return result;
}
@Override
public Boolean checkProjectExist(String relateId) {
return zentaoClient.checkProjectExist(relateId);
}
@Override
public void syncIssuesAttachment(IssuesUpdateRequest issuesRequest, File file, AttachmentSyncType syncType) {
if ("upload".equals(syncType.syncOperateType())) {
zentaoClient.uploadAttachment("bug", issuesRequest.getPlatformId(), file);
} else if ("delete".equals(syncType.syncOperateType())) {
Map bugInfo = zentaoClient.getBugById(issuesRequest.getPlatformId());
Map<String, Object> zenFiles = (Map) bugInfo.get("files");
for (String fileId : zenFiles.keySet()) {
Map fileInfo = (Map) zenFiles.get(fileId);
if (file.getName().equals(fileInfo.get("title"))) {
zentaoClient.deleteAttachment(fileId);
break;
}
}
}
}
public void syncZentaoIssueAttachments(IssuesWithBLOBs issue) {
List<String> znetaoAttachmentsName = new ArrayList<String>();
AttachmentRequest request = new AttachmentRequest();
request.setBelongType(AttachmentType.ISSUE.type());
request.setBelongId(issue.getId());
List<FileAttachmentMetadata> allMsAttachments = attachmentService.listMetadata(request);
List<String> msAttachmentsName = allMsAttachments.stream().map(FileAttachmentMetadata::getName).collect(Collectors.toList());
Map bugInfo = zentaoClient.getBugById(issue.getPlatformId());
Object files = bugInfo.get("files");
Map<String, Object> zenFiles;
if (files instanceof List && ((List) files).size() == 0) {
zenFiles = null;
} else {
zenFiles = (Map) files;
}
// 同步禅道中新的附件
if (zenFiles != null) {
for (String fileId : zenFiles.keySet()) {
Map fileInfo = (Map) zenFiles.get(fileId);
String filename = fileInfo.get("title").toString();
znetaoAttachmentsName.add(filename);
if (!msAttachmentsName.contains(filename)) {
try {
byte[] bytes = zentaoClient.getAttachmentBytes(fileId);
FileAttachmentMetadata fileAttachmentMetadata = attachmentService.saveAttachmentByBytes(bytes, AttachmentType.ISSUE.type(), issue.getId(), filename);
AttachmentModuleRelation attachmentModuleRelation = new AttachmentModuleRelation();
attachmentModuleRelation.setAttachmentId(fileAttachmentMetadata.getId());
attachmentModuleRelation.setRelationId(issue.getId());
attachmentModuleRelation.setRelationType(AttachmentType.ISSUE.type());
attachmentModuleRelationMapper.insert(attachmentModuleRelation);
} catch (Exception e) {
LogUtil.error(e);
}
}
}
}
// 删除禅道中不存在的附件
if (CollectionUtils.isNotEmpty(allMsAttachments)) {
List<FileAttachmentMetadata> deleteMsAttachments = allMsAttachments.stream()
.filter(msAttachment -> !znetaoAttachmentsName.contains(msAttachment.getName())).collect(Collectors.toList());
deleteMsAttachments.forEach(fileAttachmentMetadata -> {
List<String> ids = List.of(fileAttachmentMetadata.getId());
AttachmentModuleRelationExample example = new AttachmentModuleRelationExample();
example.createCriteria().andAttachmentIdIn(ids).andRelationTypeEqualTo(AttachmentType.ISSUE.type());
// 删除MS附件及关联数据
attachmentService.deleteAttachmentByIds(ids);
attachmentService.deleteFileAttachmentByIds(ids);
attachmentModuleRelationMapper.deleteByExample(example);
});
}
}
@Override
public List<PlatformStatusDTO> getTransitions(String issueKey) {
List<PlatformStatusDTO> platformStatusDTOS = new ArrayList<>();
for (ZentaoIssuePlatformStatus status : ZentaoIssuePlatformStatus.values()) {
PlatformStatusDTO platformStatusDTO = new PlatformStatusDTO();
platformStatusDTO.setValue(status.name());
platformStatusDTO.setLabel(status.getName());
platformStatusDTOS.add(platformStatusDTO);
}
return platformStatusDTOS;
}
@Override
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
return zentaoClient.proxyForGet(path, responseEntityClazz);
}
}

View File

@ -1,14 +1,9 @@
package io.metersphere.service.wapper;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.service.PlatformPluginService;
import io.metersphere.service.issue.platform.IssueFactory;
import io.metersphere.xpack.track.dto.request.IssuesRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@ -16,8 +11,6 @@ import javax.annotation.Resource;
@Transactional(rollbackFor = Exception.class)
public class IssueProxyResourceService {
@Resource
private RestTemplate restTemplate;
@Resource
private PlatformPluginService platformPluginService;
@ -25,20 +18,13 @@ public class IssueProxyResourceService {
* http 代理
* 如果当前访问地址是 https直接访问 http 的图片资源
* 由于浏览器的安全机制http 会被转成 https
*
* @param path
* @param platform
* @return
*/
public ResponseEntity<byte[]> getMdImageByPath(String path, String platform, String workspaceId) {
if (StringUtils.equals(IssuesManagePlatform.Zentao.name(), platform)) {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(workspaceId);
return IssueFactory.createPlatform(platform, issuesRequest)
.proxyForGet(path, byte[].class);
} else {
return platformPluginService.getPlatform(platform, workspaceId)
.proxyForGet(path, byte[].class);
}
return platformPluginService.getPlatform(platform, workspaceId)
.proxyForGet(path, byte[].class);
}
}

View File

@ -20,6 +20,9 @@ public class PluginManagerUtil {
* @param inputStream
*/
public static void loadPlugin(String id, PluginManager pluginManager, InputStream inputStream) {
if (inputStream == null) {
return;
}
if (pluginManager == null) {
pluginManager = new PluginManager();
}

View File

@ -56,14 +56,6 @@ export function getIssues(currentPage, pageSize, param) {
return post(BASE_URL + "list/" + currentPage + '/' + pageSize, param);
}
export function getZentaoBuilds(param) {
return post(BASE_URL + "zentao/builds", param);
}
export function getZentaoUser(param) {
return post(BASE_URL + "zentao/user", param);
}
export function getTapdUser(param) {
return post(BASE_URL + "tapd/user", param);
}
@ -184,16 +176,22 @@ export function deleteIssueRelate(param) {
return post('/issues/delete/relate', param);
}
function parseOptions(customFields) {
if (customFields) {
customFields.forEach(item => {
if (item.options) {
item.options = JSON.parse(item.options);
}
});
}
}
export function getIssueThirdPartTemplate() {
return get('/issues/thirdpart/template/' + getCurrentProjectID())
.then((response) => {
let template = response.data;
if (template.customFields) {
template.customFields.forEach(item => {
if (item.options) {
item.options = JSON.parse(item.options);
}
});
parseOptions(template.customFields);
}
return template
});
@ -210,8 +208,8 @@ export function getJiraIssueType(param) {
return post('/issues/jira/issuetype', param);
}
export function getPlatformTransitions(param) {
return post('/issues/platform/transitions', param);
export function getPlatformStatus(param) {
return post('/issues/platform/status', param);
}
export function enableThirdPartTemplate(projectId) {
@ -229,6 +227,10 @@ export function buildIssues(page) {
}
}
export function getPluginCustomFields(projectId) {
return get(BASE_URL + `plugin/custom/fields/${projectId}`);
}
export function getIssuePartTemplateWithProject(callback) {
getCurrentProject().then((response) => {
let currentProject = response.data;
@ -241,8 +243,13 @@ export function getIssuePartTemplateWithProject(callback) {
callback(template, currentProject);
});
} else {
getIssueTemplate()
.then((template) => {
Promise.all([getPluginCustomFields(currentProject.id), getIssueTemplate()])
.then(data => {
let pluginFields = data[0].data;
parseOptions(pluginFields);
let template = data[1];
template.customFields.push(...pluginFields);
if (callback)
callback(template, currentProject);
});

View File

@ -76,26 +76,6 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="hasZentaoId">
<el-form-item :label-width="formLabelWidth" :label="$t('test_track.issue.zentao_bug_build')"
prop="zentaoBuilds">
<el-select v-model="form.zentaoBuilds" multiple filterable
:placeholder="$t('test_track.issue.zentao_bug_build')">
<el-option v-for="(build, index) in Builds" :key="index" :label="build.name"
:value="build.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="hasZentaoId">
<el-form-item :label-width="formLabelWidth" :label="$t('test_track.issue.zentao_bug_assigned')"
prop="zentaoAssigned">
<el-select v-model="form.zentaoAssigned" filterable
:placeholder="$t('test_track.issue.please_choose_current_owner')">
<el-option v-for="(userInfo, index) in zentaoUsers" :key="index" :label="userInfo.name"
:value="userInfo.user"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<ms-form-divider :title="$t('test_track.case.other_info')"/>
@ -220,14 +200,12 @@ import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {
enableThirdPartTemplate,
getIssuePartTemplateWithProject,
getPlatformTransitions,
getPlatformStatus,
getIssuesById,
saveOrUpdateIssue,
saveFollow,
getFollow,
getComments,
getZentaoBuilds,
getZentaoUser,
getTapdUser
} from "@/api/issue";
import {
@ -293,16 +271,11 @@ export default {
creator: null,
remark: null,
tapdUsers: [],
zentaoBuilds: [],
zentaoAssigned: '',
platformStatus: null,
copyIssueId: ''
},
tapdUsers: [],
zentaoUsers: [],
Builds: [],
hasTapdId: false,
hasZentaoId: false,
platformTransitions: null,
currentProject: null,
toolbars: {
@ -392,8 +365,6 @@ export default {
creator: null,
remark: null,
tapdUsers: [],
zentaoBuilds: [],
zentaoAssigned: '',
platformStatus: null
};
this.customFieldForm = null;
@ -438,11 +409,6 @@ export default {
}
}
});
getIssuesById(data.id).then(response => {
this.form.tapdUsers = response.data.tapdUsers;
this.form.zentaoBuilds = response.data.zentaoBuilds;
this.form.zentaoAssigned = response.data.zentaoAssigned;
});
} else {
this.issueId = null;
this.form.follows = [];
@ -467,7 +433,7 @@ export default {
projectId: getCurrentProjectID(),
workspaceId: getCurrentWorkspaceId()
}
getPlatformTransitions(data).then(response => {
getPlatformStatus(data).then(response => {
if (response.data.length > 0) {
this.platformTransitions = response.data;
}
@ -478,19 +444,7 @@ export default {
projectId: this.projectId,
workspaceId: getCurrentWorkspaceId()
}
if (platform === 'Zentao') {
this.hasZentaoId = true;
getZentaoBuilds(data)
.then((response) => {
if (response.data) {
this.Builds = response.data;
}
getZentaoUser(data)
.then((response) => {
this.zentaoUsers = response.data;
})
})
} else if (platform === 'Tapd') {
if (platform === 'Tapd') {
this.hasTapdId = true;
getTapdUser(data)
.then((response) => {

View File

@ -171,7 +171,6 @@ import StatusTableItem from "@/business/common/tableItems/planview/StatusTableIt
import {testPlanTestCaseEdit, testPlanTestCaseGet} from "@/api/remote/plan/test-plan-test-case";
import {testPlanEditStatus} from "@/api/remote/plan/test-plan";
import {getTestTemplate} from "@/api/custom-field-template";
import {getCurrentProjectID} from "@/business/utils/sdk-utils";
import {checkProjectPermission} from "@/api/testCase";
export default {
@ -204,12 +203,6 @@ export default {
test: {},
activeTab: 'detail',
users: [],
Builds: [],
zentaoBuilds: [],
zentaoUsers: [],
zentaoAssigned: "",
hasTapdId: false,
hasZentaoId: false,
tableData: [],
comments: [],
testCaseTemplate: {},
@ -445,8 +438,6 @@ export default {
});
this.showDialog = true;
this.activeTab = 'detail';
this.hasTapdId = false;
this.hasZentaoId = false;
this.originalStatus = testCase.status;
this.setTitleWith();

View File

@ -206,8 +206,6 @@ export default {
api: {},
apiCase: {},
testCaseTemplate: {},
hasTapdId: false,
hasZentaoId: false,
formLabelWidth: '100px',
isCustomFiledActive: false,
oldReviewStatus: '',
@ -408,8 +406,6 @@ export default {
//
this.oldReviewStatus = testCase.reviewStatus;
this.activeTab = 'detail';
this.hasTapdId = false;
this.hasZentaoId = false;
listenGoBack(this.handleClose);
let initFuc = this.getTestCase;
this.setTitleWith();