Merge remote-tracking branch 'origin/master'

This commit is contained in:
wenyann 2020-07-01 10:26:35 +08:00
commit 5c04697c86
65 changed files with 1164 additions and 275 deletions

View File

@ -162,9 +162,15 @@
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
<groupId>com.fit2cloud</groupId>
<artifactId>quartz-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
<version>0.0.4</version>
</dependency>
<!-- LDAP Module -->
@ -288,5 +294,43 @@
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter-snapshots</id>
<name>jcenter</name>
<url>https://jcenter.bintray.com/</url>
</repository>
<repository>
<id>fit2cloud-enterprise-release</id>
<name>Fit2Cloud Enterprise Release</name>
<url>http://repository.fit2cloud.com/content/repositories/fit2cloud-enterprise-release/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>fit2cloud</id>
<name>fit2cloud</name>
<url>http://repository.fit2cloud.com/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@ -8,6 +8,7 @@ import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@ServletComponentScan
@ -15,6 +16,7 @@ import org.springframework.context.annotation.PropertySource;
KafkaProperties.class,
JmeterProperties.class
})
@EnableScheduling
@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true)
public class Application {
public static void main(String[] args) {

View File

@ -22,4 +22,6 @@ public class SaveAPITestRequest {
private String userId;
private Schedule schedule;
private String triggerMode;
}

View File

@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractRegex extends ExtractCommon {
private String useHeaders;
public ExtractRegex() {
setType(ExtractType.REGEX);
}

View File

@ -94,7 +94,7 @@ public class APIReportService {
apiTestReportMapper.updateByPrimaryKeySelective(report);
}
public String create(ApiTest test) {
public String create(ApiTest test, String triggerMode) {
ApiTestReport running = getRunningReport(test.getId());
if (running != null) {
return running.getId();
@ -104,6 +104,7 @@ public class APIReportService {
report.setId(UUID.randomUUID().toString());
report.setTestId(test.getId());
report.setName(test.getName());
report.setTriggerMode(triggerMode);
report.setDescription(test.getDescription());
report.setCreateTime(System.currentTimeMillis());
report.setUpdateTime(System.currentTimeMillis());

View File

@ -14,35 +14,24 @@ import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.job.QuartzManager;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.PerformanceTestJob;
import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@ -148,7 +137,7 @@ public class APITestService {
if (SessionUtils.getUser() == null) {
apiTest.setUserId(request.getUserId());
}
String reportId = apiReportService.create(apiTest);
String reportId = apiReportService.create(apiTest, request.getTriggerMode());
changeStatus(request.getId(), APITestStatus.Running);
jMeterService.run(request.getId(), is);

View File

@ -31,5 +31,7 @@ public class TestCase implements Serializable {
private String testId;
private Integer sort;
private static final long serialVersionUID = 1L;
}

View File

@ -385,142 +385,142 @@ public class TestCaseExample {
}
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;
}
public Criteria andTypeIsNull() {
addCriterion("type is null");
addCriterion("`type` is null");
return (Criteria) this;
}
public Criteria andTypeIsNotNull() {
addCriterion("type is not null");
addCriterion("`type` is not null");
return (Criteria) this;
}
public Criteria andTypeEqualTo(String value) {
addCriterion("type =", value, "type");
addCriterion("`type` =", value, "type");
return (Criteria) this;
}
public Criteria andTypeNotEqualTo(String value) {
addCriterion("type <>", value, "type");
addCriterion("`type` <>", value, "type");
return (Criteria) this;
}
public Criteria andTypeGreaterThan(String value) {
addCriterion("type >", value, "type");
addCriterion("`type` >", value, "type");
return (Criteria) this;
}
public Criteria andTypeGreaterThanOrEqualTo(String value) {
addCriterion("type >=", value, "type");
addCriterion("`type` >=", value, "type");
return (Criteria) this;
}
public Criteria andTypeLessThan(String value) {
addCriterion("type <", value, "type");
addCriterion("`type` <", value, "type");
return (Criteria) this;
}
public Criteria andTypeLessThanOrEqualTo(String value) {
addCriterion("type <=", value, "type");
addCriterion("`type` <=", value, "type");
return (Criteria) this;
}
public Criteria andTypeLike(String value) {
addCriterion("type like", value, "type");
addCriterion("`type` like", value, "type");
return (Criteria) this;
}
public Criteria andTypeNotLike(String value) {
addCriterion("type not like", value, "type");
addCriterion("`type` not like", value, "type");
return (Criteria) this;
}
public Criteria andTypeIn(List<String> values) {
addCriterion("type in", values, "type");
addCriterion("`type` in", values, "type");
return (Criteria) this;
}
public Criteria andTypeNotIn(List<String> values) {
addCriterion("type not in", values, "type");
addCriterion("`type` not in", values, "type");
return (Criteria) this;
}
public Criteria andTypeBetween(String value1, String value2) {
addCriterion("type between", value1, value2, "type");
addCriterion("`type` between", value1, value2, "type");
return (Criteria) this;
}
public Criteria andTypeNotBetween(String value1, String value2) {
addCriterion("type not between", value1, value2, "type");
addCriterion("`type` not between", value1, value2, "type");
return (Criteria) this;
}
@ -665,72 +665,72 @@ public class TestCaseExample {
}
public Criteria andMethodIsNull() {
addCriterion("method is null");
addCriterion("`method` is null");
return (Criteria) this;
}
public Criteria andMethodIsNotNull() {
addCriterion("method is not null");
addCriterion("`method` is not null");
return (Criteria) this;
}
public Criteria andMethodEqualTo(String value) {
addCriterion("method =", value, "method");
addCriterion("`method` =", value, "method");
return (Criteria) this;
}
public Criteria andMethodNotEqualTo(String value) {
addCriterion("method <>", value, "method");
addCriterion("`method` <>", value, "method");
return (Criteria) this;
}
public Criteria andMethodGreaterThan(String value) {
addCriterion("method >", value, "method");
addCriterion("`method` >", value, "method");
return (Criteria) this;
}
public Criteria andMethodGreaterThanOrEqualTo(String value) {
addCriterion("method >=", value, "method");
addCriterion("`method` >=", value, "method");
return (Criteria) this;
}
public Criteria andMethodLessThan(String value) {
addCriterion("method <", value, "method");
addCriterion("`method` <", value, "method");
return (Criteria) this;
}
public Criteria andMethodLessThanOrEqualTo(String value) {
addCriterion("method <=", value, "method");
addCriterion("`method` <=", value, "method");
return (Criteria) this;
}
public Criteria andMethodLike(String value) {
addCriterion("method like", value, "method");
addCriterion("`method` like", value, "method");
return (Criteria) this;
}
public Criteria andMethodNotLike(String value) {
addCriterion("method not like", value, "method");
addCriterion("`method` not like", value, "method");
return (Criteria) this;
}
public Criteria andMethodIn(List<String> values) {
addCriterion("method in", values, "method");
addCriterion("`method` in", values, "method");
return (Criteria) this;
}
public Criteria andMethodNotIn(List<String> values) {
addCriterion("method not in", values, "method");
addCriterion("`method` not in", values, "method");
return (Criteria) this;
}
public Criteria andMethodBetween(String value1, String value2) {
addCriterion("method between", value1, value2, "method");
addCriterion("`method` between", value1, value2, "method");
return (Criteria) this;
}
public Criteria andMethodNotBetween(String value1, String value2) {
addCriterion("method not between", value1, value2, "method");
addCriterion("`method` not between", value1, value2, "method");
return (Criteria) this;
}
@ -993,6 +993,66 @@ public class TestCaseExample {
addCriterion("test_id not between", value1, value2, "testId");
return (Criteria) this;
}
public Criteria andSortIsNull() {
addCriterion("sort is null");
return (Criteria) this;
}
public Criteria andSortIsNotNull() {
addCriterion("sort is not null");
return (Criteria) this;
}
public Criteria andSortEqualTo(Integer value) {
addCriterion("sort =", value, "sort");
return (Criteria) this;
}
public Criteria andSortNotEqualTo(Integer value) {
addCriterion("sort <>", value, "sort");
return (Criteria) this;
}
public Criteria andSortGreaterThan(Integer value) {
addCriterion("sort >", value, "sort");
return (Criteria) this;
}
public Criteria andSortGreaterThanOrEqualTo(Integer value) {
addCriterion("sort >=", value, "sort");
return (Criteria) this;
}
public Criteria andSortLessThan(Integer value) {
addCriterion("sort <", value, "sort");
return (Criteria) this;
}
public Criteria andSortLessThanOrEqualTo(Integer value) {
addCriterion("sort <=", value, "sort");
return (Criteria) this;
}
public Criteria andSortIn(List<Integer> values) {
addCriterion("sort in", values, "sort");
return (Criteria) this;
}
public Criteria andSortNotIn(List<Integer> values) {
addCriterion("sort not in", values, "sort");
return (Criteria) this;
}
public Criteria andSortBetween(Integer value1, Integer value2) {
addCriterion("sort between", value1, value2, "sort");
return (Criteria) this;
}
public Criteria andSortNotBetween(Integer value1, Integer value2) {
addCriterion("sort not between", value1, value2, "sort");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -15,6 +15,7 @@
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="test_id" jdbcType="VARCHAR" property="testId" />
<result column="sort" jdbcType="INTEGER" property="sort" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestCaseWithBLOBs">
<result column="remark" jdbcType="LONGVARCHAR" property="remark" />
@ -79,8 +80,8 @@
</where>
</sql>
<sql id="Base_Column_List">
id, node_id, node_path, project_id, name, type, maintainer, priority, method, prerequisite,
create_time, update_time, test_id
id, node_id, node_path, project_id, `name`, `type`, maintainer, priority, `method`,
prerequisite, create_time, update_time, test_id, sort
</sql>
<sql id="Blob_Column_List">
remark, steps
@ -135,17 +136,17 @@
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.TestCaseWithBLOBs">
insert into test_case (id, node_id, node_path,
project_id, name, type,
maintainer, priority, method,
project_id, `name`, `type`,
maintainer, priority, `method`,
prerequisite, create_time, update_time,
test_id, remark, steps
)
test_id, sort, remark,
steps)
values (#{id,jdbcType=VARCHAR}, #{nodeId,jdbcType=VARCHAR}, #{nodePath,jdbcType=VARCHAR},
#{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{maintainer,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR},
#{prerequisite,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{testId,jdbcType=VARCHAR}, #{remark,jdbcType=LONGVARCHAR}, #{steps,jdbcType=LONGVARCHAR}
)
#{testId,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{remark,jdbcType=LONGVARCHAR},
#{steps,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseWithBLOBs">
insert into test_case
@ -163,10 +164,10 @@
project_id,
</if>
<if test="name != null">
name,
`name`,
</if>
<if test="type != null">
type,
`type`,
</if>
<if test="maintainer != null">
maintainer,
@ -175,7 +176,7 @@
priority,
</if>
<if test="method != null">
method,
`method`,
</if>
<if test="prerequisite != null">
prerequisite,
@ -189,6 +190,9 @@
<if test="testId != null">
test_id,
</if>
<if test="sort != null">
sort,
</if>
<if test="remark != null">
remark,
</if>
@ -236,6 +240,9 @@
<if test="testId != null">
#{testId,jdbcType=VARCHAR},
</if>
<if test="sort != null">
#{sort,jdbcType=INTEGER},
</if>
<if test="remark != null">
#{remark,jdbcType=LONGVARCHAR},
</if>
@ -266,10 +273,10 @@
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.name != null">
name = #{record.name,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
</if>
<if test="record.type != null">
type = #{record.type,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
</if>
<if test="record.maintainer != null">
maintainer = #{record.maintainer,jdbcType=VARCHAR},
@ -278,7 +285,7 @@
priority = #{record.priority,jdbcType=VARCHAR},
</if>
<if test="record.method != null">
method = #{record.method,jdbcType=VARCHAR},
`method` = #{record.method,jdbcType=VARCHAR},
</if>
<if test="record.prerequisite != null">
prerequisite = #{record.prerequisite,jdbcType=VARCHAR},
@ -292,6 +299,9 @@
<if test="record.testId != null">
test_id = #{record.testId,jdbcType=VARCHAR},
</if>
<if test="record.sort != null">
sort = #{record.sort,jdbcType=INTEGER},
</if>
<if test="record.remark != null">
remark = #{record.remark,jdbcType=LONGVARCHAR},
</if>
@ -309,15 +319,16 @@
node_id = #{record.nodeId,jdbcType=VARCHAR},
node_path = #{record.nodePath,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
maintainer = #{record.maintainer,jdbcType=VARCHAR},
priority = #{record.priority,jdbcType=VARCHAR},
method = #{record.method,jdbcType=VARCHAR},
`method` = #{record.method,jdbcType=VARCHAR},
prerequisite = #{record.prerequisite,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
test_id = #{record.testId,jdbcType=VARCHAR},
sort = #{record.sort,jdbcType=INTEGER},
remark = #{record.remark,jdbcType=LONGVARCHAR},
steps = #{record.steps,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
@ -330,15 +341,16 @@
node_id = #{record.nodeId,jdbcType=VARCHAR},
node_path = #{record.nodePath,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR},
`name` = #{record.name,jdbcType=VARCHAR},
`type` = #{record.type,jdbcType=VARCHAR},
maintainer = #{record.maintainer,jdbcType=VARCHAR},
priority = #{record.priority,jdbcType=VARCHAR},
method = #{record.method,jdbcType=VARCHAR},
`method` = #{record.method,jdbcType=VARCHAR},
prerequisite = #{record.prerequisite,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
test_id = #{record.testId,jdbcType=VARCHAR}
test_id = #{record.testId,jdbcType=VARCHAR},
sort = #{record.sort,jdbcType=INTEGER}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -356,10 +368,10 @@
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
</if>
<if test="type != null">
type = #{type,jdbcType=VARCHAR},
`type` = #{type,jdbcType=VARCHAR},
</if>
<if test="maintainer != null">
maintainer = #{maintainer,jdbcType=VARCHAR},
@ -368,7 +380,7 @@
priority = #{priority,jdbcType=VARCHAR},
</if>
<if test="method != null">
method = #{method,jdbcType=VARCHAR},
`method` = #{method,jdbcType=VARCHAR},
</if>
<if test="prerequisite != null">
prerequisite = #{prerequisite,jdbcType=VARCHAR},
@ -382,6 +394,9 @@
<if test="testId != null">
test_id = #{testId,jdbcType=VARCHAR},
</if>
<if test="sort != null">
sort = #{sort,jdbcType=INTEGER},
</if>
<if test="remark != null">
remark = #{remark,jdbcType=LONGVARCHAR},
</if>
@ -396,15 +411,16 @@
set node_id = #{nodeId,jdbcType=VARCHAR},
node_path = #{nodePath,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
`type` = #{type,jdbcType=VARCHAR},
maintainer = #{maintainer,jdbcType=VARCHAR},
priority = #{priority,jdbcType=VARCHAR},
method = #{method,jdbcType=VARCHAR},
`method` = #{method,jdbcType=VARCHAR},
prerequisite = #{prerequisite,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
test_id = #{testId,jdbcType=VARCHAR},
sort = #{sort,jdbcType=INTEGER},
remark = #{remark,jdbcType=LONGVARCHAR},
steps = #{steps,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
@ -414,15 +430,16 @@
set node_id = #{nodeId,jdbcType=VARCHAR},
node_path = #{nodePath,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR},
`name` = #{name,jdbcType=VARCHAR},
`type` = #{type,jdbcType=VARCHAR},
maintainer = #{maintainer,jdbcType=VARCHAR},
priority = #{priority,jdbcType=VARCHAR},
method = #{method,jdbcType=VARCHAR},
`method` = #{method,jdbcType=VARCHAR},
prerequisite = #{prerequisite,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
test_id = #{testId,jdbcType=VARCHAR}
test_id = #{testId,jdbcType=VARCHAR},
sort = #{sort,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -11,7 +11,7 @@
<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.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status, r.trigger_mode,
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

View File

@ -17,7 +17,7 @@
<select id="getReportList" resultType="io.metersphere.dto.ReportDTO">
select ltr.id, ltr.name, ltr.test_id as testId, ltr.description, user.name as userName, project.name as
projectName,
projectName, ltr.trigger_mode,
ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, lt.name as testName
from load_test_report ltr
join load_test lt on ltr.test_id = lt.id

View File

@ -1,6 +1,6 @@
package io.metersphere.commons.constants;
public enum ReportKeys {
LoadChart, ResponseTimeChart, Errors, ErrorsTop5, RequestStatistics, Overview, TimeInfo
LoadChart, ResponseTimeChart, Errors, ErrorsTop5, RequestStatistics, Overview, TimeInfo, ResultStatus
}

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum ReportTriggerMode {
MANUAL,SCHEDULE,API
}

View File

@ -0,0 +1,93 @@
package io.metersphere.config;
import com.fit2cloud.autoconfigure.QuartzProperties;
import com.fit2cloud.quartz.QuartzInstanceIdGenerator;
import com.fit2cloud.quartz.SchedulerStarter;
import com.fit2cloud.quartz.service.QuartzManageService;
import com.fit2cloud.quartz.util.QuartzBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.Properties;
import java.util.TimeZone;
@Configuration
@EnableConfigurationProperties(QuartzProperties.class)
@ConditionalOnClass(DataSource.class)
@AutoConfigureAfter(DataSource.class)
public class QuartzAutoConfiguration {
private DataSource dataSource;
private QuartzProperties properties;
public QuartzAutoConfiguration(ObjectProvider<DataSource> dataSourceProvider, QuartzProperties properties) {
this.dataSource = dataSourceProvider.getIfAvailable();
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public SchedulerStarter schedulerStarter() {
return new SchedulerStarter();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public QuartzBeanFactory quartzBeanFactory() {
return new QuartzBeanFactory();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public QuartzManageService quartzManageService() {
return new QuartzManageService();
}
@Bean
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public TimeZone quartzTimeZone() {
return TimeZone.getTimeZone(properties.getTimeZone());
}
@Bean
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "quartz", value = "enabled", havingValue = "true")
public SchedulerFactoryBean clusterSchedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setDataSource(this.dataSource);
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setStartupDelay(60);// 60 秒之后开始执行定时任务
Properties props = new Properties();
props.put("org.quartz.scheduler.instanceName", "clusterScheduler");
props.put("org.quartz.scheduler.instanceId", "AUTO"); // 集群下的instanceId 必须唯一
props.put("org.quartz.scheduler.instanceIdGenerator.class", QuartzInstanceIdGenerator.class.getName());// instanceId 生成的方式
props.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
props.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
props.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
props.put("org.quartz.jobStore.isClustered", "true");
props.put("org.quartz.jobStore.clusterCheckinInterval", "20000");
props.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
props.put("org.quartz.threadPool.threadCount", "10");
props.put("org.quartz.threadPool.threadPriority", "5");
props.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
schedulerFactoryBean.setQuartzProperties(props);
if (!StringUtils.isEmpty(this.properties.getSchedulerName())) {
schedulerFactoryBean.setBeanName(this.properties.getSchedulerName());
}
return schedulerFactoryBean;
}
}

View File

@ -19,4 +19,5 @@ public class ReportDTO {
private String projectId;
private String projectName;
private String userName;
private String triggerMode;
}

View File

@ -10,6 +10,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -72,6 +73,8 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
return;
}
Collections.reverse(list);
List<TestCaseWithBLOBs> result = list.stream()
.map(item -> this.convert2TestCase(item))
.collect(Collectors.toList());

View File

@ -2,12 +2,13 @@ package io.metersphere.job.sechedule;
import io.metersphere.api.dto.SaveAPITestRequest;
import io.metersphere.api.service.APITestService;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.job.QuartzManager;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
public class ApiTestJob extends MsScheduleJob {
@ -18,15 +19,11 @@ public class ApiTestJob extends MsScheduleJob {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
if (StringUtils.isBlank(resourceId)) {
QuartzManager.removeJob(getJobKey(resourceId), getTriggerKey(resourceId));
}
LogUtil.info("ApiTestSchedule Running: " + resourceId);
LogUtil.info("CronExpression: " + expression);
void businessExecute(JobExecutionContext context) {
SaveAPITestRequest request = new SaveAPITestRequest();
request.setId(resourceId);
request.setUserId(userId);
request.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
apiTestService.run(request);
}

View File

@ -1,8 +1,9 @@
package io.metersphere.job.sechedule;
import org.quartz.Job;
import io.metersphere.commons.utils.LogUtil;
import org.quartz.*;
public abstract class MsScheduleJob implements Job{
public abstract class MsScheduleJob implements Job {
protected String resourceId;
@ -10,15 +11,19 @@ public abstract class MsScheduleJob implements Job{
protected String expression;
public void setResourceId(String resourceId) {
this.resourceId = resourceId;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey jobKey = context.getTrigger().getJobKey();
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
this.resourceId = jobDataMap.getString("resourceId");
this.userId = jobDataMap.getString("userId");
this.expression = jobDataMap.getString("expression");
LogUtil.info(jobKey.getGroup()+ " Running: " + resourceId);
LogUtil.info("CronExpression: " + expression);
businessExecute(context);
}
public void setExpression(String expression) {
this.expression = expression;
}
public void setUserId(String userId) {
this.userId = userId;
}
abstract void businessExecute(JobExecutionContext context);
}

View File

@ -1,14 +1,11 @@
package io.metersphere.job.sechedule;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.job.QuartzManager;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.track.request.testplan.RunTestPlanRequest;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
@ -21,17 +18,12 @@ public class PerformanceTestJob extends MsScheduleJob {
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
if (StringUtils.isBlank(resourceId)) {
QuartzManager.removeJob(getJobKey(resourceId), getTriggerKey(resourceId));
}
LogUtil.info("PerformanceTestSchedule Running: " + resourceId);
LogUtil.info("CronExpression: " + expression);
void businessExecute(JobExecutionContext context) {
RunTestPlanRequest request = new RunTestPlanRequest();
request.setId(resourceId);
request.setUserId(userId);
request.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
performanceTestService.run(request);
}
public static JobKey getJobKey(String testId) {

View File

@ -1,12 +1,16 @@
package io.metersphere.job;
package io.metersphere.job.sechedule;
import io.metersphere.commons.utils.LogUtil;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Component;
public class QuartzManager {
import javax.annotation.Resource;
public static StdSchedulerFactory sf = new StdSchedulerFactory();
@Component
public class ScheduleManager {
@Resource
private Scheduler scheduler;
/**
* 添加 simpleJob
@ -17,24 +21,28 @@ public class QuartzManager {
* @param jobDataMap
* @throws SchedulerException
*/
public static void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime,
JobDataMap jobDataMap) throws SchedulerException {
public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime,
JobDataMap jobDataMap) throws SchedulerException {
Scheduler sched = sf.getScheduler();
JobBuilder jobBuilder = JobBuilder.newJob(cls).withIdentity(jobKey);
JobDetail jd = JobBuilder.newJob(cls).withIdentity(jobKey).setJobData(jobDataMap).build();
if (jobDataMap != null) {
jobBuilder.usingJobData(jobDataMap);
}
JobDetail jd = jobBuilder.build();
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(repeatIntervalTime).repeatForever())
.startNow().build();
sched.scheduleJob(jd, trigger);
scheduler.scheduleJob(jd, trigger);
try {
if (!sched.isShutdown()) {
sched.start();
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (SchedulerException e) {
@ -44,8 +52,8 @@ public class QuartzManager {
}
}
public static void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime) throws SchedulerException {
addSimpleJob(jobKey, triggerKey, cls, repeatIntervalTime);
public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime) throws SchedulerException {
addSimpleJob(jobKey, triggerKey, cls, repeatIntervalTime);
}
/**
@ -56,11 +64,14 @@ public class QuartzManager {
* @param cron
* @param jobDataMap
*/
public static void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) {
public void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) {
try {
LogUtil.info("addCronJob: " + triggerKey.getName() + "," + triggerKey.getGroup());
JobBuilder jobBuilder = JobBuilder.newJob(jobClass).withIdentity(jobKey);
if (jobDataMap != null) {
jobBuilder.setJobData(jobDataMap);
jobBuilder.usingJobData(jobDataMap);
}
JobDetail jobDetail = jobBuilder.build();
@ -74,12 +85,10 @@ public class QuartzManager {
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
Scheduler sched = sf.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
sched.start();
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
@ -87,7 +96,7 @@ public class QuartzManager {
}
}
public static void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron) {
public void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron) {
addCronJob(jobKey, triggerKey, jobClass, cron, null);
}
@ -97,14 +106,12 @@ public class QuartzManager {
* @param cron
* @throws SchedulerException
*/
public static void modifyCronJobTime(TriggerKey triggerKey, String cron) throws SchedulerException {
Scheduler sched = sf.getScheduler();
public void modifyCronJobTime(TriggerKey triggerKey, String cron) throws SchedulerException {
LogUtil.info("modifyCronJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup());
try {
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
@ -125,7 +132,7 @@ public class QuartzManager {
trigger = (CronTrigger) triggerBuilder.build();// 创建Trigger对象
sched.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
scheduler.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
/** 方式一 :调用 rescheduleJob 结束 */
/** 方式二先删除然后在创建一个新的Job */
@ -146,15 +153,13 @@ public class QuartzManager {
* @param repeatIntervalTime
* @throws SchedulerException
*/
public static void modifySimpleJobTime(TriggerKey triggerKey, int repeatIntervalTime) throws SchedulerException {
Scheduler sched = sf.getScheduler();
public void modifySimpleJobTime(TriggerKey triggerKey, int repeatIntervalTime) throws SchedulerException {
try {
LogUtil.info("modifySimpleJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup());
SimpleTrigger trigger = (SimpleTrigger) sched.getTrigger(triggerKey);
SimpleTrigger trigger = (SimpleTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
@ -175,7 +180,7 @@ public class QuartzManager {
trigger = (SimpleTrigger) triggerBuilder.build();// 创建Trigger对象
sched.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
scheduler.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
/** 方式一 :调用 rescheduleJob 结束 */
@ -201,19 +206,17 @@ public class QuartzManager {
* @param jobKey
* @param triggerKey
*/
public static void removeJob(JobKey jobKey, TriggerKey triggerKey) {
public void removeJob(JobKey jobKey, TriggerKey triggerKey) {
try {
LogUtil.info("RemoveJob: " + jobKey.getName() + "," + jobKey.getGroup());
Scheduler sched = sf.getScheduler();
scheduler.pauseTrigger(triggerKey);
sched.pauseTrigger(triggerKey);
scheduler.unscheduleJob(triggerKey);
sched.unscheduleJob(triggerKey);
sched.deleteJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
@ -232,7 +235,7 @@ public class QuartzManager {
}
public static void shutdownJobs(Scheduler sched) {
public void shutdownJobs(Scheduler sched) {
try {
if (!sched.isShutdown()) {
sched.shutdown();
@ -252,12 +255,10 @@ public class QuartzManager {
* @param jobDataMap
* @throws SchedulerException
*/
public static void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
int intervalTime, JobDataMap jobDataMap) throws SchedulerException {
public void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
int intervalTime, JobDataMap jobDataMap) throws SchedulerException {
Scheduler sched = sf.getScheduler();
if (sched.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
modifySimpleJobTime(triggerKey, intervalTime);
} else {
addSimpleJob(jobKey, triggerKey, clz, intervalTime, jobDataMap);
@ -265,7 +266,7 @@ public class QuartzManager {
}
public static void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz, int intervalTime) throws SchedulerException {
public void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz, int intervalTime) throws SchedulerException {
addOrUpdateSimpleJob(jobKey, triggerKey, clz, intervalTime, null);
}
@ -279,23 +280,22 @@ public class QuartzManager {
* @param jobDataMap
* @throws SchedulerException
*/
public static void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) throws SchedulerException {
Scheduler sched = sf.getScheduler();
public void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) throws SchedulerException {
LogUtil.info("AddOrUpdateCronJob: " + jobKey.getName() + "," + triggerKey.getGroup());
if (sched.checkExists(triggerKey)) {
if (scheduler.checkExists(triggerKey)) {
modifyCronJobTime(triggerKey, cron);
} else {
addCronJob(jobKey, triggerKey, jobClass, cron, jobDataMap);
}
}
public static void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron) throws SchedulerException {
public void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron) throws SchedulerException {
addOrUpdateCronJob(jobKey, triggerKey, jobClass, cron, null);
}
public static JobDataMap getDefaultJobDataMap(String resourceId, String expression, String userId) {
public JobDataMap getDefaultJobDataMap(String resourceId, String expression, String userId) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("resourceId", resourceId);
jobDataMap.put("expression", expression);

View File

@ -20,9 +20,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping(value = "performance/report")
@ -63,7 +62,7 @@ public class PerformanceReportController {
@GetMapping("/content/{reportId}")
public List<Statistics> getReportContent(@PathVariable String reportId) {
return reportService.getReport(reportId);
return reportService.getReportStatistics(reportId);
}
@GetMapping("/content/errors/{reportId}")

View File

@ -21,6 +21,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@ -101,6 +102,11 @@ public class PerformanceTestController {
return performanceTestService.run(request);
}
@GetMapping("stop/{reportId}")
public void stopTest(@PathVariable String reportId) {
performanceTestService.stopTest(reportId);
}
@GetMapping("/file/metadata/{testId}")
public List<FileMetadata> getFileMetadata(@PathVariable String testId) {
return fileService.getFileMetadataByTestId(testId);

View File

@ -5,10 +5,7 @@ import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils;
@ -74,6 +71,8 @@ public class PerformanceTestService {
private KafkaProperties kafkaProperties;
@Resource
private ScheduleService scheduleService;
@Resource
private TestCaseMapper testCaseMapper;
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -82,6 +81,20 @@ public class PerformanceTestService {
public void delete(DeleteTestPlanRequest request) {
String testId = request.getId();
// 是否关联测试用例
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andTestIdEqualTo(testId);
List<TestCase> testCases = testCaseMapper.selectByExample(testCaseExample);
if (testCases.size() > 0) {
String caseName = "";
for (int i = 0; i < testCases.size(); i++) {
caseName = caseName + testCases.get(i).getName() + ",";
}
caseName = caseName.substring(0, caseName.length() - 1);
MSException.throwException(Translator.get("related_case_del_fail_prefix") + caseName + Translator.get("related_case_del_fail_suffix"));
}
LoadTestReportExample loadTestReportExample = new LoadTestReportExample();
loadTestReportExample.createCriteria().andTestIdEqualTo(testId);
List<LoadTestReport> loadTestReports = loadTestReportMapper.selectByExample(loadTestReportExample);
@ -222,7 +235,7 @@ public class PerformanceTestService {
MSException.throwException(String.format("Test cannot be runtest ID%s", request.getId()));
}
startEngine(loadTest, engine);
startEngine(loadTest, engine, request.getTriggerMode());
// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
@ -254,14 +267,19 @@ public class PerformanceTestService {
}
}
private void startEngine(LoadTestWithBLOBs loadTest, Engine engine) {
private void startEngine(LoadTestWithBLOBs loadTest, Engine engine, String triggerMode) {
LoadTestReport testReport = new LoadTestReport();
testReport.setId(engine.getReportId());
testReport.setCreateTime(engine.getStartTime());
testReport.setUpdateTime(engine.getStartTime());
testReport.setTestId(loadTest.getId());
testReport.setName(loadTest.getName());
testReport.setUserId(Optional.ofNullable(SessionUtils.getUser().getId()).orElse(loadTest.getUserId()));
testReport.setTriggerMode(triggerMode);
if (SessionUtils.getUser() == null) {
testReport.setUserId(loadTest.getUserId());
} else {
testReport.setUserId(SessionUtils.getUser().getId());
}
// 启动测试
try {
@ -280,6 +298,13 @@ public class PerformanceTestService {
loadTestReportDetailMapper.insertSelective(reportDetail);
// append \n
extLoadTestReportDetailMapper.appendLine(testReport.getId(), "\n");
// 保存一个 reportStatus
LoadTestReportResult reportResult = new LoadTestReportResult();
reportResult.setId(UUID.randomUUID().toString());
reportResult.setReportId(testReport.getId());
reportResult.setReportKey(ReportKeys.ResultStatus.name());
reportResult.setReportValue("Ready"); // 初始化一个 result_status, 这个值用在data-streaming中
loadTestReportResultMapper.insertSelective(reportResult);
} catch (MSException e) {
LogUtil.error(e);
loadTest.setStatus(PerformanceTestStatus.Error.name());
@ -388,4 +413,8 @@ public class PerformanceTestService {
private void addOrUpdatePerformanceTestCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, PerformanceTestJob.getJobKey(request.getResourceId()), PerformanceTestJob.getTriggerKey(request.getResourceId()), PerformanceTestJob.class);
}
public void stopTest(String reportId) {
reportService.deleteReport(reportId);
}
}

View File

@ -25,11 +25,10 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
@Service
@Transactional(rollbackFor = Exception.class)
public class ReportService {
@ -109,7 +108,7 @@ public class ReportService {
return loadTestReportResults.get(0).getReportValue();
}
public List<Statistics> getReport(String id) {
public List<Statistics> getReportStatistics(String id) {
checkReportStatus(id);
String reportValue = getContent(id, ReportKeys.RequestStatistics);
return JSON.parseArray(reportValue, Statistics.class);
@ -154,11 +153,7 @@ public class ReportService {
public void checkReportStatus(String reportId) {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
String reportStatus = loadTestReport.getStatus();
if (StringUtils.equals(PerformanceTestStatus.Running.name(), reportStatus)) {
MSException.throwException("Reporting in progress...");
} else if (StringUtils.equals(PerformanceTestStatus.Reporting.name(), reportStatus)) {
MSException.throwException("Reporting in progress...");
} else if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) {
if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) {
MSException.throwException("Report generation error!");
}
}
@ -214,4 +209,8 @@ public class ReportService {
String content = loadTestReportLogs.stream().map(LoadTestReportLog::getContent).reduce("", (a, b) -> a + b);
return content.getBytes();
}
public LoadTestReport getReport(String reportId) {
return loadTestReportMapper.selectByPrimaryKey(reportId);
}
}

View File

@ -4,13 +4,10 @@ import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.Schedule;
import io.metersphere.base.domain.ScheduleExample;
import io.metersphere.base.mapper.ScheduleMapper;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.job.QuartzManager;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.ScheduleManager;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
@ -28,6 +25,8 @@ public class ScheduleService {
@Resource
private ScheduleMapper scheduleMapper;
@Resource
private ScheduleManager scheduleManager;
public void addSchedule(Schedule schedule) {
schedule.setId(UUID.randomUUID().toString());
@ -73,11 +72,10 @@ public class ScheduleService {
try {
if (schedule.getEnable()) {
LogUtil.error("初始化任务:" + JSON.toJSONString(schedule));
QuartzManager.addOrUpdateCronJob(new JobKey(schedule.getKey(), schedule.getGroup()),
scheduleManager.addOrUpdateCronJob(new JobKey(schedule.getKey(), schedule.getGroup()),
new TriggerKey(schedule.getKey(), schedule.getGroup()), Class.forName(schedule.getJob()), schedule.getValue(),
QuartzManager.getDefaultJobDataMap(schedule.getResourceId(), schedule.getValue(), schedule.getUserId()));
scheduleManager.getDefaultJobDataMap(schedule.getResourceId(), schedule.getValue(), schedule.getUserId()));
}
Thread.sleep(1*60*1000);
} catch (Exception e) {
LogUtil.error("初始化任务失败", e);
e.printStackTrace();
@ -100,14 +98,14 @@ public class ScheduleService {
String cronExpression = request.getValue();
if (enable != null && enable && StringUtils.isNotBlank(cronExpression)) {
try {
QuartzManager.addOrUpdateCronJob(jobKey, triggerKey, clazz, cronExpression, QuartzManager.getDefaultJobDataMap(request.getResourceId(), cronExpression, SessionUtils.getUser().getId()));
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, clazz, cronExpression, scheduleManager.getDefaultJobDataMap(request.getResourceId(), cronExpression, SessionUtils.getUser().getId()));
} catch (SchedulerException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException("定时任务开启异常");
}
} else {
try {
QuartzManager.removeJob(jobKey, triggerKey);
scheduleManager.removeJob(jobKey, triggerKey);
} catch (Exception e) {
MSException.throwException("定时任务关闭异常");
}

View File

@ -231,7 +231,7 @@ public class TestResourcePoolService {
List<TestResourcePoolDTO> testResourcePools = listResourcePools(request);
// 重新校验 pool
for (TestResourcePoolDTO pool : testResourcePools) {
// 手动设置成无效的, 排除
// 手动设置成无效的, 排除
if (INVALID.name().equals(pool.getStatus())) {
continue;
}

View File

@ -7,4 +7,5 @@ import lombok.Setter;
@Setter
public class RunTestPlanRequest extends TestPlanRequest {
private String userId;
private String triggerMode;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.track.request.testplan;
import io.metersphere.base.domain.Schedule;
import lombok.Getter;
import lombok.Setter;
@ -27,7 +28,7 @@ public class TestPlanRequest {
private String runtimeConfiguration;
private String schedule;
private Schedule schedule;
private String testResourcePoolId;

View File

@ -229,6 +229,9 @@ public class TestCaseNodeService {
public List<TestCaseNodeDTO> getAllNodeByPlanId(String planId) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
if (testPlan == null) {
return Collections.emptyList();
}
return getNodeTreeByProjectId(testPlan.getProjectId());
}

View File

@ -35,6 +35,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Service
@ -121,7 +122,13 @@ public class TestCaseService {
}
public List<TestCaseDTO> listTestCase(QueryTestCaseRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
// 对模板导入的测试用例排序
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
return extTestCaseMapper.list(request);
}
@ -140,7 +147,9 @@ public class TestCaseService {
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
if (StringUtils.isNotBlank(request.getPlanId())) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getPlanId());
request.setProjectId(testPlan.getProjectId());
if (testPlan != null) {
request.setProjectId(testPlan.getProjectId());
}
}
List<TestCase> testCaseNames = extTestCaseMapper.getTestCaseNames(request);
@ -182,7 +191,7 @@ public class TestCaseService {
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andProjectIdIn(projectIds);
testCaseExample.setOrderByClause("update_time desc");
testCaseExample.setOrderByClause("update_time desc, sort desc");
return testCaseMapper.selectByExample(testCaseExample);
}
@ -241,8 +250,10 @@ public class TestCaseService {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
if (!testCases.isEmpty()) {
AtomicInteger sort = new AtomicInteger();
testCases.forEach(testcase -> {
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
testcase.setSort(sort.getAndIncrement());
mapper.insert(testcase);
});
}

View File

@ -91,7 +91,7 @@ public class TestPlanService {
}
public TestPlan getTestPlan(String testPlanId) {
return testPlanMapper.selectByPrimaryKey(testPlanId);
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlan());
}
public int editTestPlan(TestPlan testPlan) {

View File

@ -0,0 +1,97 @@
package io.metersphere.websocket;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.performance.service.ReportService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/performance/report/{reportId}")
@Component
public class ReportWebSocket {
private static ReportService reportService;
@Resource
public void setReportService(ReportService reportService) {
ReportWebSocket.reportService = reportService;
}
/**
* 开启连接的操作
*/
@OnOpen
public void onOpen(@PathParam("reportId") String reportId, Session session) throws IOException {
//开启一个线程对数据库中的数据进行轮询
ReportThread reportThread = new ReportThread(session, reportId);
Thread thread = new Thread(reportThread);
thread.start();
}
/**
* 连接关闭的操作
*/
@OnClose
public void onClose(Session session) {
}
/**
* 给服务器发送消息告知数据库发生变化
*/
@OnMessage
public void onMessage(Session session, String message) {
}
/**
* 出错的操作
*/
@OnError
public void onError(Throwable error) {
System.out.println(error);
error.printStackTrace();
}
public static class ReportThread implements Runnable {
private boolean stopMe = true;
private final String reportId;
private final Session session;
private int refresh;
public ReportThread(Session session, String reportId) {
this.session = session;
this.reportId = reportId;
this.refresh = 0;
}
public void stopMe() {
stopMe = false;
}
public void run() {
while (stopMe) {
try {
LoadTestReport report = reportService.getReport(reportId);
if (StringUtils.equalsAny(report.getStatus(), PerformanceTestStatus.Completed.name(), PerformanceTestStatus.Error.name())) {
this.stopMe();
session.close();
break;
}
if (PerformanceTestStatus.Running.name().equals(report.getStatus())) {
session.getBasicRemote().sendText("refresh-" + this.refresh++);
}
Thread.sleep(20 * 1000L);
} catch (Exception e) {
LogUtil.error(e);
}
}
}
}
}

View File

@ -64,4 +64,13 @@ kafka.ssl.provider=
kafka.ssl.truststore-type=
# jmeter
jmeter.home=/opt/jmeter
jmeter.home=/opt/jmeter
# quartz
quartz.enabled=true
quartz.scheduler-name=msServerJob
# file upload
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=30MB

View File

@ -1,3 +1,155 @@
-- quartz start
CREATE TABLE `qrtz_job_details` (
`SCHED_NAME` varchar(120) NOT NULL,
`JOB_NAME` varchar(200) NOT NULL,
`JOB_GROUP` varchar(200) NOT NULL,
`DESCRIPTION` varchar(250) DEFAULT NULL,
`JOB_CLASS_NAME` varchar(250) NOT NULL,
`IS_DURABLE` varchar(1) NOT NULL,
`IS_NONCONCURRENT` varchar(1) NOT NULL,
`IS_UPDATE_DATA` varchar(1) NOT NULL,
`REQUESTS_RECOVERY` varchar(1) NOT NULL,
`JOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
KEY `IDX_QRTZ_J_REQ_RECOVERY` (`SCHED_NAME`,`REQUESTS_RECOVERY`),
KEY `IDX_QRTZ_J_GRP` (`SCHED_NAME`,`JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`JOB_NAME` varchar(200) NOT NULL,
`JOB_GROUP` varchar(200) NOT NULL,
`DESCRIPTION` varchar(250) DEFAULT NULL,
`NEXT_FIRE_TIME` bigint(13) DEFAULT NULL,
`PREV_FIRE_TIME` bigint(13) DEFAULT NULL,
`PRIORITY` int(11) DEFAULT NULL,
`TRIGGER_STATE` varchar(16) NOT NULL,
`TRIGGER_TYPE` varchar(8) NOT NULL,
`START_TIME` bigint(13) NOT NULL,
`END_TIME` bigint(13) DEFAULT NULL,
`CALENDAR_NAME` varchar(200) DEFAULT NULL,
`MISFIRE_INSTR` smallint(2) DEFAULT NULL,
`JOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
KEY `IDX_QRTZ_T_J` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
KEY `IDX_QRTZ_T_JG` (`SCHED_NAME`,`JOB_GROUP`),
KEY `IDX_QRTZ_T_C` (`SCHED_NAME`,`CALENDAR_NAME`),
KEY `IDX_QRTZ_T_G` (`SCHED_NAME`,`TRIGGER_GROUP`),
KEY `IDX_QRTZ_T_STATE` (`SCHED_NAME`,`TRIGGER_STATE`),
KEY `IDX_QRTZ_T_N_STATE` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
KEY `IDX_QRTZ_T_N_G_STATE` (`SCHED_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
KEY `IDX_QRTZ_T_NEXT_FIRE_TIME` (`SCHED_NAME`,`NEXT_FIRE_TIME`),
KEY `IDX_QRTZ_T_NFT_ST` (`SCHED_NAME`,`TRIGGER_STATE`,`NEXT_FIRE_TIME`),
KEY `IDX_QRTZ_T_NFT_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`),
KEY `IDX_QRTZ_T_NFT_ST_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_STATE`),
KEY `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `qrtz_job_details` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_blob_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`BLOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
KEY `SCHED_NAME` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_calendars` (
`SCHED_NAME` varchar(120) NOT NULL,
`CALENDAR_NAME` varchar(200) NOT NULL,
`CALENDAR` blob NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_cron_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`CRON_EXPRESSION` varchar(120) NOT NULL,
`TIME_ZONE_ID` varchar(80) DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_fired_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`ENTRY_ID` varchar(95) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`INSTANCE_NAME` varchar(200) NOT NULL,
`FIRED_TIME` bigint(13) NOT NULL,
`SCHED_TIME` bigint(13) NOT NULL,
`PRIORITY` int(11) NOT NULL,
`STATE` varchar(16) NOT NULL,
`JOB_NAME` varchar(200) DEFAULT NULL,
`JOB_GROUP` varchar(200) DEFAULT NULL,
`IS_NONCONCURRENT` varchar(1) DEFAULT NULL,
`REQUESTS_RECOVERY` varchar(1) DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`),
KEY `IDX_QRTZ_FT_TRIG_INST_NAME` (`SCHED_NAME`,`INSTANCE_NAME`),
KEY `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY` (`SCHED_NAME`,`INSTANCE_NAME`,`REQUESTS_RECOVERY`),
KEY `IDX_QRTZ_FT_J_G` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
KEY `IDX_QRTZ_FT_JG` (`SCHED_NAME`,`JOB_GROUP`),
KEY `IDX_QRTZ_FT_T_G` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
KEY `IDX_QRTZ_FT_TG` (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_locks` (
`SCHED_NAME` varchar(120) NOT NULL,
`LOCK_NAME` varchar(40) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_paused_trigger_grps` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_scheduler_state` (
`SCHED_NAME` varchar(120) NOT NULL,
`INSTANCE_NAME` varchar(200) NOT NULL,
`LAST_CHECKIN_TIME` bigint(13) NOT NULL,
`CHECKIN_INTERVAL` bigint(13) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_simple_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`REPEAT_COUNT` bigint(7) NOT NULL,
`REPEAT_INTERVAL` bigint(12) NOT NULL,
`TIMES_TRIGGERED` bigint(10) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `qrtz_simprop_triggers` (
`SCHED_NAME` varchar(120) NOT NULL,
`TRIGGER_NAME` varchar(200) NOT NULL,
`TRIGGER_GROUP` varchar(200) NOT NULL,
`STR_PROP_1` varchar(512) DEFAULT NULL,
`STR_PROP_2` varchar(512) DEFAULT NULL,
`STR_PROP_3` varchar(512) DEFAULT NULL,
`INT_PROP_1` int(11) DEFAULT NULL,
`INT_PROP_2` int(11) DEFAULT NULL,
`LONG_PROP_1` bigint(20) DEFAULT NULL,
`LONG_PROP_2` bigint(20) DEFAULT NULL,
`DEC_PROP_1` decimal(13,4) DEFAULT NULL,
`DEC_PROP_2` decimal(13,4) DEFAULT NULL,
`BOOL_PROP_1` varchar(1) DEFAULT NULL,
`BOOL_PROP_2` varchar(1) DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `qrtz_triggers` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- quartz end
CREATE TABLE IF NOT EXISTS `schedule` (
`id` varchar(50) NOT NULL COMMENT 'Schedule ID',
`key` varchar(50) NOT NULL COMMENT 'Schedule Key',
@ -16,4 +168,8 @@ CREATE TABLE IF NOT EXISTS `schedule` (
ALTER TABLE `api_test` DROP COLUMN `schedule`;
ALTER TABLE `load_test` DROP COLUMN `schedule`;
ALTER TABLE `api_test_report` ADD `trigger_mode` varchar(64) NULL;
ALTER TABLE `load_test_report` ADD `trigger_mode` varchar(64) NULL;
ALTER TABLE `load_test_report` ADD `trigger_mode` varchar(64) NULL;
UPDATE `api_test_report` SET `trigger_mode` = 'MANUAL' WHERE 1;
UPDATE `load_test_report` SET `trigger_mode` = 'MANUAL' WHERE 1;
alter table test_case add sort int null comment 'Import test case sort';

View File

@ -43,6 +43,8 @@ load_test_already_exists=Duplicate load test name
no_nodes_message=No node message
duplicate_node_ip=Duplicate IPs
max_thread_insufficient=The number of concurrent users exceeds
related_case_del_fail_prefix=Connected to
related_case_del_fail_suffix=TestCase, please disassociate first
#workspace
workspace_name_is_null=Workspace name cannot be null
workspace_name_already_exists=The workspace name already exists

View File

@ -43,6 +43,8 @@ load_test_already_exists=测试名称不能重复
no_nodes_message=没有节点信息
duplicate_node_ip=节点 IP 重复
max_thread_insufficient=并发用户数超额
related_case_del_fail_prefix=已关联到
related_case_del_fail_suffix=测试用例,请先解除关联
#workspace
workspace_name_is_null=工作空间名不能为空
workspace_name_already_exists=工作空间名已存在

View File

@ -43,6 +43,8 @@ load_test_already_exists=測試名稱不能重復
no_nodes_message=沒有節點信息
duplicate_node_ip=節點 IP 重復
max_thread_insufficient=並發用戶數超額
related_case_del_fail_prefix=已關聯到
related_case_del_fail_suffix=測試用例,請先解除關聯
#workspace
workspace_name_is_null=工作空間名不能為空
workspace_name_already_exists=工作空間名已存在

View File

@ -15,6 +15,11 @@
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="triggerMode" width="150" :label="'触发方式'">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-report-status :row="row"/>
@ -26,11 +31,12 @@
<script>
import MsApiReportStatus from "../report/ApiReportStatus";
import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem";
export default {
name: "MsApiReportRecentList",
components: {MsApiReportStatus},
components: {ReportTriggerModeItem, MsApiReportStatus},
data() {
return {

View File

@ -20,6 +20,11 @@
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="triggerMode" width="150" :label="'触发方式'" column-key="triggerMode" :filters="triggerFilters">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')"
column-key="status"
:filters="statusFilters">
@ -49,9 +54,11 @@
import MsApiReportStatus from "./ApiReportStatus";
import {_filter, _sort} from "../../../../common/js/utils";
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem";
export default {
components: {
ReportTriggerModeItem,
MsTableOperatorButton,
MsApiReportStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination},
data() {
@ -71,7 +78,12 @@
{text: 'Reporting', value: 'Reporting'},
{text: 'Completed', value: 'Completed'},
{text: 'Error', value: 'Error'}
]
],
triggerFilters: [
{text: '手动', value: 'MANUAL'},
{text: '定时任务', value: 'SCHEDULE'},
{text: 'API', value: 'API'}
],
}
},
@ -85,7 +97,7 @@
this.condition.testId = this.testId;
}
let url = "/api/report/list/" + this.currentPage + "/" + this.pageSize
let url = "/api/report/list/" + this.currentPage + "/" + this.pageSize;
this.result = this.$post(url, this.condition, response => {
let data = response.data;
this.total = data.itemCount;

View File

@ -154,7 +154,7 @@
})
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id}, (response) => {
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('api_test.running'));
this.$router.push({
path: '/api/report/view/' + response.data

View File

@ -0,0 +1,67 @@
<template>
<el-dialog :title="'环境配置'" :visible.sync="visible" class="environment-dialog">
<el-container >
<ms-aside-item :title="'环境列表'" :data="environments" :add-fuc="addEnvironment" :delete-fuc="deleteEnvironment" @itemSelected="environmentSelected"/>
<el-main>
</el-main>
</el-container>
</el-dialog>
</template>
<script>
import MsApiCollapse from "./collapse/ApiCollapse";
import MsApiCollapseItem from "./collapse/ApiCollapseItem";
import draggable from 'vuedraggable';
import MsContainer from "../../../common/components/MsContainer";
import MsAsideContainer from "../../../common/components/MsAsideContainer";
import MsMainContainer from "../../../common/components/MsMainContainer";
import MsAsideItem from "../../../common/components/MsAsideItem";
export default {
name: "ApiEnvironmentConfig",
components: {
MsAsideItem,
MsMainContainer, MsAsideContainer, MsContainer, MsApiCollapseItem, MsApiCollapse, draggable},
data() {
return {
visible:false,
environments: [{name: 'tesddd'}]
}
},
methods: {
open(project) {
this.visible = true
},
deleteEnvironment(environment) {
console.log(environment);
for (let i = 0; i < this.environments.length; i++) {
if (this.environments[i].name === environment.name) {
this.environments.splice(i, 1);
break;
}
}
},
addEnvironment() {
this.environments.push({name: '新建'});
console.log('add');
},
environmentSelected() {
console.log('select');
}
}
}
</script>
<style scoped>
.environment-dialog >>> .el-dialog__body {
padding-top: 20px;
}
.ms-aside-container {
height: calc(100vh - 500px);
}
</style>

View File

@ -4,7 +4,7 @@
{{$t('api_test.request.extract.description')}}
</div>
<el-row :gutter="10">
<el-col :span="4">
<el-col :span="2">
<el-select :disabled="isReadOnly" class="extract-item" v-model="type" :placeholder="$t('api_test.request.extract.select_type')"
size="small">
<el-option :label="$t('api_test.request.extract.regex')" :value="options.REGEX"/>
@ -12,7 +12,7 @@
<el-option label="XPath" :value="options.XPATH"/>
</el-select>
</el-col>
<el-col :span="20">
<el-col :span="22">
<ms-api-extract-common :is-read-only="isReadOnly" :extract-type="type" :list="list" v-if="type" :callback="after"/>
</el-col>
</el-row>

View File

@ -1,7 +1,12 @@
<template>
<div>
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col :span="10">
<el-col v-if="extractType == 'Regex'" :span="5">
<el-select :disabled="isReadOnly" class="extract-item" v-model="common.useHeaders" :placeholder="$t('api_test.request.assertions.select_subject')" size="small">
<el-option v-for="item in useHeadersOption" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</el-col>
<el-col>
<ms-api-variable-input :is-read-only="isReadOnly" v-model="common.variable" size="small" maxlength="60"
@change="change" show-word-limit :placeholder="$t('api_test.variable_name')"/>
</el-col>
@ -53,7 +58,17 @@
data() {
return {
visible: false
visible: false,
useHeadersOption: [
{label: 'Body',value:'false'},
{label: 'Request Headers',value:'request_headers'},
{label: 'Body (unescaped)', value:'unescaped'},
{label: 'Body as a Document', value:'as_document'},
{label: 'Response Headers', value:'true'},
{label: 'URL', value:'URL'},
{label: 'Response Code', value:'code'},
{label: 'Response Message', value:'message'}
]
}
},

View File

@ -344,6 +344,7 @@ export class ExtractCommon extends ExtractType {
constructor(type, options) {
super(type);
this.variable = undefined;
this.useHeaders = undefined;
this.value = ""; // ${variable}
this.expression = undefined;
this.description = undefined;
@ -562,7 +563,7 @@ class JMXGenerator {
switch (extractCommon.type) {
case EXTRACT_TYPE.REGEX:
testName += " RegexExtractor";
props.headers = "false"; // 对应jMeter body
props.headers = extractCommon.useHeaders; // 对应jMeter body
props.template = "$1$";
return new RegexExtractor(testName, props);
case EXTRACT_TYPE.JSON_PATH:

View File

@ -1,6 +1,6 @@
<template>
<el-aside class="ms-aside-container">
<el-aside :width="width" class="ms-aside-container">
<slot></slot>
</el-aside>
@ -8,7 +8,13 @@
<script>
export default {
name: "MsAsideContainer"
name: "MsAsideContainer",
props: {
width: {
type: String,
default: '300px'
}
}
}
</script>

View File

@ -0,0 +1,104 @@
<template>
<ms-aside-container :width="width">
<div class="title-bar">
<span class="title-left">{{title}}</span>
<span class="title-right">
<i class="el-icon-plus" @click="addFuc"/>
</span>
</div>
<div v-for="(item, index) in data" :key="index" class="item-bar" @click="itemSelected(index, item)" :class="{'item-selected' : index == selectIndex}">
<span class="item-left">{{item.name}}</span>
<span class="item-right">
<i class="el-icon-delete" @click="deleteFuc(item)"/>
</span>
</div>
</ms-aside-container>
</template>
<script>
import MsAsideContainer from "./MsAsideContainer";
export default {
name: "MsAsideItem",
components: {MsAsideContainer},
data() {
return {
selectIndex: -1
}
},
props: {
width: {
type: String,
default: '200px'
},
title: String,
data: Array,
deleteFuc: Function,
addFuc: Function,
},
methods: {
itemSelected(index, item) {
this.selectIndex = index;
this.$emit('itemSelected', item);
}
}
}
</script>
<style scoped>
.title-bar {
width: 100%;
background: #e9ebef;
height: 40px;
padding: 5px 10px;
box-sizing: border-box;
}
.title-bar span {
line-height: 30px;
}
.item-bar {
width: 100%;
background: #F9F9F9;
height: 35px;
padding: 5px 10px;
box-sizing: border-box;
}
.item-bar span {
line-height: 25px;
}
.title-right,.item-right {
float: right;
}
.item-right {
visibility: hidden;
}
.ms-aside-container {
padding: 0;
}
i:hover {
color: #409EFF;
font-size: large;
}
.item-bar:hover .item-right {
visibility: visible;
}
.item-selected {
background: #edf6fd;
}
.item-selected .item-right {
visibility: visible;
}
</style>

View File

@ -6,8 +6,8 @@
<span class="character" @click="scheduleEdit">SCHEDULER</span>
</span>
<el-switch :disabled="!schedule.value" v-model="schedule.enable" @change="scheduleChange"/>
<ms-schedule-edit :schedule="schedule" :save="save" ref="scheduleEdit"/>
<crontab-result v-show="false" :ex="schedule.value" ref="crontabResult" @resultListChange="recentListChange"/>
<ms-schedule-edit :schedule="schedule" :save="save" :custom-validate="customValidate" ref="scheduleEdit"/>
<crontab-result v-show="false" :ex="schedule.value" ref="crontabResult" @resultListChange="resultListChange"/>
</div>
<div>
<span :class="{'disable-character': !schedule.enable}"> 下次执行时间{{this.recentList.length > 0 ? this.recentList[0] : '未设置'}} </span>
@ -18,6 +18,9 @@
<script>
import MsScheduleEdit from "./MsScheduleEdit";
import CrontabResult from "../cron/CrontabResult";
function defaultCustomValidate() {return {pass: true};}
export default {
name: "MsScheduleConfig",
components: {CrontabResult, MsScheduleEdit},
@ -37,6 +40,10 @@
}
}
},
customValidate: {
type: Function,
default: defaultCustomValidate
},
},
methods: {
scheduleEdit() {
@ -48,8 +55,11 @@
scheduleChange() {
this.$emit('scheduleChange');
},
recentListChange(resultList) {
resultListChange(resultList) {
this.recentList = resultList;
},
flashResultList() {
this.$refs.crontabResult.expressionChange();
}
}
}

View File

@ -9,10 +9,10 @@
<el-button type="primary" @click="showCronDialog">生成 Cron</el-button>
<el-button type="primary" @click="saveCron">保存</el-button>
</el-form-item>
<crontab-result :ex="form.cronValue" ref="crontabResult"/>
<crontab-result :ex="form.cronValue" ref="crontabResult" />
</el-form>
<el-dialog title="生成 cron" :visible.sync="showCron" :modal="false">
<crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.value"/>
<crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.value" ref="crontab"/>
</el-dialog>
</div>
</el-dialog>
@ -24,12 +24,18 @@
import CrontabResult from "../cron/CrontabResult";
import {cronValidate} from "../../../../common/js/cron";
function defaultCustomValidate() {return {pass: true};}
export default {
name: "MsScheduleEdit",
components: {CrontabResult, Crontab},
props: {
save: Function,
schedule: {},
customValidate: {
type: Function,
default: defaultCustomValidate
},
},
watch: {
'schedule.value'() {
@ -38,8 +44,13 @@
},
data() {
const validateCron = (rule, cronValue, callback) => {
let customValidate = this.customValidate(this.getIntervalTime());
if (!cronValidate(cronValue)) {
callback(new Error('Cron 表达式格式错误'));
} else if(!this.intervalShortValidate()) {
callback(new Error('间隔时间请大于 5 分钟'));
} else if (!customValidate.pass){
callback(new Error(customValidate.info));
} else {
callback();
}
@ -58,10 +69,12 @@
methods: {
open() {
this.dialogVisible = true;
this.form.cronValue = this.schedule.value;
},
crontabFill(value) {
crontabFill(value, resultList) {
//
this.form.cronValue = value;
this.$refs.crontabResult.resultList = resultList;
this.$refs['from'].validate();
},
showCronDialog() {
@ -78,11 +91,27 @@
});
},
close() {
this.dialogVisible = false;
this.form.cronValue = '';
this.$refs['from'].resetFields();
if (!this.schedule.value) {
this.$refs.crontabResult.resultList = [];
}
},
intervalShortValidate() {
if (this.getIntervalTime() < 5*60*1000) {
return false;
}
return true;
},
resultListChange() {
this.$refs['from'].validate();
},
getIntervalTime() {
let resultList = this.$refs.crontabResult.resultList;
let time1 = new Date(resultList[0]);
let time2 = new Date(resultList[1]);
return time2 - time1;
}
}
}

View File

@ -98,7 +98,7 @@
</tbody>
</table>
</div>
<crontab-result :ex="contabValueString"/>
<crontab-result :ex="contabValueString" ref="crontabResult"/>
<div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill">确定</el-button>
@ -305,7 +305,7 @@
},
//
submitFill() {
this.$emit("fill", this.contabValueString);
this.$emit("fill", this.contabValueString, this.$refs.crontabResult.resultList);
this.hidePopup();
},
clearCron() {

View File

@ -10,6 +10,8 @@
</template>
<script>
import {cronValidate} from "../../../../common/js/cron";
export default {
name: 'CrontabResult',
data() {
@ -30,11 +32,11 @@ export default {
this.expressionChange();
},
methods: {
//
//
expressionChange() {
// -
this.isShow = false;
if (!this.ex) {
if (!cronValidate(this.ex)) {
this.resultList = [];
this.$emit("resultListChange", this.resultList);
return;

View File

@ -0,0 +1,18 @@
<template>
<span>
<span v-if="triggerMode == 'MANUAL'">手动</span>
<span v-if="triggerMode == 'SCHEDULE'">定时任务</span>
<span v-if="triggerMode == 'API'">API</span>
</span>
</template>
<script>
export default {
name: "ReportTriggerModeItem",
props: ['triggerMode']
}
</script>
<style scoped>
</style>

View File

@ -15,6 +15,11 @@
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="triggerMode" width="150" :label="'触发方式'">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-performance-report-status :row="row"/>
@ -27,10 +32,11 @@
<script>
import MsPerformanceReportStatus from "../report/PerformanceReportStatus";
import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem";
export default {
name: "MsPerformanceReportRecentList",
components: {MsPerformanceReportStatus},
components: {ReportTriggerModeItem, MsPerformanceReportStatus},
data() {
return {
result: {},

View File

@ -13,12 +13,21 @@
<el-breadcrumb-item>{{reportName}}</el-breadcrumb-item>
</el-breadcrumb>
</el-row>
<!-- <el-row class="ms-report-view-btns">-->
<!-- <el-button :disabled="isReadOnly" type="primary" plain size="mini">{{$t('report.test_stop_now')}}</el-button>-->
<!-- <el-button :disabled="isReadOnly" type="success" plain size="mini">{{$t('report.test_execute_again')}}</el-button>-->
<!-- <el-button :disabled="isReadOnly" type="info" plain size="mini">{{$t('report.export')}}</el-button>-->
<!-- <el-button :disabled="isReadOnly" type="warning" plain size="mini">{{$t('report.compare')}}</el-button>-->
<!-- </el-row>-->
<el-row class="ms-report-view-btns">
<el-button :disabled="isReadOnly || status !== 'Running'" type="primary" plain size="mini"
@click="stopTest(reportId)">
{{$t('report.test_stop_now')}}
</el-button>
<!--<el-button :disabled="isReadOnly || status !== 'Completed'" type="success" plain size="mini">
{{$t('report.test_execute_again')}}
</el-button>
<el-button :disabled="isReadOnly" type="info" plain size="mini">
{{$t('report.export')}}
</el-button>
<el-button :disabled="isReadOnly" type="warning" plain size="mini">
{{$t('report.compare')}}
</el-button>-->
</el-row>
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
@ -37,7 +46,7 @@
<el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_overview')">
<!-- <ms-report-test-overview :id="reportId" :status="status"/>-->
<!-- <ms-report-test-overview :id="reportId" :status="status"/>-->
<ms-report-test-overview :report="report"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')">
@ -92,7 +101,8 @@
seconds: '0',
title: 'Logging',
report: {},
isReadOnly: false
isReadOnly: false,
websocket: null
}
},
methods: {
@ -114,19 +124,27 @@
if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => {
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
let data = res.data.data;
if (data) {
this.startTime = data.startTime;
this.endTime = data.endTime;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
}).catch(() => {
this.clearData();
})
}
},
initWebSocket() {
const uri = "ws://" + window.location.host + "/performance/report/" + this.reportId;
this.websocket = new WebSocket(uri);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
checkReportStatus(status) {
switch (status) {
case 'Error':
@ -136,11 +154,7 @@
this.$warning(this.$t('report.start_status'));
break;
case 'Reporting':
this.$info(this.$t('report.being_generated'));
break;
case 'Running':
this.$warning(this.$t('report.run_status'));
break;
case 'Completed':
default:
break;
@ -151,6 +165,33 @@
this.endTime = '0';
this.minutes = '0';
this.seconds = '0';
},
stopTest(reportId) {
this.$confirm(this.$t('report.test_stop_now_confirm'), '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$get('/performance/stop/' + reportId, () => {
this.$success(this.$t('report.test_stop_success'));
this.$router.push('/performance/report/all');
})
}).catch(() => {
});
},
onOpen() {
window.console.log("open WebSocket");
},
onError(e) {
window.console.error(e)
},
onMessage(e) {
this.$set(this.report, "refresh", e.data); //
this.initReportTimeInfo();
},
onClose(e) {
this.$set(this.report, "refresh", e.data); //
this.initReportTimeInfo();
}
},
created() {
@ -165,12 +206,15 @@
this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status);
if (this.status === "Completed") {
if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo();
}
})
this.initBreadcrumb();
this.initWebSocket();
},
beforeDestroy() {
this.websocket.close() //websocket
},
watch: {
'$route'(to) {

View File

@ -53,6 +53,11 @@
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="triggerMode" width="150" :label="'触发方式'" column-key="triggerMode" :filters="triggerFilters">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column
prop="status"
column-key="status"
@ -85,10 +90,13 @@
import MsPerformanceReportStatus from "./PerformanceReportStatus";
import {_filter, _sort} from "../../../../common/js/utils";
import MsTableOperatorButton from "../../common/components/MsTableOperatorButton";
import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem";
export default {
name: "PerformanceTestReport",
components: {MsTableOperatorButton, MsPerformanceReportStatus, MsTablePagination, MsContainer, MsMainContainer},
components: {
ReportTriggerModeItem,
MsTableOperatorButton, MsPerformanceReportStatus, MsTablePagination, MsContainer, MsMainContainer},
created: function () {
this.initTableData();
},
@ -112,7 +120,12 @@
{text: 'Reporting', value: 'Reporting'},
{text: 'Completed', value: 'Completed'},
{text: 'Error', value: 'Error'}
]
],
triggerFilters: [
{text: '手动', value: 'MANUAL'},
{text: '定时任务', value: 'SCHEDULE'},
{text: 'API', value: 'API'}
],
}
},
methods: {

View File

@ -155,7 +155,7 @@
handler(val) {
let status = val.status;
this.id = val.id;
if (status === "Completed") {
if (status === "Completed" || status === "Running") {
this.initTableData();
} else {
this.tableData = [];

View File

@ -81,7 +81,7 @@
handler(val) {
let status = val.status;
this.id = val.id;
if (status === "Completed") {
if (status === "Completed" || status === "Running") {
this.getResource();
} else {
this.resource = [];

View File

@ -161,7 +161,7 @@
handler(val){
let status = val.status;
this.id = val.id;
if (status === "Completed") {
if (status === "Completed" || status === "Running") {
this.initTableData();
} else {
this.tableData = [];

View File

@ -324,10 +324,10 @@
},
watch: {
report: {
handler(val){
handler(val) {
let status = val.status;
this.id = val.id;
if (status === "Completed") {
if (status === "Completed" || status === "Running") {
this.initTableData();
} else {
this.maxUsers = '0';
@ -340,7 +340,7 @@
this.resOption = {};
}
},
deep:true
deep: true
}
},
props: ['report']

View File

@ -24,7 +24,7 @@
<el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">{{$t('load_test.save_and_run')}}</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit"/>
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit" :custom-validate="durationValidate"/>
</el-col>
</el-row>
@ -177,7 +177,7 @@
this.result = this.$request(options, (response) => {
this.testPlan.id = response.data;
this.$success(this.$t('commons.save_success'));
this.result = this.$post(this.runPath, {id: this.testPlan.id}, () => {
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, () => {
this.$success(this.$t('load_test.is_running'))
this.$router.push({path: '/performance/report/all'})
})
@ -278,6 +278,18 @@
return false;
}
return true;
},
durationValidate(intervalTime) {
let duration = this.$refs.pressureConfig.duration * 60 * 1000;
if (intervalTime < duration) {
return {
pass: false,
info: '间隔时间不能小于压测时长'
}
}
return {
pass: true
}
}
}
}

View File

@ -29,8 +29,13 @@
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="edit(scope.row)" @deleteClick="handleDelete(scope.row)"/>
<template v-if="baseUrl == 'api'" v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="edit(scope.row)" @deleteClick="handleDelete(scope.row)">
<template v-slot:behind>
<ms-table-operator-button :is-tester-permission="true" :tip="'环境配置'" icon="el-icon-setting"
type="info" @exec="openEnvironmentConfig(scope.row)"/>
</template>
</ms-table-operator>
</template>
</el-table-column>
</el-table>
@ -59,6 +64,8 @@
<ms-delete-confirm :title="$t('project.delete')" @delete="_handleDelete" ref="deleteConfirm"/>
<api-environment-config ref="environmentConfig"/>
</ms-container>
</template>
@ -69,15 +76,18 @@
import MsTableHeader from "../common/components/MsTableHeader";
import MsTableOperator from "../common/components/MsTableOperator";
import MsDialogFooter from "../common/components/MsDialogFooter";
import {_sort, getCurrentOrganizationId, getCurrentUser, refreshSessionAndCookies} from "../../../common/js/utils";
import {_sort, getCurrentUser} from "../../../common/js/utils";
import MsContainer from "../common/components/MsContainer";
import MsMainContainer from "../common/components/MsMainContainer";
import MsDeleteConfirm from "../common/components/MsDeleteConfirm";
import {DEFAULT} from "../../../common/js/constants";
import MsTableOperatorButton from "../common/components/MsTableOperatorButton";
import ApiEnvironmentConfig from "../api/test/components/ApiEnvironmentConfig";
export default {
name: "MsProject",
components: {
ApiEnvironmentConfig,
MsTableOperatorButton,
MsDeleteConfirm,
MsMainContainer,
MsContainer, MsTableOperator, MsCreateBox, MsTablePagination, MsTableHeader, MsDialogFooter},
@ -227,6 +237,9 @@
_sort(column, this.condition);
this.list();
},
openEnvironmentConfig(project) {
this.$refs.environmentConfig.open(project);
}
}
}
</script>

View File

@ -151,11 +151,7 @@
this.$warning(this.$t('report.start_status'));
break;
case 'Reporting':
this.$info(this.$t('report.being_generated'));
break;
case 'Running':
this.$warning(this.$t('report.run_status'));
break;
case 'Completed':
default:
break;

View File

@ -128,9 +128,9 @@ export function _filter(filters, condition) {
for (let filter in filters) {
if (filters.hasOwnProperty(filter)) {
if (filters[filter] && filters[filter].length > 0) {
condition.filters[filter] = filters[filter];
condition.filters[humpToLine(filter)] = filters[filter];
} else {
condition.filters[filter] = null;
condition.filters[humpToLine(filter)] = null;
}
}
}

View File

@ -204,6 +204,8 @@ export default {
'test_start_time': 'Test Start Time',
'test_end_time': 'Test End Time',
'test_stop_now': 'Test Stop Now',
'test_stop_now_confirm': 'Are you sure you want to stop the current test immediately?',
'test_stop_success': 'Test stop successfully',
'test_execute_again': 'Test Execute Again',
'export': 'Export',
'compare': 'Compare',

View File

@ -202,6 +202,8 @@ export default {
'test_start_time': '开始时间',
'test_end_time': '结束时间',
'test_stop_now': '立即停止',
'test_stop_now_confirm': '确定要立即停止当前测试吗?',
'test_stop_success': '停止成功',
'test_execute_again': '再次执行',
'export': '导出',
'compare': '比较',

View File

@ -202,6 +202,8 @@ export default {
'test_start_time': '開始時間',
'test_end_time': '結束時間',
'test_stop_now': '立即停止',
'test_stop_now_confirm': '確定要立即停止當前測試嗎?',
'test_stop_success': '停止成功',
'test_execute_again': '再次執行',
'export': '導出',
'compare': '比較',