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>
<dependency> <dependency>
<groupId>org.quartz-scheduler</groupId> <groupId>com.fit2cloud</groupId>
<artifactId>quartz</artifactId> <artifactId>quartz-spring-boot-starter</artifactId>
<version>2.3.0</version> <exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</exclusion>
</exclusions>
<version>0.0.4</version>
</dependency> </dependency>
<!-- LDAP Module --> <!-- LDAP Module -->
@ -288,5 +294,43 @@
</plugins> </plugins>
</build> </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> </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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) @SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@ServletComponentScan @ServletComponentScan
@ -15,6 +16,7 @@ import org.springframework.context.annotation.PropertySource;
KafkaProperties.class, KafkaProperties.class,
JmeterProperties.class JmeterProperties.class
}) })
@EnableScheduling
@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true)
public class Application { public class Application {
public static void main(String[] args) { public static void main(String[] args) {

View File

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

View File

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

View File

@ -94,7 +94,7 @@ public class APIReportService {
apiTestReportMapper.updateByPrimaryKeySelective(report); apiTestReportMapper.updateByPrimaryKeySelective(report);
} }
public String create(ApiTest test) { public String create(ApiTest test, String triggerMode) {
ApiTestReport running = getRunningReport(test.getId()); ApiTestReport running = getRunningReport(test.getId());
if (running != null) { if (running != null) {
return running.getId(); return running.getId();
@ -104,6 +104,7 @@ public class APIReportService {
report.setId(UUID.randomUUID().toString()); report.setId(UUID.randomUUID().toString());
report.setTestId(test.getId()); report.setTestId(test.getId());
report.setName(test.getName()); report.setName(test.getName());
report.setTriggerMode(triggerMode);
report.setDescription(test.getDescription()); report.setDescription(test.getDescription());
report.setCreateTime(System.currentTimeMillis()); report.setCreateTime(System.currentTimeMillis());
report.setUpdateTime(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.constants.ScheduleType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.job.QuartzManager;
import io.metersphere.job.sechedule.ApiTestJob; import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.PerformanceTestJob;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService; 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -148,7 +137,7 @@ public class APITestService {
if (SessionUtils.getUser() == null) { if (SessionUtils.getUser() == null) {
apiTest.setUserId(request.getUserId()); apiTest.setUserId(request.getUserId());
} }
String reportId = apiReportService.create(apiTest); String reportId = apiReportService.create(apiTest, request.getTriggerMode());
changeStatus(request.getId(), APITestStatus.Running); changeStatus(request.getId(), APITestStatus.Running);
jMeterService.run(request.getId(), is); jMeterService.run(request.getId(), is);

View File

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

View File

@ -385,142 +385,142 @@ public class TestCaseExample {
} }
public Criteria andNameIsNull() { public Criteria andNameIsNull() {
addCriterion("name is null"); addCriterion("`name` is null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameIsNotNull() { public Criteria andNameIsNotNull() {
addCriterion("name is not null"); addCriterion("`name` is not null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameEqualTo(String value) { public Criteria andNameEqualTo(String value) {
addCriterion("name =", value, "name"); addCriterion("`name` =", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameNotEqualTo(String value) { public Criteria andNameNotEqualTo(String value) {
addCriterion("name <>", value, "name"); addCriterion("`name` <>", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameGreaterThan(String value) { public Criteria andNameGreaterThan(String value) {
addCriterion("name >", value, "name"); addCriterion("`name` >", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameGreaterThanOrEqualTo(String value) { public Criteria andNameGreaterThanOrEqualTo(String value) {
addCriterion("name >=", value, "name"); addCriterion("`name` >=", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameLessThan(String value) { public Criteria andNameLessThan(String value) {
addCriterion("name <", value, "name"); addCriterion("`name` <", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameLessThanOrEqualTo(String value) { public Criteria andNameLessThanOrEqualTo(String value) {
addCriterion("name <=", value, "name"); addCriterion("`name` <=", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameLike(String value) { public Criteria andNameLike(String value) {
addCriterion("name like", value, "name"); addCriterion("`name` like", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameNotLike(String value) { public Criteria andNameNotLike(String value) {
addCriterion("name not like", value, "name"); addCriterion("`name` not like", value, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameIn(List<String> values) { public Criteria andNameIn(List<String> values) {
addCriterion("name in", values, "name"); addCriterion("`name` in", values, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameNotIn(List<String> values) { public Criteria andNameNotIn(List<String> values) {
addCriterion("name not in", values, "name"); addCriterion("`name` not in", values, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameBetween(String value1, String value2) { public Criteria andNameBetween(String value1, String value2) {
addCriterion("name between", value1, value2, "name"); addCriterion("`name` between", value1, value2, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andNameNotBetween(String value1, String value2) { public Criteria andNameNotBetween(String value1, String value2) {
addCriterion("name not between", value1, value2, "name"); addCriterion("`name` not between", value1, value2, "name");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeIsNull() { public Criteria andTypeIsNull() {
addCriterion("type is null"); addCriterion("`type` is null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeIsNotNull() { public Criteria andTypeIsNotNull() {
addCriterion("type is not null"); addCriterion("`type` is not null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeEqualTo(String value) { public Criteria andTypeEqualTo(String value) {
addCriterion("type =", value, "type"); addCriterion("`type` =", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeNotEqualTo(String value) { public Criteria andTypeNotEqualTo(String value) {
addCriterion("type <>", value, "type"); addCriterion("`type` <>", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeGreaterThan(String value) { public Criteria andTypeGreaterThan(String value) {
addCriterion("type >", value, "type"); addCriterion("`type` >", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeGreaterThanOrEqualTo(String value) { public Criteria andTypeGreaterThanOrEqualTo(String value) {
addCriterion("type >=", value, "type"); addCriterion("`type` >=", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeLessThan(String value) { public Criteria andTypeLessThan(String value) {
addCriterion("type <", value, "type"); addCriterion("`type` <", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeLessThanOrEqualTo(String value) { public Criteria andTypeLessThanOrEqualTo(String value) {
addCriterion("type <=", value, "type"); addCriterion("`type` <=", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeLike(String value) { public Criteria andTypeLike(String value) {
addCriterion("type like", value, "type"); addCriterion("`type` like", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeNotLike(String value) { public Criteria andTypeNotLike(String value) {
addCriterion("type not like", value, "type"); addCriterion("`type` not like", value, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeIn(List<String> values) { public Criteria andTypeIn(List<String> values) {
addCriterion("type in", values, "type"); addCriterion("`type` in", values, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeNotIn(List<String> values) { public Criteria andTypeNotIn(List<String> values) {
addCriterion("type not in", values, "type"); addCriterion("`type` not in", values, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeBetween(String value1, String value2) { public Criteria andTypeBetween(String value1, String value2) {
addCriterion("type between", value1, value2, "type"); addCriterion("`type` between", value1, value2, "type");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andTypeNotBetween(String value1, String value2) { public Criteria andTypeNotBetween(String value1, String value2) {
addCriterion("type not between", value1, value2, "type"); addCriterion("`type` not between", value1, value2, "type");
return (Criteria) this; return (Criteria) this;
} }
@ -665,72 +665,72 @@ public class TestCaseExample {
} }
public Criteria andMethodIsNull() { public Criteria andMethodIsNull() {
addCriterion("method is null"); addCriterion("`method` is null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodIsNotNull() { public Criteria andMethodIsNotNull() {
addCriterion("method is not null"); addCriterion("`method` is not null");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodEqualTo(String value) { public Criteria andMethodEqualTo(String value) {
addCriterion("method =", value, "method"); addCriterion("`method` =", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodNotEqualTo(String value) { public Criteria andMethodNotEqualTo(String value) {
addCriterion("method <>", value, "method"); addCriterion("`method` <>", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodGreaterThan(String value) { public Criteria andMethodGreaterThan(String value) {
addCriterion("method >", value, "method"); addCriterion("`method` >", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodGreaterThanOrEqualTo(String value) { public Criteria andMethodGreaterThanOrEqualTo(String value) {
addCriterion("method >=", value, "method"); addCriterion("`method` >=", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodLessThan(String value) { public Criteria andMethodLessThan(String value) {
addCriterion("method <", value, "method"); addCriterion("`method` <", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodLessThanOrEqualTo(String value) { public Criteria andMethodLessThanOrEqualTo(String value) {
addCriterion("method <=", value, "method"); addCriterion("`method` <=", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodLike(String value) { public Criteria andMethodLike(String value) {
addCriterion("method like", value, "method"); addCriterion("`method` like", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodNotLike(String value) { public Criteria andMethodNotLike(String value) {
addCriterion("method not like", value, "method"); addCriterion("`method` not like", value, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodIn(List<String> values) { public Criteria andMethodIn(List<String> values) {
addCriterion("method in", values, "method"); addCriterion("`method` in", values, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodNotIn(List<String> values) { public Criteria andMethodNotIn(List<String> values) {
addCriterion("method not in", values, "method"); addCriterion("`method` not in", values, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodBetween(String value1, String value2) { public Criteria andMethodBetween(String value1, String value2) {
addCriterion("method between", value1, value2, "method"); addCriterion("`method` between", value1, value2, "method");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andMethodNotBetween(String value1, String value2) { public Criteria andMethodNotBetween(String value1, String value2) {
addCriterion("method not between", value1, value2, "method"); addCriterion("`method` not between", value1, value2, "method");
return (Criteria) this; return (Criteria) this;
} }
@ -993,6 +993,66 @@ public class TestCaseExample {
addCriterion("test_id not between", value1, value2, "testId"); addCriterion("test_id not between", value1, value2, "testId");
return (Criteria) this; 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 { public static class Criteria extends GeneratedCriteria {

View File

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

View File

@ -11,7 +11,7 @@
<select id="list" resultMap="BaseResultMap"> <select id="list" resultMap="BaseResultMap">
SELECT t.name AS test_name, 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 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 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 project ON project.id = t.project_id

View File

@ -17,7 +17,7 @@
<select id="getReportList" resultType="io.metersphere.dto.ReportDTO"> <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 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 ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, lt.name as testName
from load_test_report ltr from load_test_report ltr
join load_test lt on ltr.test_id = lt.id join load_test lt on ltr.test_id = lt.id

View File

@ -1,6 +1,6 @@
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum ReportKeys { 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 projectId;
private String projectName; private String projectName;
private String userName; private String userName;
private String triggerMode;
} }

View File

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

View File

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

View File

@ -1,8 +1,9 @@
package io.metersphere.job.sechedule; 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; protected String resourceId;
@ -10,15 +11,19 @@ public abstract class MsScheduleJob implements Job{
protected String expression; protected String expression;
public void setResourceId(String resourceId) { @Override
this.resourceId = resourceId; 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) { abstract void businessExecute(JobExecutionContext context);
this.expression = expression;
}
public void setUserId(String userId) {
this.userId = userId;
}
} }

View File

@ -1,14 +1,11 @@
package io.metersphere.job.sechedule; package io.metersphere.job.sechedule;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup; import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory; 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.performance.service.PerformanceTestService;
import io.metersphere.track.request.testplan.RunTestPlanRequest; import io.metersphere.track.request.testplan.RunTestPlanRequest;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.TriggerKey; import org.quartz.TriggerKey;
@ -21,17 +18,12 @@ public class PerformanceTestJob extends MsScheduleJob {
} }
@Override @Override
public void execute(JobExecutionContext context) throws JobExecutionException { void businessExecute(JobExecutionContext context) {
if (StringUtils.isBlank(resourceId)) {
QuartzManager.removeJob(getJobKey(resourceId), getTriggerKey(resourceId));
}
LogUtil.info("PerformanceTestSchedule Running: " + resourceId);
LogUtil.info("CronExpression: " + expression);
RunTestPlanRequest request = new RunTestPlanRequest(); RunTestPlanRequest request = new RunTestPlanRequest();
request.setId(resourceId); request.setId(resourceId);
request.setUserId(userId); request.setUserId(userId);
request.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
performanceTestService.run(request); performanceTestService.run(request);
} }
public static JobKey getJobKey(String testId) { 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 io.metersphere.commons.utils.LogUtil;
import org.quartz.*; 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 * 添加 simpleJob
@ -17,24 +21,28 @@ public class QuartzManager {
* @param jobDataMap * @param jobDataMap
* @throws SchedulerException * @throws SchedulerException
*/ */
public static void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime, public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime,
JobDataMap jobDataMap) throws SchedulerException { 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) SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
.withSchedule( .withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(repeatIntervalTime).repeatForever()) SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(repeatIntervalTime).repeatForever())
.startNow().build(); .startNow().build();
sched.scheduleJob(jd, trigger); scheduler.scheduleJob(jd, trigger);
try { try {
if (!sched.isShutdown()) { if (!scheduler.isShutdown()) {
sched.start(); scheduler.start();
} }
} catch (SchedulerException e) { } 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 { public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime) throws SchedulerException {
addSimpleJob(jobKey, triggerKey, cls, repeatIntervalTime); addSimpleJob(jobKey, triggerKey, cls, repeatIntervalTime);
} }
/** /**
@ -56,11 +64,14 @@ public class QuartzManager {
* @param cron * @param cron
* @param jobDataMap * @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 { try {
LogUtil.info("addCronJob: " + triggerKey.getName() + "," + triggerKey.getGroup());
JobBuilder jobBuilder = JobBuilder.newJob(jobClass).withIdentity(jobKey); JobBuilder jobBuilder = JobBuilder.newJob(jobClass).withIdentity(jobKey);
if (jobDataMap != null) { if (jobDataMap != null) {
jobBuilder.setJobData(jobDataMap); jobBuilder.usingJobData(jobDataMap);
} }
JobDetail jobDetail = jobBuilder.build(); JobDetail jobDetail = jobBuilder.build();
@ -74,12 +85,10 @@ public class QuartzManager {
CronTrigger trigger = (CronTrigger) triggerBuilder.build(); CronTrigger trigger = (CronTrigger) triggerBuilder.build();
Scheduler sched = sf.getScheduler(); scheduler.scheduleJob(jobDetail, trigger);
sched.scheduleJob(jobDetail, trigger); if (!scheduler.isShutdown()) {
scheduler.start();
if (!sched.isShutdown()) {
sched.start();
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), 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); addCronJob(jobKey, triggerKey, jobClass, cron, null);
} }
@ -97,14 +106,12 @@ public class QuartzManager {
* @param cron * @param cron
* @throws SchedulerException * @throws SchedulerException
*/ */
public static void modifyCronJobTime(TriggerKey triggerKey, String cron) throws SchedulerException { public void modifyCronJobTime(TriggerKey triggerKey, String cron) throws SchedulerException {
Scheduler sched = sf.getScheduler();
LogUtil.info("modifyCronJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup()); LogUtil.info("modifyCronJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup());
try { try {
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) { if (trigger == null) {
return; return;
@ -125,7 +132,7 @@ public class QuartzManager {
trigger = (CronTrigger) triggerBuilder.build();// 创建Trigger对象 trigger = (CronTrigger) triggerBuilder.build();// 创建Trigger对象
sched.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间 scheduler.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
/** 方式一 :调用 rescheduleJob 结束 */ /** 方式一 :调用 rescheduleJob 结束 */
/** 方式二先删除然后在创建一个新的Job */ /** 方式二先删除然后在创建一个新的Job */
@ -146,15 +153,13 @@ public class QuartzManager {
* @param repeatIntervalTime * @param repeatIntervalTime
* @throws SchedulerException * @throws SchedulerException
*/ */
public static void modifySimpleJobTime(TriggerKey triggerKey, int repeatIntervalTime) throws SchedulerException { public void modifySimpleJobTime(TriggerKey triggerKey, int repeatIntervalTime) throws SchedulerException {
Scheduler sched = sf.getScheduler();
try { try {
LogUtil.info("modifySimpleJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup()); LogUtil.info("modifySimpleJobTime: " + triggerKey.getName() + "," + triggerKey.getGroup());
SimpleTrigger trigger = (SimpleTrigger) sched.getTrigger(triggerKey); SimpleTrigger trigger = (SimpleTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) { if (trigger == null) {
return; return;
@ -175,7 +180,7 @@ public class QuartzManager {
trigger = (SimpleTrigger) triggerBuilder.build();// 创建Trigger对象 trigger = (SimpleTrigger) triggerBuilder.build();// 创建Trigger对象
sched.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间 scheduler.rescheduleJob(triggerKey, trigger);// 修改一个任务的触发时间
/** 方式一 :调用 rescheduleJob 结束 */ /** 方式一 :调用 rescheduleJob 结束 */
@ -201,19 +206,17 @@ public class QuartzManager {
* @param jobKey * @param jobKey
* @param triggerKey * @param triggerKey
*/ */
public static void removeJob(JobKey jobKey, TriggerKey triggerKey) { public void removeJob(JobKey jobKey, TriggerKey triggerKey) {
try { try {
LogUtil.info("RemoveJob: " + jobKey.getName() + "," + jobKey.getGroup()); LogUtil.info("RemoveJob: " + jobKey.getName() + "," + jobKey.getGroup());
Scheduler sched = sf.getScheduler(); scheduler.pauseTrigger(triggerKey);
sched.pauseTrigger(triggerKey); scheduler.unscheduleJob(triggerKey);
sched.unscheduleJob(triggerKey); scheduler.deleteJob(jobKey);
sched.deleteJob(jobKey);
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), 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 { try {
if (!sched.isShutdown()) { if (!sched.isShutdown()) {
sched.shutdown(); sched.shutdown();
@ -252,12 +255,10 @@ public class QuartzManager {
* @param jobDataMap * @param jobDataMap
* @throws SchedulerException * @throws SchedulerException
*/ */
public static void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz, public void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
int intervalTime, JobDataMap jobDataMap) throws SchedulerException { int intervalTime, JobDataMap jobDataMap) throws SchedulerException {
Scheduler sched = sf.getScheduler(); if (scheduler.checkExists(triggerKey)) {
if (sched.checkExists(triggerKey)) {
modifySimpleJobTime(triggerKey, intervalTime); modifySimpleJobTime(triggerKey, intervalTime);
} else { } else {
addSimpleJob(jobKey, triggerKey, clz, intervalTime, jobDataMap); 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); addOrUpdateSimpleJob(jobKey, triggerKey, clz, intervalTime, null);
} }
@ -279,23 +280,22 @@ public class QuartzManager {
* @param jobDataMap * @param jobDataMap
* @throws SchedulerException * @throws SchedulerException
*/ */
public static void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) throws SchedulerException { public void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, JobDataMap jobDataMap) throws SchedulerException {
Scheduler sched = sf.getScheduler();
LogUtil.info("AddOrUpdateCronJob: " + jobKey.getName() + "," + triggerKey.getGroup()); LogUtil.info("AddOrUpdateCronJob: " + jobKey.getName() + "," + triggerKey.getGroup());
if (sched.checkExists(triggerKey)) { if (scheduler.checkExists(triggerKey)) {
modifyCronJobTime(triggerKey, cron); modifyCronJobTime(triggerKey, cron);
} else { } else {
addCronJob(jobKey, triggerKey, jobClass, cron, jobDataMap); 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); 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 jobDataMap = new JobDataMap();
jobDataMap.put("resourceId", resourceId); jobDataMap.put("resourceId", resourceId);
jobDataMap.put("expression", expression); jobDataMap.put("expression", expression);

View File

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

View File

@ -21,6 +21,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -101,6 +102,11 @@ public class PerformanceTestController {
return performanceTestService.run(request); return performanceTestService.run(request);
} }
@GetMapping("stop/{reportId}")
public void stopTest(@PathVariable String reportId) {
performanceTestService.stopTest(reportId);
}
@GetMapping("/file/metadata/{testId}") @GetMapping("/file/metadata/{testId}")
public List<FileMetadata> getFileMetadata(@PathVariable String testId) { public List<FileMetadata> getFileMetadata(@PathVariable String testId) {
return fileService.getFileMetadataByTestId(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.ExtLoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper; import io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper; import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.APITestStatus; import io.metersphere.commons.constants.*;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.ServiceUtils;
@ -74,6 +71,8 @@ public class PerformanceTestService {
private KafkaProperties kafkaProperties; private KafkaProperties kafkaProperties;
@Resource @Resource
private ScheduleService scheduleService; private ScheduleService scheduleService;
@Resource
private TestCaseMapper testCaseMapper;
public List<LoadTestDTO> list(QueryTestPlanRequest request) { public List<LoadTestDTO> list(QueryTestPlanRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -82,6 +81,20 @@ public class PerformanceTestService {
public void delete(DeleteTestPlanRequest request) { public void delete(DeleteTestPlanRequest request) {
String testId = request.getId(); 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 loadTestReportExample = new LoadTestReportExample();
loadTestReportExample.createCriteria().andTestIdEqualTo(testId); loadTestReportExample.createCriteria().andTestIdEqualTo(testId);
List<LoadTestReport> loadTestReports = loadTestReportMapper.selectByExample(loadTestReportExample); 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())); 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 // 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(); LoadTestReport testReport = new LoadTestReport();
testReport.setId(engine.getReportId()); testReport.setId(engine.getReportId());
testReport.setCreateTime(engine.getStartTime()); testReport.setCreateTime(engine.getStartTime());
testReport.setUpdateTime(engine.getStartTime()); testReport.setUpdateTime(engine.getStartTime());
testReport.setTestId(loadTest.getId()); testReport.setTestId(loadTest.getId());
testReport.setName(loadTest.getName()); 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 { try {
@ -280,6 +298,13 @@ public class PerformanceTestService {
loadTestReportDetailMapper.insertSelective(reportDetail); loadTestReportDetailMapper.insertSelective(reportDetail);
// append \n // append \n
extLoadTestReportDetailMapper.appendLine(testReport.getId(), "\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) { } catch (MSException e) {
LogUtil.error(e); LogUtil.error(e);
loadTest.setStatus(PerformanceTestStatus.Error.name()); loadTest.setStatus(PerformanceTestStatus.Error.name());
@ -388,4 +413,8 @@ public class PerformanceTestService {
private void addOrUpdatePerformanceTestCronJob(Schedule request) { private void addOrUpdatePerformanceTestCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, PerformanceTestJob.getJobKey(request.getResourceId()), PerformanceTestJob.getTriggerKey(request.getResourceId()), PerformanceTestJob.class); 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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.annotation.Resource;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public class ReportService { public class ReportService {
@ -109,7 +108,7 @@ public class ReportService {
return loadTestReportResults.get(0).getReportValue(); return loadTestReportResults.get(0).getReportValue();
} }
public List<Statistics> getReport(String id) { public List<Statistics> getReportStatistics(String id) {
checkReportStatus(id); checkReportStatus(id);
String reportValue = getContent(id, ReportKeys.RequestStatistics); String reportValue = getContent(id, ReportKeys.RequestStatistics);
return JSON.parseArray(reportValue, Statistics.class); return JSON.parseArray(reportValue, Statistics.class);
@ -154,11 +153,7 @@ public class ReportService {
public void checkReportStatus(String reportId) { public void checkReportStatus(String reportId) {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId); LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
String reportStatus = loadTestReport.getStatus(); String reportStatus = loadTestReport.getStatus();
if (StringUtils.equals(PerformanceTestStatus.Running.name(), reportStatus)) { if (StringUtils.equals(PerformanceTestStatus.Error.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)) {
MSException.throwException("Report generation error!"); MSException.throwException("Report generation error!");
} }
} }
@ -214,4 +209,8 @@ public class ReportService {
String content = loadTestReportLogs.stream().map(LoadTestReportLog::getContent).reduce("", (a, b) -> a + b); String content = loadTestReportLogs.stream().map(LoadTestReportLog::getContent).reduce("", (a, b) -> a + b);
return content.getBytes(); 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.Schedule;
import io.metersphere.base.domain.ScheduleExample; import io.metersphere.base.domain.ScheduleExample;
import io.metersphere.base.mapper.ScheduleMapper; 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.exception.MSException;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.job.QuartzManager; import io.metersphere.job.sechedule.ScheduleManager;
import io.metersphere.job.sechedule.ApiTestJob;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
@ -28,6 +25,8 @@ public class ScheduleService {
@Resource @Resource
private ScheduleMapper scheduleMapper; private ScheduleMapper scheduleMapper;
@Resource
private ScheduleManager scheduleManager;
public void addSchedule(Schedule schedule) { public void addSchedule(Schedule schedule) {
schedule.setId(UUID.randomUUID().toString()); schedule.setId(UUID.randomUUID().toString());
@ -73,11 +72,10 @@ public class ScheduleService {
try { try {
if (schedule.getEnable()) { if (schedule.getEnable()) {
LogUtil.error("初始化任务:" + JSON.toJSONString(schedule)); 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(), 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) { } catch (Exception e) {
LogUtil.error("初始化任务失败", e); LogUtil.error("初始化任务失败", e);
e.printStackTrace(); e.printStackTrace();
@ -100,14 +98,14 @@ public class ScheduleService {
String cronExpression = request.getValue(); String cronExpression = request.getValue();
if (enable != null && enable && StringUtils.isNotBlank(cronExpression)) { if (enable != null && enable && StringUtils.isNotBlank(cronExpression)) {
try { 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) { } catch (SchedulerException e) {
LogUtil.error(e.getMessage(), e); LogUtil.error(e.getMessage(), e);
MSException.throwException("定时任务开启异常"); MSException.throwException("定时任务开启异常");
} }
} else { } else {
try { try {
QuartzManager.removeJob(jobKey, triggerKey); scheduleManager.removeJob(jobKey, triggerKey);
} catch (Exception e) { } catch (Exception e) {
MSException.throwException("定时任务关闭异常"); MSException.throwException("定时任务关闭异常");
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -121,7 +122,13 @@ public class TestCaseService {
} }
public List<TestCaseDTO> listTestCase(QueryTestCaseRequest request) { 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); return extTestCaseMapper.list(request);
} }
@ -140,7 +147,9 @@ public class TestCaseService {
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) { public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
if (StringUtils.isNotBlank(request.getPlanId())) { if (StringUtils.isNotBlank(request.getPlanId())) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(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); List<TestCase> testCaseNames = extTestCaseMapper.getTestCaseNames(request);
@ -182,7 +191,7 @@ public class TestCaseService {
TestCaseExample testCaseExample = new TestCaseExample(); TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andProjectIdIn(projectIds); testCaseExample.createCriteria().andProjectIdIn(projectIds);
testCaseExample.setOrderByClause("update_time desc"); testCaseExample.setOrderByClause("update_time desc, sort desc");
return testCaseMapper.selectByExample(testCaseExample); return testCaseMapper.selectByExample(testCaseExample);
} }
@ -241,8 +250,10 @@ public class TestCaseService {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class); TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
if (!testCases.isEmpty()) { if (!testCases.isEmpty()) {
AtomicInteger sort = new AtomicInteger();
testCases.forEach(testcase -> { testCases.forEach(testcase -> {
testcase.setNodeId(nodePathMap.get(testcase.getNodePath())); testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
testcase.setSort(sort.getAndIncrement());
mapper.insert(testcase); mapper.insert(testcase);
}); });
} }

View File

@ -91,7 +91,7 @@ public class TestPlanService {
} }
public TestPlan getTestPlan(String testPlanId) { public TestPlan getTestPlan(String testPlanId) {
return testPlanMapper.selectByPrimaryKey(testPlanId); return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlan());
} }
public int editTestPlan(TestPlan 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= kafka.ssl.truststore-type=
# jmeter # 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` ( CREATE TABLE IF NOT EXISTS `schedule` (
`id` varchar(50) NOT NULL COMMENT 'Schedule ID', `id` varchar(50) NOT NULL COMMENT 'Schedule ID',
`key` varchar(50) NOT NULL COMMENT 'Schedule Key', `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 `api_test` DROP COLUMN `schedule`;
ALTER TABLE `load_test` DROP COLUMN `schedule`; ALTER TABLE `load_test` DROP COLUMN `schedule`;
ALTER TABLE `api_test_report` ADD `trigger_mode` varchar(64) NULL; 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 no_nodes_message=No node message
duplicate_node_ip=Duplicate IPs duplicate_node_ip=Duplicate IPs
max_thread_insufficient=The number of concurrent users exceeds 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
workspace_name_is_null=Workspace name cannot be null workspace_name_is_null=Workspace name cannot be null
workspace_name_already_exists=The workspace name already exists workspace_name_already_exists=The workspace name already exists

View File

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

View File

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

View File

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

View File

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

View File

@ -154,7 +154,7 @@
}) })
}, },
runTest() { 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.$success(this.$t('api_test.running'));
this.$router.push({ this.$router.push({
path: '/api/report/view/' + response.data 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')}} {{$t('api_test.request.extract.description')}}
</div> </div>
<el-row :gutter="10"> <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')" <el-select :disabled="isReadOnly" class="extract-item" v-model="type" :placeholder="$t('api_test.request.extract.select_type')"
size="small"> size="small">
<el-option :label="$t('api_test.request.extract.regex')" :value="options.REGEX"/> <el-option :label="$t('api_test.request.extract.regex')" :value="options.REGEX"/>
@ -12,7 +12,7 @@
<el-option label="XPath" :value="options.XPATH"/> <el-option label="XPath" :value="options.XPATH"/>
</el-select> </el-select>
</el-col> </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"/> <ms-api-extract-common :is-read-only="isReadOnly" :extract-type="type" :list="list" v-if="type" :callback="after"/>
</el-col> </el-col>
</el-row> </el-row>

View File

@ -1,7 +1,12 @@
<template> <template>
<div> <div>
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <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" <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')"/> @change="change" show-word-limit :placeholder="$t('api_test.variable_name')"/>
</el-col> </el-col>
@ -53,7 +58,17 @@
data() { data() {
return { 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) { constructor(type, options) {
super(type); super(type);
this.variable = undefined; this.variable = undefined;
this.useHeaders = undefined;
this.value = ""; // ${variable} this.value = ""; // ${variable}
this.expression = undefined; this.expression = undefined;
this.description = undefined; this.description = undefined;
@ -562,7 +563,7 @@ class JMXGenerator {
switch (extractCommon.type) { switch (extractCommon.type) {
case EXTRACT_TYPE.REGEX: case EXTRACT_TYPE.REGEX:
testName += " RegexExtractor"; testName += " RegexExtractor";
props.headers = "false"; // 对应jMeter body props.headers = extractCommon.useHeaders; // 对应jMeter body
props.template = "$1$"; props.template = "$1$";
return new RegexExtractor(testName, props); return new RegexExtractor(testName, props);
case EXTRACT_TYPE.JSON_PATH: case EXTRACT_TYPE.JSON_PATH:

View File

@ -1,6 +1,6 @@
<template> <template>
<el-aside class="ms-aside-container"> <el-aside :width="width" class="ms-aside-container">
<slot></slot> <slot></slot>
</el-aside> </el-aside>
@ -8,7 +8,13 @@
<script> <script>
export default { export default {
name: "MsAsideContainer" name: "MsAsideContainer",
props: {
width: {
type: String,
default: '300px'
}
}
} }
</script> </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 class="character" @click="scheduleEdit">SCHEDULER</span>
</span> </span>
<el-switch :disabled="!schedule.value" v-model="schedule.enable" @change="scheduleChange"/> <el-switch :disabled="!schedule.value" v-model="schedule.enable" @change="scheduleChange"/>
<ms-schedule-edit :schedule="schedule" :save="save" ref="scheduleEdit"/> <ms-schedule-edit :schedule="schedule" :save="save" :custom-validate="customValidate" ref="scheduleEdit"/>
<crontab-result v-show="false" :ex="schedule.value" ref="crontabResult" @resultListChange="recentListChange"/> <crontab-result v-show="false" :ex="schedule.value" ref="crontabResult" @resultListChange="resultListChange"/>
</div> </div>
<div> <div>
<span :class="{'disable-character': !schedule.enable}"> 下次执行时间{{this.recentList.length > 0 ? this.recentList[0] : '未设置'}} </span> <span :class="{'disable-character': !schedule.enable}"> 下次执行时间{{this.recentList.length > 0 ? this.recentList[0] : '未设置'}} </span>
@ -18,6 +18,9 @@
<script> <script>
import MsScheduleEdit from "./MsScheduleEdit"; import MsScheduleEdit from "./MsScheduleEdit";
import CrontabResult from "../cron/CrontabResult"; import CrontabResult from "../cron/CrontabResult";
function defaultCustomValidate() {return {pass: true};}
export default { export default {
name: "MsScheduleConfig", name: "MsScheduleConfig",
components: {CrontabResult, MsScheduleEdit}, components: {CrontabResult, MsScheduleEdit},
@ -37,6 +40,10 @@
} }
} }
}, },
customValidate: {
type: Function,
default: defaultCustomValidate
},
}, },
methods: { methods: {
scheduleEdit() { scheduleEdit() {
@ -48,8 +55,11 @@
scheduleChange() { scheduleChange() {
this.$emit('scheduleChange'); this.$emit('scheduleChange');
}, },
recentListChange(resultList) { resultListChange(resultList) {
this.recentList = 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="showCronDialog">生成 Cron</el-button>
<el-button type="primary" @click="saveCron">保存</el-button> <el-button type="primary" @click="saveCron">保存</el-button>
</el-form-item> </el-form-item>
<crontab-result :ex="form.cronValue" ref="crontabResult"/> <crontab-result :ex="form.cronValue" ref="crontabResult" />
</el-form> </el-form>
<el-dialog title="生成 cron" :visible.sync="showCron" :modal="false"> <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> </el-dialog>
</div> </div>
</el-dialog> </el-dialog>
@ -24,12 +24,18 @@
import CrontabResult from "../cron/CrontabResult"; import CrontabResult from "../cron/CrontabResult";
import {cronValidate} from "../../../../common/js/cron"; import {cronValidate} from "../../../../common/js/cron";
function defaultCustomValidate() {return {pass: true};}
export default { export default {
name: "MsScheduleEdit", name: "MsScheduleEdit",
components: {CrontabResult, Crontab}, components: {CrontabResult, Crontab},
props: { props: {
save: Function, save: Function,
schedule: {}, schedule: {},
customValidate: {
type: Function,
default: defaultCustomValidate
},
}, },
watch: { watch: {
'schedule.value'() { 'schedule.value'() {
@ -38,8 +44,13 @@
}, },
data() { data() {
const validateCron = (rule, cronValue, callback) => { const validateCron = (rule, cronValue, callback) => {
let customValidate = this.customValidate(this.getIntervalTime());
if (!cronValidate(cronValue)) { if (!cronValidate(cronValue)) {
callback(new Error('Cron 表达式格式错误')); callback(new Error('Cron 表达式格式错误'));
} else if(!this.intervalShortValidate()) {
callback(new Error('间隔时间请大于 5 分钟'));
} else if (!customValidate.pass){
callback(new Error(customValidate.info));
} else { } else {
callback(); callback();
} }
@ -58,10 +69,12 @@
methods: { methods: {
open() { open() {
this.dialogVisible = true; this.dialogVisible = true;
this.form.cronValue = this.schedule.value;
}, },
crontabFill(value) { crontabFill(value, resultList) {
// //
this.form.cronValue = value; this.form.cronValue = value;
this.$refs.crontabResult.resultList = resultList;
this.$refs['from'].validate(); this.$refs['from'].validate();
}, },
showCronDialog() { showCronDialog() {
@ -78,11 +91,27 @@
}); });
}, },
close() { close() {
this.dialogVisible = false;
this.form.cronValue = ''; this.form.cronValue = '';
this.$refs['from'].resetFields(); this.$refs['from'].resetFields();
if (!this.schedule.value) { if (!this.schedule.value) {
this.$refs.crontabResult.resultList = []; 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> </tbody>
</table> </table>
</div> </div>
<crontab-result :ex="contabValueString"/> <crontab-result :ex="contabValueString" ref="crontabResult"/>
<div class="pop_btn"> <div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill">确定</el-button> <el-button size="small" type="primary" @click="submitFill">确定</el-button>
@ -305,7 +305,7 @@
}, },
// //
submitFill() { submitFill() {
this.$emit("fill", this.contabValueString); this.$emit("fill", this.contabValueString, this.$refs.crontabResult.resultList);
this.hidePopup(); this.hidePopup();
}, },
clearCron() { clearCron() {

View File

@ -10,6 +10,8 @@
</template> </template>
<script> <script>
import {cronValidate} from "../../../../common/js/cron";
export default { export default {
name: 'CrontabResult', name: 'CrontabResult',
data() { data() {
@ -30,11 +32,11 @@ export default {
this.expressionChange(); this.expressionChange();
}, },
methods: { methods: {
// //
expressionChange() { expressionChange() {
// - // -
this.isShow = false; this.isShow = false;
if (!this.ex) { if (!cronValidate(this.ex)) {
this.resultList = []; this.resultList = [];
this.$emit("resultListChange", this.resultList); this.$emit("resultListChange", this.resultList);
return; 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> <span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template> </template>
</el-table-column> </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')"> <el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<ms-performance-report-status :row="row"/> <ms-performance-report-status :row="row"/>
@ -27,10 +32,11 @@
<script> <script>
import MsPerformanceReportStatus from "../report/PerformanceReportStatus"; import MsPerformanceReportStatus from "../report/PerformanceReportStatus";
import ReportTriggerModeItem from "../../common/tableItem/ReportTriggerModeItem";
export default { export default {
name: "MsPerformanceReportRecentList", name: "MsPerformanceReportRecentList",
components: {MsPerformanceReportStatus}, components: {ReportTriggerModeItem, MsPerformanceReportStatus},
data() { data() {
return { return {
result: {}, result: {},

View File

@ -13,12 +13,21 @@
<el-breadcrumb-item>{{reportName}}</el-breadcrumb-item> <el-breadcrumb-item>{{reportName}}</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</el-row> </el-row>
<!-- <el-row class="ms-report-view-btns">--> <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 || status !== 'Running'" type="primary" plain size="mini"
<!-- <el-button :disabled="isReadOnly" type="success" plain size="mini">{{$t('report.test_execute_again')}}</el-button>--> @click="stopTest(reportId)">
<!-- <el-button :disabled="isReadOnly" type="info" plain size="mini">{{$t('report.export')}}</el-button>--> {{$t('report.test_stop_now')}}
<!-- <el-button :disabled="isReadOnly" type="warning" plain size="mini">{{$t('report.compare')}}</el-button>--> </el-button>
<!-- </el-row>--> <!--<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>
<el-col :span="8"> <el-col :span="8">
<span class="ms-report-time-desc"> <span class="ms-report-time-desc">
@ -37,7 +46,7 @@
<el-tabs v-model="active" type="border-card" :stretch="true"> <el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_overview')"> <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"/> <ms-report-test-overview :report="report"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')"> <el-tab-pane :label="$t('report.test_request_statistics')">
@ -92,7 +101,8 @@
seconds: '0', seconds: '0',
title: 'Logging', title: 'Logging',
report: {}, report: {},
isReadOnly: false isReadOnly: false,
websocket: null
} }
}, },
methods: { methods: {
@ -114,19 +124,27 @@
if (this.reportId) { if (this.reportId) {
this.result = this.$get("/performance/report/content/report_time/" + this.reportId) this.result = this.$get("/performance/report/content/report_time/" + this.reportId)
.then(res => { .then(res => {
let data = res.data.data; let data = res.data.data;
if (data) { if (data) {
this.startTime = data.startTime; this.startTime = data.startTime;
this.endTime = data.endTime; this.endTime = data.endTime;
let duration = data.duration; let duration = data.duration;
this.minutes = Math.floor(duration / 60); this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60; this.seconds = duration % 60;
} }
}).catch(() => { }).catch(() => {
this.clearData(); 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) { checkReportStatus(status) {
switch (status) { switch (status) {
case 'Error': case 'Error':
@ -136,11 +154,7 @@
this.$warning(this.$t('report.start_status')); this.$warning(this.$t('report.start_status'));
break; break;
case 'Reporting': case 'Reporting':
this.$info(this.$t('report.being_generated'));
break;
case 'Running': case 'Running':
this.$warning(this.$t('report.run_status'));
break;
case 'Completed': case 'Completed':
default: default:
break; break;
@ -151,6 +165,33 @@
this.endTime = '0'; this.endTime = '0';
this.minutes = '0'; this.minutes = '0';
this.seconds = '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() { created() {
@ -165,12 +206,15 @@
this.$set(this.report, "id", this.reportId); this.$set(this.report, "id", this.reportId);
this.$set(this.report, "status", data.status); this.$set(this.report, "status", data.status);
this.checkReportStatus(data.status); this.checkReportStatus(data.status);
if (this.status === "Completed") { if (this.status === "Completed" || this.status === "Running") {
this.initReportTimeInfo(); this.initReportTimeInfo();
} }
}) })
this.initBreadcrumb(); this.initBreadcrumb();
this.initWebSocket();
},
beforeDestroy() {
this.websocket.close() //websocket
}, },
watch: { watch: {
'$route'(to) { '$route'(to) {

View File

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

View File

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

View File

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

View File

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

View File

@ -324,10 +324,10 @@
}, },
watch: { watch: {
report: { report: {
handler(val){ handler(val) {
let status = val.status; let status = val.status;
this.id = val.id; this.id = val.id;
if (status === "Completed") { if (status === "Completed" || status === "Running") {
this.initTableData(); this.initTableData();
} else { } else {
this.maxUsers = '0'; this.maxUsers = '0';
@ -340,7 +340,7 @@
this.resOption = {}; this.resOption = {};
} }
}, },
deep:true deep: true
} }
}, },
props: ['report'] 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="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> <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-col>
</el-row> </el-row>
@ -177,7 +177,7 @@
this.result = this.$request(options, (response) => { this.result = this.$request(options, (response) => {
this.testPlan.id = response.data; this.testPlan.id = response.data;
this.$success(this.$t('commons.save_success')); 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.$success(this.$t('load_test.is_running'))
this.$router.push({path: '/performance/report/all'}) this.$router.push({path: '/performance/report/all'})
}) })
@ -278,6 +278,18 @@
return false; return false;
} }
return true; 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> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.operating')"> <el-table-column :label="$t('commons.operating')">
<template v-slot:default="scope"> <template v-if="baseUrl == 'api'" v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="edit(scope.row)" @deleteClick="handleDelete(scope.row)"/> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -59,6 +64,8 @@
<ms-delete-confirm :title="$t('project.delete')" @delete="_handleDelete" ref="deleteConfirm"/> <ms-delete-confirm :title="$t('project.delete')" @delete="_handleDelete" ref="deleteConfirm"/>
<api-environment-config ref="environmentConfig"/>
</ms-container> </ms-container>
</template> </template>
@ -69,15 +76,18 @@
import MsTableHeader from "../common/components/MsTableHeader"; import MsTableHeader from "../common/components/MsTableHeader";
import MsTableOperator from "../common/components/MsTableOperator"; import MsTableOperator from "../common/components/MsTableOperator";
import MsDialogFooter from "../common/components/MsDialogFooter"; 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 MsContainer from "../common/components/MsContainer";
import MsMainContainer from "../common/components/MsMainContainer"; import MsMainContainer from "../common/components/MsMainContainer";
import MsDeleteConfirm from "../common/components/MsDeleteConfirm"; 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 { export default {
name: "MsProject", name: "MsProject",
components: { components: {
ApiEnvironmentConfig,
MsTableOperatorButton,
MsDeleteConfirm, MsDeleteConfirm,
MsMainContainer, MsMainContainer,
MsContainer, MsTableOperator, MsCreateBox, MsTablePagination, MsTableHeader, MsDialogFooter}, MsContainer, MsTableOperator, MsCreateBox, MsTablePagination, MsTableHeader, MsDialogFooter},
@ -227,6 +237,9 @@
_sort(column, this.condition); _sort(column, this.condition);
this.list(); this.list();
}, },
openEnvironmentConfig(project) {
this.$refs.environmentConfig.open(project);
}
} }
} }
</script> </script>

View File

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

View File

@ -128,9 +128,9 @@ export function _filter(filters, condition) {
for (let filter in filters) { for (let filter in filters) {
if (filters.hasOwnProperty(filter)) { if (filters.hasOwnProperty(filter)) {
if (filters[filter] && filters[filter].length > 0) { if (filters[filter] && filters[filter].length > 0) {
condition.filters[filter] = filters[filter]; condition.filters[humpToLine(filter)] = filters[filter];
} else { } 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_start_time': 'Test Start Time',
'test_end_time': 'Test End Time', 'test_end_time': 'Test End Time',
'test_stop_now': 'Test Stop Now', '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', 'test_execute_again': 'Test Execute Again',
'export': 'Export', 'export': 'Export',
'compare': 'Compare', 'compare': 'Compare',

View File

@ -202,6 +202,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': '确定要立即停止当前测试吗?',
'test_stop_success': '停止成功',
'test_execute_again': '再次执行', 'test_execute_again': '再次执行',
'export': '导出', 'export': '导出',
'compare': '比较', 'compare': '比较',

View File

@ -202,6 +202,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': '確定要立即停止當前測試嗎?',
'test_stop_success': '停止成功',
'test_execute_again': '再次執行', 'test_execute_again': '再次執行',
'export': '導出', 'export': '導出',
'compare': '比較', 'compare': '比較',