Merge remote-tracking branch 'origin/master'

This commit is contained in:
song.tianyang 2021-03-02 18:27:11 +08:00
commit 1df8b0c38a
29 changed files with 843 additions and 123 deletions

View File

@ -280,7 +280,9 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
preCreate(hashTree);
// 更新数据源
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
if (dataPools.getDataSources() != null) {
dataPools.getEnvConfig().setDatabaseConfigs(new ArrayList<>(dataPools.getDataSources().values()));
}
if (dataPools.getIsCreate()) {
dataPools.getTestEnvironmentWithBLOBs().setConfig(JSON.toJSONString(dataPools.getEnvConfig()));
String id = environmentService.add(dataPools.getTestEnvironmentWithBLOBs());

View File

@ -1,19 +1,15 @@
package io.metersphere.api.dto.definition.request.auth;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.utils.CommonBeanFactory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.http.control.AuthManager;
import org.apache.jmeter.protocol.http.control.Authorization;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
@ -66,18 +62,37 @@ public class MsAuthManager extends MsTestElement {
if (this.url != null) {
auth.setURL(this.url);
} else {
if (environment != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environmentWithBLOBs = environmentService.get(environment);
EnvironmentConfig envConfig = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
this.url = envConfig.getHttpConfig().getProtocol() + "://" + envConfig.getHttpConfig().getSocket();
if (config != null && config.isEffective(this.getProjectId())) {
if (config.isEffective(this.getProjectId())) {
String url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
auth.setURL(url);
}
}
}
auth.setDomain(this.domain);
auth.setUser(this.username);
auth.setPass(this.password);
auth.setMechanism(AuthManager.Mechanism.DIGEST);
authManager.addAuth(auth);
tree.add(authManager);
}
public void setAuth(HashTree tree, MsAuthManager msAuthManager, HTTPSamplerProxy samplerProxy) {
try {
AuthManager authManager = new AuthManager();
authManager.setEnabled(true);
authManager.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "AuthManager");
authManager.setProperty(TestElement.TEST_CLASS, AuthManager.class.getName());
authManager.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("AuthPanel"));
Authorization auth = new Authorization();
auth.setURL(samplerProxy.getUrl().toString());
auth.setDomain(samplerProxy.getDomain());
auth.setUser(msAuthManager.getUsername());
auth.setPass(msAuthManager.getPassword());
auth.setMechanism(AuthManager.Mechanism.DIGEST);
authManager.addAuth(auth);
tree.add(authManager);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -8,7 +8,6 @@ import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.commons.constants.MsTestElementConstants;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ScriptEngineUtils;
@ -232,7 +231,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
if (this.authManager != null) {
this.authManager.toHashTree(tree, hashTree, config);
this.authManager.setAuth(tree, this.authManager, sampler);
}
}

View File

@ -5,6 +5,7 @@ import io.metersphere.api.dto.scenario.HttpConfig;
import io.metersphere.api.dto.scenario.TCPConfig;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@ -13,4 +14,11 @@ public class EnvironmentConfig {
private HttpConfig httpConfig;
private List<DatabaseConfig> databaseConfigs;
private TCPConfig tcpConfig;
public EnvironmentConfig() {
this.commonConfig = new CommonConfig();
this.httpConfig = new HttpConfig();
this.databaseConfigs = new ArrayList<>();
this.tcpConfig = new TCPConfig();
}
}

View File

@ -486,6 +486,7 @@ public class ApiTestCaseService {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MsTestElement element = mapper.readValue(testCaseWithBLOBs.getRequest(), new TypeReference<MsTestElement>() {
});
element.setProjectId(testCaseWithBLOBs.getProjectId());
if (StringUtils.isBlank(request.getEnvironmentId())) {
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andTestPlanIdEqualTo(request.getTestPlanId()).andApiCaseIdEqualTo(request.getCaseId());
@ -513,9 +514,14 @@ public class ApiTestCaseService {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(request.getEnvironmentId());
ParameterConfig parameterConfig = new ParameterConfig();
// if (environment != null && environment.getConfig() != null) {
// parameterConfig.setConfig(JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class));
// }
Map<String, EnvironmentConfig> envConfig = new HashMap<>(16);
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig environmentConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
envConfig.put(testCaseWithBLOBs.getProjectId(), environmentConfig);
parameterConfig.setConfig(envConfig);
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), parameterConfig);
return jmeterHashTree;
}

View File

@ -21,6 +21,8 @@ public class TestCaseReview implements Serializable {
private String projectId;
private String tags;
private String description;
private static final long serialVersionUID = 1L;

View File

@ -633,6 +633,76 @@ public class TestCaseReviewExample {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andTagsIsNull() {
addCriterion("tags is null");
return (Criteria) this;
}
public Criteria andTagsIsNotNull() {
addCriterion("tags is not null");
return (Criteria) this;
}
public Criteria andTagsEqualTo(String value) {
addCriterion("tags =", value, "tags");
return (Criteria) this;
}
public Criteria andTagsNotEqualTo(String value) {
addCriterion("tags <>", value, "tags");
return (Criteria) this;
}
public Criteria andTagsGreaterThan(String value) {
addCriterion("tags >", value, "tags");
return (Criteria) this;
}
public Criteria andTagsGreaterThanOrEqualTo(String value) {
addCriterion("tags >=", value, "tags");
return (Criteria) this;
}
public Criteria andTagsLessThan(String value) {
addCriterion("tags <", value, "tags");
return (Criteria) this;
}
public Criteria andTagsLessThanOrEqualTo(String value) {
addCriterion("tags <=", value, "tags");
return (Criteria) this;
}
public Criteria andTagsLike(String value) {
addCriterion("tags like", value, "tags");
return (Criteria) this;
}
public Criteria andTagsNotLike(String value) {
addCriterion("tags not like", value, "tags");
return (Criteria) this;
}
public Criteria andTagsIn(List<String> values) {
addCriterion("tags in", values, "tags");
return (Criteria) this;
}
public Criteria andTagsNotIn(List<String> values) {
addCriterion("tags not in", values, "tags");
return (Criteria) this;
}
public Criteria andTagsBetween(String value1, String value2) {
addCriterion("tags between", value1, value2, "tags");
return (Criteria) this;
}
public Criteria andTagsNotBetween(String value1, String value2) {
addCriterion("tags not between", value1, value2, "tags");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -10,6 +10,7 @@
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="end_time" jdbcType="BIGINT" property="endTime" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="tags" jdbcType="VARCHAR" property="tags" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestCaseReview">
<result column="description" jdbcType="LONGVARCHAR" property="description" />
@ -73,7 +74,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, `name`, creator, `status`, create_time, update_time, end_time, project_id
id, `name`, creator, `status`, create_time, update_time, end_time, project_id, tags
</sql>
<sql id="Blob_Column_List">
description
@ -129,12 +130,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.TestCaseReview">
insert into test_case_review (id, `name`, creator,
`status`, create_time, update_time,
end_time, project_id, description
)
end_time, project_id, tags,
description)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{creator,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{endTime,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}
)
#{endTime,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR}, #{tags,jdbcType=VARCHAR},
#{description,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseReview">
insert into test_case_review
@ -163,6 +164,9 @@
<if test="projectId != null">
project_id,
</if>
<if test="tags != null">
tags,
</if>
<if test="description != null">
description,
</if>
@ -192,6 +196,9 @@
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="tags != null">
#{tags,jdbcType=VARCHAR},
</if>
<if test="description != null">
#{description,jdbcType=LONGVARCHAR},
</if>
@ -230,6 +237,9 @@
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.tags != null">
tags = #{record.tags,jdbcType=VARCHAR},
</if>
<if test="record.description != null">
description = #{record.description,jdbcType=LONGVARCHAR},
</if>
@ -248,6 +258,7 @@
update_time = #{record.updateTime,jdbcType=BIGINT},
end_time = #{record.endTime,jdbcType=BIGINT},
project_id = #{record.projectId,jdbcType=VARCHAR},
tags = #{record.tags,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -262,7 +273,8 @@
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
end_time = #{record.endTime,jdbcType=BIGINT},
project_id = #{record.projectId,jdbcType=VARCHAR}
project_id = #{record.projectId,jdbcType=VARCHAR},
tags = #{record.tags,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -291,6 +303,9 @@
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="tags != null">
tags = #{tags,jdbcType=VARCHAR},
</if>
<if test="description != null">
description = #{description,jdbcType=LONGVARCHAR},
</if>
@ -306,6 +321,7 @@
update_time = #{updateTime,jdbcType=BIGINT},
end_time = #{endTime,jdbcType=BIGINT},
project_id = #{projectId,jdbcType=VARCHAR},
tags = #{tags,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
@ -317,7 +333,8 @@
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
end_time = #{endTime,jdbcType=BIGINT},
project_id = #{projectId,jdbcType=VARCHAR}
project_id = #{projectId,jdbcType=VARCHAR},
tags = #{tags,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -5,6 +5,7 @@
<select id="list" resultType="io.metersphere.track.dto.TestCaseReviewDTO"
parameterType="io.metersphere.track.request.testreview.QueryCaseReviewRequest">
select distinct test_case_review.id, test_case_review.name, test_case_review.creator, test_case_review.status,
test_case_review.tags,
test_case_review.create_time, test_case_review.update_time, test_case_review.end_time,
test_case_review.description, user.name as creatorName, project.name as projectName, test_case_review.project_id
from test_case_review

View File

@ -70,6 +70,7 @@
<!--<table tableName="test_plan_api_scenario"/>-->
<!--<table tableName="test_plan"/>-->
<!--<table tableName="api_scenario_report"/>-->
<table tableName="test_case_review"/>
</context>
</generatorConfiguration>

View File

@ -6,8 +6,8 @@
:destroy-on-close="true"
:before-close="handleClose">
<div v-for="pe in data" :key="pe.id">
<div>
<div v-loading="result.loading">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px;">
{{ getProjectName(pe.id) }}
<el-select v-model="pe['selectEnv']" placeholder="请选择环境" style="margin-left:10px; margin-top: 10px;"
size="small">
@ -32,48 +32,52 @@
<el-button @click="dialogVisible = false" size="small"> </el-button>
<el-button type="primary" @click="handleConfirm" size="small"> </el-button>
</span>
<!-- 环境配置 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-dialog>
</template>
<script>
import {parseEnvironment} from "@/business/components/api/test/model/EnvironmentModel";
import ApiEnvironmentConfig from "@/business/components/api/definition/components/environment/ApiEnvironmentConfig";
export default {
name: "ApiScenarioEnv",
components: {ApiEnvironmentConfig},
props: {
envMap: Map,
projectIds: Set,
envMap: Map
projectList: Array
},
data() {
return {
dialogVisible: false,
projects: [],
data: [],
result: {},
projects: [],
environmentId: '',
environments: []
environments: [],
dialogVisible: false
}
},
created() {
this.getWsProjects();
},
methods: {
handleClose() {
this.dialogVisible = false;
},
init() {
this.projectIds.forEach(id => {
let item = {id: id, envs: [], selectEnv: ""};
this.data.push(item);
this.result = this.$get('/api/environment/list/' + id, res => {
let envs = res.data;
envs.forEach(environment => {
parseEnvironment(environment);
});
let item = {};
item.id = id;
item.envs = envs;
item.selectEnv = this.envMap.get(id);
this.data.push(item)
//
let temp = this.data.find(dt => dt.id === id);
temp.envs = envs;
temp.selectEnv = this.envMap.get(id);
})
})
},
@ -84,13 +88,8 @@ export default {
this.init();
}
},
getWsProjects() {
this.$get("/project/listAll", res => {
this.projects = res.data;
})
},
getProjectName(id) {
const project = this.projects.find(p => p.id === id);
const project = this.projectList.find(p => p.id === id);
return project ? project.name : "";
},
openEnvironmentConfig(projectId) {
@ -123,7 +122,7 @@ export default {
this.data.forEach(dt => {
if (!dt.selectEnv) {
sign = false;
return;
return false;
}
})
} else {
@ -136,13 +135,16 @@ export default {
}
return true;
},
environmentConfigClose(id) {
// todo
environmentConfigClose() {
this.data = [];
this.init();
}
}
}
</script>
<style scoped>
.ms-scenario-button {
margin-left: 20px;
}
</style>

View File

@ -111,26 +111,9 @@
<el-col :span="3" class="ms-col-one ms-font">
<el-checkbox v-model="enableCookieShare">共享cookie</el-checkbox>
</el-col>
<el-col :span="7" class="ms-col-one ms-font">
<el-link type="primary" @click="handleEnv">环境配置</el-link>
<!-- <el-select v-model="currentEnvironmentId" size="small" class="ms-htt-width"-->
<!-- :placeholder="$t('api_test.definition.request.run_env')"-->
<!-- clearable>-->
<!-- <el-option v-for="(environment, index) in environments" :key="index"-->
<!-- :label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"-->
<!-- :value="environment.id"/>-->
<!-- <el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig">-->
<!-- {{ $t('api_test.environment.environment_config') }}-->
<!-- </el-button>-->
<!-- <template v-slot:empty>-->
<!-- <div class="empty-environment">-->
<!-- <el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig">-->
<!-- {{ $t('api_test.environment.environment_config') }}-->
<!-- </el-button>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-select>-->
<el-col :span="7">
<env-popover :env-map="projectEnvMap" :project-ids="projectIds" @setProjectEnvMap="setProjectEnvMap"
:project-list="projectList" ref="envPopover"/>
</el-col>
<el-col :span="2">
<el-button :disabled="scenarioDefinition.length < 1" size="small" type="primary" @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
@ -186,8 +169,6 @@
<!--场景导入 -->
<scenario-relevance @save="addScenario" ref="scenarioRelevance"/>
<api-scenario-env :project-ids="projectIds" :env-map="projectEnvMap" ref="apiScenarioEnv" @setProjectEnvMap="setProjectEnvMap"/>
<!-- 环境 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
@ -235,8 +216,7 @@
import MsComponentConfig from "./component/ComponentConfig";
import {handleCtrlSEvent} from "../../../../../common/js/utils";
import {getProject} from "@/business/components/api/automation/scenario/event";
import ApiScenarioEnv from "@/business/components/api/automation/scenario/ApiScenarioEnv";
import EnvPopover from "@/business/components/api/automation/scenario/EnvPopover";
export default {
name: "EditApiScenario",
props: {
@ -244,7 +224,6 @@
currentScenario: {},
},
components: {
ApiScenarioEnv,
MsVariableList,
ScenarioRelevance,
ScenarioApiRelevance,
@ -254,6 +233,7 @@
MsApiCustomize,
ApiImport,
MsComponentConfig,
EnvPopover
},
data() {
return {
@ -711,7 +691,7 @@
// this.$error(this.$t('api_test.environment.select_environment'));
// return;
// }
let sign = this.$refs.apiScenarioEnv.checkEnv();
let sign = this.$refs.envPopover.checkEnv();
if (!sign) {
return;
}

View File

@ -0,0 +1,49 @@
<template>
<el-popover
v-model="visible"
placement="bottom"
width="400"
@show="showPopover"
trigger="click">
<env-select :project-ids="projectIds" :env-map="envMap" @close="visible = false"
ref="envSelect" @setProjectEnvMap="setProjectEnvMap" :project-list="projectList"/>
<el-button type="primary" slot="reference" size="mini" style="margin-top: 2px;">
环境配置<i class="el-icon-caret-bottom el-icon--right"></i>
</el-button>
</el-popover>
</template>
<script>
import EnvSelect from "@/business/components/api/automation/scenario/EnvSelect";
export default {
name: "EnvPopover",
components: {EnvSelect},
props: {
envMap: Map,
projectIds: Set,
projectList: Array,
},
data() {
return {
visible: false
}
},
methods: {
showPopover() {
this.$refs.envSelect.open();
},
setProjectEnvMap(map) {
this.$emit("setProjectEnvMap", map);
},
checkEnv() {
return this.$refs.envSelect.checkEnv();
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,149 @@
<template>
<div v-loading="result.loading">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px;">
<el-select v-model="pe['selectEnv']" placeholder="请选择环境" style="margin-top: 8px;width: 200px;" size="small">
<el-option v-for="(environment, index) in pe.envs" :key="index"
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
:value="environment.id"/>
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
<span class="project-name" :title="getProjectName(pe.id)">
{{ getProjectName(pe.id) }}
</span>
</div>
<el-button type="primary" @click="handleConfirm" size="small" class="env-confirm"> </el-button>
<!-- 环境配置 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</div>
</template>
<script>
import {parseEnvironment} from "@/business/components/api/test/model/EnvironmentModel";
import ApiEnvironmentConfig from "@/business/components/api/definition/components/environment/ApiEnvironmentConfig";
export default {
name: "EnvironmentSelect",
components: {ApiEnvironmentConfig},
props: {
envMap: Map,
projectIds: Set,
projectList: Array
},
data() {
return {
data: [],
result: {},
projects: [],
environments: [],
dialogVisible: false
}
},
methods: {
init() {
this.projectIds.forEach(id => {
let item = {id: id, envs: [], selectEnv: ""};
this.data.push(item);
this.result = this.$get('/api/environment/list/' + id, res => {
let envs = res.data;
envs.forEach(environment => {
parseEnvironment(environment);
});
//
let temp = this.data.find(dt => dt.id === id);
temp.envs = envs;
temp.selectEnv = this.envMap.get(id);
})
})
},
open() {
this.data = [];
if (this.projectIds.size > 0) {
this.init();
}
},
getProjectName(id) {
const project = this.projectList.find(p => p.id === id);
return project ? project.name : "";
},
openEnvironmentConfig(projectId) {
if (!projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(projectId);
},
handleConfirm() {
let map = new Map();
let sign = true;
this.data.forEach(dt => {
if (!dt.selectEnv) {
sign = false;
return;
}
map.set(dt.id, dt.selectEnv);
})
if (!sign) {
this.$warning("请为每个项目选择一个运行环境!");
return;
}
this.$emit('setProjectEnvMap', map);
this.$emit('close');
},
checkEnv() {
let sign = true;
if (this.data.length > 0) {
this.data.forEach(dt => {
if (!dt.selectEnv) {
sign = false;
return false;
}
})
} else {
sign = false;
}
if (!sign) {
this.$warning("请为每个项目选择一个运行环境!");
return false;
}
return true;
},
environmentConfigClose() {
// todo
}
}
}
</script>
<style scoped>
.ms-scenario-button {
margin-left: 20px;
}
.env-confirm {
margin-left: 20px;
width: 360px;
margin-top: 10px;
}
.project-name {
display:inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 150px;
margin-left: 8px;
vertical-align:middle;
}
</style>

View File

@ -222,7 +222,7 @@ import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, API_STATUS, DUBBO_METHOD, REQ_METHOD, SQL_METHOD, TCP_METHOD} from "../../model/JsonData";
import {downloadFile} from "@/common/js/utils";
import {downloadFile, getUUID} from "@/common/js/utils";
import {PROJECT_NAME} from '@/common/js/constants';
import {getCurrentProjectID, getCurrentUser} from "@/common/js/utils";
import {API_LIST, TEST_CASE_LIST, WORKSPACE_ID} from '@/common/js/constants';
@ -244,6 +244,7 @@ import {_filter, _sort} from "@/common/js/tableUtils";
import {Api_List, Track_Test_Case} from "@/business/components/common/model/JsonData";
import HeaderCustom from "@/business/components/common/head/HeaderCustom";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import {Body} from "@/business/components/api/definition/model/ApiTestModel";
export default {
@ -533,7 +534,35 @@ export default {
},
runApi(row) {
let request = JSON.parse(row.request);
if (row.tags instanceof Array) {
row.tags = JSON.stringify(row.tags);
}
let response = ""
if (row.response != null && row.response != 'null' && row.response != undefined) {
if (Object.prototype.toString.call(row.response).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
response = row.response;
} else {
response = JSON.parse(row.response);
}
} else {
response = {headers: [], body: new Body(), statusCode: [], type: "HTTP"};
}
if (response.body) {
let body = new Body();
Object.assign(body, response.body);
if (!body.binary) {
body.binary = [];
}
if (!body.kvs) {
body.kvs = [];
}
if (!body.binary) {
body.binary = [];
}
response.body = body;
}
row.request = request
row.response = response
this.$emit('runTest', row);
},
reductionApi(row) {

View File

@ -202,6 +202,8 @@ export default {
let bodyFiles = this.getBodyUploadFiles();
this.api.method = this.api.request.method;
this.api.path = this.api.request.path;
console.log(this.api)
console.log(typeof (bodyFiles))
this.$fileUpload(url, null, bodyFiles, this.api, () => {
this.$success(this.$t('commons.save_success'));
this.$emit('saveApi', this.api);

View File

@ -5,6 +5,9 @@
:draggable="true"
:to_data='fieldSelected'
:defaultProps="{label:'label'}"
:allow-drop="allowDrop"
:default-checked-keys="defaultCheckedKeys"
:default-transfer="defaultTransfer"
:mode='mode' height='540px' filter openAll/>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="saveHeader"/>
@ -25,6 +28,8 @@ export default {
dialogTableVisible: false,
value: [],
fieldSelected: [],
defaultCheckedKeys: [],
defaultTransfer: true,
mode: "transfer", // transfer addressList
}
},
@ -37,8 +42,15 @@ export default {
type: String
},
methods: {
allowDrop(draggingNode, dropNode, type) {
return type !== 'inner';
},
open(items) {
this.dialogTableVisible = true
items.forEach(i => {
this.defaultCheckedKeys.push(i.id)
}
)
/*this.optionalField = items*/
},
saveHeader() {

View File

@ -21,6 +21,7 @@ export const Test_Case_Review = [
{id: 'status', label: i18n.t('test_track.review.review_status')},
{id: 'createTime', label: i18n.t('commons.create_time')},
{id: 'endTime', label: i18n.t('test_track.review.end_time')},
{id: 'tags', label: '标签'},
]
//测试计划-测试用例
export const Test_Plan_List = [

View File

@ -83,13 +83,13 @@
<el-progress :percentage="scope.row.testRate"></el-progress>
</template>
</el-table-column>
<el-table-colum
<el-table-column
v-if="item.id == 'projectName'"
prop="projectName"
:label="$t('test_track.plan.plan_project')"
show-overflow-tooltip
:key="index">
</el-table-colum>
</el-table-column>
<el-table-column
v-if="item.id == 'plannedStartTime'"
sortable

View File

@ -21,6 +21,11 @@
<el-input v-model="form.name"/>
</el-form-item>
</el-col>
<el-col :span="10" :offset="1">
<el-form-item :label="$t('commons.tag')" :label-width="formLabelWidth" prop="tag">
<ms-input-tag :currentScenario="form" ref="tag"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
@ -80,6 +85,9 @@
<el-button type="primary" @click="saveReview">
{{ $t('test_track.confirm') }}
</el-button>
<el-button type="primary" @click="reviewInfo('form')">
{{ $t('test_track.planning_execution') }}
</el-button>
</div>
</template>
</el-dialog>
@ -94,10 +102,11 @@
import TestPlanStatusButton from "../../plan/common/TestPlanStatusButton";
import {WORKSPACE_ID} from "@/common/js/constants";
import {getCurrentProjectID, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
export default {
name: "TestCaseReviewEdit",
components: {TestPlanStatusButton},
components: {MsInputTag, TestPlanStatusButton},
data() {
return {
dialogFormVisible: false,
@ -128,6 +137,34 @@ export default {
};
},
methods: {
reviewInfo(form) {
this.$refs['reviewForm'].validate((valid) => {
if (valid) {
let param = {};
Object.assign(param, this.form);
param.name = param.name.trim();
if (this.form.tags instanceof Array) {
this.form.tags = JSON.stringify(this.form.tags);
}
param.tags = this.form.tags;
if (param.name === '') {
this.$warning(this.$t('test_track.plan.input_plan_name'));
return;
}
if (!this.compareTime(new Date().getTime(), this.form.endTime)) {
return false;
}
this.result = this.$post('/test/case/review/' + this.operationType, param, response => {
this.dialogFormVisible = false;
this.$router.push('/track/review/view/' + response.data);
});
} else {
return false;
}
});
},
openCaseReviewEditDialog(caseReview) {
this.resetForm();
this.setReviewerOptions();
@ -149,6 +186,10 @@ export default {
let param = {};
Object.assign(param, this.form);
param.name = param.name.trim();
if (this.form.tags instanceof Array) {
this.form.tags = JSON.stringify(this.form.tags);
}
param.tags = this.form.tags;
if (param.name === '') {
this.$warning(this.$t('test_track.plan.input_plan_name'));
return;

View File

@ -58,6 +58,13 @@
</span>
</template>
</el-table-column>
<el-table-column v-if="item.id == 'tags'" prop="tags"
:label="$t('api_test.automation.tag')" :key="index">
<template v-slot:default="scope">
<ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain"
:content="itemName" style="margin-left: 5px"></ms-tag>
</template>
</el-table-column>
<el-table-column
v-if="item.id=='createTime'"
prop="createTime"
@ -122,10 +129,12 @@ import {Test_Case_Review} from "@/business/components/common/model/JsonData";
import {TEST_CASE_LIST, TEST_CASE_REVIEW_LIST} from "@/common/js/constants";
import HeaderCustom from "@/business/components/common/head/HeaderCustom";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import MsTag from "@/business/components/common/components/MsTag";
export default {
name: "TestCaseReviewList",
components: {
MsTag,
HeaderLabelOperate,
HeaderCustom,
MsDeleteConfirm,
@ -184,6 +193,11 @@ export default {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
})
for (let i = 0; i < this.tableData.length; i++) {
let path = "/test/case/review/project";
this.$post(path, {id: this.tableData[i].id}, res => {

View File

@ -1,37 +1,31 @@
<template>
<ms-container>
<ms-aside-container>
<div>
<ms-test-plan-header-bar>
<template v-slot:info>
<select-menu
:data="testReviews"
:current-data="currentReview"
:title="$t('test_track.review_view.review')"
@dataChange="changeReview"/>
<node-tree class="node-tree"
:all-label="$t('commons.all_label.review')"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</ms-aside-container>
</template>
<template v-slot:menu>
<el-menu v-if="isMenuShow" active-text-color="#6d317c"
class="el-menu-demo header-menu" mode="horizontal" @select="handleSelect">
<el-menu-item index="functional">功能测试用例</el-menu-item>
<el-menu-item index="api">接口测试用例</el-menu-item>
<el-menu-item index="load">性能测试用例</el-menu-item>
<el-menu-item index="report">报告统计</el-menu-item>
</el-menu>
</template>
</ms-test-plan-header-bar>
<test-review-function v-if="activeIndex === 'functional'" :redirectCharType="redirectCharType"
:clickType="clickType" :review-id="reviewId"></test-review-function>
<test-review-api v-if="activeIndex === 'api'" :redirectCharType="redirectCharType" :clickType="clickType"
:review-id="reviewId"></test-review-api>
<test-review-load v-if="activeIndex === 'load'" :redirectCharType="redirectCharType" :clickType="clickType"
:review-id="reviewId"></test-review-load>
</div>
<ms-main-container>
<test-review-test-case-list
class="table-list"
@openTestReviewRelevanceDialog="openTestReviewRelevanceDialog"
@refresh="refresh"
:review-id="reviewId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
ref="testPlanTestCaseList"/>
</ms-main-container>
<test-review-relevance
@refresh="refresh"
:review-id="reviewId"
ref="testReviewRelevance"/>
</ms-container>
</template>
<script>
@ -44,10 +38,18 @@ import NodeTree from "../../common/NodeTree";
import TestReviewTestCaseList from "./components/TestReviewTestCaseList";
import SelectMenu from "../../common/SelectMenu";
import TestReviewRelevance from "./components/TestReviewRelevance";
import MsTestPlanHeaderBar from "@/business/components/track/plan/view/comonents/head/TestPlanHeaderBar";
import TestReviewFunction from "@/business/components/track/review/view/components/TestReviewFunction";
import TestReviewApi from "@/business/components/track/review/view/components/TestReviewApi";
import TestReviewLoad from "@/business/components/track/review/view/components/TestReviewLoad";
export default {
name: "TestCaseReviewView",
components: {
TestReviewLoad,
TestReviewApi,
TestReviewFunction,
MsTestPlanHeaderBar,
MsMainContainer,
MsAsideContainer,
MsContainer,
@ -63,7 +65,14 @@ export default {
currentReview: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: []
treeNodes: [],
currentPlan: {},
activeIndex: "functional",
isMenuShow: true,
//-
redirectCharType: '',
//-
clickType: '',
}
},
computed: {
@ -83,12 +92,25 @@ export default {
this.initData();
}
},
activated() {
this.genRedirectParam();
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testReviewRelevance.search();
this.getNodeTreeByReviewId();
handleSelect(key) {
this.activeIndex = key;
},
genRedirectParam() {
this.redirectCharType = this.$route.params.charType;
this.clickType = this.$route.params.clickType;
if (this.redirectCharType != "") {
if (this.redirectCharType == 'scenario') {
this.activeIndex = 'api';
} else if (this.redirectCharType != null && this.redirectCharType != '') {
this.activeIndex = this.redirectCharType;
}
} else {
this.activeIndex = "functional";
}
},
initData() {
this.getTestReviews();
@ -133,6 +155,12 @@ export default {
}
});
}
},
reloadMenu() {
this.isMenuShow = false;
this.$nextTick(() => {
this.isMenuShow = true;
});
}
}
}

View File

@ -0,0 +1,94 @@
<template>
<ms-test-plan-common-component>
<template v-slot:aside>
<ms-node-tree
class="node-tree"
:all-label="$t('commons.all_label.review')"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-review-test-case-list
class="table-list"
@openTestReviewRelevanceDialog="openTestReviewRelevanceDialog"
@refresh="refresh"
:review-id="reviewId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
:clickType="clickType"
ref="testPlanTestCaseList"/>
</template>
<test-review-relevance
@refresh="refresh"
:review-id="reviewId"
ref="testReviewRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import MsTestPlanCommonComponent from "@/business/components/track/plan/view/comonents/base/TestPlanCommonComponent";
import FunctionalTestCaseList from "@/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList";
import MsNodeTree from "@/business/components/track/common/NodeTree";
import TestReviewRelevance from "@/business/components/track/review/view/components/TestReviewRelevance";
import TestReviewTestCaseList from "@/business/components/track/review/view/components/TestReviewTestCaseList";
export default {
name: "TestReviewApi",
components: {
TestReviewTestCaseList,
TestReviewRelevance, MsNodeTree, FunctionalTestCaseList, MsTestPlanCommonComponent
},
data() {
return {
result: {},
testReviews: [],
currentReview: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: [],
isMenuShow: true,
}
},
props: [
'reviewId',
'redirectCharType',
'clickType'
],
mounted() {
this.getNodeTreeByReviewId()
},
activated() {
this.getNodeTreeByReviewId()
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testReviewRelevance.search();
this.getNodeTreeByReviewId();
},
nodeChange(node, nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
},
getNodeTreeByReviewId() {
if (this.reviewId) {
this.result = this.$get("/case/node/list/review/" + this.reviewId, response => {
this.treeNodes = response.data;
});
}
},
openTestReviewRelevanceDialog() {
this.$refs.testReviewRelevance.openTestReviewRelevanceDialog();
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,93 @@
<template>
<ms-test-plan-common-component>
<template v-slot:aside>
<ms-node-tree
class="node-tree"
:all-label="$t('commons.all_label.review')"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-review-test-case-list
class="table-list"
@openTestReviewRelevanceDialog="openTestReviewRelevanceDialog"
@refresh="refresh"
:review-id="reviewId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
:clickType="clickType"
ref="testPlanTestCaseList"/>
</template>
<test-review-relevance
@refresh="refresh"
:review-id="reviewId"
ref="testReviewRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import MsTestPlanCommonComponent from "@/business/components/track/plan/view/comonents/base/TestPlanCommonComponent";
import FunctionalTestCaseList from "@/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList";
import MsNodeTree from "@/business/components/track/common/NodeTree";
import TestReviewRelevance from "@/business/components/track/review/view/components/TestReviewRelevance";
import TestReviewTestCaseList from "@/business/components/track/review/view/components/TestReviewTestCaseList";
export default {
name: "TestReviewFunction",
components: {
TestReviewTestCaseList,
TestReviewRelevance, MsNodeTree, FunctionalTestCaseList, MsTestPlanCommonComponent
},
data() {
return {
result: {},
testReviews: [],
currentReview: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: [],
isMenuShow: true,
}
},
props: [
'reviewId',
'redirectCharType',
'clickType'
],
mounted() {
this.getNodeTreeByReviewId()
},
activated() {
this.getNodeTreeByReviewId()
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testReviewRelevance.search();
this.getNodeTreeByReviewId();
},
nodeChange(node, nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
},
getNodeTreeByReviewId() {
if (this.reviewId) {
this.result = this.$get("/case/node/list/review/" + this.reviewId, response => {
this.treeNodes = response.data;
});
}
},
openTestReviewRelevanceDialog() {
this.$refs.testReviewRelevance.openTestReviewRelevanceDialog();
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,94 @@
<template>
<ms-test-plan-common-component>
<template v-slot:aside>
<ms-node-tree
class="node-tree"
:all-label="$t('commons.all_label.review')"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</template>
<template v-slot:main>
<test-review-test-case-list
class="table-list"
@openTestReviewRelevanceDialog="openTestReviewRelevanceDialog"
@refresh="refresh"
:review-id="reviewId"
:select-node-ids="selectNodeIds"
:select-parent-nodes="selectParentNodes"
:clickType="clickType"
ref="testPlanTestCaseList"/>
</template>
<test-review-relevance
@refresh="refresh"
:review-id="reviewId"
ref="testReviewRelevance"/>
</ms-test-plan-common-component>
</template>
<script>
import MsTestPlanCommonComponent from "@/business/components/track/plan/view/comonents/base/TestPlanCommonComponent";
import FunctionalTestCaseList from "@/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList";
import MsNodeTree from "@/business/components/track/common/NodeTree";
import TestReviewRelevance from "@/business/components/track/review/view/components/TestReviewRelevance";
import TestReviewTestCaseList from "@/business/components/track/review/view/components/TestReviewTestCaseList";
export default {
name: "TestReviewLoad",
components: {
TestReviewTestCaseList,
TestReviewRelevance, MsNodeTree, FunctionalTestCaseList, MsTestPlanCommonComponent
},
data() {
return {
result: {},
testReviews: [],
currentReview: {},
selectNodeIds: [],
selectParentNodes: [],
treeNodes: [],
isMenuShow: true,
}
},
props: [
'reviewId',
'redirectCharType',
'clickType'
],
mounted() {
this.getNodeTreeByReviewId()
},
activated() {
this.getNodeTreeByReviewId()
},
methods: {
refresh() {
this.selectNodeIds = [];
this.selectParentNodes = [];
this.$refs.testReviewRelevance.search();
this.getNodeTreeByReviewId();
},
nodeChange(node, nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
this.selectParentNodes = pNodes;
},
getNodeTreeByReviewId() {
if (this.reviewId) {
this.result = this.$get("/case/node/list/review/" + this.reviewId, response => {
this.treeNodes = response.data;
});
}
},
openTestReviewRelevanceDialog() {
this.$refs.testReviewRelevance.openTestReviewRelevanceDialog();
},
}
}
</script>
<style scoped>
</style>

View File

@ -313,6 +313,14 @@ export default {
if (this.reviewId) {
this.condition.reviewId = this.reviewId;
}
if (this.clickType) {
if (this.status == 'default') {
this.condition.status = this.clickType;
} else {
this.condition.status = null;
}
this.status = 'all';
}
this.condition.nodeIds = this.selectNodeIds;
if (this.reviewId) {
this.result = this.$post(this.buildPagePath('/test/review/case/list'), this.condition, response => {

View File

@ -1042,6 +1042,7 @@ export default {
test_track: "Track",
confirm: "Confirm",
cancel: "Cancel",
planning_execution: "Planning&Execution",
project: "Project",
save: "Save",
return: "Return",

View File

@ -1044,6 +1044,7 @@ export default {
},
test_track: {
test_track: "测试跟踪",
planning_execution: "规划&执行",
confirm: "确 定",
cancel: "取 消",
project: "项目",

View File

@ -1044,6 +1044,7 @@ export default {
test_track: "測試跟蹤",
confirm: "確 定",
cancel: "取 消",
planning_execution: "規劃&執行",
project: "項目",
save: "保 存",
return: "返 回",