提交 155b340e 编写于 作者: Q q4speed

Merge remote-tracking branch 'origin/dev' into dev

......@@ -36,10 +36,6 @@
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
<exclusion>
<artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId>
</exclusion>
</exclusions>
</dependency>
......@@ -133,6 +129,13 @@
<version>5.1</version>
</dependency>
<!-- easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.7</version>
</dependency>
</dependencies>
<build>
......
package io.metersphere.base.domain;
import java.io.Serializable;
public class LoadTestReportDetail implements Serializable {
private String reportId;
private String content;
private static final long serialVersionUID = 1L;
public String getReportId() {
return reportId;
}
public void setReportId(String reportId) {
this.reportId = reportId == null ? null : reportId.trim();
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content == null ? null : content.trim();
}
}
\ No newline at end of file
package io.metersphere.base.domain;
import java.util.ArrayList;
import java.util.List;
public class LoadTestReportDetailExample {
protected String orderByClause;
protected boolean distinct;
protected List<Criteria> oredCriteria;
public LoadTestReportDetailExample() {
oredCriteria = new ArrayList<Criteria>();
}
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public String getOrderByClause() {
return orderByClause;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public boolean isDistinct() {
return distinct;
}
public List<Criteria> getOredCriteria() {
return oredCriteria;
}
public void or(Criteria criteria) {
oredCriteria.add(criteria);
}
public Criteria or() {
Criteria criteria = createCriteriaInternal();
oredCriteria.add(criteria);
return criteria;
}
public Criteria createCriteria() {
Criteria criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
oredCriteria.add(criteria);
}
return criteria;
}
protected Criteria createCriteriaInternal() {
Criteria criteria = new Criteria();
return criteria;
}
public void clear() {
oredCriteria.clear();
orderByClause = null;
distinct = false;
}
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
protected GeneratedCriteria() {
super();
criteria = new ArrayList<Criterion>();
}
public boolean isValid() {
return criteria.size() > 0;
}
public List<Criterion> getAllCriteria() {
return criteria;
}
public List<Criterion> getCriteria() {
return criteria;
}
protected void addCriterion(String condition) {
if (condition == null) {
throw new RuntimeException("Value for condition cannot be null");
}
criteria.add(new Criterion(condition));
}
protected void addCriterion(String condition, Object value, String property) {
if (value == null) {
throw new RuntimeException("Value for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value));
}
protected void addCriterion(String condition, Object value1, Object value2, String property) {
if (value1 == null || value2 == null) {
throw new RuntimeException("Between values for " + property + " cannot be null");
}
criteria.add(new Criterion(condition, value1, value2));
}
public Criteria andReportIdIsNull() {
addCriterion("report_id is null");
return (Criteria) this;
}
public Criteria andReportIdIsNotNull() {
addCriterion("report_id is not null");
return (Criteria) this;
}
public Criteria andReportIdEqualTo(String value) {
addCriterion("report_id =", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotEqualTo(String value) {
addCriterion("report_id <>", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdGreaterThan(String value) {
addCriterion("report_id >", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdGreaterThanOrEqualTo(String value) {
addCriterion("report_id >=", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLessThan(String value) {
addCriterion("report_id <", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLessThanOrEqualTo(String value) {
addCriterion("report_id <=", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdLike(String value) {
addCriterion("report_id like", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotLike(String value) {
addCriterion("report_id not like", value, "reportId");
return (Criteria) this;
}
public Criteria andReportIdIn(List<String> values) {
addCriterion("report_id in", values, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotIn(List<String> values) {
addCriterion("report_id not in", values, "reportId");
return (Criteria) this;
}
public Criteria andReportIdBetween(String value1, String value2) {
addCriterion("report_id between", value1, value2, "reportId");
return (Criteria) this;
}
public Criteria andReportIdNotBetween(String value1, String value2) {
addCriterion("report_id not between", value1, value2, "reportId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {
protected Criteria() {
super();
}
}
public static class Criterion {
private String condition;
private Object value;
private Object secondValue;
private boolean noValue;
private boolean singleValue;
private boolean betweenValue;
private boolean listValue;
private String typeHandler;
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
protected Criterion(String condition) {
super();
this.condition = condition;
this.typeHandler = null;
this.noValue = true;
}
protected Criterion(String condition, Object value, String typeHandler) {
super();
this.condition = condition;
this.value = value;
this.typeHandler = typeHandler;
if (value instanceof List<?>) {
this.listValue = true;
} else {
this.singleValue = true;
}
}
protected Criterion(String condition, Object value) {
this(condition, value, null);
}
protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
super();
this.condition = condition;
this.value = value;
this.secondValue = secondValue;
this.typeHandler = typeHandler;
this.betweenValue = true;
}
protected Criterion(String condition, Object value, Object secondValue) {
this(condition, value, secondValue, null);
}
}
}
\ No newline at end of file
package io.metersphere.base.domain;
public class ZaleniumTest {
private String seleniumSessionId;
private String testName;
private String timestamp;
private String addedToDashboardTime;
private String browser;
private String browserVersion;
private String proxyName;
private String platform;
private String fileName;
private String fileExtension;
private String videoFolderPath;
private String logsFolderPath;
private String testNameNoExtension;
private String screenDimension;
private String timeZone;
private String build;
private String testFileNameTemplate;
private String browserDriverLogFileName;
private String retentionDate;
private String testStatus;
private boolean videoRecorded;
public String getSeleniumSessionId() {
return seleniumSessionId;
}
public void setSeleniumSessionId(String seleniumSessionId) {
this.seleniumSessionId = seleniumSessionId;
}
public String getTestName() {
return testName;
}
public void setTestName(String testName) {
this.testName = testName;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getAddedToDashboardTime() {
return addedToDashboardTime;
}
public void setAddedToDashboardTime(String addedToDashboardTime) {
this.addedToDashboardTime = addedToDashboardTime;
}
public String getBrowser() {
return browser;
}
public void setBrowser(String browser) {
this.browser = browser;
}
public String getBrowserVersion() {
return browserVersion;
}
public void setBrowserVersion(String browserVersion) {
this.browserVersion = browserVersion;
}
public String getProxyName() {
return proxyName;
}
public void setProxyName(String proxyName) {
this.proxyName = proxyName;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileExtension() {
return fileExtension;
}
public void setFileExtension(String fileExtension) {
this.fileExtension = fileExtension;
}
public String getVideoFolderPath() {
return videoFolderPath;
}
public void setVideoFolderPath(String videoFolderPath) {
this.videoFolderPath = videoFolderPath;
}
public String getLogsFolderPath() {
return logsFolderPath;
}
public void setLogsFolderPath(String logsFolderPath) {
this.logsFolderPath = logsFolderPath;
}
public String getTestNameNoExtension() {
return testNameNoExtension;
}
public void setTestNameNoExtension(String testNameNoExtension) {
this.testNameNoExtension = testNameNoExtension;
}
public String getScreenDimension() {
return screenDimension;
}
public void setScreenDimension(String screenDimension) {
this.screenDimension = screenDimension;
}
public String getTimeZone() {
return timeZone;
}
public void setTimeZone(String timeZone) {
this.timeZone = timeZone;
}
public String getBuild() {
return build;
}
public void setBuild(String build) {
this.build = build;
}
public String getTestFileNameTemplate() {
return testFileNameTemplate;
}
public void setTestFileNameTemplate(String testFileNameTemplate) {
this.testFileNameTemplate = testFileNameTemplate;
}
public String getBrowserDriverLogFileName() {
return browserDriverLogFileName;
}
public void setBrowserDriverLogFileName(String browserDriverLogFileName) {
this.browserDriverLogFileName = browserDriverLogFileName;
}
public String getRetentionDate() {
return retentionDate;
}
public void setRetentionDate(String retentionDate) {
this.retentionDate = retentionDate;
}
public String getTestStatus() {
return testStatus;
}
public void setTestStatus(String testStatus) {
this.testStatus = testStatus;
}
public boolean isVideoRecorded() {
return videoRecorded;
}
public void setVideoRecorded(boolean videoRecorded) {
this.videoRecorded = videoRecorded;
}
}
package io.metersphere.base.mapper;
import io.metersphere.base.domain.LoadTestReportDetail;
import io.metersphere.base.domain.LoadTestReportDetailExample;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface LoadTestReportDetailMapper {
long countByExample(LoadTestReportDetailExample example);
int deleteByExample(LoadTestReportDetailExample example);
int deleteByPrimaryKey(String reportId);
int insert(LoadTestReportDetail record);
int insertSelective(LoadTestReportDetail record);
List<LoadTestReportDetail> selectByExampleWithBLOBs(LoadTestReportDetailExample example);
List<LoadTestReportDetail> selectByExample(LoadTestReportDetailExample example);
LoadTestReportDetail selectByPrimaryKey(String reportId);
int updateByExampleSelective(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example);
int updateByExampleWithBLOBs(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example);
int updateByExample(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example);
int updateByPrimaryKeySelective(LoadTestReportDetail record);
int updateByPrimaryKeyWithBLOBs(LoadTestReportDetail record);
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.LoadTestReportDetailMapper">
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.LoadTestReportDetail">
<id column="report_id" jdbcType="VARCHAR" property="reportId" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReportDetail">
<result column="content" jdbcType="LONGVARCHAR" property="content" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
report_id
</sql>
<sql id="Blob_Column_List">
content
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportDetailExample" resultMap="ResultMapWithBLOBs">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from load_test_report_detail
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByExample" parameterType="io.metersphere.base.domain.LoadTestReportDetailExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from load_test_report_detail
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="ResultMapWithBLOBs">
select
<include refid="Base_Column_List" />
,
<include refid="Blob_Column_List" />
from load_test_report_detail
where report_id = #{reportId,jdbcType=VARCHAR}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.String">
delete from load_test_report_detail
where report_id = #{reportId,jdbcType=VARCHAR}
</delete>
<delete id="deleteByExample" parameterType="io.metersphere.base.domain.LoadTestReportDetailExample">
delete from load_test_report_detail
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReportDetail">
insert into load_test_report_detail (report_id, content)
values (#{reportId,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReportDetail">
insert into load_test_report_detail
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="reportId != null">
report_id,
</if>
<if test="content != null">
content,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="reportId != null">
#{reportId,jdbcType=VARCHAR},
</if>
<if test="content != null">
#{content,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.LoadTestReportDetailExample" resultType="java.lang.Long">
select count(*) from load_test_report_detail
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update load_test_report_detail
<set>
<if test="record.reportId != null">
report_id = #{record.reportId,jdbcType=VARCHAR},
</if>
<if test="record.content != null">
content = #{record.content,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExampleWithBLOBs" parameterType="map">
update load_test_report_detail
set report_id = #{record.reportId,jdbcType=VARCHAR},
content = #{record.content,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update load_test_report_detail
set report_id = #{record.reportId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.LoadTestReportDetail">
update load_test_report_detail
<set>
<if test="content != null">
content = #{content,jdbcType=LONGVARCHAR},
</if>
</set>
where report_id = #{reportId,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportDetail">
update load_test_report_detail
set content = #{content,jdbcType=LONGVARCHAR}
where report_id = #{reportId,jdbcType=VARCHAR}
</update>
</mapper>
\ No newline at end of file
package io.metersphere.base.mapper.ext;
import org.apache.ibatis.annotations.Param;
public interface ExtLoadTestReportDetailMapper {
int appendLine(@Param("reportId") String id, @Param("line") String line);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper">
<update id="appendLine">
UPDATE load_test_report_detail
SET content = concat(content, #{line})
WHERE report_id = #{reportId}
</update>
</mapper>
\ No newline at end of file
package io.metersphere.commons.constants;
public enum PerformanceTestStatus {
Saved, Starting, Running, Completed, Error
}
package io.metersphere.commons.constants;
public enum TestStatus {
Starting, Running, Completed, Error
public class TestCaseConstants {
public static final int MAX_NODE_DEPTH = 5;
}
......@@ -4,7 +4,6 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.FileMetadata;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.testplan.*;
......@@ -86,10 +85,7 @@ public class PerformanceTestController {
@PostMapping("/run")
public void run(@RequestBody RunTestPlanRequest request) {
boolean started = performanceTestService.run(request);
if (!started) {
MSException.throwException("Start engine error, please check log.");
}
performanceTestService.run(request);
}
@GetMapping("/file/metadata/{testId}")
......
......@@ -6,14 +6,11 @@ import io.metersphere.base.domain.*;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.controller.request.testplan.QueryTestPlanRequest;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.TestCaseNodeDTO;
import io.metersphere.dto.TestPlanCaseDTO;
import io.metersphere.service.TestCaseNodeService;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.service.TestCaseService;
import io.metersphere.user.SessionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
......@@ -74,5 +71,10 @@ public class TestCaseController {
return testCaseService.deleteTestCase(testCaseId);
}
@PostMapping("/import/{projectId}")
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId){
return testCaseService.testCaseImport(file, projectId);
}
}
......@@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.base.domain.TestResource;
import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.config.JmeterProperties;
......@@ -70,7 +70,7 @@ public abstract class AbstractEngine implements Engine {
List<LoadTestWithBLOBs> loadTests = performanceTestService.selectByTestResourcePoolId(loadTest.getTestResourcePoolId());
// 使用当前资源池正在运行的测试占用的并发数
return loadTests.stream()
.filter(t -> TestStatus.Running.name().equals(t.getStatus()))
.filter(t -> PerformanceTestStatus.Running.name().equals(t.getStatus()))
.map(this::getThreadNum)
.reduce(Integer::sum)
.orElse(0);
......
......@@ -12,6 +12,7 @@ import io.metersphere.engine.EngineContext;
import io.metersphere.engine.EngineFactory;
import io.metersphere.engine.docker.request.BaseRequest;
import io.metersphere.engine.docker.request.TestRequest;
import io.metersphere.i18n.Translator;
import org.springframework.web.client.RestTemplate;
import java.util.List;
......@@ -41,7 +42,7 @@ public class DockerTestEngine extends AbstractEngine {
.reduce(Integer::sum)
.orElse(0);
if (threadNum > totalThreadNum - runningSumThreadNum) {
MSException.throwException("Insufficient resources");
MSException.throwException(Translator.get("max_thread_insufficient"));
}
List<Integer> resourceRatio = resourceList.stream()
.filter(r -> ResourceStatusEnum.VALID.name().equals(r.getStatus()))
......
......@@ -15,6 +15,7 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter;
import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec;
import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.MapUtils;
import java.util.HashMap;
......@@ -43,7 +44,7 @@ public class KubernetesTestEngine extends AbstractEngine {
Integer maxConcurrency = clientCredential.getMaxConcurrency();
// 当前测试需要的并发数大于剩余的并发数报错
if (threadNum > maxConcurrency - sumThreadNum) {
MSException.throwException("Insufficient resources");
MSException.throwException(Translator.get("max_thread_insufficient"));
}
try {
EngineContext context = EngineFactory.createContext(loadTest, threadNum, this.getStartTime(), this.getReportId());
......
package io.metersphere.excel.domain;
public class ExcelErrData<T> {
private T t;
private Integer rowNum;
private String errMsg;
public ExcelErrData(){}
public ExcelErrData(T t, Integer rowNum,String errMsg){
this.t = t;
this.rowNum = rowNum;
this.errMsg = errMsg;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public Integer getRowNum() {
return rowNum;
}
public void setRowNum(Integer rowNum) {
this.rowNum = rowNum;
}
}
\ No newline at end of file
package io.metersphere.excel.domain;
import java.util.List;
public class ExcelResponse<T> {
private Boolean success;
private List<ExcelErrData<T>> errList;
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public List<ExcelErrData<T>> getErrList() {
return errList;
}
public void setErrList(List<ExcelErrData<T>> errList) {
this.errList = errList;
}
}
package io.metersphere.excel.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class TestCaseExcelData {
@NotBlank
@Length(max=1000)
@ExcelProperty("所属模块")
@Pattern(regexp = "^(?!.*//).*$", message = "格式不正确")
private String nodePath;
@NotBlank
@Length(max=50)
@ExcelProperty("用例名称")
private String name;
@NotBlank
@ExcelProperty("用例类型")
@Pattern(regexp = "(^functional$)|(^performance$)|(^api$)", message = "必须为functional、performance、api")
private String type;
@NotBlank
@ExcelProperty("维护人")
private String maintainer;
@NotBlank
@ExcelProperty("优先级")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "必须为P0、P1、P2、P3")
private String priority;
@NotBlank
@ExcelProperty("测试方式")
@Pattern(regexp = "(^manual$)|(^auto$)", message = "必须为manual、auto")
private String method;
@ExcelProperty("前置条件")
@Length(min=0, max=1000)
private String prerequisite;
@ExcelProperty("备注")
@Length(max=1000)
private String remark;
@ExcelProperty("步骤描述")
@Length(max=1000)
private String stepDesc;
@ExcelProperty("预期结果")
@Length(max=1000)
private String stepResult;
public String getNodePath() {
return nodePath;
}
public void setNodePath(String nodePath) {
this.nodePath = nodePath;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMaintainer() {
return maintainer;
}
public void setMaintainer(String maintainer) {
this.maintainer = maintainer;
}
public String getPriority() {
return priority;
}
public void setPriority(String priority) {
this.priority = priority;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPrerequisite() {
return prerequisite;
}
public void setPrerequisite(String prerequisite) {
this.prerequisite = prerequisite;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getStepDesc() {
return stepDesc;
}
public void setStepDesc(String stepDesc) {
this.stepDesc = stepDesc;
}
public String getStepResult() {
return stepResult;
}
public void setStepResult(String stepResult) {
this.stepResult = stepResult;
}
}
package io.metersphere.excel.listener;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.util.StringUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.util.ExcelValidateHelper;
import io.metersphere.excel.domain.ExcelErrData;
import java.lang.reflect.Field;
import java.util.*;
public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
protected List<ExcelErrData<T>> errList = new ArrayList<>();
protected List<T> list = new ArrayList<>();
/**
* 每隔2000条存储数据库,然后清理list ,方便内存回收
*/
protected static final int BATCH_COUNT = 2000;
protected Class<T> clazz;
public EasyExcelListener(Class<T> clazz){
this.clazz = clazz;
}
/**
* 这个每一条数据解析都会来调用
*
* @param t
* @param analysisContext
*/
@Override
public void invoke(T t, AnalysisContext analysisContext) {
String errMsg;
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
try {
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
errMsg = ExcelValidateHelper.validateEntity(t);
//自定义校验规则
errMsg = validate(t, errMsg);
} catch (NoSuchFieldException e) {
errMsg = "解析数据出错";
LogUtil.error(e.getMessage(), e);
}
if (!StringUtils.isEmpty(errMsg)) {
ExcelErrData excelErrData = new ExcelErrData(t, rowIndex, "第" + rowIndex + "行出错:" + errMsg);
errList.add(excelErrData);
} else {
list.add(t);
}
if (list.size() > BATCH_COUNT) {
saveData();
list.clear();
}
}
/**
* 可重写该方法
* 自定义校验规则
* @param data
* @param errMsg
* @return
*/
public String validate(T data, String errMsg) {
return errMsg;
}
/**
* 自定义数据保存操作
*/
public abstract void saveData();
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();
list.clear();
}
/**
* 校验excel头部
* @param headMap 传入excel的头部(第一行数据)数据的index,name
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
super.invokeHeadMap(headMap, context);
if (clazz != null){
try {
Set<String> fieldNameSet = getFieldNameSet(clazz);
Collection<String> values = headMap.values();
for (String key : fieldNameSet) {
if (!values.contains(key)){
throw new ExcelAnalysisException("缺少头部信息:" + key);
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
/**
* @description: 获取注解里ExcelProperty的value
*/
public Set<String> getFieldNameSet(Class clazz) throws NoSuchFieldException {
Set<String> result = new HashSet<>();
Field field;
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length ; i++) {
field = clazz.getDeclaredField(fields[i].getName());
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if(excelProperty != null){
StringBuilder value = new StringBuilder();
for (String v : excelProperty.value()) {
value.append(v);
}
result.add(value.toString());
}
}
return result;
}
public List<ExcelErrData<T>> getErrList() {
return errList;
}
}
\ No newline at end of file
package io.metersphere.excel.listener;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.service.TestCaseService;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
private TestCaseService testCaseService;
private String projectId;
Set<String> testCaseNames;
Set<String> userNames;
public TestCaseDataListener(TestCaseService testCaseService, String projectId,
Set<String> testCaseNames, Set<String> userNames, Class<TestCaseExcelData> clazz) {
super(clazz);
this.testCaseService = testCaseService;
this.projectId = projectId;
this.testCaseNames = testCaseNames;
this.userNames = userNames;
}
@Override
public String validate(TestCaseExcelData data, String errMsg) {
String nodePath = data.getNodePath();
StringBuilder stringBuilder = new StringBuilder(errMsg);
if ( nodePath.split("/").length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
stringBuilder.append("节点最多为" + TestCaseConstants.MAX_NODE_DEPTH + "层;");
}
if (!userNames.contains(data.getMaintainer())) {
stringBuilder.append("该工作空间下无该用户:" + data.getMaintainer() + ";");
}
if (testCaseNames.contains(data.getName())) {
stringBuilder.append("该项目下已存在该测试用例:" + data.getName() + ";");
}
return stringBuilder.toString();
}
@Override
public void saveData() {
//无错误数据才插入数据
if (!errList.isEmpty()) {
return;
}
List<TestCaseWithBLOBs> result = list.stream()
.map(item -> this.convert2TestCase(item))
.collect(Collectors.toList());
testCaseService.saveImportData(result, projectId);
}
private TestCaseWithBLOBs convert2TestCase(TestCaseExcelData data) {
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
BeanUtils.copyBean(testCase, data);
testCase.setId(UUID.randomUUID().toString());
testCase.setProjectId(this.projectId);
testCase.setCreateTime(System.currentTimeMillis());
testCase.setUpdateTime(System.currentTimeMillis());
String nodePath = data.getNodePath();
if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath;
}
if (nodePath.endsWith("/")) {
nodePath = nodePath.substring(0, nodePath.length() - 1);
}
testCase.setNodePath(nodePath);
JSONArray jsonArray = new JSONArray();
String[] stepDesc = new String[0];
String[] stepRes = new String[0];
if (data.getStepDesc() != null) {
stepDesc = data.getStepDesc().split("\n");
}
if (data.getStepResult() != null) {
stepRes = data.getStepResult().split("\n");
}
String pattern = "(^\\d+)(\\.)?";
int index = stepDesc.length > stepRes.length ? stepDesc.length : stepRes.length;
for (int i = 0; i < index; i++){
JSONObject step = new JSONObject();
step.put("num", i + 1);
Pattern descPattern = Pattern.compile(pattern);
Pattern resPattern = Pattern.compile(pattern);
if (i < stepDesc.length) {
Matcher descMatcher = descPattern.matcher(stepDesc[i]);
if (descMatcher.find()) {
step.put("desc", descMatcher.replaceAll(""));
} else {
step.put("desc", stepDesc[i]);
}
}
if (i < stepRes.length) {
Matcher resMatcher = resPattern.matcher(stepRes[i]);
if (resMatcher.find()) {
step.put("result", resMatcher.replaceAll(""));
} else {
step.put("result", stepRes[i]);
}
}
jsonArray.add(step);
}
testCase.setSteps(jsonArray.toJSONString());
return testCase;
}
}
package io.metersphere.excel.util;
import com.alibaba.excel.annotation.ExcelProperty;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.lang.reflect.Field;
import java.util.Set;
public class ExcelValidateHelper {
private ExcelValidateHelper(){}
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static <T> String validateEntity(T obj) throws NoSuchFieldException {
StringBuilder result = new StringBuilder();
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
if (set != null && !set.isEmpty()) {
for (ConstraintViolation<T> cv : set) {
Field declaredField = obj.getClass().getDeclaredField(cv.getPropertyPath().toString());
ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
//拼接错误信息,包含当前出错数据的标题名字+错误信息
result.append(annotation.value()[0]+cv.getMessage()).append(";");
}
}
return result.toString();
}
}
\ No newline at end of file
package io.metersphere.exception;
/**
* @author jianxing.chen
*/
public class ExcelImportException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ExcelImportException(String message, Exception e){
super(message, e);
}
public ExcelImportException(String message){
super(message);
}
}
......@@ -104,7 +104,7 @@ public class JtlResolver {
String average = decimalFormat.format((float) oneLineElapsedTime / jtlSamplesSize);
requestStatistics.setAverage(average);
/**
/*
* TP90的计算
* 1,把一段时间内全部的请求的响应时间,从小到大排序,获得序列A
* 2,总的请求数量,乘以90%,获得90%对应的请求个数C
......@@ -123,7 +123,7 @@ public class JtlResolver {
requestStatistics.setMax(elapsedList.get(jtlSamplesSize - 1) + "");
requestStatistics.setErrors(decimalFormat.format(failSize * 100.0 / jtlSamplesSize) + "%");
requestStatistics.setKo(failSize);
/**
/*
* 所有的相同请求的bytes总和 / 1024 / 请求持续运行的时间=sum(bytes)/1024/total time
* total time = 最大时间戳 - 最小时间戳 + 最后请求的响应时间
*/
......@@ -268,20 +268,34 @@ public class JtlResolver {
DecimalFormat decimalFormat = new DecimalFormat("0.00");
List<Metric> totalLineList = JtlResolver.resolver(jtlString);
// todo
List<Metric> totalLineList2 = JtlResolver.resolver(jtlString);
// 时间戳转时间
for (Metric metric : totalLineList2) {
metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
}
Map<String, List<Metric>> collect2 = Objects.requireNonNull(totalLineList2).stream().collect(Collectors.groupingBy(Metric::getTimestamp));
List<Map.Entry<String, List<Metric>>> entries = new ArrayList<>(collect2.entrySet());
int maxUsers = 0;
for (Map.Entry<String, List<Metric>> entry : entries) {
List<Metric> metrics = entry.getValue();
Map<String, List<Metric>> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName));
if (metricsMap.size() > maxUsers) {
maxUsers = metricsMap.size();
}
}
Map<String, List<Metric>> collect = totalLineList.stream().collect(Collectors.groupingBy(Metric::getTimestamp));
Iterator<Map.Entry<String, List<Metric>>> iterator = collect.entrySet().iterator();
int maxUsers = 0, totalElapsed = 0;
int totalElapsed = 0;
float totalBytes = 0f;
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> entry = iterator.next();
List<Metric> metricList = entry.getValue();
if (metricList.size() > maxUsers) {
maxUsers = metricList.size();
}
for (Metric metric : metricList) {
String elapsed = metric.getElapsed();
totalElapsed += Integer.parseInt(elapsed);
......@@ -420,22 +434,22 @@ public class JtlResolver {
totalLineList.sort(Comparator.comparing(t0 -> Long.valueOf(t0.getTimestamp())));
String startTimeStamp = totalLineList.get(0).getTimestamp();
String endTimeStamp = totalLineList.get(totalLineList.size() - 1).getTimestamp();
String endTimeStamp = totalLineList.get(totalLineList.size()-1).getTimestamp();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String startTime = dtf.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(startTimeStamp)), ZoneId.systemDefault()));
String endTime = dtf.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(endTimeStamp)), ZoneId.systemDefault()));
reportTimeInfo.setStartTime(startTime);
reportTimeInfo.setEndTime(endTime);
Date startDate = new Date(Long.parseLong(startTimeStamp));
Date endDate = new Date(Long.parseLong(endTimeStamp));
long timestamp = endDate.getTime() - startDate.getTime();
reportTimeInfo.setDuration(String.valueOf(timestamp*1.0 / 1000 / 60));
// todo 时间问题
long seconds = Duration.between(Instant.ofEpochMilli(Long.parseLong(startTimeStamp)), Instant.ofEpochMilli(Long.parseLong(endTimeStamp))).getSeconds();
String duration;
if (seconds / 60 == 0) {
duration = String.valueOf(1);
} else {
duration = String.valueOf(seconds / 60);
}
reportTimeInfo.setDuration(duration);
reportTimeInfo.setDuration(String.valueOf(seconds));
return reportTimeInfo;
}
......
......@@ -3,9 +3,10 @@ package io.metersphere.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.testplan.*;
......@@ -48,6 +49,10 @@ public class PerformanceTestService {
private LoadTestReportMapper loadTestReportMapper;
@Resource
private ExtLoadTestReportMapper extLoadTestReportMapper;
@Resource
private LoadTestReportDetailMapper loadTestReportDetailMapper;
@Resource
private ExtLoadTestReportDetailMapper extLoadTestReportDetailMapper;
public List<LoadTestDTO> list(QueryTestPlanRequest request) {
return extLoadTestMapper.list(request);
......@@ -93,6 +98,7 @@ public class PerformanceTestService {
loadTest.setTestResourcePoolId(request.getTestResourcePoolId());
loadTest.setLoadConfiguration(request.getLoadConfiguration());
loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration());
loadTest.setStatus(PerformanceTestStatus.Saved.name());
loadTestMapper.insert(loadTest);
return loadTest;
}
......@@ -158,19 +164,22 @@ public class PerformanceTestService {
loadTest.setLoadConfiguration(request.getLoadConfiguration());
loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration());
loadTest.setTestResourcePoolId(request.getTestResourcePoolId());
// todo 修改 load_test 的时候排除状态,这里存在修改了 Running 的测试状态的风险
// loadTest.setStatus(PerformanceTestStatus.Saved.name());
loadTestMapper.updateByPrimaryKeySelective(loadTest);
}
return request.getId();
}
public boolean run(RunTestPlanRequest request) {
@Transactional(noRollbackFor = MSException.class)// 保存失败的信息
public void run(RunTestPlanRequest request) {
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
if (loadTest == null) {
MSException.throwException(Translator.get("run_load_test_not_found") + request.getId());
}
if (StringUtils.equalsAny(loadTest.getStatus(), TestStatus.Running.name(), TestStatus.Starting.name())) {
if (StringUtils.equalsAny(loadTest.getStatus(), PerformanceTestStatus.Running.name(), PerformanceTestStatus.Starting.name())) {
MSException.throwException(Translator.get("load_test_is_running"));
}
......@@ -181,12 +190,12 @@ public class PerformanceTestService {
MSException.throwException(String.format("Test cannot be run,test ID:%s", request.getId()));
}
return startEngine(loadTest, engine);
startEngine(loadTest, engine);
// todo:通过调用stop方法能够停止正在运行的engine,但是如果部署了多个backend实例,页面发送的停止请求如何定位到具体的engine
}
private boolean startEngine(LoadTestWithBLOBs loadTest, Engine engine) {
private void startEngine(LoadTestWithBLOBs loadTest, Engine engine) {
LoadTestReportWithBLOBs testReport = new LoadTestReportWithBLOBs();
testReport.setId(engine.getReportId());
testReport.setCreateTime(engine.getStartTime());
......@@ -194,31 +203,32 @@ public class PerformanceTestService {
testReport.setTestId(loadTest.getId());
testReport.setName(loadTest.getName());
// 启动测试
boolean started = true;
try {
engine.start();
// 标记running状态
loadTest.setStatus(TestStatus.Starting.name());
// 启动正常修改状态 starting
loadTest.setStatus(PerformanceTestStatus.Starting.name());
loadTestMapper.updateByPrimaryKeySelective(loadTest);
// 启动正常插入 report
testReport.setContent(HEADERS);
testReport.setStatus(TestStatus.Starting.name());
testReport.setStatus(PerformanceTestStatus.Starting.name());
loadTestReportMapper.insertSelective(testReport);
LoadTestReportDetail reportDetail = new LoadTestReportDetail();
reportDetail.setContent(HEADERS);
reportDetail.setReportId(testReport.getId());
loadTestReportDetailMapper.insertSelective(reportDetail);
// append \n
extLoadTestReportMapper.appendLine(testReport.getId(), "\n");
} catch (Exception e) {
// append \n
extLoadTestReportDetailMapper.appendLine(testReport.getId(), "\n");
} catch (MSException e) {
LogUtil.error(e);
started = false;
loadTest.setStatus(TestStatus.Error.name());
loadTest.setStatus(PerformanceTestStatus.Error.name());
loadTest.setDescription(e.getMessage());
loadTestMapper.updateByPrimaryKeySelective(loadTest);
//
testReport.setStatus(TestStatus.Error.name());
testReport.setDescription(e.getMessage());
loadTestReportMapper.insertSelective(testReport);
throw e;
}
return started;
}
public List<LoadTestDTO> recentTestPlans(QueryTestPlanRequest request) {
......
......@@ -5,7 +5,7 @@ import io.metersphere.base.domain.LoadTestReportExample;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.mapper.LoadTestReportMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.ReportRequest;
import io.metersphere.dto.ReportDTO;
......@@ -112,9 +112,9 @@ public class ReportService {
public void checkReportStatus(String reportId) {
LoadTestReportWithBLOBs loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
String reportStatus = loadTestReport.getStatus();
if (StringUtils.equals(TestStatus.Running.name(), reportStatus)) {
if (StringUtils.equals(PerformanceTestStatus.Running.name(), reportStatus)) {
MSException.throwException("Reporting in progress...");
} else if (StringUtils.equals(TestStatus.Error.name(), reportStatus)) {
} else if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) {
MSException.throwException("Report generation error!");
}
}
......
......@@ -6,8 +6,11 @@ import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.dto.TestCaseNodeDTO;
import io.metersphere.exception.ExcelImportException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -30,8 +33,8 @@ public class TestCaseNodeService {
public int addNode(TestCaseNode node) {
if(node.getLevel() > 5){
throw new RuntimeException("模块树最大深度为5层!");
if(node.getLevel() > TestCaseConstants.MAX_NODE_DEPTH){
throw new RuntimeException("模块树最大深度为" + TestCaseConstants.MAX_NODE_DEPTH + "层!");
}
node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis());
......@@ -196,4 +199,124 @@ public class TestCaseNodeService {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
return getNodeTreeByProjectId(testPlan.getProjectId());
}
public Map<String, Integer> createNodeByTestCases(List<TestCaseWithBLOBs> testCases, String projectId) {
List<TestCaseNodeDTO> nodeTrees = getNodeTreeByProjectId(projectId);
Map<String, Integer> pathMap = new HashMap<>();
List<String> nodePaths = testCases.stream()
.map(TestCase::getNodePath)
.collect(Collectors.toList());
nodePaths.forEach(path -> {
if (path == null) {
throw new ExcelImportException("所属模块不能为空!");
}
List<String> nodeNameList = new ArrayList<>(Arrays.asList(path.split("/")));
Iterator<String> pathIterator = nodeNameList.iterator();
Boolean hasNode = false;
String rootNodeName = null;
if (nodeNameList.size() <= 1) {
throw new ExcelImportException("创建模块失败:" + path);
} else {
pathIterator.next();
pathIterator.remove();
rootNodeName = pathIterator.next().trim();
for (TestCaseNodeDTO nodeTree : nodeTrees) {
if (StringUtils.equals(rootNodeName, nodeTree.getName())) {
hasNode = true;
createNodeByPathIterator(pathIterator, "/" + rootNodeName, nodeTree,
pathMap, projectId, 2);
};
}
}
if (!hasNode) {
createNodeByPath(pathIterator, rootNodeName, null, projectId, 1, "", pathMap);
}
});
return pathMap;
}
/**
* 根据目标节点路径,创建相关节点
* @param pathIterator 遍历子路径
* @param path 当前路径
* @param treeNode 当前节点
* @param pathMap 记录节点路径对应的nodeId
*/
private void createNodeByPathIterator(Iterator<String> pathIterator, String path, TestCaseNodeDTO treeNode,
Map<String, Integer> pathMap, String projectId, Integer level) {
List<TestCaseNodeDTO> children = treeNode.getChildren();
if (children == null || children.isEmpty() || !pathIterator.hasNext()) {
pathMap.put(path , treeNode.getId());
if (pathIterator.hasNext()) {
createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap);
}
return;
}
String nodeName = pathIterator.next().trim();
Boolean hasNode = false;
for (TestCaseNodeDTO child : children) {
if (StringUtils.equals(nodeName, child.getName())) {
hasNode = true;
createNodeByPathIterator(pathIterator, path + "/" + child.getName(),
child, pathMap, projectId, level + 1);
};
}
//若子节点中不包含该目标节点,则在该节点下创建
if (!hasNode) {
createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap);
}
}
/**
*
* @param pathIterator 迭代器,遍历子节点
* @param nodeName 当前节点
* @param pNode 父节点
*/
private void createNodeByPath(Iterator<String> pathIterator, String nodeName,
TestCaseNodeDTO pNode, String projectId, Integer level,
String rootPath, Map<String, Integer> pathMap) {
StringBuilder path = new StringBuilder(rootPath);
Integer pid = insertTestCaseNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level);
path.append("/" + nodeName);
pathMap.put(path.toString(), pid);
while (pathIterator.hasNext()) {
String nextNodeName = pathIterator.next();
path.append("/" + nextNodeName);
pid = insertTestCaseNode(nextNodeName, pid, projectId, ++level);
pathMap.put(path.toString(), pid);
}
}
private Integer insertTestCaseNode(String nodName, Integer pId, String projectId, Integer level) {
TestCaseNode testCaseNode = new TestCaseNode();
testCaseNode.setName(nodName.trim());
testCaseNode.setpId(pId);
testCaseNode.setProjectId(projectId);
testCaseNode.setCreateTime(System.currentTimeMillis());
testCaseNode.setUpdateTime(System.currentTimeMillis());
testCaseNode.setLevel(level);
testCaseNodeMapper.insert(testCaseNode);
return testCaseNode.getId();
}
}
package io.metersphere.service;
import com.alibaba.excel.EasyExcelFactory;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.dto.TestPlanCaseDTO;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.listener.EasyExcelListener;
import io.metersphere.excel.listener.TestCaseDataListener;
import io.metersphere.user.SessionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@Transactional(rollbackFor = Exception.class)
......@@ -41,6 +49,15 @@ public class TestCaseService {
@Resource
ProjectMapper projectMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
TestCaseNodeService testCaseNodeService;
@Resource
UserMapper userMapper;
public void addTestCase(TestCaseWithBLOBs testCase) {
testCase.setId(UUID.randomUUID().toString());
testCase.setCreateTime(System.currentTimeMillis());
......@@ -144,4 +161,59 @@ public class TestCaseService {
}
return projectMapper.selectByPrimaryKey(testCaseWithBLOBs.getProjectId());
}
public ExcelResponse testCaseImport(MultipartFile file, String projectId) {
try {
ExcelResponse excelResponse = new ExcelResponse();
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest();
queryTestCaseRequest.setProjectId(projectId);
List<TestCase> testCases = extTestCaseMapper.getTestCaseNames(queryTestCaseRequest);
Set<String> testCaseNames = testCases.stream()
.map(TestCase::getName)
.collect(Collectors.toSet());
UserExample userExample = new UserExample();
userExample.createCriteria().andLastWorkspaceIdEqualTo(currentWorkspaceId);
List<User> users = userMapper.selectByExample(userExample);
Set<String> userNames = users.stream().map(User::getName).collect(Collectors.toSet());
EasyExcelListener easyExcelListener = new TestCaseDataListener(this, projectId,
testCaseNames, userNames, TestCaseExcelData.class);
EasyExcelFactory.read(file.getInputStream(), TestCaseExcelData.class, easyExcelListener).sheet().doRead();
List<ExcelErrData<TestCaseExcelData>> errList = easyExcelListener.getErrList();
//如果包含错误信息就导出错误信息
if (!errList.isEmpty()) {
excelResponse.setSuccess(false);
excelResponse.setErrList(errList);
} else {
excelResponse.setSuccess(true);
}
return excelResponse;
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
public void saveImportData(List<TestCaseWithBLOBs> testCases, String projectId) {
Map<String, Integer> nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
if (!testCases.isEmpty()) {
testCases.forEach(testcase -> {
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
mapper.insert(testcase);
});
}
sqlSession.flushStatements();
}
}
......@@ -62,6 +62,15 @@ CREATE TABLE IF NOT EXISTS `load_test_report` (
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_bin;
CREATE TABLE IF NOT EXISTS `load_test_report_detail` (
`report_id` varchar(50) NOT NULL,
`content` longtext,
PRIMARY KEY (`report_id`)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_bin;
CREATE TABLE IF NOT EXISTS `organization` (
`id` varchar(50) NOT NULL COMMENT 'Organization ID',
`name` varchar(64) NOT NULL COMMENT 'Organization name',
......
......@@ -18,5 +18,6 @@
"no_nodes_message": "No node message",
"duplicate_node_ip": "Duplicate IPs",
"only_one_k8s": "Only one K8s can be added",
"organization_id_is_null": "Organization ID cannot be null"
"organization_id_is_null": "Organization ID cannot be null",
"max_thread_insufficient": "The number of concurrent users exceeds"
}
\ No newline at end of file
......@@ -18,5 +18,6 @@
"no_nodes_message": "没有节点信息",
"duplicate_node_ip": "节点 IP 重复",
"only_one_k8s": "只能添加一个 K8s",
"organization_id_is_null": "组织 ID 不能为空"
"organization_id_is_null": "组织 ID 不能为空",
"max_thread_insufficient": "并发用户数超额"
}
\ No newline at end of file
package io.metersphere;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import io.metersphere.base.domain.LoadTestReportDetail;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.mapper.LoadTestReportDetailMapper;
import io.metersphere.base.mapper.LoadTestReportMapper;
import io.metersphere.report.base.Metric;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.io.Reader;
import java.io.StringReader;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ReportContentTests {
@Resource
private LoadTestReportDetailMapper loadTestReportDetailMapper;
@Resource
private LoadTestReportMapper loadTestReportMapper;
@Test
public void test1() {
String reportId = "ba972086-7d74-4f58-99b0-9c014114fd99";
LoadTestReportDetail loadTestReportDetail = loadTestReportDetailMapper.selectByPrimaryKey(reportId);
LoadTestReportWithBLOBs loadTestReportWithBLOBs = loadTestReportMapper.selectByPrimaryKey(reportId);
HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
ms.setType(Metric.class);
try (Reader reader = new StringReader(loadTestReportDetail.getContent())) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
System.out.println(cb.parse().size());
} catch (Exception ex) {
ex.printStackTrace();
}
try (Reader reader = new StringReader(loadTestReportWithBLOBs.getContent())) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
System.out.println(cb.parse().size());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
......@@ -51,7 +51,7 @@
TokenKey,
WORKSPACE_ID
} from '../../../../common/js/constants';
import {hasRoles} from "../../../../common/js/utils";
import {hasRoles, saveLocalStorage} from "../../../../common/js/utils";
export default {
name: "MsUser",
......@@ -136,7 +136,7 @@
changeOrg(data) {
let orgId = data.id;
this.$post("/user/switch/source/org/" + orgId, {}, response => {
localStorage.setItem(TokenKey, JSON.stringify(response.data));
saveLocalStorage(response);
this.$router.push('/');
window.location.reload();
})
......@@ -147,7 +147,7 @@
return false;
}
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
localStorage.setItem(TokenKey, JSON.stringify(response.data));
saveLocalStorage(response);
localStorage.setItem("workspace_id", workspaceId);
this.$router.push('/');
window.location.reload();
......
......@@ -20,7 +20,7 @@
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
持续时间: {{minutes}} 分钟
持续时间: {{minutes}} 分钟 {{seconds}}
</span>
<span class="ms-report-time-desc">
开始时间: {{startTime}}
......@@ -79,6 +79,7 @@
startTime: '0',
endTime: '0',
minutes: '0',
seconds: '0'
}
},
methods: {
......@@ -101,7 +102,9 @@
if(data){
this.startTime = data.startTime;
this.endTime = data.endTime;
this.minutes = data.duration;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
})
}
......@@ -144,7 +147,9 @@
if(data){
this.startTime = data.startTime;
this.endTime = data.endTime;
this.minutes = data.duration;
let duration = data.duration;
this.minutes = Math.floor(duration / 60);
this.seconds = duration % 60;
}
})
window.location.reload();
......
......@@ -96,6 +96,10 @@
})
this.$get("/performance/report/content/load_chart/" + this.id, res => {
let data = res.data;
let userList = data.filter(m => m.groupName === "users").map(m => m.yAxis);
let hitsList = data.filter(m => m.groupName === "hits").map(m => m.yAxis);
let userMax = this._getChartMax(userList);
let hitsMax = this._getChartMax(hitsList);
let loadOption = {
title: {
text: 'Load',
......@@ -105,30 +109,57 @@
color: '#65A2FF'
},
},
tooltip: {
show: true,
trigger: 'axis'
},
legend: {},
xAxis: {},
yAxis: [{
name: 'User',
type: 'value',
min: 0,
max: userMax,
splitNumber: 5,
// interval: 10 / 5
interval: userMax / 5
},
{
name: 'Hits/s',
type: 'value',
splitNumber: 5,
min: 0,
// max: 5,
// interval: 5 / 5
max: hitsMax,
interval: hitsMax / 5
}
],
series: []
};
let setting = {
series: [
{
name: 'users',
color: '#0CA74A',
},
{
name: 'hits',
yAxisIndex: '1',
color: '#65A2FF',
},
{
name: 'errors',
yAxisIndex: '1',
color: '#E6113C',
}
]
}
this.loadOption = this.generateOption(loadOption, data);
this.loadOption = this.generateOption(loadOption, data, setting);
})
this.$get("/performance/report/content/res_chart/" + this.id, res => {
let data = res.data;
let userList = data.filter(m => m.groupName === "users").map(m => m.yAxis);
let responseTimeList = data.filter(m => m.groupName === "responseTime").map(m => m.yAxis);
let userMax = this._getChartMax(userList);
let resMax = this._getChartMax(responseTimeList);
let resOption = {
title: {
text: 'Response Time',
......@@ -138,28 +169,55 @@
color: '#99743C'
},
},
tooltip: {
show: true,
trigger: 'axis'
},
legend: {},
xAxis: {},
yAxis: [{
name: 'User',
type: 'value',
splitNumber: 5,
min: 0
min: 0,
max: userMax,
interval: userMax / 5
},
{
name: 'Response Time',
type: 'value',
splitNumber: 5,
min: 0
min: 0,
max: resMax,
interval: resMax / 5
}
],
series: []
}
this.resOption = this.generateOption(resOption, data);
let setting = {
series: [
{
name: 'users',
color: '#0CA74A',
},
{
name: "responseTime",
yAxisIndex: '1',
color: '#99743C',
}
]
}
this.resOption = this.generateOption(resOption, data, setting);
})
},
generateOption(option, data) {
generateOption(option, data, setting) {
let chartData = data;
let seriesArray = [];
for (let set in setting) {
if (set === "series") {
seriesArray = setting[set];
continue;
}
this.$set(option, set, setting[set]);
}
let legend = [], series = {}, xAxis = [], seriesData = [];
chartData.forEach(item => {
if (!xAxis.includes(item.xAxis)) {
......@@ -183,11 +241,24 @@
type: 'line',
data: data
};
let seriesArrayNames = seriesArray.map(m => m.name);
if (seriesArrayNames.includes(name)) {
for (let j = 0; j < seriesArray.length; j++) {
let seriesObj = seriesArray[j];
if (seriesObj['name'] === name) {
Object.assign(items, seriesObj);
}
}
}
seriesData.push(items);
}
this.$set(option, "series", seriesData);
return option;
},
_getChartMax(arr) {
const max = Math.max(...arr);
return Math.ceil(max / 4.5) * 5;
}
},
watch: {
status() {
......
......@@ -46,7 +46,10 @@
prop="status"
:label="$t('commons.status')">
<template v-slot:default="{row}">
<el-tag size="mini" type="primary" v-if="row.status === 'Starting'">
<el-tag size="mini" type="info" v-if="row.status === 'Saved'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="primary" v-else-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
......
......@@ -26,6 +26,7 @@
:current-project="currentProject"
@openTestCaseEditDialog="openTestCaseEditDialog"
@testCaseEdit="openTestCaseEditDialog"
@refresh="refresh"
ref="testCaseList">
</test-case-list>
</el-main>
......
<template>
<div>
<el-tooltip class="item" effect="dark" content="导出用例" placement="right">
<el-button type="info" icon="el-icon-download" size="mini" circle></el-button>
</el-tooltip>
</div>
</template>
<script>
export default {
name: "TestCaseImport"
}
</script>
<style scoped>
</style>
<template>
<div>
<el-tooltip class="item" effect="dark" content="导入用例" placement="right">
<el-button type="info" icon="el-icon-upload2" size="mini" circle
@click="dialogVisible = true"></el-button>
</el-tooltip>
<el-dialog width="30%" title="导入测试用例" :visible.sync="dialogVisible"
@close="init">
<el-row>
<el-link type="primary" class="download-template">下载模版</el-link>
</el-row>
<el-row>
<el-upload
class="upload-demo"
:action="'/test/case/import/' + projectId"
:on-preview="handlePreview"
multiple
:limit="1"
:on-exceed="handleExceed"
:beforeUpload="UploadValidate"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList">
<template v-slot:trigger>
<el-button size="mini" type="success" plain>点击上传</el-button>
</template>
<template v-slot:tip>
<div class="el-upload__tip">只能上传xls/xlsx文件,且不超过20M</div>
</template>
</el-upload>
</el-row>
<el-row>
<ul>
<li v-for="errFile in errList" :key="errFile.rowNum">
{{errFile.errMsg}}
</li>
</ul>
</el-row>
</el-dialog>
</div>
</template>
<script>
import ElUploadList from "element-ui/packages/upload/src/upload-list";
export default {
name: "TestCaseImport",
components: {ElUploadList},
data() {
return {
dialogVisible: false,
fileList: [],
errList: []
}
},
props: {
projectId: {
type: String
}
},
methods: {
handlePreview(file) {
console.log("init");
this.init();
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件`);
},
UploadValidate(file) {
var suffix =file.name.substring(file.name.lastIndexOf('.') + 1);
if (suffix != 'xls' && suffix != 'xlsx') {
this.$message({
message: '上传文件只能是 xls、xlsx格式!',
type: 'warning'
});
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$message({
message: '上传文件大小不能超过 20MB!',
type: 'warning'
});
return false;
}
return true;
},
handleSuccess(response) {
let res = response.data;
if (res.success) {
this.$message.success("导入成功!");
this.dialogVisible = false;
this.$emit("refresh");
} else {
this.errList = res.errList;
}
this.fileList = [];
},
handleError(err, file, fileList) {
this.$message.error(err.message);
},
init() {
this.fileList = [];
this.errList = [];
}
}
}
</script>
<style>
.el-dialog__body {
padding-top: 10px;
}
.download-template {
padding-top: 0px;
padding-bottom: 10px;
}
</style>
<style scoped>
</style>
......@@ -4,12 +4,21 @@
<el-card v-loading="result.loading">
<template v-slot:header>
<div>
<el-row type="flex" justify="space-between" align="middle">
<el-row type="flex" justify="start" align="middle">
<el-col :span="5">
<span class="title">{{$t('test_track.test_case')}}</span>
<ms-create-box :tips="$t('test_track.create')" :exec="testCaseCreate"/>
</el-col>
<el-col :span="1" :offset="12">
<test-case-import :projectId="currentProject == null? null : currentProject.id"
@refresh="refresh"/>
</el-col>
<el-col :span="1">
<test-case-export/>
</el-col>
<el-col :span="5">
<span class="search">
<el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')"
......@@ -105,10 +114,12 @@
<script>
import MsCreateBox from '../../../settings/CreateBox';
import TestCaseImport from '../components/TestCaseImport';
import TestCaseExport from '../components/TestCaseExport';
export default {
name: "TestCaseList",
components: {MsCreateBox},
components: {MsCreateBox, TestCaseImport, TestCaseExport},
data() {
return {
result: {},
......@@ -195,6 +206,9 @@
type: 'success'
});
});
},
refresh() {
this.$emit('refresh');
}
}
}
......
......@@ -28,3 +28,12 @@ export function checkoutCurrentWorkspace() {
// 查看当前用户是否是 lastWorkspaceId 的工作空间用户
return user.userRoles.filter(ur => hasRoles(ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER) && user.lastWorkspaceId === ur.sourceId).length > 0;
}
export function saveLocalStorage(response) {
// 登录信息保存 cookie
localStorage.setItem(TokenKey, JSON.stringify(response.data));
let rolesArray = response.data.roles;
let roles = rolesArray.map(r => r.id);
// 保存角色
localStorage.setItem("roles", roles);
}
......@@ -41,7 +41,7 @@
</template>
<script>
import {TokenKey} from '../common/js/constants';
import {saveLocalStorage} from '../common/js/utils';
export default {
......@@ -105,12 +105,7 @@
this.$refs[form].validate((valid) => {
if (valid) {
this.$post("signin", this.form, (response) => {
// 登录信息保存 cookie
localStorage.setItem(TokenKey, JSON.stringify(response.data));
let rolesArray = response.data.roles;
let roles = rolesArray.map(r => r.id);
// 保存角色
localStorage.setItem("roles", roles);
saveLocalStorage(response);
window.location.href = "/"
});
} else {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册