feat(系统设置): 个人信息第三方账号插件化

This commit is contained in:
chenjianxing 2022-11-16 16:46:11 +08:00 committed by jianxing
parent 6c6fd3e2b3
commit 7b05cf730b
15 changed files with 220 additions and 46 deletions

View File

@ -0,0 +1,10 @@
import {post, get} from "../plugins/request";
const BASE_URL = "/global/platform/plugin/";
export function getPlatformAccountInfo() {
return get(BASE_URL + 'account/info');
}
export function validateAccountConfig(pluginId, config) {
return post(BASE_URL + `account/validate/${pluginId}`, config);
}

View File

@ -11,7 +11,8 @@
:label="$t('member.edit_password')" :label="$t('member.edit_password')"
class="setting-item"></el-tab-pane> class="setting-item"></el-tab-pane>
<el-tab-pane <el-tab-pane
v-if="hasPermission('PERSONAL_INFORMATION:READ+THIRD_ACCOUNT')&&(hasJira||hasTapd||hasZentao||hasAzure)&&hasPermission('WORKSPACE_SERVICE:READ')" v-if="hasPermission('PERSONAL_INFORMATION:READ+THIRD_ACCOUNT')
&&(platformAccountConfigs.length > 0 || hasTapd || hasZentao || hasAzure) && hasPermission('WORKSPACE_SERVICE:READ')"
name="third_account" :label="$t('commons.third_account')" class="setting-item"></el-tab-pane> name="third_account" :label="$t('commons.third_account')" class="setting-item"></el-tab-pane>
<el-tab-pane v-if="hasPermission('PERSONAL_INFORMATION:READ+UI_SETTING') && isXpack" name="commons.ui_setting" <el-tab-pane v-if="hasPermission('PERSONAL_INFORMATION:READ+UI_SETTING') && isXpack" name="commons.ui_setting"
:label="$t('commons.ui_setting')" :label="$t('commons.ui_setting')"
@ -24,7 +25,13 @@
<password-info v-if="activeIndex==='change_password'" :rule-form="ruleForm" @cancel="cancel"></password-info> <password-info v-if="activeIndex==='change_password'" :rule-form="ruleForm" @cancel="cancel"></password-info>
<ui-setting v-if="activeIndex==='commons.ui_setting'" @cancel="cancel"></ui-setting> <ui-setting v-if="activeIndex==='commons.ui_setting'" @cancel="cancel"></ui-setting>
<el-form v-if="activeIndex==='third_account'"> <el-form v-if="activeIndex==='third_account'">
<jira-user-info @auth="handleAuth" v-if="hasJira" :data="currentPlatformInfo"/> <div v-for="config in platformAccountConfigs" :key="config.key">
<platform-account-config
:config="config"
:account-config="currentPlatformInfo"
v-if="config.key === 'Jira'"
/>
</div>
<tapd-user-info @auth="handleAuth" v-if="hasTapd" :data="currentPlatformInfo"/> <tapd-user-info @auth="handleAuth" v-if="hasTapd" :data="currentPlatformInfo"/>
<zentao-user-info @auth="handleAuth" v-if="hasZentao" :data="currentPlatformInfo"/> <zentao-user-info @auth="handleAuth" v-if="hasZentao" :data="currentPlatformInfo"/>
<azure-devops-user-info @auth="handleAuth" v-if="hasAzure" :data="currentPlatformInfo"/> <azure-devops-user-info @auth="handleAuth" v-if="hasAzure" :data="currentPlatformInfo"/>
@ -51,24 +58,26 @@ import {getCurrentUser, getCurrentWorkspaceId} from "../../utils/token";
import {hasLicense, hasPermission} from "../../utils/permission"; import {hasLicense, hasPermission} from "../../utils/permission";
import ZentaoUserInfo from "./ZentaoUserInfo"; import ZentaoUserInfo from "./ZentaoUserInfo";
import TapdUserInfo from "./TapdUserInfo"; import TapdUserInfo from "./TapdUserInfo";
import JiraUserInfo from "./JiraUserInfo";
import AzureDevopsUserInfo from "./AzureDevopsUserInfo"; import AzureDevopsUserInfo from "./AzureDevopsUserInfo";
import {getIntegrationService} from "../../api/workspace"; import {getIntegrationService} from "../../api/workspace";
import {useUserStore} from "@/store"; import {useUserStore} from "@/store";
import {handleAuth as _handleAuth, getUserInfo, getWsAndPj, updateInfo} from "../../api/user"; import {handleAuth as _handleAuth, getUserInfo, getWsAndPj, updateInfo} from "../../api/user";
import PlatformAccountConfig from "./PlatformAccountConfig";
import {getPlatformAccountInfo} from "../../api/platform-plugin";
const userStore = useUserStore(); const userStore = useUserStore();
export default { export default {
name: "MsPersonRouter", name: "MsPersonRouter",
components: { components: {
PlatformAccountConfig,
MsMainContainer, MsMainContainer,
MsPersonFromSetting, MsPersonFromSetting,
MsApiKeys, MsApiKeys,
PasswordInfo, PasswordInfo,
ZentaoUserInfo, ZentaoUserInfo,
TapdUserInfo, TapdUserInfo,
JiraUserInfo,
AzureDevopsUserInfo, AzureDevopsUserInfo,
UiSetting UiSetting
}, },
@ -79,7 +88,6 @@ export default {
return { return {
activeIndex: '', activeIndex: '',
ruleForm: {}, ruleForm: {},
hasJira: false,
hasTapd: false, hasTapd: false,
hasZentao: false, hasZentao: false,
hasAzure: false, hasAzure: false,
@ -94,6 +102,7 @@ export default {
zentaoPassword: '', zentaoPassword: '',
azureDevopsPat: '' azureDevopsPat: ''
}, },
platformAccountConfigs: [],
result: {}, result: {},
loading: false, loading: false,
workspaceList: [], workspaceList: [],
@ -118,16 +127,7 @@ export default {
}, },
handleAuth(type) { handleAuth(type) {
let param = {...this.currentPlatformInfo}; let param = {...this.currentPlatformInfo};
if (type === 'Jira') { if (type === 'Zentao') {
if (!param.jiraAccount) {
this.$error(this.$t('organization.integration.input_api_account'));
return
} else if (!param.jiraPassword) {
this.$error(this.$t('organization.integration.input_api_password'));
return
}
} else if (type === 'Zentao') {
if (!param.zentaoUserName) { if (!param.zentaoUserName) {
this.$error(this.$t('organization.integration.input_api_account')); this.$error(this.$t('organization.integration.input_api_account'));
return return
@ -167,9 +167,6 @@ export default {
if (platforms.indexOf("Tapd") !== -1) { if (platforms.indexOf("Tapd") !== -1) {
this.hasTapd = true; this.hasTapd = true;
} }
if (platforms.indexOf("Jira") !== -1) {
this.hasJira = true;
}
if (platforms.indexOf("Zentao") !== -1) { if (platforms.indexOf("Zentao") !== -1) {
this.hasZentao = true; this.hasZentao = true;
} }
@ -194,6 +191,10 @@ export default {
}); });
}, },
initTableData() { initTableData() {
getPlatformAccountInfo()
.then((r) => {
this.platformAccountConfigs = r.data;
});
this.result = getUserInfo(encodeURIComponent(this.currentUser().id)) this.result = getUserInfo(encodeURIComponent(this.currentUser().id))
.then(response => { .then(response => {
let data = response.data; let data = response.data;

View File

@ -0,0 +1,100 @@
<template>
<el-form :model="accountConfig" ref="form" label-width="100px" size="small" :rules="rules">
<el-form-item :label="config.i18n ? $t(config.label) : config.label">
<ms-instructions-icon size="10" :content="config.i18n ? $t(config.instructionsInfo) : config.instructionsInfo"/>
</el-form-item>
<el-form-item
v-for="item in config.formItems"
:key="item.name"
:label="item.i18n ? $t(item.label) : item.label"
:prop="item.name">
<custom-filed-component :form="accountConfig"
:data="item"
prop="defaultValue"/>
</el-form-item>
<el-form-item>
<el-button type="primary" style="float: right" @click="handleAuth" size="mini">
{{ $t('commons.validate') }}
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import MsInstructionsIcon from "../MsInstructionsIcon";
import {getPlatformFormRules} from "../../utils/platform";
import CustomFiledComponent from "metersphere-frontend/src/components/template/CustomFiledComponent";
import {validateAccountConfig} from "../../api/platform-plugin";
export default {
name: "PlatformAccountConfig",
components: {MsInstructionsIcon, CustomFiledComponent},
props: {
config: {
type: Object,
default() {
return {}
},
},
accountConfig: {
type: Object,
default() {
return {}
},
}
},
data() {
return {
rules: {},
}
},
watch: {
accountConfig() {
this.init();
},
},
mounted() {
this.init();
},
methods: {
init() {
this.config.formItems.forEach(item => {
if (!item.options) {
item.options = [];
}
//
if (this.accountConfig[item.name]) {
this.$set(item, 'defaultValue', this.accountConfig[item.name]);
}
});
this.rules = getPlatformFormRules(this.config);
},
validate() {
return new Promise((resolve, reject) => {
this.$refs['form'].validate((valid) => {
if (!valid) {
reject();
}
resolve();
});
});
},
handleAuth() {
this.validate()
.then(() => {
validateAccountConfig(this.config.id, this.accountConfig)
.then(() => {
this.$success(this.$t('organization.integration.verified'));
});
});
},
}
}
</script>
<style scoped>
.instructions-icon {
margin-left: -5px;
}
</style>

View File

@ -774,7 +774,8 @@ const message = {
input_azure_url: 'Please enter Azure Devops Url', input_azure_url: 'Please enter Azure Devops Url',
input_azure_id: 'Please enter Azure Organization ID', input_azure_id: 'Please enter Azure Organization ID',
use_tip_azure: 'Azure Devops URL+PersonalAccessTokens(User Settings-Personal Access Tokens-New Token)', use_tip_azure: 'Azure Devops URL+PersonalAccessTokens(User Settings-Personal Access Tokens-New Token)',
jira_prompt_information: 'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used' jira_prompt_information: 'This information is the user authentication information for submitting defects through Jira. If it is not filled in, the default information configured in the workspace will be used',
jira_information: 'Jira information'
} }
}, },
project: { project: {

View File

@ -781,7 +781,8 @@ const message = {
input_azure_url: '请输入 Azure Devops 地址', input_azure_url: '请输入 Azure Devops 地址',
input_azure_organization_id: '请输入 Azure 组织ID', input_azure_organization_id: '请输入 Azure 组织ID',
use_tip_azure: 'Azure Devops 地址+令牌(账户设置-个人访问令牌-创建令牌)', use_tip_azure: 'Azure Devops 地址+令牌(账户设置-个人访问令牌-创建令牌)',
jira_prompt_information: '该信息为通过Jira提交缺陷的用户认证信息若未填写则使用工作空间中配置的默认信息' jira_prompt_information: '该信息为通过Jira提交缺陷的用户认证信息若未填写则使用工作空间中配置的默认信息',
jira_information: 'Jira 信息'
} }
}, },
project: { project: {

View File

@ -778,7 +778,8 @@ const message = {
input_azure_url: '請輸入 Azure Devops 地址', input_azure_url: '請輸入 Azure Devops 地址',
input_azure_organization_id: '請輸入 Azure 組織ID', input_azure_organization_id: '請輸入 Azure 組織ID',
use_tip_azure: 'Azure Devops 地址+令牌(賬戶設置-個人訪問令牌-創建令牌)', use_tip_azure: 'Azure Devops 地址+令牌(賬戶設置-個人訪問令牌-創建令牌)',
jira_prompt_information: '該信息為通過Jira提交缺陷的用戶認證信息若未填寫則使用工作空間中配置的默認信息' jira_prompt_information: '該信息為通過Jira提交缺陷的用戶認證信息若未填寫則使用工作空間中配置的默認信息',
jira_information: 'Jira 信息',
} }
}, },
project: { project: {

View File

@ -1,4 +1,4 @@
import i18n from "@/i18n"; import i18n from "../i18n";
export function getPlatformFormRules(config) { export function getPlatformFormRules(config) {
let rules = {}; let rules = {};

View File

@ -0,0 +1,28 @@
package io.metersphere.controller.remote;
import io.metersphere.service.remote.SystemSettingService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/global/platform/plugin")
// 前缀加 global 避免 setting 服务循环调用
public class BasePlatformPluginController {
@Resource
SystemSettingService systemSettingService;
@PostMapping("/**")
public Object list(HttpServletRequest request, @RequestBody Object param) {
String url = request.getRequestURI().replace("/global", "");
return systemSettingService.post(url, param);
}
@GetMapping("/**")
public Object get(HttpServletRequest request) {
String url = request.getRequestURI().replace("/global", "");
return systemSettingService.get(url);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.service.remote;
import io.metersphere.commons.constants.MicroServiceName;
import io.metersphere.service.RemoteService;
import org.springframework.stereotype.Service;
@Service
public class SystemSettingService extends RemoteService {
public SystemSettingService() {
super(MicroServiceName.SYSTEM_SETTING);
}
}

View File

@ -39,7 +39,7 @@ import {
validateProjectConfig, validateProjectConfig,
} from "@/api/platform-plugin"; } from "@/api/platform-plugin";
import CustomFiledComponent from "metersphere-frontend/src/components/template/CustomFiledComponent"; import CustomFiledComponent from "metersphere-frontend/src/components/template/CustomFiledComponent";
import {getPlatformFormRules} from "@/business/home/platform"; import {getPlatformFormRules} from "metersphere-frontend/src/utils/platform";
export default { export default {
name: "ProjectPlatformConfig", name: "ProjectPlatformConfig",

View File

@ -28,6 +28,11 @@ public class PlatformPluginController {
return platformPluginService.getProjectInfo(key); return platformPluginService.getProjectInfo(key);
} }
@GetMapping("/account/info")
public Object getAccountInfoList() {
return platformPluginService.getAccountInfoList();
}
@GetMapping("/resource/{pluginId}") @GetMapping("/resource/{pluginId}")
public void getPluginResource(@PathVariable("pluginId") String pluginId, @RequestParam("fileName") String fileName, HttpServletResponse response) { public void getPluginResource(@PathVariable("pluginId") String pluginId, @RequestParam("fileName") String fileName, HttpServletResponse response) {
platformPluginService.getPluginResource(pluginId, fileName, response); platformPluginService.getPluginResource(pluginId, fileName, response);
@ -37,11 +42,17 @@ public class PlatformPluginController {
public void validateIntegration(@PathVariable("pluginId") String pluginId, @RequestBody Map config) { public void validateIntegration(@PathVariable("pluginId") String pluginId, @RequestBody Map config) {
platformPluginService.validateIntegration(pluginId, config); platformPluginService.validateIntegration(pluginId, config);
} }
@PostMapping("/project/validate/{pluginId}") @PostMapping("/project/validate/{pluginId}")
public void validateProjectConfig(@PathVariable("pluginId") String pluginId, @RequestBody Map config) { public void validateProjectConfig(@PathVariable("pluginId") String pluginId, @RequestBody Map config) {
platformPluginService.validateProjectConfig(pluginId, config); platformPluginService.validateProjectConfig(pluginId, config);
} }
@PostMapping("/account/validate/{pluginId}")
public void validateAccountConfig(@PathVariable("pluginId") String pluginId, @RequestBody Map config) {
platformPluginService.validateAccountConfig(pluginId, config);
}
@PostMapping("/project/option") @PostMapping("/project/option")
public List<SelectOption> getProjectOption(@RequestBody PlatformProjectOptionRequest request) { public List<SelectOption> getProjectOption(@RequestBody PlatformProjectOptionRequest request) {
return platformPluginService.getProjectOption(request); return platformPluginService.getProjectOption(request);

View File

@ -109,6 +109,21 @@ public class PlatformPluginService {
return null; return null;
} }
public List getAccountInfoList() {
List<PluginWithBLOBs> plugins = basePluginService.getPlugins(PluginScenario.platform.name());
List<Map> configs = new ArrayList<>();
plugins.forEach(item -> configs.add(getFrontendMetaDataConfig(item, "accountConfig")));
// 过滤掉服务集成中没有的
List<ServiceIntegration> integrations = baseIntegrationService.getAll(SessionUtils.getCurrentWorkspaceId());
return configs.stream()
.filter(config ->
integrations.stream()
.filter(integration -> StringUtils.equals(integration.getPlatform(), config.get("key").toString()))
.collect(Collectors.toList()).size() > 0
).collect(Collectors.toList());
}
public List<SelectOption> getProjectOption(PlatformProjectOptionRequest request) { public List<SelectOption> getProjectOption(PlatformProjectOptionRequest request) {
IntegrationRequest integrationRequest = new IntegrationRequest(); IntegrationRequest integrationRequest = new IntegrationRequest();
BeanUtils.copyBean(integrationRequest, request); BeanUtils.copyBean(integrationRequest, request);
@ -131,10 +146,10 @@ public class PlatformPluginService {
public Map getFrontendMetaDataConfig(PluginWithBLOBs plugin, String configName) { public Map getFrontendMetaDataConfig(PluginWithBLOBs plugin, String configName) {
Map metaData = JSON.parseMap(plugin.getFormScript()); Map metaData = JSON.parseMap(plugin.getFormScript());
Map serviceIntegration = (Map) metaData.get(configName); Map config = (Map) metaData.get(configName);
serviceIntegration.put("id", metaData.get("id")); config.put("id", metaData.get("id"));
serviceIntegration.put("key", metaData.get("key")); config.put("key", metaData.get("key"));
return serviceIntegration; return config;
} }
public void getImage(InputStream in, HttpServletResponse response) { public void getImage(InputStream in, HttpServletResponse response) {
@ -181,13 +196,23 @@ public class PlatformPluginService {
} }
public void validateProjectConfig(String pluginId, Map projectConfig) { public void validateProjectConfig(String pluginId, Map projectConfig) {
Platform platform = getPlatformByPluginId(pluginId);
platform.validateProjectConfig(JSON.toJSONString(projectConfig));
}
private Platform getPlatformByPluginId(String pluginId) {
PluginMetaInfo pluginMetaInfo = pluginManager.getPluginMetaInfo(pluginId); PluginMetaInfo pluginMetaInfo = pluginManager.getPluginMetaInfo(pluginId);
IntegrationRequest integrationRequest = new IntegrationRequest(); IntegrationRequest integrationRequest = new IntegrationRequest();
integrationRequest.setPlatform(pluginMetaInfo.getKey()); integrationRequest.setPlatform(pluginMetaInfo.getKey());
integrationRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); integrationRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
ServiceIntegration serviceIntegration = baseIntegrationService.get(integrationRequest); ServiceIntegration serviceIntegration = baseIntegrationService.get(integrationRequest);
Platform platform = getPlatFormInstance(pluginId, serviceIntegration.getConfiguration()); Platform platform = getPlatFormInstance(pluginId, serviceIntegration.getConfiguration());
platform.validateProjectConfig(JSON.toJSONString(projectConfig)); return platform;
}
public void validateAccountConfig(String pluginId, Map accountConfig) {
Platform platform = getPlatformByPluginId(pluginId);
platform.validateUserConfig(JSON.toJSONString(accountConfig));
} }
public List<SelectOption> getPlatformOptions() { public List<SelectOption> getPlatformOptions() {

View File

@ -66,7 +66,7 @@ import {
saveServiceIntegration saveServiceIntegration
} from "../../../api/workspace"; } from "../../../api/workspace";
import {validateServiceIntegration} from "@/api/platform-plugin"; import {validateServiceIntegration} from "@/api/platform-plugin";
import {getPlatformFormRules} from "@/business/workspace/integration/platform"; import {getPlatformFormRules} from "metersphere-frontend/src/utils/platform";
export default { export default {
name: "PlatformConfig", name: "PlatformConfig",

View File

@ -1,15 +0,0 @@
import i18n from "@/i18n";
export function getPlatformFormRules(config) {
let rules = {};
if (config && config.formItems) {
config.formItems.forEach(item => {
rules[item.name] = {
required: item.required,
message: item.i18n ? i18n.t(item.message) : item.message,
trigger: ['change', 'blur']
}
});
}
return rules;
}

View File

@ -38,7 +38,7 @@ import {
getPlatformProjectOption, getPlatformProjectOption,
validateProjectConfig, validateProjectConfig,
} from "@/api/platform-plugin"; } from "@/api/platform-plugin";
import {getPlatformFormRules} from "@/business/workspace/integration/platform"; import {getPlatformFormRules} from "metersphere-frontend/src/utils/platform";
import CustomFiledComponent from "metersphere-frontend/src/components/template/CustomFiledComponent"; import CustomFiledComponent from "metersphere-frontend/src/components/template/CustomFiledComponent";
export default { export default {
@ -62,7 +62,6 @@ export default {
}, },
data() { data() {
return { return {
issueTypes: [],
form: {}, form: {},
rules: {}, rules: {},
config: {} config: {}