Merge branch 'main' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
e812265f40
|
@ -97,7 +97,7 @@ pipeline {
|
|||
|
||||
LOCAL_REPOSITORY=$(./mvnw help:evaluate -Dexpression=settings.localRepository --settings ./settings.xml -q -DforceStdout)
|
||||
|
||||
libraries=('api-test' 'performance-test' 'project-management' 'system-setting' 'test-track' 'report-stat')
|
||||
libraries=('api-test' 'performance-test' 'project-management' 'system-setting' 'test-track' 'report-stat' 'workstation')
|
||||
for library in "${libraries[@]}";
|
||||
do
|
||||
mkdir -p $library/backend/target/dependency && (cd $library/backend/target/dependency; jar -xf ../*.jar; cp $LOCAL_REPOSITORY/io/metersphere/metersphere-xpack/${REVISION}/metersphere-xpack-${REVISION}.jar ./BOOT-INF/lib/)
|
||||
|
@ -119,7 +119,7 @@ pipeline {
|
|||
try {
|
||||
sh '''#!/bin/bash -xe
|
||||
cd ${WORKSPACE}
|
||||
libraries=('framework/eureka' 'framework/gateway' 'api-test' 'performance-test' 'project-management' 'report-stat' 'system-setting' 'test-track')
|
||||
libraries=('framework/eureka' 'framework/gateway' 'api-test' 'performance-test' 'project-management' 'report-stat' 'system-setting' 'test-track' 'workstation')
|
||||
for library in "${libraries[@]}";
|
||||
do
|
||||
IMAGE_NAME=${library#*/}
|
||||
|
|
|
@ -110,13 +110,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
|
|
|
@ -554,8 +554,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
try {
|
||||
String value = keyValue.getValue() != null && keyValue.getValue().startsWith("@") ?
|
||||
ScriptEngineUtils.buildFunctionCallString(keyValue.getValue()) : keyValue.getValue();
|
||||
value = keyValue.isUrlEncode() ? "${__urlencode(" + value + ")}" : value;
|
||||
keyValueMap.put(keyValue.getName(), value);
|
||||
value = keyValue.isUrlEncode() ? StringUtils.join(StringUtils.join("${__urlencode(", value), ")}") : value;
|
||||
String key = keyValue.isUrlEncode() ? StringUtils.join(StringUtils.join("${__urlencode(", keyValue.getName()), ")}") : keyValue.getName();
|
||||
keyValueMap.put(key, value);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
|
@ -585,11 +586,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
stringBuffer.append("?");
|
||||
}
|
||||
this.getArguments().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue -> {
|
||||
stringBuffer.append(keyValue.getName());
|
||||
stringBuffer.append(keyValue.isUrlEncode() ? StringUtils.join(StringUtils.join("${__urlencode(", keyValue.getName()), ")}") : keyValue.getName());
|
||||
if (keyValue.getValue() != null) {
|
||||
try {
|
||||
String value = keyValue.getValue().startsWith("@") ? ScriptEngineUtils.buildFunctionCallString(keyValue.getValue()) : keyValue.getValue();
|
||||
value = keyValue.isUrlEncode() ? "${__urlencode(" + value + ")}" : value;
|
||||
value = keyValue.isUrlEncode() ? StringUtils.join(StringUtils.join("${__urlencode(", value), ")}") : value;
|
||||
if (StringUtils.isNotEmpty(value) && value.contains(StringUtils.CR)) {
|
||||
value = value.replaceAll(StringUtils.CR, StringUtils.EMPTY);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package io.metersphere.api.exec.engine;
|
|||
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.metersphere.api.dto.MsgDTO;
|
||||
import io.metersphere.base.domain.TestResource;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.constants.ExtendedParameter;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.WebSocketUtil;
|
||||
import io.metersphere.dto.JmeterRunRequestDTO;
|
||||
import io.metersphere.utils.LoggerUtil;
|
||||
import io.metersphere.xpack.resourcepool.engine.provider.ClientCredential;
|
||||
|
@ -21,6 +23,7 @@ import java.util.Set;
|
|||
|
||||
public class KubernetesTestEngine extends AbstractEngine {
|
||||
private JmeterRunRequestDTO runRequest;
|
||||
private final String DEBUG_ERROR = "DEBUG_ERROR";
|
||||
|
||||
// 初始化API调用
|
||||
public KubernetesTestEngine(JmeterRunRequestDTO runRequest) {
|
||||
|
@ -77,6 +80,14 @@ public class KubernetesTestEngine extends AbstractEngine {
|
|||
command.append(StringUtils.SPACE).append("http://127.0.0.1:8082/jmeter/").append(path);
|
||||
KubernetesApiExec.newExecWatch(client, clientCredential.getNamespace(), pod.getMetadata().getName(), command.toString());
|
||||
} catch (Exception e) {
|
||||
MsgDTO dto = new MsgDTO();
|
||||
dto.setExecEnd(false);
|
||||
dto.setContent(DEBUG_ERROR);
|
||||
dto.setReportId("send." + runRequest.getReportId());
|
||||
dto.setToReport(runRequest.getReportId());
|
||||
LoggerUtil.debug("send. " + runRequest.getReportId());
|
||||
WebSocketUtil.sendMessageSingle(dto);
|
||||
WebSocketUtil.onClose(runRequest.getReportId());
|
||||
LoggerUtil.error("当前报告:【" + runRequest.getReportId() + "】资源:【" + runRequest.getTestId() + "】CURL失败:", e);
|
||||
MSException.throwException(e);
|
||||
}
|
||||
|
|
|
@ -333,10 +333,14 @@ public class ApiTestCaseService {
|
|||
extApiDefinitionExecResultMapper.deleteByResourceId(testId);
|
||||
apiTestCaseMapper.deleteByPrimaryKey(testId);
|
||||
esbApiParamService.deleteByResourceId(testId);
|
||||
deleteBodyFiles(testId);
|
||||
// 删除附件关系
|
||||
extFileAssociationService.deleteByResourceId(testId);
|
||||
deleteFollows(testId);
|
||||
ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testId);
|
||||
if (apiTestCase != null) {
|
||||
String filePath = StringUtils.join(BODY_FILE_DIR, File.separator, apiTestCase.getId());
|
||||
FileUtil.deleteContents(new File(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteFollows(String testId) {
|
||||
|
@ -362,14 +366,6 @@ public class ApiTestCaseService {
|
|||
}
|
||||
}
|
||||
|
||||
public void deleteBodyFiles(String testId) {
|
||||
File file = new File(BODY_FILE_DIR + "/" + testId);
|
||||
FileUtil.deleteContents(file);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkNameExist(SaveApiTestCaseRequest request) {
|
||||
if (hasSameCase(request)) {
|
||||
MSException.throwException(Translator.get("case_name_is_already_exist") + ": " + request.getName());
|
||||
|
|
|
@ -503,6 +503,14 @@ export default {
|
|||
this.runningEvaluation(e.data);
|
||||
this.sort(this.fullTreeNodes);
|
||||
}
|
||||
if (e && e.data === 'DEBUG_ERROR') {
|
||||
this.$error(this.$t('api_definition.debug_pool_warning'));
|
||||
this.messageWebSocket.close();
|
||||
this.cleanHeartBeat();
|
||||
this.$EventBus.$emit('hide', this.scenarioId);
|
||||
this.$emit('refresh', this.debugResult);
|
||||
}
|
||||
|
||||
if (e.data && e.data.indexOf('MS_TEST_END') !== -1) {
|
||||
this.getReport();
|
||||
this.messageWebSocket.close();
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<pre>{{ request.headers }}</pre>
|
||||
</div>
|
||||
<div class="ms-div" v-if="request.url && request.url !== ''">
|
||||
Cookies :
|
||||
Cookie :
|
||||
<pre>{{ request.cookies }}</pre>
|
||||
</div>
|
||||
<div class="ms-div">
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<slot name="version"></slot>
|
||||
<ms-search :condition.sync="condition" :base-search-tip="$t('commons.search_by_id_name_tag')" @search="search">
|
||||
</ms-search>
|
||||
|
||||
<ms-table
|
||||
:data="tableData"
|
||||
:screen-height="isRelate ? 'calc(100vh - 400px)' : screenHeight"
|
||||
|
|
|
@ -1233,6 +1233,11 @@ export default {
|
|||
if (e && e.data === 'CONN_SUCCEEDED') {
|
||||
this.run();
|
||||
}
|
||||
if (e && e.data === 'DEBUG_ERROR') {
|
||||
this.$error(this.$t('api_definition.debug_pool_warning'));
|
||||
this.messageWebSocket.close();
|
||||
this.errorRefresh();
|
||||
}
|
||||
if (e.data && e.data.startsWith('result_')) {
|
||||
let data = JSON.parse(e.data.substring(7));
|
||||
this.reqTotal += 1;
|
||||
|
|
|
@ -292,6 +292,13 @@ export default {
|
|||
if (e && e.data === 'CONN_SUCCEEDED') {
|
||||
this.runDebug();
|
||||
}
|
||||
if (e && e.data === 'DEBUG_ERROR') {
|
||||
this.$error(this.$t('api_definition.debug_pool_warning'));
|
||||
this.loading = false;
|
||||
this.node.expanded = true;
|
||||
this.messageWebSocket.close();
|
||||
this.reload();
|
||||
}
|
||||
if (e.data && e.data.startsWith('result_')) {
|
||||
let data = JSON.parse(e.data.substring(7));
|
||||
this.debugCode(data);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
v-if="type === 'body'"
|
||||
:disabled="isReadOnly"
|
||||
v-model="scope.row.type"
|
||||
size="mini"
|
||||
@change="typeChange(item)">
|
||||
<el-option value="text" />
|
||||
<el-option value="file" />
|
||||
|
@ -150,8 +151,16 @@
|
|||
circle
|
||||
@click="remove(scope.$index)"
|
||||
:disabled="isDisable(scope.$index) || isReadOnly" />
|
||||
<i class="el-icon-top" style="cursor: pointer" @click="moveTop(scope.$index)" />
|
||||
<i class="el-icon-bottom" style="cursor: pointer" @click="moveBottom(scope.$index)" />
|
||||
<i
|
||||
class="el-icon-top"
|
||||
v-show="!(isDisable(scope.$index) || isReadOnly)"
|
||||
style="cursor: pointer"
|
||||
@click="moveTop(scope.$index)" />
|
||||
<i
|
||||
class="el-icon-bottom"
|
||||
v-show="!(isDisable(scope.$index) || isReadOnly)"
|
||||
style="cursor: pointer"
|
||||
@click="moveBottom(scope.$index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
|
@ -62,7 +62,11 @@ export default {
|
|||
if (e && e.data === 'CONN_SUCCEEDED') {
|
||||
this.run();
|
||||
}
|
||||
|
||||
if (e && e.data === 'DEBUG_ERROR') {
|
||||
this.$error(this.$t('api_definition.debug_pool_warning'));
|
||||
this.websocket.close();
|
||||
this.$emit('errorRefresh', '');
|
||||
}
|
||||
if (e.data && e.data.startsWith('result_')) {
|
||||
try {
|
||||
let data = e.data.substring(7);
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
:isStop="isStop"
|
||||
:run-data="runData"
|
||||
@runRefresh="runRefresh"
|
||||
@errorRefresh="errorRefresh"
|
||||
ref="runTest" />
|
||||
</el-card>
|
||||
<div v-if="scenario">
|
||||
|
@ -188,11 +189,14 @@ export default {
|
|||
this.reportId = getUUID().substring(0, 8);
|
||||
},
|
||||
runRefresh(data) {
|
||||
this.responseData = data;
|
||||
this.responseData = data || { type: 'DUBBO', responseResult: {}, subRequestResults: [] };
|
||||
this.loading = false;
|
||||
this.isStop = false;
|
||||
this.$refs.debugResult.reload();
|
||||
},
|
||||
errorRefresh() {
|
||||
this.runRefresh();
|
||||
},
|
||||
saveAsApi() {
|
||||
let obj = { request: this.request };
|
||||
obj.request.id = getUUID();
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
:isStop="isStop"
|
||||
:run-data="runData"
|
||||
@runRefresh="runRefresh"
|
||||
@errorRefresh="errorRefresh"
|
||||
ref="runTest" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
@ -240,13 +241,16 @@ export default {
|
|||
this.$emit('refreshModule');
|
||||
},
|
||||
runRefresh(data) {
|
||||
this.responseData = data;
|
||||
this.responseData = data || { type: 'HTTP', responseResult: {}, subRequestResults: [] };
|
||||
this.loading = false;
|
||||
this.isStop = false;
|
||||
if (this.$refs.debugResult) {
|
||||
this.$refs.debugResult.reload();
|
||||
}
|
||||
},
|
||||
errorRefresh() {
|
||||
this.runRefresh();
|
||||
},
|
||||
saveAsApi() {
|
||||
this.$refs['debugForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
:isStop="isStop"
|
||||
:run-data="runData"
|
||||
@runRefresh="runRefresh"
|
||||
@errorRefresh="errorRefresh"
|
||||
ref="runTest" />
|
||||
</el-card>
|
||||
<div v-if="scenario">
|
||||
|
@ -194,13 +195,16 @@ export default {
|
|||
this.reportId = getUUID().substring(0, 8);
|
||||
},
|
||||
runRefresh(data) {
|
||||
this.responseData = data;
|
||||
this.responseData = data || { type: 'JDBC', responseResult: {}, subRequestResults: [] };
|
||||
this.loading = false;
|
||||
this.isStop = false;
|
||||
if (this.$refs.debugResult) {
|
||||
this.$refs.debugResult.reload();
|
||||
}
|
||||
},
|
||||
errorRefresh() {
|
||||
this.runRefresh();
|
||||
},
|
||||
saveAsApi() {
|
||||
let obj = { request: this.request };
|
||||
obj.request.id = getUUID();
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
:isStop="isStop"
|
||||
:run-data="runData"
|
||||
@runRefresh="runRefresh"
|
||||
@errorRefresh="errorRefresh"
|
||||
ref="runTest" />
|
||||
</el-card>
|
||||
<div v-if="scenario">
|
||||
|
@ -193,13 +194,16 @@ export default {
|
|||
this.reportId = getUUID().substring(0, 8);
|
||||
},
|
||||
runRefresh(data) {
|
||||
this.responseData = data;
|
||||
this.responseData = data || { type: 'TCP', responseResult: {}, subRequestResults: [] };
|
||||
this.loading = false;
|
||||
this.isStop = false;
|
||||
if (this.$refs.debugResult) {
|
||||
this.$refs.debugResult.reload();
|
||||
}
|
||||
},
|
||||
errorRefresh() {
|
||||
this.runRefresh();
|
||||
},
|
||||
saveAsApi() {
|
||||
let obj = { request: this.request };
|
||||
obj.request.id = getUUID();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<api-params-config
|
||||
v-if="apiParamsConfigFields"
|
||||
@refresh="refreshApiParamsField"
|
||||
:storage-key="apiParamStorageKey"
|
||||
:api-params-config-fields="apiParamsConfigFields" />
|
||||
</div>
|
||||
</el-row>
|
||||
|
@ -69,40 +70,17 @@ export default {
|
|||
apiInfo: Object,
|
||||
},
|
||||
activated() {
|
||||
this.formatTableData();
|
||||
this.initTableColumn();
|
||||
},
|
||||
created: function () {
|
||||
this.formatTableData();
|
||||
this.initTableColumn();
|
||||
},
|
||||
mounted() {
|
||||
this.formatTableData();
|
||||
this.initTableColumn();
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
methods: {
|
||||
formatTableData() {
|
||||
if (this.tableData) {
|
||||
this.tableData.forEach((item) => {
|
||||
if (item.urlEncode !== null && item.urlEncode !== undefined) {
|
||||
if (item.urlEncode === true) {
|
||||
item.urlEncode = this.$t('commons.yes');
|
||||
} else {
|
||||
item.urlEncode = this.$t('commons.no');
|
||||
}
|
||||
}
|
||||
if (item.enable !== null && item.enable !== undefined) {
|
||||
if (item.enable === true) {
|
||||
item.enable = this.$t('commons.yes');
|
||||
} else {
|
||||
item.enable = this.$t('commons.no');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
refreshApiParamsField() {
|
||||
this.initTableColumn();
|
||||
this.reloadedApiVariable = false;
|
||||
|
@ -176,6 +154,24 @@ export default {
|
|||
returnJsonArr.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
returnJsonArr.forEach((item) => {
|
||||
if (item.urlEncode !== null && item.urlEncode !== undefined) {
|
||||
if (item.urlEncode === true) {
|
||||
item.urlEncode = this.$t('commons.yes');
|
||||
} else {
|
||||
item.urlEncode = this.$t('commons.no');
|
||||
}
|
||||
}
|
||||
if (item.enable !== null && item.enable !== undefined) {
|
||||
if (item.enable === true) {
|
||||
item.enable = this.$t('commons.yes');
|
||||
} else {
|
||||
item.enable = this.$t('commons.no');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return returnJsonArr;
|
||||
},
|
||||
formatBoolean(row, column, cellValue) {
|
||||
|
|
|
@ -214,7 +214,7 @@ export default {
|
|||
':\n' +
|
||||
this.response.headers +
|
||||
'\n' +
|
||||
'Cookies :\n' +
|
||||
'Cookie :' +
|
||||
this.response.cookies +
|
||||
'\n' +
|
||||
'Body:' +
|
||||
|
|
|
@ -234,7 +234,7 @@ export default {
|
|||
':\n' +
|
||||
this.response.headers +
|
||||
'\n' +
|
||||
'Cookies :\n' +
|
||||
'Cookie :' +
|
||||
this.response.cookies +
|
||||
'\n' +
|
||||
'Body:' +
|
||||
|
|
|
@ -166,7 +166,7 @@ export default {
|
|||
}
|
||||
|
||||
if (this.response.cookies) {
|
||||
this.reqMessages += 'Cookies :\n' + this.response.cookies + '\n';
|
||||
this.reqMessages += 'Cookie :' + this.response.cookies + '\n';
|
||||
}
|
||||
this.reqMessages += 'Body:' + '\n' + this.response.body;
|
||||
}
|
||||
|
|
|
@ -453,13 +453,24 @@ export default {
|
|||
this.runLoading = false;
|
||||
this.checkVersionEnable();
|
||||
},
|
||||
margeFiles(targetFiles, sourceFiles) {
|
||||
targetFiles.forEach((target) => {
|
||||
sourceFiles.forEach((source) => {
|
||||
if (target.uuid === source.uuid) {
|
||||
source.file = target.file;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
initLocalFile() {
|
||||
if (this.apiData.request && this.apiData.request.body) {
|
||||
if (this.apiData.request.body.binary && this.apiData.request.body.binary.length > 0) {
|
||||
this.apiData.request.body.binary.forEach((item) => {
|
||||
this.api.request.body.binary.forEach((api) => {
|
||||
if (item.uuid && api.uuid && item.uuid === api.uuid) {
|
||||
api = item;
|
||||
api.files = item.files;
|
||||
} else if (item.files && api.files) {
|
||||
this.margeFiles(item.files, api.files);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -469,6 +480,8 @@ export default {
|
|||
this.api.request.body.kvs.forEach((api) => {
|
||||
if (item.uuid && api.uuid && item.uuid === api.uuid && item.files && api.files) {
|
||||
api.files = item.files;
|
||||
} else if (item.files && api.files) {
|
||||
this.margeFiles(item.files, api.files);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -476,6 +489,7 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
|
|
|
@ -95,10 +95,10 @@ export default {
|
|||
borderWidth = 3;
|
||||
dataIsNotEmpty = true;
|
||||
protocolData = [
|
||||
{ value: this.apiData.httpCount, name: 'HTTP' },
|
||||
{ value: this.apiData.rpcCount, name: 'RPC' },
|
||||
{ value: this.apiData.tcpCount, name: 'TCP' },
|
||||
{ value: this.apiData.sqlCount, name: 'SQL' },
|
||||
{ value: this.apiData.httpCount > 0 ? this.apiData.httpCount : '-', name: 'HTTP' },
|
||||
{ value: this.apiData.rpcCount > 0 ? this.apiData.rpcCount : '-', name: 'RPC' },
|
||||
{ value: this.apiData.tcpCount > 0 ? this.apiData.tcpCount : '-', name: 'TCP' },
|
||||
{ value: this.apiData.sqlCount > 0 ? this.apiData.sqlCount : '-', name: 'SQL' },
|
||||
];
|
||||
}
|
||||
let optionData = {
|
||||
|
@ -109,6 +109,7 @@ export default {
|
|||
legend: {
|
||||
orient: 'vertical',
|
||||
icon: 'rect',
|
||||
inactiveBorderWidth: 0.1,
|
||||
selectedMode: dataIsNotEmpty,
|
||||
itemGap: 16,
|
||||
left: '50%',
|
||||
|
@ -141,12 +142,14 @@ export default {
|
|||
},
|
||||
data: protocolData,
|
||||
formatter: function (name) {
|
||||
//通过name获取到数组对象中的单个对象
|
||||
let singleData = protocolData.filter(function (item) {
|
||||
return item.name === name;
|
||||
});
|
||||
let value = singleData[0].value;
|
||||
return [`{protocol|${name}}`, `{num|${value}}`].join('');
|
||||
let showValue = singleData[0].value;
|
||||
if (showValue === '-') {
|
||||
showValue = 0;
|
||||
}
|
||||
return [`{protocol|${name}}`, `{num|${showValue}}`].join('');
|
||||
},
|
||||
},
|
||||
title: {
|
||||
|
|
|
@ -7,6 +7,8 @@ const message = {
|
|||
please_add_api_case: 'Please add api case',
|
||||
},
|
||||
api_definition: {
|
||||
debug_pool_warning:
|
||||
'Failed to call the resource pool, please check whether the configuration of the resource pool is normal',
|
||||
document: {
|
||||
name: 'name',
|
||||
value: 'value',
|
||||
|
|
|
@ -7,6 +7,7 @@ const message = {
|
|||
please_add_api_case: '请先添加接口用例',
|
||||
},
|
||||
api_definition: {
|
||||
debug_pool_warning: '调用资源池执行失败,请检查资源池是否配置正常',
|
||||
document: {
|
||||
name: '名称',
|
||||
value: '值',
|
||||
|
|
|
@ -7,6 +7,7 @@ const message = {
|
|||
please_add_api_case: '请先添加接口用例',
|
||||
},
|
||||
api_definition: {
|
||||
debug_pool_warning: '調用資源池執行失敗,請檢查資源池是否配置正常',
|
||||
document: {
|
||||
name: '名稱',
|
||||
value: '值',
|
||||
|
|
|
@ -284,13 +284,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public class SessionFilter implements WebFilter {
|
|||
private static final String[] TO_SUB_SERVICE = new String[]{"/license", "/system", "/resource", "/sso/callback/logout", "/sso/callback/cas/logout"};
|
||||
private static final String PERFORMANCE_DOWNLOAD_PREFIX = "/jmeter/";
|
||||
private static final String API_DOWNLOAD_PREFIX = "/api/jmeter/";
|
||||
private static final String TRACK_IMAGE_PREFIX = "/resource/md/get/url";
|
||||
private static final String TRACK_IMAGE_PREFIX = "/resource/md/get/path";
|
||||
|
||||
@Resource
|
||||
private DiscoveryClient discoveryClient;
|
||||
|
|
|
@ -48,6 +48,8 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
@Service
|
||||
|
@ -278,6 +280,7 @@ public class SSOService {
|
|||
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange(userInfoUrl, HttpMethod.GET, httpEntity, String.class);
|
||||
resultObj = JSON.parseObject(response.getBody(), new TypeReference<HashMap<String, Object>>() {});
|
||||
LogUtil.info("user info: " + response.getBody());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("fail to get user info", e);
|
||||
MSException.throwException("fail to get user info!");
|
||||
|
@ -293,6 +296,12 @@ public class SSOService {
|
|||
if (StringUtils.isBlank(userid)) {
|
||||
MSException.throwException("userid is empty!");
|
||||
}
|
||||
Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
|
||||
Matcher m = p.matcher(userid);
|
||||
if (m.find()) {
|
||||
MSException.throwException("userid cannot contain Chinese characters!");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(username)) {
|
||||
username = userid;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,8 @@ package io.metersphere.gateway.service;
|
|||
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.commons.constants.SessionConstants;
|
||||
import io.metersphere.commons.constants.UserGroupType;
|
||||
import io.metersphere.commons.constants.UserSource;
|
||||
import io.metersphere.commons.constants.UserStatus;
|
||||
import io.metersphere.base.mapper.ext.BaseProjectMapper;
|
||||
import io.metersphere.commons.constants.*;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.CodingUtil;
|
||||
|
@ -39,6 +37,8 @@ public class UserLoginService {
|
|||
private UserGroupPermissionMapper userGroupPermissionMapper;
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
@Resource
|
||||
private BaseProjectMapper baseProjectMapper;
|
||||
|
||||
public Optional<SessionUser> login(LoginRequest request, WebSession session, Locale locale) {
|
||||
UserDTO userDTO;
|
||||
|
@ -173,31 +173,55 @@ public class UserLoginService {
|
|||
|
||||
private void checkNewWorkspaceAndProject(WebSession session, UserDTO user) {
|
||||
List<UserGroup> userGroups = user.getUserGroups();
|
||||
List<String> projectGroupIds = user.getGroups()
|
||||
.stream().filter(ug -> StringUtils.equals(ug.getType(), UserGroupType.PROJECT))
|
||||
List<Group> groups = user.getGroups();
|
||||
|
||||
List<String> projectGroupIds = groups
|
||||
.stream()
|
||||
.filter(ug -> StringUtils.equals(ug.getType(), UserGroupType.PROJECT))
|
||||
.map(Group::getId)
|
||||
.collect(Collectors.toList());
|
||||
List<UserGroup> project = userGroups.stream().filter(ug -> projectGroupIds.contains(ug.getGroupId()))
|
||||
|
||||
List<UserGroup> projects = userGroups
|
||||
.stream()
|
||||
.filter(ug -> projectGroupIds.contains(ug.getGroupId()))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(project)) {
|
||||
List<String> workspaceIds = user.getGroups()
|
||||
|
||||
if (CollectionUtils.isEmpty(projects)) {
|
||||
List<String> workspaceIds = groups
|
||||
.stream()
|
||||
.filter(ug -> StringUtils.equals(ug.getType(), UserGroupType.WORKSPACE))
|
||||
.map(Group::getId)
|
||||
.collect(Collectors.toList());
|
||||
List<UserGroup> workspaces = userGroups.stream().filter(ug -> workspaceIds.contains(ug.getGroupId()))
|
||||
|
||||
List<UserGroup> workspaces = userGroups
|
||||
.stream()
|
||||
.filter(ug -> workspaceIds.contains(ug.getGroupId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (workspaces.size() > 0) {
|
||||
String wsId = workspaces.get(0).getSourceId();
|
||||
switchUserResource(session, "workspace", wsId, user);
|
||||
} else {
|
||||
// 用户登录之后没有项目和工作空间的权限就把值清空
|
||||
user.setLastWorkspaceId("");
|
||||
user.setLastProjectId("");
|
||||
updateUser(user);
|
||||
List<String> superGroupIds = groups
|
||||
.stream()
|
||||
.map(Group::getId)
|
||||
.filter(id -> StringUtils.equals(id, UserGroupConstants.SUPER_GROUP))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(superGroupIds)) {
|
||||
Project p = baseProjectMapper.selectOne();
|
||||
if (p != null) {
|
||||
switchSuperUserResource(session, p.getId(), p.getWorkspaceId(), user);
|
||||
}
|
||||
} else {
|
||||
// 用户登录之后没有项目和工作空间的权限就把值清空
|
||||
user.setLastWorkspaceId(StringUtils.EMPTY);
|
||||
user.setLastProjectId(StringUtils.EMPTY);
|
||||
updateUser(user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UserGroup userGroup = project.stream().filter(p -> StringUtils.isNotBlank(p.getSourceId()))
|
||||
UserGroup userGroup = projects.stream()
|
||||
.filter(p -> StringUtils.isNotBlank(p.getSourceId()))
|
||||
.collect(Collectors.toList()).get(0);
|
||||
String projectId = userGroup.getSourceId();
|
||||
Project p = projectMapper.selectByPrimaryKey(projectId);
|
||||
|
@ -231,6 +255,20 @@ public class UserLoginService {
|
|||
userMapper.updateByPrimaryKeySelective(newUser);
|
||||
}
|
||||
|
||||
private void switchSuperUserResource(WebSession session, String projectId, String workspaceId, UserDTO sessionUser) {
|
||||
// 获取最新UserDTO
|
||||
UserDTO user = getUserDTO(sessionUser.getId());
|
||||
User newUser = new User();
|
||||
user.setLastWorkspaceId(workspaceId);
|
||||
sessionUser.setLastWorkspaceId(workspaceId);
|
||||
user.setLastProjectId(projectId);
|
||||
BeanUtils.copyProperties(user, newUser);
|
||||
// 切换工作空间或组织之后更新 session 里的 user
|
||||
session.getAttributes().put(SessionConstants.ATTR_USER, SessionUser.fromUser(user, session.getId()));
|
||||
session.getAttributes().put(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, sessionUser.getId());
|
||||
userMapper.updateByPrimaryKeySelective(newUser);
|
||||
}
|
||||
|
||||
public UserDTO getLoginUser(String userId, List<String> list) {
|
||||
UserExample example = new UserExample();
|
||||
example.createCriteria().andIdEqualTo(userId).andSourceIn(list);
|
||||
|
|
|
@ -34,13 +34,7 @@
|
|||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.7 KiB |
|
@ -250,6 +250,7 @@ export default {
|
|||
itemStyle: {
|
||||
borderWidth: 0.1
|
||||
},
|
||||
inactiveBorderWidth:0.1,
|
||||
textStyle: {
|
||||
rich: {
|
||||
name: {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
background-color="rgba(0,0,0,0)"
|
||||
@select="handleSelect"
|
||||
router>
|
||||
<el-menu-item index="/workstation" v-xpack v-if="check('workstation')">
|
||||
<el-menu-item index="/workstation" v-if="check('workstation')">
|
||||
<div>
|
||||
<svg-icon iconClass="workstation" class-name="ms-menu-img"/>
|
||||
<span slot="title" class="ms-menu-item-title">{{ $t('commons.my_workstation') }}</span>
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<el-form label-position="right" label-width="115px" size="small">
|
||||
<el-form-item :label="'Jira '+ $t('commons.information')">
|
||||
<ms-instructions-icon size="10" :content="$t('organization.integration.jira_prompt_information')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="'Jira ' + $t('organization.integration.account')" prop="account">
|
||||
<el-input v-model="data.jiraAccount" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="password">
|
||||
<el-input v-model="data.jiraPassword" auto-complete="new-password"
|
||||
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="float: right" @click="$emit('auth', 'Jira')" size="mini">
|
||||
{{ $t('commons.validate') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsInstructionsIcon from "../MsInstructionsIcon";
|
||||
|
||||
export default {
|
||||
name: "JiraUserInfo",
|
||||
components: {MsInstructionsIcon},
|
||||
props: ['data'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.instructions-icon {
|
||||
margin-left: -5px;
|
||||
}
|
||||
</style>
|
|
@ -27,10 +27,6 @@ import MsDialogFooter from "../MsDialogFooter";
|
|||
import {removeGoBackListener} from "../../utils";
|
||||
import MsTableOperatorButton from "../MsTableOperatorButton";
|
||||
import {EMAIL_REGEX, PHONE_REGEX} from "../../utils/regex";
|
||||
import JiraUserInfo from "./JiraUserInfo";
|
||||
import TapdUserInfo from "./TapdUserInfo";
|
||||
import ZentaoUserInfo from "./ZentaoUserInfo";
|
||||
import AzureDevopsUserInfo from "./AzureDevopsUserInfo";
|
||||
import {useUserStore} from "@/store";
|
||||
import {updateInfo} from "../../api/user";
|
||||
|
||||
|
@ -38,7 +34,7 @@ const userStore = useUserStore();
|
|||
|
||||
export default {
|
||||
name: "MsPersonFromSetting",
|
||||
components: {ZentaoUserInfo, TapdUserInfo, JiraUserInfo, AzureDevopsUserInfo, MsDialogFooter, MsTableOperatorButton},
|
||||
components: {MsDialogFooter, MsTableOperatorButton},
|
||||
inject: [
|
||||
'reload',
|
||||
'reloadTopMenus'
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
class="setting-item"></el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-if="hasPermission('PERSONAL_INFORMATION:READ+THIRD_ACCOUNT')
|
||||
&&(platformAccountConfigs.length > 0 || hasTapd || hasZentao || hasAzure) && hasPermission('WORKSPACE_SERVICE:READ')"
|
||||
&&(platformAccountConfigs.length > 0 || hasTapd || hasAzure) && hasPermission('WORKSPACE_SERVICE:READ')"
|
||||
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"
|
||||
:label="$t('commons.ui_setting')"
|
||||
|
@ -33,7 +33,6 @@
|
|||
/>
|
||||
</div>
|
||||
<tapd-user-info @auth="handleAuth" v-if="hasTapd" :data="currentPlatformInfo"/>
|
||||
<zentao-user-info @auth="handleAuth" v-if="hasZentao" :data="currentPlatformInfo"/>
|
||||
<azure-devops-user-info @auth="handleAuth" v-if="hasAzure" :data="currentPlatformInfo"/>
|
||||
<el-form-item class="el-form-item-class">
|
||||
<el-button size="small" @click="cancel">{{ $t('commons.cancel') }}</el-button>
|
||||
|
@ -56,7 +55,6 @@ import PasswordInfo from "./PasswordInfo";
|
|||
import UiSetting from "./UiSetting";
|
||||
import {getCurrentUser, getCurrentWorkspaceId} from "../../utils/token";
|
||||
import {hasLicense, hasPermission} from "../../utils/permission";
|
||||
import ZentaoUserInfo from "./ZentaoUserInfo";
|
||||
import TapdUserInfo from "./TapdUserInfo";
|
||||
import AzureDevopsUserInfo from "./AzureDevopsUserInfo";
|
||||
import {getIntegrationService} from "../../api/workspace";
|
||||
|
@ -77,7 +75,6 @@ export default {
|
|||
MsPersonFromSetting,
|
||||
MsApiKeys,
|
||||
PasswordInfo,
|
||||
ZentaoUserInfo,
|
||||
TapdUserInfo,
|
||||
AzureDevopsUserInfo,
|
||||
UiSetting
|
||||
|
@ -90,7 +87,6 @@ export default {
|
|||
activeIndex: '',
|
||||
ruleForm: {},
|
||||
hasTapd: false,
|
||||
hasZentao: false,
|
||||
hasAzure: false,
|
||||
isXpack: false,
|
||||
updatePath: '/user/update/current',
|
||||
|
@ -131,15 +127,7 @@ export default {
|
|||
},
|
||||
handleAuth(type) {
|
||||
let param = {...this.currentPlatformInfo};
|
||||
if (type === 'Zentao') {
|
||||
if (!param.zentaoUserName) {
|
||||
this.$error(this.$t('organization.integration.input_api_account'));
|
||||
return
|
||||
} else if (!param.zentaoPassword) {
|
||||
this.$error(this.$t('organization.integration.input_api_password'));
|
||||
return
|
||||
}
|
||||
} else if (type === 'AzureDevops') {
|
||||
if (type === 'AzureDevops') {
|
||||
if (!param.azureDevopsPat) {
|
||||
this.$error(this.$t('organization.integration.input_azure_pat'));
|
||||
return
|
||||
|
@ -171,9 +159,6 @@ export default {
|
|||
if (platforms.indexOf("Tapd") !== -1) {
|
||||
this.hasTapd = true;
|
||||
}
|
||||
if (platforms.indexOf("Zentao") !== -1) {
|
||||
this.hasZentao = true;
|
||||
}
|
||||
if (platforms.indexOf("AzureDevops") !== -1) {
|
||||
this.hasAzure = true;
|
||||
}
|
||||
|
|
|
@ -52,9 +52,15 @@
|
|||
<el-input v-model="form.phone" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<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="showPlatformConfig(config.key)"
|
||||
/>
|
||||
</div>
|
||||
<tapd-user-info @auth="handleAuth" v-if="hasTapd" :data="currentPlatformInfo"/>
|
||||
<zentao-user-info @auth="handleAuth" v-if="hasZentao" :data="currentPlatformInfo"/>
|
||||
<azure-devops-user-info @auth="handleAuth" v-if="hasAzure" :data="currentPlatformInfo"/>
|
||||
<template v-slot:footer>
|
||||
<ms-dialog-footer
|
||||
|
@ -94,19 +100,20 @@ import {listenGoBack, removeGoBackListener} from "../../utils";
|
|||
import {getCurrentUser, getCurrentWorkspaceId} from "../../utils/token";
|
||||
import MsTableOperatorButton from "../MsTableOperatorButton";
|
||||
import {EMAIL_REGEX, PHONE_REGEX} from "../../utils/regex";
|
||||
import JiraUserInfo from "./JiraUserInfo";
|
||||
import TapdUserInfo from "./TapdUserInfo";
|
||||
import {getIntegrationService} from "../../api/workspace";
|
||||
import ZentaoUserInfo from "./ZentaoUserInfo";
|
||||
import AzureDevopsUserInfo from "./AzureDevopsUserInfo";
|
||||
import {useUserStore} from "@/store";
|
||||
import {handleAuth as _handleAuth, updateInfo, updatePassword} from "../../api/user";
|
||||
import {getPlatformAccountInfo} from "../../api/platform-plugin";
|
||||
import {ISSUE_PLATFORM_OPTION} from "../../utils/table-constants";
|
||||
import PlatformAccountConfig from "./PlatformAccountConfig";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
export default {
|
||||
name: "MsPersonSetting",
|
||||
components: {ZentaoUserInfo, TapdUserInfo, JiraUserInfo, AzureDevopsUserInfo, MsDialogFooter, MsTableOperatorButton},
|
||||
components: {TapdUserInfo, AzureDevopsUserInfo, MsDialogFooter, MsTableOperatorButton, PlatformAccountConfig},
|
||||
inject: [
|
||||
'reload'
|
||||
],
|
||||
|
@ -128,10 +135,9 @@ export default {
|
|||
zentaoPassword: '',
|
||||
azureDevopsPat: ''
|
||||
},
|
||||
platformAccountConfigs: [],
|
||||
ruleForm: {},
|
||||
hasJira: false,
|
||||
hasTapd: false,
|
||||
hasZentao: false,
|
||||
hasAzure: false,
|
||||
rule: {
|
||||
name: [
|
||||
|
@ -188,6 +194,10 @@ export default {
|
|||
},
|
||||
|
||||
activated() {
|
||||
getPlatformAccountInfo()
|
||||
.then((r) => {
|
||||
this.platformAccountConfigs = r.data;
|
||||
});
|
||||
this.initTableData();
|
||||
// remove router query _token _csrf
|
||||
if (this.$route.query && Object.keys(this.$route.query).length > 0) {
|
||||
|
@ -198,6 +208,9 @@ export default {
|
|||
currentUser: () => {
|
||||
return getCurrentUser();
|
||||
},
|
||||
showPlatformConfig(platform) {
|
||||
return ISSUE_PLATFORM_OPTION.map(item => item.value).indexOf(platform) < 0;
|
||||
},
|
||||
edit: function (row) {
|
||||
this.updateVisible = true;
|
||||
this.form = Object.assign({}, row);
|
||||
|
@ -221,12 +234,6 @@ export default {
|
|||
if (platforms.indexOf("Tapd") !== -1) {
|
||||
this.hasTapd = true;
|
||||
}
|
||||
if (platforms.indexOf("Jira") !== -1) {
|
||||
this.hasJira = true;
|
||||
}
|
||||
if (platforms.indexOf("Zentao") !== -1) {
|
||||
this.hasZentao = true;
|
||||
}
|
||||
if (platforms.indexOf("AzureDevops") !== -1) {
|
||||
this.hasAzure = true;
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<el-form label-position="right" label-width="115px" size="small">
|
||||
<el-form-item :label="$t('organization.integration.zentao_info')">
|
||||
<ms-instructions-icon size="10" :content="$t('organization.integration.zentao_prompt_information')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.zentao_account')" prop="account">
|
||||
<el-input v-model="data.zentaoUserName" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.zentao_password')" prop="password">
|
||||
<el-input v-model="data.zentaoPassword" auto-complete="new-password"
|
||||
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="float: right" @click="$emit('auth', 'Zentao')" size="mini">
|
||||
{{ $t('commons.validate') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsInstructionsIcon from "../MsInstructionsIcon";
|
||||
|
||||
export default {
|
||||
name: "ZentaoUserInfo",
|
||||
components: {MsInstructionsIcon},
|
||||
props: ['data'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.instructions-icon {
|
||||
margin-left: -5px;
|
||||
}
|
||||
</style>
|
|
@ -1350,7 +1350,7 @@ const message = {
|
|||
case: "Case",
|
||||
responsible: "Executor",
|
||||
title: "Create api",
|
||||
path_info: "Please enter the URL of the interface, such as /api/demo/#{id}, where id is the path parameter",
|
||||
path_info: "Please enter the URL of the interface, such as /api/demo/${id}, where id is the path parameter",
|
||||
path_all_info: "Please enter the complete test address",
|
||||
fast_debug: "Fast debug",
|
||||
close_all_label: "close all label",
|
||||
|
|
|
@ -1360,7 +1360,7 @@ const message = {
|
|||
case: "用例",
|
||||
responsible: "责任人",
|
||||
title: "创建接口",
|
||||
path_info: "请输入接口的URL,如/api/demo/#{id},其中id为路径参数",
|
||||
path_info: "请输入接口的URL,如/api/demo/${id},其中id为路径参数",
|
||||
path_all_info: "请输入完整测试地址",
|
||||
fast_debug: "快捷调试",
|
||||
close_all_label: "关闭所有标签",
|
||||
|
|
|
@ -1357,7 +1357,7 @@ const message = {
|
|||
case: "用例",
|
||||
responsible: "責任人",
|
||||
title: "創建接口",
|
||||
path_info: "請輸入接口的URL,如/api/demo/#{id},其中id為路徑參數",
|
||||
path_info: "請輸入接口的URL,如/api/demo/${id},其中id為路徑參數",
|
||||
path_all_info: "請輸入完整測試地址",
|
||||
fast_debug: "快捷調試",
|
||||
close_all_label: "關閉所有標簽",
|
||||
|
|
|
@ -13,6 +13,20 @@ export function hasPermission(permission) {
|
|||
});
|
||||
});
|
||||
|
||||
let superGroupPermissions = user.userGroups.filter(ug => ug.group && ug.group.id === 'super_group')
|
||||
.flatMap(ug => ug.userGroupPermissions)
|
||||
.map(g => g.permissionId)
|
||||
.reduce((total, current) => {
|
||||
total.add(current);
|
||||
return total;
|
||||
}, new Set);
|
||||
|
||||
for (const p of superGroupPermissions) {
|
||||
if (p === permission) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// todo 权限验证
|
||||
let currentProjectPermissions = user.userGroups.filter(ug => ug.group && ug.group.type === 'PROJECT')
|
||||
.filter(ug => ug.sourceId === getCurrentProjectID())
|
||||
|
|
|
@ -68,7 +68,6 @@ export function CASE_TYPE_OPTION(){
|
|||
export const ISSUE_PLATFORM_OPTION = [
|
||||
{value: LOCAL, text: 'Local'},
|
||||
{value: TAPD, text: 'Tapd'},
|
||||
{value: ZEN_TAO, text: 'Zentao'},
|
||||
{value: AZURE_DEVOPS, text: 'Azure Devops'},
|
||||
];
|
||||
|
||||
|
|
|
@ -168,6 +168,17 @@
|
|||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>jmeter-plugins-dubbo</artifactId>
|
||||
<version>${jmeter-plugins-dubbo.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>avro</artifactId>
|
||||
<groupId>org.apache.avro</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>avro</artifactId>
|
||||
<groupId>org.apache.avro</groupId>
|
||||
<version>${avro.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -180,7 +191,7 @@
|
|||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-jsr223</artifactId>
|
||||
<version>${groovy.version}</version>
|
||||
<version>${codehaus-groovy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
|
||||
|
@ -255,6 +266,12 @@
|
|||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
|
||||
<version>${dingtalk-sdk.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>log4j</artifactId>
|
||||
<groupId>log4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
|
@ -272,13 +289,7 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -319,6 +319,12 @@
|
|||
<artifactId>json-lib</artifactId>
|
||||
<version>${json-lib.version}</version>
|
||||
<classifier>jdk15</classifier>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<groupId>commons-collections</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--java json schema json格式校验-->
|
||||
|
@ -369,13 +375,7 @@
|
|||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ public class PermissionConfig implements ApplicationRunner {
|
|||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
LogUtil.info("load permission form {} service permission.json file", service);
|
||||
try (InputStream inputStream = PermissionConfig.class.getResourceAsStream("/permission.json")){
|
||||
String permission = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||
stringRedisTemplate.opsForHash().put(RedisKey.MS_PERMISSION_KEY, service, permission);
|
||||
|
|
|
@ -18,7 +18,7 @@ public interface BaseProjectMapper {
|
|||
|
||||
int removeIssuePlatform(@Param("platform") String platform, @Param("workspaceId") String workspaceId);
|
||||
|
||||
List<ProjectDTO> getUserProject(@Param("proRequest") ProjectRequest request);
|
||||
List<Project> getUserProject(@Param("proRequest") ProjectRequest request);
|
||||
|
||||
String getSystemIdByProjectId(String projectId);
|
||||
|
||||
|
@ -49,4 +49,6 @@ public interface BaseProjectMapper {
|
|||
void updateUseDefaultCaseTemplateProject(@Param("originId") String originId,@Param("templateId") String templateId,@Param("projectId") String projectId);
|
||||
|
||||
List<String> getThirdPartProjectIds();
|
||||
|
||||
Project selectOne();
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
FROM project
|
||||
WHERE workspace_id = #{workspaceId}
|
||||
</select>
|
||||
<select id="getUserProject" resultType="io.metersphere.dto.ProjectDTO">
|
||||
<select id="getUserProject" resultType="io.metersphere.base.domain.Project">
|
||||
SELECT DISTINCT p.*
|
||||
FROM `group` g
|
||||
JOIN user_group ug ON g.id = ug.group_id
|
||||
|
@ -435,4 +435,7 @@
|
|||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="selectOne" resultType="io.metersphere.base.domain.Project">
|
||||
SELECT * FROM project LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
|
@ -34,4 +34,6 @@ public interface BaseUserMapper {
|
|||
void updateLastProjectIdIfNull(@Param("projectId") String projectId, @Param("userId") String userId);
|
||||
|
||||
void updateLastWorkspaceIdIfNull(@Param("workspaceId") String workspaceId, @Param("userId") String userId);
|
||||
|
||||
boolean isSuperUser(String userId);
|
||||
}
|
||||
|
|
|
@ -112,7 +112,9 @@
|
|||
FROM user
|
||||
<include refid="queryWhereCondition"/>
|
||||
</select>
|
||||
|
||||
<select id="isSuperUser" resultType="java.lang.Boolean">
|
||||
select count(*) from user_group where user_id = #{userId} and group_id = 'super_group'
|
||||
</select>
|
||||
<update id="updateLastProjectIdIfNull">
|
||||
UPDATE user SET last_project_id = #{projectId} WHERE id = #{userId}
|
||||
AND (last_project_id IS NULL OR last_project_id = '')
|
||||
|
|
|
@ -4,7 +4,7 @@ public enum FileType {
|
|||
JMX(".jmx"), CSV(".csv"), JSON(".json"), PDF(".pdf"),
|
||||
JPG(".jpg"), PNG(".png"), JPEG(".jpeg"), DOC(".doc"),
|
||||
XLSX(".xlsx"), DOCX(".docx"), JAR(".jar"), JS(".js"), TXT(".txt"),
|
||||
P12(".p12"), JKS(".jks"), PFX(".pfx"),
|
||||
P12(".p12"), JKS(".jks"), PFX(".pfx"), DCM(".dcm"),
|
||||
DER(".der"), CER(".cer"), PEM(".pem"), CRT(".crt"), SIDE(".side");
|
||||
|
||||
// 保存后缀
|
||||
|
|
|
@ -4,6 +4,7 @@ package io.metersphere.commons.constants;
|
|||
* 系统内置用户组常量
|
||||
*/
|
||||
public class UserGroupConstants {
|
||||
public static final String SUPER_GROUP = "super_group";
|
||||
public static final String ADMIN = "admin";
|
||||
public static final String ORG_ADMIN = "org_admin";
|
||||
public static final String ORG_MEMBER = "org_member";
|
||||
|
|
|
@ -29,7 +29,7 @@ public class BaseProjectController {
|
|||
* @return List<ProjectDTO>
|
||||
*/
|
||||
@PostMapping("/list/related")
|
||||
public List<ProjectDTO> getUserProject(@RequestBody ProjectRequest request) {
|
||||
public List<Project> getUserProject(@RequestBody ProjectRequest request) {
|
||||
return baseProjectService.getUserProject(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,4 +105,9 @@ public class BaseUserController {
|
|||
public void updateCurrentUserByResourceId(@PathVariable String resourceId) {
|
||||
baseUserService.updateCurrentUserByResourceId(resourceId);
|
||||
}
|
||||
|
||||
@GetMapping("/is/super/{userid}")
|
||||
public boolean isSuperUser(@PathVariable String userid) {
|
||||
return baseUserService.isSuperUser(userid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class SystemParameterController {
|
|||
|
||||
@GetMapping("timeout")
|
||||
public long getTimeout() {
|
||||
return env.getProperty("session.timeout", Long.class, 43200L); // 默认43200s, 12个小时
|
||||
return env.getProperty("spring.session.timeout", Long.class, 43200L); // 默认43200s, 12个小时
|
||||
}
|
||||
|
||||
@GetMapping("/mail/info")
|
||||
|
|
|
@ -11,4 +11,10 @@ public class GroupResource implements Serializable {
|
|||
private String id;
|
||||
private String name;
|
||||
private Boolean license = false;
|
||||
|
||||
/**
|
||||
* 系统设置、工作空间、项目类型 公用的权限模块
|
||||
* e.g. 个人信息
|
||||
*/
|
||||
private boolean global = false;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.metersphere.metadata.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.metersphere.base.mapper.UserMapper;
|
|||
import io.metersphere.base.mapper.ext.BaseProjectMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseProjectVersionMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseUserGroupMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseUserMapper;
|
||||
import io.metersphere.commons.constants.ProjectApplicationType;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
|
@ -54,6 +55,8 @@ public class BaseProjectService {
|
|||
private BaseProjectVersionMapper baseProjectVersionMapper;
|
||||
@Resource
|
||||
private BaseProjectApplicationService baseProjectApplicationService;
|
||||
@Resource
|
||||
private BaseUserMapper baseUserMapper;
|
||||
|
||||
|
||||
private String genSystemId() {
|
||||
|
@ -90,7 +93,19 @@ public class BaseProjectService {
|
|||
return baseProjectMapper.getProjectWithWorkspace(request);
|
||||
}
|
||||
|
||||
public List<ProjectDTO> getUserProject(ProjectRequest request) {
|
||||
public List<Project> getUserProject(ProjectRequest request) {
|
||||
boolean isSuper = baseUserMapper.isSuperUser(SessionUtils.getUserId());
|
||||
if (isSuper) {
|
||||
ProjectExample example = new ProjectExample();
|
||||
ProjectExample.Criteria criteria = example.createCriteria();
|
||||
if (StringUtils.isNotBlank(request.getName())) {
|
||||
criteria.andNameLike(request.getName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getWorkspaceId())) {
|
||||
criteria.andWorkspaceIdEqualTo(request.getWorkspaceId());
|
||||
}
|
||||
return projectMapper.selectByExample(example);
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getName())) {
|
||||
request.setName(StringUtils.wrapIfMissing(request.getName(), "%"));
|
||||
}
|
||||
|
@ -359,4 +374,8 @@ public class BaseProjectService {
|
|||
public void deleteFile(String fileId) {
|
||||
fileMetadataService.deleteFile(fileId);
|
||||
}
|
||||
|
||||
public Project selectOne() {
|
||||
return baseProjectMapper.selectOne();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -242,15 +242,24 @@ public class BaseUserService {
|
|||
// 获取最新UserDTO
|
||||
UserDTO user = getUserDTO(sessionUser.getId());
|
||||
User newUser = new User();
|
||||
|
||||
boolean isSuper = baseUserMapper.isSuperUser(sessionUser.getId());
|
||||
if (StringUtils.equals("workspace", sign)) {
|
||||
user.setLastWorkspaceId(sourceId);
|
||||
sessionUser.setLastWorkspaceId(sourceId);
|
||||
List<Project> projects = getProjectListByWsAndUserId(sessionUser.getId(), sourceId);
|
||||
if (projects.size() > 0) {
|
||||
if (CollectionUtils.isNotEmpty(projects)) {
|
||||
user.setLastProjectId(projects.get(0).getId());
|
||||
} else {
|
||||
user.setLastProjectId(StringUtils.EMPTY);
|
||||
if (isSuper) {
|
||||
ProjectExample example = new ProjectExample();
|
||||
example.createCriteria().andWorkspaceIdEqualTo(sourceId);
|
||||
List<Project> allWsProject = projectMapper.selectByExample(example);
|
||||
if (CollectionUtils.isNotEmpty(allWsProject)) {
|
||||
user.setLastProjectId(allWsProject.get(0).getId());
|
||||
}
|
||||
} else {
|
||||
user.setLastProjectId(StringUtils.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
BeanUtils.copyProperties(user, newUser);
|
||||
|
@ -259,6 +268,19 @@ public class BaseUserService {
|
|||
userMapper.updateByPrimaryKeySelective(newUser);
|
||||
}
|
||||
|
||||
private void switchSuperUserResource(String projectId, String workspaceId, UserDTO sessionUser) {
|
||||
// 获取最新UserDTO
|
||||
UserDTO user = getUserDTO(sessionUser.getId());
|
||||
User newUser = new User();
|
||||
user.setLastWorkspaceId(workspaceId);
|
||||
sessionUser.setLastWorkspaceId(workspaceId);
|
||||
user.setLastProjectId(projectId);
|
||||
BeanUtils.copyProperties(user, newUser);
|
||||
// 切换工作空间或组织之后更新 session 里的 user
|
||||
SessionUtils.putUser(SessionUser.fromUser(user, SessionUtils.getSessionId()));
|
||||
userMapper.updateByPrimaryKeySelective(newUser);
|
||||
}
|
||||
|
||||
private List<Project> getProjectListByWsAndUserId(String userId, String workspaceId) {
|
||||
ProjectExample projectExample = new ProjectExample();
|
||||
projectExample.createCriteria().andWorkspaceIdEqualTo(workspaceId);
|
||||
|
@ -418,10 +440,22 @@ public class BaseUserService {
|
|||
String wsId = workspaces.get(0).getSourceId();
|
||||
switchUserResource("workspace", wsId, user);
|
||||
} else {
|
||||
// 用户登录之后没有项目和工作空间的权限就把值清空
|
||||
user.setLastWorkspaceId(StringUtils.EMPTY);
|
||||
user.setLastProjectId(StringUtils.EMPTY);
|
||||
updateUser(user);
|
||||
List<String> superGroupIds = user.getGroups()
|
||||
.stream()
|
||||
.map(Group::getId)
|
||||
.filter(id -> StringUtils.equals(id, UserGroupConstants.SUPER_GROUP))
|
||||
.collect(Collectors.toList());
|
||||
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(superGroupIds)) {
|
||||
Project p = baseProjectMapper.selectOne();
|
||||
if (p != null) {
|
||||
switchSuperUserResource(p.getId(), p.getWorkspaceId(), user);
|
||||
}
|
||||
} else {
|
||||
// 用户登录之后没有项目和工作空间的权限就把值清空
|
||||
user.setLastWorkspaceId(StringUtils.EMPTY);
|
||||
user.setLastProjectId(StringUtils.EMPTY);
|
||||
updateUser(user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UserGroup userGroup = project.stream().filter(p -> StringUtils.isNotBlank(p.getSourceId()))
|
||||
|
@ -450,6 +484,8 @@ public class BaseUserService {
|
|||
user.setLastWorkspaceId(project.getWorkspaceId());
|
||||
updateUser(user);
|
||||
return true;
|
||||
} else {
|
||||
return baseUserMapper.isSuperUser(user.getId());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -496,6 +532,8 @@ public class BaseUserService {
|
|||
user.setLastWorkspaceId(wsId);
|
||||
updateUser(user);
|
||||
return true;
|
||||
} else {
|
||||
return baseUserMapper.isSuperUser(user.getId());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -739,4 +777,16 @@ public class BaseUserService {
|
|||
user.setLastWorkspaceId(project.getWorkspaceId());
|
||||
userMapper.updateByPrimaryKeySelective(user);
|
||||
}
|
||||
|
||||
public boolean isSuperUser(String userid) {
|
||||
if (StringUtils.isBlank(userid)) {
|
||||
MSException.throwException("userid is blank.");
|
||||
}
|
||||
return baseUserMapper.isSuperUser(userid);
|
||||
}
|
||||
|
||||
public List<String> getAllUserIds() {
|
||||
List<User> users = userMapper.selectByExample(new UserExample());
|
||||
return users.stream().map(User::getId).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.metersphere.base.domain.Workspace;
|
|||
import io.metersphere.base.domain.WorkspaceExample;
|
||||
import io.metersphere.base.mapper.WorkspaceMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseUserGroupMapper;
|
||||
import io.metersphere.base.mapper.ext.BaseUserMapper;
|
||||
import io.metersphere.dto.RelatedSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
@ -19,8 +20,14 @@ public class BaseWorkspaceService {
|
|||
private BaseUserGroupMapper baseUserGroupMapper;
|
||||
@Resource
|
||||
private WorkspaceMapper workspaceMapper;
|
||||
@Resource
|
||||
private BaseUserMapper baseUserMapper;
|
||||
|
||||
public List<Workspace> getWorkspaceListByUserId(String userId) {
|
||||
boolean isSuper = baseUserMapper.isSuperUser(userId);
|
||||
if (isSuper) {
|
||||
return workspaceMapper.selectByExample(new WorkspaceExample());
|
||||
}
|
||||
List<RelatedSource> relatedSource = baseUserGroupMapper.getRelatedSource(userId);
|
||||
List<String> wsIds = relatedSource
|
||||
.stream()
|
||||
|
|
|
@ -88,10 +88,8 @@ management.server.port=8083
|
|||
management.endpoints.web.exposure.include=*
|
||||
management.endpoints.enabled-by-default=false
|
||||
#spring.freemarker.checkTemplateLocation=false
|
||||
spring.session.timeout=${session.timeout:43200}
|
||||
spring.session.timeout=43200s
|
||||
spring.session.store-type=none
|
||||
# cookie
|
||||
server.servlet.session.cookie.max-age=${session.timeout:43200}
|
||||
# eureka
|
||||
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
|
||||
eureka.instance.prefer-ip-address=true
|
||||
|
|
|
@ -11,7 +11,7 @@ upload_fail=文件上传失败
|
|||
invalid_parameter=非法的参数
|
||||
name_already_exists=该名称已经存在
|
||||
resource_not_exist=资源不存在或已删除
|
||||
upload_file_fail_get_file_path_fail=文件上傳失敗,獲取文件上傳路徑為空
|
||||
upload_file_fail_get_file_path_fail=文件上传失败,获取文件上传路径为空
|
||||
#user related
|
||||
user_email_already_exists=用户邮箱已存在
|
||||
user_id_is_null=用户ID不能为空
|
||||
|
|
|
@ -11,7 +11,7 @@ upload_fail=文件上傳失敗
|
|||
invalid_parameter=非法的參數
|
||||
name_already_exists=該名稱已經存在
|
||||
resource_not_exist=資源不存在或已刪除
|
||||
upload_file_fail_get_file_path_fail=文件上傳失敗,獲取文件路徑失敗
|
||||
upload_file_fail_get_file_path_fail=文件上傳失敗,獲取文件上傳路徑為空
|
||||
#user related
|
||||
user_email_already_exists=用戶郵箱已存在
|
||||
user_id_is_null=用戶ID不能為空
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -106,10 +106,10 @@ public interface IssuesPlatform {
|
|||
|
||||
/**
|
||||
* Get请求的代理
|
||||
* @param url
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
ResponseEntity proxyForGet(String url, Class responseEntityClazz);
|
||||
ResponseEntity proxyForGet(String path, Class responseEntityClazz);
|
||||
|
||||
/**
|
||||
* 同步MS缺陷附件到第三方平台
|
||||
|
|
|
@ -110,13 +110,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<el-upload
|
||||
v-else
|
||||
style="margin-bottom: 10px"
|
||||
accept=".jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx,.txt,.der,.cer,.pem,.crt,.pfx,.p12,.jks"
|
||||
accept=".jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx,.txt,.der,.cer,.pem,.crt,.pfx,.p12,.jks,.dcm"
|
||||
action=""
|
||||
multiple
|
||||
:show-file-list="false"
|
||||
|
@ -65,7 +65,7 @@
|
|||
<template v-slot:default="scope">
|
||||
<el-upload
|
||||
style="width: 38px; float: left;"
|
||||
accept=".jmx,.jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx,.txt,.der,.cer,.pem,.crt,.pfx,.p12,.jks"
|
||||
accept=".jmx,.jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx,.txt,.der,.cer,.pem,.crt,.pfx,.p12,.jks,.dcm"
|
||||
action=""
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpdateUploadFile"
|
||||
|
|
20
pom.xml
20
pom.xml
|
@ -22,12 +22,12 @@
|
|||
<java.version>11</java.version>
|
||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
||||
<spring-security.version>5.7.5</spring-security.version>
|
||||
<dubbo.version>2.7.17</dubbo.version>
|
||||
<platform-plugin-sdk.version>1.0.0</platform-plugin-sdk.version>
|
||||
<dubbo.version>2.7.18</dubbo.version>
|
||||
<platform-plugin-sdk.version>1.1.0</platform-plugin-sdk.version>
|
||||
<flyway.version>7.15.0</flyway.version>
|
||||
<shiro.version>1.10.0</shiro.version>
|
||||
<shiro.version>1.10.1</shiro.version>
|
||||
<mssql-jdbc.version>7.4.1.jre8</mssql-jdbc.version>
|
||||
<postgresql.version>42.3.2</postgresql.version>
|
||||
<postgresql.version>42.3.8</postgresql.version>
|
||||
<java-websocket.version>1.5.3</java-websocket.version>
|
||||
<easyexcel.version>3.1.1</easyexcel.version>
|
||||
<dom4j.version>2.1.3</dom4j.version>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<quartz-starter.version>1.0.6</quartz-starter.version>
|
||||
<redisson-starter.version>3.17.7</redisson-starter.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<mybatis-starter.version>2.2.2</mybatis-starter.version>
|
||||
<mybatis-starter.version>2.3.0</mybatis-starter.version>
|
||||
<reflections.version>0.10.2</reflections.version>
|
||||
<bcprov-jdk15on.version>1.70</bcprov-jdk15on.version>
|
||||
<commons-io.version>2.11.0</commons-io.version>
|
||||
|
@ -61,17 +61,20 @@
|
|||
<springdoc-openapi-ui.version>1.6.11</springdoc-openapi-ui.version>
|
||||
<flatten.version>1.2.7</flatten.version>
|
||||
<jmeter.version>5.5</jmeter.version>
|
||||
<codehaus-groovy.version>3.0.11</codehaus-groovy.version>
|
||||
<jython.version>2.7.3</jython.version>
|
||||
<docker-java.version>3.2.13</docker-java.version>
|
||||
<jmeter-plugins-webdriver.version>3.4.4</jmeter-plugins-webdriver.version>
|
||||
<oracle-database.version>19.7.0.0</oracle-database.version>
|
||||
<zookeeper.version>3.4.14</zookeeper.version>
|
||||
<zookeeper.version>3.8.0</zookeeper.version>
|
||||
<commons-beanutils.version>1.9.4</commons-beanutils.version>
|
||||
<jmeter-plugins-dubbo.version>2.7.17</jmeter-plugins-dubbo.version>
|
||||
<hessian-lite.version>3.2.12</hessian-lite.version>
|
||||
<hessian-lite.version>3.2.13</hessian-lite.version>
|
||||
<avro.version>1.11.1</avro.version>
|
||||
<dec.version>0.1.2</dec.version>
|
||||
<dingtalk-sdk.version>2.0.0</dingtalk-sdk.version>
|
||||
<org-json.version>20171018</org-json.version>
|
||||
<org-json.version>20220924</org-json.version>
|
||||
<jmeter-plugins-dubbo.version>2.7.17</jmeter-plugins-dubbo.version>
|
||||
<nacos.version>1.4.4</nacos.version>
|
||||
<minio.version>8.4.5</minio.version>
|
||||
<hikaricp.version>5.0.1</hikaricp.version>
|
||||
|
@ -94,6 +97,7 @@
|
|||
<module>report-stat</module>
|
||||
<module>system-setting</module>
|
||||
<module>test-track</module>
|
||||
<module>workstation</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -110,13 +110,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
|
|
|
@ -5,32 +5,9 @@
|
|||
|
||||
<select id="getGroupList" resultType="io.metersphere.dto.GroupDTO">
|
||||
SELECT *,
|
||||
<if test="request.onlyQueryCurrentProject == true">
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id AND ug.source_id = #{request.projectId}) AS memberSize
|
||||
</if>
|
||||
<if test="request.onlyQueryCurrentProject == false">
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id) AS memberSize
|
||||
</if>
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id AND ug.source_id = #{request.projectId}) AS memberSize
|
||||
FROM (
|
||||
SELECT g.*, w.name AS scopeName FROM `group` g, workspace w
|
||||
<where>
|
||||
and g.scope_id = w.id
|
||||
<if test="request.types != null and request.types.size() > 0">
|
||||
AND g.type IN
|
||||
<foreach collection="request.types" item="type" separator="," open="(" close=")">
|
||||
#{type}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="request.scopes != null and request.scopes.size() > 0">
|
||||
AND g.scope_id IN
|
||||
<foreach collection="request.scopes" item="scope" separator="," open="(" close=")">
|
||||
#{scope}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
UNION DISTINCT
|
||||
SELECT g.*, 'global' AS scopeName FROM `group` g
|
||||
<where>
|
||||
g.scope_id = 'global'
|
||||
|
@ -63,7 +40,7 @@
|
|||
WHERE temp.name LIKE CONCAT('%', #{request.name},'%')
|
||||
</if>
|
||||
<if test="request.orders == null or request.orders.size() == 0">
|
||||
ORDER BY field(temp.type, 'SYSTEM', 'ORGANIZATION', 'WORKSPACE', 'PROJECT'), temp.update_time, temp.name
|
||||
ORDER BY field(temp.type, 'SYSTEM', 'WORKSPACE', 'PROJECT'), temp.update_time, temp.name
|
||||
</if>
|
||||
<if test="request.orders != null and request.orders.size() > 0">
|
||||
ORDER BY
|
||||
|
|
|
@ -36,21 +36,12 @@ public class GroupController {
|
|||
@Resource
|
||||
private GroupService groupService;
|
||||
|
||||
@PostMapping("/get/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.SYSTEM_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<GroupDTO>> getGroupList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest request) {
|
||||
request.setGoPage(goPage);
|
||||
request.setPageSize(pageSize);
|
||||
return groupService.getGroupList(request);
|
||||
}
|
||||
|
||||
@PostMapping("/get/current/project/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.PROJECT_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<GroupDTO>> getCurrentProjectGroupList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest request) {
|
||||
request.setOnlyQueryCurrentProject(true);
|
||||
request.setGoPage(goPage);
|
||||
request.setPageSize(pageSize);
|
||||
return groupService.getGroupList(request);
|
||||
return groupService.getProjectGroupList(request);
|
||||
}
|
||||
|
||||
@GetMapping("/get/all")
|
||||
|
@ -127,13 +118,6 @@ public class GroupController {
|
|||
return groupService.getResource(type, id);
|
||||
}
|
||||
|
||||
@PostMapping("/user/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.SYSTEM_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<User>> getGroupUser(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest editGroupRequest) {
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, groupService.getGroupUser(editGroupRequest));
|
||||
}
|
||||
|
||||
@PostMapping("/current/project/user/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.PROJECT_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<User>> getCurrentProjectGroupUser(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest editGroupRequest) {
|
||||
|
|
|
@ -67,6 +67,8 @@ public class GroupService {
|
|||
private UserMapper userMapper;
|
||||
@Resource
|
||||
private MicroService microService;
|
||||
@Resource
|
||||
private BaseUserService baseUserService;
|
||||
private static final String GLOBAL = "global";
|
||||
|
||||
// 服务权限拼装顺序
|
||||
|
@ -86,22 +88,27 @@ public class GroupService {
|
|||
put(UserGroupType.PROJECT, "项目");
|
||||
}};
|
||||
|
||||
public Pager<List<GroupDTO>> getGroupList(EditGroupRequest request) {
|
||||
public Pager<List<GroupDTO>> getProjectGroupList(EditGroupRequest request) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
List<UserGroupDTO> userGroup = baseUserGroupMapper.getUserGroup(Objects.requireNonNull(user).getId(), request.getProjectId());
|
||||
List<String> groupTypeList = userGroup.stream().map(UserGroupDTO::getType).distinct().collect(Collectors.toList());
|
||||
if (groupTypeList.isEmpty()) {
|
||||
if (baseUserService.isSuperUser(user.getId())) {
|
||||
groupTypeList.add(UserGroupType.PROJECT);
|
||||
}
|
||||
}
|
||||
return getGroups(groupTypeList, request);
|
||||
}
|
||||
|
||||
public void buildUserInfo(List<GroupDTO> testCases) {
|
||||
if (CollectionUtils.isEmpty(testCases)) {
|
||||
public void buildUserInfo(List<GroupDTO> groups) {
|
||||
if (CollectionUtils.isEmpty(groups)) {
|
||||
return;
|
||||
}
|
||||
List<String> userIds = testCases.stream().map(GroupDTO::getCreator).collect(Collectors.toList());
|
||||
List<String> userIds = groups.stream().map(GroupDTO::getCreator).collect(Collectors.toList());
|
||||
if (!userIds.isEmpty()) {
|
||||
Map<String, String> userMap = ServiceUtils.getUserNameMap(userIds);
|
||||
testCases.forEach(caseResult -> {
|
||||
caseResult.setCreator(userMap.get(caseResult.getCreator()));
|
||||
groups.forEach(caseResult -> {
|
||||
caseResult.setCreator(userMap.getOrDefault(caseResult.getCreator(), caseResult.getCreator()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +149,9 @@ public class GroupService {
|
|||
}
|
||||
|
||||
public void editGroup(EditGroupRequest request) {
|
||||
if (StringUtils.equals(request.getId(), UserGroupConstants.SUPER_GROUP)) {
|
||||
MSException.throwException("超级管理员无法编辑!");
|
||||
}
|
||||
if (StringUtils.equals(request.getId(), UserGroupConstants.ADMIN)) {
|
||||
MSException.throwException("系统管理员无法编辑!");
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@
|
|||
<el-form-item :label-width="labelWidth"
|
||||
:label="$t('workspace.issue_template_manage')" prop="issueTemplateId">
|
||||
<template-select :platform="form.platform" :data="form" scene="ISSUE" prop="issueTemplateId"
|
||||
:disabled="form.platform === 'Jira' && form.thirdPartTemplate"
|
||||
:disabled="thirdPartTemplateSupport && form.thirdPartTemplate"
|
||||
:platformOptions="platformOptions" :project-id="form.id"
|
||||
ref="issueTemplate"/>
|
||||
|
||||
<el-checkbox @change="thirdPartTemplateChange" v-if="form.platform === 'Jira' && thirdPartTemplateSupport"
|
||||
<el-checkbox @change="thirdPartTemplateChange" v-if="thirdPartTemplateSupport"
|
||||
v-model="form.thirdPartTemplate" style="margin-left: 10px">
|
||||
{{ $t('test_track.issue.use_third_party') }}
|
||||
</el-checkbox>
|
||||
|
@ -61,20 +61,6 @@
|
|||
ref="platformConfig"
|
||||
/>
|
||||
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.zentao_id')" v-if="zentao">
|
||||
<el-input v-model="form.zentaoId" autocomplete="off"></el-input>
|
||||
<el-button @click="check" type="primary" class="checkButton">
|
||||
{{ $t('test_track.issue.check_id_exist') }}
|
||||
</el-button>
|
||||
<ms-instructions-icon effect="light">
|
||||
<template>
|
||||
禅道流程:产品-项目 | 产品-迭代 | 产品-冲刺 | 项目-迭代 | 项目-冲刺 <br/><br/>
|
||||
根据 "后台 -> 自定义 -> 流程" 查看对应流程,根据流程填写ID <br/><br/>
|
||||
产品-项目 | 产品-迭代 | 产品-冲刺 需要填写产品ID <br/><br/>
|
||||
项目-迭代 | 项目-冲刺 需要填写项目ID
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.azureDevops_id')" v-if="azuredevops">
|
||||
<el-input v-model="form.azureDevopsId" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -198,9 +184,6 @@ export default {
|
|||
tapd() {
|
||||
return this.showPlatform(TAPD);
|
||||
},
|
||||
zentao() {
|
||||
return this.showPlatform(ZEN_TAO);
|
||||
},
|
||||
azuredevops() {
|
||||
return this.showPlatform(AZURE_DEVOPS);
|
||||
},
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.jira_key')">
|
||||
<el-input v-model="form.jiraKey" autocomplete="off" @blur="getIssueTypeOption"/>
|
||||
<slot name="checkBtn"></slot>
|
||||
<ms-instructions-icon effect="light">
|
||||
<template>
|
||||
<img class="jira-image" src="assets/jira-key.png"/>
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('organization.integration.jira_issuetype')" prop="issuetype">
|
||||
<el-select filterable v-model="form.issueConfigObj.jiraIssueTypeId">
|
||||
<el-option v-for="item in issueTypes" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('organization.integration.jira_storytype')" prop="storytype">
|
||||
<el-select filterable v-model="form.issueConfigObj.jiraStoryTypeId">
|
||||
<el-option v-for="item in issueTypes" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsInstructionsIcon from "metersphere-frontend/src/components/MsInstructionsIcon";
|
||||
import {getJiraIssueType} from "../../api/project";
|
||||
import {getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
|
||||
export default {
|
||||
name: "ProjectJiraConfig",
|
||||
components: {MsInstructionsIcon},
|
||||
props: ['labelWidth', 'form', 'result'],
|
||||
data() {
|
||||
return {
|
||||
issueTypes: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getIssueTypeOption();
|
||||
},
|
||||
methods: {
|
||||
getIssueTypeOption() {
|
||||
this.issueTypes = [];
|
||||
this.result.loading = true;
|
||||
let param = {
|
||||
projectId: this.form.id,
|
||||
workspaceId: getCurrentWorkspaceId(),
|
||||
jiraKey: this.form.jiraKey
|
||||
}
|
||||
getJiraIssueType(param)
|
||||
.then((response) => {
|
||||
this.issueTypes = response.data;
|
||||
let hasJiraIssueType = false;
|
||||
let hasJiraStoryType = false;
|
||||
if (response.data) {
|
||||
response.data.forEach(item => {
|
||||
if (this.form.issueConfigObj.jiraIssueTypeId === item.id) {
|
||||
hasJiraIssueType = true;
|
||||
} else if (this.form.issueConfigObj.jiraStoryTypeId === item.id) {
|
||||
hasJiraStoryType = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!hasJiraIssueType) {
|
||||
this.form.issueConfigObj.jiraIssueTypeId = null;
|
||||
}
|
||||
if (!hasJiraStoryType) {
|
||||
this.form.issueConfigObj.jiraStoryTypeId = null;
|
||||
}
|
||||
this.result.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-input, .el-textarea {
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
|
@ -19,10 +19,13 @@
|
|||
class="checkButton">
|
||||
{{ $t('test_track.issue.check_id_exist') }}
|
||||
</el-button>
|
||||
<ms-instructions-icon v-if="item.instructionsIcon" effect="light">
|
||||
<ms-instructions-icon v-if="item.instructionsIcon || item.instructionsTip" effect="light">
|
||||
<template>
|
||||
<img class="jira-image"
|
||||
<img v-if="item.instructionsIcon"
|
||||
:src="getPlatformImageUrl(config, item)"/>
|
||||
<span v-if="item.instructionsTip">
|
||||
{{ item.instructionsTip }}
|
||||
</span>
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
|
|
|
@ -110,13 +110,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -121,13 +121,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -5,14 +5,8 @@
|
|||
|
||||
<select id="getGroupList" resultType="io.metersphere.dto.GroupDTO">
|
||||
SELECT *,
|
||||
<if test="request.onlyQueryCurrentProject == true">
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id AND ug.source_id = #{request.projectId}) AS memberSize
|
||||
</if>
|
||||
<if test="request.onlyQueryCurrentProject == false">
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id) AS memberSize
|
||||
</if>
|
||||
(SELECT COUNT(DISTINCT ug.user_id) FROM user_group ug JOIN user ON ug.user_id = user.id WHERE ug.group_id =
|
||||
temp.id) AS memberSize
|
||||
FROM (
|
||||
SELECT g.*, w.name AS scopeName FROM `group` g, workspace w
|
||||
<where>
|
||||
|
@ -41,6 +35,17 @@
|
|||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
UNION DISTINCT
|
||||
SELECT g.*, 'system' AS scopeName FROM `group` g
|
||||
<where>
|
||||
g.scope_id = 'system'
|
||||
<if test="request.types != null and request.types.size() > 0">
|
||||
AND g.type IN
|
||||
<foreach collection="request.types" item="type" separator="," open="(" close=")">
|
||||
#{type}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
union distinct
|
||||
select g.*, p.name as scopeName from `group` g, project p
|
||||
<where>
|
||||
|
@ -63,7 +68,9 @@
|
|||
WHERE temp.name LIKE CONCAT('%', #{request.name},'%')
|
||||
</if>
|
||||
<if test="request.orders == null or request.orders.size() == 0">
|
||||
ORDER BY field(temp.type, 'SYSTEM', 'ORGANIZATION', 'WORKSPACE', 'PROJECT'), temp.update_time, temp.name
|
||||
ORDER BY field(temp.type, 'SYSTEM', 'WORKSPACE', 'PROJECT'),
|
||||
field(temp.scope_id, 'system') desc,
|
||||
temp.update_time, temp.name
|
||||
</if>
|
||||
<if test="request.orders != null and request.orders.size() > 0">
|
||||
ORDER BY
|
||||
|
|
|
@ -44,15 +44,6 @@ public class GroupController {
|
|||
return groupService.getGroupList(request);
|
||||
}
|
||||
|
||||
@PostMapping("/get/current/project/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.PROJECT_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<GroupDTO>> getCurrentProjectGroupList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest request) {
|
||||
request.setOnlyQueryCurrentProject(true);
|
||||
request.setGoPage(goPage);
|
||||
request.setPageSize(pageSize);
|
||||
return groupService.getGroupList(request);
|
||||
}
|
||||
|
||||
@GetMapping("/get/all")
|
||||
public List<GroupDTO> getAllGroup() {
|
||||
return groupService.getAllGroup();
|
||||
|
@ -134,17 +125,6 @@ public class GroupController {
|
|||
return PageUtils.setPageInfo(page, groupService.getGroupUser(editGroupRequest));
|
||||
}
|
||||
|
||||
@PostMapping("/current/project/user/{goPage}/{pageSize}")
|
||||
@RequiresPermissions(value = {PermissionConstants.PROJECT_GROUP_READ}, logical = Logical.OR)
|
||||
public Pager<List<User>> getCurrentProjectGroupUser(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody EditGroupRequest editGroupRequest) {
|
||||
editGroupRequest.setOnlyQueryCurrentProject(true);
|
||||
if (StringUtils.isBlank(editGroupRequest.getProjectId())) {
|
||||
editGroupRequest.setProjectId(SessionUtils.getCurrentProjectId());
|
||||
}
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, groupService.getGroupUser(editGroupRequest));
|
||||
}
|
||||
|
||||
@GetMapping("/rm/{userId}/{groupId}")
|
||||
public void removeGroupMember(@PathVariable String userId, @PathVariable String groupId) {
|
||||
groupService.removeGroupMember(userId, groupId);
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package io.metersphere.job.schedule;
|
||||
|
||||
import com.fit2cloud.quartz.anno.QuartzScheduled;
|
||||
import io.metersphere.commons.utils.JSON;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class CleanSessionJob {
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
@Resource
|
||||
private RedisIndexedSessionRepository redisIndexedSessionRepository;
|
||||
|
||||
@Value("${spring.session.timeout:43200s}")
|
||||
private Duration timeout;
|
||||
|
||||
/**
|
||||
* 清理没有绑定user的session
|
||||
* redisson 有时会把ttl设置成-1 https://github.com/redisson/redisson/issues/4200
|
||||
*/
|
||||
@QuartzScheduled(cron = "0 2 0 * * ?")
|
||||
public void cleanSession() {
|
||||
Map<String, Long> userCount = new HashMap<>();
|
||||
ScanOptions options = ScanOptions.scanOptions().match("spring:session:sessions:*").count(1000).build();
|
||||
try (
|
||||
Cursor<String> scan = stringRedisTemplate.scan(options)
|
||||
) {
|
||||
while (scan.hasNext()) {
|
||||
String key = scan.next();
|
||||
if (StringUtils.contains(key, "spring:session:sessions:expires:")) {
|
||||
continue;
|
||||
}
|
||||
Boolean exists = stringRedisTemplate.opsForHash().hasKey(key, "sessionAttr:user");
|
||||
if (!exists) {
|
||||
stringRedisTemplate.delete(key);
|
||||
} else {
|
||||
Object user = redisIndexedSessionRepository.getSessionRedisOperations().opsForHash().get(key, "sessionAttr:user");
|
||||
Long expire = redisIndexedSessionRepository.getSessionRedisOperations().getExpire(key);
|
||||
String userId = (String) MethodUtils.invokeMethod(user, "getId");
|
||||
Long count = userCount.getOrDefault(userId, 0L);
|
||||
count++;
|
||||
userCount.put(userId, count);
|
||||
LogUtil.info(key + " : " + userId + " 过期时间: " + expire);
|
||||
if (expire != null && expire.intValue() == -1) {
|
||||
redisIndexedSessionRepository.getSessionRedisOperations().expire(key, timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogUtil.info(JSON.toJSONString(userCount));
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.listener;
|
||||
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.service.PlatformPluginService;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
|
@ -15,6 +16,7 @@ public class InitListener implements ApplicationRunner {
|
|||
|
||||
@Override
|
||||
public void run(ApplicationArguments applicationArguments) {
|
||||
LogUtil.info("================= SYSTEM-SETTING 应用启动 =================");
|
||||
platformPluginService.loadPlatFormPlugins();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,11 +68,20 @@ public class GroupService {
|
|||
private UserMapper userMapper;
|
||||
|
||||
private static final String GLOBAL = "global";
|
||||
private static final String SUPER_GROUP = "super_group";
|
||||
private static final String PERSONAL_PREFIX = "PERSONAL";
|
||||
|
||||
|
||||
// 服务权限拼装顺序
|
||||
private static final String[] servicePermissionLoadOrder = {MicroServiceName.PROJECT_MANAGEMENT,
|
||||
MicroServiceName.TEST_TRACK, MicroServiceName.API_TEST, MicroServiceName.UI_TEST,
|
||||
MicroServiceName.PERFORMANCE_TEST, MicroServiceName.REPORT_STAT, MicroServiceName.SYSTEM_SETTING};
|
||||
private static final String[] servicePermissionLoadOrder = {
|
||||
MicroServiceName.SYSTEM_SETTING,
|
||||
MicroServiceName.PROJECT_MANAGEMENT,
|
||||
MicroServiceName.TEST_TRACK,
|
||||
MicroServiceName.API_TEST,
|
||||
MicroServiceName.UI_TEST,
|
||||
MicroServiceName.PERFORMANCE_TEST,
|
||||
MicroServiceName.REPORT_STAT
|
||||
};
|
||||
|
||||
private static final Map<String, List<String>> map = new HashMap<>(4) {{
|
||||
put(UserGroupType.SYSTEM, Arrays.asList(UserGroupType.SYSTEM, UserGroupType.WORKSPACE, UserGroupType.PROJECT));
|
||||
|
@ -93,14 +102,11 @@ public class GroupService {
|
|||
return getGroups(groupTypeList, request);
|
||||
}
|
||||
|
||||
public void buildUserInfo(List<GroupDTO> testCases) {
|
||||
List<String> userIds = new ArrayList();
|
||||
userIds.addAll(testCases.stream().map(GroupDTO::getCreator).collect(Collectors.toList()));
|
||||
public void buildUserInfo(List<GroupDTO> groups) {
|
||||
List<String> userIds = groups.stream().map(GroupDTO::getCreator).collect(Collectors.toList());
|
||||
if (!userIds.isEmpty()) {
|
||||
Map<String, String> userMap = ServiceUtils.getUserNameMap(userIds);
|
||||
testCases.forEach(caseResult -> {
|
||||
caseResult.setCreator(userMap.get(caseResult.getCreator()));
|
||||
});
|
||||
groups.forEach(caseResult -> caseResult.setCreator(userMap.getOrDefault(caseResult.getCreator(), caseResult.getCreator())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +146,9 @@ public class GroupService {
|
|||
}
|
||||
|
||||
public void editGroup(EditGroupRequest request) {
|
||||
if (StringUtils.equals(request.getId(), UserGroupConstants.SUPER_GROUP)) {
|
||||
MSException.throwException("超级管理员无法编辑!");
|
||||
}
|
||||
if (StringUtils.equals(request.getId(), UserGroupConstants.ADMIN)) {
|
||||
MSException.throwException("系统管理员无法编辑!");
|
||||
}
|
||||
|
@ -176,19 +185,22 @@ public class GroupService {
|
|||
example.createCriteria().andGroupIdEqualTo(group.getId());
|
||||
List<UserGroupPermission> groupPermissions = userGroupPermissionMapper.selectByExample(example);
|
||||
List<String> groupPermissionIds = groupPermissions.stream().map(UserGroupPermission::getPermissionId).collect(Collectors.toList());
|
||||
|
||||
GroupJson groupJson = this.loadPermissionJsonFromService();
|
||||
if (groupJson == null) {
|
||||
MSException.throwException(Translator.get("read_permission_file_fail"));
|
||||
}
|
||||
|
||||
List<GroupResource> resource = groupJson.getResource();
|
||||
List<GroupPermission> permissions = groupJson.getPermissions();
|
||||
List<GroupResourceDTO> dtoPermissions = dto.getPermissions();
|
||||
dtoPermissions.addAll(getResourcePermission(resource, permissions, group.getType(), groupPermissionIds));
|
||||
dtoPermissions.addAll(getResourcePermission(resource, permissions, group, groupPermissionIds));
|
||||
return dto;
|
||||
}
|
||||
|
||||
private GroupJson loadPermissionJsonFromService() {
|
||||
GroupJson groupJson = null;
|
||||
List<GroupResource> globalResource = new ArrayList<>();
|
||||
try {
|
||||
for (String service : servicePermissionLoadOrder) {
|
||||
Object obj = stringRedisTemplate.opsForHash().get(RedisKey.MS_PERMISSION_KEY, service);
|
||||
|
@ -199,11 +211,23 @@ public class GroupService {
|
|||
GroupJson temp = JSON.parseObject((String) obj, GroupJson.class);
|
||||
if (groupJson == null) {
|
||||
groupJson = temp;
|
||||
// 全局权限放系统设置模块
|
||||
if (StringUtils.equals(service, MicroServiceName.SYSTEM_SETTING)) {
|
||||
globalResource = temp.getResource()
|
||||
.stream()
|
||||
.filter(gp -> BooleanUtils.isTrue(gp.isGlobal()))
|
||||
.collect(Collectors.toList());
|
||||
temp.getResource().removeIf(gp -> BooleanUtils.isTrue(gp.isGlobal()));
|
||||
}
|
||||
} else {
|
||||
groupJson.getResource().addAll(temp.getResource());
|
||||
groupJson.getPermissions().addAll(temp.getPermissions());
|
||||
}
|
||||
}
|
||||
// 拼装权限的时候放在最后
|
||||
if (groupJson != null && !globalResource.isEmpty()) {
|
||||
groupJson.getResource().addAll(globalResource);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
|
@ -292,8 +316,7 @@ public class GroupService {
|
|||
scopeList = Arrays.asList(GLOBAL, resourceId, request.getProjectId());
|
||||
}
|
||||
GroupExample groupExample = new GroupExample();
|
||||
groupExample.createCriteria().andScopeIdIn(scopeList)
|
||||
.andTypeEqualTo(type);
|
||||
groupExample.createCriteria().andScopeIdIn(scopeList).andTypeEqualTo(type);
|
||||
return groupMapper.selectByExample(groupExample);
|
||||
}
|
||||
|
||||
|
@ -301,15 +324,23 @@ public class GroupService {
|
|||
return baseUserGroupMapper.getWorkspaceMemberGroups(workspaceId, userId);
|
||||
}
|
||||
|
||||
private List<GroupResourceDTO> getResourcePermission(List<GroupResource> resource, List<GroupPermission> permissions, String type, List<String> permissionList) {
|
||||
private List<GroupResourceDTO> getResourcePermission(List<GroupResource> resources, List<GroupPermission> permissions, Group group, List<String> permissionList) {
|
||||
List<GroupResourceDTO> dto = new ArrayList<>();
|
||||
List<GroupResource> resources = resource.stream().filter(g -> g.getId().startsWith(type) || g.getId().startsWith("PERSONAL")).collect(Collectors.toList());
|
||||
List<GroupResource> grs;
|
||||
if (StringUtils.equals(group.getId(), SUPER_GROUP)) {
|
||||
grs = resources;
|
||||
} else {
|
||||
grs = resources
|
||||
.stream()
|
||||
.filter(g -> g.getId().startsWith(group.getType()) || g.getId().startsWith(PERSONAL_PREFIX))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
permissions.forEach(p -> {
|
||||
if (permissionList.contains(p.getId())) {
|
||||
p.setChecked(true);
|
||||
}
|
||||
});
|
||||
for (GroupResource r : resources) {
|
||||
for (GroupResource r : grs) {
|
||||
GroupResourceDTO resourceDTO = new GroupResourceDTO();
|
||||
resourceDTO.setResource(r);
|
||||
List<GroupPermission> collect = permissions
|
||||
|
@ -453,9 +484,14 @@ public class GroupService {
|
|||
UserGroupExample userGroupExample = new UserGroupExample();
|
||||
userGroupExample.createCriteria().andUserIdEqualTo(userId).andGroupIdEqualTo(group.getId());
|
||||
List<UserGroup> userGroups = userGroupMapper.selectByExample(userGroupExample);
|
||||
if (userGroups.size() <= 0) {
|
||||
UserGroup userGroup = new UserGroup(UUID.randomUUID().toString(), userId, group.getId(),
|
||||
"system", System.currentTimeMillis(), System.currentTimeMillis());
|
||||
if (userGroups.size() == 0) {
|
||||
UserGroup userGroup = new UserGroup(
|
||||
UUID.randomUUID().toString(),
|
||||
userId,
|
||||
group.getId(),
|
||||
"system",
|
||||
System.currentTimeMillis(),
|
||||
System.currentTimeMillis());
|
||||
userGroupMapper.insertSelective(userGroup);
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +528,7 @@ public class GroupService {
|
|||
|
||||
private void checkQuota(QuotaService quotaService, String type, List<String> sourceIds, List<String> userIds) {
|
||||
if (quotaService != null) {
|
||||
Map<String, List<String>> addMemberMap = sourceIds.stream().collect(Collectors.toMap( id -> id, id -> userIds));
|
||||
Map<String, List<String>> addMemberMap = sourceIds.stream().collect(Collectors.toMap(id -> id, id -> userIds));
|
||||
quotaService.checkMemberCount(addMemberMap, type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,14 +195,6 @@ public class SystemProjectService {
|
|||
return baseProjectMapper.getProjectWithWorkspace(request);
|
||||
}
|
||||
|
||||
public List<ProjectDTO> getUserProject(ProjectRequest request) {
|
||||
if (StringUtils.isNotBlank(request.getName())) {
|
||||
request.setName(StringUtils.wrapIfMissing(request.getName(), "%"));
|
||||
}
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
return baseProjectMapper.getUserProject(request);
|
||||
}
|
||||
|
||||
public List<Project> getProjectByIds(List<String> ids) {
|
||||
if (!CollectionUtils.isEmpty(ids)) {
|
||||
ProjectExample example = new ProjectExample();
|
||||
|
|
|
@ -85,6 +85,8 @@ public class UserService {
|
|||
private BaseProjectMapper baseProjectMapper;
|
||||
@Resource
|
||||
private BaseWorkspaceMapper baseWorkspaceMapper;
|
||||
@Resource
|
||||
private BaseUserService baseUserService;
|
||||
|
||||
public List<UserDetail> queryTypeByIds(List<String> userIds) {
|
||||
return baseUserMapper.queryTypeByIds(userIds);
|
||||
|
@ -412,30 +414,53 @@ public class UserService {
|
|||
|
||||
|
||||
public void addMember(AddMemberRequest request) {
|
||||
if (!CollectionUtils.isEmpty(request.getUserIds())) {
|
||||
if (CollectionUtils.isEmpty(request.getUserIds())
|
||||
|| CollectionUtils.isEmpty(request.getGroupIds())) {
|
||||
LogUtil.warn("user ids or group ids is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(request.getUserIds())) {
|
||||
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
|
||||
checkQuota(quotaService, "WORKSPACE", Collections.singletonList(request.getWorkspaceId()), request.getUserIds());
|
||||
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
|
||||
checkQuota(quotaService, "WORKSPACE", Collections.singletonList(request.getWorkspaceId()), request.getUserIds());
|
||||
|
||||
List<String> allUserIds = baseUserService.getAllUserIds();
|
||||
|
||||
GroupExample groupExample = new GroupExample();
|
||||
groupExample.createCriteria().andTypeEqualTo(UserGroupType.WORKSPACE);
|
||||
List<Group> wsGroups = groupMapper.selectByExample(groupExample);
|
||||
List<String> wsGroupIds = wsGroups
|
||||
.stream()
|
||||
.map(Group::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (String userId : request.getUserIds()) {
|
||||
if (!allUserIds.contains(userId)) {
|
||||
LogUtil.warn("user id {} is not exist!", userId);
|
||||
continue;
|
||||
}
|
||||
for (String userId : request.getUserIds()) {
|
||||
UserGroupExample userGroupExample = new UserGroupExample();
|
||||
userGroupExample.createCriteria().andUserIdEqualTo(userId).andSourceIdEqualTo(request.getWorkspaceId());
|
||||
List<UserGroup> userGroups = userGroupMapper.selectByExample(userGroupExample);
|
||||
if (userGroups.size() > 0) {
|
||||
MSException.throwException(Translator.get("user_already_exists"));
|
||||
} else {
|
||||
for (String groupId : request.getGroupIds()) {
|
||||
UserGroup userGroup = new UserGroup();
|
||||
userGroup.setGroupId(groupId);
|
||||
userGroup.setSourceId(request.getWorkspaceId());
|
||||
userGroup.setUserId(userId);
|
||||
userGroup.setId(UUID.randomUUID().toString());
|
||||
userGroup.setUpdateTime(System.currentTimeMillis());
|
||||
userGroup.setCreateTime(System.currentTimeMillis());
|
||||
userGroupMapper.insertSelective(userGroup);
|
||||
}
|
||||
|
||||
UserGroupExample userGroupExample = new UserGroupExample();
|
||||
userGroupExample.createCriteria().andUserIdEqualTo(userId).andSourceIdEqualTo(request.getWorkspaceId());
|
||||
List<UserGroup> userGroups = userGroupMapper.selectByExample(userGroupExample);
|
||||
|
||||
if (userGroups.size() > 0) {
|
||||
MSException.throwException(Translator.get("user_already_exists"));
|
||||
}
|
||||
|
||||
for (String groupId : request.getGroupIds()) {
|
||||
if (!wsGroupIds.contains(groupId)) {
|
||||
LogUtil.warn("group id {} is not exist or not belong to workspace level.", groupId);
|
||||
continue;
|
||||
}
|
||||
|
||||
UserGroup userGroup = new UserGroup();
|
||||
userGroup.setGroupId(groupId);
|
||||
userGroup.setSourceId(request.getWorkspaceId());
|
||||
userGroup.setUserId(userId);
|
||||
userGroup.setId(UUID.randomUUID().toString());
|
||||
userGroup.setUpdateTime(System.currentTimeMillis());
|
||||
userGroup.setCreateTime(System.currentTimeMillis());
|
||||
userGroupMapper.insertSelective(userGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1240,7 +1265,7 @@ public class UserService {
|
|||
private void addGroupMember(String type, String sourceId, List<String> userIds, List<String> groupIds) {
|
||||
if (!StringUtils.equalsAny(type, "PROJECT", "WORKSPACE") || StringUtils.isBlank(sourceId)
|
||||
|| CollectionUtils.isEmpty(userIds) || CollectionUtils.isEmpty(groupIds)) {
|
||||
LogUtil.info("add member warning, please check param!");
|
||||
LogUtil.warn("add member warning, please check param!");
|
||||
return;
|
||||
}
|
||||
this.checkQuotaOfMemberSize(type, sourceId, userIds);
|
||||
|
@ -1248,7 +1273,7 @@ public class UserService {
|
|||
for (String userId : userIds) {
|
||||
User user = userMapper.selectByPrimaryKey(userId);
|
||||
if (user == null) {
|
||||
LogUtil.info("add member warning, invalid user id: " + userId);
|
||||
LogUtil.warn("add member warning, invalid user id: " + userId);
|
||||
continue;
|
||||
}
|
||||
List<String> toAddGroupIds = new ArrayList<>(groupIds);
|
||||
|
@ -1260,8 +1285,13 @@ public class UserService {
|
|||
continue;
|
||||
}
|
||||
for (String groupId : toAddGroupIds) {
|
||||
UserGroup userGroup = new UserGroup(UUID.randomUUID().toString(), userId, groupId,
|
||||
sourceId, System.currentTimeMillis(), System.currentTimeMillis());
|
||||
UserGroup userGroup = new UserGroup(
|
||||
UUID.randomUUID().toString(),
|
||||
userId,
|
||||
groupId,
|
||||
sourceId,
|
||||
System.currentTimeMillis(),
|
||||
System.currentTimeMillis());
|
||||
userGroupMapper.insertSelective(userGroup);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -347,6 +347,10 @@
|
|||
"id": "SYSTEM_OPERATING_LOG",
|
||||
"name": "permission.system_operation_log.name"
|
||||
},
|
||||
{
|
||||
"id": "SYSTEM_PLUGIN",
|
||||
"name": "permission.system_plugin.name"
|
||||
},
|
||||
{
|
||||
"id": "WORKSPACE_USER",
|
||||
"name": "permission.workspace_user.name"
|
||||
|
@ -372,13 +376,10 @@
|
|||
"id": "WORKSPACE_OPERATING_LOG",
|
||||
"name": "permission.workspace_operation_log.name"
|
||||
},
|
||||
{
|
||||
"id": "SYSTEM_PLUGIN",
|
||||
"name": "permission.system_plugin.name"
|
||||
},
|
||||
{
|
||||
"id": "PERSONAL_INFORMATION",
|
||||
"name": "permission.personal_information.name"
|
||||
"name": "permission.personal_information.name",
|
||||
"global": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -99,7 +99,7 @@ export default {
|
|||
},
|
||||
isReadOnly() {
|
||||
return function (data) {
|
||||
const isDefaultSystemGroup = this.group.id === 'admin' && data.resource.id === 'SYSTEM_GROUP';
|
||||
const isDefaultSystemGroup = (this.group.id === 'admin' || this.group.id === 'super_group') && data.resource.id === 'SYSTEM_GROUP';
|
||||
return this.readOnly || isDefaultSystemGroup;
|
||||
}
|
||||
}
|
||||
|
@ -143,8 +143,13 @@ export default {
|
|||
},
|
||||
_getUniteMenu() {
|
||||
let menu = ['TRACK', 'API', 'UI', 'PERFORMANCE', 'REPORT'];
|
||||
// 是否是第一个无子分类的项目类型模块 如:PROJECT_USER
|
||||
let isFirstProjectType = false;
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
if (i === 0) {
|
||||
if (this.tableData[i].type === GROUP_TYPE.PROJECT) {
|
||||
isFirstProjectType = true;
|
||||
}
|
||||
this.spanArr.push(1);
|
||||
this.pos = 0
|
||||
} else {
|
||||
|
@ -153,8 +158,13 @@ export default {
|
|||
if (this.tableData[i].type !== GROUP_TYPE.PROJECT) {
|
||||
sign = this.tableData[i].type === this.tableData[i - 1].type;
|
||||
} else {
|
||||
sign = !menu.includes(this.tableData[i].resource.id.split('_')[1]) ?
|
||||
true : this.tableData[i].resource.id.split('_')[1] === this.tableData[i - 1].resource.id.split('_')[1]
|
||||
let hasSubModule = menu.includes(this.tableData[i].resource.id.split('_')[1]);
|
||||
if (hasSubModule) {
|
||||
sign = this.tableData[i].resource.id.split('_')[1] === this.tableData[i - 1].resource.id.split('_')[1];
|
||||
} else {
|
||||
sign = isFirstProjectType;
|
||||
isFirstProjectType = true;
|
||||
}
|
||||
}
|
||||
if (sign) {
|
||||
this.spanArr[this.pos] += 1;
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
return function (permission) {
|
||||
// 禁止取消系统管理员用户组的读取和设置权限
|
||||
const isSystemGroupPermission = permission.id === 'SYSTEM_GROUP:READ' || permission.id === 'SYSTEM_GROUP:READ+SETTING_PERMISSION';
|
||||
const isDefaultSystemGroup = this.group.id === 'admin' && isSystemGroupPermission;
|
||||
const isDefaultSystemGroup = (this.group.id === 'admin' || this.group.id === 'super_group') && isSystemGroupPermission;
|
||||
return this.readOnly || isDefaultSystemGroup;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<el-table-column prop="scopeName" :label="$t('group.scope')">
|
||||
<template v-slot="scope">
|
||||
<span v-if="scope.row.scopeId ==='global'">{{ $t('group.global') }}</span>
|
||||
<span v-else-if="scope.row.scopeId ==='system'">{{ $t('group.system') }}</span>
|
||||
<span v-else>{{ scope.row.scopeName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -42,10 +43,22 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" :label="$t('group.operator')"/>
|
||||
<el-table-column prop="description" :label="$t('group.description')"/>
|
||||
<el-table-column prop="description" :label="$t('group.description')" show-overflow-tooltip/>
|
||||
<el-table-column :label="$t('commons.operating')" min-width="120">
|
||||
<template v-slot:default="scope">
|
||||
<div>
|
||||
<template v-slot="scope">
|
||||
<div v-if="scope.row.id === 'super_group'">
|
||||
<ms-table-operator
|
||||
:is-show="true"
|
||||
@editClick="edit(scope.row)" @deleteClick="del(scope.row)">
|
||||
<template v-slot:middle>
|
||||
<ms-table-operator-button
|
||||
v-permission="['SYSTEM_GROUP:READ+SETTING_PERMISSION']"
|
||||
:tip="$t('group.set_permission')" icon="el-icon-s-tools"
|
||||
@exec="setPermission(scope.row)"/>
|
||||
</template>
|
||||
</ms-table-operator>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ms-table-operator
|
||||
:edit-permission="['SYSTEM_GROUP:READ+EDIT']"
|
||||
:delete-permission="['SYSTEM_GROUP:READ+DELETE']"
|
||||
|
|
|
@ -360,6 +360,9 @@ export default {
|
|||
let data = res.data;
|
||||
if (data) {
|
||||
this._setResource(data, index, type);
|
||||
if (id === 'super_group') {
|
||||
return;
|
||||
}
|
||||
if (isHaveWorkspace === false) {
|
||||
this.addWorkspaceGroup(id, index);
|
||||
}
|
||||
|
|
|
@ -11,9 +11,6 @@
|
|||
<el-radio label="Tapd">
|
||||
<img class="platform" src="/assets/tapd.png" alt="Tapd"/>
|
||||
</el-radio>
|
||||
<el-radio label="Zentao">
|
||||
<img class="zentao_platform" src="/assets/zentao.jpg" alt="Zentao"/>
|
||||
</el-radio>
|
||||
<el-radio label="AzureDevops" v-xpack>
|
||||
<img class="platform" src="/assets/AzureDevops.png" alt="AzureDevops"/>
|
||||
</el-radio>
|
||||
|
@ -21,7 +18,6 @@
|
|||
</div>
|
||||
|
||||
<tapd-setting v-if="tapdEnable" ref="tapdSetting"/>
|
||||
<zentao-setting v-if="zentaoEnable" ref="zentaoSetting"/>
|
||||
<azuredevops-setting v-if="azuredevopsEnable" ref="azureDevopsSetting"/>
|
||||
|
||||
<div v-for="config in platformConfigs" :key="config.key">
|
||||
|
@ -35,16 +31,14 @@
|
|||
|
||||
<script>
|
||||
import TapdSetting from '@/business/workspace/integration/TapdSetting';
|
||||
import JiraSetting from '@/business/workspace/integration/JiraSetting';
|
||||
import ZentaoSetting from '@/business/workspace/integration/ZentaoSetting';
|
||||
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, ZentaoSetting, AzuredevopsSetting},
|
||||
components: {PlatformConfig, TapdSetting, AzuredevopsSetting},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
@ -66,9 +60,6 @@ export default {
|
|||
tapdEnable() {
|
||||
return this.platform === TAPD;
|
||||
},
|
||||
zentaoEnable() {
|
||||
return this.platform === ZEN_TAO;
|
||||
},
|
||||
azuredevopsEnable() {
|
||||
return this.platform === AZURE_DEVOPS;
|
||||
}
|
||||
|
@ -90,9 +81,4 @@ export default {
|
|||
height: 80px;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.zentao_platform {
|
||||
height: 100px;
|
||||
vertical-align: middle
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -11,6 +11,15 @@
|
|||
<custom-filed-component :form="form"
|
||||
:data="item"
|
||||
prop="defaultValue"/>
|
||||
<ms-instructions-icon v-if="item.instructionsIcon || item.instructionsTip" effect="light">
|
||||
<template>
|
||||
<img v-if="item.instructionsIcon"
|
||||
:src="getPlatformImageUrl(config, item)"/>
|
||||
<span v-if="item.instructionsTip">
|
||||
{{ item.instructionsTip }}
|
||||
</span>
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
@ -65,7 +74,7 @@ import {
|
|||
getServiceIntegration,
|
||||
saveServiceIntegration
|
||||
} from "../../../api/workspace";
|
||||
import {validateServiceIntegration} from "@/api/platform-plugin";
|
||||
import {generatePlatformResourceUrl, validateServiceIntegration} from "@/api/platform-plugin";
|
||||
import {getPlatformFormRules} from "metersphere-frontend/src/utils/platform";
|
||||
|
||||
export default {
|
||||
|
@ -181,6 +190,9 @@ export default {
|
|||
},
|
||||
closeDialog() {
|
||||
this.resVisible = false;
|
||||
},
|
||||
getPlatformImageUrl(config, item) {
|
||||
return generatePlatformResourceUrl(config.id, item.instructionsIcon);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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>
|
|
@ -25,11 +25,11 @@
|
|||
<el-form-item :label-width="labelWidth"
|
||||
:label="$t('workspace.issue_template_manage')" prop="issueTemplateId">
|
||||
<template-select :platform="form.platform" :data="form" scene="ISSUE" prop="issueTemplateId"
|
||||
:disabled="form.platform === 'Jira' && form.thirdPartTemplate"
|
||||
:disabled="thirdPartTemplateSupport && form.thirdPartTemplate"
|
||||
:platformOptions="platformOptions" :project-id="form.id"
|
||||
ref="issueTemplate"/>
|
||||
|
||||
<el-checkbox @change="thirdPartTemplateChange" v-if="form.platform === 'Jira' && thirdPartTemplateSupport"
|
||||
<el-checkbox @change="thirdPartTemplateChange" v-if="thirdPartTemplateSupport"
|
||||
v-model="form.thirdPartTemplate" style="margin-left: 10px">
|
||||
{{ $t('test_track.issue.use_third_party') }}
|
||||
</el-checkbox>
|
||||
|
@ -61,20 +61,6 @@
|
|||
ref="platformConfig"
|
||||
/>
|
||||
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.zentao_id')" v-if="zentao">
|
||||
<el-input v-model="form.zentaoId" autocomplete="off"></el-input>
|
||||
<el-button @click="check" type="primary" class="checkButton">
|
||||
{{ $t('test_track.issue.check_id_exist') }}
|
||||
</el-button>
|
||||
<ms-instructions-icon effect="light">
|
||||
<template>
|
||||
禅道流程:产品-项目 | 产品-迭代 | 产品-冲刺 | 项目-迭代 | 项目-冲刺 <br/><br/>
|
||||
根据 "后台 -> 自定义 -> 流程" 查看对应流程,根据流程填写ID <br/><br/>
|
||||
产品-项目 | 产品-迭代 | 产品-冲刺 需要填写产品ID <br/><br/>
|
||||
项目-迭代 | 项目-冲刺 需要填写项目ID
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.azureDevops_id')" v-if="azuredevops">
|
||||
<el-input v-model="form.azureDevopsId" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
|
@ -194,9 +180,6 @@ export default {
|
|||
tapd() {
|
||||
return this.showPlatform(TAPD);
|
||||
},
|
||||
zentao() {
|
||||
return this.showPlatform(ZEN_TAO);
|
||||
},
|
||||
azuredevops() {
|
||||
return this.showPlatform(AZURE_DEVOPS);
|
||||
},
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('project.jira_key')">
|
||||
<el-input v-model="form.jiraKey" autocomplete="off" @blur="getIssueTypeOption"/>
|
||||
<slot name="checkBtn"></slot>
|
||||
<ms-instructions-icon effect="light">
|
||||
<template>
|
||||
<img class="jira-image" src="assets/jira-key.png"/>
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('organization.integration.jira_issuetype')" prop="issuetype">
|
||||
<el-select filterable v-model="form.issueConfigObj.jiraIssueTypeId">
|
||||
<el-option v-for="item in issueTypes" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label-width="labelWidth" :label="$t('organization.integration.jira_storytype')" prop="storytype">
|
||||
<el-select filterable v-model="form.issueConfigObj.jiraStoryTypeId">
|
||||
<el-option v-for="item in issueTypes" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsInstructionsIcon from "metersphere-frontend/src/components/MsInstructionsIcon";
|
||||
import {getJiraIssueType} from "../../../api/project";
|
||||
import {getCurrentWorkspaceId} from "metersphere-frontend/src/utils/token";
|
||||
|
||||
export default {
|
||||
name: "ProjectJiraConfig",
|
||||
components: {MsInstructionsIcon},
|
||||
props: ['labelWidth', 'form', 'result'],
|
||||
data() {
|
||||
return {
|
||||
issueTypes: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getIssueTypeOption();
|
||||
},
|
||||
methods: {
|
||||
getIssueTypeOption() {
|
||||
this.issueTypes = [];
|
||||
this.result.loading = true;
|
||||
let param = {
|
||||
projectId: this.form.id,
|
||||
workspaceId: getCurrentWorkspaceId(),
|
||||
jiraKey: this.form.jiraKey
|
||||
}
|
||||
getJiraIssueType(param)
|
||||
.then((response) => {
|
||||
this.issueTypes = response.data;
|
||||
let hasJiraIssueType = false;
|
||||
let hasJiraStoryType = false;
|
||||
if (response.data) {
|
||||
response.data.forEach(item => {
|
||||
if (this.form.issueConfigObj.jiraIssueTypeId === item.id) {
|
||||
hasJiraIssueType = true;
|
||||
} else if (this.form.issueConfigObj.jiraStoryTypeId === item.id) {
|
||||
hasJiraStoryType = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!hasJiraIssueType) {
|
||||
this.form.issueConfigObj.jiraIssueTypeId = null;
|
||||
}
|
||||
if (!hasJiraStoryType) {
|
||||
this.form.issueConfigObj.jiraStoryTypeId = null;
|
||||
}
|
||||
this.result.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-input, .el-textarea {
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
|
@ -19,10 +19,13 @@
|
|||
class="checkButton">
|
||||
{{ $t('test_track.issue.check_id_exist') }}
|
||||
</el-button>
|
||||
<ms-instructions-icon v-if="item.instructionsIcon" effect="light">
|
||||
<ms-instructions-icon v-if="item.instructionsIcon || item.instructionsTip" effect="light">
|
||||
<template>
|
||||
<img class="jira-image"
|
||||
<img v-if="item.instructionsIcon"
|
||||
:src="getPlatformImageUrl(config, item)"/>
|
||||
<span v-if="item.instructionsTip">
|
||||
{{ item.instructionsTip }}
|
||||
</span>
|
||||
</template>
|
||||
</ms-instructions-icon>
|
||||
</el-form-item>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue