Conflicts:
	backend/src/main/resources/i18n/messages_en_US.properties
	backend/src/main/resources/i18n/messages_zh_CN.properties
	backend/src/main/resources/i18n/messages_zh_TW.properties
This commit is contained in:
wenyann 2020-09-16 10:03:31 +08:00
commit 43bfebfcaa
80 changed files with 2269 additions and 753 deletions

View File

@ -155,8 +155,8 @@
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
@ -165,6 +165,12 @@
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_jdbc</artifactId>
<version>${jmeter.version}</version>
</dependency>
<!-- Zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>
@ -330,7 +336,7 @@
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>

View File

@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@RestController
@ -44,6 +45,11 @@ public class APITestController {
return PageUtils.setPageInfo(page, apiTestService.list(request));
}
@PostMapping("/list/ids")
public List<ApiTest> listByIds(@RequestBody QueryAPITestRequest request) {
return apiTestService.listByIds(request);
}
@GetMapping("/list/{projectId}")
public List<ApiTest> list(@PathVariable String projectId) {
return apiTestService.getApiTestByProjectId(projectId);
@ -94,6 +100,7 @@ public class APITestController {
public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiTestService.runDebug(request, file, bodyFiles);
}
@PostMapping(value = "/checkName")
public void checkName(@RequestBody SaveAPITestRequest request) {
apiTestService.checkName(request);

View File

@ -12,6 +12,7 @@ import java.util.Map;
public class QueryAPITestRequest {
private String id;
private String excludeId;
private String projectId;
private String name;
private String workspaceId;
@ -19,4 +20,5 @@ public class QueryAPITestRequest {
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
private Map<String, Object> combine;
private List<String> ids;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
@Data
public class DatabaseConfig {
private String id;
private String name;
private long poolMax;
private long timeout;
private String driver;
private String dbUrl;
private String username;
private String password;
}

View File

@ -7,6 +7,7 @@ import java.util.List;
@Data
public class Scenario {
private String id;
private String name;
private String url;
private String environmentId;
@ -15,5 +16,6 @@ public class Scenario {
private List<KeyValue> headers;
private List<Request> requests;
private DubboConfig dubboConfig;
private List<DatabaseConfig> databaseConfigs;
private Boolean enable;
}

View File

@ -23,6 +23,8 @@ public class DubboRequest implements Request {
// type 必须放最前面以便能够转换正确的类
private String type = RequestType.DUBBO;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 1)
private String name;
@JSONField(ordinal = 2)
private String protocol;

View File

@ -20,6 +20,8 @@ public class HttpRequest implements Request {
// type 必须放最前面以便能够转换正确的类
private String type = RequestType.HTTP;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 1)
private String name;
@JSONField(ordinal = 2)
private String url;

View File

@ -7,8 +7,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = HttpRequest.class, name = RequestType.HTTP),
@JsonSubTypes.Type(value = DubboRequest.class, name = RequestType.DUBBO)
@JsonSubTypes.Type(value = DubboRequest.class, name = RequestType.DUBBO),
@JsonSubTypes.Type(value = SqlRequest.class, name = RequestType.SQL)
})
@JSONType(seeAlso = {HttpRequest.class, DubboRequest.class}, typeKey = "type")
@JSONType(seeAlso = {HttpRequest.class, DubboRequest.class, SqlRequest.class}, typeKey = "type")
public interface Request {
}

View File

@ -5,4 +5,6 @@ public class RequestType {
public static final String HTTP = "HTTP";
public static final String DUBBO = "DUBBO";
public static final String SQL = "SQL";
}

View File

@ -0,0 +1,40 @@
package io.metersphere.api.dto.scenario.request;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.extract.Extract;
import io.metersphere.api.dto.scenario.processor.JSR223PostProcessor;
import io.metersphere.api.dto.scenario.processor.JSR223PreProcessor;
import lombok.Data;
@Data
@JSONType(typeName = RequestType.SQL)
public class SqlRequest implements Request {
// type 必须放最前面以便能够转换正确的类
private String type = RequestType.SQL;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 2)
private String name;
@JSONField(ordinal = 3)
private String dataSource;
@JSONField(ordinal = 4)
private String query;
@JSONField(ordinal = 5)
private long queryTimeout;
@JSONField(ordinal = 6)
private Boolean useEnvironment;
@JSONField(ordinal = 7)
private Assertions assertions;
@JSONField(ordinal = 8)
private Extract extract;
@JSONField(ordinal = 9)
private Boolean enable;
@JSONField(ordinal = 10)
private Boolean followRedirects;
@JSONField(ordinal = 11)
private JSR223PreProcessor jsr223PreProcessor;
@JSONField(ordinal = 12)
private JSR223PostProcessor jsr223PostProcessor;
}

View File

@ -83,6 +83,10 @@ public class APITestService {
return extApiTestMapper.list(request);
}
public List<ApiTest> listByIds(QueryAPITestRequest request) {
return extApiTestMapper.listByIds(request.getIds());
}
public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
if (file == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
@ -109,9 +113,6 @@ public class APITestService {
}
private void createBodyFiles(ApiTest test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
if (bodyFiles == null || bodyFiles.isEmpty()) {
}
String dir = BODY_FILE_DIR + "/" + test.getId();
File testDir = new File(dir);
if (!testDir.exists()) {
@ -120,24 +121,12 @@ public class APITestService {
for (int i = 0; i < bodyUploadIds.size(); i++) {
MultipartFile item = bodyFiles.get(i);
File file = new File(testDir + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
InputStream in = null;
OutputStream out = null;
try {
try (InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(file)) {
file.createNewFile();
in = item.getInputStream();
out = new FileOutputStream(file);
FileUtil.copyStream(in, out);
} catch (IOException e) {
LogUtil.error(e);
MSException.throwException(Translator.get("upload_fail"));
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
LogUtil.error(e);
MSException.throwException(Translator.get("upload_fail"));
}
}
}
}

View File

@ -26,5 +26,7 @@ public class Issues implements Serializable {
private String model;
private String projectName;
private static final long serialVersionUID = 1L;
}

View File

@ -7,8 +7,6 @@ import lombok.Data;
public class TestPlan implements Serializable {
private String id;
private String projectId;
private String workspaceId;
private String reportId;

View File

@ -174,76 +174,6 @@ public class TestPlanExample {
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;
}
public Criteria andProjectIdIsNotNull() {
addCriterion("project_id is not null");
return (Criteria) this;
}
public Criteria andProjectIdEqualTo(String value) {
addCriterion("project_id =", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotEqualTo(String value) {
addCriterion("project_id <>", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThan(String value) {
addCriterion("project_id >", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("project_id >=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThan(String value) {
addCriterion("project_id <", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThanOrEqualTo(String value) {
addCriterion("project_id <=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLike(String value) {
addCriterion("project_id like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotLike(String value) {
addCriterion("project_id not like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdIn(List<String> values) {
addCriterion("project_id in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotIn(List<String> values) {
addCriterion("project_id not in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdBetween(String value1, String value2) {
addCriterion("project_id between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotBetween(String value1, String value2) {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andWorkspaceIdIsNull() {
addCriterion("workspace_id is null");
return (Criteria) this;
@ -385,72 +315,72 @@ public class TestPlanExample {
}
public Criteria andNameIsNull() {
addCriterion("name is null");
addCriterion("`name` is null");
return (Criteria) this;
}
public Criteria andNameIsNotNull() {
addCriterion("name is not null");
addCriterion("`name` is not null");
return (Criteria) this;
}
public Criteria andNameEqualTo(String value) {
addCriterion("name =", value, "name");
addCriterion("`name` =", value, "name");
return (Criteria) this;
}
public Criteria andNameNotEqualTo(String value) {
addCriterion("name <>", value, "name");
addCriterion("`name` <>", value, "name");
return (Criteria) this;
}
public Criteria andNameGreaterThan(String value) {
addCriterion("name >", value, "name");
addCriterion("`name` >", value, "name");
return (Criteria) this;
}
public Criteria andNameGreaterThanOrEqualTo(String value) {
addCriterion("name >=", value, "name");
addCriterion("`name` >=", value, "name");
return (Criteria) this;
}
public Criteria andNameLessThan(String value) {
addCriterion("name <", value, "name");
addCriterion("`name` <", value, "name");
return (Criteria) this;
}
public Criteria andNameLessThanOrEqualTo(String value) {
addCriterion("name <=", value, "name");
addCriterion("`name` <=", value, "name");
return (Criteria) this;
}
public Criteria andNameLike(String value) {
addCriterion("name like", value, "name");
addCriterion("`name` like", value, "name");
return (Criteria) this;
}
public Criteria andNameNotLike(String value) {
addCriterion("name not like", value, "name");
addCriterion("`name` not like", value, "name");
return (Criteria) this;
}
public Criteria andNameIn(List<String> values) {
addCriterion("name in", values, "name");
addCriterion("`name` in", values, "name");
return (Criteria) this;
}
public Criteria andNameNotIn(List<String> values) {
addCriterion("name not in", values, "name");
addCriterion("`name` not in", values, "name");
return (Criteria) this;
}
public Criteria andNameBetween(String value1, String value2) {
addCriterion("name between", value1, value2, "name");
addCriterion("`name` between", value1, value2, "name");
return (Criteria) this;
}
public Criteria andNameNotBetween(String value1, String value2) {
addCriterion("name not between", value1, value2, "name");
addCriterion("`name` not between", value1, value2, "name");
return (Criteria) this;
}
@ -525,72 +455,72 @@ public class TestPlanExample {
}
public Criteria andStatusIsNull() {
addCriterion("status is null");
addCriterion("`status` is null");
return (Criteria) this;
}
public Criteria andStatusIsNotNull() {
addCriterion("status is not null");
addCriterion("`status` is not null");
return (Criteria) this;
}
public Criteria andStatusEqualTo(String value) {
addCriterion("status =", value, "status");
addCriterion("`status` =", value, "status");
return (Criteria) this;
}
public Criteria andStatusNotEqualTo(String value) {
addCriterion("status <>", value, "status");
addCriterion("`status` <>", value, "status");
return (Criteria) this;
}
public Criteria andStatusGreaterThan(String value) {
addCriterion("status >", value, "status");
addCriterion("`status` >", value, "status");
return (Criteria) this;
}
public Criteria andStatusGreaterThanOrEqualTo(String value) {
addCriterion("status >=", value, "status");
addCriterion("`status` >=", value, "status");
return (Criteria) this;
}
public Criteria andStatusLessThan(String value) {
addCriterion("status <", value, "status");
addCriterion("`status` <", value, "status");
return (Criteria) this;
}
public Criteria andStatusLessThanOrEqualTo(String value) {
addCriterion("status <=", value, "status");
addCriterion("`status` <=", value, "status");
return (Criteria) this;
}
public Criteria andStatusLike(String value) {
addCriterion("status like", value, "status");
addCriterion("`status` like", value, "status");
return (Criteria) this;
}
public Criteria andStatusNotLike(String value) {
addCriterion("status not like", value, "status");
addCriterion("`status` not like", value, "status");
return (Criteria) this;
}
public Criteria andStatusIn(List<String> values) {
addCriterion("status in", values, "status");
addCriterion("`status` in", values, "status");
return (Criteria) this;
}
public Criteria andStatusNotIn(List<String> values) {
addCriterion("status not in", values, "status");
addCriterion("`status` not in", values, "status");
return (Criteria) this;
}
public Criteria andStatusBetween(String value1, String value2) {
addCriterion("status between", value1, value2, "status");
addCriterion("`status` between", value1, value2, "status");
return (Criteria) this;
}
public Criteria andStatusNotBetween(String value1, String value2) {
addCriterion("status not between", value1, value2, "status");
addCriterion("`status` not between", value1, value2, "status");
return (Criteria) this;
}

View File

@ -3,7 +3,6 @@
<mapper namespace="io.metersphere.base.mapper.TestPlanMapper">
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.TestPlan">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="workspace_id" jdbcType="VARCHAR" property="workspaceId" />
<result column="report_id" jdbcType="VARCHAR" property="reportId" />
<result column="name" jdbcType="VARCHAR" property="name" />
@ -78,8 +77,8 @@
</where>
</sql>
<sql id="Base_Column_List">
id, project_id, workspace_id, report_id, name, description, status, stage, principal,
test_case_match_rule, executor_match_rule, create_time, update_time
id, workspace_id, report_id, `name`, description, `status`, stage, principal, test_case_match_rule,
executor_match_rule, create_time, update_time
</sql>
<sql id="Blob_Column_List">
tags
@ -133,16 +132,16 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.TestPlan">
insert into test_plan (id, project_id, workspace_id,
report_id, name, description,
status, stage, principal,
test_case_match_rule, executor_match_rule, create_time,
update_time, tags)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR},
#{reportId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR}, #{stage,jdbcType=VARCHAR}, #{principal,jdbcType=VARCHAR},
#{testCaseMatchRule,jdbcType=VARCHAR}, #{executorMatchRule,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{updateTime,jdbcType=BIGINT}, #{tags,jdbcType=LONGVARCHAR})
insert into test_plan (id, workspace_id, report_id,
`name`, description, `status`,
stage, principal, test_case_match_rule,
executor_match_rule, create_time, update_time,
tags)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{stage,jdbcType=VARCHAR}, #{principal,jdbcType=VARCHAR}, #{testCaseMatchRule,jdbcType=VARCHAR},
#{executorMatchRule,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{tags,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestPlan">
insert into test_plan
@ -150,9 +149,6 @@
<if test="id != null">
id,
</if>
<if test="projectId != null">
project_id,
</if>
<if test="workspaceId != null">
workspace_id,
</if>
@ -160,13 +156,13 @@
report_id,
</if>
<if test="name != null">
name,
`name`,
</if>
<if test="description != null">
description,
</if>
<if test="status != null">
status,
`status`,
</if>
<if test="stage != null">
stage,
@ -194,9 +190,6 @@
<if test="id != null">
#{id,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="workspaceId != null">
#{workspaceId,jdbcType=VARCHAR},
</if>
@ -247,9 +240,6 @@
<if test="record.id != null">
id = #{record.id,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.workspaceId != null">
workspace_id = #{record.workspaceId,jdbcType=VARCHAR},
</if>
@ -257,13 +247,13 @@
report_id = #{record.reportId,jdbcType=VARCHAR},
</if>
<if test="record.name != null">
name = #{record.name,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
</if>
<if test="record.description != null">
description = #{record.description,jdbcType=VARCHAR},
</if>
<if test="record.status != null">
status = #{record.status,jdbcType=VARCHAR},
`status` = #{record.status,jdbcType=VARCHAR},
</if>
<if test="record.stage != null">
stage = #{record.stage,jdbcType=VARCHAR},
@ -294,12 +284,11 @@
<update id="updateByExampleWithBLOBs" parameterType="map">
update test_plan
set id = #{record.id,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
workspace_id = #{record.workspaceId,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
description = #{record.description,jdbcType=VARCHAR},
status = #{record.status,jdbcType=VARCHAR},
`status` = #{record.status,jdbcType=VARCHAR},
stage = #{record.stage,jdbcType=VARCHAR},
principal = #{record.principal,jdbcType=VARCHAR},
test_case_match_rule = #{record.testCaseMatchRule,jdbcType=VARCHAR},
@ -314,12 +303,11 @@
<update id="updateByExample" parameterType="map">
update test_plan
set id = #{record.id,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
workspace_id = #{record.workspaceId,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
description = #{record.description,jdbcType=VARCHAR},
status = #{record.status,jdbcType=VARCHAR},
`status` = #{record.status,jdbcType=VARCHAR},
stage = #{record.stage,jdbcType=VARCHAR},
principal = #{record.principal,jdbcType=VARCHAR},
test_case_match_rule = #{record.testCaseMatchRule,jdbcType=VARCHAR},
@ -333,9 +321,6 @@
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.TestPlan">
update test_plan
<set>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="workspaceId != null">
workspace_id = #{workspaceId,jdbcType=VARCHAR},
</if>
@ -343,13 +328,13 @@
report_id = #{reportId,jdbcType=VARCHAR},
</if>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
</if>
<if test="description != null">
description = #{description,jdbcType=VARCHAR},
</if>
<if test="status != null">
status = #{status,jdbcType=VARCHAR},
`status` = #{status,jdbcType=VARCHAR},
</if>
<if test="stage != null">
stage = #{stage,jdbcType=VARCHAR},
@ -377,12 +362,11 @@
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.TestPlan">
update test_plan
set project_id = #{projectId,jdbcType=VARCHAR},
workspace_id = #{workspaceId,jdbcType=VARCHAR},
set workspace_id = #{workspaceId,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
description = #{description,jdbcType=VARCHAR},
status = #{status,jdbcType=VARCHAR},
`status` = #{status,jdbcType=VARCHAR},
stage = #{stage,jdbcType=VARCHAR},
principal = #{principal,jdbcType=VARCHAR},
test_case_match_rule = #{testCaseMatchRule,jdbcType=VARCHAR},
@ -394,12 +378,11 @@
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.TestPlan">
update test_plan
set project_id = #{projectId,jdbcType=VARCHAR},
workspace_id = #{workspaceId,jdbcType=VARCHAR},
set workspace_id = #{workspaceId,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
description = #{description,jdbcType=VARCHAR},
status = #{status,jdbcType=VARCHAR},
`status` = #{status,jdbcType=VARCHAR},
stage = #{stage,jdbcType=VARCHAR},
principal = #{principal,jdbcType=VARCHAR},
test_case_match_rule = #{testCaseMatchRule,jdbcType=VARCHAR},

View File

@ -11,4 +11,6 @@ public interface ExtApiTestMapper {
List<APITestResult> list(@Param("request") QueryAPITestRequest request);
List<ApiTest> getApiTestByProjectId(String projectId);
List<ApiTest> listByIds(@Param("ids") List<String> ids);
}

View File

@ -105,6 +105,9 @@
</include>
</if>
<if test="request.excludeId != null">
and api_test.id != #{request.excludeId}
</if>
<if test="request.name != null">
and api_test.name like CONCAT('%', #{request.name},'%')
</if>
@ -143,4 +146,17 @@
where project_id = #{projectId}
</select>
<select id="listByIds" resultType="io.metersphere.base.domain.ApiTest">
select *
from api_test
<where>
<if test="ids != null and ids.size() > 0">
id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
</where>
</select>
</mapper>

View File

@ -97,9 +97,7 @@
<select id="list" resultMap="BaseResultMap"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
select test_plan.*, project.name as project_name
from test_plan
left join project on test_plan.project_id = project.id
select test_plan.* from test_plan
<where>
<if test="request.combine != null">
<include refid="combine">
@ -111,10 +109,7 @@
and test_plan.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.workspaceId != null">
AND project.workspace_id = #{request.workspaceId}
</if>
<if test="request.projectId != null">
AND project.id = #{request.projectId}
AND test_plan.workspace_id = #{request.workspaceId}
</if>
<if test="request.id != null">
AND test_plan.id = #{request.id}
@ -150,9 +145,7 @@
</select>
<select id="listRelate" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric">
select test_plan.*, project.name as project_name
from test_plan
left join project on test_plan.project_id = project.id
select test_plan.* from test_plan
where test_plan.workspace_id = #{request.workspaceId}
and (test_plan.principal = #{request.principal}
<if test="request.planIds != null and request.planIds.size() > 0">

View File

@ -115,10 +115,11 @@
</select>
<select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_case.remark, test_plan_test_case.id as id, test_plan_test_case.*,test_case.*,test_case_node.name as model
select test_case.remark, test_plan_test_case.id as id, test_plan_test_case.*,test_case.*,test_case_node.name as model, project.name as projectName
from test_plan_test_case
inner join test_case on test_plan_test_case.case_id = test_case.id left join test_case_node on
test_case_node.id=test_case.node_id
inner join test_case on test_plan_test_case.case_id = test_case.id
left join test_case_node on test_case_node.id=test_case.node_id
inner join project on project.id = test_case.project_id
<where>
<if test="request.combine != null">
<include refid="combine">

View File

@ -39,18 +39,6 @@ public class ScheduleManager {
.startNow().build();
scheduler.scheduleJob(jd, trigger);
try {
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (SchedulerException e) {
LogUtil.error(e.getMessage(), e);
e.printStackTrace();
throw new RuntimeException(e);
}
}
public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime) throws SchedulerException {
@ -89,9 +77,6 @@ public class ScheduleManager {
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
throw new RuntimeException(e);

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.LoginRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.service.SystemParameterService;
@ -47,6 +48,7 @@ public class LdapService {
// 执行登录认证
authenticate(String.valueOf(dirContextOperations.getDn()), credentials);
} catch (AuthenticationException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("authentication_failed"));
}
@ -93,8 +95,10 @@ public class LdapService {
return result.get(0);
}
} catch (NameNotFoundException | InvalidNameException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("login_fail_ou_error"));
} catch (InvalidSearchFilterException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("login_fail_filter_error"));
}
}
@ -161,8 +165,10 @@ public class LdapService {
try {
authenticate(dn, credentials, ldapTemplate);
} catch (AuthenticationException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("ldap_connect_fail_user"));
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("ldap_connect_fail"));
}

View File

@ -19,7 +19,7 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
System.out.println("================= 应用启动 =================");
try {
Thread.sleep(5 * 60 * 1000);
Thread.sleep(3 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

View File

@ -6,7 +6,7 @@ import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.ResultHolder;
import io.metersphere.dto.NodeDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.engine.AbstractEngine;
@ -87,12 +87,14 @@ public class DockerTestEngine extends AbstractEngine {
testRequest.setTestData(context.getTestData());
testRequest.setEnv(context.getEnv());
try {
restTemplate.postForObject(uri, testRequest, String.class);
} catch (Exception e) {
LogUtil.error(e);
ResultHolder result = restTemplate.postForObject(uri, testRequest, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("start_engine_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
}
}
@Override
@ -105,12 +107,13 @@ public class DockerTestEngine extends AbstractEngine {
Integer port = node.getPort();
String uri = String.format(BASE_URL + "/jmeter/container/stop/" + testId, ip, port);
try {
restTemplateWithTimeOut.getForObject(uri, String.class);
} catch (Exception e) {
LogUtil.error("stop load test fail... " + testId, e);
ResultHolder result = restTemplateWithTimeOut.getForObject(uri, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("container_delete_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
}
});
}
}

View File

@ -16,13 +16,14 @@ import io.metersphere.controller.request.ProjectRequest;
import io.metersphere.dto.ProjectDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.DeleteTestPlanRequest;
import io.metersphere.track.service.TestCaseService;
import io.metersphere.track.service.TestPlanProjectService;
import io.metersphere.track.service.TestPlanService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
@ -54,6 +55,8 @@ public class ProjectService {
private TestCaseService testCaseService;
@Resource
private APITestService apiTestService;
@Resource
private TestPlanProjectService testPlanProjectService;
public Project addProject(Project project) {
if (StringUtils.isBlank(project.getName())) {
@ -96,21 +99,22 @@ public class ProjectService {
performanceTestService.delete(deleteTestPlanRequest);
});
// TODO 删除项目下 测试跟踪 相关
// 删除项目下 测试跟踪 相关
deleteTrackResourceByProjectId(projectId);
// TODO 删除项目下 接口测试 相关
// 删除项目下 接口测试 相关
deleteAPIResourceByProjectId(projectId);
// delete project
projectMapper.deleteByPrimaryKey(projectId);
}
private void deleteTrackResourceByProjectId(String projectId) {
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setProjectId(projectId);
testPlanService.listTestPlan(request).forEach(testPlan -> {
testPlanService.deleteTestPlan(testPlan.getId());
List<String> testPlanIds = testPlanProjectService.getPlanIdByProjectId(projectId);
if (!CollectionUtils.isEmpty(testPlanIds)) {
testPlanIds.forEach(testPlanId -> {
testPlanService.deleteTestPlan(testPlanId);
});
}
testCaseService.deleteTestCaseByProjectId(projectId);
}

View File

@ -1,6 +1,7 @@
package io.metersphere.track.controller;
import io.metersphere.base.domain.Issues;
import io.metersphere.track.domain.TapdUser;
import io.metersphere.track.service.IssuesService;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.springframework.web.bind.annotation.*;
@ -35,4 +36,9 @@ public class TestCaseIssuesController {
issuesService.closeLocalIssue(id);
}
@GetMapping("/tapd/user/{caseId}")
public List<TapdUser> getTapdUsers(@PathVariable String caseId) {
return issuesService.getTapdProjectUsers(caseId);
}
}

View File

@ -4,6 +4,7 @@ import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.track.request.testcase.DragNodeRequest;
import io.metersphere.track.request.testcase.QueryNodeRequest;
import io.metersphere.track.service.TestCaseNodeService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -26,9 +27,9 @@ public class TestCaseNodeController {
}
/*模块列表列表*/
@GetMapping("/list/all/plan/{planId}")
public List<TestCaseNodeDTO> getAllNodeByPlanId(@PathVariable String planId) {
return testCaseNodeService.getAllNodeByPlanId(planId);
@PostMapping("/list/all/plan")
public List<TestCaseNodeDTO> getAllNodeByPlanId(@RequestBody QueryNodeRequest request) {
return testCaseNodeService.getAllNodeByPlanId(request);
}
@GetMapping("/list/plan/{planId}")

View File

@ -2,6 +2,7 @@ package io.metersphere.track.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.TestPlan;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
@ -12,6 +13,9 @@ import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest;
import io.metersphere.track.request.testplancase.TestCaseRelevanceRequest;
import io.metersphere.track.service.TestPlanProjectService;
import io.metersphere.track.service.TestPlanService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -26,6 +30,8 @@ public class TestPlanController {
@Resource
TestPlanService testPlanService;
@Resource
TestPlanProjectService testPlanProjectService;
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<TestPlanDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
@ -37,10 +43,10 @@ public class TestPlanController {
/*jenkins测试计划*/
@GetMapping("/list/all/{projectId}/{workspaceId}")
public List<TestPlanDTO> listByprojectId(@PathVariable String projectId, @PathVariable String workspaceId) {
public List<TestPlanDTO> listByProjectId(@PathVariable String projectId, @PathVariable String workspaceId) {
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setWorkspaceId(workspaceId);
request.setProjectId(projectId);
// request.setProjectId(projectId);
return testPlanService.listTestPlan(request);
}
@ -69,7 +75,7 @@ public class TestPlanController {
@PostMapping("/add")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void addTestPlan(@RequestBody TestPlan testPlan) {
public void addTestPlan(@RequestBody AddTestPlanRequest testPlan) {
testPlanService.addTestPlan(testPlan);
}
@ -100,4 +106,24 @@ public class TestPlanController {
public TestCaseReportMetricDTO getMetric(@PathVariable String planId) {
return testPlanService.getMetric(planId);
}
@GetMapping("/project/name/{planId}")
public String getProjectNameByPlanId(@PathVariable String planId) {
return testPlanService.getProjectNameByPlanId(planId);
}
@PostMapping("/project")
public List<Project> getProjectByPlanId(@RequestBody TestCaseRelevanceRequest request) {
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(request.getPlanId());
request.setProjectIds(projectIds);
return testPlanProjectService.getProjectByPlanId(request);
}
@PostMapping("/project/{goPage}/{pageSize}")
public Pager<List<Project>> getProjectByPlanId(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody TestCaseRelevanceRequest request) {
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(request.getPlanId());
request.setProjectIds(projectIds);
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testPlanProjectService.getProjectByPlanId(request));
}
}

View File

@ -1,7 +1,6 @@
package io.metersphere.track.domain;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.base.domain.TestCaseNodeExample;
import io.metersphere.base.mapper.TestCaseNodeMapper;
@ -11,6 +10,7 @@ import io.metersphere.commons.utils.MathUtils;
import io.metersphere.track.dto.*;
import io.metersphere.track.service.IssuesService;
import io.metersphere.track.service.TestCaseNodeService;
import io.metersphere.track.service.TestPlanProjectService;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
@ -30,8 +30,9 @@ public class ReportResultComponent extends ReportComponent {
public void init() {
TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService");
TestCaseNodeMapper testCaseNodeMapper = (TestCaseNodeMapper) CommonBeanFactory.getBean("testCaseNodeMapper");
TestPlanProjectService testPlanProjectService = (TestPlanProjectService) CommonBeanFactory.getBean("testPlanProjectService");
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
testCaseNodeExample.createCriteria().andProjectIdIn(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId()));
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
nodeTrees = testCaseNodeService.getNodeTrees(nodes);
nodeTrees.forEach(item -> {
@ -52,6 +53,9 @@ public class ReportResultComponent extends ReportComponent {
nodeTrees.forEach(rootNode -> {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId());
if (moduleResult != null) {
TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService");
Project project = testCaseNodeService.getProjectByNode(rootNode.getId());
moduleResult.setProjectName(project.getName());
moduleResult.setModuleName(rootNode.getName());
}
});

View File

@ -0,0 +1,12 @@
package io.metersphere.track.domain;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class TapdUser implements Serializable {
private List<String> roleId;
private String name;
private String user;
}

View File

@ -20,4 +20,5 @@ public class TestCaseReportModuleResultDTO {
private Integer failureCount;
private Integer blockingCount;
private Integer underwayCount;
private String projectName;
}

View File

@ -17,4 +17,5 @@ public class TestPlanCaseDTO extends TestCaseWithBLOBs {
private String issues;
private String reportId;
private String model;
private String projectName;
}

View File

@ -3,6 +3,8 @@ package io.metersphere.track.request.testcase;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class IssuesRequest {
@ -10,4 +12,5 @@ public class IssuesRequest {
private String content;
private String projectId;
private String testCaseId;
private List<String> tapdUsers;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.track.request.testcase;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class QueryNodeRequest {
private String testPlanId;
private String projectId;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.track.request.testplan;
import io.metersphere.base.domain.TestPlan;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class AddTestPlanRequest extends TestPlan {
private List<String> projectIds;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.track.request.testplancase;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestCaseRelevanceRequest {
private String planId;
private String name;
private List<String> projectIds;
}

View File

@ -1,6 +1,7 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.IssuesMapper;
@ -17,6 +18,7 @@ import io.metersphere.controller.ResultHolder;
import io.metersphere.controller.request.IntegrationRequest;
import io.metersphere.service.IntegrationService;
import io.metersphere.service.ProjectService;
import io.metersphere.track.domain.TapdUser;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@ -188,10 +190,14 @@ public class IssuesService {
MSException.throwException("未关联Tapd 项目ID");
}
List<String> tapdUsers = issuesRequest.getTapdUsers();
String usersStr = String.join(";", tapdUsers);
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("title", issuesRequest.getTitle());
paramMap.add("workspace_id", tapdId);
paramMap.add("description", issuesRequest.getContent());
paramMap.add("current_owner", usersStr);
ResultHolder result = call(url, HttpMethod.POST, paramMap);
@ -264,7 +270,7 @@ public class IssuesService {
String result = addJiraIssue(url, auth, json);
JSONObject jsonObject = JSON.parseObject(result);
String id = jsonObject.getString("id");
String id = jsonObject.getString("key");
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
@ -346,12 +352,21 @@ public class IssuesService {
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
LogUtil.info(obj);
String lastmodify = "";
String status = "";
JSONObject fields = (JSONObject) obj.get("fields");
JSONObject statusObj = (JSONObject) fields.get("status");
JSONObject assignee = (JSONObject) fields.get("assignee");
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
String id = obj.getString("id");
if (statusObj != null) {
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
status = statusCategory.getString("key");
}
String id = obj.getString("key");
String title = fields.getString("summary");
String description = fields.getString("description");
@ -360,9 +375,8 @@ public class IssuesService {
HtmlRenderer renderer = HtmlRenderer.builder().build();
description = renderer.render(document);
String status = statusCategory.getString("key");
Long createTime = fields.getLong("created");
String lastmodify = "";
if (assignee != null) {
lastmodify = assignee.getString("displayName");
}
@ -527,4 +541,19 @@ public class IssuesService {
issuesMapper.updateByPrimaryKeySelective(issues);
}
public List<TapdUser> getTapdProjectUsers(String caseId) {
List<TapdUser> users = new ArrayList<>();
String projectId = getTapdProjectId(caseId);
String url = "https://api.tapd.cn/workspaces/users?workspace_id=" + projectId;
ResultHolder call = call(url);
String listJson = JSON.toJSONString(call.getData());
JSONArray jsonArray = JSON.parseArray(listJson);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject o = jsonArray.getJSONObject(i);
TapdUser user = o.getObject("UserWorkspace", TapdUser.class);
users.add(user);
}
return users;
}
}

View File

@ -2,10 +2,7 @@ package io.metersphere.track.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.exception.MSException;
@ -15,6 +12,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.track.request.testcase.DragNodeRequest;
import io.metersphere.track.request.testcase.QueryNodeRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
@ -43,6 +41,10 @@ public class TestCaseNodeService {
ExtTestCaseMapper extTestCaseMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
TestPlanProjectService testPlanProjectService;
@Resource
ProjectMapper projectMapper;
public String addNode(TestCaseNode node) {
validateNode(node);
@ -182,8 +184,22 @@ public class TestCaseNodeService {
*/
public List<TestCaseNodeDTO> getNodeByPlanId(String planId) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
List<TestCaseNodeDTO> list = new ArrayList<>();
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(planId);
projectIds.forEach(id -> {
String name = projectMapper.selectByPrimaryKey(id).getName();
List<TestCaseNodeDTO> nodeList = getNodeDTO(id, planId);
TestCaseNodeDTO testCaseNodeDTO = new TestCaseNodeDTO();
testCaseNodeDTO.setName(name);
testCaseNodeDTO.setLabel(name);
testCaseNodeDTO.setChildren(nodeList);
list.add(testCaseNodeDTO);
});
return list;
}
private List<TestCaseNodeDTO> getNodeDTO(String projectId, String planId) {
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId);
List<TestPlanTestCase> testPlanTestCases = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample);
@ -193,7 +209,7 @@ public class TestCaseNodeService {
}
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<String> caseIds = testPlanTestCases.stream()
@ -254,12 +270,15 @@ public class TestCaseNodeService {
return false;
}
public List<TestCaseNodeDTO> getAllNodeByPlanId(String planId) {
public List<TestCaseNodeDTO> getAllNodeByPlanId(QueryNodeRequest request) {
String planId = request.getTestPlanId();
String projectId = request.getProjectId();
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
if (testPlan == null) {
return Collections.emptyList();
}
return getNodeTreeByProjectId(testPlan.getProjectId());
return getNodeTreeByProjectId(projectId);
}
public Map<String, String> createNodeByTestCases(List<TestCaseWithBLOBs> testCases, String projectId) {
@ -479,4 +498,12 @@ public class TestCaseNodeService {
}
}
public Project getProjectByNode(String nodeId) {
TestCaseNodeExample example = new TestCaseNodeExample();
example.createCriteria().andIdEqualTo(nodeId);
List<TestCaseNode> testCaseNodes = testCaseNodeMapper.selectByExample(example);
String projectId = testCaseNodes.get(0).getProjectId();
return projectMapper.selectByPrimaryKey(projectId);
}
}

View File

@ -182,9 +182,7 @@ public class TestCaseService {
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
if (StringUtils.isNotBlank(request.getPlanId())) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getPlanId());
if (testPlan != null) {
request.setProjectId(testPlan.getProjectId());
}
// request 传入要查询的 projectId 切换的项目ID
}
List<TestCase> testCaseNames = extTestCaseMapper.getTestCaseNames(request);
@ -386,7 +384,13 @@ public class TestCaseService {
data.setPrerequisite(t.getPrerequisite());
if (t.getMethod().equals("manual")) {
String steps = t.getSteps();
JSONArray jsonArray = JSON.parseArray(steps);
String setp = "";
if (steps.contains("null")) {
setp = steps.replace("null", "");
} else {
setp = steps;
}
JSONArray jsonArray = JSON.parseArray(setp);
for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1;
step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\n");

View File

@ -0,0 +1,71 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.ProjectExample;
import io.metersphere.base.domain.TestPlanProject;
import io.metersphere.base.domain.TestPlanProjectExample;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.TestPlanProjectMapper;
import io.metersphere.track.request.testplancase.TestCaseRelevanceRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanProjectService {
@Resource
TestPlanProjectMapper testPlanProjectMapper;
@Resource
ProjectMapper projectMapper;
public List<String> getProjectIdsByPlanId(String planId) {
TestPlanProjectExample example = new TestPlanProjectExample();
example.createCriteria().andTestPlanIdEqualTo(planId);
List<String> projectIds = testPlanProjectMapper.selectByExample(example)
.stream()
.map(TestPlanProject::getProjectId)
.collect(Collectors.toList());
if (projectIds.isEmpty()) {
return null;
}
return projectIds;
}
public List<Project> getProjectByPlanId(TestCaseRelevanceRequest request) {
ProjectExample projectExample = new ProjectExample();
ProjectExample.Criteria criteria = projectExample.createCriteria();
criteria.andIdIn(request.getProjectIds());
if (StringUtils.isNotBlank(request.getName())) {
criteria.andNameLike(StringUtils.wrapIfMissing(request.getName(), "%"));
}
return projectMapper.selectByExample(projectExample);
}
public void deleteTestPlanProjectByPlanId(String planId) {
TestPlanProjectExample testPlanProjectExample = new TestPlanProjectExample();
testPlanProjectExample.createCriteria().andTestPlanIdEqualTo(planId);
testPlanProjectMapper.deleteByExample(testPlanProjectExample);
}
public List<String> getPlanIdByProjectId(String projectId) {
TestPlanProjectExample testPlanProjectExample = new TestPlanProjectExample();
testPlanProjectExample.createCriteria().andProjectIdEqualTo(projectId);
List<TestPlanProject> testPlanProjects = testPlanProjectMapper.selectByExample(testPlanProjectExample);
if (CollectionUtils.isEmpty(testPlanProjects)) {
return null;
}
return testPlanProjects
.stream()
.map(TestPlanProject::getTestPlanId)
.collect(Collectors.toList());
}
}

View File

@ -4,10 +4,7 @@ package io.metersphere.track.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseReportMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
@ -28,6 +25,7 @@ import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
@ -72,12 +70,29 @@ public class TestPlanService {
@Resource
TestCaseReportMapper testCaseReportMapper;
public void addTestPlan(TestPlan testPlan) {
@Resource
TestPlanProjectMapper testPlanProjectMapper;
@Resource
TestPlanProjectService testPlanProjectService;
@Resource
ProjectMapper projectMapper;
public void addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
MSException.throwException(Translator.get("plan_name_already_exists"));
}
;
testPlan.setId(UUID.randomUUID().toString());
String testPlanId = UUID.randomUUID().toString();
List<String> projectIds = testPlan.getProjectIds();
projectIds.forEach(id -> {
TestPlanProject testPlanProject = new TestPlanProject();
testPlanProject.setProjectId(id);
testPlanProject.setTestPlanId(testPlanId);
testPlanProjectMapper.insertSelective(testPlanProject);
});
testPlan.setId(testPlanId);
testPlan.setStatus(TestPlanStatus.Prepare.name());
testPlan.setCreateTime(System.currentTimeMillis());
testPlan.setUpdateTime(System.currentTimeMillis());
@ -116,6 +131,7 @@ public class TestPlanService {
public int deleteTestPlan(String planId) {
deleteTestCaseByPlanId(planId);
testPlanProjectService.deleteTestPlanProjectByPlanId(planId);
return testPlanMapper.deleteByPrimaryKey(planId);
}
@ -257,6 +273,9 @@ public class TestPlanService {
queryTestPlanRequest.setId(planId);
TestPlanDTO testPlan = extTestPlanMapper.list(queryTestPlanRequest).get(0);
String projectName = getProjectNameByPlanId(planId);
testPlan.setProjectName(projectName);
TestCaseReport testCaseReport = testCaseReportMapper.selectByPrimaryKey(testPlan.getReportId());
JSONObject content = JSONObject.parseObject(testCaseReport.getContent());
JSONArray componentIds = content.getJSONArray("components");
@ -270,6 +289,7 @@ public class TestPlanService {
if (issue.size() > 0) {
for (Issues i : issue) {
i.setModel(testCase.getNodePath());
i.setProjectName(testCase.getProjectName());
String des = i.getDescription().replaceAll("<p>", "").replaceAll("</p>", "");
i.setDescription(des);
if (i.getLastmodify() == null || i.getLastmodify() == "") {
@ -316,4 +336,23 @@ public class TestPlanService {
testPlan.setStatus(TestPlanStatus.Completed.name());
testPlanMapper.updateByPrimaryKeySelective(testPlan);
}
public String getProjectNameByPlanId(String testPlanId) {
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(testPlanId);
ProjectExample projectExample = new ProjectExample();
projectExample.createCriteria().andIdIn(projectIds);
List<Project> projects = projectMapper.selectByExample(projectExample);
StringBuilder stringBuilder = new StringBuilder();
String projectName = "";
if (projects.size() > 0) {
for (Project project : projects) {
stringBuilder.append(project.getName()).append("");
}
projectName = stringBuilder.toString().substring(0, stringBuilder.length() - 1);
}
return projectName;
}
}

@ -1 +1 @@
Subproject commit b86032cbbda9a9e6028308aa95a887cff2192f1c
Subproject commit d5b4969642fd8d10cc2f949d7377e0a0e5217a3a

View File

@ -0,0 +1,7 @@
CREATE TABLE `license` (
`id` varchar(50) NOT NULL COMMENT 'ID',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
`license_code` longtext DEFAULT NULL COMMENT 'license_code',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@ -0,0 +1 @@
alter table test_plan drop column project_id;

View File

@ -151,4 +151,8 @@ quota_max_threads_excess_workspace=最大並發數超過工作空間限額
quota_max_threads_excess_organization=最大並發數超過組織限額
quota_duration_excess_workspace=壓測時長超過工作空間限額
quota_duration_excess_organization=壓測時長超過組織限額
license_valid_license_error=授權驗證失敗
license_valid_license_code=授權碼已經存在
email_subject=MeterSphere定時任務結果通知

View File

@ -57,7 +57,8 @@
</el-row>
</el-header>
<ms-api-scenario-config :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly"
:scenarios="test.scenarioDefinition" :project-id="test.projectId" ref="config"/>
:test-id="test.id" :scenarios="test.scenarioDefinition" :project-id="test.projectId"
ref="config"/>
</el-container>
</el-card>
</ms-main-container>
@ -66,13 +67,12 @@
<script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Test} from "./model/ScenarioModel"
import {Test, Scenario} from "./model/ScenarioModel"
import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils";
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import ApiImport from "./components/import/ApiImport";
import {getUUID} from "../../../../common/js/utils";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
@ -134,6 +134,39 @@ export default {
if (projectId) this.test.projectId = projectId;
})
},
updateReference() {
let updateIds = [];
this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) {
updateIds.push(scenario.id.split("#")[0]);
}
})
if (updateIds.length === 0) return;
//
this.result = this.$post("/api/list/ids", {ids: updateIds}, response => {
let scenarioMap = {};
if (response.data) {
response.data.forEach(test => {
JSON.parse(test.scenarioDefinition).forEach(options => {
let referenceId = test.id + "#" + options.id;
scenarioMap[referenceId] = new Scenario(options);
scenarioMap[referenceId].id = referenceId;
})
})
}
let scenarios = [];
this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) {
if (scenarioMap[scenario.id]) scenarios.push(scenarioMap[scenario.id]);
} else {
scenarios.push(scenario);
}
})
this.test.scenarioDefinition = scenarios;
})
},
getTest(id) {
this.result = this.$get("/api/get/" + id, response => {
if (response.data) {
@ -147,6 +180,8 @@ export default {
scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: item.schedule ? item.schedule : {},
});
this.updateReference();
this.$refs.config.reset();
}
});
@ -163,7 +198,7 @@ export default {
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.result = this.$fileUpload(url, file, bodyFiles, this.test, response => {
this.result = this.$fileUpload(url, file, bodyFiles, this.test, () => {
if (callback) callback();
this.create = false;
this.resetBodyFile();

View File

@ -1,7 +1,7 @@
<template>
<div>
<el-card class="table-card">
<el-table :data="hostTable" style="width: 100%" @cell-dblclick="dblHostTable">
<el-table :data="hostTable" style="width: 100%" @cell-dblclick="dblHostTable" class="ht-tb">
<el-table-column prop="ip" label="IP">
<template slot-scope="scope">
<el-input v-if="scope.row.status" v-model="scope.row.ip"></el-input>
@ -23,7 +23,7 @@
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')">
<el-table-column :label="$t('commons.operating')" width="100">
<template v-slot:default="scope">
<span>
<el-button size="mini" p="$t('commons.remove')" icon="el-icon-close" circle @click="remove(scope.row)"
@ -36,8 +36,9 @@
</el-table-column>
</el-table>
<el-button size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" circle @click="add"
class="ht-btn-add"></el-button>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add"
>添加
</el-button>
</el-card>
@ -94,6 +95,10 @@
confirm: function (row) {
this.validateIp(row.ip) && this.validateDomain(row.domain) ? row.status = '' : row.status;
this.$emit('change', this.hostTable);
if (row.status === "") {
return true;
}
return false;
},
init: function () {
if (this.hostTable === undefined || this.hostTable.length === 0) {
@ -118,6 +123,9 @@
validateDomain(domain) {
let url = {};
try {
if (!domain.startsWith("http") || !domain.startsWith("https")) {
domain += "http://";
}
url = new URL(domain);
} catch (e) {
this.$warning(this.$t('load_test.input_domain'));
@ -138,22 +146,24 @@
<style scoped>
.ht-btn-remove {
font-size: 8px;
color: white;
background-color: #DCDFE6;
}
.ht-btn-confirm {
font-size: 8px;
color: white;
background-color: #1483F6;
}
.ht-btn-add {
border: 0px;
margin-top: 10px;
font-size: 8px;
color: #1483F6;
border-color: #1483F6;
background-color: white;
}
.ht-tb {
background-color: white;
border: 0px;
}
</style>

View File

@ -2,12 +2,16 @@
<el-container>
<el-aside class="scenario-aside">
<div class="scenario-list">
<ms-api-collapse v-model="activeName" @change="handleChange" accordion>
<ms-api-collapse v-model="activeName" @change="handleChange">
<draggable :list="scenarios" group="Scenario" class="scenario-draggable" ghost-class="scenario-ghost">
<ms-api-collapse-item v-for="(scenario, index) in scenarios" :key="index"
:title="scenario.name" :name="index" :class="{'disable-scenario': !scenario.enable}">
<template slot="title">
<div class="scenario-name">
<el-tag type="info" size="small" v-if="scenario.isReference()">{{
$t('api_test.scenario.reference')
}}
</el-tag>
{{ scenario.name }}
<span id="hint" v-if="!scenario.name">
{{ $t('api_test.scenario.config') }}
@ -22,30 +26,42 @@
<el-dropdown-item :disabled="isReadOnly" :command="{type:'delete', index:index}">
{{ $t('api_test.scenario.delete') }}
</el-dropdown-item>
<el-dropdown-item v-if="scenario.enable" :disabled="isReadOnly" :command="{type:'disable', index:index}">
<el-dropdown-item v-if="scenario.enable" :disabled="isReadOnly"
:command="{type:'disable', index:index}">
{{ $t('api_test.scenario.disable') }}
</el-dropdown-item>
<el-dropdown-item v-if="!scenario.enable" :disabled="isReadOnly" :command="{type:'enable', index:index}">
<el-dropdown-item v-if="!scenario.enable" :disabled="isReadOnly"
:command="{type:'enable', index:index}">
{{ $t('api_test.scenario.enable') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<ms-api-request-config :is-read-only="isReadOnly" :scenario="scenario" @select="select"/>
<ms-api-request-config :is-read-only="disable" :scenario="scenario" @select="select"/>
</ms-api-collapse-item>
</draggable>
</ms-api-collapse>
</div>
<el-button :disabled="isReadOnly" class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createScenario"/>
<el-popover placement="top" v-model="visible">
<el-radio-group v-model="type" @change="createScenario">
<el-radio :label="types.CREATE">{{ $t('api_test.scenario.create_scenario') }}</el-radio>
<el-radio :label="types.SELECT">{{ $t('api_test.scenario.select_scenario') }}</el-radio>
</el-radio-group>
<el-button slot="reference" :disabled="isReadOnly" class="scenario-create" type="primary" size="mini"
icon="el-icon-plus" plain/>
</el-popover>
</el-aside>
<el-main class="scenario-main">
<div class="scenario-form">
<ms-api-scenario-form :is-read-only="isReadOnly" :scenario="selected" :project-id="projectId" v-if="isScenario"/>
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly"
<ms-api-scenario-form :is-read-only="disable" :scenario="selected" :project-id="projectId"
v-if="isScenario"/>
<ms-api-request-form :debug-report-id="debugReportId" @runDebug="runDebug"
:is-read-only="disable"
:request="selected" :scenario="currentScenario" v-if="isRequest"/>
</div>
</el-main>
<ms-api-scenario-select :exclude-id="testId" @select="selectScenario" ref="selectDialog"/>
</el-container>
</template>
@ -58,11 +74,13 @@ import MsApiRequestForm from "./request/ApiRequestForm";
import MsApiScenarioForm from "./ApiScenarioForm";
import {Request, Scenario} from "../model/ScenarioModel";
import draggable from 'vuedraggable';
import MsApiScenarioSelect from "@/business/components/api/test/components/ApiScenarioSelect";
export default {
name: "MsApiScenarioConfig",
components: {
MsApiScenarioSelect,
MsApiRequestConfig,
MsApiScenarioForm,
MsApiRequestForm,
@ -72,17 +90,21 @@ export default {
},
props: {
testId: String,
scenarios: Array,
projectId: String,
isReadOnly: {
type: Boolean,
default: false
},
debugReportId: String
debugReportId: String,
},
data() {
return {
visible: false,
types: {CREATE: "create", SELECT: "select"},
type: "",
activeName: 0,
selected: [Scenario, Request],
currentScenario: {}
@ -100,30 +122,49 @@ export default {
},
methods: {
createScenario: function () {
notContainsScenario(item) {
for (let scenario of this.scenarios) {
if (item.id === scenario.id) {
return false;
}
}
return true;
},
selectScenario(selection) {
selection.filter(this.notContainsScenario).forEach(item => {
this.scenarios.push(item);
})
},
createScenario() {
if (this.type === this.types.CREATE) {
this.scenarios.push(new Scenario());
} else {
this.$refs.selectDialog.open();
}
this.visible = false;
this.type = "";
},
copyScenario: function (index) {
copyScenario(index) {
let scenario = this.scenarios[index];
this.scenarios.push(new Scenario(scenario));
this.scenarios.push(scenario.clone());
},
deleteScenario: function (index) {
deleteScenario(index) {
this.scenarios.splice(index, 1);
if (this.scenarios.length === 0) {
this.createScenario();
this.select(this.scenarios[0]);
}
},
disableScenario: function (index) {
disableScenario(index) {
this.scenarios[index].enable = false;
},
enableScenario: function (index) {
enableScenario(index) {
this.scenarios[index].enable = true;
},
handleChange: function (index) {
handleChange(index) {
this.select(this.scenarios[index]);
},
handleCommand: function (command) {
handleCommand(command) {
switch (command.type) {
case "copy":
this.copyScenario(command.index);
@ -139,7 +180,7 @@ export default {
break;
}
},
select: function (obj, scenario) {
select(obj, scenario) {
this.selected = null;
this.$nextTick(function () {
if (obj instanceof Scenario) {
@ -150,13 +191,13 @@ export default {
this.selected = obj;
});
},
reset: function () {
reset() {
this.$nextTick(function () {
this.activeName = 0;
this.select(this.scenarios[0]);
});
},
initScenarioEnvironment: function () {
initScenarioEnvironment() {
if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
let environments = response.data;
@ -192,7 +233,19 @@ export default {
},
isRequest() {
return this.selected instanceof Request;
},
isReference() {
if (this.selected instanceof Scenario) {
return this.selected.isReference();
}
if (this.selected instanceof Request) {
return this.currentScenario.isReference();
}
return false;
},
disable() {
return this.isReadOnly || this.isReference;
},
},
created() {

View File

@ -1,5 +1,6 @@
<template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading">
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" v-loading="result.loading"
:disabled="isReadOnly">
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="scenario.name" maxlength="100" show-word-limit/>
</el-form-item>
@ -26,7 +27,7 @@
</el-form-item>
</el-form-item>
<el-tabs v-model="activeName">
<el-tabs v-model="activeName" :disabled="isReadOnly">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
<ms-api-scenario-variables :is-read-only="isReadOnly" :items="scenario.variables"
:description="$t('api_test.scenario.kv_description')"/>
@ -36,13 +37,16 @@
:environment="scenario.environment"
:description="$t('api_test.scenario.kv_description')"/>
</el-tab-pane>
<el-tab-pane :label="'数据库配置'" name="database">
<ms-database-config :configs="scenario.databaseConfigs"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
<div class="dubbo-config-title">Config Center</div>
<ms-dubbo-config-center :config="scenario.dubboConfig.configCenter"/>
<ms-dubbo-config-center :config="scenario.dubboConfig.configCenter" :is-read-only="isReadOnly"/>
<div class="dubbo-config-title">Registry Center</div>
<ms-dubbo-registry-center :registry="scenario.dubboConfig.registryCenter"/>
<ms-dubbo-registry-center :registry="scenario.dubboConfig.registryCenter" :is-read-only="isReadOnly"/>
<div class="dubbo-config-title">Consumer & Service</div>
<ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService"/>
<ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService" :is-read-only="isReadOnly"/>
</el-tab-pane>
</el-tabs>
@ -61,10 +65,12 @@ import {REQUEST_HEADERS} from "@/common/js/constants";
import MsDubboRegistryCenter from "@/business/components/api/test/components/request/dubbo/RegistryCenter";
import MsDubboConfigCenter from "@/business/components/api/test/components/request/dubbo/ConfigCenter";
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsDatabaseConfig from "./request/database/DatabaseConfig";
export default {
name: "MsApiScenarioForm",
components: {
MsDatabaseConfig,
MsDubboConsumerService,
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
},

View File

@ -0,0 +1,128 @@
<template>
<el-dialog :title="$t('api_test.scenario.select_scenario')" :visible.sync="visible" width="70%">
<el-table stripe :data="tableData" size="small" @expand-change="expand">
<el-table-column type="expand" width="50">
<template v-slot:default="{row}">
<ms-api-scenario-select-sub-table :row="row" v-model="row.selected"/>
</template>
</el-table-column>
<el-table-column prop="name" :label="$t('api_test.scenario.test_name')" width="400" show-overflow-tooltip/>
<el-table-column prop="sr" :label="$t('api_test.scenario.scenario_request')" width="150" show-overflow-tooltip/>
<el-table-column prop="userName" :label="$t('api_test.creator')" width="150" show-overflow-tooltip/>
<el-table-column prop="enable" :label="$t('api_test.scenario.enable_disable')" width="150"/>
<el-table-column>
<template v-slot:header>
<div class="search-header">
<ms-table-search-bar :condition.sync="condition" @change="search" class="search-bar"
:tip="$t('commons.search_by_name')"/>
<ms-table-adv-search-bar :condition.sync="condition" @search="search"/>
</div>
</template>
<template v-slot:default="{row}">
{{ row.reference }}
<el-button type="text" size="small" @click="reference(row)">
{{ $t('api_test.scenario.reference') }}
</el-button>
<el-button type="text" size="small" @click="clone(row)">{{ $t('api_test.scenario.clone') }}</el-button>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize" :total="total"/>
<div class="dialog-footer">
<el-button @click="close">{{ $t('commons.cancel') }}</el-button>
</div>
</el-dialog>
</template>
<script>
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import {TEST_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableSearchBar from "@/business/components/common/components/MsTableSearchBar";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import MsApiScenarioSelectSubTable from "@/business/components/api/test/components/ApiScenarioSelectSubTable";
import {Scenario} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsApiScenarioSelect",
components: {
MsApiScenarioSelectSubTable,
MsTableAdvSearchBar, MsTableSearchBar, MsTablePagination, MsTableHeader
},
props: {
excludeId: String
},
data() {
return {
visible: false,
condition: {
components: TEST_CONFIGS
},
tableData: [],
currentPage: 1,
pageSize: 5,
total: 0,
selection: false,
}
},
methods: {
search() {
this.condition.excludeId = this.excludeId;
let url = "/api/list/" + this.currentPage + "/" + this.pageSize;
this.result = this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(row => {
row.selected = [];
})
});
},
reference(row) {
let scenarios = [];
for (let options of row.selected) {
if (!options.id) {
this.$warning(this.$t('api_test.scenario.cant_reference'));
return;
}
let scenario = new Scenario(options);
if (!scenario.isReference()) {
scenario.id = row.id + "#" + options.id;
} else {
scenario.id = options.id;
}
scenarios.push(scenario);
}
this.$emit('select', scenarios);
},
clone(row) {
let scenarios = [];
row.selected.forEach(options => {
scenarios.push(new Scenario(options));
})
this.$emit('select', scenarios);
},
open() {
this.search();
this.visible = true;
},
close() {
this.visible = false;
},
expand(row) {
row.selected = [];
}
}
}
</script>
<style scoped>
.search-header {
text-align: right;
}
.search-bar {
width: 200px
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<el-table stripe :data="scenarios" class="adjust-table" @select-all="select" @select="select" size="small"
:show-header="false" ref="table" v-loading="result.loading">
<el-table-column type="selection" width="50"/>
<el-table-column prop="name" width="350" show-overflow-tooltip/>
<el-table-column width="150">
<template v-slot:default="{row}">
1 / {{ row.requests.length }}
</template>
</el-table-column>
<el-table-column prop="null" width="150">
{{ row.userName }}
</el-table-column>
<el-table-column prop="enable">
<template v-slot:default="{row}">
<el-tag type="success" size="small" v-if="row.enable !== false">{{ $t('api_test.scenario.enable') }}</el-tag>
<el-tag type="info" size="small" v-else>{{ $t('api_test.scenario.disable') }}</el-tag>
</template>
</el-table-column>
<el-table-column/>
</el-table>
</template>
<script>
export default {
name: "MsApiScenarioSelectSubTable",
props: {
row: Object
},
data() {
return {
result: {loading: true},
scenarios: [],
}
},
methods: {
getTest() {
this.result = this.$get("/api/get/" + this.row.id, response => {
if (response.data) {
this.scenarios = JSON.parse(response.data.scenarioDefinition);
}
});
},
select(selection) {
this.$emit('input', selection);
}
},
mounted() {
this.getTest();
}
}
</script>
<style scoped>
</style>

View File

@ -27,7 +27,7 @@
</el-switch>
</el-form-item>
<ms-api-host-table v-if="envEnable" :hostTable="environment.hosts"/>
<ms-api-host-table v-if="envEnable" :hostTable="environment.hosts" ref="refHostTable"/>
<span>{{$t('api_test.environment.globalVariable')}}</span>
<ms-api-scenario-variables :show-variable="false" :items="environment.variables"/>
@ -87,12 +87,20 @@
methods: {
save() {
this.$refs['environment'].validate((valid) => {
if (valid) {
// host
let valHost = true;
if (this.envEnable) {
for (let i = 0; i < this.environment.hosts.length; i++) {
valHost = this.$refs['refHostTable'].confirm(this.environment.hosts[i]);
}
}
if (valid && valHost) {
this._save(this.environment);
} else {
return false;
}
});
},
_save(environment) {
let param = this.buildParam(environment);

View File

@ -1,5 +1,5 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px">
<el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/>
@ -41,7 +41,9 @@
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>
</el-form-item>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}</el-button>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small"
type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}
</el-button>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">

View File

@ -1,6 +1,7 @@
<template>
<div class="request-container">
<draggable :list="this.scenario.requests" group="Request" class="request-draggable" ghost-class="request-ghost">
<draggable :list="this.scenario.requests" group="Request" class="request-draggable" ghost-class="request-ghost"
:disabled="isReference">
<div class="request-item" v-for="(request, index) in this.scenario.requests" :key="index" @click="select(request)"
:class="{'selected': isSelected(request), 'disable-request': !request.enable || !scenario.enable}">
<el-row type="flex" align="middle">
@ -23,10 +24,12 @@
<el-dropdown-item :disabled="isReadOnly" :command="{type: 'delete', index: index}">
{{ $t('api_test.request.delete') }}
</el-dropdown-item>
<el-dropdown-item v-if="request.enable" :disabled="isReadOnly" :command="{type: 'disable', index: index}">
<el-dropdown-item v-if="request.enable" :disabled="isReadOnly"
:command="{type: 'disable', index: index}">
{{ $t('api_test.scenario.disable') }}
</el-dropdown-item>
<el-dropdown-item v-if="!request.enable" :disabled="isReadOnly" :command="{type: 'enable', index: index}">
<el-dropdown-item v-if="!request.enable" :disabled="isReadOnly"
:command="{type: 'enable', index: index}">
{{ $t('api_test.scenario.enable') }}
</el-dropdown-item>
</el-dropdown-menu>
@ -39,6 +42,7 @@
<el-radio-group v-model="type" @change="createRequest">
<el-radio :label="types.HTTP">HTTP</el-radio>
<el-radio :label="types.DUBBO">DUBBO</el-radio>
<el-radio :label="types.SQL">SQL</el-radio>
</el-radio-group>
<el-button slot="reference" :disabled="isReadOnly"
class="request-create" type="primary" size="mini" icon="el-icon-plus" plain/>
@ -78,6 +82,9 @@
return function (request) {
return this.selected === request;
}
},
isReference() {
return this.scenario.isReference();
}
},

View File

@ -13,10 +13,11 @@ import MsApiHttpRequestForm from "./ApiHttpRequestForm";
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
import MsScenarioResults from "../../../report/components/ScenarioResults";
import MsRequestResultTail from "../../../report/components/RequestResultTail";
import MsApiSqlRequestForm from "./ApiSqlRequestForm";
export default {
name: "MsApiRequestForm",
components: {MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
props: {
scenario: Scenario,
request: Request,
@ -41,6 +42,9 @@ export default {
case RequestFactory.TYPES.DUBBO:
name = "MsApiDubboRequestForm";
break;
case RequestFactory.TYPES.SQL:
name = "MsApiSqlRequestForm";
break;
default:
name = "MsApiHttpRequestForm";
}

View File

@ -0,0 +1,118 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input v-model="request.name" maxlength="300" show-word-limit/>
</el-form-item>
<el-form-item :label="'连接池'" prop="dataSource">
<el-select v-model="request.dataSource">
<el-option v-for="(item, index) in scenario.databaseConfigs" :key="index" :value="item.name" :label="item.name"/>
</el-select>
</el-form-item>
<!--<el-form-item :label="'查询类型'" prop="protocol">-->
<!--<el-select v-model="request.queryType">-->
<!--<el-option label="dubbo://" :value="protocols.DUBBO"/>-->
<!--</el-select>-->
<!--</el-form-item>-->
<el-form-item :label="'超时时间'" prop="queryTimeout">
<el-input-number :disabled="isReadOnly" size="mini" v-model="request.queryTimeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
<el-tabs v-model="activeName">
<el-tab-pane :label="'sql脚本'" name="sql">
<div class="sql-content" >
<ms-code-edit mode="sql" :read-only="isReadOnly" :modes="['sql']" :data.sync="request.query" theme="eclipse" ref="codeEdit"/>
</div>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
<ms-api-assertions :is-read-only="isReadOnly" :assertions="request.assertions"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="beanShellPreProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="beanShellPostProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/>
</el-tab-pane>
</el-tabs>
</el-form>
</template>
<script>
import MsApiKeyValue from "../ApiKeyValue";
import MsApiAssertions from "../assertion/ApiAssertions";
import {DubboRequest, Scenario, SqlRequest} from "../../model/ScenarioModel";
import MsApiExtract from "../extract/ApiExtract";
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
import MsDubboInterface from "@/business/components/api/test/components/request/dubbo/Interface";
import MsDubboRegistryCenter from "@/business/components/api/test/components/request/dubbo/RegistryCenter";
import MsDubboConfigCenter from "@/business/components/api/test/components/request/dubbo/ConfigCenter";
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsJsr233Processor from "../processor/Jsr233Processor";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
export default {
name: "MsApiSqlRequestForm",
components: {
MsCodeEdit,
MsJsr233Processor,
MsDubboConsumerService,
MsDubboConfigCenter,
MsDubboRegistryCenter,
MsDubboInterface, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiKeyValue
},
props: {
request: SqlRequest,
scenario: Scenario,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
activeName: "sql",
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'},
],
dataSource: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
],
}
}
},
methods: {
useEnvironmentChange(value) {
if (value && !this.request.environment) {
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
this.request.useEnvironment = false;
}
this.$refs["request"].clearValidate();
},
runDebug() {
this.$emit('runDebug');
}
},
computed: {}
}
</script>
<style scoped>
.sql-content {
height: calc(100vh - 570px);
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div>
<ms-database-from :config="currentConfig" @save="addConfig" ref="databaseFrom"/>
<ms-database-config-list v-if="configs.length > 0" :table-data="configs"/>
</div>
</template>
<script>
import MsDatabaseConfigList from "./DatabaseConfigList";
import {DatabaseConfig} from "../../../model/ScenarioModel";
import MsDatabaseFrom from "./DatabaseFrom";
import {getUUID} from "../../../../../../../common/js/utils";
export default {
name: "MsDatabaseConfig",
components: {MsDatabaseFrom, MsDatabaseConfigList},
props: {
configs: Array,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
currentConfig: new DatabaseConfig()
}
},
methods: {
addConfig(config) {
for (let item of this.configs) {
if (item.name === config.name) {
this.$warning("名称重复");
return;
}
}
config.id = getUUID();
this.configs.push(config);
this.currentConfig = new DatabaseConfig();
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<el-dialog :title="'数据库配置'" :visible.sync="visible">
<ms-database-from :config="config" @save="editConfig"/>
</el-dialog>
</template>
<script>
import MsDatabaseConfigList from "./DatabaseConfigList";
import MsDatabaseFrom from "./DatabaseFrom";
import {DatabaseConfig} from "../../../model/ScenarioModel";
export default {
name: "MsDatabaseConfigDialog",
components: {MsDatabaseFrom, MsDatabaseConfigList},
props: {
configs: Array,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
visible: false,
config: new DatabaseConfig(),
}
},
methods: {
open(config) {
this.visible = true;
Object.assign(this.config, config);
},
editConfig(config) {
let currentConfig = undefined;
for (let item of this.configs) {
if (item.name === config.name && item.id != config.id) {
this.$warning("名称重复");
return;
}
if (item.id === config.id) {
currentConfig = item;
}
}
if (currentConfig) {
Object.assign(currentConfig, config)
} else {
//copy
this.configs.push(config);
}
this.visible = false;
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,92 @@
<template>
<ms-main-container>
<el-table border :data="tableData" class="adjust-table table-content"
@row-click="handleView">
<el-table-column prop="name" :label="'连接池名称'" show-overflow-tooltip/>
<el-table-column prop="driver" :label="'数据库驱动'" show-overflow-tooltip/>
<el-table-column prop="dbUrl" :label="'数据库连接URL'" show-overflow-tooltip/>
<el-table-column prop="username" :label="'用户名'" show-overflow-tooltip/>
<el-table-column prop="poolMax" :label="'最大连接数'" show-overflow-tooltip/>
<el-table-column prop="timeout" :label="'最大等待时间'" show-overflow-tooltip/>
<el-table-column
:label="$t('commons.operating')" min-width="100">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
@deleteClick="handleDelete(scope.$index)">
<template v-slot:middle>
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.copy')"
icon="el-icon-document-copy"
type="success" @exec="handleCopy(scope.row)"/>
</template>
</ms-table-operator>
</template>
</el-table-column>
</el-table>
<ms-database-config-dialog :configs="tableData" ref="databaseConfigEdit"/>
</ms-main-container>
</template>
<script>
import {DatabaseConfig} from "../../../model/ScenarioModel";
import MsMainContainer from "../../../../../common/components/MsMainContainer";
import MsTableOperator from "../../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../../common/components/MsTableOperatorButton";
import MsDatabaseConfigDialog from "./DatabaseConfigDialog";
import {getUUID} from "../../../../../../../common/js/utils";
export default {
name: "MsDatabaseConfigList",
components: {MsDatabaseConfigDialog, MsTableOperatorButton, MsTableOperator, MsMainContainer},
props: {
tableData: Array,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
result: {},
}
},
methods: {
handleView() {
},
handleEdit(config) {
this.$refs.databaseConfigEdit.open(config);
},
handleDelete(index) {
this.tableData.splice(index, 1);
},
handleCopy(config) {
let copy = {};
Object.assign(copy, config);
copy.id = getUUID();
this.$refs.databaseConfigEdit.open(copy);
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<div>
<el-form :model="config" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" class="database-from" ref="databaseFrom">
<el-form-item :label="'连接池名称'" prop="name">
<el-input v-model="config.name" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'数据库连接URL'" prop="dbUrl">
<el-input v-model="config.dbUrl" maxlength="500" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'数据库驱动'" prop="driver">
<el-select v-model="config.driver" class="select-100" clearable>
<el-option v-for="p in drivers" :key="p" :label="p" :value="p"/>
</el-select>
</el-form-item>
<el-form-item :label="'用户名'" prop="username">
<el-input v-model="config.username" maxlength="300" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'密码'" prop="password">
<el-input v-model="config.password" maxlength="200" show-word-limit
:placeholder="$t('commons.input_content')"/>
</el-form-item>
<el-form-item :label="'最大连接数'" prop="poolMax">
<el-input-number size="small" :disabled="isReadOnly" v-model="config.poolMax" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item :label="'最大等待时间(ms)'" prop="timeout">
<el-input-number size="small" :disabled="isReadOnly" v-model="config.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" class="addButton" @click="save">添加</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {DatabaseConfig} from "../../../model/ScenarioModel";
export default {
name: "MsDatabaseFrom",
components: {},
props: {
isReadOnly: {
type: Boolean,
default: false
},
config: {
type: Object,
default() {
return new DatabaseConfig();
}
},
},
data() {
return {
drivers: DatabaseConfig.DRIVER_CLASS,
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 300, message: this.$t('commons.input_limit', [0, 300]), trigger: 'blur'}
],
driver: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
],
password: [
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
],
dbUrl: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'}
],
username: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 200, message: this.$t('commons.input_limit', [0, 200]), trigger: 'blur'}
]
}
}
},
methods: {
save() {
this.$refs['databaseFrom'].validate((valid) => {
if (valid) {
this.$emit('save', this.config);
} else {
return false;
}
});
}
}
}
</script>
<style scoped>
.addButton {
float: right;
}
.database-from {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
border-radius: 5px;
}
</style>

View File

@ -274,6 +274,38 @@ export class DubboSample extends DefaultTestElement {
}
}
export class JDBCSampler extends DefaultTestElement {
constructor(testName, request = {}) {
super('JDBCSampler', 'TestBeanGUI', 'JDBCSampler', testName);
this.stringProp("dataSource", request.dataSource);
this.stringProp("query", request.query);
this.stringProp("queryTimeout", request.queryTimeout);
this.stringProp("queryArguments");
this.stringProp("queryArgumentsTypes");
this.stringProp("resultSetMaxRows");
this.stringProp("resultVariable");
this.stringProp("variableNames");
this.stringProp("resultSetHandler", 'Store as String');
this.stringProp("queryType", 'Callable Statement');
}
}
// <JDBCSampler guiclass="TestBeanGUI" testclass="JDBCSampler" testname="JDBC Request" enabled="true">
// <stringProp name="dataSource">test</stringProp>
// <stringProp name="query">select id from test_plan;
// select name from test_plan;
// </stringProp>
// <stringProp name="queryArguments"></stringProp>
// <stringProp name="queryArgumentsTypes"></stringProp>
// <stringProp name="queryTimeout"></stringProp>
// <stringProp name="queryType">Callable Statement</stringProp>
// <stringProp name="resultSetHandler">Store as String</stringProp>
// <stringProp name="resultSetMaxRows"></stringProp>
// <stringProp name="resultVariable"></stringProp>
// <stringProp name="variableNames"></stringProp>
// </JDBCSampler>
export class HTTPSamplerProxy extends DefaultTestElement {
constructor(testName, options = {}) {
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
@ -504,8 +536,8 @@ export class DNSCacheManager extends DefaultTestElement {
let collectionPropHosts = this.collectionProp('DNSCacheManager.hosts');
hosts.forEach(host => {
let elementProp = collectionPropHosts.elementProp('', 'StaticHost');
if (host && host.domain.trim() === domain.trim()) {
let elementProp = collectionPropHosts.elementProp(host.domain, 'StaticHost');
if (host && host.domain.trim().indexOf(domain.trim()) != -1) {
elementProp.stringProp('StaticHost.Name', host.domain);
elementProp.stringProp('StaticHost.Address', host.ip);
}
@ -515,6 +547,29 @@ export class DNSCacheManager extends DefaultTestElement {
}
}
export class JDBCDataSource extends DefaultTestElement {
constructor(testName, datasource) {
super('JDBCDataSource', 'TestBeanGUI', 'JDBCDataSource', testName);
this.boolProp('autocommit', true);
this.boolProp('keepAlive', true);
this.boolProp('preinit', false);
this.stringProp('dataSource', datasource.name);
this.stringProp('dbUrl', datasource.dbUrl);
this.stringProp('driver', datasource.driver);
this.stringProp('username', datasource.username);
this.stringProp('password', datasource.password);
this.stringProp('poolMax', datasource.poolMax);
this.stringProp('timeout', datasource.timeout);
this.stringProp('connectionAge', '5000');
this.stringProp('trimInterval', '60000');
this.stringProp('transactionIsolation', 'DEFAULT');
this.stringProp('checkQuery');
this.stringProp('initQuery');
this.stringProp('connectionProperties');
}
}
export class Arguments extends DefaultTestElement {
constructor(testName, args) {
super('Arguments', 'ArgumentsPanel', 'Arguments', testName);

View File

@ -8,7 +8,7 @@ import {
HashTree,
HeaderManager,
HTTPSamplerArguments, HTTPsamplerFiles,
HTTPSamplerProxy,
HTTPSamplerProxy, JDBCDataSource, JDBCSampler,
JSONPathAssertion,
JSONPostProcessor, JSR223PostProcessor, JSR223PreProcessor,
RegexExtractor,
@ -106,7 +106,6 @@ export class BaseConfig {
set(options) {
options = this.initOptions(options)
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (!(this[name] instanceof Array)) {
@ -142,7 +141,7 @@ export class Test extends BaseConfig {
constructor(options) {
super();
this.type = "MS API CONFIG";
this.version = '1.1.0';
this.version = '1.3.0';
this.id = uuid();
this.name = undefined;
this.projectId = undefined;
@ -201,6 +200,7 @@ export class Test extends BaseConfig {
export class Scenario extends BaseConfig {
constructor(options = {}) {
super();
this.id = undefined;
this.name = undefined;
this.url = undefined;
this.variables = [];
@ -211,20 +211,24 @@ export class Scenario extends BaseConfig {
this.environment = undefined;
this.enableCookieShare = false;
this.enable = true;
this.databaseConfigs = [];
this.set(options);
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory}, options);
this.sets({variables: KeyValue, headers: KeyValue, requests: RequestFactory, databaseConfigs: DatabaseConfig}, options);
}
initOptions(options) {
options = options || {};
initOptions(options = {}) {
options.id = options.id || uuid();
options.requests = options.requests || [new RequestFactory()];
options.databaseConfigs = options.databaseConfigs || [];
options.dubboConfig = new DubboConfig(options.dubboConfig);
return options;
}
clone() {
return new Scenario(this);
let clone = new Scenario(this);
clone.id = uuid();
return clone;
}
isValid() {
@ -238,6 +242,10 @@ export class Scenario extends BaseConfig {
}
return {isValid: true};
}
isReference() {
return this.id.indexOf("#") !== -1
}
}
class DubboConfig extends BaseConfig {
@ -265,6 +273,7 @@ export class RequestFactory {
static TYPES = {
HTTP: "HTTP",
DUBBO: "DUBBO",
SQL: "SQL",
}
constructor(options = {}) {
@ -272,6 +281,8 @@ export class RequestFactory {
switch (options.type) {
case RequestFactory.TYPES.DUBBO:
return new DubboRequest(options);
case RequestFactory.TYPES.SQL:
return new SqlRequest(options);
default:
return new HttpRequest(options);
}
@ -296,6 +307,7 @@ export class Request extends BaseConfig {
export class HttpRequest extends Request {
constructor(options) {
super(RequestFactory.TYPES.HTTP);
this.id = undefined;
this.name = undefined;
this.url = undefined;
this.path = undefined;
@ -321,8 +333,8 @@ export class HttpRequest extends Request {
this.sets({parameters: KeyValue, headers: KeyValue}, options);
}
initOptions(options) {
options = options || {};
initOptions(options = {}) {
options.id = options.id || uuid();
options.method = options.method || "GET";
options.body = new Body(options.body);
options.assertions = new Assertions(options.assertions);
@ -381,6 +393,7 @@ export class DubboRequest extends Request {
constructor(options = {}) {
super(RequestFactory.TYPES.DUBBO);
this.id = options.id || uuid();
this.name = options.name;
this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO;
this.interface = options.interface;
@ -397,7 +410,7 @@ export class DubboRequest extends Request {
this.debugReport = undefined;
this.beanShellPreProcessor = new BeanShellProcessor(options.beanShellPreProcessor);
this.beanShellPostProcessor = new BeanShellProcessor(options.beanShellPostProcessor);
this.enable = options.enable == undefined ? true : options.enable;
this.enable = options.enable === undefined ? true : options.enable;
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
@ -450,6 +463,60 @@ export class DubboRequest extends Request {
}
}
export class SqlRequest extends Request {
constructor(options = {}) {
super(RequestFactory.TYPES.SQL);
this.id = options.id || uuid();
this.name = options.name;
this.dataSource = options.dataSource;
this.query = options.query;
// this.queryType = options.queryType;
this.queryTimeout = options.queryTimeout;
this.enable = options.enable === undefined ? true : options.enable;
this.assertions = new Assertions(options.assertions);
this.extract = new Extract(options.extract);
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
}
isValid() {
if (this.enable) {
if (!this.name) {
return {
isValid: false,
info: 'name'
}
}
if (!this.dataSource) {
return {
isValid: false,
info: 'dataSource'
}
}
}
return {
isValid: true
}
}
showType() {
return "SQL";
}
showMethod() {
return "SQL";
}
clone() {
return new SqlRequest(this);
}
}
export class ConfigCenter extends BaseConfig {
static PROTOCOLS = ["zookeeper", "nacos", "apollo"];
@ -471,6 +538,33 @@ export class ConfigCenter extends BaseConfig {
}
}
export class DatabaseConfig extends BaseConfig {
static DRIVER_CLASS = ["com.mysql.jdbc.Driver"];
constructor(options) {
super();
this.id = undefined;
this.name = undefined;
this.poolMax = undefined;
this.timeout = undefined;
this.driver = undefined;
this.dbUrl = undefined;
this.username = undefined;
this.password = undefined;
this.set(options);
}
initOptions(options = {}) {
// options.id = options.id || uuid();
return options;
}
isValid() {
return !!this.name || !!this.poolMax || !!this.timeout || !!this.driver || !!this.dbUrl || !!this.username || !!this.password;
}
}
export class RegistryCenter extends BaseConfig {
static PROTOCOLS = ["none", "zookeeper", "nacos", "apollo", "multicast", "redis", "simple"];
@ -874,6 +968,8 @@ class JMXGenerator {
// 放在计划或线程组中,不建议放具体某个请求中
this.addDNSCacheManager(threadGroup, scenario.requests[0]);
this.addJDBCDataSource(threadGroup, scenario);
scenario.requests.forEach(request => {
if (request.enable) {
if (!request.isValid()) return;
@ -881,9 +977,7 @@ class JMXGenerator {
if (request instanceof DubboRequest) {
sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig));
}
if (request instanceof HttpRequest) {
} else if (request instanceof HttpRequest) {
sampler = new HTTPSamplerProxy(request.name || "", new JMXHttpRequest(request, scenario.environment));
this.addRequestHeader(sampler, request);
if (request.method.toUpperCase() === 'GET') {
@ -891,6 +985,8 @@ class JMXGenerator {
} else {
this.addRequestBody(sampler, request, testId);
}
} else if (request instanceof SqlRequest) {
sampler = new JDBCSampler(request.name || "", request);
}
this.addRequestExtractor(sampler, request);
@ -948,12 +1044,19 @@ class JMXGenerator {
let name = request.name + " DNSCacheManager";
let hosts = JSON.parse(request.environment.hosts);
if (hosts.length > 0) {
let domain = request.environment.protocol + "://" + request.environment.domain;
threadGroup.put(new DNSCacheManager(name, domain, hosts));
//let domain = request.environment.protocol + "://" + request.environment.domain;
threadGroup.put(new DNSCacheManager(name, request.environment.domain, hosts));
}
}
}
addJDBCDataSource(threadGroup, scenario) {
scenario.databaseConfigs.forEach(config => {
let name = config.name + "JDBCDataSource";
threadGroup.put(new JDBCDataSource(name, config));
});
}
addScenarioHeaders(threadGroup, scenario) {
let environment = scenario.environment;
if (environment) {
@ -1030,7 +1133,7 @@ class JMXGenerator {
this.addRequestBodyFile(httpSamplerProxy, request, testId);
} else {
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
body.push({name: '', value: request.body.raw, encode: false});
body.push({name: '', value: request.body.raw, encode: false, enable: true});
}
httpSamplerProxy.add(new HTTPSamplerArguments(body));

View File

@ -51,6 +51,7 @@ import Setting from "@/business/components/settings/router";
export default {
name: "MsSettingMenu",
data() {
let valid = false;
let getMenus = function (group) {
let menus = [];
Setting.children.forEach(child => {
@ -58,6 +59,10 @@ export default {
let menu = {index: Setting.path + "/" + child.path}
menu.title = child.meta.title;
menu.roles = child.meta.roles;
if (child.meta.valid != undefined && child.meta.valid === true) {
menu.valid = child.meta.valid;
valid = true;
}
menus.push(menu);
}
})
@ -68,18 +73,56 @@ export default {
organizations: getMenus('organization'),
workspaces: getMenus('workspace'),
persons: getMenus('person'),
isValid: valid,
isCurrentOrganizationAdmin: false,
isCurrentWorkspaceUser: false,
}
},
mounted() {
if (this.isValid === true) {
this.valid();
}
this.isCurrentOrganizationAdmin = checkoutCurrentOrganization();
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
},
methods: {
valid() {
let _this = this;
this.result = this.$get("/license/valid", response => {
let data = response.data;
if (data === undefined || data === null || data.status != "valid") {
this.systems.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.systems.splice(this.systems.indexOf(item), 1);
}
})
this.organizations.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.organizations.splice(this.organizations.indexOf(item), 1);
}
})
this.workspaces.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.workspaces.splice(this.workspaces.indexOf(item), 1);
}
})
this.persons.forEach(item => {
if (item.valid != undefined && item.valid === true) {
_this.persons.splice(this.persons.indexOf(item), 1);
}
})
}
})
}
}
}
</script>
<style scoped>
.setting {
border-right: 0;
}

View File

@ -32,7 +32,7 @@ export default {
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/system/SystemParameterSetting'),
meta: {system: true, title: 'commons.system_parameter_setting'}
},
...requireContext.keys().map(key => requireContext(key).system),
...requireContext.keys().map(key => requireContext(key).system),...requireContext.keys().map(key => requireContext(key).license),
{
path: 'organizationmember',
component: () => import(/* webpackChunkName: "setting" */ '@/business/components/settings/organization/OrganizationMember'),

View File

@ -0,0 +1,101 @@
<template>
<el-dialog v-loading="result.loading"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
class="ms-switch-project"
>
<ms-table-header :condition.sync="condition" @search="initData" title="" :show-create="false"/>
<el-table
:data="tableData"
highlight-current-row
@current-change="handleCurrentChange"
style="width: 100%">
<el-table-column prop="name" :label="$t('commons.name')" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('commons.description')" show-overflow-tooltip>
<template v-slot:default="scope">
{{ scope.row.description }}
</template>
</el-table-column>
<el-table-column
prop="createTime"
:label="$t('commons.create_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="updateTime"
:label="$t('commons.update_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="initData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<template v-slot:footer>
<div class="dialog-footer">
<ms-dialog-footer
@cancel="dialogVisible = false"
@confirm="submit()"/>
</div>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../common/components/MsDialogFooter";
import MsTableHeader from "../../../common/components/MsTableHeader";
import MsTablePagination from "../../../common/pagination/TablePagination";
export default {
name: "SwitchProject",
components: {MsDialogFooter, MsTableHeader, MsTablePagination},
data() {
return {
tableData: [],
result: {},
dialogVisible: false,
projectId: '',
planId: '',
condition: {},
currentPage: 1,
pageSize: 5,
total: 0,
}
},
methods: {
open(planId) {
this.dialogVisible = true;
this.planId = planId;
this.initData();
},
initData() {
this.condition.planId = this.planId;
this.result = this.$post("/test/plan/project/" + this.currentPage + "/" + this.pageSize, this.condition, res => {
const data = res.data;
this.total = data.itemCount;
this.tableData = data.listObject;
})
},
handleCurrentChange(currentRow) {
// initData
if (currentRow) {
this.projectId = currentRow.id;
}
},
submit() {
this.$emit('getProjectNode', this.projectId);
this.dialogVisible = false;
}
}
}
</script>
<style scoped>
.ms-switch-project >>> .el-dialog__body {
padding: 0 15px !important;
}
</style>

View File

@ -69,7 +69,7 @@
},
uploadValidate(file) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (suffix != 'xls' && suffix != 'xlsx') {
if (suffix != 'xls' && suffix != 'xlsx' && suffix != 'xmind') {
this.$warning(this.$t('test_track.case.import.upload_limit_format'));
return false;
}

View File

@ -230,12 +230,16 @@ export default {
this.$emit("refresh");
},
nodeExpand(data) {
if (data.id) {
this.expandedNode.push(data.id);
}
},
nodeCollapse(data) {
if (data.id) {
this.expandedNode.splice(this.expandedNode.indexOf(data.id), 1);
}
}
}
};
</script>

View File

@ -90,6 +90,12 @@
initTableData() {
this.result = this.$post('/test/plan/list/all/relate', this.condition, response => {
this.tableData = response.data;
for (let i = 0; i < this.tableData.length; i++) {
let path = "/test/plan/project/name/" + this.tableData[i].id;
this.$get(path, res => {
this.$set(this.tableData[i], "projectName", res.data);
})
}
});
},
intoPlan(row, event, column) {

View File

@ -22,11 +22,14 @@
</el-col>
<el-col :span="11" :offset="2">
<el-form-item :label="$t('test_track.plan.plan_project')" :label-width="formLabelWidth" prop="projectId">
<el-form-item :label="$t('test_track.plan.plan_project')" :label-width="formLabelWidth" prop="projectIds">
<el-select
:disabled="(form.status == null) ? false : true"
v-model="form.projectId"
v-model="form.projectIds"
:placeholder="$t('test_track.plan.input_plan_project')"
multiple
style="width: 100%"
collapse-tags
filterable>
<el-option
v-for="item in projects"
@ -68,7 +71,7 @@
</el-row>
<el-row type="flex" justify="left" style="margin-top: 10px;">
<el-col :span="19" :offset="1">
<el-col :span="23" :offset="1">
<el-form-item :label="$t('commons.description')" :label-width="formLabelWidth" prop="description">
<el-input v-model="form.description"
type="textarea"
@ -124,7 +127,7 @@ export default {
dialogFormVisible: false,
form: {
name: '',
projectId: '',
projectIds: [],
principal: '',
stage: '',
description: ''
@ -134,7 +137,7 @@ export default {
{required: true, message: this.$t('test_track.plan.input_plan_name'), trigger: 'blur'},
{max: 30, message: this.$t('test_track.length_less_than') + '30', trigger: 'blur'}
],
projectId: [{required: true, message: this.$t('test_track.plan.input_plan_project'), trigger: 'change'}],
projectIds: [{required: true, message: this.$t('test_track.plan.input_plan_project'), trigger: 'change'}],
principal: [{required: true, message: this.$t('test_track.plan.input_plan_principal'), trigger: 'change'}],
stage: [{required: true, message: this.$t('test_track.plan.input_plan_stage'), trigger: 'change'}],
description: [{max: 200, message: this.$t('test_track.length_less_than') + '200', trigger: 'blur'}]
@ -213,7 +216,7 @@ export default {
this.$refs['planFrom'].validate((valid) => {
this.$refs['planFrom'].resetFields();
this.form.name = '';
this.form.projectId = '';
this.form.projectIds = [];
this.form.principal = '';
this.form.stage = '';
this.form.description = '';

View File

@ -191,6 +191,16 @@ export default {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
for (let i = 0; i < this.tableData.length; i++) {
let path = "/test/plan/project";
this.$post(path,{planId: this.tableData[i].id}, res => {
let arr = res.data;
let projectName = arr.map(data => data.name).join("、");
let projectIds = arr.map(data => data.id);
this.$set(this.tableData[i], "projectName", projectName);
this.$set(this.tableData[i], "projectIds", projectIds);
})
}
});
},
buildPagePath(path) {

View File

@ -6,10 +6,12 @@
:visible.sync="dialogFormVisible"
@close="close"
width="60%" v-loading="result.loading"
:close-on-click-modal="false"
top="50px">
<el-container class="main-content">
<el-aside class="tree-aside" width="250px">
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName : '切换项目' }}</el-link>
<node-tree class="node-tree"
@nodeSelectEvent="nodeChange"
@refresh="refresh"
@ -71,6 +73,8 @@
</template>
</el-dialog>
<switch-project ref="switchProject" @getProjectNode="getProjectNode"/>
</div>
</template>
@ -86,6 +90,7 @@
import MsTableAdvSearchBar from "../../../../common/components/search/MsTableAdvSearchBar";
import MsTableHeader from "../../../../common/components/MsTableHeader";
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import SwitchProject from "../../../case/components/SwitchProject";
export default {
name: "TestCaseRelevance",
@ -96,7 +101,8 @@
TypeTableItem,
MsTableSearchBar,
MsTableAdvSearchBar,
MsTableHeader
MsTableHeader,
SwitchProject
},
data() {
return {
@ -108,6 +114,9 @@
treeNodes: [],
selectNodeIds: [],
selectNodeNames: [],
projectId: '',
projectName: '',
projects: [],
condition: {
components: TEST_CASE_CONFIGS
},
@ -135,6 +144,9 @@
},
selectNodeIds() {
this.getCaseNames();
},
projectId() {
this.getProjectNode();
}
},
updated() {
@ -157,7 +169,6 @@
});
},
getCaseNames() {
let param = {};
if (this.planId) {
// param.planId = this.planId;
this.condition.planId = this.planId;
@ -168,12 +179,17 @@
} else {
this.condition.nodeIds = [];
}
if (this.projectId) {
this.condition.projectId = this.projectId;
this.result = this.$post('/test/case/name', this.condition, response => {
this.testCases = response.data;
this.testCases.forEach(item => {
item.checked = false;
});
});
}
},
handleSelectAll(selection) {
if (selection.length > 0) {
@ -203,13 +219,18 @@
initData() {
this.getCaseNames();
this.getAllNodeTreeByPlanId();
this.getProject();
},
refresh() {
this.close();
},
getAllNodeTreeByPlanId() {
if (this.planId) {
this.result = this.$get("/case/node/list/all/plan/" + this.planId, response => {
let param = {
testPlanId: this.planId,
projectId: this.projectId
};
this.result = this.$post("/case/node/list/all/plan", param , response => {
this.treeNodes = response.data;
});
}
@ -233,6 +254,36 @@
})
})
},
getProject() {
if (this.planId) {
this.$post("/test/plan/project/", {planId: this.planId},res => {
let data = res.data;
if (data) {
this.projects = data;
this.projectId = data[0].id;
this.projectName = data[0].name;
}
})
}
},
switchProject() {
this.$refs.switchProject.open(this.planId);
},
getProjectNode(projectId) {
const index = this.projects.findIndex(project => project.id === projectId);
if (index !== -1) {
this.projectName = this.projects[index].name;
}
if (projectId) {
this.projectId = projectId;
}
this.result = this.$post("/case/node/list/all/plan",
{testPlanId: this.planId, projectId: this.projectId} , response => {
this.treeNodes = response.data;
});
this.selectNodeIds = [];
}
}
}
</script>
@ -281,4 +332,10 @@
/*border: 1px solid #EBEEF5;*/
}
.project-link {
float: right;
margin-right: 12px;
margin-bottom: 10px;
}
</style>

View File

@ -89,6 +89,13 @@
<span class="cast_item">{{testCase.nodePath}}</span>
</el-col>
<el-col :span="4" :offset="1">
<span class="cast_label">{{$t('test_track.plan.plan_project')}}</span>
<span class="cast_item">{{testCase.projectName}}</span>
</el-col>
</el-row>
<el-row>
<el-col :offset="1">
<span class="cast_label">{{$t('test_track.case.prerequisite')}}</span>
<span class="cast_item">{{testCase.prerequisite}}</span>
</el-col>
@ -211,6 +218,12 @@
/>
<ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig"
v-model="testCase.issues.content"/>
<el-row v-if="hasTapdId">
Tapd平台处理人
<el-select v-model="testCase.tapdUsers" placeholder="请选择处理人" style="width: 20%" multiple collapse-tags>
<el-option v-for="(userInfo, index) in users" :key="index" :label="userInfo.user" :value="userInfo.user"/>
</el-select>
</el-row>
<el-button type="primary" size="small" @click="saveIssues">{{$t('commons.save')}}</el-button>
<el-button size="small" @click="issuesSwitch=false">{{$t('commons.cancel')}}</el-button>
</el-col>
@ -316,6 +329,8 @@
test: {},
activeTab: 'detail',
isFailure: true,
users: [],
hasTapdId: false
};
},
props: {
@ -483,6 +498,17 @@
executeResult += this.addPLabel(stepPrefix + (step.executeResult == undefined ? '' : step.executeResult));
});
this.testCase.issues.content = desc + this.addPLabel('') + result + this.addPLabel('') + executeResult + this.addPLabel('');
this.$get("/test/case/project/" + this.testCase.caseId, res => {
const project = res.data;
if (project.tapdId) {
this.hasTapdId = true;
this.result = this.$get("/issues/tapd/user/" + this.testCase.caseId, response => {
let data = response.data;
this.users = data;
})
}
})
}
},
addPLabel(str) {
@ -508,6 +534,7 @@
param.title = this.testCase.issues.title;
param.content = this.testCase.issues.content;
param.testCaseId = this.testCase.caseId;
param.tapdUsers = this.testCase.tapdUsers;
this.result = this.$post("/issues/add", param, () => {
this.$success(this.$t('commons.save_success'));
this.getIssues(param.testCaseId);
@ -515,6 +542,7 @@
this.issuesSwitch = false;
this.testCase.issues.title = "";
this.testCase.issues.content = "";
this.testCase.tapdUsers = [];
},
getIssues(caseId) {
this.result = this.$get("/issues/get/" + caseId, response => {

View File

@ -15,13 +15,6 @@
<ms-table-button :is-tester-permission="true" icon="el-icon-connection"
:content="$t('test_track.plan_view.relevance_test_case')"
@click="$emit('openTestCaseRelevanceDialog')"/>
<!-- <ms-table-button :is-tester-permission="true" icon="el-icon-unlock"-->
<!-- :content="$t('test_track.plan_view.cancel_relevance')" @click="handleBatch('delete')"/>-->
<!-- <ms-table-button :is-tester-permission="true" icon="el-icon-edit-outline"-->
<!-- :content="$t('test_track.plan_view.change_execution_results')"-->
<!-- @click="handleBatch('status')"/>-->
<!-- <ms-table-button :is-tester-permission="true" icon="el-icon-user"-->
<!-- :content="$t('test_track.plan_view.change_executor')" @click="handleBatch('executor')"/>-->
<ms-table-button :is-tester-permission="true" v-if="!testPlan.reportId" icon="el-icon-document"
:content="$t('test_track.plan_view.create_report')" @click="openTestReport"/>
<ms-table-button :is-tester-permission="true" v-if="testPlan.reportId" icon="el-icon-document"
@ -103,7 +96,12 @@
</el-table-column>
<el-table-column
prop="nodePath"
prop="projectName"
:label="$t('test_track.plan.plan_project')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
:label="$t('test_track.issue.issue')"
show-overflow-tooltip>
<template v-slot:default="scope">
@ -112,7 +110,6 @@
width="400"
trigger="hover">
<el-table border class="adjust-table" :data="scope.row.issuesContent" style="width: 100%">
<!-- <el-table-column prop="id" label="缺陷ID" show-overflow-tooltip/>-->
<el-table-column prop="title" :label="$t('test_track.issue.title')" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('test_track.issue.description')">
<template v-slot:default="scope">
@ -127,7 +124,6 @@
</el-popover>
</template>
</el-table-column>
<!-- <el-table-column prop="status" label="缺陷状态"/>-->
<el-table-column prop="platform" :label="$t('test_track.issue.platform')"/>
</el-table>
<el-button slot="reference" type="text">{{scope.row.issuesSize}}</el-button>
@ -262,7 +258,6 @@
currentPage: 1,
pageSize: 10,
total: 0,
// selectIds: new Set(),
selectRows: new Set(),
testPlan: {},
isReadOnly: false,
@ -369,7 +364,6 @@
})
}
}
// this.selectIds.clear();
this.selectRows.clear();
});
}
@ -380,7 +374,6 @@
},
refresh() {
this.condition = {components: TEST_CASE_CONFIGS};
// this.selectIds.clear();
this.selectRows.clear();
this.$emit('refresh');
},
@ -443,13 +436,6 @@
});
},
handleSelectAll(selection) {
// if (selection.length > 0) {
// this.tableData.forEach(item => {
// this.selectIds.add(item.id);
// });
// } else {
// this.selectIds.clear();
// }
if (selection.length > 0) {
if (selection.length === 1) {
this.selectRows.add(selection[0]);
@ -467,11 +453,6 @@
}
},
handleSelectionChange(selection, row) {
// if (this.selectIds.has(row.id)) {
// this.selectIds.delete(row.id);
// } else {
// this.selectIds.add(row.id);
// }
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);

View File

@ -15,6 +15,11 @@
:label="$t('test_track.module.module')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="projectName"
:label="$t('test_track.module.project_name')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="title"
:label="$t('test_track.module.title')"

View File

@ -50,6 +50,12 @@
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="projectName"
:label="$t('test_track.case.project_name')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="executorName"
:label="$t('test_track.plan_view.executor')">

View File

@ -13,6 +13,12 @@
:label="$t('test_track.module.module')"
>
</el-table-column>
<el-table-column
prop="projectName"
:label="$t('test_track.module.project_name')"
show-overflow-tooltip
>
</el-table-column>
<el-table-column
prop="caseCount"
:label="$t('test_track.plan_view.case_count')"

@ -1 +1 @@
Subproject commit 7e4d80cc2b870a8cac6dbb9fe6711ab6041faf6d
Subproject commit 0a375848d034d20eaf05caf11769e1c75c39235c

View File

@ -110,6 +110,7 @@ export default {
please_save: 'Please save first',
formatErr: 'Format Error',
id: 'ID',
cannot_be_null: 'not null ',
millisecond: 'ms',
please_upload: 'Please upload file',
reference_documentation: "Reference documentation",
@ -153,6 +154,22 @@ export default {
}
}
},
license:{
title: 'Authorization management',
corporation: 'corporation',
time: 'Authorization time',
product: 'product',
edition: 'edition',
licenseVersion: 'licenseVersion',
count: 'count',
valid_license: 'valid license',
show_license: 'show license',
valid_license_error: 'validate license error',
status: 'valid state',
valid: 'valid',
invalid: 'invalid',
expired: 'expired',
},
workspace: {
create: 'Create Workspace',
update: 'Update Workspace',
@ -414,7 +431,15 @@ export default {
copy: "Copy scenario",
delete: "Delete scenario",
disable: "Disable",
enable: "Enable"
enable: "Enable",
create_scenario: "Create scenario",
select_scenario: "Select scenario",
scenario_request: "Scenario/Request",
enable_disable: "Enable/Disable",
test_name: "Test Name",
reference: "Reference",
clone: "Copy",
cant_reference:'Historical test files, can be referenced after re-saving'
},
request: {
debug: "Debug",
@ -622,6 +647,7 @@ export default {
batch_move_case: 'Batch move',
batch_delete_case: 'Batch delete',
batch_unlink: 'Batch Unlink',
project_name: "Project",
import: {
import: "Import test case",
case_import: "Import test case",
@ -631,6 +657,7 @@ export default {
upload_limit_count: "Only one file can be uploaded at a time",
upload_limit_format: "Upload files can only be XLS, XLSX format!",
upload_limit_size: "Upload file size cannot exceed 20MB!",
upload_limit_other_size: "Upload file size cannot exceed",
success: "Import success",
importing: "Importing...",
},
@ -676,7 +703,8 @@ export default {
describe: "Describe",
status: "Status",
current_owner: "Current Owner",
creation_time: "Creation time"
creation_time: "Creation time",
project_name: "Project"
},
home: {
recent_test: "Recent test",

View File

@ -113,6 +113,7 @@ export default {
reference_documentation: "参考文档",
id: 'ID',
millisecond: '毫秒',
cannot_be_null: '不能为空',
date: {
select_date: '选择日期',
start_date: '开始日期',
@ -153,6 +154,22 @@ export default {
}
}
},
license:{
title: '授权管理',
corporation: '客户名称',
time: '授权时间',
product: '产品名称',
edition: '产品版本',
licenseVersion: '授权版本',
count: '授权数量',
valid_license: '授权验证',
show_license: '查看授权',
valid_license_error: '授权验证失败',
status: '授权状态',
valid: '有效',
invalid: '无效',
expired: '已过期',
},
workspace: {
create: '创建工作空间',
update: '修改工作空间',
@ -414,7 +431,15 @@ export default {
copy: "复制场景",
delete: "删除场景",
disable: "禁用",
enable: "启用"
enable: "启用",
create_scenario: "创建新场景",
select_scenario: "选择已有场景",
scenario_request: "场景/请求",
enable_disable: "启用/禁用",
test_name: "测试名称",
reference: "引用",
clone: "复制",
cant_reference: '历史测试文件,重新保存后才可被引用'
},
request: {
debug: "调试",
@ -505,7 +530,7 @@ export default {
get_provider_success: "获取成功",
check_registry_center: "获取失败请检查Registry Center",
form_description: "如果当前配置项无值,则取场景配置项的值",
}
},
},
api_import: {
label: "导入",
@ -625,12 +650,14 @@ export default {
batch_move_case: '批量移动用例',
batch_delete_case: '批量删除用例',
batch_unlink: '批量取消关联',
project_name: '所属项目',
import: {
import: "导入用例",
case_import: "导入测试用例",
download_template: "下载模版",
click_upload: "点击上传",
upload_limit: "只能上传xls/xlsx文件且不超过20M",
upload_limit_other_size: "上传文件大小不能超过",
upload_limit_count: "一次只能上传一个文件",
upload_limit_format: "上传文件只能是 xls、xlsx格式!",
upload_limit_size: "上传文件大小不能超过 20MB!",
@ -679,7 +706,8 @@ export default {
status: "状态",
describe: "描述",
current_owner: "处理人",
creation_time: "创建时间"
creation_time: "创建时间",
project_name: "所属项目"
},
home: {
recent_test: "最近测试",

View File

@ -108,6 +108,7 @@ export default {
formatErr: '格式錯誤',
please_save: '請先保存',
id: 'ID',
cannot_be_null: '不能为空',
millisecond: '毫秒',
reference_documentation: "參考文檔",
please_upload: '請上傳文件',
@ -151,6 +152,21 @@ export default {
}
}
},
license:{
title: '授權管理',
corporation: '客戶名稱',
time: '授權時間',
product: '產品名稱',
edition: '產品版本',
licenseVersion: '授權版本',
count: '授權數量',
valid_license: '授權验证',
show_license: '查看授權',
valid_license_error: '授權验证失败',
status: '授權状态',
expired: '已过期',
},
workspace: {
create: '創建工作空間',
update: '修改工作空間',
@ -413,7 +429,15 @@ export default {
copy: "複製場景",
delete: "删除場景",
disable: "禁用",
enable: "啟用"
enable: "啟用",
create_scenario: "創建新場景",
select_scenario: "選擇已有場景",
scenario_request: "場景/請求",
enable_disable: "啟用/禁用",
test_name: "測試名稱",
reference: "引用",
clone: "複製",
cant_reference: '歷史測試文件,重新保存後才可被引用'
},
request: {
debug: "調試",
@ -621,6 +645,7 @@ export default {
batch_move_case: '批量移動用例',
batch_delete_case: '批量刪除用例',
batch_unlink: '批量取消關聯',
project_name: '所屬項目',
import: {
import: "導入用例",
case_import: "導入測試用例",
@ -630,6 +655,7 @@ export default {
upload_limit_count: "一次只能上傳一個文件",
upload_limit_format: "上傳文件只能是 xls、xlsx格式!",
upload_limit_size: "上傳文件大小不能超過 20MB!",
upload_limit_other_size: "上傳文件大小不能超過",
success: "導入成功!",
importing: "導入中...",
},
@ -675,7 +701,8 @@ export default {
status: "狀態",
describe: "描述",
current_owner: "處理人",
creation_time: "創建時間"
creation_time: "創建時間",
project_name: "所屬項目"
},
home: {
recent_test: "最近測試",