提交 d513ae44 编写于 作者: C chenjianxing

Merge branch 'v1.1' of https://github.com/metersphere/server into v1.1

......@@ -44,6 +44,11 @@
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
......@@ -126,10 +131,6 @@
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<!-- openapi -->
<dependency>
<groupId>org.springdoc</groupId>
......
......@@ -4,13 +4,17 @@ import io.metersphere.config.JmeterProperties;
import io.metersphere.config.KafkaProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@SpringBootApplication(exclude = {
QuartzAutoConfiguration.class,
LdapAutoConfiguration.class
})
@ServletComponentScan
@EnableConfigurationProperties({
KafkaProperties.class,
......
......@@ -48,6 +48,7 @@ public class ShiroConfig implements EnvironmentAware {
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/signin", "anon");
filterChainDefinitionMap.put("/ldap/signin", "anon");
filterChainDefinitionMap.put("/ldap/open", "anon");
filterChainDefinitionMap.put("/isLogin", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
......
......@@ -7,6 +7,7 @@ import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.util.StringUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.utils.EasyExcelI18nTranslator;
import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator;
......@@ -24,6 +25,8 @@ public abstract class EasyExcelListener<T> extends AnalysisEventListener<T> {
protected EasyExcelI18nTranslator easyExcelI18nTranslator;
protected List<TestCaseExcelData> excelDataList = new ArrayList<>();
/**
* 每隔2000条存储数据库,然后清理list ,方便内存回收
*/
......
......@@ -10,10 +10,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
......@@ -57,10 +54,35 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
if (!userIds.contains(data.getMaintainer())) {
stringBuilder.append(Translator.get("user_not_exists") + ":" + data.getMaintainer() + "; ");
}
if (testCaseNames.contains(data.getName())) {
stringBuilder.append(Translator.get("test_case_already_exists_excel") + ":" + data.getName() + "; ");
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
BeanUtils.copyBean(testCase, data);
testCase.setProjectId(projectId);
String steps = getSteps(data);
testCase.setSteps(steps);
boolean dbExist = testCaseService.exist(testCase);
boolean excelExist = false;
if (dbExist) {
// db exist
stringBuilder.append(Translator.get("test_case_already_exists_excel") + ":" + data.getName() + "; ");
} else {
// @Data 重写了 equals 和 hashCode 方法
excelExist = excelDataList.contains(data);
}
if (excelExist) {
// excel exist
stringBuilder.append(Translator.get("test_case_already_exists_excel") + ":" + data.getName() + "; ");
} else {
excelDataList.add(data);
}
} else {
testCaseNames.add(data.getName());
excelDataList.add(data);
}
return stringBuilder.toString();
}
......@@ -103,6 +125,13 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
testCase.setNodePath(nodePath);
String steps = getSteps(data);
testCase.setSteps(steps);
return testCase;
}
public String getSteps(TestCaseExcelData data) {
JSONArray jsonArray = new JSONArray();
String[] stepDesc = new String[1];
......@@ -124,7 +153,8 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
for (int i = 0; i < index; i++) {
JSONObject step = new JSONObject();
// 保持插入顺序,判断用例是否有相同的steps
JSONObject step = new JSONObject(true);
step.put("num", i + 1);
Pattern descPattern = Pattern.compile(pattern);
......@@ -150,10 +180,7 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
jsonArray.add(step);
}
testCase.setSteps(jsonArray.toJSONString());
return testCase;
return jsonArray.toJSONString();
}
}
......@@ -13,10 +13,7 @@ import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
......@@ -82,4 +79,9 @@ public class LdapController {
ldapService.authenticate(request);
}
@GetMapping("/open")
public boolean isOpen() {
return ldapService.isOpen();
}
}
......@@ -92,9 +92,7 @@ public class LdapService {
if (result.size() == 1) {
return result.get(0);
}
} catch (NameNotFoundException e) {
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidNameException e) {
} catch (NameNotFoundException | InvalidNameException e) {
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidSearchFilterException e) {
MSException.throwException(Translator.get("login_fail_filter_error"));
......@@ -125,9 +123,7 @@ public class LdapService {
MSException.throwException(Translator.get("ldap_ou_is_null"));
}
String[] arr = ou.split("\\|");
return arr;
return ou.split("\\|");
}
private static class MsContextMapper extends AbstractContextMapper<DirContextOperations> {
......@@ -217,4 +213,11 @@ public class LdapService {
return result;
}
public boolean isOpen() {
String open = service.getValue(ParamConstants.LDAP.OPEN.getValue());
if (StringUtils.isBlank(open)) {
return false;
}
return StringUtils.equals(Boolean.TRUE.toString(), open);
}
}
......@@ -106,13 +106,16 @@ public class TestCaseService {
TestCaseExample.Criteria criteria = example.createCriteria();
criteria.andNameEqualTo(testCase.getName())
.andProjectIdEqualTo(testCase.getProjectId())
.andNodeIdEqualTo(testCase.getNodeId())
.andNodePathEqualTo(testCase.getNodePath())
.andTypeEqualTo(testCase.getType())
.andMaintainerEqualTo(testCase.getMaintainer())
.andPriorityEqualTo(testCase.getPriority())
.andMethodEqualTo(testCase.getMethod());
// if (StringUtils.isNotBlank(testCase.getNodeId())) {
// criteria.andNodeIdEqualTo(testCase.getTestId());
// }
if (StringUtils.isNotBlank(testCase.getTestId())) {
criteria.andTestIdEqualTo(testCase.getTestId());
}
......@@ -371,8 +374,8 @@ public class TestCaseService {
JSONArray jsonArray = JSON.parseArray(steps);
for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1;
step.append(num + ":" + jsonArray.getJSONObject(j).getString("desc") + "\n");
result.append(num + ":" + jsonArray.getJSONObject(j).getString("result") + "\n");
step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\n");
result.append(num + "." + jsonArray.getJSONObject(j).getString("result") + "\n");
}
data.setStepDesc(step.toString());
......@@ -471,4 +474,21 @@ public class TestCaseService {
return Optional.ofNullable(testCase.getNum() + 1).orElse(100001);
}
}
/**
* 导入用例前,检查数据库是否存在此用例
* @param testCaseWithBLOBs
* @return
*/
public boolean exist(TestCaseWithBLOBs testCaseWithBLOBs) {
try {
checkTestCaseExist(testCaseWithBLOBs);
} catch (MSException e) {
return true;
}
return false;
}
}
......@@ -74,3 +74,7 @@ quartz.scheduler-name=msServerJob
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=30MB
# actuator
management.server.port=8083
management.endpoints.web.exposure.include=*
......@@ -157,6 +157,6 @@
</logger>
<logger name="com.alibaba.nacos.naming.client.listener" additivity="false" level="ERROR"/>
<logger name="org.apache.dubbo.registry.nacos.NacosRegistry" additivity="false" level="ERROR"/>
<logger name="org.apache.dubbo" additivity="false" level="ERROR"/>
</configuration>
\ No newline at end of file
<template>
<div class="container" v-loading="loading">
<div class="container" v-loading="loading" :element-loading-text="$t('api_report.running')">
<div class="main-content">
<el-card>
<section class="report-container" v-if="this.report.testId">
<header class="report-header">
<span>{{report.projectName}} / </span>
<router-link :to="path">{{report.testName}}</router-link>
<span class="time">{{report.createTime | timestampFormatDate}}</span>
<span>{{ report.projectName }} / </span>
<router-link :to="path">{{ report.testName }}</router-link>
<span class="time">{{ report.createTime | timestampFormatDate }}</span>
</header>
<main v-if="this.isNotRunning">
<ms-metric-chart :content="content"/>
......@@ -16,7 +16,7 @@
</el-tab-pane>
<el-tab-pane name="fail">
<template slot="label">
<span class="fail">{{$t('api_report.fail')}}</span>
<span class="fail">{{ $t('api_report.fail') }}</span>
</template>
<ms-scenario-results :scenarios="fails"/>
</el-tab-pane>
......@@ -30,126 +30,126 @@
<script>
import MsRequestResult from "./components/RequestResult";
import MsScenarioResult from "./components/ScenarioResult";
import MsMetricChart from "./components/MetricChart";
import MsScenarioResults from "./components/ScenarioResults";
import MsRequestResult from "./components/RequestResult";
import MsScenarioResult from "./components/ScenarioResult";
import MsMetricChart from "./components/MetricChart";
import MsScenarioResults from "./components/ScenarioResults";
export default {
name: "MsApiReportView",
components: {MsScenarioResults, MsMetricChart, MsScenarioResult, MsRequestResult},
data() {
return {
activeName: "total",
content: {},
report: {},
loading: true,
fails: []
}
export default {
name: "MsApiReportView",
components: {MsScenarioResults, MsMetricChart, MsScenarioResult, MsRequestResult},
data() {
return {
activeName: "total",
content: {},
report: {},
loading: true,
fails: []
}
},
methods: {
init() {
this.loading = true;
this.report = {};
this.content = {};
this.fails = [];
},
getReport() {
this.init();
methods: {
init() {
this.loading = true;
this.report = {};
this.content = {};
if (this.reportId) {
let url = "/api/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
console.log(this.report.content)
throw e;
}
this.getFails();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
});
}
},
getFails() {
if (this.isNotRunning) {
this.fails = [];
},
getReport() {
this.init();
if (this.reportId) {
let url = "/api/report/get/" + this.reportId;
this.$get(url, response => {
this.report = response.data || {};
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
} catch (e) {
console.log(this.report.content)
throw e;
this.content.scenarios.forEach((scenario) => {
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
this.getFails();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
}
});
}
},
getFails() {
if (this.isNotRunning) {
this.fails = [];
this.content.scenarios.forEach((scenario) => {
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
})
})
}
})
}
}
})
}
},
}
},
watch: {
'$route': 'getReport',
},
watch: {
'$route': 'getReport',
},
created() {
this.getReport();
},
created() {
this.getReport();
},
computed: {
reportId: function () {
return this.$route.params.reportId;
},
path() {
return "/api/test/edit?id=" + this.report.testId;
},
isNotRunning() {
return "Running" !== this.report.status;
}
computed: {
reportId: function () {
return this.$route.params.reportId;
},
path() {
return "/api/test/edit?id=" + this.report.testId;
},
isNotRunning() {
return "Running" !== this.report.status;
}
}
}
</script>
<style>
.report-container .el-tabs__header {
margin-bottom: 1px;
}
.report-container .el-tabs__header {
margin-bottom: 1px;
}
</style>
<style scoped>
.report-container {
height: calc(100vh - 150px);
min-height: 600px;
overflow-y: auto;
}
.report-container {
height: calc(100vh - 150px);
min-height: 600px;
overflow-y: auto;
}
.report-header {
font-size: 15px;
}
.report-header {
font-size: 15px;
}
.report-header a {
text-decoration: none;
}
.report-header a {
text-decoration: none;
}
.report-header .time {
color: #909399;
margin-left: 10px;
}
.report-header .time {
color: #909399;
margin-left: 10px;
}
.report-container .fail {
color: #F56C6C;
}
.report-container .fail {
color: #F56C6C;
}
.report-container .is-active .fail {
color: inherit;
}
.report-container .is-active .fail {
color: inherit;
}
</style>
<template>
<el-form :model="config" :rules="rules" ref="config" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description">
{{description}}
{{ description }}
</div>
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
<el-select v-model="config.protocol" class="select-100">
<el-select v-model="config.protocol" class="select-100" clearable>
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
</el-select>
</el-form-item>
......@@ -43,41 +43,41 @@
</template>
<script>
import './dubbo.css'
import {ConfigCenter} from "@/business/components/api/test/model/ScenarioModel";
import './dubbo.css'
import {ConfigCenter} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsDubboConfigCenter",
props: {
description: String,
config: ConfigCenter,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
protocols: ConfigCenter.PROTOCOLS,
methods: [],
rules: {
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
namespace: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
username: [
{max: 100, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
password: [
{max: 30, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
export default {
name: "MsDubboConfigCenter",
props: {
description: String,
config: ConfigCenter,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
protocols: ConfigCenter.PROTOCOLS,
methods: [],
rules: {
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
namespace: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
username: [
{max: 100, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
password: [
{max: 30, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
}
}
}
</script>
......@@ -2,7 +2,7 @@
<el-form :model="consumer" :rules="rules" ref="consumer" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description">
{{description}}
{{ description }}
</div>
<el-form-item label="Timeout" prop="timeout" class="dubbo-form-item">
<el-input type="number" v-model="consumer.timeout" :placeholder="$t('commons.input_content')"/>
......@@ -32,13 +32,13 @@
</el-form-item>
<el-form-item label="Async" prop="async" class="dubbo-form-item">
<el-select v-model="consumer.async" class="select-100">
<el-select v-model="consumer.async" class="select-100" clearable>
<el-option v-for="option in asyncOptions" :key="option" :label="option" :value="option"/>
</el-select>
</el-form-item>
<el-form-item label="LoadBalance" prop="loadBalance" class="dubbo-form-item">
<el-select v-model="consumer.loadBalance" class="select-100">
<el-select v-model="consumer.loadBalance" class="select-100" clearable>
<el-option v-for="option in loadBalances" :key="option" :label="option" :value="option"/>
</el-select>
</el-form-item>
......@@ -47,36 +47,36 @@
</template>
<script>
import './dubbo.css'
import {ConsumerAndService, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
import './dubbo.css'
import {ConsumerAndService, RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsDubboConsumerService",
props: {
description: String,
consumer: ConsumerAndService,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
asyncOptions: ConsumerAndService.ASYNC_OPTIONS,
loadBalances: ConsumerAndService.LOAD_BALANCE_OPTIONS,
methods: [],
rules: {
version: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
],
cluster: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
export default {
name: "MsDubboConsumerService",
props: {
description: String,
consumer: ConsumerAndService,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
asyncOptions: ConsumerAndService.ASYNC_OPTIONS,
loadBalances: ConsumerAndService.LOAD_BALANCE_OPTIONS,
methods: [],
rules: {
version: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
],
cluster: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
}
}
}
</script>
<template>
<el-form :model="registry" :rules="rules" ref="registry" label-width="100px" size="small" :disabled="isReadOnly">
<div class="dubbo-form-description" v-if="description">
{{description}}
{{ description }}
</div>
<el-form-item label="Protocol" prop="protocol" class="dubbo-form-item">
<el-select v-model="registry.protocol" class="select-100">
<el-select v-model="registry.protocol" class="select-100" clearable>
<el-option v-for="p in protocols" :key="p" :label="p" :value="p"/>
</el-select>
</el-form-item>
......@@ -36,38 +36,38 @@
</template>
<script>
import './dubbo.css'
import {RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
import './dubbo.css'
import {RegistryCenter} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsDubboRegistryCenter",
props: {
description: String,
registry: RegistryCenter,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
protocols: RegistryCenter.PROTOCOLS,
methods: [],
rules: {
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
username: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
password: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
],
address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
export default {
name: "MsDubboRegistryCenter",
props: {
description: String,
registry: RegistryCenter,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
protocols: RegistryCenter.PROTOCOLS,
methods: [],
rules: {
group: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
username: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
password: [
{max: 30, message: this.$t('commons.input_limit', [0, 30]), trigger: 'blur'}
],
address: [
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
]
}
}
}
}
</script>
......@@ -474,6 +474,7 @@ export default {
sub_result: "Sub Result",
detail: "Report detail",
delete: "Delete report",
running: "The test is running",
},
test_track: {
test_track: "Track",
......
......@@ -473,6 +473,7 @@ export default {
sub_result: "子请求",
detail: "报告详情",
delete: "删除报告",
running: "测试执行中",
},
test_track: {
test_track: "测试跟踪",
......
......@@ -473,6 +473,7 @@ export default {
sub_result: "子請求",
detail: "報告詳情",
delete: "刪除報告",
running: "測試執行中",
},
test_track: {
test_track: "測試跟踪",
......
......@@ -17,8 +17,8 @@
<div class="form">
<el-form-item v-slot:default>
<el-radio-group v-model="form.authenticate">
<el-radio label="LDAP" size="mini">LDAP</el-radio>
<el-radio label="LOCAL" size="mini">普通登录</el-radio>
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="username">
......@@ -81,7 +81,8 @@
]
},
msg: '',
ready: false
ready: false,
openLdap: false
}
},
beforeCreate() {
......@@ -92,6 +93,9 @@
window.location.href = "/"
}
});
this.$get("/ldap/open", response => {
this.openLdap = response.data;
})
},
created: function () {
// 主页添加键盘事件,注意,不能直接在焦点事件上添加回车
......@@ -145,7 +149,7 @@
if (!language) {
this.$get("language", response => {
language = response.data;
localStorage.setItem(DEFAULT_LANGUAGE, language)
localStorage.setItem(DEFAULT_LANGUAGE, language);
window.location.href = "/"
})
} else {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册