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

# Conflicts:
#	frontend/src/i18n/en-US.js
#	frontend/src/i18n/zh-CN.js
This commit is contained in:
wenyann 2020-05-20 17:27:03 +08:00
commit 2f1c847e87
150 changed files with 3139 additions and 1206 deletions

View File

@ -10,11 +10,13 @@ import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.DashboardTestDTO;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@ -34,6 +36,11 @@ public class APIReportController {
return apiReportService.recentTest(request);
}
@GetMapping("/list/{testId}")
public List<APIReportResult> listByTestId(@PathVariable String testId) {
return apiReportService.listByTestId(testId);
}
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<APIReportResult>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPIReportRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
@ -51,4 +58,10 @@ public class APIReportController {
apiReportService.delete(request);
}
@GetMapping("dashboard/tests")
public List<DashboardTestDTO> dashboardTests() {
return apiReportService.dashboardTests(SessionUtils.getCurrentWorkspaceId());
}
}

View File

@ -88,8 +88,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
scenarioResult.addSuccess();
testResult.addSuccess();
} else {
scenarioResult.addError();
testResult.addError();
scenarioResult.addError(result.getErrorCount());
testResult.addError(result.getErrorCount());
}
RequestResult requestResult = getRequestResult(result);
@ -123,6 +123,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.setRequestSize(result.getSentBytes());
requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful());
requestResult.setError(result.getErrorCount());
ResponseResult responseResult = requestResult.getResponseResult();
responseResult.setBody(result.getResponseDataAsString());

View File

@ -13,6 +13,8 @@ public class RequestResult {
private long requestSize;
private int error;
private boolean success;
private String headers;

View File

@ -28,8 +28,8 @@ public class ScenarioResult {
this.responseTime += time;
}
public void addError() {
this.error++;
public void addError(int count) {
this.error += count;
}
public void addSuccess() {

View File

@ -22,8 +22,8 @@ public class TestResult {
private final List<ScenarioResult> scenarios = new ArrayList<>();
public void addError() {
this.error++;
public void addError(int count) {
this.error += count;
}
public void addSuccess() {

View File

@ -10,9 +10,12 @@ import io.metersphere.base.domain.ApiTestWithBLOBs;
import io.metersphere.base.mapper.ApiTestReportMapper;
import io.metersphere.base.mapper.ext.ExtApiTestReportMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.dto.DashboardTestDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
@ -62,4 +65,11 @@ public class APIReportService {
report.setStatus(APITestStatus.Completed.name());
apiTestReportMapper.insert(report);
}
public List<DashboardTestDTO> dashboardTests(String workspaceId) {
Instant oneYearAgo = Instant.now().plus(-365, ChronoUnit.DAYS);
long startTimestamp = oneYearAgo.toEpochMilli();
return extApiTestReportMapper.selectDashboardTests(workspaceId, startTimestamp);
}
}

View File

@ -21,7 +21,5 @@ public class TestPlanTestCase implements Serializable {
private Long updateTime;
private String results;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestPlanTestCaseWithBLOBs extends TestPlanTestCase implements Serializable {
private String results;
private String issues;
private static final long serialVersionUID = 1L;
}

View File

@ -2,6 +2,7 @@ package io.metersphere.base.mapper;
import io.metersphere.base.domain.TestPlanTestCase;
import io.metersphere.base.domain.TestPlanTestCaseExample;
import io.metersphere.base.domain.TestPlanTestCaseWithBLOBs;
import java.util.List;
import org.apache.ibatis.annotations.Param;
@ -12,25 +13,25 @@ public interface TestPlanTestCaseMapper {
int deleteByPrimaryKey(String id);
int insert(TestPlanTestCase record);
int insert(TestPlanTestCaseWithBLOBs record);
int insertSelective(TestPlanTestCase record);
int insertSelective(TestPlanTestCaseWithBLOBs record);
List<TestPlanTestCase> selectByExampleWithBLOBs(TestPlanTestCaseExample example);
List<TestPlanTestCaseWithBLOBs> selectByExampleWithBLOBs(TestPlanTestCaseExample example);
List<TestPlanTestCase> selectByExample(TestPlanTestCaseExample example);
TestPlanTestCase selectByPrimaryKey(String id);
TestPlanTestCaseWithBLOBs selectByPrimaryKey(String id);
int updateByExampleSelective(@Param("record") TestPlanTestCase record, @Param("example") TestPlanTestCaseExample example);
int updateByExampleSelective(@Param("record") TestPlanTestCaseWithBLOBs record, @Param("example") TestPlanTestCaseExample example);
int updateByExampleWithBLOBs(@Param("record") TestPlanTestCase record, @Param("example") TestPlanTestCaseExample example);
int updateByExampleWithBLOBs(@Param("record") TestPlanTestCaseWithBLOBs record, @Param("example") TestPlanTestCaseExample example);
int updateByExample(@Param("record") TestPlanTestCase record, @Param("example") TestPlanTestCaseExample example);
int updateByPrimaryKeySelective(TestPlanTestCase record);
int updateByPrimaryKeySelective(TestPlanTestCaseWithBLOBs record);
int updateByPrimaryKeyWithBLOBs(TestPlanTestCase record);
int updateByPrimaryKeyWithBLOBs(TestPlanTestCaseWithBLOBs record);
int updateByPrimaryKey(TestPlanTestCase record);
}

View File

@ -11,8 +11,9 @@
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestPlanTestCase">
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestPlanTestCaseWithBLOBs">
<result column="results" jdbcType="LONGVARCHAR" property="results" />
<result column="issues" jdbcType="LONGVARCHAR" property="issues" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -76,7 +77,7 @@
id, plan_id, case_id, executor, status, remark, create_time, update_time
</sql>
<sql id="Blob_Column_List">
results
results, issues
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.TestPlanTestCaseExample" resultMap="ResultMapWithBLOBs">
select
@ -126,17 +127,17 @@
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.TestPlanTestCase">
<insert id="insert" parameterType="io.metersphere.base.domain.TestPlanTestCaseWithBLOBs">
insert into test_plan_test_case (id, plan_id, case_id,
executor, status, remark,
create_time, update_time, results
)
create_time, update_time, results,
issues)
values (#{id,jdbcType=VARCHAR}, #{planId,jdbcType=VARCHAR}, #{caseId,jdbcType=VARCHAR},
#{executor,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{remark,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{results,jdbcType=LONGVARCHAR}
)
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{results,jdbcType=LONGVARCHAR},
#{issues,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestPlanTestCase">
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestPlanTestCaseWithBLOBs">
insert into test_plan_test_case
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -166,6 +167,9 @@
<if test="results != null">
results,
</if>
<if test="issues != null">
issues,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -195,6 +199,9 @@
<if test="results != null">
#{results,jdbcType=LONGVARCHAR},
</if>
<if test="issues != null">
#{issues,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.TestPlanTestCaseExample" resultType="java.lang.Long">
@ -233,6 +240,9 @@
<if test="record.results != null">
results = #{record.results,jdbcType=LONGVARCHAR},
</if>
<if test="record.issues != null">
issues = #{record.issues,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -248,7 +258,8 @@
remark = #{record.remark,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
results = #{record.results,jdbcType=LONGVARCHAR}
results = #{record.results,jdbcType=LONGVARCHAR},
issues = #{record.issues,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -267,7 +278,7 @@
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.TestPlanTestCase">
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.TestPlanTestCaseWithBLOBs">
update test_plan_test_case
<set>
<if test="planId != null">
@ -294,10 +305,13 @@
<if test="results != null">
results = #{results,jdbcType=LONGVARCHAR},
</if>
<if test="issues != null">
issues = #{issues,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.TestPlanTestCase">
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.TestPlanTestCaseWithBLOBs">
update test_plan_test_case
set plan_id = #{planId,jdbcType=VARCHAR},
case_id = #{caseId,jdbcType=VARCHAR},
@ -306,7 +320,8 @@
remark = #{remark,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
results = #{results,jdbcType=LONGVARCHAR}
results = #{results,jdbcType=LONGVARCHAR},
issues = #{issues,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.TestPlanTestCase">

View File

@ -3,6 +3,7 @@ package io.metersphere.base.mapper.ext;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.QueryAPIReportRequest;
import io.metersphere.dto.ApiReportDTO;
import io.metersphere.dto.DashboardTestDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -15,4 +16,6 @@ public interface ExtApiTestReportMapper {
APIReportResult get(@Param("id") String id);
List<DashboardTestDTO> selectDashboardTests(@Param("workspaceId") String workspaceId, @Param("startTimestamp") long startTimestamp);
}

View File

@ -29,7 +29,7 @@
</select>
<select id="listByTestId" resultMap="BaseResultMap">
SELECT c,
SELECT t.name AS test_name,
r.name, r.description, r.id, r.test_id, r.create_time, r.update_time, r.status,
project.name AS project_name
FROM api_test_report r JOIN api_test t ON r.test_id = t.id
@ -50,4 +50,16 @@
ORDER BY r.update_time DESC
</select>
<select id="selectDashboardTests" resultType="io.metersphere.dto.DashboardTestDTO">
SELECT create_time AS date, count(api_test_report.id) AS count,
date_format(from_unixtime(create_time / 1000), '%Y-%m-%d') AS x
FROM api_test_report
WHERE test_id IN (SELECT api_test.id
FROM api_test
JOIN project ON api_test.project_id = project.id
WHERE workspace_id = #{workspaceId,jdbcType=VARCHAR})
AND create_time > #{startTimestamp}
GROUP BY x
</select>
</mapper>

View File

@ -25,9 +25,7 @@
AND load_test.id = #{request.id}
</if>
</where>
<if test="request.recent">
order by load_test.update_time desc
</if>
order by load_test.update_time desc
</select>
</mapper>

View File

@ -1,6 +1,7 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.ReportDTO;
import io.metersphere.performance.controller.request.ReportRequest;
import org.apache.ibatis.annotations.Param;
@ -16,4 +17,6 @@ public interface ExtLoadTestReportMapper {
int appendLine(@Param("testId") String id, @Param("line") String line);
LoadTestReport selectByPrimaryKey(String id);
List<DashboardTestDTO> selectDashboardTests(@Param("workspaceId") String workspaceId, @Param("startTimestamp") long startTimestamp);
}

View File

@ -48,10 +48,22 @@
</update>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from load_test_report
where id = #{id,jdbcType=VARCHAR}
SELECT
<include refid="Base_Column_List"/>
FROM load_test_report
WHERE id = #{id,jdbcType=VARCHAR}
</select>
<select id="selectDashboardTests" resultType="io.metersphere.dto.DashboardTestDTO">
SELECT create_time AS date, count(load_test_report.id) AS count,
date_format(from_unixtime(create_time / 1000), '%Y-%m-%d') AS x
FROM load_test_report
WHERE test_id IN (SELECT load_test.id
FROM load_test
JOIN project ON load_test.project_id = project.id
WHERE workspace_id = #{workspaceId,jdbcType=VARCHAR})
AND create_time > #{startTimestamp}
GROUP BY x
</select>
</mapper>

View File

@ -9,4 +9,6 @@ import java.util.List;
public interface ExtProjectMapper {
List<ProjectDTO> getProjectWithWorkspace(@Param("proRequest") ProjectRequest request);
List<String> getProjectIdByWorkspaceId(String workspaceId);
}

View File

@ -16,5 +16,10 @@
</if>
</where>
</select>
<select id="getProjectIdByWorkspaceId" resultType="java.lang.String">
select id
from project
where workspace_id = #{workspaceId}
</select>
</mapper>

View File

@ -1,5 +1,6 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.dto.TestPlanDTO;
import org.apache.ibatis.annotations.Param;
@ -8,4 +9,6 @@ import java.util.List;
public interface ExtTestPlanMapper {
List<TestPlanDTO> list(@Param("request") QueryTestPlanRequest params);
List<TestPlanDTOWithMetric> listRelate(@Param("request") QueryTestPlanRequest params);
}

View File

@ -30,4 +30,20 @@
</if>
</select>
<select id="listRelate" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric">
select test_plan.*, project.name as project_name
from test_plan
left join project on test_plan.project_id = project.id
where test_plan.workspace_id = #{request.workspaceId}
and (test_plan.principal = #{request.principal}
<if test="request.planIds != null and request.planIds.size() > 0">
or test_plan.id in
<foreach collection="request.planIds" item="planId" open="(" close=")" separator=",">
#{planId}
</foreach>
</if>
)
order by test_plan.update_time desc
</select>
</mapper>

View File

@ -15,4 +15,9 @@ public interface ExtTestPlanTestCaseMapper {
List<TestPlanCaseDTO> list(@Param("request") QueryTestPlanCaseRequest request);
List<String> findRelateTestPlanId(String userId);
List<TestPlanCaseDTO> getRecentTestedTestCase(@Param("request") QueryTestPlanCaseRequest request);
List<TestPlanCaseDTO> getPendingTestCases(@Param("request") QueryTestPlanCaseRequest request);
}

View File

@ -27,6 +27,9 @@
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.id != null">
and test_case.id = #{request.id}
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
and test_case.node_id in
<foreach collection="request.nodeIds" item="nodeId" separator="," open="(" close=")">
@ -58,5 +61,32 @@
</if>
</select>
<select id="findRelateTestPlanId" resultType="java.lang.String">
select distinct plan_id from test_plan_test_case where executor = #{userId}
</select>
<select id="getRecentTestedTestCase" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_plan_test_case.*, test_case.*
from test_plan_test_case
inner join test_case on test_plan_test_case.case_id = test_case.id
where status != 'Prepare'
and status != 'Underway'
and test_plan_test_case.Executor = #{request.executor}
and test_plan_test_case.plan_id in
<foreach collection="request.planIds" item="planId" separator="," open="(" close=")">
#{planId}
</foreach>
order by test_plan_test_case.update_time desc
</select>
<select id="getPendingTestCases" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_plan_test_case.*, test_case.*
from test_plan_test_case
inner join test_case on test_plan_test_case.case_id = test_case.id
where (status = 'Prepare' or status = 'Underway')
and test_plan_test_case.Executor = #{request.executor}
and test_plan_test_case.plan_id in
<foreach collection="request.planIds" item="planId" separator="," open="(" close=")">
#{planId}
</foreach>
order by test_plan_test_case.update_time asc
</select>
</mapper>

View File

@ -3,8 +3,12 @@ package io.metersphere.config;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.i18n.Translator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.Validator;
@Configuration
public class I18nConfig {
@ -20,4 +24,16 @@ public class I18nConfig {
public CommonBeanFactory commonBeanFactory() {
return new CommonBeanFactory();
}
/**
* JSR-303校验国际化
* @param messageSource
* @return
*/
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
}

View File

@ -13,19 +13,17 @@ import io.metersphere.controller.request.UserRequest;
import io.metersphere.controller.request.member.AddMemberRequest;
import io.metersphere.controller.request.member.EditPassWordRequest;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.controller.request.member.SetAdminRequest;
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
import io.metersphere.dto.UserDTO;
import io.metersphere.dto.UserRoleDTO;
import io.metersphere.service.OrganizationService;
import io.metersphere.service.UserService;
import io.metersphere.service.WorkspaceService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@ -54,6 +52,12 @@ public class UserController {
return PageUtils.setPageInfo(page, userService.getUserListWithRequest(request));
}
@GetMapping("/special/user/role/{userId}")
@RequiresRoles(RoleConstants.ADMIN)
public UserRoleDTO getUserRole(@PathVariable("userId") String userId) {
return userService.getUserRole(userId);
}
@GetMapping("/special/delete/{userId}")
@RequiresRoles(RoleConstants.ADMIN)
public void deleteUser(@PathVariable(value = "userId") String userId) {
@ -265,10 +269,4 @@ public class UserController {
return userService.updateUserPassword(request);
}
@PostMapping("/set/admin")
@RequiresRoles(RoleConstants.ADMIN)
public void setAdmin(@RequestBody SetAdminRequest request) {
userService.setAdmin(request);
}
}

View File

@ -1,10 +0,0 @@
package io.metersphere.controller.request.member;
import lombok.Data;
@Data
public class SetAdminRequest {
private String id;
private String adminId;
private String password;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DashboardTestDTO {
private Long date;
private Integer count;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.dto;
import io.metersphere.base.domain.Role;
import io.metersphere.base.domain.UserRole;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class UserRoleDTO {
private String userId;
private List<Role> roles;
private List<UserRole> userRoles;
private static final long serialVersionUID = 1L;
}

View File

@ -2,144 +2,64 @@ package io.metersphere.excel.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Data
@ColumnWidth(15)
public class TestCaseExcelData {
@NotBlank
@Length(max=50)
@ExcelProperty("用例名称")
@ExcelProperty("{test_case_name}")
private String name;
@NotBlank
@Length(max=1000)
@ExcelProperty("所属模块")
@ExcelProperty("{test_case_module}")
@ColumnWidth(30)
@Pattern(regexp = "^(?!.*//).*$", message = "格式不正确")
@Pattern(regexp = "^(?!.*//).*$", message = "{incorrect_format}")
private String nodePath;
@NotBlank
@ExcelProperty("用例类型")
@Pattern(regexp = "(^functional$)|(^performance$)|(^api$)", message = "必须为functional、performance、api")
@ExcelProperty("{test_case_type}")
@Pattern(regexp = "(^functional$)|(^performance$)|(^api$)", message = "{test_case_type_validate}")
private String type;
@NotBlank
@ExcelProperty("维护人")
@ExcelProperty("{test_case_maintainer}")
private String maintainer;
@NotBlank
@ExcelProperty("优先级")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "必须为P0、P1、P2、P3")
@ExcelProperty("{test_case_priority}")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "{test_case_priority_validate}")
private String priority;
@NotBlank
@ExcelProperty("测试方式")
@Pattern(regexp = "(^manual$)|(^auto$)", message = "必须为manual、auto")
@ExcelProperty("{test_case_method}")
@Pattern(regexp = "(^manual$)|(^auto$)", message = "{test_case_method_validate}")
private String method;
@ColumnWidth(50)
@ExcelProperty("前置条件")
@ExcelProperty("{test_case_prerequisite}")
@Length(min=0, max=1000)
private String prerequisite;
@ColumnWidth(50)
@ExcelProperty("备注")
@ExcelProperty("{test_case_remark}")
@Length(max=1000)
private String remark;
@ColumnWidth(50)
@ExcelProperty("步骤描述")
@ExcelProperty("{test_case_step_desc}")
@Length(max=1000)
private String stepDesc;
@ColumnWidth(50)
@ExcelProperty("预期结果")
@ExcelProperty("{test_case_step_result}")
@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;
}
}

View File

@ -6,19 +6,22 @@ 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.utils.ExcelValidateHelper;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.utils.EasyExcelI18nTranslator;
import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator;
import java.lang.reflect.Field;
import java.lang.reflect.*;
import java.util.*;
public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
protected List<ExcelErrData<T>> errList = new ArrayList<>();
protected List<T> list = new ArrayList<>();
protected EasyExcelI18nTranslator easyExcelI18nTranslator;
/**
* 每隔2000条存储数据库然后清理list 方便内存回收
*/
@ -26,13 +29,15 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
protected Class<T> clazz;
public EasyExcelListener(Class<T> clazz){
this.clazz = clazz;
public EasyExcelListener(){
Type type = getClass().getGenericSuperclass();
this.clazz = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
this.easyExcelI18nTranslator = new EasyExcelI18nTranslator(this.clazz);
this.easyExcelI18nTranslator.translateExcelProperty();
}
/**
* 这个条数据解析都会调用
* 每条数据解析都会调用
*
* @param t
* @param analysisContext
@ -47,12 +52,14 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
//自定义校验规则
errMsg = validate(t, errMsg);
} catch (NoSuchFieldException e) {
errMsg = "解析数据出错";
errMsg = Translator.get("parse_data_error");
LogUtil.error(e.getMessage(), e);
}
if (!StringUtils.isEmpty(errMsg)) {
ExcelErrData excelErrData = new ExcelErrData(t, rowIndex, "" + rowIndex + "行出错:" + errMsg);
ExcelErrData excelErrData = new ExcelErrData(t, rowIndex,
Translator.get("number") + rowIndex + Translator.get("row") + Translator.get("error")
+ "" + errMsg);
errList.add(excelErrData);
} else {
list.add(t);
@ -94,20 +101,20 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
*/
@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);
throw new ExcelAnalysisException(Translator.get("missing_header_information") + ":" + key);
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
super.invokeHeadMap(headMap, context);
}
/**
@ -132,9 +139,11 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
return result;
}
public List<ExcelErrData<T>> getErrList() {
return errList;
}
public void close () {
this.easyExcelI18nTranslator.resetExcelProperty();
}
}

View File

@ -2,10 +2,11 @@ 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.excel.domain.TestCaseExcelData;
import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
@ -26,9 +27,7 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
Set<String> userIds;
public TestCaseDataListener(TestCaseService testCaseService, String projectId,
Set<String> testCaseNames, Set<String> userIds, Class<TestCaseExcelData> clazz) {
super(clazz);
public TestCaseDataListener(TestCaseService testCaseService, String projectId, Set<String> testCaseNames, Set<String> userIds) {
this.testCaseService = testCaseService;
this.projectId = projectId;
this.testCaseNames = testCaseNames;
@ -39,24 +38,26 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
public String validate(TestCaseExcelData data, String errMsg) {
String nodePath = data.getNodePath();
StringBuilder stringBuilder = new StringBuilder(errMsg);
String[] nodes = nodePath.split("/");
if ( nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
stringBuilder.append("节点最多为" + TestCaseConstants.MAX_NODE_DEPTH + "层;");
}
for (int i = 0; i < nodes.length; i++) {
if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) {
stringBuilder.append("所属模块不能为空格");
break;
if (nodePath != null) {
String[] nodes = nodePath.split("/");
if ( nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
stringBuilder.append(Translator.get("test_case_node_level_tip") +
TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level"));
}
for (int i = 0; i < nodes.length; i++) {
if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) {
stringBuilder.append(Translator.get("module_not_null"));
break;
}
}
}
if (!userIds.contains(data.getMaintainer())) {
stringBuilder.append("该工作空间下无该用户" + data.getMaintainer() + ";");
stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; ");
}
if (testCaseNames.contains(data.getName())) {
stringBuilder.append("该项目下已存在该测试用例" + data.getName() + ";");
stringBuilder.append(Translator.get("test_case_already_exists") + "" + data.getName() + "; ");
}
return stringBuilder.toString();
}

View File

@ -0,0 +1,52 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.exception.ExcelException;
import io.metersphere.i18n.Translator;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class EasyExcelExporter {
EasyExcelI18nTranslator easyExcelI18nTranslator;
public EasyExcelExporter() {
easyExcelI18nTranslator = new EasyExcelI18nTranslator(TestCaseExcelData.class);
easyExcelI18nTranslator.translateExcelProperty();
}
public void export(HttpServletResponse response, Class clazz, List data, String fileName, String sheetName) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
try {
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
} catch (UnsupportedEncodingException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("Utf-8 encoding is not supported");
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("IO exception");
}
}
public void close() {
easyExcelI18nTranslator.resetExcelProperty();
}
}

View File

@ -0,0 +1,98 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.annotation.ExcelProperty;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.exception.ExcelException;
import io.metersphere.i18n.Translator;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
/**
* 表头国际化
* 先调用 saveOriginalExcelProperty 存储原表头注解值
* 再调用 translateExcelProperty 获取国际化的值
* 最后调用 resetExcelProperty 重置为原来值防止切换语言后无法国际化
*/
public class EasyExcelI18nTranslator {
private Map<String, List<String>> excelPropertyMap = new HashMap<>();
private Class clazz;
public EasyExcelI18nTranslator(Class clazz) {
this.clazz = clazz;
saveOriginalExcelProperty();
}
private void readExcelProperty(Class clazz, BiConsumer<String, Map<String, Object>> operate) {
Field field;
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length ; i++) {
try {
field = clazz.getDeclaredField(fields[i].getName());
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if(excelProperty != null){
InvocationHandler invocationHandler = Proxy.getInvocationHandler(excelProperty);
Field fieldValue = invocationHandler.getClass().getDeclaredField("memberValues");
fieldValue.setAccessible(true);
Map<String, Object> memberValues = null;
try {
memberValues = (Map<String, Object>) fieldValue.get(invocationHandler);
} catch (IllegalAccessException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException(e.getMessage());
}
operate.accept(field.getName(), memberValues);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
public void saveOriginalExcelProperty() {
readExcelProperty(clazz, (fieldName, memberValues) -> {
List<String> values = Arrays.asList((String [])memberValues.get("value"));
List<String> copyValues = new ArrayList<>();
values.forEach(value -> {
copyValues.add(value);
});
excelPropertyMap.put(fieldName, copyValues);
});
}
public void translateExcelProperty() {
readExcelProperty(TestCaseExcelData.class, (fieldName, memberValues) -> {
String [] values = (String[]) memberValues.get("value");
for (int j = 0; j < values.length; j++) {
if (Pattern.matches("^\\{.+\\}$", values[j])) {
values[j] = Translator.get(values[j].substring(1, values[j].length() - 1));
}
}
memberValues.put("value", values);
});
}
public void resetExcelProperty() {
readExcelProperty(clazz, (fieldName, memberValues) -> {
String [] values = (String[]) memberValues.get("value");
List<String> list = excelPropertyMap.get(fieldName);
for (int j = 0; j < values.length; j++) {
values[j] = list.get(j);
}
memberValues.put("value", values);
});
}
}

View File

@ -1,30 +0,0 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.EasyExcel;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.exception.ExcelException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
public class EasyExcelUtil {
public static void export(HttpServletResponse response, Class clazz, List data, String fileName, String sheetName) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
try {
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
} catch (UnsupportedEncodingException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("不支持UTF-8编码");
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("IO异常");
}
}
}

View File

@ -1,32 +1,41 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.annotation.ExcelProperty;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
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;
@Component
public class ExcelValidateHelper {
private ExcelValidateHelper(){}
private static ExcelValidateHelper excelValidateHelper;
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
@Resource
LocalValidatorFactoryBean localValidatorFactoryBean;
public static <T> String validateEntity(T obj) throws NoSuchFieldException {
public static <T> String validateEntity(T obj) throws NoSuchFieldException {
StringBuilder result = new StringBuilder();
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
Set<ConstraintViolation<T>> set = excelValidateHelper.localValidatorFactoryBean.getValidator().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(";");
result.append(annotation.value()[0]+cv.getMessage()).append("; ");
}
}
return result.toString();
}
@PostConstruct
public void initialize() {
excelValidateHelper = this;
excelValidateHelper.localValidatorFactoryBean = this.localValidatorFactoryBean;
}
}

View File

@ -7,6 +7,7 @@ import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.FileService;
@ -101,4 +102,9 @@ public class PerformanceTestController {
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileOperationRequest.getName() + "\"")
.body(bytes);
}
@GetMapping("dashboard/tests")
public List<DashboardTestDTO> dashboardTests() {
return performanceTestService.dashboardTests(SessionUtils.getCurrentWorkspaceId());
}
}

View File

@ -8,13 +8,14 @@ import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.request.testplan.*;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.engine.Engine;
import io.metersphere.performance.engine.EngineFactory;
import io.metersphere.service.FileService;
import io.metersphere.service.TestResourceService;
import io.metersphere.track.request.testplan.*;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@ -23,6 +24,8 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -247,7 +250,6 @@ public class PerformanceTestService {
public List<LoadTestDTO> recentTestPlans(QueryTestPlanRequest request) {
// 查询最近的测试计划
request.setRecent(true);
return extLoadTestMapper.list(request);
}
@ -277,4 +279,10 @@ public class PerformanceTestService {
return loadTestMapper.selectByExampleWithBLOBs(example);
}
public List<DashboardTestDTO> dashboardTests(String workspaceId) {
Instant oneYearAgo = Instant.now().plus(-365, ChronoUnit.DAYS);
long startTimestamp = oneYearAgo.toEpochMilli();
return extLoadTestReportMapper.selectDashboardTests(workspaceId, startTimestamp);
}
}

View File

@ -4,7 +4,6 @@ import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtUserMapper;
import io.metersphere.base.mapper.ext.ExtUserRoleMapper;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.CodingUtil;
@ -13,17 +12,16 @@ import io.metersphere.controller.request.UserRequest;
import io.metersphere.controller.request.member.AddMemberRequest;
import io.metersphere.controller.request.member.EditPassWordRequest;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.controller.request.member.SetAdminRequest;
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
import io.metersphere.dto.UserDTO;
import io.metersphere.dto.UserRoleDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@ -100,16 +98,24 @@ public class UserService {
}
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
UserRoleDTO userRole = getUserRole(userId);
userDTO.setUserRoles(userRole.getUserRoles());
userDTO.setRoles(userRole.getRoles());
return userDTO;
}
public UserRoleDTO getUserRole(String userId) {
UserRoleDTO userRoleDTO = new UserRoleDTO();
//
UserRoleExample userRoleExample = new UserRoleExample();
userRoleExample.createCriteria().andUserIdEqualTo(userId);
List<UserRole> userRoleList = userRoleMapper.selectByExample(userRoleExample);
if (CollectionUtils.isEmpty(userRoleList)) {
return userDTO;
return userRoleDTO;
}
// 设置 user_role
userDTO.setUserRoles(userRoleList);
userRoleDTO.setUserRoles(userRoleList);
List<String> roleIds = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
@ -117,9 +123,9 @@ public class UserService {
roleExample.createCriteria().andIdIn(roleIds);
List<Role> roleList = roleMapper.selectByExample(roleExample);
userDTO.setRoles(roleList);
userRoleDTO.setRoles(roleList);
return userDTO;
return userRoleDTO;
}
public List<User> getUserList() {
@ -328,23 +334,6 @@ public class UserService {
return extUserMapper.updatePassword(user);
}
public void setAdmin(SetAdminRequest request) {
String adminId = request.getAdminId();
String password = request.getPassword();
if (!checkUserPassword(adminId, password)) {
MSException.throwException("verification failed");
}
UserRole userRole = new UserRole();
userRole.setId(UUID.randomUUID().toString());
userRole.setUserId(request.getId());
// TODO 修改admin sourceId
userRole.setSourceId("adminSourceId");
userRole.setRoleId(RoleConstants.ADMIN);
userRole.setCreateTime(System.currentTimeMillis());
userRole.setUpdateTime(System.currentTimeMillis());
userRoleMapper.insertSelective(userRole);
}
public String getDefaultLanguage() {
final String key = "default.language";
return extUserMapper.getDefaultLanguage(key);

View File

@ -77,7 +77,7 @@ public class TestCaseController {
}
@PostMapping("/import/{projectId}")
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId){
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId) throws NoSuchFieldException {
return testCaseService.testCaseImport(file, projectId);
}
@ -91,4 +91,9 @@ public class TestCaseController {
testCaseService.editTestCaseBath(request);
}
@PostMapping("/batch/delete")
public void deleteTestCaseBath(@RequestBody TestCaseBatchRequest request){
testCaseService.deleteTestCaseBath(request);
}
}

View File

@ -41,8 +41,4 @@ public class TestCaseReportController {
return testCaseReportService.deleteTestCaseReport(id);
}
@GetMapping("/get/metric/{planId}")
public TestCaseReportMetricDTO getMetric(@PathVariable String planId){
return testCaseReportService.getMetric(planId);
}
}

View File

@ -6,7 +6,9 @@ import io.metersphere.base.domain.TestPlan;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.service.TestPlanService;
@ -36,6 +38,10 @@ public class TestPlanController {
return testPlanService.listTestAllPlan(currentWorkspaceId);
}
@PostMapping("/list/all/relate")
public List<TestPlanDTOWithMetric> listRelateAll() {
return testPlanService.listRelateAllPlan();
}
@GetMapping("recent/{count}")
public List<TestPlan> recentTestPlans(@PathVariable int count) {
@ -69,5 +75,8 @@ public class TestPlanController {
testPlanService.testPlanRelevance(request);
}
@GetMapping("/get/metric/{planId}")
public TestCaseReportMetricDTO getMetric(@PathVariable String planId){
return testPlanService.getMetric(planId);
}
}

View File

@ -3,6 +3,7 @@ package io.metersphere.track.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.TestPlanTestCase;
import io.metersphere.base.domain.TestPlanTestCaseWithBLOBs;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.track.request.testcase.TestPlanCaseBatchRequest;
@ -24,16 +25,31 @@ public class TestPlanTestCaseController {
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<TestPlanCaseDTO>> getTestPlanCases(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanCaseRequest request){
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testPlanTestCaseService.getTestPlanCases(request));
return PageUtils.setPageInfo(page, testPlanTestCaseService.list(request));
}
@GetMapping("/get/{caseId}")
public TestPlanCaseDTO getTestPlanCases(@PathVariable String caseId){
return testPlanTestCaseService.get(caseId);
}
@PostMapping("recent/{count}")
public List<TestPlanCaseDTO> getRecentTestCases(@PathVariable int count, @RequestBody QueryTestPlanCaseRequest request){
return testPlanTestCaseService.getRecentTestCases(request, count);
}
@PostMapping("pending/{count}")
public List<TestPlanCaseDTO> getPrepareTestCases(@PathVariable int count, @RequestBody QueryTestPlanCaseRequest request){
return testPlanTestCaseService.getPendingTestCases(request, count);
}
@PostMapping("/list/all")
public List<TestPlanCaseDTO> getTestPlanCases(@RequestBody QueryTestPlanCaseRequest request){
return testPlanTestCaseService.getTestPlanCases(request);
return testPlanTestCaseService.list(request);
}
@PostMapping("/edit")
public void editTestCase(@RequestBody TestPlanTestCase testPlanTestCase){
public void editTestCase(@RequestBody TestPlanTestCaseWithBLOBs testPlanTestCase){
testPlanTestCaseService.editTestCase(testPlanTestCase);
}
@ -42,6 +58,11 @@ public class TestPlanTestCaseController {
testPlanTestCaseService.editTestCaseBath(request);
}
@PostMapping("/batch/delete")
public void deleteTestCaseBath(@RequestBody TestPlanCaseBatchRequest request){
testPlanTestCaseService.deleteTestCaseBath(request);
}
@PostMapping("/delete/{id}")
public int deleteTestCase(@PathVariable String id){
return testPlanTestCaseService.deleteTestCase(id);

View File

@ -11,5 +11,5 @@ public class TestCaseReportModuleResultDTO {
private Integer caseCount;
private Integer passCount;
private Double passRate;
private Integer flawCount;
private Integer issuesCount;
}

View File

@ -7,9 +7,12 @@ import lombok.Setter;
@Getter
@Setter
public class TestPlanCaseDTO extends TestCaseWithBLOBs {
private String executor;
private String executorName;
private String status;
private String results;
private String planId;
private String planName;
private String caseId;
private String issues;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.track.dto;
import io.metersphere.base.domain.TestPlan;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestPlanDTOWithMetric extends TestPlanDTO {
private Double passRate;
private Double testRate;
private Integer passed;
private Integer tested;
private Integer total;
}

View File

@ -4,9 +4,12 @@ import io.metersphere.base.domain.TestPlan;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class QueryTestPlanRequest extends TestPlan {
private boolean recent = false;
private List<String> planIds;
}

View File

@ -7,5 +7,4 @@ import lombok.Setter;
@Setter
public class QueryTestPlanRequest extends TestPlanRequest {
private String workspaceId;
private boolean recent = false; // 表示查询最近的测试
}

View File

@ -18,6 +18,10 @@ public class QueryTestPlanCaseRequest extends TestPlanTestCase {
private Map<String, List<String>> filters;
private List<String> planIds;
private List<String> projectIds;
private String workspaceId;
private String name;

View File

@ -8,7 +8,9 @@ import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.exception.ExcelException;
@ -46,7 +48,8 @@ public class TestCaseNodeService {
public String addNode(TestCaseNode node) {
if(node.getLevel() > TestCaseConstants.MAX_NODE_DEPTH){
throw new RuntimeException("模块树最大深度为" + TestCaseConstants.MAX_NODE_DEPTH + "层!");
throw new RuntimeException(Translator.get("test_case_node_level_tip")
+ TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level"));
}
node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis());
@ -122,11 +125,11 @@ public class TestCaseNodeService {
testCases.forEach(testCase -> {
StringBuilder path = new StringBuilder(testCase.getNodePath());
List<String> list = Arrays.asList(path.toString().split("/"));
list.set(request.getLevel(), request.getName());
List<String> pathLists = Arrays.asList(path.toString().split("/"));
pathLists.set(request.getLevel(), request.getName());
path.delete( 0, path.length());
for (int i = 1; i < list.size(); i++) {
path = path.append("/").append(list.get(i));
for (int i = 1; i < pathLists.size(); i++) {
path = path.append("/").append(pathLists.get(i));
}
testCase.setNodePath(path.toString());
});
@ -243,7 +246,7 @@ public class TestCaseNodeService {
nodePaths.forEach(path -> {
if (path == null) {
throw new ExcelException("所属模块不能为空!");
throw new ExcelException(Translator.get("test_case_module_not_null"));
}
List<String> nodeNameList = new ArrayList<>(Arrays.asList(path.split("/")));
Iterator<String> pathIterator = nodeNameList.iterator();
@ -252,7 +255,7 @@ public class TestCaseNodeService {
String rootNodeName = null;
if (nodeNameList.size() <= 1) {
throw new ExcelException("创建模块失败:" + path);
throw new ExcelException(Translator.get("test_case_create_module_fail") + ":" + path);
} else {
pathIterator.next();
pathIterator.remove();
@ -371,7 +374,7 @@ public class TestCaseNodeService {
public void dragNode(DragNodeRequest request) {
editNode(request);
// editNode(request);
List<String> nodeIds = request.getNodeIds();
@ -381,7 +384,7 @@ public class TestCaseNodeService {
List<TestCaseNode> updateNodes = new ArrayList<>();
buildUpdateTestCase(nodeTree, testCases, updateNodes, "/", 1);
buildUpdateTestCase(nodeTree, testCases, updateNodes, "/", null, 1);
updateNodes = updateNodes.stream()
.filter(item -> nodeIds.contains(item.getId()))
@ -417,13 +420,18 @@ public class TestCaseNodeService {
}
private void buildUpdateTestCase(TestCaseNodeDTO rootNode, List<TestCaseDTO> testCases,
List<TestCaseNode> updateNodes, String rootPath, int level) {
List<TestCaseNode> updateNodes, String rootPath, String pId, int level) {
rootPath = rootPath + rootNode.getName();
if (level > 5) {
MSException.throwException(Translator.get("node_deep_limit"));
}
TestCaseNode testCaseNode = new TestCaseNode();
testCaseNode.setId(rootNode.getId());
testCaseNode.setLevel(level);
testCaseNode.setParentId(pId);
updateNodes.add(testCaseNode);
for (TestCaseDTO item : testCases) {
@ -435,7 +443,7 @@ public class TestCaseNodeService {
List<TestCaseNodeDTO> children = rootNode.getChildren();
if (children != null && children.size() > 0){
for (int i = 0; i < children.size(); i++) {
buildUpdateTestCase(children.get(i), testCases, updateNodes, rootPath + '/', level + 1);
buildUpdateTestCase(children.get(i), testCases, updateNodes, rootPath + '/', rootNode.getId(), level + 1);
}
}
}

View File

@ -35,21 +35,6 @@ public class TestCaseReportService {
@Resource
TestCaseReportTemplateMapper testCaseReportTemplateMapper;
@Resource
TestCaseNodeService testCaseNodeService;
@Resource
TestCaseNodeMapper testCaseNodeMapper;
@Resource
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
@Resource
TestPlanTestCaseMapper testPlanTestCaseMapper;
@Resource
ExtTestCaseMapper extTestCaseMapper;
public List<TestCaseReport> listTestCaseReport(TestCaseReport request) {
TestCaseReportExample example = new TestCaseReportExample();
if ( StringUtils.isNotBlank(request.getName()) ) {
@ -87,111 +72,4 @@ public class TestCaseReportService {
testPlanMapper.updateByPrimaryKeySelective(testPlan);
return report.getId();
}
public TestCaseReportMetricDTO getMetric(String planId) {
QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest();
queryTestPlanRequest.setId(planId);
TestPlanDTO testPlan = extTestPlanMapper.list(queryTestPlanRequest).get(0);
Set<String> executors = new HashSet<>();
Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap = new HashMap<>();
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> nodeTrees = testCaseNodeService.getNodeTrees(nodes);
Map<String, Set<String>> childIdMap = new HashMap<>();
nodeTrees.forEach(item -> {
Set<String> childIds = new HashSet<>();
getChildIds(item, childIds);
childIdMap.put(item.getId(), childIds);
});
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setPlanId(planId);
List<TestPlanCaseDTO> testPlanTestCases = extTestPlanTestCaseMapper.list(request);
Map<String, TestCaseReportModuleResultDTO> moduleResultMap = new HashMap<>();
for (TestPlanCaseDTO testCase: testPlanTestCases) {
executors.add(testCase.getExecutor());
getStatusResultMap(reportStatusResultMap, testCase);
getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees);
}
nodeTrees.forEach(rootNode -> {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId());
if (moduleResult != null) {
moduleResult.setModuleName(rootNode.getName());
}
});
for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) {
moduleResult.setPassRate(new BigDecimal(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount())
.setScale(2, BigDecimal.ROUND_HALF_UP)
.doubleValue() * 100);
if (moduleResult.getCaseCount() <= 0) {
moduleResultMap.remove(moduleResult.getModuleId());
}
}
TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO();
testCaseReportMetricDTO.setProjectName(testPlan.getProjectName());
testCaseReportMetricDTO.setPrincipal(testPlan.getPrincipal());
testCaseReportMetricDTO.setExecutors(new ArrayList<>(executors));
testCaseReportMetricDTO.setExecuteResult(new ArrayList<>(reportStatusResultMap.values()));
testCaseReportMetricDTO.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values()));
return testCaseReportMetricDTO;
}
private void getStatusResultMap(Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap, TestPlanCaseDTO testCase) {
TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus());
if (statusResult == null) {
statusResult = new TestCaseReportStatusResultDTO();
statusResult.setStatus(testCase.getStatus());
statusResult.setCount(0);
}
statusResult.setCount(statusResult.getCount() + 1);
reportStatusResultMap.put(testCase.getStatus(), statusResult);
}
private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) {
childIdMap.forEach((rootNodeId, childIds) -> {
if (childIds.contains(testCase.getNodeId())) {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId);
if (moduleResult == null) {
moduleResult = new TestCaseReportModuleResultDTO();
moduleResult.setCaseCount(0);
moduleResult.setPassCount(0);
moduleResult.setModuleId(rootNodeId);
}
moduleResult.setCaseCount(moduleResult.getCaseCount() + 1);
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
moduleResult.setPassCount(moduleResult.getPassCount() + 1);
}
moduleResultMap.put(rootNodeId, moduleResult);
return;
}
});
}
private void getChildIds(TestCaseNodeDTO rootNode, Set<String> childIds) {
childIds.add(rootNode.getId());
List<TestCaseNodeDTO> children = rootNode.getChildren();
if(children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while(iterator.hasNext()){
getChildIds(iterator.next(), childIds);
}
}
}
}

View File

@ -9,14 +9,13 @@ import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
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.excel.utils.EasyExcelUtil;
import io.metersphere.excel.utils.EasyExcelExporter;
import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
@ -31,7 +30,6 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@ -170,43 +168,40 @@ public class TestCaseService {
public ExcelResponse testCaseImport(MultipartFile file, String projectId) {
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> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
EasyExcelListener easyExcelListener = null;
List<ExcelErrData<TestCaseExcelData>> errList = null;
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> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
EasyExcelListener easyExcelListener = new TestCaseDataListener(this, projectId,
testCaseNames, userIds, TestCaseExcelData.class);
easyExcelListener = new TestCaseDataListener(this, projectId, testCaseNames, userIds);
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();
errList = easyExcelListener.getErrList();
} catch (Exception e) {
MSException.throwException(e.getMessage());
} finally {
easyExcelListener.close();
}
return null;
//如果包含错误信息就导出错误信息
if (!errList.isEmpty()) {
excelResponse.setSuccess(false);
excelResponse.setErrList(errList);
} else {
excelResponse.setSuccess(true);
}
return excelResponse;
}
public void saveImportData(List<TestCaseWithBLOBs> testCases, String projectId) {
@ -224,7 +219,16 @@ public class TestCaseService {
}
public void testCaseTemplateExport(HttpServletResponse response) {
EasyExcelUtil.export(response, TestCaseExcelData.class, generateExportTemplate(), "测试用例模版", "模版");
EasyExcelExporter easyExcelExporter = null;
try {
easyExcelExporter = new EasyExcelExporter();
easyExcelExporter.export(response, TestCaseExcelData.class, generateExportTemplate(),
Translator.get("test_case_import_template_name"), Translator.get("test_case_import_template_sheet"));
} catch (Exception e) {
MSException.throwException(e);
} finally {
easyExcelExporter.close();
}
}
private List<TestCaseExcelData> generateExportTemplate() {
@ -235,28 +239,29 @@ public class TestCaseService {
SessionUser user = SessionUtils.getUser();
for (int i = 1; i <= 5; i++) {
TestCaseExcelData data = new TestCaseExcelData();
data.setName("测试用例" + i);
path.append("/" + "模块" + i);
data.setName(Translator.get("test_case") + i);
path.append("/" + Translator.get("module") + i);
data.setNodePath(path.toString());
data.setPriority("P" + i%4);
data.setType(types.get(i%3));
data.setMethod(methods.get(i%2));
data.setPrerequisite("前置条件选填");
data.setStepDesc("1. 每个步骤以换行分隔\n2. 步骤前可标序号\n3. 测试步骤和结果选填");
data.setStepResult("1. 每条结果以换行分隔\n2. 结果前可标序号\n3. 测试步骤和结果选填");
data.setPrerequisite(Translator.get("preconditions_optional"));
data.setStepDesc("1. " + Translator.get("step_tip_separate") +
"\n2. " + Translator.get("step_tip_order") + "\n3. " + Translator.get("step_tip_optional"));
data.setStepResult("1. " + Translator.get("step_tip_order") + "\n2. " + Translator.get("result_tip_order") + "\n3. " + Translator.get("result_tip_optional"));
data.setMaintainer(user.getId());
data.setRemark("备注选填");
data.setRemark(Translator.get("remark_optional"));
list.add(data);
}
list.add(new TestCaseExcelData());
TestCaseExcelData explain = new TestCaseExcelData();
explain.setName("同一项目下测试用例名称不能重复!");
explain.setNodePath("模块名称请按照'/模块1/模块2'的格式书写; 错误格式示例:('/', '/tes//test'); 若无该模块,则自动创建模块");
explain.setType("用例类型必须为functional、performance、api");
explain.setMethod("测试方式必须为manual、auto");
explain.setPriority("优先级必须为P0、P1、P2、P3");
explain.setMaintainer("维护人必须为该工作空间相关人员");
explain.setName(Translator.get("do_not_modify_header_order"));
explain.setNodePath(Translator.get("module_created_automatically"));
explain.setType(Translator.get("options") + "functional、performance、api");
explain.setMethod(Translator.get("options") + "manual、auto");
explain.setPriority(Translator.get("options") + "P0、P1、P2、P3");
explain.setMaintainer(Translator.get("please_input_workspace_member"));
list.add(explain);
return list;
@ -273,4 +278,10 @@ public class TestCaseService {
testCaseExample);
}
public void deleteTestCaseBath(TestCaseBatchRequest request) {
TestCaseExample example = new TestCaseExample();
example.createCriteria().andIdIn(request.getIds());
testCaseMapper.deleteByExample(example);
}
}

View File

@ -1,17 +1,27 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.ProjectRequest;
import io.metersphere.dto.ProjectDTO;
import io.metersphere.track.dto.*;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
@ -20,6 +30,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@ -33,6 +44,9 @@ public class TestPlanService {
@Resource
ExtTestPlanMapper extTestPlanMapper;
@Resource
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
@Resource
TestCaseMapper testCaseMapper;
@ -42,6 +56,15 @@ public class TestPlanService {
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
TestCaseNodeMapper testCaseNodeMapper;
@Resource
TestCaseNodeService testCaseNodeService;
@Resource
ExtProjectMapper extProjectMapper;
public void addTestPlan(TestPlan testPlan) {
testPlan.setId(UUID.randomUUID().toString());
testPlan.setStatus(TestPlanStatus.Prepare.name());
@ -95,7 +118,7 @@ public class TestPlanService {
if (!testCaseIds.isEmpty()) {
testCaseIds.forEach(caseId -> {
TestCaseWithBLOBs testCase = testCaseMap.get(caseId);
TestPlanTestCase testPlanTestCase = new TestPlanTestCase();
TestPlanTestCaseWithBLOBs testPlanTestCase = new TestPlanTestCaseWithBLOBs();
testPlanTestCase.setId(UUID.randomUUID().toString());
testPlanTestCase.setExecutor(testCase.getMaintainer());
testPlanTestCase.setCaseId(caseId);
@ -132,4 +155,183 @@ public class TestPlanService {
testPlanExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId);
return testPlanMapper.selectByExample(testPlanExample);
}
public List<TestPlanDTOWithMetric> listRelateAllPlan() {
SessionUser user = SessionUtils.getUser();
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setPrincipal(user.getId());
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
request.setPlanIds(extTestPlanTestCaseMapper.findRelateTestPlanId(user.getId()));
List<String> projectIds = extProjectMapper.getProjectIdByWorkspaceId(SessionUtils.getCurrentOrganizationId());
List<TestPlanDTOWithMetric> testPlans = extTestPlanMapper.listRelate(request);
Map<String, List<TestPlanCaseDTO>> testCaseMap = new HashMap<>();
listTestCaseByProjectIds(projectIds).forEach(testCase -> {
List<TestPlanCaseDTO> list = testCaseMap.get(testCase.getPlanId());
if (list == null) {
list = new ArrayList<>();
list.add(testCase);
testCaseMap.put(testCase.getPlanId(), list);
} else {
list.add(testCase);
}
});
testPlans.forEach(testPlan -> {
List<TestPlanCaseDTO> testCases = testCaseMap.get(testPlan.getId());
testPlan.setTested(0);
testPlan.setPassed(0);
testPlan.setTotal(0);
if (testCases != null) {
testPlan.setTotal(testCases.size());
testCases.forEach(testCase -> {
if (!StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Prepare.name())
&& !StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Underway.name())) {
testPlan.setTested(testPlan.getTested() + 1);
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
testPlan.setPassed(testPlan.getPassed() + 1);
}
}
});
}
testPlan.setPassRate(getPercentWithTwoDecimals(testPlan.getTested() == 0 ? 0 : testPlan.getPassed()*1.0/testPlan.getTested()));
testPlan.setTestRate(getPercentWithTwoDecimals(testPlan.getTotal() == 0 ? 0 : testPlan.getTested()*1.0/testPlan.getTotal()));
});
return testPlans;
}
private double getPercentWithTwoDecimals(double value) {
return new BigDecimal(value)
.setScale(4, BigDecimal.ROUND_HALF_UP)
.doubleValue() * 100;
}
public List<TestPlanCaseDTO> listTestCaseByPlanId(String planId) {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setPlanId(planId);
return extTestPlanTestCaseMapper.list(request);
}
public List<TestPlanCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setProjectIds(projectIds);
return extTestPlanTestCaseMapper.list(request);
}
public TestCaseReportMetricDTO getMetric(String planId) {
QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest();
queryTestPlanRequest.setId(planId);
TestPlanDTO testPlan = extTestPlanMapper.list(queryTestPlanRequest).get(0);
Set<String> executors = new HashSet<>();
Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap = new HashMap<>();
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> nodeTrees = testCaseNodeService.getNodeTrees(nodes);
Map<String, Set<String>> childIdMap = new HashMap<>();
nodeTrees.forEach(item -> {
Set<String> childIds = new HashSet<>();
getChildIds(item, childIds);
childIdMap.put(item.getId(), childIds);
});
List<TestPlanCaseDTO> testPlanTestCases = listTestCaseByPlanId(planId);
Map<String, TestCaseReportModuleResultDTO> moduleResultMap = new HashMap<>();
for (TestPlanCaseDTO testCase: testPlanTestCases) {
executors.add(testCase.getExecutor());
getStatusResultMap(reportStatusResultMap, testCase);
getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees);
}
nodeTrees.forEach(rootNode -> {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId());
if (moduleResult != null) {
moduleResult.setModuleName(rootNode.getName());
}
});
for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) {
moduleResult.setPassRate(getPercentWithTwoDecimals(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount()));
if (moduleResult.getCaseCount() <= 0) {
moduleResultMap.remove(moduleResult.getModuleId());
}
}
TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO();
testCaseReportMetricDTO.setProjectName(testPlan.getProjectName());
testCaseReportMetricDTO.setPrincipal(testPlan.getPrincipal());
testCaseReportMetricDTO.setExecutors(new ArrayList<>(executors));
testCaseReportMetricDTO.setExecuteResult(new ArrayList<>(reportStatusResultMap.values()));
testCaseReportMetricDTO.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values()));
return testCaseReportMetricDTO;
}
private void getStatusResultMap(Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap, TestPlanCaseDTO testCase) {
TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus());
if (statusResult == null) {
statusResult = new TestCaseReportStatusResultDTO();
statusResult.setStatus(testCase.getStatus());
statusResult.setCount(0);
}
statusResult.setCount(statusResult.getCount() + 1);
reportStatusResultMap.put(testCase.getStatus(), statusResult);
}
private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) {
childIdMap.forEach((rootNodeId, childIds) -> {
if (childIds.contains(testCase.getNodeId())) {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId);
if (moduleResult == null) {
moduleResult = new TestCaseReportModuleResultDTO();
moduleResult.setCaseCount(0);
moduleResult.setPassCount(0);
moduleResult.setIssuesCount(0);
moduleResult.setModuleId(rootNodeId);
}
moduleResult.setCaseCount(moduleResult.getCaseCount() + 1);
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
moduleResult.setPassCount(moduleResult.getPassCount() + 1);
}
if (StringUtils.isNotBlank(testCase.getIssues())) {
if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) {
moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1);
};
}
moduleResultMap.put(rootNodeId, moduleResult);
return;
}
});
}
private void getChildIds(TestCaseNodeDTO rootNode, Set<String> childIds) {
childIds.add(rootNode.getId());
List<TestCaseNodeDTO> children = rootNode.getChildren();
if(children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while(iterator.hasNext()){
getChildIds(iterator.next(), childIds);
}
}
}
public List<TestPlan> getTestPlanByTestIds(List<String> planIds) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(planIds);
return testPlanMapper.selectByExample(example);
}
}

View File

@ -1,16 +1,17 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.TestPlanTestCase;
import io.metersphere.base.domain.TestPlanTestCaseExample;
import io.metersphere.base.domain.User;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.service.UserService;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.request.testcase.TestPlanCaseBatchRequest;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
import org.apache.commons.lang3.StringUtils;
@ -18,6 +19,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -32,10 +34,13 @@ public class TestPlanTestCaseService {
@Resource
UserService userService;
@Resource
TestPlanService testPlanService;
@Resource
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
public List<TestPlanCaseDTO> getTestPlanCases(QueryTestPlanCaseRequest request) {
public List<TestPlanCaseDTO> list(QueryTestPlanCaseRequest request) {
List<TestPlanCaseDTO> list = extTestPlanTestCaseMapper.list(request);
QueryMemberRequest queryMemberRequest = new QueryMemberRequest();
queryMemberRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
@ -47,7 +52,7 @@ public class TestPlanTestCaseService {
return list;
}
public void editTestCase(TestPlanTestCase testPlanTestCase) {
public void editTestCase(TestPlanTestCaseWithBLOBs testPlanTestCase) {
if (StringUtils.equals(TestPlanTestCaseStatus.Prepare.name(), testPlanTestCase.getStatus())) {
testPlanTestCase.setStatus(TestPlanTestCaseStatus.Underway.name());
}
@ -63,10 +68,55 @@ public class TestPlanTestCaseService {
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
testPlanTestCaseExample.createCriteria().andIdIn(request.getIds());
TestPlanTestCase testPlanTestCase = new TestPlanTestCase();
TestPlanTestCaseWithBLOBs testPlanTestCase = new TestPlanTestCaseWithBLOBs();
BeanUtils.copyBean(testPlanTestCase, request);
testPlanTestCaseMapper.updateByExampleSelective(
testPlanTestCase,
testPlanTestCaseExample);
}
public List<TestPlanCaseDTO> getRecentTestCases(QueryTestPlanCaseRequest request, int count) {
buildQueryRequest(request, count);
if (request.getPlanIds().isEmpty()) {
return new ArrayList<>();
}
List<TestPlanCaseDTO> recentTestedTestCase = extTestPlanTestCaseMapper.getRecentTestedTestCase(request);
List<String> planIds = recentTestedTestCase.stream().map(TestPlanCaseDTO::getPlanId).collect(Collectors.toList());
Map<String, String> testPlanMap = testPlanService.getTestPlanByTestIds(planIds).stream()
.collect(Collectors.toMap(TestPlan::getId, TestPlan::getName));
recentTestedTestCase.forEach(testCase -> {
testCase.setPlanName(testPlanMap.get(testCase.getPlanId()));
});
return recentTestedTestCase;
}
public List<TestPlanCaseDTO> getPendingTestCases(QueryTestPlanCaseRequest request, int count) {
buildQueryRequest(request, count);
if (request.getPlanIds().isEmpty()) {
return new ArrayList<>();
}
return extTestPlanTestCaseMapper.getPendingTestCases(request);
}
public void buildQueryRequest(QueryTestPlanCaseRequest request, int count) {
SessionUser user = SessionUtils.getUser();
List<String> relateTestPlanIds = extTestPlanTestCaseMapper.findRelateTestPlanId(user.getId());
PageHelper.startPage(1, count, true);
request.setPlanIds(relateTestPlanIds);
request.setExecutor(user.getId());
}
public TestPlanCaseDTO get(String caseId) {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setId(caseId);
return extTestPlanTestCaseMapper.list(request).get(0);
}
public void deleteTestCaseBath(TestPlanCaseBatchRequest request) {
TestPlanTestCaseExample example = new TestPlanTestCaseExample();
example.createCriteria().andIdIn(request.getIds());
testPlanTestCaseMapper.deleteByExample(example);
}
}

View File

@ -317,6 +317,7 @@ CREATE TABLE IF NOT EXISTS `test_plan_test_case` (
`executor` varchar(64) NOT NULL COMMENT 'Test case executor',
`status` varchar(15) NULL COMMENT 'Test case status',
`results` longtext COMMENT 'Test case result',
`issues` longtext COMMENT 'Test case result issues',
`remark` varchar(255) DEFAULT NULL COMMENT 'Test case remark',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',

View File

@ -4,10 +4,18 @@ VALUES ('admin', 'Administrator', 'admin@fit2cloud.com', md5('fit2cloud'), '1',
INSERT INTO user_role (id, user_id, role_id, source_id, create_time, update_time)
VALUES (uuid(), 'admin', 'admin', '1', 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('admin', '系统管理员', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('org_admin', '组织管理员', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_manager', '测试经理', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_user', '测试人员', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_viewer', 'Viewer', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('admin', '系统管理员', NULL, NULL, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('org_admin', '组织管理员', NULL, NULL, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('test_manager', '测试经理', NULL, NULL, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('test_user', '测试人员', NULL, NULL, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time)
VALUES ('test_viewer', 'Viewer', NULL, NULL, 1581576575948, 1581576575948);
INSERT INTO test_case_report_template (id,name,content) VALUES (uuid(), 'default','{\"components\": [1,2,3,4,5]}');
INSERT INTO test_case_report_template (id, name, content)
VALUES (uuid(), 'default', '{\"components\": [1,2,3,4,5]}');
INSERT INTO system_parameter (param_key, param_value, type, sort)
VALUES ('default.language', 'zh_CN', 'text', 5);

View File

@ -59,21 +59,7 @@
<!--要生成的数据库表 -->
<table tableName="test_plan"/>
<table tableName="test_case_node"/>
<table tableName="test_case"/>
<table tableName="test_plan_test_case"/>
<table tableName="test_case_report_template"/>
<table tableName="test_case_report"/>
<!--<table tableName="test_case_node">-->
<!--<generatedKey column="id" sqlStatement="MySql" identity="true"/>-->
<!--</table>-->
<!--<table tableName="test_case_report_template">-->
<!--<generatedKey column="id" sqlStatement="MySql" identity="true"/>-->
<!--</table>-->
</context>
</generatorConfiguration>

View File

@ -1,2 +1,44 @@
test_case_exist=
before_delete_plan=
before_delete_plan=
test_case_node_level_tip=
test_case_node_level=
test_case_module_not_null=
test_case_create_module_fail=
test_case_import_template_name=
test_case_import_template_sheet=
module_not_null=
user_not_exists=
test_case_already_exists=
parse_data_error=
missing_header_information=
number=
row=
error=
incorrect_format=
test_case_type_validate=
test_case_priority_validate=
test_case_method_validate=
test_case_name=
test_case_module=
test_case_type=
test_case_maintainer=
test_case_priority=
test_case_method=
test_case_prerequisite=
test_case_remark=
test_case_step_desc=
test_case_step_result=
test_case=
module=
preconditions_optional=
step_tip_separate=
step_tip_order=
step_tip_optional=
result_tip_separate=
result_tip_order=
result_tip_optional=
remark_optional=
do_not_modify_header_order=
module_created_automatically=
options=
please_input_workspace_member=

View File

@ -1,36 +1,85 @@
test_case_exist=A test case already exists under this project:
#commons
error_lang_invalid=Invalid language parameter
load_test_already_exists=Duplicate load test name
project_name_is_null=Project name cannot be null
project_name_already_exists=The project name already exists
workspace_name_is_null=Workspace name cannot be null
workspace_name_already_exists=The workspace name already exists
workspace_does_not_belong_to_user=The current workspace does not belong to the current user
organization_does_not_belong_to_user=The current organization does not belong to the current user
file_cannot_be_null=File cannot be empty!
#user related
user_email_already_exists=User email already exists
user_name_is_null=User name cannot be null
user_email_is_null=User email cannot be null
password_is_null=Password cannot be null
user_id_already_exists=User ID already exists
password_modification_failed=Password modification failed
cannot_delete_current_user=Cannot delete the user currently logged in
#load test
edit_load_test_not_found=Cannot edit test, test not found=
run_load_test_not_found=Cannot run test, test not found=
run_load_test_file_not_found=Unable to run test, unable to get test file meta information, test ID=
run_load_test_file_content_not_found=Cannot run test, cannot get test file content, test ID=
run_load_test_file_init_error=Failed to run test, failed to initialize run environment, test ID=
load_test_is_running=Load test is running, please wait.
node_deep_limit=The node depth does not exceed 5 layers!
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
max_thread_insufficient=The number of concurrent users exceeds
cannot_edit_load_test_running=Cannot modify the running test
test_not_found=Test cannot be found:
test_not_running=Test is not running
before_delete_plan=There is an associated test case under this plan, please unlink it first!
user_email_already_exists=User email already exists
user_name_is_null=User name cannot be null
user_email_is_null=User email cannot be null
password_is_null=Password cannot be null
load_test_already_exists=Duplicate load test name
no_nodes_message=No node message
duplicate_node_ip=Duplicate IPs
only_one_k8s=Only one K8s can be added
max_thread_insufficient=The number of concurrent users exceeds
#workspace
workspace_name_is_null=Workspace name cannot be null
workspace_name_already_exists=The workspace name already exists
workspace_does_not_belong_to_user=The current workspace does not belong to the current user
workspace_not_exists=Workspace is not exists
#project
project_name_is_null=Project name cannot be null
project_name_already_exists=The project name already exists
#organization
organization_does_not_belong_to_user=The current organization does not belong to the current user
organization_id_is_null=Organization ID cannot be null
#api
api_load_script_error=Load script error
user_id_already_exists=User ID already exists
password_modification_failed=Password modification failed
cannot_delete_current_user=Cannot delete the user currently logged in
#test case
test_case_node_level=level
test_case_node_level_tip=The node tree maximum depth is
test_case_module_not_null=The owned module cannot be empty
test_case_create_module_fail=Failed to create module
test_case_import_template_name=Test case templates
test_case_import_template_sheet=Template
module_not_null=The module must not be blank
user_not_exists=The user in this workspace is not exists
test_case_already_exists=The test case in this project is exists
parse_data_error=Parse data error
missing_header_information=Missing header information
number=Number
row=row
error=error
test_case_exist=A test case already exists under this project:
node_deep_limit=The node depth does not exceed 5 layers!
before_delete_plan=There is an associated test case under this plan, please unlink it first!
incorrect_format=Incorrect format
test_case_type_validate=must be functional, performance, api
test_case_priority_validate=must be P0, P1, P2, P3
test_case_method_validate=must be manual, auto
test_case_name=Name
test_case_type=Type
test_case_maintainer=Maintainer
test_case_priority=Priority
test_case_method=method
test_case_prerequisite=Prerequisite
test_case_remark=Remark
test_case_step_desc=Step description
test_case_step_result=Step result
test_case_module=Module
test_case=Test case
module=Module
preconditions_optional=Preconditions optional
step_tip_separate=Each step is separated by a new line
step_tip_order=The serial number can be marked before the step
step_tip_optional=Test steps and results optional
result_tip_separate=Each result is separated by a new line
result_tip_order=The sequence number can be marked before the result
result_tip_optional=Test steps and results optional
remark_optional=Remark optional
do_not_modify_header_order=Do not modify the header order
module_created_automatically=If there is no such module, will be created automatically
options=options
please_input_workspace_member=Please input workspace merber

View File

@ -1,36 +1,85 @@
test_case_exist=该项目下已存在用例:
#commons
error_lang_invalid=语言参数错误
load_test_already_exists=测试名称不能重复
project_name_is_null=项目名称不能为空
project_name_already_exists=项目名称已存在
workspace_name_is_null=工作空间名不能为空
workspace_name_already_exists=工作空间名已存在
workspace_does_not_belong_to_user=当前工作空间不属于当前用户
organization_does_not_belong_to_user=当前组织不属于当前用户
file_cannot_be_null=文件不能为空!
#user related
user_email_already_exists=用户邮箱已存在
user_name_is_null=用户名不能为空
user_email_is_null=用户邮箱不能为空
password_is_null=密码不能为空
user_id_already_exists=用户id已存在
password_modification_failed=密码修改失败
cannot_delete_current_user=无法删除当前登录用户
#load test
edit_load_test_not_found=无法编辑测试,未找到测试:
run_load_test_not_found=无法运行测试,未找到测试:
run_load_test_file_not_found=无法运行测试无法获取测试文件元信息测试ID
run_load_test_file_content_not_found=无法运行测试无法获取测试文件内容测试ID
run_load_test_file_init_error=无法运行测试初始化运行环境失败测试ID
load_test_is_running=测试正在运行, 请等待
node_deep_limit=节点深度不超过5层
no_nodes_message=没有节点信息
duplicate_node_ip=节点 IP 重复
only_one_k8s=只能添加一个 K8s
organization_id_is_null=组织 ID 不能为空
max_thread_insufficient=并发用户数超额
cannot_edit_load_test_running=不能修改正在运行的测试
test_not_found=测试不存在:
test_not_running=测试未运行
before_delete_plan=该计划下存在关联测试用例,请先取消关联!
user_email_already_exists=用户邮箱已存在
user_name_is_null=用户名不能为空
user_email_is_null=用户邮箱不能为空
password_is_null=密码不能为空
load_test_already_exists=测试名称不能重复
no_nodes_message=没有节点信息
duplicate_node_ip=节点 IP 重复
only_one_k8s=只能添加一个 K8s
max_thread_insufficient=并发用户数超额
#workspace
workspace_name_is_null=工作空间名不能为空
workspace_name_already_exists=工作空间名已存在
workspace_does_not_belong_to_user=当前工作空间不属于当前用户
workspace_not_exists=工作空间不存在
#project
project_name_is_null=项目名称不能为空
project_name_already_exists=项目名称已存在
#organization
organization_does_not_belong_to_user=当前组织不属于当前用户
organization_id_is_null=组织 ID 不能为空
#api
api_load_script_error=读取脚本失败
user_id_already_exists=用户id已存在
password_modification_failed=密码修改失败
cannot_delete_current_user=无法删除当前登录用户
#test case
test_case_node_level=
test_case_node_level_tip=模块树最大深度为
test_case_module_not_null=所属模块不能为空
test_case_create_module_fail=创建模块失败
test_case_import_template_name=测试用例模版
test_case_import_template_sheet=模版
module_not_null=所属模块不能为空格
user_not_exists=该工作空间下无该用户
test_case_already_exists=该项目下已存在该测试用例
parse_data_error=解析数据出错
missing_header_information=缺少头部信息
number=
row=
error=出错
test_case_exist=该项目下已存在用例:
node_deep_limit=节点深度不超过5层
before_delete_plan=该计划下存在关联测试用例,请先取消关联!
incorrect_format=格式错误
test_case_type_validate=必须为functional、performance、api
test_case_priority_validate=必须为P0、P1、P2、P3
test_case_method_validate=必须为manual、auto
test_case_name=用例名称
test_case_type=用例类型
test_case_maintainer=维护人
test_case_priority=优先级
test_case_method=测试方式
test_case_prerequisite=前置条件
test_case_remark=备注
test_case_step_desc=步骤描述
test_case_step_result=预期结果
test_case_module=所属模块
test_case=测试用例
module=模块
preconditions_optional=前置条件选填
step_tip_separate=每个步骤以换行分隔
step_tip_order=步骤前可标序号
step_tip_optional=步骤前可标序号
result_tip_separate=每条结果以换行分隔
result_tip_order=结果前可标序号
result_tip_optional=测试步骤和结果选填
remark_optional=备注选填
do_not_modify_header_order=请勿修改表头顺序
module_created_automatically=若无该模块将自动创建
options=选项
please_input_workspace_member=请填写该工作空间相关人员

View File

@ -1,36 +1,85 @@
test_case_exist=該項目下已存在用例:
#commons
error_lang_invalid=語言參數錯誤
load_test_already_exists=測試名稱不能重複
project_name_is_null=項目名稱不能為空
project_name_already_exists=項目名稱已存在
workspace_name_is_null=工作空間名不能為空
workspace_name_already_exists=工作空間名已存在
workspace_does_not_belong_to_user=當前工作空間不屬於當前用戶
organization_does_not_belong_to_user=當前組織不屬於當前用戶
file_cannot_be_null=文件不能為空!
#user related
user_email_already_exists=用戶郵箱已存在
user_name_is_null=用戶名不能為空
user_email_is_null=用戶郵箱不能為空
password_is_null=密碼不能為空
user_id_already_exists=用戶id已存在
password_modification_failed=密碼修改失敗
cannot_delete_current_user=無法刪除當前登錄用戶
#load test
edit_load_test_not_found=無法編輯測試,未找到測試:
run_load_test_not_found=無法運行測試,未找到測試:
run_load_test_file_not_found=無法運行測試無法獲取測試文件元信息測試ID
run_load_test_file_content_not_found=無法運行測試無法獲取測試文件內容測試ID
run_load_test_file_init_error=無法運行測試初始化運行環境失敗測試ID
load_test_is_running=測試正在運行, 請等待
node_deep_limit=節點深度不超過5層
no_nodes_message=沒有節點信息
duplicate_node_ip=節點 IP 重複
only_one_k8s=只能添加一個 K8s
organization_id_is_null=組織 ID 不能為空
max_thread_insufficient=並髮用戶數超額
cannot_edit_load_test_running=不能修改正在運行的測試
test_not_found=測試不存在:
test_not_running=測試未運行
before_delete_plan=該計劃下存在關聯測試用例,請先取消關聯!
user_email_already_exists=用戶郵箱已存在
user_name_is_null=用戶名不能為空
user_email_is_null=用戶郵箱不能為空
password_is_null=密碼不能為空
load_test_already_exists=測試名稱不能重復
no_nodes_message=沒有節點信息
duplicate_node_ip=節點 IP 重復
only_one_k8s=只能添加壹個 K8s
max_thread_insufficient=並發用戶數超額
#workspace
workspace_name_is_null=工作空間名不能為空
workspace_name_already_exists=工作空間名已存在
workspace_does_not_belong_to_user=當前工作空間不屬於當前用戶
workspace_not_exists=工作空間不存在
#project
project_name_is_null=項目名稱不能為空
project_name_already_exists=項目名稱已存在
#organization
organization_does_not_belong_to_user=當前組織不屬於當前用戶
organization_id_is_null=組織 ID 不能為空
#api
api_load_script_error=讀取腳本失敗
user_id_already_exists=用戶id已存在
password_modification_failed=密碼修改失敗
cannot_delete_current_user=無法刪除當前登錄用戶
#test case
test_case_node_level=
test_case_node_level_tip=模塊樹最大深度為
test_case_module_not_null=所屬模塊不能為空
test_case_create_module_fail=創建模塊失敗
test_case_import_template_name=測試用例模版
test_case_import_template_sheet=模版
module_not_null=所屬模塊不能為空格
user_not_exists=該工作空間下無該用戶
test_case_already_exists=該項目下已存在該測試用例
parse_data_error=解析數據出錯
missing_header_information=缺少頭部信息
number=
row=
error=出錯
test_case_exist=該項目下已存在用例:
node_deep_limit=節點深度不超過5層
before_delete_plan=該計劃下存在關聯測試用例,請先取消關聯!
incorrect_format=格式錯誤
test_case_type_validate=必須為functional、performance、api
test_case_priority_validate=必須為P0、P1、P2、P3
test_case_method_validate=必須為manual、auto
test_case_name=用例名稱
test_case_type=用例類型
test_case_maintainer=維護人
test_case_priority=優先級
test_case_method=測試方式
test_case_prerequisite=前置條件
test_case_remark=備註
test_case_step_desc=步驟描述
test_case_step_result=預期結果
test_case_module=所屬模塊
test_case=測試用例
module=模塊
preconditions_optional=前置條件選填
step_tip_separate=每個步驟以換行分隔
step_tip_order=步驟前可標序號
step_tip_optional=步驟前可標序號
result_tip_separate=每條結果以換行分隔
result_tip_order=結果前可標序號
result_tip_optional=測試步驟和結果選填
remark_optional=備註選填
do_not_modify_header_order=請勿修改表頭順序
module_created_automatically=若無該模塊將自動創建
options=選項
please_input_workspace_member=請填寫該工作空間相關人員

View File

@ -23,7 +23,8 @@
"vue-i18n": "^8.15.3",
"vue-router": "^3.1.3",
"vuedraggable": "^2.23.2",
"vuex": "^3.1.2"
"vuex": "^3.1.2",
"vue-calendar-heatmap": "^0.8.4"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",

View File

@ -73,7 +73,7 @@
return '/api/' + item.id;
},
router: function (item) {
return {name: 'fucPlan', params: {projectId: item.id, projectName: item.name}}
return {name: 'ApiTestList', params: {projectId: item.id, projectName: item.name}}
}
},
testRecent: {
@ -88,6 +88,7 @@
},
reportRecent: {
title: this.$t('report.recent'),
showTime: true,
url: "/api/report/recent/5",
index: function (item) {
return '/api/report/view/' + item.id;

View File

@ -0,0 +1,68 @@
<template>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<span class="title">{{$t('api_report.title')}}</span>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-report-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import MsApiReportStatus from "../report/ApiReportStatus";
export default {
name: "MsApiReportRecentList",
components: {MsApiReportStatus},
data() {
return {
result: {},
tableData: [],
loading: false
}
},
methods: {
search() {
this.result = this.$get("/api/report/recent/5", response => {
this.tableData = response.data;
});
},
link(row) {
this.$router.push({
path: '/api/report/view/' + row.id,
})
}
},
created() {
this.search();
}
}
</script>
<style scoped>
</style>

View File

@ -1,16 +1,57 @@
<template>
<div>
<h1>API测试首页</h1>
</div>
<ms-container>
<ms-main-container v-loading="result.loading">
<el-row :gutter="20">
<el-col :span="12">
<ms-api-test-recent-list/>
</el-col>
<el-col :span="12">
<ms-api-report-recent-list/>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ms-test-heatmap :values="values"/>
</el-col>
</el-row>
</ms-main-container>
</ms-container>
</template>
<script>
export default {
name: "ApiTestHome"
}
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsApiTestRecentList from "./ApiTestRecentList";
import MsApiReportRecentList from "./ApiReportRecentList";
import MsTestHeatmap from "../../common/components/MsTestHeatmap";
export default {
name: "ApiTestHome",
components: {MsTestHeatmap, MsApiReportRecentList, MsApiTestRecentList, MsMainContainer, MsContainer},
data() {
return {
values: [],
result: {},
}
},
mounted() {
this.result = this.$get('/api/report/dashboard/tests', response => {
this.values = response.data;
});
},
}
</script>
<style scoped>
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<span class="title">{{$t('api_test.title')}}</span>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip/>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-test-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import MsApiTestStatus from "../test/ApiTestStatus";
export default {
name: "MsApiTestRecentList",
components: {MsApiTestStatus},
data() {
return {
result: {},
tableData: [],
loading: false
}
},
methods: {
search() {
this.result = this.$get("/api/recent/5", response => {
this.tableData = response.data;
});
},
link(row) {
this.$router.push({
path: '/api/test/edit?id=' + row.id,
})
}
},
created() {
this.search();
}
}
</script>
<style scoped>
</style>

View File

@ -1,72 +1,34 @@
<template>
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card class="table-card">
<ms-container>
<ms-main-container>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
<ms-table-header :condition.sync="condition" @search="search" :title="$t('api_report.title')"
:show-create="false"/>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column
prop="name"
:label="$t('commons.name')"
width="150"
show-overflow-tooltip>
<el-table-column :label="$t('commons.name')" width="200" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="handleView(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<!-- <el-table-column-->
<!-- prop="description"-->
<!-- :label="$t('commons.description')"-->
<!-- show-overflow-tooltip>-->
<!-- </el-table-column>-->
<el-table-column
width="250"
:label="$t('commons.create_time')">
<el-table-column prop="testName" :label="$t('api_report.test_name')" width="200" show-overflow-tooltip/>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
width="250"
:label="$t('commons.update_time')">
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</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}">
<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'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
<ms-api-report-status :row="row"/>
</template>
</el-table-column>
<el-table-column
width="150"
:label="$t('commons.operating')">
<el-table-column width="150" :label="$t('commons.operating')">
<template v-slot:default="scope">
<el-button @click="handleView(scope.row)" type="primary" icon="el-icon-s-data" size="mini" circle/>
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
@ -76,21 +38,23 @@
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
import MsTablePagination from "../../common/pagination/TablePagination";
import MsTableHeader from "../../common/components/MsTableHeader";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsApiReportStatus from "./ApiReportStatus";
export default {
components: {MsTableHeader, MsTablePagination},
components: {MsApiReportStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination},
data() {
return {
result: {},
condition: {name: ""},
projectId: null,
tableData: [],
multipleSelection: [],
currentPage: 1,
@ -100,11 +64,8 @@
}
},
beforeRouteEnter(to, from, next) {
next(self => {
self.testId = to.params.testId;
self.search();
});
watch: {
'$route': 'init',
},
methods: {
@ -144,7 +105,15 @@
}
}
});
},
init() {
this.testId = this.$route.params.testId;
this.search();
}
},
created() {
this.init();
}
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<div>
<el-tag size="mini" type="primary" v-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<el-tag v-else size="mini" type="info">
{{ row.status }}
</el-tag>
</div>
</template>
<script>
export default {
name: "MsApiReportStatus",
props: {
row: Object
}
}
</script>
<style scoped>
</style>

View File

@ -2,7 +2,7 @@
<el-table :data="assertions" :row-style="getRowStyle" :header-cell-style="getRowStyle">
<el-table-column prop="name" :label="$t('api_report.assertions_name')" width="300">
</el-table-column>
<el-table-column prop="message" :label="$t('api_report.assertions_message')">
<el-table-column prop="message" :label="$t('api_report.assertions_error_message')">
</el-table-column>
<el-table-column prop="pass" :label="$t('api_report.assertions_is_success')" width="180">
<template v-slot:default="{row}">

View File

@ -105,7 +105,7 @@
},
fail() {
return (this.content.error / this.content.total).toFixed(0) + "%";
return (this.content.error / this.content.total * 100).toFixed(0) + "%";
},
assertions() {
return this.content.passAssertions + " / " + this.content.totalAssertions;

View File

@ -17,10 +17,10 @@
</div>
</el-col>
<el-col :span="2">
{{error}}
{{request.error}}
</el-col>
<el-col :span="2">
{{request.passAssertions}} / {{request.totalAssertions}}
{{assertion}}
</el-col>
<el-col :span="2">
<el-tag size="mini" type="success" v-if="request.success">
@ -72,9 +72,9 @@
},
computed: {
error() {
return this.request.totalAssertions - this.request.passAssertions;
}
assertion() {
return this.request.passAssertions + " / " + this.request.totalAssertions;
},
}
}
</script>

View File

@ -59,7 +59,7 @@
computed: {
assertion() {
return this.scenario.passAssertions - this.scenario.totalAssertions;
return this.scenario.passAssertions + " / " + this.scenario.totalAssertions;
},
success() {
return this.scenario.error === 0;
@ -75,7 +75,7 @@
}
.scenario-result + .scenario-result {
border-top: 1px solid #EBEEF5;
border-top: 1px solid #DCDFE6;
}
.scenario-result .info {

View File

@ -0,0 +1,75 @@
<template>
<div class="relate_report">
<el-button type="success" plain @click="search">{{$t('api_report.title')}}</el-button>
<el-dialog :title="$t('api_report.title')" :visible.sync="reportVisible">
<el-table :data="tableData" v-loading="result.loading">
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-report-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import MsApiReportStatus from "../report/ApiReportStatus";
export default {
name: "MsApiReportDialog",
components: {MsApiReportStatus},
props: ["testId"],
data() {
return {
reportVisible: false,
result: {},
tableData: [],
loading: false
}
},
methods: {
search() {
this.reportVisible = true;
let url = "/api/report/list/" + this.testId;
this.result = this.$get(url, response => {
this.tableData = response.data;
});
},
link(row) {
this.reportVisible = false;
this.$router.push({
path: '/api/report/view/' + row.id,
})
}
},
}
</script>
<style scoped>
.relate_report {
margin-left: 10px;
}
</style>

View File

@ -5,7 +5,8 @@
<el-container class="test-container" v-loading="result.loading">
<el-header>
<el-row type="flex" align="middle">
<el-input class="test-name" v-model="test.name" maxlength="64" :placeholder="$t('api_test.input_name')">
<el-input class="test-name" v-model="test.name" maxlength="60" :placeholder="$t('api_test.input_name')"
show-word-limit>
<el-select class="test-project" v-model="test.projectId" slot="prepend"
:placeholder="$t('api_test.select_project')">
<el-option v-for="project in projects" :key="project.id" :label="project.name" :value="project.id"/>
@ -23,7 +24,10 @@
<el-button type="primary" plain v-if="isShowRun" @click="runTest">
{{$t('api_test.run')}}
</el-button>
<el-button type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
<ms-api-report-dialog :test-id="id" v-if="test.status === 'Completed'"/>
</el-row>
</el-header>
<ms-api-scenario-config :scenarios="test.scenarioDefinition" ref="config"/>
@ -36,16 +40,19 @@
<script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Test} from "./model/ScenarioModel"
import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog";
export default {
name: "MsApiTestConfig",
components: {MsApiScenarioConfig},
components: {MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig},
props: ["id"],
data() {
return {
reportVisible: false,
create: false,
result: {},
projects: [],
@ -89,6 +96,7 @@
id: item.id,
projectId: item.projectId,
name: item.name,
status: item.status,
scenarioDefinition: JSON.parse(item.scenarioDefinition),
});
this.$refs.config.reset();
@ -106,6 +114,9 @@
saveTest: function () {
this.save(() => {
this.$success(this.$t('commons.save_success'));
this.$router.push({
path: '/api/test/edit?id=' + this.test.id
})
})
},
runTest: function () {

View File

@ -1,78 +1,35 @@
<template>
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card class="table-card">
<ms-container>
<ms-main-container>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
<ms-table-header :condition.sync="condition" @search="search" :title="$t('api_test.title')"
@create="create" :createTip="$t('load_test.create')"/>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column
prop="name"
:label="$t('commons.name')"
width="150"
show-overflow-tooltip>
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="handleEdit(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<!-- <el-table-column-->
<!-- prop="description"-->
<!-- :label="$t('commons.description')"-->
<!-- show-overflow-tooltip>-->
<!-- </el-table-column>-->
<el-table-column
prop="projectName"
:label="$t('load_test.project_name')"
width="150"
show-overflow-tooltip>
<el-table-column prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip>
</el-table-column>
<el-table-column
width="250"
:label="$t('commons.create_time')">
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
width="250"
:label="$t('commons.update_time')">
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</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}">
<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'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
<ms-api-test-status :row="row"/>
</template>
</el-table-column>
<el-table-column
width="150"
:label="$t('commons.operating')">
<el-table-column width="150" :label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator @editClick="handleEdit(scope.row)" @deleteClick="handleDelete(scope.row)"/>
</template>
@ -81,17 +38,20 @@
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
import MsTablePagination from "../../common/pagination/TablePagination";
import MsTableHeader from "../../common/components/MsTableHeader";
import MsTableOperator from "../../common/components/MsTableOperator";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsApiTestStatus from "./ApiTestStatus";
export default {
components: {MsTableHeader, MsTablePagination, MsTableOperator},
components: {MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator},
data() {
return {
result: {},
@ -106,11 +66,8 @@
}
},
beforeRouteEnter(to, from, next) {
next(self => {
self.projectId = to.params.projectId;
self.search();
});
watch: {
'$route': 'init'
},
methods: {
@ -153,7 +110,14 @@
}
}
});
},
init() {
this.projectId = this.$route.params.projectId;
this.search();
}
},
created() {
this.init();
}
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<div>
<el-tag size="mini" type="primary" v-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<el-tag v-else size="mini" type="info">
{{ row.status }}
</el-tag>
</div>
</template>
<script>
export default {
name: "MsApiTestStatus",
props: {
row: Object
}
}
</script>
<style scoped>
</style>

View File

@ -13,8 +13,12 @@
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link el-icon-more"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{type: 'copy', index: index}">复制请求</el-dropdown-item>
<el-dropdown-item :command="{type: 'delete', index: index}">删除请求</el-dropdown-item>
<el-dropdown-item :command="{type: 'copy', index: index}">
{{$t('api_test.request.copy')}}
</el-dropdown-item>
<el-dropdown-item :command="{type: 'delete', index: index}">
{{$t('api_test.request.delete')}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@ -50,12 +54,12 @@
methods: {
createRequest: function () {
let request = new Request({method: "GET"});
let request = new Request();
this.requests.push(request);
},
copyRequest: function (index) {
let request = this.requests[index];
this.requests.push(JSON.parse(JSON.stringify(request)));
this.requests.push(request.clone());
},
deleteRequest: function (index) {
this.requests.splice(index, 1);

View File

@ -1,7 +1,7 @@
<template>
<el-form :model="request" :rules="rules" ref="request" label-width="100px">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input v-model="request.name" maxlength="100"/>
<el-input v-model="request.name" maxlength="100" @input="valid"/>
</el-form-item>
<el-form-item :label="$t('api_test.request.url')" prop="url">
@ -108,6 +108,10 @@
}
}
return url;
},
valid(value) {
value = value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·!¥…()—\-《》?:“”【】、;‘’,。]/g, '').replace(/\s/g, "");
this.request.name = value;
}
},

View File

@ -12,20 +12,23 @@
{{$t('api_test.scenario.config')}}
</span>
</div>
<!-- 暂时去掉将来再加-->
<!-- <el-dropdown trigger="click" @command="handleCommand">-->
<!-- <span class="el-dropdown-link el-icon-more scenario-btn"/>-->
<!-- <el-dropdown-menu slot="dropdown">-->
<!-- <el-dropdown-item :command="{type:'delete', index:index}">删除场景</el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </el-dropdown>-->
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link el-icon-more scenario-btn"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{type: 'copy', index: index}">
{{$t('api_test.scenario.copy')}}
</el-dropdown-item>
<el-dropdown-item :command="{type:'delete', index:index}">
{{$t('api_test.scenario.delete')}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<ms-api-request-config :requests="scenario.requests" :open="select"/>
</ms-api-collapse-item>
</ms-api-collapse>
</div>
<!-- 暂时去掉将来再加-->
<!-- <el-button class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createScenario"/>-->
<el-button class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="createScenario"/>
</el-aside>
<el-main class="scenario-main">
@ -72,6 +75,10 @@
createScenario: function () {
this.scenarios.push(new Scenario());
},
copyScenario: function (index) {
let scenario = this.scenarios[index];
this.scenarios.push(scenario.clone());
},
deleteScenario: function (index) {
this.scenarios.splice(index, 1);
if (this.scenarios.length === 0) {
@ -84,6 +91,9 @@
},
handleCommand: function (command) {
switch (command.type) {
case "copy":
this.copyScenario(command.index);
break;
case "delete":
this.deleteScenario(command.index);
break;

View File

@ -1,12 +1,12 @@
<template>
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px">
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
<el-input v-model="scenario.name" maxlength="100"/>
<el-input v-model="scenario.name" maxlength="100" @input="valid"/>
</el-form-item>
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>-->
<!-- </el-form-item>-->
<!-- <el-form-item :label="$t('api_test.scenario.base_url')" prop="url">-->
<!-- <el-input :placeholder="$t('api_test.scenario.base_url_description')" v-model="scenario.url" maxlength="100"/>-->
<!-- </el-form-item>-->
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.scenario.variables')" name="parameters">
@ -43,6 +43,13 @@
]
}
}
},
methods: {
valid(value) {
value = value.replace(/[`~!@#$%^&*()\-+=<>?:"{}|,./;'\\[\]·!¥…()—\-《》?:“”【】、;‘’,。]/g, '').replace(/\s/g, "");
this.scenario.name = value;
}
}
}
</script>

View File

@ -32,13 +32,12 @@
methods: {
add: function () {
this.remove();
setTimeout(() => {
this.duration.value = this.time;
})
},
remove: function () {
this.duration.value = null;
this.duration.value = undefined;
}
}
}

View File

@ -20,7 +20,7 @@
</el-select>
</el-col>
<el-col>
<el-input v-model="value" maxlength="255" size="small" show-word-limit
<el-input v-model="value" maxlength="200" size="small" show-word-limit
:placeholder="$t('api_test.request.assertions.value')"/>
</el-col>
<el-col class="assertion-btn">
@ -62,7 +62,7 @@
description += " contains: " + this.value;
break;
case "NOT_CONTAINS":
expression = "^((?!" + this.value + ").)*$";
expression = "(?s)^((?!" + this.value + ").)*$";
description += " not contains: " + this.value;
break;
case "EQUALS":

View File

@ -13,7 +13,7 @@
<div>
{{$t("api_test.request.assertions.response_time")}}
</div>
<ms-api-assertion-response-time :response-time="assertions.responseTime" :edit="true"/>
<ms-api-assertion-response-time :duration="assertions.duration" :edit="true"/>
</div>
</div>

View File

@ -255,7 +255,7 @@ export class HTTPSamplerProxy extends DefaultTestElement {
this.stringProp("HTTPSampler.protocol", this.request.protocol.split(":")[0]);
this.stringProp("HTTPSampler.path", this.request.pathname);
this.stringProp("HTTPSampler.method", this.request.method);
if (this.request.port) {
if (!this.request.port) {
this.stringProp("HTTPSampler.port", "");
} else {
this.stringProp("HTTPSampler.port", this.request.port);

View File

@ -96,8 +96,8 @@ export class Test extends BaseConfig {
super();
this.version = '1.0.0';
this.id = uuid();
this.name = null;
this.projectId = null;
this.name = undefined;
this.projectId = undefined;
this.scenarioDefinition = [];
this.set(options);
@ -122,8 +122,8 @@ export class Scenario extends BaseConfig {
constructor(options) {
super();
this.id = uuid();
this.name = null;
this.url = null;
this.name = undefined;
this.url = undefined;
this.variables = [];
this.headers = [];
this.requests = [];
@ -137,20 +137,30 @@ export class Scenario extends BaseConfig {
options.requests = options.requests || [new Request()];
return options;
}
clone() {
let scenario = new Scenario(this);
scenario.id = uuid();
scenario.requests.forEach(function (request) {
request.id = uuid();
});
return scenario;
}
}
export class Request extends BaseConfig {
constructor(options) {
super();
this.id = uuid();
this.name = null;
this.url = null;
this.method = null;
this.name = undefined;
this.url = undefined;
this.method = undefined;
this.parameters = [];
this.headers = [];
this.body = null;
this.assertions = null;
this.extract = null;
this.body = undefined;
this.assertions = undefined;
this.extract = undefined;
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue}, options);
@ -168,13 +178,19 @@ export class Request extends BaseConfig {
isValid() {
return !!this.url && !!this.method
}
clone() {
let request = new Request(this);
request.id = uuid();
return request;
}
}
export class Body extends BaseConfig {
constructor(options) {
super();
this.type = null;
this.raw = null;
this.type = undefined;
this.raw = undefined;
this.kvs = [];
this.set(options);
@ -225,7 +241,7 @@ export class Assertions extends BaseConfig {
super();
this.text = [];
this.regex = [];
this.duration = null;
this.duration = undefined;
this.set(options);
this.sets({text: Text, regex: Regex}, options);
@ -248,9 +264,9 @@ export class AssertionType extends BaseConfig {
export class Text extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.TEXT);
this.subject = null;
this.condition = null;
this.value = null;
this.subject = undefined;
this.condition = undefined;
this.value = undefined;
this.set(options);
}
@ -259,9 +275,9 @@ export class Text extends AssertionType {
export class Regex extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.REGEX);
this.subject = null;
this.expression = null;
this.description = null;
this.subject = undefined;
this.expression = undefined;
this.description = undefined;
this.set(options);
}
@ -274,7 +290,7 @@ export class Regex extends AssertionType {
export class ResponseTime extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.RESPONSE_TIME);
this.value = null;
this.value = undefined;
this.set(options);
}
@ -311,10 +327,10 @@ export class ExtractType extends BaseConfig {
export class ExtractCommon extends ExtractType {
constructor(type, options) {
super(type);
this.variable = null;
this.variable = undefined;
this.value = ""; // ${variable}
this.expression = null;
this.description = null;
this.expression = undefined;
this.description = undefined;
this.set(options);
}
@ -386,7 +402,7 @@ class JMeterTestPlan extends Element {
class JMXGenerator {
constructor(test) {
if (!test || !(test instanceof Test)) return null;
if (!test || !(test instanceof Test)) return undefined;
if (!test.id) {
test.id = "#NULL_TEST_ID#";
@ -395,7 +411,8 @@ class JMXGenerator {
let testPlan = new TestPlan(test.name);
test.scenarioDefinition.forEach(scenario => {
let threadGroup = new ThreadGroup(scenario.name + SPLIT + scenario.id);
let testName = scenario.name ? scenario.name + SPLIT + scenario.id : SPLIT + scenario.id;
let threadGroup = new ThreadGroup(testName);
this.addScenarioVariables(threadGroup, scenario);
@ -405,7 +422,7 @@ class JMXGenerator {
if (!request.isValid()) return;
// test.id用于处理结果时区分属于哪个测试
let name = request.name + SPLIT + test.id;
let name = request.name ? request.name + SPLIT + test.id : SPLIT + test.id;
let httpSamplerProxy = new HTTPSamplerProxy(name, new JMXRequest(request));
this.addRequestHeader(httpSamplerProxy, request);
@ -496,7 +513,7 @@ class JMXGenerator {
getAssertion(regex) {
let name = regex.description;
let type = JMX_ASSERTION_CONDITION.MATCH; // 固定用Match自己写正则
let type = JMX_ASSERTION_CONDITION.CONTAINS; // 固定用Match自己写正则
let value = regex.expression;
switch (regex.subject) {
case ASSERTION_REGEX_SUBJECT.RESPONSE_CODE:

View File

@ -0,0 +1,26 @@
<template>
<el-aside class="ms-aside-container">
<slot></slot>
</el-aside>
</template>
<script>
export default {
name: "MsAsideContainer"
}
</script>
<style scoped>
.ms-aside-container {
border: 1px solid #E6E6E6;
padding: 10px;
border-radius: 2px;
box-sizing: border-box;
background-color: #FFF;
height: calc(100vh - 80px);
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<el-container class="ms-container">
<slot></slot>
</el-container>
</template>
<script>
export default {
name: "MsContainer"
}
</script>
<style scoped>
.ms-container >>> span.title {
font-size: 16px;
font-weight: 500;
margin-top: 0;
text-overflow: ellipsis;
overflow: hidden;
word-wrap: break-word;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<el-main class="ms-main-container">
<slot></slot>
</el-main>
</template>
<script>
export default {
name: "MsMainContainer"
}
</script>
<style scoped>
.ms-main-container {
padding: 15px;
height: calc(100vh - 80px);
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<el-card>
<template v-slot:header>
<span class="title">{{$t('commons.calendar_heatmap')}}</span>
</template>
<calendar-heatmap :end-date="endDate" :values="values" :locale="locale"
:tooltip-unit="unit"
:range-color="colorRange"/>
</el-card>
</template>
<script>
export default {
name: "MsTestHeatmap",
props: ['values'],
data() {
return {
endDate: new Date(),
colorRange: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'],
}
},
computed: {
locale() {
return {
months: [
this.$t('commons.months_1'),
this.$t('commons.months_2'),
this.$t('commons.months_3'),
this.$t('commons.months_4'),
this.$t('commons.months_5'),
this.$t('commons.months_6'),
this.$t('commons.months_7'),
this.$t('commons.months_8'),
this.$t('commons.months_9'),
this.$t('commons.months_10'),
this.$t('commons.months_11'),
this.$t('commons.months_12')
],
days: [
this.$t('commons.weeks_0'),
this.$t('commons.weeks_1'),
this.$t('commons.weeks_2'),
this.$t('commons.weeks_3'),
this.$t('commons.weeks_4'),
this.$t('commons.weeks_5'),
this.$t('commons.weeks_6')
],
No: 'No',
on: ',',
less: 'Less',
more: 'More'
}
},
unit() {
return this.$t('commons.test_unit')
}
},
}
</script>
<style scoped>
</style>

View File

@ -34,7 +34,9 @@
},
watch: {
'$route'(to) {
this.activeIndex = to.matched[0].path;
if (to.matched.length > 0) {
this.activeIndex = to.matched[0].path;
}
this.handleSelect(this.activeIndex);
}
},

View File

@ -13,7 +13,6 @@
</template>
<script>
import {TokenKey} from '../../../../common/js/constants';
import {getCurrentUser} from "../../../../common/js/utils";
export default {
@ -32,7 +31,7 @@
break;
case "logout":
this.$get("/signout", function () {
localStorage.removeItem(TokenKey);
localStorage.clear();
window.location.href = "/login";
});
break;

View File

@ -18,7 +18,7 @@
</template>
<script>
import {EN_US, TokenKey, ZH_CN, ZH_TW} from '../../../../common/js/constants';
import {DEFAULT_LANGUAGE, EN_US, TokenKey, ZH_CN, ZH_TW} from '../../../../common/js/constants';
import {getCurrentUser} from "../../../../common/js/utils";
export default {
@ -38,7 +38,7 @@
let lang = this.currentUser().language;
this.currentUserInfo = this.currentUser();
if (!lang) {
lang = 'zh_CN';
lang = localStorage.getItem(DEFAULT_LANGUAGE);
}
this.checkLanguage(lang)
},

View File

@ -6,7 +6,10 @@
<i class="el-icon-refresh" @click="recent"/>
</div>
<el-menu-item :key="i.id" v-for="i in items" :index="getIndex(i)" :route="getRouter(i)">
<span class="title">{{ i.name }}</span>
<template slot="title">
<div class="title">{{ i.name }}</div>
<div class="time" v-if="options.showTime && i.updateTime">{{ i.updateTime | timestampFormatDate}}</div>
</template>
</el-menu-item>
</div>
</template>
@ -81,6 +84,18 @@
}
.title {
display: inline-block;
padding-left: 20px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time {
color: #C0C4CC;
display: inline-block;
padding-left: 20px;
float: right;
}
</style>

View File

@ -224,6 +224,11 @@ const router = new VueRouter({
name: "planView",
component: TestPlanView
},
{
path: "plan/view/edit/:caseId",
name: "planViewEdit",
component: TestPlanView
},
{
path: "project/:type",
name: "trackProject",

View File

@ -0,0 +1,66 @@
<template>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<span class="title">{{$t('api_report.title')}}</span>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-performance-report-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import MsPerformanceReportStatus from "../report/PerformanceReportStatus";
export default {
name: "MsPerformanceReportRecentList",
components: {MsPerformanceReportStatus},
data() {
return {
result: {},
tableData: []
}
},
methods: {
search() {
this.result = this.$get("/performance/report/recent/5", response => {
this.tableData = response.data;
});
},
link(row) {
this.$router.push({
path: '/performance/report/view/' + row.id,
})
}
},
created() {
this.search();
}
}
</script>
<style scoped>
</style>

View File

@ -1,16 +1,56 @@
<template>
<div>
<h1>性能测试首页</h1>
</div>
<ms-container>
<ms-main-container v-loading="result.loading">
<el-row :gutter="20">
<el-col :span="12">
<ms-performance-report-recent-list/>
</el-col>
<el-col :span="12">
<ms-performance-test-recent-list/>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<ms-test-heatmap :values="values"/>
</el-col>
</el-row>
</ms-main-container>
</ms-container>
</template>
<script>
export default {
name: "PerformanceTestHome"
}
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsPerformanceTestRecentList from "./PerformanceTestRecentList"
import MsPerformanceReportRecentList from "./PerformanceReportRecentList"
import MsTestHeatmap from "../../common/components/MsTestHeatmap";
export default {
name: "PerformanceTestHome",
components: {
MsTestHeatmap,
MsMainContainer,
MsContainer,
MsPerformanceTestRecentList,
MsPerformanceReportRecentList
},
data() {
return {
values: [],
result: {},
}
},
mounted() {
this.result = this.$get('/performance/dashboard/tests', response => {
this.values = response.data;
});
},
}
</script>
<style scoped>
.el-row {
padding-bottom: 20px;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<span class="title">{{$t('api_test.title')}}</span>
</template>
<el-table :data="tableData" class="table-content">
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip/>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-performance-test-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import MsPerformanceTestStatus from "../test/PerformanceTestStatus";
export default {
name: "MsPerformanceTestRecentList",
components: {MsPerformanceTestStatus},
data() {
return {
result: {},
tableData: []
}
},
methods: {
search() {
this.result = this.$get("/performance/recent/5", response => {
this.tableData = response.data;
});
},
link(row) {
this.$router.push({
path: '/performance/test/edit/' + row.id,
})
}
},
created() {
this.search();
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,41 @@
<template>
<div>
<el-tag size="mini" type="primary" v-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
</div>
</template>
<script>
export default {
name: "MsPerformanceReportStatus",
props: {
row: Object
}
}
</script>
<style scoped>
</style>

View File

@ -1,7 +1,7 @@
<template>
<div v-loading="result.loading" class="container">
<div class="main-content">
<el-card>
<ms-container>
<ms-main-container>
<el-card v-loading="result.loading">
<el-row>
<el-col :span="16">
<el-row>
@ -51,8 +51,8 @@
</el-tabs>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
@ -60,6 +60,8 @@
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
export default {
name: "PerformanceReportView",
@ -67,7 +69,9 @@
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview
MsReportTestOverview,
MsContainer,
MsMainContainer
},
data() {
return {

View File

@ -1,8 +1,7 @@
<template>
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card class="table-card">
<ms-container>
<ms-main-container>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<div>
<el-row type="flex" justify="space-between" align="middle">
@ -47,29 +46,7 @@
prop="status"
:label="$t('commons.status')">
<template v-slot:default="{row}">
<el-tag size="mini" type="primary" v-if="row.status === 'Starting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="success" v-else-if="row.status === 'Running'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
<ms-performance-report-status :row="row"/>
</template>
</el-table-column>
<el-table-column
@ -84,17 +61,19 @@
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
import MsTablePagination from "../../common/pagination/TablePagination";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsPerformanceReportStatus from "./PerformanceReportStatus";
export default {
name: "PerformanceTestReport",
components: {MsTablePagination},
components: {MsPerformanceReportStatus, MsTablePagination, MsContainer, MsMainContainer},
created: function () {
this.initTableData();
},

View File

@ -1,7 +1,7 @@
<template>
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card>
<ms-container>
<ms-main-container>
<el-card v-loading="result.loading">
<el-row>
<el-col :span="10">
<el-input :placeholder="$t('load_test.input_name')" v-model="testPlan.name" class="input-with-select">
@ -37,21 +37,25 @@
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
import PerformanceBasicConfig from "./components/PerformanceBasicConfig";
import PerformancePressureConfig from "./components/PerformancePressureConfig";
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
export default {
name: "EditPerformanceTestPlan",
components: {
PerformancePressureConfig,
PerformanceBasicConfig,
PerformanceAdvancedConfig
PerformanceAdvancedConfig,
MsContainer,
MsMainContainer
},
data() {
return {

View File

@ -1,7 +1,7 @@
<template>
<div class="container" v-loading="result.loading">
<div class="main-content">
<el-card class="table-card">
<ms-container>
<ms-main-container>
<el-card class="table-card" v-loading="result.loading">
<template v-slot:header>
<div>
<el-row type="flex" justify="space-between" align="middle">
@ -46,32 +46,7 @@
prop="status"
:label="$t('commons.status')">
<template v-slot:default="{row}">
<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'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
<ms-performance-test-status :row="row"/>
</template>
</el-table-column>
<el-table-column
@ -85,16 +60,25 @@
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
</div>
</div>
</ms-main-container>
</ms-container>
</template>
<script>
import MsTablePagination from "../../common/pagination/TablePagination";
import MsTableOperator from "../../common/components/MsTableOperator";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import MsPerformanceTestStatus from "./PerformanceTestStatus";
export default {
components: {MsTablePagination, MsTableOperator},
components: {
MsPerformanceTestStatus,
MsTablePagination,
MsTableOperator,
MsContainer,
MsMainContainer
},
data() {
return {
result: {},

View File

@ -0,0 +1,44 @@
<template>
<div>
<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'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
{{ row.status }}
</el-tag>
<el-tag size="mini" type="info" v-else-if="row.status === 'Completed'">
{{ row.status }}
</el-tag>
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
<template v-slot:content>
<div>{{row.description}}</div>
</template>
<el-tag size="mini" type="danger">
{{ row.status }}
</el-tag>
</el-tooltip>
<span v-else>
{{ row.status }}
</span>
</div>
</template>
<script>
export default {
name: "MsPerformanceTestStatus",
props: {
row: Object
}
}
</script>
<style scoped>
</style>

Some files were not shown because too many files have changed in this diff Show More