Merge remote-tracking branch 'origin/master'
# Conflicts: # frontend/src/business/components/api/test/model/ScenarioModel.js
This commit is contained in:
commit
3994e346b5
|
@ -165,6 +165,30 @@
|
|||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_jdbc</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>7.4.1.jre8</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.2.14</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>19.7.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Zookeeper -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
|
@ -297,6 +321,31 @@
|
|||
<version>0.15.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.20</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<!--xpath不加这个依赖会报错-->
|
||||
<dependency>
|
||||
<groupId>jaxen</groupId>
|
||||
<artifactId>jaxen</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20171018</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -16,5 +16,6 @@ public class Scenario {
|
|||
private List<KeyValue> headers;
|
||||
private List<Request> requests;
|
||||
private DubboConfig dubboConfig;
|
||||
private List<DatabaseConfig> databaseConfigs;
|
||||
private Boolean enable;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -3,16 +3,20 @@ package io.metersphere.api.jmeter;
|
|||
import io.metersphere.api.service.APIReportService;
|
||||
import io.metersphere.api.service.APITestService;
|
||||
import io.metersphere.base.domain.ApiTestReport;
|
||||
import io.metersphere.base.domain.Notice;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.notice.service.MailService;
|
||||
import io.metersphere.notice.service.NoticeService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.assertions.AssertionResult;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListenerContext;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -113,9 +117,12 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
report = apiReportService.getRunningReport(testResult.getTestId());
|
||||
}
|
||||
apiReportService.complete(testResult, report);
|
||||
|
||||
queue.clear();
|
||||
super.teardownTest(context);
|
||||
NoticeService noticeService = CommonBeanFactory.getBean(NoticeService.class);
|
||||
List<Notice> notice = noticeService.queryNotice(testResult.getTestId());
|
||||
MailService mailService = CommonBeanFactory.getBean(MailService.class);
|
||||
mailService.sendHtml(report.getId(), notice, report.getStatus(), "api");
|
||||
}
|
||||
|
||||
private RequestResult getRequestResult(SampleResult result) {
|
||||
|
@ -170,7 +177,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
this.runMode = context.getParameter("runMode");
|
||||
this.debugReportId = context.getParameter("debugReportId");
|
||||
if (StringUtils.isBlank(this.runMode)) {
|
||||
this.runMode = ApiRunMode.RUN.name();
|
||||
this.runMode = ApiRunMode.RUN.name();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import io.metersphere.api.parse.JmeterDocumentParser;
|
|||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiTestFileMapper;
|
||||
import io.metersphere.base.mapper.ApiTestMapper;
|
||||
import io.metersphere.base.mapper.UserMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiTestMapper;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.constants.FileType;
|
||||
|
@ -23,9 +24,12 @@ import io.metersphere.controller.request.QueryScheduleRequest;
|
|||
import io.metersphere.dto.ScheduleDao;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.job.sechedule.ApiTestJob;
|
||||
import io.metersphere.notice.service.MailService;
|
||||
import io.metersphere.notice.service.NoticeService;
|
||||
import io.metersphere.service.FileService;
|
||||
import io.metersphere.service.QuotaService;
|
||||
import io.metersphere.service.ScheduleService;
|
||||
import io.metersphere.service.UserService;
|
||||
import io.metersphere.track.service.TestCaseService;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
|
@ -44,7 +48,8 @@ import java.util.stream.Collectors;
|
|||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class APITestService {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
@Resource
|
||||
private ApiTestMapper apiTestMapper;
|
||||
@Resource
|
||||
|
@ -61,6 +66,10 @@ public class APITestService {
|
|||
private ScheduleService scheduleService;
|
||||
@Resource
|
||||
private TestCaseService testCaseService;
|
||||
@Resource
|
||||
private MailService mailService;
|
||||
@Resource
|
||||
private NoticeService noticeService;
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
|
@ -214,8 +223,11 @@ public class APITestService {
|
|||
apiTest.setUserId(request.getUserId());
|
||||
}
|
||||
String reportId = apiReportService.create(apiTest, request.getTriggerMode());
|
||||
/*if (request.getTriggerMode().equals("SCHEDULE")) {
|
||||
List<Notice> notice = noticeService.queryNotice(request.getId());
|
||||
mailService.sendHtml(reportId,notice,"api");
|
||||
}*/
|
||||
changeStatus(request.getId(), APITestStatus.Running);
|
||||
|
||||
jMeterService.run(request.getId(), null, is);
|
||||
return reportId;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
@ -14,8 +13,9 @@ public class ApiTestEnvironmentWithBLOBs extends ApiTestEnvironment implements S
|
|||
|
||||
private String headers;
|
||||
|
||||
private String customData;
|
||||
private String config;
|
||||
|
||||
private String hosts;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -15,5 +15,9 @@ public class Notice implements Serializable {
|
|||
|
||||
private String enable;
|
||||
|
||||
private String[] names;
|
||||
|
||||
private String[] emails;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -7,8 +7,6 @@ import lombok.Data;
|
|||
public class TestPlan implements Serializable {
|
||||
private String id;
|
||||
|
||||
private String projectId;
|
||||
|
||||
private String workspaceId;
|
||||
|
||||
private String reportId;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs">
|
||||
<result column="variables" jdbcType="LONGVARCHAR" property="variables" />
|
||||
<result column="headers" jdbcType="LONGVARCHAR" property="headers" />
|
||||
<result column="custom_data" jdbcType="LONGVARCHAR" property="customData" />
|
||||
<result column="config" jdbcType="LONGVARCHAR" property="config" />
|
||||
<result column="hosts" jdbcType="LONGVARCHAR" property="hosts" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
|
@ -78,7 +78,7 @@
|
|||
id, `name`, project_id, protocol, socket, `domain`, port
|
||||
</sql>
|
||||
<sql id="Blob_Column_List">
|
||||
`variables`, headers, custom_data,hosts
|
||||
`variables`, headers, config, `hosts`
|
||||
</sql>
|
||||
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.ApiTestEnvironmentExample" resultMap="ResultMapWithBLOBs">
|
||||
select
|
||||
|
@ -132,11 +132,11 @@
|
|||
insert into api_test_environment (id, `name`, project_id,
|
||||
protocol, socket, `domain`,
|
||||
port, `variables`, headers,
|
||||
custom_data,hosts)
|
||||
config, `hosts`)
|
||||
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR},
|
||||
#{protocol,jdbcType=VARCHAR}, #{socket,jdbcType=VARCHAR}, #{domain,jdbcType=VARCHAR},
|
||||
#{port,jdbcType=INTEGER}, #{variables,jdbcType=LONGVARCHAR}, #{headers,jdbcType=LONGVARCHAR},
|
||||
#{customData,jdbcType=LONGVARCHAR},#{hosts,jdbcType=LONGVARCHAR})
|
||||
#{config,jdbcType=LONGVARCHAR}, #{hosts,jdbcType=LONGVARCHAR})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs">
|
||||
insert into api_test_environment
|
||||
|
@ -168,13 +168,12 @@
|
|||
<if test="headers != null">
|
||||
headers,
|
||||
</if>
|
||||
<if test="customData != null">
|
||||
custom_data,
|
||||
<if test="config != null">
|
||||
config,
|
||||
</if>
|
||||
<if test="hosts != null">
|
||||
hosts,
|
||||
`hosts`,
|
||||
</if>
|
||||
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
|
@ -204,8 +203,8 @@
|
|||
<if test="headers != null">
|
||||
#{headers,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="customData != null">
|
||||
#{customData,jdbcType=LONGVARCHAR},
|
||||
<if test="config != null">
|
||||
#{config,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="hosts != null">
|
||||
#{hosts,jdbcType=LONGVARCHAR},
|
||||
|
@ -248,11 +247,11 @@
|
|||
<if test="record.headers != null">
|
||||
headers = #{record.headers,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="record.customData != null">
|
||||
custom_data = #{record.customData,jdbcType=LONGVARCHAR},
|
||||
<if test="record.config != null">
|
||||
config = #{record.config,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="record.hosts != null">
|
||||
hosts = #{hosts,jdbcType=LONGVARCHAR},
|
||||
`hosts` = #{record.hosts,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
|
@ -270,8 +269,8 @@
|
|||
port = #{record.port,jdbcType=INTEGER},
|
||||
`variables` = #{record.variables,jdbcType=LONGVARCHAR},
|
||||
headers = #{record.headers,jdbcType=LONGVARCHAR},
|
||||
custom_data = #{record.customData,jdbcType=LONGVARCHAR},
|
||||
hosts = #{hosts,jdbcType=LONGVARCHAR}
|
||||
config = #{record.config,jdbcType=LONGVARCHAR},
|
||||
`hosts` = #{record.hosts,jdbcType=LONGVARCHAR}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -316,11 +315,11 @@
|
|||
<if test="headers != null">
|
||||
headers = #{headers,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="customData != null">
|
||||
custom_data = #{customData,jdbcType=LONGVARCHAR},
|
||||
<if test="config != null">
|
||||
config = #{config,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="hosts != null">
|
||||
hosts = #{hosts,jdbcType=LONGVARCHAR},
|
||||
`hosts` = #{hosts,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
|
@ -335,8 +334,8 @@
|
|||
port = #{port,jdbcType=INTEGER},
|
||||
`variables` = #{variables,jdbcType=LONGVARCHAR},
|
||||
headers = #{headers,jdbcType=LONGVARCHAR},
|
||||
custom_data = #{customData,jdbcType=LONGVARCHAR},
|
||||
hosts = #{hosts,jdbcType=LONGVARCHAR}
|
||||
config = #{config,jdbcType=LONGVARCHAR},
|
||||
`hosts` = #{hosts,jdbcType=LONGVARCHAR}
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.ApiTestEnvironment">
|
||||
|
|
|
@ -2,9 +2,10 @@ package io.metersphere.base.mapper;
|
|||
|
||||
import io.metersphere.base.domain.TestCaseNode;
|
||||
import io.metersphere.base.domain.TestCaseNodeExample;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TestCaseNodeMapper {
|
||||
long countByExample(TestCaseNodeExample example);
|
||||
|
||||
|
@ -14,6 +15,9 @@ public interface TestCaseNodeMapper {
|
|||
|
||||
int insert(TestCaseNode record);
|
||||
|
||||
int insertBatch(@Param("records") List<TestCaseNode> records);
|
||||
|
||||
|
||||
int insertSelective(TestCaseNode record);
|
||||
|
||||
List<TestCaseNode> selectByExample(TestCaseNodeExample example);
|
||||
|
|
|
@ -101,6 +101,19 @@
|
|||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</delete>
|
||||
|
||||
<insert id="insertBatch" parameterType="io.metersphere.base.domain.TestCaseNode">
|
||||
insert into test_case_node (id, project_id, name,
|
||||
parent_id, level, create_time,
|
||||
update_time)
|
||||
values
|
||||
<foreach collection="records" item="emp" separator=",">
|
||||
(#{emp.id,jdbcType=VARCHAR}, #{emp.projectId,jdbcType=VARCHAR}, #{emp.name,jdbcType=VARCHAR},
|
||||
#{emp.parentId,jdbcType=VARCHAR}, #{emp.level,jdbcType=INTEGER}, #{emp.createTime,jdbcType=BIGINT},
|
||||
#{emp.updateTime,jdbcType=BIGINT})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<insert id="insert" parameterType="io.metersphere.base.domain.TestCaseNode">
|
||||
insert into test_case_node (id, project_id, name,
|
||||
parent_id, level, create_time,
|
||||
|
@ -109,6 +122,7 @@
|
|||
#{parentId,jdbcType=VARCHAR}, #{level,jdbcType=INTEGER}, #{createTime,jdbcType=BIGINT},
|
||||
#{updateTime,jdbcType=BIGINT})
|
||||
</insert>
|
||||
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseNode">
|
||||
insert into test_case_node
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -57,7 +57,16 @@
|
|||
</include>
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="get" resultMap="BaseResultMap">
|
||||
SELECT r.*, t.name AS test_name, project.name AS project_name, user.name AS user_name
|
||||
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
|
||||
LEFT JOIN project ON project.id = t.project_id
|
||||
LEFT JOIN user ON user.id = r.user_id
|
||||
<where>
|
||||
r.id = #{id}
|
||||
</where>
|
||||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
<select id="list" resultMap="BaseResultMap">
|
||||
SELECT t.name AS test_name,
|
||||
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status, r.trigger_mode,
|
||||
|
@ -125,16 +134,7 @@
|
|||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
|
||||
<select id="get" resultMap="BaseResultMap">
|
||||
SELECT r.*, t.name AS test_name, project.name AS project_name, user.name AS user_name
|
||||
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
|
||||
LEFT JOIN project ON project.id = t.project_id
|
||||
LEFT JOIN user ON user.id = r.user_id
|
||||
<where>
|
||||
r.id = #{id}
|
||||
</where>
|
||||
ORDER BY r.update_time DESC
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectDashboardTests" resultType="io.metersphere.dto.DashboardTestDTO">
|
||||
SELECT min(create_time) AS date, count(api_test_report.id) AS count,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.base.mapper.ext;
|
||||
|
||||
public interface ExtSystemParameterMapper {
|
||||
String email();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="io.metersphere.base.mapper.ext.ExtSystemParameterMapper">
|
||||
|
||||
<select id="email" resultType="string">
|
||||
select param_value from system_parameter where param_key=#{smtp.account}
|
||||
</select>
|
||||
</mapper>
|
|
@ -224,6 +224,9 @@
|
|||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="request.projectId!=null">
|
||||
and test_case.project_id=#{request.projectId}
|
||||
</if>
|
||||
</where>
|
||||
<if test="request.orders != null and request.orders.size() > 0">
|
||||
order by
|
||||
|
|
|
@ -11,4 +11,6 @@ public interface ExtTestPlanMapper {
|
|||
List<TestPlanDTO> list(@Param("request") QueryTestPlanRequest params);
|
||||
|
||||
List<TestPlanDTOWithMetric> listRelate(@Param("request") QueryTestPlanRequest params);
|
||||
|
||||
List<TestPlanDTO> planList(@Param("request") QueryTestPlanRequest params);
|
||||
}
|
||||
|
|
|
@ -143,6 +143,18 @@
|
|||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
<select id="planList" resultMap="BaseResultMap"
|
||||
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
|
||||
SELECT * FROM TEST_PLAN p LEFT JOIN test_plan_project t ON t.test_plan_id=p.ID
|
||||
<where>
|
||||
<if test="request.workspaceId != null">
|
||||
AND p.workspace_id = #{request.workspaceId}
|
||||
</if>
|
||||
<if test="request.projectId != null">
|
||||
AND t.project_id = #{request.projectId}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="listRelate" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric">
|
||||
select test_plan.* from test_plan
|
||||
|
|
|
@ -16,4 +16,6 @@ public interface ExtUserMapper {
|
|||
|
||||
List<User> searchUser(String condition);
|
||||
|
||||
List<String> queryEmails(String[] names);
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,18 @@
|
|||
</where>
|
||||
order by u.update_time desc
|
||||
</select>
|
||||
<!--查询邮箱-->
|
||||
<select id="queryEmails" parameterType="java.lang.String" resultType="java.lang.String">
|
||||
SELECT
|
||||
email
|
||||
from user
|
||||
WHERE name IN
|
||||
<foreach collection="array" item="id" index="index"
|
||||
open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
|
||||
</select>
|
||||
|
||||
<!--修改密码-->
|
||||
<update id="updatePassword" parameterType="io.metersphere.base.domain.User">
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class TestCaseConstants {
|
||||
|
||||
public static final int MAX_NODE_DEPTH = 5;
|
||||
public static final int MAX_NODE_DEPTH = 8;
|
||||
|
||||
public enum Type {
|
||||
Functional("functional"), Performance("performance"), Aapi("api");
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.job.sechedule;
|
|||
|
||||
import io.metersphere.api.dto.SaveAPITestRequest;
|
||||
import io.metersphere.api.service.APITestService;
|
||||
import io.metersphere.notice.service.MailService;
|
||||
import io.metersphere.commons.constants.ReportTriggerMode;
|
||||
import io.metersphere.commons.constants.ScheduleGroup;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
|
@ -13,7 +14,7 @@ import org.quartz.TriggerKey;
|
|||
public class ApiTestJob extends MsScheduleJob {
|
||||
|
||||
private APITestService apiTestService;
|
||||
|
||||
private MailService mailService;
|
||||
public ApiTestJob() {
|
||||
apiTestService = (APITestService) CommonBeanFactory.getBean(APITestService.class);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package io.metersphere.notice.controller;
|
||||
|
||||
import io.metersphere.base.domain.Notice;
|
||||
import io.metersphere.notice.controller.request.NoticeRequest;
|
||||
import io.metersphere.notice.service.MailService;
|
||||
import io.metersphere.notice.service.NoticeService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("notice")
|
||||
public class NoticeController {
|
||||
@Resource
|
||||
private NoticeService noticeService;
|
||||
|
||||
@PostMapping("/save")
|
||||
public void saveNotice(@RequestBody NoticeRequest noticeRequest) {
|
||||
noticeService.saveNotice(noticeRequest);
|
||||
}
|
||||
|
||||
@GetMapping("/query/{testId}")
|
||||
public List<Notice> queryNotice(@PathVariable String testId) {
|
||||
return noticeService.queryNotice(testId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.notice.controller.request;
|
||||
|
||||
import io.metersphere.base.domain.Notice;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class NoticeRequest extends Notice {
|
||||
private String testId;
|
||||
private List<Notice> notices;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.metersphere.notice.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Mail {
|
||||
// 发送给谁
|
||||
private String to;
|
||||
|
||||
// 发送主题
|
||||
private String subject;
|
||||
|
||||
// 发送内容
|
||||
private String content;
|
||||
|
||||
// 附件地址
|
||||
private String filePath;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package io.metersphere.notice.service;
|
||||
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.base.domain.ApiTestReportDetail;
|
||||
import io.metersphere.base.domain.Schedule;
|
||||
import io.metersphere.base.mapper.ApiTestReportDetailMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiTestReportMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
|
||||
import io.metersphere.commons.constants.ScheduleGroup;
|
||||
import io.metersphere.dto.LoadTestDTO;
|
||||
import io.metersphere.service.ScheduleService;
|
||||
import io.metersphere.track.request.testplan.QueryTestPlanRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Service
|
||||
public class ApiAndPerformanceHelper {
|
||||
@Resource
|
||||
private ExtLoadTestMapper extLoadTestMapper;
|
||||
@Resource
|
||||
private ExtApiTestReportMapper extApiTestReportMapper;
|
||||
@Resource
|
||||
private ApiTestReportDetailMapper apiTestReportDetailMapper;
|
||||
@Resource
|
||||
private ScheduleService scheduleService;
|
||||
|
||||
public APIReportResult getApi(String reportId) {
|
||||
APIReportResult result = extApiTestReportMapper.get(reportId);
|
||||
ApiTestReportDetail detail = apiTestReportDetailMapper.selectByPrimaryKey(reportId);
|
||||
if (detail != null) {
|
||||
result.setContent(new String(detail.getContent(), StandardCharsets.UTF_8));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LoadTestDTO getPerformance(String testId) {
|
||||
QueryTestPlanRequest request = new QueryTestPlanRequest();
|
||||
request.setId(testId);
|
||||
List<LoadTestDTO> testDTOS = extLoadTestMapper.list(request);
|
||||
if (!CollectionUtils.isEmpty(testDTOS)) {
|
||||
LoadTestDTO loadTestDTO = testDTOS.get(0);
|
||||
Schedule schedule = scheduleService.getScheduleByResource(loadTestDTO.getId(), ScheduleGroup.PERFORMANCE_TEST.name());
|
||||
loadTestDTO.setSchedule(schedule);
|
||||
return loadTestDTO;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package io.metersphere.notice.service;
|
||||
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.base.domain.Notice;
|
||||
import io.metersphere.base.domain.SystemParameter;
|
||||
import io.metersphere.commons.constants.ParamConstants;
|
||||
import io.metersphere.commons.utils.EncryptUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.dto.LoadTestDTO;
|
||||
import io.metersphere.service.SystemParameterService;
|
||||
import io.metersphere.service.UserService;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@Service
|
||||
public class MailService {
|
||||
@Resource
|
||||
private ApiAndPerformanceHelper apiAndPerformanceHelper;
|
||||
@Resource
|
||||
private UserService userService;
|
||||
@Resource
|
||||
private SystemParameterService systemParameterService;
|
||||
|
||||
public void sendHtml(String id, List<Notice> notice, String status, String type) {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
List<SystemParameter> paramList = systemParameterService.getParamList(ParamConstants.Classify.MAIL.getValue());
|
||||
javaMailSender.setDefaultEncoding("UTF-8");
|
||||
javaMailSender.setProtocol("smtps");
|
||||
for (SystemParameter p : paramList) {
|
||||
if (p.getParamKey().equals("smtp.host")) {
|
||||
javaMailSender.setHost(p.getParamValue());
|
||||
}
|
||||
if (p.getParamKey().equals("smtp.port")) {
|
||||
javaMailSender.setPort(Integer.parseInt(p.getParamValue()));
|
||||
}
|
||||
if (p.getParamKey().equals("smtp.account")) {
|
||||
javaMailSender.setUsername(p.getParamValue());
|
||||
}
|
||||
if (p.getParamKey().equals("smtp.password")) {
|
||||
javaMailSender.setPassword(EncryptUtils.aesDecrypt(p.getParamValue()).toString());
|
||||
}
|
||||
}
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.smtp.starttls.required", "true");
|
||||
props.put("mail.smtp.timeout", "30000");
|
||||
props.put("mail.smtp.connectiontimeout", "5000");
|
||||
javaMailSender.setJavaMailProperties(props);
|
||||
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||
String testName = "";
|
||||
if (type.equals("api")) {
|
||||
APIReportResult reportResult = apiAndPerformanceHelper.getApi(id);
|
||||
testName = reportResult.getTestName();
|
||||
} else if (type.equals("performance")) {
|
||||
LoadTestDTO performanceResult = apiAndPerformanceHelper.getPerformance(id);
|
||||
testName = performanceResult.getName();
|
||||
}
|
||||
String html1 = "<!DOCTYPE html>\n" +
|
||||
"<html lang=\"en\">\n" +
|
||||
"<head>\n" +
|
||||
" <meta charset=\"UTF-8\">\n" +
|
||||
" <title>MeterSphere</title>\n" +
|
||||
"</head>\n" +
|
||||
"<body style=\"text-align: left\">\n" +
|
||||
" <div>\n" +
|
||||
" <h3>" + type + "定时任务结果通知</h3>\n" +
|
||||
" <p> 尊敬的用户:您好,您所执行的" + testName + "运行失败,请点击报告链接查看</p>\n" +
|
||||
" </div>\n" +
|
||||
"</body>\n" +
|
||||
"</html>";
|
||||
String html2 = "<!DOCTYPE html>\n" +
|
||||
"<html lang=\"en\">\n" +
|
||||
"<head>\n" +
|
||||
" <meta charset=\"UTF-8\">\n" +
|
||||
" <title>MeterSphere</title>\n" +
|
||||
"</head>\n" +
|
||||
"<body style=\"text-align: left\">\n" +
|
||||
" <div>\n" +
|
||||
" <h3>" + type + "定时任务结果通知</h3>\n" +
|
||||
" <p> 尊敬的用户:您好," + testName + "运行成功,请点击报告链接查看</p>\n" +
|
||||
" </div>\n" +
|
||||
"</body>\n" +
|
||||
"</html>";
|
||||
try {
|
||||
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
|
||||
helper.setFrom(javaMailSender.getUsername());
|
||||
helper.setSubject("MeterSphere定时任务结果通知");
|
||||
String users[] = {};
|
||||
List<String> successEmailList = new ArrayList<>();
|
||||
List<String> failEmailList = new ArrayList<>();
|
||||
if (notice.size() > 0) {
|
||||
for (Notice n : notice) {
|
||||
if (n.getEnable().equals("true") && n.getEvent().equals("执行成功")) {
|
||||
successEmailList = userService.queryEmail(n.getNames());
|
||||
}
|
||||
if (n.getEnable().equals("true") && n.getEvent().equals("执行失败")) {
|
||||
failEmailList = userService.queryEmail(n.getNames());
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
LogUtil.error("Recipient information is empty");
|
||||
}
|
||||
|
||||
if (status.equals("Success")) {
|
||||
users = successEmailList.toArray(new String[successEmailList.size()]);
|
||||
helper.setText(html2, true);
|
||||
} else {
|
||||
users = failEmailList.toArray(new String[failEmailList.size()]);
|
||||
helper.setText(html1, true);
|
||||
|
||||
}
|
||||
helper.setTo(users);
|
||||
|
||||
} catch (MessagingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
javaMailSender.send(mimeMessage);
|
||||
} catch (MailException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package io.metersphere.notice.service;
|
||||
|
||||
import io.metersphere.base.domain.Notice;
|
||||
import io.metersphere.base.domain.NoticeExample;
|
||||
import io.metersphere.base.mapper.NoticeMapper;
|
||||
import io.metersphere.notice.controller.request.NoticeRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class NoticeService {
|
||||
@Resource
|
||||
private NoticeMapper noticeMapper;
|
||||
|
||||
public void saveNotice(NoticeRequest noticeRequest) {
|
||||
Notice notice = new Notice();
|
||||
NoticeExample example = new NoticeExample();
|
||||
example.createCriteria().andTestIdEqualTo(noticeRequest.getTestId());
|
||||
List<Notice> notices = noticeMapper.selectByExample(example);
|
||||
if (notices.size() > 0) {
|
||||
noticeMapper.deleteByExample(example);
|
||||
noticeRequest.getNotices().forEach(n -> {
|
||||
if (n.getNames().length > 0) {
|
||||
for (String x : n.getNames()) {
|
||||
notice.setEvent(n.getEvent());
|
||||
notice.setEmail(n.getEmail());
|
||||
notice.setEnable(n.getEnable());
|
||||
notice.setTestId(noticeRequest.getTestId());
|
||||
notice.setName(x);
|
||||
noticeMapper.insert(notice);
|
||||
}
|
||||
} else {
|
||||
notice.setEvent(n.getEvent());
|
||||
notice.setEmail(n.getEmail());
|
||||
notice.setEnable(n.getEnable());
|
||||
notice.setTestId(noticeRequest.getTestId());
|
||||
notice.setName("");
|
||||
noticeMapper.insert(notice);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
noticeRequest.getNotices().forEach(n -> {
|
||||
if (n.getNames().length > 0) {
|
||||
for (String x : n.getNames()) {
|
||||
notice.setEvent(n.getEvent());
|
||||
notice.setEmail(n.getEmail());
|
||||
notice.setEnable(n.getEnable());
|
||||
notice.setTestId(noticeRequest.getTestId());
|
||||
notice.setName(x);
|
||||
noticeMapper.insert(notice);
|
||||
}
|
||||
} else {
|
||||
notice.setEvent(n.getEvent());
|
||||
notice.setEmail(n.getEmail());
|
||||
notice.setEnable(n.getEnable());
|
||||
notice.setTestId(noticeRequest.getTestId());
|
||||
notice.setName("");
|
||||
noticeMapper.insert(notice);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<Notice> queryNotice(String id) {
|
||||
NoticeExample example = new NoticeExample();
|
||||
example.createCriteria().andTestIdEqualTo(id);
|
||||
List<Notice> notices = noticeMapper.selectByExample(example);
|
||||
List<Notice> notice = new ArrayList<>();
|
||||
List<String> success = new ArrayList<>();
|
||||
List<String> fail = new ArrayList<>();
|
||||
String[] successArray = new String[success.size()];
|
||||
String[] failArray = new String[fail.size()];
|
||||
Notice notice1 = new Notice();
|
||||
Notice notice2 = new Notice();
|
||||
if (notices.size() > 0) {
|
||||
for (Notice n : notices) {
|
||||
if (n.getEvent().equals("执行成功")) {
|
||||
success.add(n.getName());
|
||||
notice1.setEnable(n.getEnable());
|
||||
notice1.setTestId(id);
|
||||
notice1.setEvent(n.getEvent());
|
||||
notice1.setEmail(n.getEmail());
|
||||
}
|
||||
if (n.getEvent().equals("执行失败")) {
|
||||
fail.add(n.getName());
|
||||
notice2.setEnable(n.getEnable());
|
||||
notice2.setTestId(id);
|
||||
notice2.setEvent(n.getEvent());
|
||||
notice2.setEmail(n.getEmail());
|
||||
}
|
||||
}
|
||||
successArray = success.toArray(new String[success.size()]);
|
||||
failArray = fail.toArray(new String[fail.size()]);
|
||||
notice1.setNames(successArray);
|
||||
notice2.setNames(failArray);
|
||||
notice.add(notice1);
|
||||
notice.add(notice2);
|
||||
}
|
||||
return notice;
|
||||
}
|
||||
|
||||
}
|
|
@ -439,6 +439,14 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
item.appendChild(ele.getOwnerDocument().createTextNode(context.getProperty("timeout").toString()));
|
||||
}
|
||||
}
|
||||
// 增加一个response_timeout,避免目标网站不反回结果导致测试不能结束
|
||||
if (item instanceof Element && nodeNameEquals(item, STRING_PROP)
|
||||
&& StringUtils.equals(((Element) item).getAttribute("name"), "HTTPSampler.response_timeout")) {
|
||||
if (context.getProperty("responseTimeout") != null) {
|
||||
removeChildren(item);
|
||||
item.appendChild(ele.getOwnerDocument().createTextNode(context.getProperty("responseTimeout").toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import io.metersphere.dto.LoadTestDTO;
|
|||
import io.metersphere.dto.ScheduleDao;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.job.sechedule.PerformanceTestJob;
|
||||
import io.metersphere.notice.service.MailService;
|
||||
import io.metersphere.notice.service.NoticeService;
|
||||
import io.metersphere.performance.engine.Engine;
|
||||
import io.metersphere.performance.engine.EngineFactory;
|
||||
import io.metersphere.service.FileService;
|
||||
|
@ -80,6 +82,10 @@ public class PerformanceTestService {
|
|||
private TestCaseMapper testCaseMapper;
|
||||
@Resource
|
||||
private TestCaseService testCaseService;
|
||||
@Resource
|
||||
private NoticeService noticeService;
|
||||
@Resource
|
||||
private MailService mailService;
|
||||
|
||||
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
|
@ -231,7 +237,10 @@ public class PerformanceTestService {
|
|||
}
|
||||
|
||||
startEngine(loadTest, engine, request.getTriggerMode());
|
||||
|
||||
if (request.getTriggerMode().equals("SCHEDULE")) {
|
||||
List<Notice> notice = noticeService.queryNotice(request.getId());
|
||||
mailService.sendHtml(engine.getReportId(), notice, "success", "performance");
|
||||
}
|
||||
return engine.getReportId();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.service;
|
|||
import io.metersphere.base.domain.SystemParameter;
|
||||
import io.metersphere.base.domain.SystemParameterExample;
|
||||
import io.metersphere.base.mapper.SystemParameterMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtSystemParameterMapper;
|
||||
import io.metersphere.commons.constants.ParamConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.EncryptUtils;
|
||||
|
@ -24,7 +25,12 @@ public class SystemParameterService {
|
|||
|
||||
@Resource
|
||||
private SystemParameterMapper systemParameterMapper;
|
||||
@Resource
|
||||
private ExtSystemParameterMapper extSystemParameterMapper;
|
||||
|
||||
public String searchEmail(){
|
||||
return extSystemParameterMapper.email();
|
||||
}
|
||||
public String getSystemLanguage() {
|
||||
String result = StringUtils.EMPTY;
|
||||
SystemParameterExample example = new SystemParameterExample();
|
||||
|
|
|
@ -61,6 +61,9 @@ public class UserService {
|
|||
@Resource
|
||||
private WorkspaceService workspaceService;
|
||||
|
||||
public List<String> queryEmail(String[] names){
|
||||
return extUserMapper.queryEmails(names);
|
||||
}
|
||||
public UserDTO insert(UserRequest user) {
|
||||
checkUserParam(user);
|
||||
//
|
||||
|
|
|
@ -99,10 +99,10 @@ public class TestCaseController {
|
|||
return testCaseService.deleteTestCase(testCaseId);
|
||||
}
|
||||
|
||||
@PostMapping("/import/{projectId}")
|
||||
@PostMapping("/import/{projectId}/{userId}")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId) throws NoSuchFieldException {
|
||||
return testCaseService.testCaseImport(file, projectId);
|
||||
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId,@PathVariable String userId) throws NoSuchFieldException {
|
||||
return testCaseService.testCaseImport(file, projectId,userId);
|
||||
}
|
||||
|
||||
@GetMapping("/export/template")
|
||||
|
@ -110,6 +110,11 @@ public class TestCaseController {
|
|||
public void testCaseTemplateExport(HttpServletResponse response) {
|
||||
testCaseService.testCaseTemplateExport(response);
|
||||
}
|
||||
@GetMapping("/export/xmindTemplate")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public void xmindTemplate(HttpServletResponse response) {
|
||||
testCaseService.testCaseXmindTemplateExport(response);
|
||||
}
|
||||
|
||||
@PostMapping("/export/testcase")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class TestPlanController {
|
|||
QueryTestPlanRequest request = new QueryTestPlanRequest();
|
||||
request.setWorkspaceId(workspaceId);
|
||||
request.setProjectId(projectId);
|
||||
return testPlanService.listTestPlan(request);
|
||||
return testPlanService.listTestPlanByProject(request);
|
||||
}
|
||||
|
||||
@PostMapping("/list/all")
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,4 +21,6 @@ public class QueryTestPlanRequest extends TestPlan {
|
|||
private Map<String, List<String>> filters;
|
||||
|
||||
private Map<String, Object> combine;
|
||||
|
||||
private String projectId;
|
||||
}
|
||||
|
|
|
@ -12,4 +12,5 @@ import java.util.List;
|
|||
public class TestCaseBatchRequest extends TestCaseWithBLOBs {
|
||||
private List<String> ids;
|
||||
private List<OrderRequest> orders;
|
||||
private String projectId;
|
||||
}
|
||||
|
|
|
@ -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,17 @@ public class IssuesService {
|
|||
MSException.throwException("未关联Tapd 项目ID");
|
||||
}
|
||||
|
||||
List<String> tapdUsers = issuesRequest.getTapdUsers();
|
||||
String usersStr = String.join(";", tapdUsers);
|
||||
|
||||
String username = SessionUtils.getUser().getName();
|
||||
|
||||
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||
paramMap.add("title", issuesRequest.getTitle());
|
||||
paramMap.add("workspace_id", tapdId);
|
||||
paramMap.add("description", issuesRequest.getContent());
|
||||
paramMap.add("reporter", username);
|
||||
paramMap.add("current_owner", usersStr);
|
||||
|
||||
ResultHolder result = call(url, HttpMethod.POST, paramMap);
|
||||
|
||||
|
@ -535,4 +544,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import io.metersphere.i18n.Translator;
|
|||
import io.metersphere.track.dto.TestCaseDTO;
|
||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
|
||||
import io.metersphere.xmind.XmindToTestCaseParser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.session.ExecutorType;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
|
@ -38,6 +39,8 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -67,9 +70,6 @@ public class TestCaseService {
|
|||
@Resource
|
||||
TestCaseNodeService testCaseNodeService;
|
||||
|
||||
@Resource
|
||||
UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
UserRoleMapper userRoleMapper;
|
||||
|
||||
|
@ -236,10 +236,10 @@ public class TestCaseService {
|
|||
return projectMapper.selectByPrimaryKey(testCaseWithBLOBs.getProjectId());
|
||||
}
|
||||
|
||||
public ExcelResponse testCaseImport(MultipartFile file, String projectId) {
|
||||
|
||||
public ExcelResponse testCaseImport(MultipartFile multipartFile, String projectId, String userId) {
|
||||
|
||||
ExcelResponse excelResponse = new ExcelResponse();
|
||||
|
||||
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest();
|
||||
queryTestCaseRequest.setProjectId(projectId);
|
||||
|
@ -247,25 +247,44 @@ public class TestCaseService {
|
|||
Set<String> testCaseNames = testCases.stream()
|
||||
.map(TestCase::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
UserRoleExample userRoleExample = new UserRoleExample();
|
||||
userRoleExample.createCriteria()
|
||||
.andRoleIdIn(Arrays.asList(RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER))
|
||||
.andSourceIdEqualTo(currentWorkspaceId);
|
||||
|
||||
Set<String> userIds = userRoleMapper.selectByExample(userRoleExample).stream().map(UserRole::getUserId).collect(Collectors.toSet());
|
||||
|
||||
EasyExcelListener easyExcelListener = null;
|
||||
List<ExcelErrData<TestCaseExcelData>> errList = null;
|
||||
try {
|
||||
easyExcelListener = new TestCaseDataListener(this, projectId, testCaseNames, userIds);
|
||||
EasyExcelFactory.read(file.getInputStream(), TestCaseExcelData.class, easyExcelListener).sheet().doRead();
|
||||
errList = easyExcelListener.getErrList();
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException(e.getMessage());
|
||||
} finally {
|
||||
easyExcelListener.close();
|
||||
|
||||
if (multipartFile.getOriginalFilename().endsWith(".xmind")) {
|
||||
try {
|
||||
errList = new ArrayList<>();
|
||||
String processLog = new XmindToTestCaseParser(this, userId, projectId, testCaseNames).importXmind(multipartFile);
|
||||
if (!StringUtils.isEmpty(processLog)) {
|
||||
excelResponse.setSuccess(false);
|
||||
ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail")+":"+ processLog);
|
||||
errList.add(excelErrData);
|
||||
excelResponse.setErrList(errList);
|
||||
} else {
|
||||
excelResponse.setSuccess(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
UserRoleExample userRoleExample = new UserRoleExample();
|
||||
userRoleExample.createCriteria()
|
||||
.andRoleIdIn(Arrays.asList(RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER))
|
||||
.andSourceIdEqualTo(currentWorkspaceId);
|
||||
|
||||
Set<String> userIds = userRoleMapper.selectByExample(userRoleExample).stream().map(UserRole::getUserId).collect(Collectors.toSet());
|
||||
|
||||
EasyExcelListener easyExcelListener = null;
|
||||
try {
|
||||
easyExcelListener = new TestCaseDataListener(this, projectId, testCaseNames, userIds);
|
||||
EasyExcelFactory.read(multipartFile.getInputStream(), TestCaseExcelData.class, easyExcelListener).sheet().doRead();
|
||||
errList = easyExcelListener.getErrList();
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException(e.getMessage());
|
||||
} finally {
|
||||
easyExcelListener.close();
|
||||
}
|
||||
|
||||
}
|
||||
//如果包含错误信息就导出错误信息
|
||||
if (!errList.isEmpty()) {
|
||||
|
@ -309,6 +328,34 @@ public class TestCaseService {
|
|||
}
|
||||
}
|
||||
|
||||
public void download(HttpServletResponse res) throws IOException {
|
||||
// 发送给客户端的数据
|
||||
byte[] buff = new byte[1024];
|
||||
try (OutputStream outputStream = res.getOutputStream();
|
||||
BufferedInputStream bis = new BufferedInputStream(this.getClass().getResourceAsStream("/template/testcase.xmind"));) {
|
||||
int i = bis.read(buff);
|
||||
while (i != -1) {
|
||||
outputStream.write(buff, 0, buff.length);
|
||||
outputStream.flush();
|
||||
i = bis.read(buff);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogUtil.error(ex.getMessage());
|
||||
MSException.throwException("下载思维导图模版失败");
|
||||
}
|
||||
}
|
||||
|
||||
public void testCaseXmindTemplateExport(HttpServletResponse response) {
|
||||
try {
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("思维导图用例模版", "UTF-8") + ".xmind");
|
||||
download(response);
|
||||
} catch (Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private List<TestCaseExcelData> generateExportTemplate() {
|
||||
List<TestCaseExcelData> list = new ArrayList<>();
|
||||
StringBuilder path = new StringBuilder("");
|
||||
|
@ -406,18 +453,18 @@ public class TestCaseService {
|
|||
} else if (t.getMethod().equals("auto") && t.getType().equals("api")) {
|
||||
data.setStepDesc("");
|
||||
data.setStepResult("");
|
||||
if(t.getTestId().equals("other")){
|
||||
if (t.getTestId().equals("other")) {
|
||||
data.setRemark(t.getOtherTestName());
|
||||
}else{
|
||||
} else {
|
||||
data.setRemark(t.getApiName());
|
||||
}
|
||||
|
||||
} else if (t.getMethod().equals("auto") && t.getType().equals("performance")) {
|
||||
data.setStepDesc("");
|
||||
data.setStepResult("");
|
||||
if(t.getTestId().equals("other")){
|
||||
if (t.getTestId().equals("other")) {
|
||||
data.setRemark(t.getOtherTestName());
|
||||
}else{
|
||||
} else {
|
||||
data.setRemark(t.getPerformName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,10 @@ public class TestPlanService {
|
|||
return extTestPlanMapper.list(request);
|
||||
}
|
||||
|
||||
public List<TestPlanDTO> listTestPlanByProject(QueryTestPlanRequest request) {
|
||||
return extTestPlanMapper.planList(request);
|
||||
}
|
||||
|
||||
public void testPlanRelevance(PlanCaseRelevanceRequest request) {
|
||||
|
||||
List<String> testCaseIds = request.getTestCaseIds();
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
package io.metersphere.xmind;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
import io.metersphere.commons.constants.TestCaseConstants;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.excel.domain.TestCaseExcelData;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.track.service.TestCaseService;
|
||||
import io.metersphere.xmind.parser.XmindParser;
|
||||
import io.metersphere.xmind.parser.domain.Attached;
|
||||
import io.metersphere.xmind.parser.domain.JsonRootBean;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 数据转换
|
||||
* 1 解析Xmind文件 XmindParser.parseJson
|
||||
* 2 解析后的JSON 转成测试用例
|
||||
*/
|
||||
public class XmindToTestCaseParser {
|
||||
|
||||
private TestCaseService testCaseService;
|
||||
private String maintainer;
|
||||
private String projectId;
|
||||
private StringBuffer process; // 过程校验记录
|
||||
private Set<String> testCaseNames;
|
||||
|
||||
public XmindToTestCaseParser(TestCaseService testCaseService, String userId, String projectId, Set<String> testCaseNames) {
|
||||
this.testCaseService = testCaseService;
|
||||
this.maintainer = userId;
|
||||
this.projectId = projectId;
|
||||
this.testCaseNames = testCaseNames;
|
||||
testCaseWithBLOBs = new LinkedList<>();
|
||||
xmindDataList = new ArrayList<>();
|
||||
process = new StringBuffer();
|
||||
}
|
||||
|
||||
// 案例详情
|
||||
private List<TestCaseWithBLOBs> testCaseWithBLOBs;
|
||||
// 用于重复对比
|
||||
protected List<TestCaseExcelData> xmindDataList;
|
||||
|
||||
// 递归处理案例数据
|
||||
private void makeXmind(StringBuffer processBuffer, Attached parent, int level, String nodePath, List<Attached> attacheds) {
|
||||
for (Attached item : attacheds) {
|
||||
if (isBlack(item.getTitle(), "(?:tc|tc)")) { // 用例
|
||||
item.setParent(parent);
|
||||
this.newTestCase(item.getTitle(), parent.getPath(), item.getChildren() != null ? item.getChildren().getAttached() : null);
|
||||
} else {
|
||||
nodePath = parent.getPath() + "/" + item.getTitle();
|
||||
item.setPath(nodePath);
|
||||
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) {
|
||||
item.setParent(parent);
|
||||
makeXmind(processBuffer, item, level + 1, nodePath, item.getChildren().getAttached());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBlack(String str, String regex) {
|
||||
// regex = "(?:tc:|tc:)"
|
||||
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex))
|
||||
return false;
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
Matcher result = pattern.matcher(str);
|
||||
return result.find();
|
||||
}
|
||||
|
||||
private String replace(String str, String regex) {
|
||||
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex))
|
||||
return str;
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
Matcher result = pattern.matcher(str);
|
||||
str = result.replaceAll("");
|
||||
return str;
|
||||
}
|
||||
|
||||
// 获取步骤数据
|
||||
public String getSteps(List<Attached> attacheds) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for (int i = 0; i < attacheds.size(); i++) {
|
||||
// 保持插入顺序,判断用例是否有相同的steps
|
||||
JSONObject step = new JSONObject(true);
|
||||
step.put("num", i + 1);
|
||||
step.put("desc", attacheds.get(i).getTitle());
|
||||
if (attacheds.get(i).getChildren() != null && !attacheds.get(i).getChildren().getAttached().isEmpty()) {
|
||||
step.put("result", attacheds.get(i).getChildren().getAttached().get(0).getTitle());
|
||||
}
|
||||
jsonArray.add(step);
|
||||
}
|
||||
return jsonArray.toJSONString();
|
||||
}
|
||||
|
||||
// 初始化一个用例
|
||||
private void newTestCase(String title, String nodePath, List<Attached> attacheds) {
|
||||
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
|
||||
testCase.setProjectId(projectId);
|
||||
testCase.setMaintainer(maintainer);
|
||||
testCase.setPriority("P0");
|
||||
testCase.setMethod("manual");
|
||||
testCase.setType("functional");
|
||||
|
||||
String tc = title.replace(":", ":");
|
||||
String tcArr[] = tc.split(":");
|
||||
if (tcArr.length != 2) {
|
||||
process.append(Translator.get("test_case_name") + "【 " + title + " 】" + Translator.get("incorrect_format"));
|
||||
return;
|
||||
}
|
||||
// 用例名称
|
||||
testCase.setName(this.replace(tcArr[1], "tc:|tc:|tc"));
|
||||
|
||||
if (!nodePath.startsWith("/")) {
|
||||
nodePath = "/" + nodePath;
|
||||
}
|
||||
if (nodePath.endsWith("/")) {
|
||||
nodePath = nodePath.substring(0, nodePath.length() - 1);
|
||||
}
|
||||
testCase.setNodePath(nodePath);
|
||||
|
||||
// 用例等级和用例性质处理
|
||||
if (tcArr[0].indexOf("-") != -1) {
|
||||
String otArr[] = tcArr[0].split("-");
|
||||
for (int i = 0; i < otArr.length; i++) {
|
||||
if (otArr[i].startsWith("P") || otArr[i].startsWith("p")) {
|
||||
testCase.setPriority(otArr[i].toUpperCase());
|
||||
} else if (otArr[i].endsWith("功能测试")) {
|
||||
testCase.setType("functional");
|
||||
} else if (otArr[i].endsWith("性能测试")) {
|
||||
testCase.setType("performance");
|
||||
} else if (otArr[i].endsWith("接口测试")) {
|
||||
testCase.setType("api");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 测试步骤处理
|
||||
List<Attached> steps = new LinkedList<>();
|
||||
if (attacheds != null && !attacheds.isEmpty()) {
|
||||
attacheds.forEach(item -> {
|
||||
if (isBlack(item.getTitle(), "(?:pc:|pc:)")) {
|
||||
testCase.setPrerequisite(replace(item.getTitle(), "(?:pc:|pc:)"));
|
||||
} else if (isBlack(item.getTitle(), "(?:rc:|rc:)")) {
|
||||
testCase.setRemark(replace(item.getTitle(), "(?:rc:|rc:)"));
|
||||
} else {
|
||||
steps.add(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!steps.isEmpty()) {
|
||||
testCase.setSteps(this.getSteps(steps));
|
||||
} else {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
// 保持插入顺序,判断用例是否有相同的steps
|
||||
JSONObject step = new JSONObject(true);
|
||||
step.put("num", 1);
|
||||
step.put("desc", "");
|
||||
step.put("result", "");
|
||||
jsonArray.add(step);
|
||||
testCase.setSteps(jsonArray.toJSONString());
|
||||
}
|
||||
TestCaseExcelData compartData = new TestCaseExcelData();
|
||||
BeanUtils.copyBean(compartData, testCase);
|
||||
if (xmindDataList.contains(compartData)) {
|
||||
process.append(Translator.get("test_case_already_exists_excel") + ":" + testCase.getName() + "; ");
|
||||
} else if (validate(testCase)) {
|
||||
testCase.setId(UUID.randomUUID().toString());
|
||||
testCase.setCreateTime(System.currentTimeMillis());
|
||||
testCase.setUpdateTime(System.currentTimeMillis());
|
||||
testCaseWithBLOBs.add(testCase);
|
||||
}
|
||||
xmindDataList.add(compartData);
|
||||
}
|
||||
|
||||
//获取流文件
|
||||
private static void inputStreamToFile(InputStream ins, File file) {
|
||||
try (OutputStream os = new FileOutputStream(file);) {
|
||||
int bytesRead = 0;
|
||||
byte[] buffer = new byte[8192];
|
||||
while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MultipartFile 转 File
|
||||
*
|
||||
* @param file
|
||||
* @throws Exception
|
||||
*/
|
||||
private File multipartFileToFile(MultipartFile file) throws Exception {
|
||||
if (file != null && file.getSize() > 0) {
|
||||
try (InputStream ins = file.getInputStream();) {
|
||||
File toFile = new File(file.getOriginalFilename());
|
||||
inputStreamToFile(ins, toFile);
|
||||
return toFile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public boolean validate(TestCaseWithBLOBs data) {
|
||||
String nodePath = data.getNodePath();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
if (nodePath != null) {
|
||||
String[] nodes = nodePath.split("/");
|
||||
if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
|
||||
stringBuilder.append(Translator.get("test_case_node_level_tip") +
|
||||
TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level") + "; ");
|
||||
}
|
||||
for (int i = 0; i < nodes.length; i++) {
|
||||
if (i != 0 && org.apache.commons.lang3.StringUtils.equals(nodes[i].trim(), "")) {
|
||||
stringBuilder.append(Translator.get("module_not_null") + "; ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (org.apache.commons.lang3.StringUtils.equals(data.getType(), TestCaseConstants.Type.Functional.getValue()) && org.apache.commons.lang3.StringUtils.equals(data.getMethod(), TestCaseConstants.Method.Auto.getValue())) {
|
||||
stringBuilder.append(Translator.get("functional_method_tip") + "; ");
|
||||
}
|
||||
|
||||
if (testCaseNames.contains(data.getName())) {
|
||||
boolean dbExist = testCaseService.exist(data);
|
||||
boolean excelExist = false;
|
||||
|
||||
if (dbExist) {
|
||||
// db exist
|
||||
stringBuilder.append(Translator.get("test_case_already_exists_excel") + ":" + data.getName() + "; ");
|
||||
}
|
||||
|
||||
} else {
|
||||
testCaseNames.add(data.getName());
|
||||
}
|
||||
if (!StringUtils.isEmpty(stringBuilder.toString())) {
|
||||
process.append(stringBuilder.toString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 导入思维导图处理
|
||||
public String importXmind(MultipartFile multipartFile) {
|
||||
StringBuffer processBuffer = new StringBuffer();
|
||||
File file = null;
|
||||
try {
|
||||
file = multipartFileToFile(multipartFile);
|
||||
if (file == null || !file.exists())
|
||||
return Translator.get("incorrect_format");
|
||||
|
||||
// 获取思维导图内容
|
||||
String content = XmindParser.parseJson(file);
|
||||
if (StringUtils.isEmpty(content) || content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length == 1) {
|
||||
return Translator.get("import_xmind_not_found");
|
||||
}
|
||||
if (!StringUtils.isEmpty(content) && content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length > 500) {
|
||||
return Translator.get("import_xmind_count_error");
|
||||
}
|
||||
JsonRootBean root = JSON.parseObject(content, JsonRootBean.class);
|
||||
|
||||
if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) {
|
||||
// 判断是模块还是用例
|
||||
for (Attached item : root.getRootTopic().getChildren().getAttached()) {
|
||||
if (isBlack(item.getTitle(), "(?:tc:|tc:|tc)")) { // 用例
|
||||
return replace(item.getTitle(), "(?:tc:|tc:|tc)") + ":" + Translator.get("test_case_create_module_fail");
|
||||
} else {
|
||||
item.setPath(item.getTitle());
|
||||
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) {
|
||||
item.setPath(item.getTitle());
|
||||
makeXmind(processBuffer, item, 1, item.getPath(), item.getChildren().getAttached());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (StringUtils.isEmpty(process.toString()) && !testCaseWithBLOBs.isEmpty()) {
|
||||
testCaseService.saveImportData(testCaseWithBLOBs, projectId);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
processBuffer.append(Translator.get("incorrect_format"));
|
||||
LogUtil.error(ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
if (file != null)
|
||||
file.delete();
|
||||
testCaseWithBLOBs.clear();
|
||||
}
|
||||
return process.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package io.metersphere.xmind.parser;
|
||||
|
||||
import org.dom4j.*;
|
||||
import org.json.JSONObject;
|
||||
import org.json.XML;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class XmindLegacy {
|
||||
|
||||
/**
|
||||
* 返回content.xml和comments.xml合并后的json
|
||||
*
|
||||
* @param xmlContent
|
||||
* @param xmlComments
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws DocumentException
|
||||
*/
|
||||
public static String getContent(String xmlContent, String xmlComments) throws IOException, DocumentException {
|
||||
// 删除content.xml里面不能识别的字符串
|
||||
xmlContent = xmlContent.replace("xmlns=\"urn:xmind:xmap:xmlns:content:2.0\"", "");
|
||||
xmlContent = xmlContent.replace("xmlns:fo=\"http://www.w3.org/1999/XSL/Format\"", "");
|
||||
// 删除<topic>节点
|
||||
xmlContent = xmlContent.replace("<topics type=\"attached\">", "");
|
||||
xmlContent = xmlContent.replace("</topics>", "");
|
||||
|
||||
// 去除title中svg:width属性
|
||||
xmlContent = xmlContent.replaceAll("<title svg:width=\"[0-9]*\">", "<title>");
|
||||
|
||||
Document document = DocumentHelper.parseText(xmlContent);// 读取XML文件,获得document对象
|
||||
Element root = document.getRootElement();
|
||||
List<Node> topics = root.selectNodes("//topic");
|
||||
|
||||
if (xmlComments != null) {
|
||||
// 删除comments.xml里面不能识别的字符串
|
||||
xmlComments = xmlComments.replace("xmlns=\"urn:xmind:xmap:xmlns:comments:2.0\"", "");
|
||||
|
||||
// 添加评论到content中
|
||||
Document commentDocument = DocumentHelper.parseText(xmlComments);
|
||||
List<Node> commentsList = commentDocument.selectNodes("//comment");
|
||||
|
||||
for (Node topic : topics) {
|
||||
for (Node commentNode : commentsList) {
|
||||
Element commentElement = (Element) commentNode;
|
||||
Element topicElement = (Element) topic;
|
||||
if (topicElement.attribute("id").getValue()
|
||||
.equals(commentElement.attribute("object-id").getValue())) {
|
||||
Element comment = topicElement.addElement("comments");
|
||||
comment.addAttribute("creationTime", commentElement.attribute("time").getValue());
|
||||
comment.addAttribute("author", commentElement.attribute("author").getValue());
|
||||
comment.addAttribute("content", commentElement.element("content").getText());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 第一个topic转换为json中的rootTopic
|
||||
Node rootTopic = root.selectSingleNode("/xmap-content/sheet/topic");
|
||||
rootTopic.setName("rootTopic");
|
||||
|
||||
// 将xml中topic节点转换为attached节点
|
||||
List<Node> topicList = rootTopic.selectNodes("//topic");
|
||||
|
||||
for (Node node : topicList) {
|
||||
node.setName("attached");
|
||||
}
|
||||
// 选取第一个sheet
|
||||
Element sheet = root.elements("sheet").get(0);
|
||||
String res = sheet.asXML();
|
||||
// 将xml转为json
|
||||
JSONObject xmlJSONObj = XML.toJSONObject(res);
|
||||
JSONObject jsonObject = xmlJSONObj.getJSONObject("sheet");
|
||||
// 设置缩进
|
||||
return jsonObject.toString(4);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package io.metersphere.xmind.parser;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.xmind.parser.domain.JsonRootBean;
|
||||
import org.apache.commons.compress.archivers.ArchiveException;
|
||||
import org.dom4j.DocumentException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Description 解析主体
|
||||
*/
|
||||
public class XmindParser {
|
||||
public static final String xmindZenJson = "content.json";
|
||||
public static final String xmindLegacyContent = "content.xml";
|
||||
public static final String xmindLegacyComments = "comments.xml";
|
||||
|
||||
/**
|
||||
* 解析脑图文件,返回content整合后的内容
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ArchiveException
|
||||
* @throws DocumentException
|
||||
*/
|
||||
public static String parseJson(File file) throws IOException, ArchiveException, DocumentException {
|
||||
String res = ZipUtils.extract(file);
|
||||
|
||||
String content = null;
|
||||
if (isXmindZen(res, file)) {
|
||||
content = getXmindZenContent(file, res);
|
||||
} else {
|
||||
content = getXmindLegacyContent(file, res);
|
||||
}
|
||||
|
||||
// 删除生成的文件夹
|
||||
File dir = new File(res);
|
||||
boolean flag = deleteDir(dir);
|
||||
if (flag) {
|
||||
// do something
|
||||
}
|
||||
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
|
||||
return (JSON.toJSONString(jsonRootBean, false));
|
||||
}
|
||||
|
||||
public static JsonRootBean parseObject(File file) throws DocumentException, ArchiveException, IOException {
|
||||
String content = parseJson(file);
|
||||
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
|
||||
return jsonRootBean;
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
String[] children = dir.list();
|
||||
// 递归删除目录中的子目录下
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
boolean success = deleteDir(new File(dir, children[i]));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 目录此时为空,可以删除
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static String getXmindZenContent(File file, String extractFileDir)
|
||||
throws IOException, ArchiveException {
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.add(xmindZenJson);
|
||||
Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir);
|
||||
String content = map.get(xmindZenJson);
|
||||
content = XmindZen.getContent(content);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public static String getXmindLegacyContent(File file, String extractFileDir)
|
||||
throws IOException, ArchiveException, DocumentException {
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.add(xmindLegacyContent);
|
||||
keys.add(xmindLegacyComments);
|
||||
Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir);
|
||||
|
||||
String contentXml = map.get(xmindLegacyContent);
|
||||
String commentsXml = map.get(xmindLegacyComments);
|
||||
String xmlContent = XmindLegacy.getContent(contentXml, commentsXml);
|
||||
|
||||
return xmlContent;
|
||||
}
|
||||
|
||||
private static boolean isXmindZen(String res, File file) throws IOException, ArchiveException {
|
||||
// 解压
|
||||
File parent = new File(res);
|
||||
if (parent.isDirectory()) {
|
||||
String[] files = parent.list(new ZipUtils.FileFilter());
|
||||
for (int i = 0; i < Objects.requireNonNull(files).length; i++) {
|
||||
if (files[i].equals(xmindZenJson)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.xmind.parser;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.dom4j.DocumentException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class XmindZen {
|
||||
|
||||
/**
|
||||
* @param jsonContent
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws DocumentException
|
||||
*/
|
||||
public static String getContent(String jsonContent) {
|
||||
JSONObject jsonObject = JSONArray.parseArray(jsonContent).getJSONObject(0);
|
||||
JSONObject rootTopic = jsonObject.getJSONObject("rootTopic");
|
||||
transferNotes(rootTopic);
|
||||
JSONObject children = rootTopic.getJSONObject("children");
|
||||
recursionChildren(children);
|
||||
return jsonObject.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归转换children
|
||||
*
|
||||
* @param children
|
||||
*/
|
||||
private static void recursionChildren(JSONObject children) {
|
||||
if (children == null) {
|
||||
return;
|
||||
}
|
||||
JSONArray attachedArray = children.getJSONArray("attached");
|
||||
if (attachedArray == null) {
|
||||
return;
|
||||
}
|
||||
for (Object attached : attachedArray) {
|
||||
JSONObject attachedObject = (JSONObject) attached;
|
||||
transferNotes(attachedObject);
|
||||
JSONObject childrenObject = attachedObject.getJSONObject("children");
|
||||
if (childrenObject == null) {
|
||||
continue;
|
||||
}
|
||||
recursionChildren(childrenObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transferNotes(JSONObject object) {
|
||||
JSONObject notes = object.getJSONObject("notes");
|
||||
if (notes == null) {
|
||||
return;
|
||||
}
|
||||
JSONObject plain = notes.getJSONObject("plain");
|
||||
if (plain != null) {
|
||||
String content = plain.getString("content");
|
||||
notes.remove("plain");
|
||||
notes.put("content", content);
|
||||
} else {
|
||||
notes.put("content", null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package io.metersphere.xmind.parser;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveException;
|
||||
import org.apache.commons.compress.archivers.examples.Expander;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Description zip解压工具
|
||||
*/
|
||||
public class ZipUtils {
|
||||
|
||||
private static final String currentPath = System.getProperty("user.dir");
|
||||
|
||||
/**
|
||||
* 找到压缩文件中匹配的子文件,返回的为 getContents("comments.xml, unzip
|
||||
*
|
||||
* @param subFileNames
|
||||
* @param file
|
||||
*/
|
||||
public static Map<String, String> getContents(List<String> subFileNames, File file, String extractFileDir)
|
||||
throws IOException, ArchiveException {
|
||||
String destFilePath = extractFileDir;
|
||||
Map<String, String> map = new HashMap<>();
|
||||
File destFile = new File(destFilePath);
|
||||
if (destFile.isDirectory()) {
|
||||
String[] res = destFile.list(new FileFilter());
|
||||
for (int i = 0; i < Objects.requireNonNull(res).length; i++) {
|
||||
if (subFileNames.contains(res[i])) {
|
||||
String s = destFilePath + File.separator + res[i];
|
||||
String content = getFileContent(s);
|
||||
map.put(res[i], content);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回解压后的文件夹名字
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ArchiveException
|
||||
*/
|
||||
public static String extract(File file) throws IOException, ArchiveException {
|
||||
Expander expander = new Expander();
|
||||
String destFileName = currentPath + File.separator + "XMind" + System.currentTimeMillis(); // 目标文件夹名字
|
||||
expander.expand(file, new File(destFileName));
|
||||
return destFileName;
|
||||
}
|
||||
|
||||
// 这是一个内部类过滤器,策略模式
|
||||
static class FileFilter implements FilenameFilter {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
// String的 endsWith(String str)方法 筛选出以str结尾的字符串
|
||||
if (name.endsWith(".xml") || name.endsWith(".json")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileContent(String fileName) throws IOException {
|
||||
File file;
|
||||
try {
|
||||
file = new File(fileName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("找不到该文件");
|
||||
}
|
||||
FileReader fileReader = new FileReader(file);
|
||||
BufferedReader bufferedReder = new BufferedReader(fileReader);
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
while (bufferedReder.ready()) {
|
||||
stringBuffer.append(bufferedReder.readLine());
|
||||
}
|
||||
// 打开的文件需关闭,在unix下可以删除,否则在windows下不能删除(file.delete())
|
||||
bufferedReder.close();
|
||||
fileReader.close();
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Attached {
|
||||
|
||||
private String id;
|
||||
private String title;
|
||||
private Notes notes;
|
||||
private String path;
|
||||
private Attached parent;
|
||||
private List<Comments> comments;
|
||||
private Children children;
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Children {
|
||||
|
||||
private List<Attached> attached;
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Comments {
|
||||
|
||||
private long creationTime;
|
||||
private String author;
|
||||
private String content;
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class JsonRootBean {
|
||||
|
||||
private String id;
|
||||
private String title;
|
||||
private RootTopic rootTopic;
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Notes {
|
||||
|
||||
private String content;
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package io.metersphere.xmind.parser.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class RootTopic {
|
||||
|
||||
private String id;
|
||||
private String title;
|
||||
private Notes notes;
|
||||
private List<Comments> comments;
|
||||
private Children children;
|
||||
|
||||
}
|
|
@ -69,13 +69,13 @@ jmeter.home=/opt/jmeter
|
|||
# quartz
|
||||
quartz.enabled=true
|
||||
quartz.scheduler-name=msServerJob
|
||||
|
||||
# file upload
|
||||
spring.servlet.multipart.max-file-size=500MB
|
||||
spring.servlet.multipart.max-request-size=500MB
|
||||
|
||||
# actuator
|
||||
management.server.port=8083
|
||||
management.endpoints.web.exposure.include=*
|
||||
#spring.freemarker.checkTemplateLocation=false
|
||||
|
||||
|
||||
|
||||
spring.freemarker.checkTemplateLocation=false
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
alter table test_plan drop column project_id;
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE api_test_environment MODIFY COLUMN protocol varchar(20) NULL COMMENT 'Api Test Protocol';
|
||||
ALTER TABLE api_test_environment MODIFY COLUMN socket varchar(225) NULL COMMENT 'Api Test Socket';
|
||||
ALTER TABLE api_test_environment MODIFY COLUMN `domain` varchar(225) NULL COMMENT 'Api Test Domain';
|
||||
ALTER TABLE api_test_environment CHANGE custom_data `config` longtext COMMENT 'Config Data (JSON format)';
|
|
@ -151,5 +151,7 @@ quota_max_threads_excess_workspace=The maximum number of concurrent threads exce
|
|||
quota_max_threads_excess_organization=The maximum number of concurrent threads exceeds the organization quota
|
||||
quota_duration_excess_workspace=The stress test duration exceeds the work space quota
|
||||
quota_duration_excess_organization=The stress test duration exceeds the organization quota
|
||||
license_valid_license_error=valid license error
|
||||
license_valid_license_code=The authorization code already exists
|
||||
|
||||
email_subject=Metersphere timing task result notification
|
||||
import_xmind_count_error=The number of use cases imported into the mind map cannot exceed 500
|
||||
import_xmind_not_found=Test case not found
|
|
@ -151,7 +151,9 @@ 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定时任务结果通知
|
||||
import_xmind_count_error=思维导图导入用例数量不能超过 500 条
|
||||
import_xmind_not_found=未找到测试用例
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -154,3 +154,7 @@ quota_duration_excess_organization=壓測時長超過組織限額
|
|||
license_valid_license_error=授權驗證失敗
|
||||
license_valid_license_code=授權碼已經存在
|
||||
|
||||
|
||||
email_subject=MeterSphere定時任務結果通知
|
||||
import_xmind_count_error=思維導圖導入用例數量不能超過 500 條
|
||||
import_xmind_not_found=未找到测试用例
|
Binary file not shown.
|
@ -1,29 +1,29 @@
|
|||
FROM alpine:latest
|
||||
LABEL maintainer="support@fit2cloud.com"
|
||||
|
||||
ENV JMETER_VERSION "5.2.1"
|
||||
|
||||
ENV JMETER_VERSION "5.3"
|
||||
ENV KAFKA_BACKEND_LISTENER_VERSION "1.0.4"
|
||||
#定义时区参数
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update openjdk8-jre wget tar bash && \
|
||||
apk add --update openjdk8 wget tar bash && \
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/apache/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz && \
|
||||
wget https://jmeter-plugins.org/files/packages/jpgc-casutg-2.9.zip && \
|
||||
wget https://jmeter-plugins.org/files/packages/jpgc-tst-2.5.zip && \
|
||||
wget https://github.com/metersphere/jmeter-backend-listener-kafka/releases/download/v1.0.2/jmeter.backendlistener.kafka-1.0.2.jar && \
|
||||
wget https://github.com/metersphere/jmeter-backend-listener-kafka/releases/download/v${KAFKA_BACKEND_LISTENER_VERSION}/jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
|
||||
wget https://github.com/metersphere/jmeter-plugins-for-apache-dubbo/releases/download/2.7.7/jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
|
||||
mkdir -p /opt/jmeter && \
|
||||
tar -zxf apache-jmeter-${JMETER_VERSION}.tgz -C /opt/jmeter/ --strip-components=1 && \
|
||||
unzip -o jpgc-casutg-2.9.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-casutg-2.9.jar /opt/jmeter/lib/ext && \
|
||||
unzip -o jpgc-tst-2.5.zip -d /tmp/ && mv /tmp/lib/ext/jmeter-plugins-tst-2.5.jar /opt/jmeter/lib/ext && \
|
||||
mv jmeter.backendlistener.kafka-1.0.2.jar /opt/jmeter/lib/ext && \
|
||||
mv jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar /opt/jmeter/lib/ext && \
|
||||
mv jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar /opt/jmeter/lib/ext && \
|
||||
rm -rf apache-jmeter-${JMETER_VERSION}.tgz && \
|
||||
rm -rf jpgc-casutg-2.9.zip && \
|
||||
rm -rf jpgc-tst-2.5.zip && \
|
||||
rm -rf jmeter.backendlistener.kafka-1.0.2.jar && \
|
||||
rm -rf jmeter.backendlistener.kafka-${KAFKA_BACKEND_LISTENER_VERSION}.jar && \
|
||||
rm -rf jmeter-plugins-dubbo-2.7.7-jar-with-dependencies.jar && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
wget -O /usr/bin/tpl https://github.com/schneidexe/tpl/releases/download/v0.5.0/tpl-linux-amd64 && \
|
||||
|
@ -31,7 +31,7 @@ RUN apk update && \
|
|||
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo "$TZ" > /etc/timezone
|
||||
|
||||
ENV JMETER_HOME /opt/jmeter
|
||||
ENV PATH $PATH:$JMETER_HOME/bin
|
||||
ENV PATH $PATH:$JMETER_HOME/bin:/usr/lib/jvm/java-1.8-openjdk/bin
|
||||
|
||||
ADD log4j2.xml $JMETER_HOME/bin/log4j2.xml
|
||||
ADD jmeter.properties $JMETER_HOME/bin/jmeter.properties
|
|
@ -1,4 +1,4 @@
|
|||
FROM registry.fit2cloud.com/metersphere/jmeter-base:latest
|
||||
FROM registry.fit2cloud.com/metersphere/jmeter-base:0.0.1
|
||||
LABEL maintainer="support@fit2cloud.com"
|
||||
|
||||
EXPOSE 60000
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
|
@ -53,7 +53,7 @@
|
|||
<ms-api-report-dialog :test-id="id" ref="reportDialog"/>
|
||||
|
||||
<ms-schedule-config :schedule="test.schedule" :is-read-only="isReadOnly" :save="saveCronExpression"
|
||||
@scheduleChange="saveSchedule" :check-open="checkScheduleEdit"/>
|
||||
@scheduleChange="saveSchedule" :test-id="id" :check-open="checkScheduleEdit"/>
|
||||
</el-row>
|
||||
</el-header>
|
||||
<ms-api-scenario-config :debug-report-id="debugReportId" @runDebug="runDebug" :is-read-only="isReadOnly"
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
import MsAsideItem from "../../../common/components/MsAsideItem";
|
||||
import EnvironmentEdit from "./environment/EnvironmentEdit";
|
||||
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import {Environment, parseEnvironment} from "../model/EnvironmentModel";
|
||||
|
||||
export default {
|
||||
name: "ApiEnvironmentConfig",
|
||||
|
@ -35,7 +36,7 @@
|
|||
visible: false,
|
||||
projectId: '',
|
||||
environments: [],
|
||||
currentEnvironment: {variables: [{}], headers: [{}], protocol: 'https', projectId: this.projectId, hosts: [{}]},
|
||||
currentEnvironment: new Environment(),
|
||||
environmentOperators: [
|
||||
{
|
||||
icon: 'el-icon-document-copy',
|
||||
|
@ -68,7 +69,7 @@
|
|||
},
|
||||
copyEnvironment(environment) {
|
||||
if (!environment.id) {
|
||||
this.$warning(this.$t('commons.please_save'))
|
||||
this.$warning(this.$t('commons.please_save'));
|
||||
return;
|
||||
}
|
||||
let newEnvironment = {};
|
||||
|
@ -102,7 +103,9 @@
|
|||
return name;
|
||||
},
|
||||
addEnvironment() {
|
||||
let newEnvironment = this.getDefaultEnvironment();
|
||||
let newEnvironment = new Environment({
|
||||
projectId: this.projectId
|
||||
});
|
||||
this.environments.push(newEnvironment);
|
||||
this.$refs.environmentItems.itemSelected(this.environments.length - 1, newEnvironment);
|
||||
},
|
||||
|
@ -116,7 +119,9 @@
|
|||
if (this.environments.length > 0) {
|
||||
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
|
||||
} else {
|
||||
let item = this.getDefaultEnvironment();
|
||||
let item = new Environment({
|
||||
projectId: this.projectId
|
||||
});
|
||||
this.environments.push(item);
|
||||
this.$refs.environmentItems.itemSelected(0, item);
|
||||
}
|
||||
|
@ -124,25 +129,9 @@
|
|||
}
|
||||
},
|
||||
getEnvironment(environment) {
|
||||
if (!(environment.variables instanceof Array)) {
|
||||
environment.variables = JSON.parse(environment.variables);
|
||||
}
|
||||
if (!(environment.headers instanceof Array)) {
|
||||
environment.headers = JSON.parse(environment.headers);
|
||||
}
|
||||
if(environment.hosts === undefined || environment.hosts ===null || environment.hosts ===''){
|
||||
environment.hosts = [];
|
||||
environment.enable =false;
|
||||
}
|
||||
else if (!(environment.hosts instanceof Array)) {
|
||||
environment.hosts = JSON.parse(environment.hosts);
|
||||
environment.enable =true;
|
||||
}
|
||||
parseEnvironment(environment);
|
||||
this.currentEnvironment = environment;
|
||||
},
|
||||
getDefaultEnvironment() {
|
||||
return {variables: [{}], headers: [{}], protocol: 'https', projectId: this.projectId, hosts: [{}]};
|
||||
},
|
||||
close() {
|
||||
this.$emit('close');
|
||||
this.visible = false;
|
||||
|
|
|
@ -1,47 +1,42 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="table-card">
|
||||
<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>
|
||||
<span v-else>{{scope.row.ip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<div class="ms-border">
|
||||
<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>
|
||||
<span v-else>{{scope.row.ip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="domain" :label="$t('load_test.domain')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.status" v-model="scope.row.domain"></el-input>
|
||||
<span v-else>{{scope.row.domain}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="domain" :label="$t('load_test.domain')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.status" v-model="scope.row.domain"></el-input>
|
||||
<span v-else>{{scope.row.domain}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="annotation" :label="$t('commons.annotation')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.status" v-model="scope.row.annotation"></el-input>
|
||||
<span v-else>{{scope.row.annotation}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="annotation" :label="$t('commons.annotation')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.status" v-model="scope.row.annotation"></el-input>
|
||||
<span v-else>{{scope.row.annotation}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<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)"
|
||||
class="ht-btn-remove"/>
|
||||
<el-button size="mini" p="$t('commons.save')" icon="el-icon-check" circle @click="confirm(scope.row)"
|
||||
class="ht-btn-confirm"/>
|
||||
</span>
|
||||
</template>
|
||||
<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)"
|
||||
class="ht-btn-remove"/>
|
||||
<el-button size="mini" p="$t('commons.save')" icon="el-icon-check" circle @click="confirm(scope.row)"
|
||||
class="ht-btn-confirm"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add"
|
||||
>添加
|
||||
</el-button>
|
||||
|
||||
</el-card>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add">添加
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ import MsApiScenarioForm from "./ApiScenarioForm";
|
|||
import {Request, Scenario} from "../model/ScenarioModel";
|
||||
import draggable from 'vuedraggable';
|
||||
import MsApiScenarioSelect from "@/business/components/api/test/components/ApiScenarioSelect";
|
||||
import {parseEnvironment} from "../model/EnvironmentModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioConfig",
|
||||
|
@ -203,6 +204,7 @@ export default {
|
|||
let environments = response.data;
|
||||
let environmentMap = new Map();
|
||||
environments.forEach(environment => {
|
||||
parseEnvironment(environment);
|
||||
environmentMap.set(environment.id, environment);
|
||||
});
|
||||
this.scenarios.forEach(scenario => {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select"
|
||||
@change="environmentChange" clearable>
|
||||
<el-option v-for="(environment, index) in environments" :key="index"
|
||||
:label="environment.name + ': ' + environment.protocol + '://' + environment.socket"
|
||||
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
|
||||
:value="environment.id"/>
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
|
||||
{{ $t('api_test.environment.environment_config') }}
|
||||
|
@ -29,15 +29,15 @@
|
|||
|
||||
<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"
|
||||
<ms-api-scenario-variables :isShowEnable="true" :is-read-only="isReadOnly" :items="scenario.variables"
|
||||
:description="$t('api_test.scenario.kv_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :items="scenario.headers" :suggestions="headerSuggestions"
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="true" :items="scenario.headers" :suggestions="headerSuggestions"
|
||||
:environment="scenario.environment"
|
||||
:description="$t('api_test.scenario.kv_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'数据库配置'" name="database">
|
||||
<el-tab-pane :label="$t('api_test.environment.database_config')" name="database">
|
||||
<ms-database-config :configs="scenario.databaseConfigs"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.dubbo')" name="dubbo">
|
||||
|
@ -66,6 +66,7 @@ import MsDubboRegistryCenter from "@/business/components/api/test/components/req
|
|||
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";
|
||||
import {parseEnvironment} from "../model/EnvironmentModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioForm",
|
||||
|
@ -111,6 +112,9 @@ export default {
|
|||
if (this.projectId) {
|
||||
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
|
||||
this.environments = response.data;
|
||||
this.environments.forEach(environment => {
|
||||
parseEnvironment(environment);
|
||||
});
|
||||
let hasEnvironment = false;
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === this.scenario.environmentId) {
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
</span>
|
||||
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col v-if="isShowEnable" class="kv-checkbox">
|
||||
<input type="checkbox" v-if="!isDisable(index)" @change="change" :value="item.uuid" v-model="checkedValues"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<ms-api-variable-input :show-variable="showVariable" :is-read-only="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change"
|
||||
:placeholder="$t('api_test.variable_name')" show-word-limit/>
|
||||
|
@ -36,14 +41,27 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showVariable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
checkedValues: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
if (this.isShowEnable) {
|
||||
// 移除勾选内容
|
||||
let checkIndex = this.checkedValues.indexOf(this.items[index].uuid);
|
||||
checkIndex != -1 ? this.checkedValues.splice(checkIndex, 1) : this.checkedValues;
|
||||
}
|
||||
this.items.splice(index, 1);
|
||||
this.$emit('change', this.items);
|
||||
},
|
||||
|
@ -51,6 +69,10 @@
|
|||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.items.forEach((item, index) => {
|
||||
// 启用行赋值
|
||||
if (this.isShowEnable) {
|
||||
item.enable = this.checkedValues.indexOf(item.uuid) != -1 ? true : false;
|
||||
}
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.items.length - 1) {
|
||||
|
@ -61,11 +83,20 @@
|
|||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
// 往后台送入的复选框值布尔值
|
||||
if (this.isShowEnable) {
|
||||
this.items[this.items.length - 1].enable = true;
|
||||
// v-model 选中状态
|
||||
this.checkedValues.push(this.items[this.items.length - 1].uuid);
|
||||
}
|
||||
this.items.push(new KeyValue());
|
||||
}
|
||||
this.$emit('change', this.items);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.items.length - 1 === index;
|
||||
}
|
||||
|
@ -74,6 +105,14 @@
|
|||
created() {
|
||||
if (this.items.length === 0) {
|
||||
this.items.push(new KeyValue());
|
||||
}else if (this.isShowEnable) {
|
||||
this.items.forEach((item, index) => {
|
||||
let uuid = this.uuid();
|
||||
item.uuid = uuid;
|
||||
if (item.enable) {
|
||||
this.checkedValues.push(uuid);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +123,11 @@
|
|||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
|
@ -38,17 +38,16 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
setActiveNames(activeNames) {
|
||||
setActiveNames(activeNames, item) {
|
||||
activeNames = [].concat(activeNames);
|
||||
let value = this.accordion ? activeNames[0] : activeNames;
|
||||
this.activeNames = activeNames;
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
this.$emit('input', item.name);
|
||||
this.$emit('change', item.name);
|
||||
},
|
||||
handleItemClick(item) {
|
||||
if (this.accordion) {
|
||||
this.setActiveNames(
|
||||
(this.activeNames[0] || this.activeNames[0] === 0) && item.name);
|
||||
(this.activeNames[0] || this.activeNames[0] === 0) && item.name, item);
|
||||
} else {
|
||||
let activeNames = this.activeNames.slice(0);
|
||||
let index = activeNames.indexOf(item.name);
|
||||
|
@ -58,7 +57,7 @@
|
|||
} else {
|
||||
activeNames.push(item.name);
|
||||
}
|
||||
this.setActiveNames(activeNames);
|
||||
this.setActiveNames(activeNames, item);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
>
|
||||
<div
|
||||
class="el-collapse-item__header"
|
||||
@click="handleHeaderClick"
|
||||
role="button"
|
||||
:id="`el-collapse-head-${id}`"
|
||||
:tabindex="disabled ? undefined : 0"
|
||||
|
@ -21,7 +20,7 @@
|
|||
@focus="handleFocus"
|
||||
@blur="focusing = false"
|
||||
>
|
||||
<i
|
||||
<i @click="handleHeaderClick"
|
||||
class="el-collapse-item__arrow el-icon-arrow-right"
|
||||
:class="{'is-active': isActive}">
|
||||
</i>
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="commonConfig" :rules="rules" ref="commonConfig">
|
||||
|
||||
<span>{{$t('api_test.environment.globalVariable')}}</span>
|
||||
<ms-api-scenario-variables :items="commonConfig.variables"/>
|
||||
|
||||
<el-form-item>
|
||||
<el-switch v-model="commonConfig.enableHost" active-text="Hosts"/>
|
||||
</el-form-item>
|
||||
<ms-api-host-table v-if="commonConfig.enableHost" :hostTable="commonConfig.hosts" ref="refHostTable"/>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {CommonConfig, Environment} from "../../model/EnvironmentModel";
|
||||
import MsApiScenarioVariables from "../ApiScenarioVariables";
|
||||
import MsApiHostTable from "../ApiHostTable";
|
||||
|
||||
export default {
|
||||
name: "MsEnvironmentCommonConfig",
|
||||
components: {MsApiHostTable, MsApiScenarioVariables},
|
||||
props: {
|
||||
commonConfig: new CommonConfig(),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs['commonConfig'].validate((valid) => {
|
||||
if (valid) {
|
||||
// 校验host列表
|
||||
let valHost = true;
|
||||
if (this.commonConfig.enableHost) {
|
||||
for (let i = 0; i < this.commonConfig.hosts.length; i++) {
|
||||
valHost = this.$refs['refHostTable'].confirm(this.commonConfig.hosts[i]);
|
||||
}
|
||||
}
|
||||
if (valHost) {
|
||||
isValidate = true;
|
||||
} else {
|
||||
isValidate = false;
|
||||
}
|
||||
} else {
|
||||
isValidate = false;
|
||||
}
|
||||
});
|
||||
return isValidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -3,37 +3,24 @@
|
|||
<el-form :model="environment" :rules="rules" ref="environment">
|
||||
|
||||
<span>{{$t('api_test.environment.name')}}</span>
|
||||
<el-form-item
|
||||
prop="name">
|
||||
<el-input v-model="environment.name" :placeholder="this.$t('commons.input_name')" clearable></el-input>
|
||||
</el-form-item>
|
||||
<span>{{$t('api_test.environment.socket')}}</span>
|
||||
<el-form-item
|
||||
prop="socket">
|
||||
<el-input v-model="environment.socket" :placeholder="$t('api_test.request.url_description')" clearable>
|
||||
<template v-slot:prepend>
|
||||
<el-select v-model="environment.protocol" class="request-protocol-select">
|
||||
<el-option label="http://" value="http"/>
|
||||
<el-option label="https://" value="https"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-form-item prop="name">
|
||||
<el-input v-model="environment.name" :placeholder="this.$t('commons.input_name')" clearable/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-switch
|
||||
v-model="envEnable"
|
||||
inactive-text="hosts">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<ms-api-host-table v-if="envEnable" :hostTable="environment.hosts" ref="refHostTable"/>
|
||||
<el-tabs v-model="activeName">
|
||||
|
||||
<span>{{$t('api_test.environment.globalVariable')}}</span>
|
||||
<ms-api-scenario-variables :show-variable="false" :items="environment.variables"/>
|
||||
<el-tab-pane :label="$t('api_test.environment.common_config')" name="common">
|
||||
<ms-environment-common-config :common-config="environment.config.commonConfig" ref="commonConfig"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<span>{{$t('api_test.request.headers')}}</span>
|
||||
<ms-api-key-value :items="environment.headers" :suggestions="headerSuggestions"/>
|
||||
<el-tab-pane :label="$t('api_test.environment.http_config')" name="http">
|
||||
<ms-environment-http-config :http-config="environment.config.httpConfig" ref="httpConfig"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
|
||||
<ms-database-config :configs="environment.config.databaseConfigs"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="environment-footer">
|
||||
<ms-dialog-footer
|
||||
|
@ -49,23 +36,23 @@
|
|||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
|
||||
import {KeyValue} from "../../model/ScenarioModel";
|
||||
import {Environment} from "../../model/EnvironmentModel";
|
||||
import MsApiHostTable from "../ApiHostTable";
|
||||
import MsDatabaseConfig from "../request/database/DatabaseConfig";
|
||||
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
|
||||
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
|
||||
|
||||
export default {
|
||||
name: "EnvironmentEdit",
|
||||
components: {MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
|
||||
components: {
|
||||
MsEnvironmentCommonConfig,
|
||||
MsEnvironmentHttpConfig,
|
||||
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
|
||||
props: {
|
||||
environment: Object,
|
||||
environment: new Environment(),
|
||||
},
|
||||
data() {
|
||||
let socketValidator = (rule, value, callback) => {
|
||||
if (!this.validateSocket(value)) {
|
||||
callback(new Error(this.$t('commons.formatErr')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: {},
|
||||
envEnable: false,
|
||||
|
@ -74,9 +61,9 @@
|
|||
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
|
||||
{max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'}
|
||||
],
|
||||
socket: [{required: true, validator: socketValidator, trigger: 'blur'}],
|
||||
},
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
activeName: 'common'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -87,20 +74,10 @@
|
|||
methods: {
|
||||
save() {
|
||||
this.$refs['environment'].validate((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) {
|
||||
if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) {
|
||||
this._save(this.environment);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
_save(environment) {
|
||||
let param = this.buildParam(environment);
|
||||
|
@ -115,17 +92,11 @@
|
|||
this.$success(this.$t('commons.save_success'));
|
||||
});
|
||||
},
|
||||
buildParam(environment) {
|
||||
buildParam: function (environment) {
|
||||
let param = {};
|
||||
Object.assign(param, environment);
|
||||
if (!(environment.variables instanceof String)) {
|
||||
param.variables = JSON.stringify(environment.variables);
|
||||
}
|
||||
if (!(environment.headers instanceof String)) {
|
||||
param.headers = JSON.stringify(environment.headers);
|
||||
}
|
||||
if (environment.hosts != undefined && !(environment.hosts instanceof String)) {
|
||||
let hosts = JSON.parse(JSON.stringify(environment.hosts));
|
||||
let hosts = param.config.commonConfig.hosts;
|
||||
if (hosts != undefined) {
|
||||
let validHosts = [];
|
||||
// 去除掉未确认的host
|
||||
hosts.forEach(host => {
|
||||
|
@ -133,33 +104,11 @@
|
|||
validHosts.push(host);
|
||||
}
|
||||
});
|
||||
environment.hosts = validHosts;
|
||||
param.hosts = JSON.stringify(validHosts);
|
||||
}
|
||||
if (!this.envEnable) {
|
||||
param.hosts = null;
|
||||
param.config.commonConfig.hosts = validHosts;
|
||||
}
|
||||
param.config = JSON.stringify(param.config);
|
||||
return param;
|
||||
},
|
||||
validateSocket(socket) {
|
||||
if (!socket) return;
|
||||
let urlStr = this.environment.protocol + '://' + socket;
|
||||
let url = {};
|
||||
try {
|
||||
url = new URL(urlStr);
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.environment.port = url.port;
|
||||
this.environment.domain = decodeURIComponent(url.hostname);
|
||||
if (url.port) {
|
||||
this.environment.socket = this.environment.domain + ':' + url.port + url.pathname;
|
||||
} else {
|
||||
this.environment.socket = this.environment.domain + url.pathname;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('close');
|
||||
},
|
||||
|
@ -175,10 +124,9 @@
|
|||
.el-main {
|
||||
border: solid 1px #EBEEF5;
|
||||
margin-left: 200px;
|
||||
}
|
||||
min-height: 400px;
|
||||
max-height: 700px;
|
||||
|
||||
.request-protocol-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<el-form :model="httpConfig" :rules="rules" ref="httpConfig">
|
||||
<span>{{$t('api_test.environment.socket')}}</span>
|
||||
<el-form-item prop="socket">
|
||||
<el-input v-model="httpConfig.socket" :placeholder="$t('api_test.request.url_description')" clearable>
|
||||
<template v-slot:prepend>
|
||||
<el-select v-model="httpConfig.protocol" class="request-protocol-select">
|
||||
<el-option label="http://" value="http"/>
|
||||
<el-option label="https://" value="https"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<span>{{$t('api_test.request.headers')}}</span>
|
||||
<ms-api-key-value :items="httpConfig.headers" :isShowEnable="true" :suggestions="headerSuggestions"/>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {HttpConfig} from "../../model/EnvironmentModel";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "MsEnvironmentHttpConfig",
|
||||
components: {MsApiKeyValue},
|
||||
props: {
|
||||
httpConfig: new HttpConfig(),
|
||||
},
|
||||
data() {
|
||||
let socketValidator = (rule, value, callback) => {
|
||||
if (!this.validateSocket(value)) {
|
||||
callback(new Error(this.$t('commons.formatErr')));
|
||||
return false;
|
||||
} else {
|
||||
callback();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
headerSuggestions: REQUEST_HEADERS,
|
||||
rules: {
|
||||
socket: [{required: false, validator: socketValidator, trigger: 'blur'}],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateSocket(socket) {
|
||||
if (!socket) return true;
|
||||
let urlStr = this.httpConfig.protocol + '://' + socket;
|
||||
let url = {};
|
||||
try {
|
||||
url = new URL(urlStr);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
this.httpConfig.domain = decodeURIComponent(url.hostname);
|
||||
|
||||
this.httpConfig.port = url.port;
|
||||
if (url.port) {
|
||||
this.httpConfig.socket = this.httpConfig.domain + ':' + url.port + url.pathname;
|
||||
} else {
|
||||
this.httpConfig.socket = this.httpConfig.domain + url.pathname;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
validate() {
|
||||
let isValidate = false;
|
||||
this.$refs['httpConfig'].validate((valid) => {
|
||||
isValidate = valid;
|
||||
});
|
||||
return isValidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.request-protocol-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -148,7 +148,7 @@ export default {
|
|||
if (!this.request.path) return;
|
||||
let url = this.getURL(this.displayUrl);
|
||||
let urlStr = url.origin + url.pathname;
|
||||
let envUrl = this.request.environment.protocol + '://' + this.request.environment.socket;
|
||||
let envUrl = this.request.environment.config.httpConfig.protocol + '://' + this.request.environment.config.httpConfig.socket;
|
||||
this.request.path = decodeURIComponent(urlStr.substring(envUrl.length, urlStr.length));
|
||||
},
|
||||
getURL(urlStr) {
|
||||
|
@ -194,7 +194,9 @@ export default {
|
|||
return this.request.method !== "GET";
|
||||
},
|
||||
displayUrl() {
|
||||
return this.request.environment ? this.request.environment.protocol + '://' + this.request.environment.socket + (this.request.path ? this.request.path : '') : '';
|
||||
return (this.request.environment && this.request.environment.config.httpConfig.socket) ?
|
||||
this.request.environment.config.httpConfig.protocol + '://' + this.request.environment.config.httpConfig.socket + (this.request.path ? this.request.path : '')
|
||||
: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,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/>
|
||||
|
|
|
@ -9,14 +9,15 @@
|
|||
|
||||
<script>
|
||||
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
||||
import MsRequestResultTail from "../../../report/components/RequestResultTail";
|
||||
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";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<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="$t('api_test.request.sql.dataSource')" prop="dataSource">
|
||||
<el-select v-model="request.dataSource">
|
||||
<el-option v-for="(item, index) in databaseConfigsOptions" :key="index" :value="item.id" :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="$t('api_test.request.sql.timeout')" 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-form-item>
|
||||
<el-switch
|
||||
v-model="request.useEnvironment"
|
||||
:active-text="$t('api_test.request.refer_to_environment')" @change="getDatabaseConfigsOptions">
|
||||
</el-switch>
|
||||
</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="$t('api_test.request.sql.sql_script')" 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",
|
||||
databaseConfigsOptions: [],
|
||||
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: {
|
||||
getDatabaseConfigsOptions() {
|
||||
this.databaseConfigsOptions = [];
|
||||
let names = new Set();
|
||||
let ids = new Set();
|
||||
this.scenario.databaseConfigs.forEach(config => {
|
||||
this.databaseConfigsOptions.push(config);
|
||||
names.add(config.name);
|
||||
ids.add(config.id);
|
||||
});
|
||||
if (this.request.useEnvironment && this.scenario.environment) {
|
||||
this.scenario.environment.config.databaseConfigs.forEach(config => {
|
||||
if (!names.has(config.name)) {
|
||||
this.databaseConfigsOptions.push(config);
|
||||
ids.add(config.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!ids.has(this.request.dataSource)) {
|
||||
this.request.dataSource = undefined;
|
||||
}
|
||||
},
|
||||
runDebug() {
|
||||
this.$emit('runDebug');
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getDatabaseConfigsOptions();
|
||||
},
|
||||
activated() {
|
||||
this.getDatabaseConfigsOptions();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.sql-content {
|
||||
height: calc(100vh - 570px);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<ms-database-from :config="currentConfig" @save="addConfig" ref="databaseFrom"/>
|
||||
<ms-database-from :config="currentConfig" :callback="addConfig" ref="databaseFrom"/>
|
||||
<ms-database-config-list v-if="configs.length > 0" :table-data="configs"/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -31,14 +31,16 @@
|
|||
addConfig(config) {
|
||||
for (let item of this.configs) {
|
||||
if (item.name === config.name) {
|
||||
this.$warning("名称重复");
|
||||
this.$warning(this.$t('commons.already_exists'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
config.id = getUUID();
|
||||
this.configs.push(config);
|
||||
this.currentConfig = new DatabaseConfig();
|
||||
}
|
||||
let item = {};
|
||||
Object.assign(item, config);
|
||||
this.configs.push(item);
|
||||
this.currentConfig = new DatabaseConfig();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
<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>
|
|
@ -1,49 +1,44 @@
|
|||
<template>
|
||||
<ms-main-container>
|
||||
<div class="database-config-list">
|
||||
|
||||
<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 type="expand">
|
||||
<template slot-scope="props">
|
||||
<ms-database-from :callback="editConfig" :config="props.row"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" :label="$t('api_test.request.sql.dataSource')" show-overflow-tooltip/>
|
||||
<el-table-column prop="driver" :label="$t('api_test.request.sql.database_driver')" show-overflow-tooltip/>
|
||||
<el-table-column prop="dbUrl" :label="$t('api_test.request.sql.database_url')" show-overflow-tooltip/>
|
||||
<el-table-column prop="username" :label="$t('api_test.request.sql.username')" show-overflow-tooltip/>
|
||||
<el-table-column prop="poolMax" :label="$t('api_test.request.sql.pool_max')" show-overflow-tooltip/>
|
||||
<el-table-column prop="timeout" :label="$t('api_test.request.sql.query_timeout')" show-overflow-tooltip/>
|
||||
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')" min-width="100">
|
||||
<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>
|
||||
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.copy')" icon="el-icon-document-copy" type="success" @exec="handleCopy(scope.$index, scope.row)"/>
|
||||
<ms-table-operator-button :isTesterPermission="true" :tip="$t('commons.delete')" icon="el-icon-delete" type="danger" @exec="handleDelete(scope.$index)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
|
||||
<ms-database-config-dialog :configs="tableData" ref="databaseConfigEdit"/>
|
||||
|
||||
</ms-main-container>
|
||||
</div>
|
||||
</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";
|
||||
import MsDatabaseFrom from "./DatabaseFrom";
|
||||
|
||||
export default {
|
||||
name: "MsDatabaseConfigList",
|
||||
components: {MsDatabaseConfigDialog, MsTableOperatorButton, MsTableOperator, MsMainContainer},
|
||||
components: {MsDatabaseFrom, MsTableOperatorButton, MsTableOperator},
|
||||
props: {
|
||||
tableData: Array,
|
||||
isReadOnly: {
|
||||
|
@ -63,15 +58,39 @@
|
|||
handleEdit(config) {
|
||||
this.$refs.databaseConfigEdit.open(config);
|
||||
},
|
||||
editConfig(config) {
|
||||
let index = 0;
|
||||
for (let i in this.tableData) {
|
||||
let item = this.tableData[i];
|
||||
if (item.name === config.name && item.id != config.id) {
|
||||
this.$warning(this.$t('commons.already_exists'));
|
||||
return;
|
||||
}
|
||||
if (item.id === config.id) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
Object.assign(this.tableData[index], config);
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
},
|
||||
handleDelete(index) {
|
||||
this.tableData.splice(index, 1);
|
||||
},
|
||||
handleCopy(config) {
|
||||
handleCopy(index, config) {
|
||||
let copy = {};
|
||||
Object.assign(copy, config);
|
||||
copy.id = getUUID();
|
||||
this.$refs.databaseConfigEdit.open(copy);
|
||||
}
|
||||
copy.name = this.getNoRepeatName(copy.name);
|
||||
this.tableData.splice(index + 1, 0, copy);
|
||||
},
|
||||
getNoRepeatName(name) {
|
||||
for (let i in this.tableData) {
|
||||
if (this.tableData[i].name === name) {
|
||||
return this.getNoRepeatName(name + ' copy');
|
||||
}
|
||||
}
|
||||
return name;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -82,11 +101,4 @@
|
|||
float: right;
|
||||
}
|
||||
|
||||
.database-from {
|
||||
padding: 10px;
|
||||
border: #DCDFE6 solid 1px;
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form :model="config" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" class="database-from" ref="databaseFrom">
|
||||
<div class="database-from">
|
||||
<el-form :model="currentConfig" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" ref="databaseFrom">
|
||||
|
||||
<el-form-item :label="'连接池名称'" prop="name">
|
||||
<el-input v-model="config.name" maxlength="300" show-word-limit
|
||||
<el-form-item :label="$t('api_test.request.sql.dataSource')" prop="name">
|
||||
<el-input v-model="currentConfig.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="300" show-word-limit
|
||||
<el-form-item :label="$t('api_test.request.sql.database_url')" prop="dbUrl">
|
||||
<el-input v-model="currentConfig.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-form-item :label="$t('api_test.request.sql.database_driver')" prop="driver">
|
||||
<el-select v-model="currentConfig.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
|
||||
<el-form-item :label="$t('api_test.request.sql.username')" prop="username">
|
||||
<el-input v-model="currentConfig.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="300" show-word-limit
|
||||
<el-form-item :label="$t('api_test.request.sql.password')" prop="password">
|
||||
<el-input v-model="currentConfig.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 :label="$t('api_test.request.sql.pool_max')" prop="poolMax">
|
||||
<el-input-number size="small" :disabled="isReadOnly" v-model="currentConfig.poolMax" :placeholder="$t('commons.please_select')" :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 :label="$t('api_test.request.sql.timeout')" prop="timeout">
|
||||
<el-input-number size="small" :disabled="isReadOnly" v-model="currentConfig.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-button type="primary" size="small" class="addButton" @click="save">{{currentConfig.id ? $t('commons.save') : $t('commons.add')}}</el-button>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
@ -63,11 +63,22 @@
|
|||
return new DatabaseConfig();
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
type: Function
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
config() {
|
||||
Object.assign(this.currentConfig, this.config);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Object.assign(this.currentConfig, this.config);
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drivers: DatabaseConfig.DRIVER_CLASS,
|
||||
// config: new DatabaseConfig(),
|
||||
currentConfig: new DatabaseConfig(),
|
||||
rules: {
|
||||
name: [
|
||||
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
|
||||
|
@ -94,8 +105,9 @@
|
|||
save() {
|
||||
this.$refs['databaseFrom'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$emit('save', this.config);
|
||||
// this.config = new DatabaseConfig();
|
||||
if (this.callback) {
|
||||
this.callback(this.currentConfig);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -111,11 +123,4 @@
|
|||
float: right;
|
||||
}
|
||||
|
||||
.database-from {
|
||||
padding: 10px;
|
||||
border: #DCDFE6 solid 1px;
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import {BaseConfig, DatabaseConfig, KeyValue} from "./ScenarioModel";
|
||||
|
||||
export class Environment extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
|
||||
super();
|
||||
|
||||
this.projectId = undefined;
|
||||
this.name = undefined;
|
||||
this.id = undefined;
|
||||
this.config = options.config || new Config();
|
||||
|
||||
this.set(options);
|
||||
this.sets({}, options);
|
||||
}
|
||||
|
||||
initOptions(options = {}) {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
export class Config extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.commonConfig = options.commonConfig || new CommonConfig();
|
||||
this.httpConfig = options.httpConfig || new HttpConfig();
|
||||
this.databaseConfigs = [];
|
||||
|
||||
this.set(options);
|
||||
this.sets({databaseConfigs: DatabaseConfig}, options);
|
||||
}
|
||||
initOptions(options = {}) {
|
||||
options.databaseConfigs = options.databaseConfigs || [];
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
export class CommonConfig extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
this.variables = [];
|
||||
this.enableHost = false;
|
||||
this.hosts = [];
|
||||
|
||||
this.set(options);
|
||||
this.sets({variables: KeyValue, hosts: Host}, options);
|
||||
}
|
||||
|
||||
initOptions(options = {}) {
|
||||
options.variables = options.variables || [new KeyValue()];
|
||||
options.hosts = options.hosts || [];
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpConfig extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
this.socket = undefined;
|
||||
this.domain = undefined;
|
||||
this.headers = [];
|
||||
this.protocol = 'https';
|
||||
this.port = undefined;
|
||||
|
||||
this.set(options);
|
||||
this.sets({headers: KeyValue}, options);
|
||||
}
|
||||
|
||||
initOptions(options = {}) {
|
||||
options.headers = options.headers || [new KeyValue()];
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
export class Host extends BaseConfig {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
this.ip = undefined;
|
||||
this.domain = undefined;
|
||||
this.status = undefined;
|
||||
this.annotation = undefined;
|
||||
this.uuid = undefined;
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ---------- Functions ------- */
|
||||
|
||||
export function compatibleWithEnvironment(environment) {
|
||||
//兼容旧版本
|
||||
if (!environment.config) {
|
||||
let config = new Config();
|
||||
if (!(environment.variables instanceof Array)) {
|
||||
config.commonConfig.variables = JSON.parse(environment.variables);
|
||||
}
|
||||
if (environment.hosts && !(environment.hosts instanceof Array)) {
|
||||
config.commonConfig.hosts = JSON.parse(environment.hosts);
|
||||
config.commonConfig.enableHost = true;
|
||||
}
|
||||
if (!(environment.headers instanceof Array)) {
|
||||
config.httpConfig.headers = JSON.parse(environment.headers);
|
||||
}
|
||||
config.httpConfig.port = environment.port;
|
||||
config.httpConfig.protocol = environment.protocol;
|
||||
config.httpConfig.domain = environment.domain;
|
||||
config.httpConfig.socket = environment.socket;
|
||||
environment.config = JSON.stringify(config);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseEnvironment(environment) {
|
||||
compatibleWithEnvironment(environment);
|
||||
if (!(environment.config instanceof Config)) {
|
||||
environment.config = new Config(JSON.parse(environment.config));
|
||||
}
|
||||
}
|
|
@ -185,7 +185,7 @@ export class TestPlan extends DefaultTestElement {
|
|||
|
||||
props = props || {};
|
||||
this.boolProp("TestPlan.functional_mode", props.mode, false);
|
||||
this.boolProp("TestPlan.serialize_threadgroups", props.stg, false);
|
||||
this.boolProp("TestPlan.serialize_threadgroups", props.stg, true);
|
||||
this.boolProp("TestPlan.tearDown_on_shutdown", props.tos, true);
|
||||
this.stringProp("TestPlan.comments", props.comments);
|
||||
this.stringProp("TestPlan.user_define_classpath", props.classpath);
|
||||
|
@ -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);
|
||||
|
@ -318,7 +350,7 @@ export class HTTPSamplerArguments extends Element {
|
|||
|
||||
let collectionProp = this.collectionProp('Arguments.arguments');
|
||||
this.args.forEach(arg => {
|
||||
if (arg.enable === true) { // 非禁用的条件加入执行
|
||||
if (arg.enable === true || arg.enable === undefined) { // 非禁用的条件加入执行
|
||||
let elementProp = collectionProp.elementProp(arg.name, 'HTTPArgument');
|
||||
elementProp.boolProp('HTTPArgument.always_encode', arg.encode, true);
|
||||
elementProp.boolProp('HTTPArgument.use_equals', arg.equals, true);
|
||||
|
@ -507,7 +539,7 @@ export class HeaderManager extends DefaultTestElement {
|
|||
|
||||
let collectionProp = this.collectionProp('HeaderManager.headers');
|
||||
this.headers.forEach(header => {
|
||||
if (header.enable === true) {
|
||||
if (header.enable === true || header.enable === undefined) {
|
||||
let elementProp = collectionProp.elementProp('', 'Header');
|
||||
elementProp.stringProp('Header.name', header.name);
|
||||
elementProp.stringProp('Header.value', header.value);
|
||||
|
@ -517,23 +549,44 @@ export class HeaderManager extends DefaultTestElement {
|
|||
}
|
||||
|
||||
export class DNSCacheManager extends DefaultTestElement {
|
||||
constructor(testName, domain, hosts) {
|
||||
constructor(testName, hosts) {
|
||||
super('DNSCacheManager', 'DNSCachePanel', 'DNSCacheManager', testName);
|
||||
let collectionPropServers = this.collectionProp('DNSCacheManager.servers');
|
||||
let collectionPropHosts = this.collectionProp('DNSCacheManager.hosts');
|
||||
|
||||
hosts.forEach(host => {
|
||||
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);
|
||||
}
|
||||
elementProp.stringProp('StaticHost.Name', host.domain);
|
||||
elementProp.stringProp('StaticHost.Address', host.ip);
|
||||
});
|
||||
|
||||
let boolProp = this.boolProp('DNSCacheManager.isCustomResolver', true);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -542,7 +595,7 @@ export class Arguments extends DefaultTestElement {
|
|||
let collectionProp = this.collectionProp('Arguments.arguments');
|
||||
|
||||
this.args.forEach(arg => {
|
||||
if (arg.enable === true) { // 非禁用的条件加入执行
|
||||
if (arg.enable === true || arg.enable === undefined) { // 非禁用的条件加入执行
|
||||
let elementProp = collectionProp.elementProp(arg.name, 'Argument');
|
||||
elementProp.stringProp('Argument.name', arg.name);
|
||||
elementProp.stringProp('Argument.value', arg.value);
|
||||
|
@ -567,7 +620,7 @@ export class ElementArguments extends Element {
|
|||
let collectionProp = this.collectionProp('Arguments.arguments');
|
||||
if (args) {
|
||||
args.forEach(arg => {
|
||||
if (arg.enable === true) { // 非禁用的条件加入执行
|
||||
if (arg.enable === true || arg.enable === undefined) { // 非禁用的条件加入执行
|
||||
let elementProp = collectionProp.elementProp(arg.name, 'Argument');
|
||||
elementProp.stringProp('Argument.name', arg.name);
|
||||
elementProp.stringProp('Argument.value', arg.value);
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
HTTPSamplerArguments,
|
||||
HTTPsamplerFiles,
|
||||
HTTPSamplerProxy,
|
||||
HTTPSamplerArguments, HTTPsamplerFiles,
|
||||
HTTPSamplerProxy, JDBCDataSource, JDBCSampler,
|
||||
JSONPathAssertion,
|
||||
JSONPostProcessor,
|
||||
JSR223PostProcessor,
|
||||
|
@ -216,7 +218,7 @@ export class Scenario extends BaseConfig {
|
|||
this.environment = undefined;
|
||||
this.enableCookieShare = false;
|
||||
this.enable = true;
|
||||
this.databaseConfigs = undefined;
|
||||
this.databaseConfigs = [];
|
||||
|
||||
this.set(options);
|
||||
this.sets({
|
||||
|
@ -283,6 +285,7 @@ export class RequestFactory {
|
|||
static TYPES = {
|
||||
HTTP: "HTTP",
|
||||
DUBBO: "DUBBO",
|
||||
SQL: "SQL",
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
|
@ -290,6 +293,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);
|
||||
}
|
||||
|
@ -470,6 +475,62 @@ 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.useEnvironment = options.useEnvironment;
|
||||
this.debugReport = undefined;
|
||||
this.dataSource = options.dataSource;
|
||||
this.query = options.query;
|
||||
// this.queryType = options.queryType;
|
||||
this.queryTimeout = options.queryTimeout || 60000;
|
||||
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: 'api_test.request.sql.name_cannot_be_empty'
|
||||
}
|
||||
}
|
||||
if (!this.dataSource) {
|
||||
return {
|
||||
isValid: false,
|
||||
info: 'api_test.request.sql.dataSource_cannot_be_empty'
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
isValid: true
|
||||
}
|
||||
}
|
||||
|
||||
showType() {
|
||||
return "SQL";
|
||||
}
|
||||
|
||||
showMethod() {
|
||||
return "SQL";
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new SqlRequest(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ConfigCenter extends BaseConfig {
|
||||
static PROTOCOLS = ["zookeeper", "nacos", "apollo"];
|
||||
|
||||
|
@ -492,7 +553,7 @@ export class ConfigCenter extends BaseConfig {
|
|||
}
|
||||
|
||||
export class DatabaseConfig extends BaseConfig {
|
||||
static DRIVER_CLASS = ["com.mysql.jdbc.Driver"];
|
||||
static DRIVER_CLASS = ["com.mysql.jdbc.Driver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "org.postgresql.Driver", "oracle.jdbc.OracleDriver"];
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
|
@ -513,25 +574,6 @@ export class DatabaseConfig extends BaseConfig {
|
|||
return options;
|
||||
}
|
||||
|
||||
// <JDBCDataSource guiclass="TestBeanGUI" testclass="JDBCDataSource" testname="JDBC Connection Configurationqqq" enabled="true">
|
||||
// <boolProp name="autocommit">true</boolProp>
|
||||
// <stringProp name="checkQuery"></stringProp>
|
||||
// <stringProp name="connectionAge">5000</stringProp>
|
||||
// <stringProp name="connectionProperties"></stringProp>
|
||||
// <stringProp name="dataSource">test</stringProp>
|
||||
// <stringProp name="dbUrl">jdbc:mysql://localhost:3306/metersphere?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true</stringProp>
|
||||
// <stringProp name="driver">com.mysql.jdbc.Driver</stringProp>
|
||||
// <stringProp name="initQuery"></stringProp>
|
||||
// <boolProp name="keepAlive">true</boolProp>
|
||||
// <stringProp name="password">root</stringProp>
|
||||
// <stringProp name="poolMax">10</stringProp>
|
||||
// <boolProp name="preinit">false</boolProp>
|
||||
// <stringProp name="timeout">10000</stringProp>
|
||||
// <stringProp name="transactionIsolation">DEFAULT</stringProp>
|
||||
// <stringProp name="trimInterval">60000</stringProp>
|
||||
// <stringProp name="username">root</stringProp>
|
||||
// </JDBCDataSource>
|
||||
|
||||
isValid() {
|
||||
return !!this.name || !!this.poolMax || !!this.timeout || !!this.driver || !!this.dbUrl || !!this.username || !!this.password;
|
||||
}
|
||||
|
@ -901,10 +943,10 @@ class JMXHttpRequest {
|
|||
this.protocol = url.protocol.split(":")[0];
|
||||
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname));
|
||||
} else {
|
||||
this.domain = environment.domain;
|
||||
this.port = environment.port;
|
||||
this.protocol = environment.protocol;
|
||||
let url = new URL(environment.protocol + "://" + environment.socket);
|
||||
this.domain = environment.config.httpConfig.domain;
|
||||
this.port = environment.config.httpConfig.port;
|
||||
this.protocol = environment.config.httpConfig.protocol;
|
||||
let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.commonConfig.socket);
|
||||
this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : '')));
|
||||
}
|
||||
this.connectTimeout = request.connectTimeout;
|
||||
|
@ -1011,6 +1053,8 @@ class JMXGenerator {
|
|||
// 放在计划或线程组中,不建议放具体某个请求中
|
||||
this.addDNSCacheManager(threadGroup, scenario.requests[0]);
|
||||
|
||||
this.addJDBCDataSources(threadGroup, scenario);
|
||||
|
||||
scenario.requests.forEach(request => {
|
||||
if (request.enable) {
|
||||
if (!request.isValid()) return;
|
||||
|
@ -1018,9 +1062,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') {
|
||||
|
@ -1028,6 +1070,9 @@ class JMXGenerator {
|
|||
} else {
|
||||
this.addRequestBody(sampler, request, testId);
|
||||
}
|
||||
} else if (request instanceof SqlRequest) {
|
||||
request.dataSource = scenario.databaseConfigMap.get(request.dataSource);
|
||||
sampler = new JDBCSampler(request.name || "", request);
|
||||
}
|
||||
|
||||
this.addRequestExtractor(sampler, request);
|
||||
|
@ -1062,22 +1107,22 @@ class JMXGenerator {
|
|||
let envArray = environments;
|
||||
if (!(envArray instanceof Array)) {
|
||||
envArray = JSON.parse(environments);
|
||||
envArray.forEach(item => {
|
||||
if (item.name && !keys.has(item.name)) {
|
||||
target.push(new KeyValue(item.name, item.value));
|
||||
}
|
||||
})
|
||||
}
|
||||
envArray.forEach(item => {
|
||||
if (item.name && !keys.has(item.name)) {
|
||||
target.push(new KeyValue(item.name, item.value));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addScenarioVariables(threadGroup, scenario) {
|
||||
let environment = scenario.environment;
|
||||
if (environment) {
|
||||
this.addEnvironments(environment.variables, scenario.variables)
|
||||
if (scenario.environment) {
|
||||
let commonConfig = scenario.environment.config.commonConfig;
|
||||
this.addEnvironments(commonConfig.variables, scenario.variables)
|
||||
}
|
||||
let args = this.filterKV(scenario.variables);
|
||||
if (args.length > 0) {
|
||||
let name = scenario.name + " Variables"
|
||||
let name = scenario.name + " Variables";
|
||||
threadGroup.put(new Arguments(name, args));
|
||||
}
|
||||
}
|
||||
|
@ -1089,24 +1134,58 @@ class JMXGenerator {
|
|||
}
|
||||
|
||||
addDNSCacheManager(threadGroup, request) {
|
||||
if (request.environment && request.environment.hosts) {
|
||||
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, request.environment.domain, hosts));
|
||||
if (request.environment) {
|
||||
let commonConfig = request.environment.config.commonConfig;
|
||||
let hosts = commonConfig.hosts;
|
||||
if (commonConfig.enableHost && hosts.length > 0) {
|
||||
let name = request.name + " DNSCacheManager";
|
||||
// 强化判断,如果未匹配到合适的host则不开启DNSCache
|
||||
let domain = request.environment.config.httpConfig.domain;
|
||||
let validHosts = [];
|
||||
hosts.forEach(item => {
|
||||
let d = item.domain.trim().replace("http://", "").replace("https://", "");
|
||||
if (item && d === domain.trim()) {
|
||||
item.domain = d; // 域名去掉协议
|
||||
validHosts.push(item);
|
||||
}
|
||||
});
|
||||
if (validHosts.length > 0) {
|
||||
threadGroup.put(new DNSCacheManager(name, validHosts));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addJDBCDataSources(threadGroup, scenario) {
|
||||
let names = new Set();
|
||||
let databaseConfigMap = new Map();
|
||||
scenario.databaseConfigs.forEach(config => {
|
||||
let name = config.name + "JDBCDataSource";
|
||||
threadGroup.put(new JDBCDataSource(name, config));
|
||||
names.add(name);
|
||||
databaseConfigMap.set(config.id, config.name);
|
||||
});
|
||||
if (scenario.environment) {
|
||||
let envDatabaseConfigs = scenario.environment.config.databaseConfigs;
|
||||
envDatabaseConfigs.forEach(config => {
|
||||
if (!names.has(config.name)) {
|
||||
let name = config.name + "JDBCDataSource";
|
||||
threadGroup.put(new JDBCDataSource(name, config));
|
||||
databaseConfigMap.set(config.id, config.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
scenario.databaseConfigMap = databaseConfigMap;
|
||||
}
|
||||
|
||||
addScenarioHeaders(threadGroup, scenario) {
|
||||
let environment = scenario.environment;
|
||||
if (environment) {
|
||||
this.addEnvironments(environment.headers, scenario.headers)
|
||||
if (scenario.environment) {
|
||||
let httpConfig = scenario.environment.config.httpConfig;
|
||||
this.addEnvironments(httpConfig.headers, scenario.headers)
|
||||
}
|
||||
let headers = this.filterKV(scenario.headers);
|
||||
if (headers.length > 0) {
|
||||
let name = scenario.name + " Headers"
|
||||
let name = scenario.name + " Headers";
|
||||
threadGroup.put(new HeaderManager(name, headers));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<span class="character">SCHEDULER</span>
|
||||
</span>
|
||||
<el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" @change="scheduleChange"/>
|
||||
<ms-schedule-edit :is-read-only="isReadOnly" :schedule="schedule" :save="save" :custom-validate="customValidate"
|
||||
<ms-schedule-edit :is-read-only="isReadOnly" :schedule="schedule" :test-id="testId" :save="save" :custom-validate="customValidate"
|
||||
ref="scheduleEdit"/>
|
||||
|
||||
</div>
|
||||
|
@ -38,6 +38,7 @@ export default {
|
|||
}
|
||||
},
|
||||
props: {
|
||||
testId:String,
|
||||
save: Function,
|
||||
schedule: {},
|
||||
checkOpen: {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
|
||||
<el-tab-pane :label="$t('schedule.edit_timer_task')" name="first">
|
||||
<el-form :model="form" :rules="rules" ref="from">
|
||||
<el-form-item
|
||||
|
@ -26,81 +27,51 @@
|
|||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('schedule.task_notification')" name="second">
|
||||
<template>
|
||||
<el-select v-model="value" :placeholder="$t('commons.please_select')">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="receiver"
|
||||
prop="event"
|
||||
:label="$t('schedule.event')"
|
||||
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('schedule.receiver')"
|
||||
width="200"
|
||||
>
|
||||
<template v-slot:default="{row}">
|
||||
<el-input
|
||||
size="mini"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
class="edit-input"
|
||||
v-model="row.receiver"
|
||||
:placeholder="$t('schedule.receiver')"
|
||||
clearable>
|
||||
</el-input>
|
||||
<el-select v-model="row.names" filterable multiple placeholder="请选择" @click.native="userList()">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="email"
|
||||
:label="$t('schedule.receiving_mode')"
|
||||
width="200"
|
||||
>
|
||||
<template v-slot:default="{row}">
|
||||
<el-input
|
||||
size="mini"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
class="edit-input"
|
||||
v-model="row.email"
|
||||
:placeholder="$t('schedule.input_email')"
|
||||
clearable>
|
||||
</el-input>
|
||||
</template>
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="enable"
|
||||
:label="$t('test_resource_pool.enable_disable')"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
prop="enable"
|
||||
>
|
||||
<template v-slot:default="{row}">
|
||||
<el-switch
|
||||
v-model="scope.row.enable"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
active-color="#13ce66"
|
||||
v-model="row.enable"
|
||||
active-value="true"
|
||||
inactive-value="false"
|
||||
inactive-color="#ff4949"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('schedule.operation')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
circle size="mini"
|
||||
@click="handleAddStep(scope.$index)"></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
circle size="mini"
|
||||
@click="handleDeleteStep(scope.$index)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-button type="primary" @click="saveNotice">{{$t('commons.save')}}</el-button>
|
||||
<el-button type="primary" @click="saveNotice">{{$t('commons.save')}}</el-button>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
@ -111,19 +82,20 @@
|
|||
|
||||
<script>
|
||||
|
||||
import Crontab from "../cron/Crontab";
|
||||
import CrontabResult from "../cron/CrontabResult";
|
||||
import {cronValidate} from "@/common/js/cron";
|
||||
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
||||
import Crontab from "../cron/Crontab";
|
||||
import CrontabResult from "../cron/CrontabResult";
|
||||
import {cronValidate} from "@/common/js/cron";
|
||||
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
||||
|
||||
function defaultCustomValidate() {
|
||||
return {pass: true};
|
||||
}
|
||||
function defaultCustomValidate() {
|
||||
return {pass: true};
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "MsScheduleEdit",
|
||||
components: {CrontabResult, Crontab},
|
||||
props: {
|
||||
testId: String,
|
||||
save: Function,
|
||||
schedule: {},
|
||||
customValidate: {
|
||||
|
@ -133,8 +105,10 @@ function defaultCustomValidate() {
|
|||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
'schedule.value'() {
|
||||
this.form.cronValue = this.schedule.value;
|
||||
|
@ -164,25 +138,22 @@ function defaultCustomValidate() {
|
|||
form: {
|
||||
cronValue: ""
|
||||
},
|
||||
options: [{
|
||||
value: 'success',
|
||||
label: '执行成功通知'
|
||||
}, {
|
||||
value: 'fail',
|
||||
label: '执行失败通知'
|
||||
}, {
|
||||
value: 'all',
|
||||
label: '全部通知'
|
||||
}],
|
||||
value: '',
|
||||
tableData: [
|
||||
{
|
||||
receiver: "",
|
||||
email: "",
|
||||
event: "执行成功",
|
||||
names: [],
|
||||
email: "邮箱",
|
||||
enable: false
|
||||
},
|
||||
{
|
||||
event: "执行失败",
|
||||
names: [],
|
||||
email: "邮箱",
|
||||
enable: false
|
||||
}
|
||||
],
|
||||
enable: false,
|
||||
options: [{}],
|
||||
enable: true,
|
||||
email: "",
|
||||
activeName: 'first',
|
||||
rules: {
|
||||
|
@ -191,37 +162,31 @@ function defaultCustomValidate() {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
|
||||
userList() {
|
||||
this.result = this.$get('user/list', response => {
|
||||
this.options = response.data
|
||||
})
|
||||
},
|
||||
saveNotice(){
|
||||
let param = this.buildParam();
|
||||
/* this.result=this.$post("notice/save",param,()=>{
|
||||
|
||||
})*/
|
||||
|
||||
handleClick() {
|
||||
if (this.activeName == "second") {
|
||||
this.result = this.$get('notice/query/' + this.testId, response => {
|
||||
if (response.data.length > 0) {
|
||||
this.tableData = response.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
buildParam() {
|
||||
let param = {};
|
||||
param.form = this.tableData
|
||||
param.testId = ""
|
||||
param.event = this.value
|
||||
param.notices = this.tableData
|
||||
param.testId = this.testId
|
||||
return param;
|
||||
},
|
||||
handleAddStep(index) {
|
||||
let form = {}
|
||||
form.receiver = null;
|
||||
form.email = null;
|
||||
form.enable = null
|
||||
this.tableData.splice(index + 1, 0, form);
|
||||
},
|
||||
handleDeleteStep(index) {
|
||||
this.tableData.splice(index, 1);
|
||||
},
|
||||
open() {
|
||||
this.dialogVisible = true;
|
||||
this.form.cronValue = this.schedule.value;
|
||||
listenGoBack(this.close);
|
||||
this.handleClick()
|
||||
},
|
||||
crontabFill(value, resultList) {
|
||||
//确定后回传的值
|
||||
|
@ -243,6 +208,12 @@ function defaultCustomValidate() {
|
|||
}
|
||||
});
|
||||
},
|
||||
saveNotice(){
|
||||
let param = this.buildParam();
|
||||
this.result = this.$post("notice/save", param, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
})
|
||||
},
|
||||
close() {
|
||||
this.dialogVisible = false;
|
||||
this.form.cronValue = '';
|
||||
|
@ -274,13 +245,13 @@ function defaultCustomValidate() {
|
|||
|
||||
<style scoped>
|
||||
|
||||
.inp {
|
||||
width: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.inp {
|
||||
width: 50%;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</el-button>
|
||||
|
||||
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
||||
:check-open="checkScheduleEdit" :custom-validate="durationValidate"/>
|
||||
:check-open="checkScheduleEdit" :test-id="testId" :custom-validate="durationValidate"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
|
|
@ -90,6 +90,20 @@
|
|||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.response_timeout') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number :disabled="readOnly" size="mini" v-model="responseTimeout" :min="10"
|
||||
:max="100000"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
ms
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
|
@ -109,169 +123,170 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||
|
||||
export default {
|
||||
name: "PerformanceAdvancedConfig",
|
||||
components: {MsTableOperatorButton},
|
||||
data() {
|
||||
return {
|
||||
timeout: 2000,
|
||||
statusCode: [],
|
||||
domains: [],
|
||||
params: [],
|
||||
statusCodeStr: '',
|
||||
}
|
||||
export default {
|
||||
name: "PerformanceAdvancedConfig",
|
||||
components: {MsTableOperatorButton},
|
||||
data() {
|
||||
return {
|
||||
timeout: 2000,
|
||||
responseTimeout: null,
|
||||
statusCode: [],
|
||||
domains: [],
|
||||
params: [],
|
||||
statusCodeStr: '',
|
||||
}
|
||||
},
|
||||
props: {
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
props: {
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
testId: String,
|
||||
},
|
||||
mounted() {
|
||||
testId: String,
|
||||
},
|
||||
mounted() {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAdvancedConfig() {
|
||||
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
this.timeout = data.timeout || 10;
|
||||
this.responseTimeout = data.responseTimeout;
|
||||
this.statusCode = data.statusCode || [];
|
||||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
}
|
||||
});
|
||||
},
|
||||
add(dataName) {
|
||||
if (dataName === 'domains') {
|
||||
this[dataName].push({
|
||||
domain: 'fit2cloud.com',
|
||||
enable: true,
|
||||
ip: '127.0.0.1',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
if (dataName === 'params') {
|
||||
this[dataName].push({
|
||||
name: 'param1',
|
||||
enable: true,
|
||||
value: '0',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAdvancedConfig() {
|
||||
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
this.timeout = data.timeout || 10;
|
||||
this.statusCode = data.statusCode || [];
|
||||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
/*this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);*/
|
||||
}
|
||||
});
|
||||
},
|
||||
add(dataName) {
|
||||
if (dataName === 'domains') {
|
||||
this[dataName].push({
|
||||
domain: 'fit2cloud.com',
|
||||
enable: true,
|
||||
ip: '127.0.0.1',
|
||||
edit: true,
|
||||
});
|
||||
edit(row) {
|
||||
row.edit = !row.edit
|
||||
},
|
||||
del(row, dataName, index) {
|
||||
this[dataName].splice(index, 1);
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false;
|
||||
row.enable = true;
|
||||
},
|
||||
groupBy(data, key) {
|
||||
return data.reduce((p, c) => {
|
||||
let name = c[key];
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = 0;
|
||||
}
|
||||
if (dataName === 'params') {
|
||||
this[dataName].push({
|
||||
name: 'param1',
|
||||
enable: true,
|
||||
value: '0',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
edit(row) {
|
||||
row.edit = !row.edit
|
||||
},
|
||||
del(row, dataName, index) {
|
||||
this[dataName].splice(index, 1);
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false;
|
||||
row.enable = true;
|
||||
},
|
||||
groupBy(data, key) {
|
||||
return data.reduce((p, c) => {
|
||||
let name = c[key];
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = 0;
|
||||
}
|
||||
p[name]++;
|
||||
return p;
|
||||
}, {});
|
||||
},
|
||||
validConfig() {
|
||||
let counts = this.groupBy(this.domains, 'domain');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.domain_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
counts = this.groupBy(this.params, 'name');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
|
||||
this.$error(this.$t('load_test.domain_ip_is_empty'));
|
||||
p[name]++;
|
||||
return p;
|
||||
}, {});
|
||||
},
|
||||
validConfig() {
|
||||
let counts = this.groupBy(this.domains, 'domain');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.domain_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
if (this.params.filter(d => !d.name || !d.value).length > 0) {
|
||||
this.$error(this.$t('load_test.param_name_value_is_empty'));
|
||||
}
|
||||
counts = this.groupBy(this.params, 'name');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
checkStatusCode() {
|
||||
let license_num = this.statusCodeStr;
|
||||
license_num = license_num.replace(/[^\d,]/g, ''); // 清除“数字”和“.”以外的字符
|
||||
this.statusCodeStr = license_num;
|
||||
},
|
||||
cancelAllEdit() {
|
||||
this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);
|
||||
},
|
||||
configurations() {
|
||||
let statusCode = [];
|
||||
if (this.statusCodeStr) {
|
||||
statusCode = this.statusCodeStr.split(',');
|
||||
}
|
||||
return {
|
||||
timeout: this.timeout,
|
||||
statusCode: statusCode,
|
||||
params: this.params,
|
||||
domains: this.domains,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
|
||||
this.$error(this.$t('load_test.domain_ip_is_empty'));
|
||||
return false;
|
||||
}
|
||||
if (this.params.filter(d => !d.name || !d.value).length > 0) {
|
||||
this.$error(this.$t('load_test.param_name_value_is_empty'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
checkStatusCode() {
|
||||
let license_num = this.statusCodeStr;
|
||||
license_num = license_num.replace(/[^\d,]/g, ''); // 清除“数字”和“.”以外的字符
|
||||
this.statusCodeStr = license_num;
|
||||
},
|
||||
cancelAllEdit() {
|
||||
this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);
|
||||
},
|
||||
configurations() {
|
||||
let statusCode = [];
|
||||
if (this.statusCodeStr) {
|
||||
statusCode = this.statusCodeStr.split(',');
|
||||
}
|
||||
return {
|
||||
timeout: this.timeout,
|
||||
responseTimeout: this.responseTimeout,
|
||||
statusCode: statusCode,
|
||||
params: this.params,
|
||||
domains: this.domains,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
padding-right: 0px;
|
||||
}
|
||||
.edit-input {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.tb-edit .el-textarea {
|
||||
display: none;
|
||||
}
|
||||
.tb-edit .el-textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tb-edit .current-row .el-textarea {
|
||||
display: block;
|
||||
}
|
||||
.tb-edit .current-row .el-textarea {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tb-edit .current-row .el-textarea + span {
|
||||
display: none;
|
||||
}
|
||||
.tb-edit .current-row .el-textarea + span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-col {
|
||||
text-align: left;
|
||||
}
|
||||
.el-col {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-col .el-table {
|
||||
align: center;
|
||||
}
|
||||
.el-col .el-table {
|
||||
align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,123 +1,235 @@
|
|||
<template>
|
||||
<el-dialog class="testcase-import" :title="$t('test_track.case.import.case_import')" :visible.sync="dialogVisible"
|
||||
@close="close">
|
||||
<el-dialog class="testcase-import" :title="$t('test_track.case.import.case_import')" :visible.sync="dialogVisible"
|
||||
@close="close">
|
||||
|
||||
<el-row>
|
||||
<el-link type="primary" class="download-template"
|
||||
@click="downloadTemplate"
|
||||
>{{$t('test_track.case.import.download_template')}}</el-link></el-row>
|
||||
<el-row>
|
||||
<el-upload
|
||||
v-loading="result.loading"
|
||||
:element-loading-text="$t('test_track.case.import.importing')"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
class="upload-demo"
|
||||
multiple
|
||||
:limit="1"
|
||||
action=""
|
||||
:on-exceed="handleExceed"
|
||||
:beforeUpload="uploadValidate"
|
||||
:on-error="handleError"
|
||||
:show-file-list="false"
|
||||
:http-request="upload"
|
||||
:file-list="fileList">
|
||||
<template v-slot:trigger>
|
||||
<el-button size="mini" type="success" plain>{{$t('test_track.case.import.click_upload')}}</el-button>
|
||||
</template>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">{{$t('test_track.case.import.upload_limit')}}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
<el-tabs v-model="activeName" simple>
|
||||
<el-tab-pane :label="$t('test_track.case.import.excel_title')" name="excelImport">
|
||||
|
||||
<el-row>
|
||||
<ul>
|
||||
<li v-for="errFile in errList" :key="errFile.rowNum">
|
||||
{{errFile.errMsg}}
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-link type="primary" class="download-template"
|
||||
@click="downloadTemplate"
|
||||
>{{$t('test_track.case.import.download_template')}}
|
||||
</el-link>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-upload
|
||||
v-loading="result.loading"
|
||||
:element-loading-text="$t('test_track.case.import.importing')"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
class="upload-demo"
|
||||
multiple
|
||||
:limit="1"
|
||||
action=""
|
||||
:on-exceed="handleExceed"
|
||||
:beforeUpload="uploadValidate"
|
||||
:on-error="handleError"
|
||||
:show-file-list="false"
|
||||
:http-request="upload"
|
||||
:file-list="fileList">
|
||||
<template v-slot:trigger>
|
||||
<el-button size="mini" type="success" plain>{{$t('test_track.case.import.click_upload')}}</el-button>
|
||||
</template>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">{{$t('test_track.case.import.upload_limit')}}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
<el-row>
|
||||
<ul>
|
||||
<li v-for="errFile in errList" :key="errFile.rowNum">
|
||||
{{errFile.errMsg}}
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<!-- Xmind 导入 -->
|
||||
<el-tab-pane :label="$t('test_track.case.import.xmind_title')" name="xmindImport" style="border: 0px">
|
||||
<el-row class="import-row">
|
||||
<div class="el-step__icon is-text" style="background-color: #C9E6F8;border-color: #C9E6F8;margin-right: 10px">
|
||||
<div class="el-step__icon-inner">1</div>
|
||||
</div>
|
||||
<label class="ms-license-label">{{$t('test_track.case.import.import_desc')}}</label>
|
||||
</el-row>
|
||||
<el-row class="import-row">
|
||||
<el-card :body-style="{ padding: '0px' }">
|
||||
<img src="../../../../../assets/xmind.jpg"
|
||||
class="testcase-import-img">
|
||||
</el-card>
|
||||
|
||||
</el-row>
|
||||
<el-row class="import-row">
|
||||
<div class="el-step__icon is-text"
|
||||
style="background-color: #C9E6F8;border-color: #C9E6F8;margin-right: 10px ">
|
||||
<div class="el-step__icon-inner">2</div>
|
||||
</div>
|
||||
<label class="ms-license-label">{{$t('test_track.case.import.import_file')}}</label>
|
||||
</el-row>
|
||||
<el-row class="import-row">
|
||||
<el-link type="primary" class="download-template"
|
||||
@click="downloadXmindTemplate"
|
||||
>{{$t('test_track.case.import.download_template')}}
|
||||
</el-link>
|
||||
</el-row>
|
||||
<el-row class="import-row">
|
||||
<el-upload
|
||||
v-loading="result.loading"
|
||||
:element-loading-text="$t('test_track.case.import.importing')"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
class="upload-demo"
|
||||
multiple
|
||||
:limit="1"
|
||||
action=""
|
||||
:on-exceed="handleExceed"
|
||||
:beforeUpload="uploadValidateXmind"
|
||||
:on-error="handleError"
|
||||
:show-file-list="false"
|
||||
:http-request="uploadXmind"
|
||||
:file-list="fileList">
|
||||
<template v-slot:trigger>
|
||||
<el-button size="mini" type="success" plain>{{$t('test_track.case.import.click_upload')}}</el-button>
|
||||
</template>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">{{$t('test_track.case.import.upload_xmind')}}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<ul>
|
||||
<li v-for="errFile in xmindErrList" :key="errFile.rowNum">
|
||||
{{errFile.errMsg}}
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ElUploadList from "element-ui/packages/upload/src/upload-list";
|
||||
import MsTableButton from '../../../../components/common/components/MsTableButton';
|
||||
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import ElUploadList from "element-ui/packages/upload/src/upload-list";
|
||||
import MsTableButton from '../../../../components/common/components/MsTableButton';
|
||||
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import {TokenKey, WORKSPACE_ID} from '../../../../../common/js/constants';
|
||||
|
||||
export default {
|
||||
name: "TestCaseImport",
|
||||
components: {ElUploadList, MsTableButton},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
dialogVisible: false,
|
||||
fileList: [],
|
||||
errList: [],
|
||||
isLoading: false
|
||||
}
|
||||
export default {
|
||||
name: "TestCaseImport",
|
||||
components: {ElUploadList, MsTableButton},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
activeName: 'excelImport',
|
||||
dialogVisible: false,
|
||||
fileList: [],
|
||||
errList: [],
|
||||
xmindErrList: [],
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExceed(files, fileList) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
type: String
|
||||
uploadValidate(file) {
|
||||
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
|
||||
if (suffix != 'xls' && suffix != 'xlsx') {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_format'));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExceed(files, fileList) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
|
||||
},
|
||||
uploadValidate(file) {
|
||||
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
|
||||
if (suffix != 'xls' && suffix != 'xlsx' && suffix != 'xmind') {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_format'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size / 1024 / 1024 > 20) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
|
||||
return false;
|
||||
if (file.size / 1024 / 1024 > 20) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
|
||||
return false;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.errList = [];
|
||||
this.xmindErrList = [];
|
||||
return true;
|
||||
},
|
||||
uploadValidateXmind(file) {
|
||||
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
|
||||
if (suffix != 'xmind') {
|
||||
this.$warning(this.$t('test_track.case.import.upload_xmind_format'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size / 1024 / 1024 > 20) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
|
||||
return false;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.errList = [];
|
||||
this.xmindErrList = [];
|
||||
return true;
|
||||
},
|
||||
handleError(err, file, fileList) {
|
||||
this.isLoading = false;
|
||||
this.$error(err.message);
|
||||
},
|
||||
open() {
|
||||
listenGoBack(this.close);
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
close() {
|
||||
removeGoBackListener(this.close);
|
||||
this.dialogVisible = false;
|
||||
this.fileList = [];
|
||||
this.errList = [];
|
||||
this.xmindErrList = [];
|
||||
},
|
||||
downloadTemplate() {
|
||||
this.$fileDownload('/test/case/export/template');
|
||||
},
|
||||
downloadXmindTemplate() {
|
||||
this.$fileDownload('/test/case/export/xmindTemplate');
|
||||
},
|
||||
upload(file) {
|
||||
this.isLoading = false;
|
||||
this.fileList.push(file.file);
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
|
||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId + '/' + user.id, file.file, null, {}, response => {
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
this.dialogVisible = false;
|
||||
this.$emit("refresh");
|
||||
} else {
|
||||
this.errList = res.errList;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.errList = [];
|
||||
return true;
|
||||
},
|
||||
handleError(err, file, fileList) {
|
||||
this.isLoading = false;
|
||||
this.$error(err.message);
|
||||
},
|
||||
open() {
|
||||
listenGoBack(this.close);
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
close() {
|
||||
removeGoBackListener(this.close);
|
||||
this.dialogVisible = false;
|
||||
this.fileList = [];
|
||||
this.errList = [];
|
||||
},
|
||||
downloadTemplate() {
|
||||
this.$fileDownload('/test/case/export/template');
|
||||
},
|
||||
upload(file) {
|
||||
this.isLoading = false;
|
||||
this.fileList.push(file.file);
|
||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId, file.file, null, {}, response => {
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
this.dialogVisible = false;
|
||||
this.$emit("refresh");
|
||||
} else {
|
||||
this.errList = res.errList;
|
||||
}
|
||||
this.fileList = [];
|
||||
}, erro => {
|
||||
this.fileList = [];
|
||||
});
|
||||
}
|
||||
}, erro => {
|
||||
this.fileList = [];
|
||||
});
|
||||
},
|
||||
uploadXmind(file) {
|
||||
this.isLoading = false;
|
||||
this.fileList.push(file.file);
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
|
||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId + '/' + user.id, file.file, null, {}, response => {
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
this.dialogVisible = false;
|
||||
this.$emit("refresh");
|
||||
} else {
|
||||
this.xmindErrList = res.errList;
|
||||
}
|
||||
this.fileList = [];
|
||||
}, erro => {
|
||||
this.fileList = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -130,8 +242,18 @@
|
|||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.import-row {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.testcase-import >>> .el-dialog {
|
||||
width: 400px;
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
.testcase-import-img {
|
||||
width: 614px;
|
||||
height: 312px;
|
||||
size: 200px;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -382,7 +382,7 @@ export default {
|
|||
method: 'post',
|
||||
responseType: 'blob',
|
||||
// data: {ids: [...this.selectIds]}
|
||||
data: {ids: ids}
|
||||
data: {ids: ids, projectId: this.currentProject.id}
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const filename = this.$t('test_track.case.test_case') + ".xlsx";
|
||||
|
@ -399,9 +399,22 @@ export default {
|
|||
});
|
||||
},
|
||||
handleBatch(type) {
|
||||
|
||||
if (this.selectRows.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
return;
|
||||
if (type === 'export') {
|
||||
this.$alert(this.$t('test_track.case.export_all_cases'), '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this.exportTestCase();
|
||||
}
|
||||
}
|
||||
})
|
||||
return;
|
||||
} else {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (type === 'move') {
|
||||
let ids = Array.from(this.selectRows).map(row => row.id);
|
||||
|
|
|
@ -218,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>
|
||||
|
@ -323,6 +329,8 @@
|
|||
test: {},
|
||||
activeTab: 'detail',
|
||||
isFailure: true,
|
||||
users: [],
|
||||
hasTapdId: false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
@ -490,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) {
|
||||
|
@ -515,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);
|
||||
|
@ -522,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 => {
|
||||
|
|
|
@ -94,3 +94,10 @@ body {
|
|||
border: 1px solid #409EFF;
|
||||
}
|
||||
/* 表格 input 编辑效果 --> */
|
||||
|
||||
.ms-border {
|
||||
padding: 10px;
|
||||
border: #DCDFE6 solid 1px;
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ export default {
|
|||
millisecond: 'ms',
|
||||
please_upload: 'Please upload file',
|
||||
reference_documentation: "Reference documentation",
|
||||
already_exists: 'The name already exists',
|
||||
date: {
|
||||
select_date: 'Select date',
|
||||
start_date: 'Start date',
|
||||
|
@ -378,6 +379,7 @@ export default {
|
|||
domain_ip_is_empty: 'Domain and IP cannot be empty',
|
||||
param_name_value_is_empty: 'Parameters cannot be empty',
|
||||
connect_timeout: 'Timeout to establish a connection',
|
||||
response_timeout: 'Timeout to response',
|
||||
custom_http_code: 'Custom HTTP response success status code',
|
||||
separated_by_commas: 'Separated by commas',
|
||||
create: 'Create Test',
|
||||
|
@ -416,6 +418,9 @@ export default {
|
|||
environment: "Environment",
|
||||
select_environment: "Please select environment",
|
||||
please_save_test: "Please Save Test First",
|
||||
common_config: "Common Config",
|
||||
http_config: "HTTP Config",
|
||||
database_config: "Database Config",
|
||||
},
|
||||
scenario: {
|
||||
scenario: "Scenario",
|
||||
|
@ -534,6 +539,19 @@ export default {
|
|||
check_registry_center: "Can't get interface list, please check the registry center",
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
dataSource: "Data Source",
|
||||
sql_script: "Sql Script",
|
||||
timeout: "Timeout(ms)",
|
||||
database_driver: "Driver",
|
||||
database_url: "Database URL",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
pool_max: "Max Number of Configuration",
|
||||
query_timeout: "Max Wait(ms)",
|
||||
name_cannot_be_empty: "SQL request name cannot be empty",
|
||||
dataSource_cannot_be_empty: "SQL request datasource cannot be empty",
|
||||
},
|
||||
api_import: {
|
||||
label: "Import",
|
||||
title: "API test import",
|
||||
|
@ -600,6 +618,7 @@ export default {
|
|||
execution_result: ": Please select the execution result",
|
||||
actual_result: ": The actual result is empty",
|
||||
case: {
|
||||
export_all_cases: 'Are you sure you want to export all use cases?',
|
||||
input_test_case: 'Please enter the associated case name',
|
||||
test_name: 'TestName',
|
||||
other: '--Other--',
|
||||
|
@ -657,13 +676,19 @@ export default {
|
|||
case_import: "Import test case",
|
||||
download_template: "Download template",
|
||||
click_upload: "Upload",
|
||||
upload_limit: "Only XLS/XLSX files can be uploaded, and no more than 20M",
|
||||
upload_limit: "Only XLS/XLSX/XMIND files can be uploaded, and no more than 20M",
|
||||
upload_xmind_format: "Upload files can only be .xmind format",
|
||||
upload_xmind: "Only xmind files can be uploaded, and no more than 500",
|
||||
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...",
|
||||
excel_title: "Excel ",
|
||||
xmind_title: "Xmind",
|
||||
import_desc: "Import instructions",
|
||||
import_file: "upload files",
|
||||
},
|
||||
export: {
|
||||
export: "Export cases"
|
||||
|
|
|
@ -114,6 +114,7 @@ export default {
|
|||
id: 'ID',
|
||||
millisecond: '毫秒',
|
||||
cannot_be_null: '不能为空',
|
||||
already_exists: '名称不能重复',
|
||||
date: {
|
||||
select_date: '选择日期',
|
||||
start_date: '开始日期',
|
||||
|
@ -377,6 +378,7 @@ export default {
|
|||
domain_ip_is_empty: '域名和IP不能为空',
|
||||
param_name_value_is_empty: '参数名和参数值不能为空',
|
||||
connect_timeout: '建立连接超时时间',
|
||||
response_timeout: '响应超时时间',
|
||||
custom_http_code: '自定义 HTTP 响应成功状态码',
|
||||
separated_by_commas: '按逗号分隔',
|
||||
create: '创建测试',
|
||||
|
@ -417,6 +419,9 @@ export default {
|
|||
environment: "环境",
|
||||
select_environment: "请选择环境",
|
||||
please_save_test: "请先保存测试",
|
||||
common_config: "通用配置",
|
||||
http_config: "HTTP配置",
|
||||
database_config: "数据库配置",
|
||||
},
|
||||
scenario: {
|
||||
scenario: "场景",
|
||||
|
@ -535,6 +540,19 @@ export default {
|
|||
check_registry_center: "获取失败,请检查Registry Center",
|
||||
form_description: "如果当前配置项无值,则取场景配置项的值",
|
||||
},
|
||||
sql: {
|
||||
dataSource: "数据源名称",
|
||||
sql_script: "SQL脚本",
|
||||
timeout: "超时时间(ms)",
|
||||
database_driver: "数据库驱动",
|
||||
database_url: "数据库连接URL",
|
||||
username: "用户名",
|
||||
password: "密码",
|
||||
pool_max: "最大连接数",
|
||||
query_timeout: "最大等待时间(ms)",
|
||||
name_cannot_be_empty: "SQL请求名称不能为空",
|
||||
dataSource_cannot_be_empty: "SQL请求数据源不能为空",
|
||||
}
|
||||
},
|
||||
api_import: {
|
||||
label: "导入",
|
||||
|
@ -603,6 +621,7 @@ export default {
|
|||
actual_result: ": 实际结果为空",
|
||||
|
||||
case: {
|
||||
export_all_cases: '确定要导出全部用例吗?',
|
||||
input_test_case: '请输入关联用例名称',
|
||||
test_name: '测试名称',
|
||||
other: "--其他--",
|
||||
|
@ -661,12 +680,18 @@ export default {
|
|||
download_template: "下载模版",
|
||||
click_upload: "点击上传",
|
||||
upload_limit: "只能上传xls/xlsx文件,且不超过20M",
|
||||
upload_xmind: "支持文件类型:.xmind;一次至多导入500 条用例",
|
||||
upload_xmind_format: "上传文件只能是 .xmind 格式",
|
||||
upload_limit_other_size: "上传文件大小不能超过",
|
||||
upload_limit_count: "一次只能上传一个文件",
|
||||
upload_limit_format: "上传文件只能是 xls、xlsx格式!",
|
||||
upload_limit_size: "上传文件大小不能超过 20MB!",
|
||||
success: "导入成功!",
|
||||
importing: "导入中...",
|
||||
excel_title: "表格文件",
|
||||
xmind_title: "思维导图",
|
||||
import_desc: "导入说明",
|
||||
import_file: "上传文件",
|
||||
},
|
||||
export: {
|
||||
export: "导出用例"
|
||||
|
@ -853,7 +878,7 @@ export default {
|
|||
schedule: {
|
||||
input_email: "请输入邮箱账号",
|
||||
event: "事件",
|
||||
receiving_mode: "邮箱",
|
||||
receiving_mode: "接收方式",
|
||||
receiver: "接收人",
|
||||
operation: "操作",
|
||||
task_notification: "任务通知",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
commons: {
|
||||
help_documentation: '幫助文檔',
|
||||
delete_cancelled: '已取消删除',
|
||||
delete_cancelled: '已取消刪除',
|
||||
workspace: '工作空間',
|
||||
organization: '組織',
|
||||
setting: '設置',
|
||||
|
@ -15,7 +15,7 @@ export default {
|
|||
save: '保存',
|
||||
save_success: '保存成功',
|
||||
delete_success: '刪除成功',
|
||||
copy_success: '複製成功',
|
||||
copy_success: '復制成功',
|
||||
modify_success: '修改成功',
|
||||
delete_cancel: '已取消刪除',
|
||||
confirm: '確定',
|
||||
|
@ -24,10 +24,10 @@ export default {
|
|||
operating: '操作',
|
||||
input_limit: '長度在 {0} 到 {1} 個字符',
|
||||
login: '登錄',
|
||||
welcome: '歡迎回來,請輸入用戶名和密碼登錄MeterSphere',
|
||||
username: '用戶名',
|
||||
welcome: '歡迎回來,請輸入用戶名和密碼登錄MeterSphere',
|
||||
username: '姓名',
|
||||
password: '密碼',
|
||||
input_username: '請輸入用戶名',
|
||||
input_username: '請輸入用戶姓名',
|
||||
input_password: '請輸入密碼',
|
||||
test: '測試',
|
||||
create_time: '創建時間',
|
||||
|
@ -38,6 +38,8 @@ export default {
|
|||
phone: '電話',
|
||||
role: '角色',
|
||||
personal_info: '個人信息',
|
||||
api_keys: 'API Keys',
|
||||
quota: '配額管理',
|
||||
status: '狀態',
|
||||
show_all: '顯示全部',
|
||||
show: '顯示',
|
||||
|
@ -45,8 +47,6 @@ export default {
|
|||
user: '用戶',
|
||||
system: '系統',
|
||||
personal_setting: '個人設置',
|
||||
api_keys: 'API Keys',
|
||||
quota: '配額管理',
|
||||
test_resource_pool: '測試資源池',
|
||||
system_setting: '系統設置',
|
||||
api: '接口測試',
|
||||
|
@ -55,7 +55,7 @@ export default {
|
|||
input_content: '請輸入內容',
|
||||
create: '新建',
|
||||
edit: '編輯',
|
||||
copy: '複製',
|
||||
copy: '復制',
|
||||
refresh: '刷新',
|
||||
remark: '備註',
|
||||
delete: '刪除',
|
||||
|
@ -69,8 +69,8 @@ export default {
|
|||
title: '標題',
|
||||
custom: '自定義',
|
||||
select_date: '選擇日期',
|
||||
calendar_heatmap: '測試日曆',
|
||||
months_1: '一月',
|
||||
calendar_heatmap: '測試日歷',
|
||||
months_1: '壹月',
|
||||
months_2: '二月',
|
||||
months_3: '三月',
|
||||
months_4: '四月',
|
||||
|
@ -80,10 +80,10 @@ export default {
|
|||
months_8: '八月',
|
||||
months_9: '九月',
|
||||
months_10: '十月',
|
||||
months_11: '十一月',
|
||||
months_11: '十壹月',
|
||||
months_12: '十二月',
|
||||
weeks_0: '周日',
|
||||
weeks_1: '周一',
|
||||
weeks_1: '周壹',
|
||||
weeks_2: '周二',
|
||||
weeks_3: '周三',
|
||||
weeks_4: '周四',
|
||||
|
@ -95,23 +95,26 @@ export default {
|
|||
connection_failed: '連接失敗',
|
||||
save_failed: '保存失敗',
|
||||
host_cannot_be_empty: '主機不能為空',
|
||||
port_cannot_be_empty: '埠號不能為空',
|
||||
port_cannot_be_empty: '端口號不能為空',
|
||||
account_cannot_be_empty: '帳戶不能為空',
|
||||
remove: '移除',
|
||||
remove_cancel: '移除取消',
|
||||
remove_success: '移除成功',
|
||||
tips: '认認證資訊已過期,請重新登入',
|
||||
tips: '認證信息已過期,請重新登錄',
|
||||
not_performed_yet: '尚未執行',
|
||||
incorrect_input: '輸入內容不正確',
|
||||
delete_confirm: '請輸入以下內容,確認刪除:',
|
||||
login_username: 'ID 或 郵箱',
|
||||
input_login_username: '請輸入用戶 ID 或 郵箱',
|
||||
input_name: '請輸入名稱',
|
||||
please_upload: '請上傳文件',
|
||||
formatErr: '格式錯誤',
|
||||
please_save: '請先保存',
|
||||
id: 'ID',
|
||||
cannot_be_null: '不能为空',
|
||||
millisecond: '毫秒',
|
||||
reference_documentation: "參考文檔",
|
||||
please_upload: '請上傳文件',
|
||||
id: 'ID',
|
||||
millisecond: '毫秒',
|
||||
cannot_be_null: '不能為空',
|
||||
already_exists: '名稱不能重復',
|
||||
date: {
|
||||
select_date: '選擇日期',
|
||||
start_date: '開始日期',
|
||||
|
@ -136,7 +139,7 @@ export default {
|
|||
search: "查詢",
|
||||
reset: "重置",
|
||||
and: '所有',
|
||||
or: '任意一個',
|
||||
or: '任意壹個',
|
||||
operators: {
|
||||
like: "包含",
|
||||
not_like: "不包含",
|
||||
|
@ -161,45 +164,46 @@ export default {
|
|||
edition: '產品版本',
|
||||
licenseVersion: '授權版本',
|
||||
count: '授權數量',
|
||||
valid_license: '授權验证',
|
||||
valid_license: '授權驗證',
|
||||
show_license: '查看授權',
|
||||
valid_license_error: '授權验证失败',
|
||||
status: '授權状态',
|
||||
expired: '已过期',
|
||||
valid_license_error: '授權驗證失敗',
|
||||
status: '授權狀態',
|
||||
valid: '有效',
|
||||
invalid: '無效',
|
||||
expired: '已過期',
|
||||
},
|
||||
|
||||
workspace: {
|
||||
create: '創建工作空間',
|
||||
update: '修改工作空間',
|
||||
delete: '刪除工作空間',
|
||||
delete_confirm: '删除該工作空間會關聯删除該工作空間下的所有資源(如:相關項目,測試用例等),確定要删除嗎?',
|
||||
delete_confirm: '刪除該工作空間會關聯刪除該工作空間下的所有資源(如:相關項目,測試用例等),確定要刪除嗎?',
|
||||
add: '添加工作空間',
|
||||
input_name: '請輸入工作空間名稱',
|
||||
search_by_name: '根據名稱搜索',
|
||||
organization_name: '所屬組織',
|
||||
please_choose_organization: '請選擇組織',
|
||||
please_select_a_workspace_first: '請先選擇工作空間! ',
|
||||
please_select_a_workspace_first: '請先選擇工作空間!',
|
||||
none: '無工作空間',
|
||||
select: '選擇工作空間',
|
||||
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
|
||||
delete_warning: '删除该工作空间将同步删除该工作空间下所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
|
||||
delete_warning: '刪除該工作空間將同步刪除該工作空間下所有項目,以及項目中的所有用例、接口測試、性能測試等,確定要刪除嗎?',
|
||||
},
|
||||
organization: {
|
||||
create: '創建組織',
|
||||
modify: '修改組織',
|
||||
delete: '刪除組織',
|
||||
delete_confirm: '删除該組織會關聯删除該組織下的所有資源(如:相關工作空間,項目,測試用例等),確定要删除嗎?',
|
||||
delete_confirm: '刪除該組織會關聯刪除該組織下的所有資源(如:相關工作空間,項目,測試用例等),確定要刪除嗎?',
|
||||
input_name: '請輸入組織名稱',
|
||||
select_organization: '請選擇組織',
|
||||
search_by_name: '根據名稱搜索',
|
||||
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
|
||||
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
|
||||
none: '無組織',
|
||||
select: '選擇組織',
|
||||
delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?',
|
||||
delete_warning: '刪除該組織將同步刪除該組織下所有相關工作空間和相關工作空間下的所有項目,以及項目中的所有用例、接口測試、性能測試等,確定要刪除嗎?',
|
||||
service_integration: '服務集成',
|
||||
defect_manage: '缺陷管理平台',
|
||||
defect_manage: '缺陷管理平臺',
|
||||
integration: {
|
||||
select_defect_platform: '請選擇要集成的缺陷管理平台:',
|
||||
select_defect_platform: '請選擇要集成的缺陷管理平臺:',
|
||||
basic_auth_info: 'Basic Auth 賬號信息:',
|
||||
api_account: 'API 賬號',
|
||||
api_password: 'API 口令',
|
||||
|
@ -210,7 +214,7 @@ export default {
|
|||
input_jira_url: '請輸入Jira地址,例:https://metersphere.atlassian.net/',
|
||||
input_jira_issuetype: '請輸入問題類型',
|
||||
use_tip: '使用指引:',
|
||||
use_tip_tapd: 'Basic Auth 賬號信息在"公司管理-安全與集成-開放平台"中查詢',
|
||||
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
|
||||
use_tip_jira: 'Jira software server 認證信息為 賬號密碼,Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
|
||||
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
|
||||
link_the_project_now: '馬上關聯項目',
|
||||
|
@ -218,8 +222,8 @@ export default {
|
|||
cancel_integration: '取消集成',
|
||||
cancel_confirm: '確認取消集成 ',
|
||||
successful_operation: '操作成功',
|
||||
not_integrated: '未集成該平台',
|
||||
choose_platform: '請選擇集成的平台',
|
||||
not_integrated: '未集成該平臺',
|
||||
choose_platform: '請選擇集成的平臺',
|
||||
verified: '驗證通過'
|
||||
}
|
||||
},
|
||||
|
@ -229,7 +233,7 @@ export default {
|
|||
edit: '編輯項目',
|
||||
delete: '刪除項目',
|
||||
delete_confirm: '確定要刪除這個項目嗎?',
|
||||
delete_tip: '删除該項目,會删除該項目下所有測試資源,確定要删除嗎?',
|
||||
delete_tip: '刪除該項目,會刪除該項目下所有測試資源,確定要刪除嗎?',
|
||||
search_by_name: '根據名稱搜索',
|
||||
input_name: '請輸入項目名稱',
|
||||
owning_workspace: '所屬工作空間',
|
||||
|
@ -252,11 +256,11 @@ export default {
|
|||
special_characters_are_not_supported: '不支持特殊字符',
|
||||
mobile_number_format_is_incorrect: '手機號碼格式不正確',
|
||||
email_format_is_incorrect: '郵箱格式不正確',
|
||||
password_format_is_incorrect: '有效密碼:8-30 位,英文大小寫字母+數位+特殊字元(可選)',
|
||||
password_format_is_incorrect: '有效密碼:8-30位,英文大小寫字母+數字+特殊字符(可選)',
|
||||
old_password: '舊密碼',
|
||||
new_password: '新密碼',
|
||||
repeat_password: '確認密碼',
|
||||
inconsistent_passwords: '兩次輸入的密碼不一致',
|
||||
inconsistent_passwords: '兩次輸入的密碼不壹致',
|
||||
remove_member: '確定要移除該成員嗎',
|
||||
input_id_or_email: '請輸入用戶 ID, 或者 用戶郵箱',
|
||||
no_such_user: '無此用戶信息, 請輸入正確的用戶 ID 或者 用戶郵箱!',
|
||||
|
@ -264,7 +268,7 @@ export default {
|
|||
user: {
|
||||
create: '創建用戶',
|
||||
modify: '修改用戶',
|
||||
input_name: '請輸入用戶名',
|
||||
input_name: '請輸入用戶姓名',
|
||||
input_id: '請輸入ID',
|
||||
input_email: '請輸入郵箱',
|
||||
input_password: '請輸入密碼',
|
||||
|
@ -287,7 +291,6 @@ export default {
|
|||
add: '添加角色',
|
||||
},
|
||||
report: {
|
||||
name: '項目名稱',
|
||||
recent: '最近的報告',
|
||||
search_by_name: '根據名稱搜索',
|
||||
test_name: '所屬測試',
|
||||
|
@ -322,8 +325,8 @@ export default {
|
|||
delete_batch_confirm: '確認批量刪除報告',
|
||||
},
|
||||
load_test: {
|
||||
same_project_test: '只能運行同一項目內的測試',
|
||||
run: '一鍵運行',
|
||||
same_project_test: '只能運行同壹項目內的測試',
|
||||
already_exists: '測試名稱不能重復',
|
||||
operating: '操作',
|
||||
recent: '最近的測試',
|
||||
search_by_name: '根據名稱搜索',
|
||||
|
@ -336,30 +339,30 @@ export default {
|
|||
pressure_config: '壓力配置',
|
||||
advanced_config: '高級配置',
|
||||
runtime_config: '運行配置',
|
||||
is_running: '正在運行! ',
|
||||
test_name_is_null: '測試名稱不能為空! ',
|
||||
project_is_null: '項目不能為空! ',
|
||||
jmx_is_null: '必需包含一個JMX文件,且只能包含一個JMX文件!',
|
||||
is_running: '正在運行!',
|
||||
test_name_is_null: '測試名稱不能為空!',
|
||||
project_is_null: '項目不能為空!',
|
||||
jmx_is_null: '必需包含壹個JMX文件,且只能包含壹個JMX文件!',
|
||||
file_name: '文件名',
|
||||
file_size: '文件大小',
|
||||
file_type: '文件類型',
|
||||
file_status: '文件狀態',
|
||||
last_modify_time: '修改時間',
|
||||
upload_tips: '將文件拖到此處,或<em>點擊上傳</em>',
|
||||
upload_tips: '將文件拖到此處,或<em>點擊上傳</em>',
|
||||
upload_type: '只能上傳JMX/CSV文件',
|
||||
related_file_not_found: "未找到關聯的測試文件!",
|
||||
delete_file_confirm: '確認刪除文件: ',
|
||||
file_size_limit: "文件個數超出限制!",
|
||||
delete_file: "文件已存在,請先刪除同名文件!",
|
||||
thread_num: '並髮用戶數:',
|
||||
thread_num: '並發用戶數:',
|
||||
input_thread_num: '請輸入線程數',
|
||||
duration: '壓測時長(分鐘):',
|
||||
input_duration: '請輸入時長',
|
||||
rps_limit: 'RPS上限:',
|
||||
input_rps_limit: '請輸入限制',
|
||||
ramp_up_time_within: '在',
|
||||
ramp_up_time_minutes: '分鐘內,分',
|
||||
ramp_up_time_times: '次增加並髮用戶',
|
||||
ramp_up_time_minutes: '分鐘內,分',
|
||||
ramp_up_time_times: '次增加並發用戶',
|
||||
advanced_config_error: '高級配置校驗失敗',
|
||||
domain_bind: '域名綁定',
|
||||
domain: '域名',
|
||||
|
@ -370,14 +373,16 @@ export default {
|
|||
params: '自定義屬性',
|
||||
param_name: '屬性名',
|
||||
param_value: '屬性值',
|
||||
domain_is_duplicate: '域名不能重複',
|
||||
param_is_duplicate: '參數名不能重複',
|
||||
domain_is_duplicate: '域名不能重復',
|
||||
param_is_duplicate: '參數名不能重復',
|
||||
domain_ip_is_empty: '域名和IP不能為空',
|
||||
param_name_value_is_empty: '參數名和參數值不能為空',
|
||||
connect_timeout: '建立連接超時時間',
|
||||
response_timeout: '響應超時時間',
|
||||
custom_http_code: '自定義 HTTP 響應成功狀態碼',
|
||||
separated_by_commas: '按逗號分隔',
|
||||
create: '創建測試',
|
||||
run: '壹鍵運行',
|
||||
select_resource_pool: '請選擇資源池',
|
||||
resource_pool_is_null: '資源池為空',
|
||||
download_log_file: '下載完整日誌文件',
|
||||
|
@ -395,13 +400,13 @@ export default {
|
|||
reset: "重置",
|
||||
input_name: "請輸入測試名稱",
|
||||
select_project: "請選擇項目",
|
||||
variable_name: "變數名",
|
||||
variable: "變數",
|
||||
variable_name: "變量名",
|
||||
variable: "變量",
|
||||
copied: "已拷貝",
|
||||
key: "鍵",
|
||||
value: "值",
|
||||
create_performance_test: "創建性能測試",
|
||||
export_config: "匯出",
|
||||
export_config: "導出",
|
||||
enable_validate_tip: "沒有可用請求",
|
||||
copy: "復制測試",
|
||||
environment: {
|
||||
|
@ -414,21 +419,23 @@ export default {
|
|||
environment: "環境",
|
||||
select_environment: "請選擇環境",
|
||||
please_save_test: "請先保存測試",
|
||||
common_config: "通用配置",
|
||||
http_config: "HTTP配置",
|
||||
database_config: "數據庫配置",
|
||||
},
|
||||
scenario: {
|
||||
scenario: "場景",
|
||||
dubbo: "Dubbo配寘",
|
||||
creator: "創建人",
|
||||
config: "場景配寘",
|
||||
dubbo: "Dubbo配置",
|
||||
config: "場景配置",
|
||||
input_name: "請輸入場景名稱",
|
||||
name: "場景名稱",
|
||||
base_url: "基礎URL",
|
||||
base_url_description: "基礎URL作為所有請求的URL首碼",
|
||||
variables: "自定義變數",
|
||||
base_url_description: "基礎URL作為所有請求的URL前綴",
|
||||
variables: "自定義變量",
|
||||
headers: "請求頭",
|
||||
kv_description: "所有請求可以使用自定義變數",
|
||||
copy: "複製場景",
|
||||
delete: "删除場景",
|
||||
kv_description: "所有請求可以使用自定義變量",
|
||||
copy: "復制場景",
|
||||
delete: "刪除場景",
|
||||
disable: "禁用",
|
||||
enable: "啟用",
|
||||
create_scenario: "創建新場景",
|
||||
|
@ -437,13 +444,13 @@ export default {
|
|||
enable_disable: "啟用/禁用",
|
||||
test_name: "測試名稱",
|
||||
reference: "引用",
|
||||
clone: "複製",
|
||||
clone: "復制",
|
||||
cant_reference: '歷史測試文件,重新保存後才可被引用'
|
||||
},
|
||||
request: {
|
||||
debug: "調試",
|
||||
copy: "複製請求",
|
||||
delete: "删除請求",
|
||||
copy: "復制請求",
|
||||
delete: "刪除請求",
|
||||
input_name: "請輸入請求名稱",
|
||||
input_url: "請輸入請求URL",
|
||||
input_path: "請輸入請求路徑",
|
||||
|
@ -461,7 +468,7 @@ export default {
|
|||
parameters: "請求參數",
|
||||
jmeter_func: "Jmeter 方法",
|
||||
parameters_filter_example: "示例",
|
||||
parameters_filter_tips: "只支持MockJs函數結果預覽",
|
||||
parameters_filter_tips: "只支持 MockJs 函數結果預覽",
|
||||
parameters_advance: "高級參數設置",
|
||||
parameters_preview: "預覽",
|
||||
parameters_mock_filter_tips: "請輸入關鍵字進行過濾",
|
||||
|
@ -471,47 +478,48 @@ export default {
|
|||
parameters_advance_add_func_limit: "最多支持5個函數",
|
||||
parameters_advance_add_func_error: "請先選擇函數",
|
||||
parameters_advance_add_param_error: "請輸入函數參數",
|
||||
parameters_desc: "參數追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
||||
parameters_desc: "參數追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
||||
headers: "請求頭",
|
||||
body: "請求內容",
|
||||
body_kv: "鍵值對",
|
||||
body_text: "文字",
|
||||
body_text: "文本",
|
||||
timeout_config: "超時設置",
|
||||
connect_timeout: "連接超時",
|
||||
response_timeout: "響應超時",
|
||||
follow_redirects: "跟隨重定向",
|
||||
body_upload_limit_size: "上傳文件大小不能超過 500 MB!",
|
||||
condition: "條件",
|
||||
condition_variable: "變量,例如: ${var}",
|
||||
wait: "等待",
|
||||
assertions: {
|
||||
label: "斷言",
|
||||
text: "文字",
|
||||
text: "文本",
|
||||
regex: "正則",
|
||||
response_time: "回應時間",
|
||||
response_time: "響應時間",
|
||||
select_type: "請選擇類型",
|
||||
select_subject: "請選擇對象",
|
||||
select_condition: "請選擇條件",
|
||||
contains: "包含",
|
||||
not_contains: "不包含",
|
||||
equals: "等於",
|
||||
start_with: "以…開始",
|
||||
end_with: "以…結束",
|
||||
start_with: "以...開始",
|
||||
end_with: "以...結束",
|
||||
value: "值",
|
||||
expect: "期望值",
|
||||
expression: "Perl型規則運算式",
|
||||
response_in_time: "回應時間在…毫秒以內",
|
||||
expression: "Perl型正則表達式",
|
||||
response_in_time: "響應時間在...毫秒以內",
|
||||
},
|
||||
extract: {
|
||||
label: "選取",
|
||||
label: "提取",
|
||||
select_type: "請選擇類型",
|
||||
description: "從響應結果中選取數據並將其存儲在變數中,在後續請求中使用變數。",
|
||||
description: "從響應結果中提取數據並將其存儲在變量中,在後續請求中使用變量。",
|
||||
regex: "正則",
|
||||
regex_expression: "Perl型規則運算式",
|
||||
json_path_expression: "JSONPath運算式",
|
||||
xpath_expression: "XPath運算式",
|
||||
regex_expression: "Perl型正則表達式",
|
||||
json_path_expression: "JSONPath表達式",
|
||||
xpath_expression: "XPath表達式",
|
||||
},
|
||||
processor: {
|
||||
pre_exec_script : "預執行腳本",
|
||||
pre_exec_script: "預執行腳本",
|
||||
post_exec_script: "後執行腳本",
|
||||
code_template: "代碼模版",
|
||||
bean_shell_processor_tip: "僅支持 BeanShell 腳本",
|
||||
|
@ -522,14 +530,28 @@ export default {
|
|||
code_template_get_response_result: "獲取響應結果"
|
||||
},
|
||||
dubbo: {
|
||||
protocol: "協定",
|
||||
protocol: "協議",
|
||||
input_interface: "請輸入Interface",
|
||||
input_method: "請輸入Method",
|
||||
input_config_center: "請輸入Config Center",
|
||||
get_provider_success: "獲取成功",
|
||||
input_registry_center: "請輸入Registry Center",
|
||||
input_consumer_service: "請輸入Consumer & Service",
|
||||
get_provider_success: "獲取成功",
|
||||
check_registry_center: "獲取失敗,請檢查Registry Center",
|
||||
form_description: "如果當前配置項無值,則取場景配置項的值",
|
||||
},
|
||||
sql: {
|
||||
dataSource: "數據源名稱",
|
||||
sql_script: "SQL腳本",
|
||||
timeout: "超時時間(ms)",
|
||||
database_driver: "數據庫驅動",
|
||||
database_url: "數據庫連接URL",
|
||||
username: "用戶名",
|
||||
password: "密碼",
|
||||
pool_max: "最大連接數",
|
||||
query_timeout: "最大等待時間(ms)",
|
||||
name_cannot_be_empty: "SQL請求名稱不能為空",
|
||||
dataSource_cannot_be_empty: "SQL請求數據源不能為空",
|
||||
}
|
||||
},
|
||||
api_import: {
|
||||
|
@ -539,10 +561,10 @@ export default {
|
|||
file_size_limit: "文件大小不超過 20 M",
|
||||
tip: "說明",
|
||||
export_tip: "導出方法",
|
||||
ms_tip: "支持 MeterSphere json 格式",
|
||||
ms_export_tip: "通過 MeterSphere Api 測試頁面或者瀏覽器插件導出 json 格式文件",
|
||||
ms_tip: "支持 Metersphere json 格式",
|
||||
ms_export_tip: "通過 Metersphere 接口測試頁面或者瀏覽器插件導出 json 格式文件",
|
||||
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
|
||||
swagger_tip: "只支持 Swagger2.x 版本的 json 文件",
|
||||
swagger_tip: "只支持 Swagger 2.x 版本的 json 文件",
|
||||
post_export_tip: "通過 Postman 導出測試集合",
|
||||
swagger_export_tip: "通過 Swagger 頁面導出",
|
||||
suffixFormatErr: "文件格式不符合要求",
|
||||
|
@ -556,11 +578,11 @@ export default {
|
|||
request_headers: "請求頭",
|
||||
request_cookie: "Cookie",
|
||||
response: "響應",
|
||||
delete_confirm: '確認删除報告:',
|
||||
delete_confirm: '確認刪除報告: ',
|
||||
delete_batch_confirm: '確認批量刪除報告',
|
||||
scenario_name: "場景名稱",
|
||||
response_time: "回應時間(ms)",
|
||||
latency: "網路延遲",
|
||||
response_time: "響應時間(ms)",
|
||||
latency: "網絡延遲",
|
||||
request_size: "請求大小",
|
||||
response_size: "響應大小",
|
||||
response_code: "狀態碼",
|
||||
|
@ -569,7 +591,7 @@ export default {
|
|||
assertions: "斷言",
|
||||
assertions_pass: "成功斷言",
|
||||
assertions_name: "斷言名稱",
|
||||
assertions_error_message: "錯誤資訊",
|
||||
assertions_error_message: "錯誤信息",
|
||||
assertions_is_success: "是否成功",
|
||||
result: "結果",
|
||||
success: "成功",
|
||||
|
@ -585,7 +607,7 @@ export default {
|
|||
not_exist: "測試報告不存在",
|
||||
},
|
||||
test_track: {
|
||||
test_track: "測試跟踪",
|
||||
test_track: "測試跟蹤",
|
||||
confirm: "確 定",
|
||||
cancel: "取 消",
|
||||
project: "項目",
|
||||
|
@ -597,17 +619,19 @@ export default {
|
|||
pass_rate: "通過率",
|
||||
execution_result: ": 請選擇執行結果",
|
||||
actual_result: ": 實際結果為空",
|
||||
|
||||
case: {
|
||||
export_all_cases: '確定要匯出全部用例嗎?',
|
||||
input_test_case: '請輸入關聯用例名稱',
|
||||
test_name: '測試名稱',
|
||||
other: '--其他--',
|
||||
other: "--其他--",
|
||||
test_case: "測試用例",
|
||||
move: "移動用例",
|
||||
case_list: "用例列表",
|
||||
create_case: "創建用例",
|
||||
edit_case: "編輯用例",
|
||||
view_case: "查看用例",
|
||||
no_project: "該工作空間下無項目,請先創建項目",
|
||||
no_project: "該工作空間下無項目,請先創建項目",
|
||||
priority: "用例等級",
|
||||
type: "類型",
|
||||
method: "測試方式",
|
||||
|
@ -630,14 +654,14 @@ export default {
|
|||
input_type: "請選擇用例類型",
|
||||
input_method: "請選擇測試方式",
|
||||
input_prerequisite: "請輸入前置條件",
|
||||
delete_confirm: "確認刪除測試用例: ",
|
||||
delete: "删除用例",
|
||||
delete_confirm: "確認刪除測試用例",
|
||||
delete: "刪除用例",
|
||||
save_create_continue: "保存並繼續創建",
|
||||
please_create_project: "暫無項目,請先創建項目",
|
||||
create_module_first: "請先新建模塊",
|
||||
relate_test: "關聯測試",
|
||||
relate_test_not_find: '關聯的測試不存在,請檢查用例',
|
||||
other_relate_test_not_find: '關聯的測試名,請前往協力廠商平臺執行',
|
||||
other_relate_test_not_find: '關聯的測試名,請前往第三方平臺執行',
|
||||
batch_handle: '批量處理 (選中{0}項)',
|
||||
batch_update: '更新{0}個用例的屬性',
|
||||
select_catalog: '請選擇用例目錄',
|
||||
|
@ -655,13 +679,19 @@ export default {
|
|||
case_import: "導入測試用例",
|
||||
download_template: "下載模版",
|
||||
click_upload: "點擊上傳",
|
||||
upload_limit: "只能上傳xls/xlsx文件,且不超過20M",
|
||||
upload_limit_count: "一次只能上傳一個文件",
|
||||
upload_limit: "只能上傳xls/xlsx文件,且不超過20M",
|
||||
upload_xmind: "支持文件類型:.xmind;壹次至多導入500 條用例",
|
||||
upload_xmind_format: "上傳文件只能是 .xmind 格式",
|
||||
upload_limit_other_size: "上傳文件大小不能超過",
|
||||
upload_limit_count: "壹次只能上傳壹個文件",
|
||||
upload_limit_format: "上傳文件只能是 xls、xlsx格式!",
|
||||
upload_limit_size: "上傳文件大小不能超過 20MB!",
|
||||
upload_limit_other_size: "上傳文件大小不能超過",
|
||||
success: "導入成功!",
|
||||
importing: "導入中...",
|
||||
excel_title: "表格文件",
|
||||
xmind_title: "思維導圖",
|
||||
import_desc: "導入說明",
|
||||
import_file: "上傳文件",
|
||||
},
|
||||
export: {
|
||||
export: "導出用例"
|
||||
|
@ -727,13 +757,13 @@ export default {
|
|||
step_result: "步驟執行結果",
|
||||
my_case: "我的用例",
|
||||
all_case: "全部用例",
|
||||
pre_case: "上一條用例",
|
||||
next_case: "下一條用例",
|
||||
pre_case: "上壹條用例",
|
||||
next_case: "下壹條用例",
|
||||
change_execution_results: "更改執行結果",
|
||||
change_executor: "更改執行人",
|
||||
select_executor: "請選擇執行人",
|
||||
select_execute_result: "選擇執行結果",
|
||||
cancel_relevance: "取消關聯",
|
||||
cancel_relevance: "取消用例關聯",
|
||||
confirm_cancel_relevance: "確認取消關聯",
|
||||
select_manipulate: "請選擇需要操作的數據",
|
||||
select_template: "選擇模版",
|
||||
|
@ -745,11 +775,11 @@ export default {
|
|||
test_result: "測試結果",
|
||||
result_distribution: "測試結果分布",
|
||||
custom_component: "自定義模塊",
|
||||
defect_list: "缺陷列表",
|
||||
create_report: "創建測試報告",
|
||||
defect_list:"缺陷清單",
|
||||
view_report: "查看測試報告",
|
||||
component_library: "組件庫",
|
||||
component_library_tip: "拖拽組件庫中組件,添加至右側,預覽報告效果,每個系統組件只能添加壹個。",
|
||||
component_library_tip: "拖拽組件庫中組件,添加至右側,預覽報告效果,每個系統組件只能添加壹個。",
|
||||
delete_component_tip: "請至少保留壹個組件",
|
||||
input_template_name: "輸入模版名稱",
|
||||
template_special_characters: '模版名稱不支持特殊字符',
|
||||
|
@ -761,17 +791,17 @@ export default {
|
|||
report_template: "測試報告模版",
|
||||
test_detail: "測試詳情",
|
||||
failure_case: "失敗用例",
|
||||
export_report: "匯出報告"
|
||||
export_report: "導出報告"
|
||||
},
|
||||
issue: {
|
||||
issue: "缺陷",
|
||||
platform_tip: "在系統設置-組織-服務集成中集成缺陷管理平台可以自動提交缺陷到指定缺陷管理平台",
|
||||
platform_tip: "在系統設置-組織-服務集成中集成缺陷管理平臺可以自動提交缺陷到指定缺陷管理平臺",
|
||||
input_title: "請輸入標題",
|
||||
id: "缺陷ID",
|
||||
title: "缺陷標題",
|
||||
description: "缺陷描述",
|
||||
status: "缺陷狀態",
|
||||
platform: "平台",
|
||||
platform: "平臺",
|
||||
operate: "操作",
|
||||
close: "關閉缺陷",
|
||||
title_description_required: "標題和描述必填",
|
||||
|
@ -800,17 +830,17 @@ export default {
|
|||
system_parameter_setting: {
|
||||
mailbox_service_settings: '郵件設置',
|
||||
ldap_setting: 'LDAP設置',
|
||||
test_connection: '測試連結',
|
||||
test_connection: '測試連接',
|
||||
SMTP_host: 'SMTP主機',
|
||||
SMTP_port: 'SMTP埠',
|
||||
SMTP_account: 'SMTP帳戶',
|
||||
SMTP_port: 'SMTP端口',
|
||||
SMTP_account: 'SMTP賬戶',
|
||||
SMTP_password: 'SMTP密碼',
|
||||
SSL: '開啟SSL(如果SMTP埠是465,通常需要啟用SSL)',
|
||||
TLS: '開啟TLS(如果SMTP埠是587,通常需要啟用TLS)',
|
||||
SSL: '開啟SSL(如果SMTP端口是465,通常需要啟用SSL)',
|
||||
TLS: '開啟TLS(如果SMTP端口是587,通常需要啟用TLS)',
|
||||
SMTP: '是否匿名 SMTP',
|
||||
host: '主機號不能為空',
|
||||
port: '埠號不能為空',
|
||||
account: '帳戶不能為空',
|
||||
port: '端口號不能為空',
|
||||
account: '賬戶不能為空',
|
||||
},
|
||||
i18n: {
|
||||
home: '首頁'
|
||||
|
@ -842,23 +872,23 @@ export default {
|
|||
dn_cannot_be_empty: 'LDAP DN不能為空',
|
||||
ou_cannot_be_empty: 'LDAP OU不能為空',
|
||||
filter_cannot_be_empty: 'LDAP 用戶過濾器不能為空',
|
||||
password_cannot_be_empty: 'LDAP 密碼不能為空',
|
||||
mapping_cannot_be_empty: 'LDAP 用戶屬性映射不能為空',
|
||||
password_cannot_be_empty: 'LDAP 密碼不能為空',
|
||||
},
|
||||
schedule: {
|
||||
input_email: "請輸入郵箱帳號",
|
||||
input_email: "請輸入郵箱賬號",
|
||||
event: "事件",
|
||||
receiving_mode: "郵箱",
|
||||
receiving_mode: "接收方式",
|
||||
receiver: "接收人",
|
||||
operation: "操作",
|
||||
task_notification: "任務通知",
|
||||
not_set: "未設置",
|
||||
next_execution_time: "下次執行時間",
|
||||
edit_timer_task: "編輯定時任務",
|
||||
test_name: '測試名稱',
|
||||
running_rule: '運行規則',
|
||||
job_status: '任務狀態',
|
||||
running_task: '運行中的任務',
|
||||
next_execution_time: "下次執行時間",
|
||||
edit_timer_task: "編輯定時任務",
|
||||
please_input_cron_expression: "請輸入 Cron 表達式",
|
||||
generate_expression: "生成表達式",
|
||||
cron_expression_format_error: "Cron 表達式格式錯誤",
|
||||
|
|
Loading…
Reference in New Issue