This commit is contained in:
wenyann 2020-10-12 14:21:50 +08:00
commit 6d265b55db
70 changed files with 1138 additions and 629 deletions

View File

@ -11,6 +11,7 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.DashboardTestDTO; import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.service.CheckOwnerService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -25,6 +26,8 @@ public class APIReportController {
@Resource @Resource
private APIReportService apiReportService; private APIReportService apiReportService;
@Resource
private CheckOwnerService checkOwnerService;
@GetMapping("recent/{count}") @GetMapping("recent/{count}")
public List<APIReportResult> recentTest(@PathVariable int count) { public List<APIReportResult> recentTest(@PathVariable int count) {
@ -37,6 +40,7 @@ public class APIReportController {
@GetMapping("/list/{testId}") @GetMapping("/list/{testId}")
public List<APIReportResult> listByTestId(@PathVariable String testId) { public List<APIReportResult> listByTestId(@PathVariable String testId) {
checkOwnerService.checkApiTestOwner(testId);
return apiReportService.listByTestId(testId); return apiReportService.listByTestId(testId);
} }

View File

@ -13,6 +13,7 @@ import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.QueryScheduleRequest; import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.service.CheckOwnerService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -27,6 +28,8 @@ import java.util.List;
public class APITestController { public class APITestController {
@Resource @Resource
private APITestService apiTestService; private APITestService apiTestService;
@Resource
private CheckOwnerService checkownerService;
@GetMapping("recent/{count}") @GetMapping("recent/{count}")
public List<APITestResult> recentTest(@PathVariable int count) { public List<APITestResult> recentTest(@PathVariable int count) {
@ -51,6 +54,7 @@ public class APITestController {
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<ApiTest> list(@PathVariable String projectId) { public List<ApiTest> list(@PathVariable String projectId) {
checkownerService.checkProjectOwner(projectId);
return apiTestService.getApiTestByProjectId(projectId); return apiTestService.getApiTestByProjectId(projectId);
} }
@ -71,6 +75,7 @@ public class APITestController {
@PostMapping(value = "/update", consumes = {"multipart/form-data"}) @PostMapping(value = "/update", consumes = {"multipart/form-data"})
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
checkownerService.checkApiTestOwner(request.getId());
apiTestService.update(request, file, bodyFiles); apiTestService.update(request, file, bodyFiles);
} }
@ -81,13 +86,16 @@ public class APITestController {
@GetMapping("/get/{testId}") @GetMapping("/get/{testId}")
public APITestResult get(@PathVariable String testId) { public APITestResult get(@PathVariable String testId) {
checkownerService.checkApiTestOwner(testId);
return apiTestService.get(testId); return apiTestService.get(testId);
} }
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestBody DeleteAPITestRequest request) { public void delete(@RequestBody DeleteAPITestRequest request) {
apiTestService.delete(request.getId()); String testId = request.getId();
checkownerService.checkApiTestOwner(testId);
apiTestService.delete(testId);
} }
@PostMapping(value = "/run") @PostMapping(value = "/run")

View File

@ -3,6 +3,7 @@ package io.metersphere.api.controller;
import io.metersphere.api.service.ApiTestEnvironmentService; import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.service.CheckOwnerService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -17,9 +18,12 @@ public class ApiTestEnvironmentController {
@Resource @Resource
ApiTestEnvironmentService apiTestEnvironmentService; ApiTestEnvironmentService apiTestEnvironmentService;
@Resource
private CheckOwnerService checkOwnerService;
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<ApiTestEnvironmentWithBLOBs> list(@PathVariable String projectId) { public List<ApiTestEnvironmentWithBLOBs> list(@PathVariable String projectId) {
checkOwnerService.checkProjectOwner(projectId);
return apiTestEnvironmentService.list(projectId); return apiTestEnvironmentService.list(projectId);
} }

View File

@ -16,16 +16,19 @@ public class KeyValue {
private boolean enable; private boolean enable;
public KeyValue() { public KeyValue() {
this.enable = true;
} }
public KeyValue(String name, String value) { public KeyValue(String name, String value) {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.enable = true;
} }
public KeyValue(String name, String value, String description) { public KeyValue(String name, String value, String description) {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.enable = true;
this.description = description; this.description = description;
} }
} }

View File

@ -137,8 +137,10 @@ public class Swagger2Parser extends ApiImportAbstractParser {
Model model = definitions.get(simpleRef); Model model = definitions.get(simpleRef);
HashSet<String> refSet = new HashSet<>(); HashSet<String> refSet = new HashSet<>();
refSet.add(simpleRef); refSet.add(simpleRef);
JSONObject bodyParameters = getBodyJSONObjectParameters(model.getProperties(), definitions, refSet); if (model != null ) {
body.setRaw(bodyParameters.toJSONString()); JSONObject bodyParameters = getBodyJSONObjectParameters(model.getProperties(), definitions, refSet);
body.setRaw(bodyParameters.toJSONString());
}
} else if (schema instanceof ArrayModel) { } else if (schema instanceof ArrayModel) {
ArrayModel arrayModel = (ArrayModel) bodyParameter.getSchema(); ArrayModel arrayModel = (ArrayModel) bodyParameter.getSchema();
Property items = arrayModel.getItems(); Property items = arrayModel.getItems();

View File

@ -37,5 +37,7 @@ public class TestCase implements Serializable {
private String otherTestName; private String otherTestName;
private String reviewStatus;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }

View File

@ -1183,6 +1183,76 @@ public class TestCaseExample {
addCriterion("other_test_name not between", value1, value2, "otherTestName"); addCriterion("other_test_name not between", value1, value2, "otherTestName");
return (Criteria) this; return (Criteria) this;
} }
public Criteria andReviewStatusIsNull() {
addCriterion("review_status is null");
return (Criteria) this;
}
public Criteria andReviewStatusIsNotNull() {
addCriterion("review_status is not null");
return (Criteria) this;
}
public Criteria andReviewStatusEqualTo(String value) {
addCriterion("review_status =", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusNotEqualTo(String value) {
addCriterion("review_status <>", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusGreaterThan(String value) {
addCriterion("review_status >", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusGreaterThanOrEqualTo(String value) {
addCriterion("review_status >=", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusLessThan(String value) {
addCriterion("review_status <", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusLessThanOrEqualTo(String value) {
addCriterion("review_status <=", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusLike(String value) {
addCriterion("review_status like", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusNotLike(String value) {
addCriterion("review_status not like", value, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusIn(List<String> values) {
addCriterion("review_status in", values, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusNotIn(List<String> values) {
addCriterion("review_status not in", values, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusBetween(String value1, String value2) {
addCriterion("review_status between", value1, value2, "reviewStatus");
return (Criteria) this;
}
public Criteria andReviewStatusNotBetween(String value1, String value2) {
addCriterion("review_status not between", value1, value2, "reviewStatus");
return (Criteria) this;
}
} }
public static class Criteria extends GeneratedCriteria { public static class Criteria extends GeneratedCriteria {

View File

@ -18,6 +18,7 @@
<result column="sort" jdbcType="INTEGER" property="sort" /> <result column="sort" jdbcType="INTEGER" property="sort" />
<result column="num" jdbcType="INTEGER" property="num" /> <result column="num" jdbcType="INTEGER" property="num" />
<result column="other_test_name" jdbcType="VARCHAR" property="otherTestName" /> <result column="other_test_name" jdbcType="VARCHAR" property="otherTestName" />
<result column="review_status" jdbcType="VARCHAR" property="reviewStatus" />
</resultMap> </resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestCaseWithBLOBs"> <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestCaseWithBLOBs">
<result column="remark" jdbcType="LONGVARCHAR" property="remark" /> <result column="remark" jdbcType="LONGVARCHAR" property="remark" />
@ -83,7 +84,7 @@
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, node_id, node_path, project_id, `name`, `type`, maintainer, priority, `method`, id, node_id, node_path, project_id, `name`, `type`, maintainer, priority, `method`,
prerequisite, create_time, update_time, test_id, sort, num, other_test_name prerequisite, create_time, update_time, test_id, sort, num, other_test_name, review_status
</sql> </sql>
<sql id="Blob_Column_List"> <sql id="Blob_Column_List">
remark, steps remark, steps
@ -142,15 +143,15 @@
maintainer, priority, `method`, maintainer, priority, `method`,
prerequisite, create_time, update_time, prerequisite, create_time, update_time,
test_id, sort, num, test_id, sort, num,
other_test_name, remark, steps other_test_name, review_status, remark,
) steps)
values (#{id,jdbcType=VARCHAR}, #{nodeId,jdbcType=VARCHAR}, #{nodePath,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{nodeId,jdbcType=VARCHAR}, #{nodePath,jdbcType=VARCHAR},
#{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{maintainer,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR}, #{maintainer,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR},
#{prerequisite,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{prerequisite,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{testId,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{num,jdbcType=INTEGER}, #{testId,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{num,jdbcType=INTEGER},
#{otherTestName,jdbcType=VARCHAR}, #{remark,jdbcType=LONGVARCHAR}, #{steps,jdbcType=LONGVARCHAR} #{otherTestName,jdbcType=VARCHAR}, #{reviewStatus,jdbcType=VARCHAR}, #{remark,jdbcType=LONGVARCHAR},
) #{steps,jdbcType=LONGVARCHAR})
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseWithBLOBs"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseWithBLOBs">
insert into test_case insert into test_case
@ -203,6 +204,9 @@
<if test="otherTestName != null"> <if test="otherTestName != null">
other_test_name, other_test_name,
</if> </if>
<if test="reviewStatus != null">
review_status,
</if>
<if test="remark != null"> <if test="remark != null">
remark, remark,
</if> </if>
@ -259,6 +263,9 @@
<if test="otherTestName != null"> <if test="otherTestName != null">
#{otherTestName,jdbcType=VARCHAR}, #{otherTestName,jdbcType=VARCHAR},
</if> </if>
<if test="reviewStatus != null">
#{reviewStatus,jdbcType=VARCHAR},
</if>
<if test="remark != null"> <if test="remark != null">
#{remark,jdbcType=LONGVARCHAR}, #{remark,jdbcType=LONGVARCHAR},
</if> </if>
@ -324,6 +331,9 @@
<if test="record.otherTestName != null"> <if test="record.otherTestName != null">
other_test_name = #{record.otherTestName,jdbcType=VARCHAR}, other_test_name = #{record.otherTestName,jdbcType=VARCHAR},
</if> </if>
<if test="record.reviewStatus != null">
review_status = #{record.reviewStatus,jdbcType=VARCHAR},
</if>
<if test="record.remark != null"> <if test="record.remark != null">
remark = #{record.remark,jdbcType=LONGVARCHAR}, remark = #{record.remark,jdbcType=LONGVARCHAR},
</if> </if>
@ -353,6 +363,7 @@
sort = #{record.sort,jdbcType=INTEGER}, sort = #{record.sort,jdbcType=INTEGER},
num = #{record.num,jdbcType=INTEGER}, num = #{record.num,jdbcType=INTEGER},
other_test_name = #{record.otherTestName,jdbcType=VARCHAR}, other_test_name = #{record.otherTestName,jdbcType=VARCHAR},
review_status = #{record.reviewStatus,jdbcType=VARCHAR},
remark = #{record.remark,jdbcType=LONGVARCHAR}, remark = #{record.remark,jdbcType=LONGVARCHAR},
steps = #{record.steps,jdbcType=LONGVARCHAR} steps = #{record.steps,jdbcType=LONGVARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
@ -376,7 +387,8 @@
test_id = #{record.testId,jdbcType=VARCHAR}, test_id = #{record.testId,jdbcType=VARCHAR},
sort = #{record.sort,jdbcType=INTEGER}, sort = #{record.sort,jdbcType=INTEGER},
num = #{record.num,jdbcType=INTEGER}, num = #{record.num,jdbcType=INTEGER},
other_test_name = #{record.otherTestName,jdbcType=VARCHAR} other_test_name = #{record.otherTestName,jdbcType=VARCHAR},
review_status = #{record.reviewStatus,jdbcType=VARCHAR}
<if test="_parameter != null"> <if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" /> <include refid="Update_By_Example_Where_Clause" />
</if> </if>
@ -429,6 +441,9 @@
<if test="otherTestName != null"> <if test="otherTestName != null">
other_test_name = #{otherTestName,jdbcType=VARCHAR}, other_test_name = #{otherTestName,jdbcType=VARCHAR},
</if> </if>
<if test="reviewStatus != null">
review_status = #{reviewStatus,jdbcType=VARCHAR},
</if>
<if test="remark != null"> <if test="remark != null">
remark = #{remark,jdbcType=LONGVARCHAR}, remark = #{remark,jdbcType=LONGVARCHAR},
</if> </if>
@ -455,6 +470,7 @@
sort = #{sort,jdbcType=INTEGER}, sort = #{sort,jdbcType=INTEGER},
num = #{num,jdbcType=INTEGER}, num = #{num,jdbcType=INTEGER},
other_test_name = #{otherTestName,jdbcType=VARCHAR}, other_test_name = #{otherTestName,jdbcType=VARCHAR},
review_status = #{reviewStatus,jdbcType=VARCHAR},
remark = #{remark,jdbcType=LONGVARCHAR}, remark = #{remark,jdbcType=LONGVARCHAR},
steps = #{steps,jdbcType=LONGVARCHAR} steps = #{steps,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
@ -475,7 +491,8 @@
test_id = #{testId,jdbcType=VARCHAR}, test_id = #{testId,jdbcType=VARCHAR},
sort = #{sort,jdbcType=INTEGER}, sort = #{sort,jdbcType=INTEGER},
num = #{num,jdbcType=INTEGER}, num = #{num,jdbcType=INTEGER},
other_test_name = #{otherTestName,jdbcType=VARCHAR} other_test_name = #{otherTestName,jdbcType=VARCHAR},
review_status = #{reviewStatus,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR}
</update> </update>
</mapper> </mapper>

View File

@ -20,4 +20,12 @@ public interface ExtTestCaseMapper {
TestCase getMaxNumByProjectId(@Param("projectId") String projectId); TestCase getMaxNumByProjectId(@Param("projectId") String projectId);
/**
* 检查某工作空间下是否有某用例
* @param caseId
* @param workspaceId
* @return TestCase ID
*/
List<String> checkIsHave(@Param("caseId") String caseId, @Param("workspaceId") String workspaceId);
} }

View File

@ -99,7 +99,7 @@
<select id="getTestCaseNames" resultType="io.metersphere.base.domain.TestCase"> <select id="getTestCaseNames" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name, test_case.priority, test_case.type select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status
from test_case from test_case
<where> <where>
<if test="request.combine != null"> <if test="request.combine != null">
@ -130,6 +130,12 @@
#{value} #{value}
</foreach> </foreach>
</when> </when>
<when test="key=='status'">
and test_case.review_status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<otherwise> <otherwise>
and test_case.type in and test_case.type in
<foreach collection="values" item="value" separator="," open="(" close=")"> <foreach collection="values" item="value" separator="," open="(" close=")">
@ -181,6 +187,12 @@
#{value} #{value}
</foreach> </foreach>
</when> </when>
<when test="key=='status'">
and test_case.review_status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<otherwise> <otherwise>
and test_case.method in and test_case.method in
<foreach collection="values" item="value" separator="," open="(" close=")"> <foreach collection="values" item="value" separator="," open="(" close=")">
@ -239,4 +251,11 @@
<select id="getMaxNumByProjectId" resultType="io.metersphere.base.domain.TestCase"> <select id="getMaxNumByProjectId" resultType="io.metersphere.base.domain.TestCase">
select * from test_case where test_case.project_id = #{projectId} order by num desc limit 1; select * from test_case where test_case.project_id = #{projectId} order by num desc limit 1;
</select> </select>
<select id="checkIsHave" resultType="java.lang.String">
select distinct test_case.id
from test_case, project
where test_case.project_id = project.id
and project.workspace_id = #{workspaceId}
and test_case.id = #{caseId};
</select>
</mapper> </mapper>

View File

@ -15,4 +15,12 @@ public interface ExtTestCaseReviewMapper {
List<TestCaseReviewDTO> listByWorkspaceId(@Param("workspaceId") String workspaceId); List<TestCaseReviewDTO> listByWorkspaceId(@Param("workspaceId") String workspaceId);
List<TestReviewDTOWithMetric> listRelate(@Param("request") QueryTestReviewRequest request); List<TestReviewDTOWithMetric> listRelate(@Param("request") QueryTestReviewRequest request);
/**
* 检查某工作空间下是否有某测试评审
* @param reviewId
* @param workspaceId
* @return Review ID
*/
List<String> checkIsHave(@Param("reviewId") String reviewId, @Param("workspaceId") String workspaceId);
} }

View File

@ -58,4 +58,11 @@
order by test_case_review.update_time desc order by test_case_review.update_time desc
</select> </select>
<select id="checkIsHave" resultType="java.lang.String">
select distinct review_id
from project, test_case_review_project
where project.id = test_case_review_project.project_id
and project.workspace_id = #{workspaceId}
and test_case_review_project.review_id = #{reviewId};
</select>
</mapper> </mapper>

View File

@ -10,6 +10,6 @@ import java.util.List;
public interface ExtTestReviewCaseMapper { public interface ExtTestReviewCaseMapper {
List<TestReviewCaseDTO> list(@Param("request") QueryCaseReviewRequest request); List<TestReviewCaseDTO> list(@Param("request") QueryCaseReviewRequest request);
List<String> getStatusByReviewId(String planId); List<String> getStatusByReviewId(String reviewId);
List<String> findRelateTestReviewId(String userId, String workspaceId); List<String> findRelateTestReviewId(String userId, String workspaceId);
} }

View File

@ -182,9 +182,9 @@
</select> </select>
<select id="getStatusByReviewId" resultType="java.lang.String"> <select id="getStatusByReviewId" resultType="java.lang.String">
select status select review_status
from test_case_review_test_case from test_case
where review_id = #{reviewId} where id in (select case_id from test_case_review_test_case where review_id = #{reviewId});
</select> </select>
<select id="findRelateTestReviewId" resultType="java.lang.String"> <select id="findRelateTestReviewId" resultType="java.lang.String">

View File

@ -9,6 +9,7 @@ import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.ProjectRequest; import io.metersphere.controller.request.ProjectRequest;
import io.metersphere.dto.ProjectDTO; import io.metersphere.dto.ProjectDTO;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.service.ProjectService; import io.metersphere.service.ProjectService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
@ -22,6 +23,8 @@ import java.util.List;
public class ProjectController { public class ProjectController {
@Resource @Resource
private ProjectService projectService; private ProjectService projectService;
@Resource
private CheckOwnerService checkOwnerService;
@GetMapping("/listAll") @GetMapping("/listAll")
public List<ProjectDTO> listAll() { public List<ProjectDTO> listAll() {
@ -71,6 +74,7 @@ public class ProjectController {
@GetMapping("/delete/{projectId}") @GetMapping("/delete/{projectId}")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR)
public void deleteProject(@PathVariable(value = "projectId") String projectId) { public void deleteProject(@PathVariable(value = "projectId") String projectId) {
checkOwnerService.checkProjectOwner(projectId);
projectService.deleteProject(projectId); projectService.deleteProject(projectId);
} }

View File

@ -4,6 +4,7 @@ package io.metersphere.controller.handler;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.ResultHolder; import io.metersphere.controller.ResultHolder;
import org.apache.shiro.ShiroException; import org.apache.shiro.ShiroException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -21,6 +22,13 @@ public class RestControllerExceptionHandler {
return ResultHolder.error(exception.getMessage()); return ResultHolder.error(exception.getMessage());
} }
/*=========== Shiro 异常拦截==============*/
@ExceptionHandler(UnauthorizedException.class)
public ResultHolder unauthorizedExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return ResultHolder.error(exception.getMessage());
}
@ExceptionHandler(MSException.class) @ExceptionHandler(MSException.class)
public ResultHolder msExceptionHandler(HttpServletRequest request, HttpServletResponse response, MSException e) { public ResultHolder msExceptionHandler(HttpServletRequest request, HttpServletResponse response, MSException e) {

View File

@ -14,6 +14,7 @@ import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO; import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.performance.service.PerformanceTestService; import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.track.request.testplan.*; import io.metersphere.track.request.testplan.*;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
@ -35,6 +36,8 @@ public class PerformanceTestController {
private PerformanceTestService performanceTestService; private PerformanceTestService performanceTestService;
@Resource @Resource
private FileService fileService; private FileService fileService;
@Resource
private CheckOwnerService checkOwnerService;
@GetMapping("recent/{count}") @GetMapping("recent/{count}")
public List<LoadTestDTO> recentTestPlans(@PathVariable int count) { public List<LoadTestDTO> recentTestPlans(@PathVariable int count) {
@ -54,12 +57,14 @@ public class PerformanceTestController {
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<LoadTest> list(@PathVariable String projectId) { public List<LoadTest> list(@PathVariable String projectId) {
checkOwnerService.checkProjectOwner(projectId);
return performanceTestService.getLoadTestByProjectId(projectId); return performanceTestService.getLoadTestByProjectId(projectId);
} }
@GetMapping("/state/get/{testId}") @GetMapping("/state/get/{testId}")
public LoadTest listByTestId(@PathVariable String testId) { public LoadTest listByTestId(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return performanceTestService.getLoadTestBytestId(testId); return performanceTestService.getLoadTestBytestId(testId);
} }
@ -76,26 +81,31 @@ public class PerformanceTestController {
@RequestPart("request") EditTestPlanRequest request, @RequestPart("request") EditTestPlanRequest request,
@RequestPart(value = "file", required = false) List<MultipartFile> files @RequestPart(value = "file", required = false) List<MultipartFile> files
) { ) {
checkOwnerService.checkPerformanceTestOwner(request.getId());
return performanceTestService.edit(request, files); return performanceTestService.edit(request, files);
} }
@GetMapping("/get/{testId}") @GetMapping("/get/{testId}")
public LoadTestDTO get(@PathVariable String testId) { public LoadTestDTO get(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return performanceTestService.get(testId); return performanceTestService.get(testId);
} }
@GetMapping("/get-advanced-config/{testId}") @GetMapping("/get-advanced-config/{testId}")
public String getAdvancedConfiguration(@PathVariable String testId) { public String getAdvancedConfiguration(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return performanceTestService.getAdvancedConfiguration(testId); return performanceTestService.getAdvancedConfiguration(testId);
} }
@GetMapping("/get-load-config/{testId}") @GetMapping("/get-load-config/{testId}")
public String getLoadConfiguration(@PathVariable String testId) { public String getLoadConfiguration(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return performanceTestService.getLoadConfiguration(testId); return performanceTestService.getLoadConfiguration(testId);
} }
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestBody DeleteTestPlanRequest request) { public void delete(@RequestBody DeleteTestPlanRequest request) {
checkOwnerService.checkPerformanceTestOwner(request.getId());
performanceTestService.delete(request); performanceTestService.delete(request);
} }
@ -111,6 +121,7 @@ public class PerformanceTestController {
@GetMapping("/file/metadata/{testId}") @GetMapping("/file/metadata/{testId}")
public List<FileMetadata> getFileMetadata(@PathVariable String testId) { public List<FileMetadata> getFileMetadata(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return fileService.getFileMetadataByTestId(testId); return fileService.getFileMetadataByTestId(testId);
} }

View File

@ -0,0 +1,97 @@
package io.metersphere.service;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.base.domain.Project;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.ext.*;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.request.testplan.QueryTestPlanRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class CheckOwnerService {
@Resource
private ProjectMapper projectMapper;
@Resource
private ExtApiTestMapper extApiTestMapper;
@Resource
private ExtLoadTestMapper extLoadTestMapper;
@Resource
private ExtTestCaseMapper extTestCaseMapper;
@Resource
private ExtTestPlanMapper extTestPlanMapper;
@Resource
private ExtTestCaseReviewMapper extTestCaseReviewMapper;
public void checkProjectOwner(String projectId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
Project project = projectMapper.selectByPrimaryKey(projectId);
if (project == null) {
return;
}
if (!StringUtils.equals(workspaceId, project.getWorkspaceId())) {
throw new UnauthorizedException(Translator.get("check_owner_project"));
}
}
public void checkApiTestOwner(String testId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
QueryAPITestRequest request = new QueryAPITestRequest();
request.setWorkspaceId(workspaceId);
request.setId(testId);
List<APITestResult> apiTestResults = extApiTestMapper.list(request);
if (CollectionUtils.size(apiTestResults) != 1) {
throw new UnauthorizedException(Translator.get("check_owner_test"));
}
}
public void checkPerformanceTestOwner(String testId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setWorkspaceId(workspaceId);
request.setId(testId);
List<LoadTestDTO> loadTestDTOS = extLoadTestMapper.list(request);
if (CollectionUtils.size(loadTestDTOS) != 1) {
throw new UnauthorizedException(Translator.get("check_owner_test"));
}
}
public void checkTestCaseOwner(String caseId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
List<String> list = extTestCaseMapper.checkIsHave(caseId, workspaceId);
if (CollectionUtils.size(list) != 1) {
throw new UnauthorizedException(Translator.get("check_owner_case"));
}
}
public void checkTestPlanOwner(String planId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
io.metersphere.track.request.testcase.QueryTestPlanRequest request = new io.metersphere.track.request.testcase.QueryTestPlanRequest();
request.setWorkspaceId(workspaceId);
request.setId(planId);
List<TestPlanDTO> list = extTestPlanMapper.list(request);
if (CollectionUtils.size(list) != 1) {
throw new UnauthorizedException(Translator.get("check_owner_plan"));
}
}
public void checkTestReviewOwner(String reviewId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId();
List<String> list = extTestCaseReviewMapper.checkIsHave(reviewId, workspaceId);
if (CollectionUtils.size(list) != 1) {
throw new UnauthorizedException(Translator.get("check_owner_review"));
}
}
}

View File

@ -487,20 +487,18 @@ public class UserService {
/*修改当前用户用户密码*/ /*修改当前用户用户密码*/
private User updateCurrentUserPwd(EditPassWordRequest request) { private User updateCurrentUserPwd(EditPassWordRequest request) {
if (SessionUtils.getUser() != null) { String oldPassword = CodingUtil.md5(request.getPassword(), "utf-8");
User user = userMapper.selectByPrimaryKey(SessionUtils.getUser().getId()); String newPassword = request.getNewpassword();
String pwd = user.getPassword(); UserExample userExample = new UserExample();
String prepwd = CodingUtil.md5(request.getPassword(), "utf-8"); userExample.createCriteria().andIdEqualTo(SessionUtils.getUser().getId()).andPasswordEqualTo(oldPassword);
String newped = request.getNewpassword(); List<User> users = userMapper.selectByExample(userExample);
if (StringUtils.isNotBlank(prepwd)) { if (!CollectionUtils.isEmpty(users)) {
if (prepwd.trim().equals(pwd.trim())) { User user = users.get(0);
user.setPassword(CodingUtil.md5(newped)); user.setPassword(CodingUtil.md5(newPassword));
user.setUpdateTime(System.currentTimeMillis()); user.setUpdateTime(System.currentTimeMillis());
return user; return user;
}
}
MSException.throwException(Translator.get("password_modification_failed"));
} }
MSException.throwException(Translator.get("password_modification_failed"));
return null; return null;
} }
@ -512,8 +510,8 @@ public class UserService {
/*管理员修改用户密码*/ /*管理员修改用户密码*/
private User updateUserPwd(EditPassWordRequest request) { private User updateUserPwd(EditPassWordRequest request) {
User user = userMapper.selectByPrimaryKey(request.getId()); User user = userMapper.selectByPrimaryKey(request.getId());
String newped = request.getNewpassword(); String newPassword = request.getNewpassword();
user.setPassword(CodingUtil.md5(newped)); user.setPassword(CodingUtil.md5(newPassword));
user.setUpdateTime(System.currentTimeMillis()); user.setUpdateTime(System.currentTimeMillis());
return user; return user;
} }

View File

@ -10,6 +10,7 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.excel.domain.ExcelResponse; import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.track.dto.TestCaseDTO; import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.QueryTestCaseRequest; import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testcase.TestCaseBatchRequest; import io.metersphere.track.request.testcase.TestCaseBatchRequest;
@ -30,6 +31,8 @@ public class TestCaseController {
@Resource @Resource
TestCaseService testCaseService; TestCaseService testCaseService;
@Resource
private CheckOwnerService checkOwnerService;
@PostMapping("/list/{goPage}/{pageSize}") @PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<TestCaseDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) { public Pager<List<TestCaseDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
@ -39,6 +42,7 @@ public class TestCaseController {
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<TestCaseDTO> list(@PathVariable String projectId) { public List<TestCaseDTO> list(@PathVariable String projectId) {
checkOwnerService.checkProjectOwner(projectId);
QueryTestCaseRequest request = new QueryTestCaseRequest(); QueryTestCaseRequest request = new QueryTestCaseRequest();
request.setProjectId(projectId); request.setProjectId(projectId);
return testCaseService.listTestCase(request); return testCaseService.listTestCase(request);
@ -47,6 +51,7 @@ public class TestCaseController {
@GetMapping("/list/method/{projectId}") @GetMapping("/list/method/{projectId}")
public List<TestCaseDTO> listByMethod(@PathVariable String projectId) { public List<TestCaseDTO> listByMethod(@PathVariable String projectId) {
checkOwnerService.checkProjectOwner(projectId);
QueryTestCaseRequest request = new QueryTestCaseRequest(); QueryTestCaseRequest request = new QueryTestCaseRequest();
request.setProjectId(projectId); request.setProjectId(projectId);
return testCaseService.listTestCaseMthod(request); return testCaseService.listTestCaseMthod(request);
@ -78,11 +83,13 @@ public class TestCaseController {
@GetMapping("/get/{testCaseId}") @GetMapping("/get/{testCaseId}")
public TestCaseWithBLOBs getTestCase(@PathVariable String testCaseId) { public TestCaseWithBLOBs getTestCase(@PathVariable String testCaseId) {
checkOwnerService.checkTestCaseOwner(testCaseId);
return testCaseService.getTestCase(testCaseId); return testCaseService.getTestCase(testCaseId);
} }
@GetMapping("/project/{testCaseId}") @GetMapping("/project/{testCaseId}")
public Project getProjectByTestCaseId(@PathVariable String testCaseId) { public Project getProjectByTestCaseId(@PathVariable String testCaseId) {
checkOwnerService.checkTestCaseOwner(testCaseId);
return testCaseService.getProjectByTestCaseId(testCaseId); return testCaseService.getProjectByTestCaseId(testCaseId);
} }
@ -101,13 +108,15 @@ public class TestCaseController {
@PostMapping("/delete/{testCaseId}") @PostMapping("/delete/{testCaseId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public int deleteTestCase(@PathVariable String testCaseId) { public int deleteTestCase(@PathVariable String testCaseId) {
checkOwnerService.checkTestCaseOwner(testCaseId);
return testCaseService.deleteTestCase(testCaseId); return testCaseService.deleteTestCase(testCaseId);
} }
@PostMapping("/import/{projectId}/{userId}") @PostMapping("/import/{projectId}/{userId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId,@PathVariable String userId) throws NoSuchFieldException { public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId, @PathVariable String userId) {
return testCaseService.testCaseImport(file, projectId,userId); checkOwnerService.checkProjectOwner(projectId);
return testCaseService.testCaseImport(file, projectId, userId);
} }
@GetMapping("/export/template") @GetMapping("/export/template")
@ -115,6 +124,7 @@ public class TestCaseController {
public void testCaseTemplateExport(HttpServletResponse response) { public void testCaseTemplateExport(HttpServletResponse response) {
testCaseService.testCaseTemplateExport(response); testCaseService.testCaseTemplateExport(response);
} }
@GetMapping("/export/xmindTemplate") @GetMapping("/export/xmindTemplate")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void xmindTemplate(HttpServletResponse response) { public void xmindTemplate(HttpServletResponse response) {

View File

@ -2,6 +2,7 @@ package io.metersphere.track.controller;
import io.metersphere.base.domain.TestCaseNode; import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.track.dto.TestCaseNodeDTO; import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.track.request.testcase.DragNodeRequest; import io.metersphere.track.request.testcase.DragNodeRequest;
import io.metersphere.track.request.testcase.QueryNodeRequest; import io.metersphere.track.request.testcase.QueryNodeRequest;
@ -20,9 +21,12 @@ public class TestCaseNodeController {
@Resource @Resource
TestCaseNodeService testCaseNodeService; TestCaseNodeService testCaseNodeService;
@Resource
private CheckOwnerService checkOwnerService;
@GetMapping("/list/{projectId}") @GetMapping("/list/{projectId}")
public List<TestCaseNodeDTO> getNodeByProjectId(@PathVariable String projectId) { public List<TestCaseNodeDTO> getNodeByProjectId(@PathVariable String projectId) {
checkOwnerService.checkProjectOwner(projectId);
return testCaseNodeService.getNodeTreeByProjectId(projectId); return testCaseNodeService.getNodeTreeByProjectId(projectId);
} }
@ -39,11 +43,13 @@ public class TestCaseNodeController {
@GetMapping("/list/plan/{planId}") @GetMapping("/list/plan/{planId}")
public List<TestCaseNodeDTO> getNodeByPlanId(@PathVariable String planId) { public List<TestCaseNodeDTO> getNodeByPlanId(@PathVariable String planId) {
checkOwnerService.checkTestPlanOwner(planId);
return testCaseNodeService.getNodeByPlanId(planId); return testCaseNodeService.getNodeByPlanId(planId);
} }
@GetMapping("/list/review/{reviewId}") @GetMapping("/list/review/{reviewId}")
public List<TestCaseNodeDTO> getNodeByReviewId(@PathVariable String reviewId) { public List<TestCaseNodeDTO> getNodeByReviewId(@PathVariable String reviewId) {
checkOwnerService.checkTestReviewOwner(reviewId);
return testCaseNodeService.getNodeByReviewId(reviewId); return testCaseNodeService.getNodeByReviewId(reviewId);
} }

View File

@ -9,6 +9,7 @@ import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.track.dto.TestCaseReviewDTO; import io.metersphere.track.dto.TestCaseReviewDTO;
import io.metersphere.track.dto.TestReviewDTOWithMetric; import io.metersphere.track.dto.TestReviewDTOWithMetric;
import io.metersphere.track.request.testreview.ReviewRelevanceRequest; import io.metersphere.track.request.testreview.ReviewRelevanceRequest;
@ -32,6 +33,8 @@ public class TestCaseReviewController {
TestCaseReviewService testCaseReviewService; TestCaseReviewService testCaseReviewService;
@Resource @Resource
TestReviewProjectService testReviewProjectService; TestReviewProjectService testReviewProjectService;
@Resource
CheckOwnerService checkOwnerService;
@PostMapping("/list/{goPage}/{pageSize}") @PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<TestCaseReviewDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryCaseReviewRequest request) { public Pager<List<TestCaseReviewDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryCaseReviewRequest request) {
@ -71,6 +74,7 @@ public class TestCaseReviewController {
@GetMapping("/delete/{reviewId}") @GetMapping("/delete/{reviewId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void deleteCaseReview(@PathVariable String reviewId) { public void deleteCaseReview(@PathVariable String reviewId) {
checkOwnerService.checkTestReviewOwner(reviewId);
testCaseReviewService.deleteCaseReview(reviewId); testCaseReviewService.deleteCaseReview(reviewId);
} }
@ -103,12 +107,14 @@ public class TestCaseReviewController {
@PostMapping("/get/{reviewId}") @PostMapping("/get/{reviewId}")
public TestCaseReview getTestReview(@PathVariable String reviewId) { public TestCaseReview getTestReview(@PathVariable String reviewId) {
checkOwnerService.checkTestReviewOwner(reviewId);
return testCaseReviewService.getTestReview(reviewId); return testCaseReviewService.getTestReview(reviewId);
} }
@PostMapping("/edit/status/{reviewId}") @PostMapping("/edit/status/{reviewId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void editTestPlanStatus(@PathVariable String reviewId) { public void editTestPlanStatus(@PathVariable String reviewId) {
checkOwnerService.checkTestReviewOwner(reviewId);
testCaseReviewService.editTestReviewStatus(reviewId); testCaseReviewService.editTestReviewStatus(reviewId);
} }

View File

@ -8,6 +8,7 @@ import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.service.CheckOwnerService;
import io.metersphere.track.dto.TestCaseReportMetricDTO; import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanDTO; import io.metersphere.track.dto.TestPlanDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric; import io.metersphere.track.dto.TestPlanDTOWithMetric;
@ -32,6 +33,8 @@ public class TestPlanController {
TestPlanService testPlanService; TestPlanService testPlanService;
@Resource @Resource
TestPlanProjectService testPlanProjectService; TestPlanProjectService testPlanProjectService;
@Resource
CheckOwnerService checkOwnerService;
@PostMapping("/list/{goPage}/{pageSize}") @PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<TestPlanDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) { public Pager<List<TestPlanDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
@ -70,6 +73,7 @@ public class TestPlanController {
@PostMapping("/get/{testPlanId}") @PostMapping("/get/{testPlanId}")
public TestPlan getTestPlan(@PathVariable String testPlanId) { public TestPlan getTestPlan(@PathVariable String testPlanId) {
checkOwnerService.checkTestPlanOwner(testPlanId);
return testPlanService.getTestPlan(testPlanId); return testPlanService.getTestPlan(testPlanId);
} }
@ -88,12 +92,14 @@ public class TestPlanController {
@PostMapping("/edit/status/{planId}") @PostMapping("/edit/status/{planId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void editTestPlanStatus(@PathVariable String planId) { public void editTestPlanStatus(@PathVariable String planId) {
checkOwnerService.checkTestPlanOwner(planId);
testPlanService.editTestPlanStatus(planId); testPlanService.editTestPlanStatus(planId);
} }
@PostMapping("/delete/{testPlanId}") @PostMapping("/delete/{testPlanId}")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public int deleteTestPlan(@PathVariable String testPlanId) { public int deleteTestPlan(@PathVariable String testPlanId) {
checkOwnerService.checkTestPlanOwner(testPlanId);
return testPlanService.deleteTestPlan(testPlanId); return testPlanService.deleteTestPlan(testPlanId);
} }
@ -109,6 +115,7 @@ public class TestPlanController {
@GetMapping("/project/name/{planId}") @GetMapping("/project/name/{planId}")
public String getProjectNameByPlanId(@PathVariable String planId) { public String getProjectNameByPlanId(@PathVariable String planId) {
checkOwnerService.checkTestPlanOwner(planId);
return testPlanService.getProjectNameByPlanId(planId); return testPlanService.getProjectNameByPlanId(planId);
} }

View File

@ -9,7 +9,7 @@ import lombok.Setter;
public class TestReviewCaseDTO extends TestCaseWithBLOBs { public class TestReviewCaseDTO extends TestCaseWithBLOBs {
private String reviewer; private String reviewer;
private String reviewerName; private String reviewerName;
private String status; private String reviewStatus;
private String results; private String results;
private String reviewId; private String reviewId;
private String caseId; private String caseId;

View File

@ -9,4 +9,5 @@ public class TestReviewDTOWithMetric extends TestCaseReviewDTO {
private Double testRate; private Double testRate;
private Integer reviewed; private Integer reviewed;
private Integer total; private Integer total;
private Integer pass;
} }

View File

@ -6,8 +6,8 @@ import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper; import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper;
import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper; import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper;
import io.metersphere.commons.constants.TestCaseReviewStatus; import io.metersphere.commons.constants.TestCaseReviewStatus;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.constants.TestReviewCaseStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
@ -32,7 +32,6 @@ import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -272,14 +271,20 @@ public class TestCaseReviewService {
List<Project> projects = projectMapper.selectByExample(projectExample); List<Project> projects = projectMapper.selectByExample(projectExample);
List<String> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList()); List<String> projectIds = projects.stream().map(Project::getId).collect(Collectors.toList());
TestCaseReviewProjectExample testCaseReviewProjectExample = new TestCaseReviewProjectExample(); if (!CollectionUtils.isEmpty(projectIds)) {
testCaseReviewProjectExample.createCriteria().andProjectIdIn(projectIds); TestCaseReviewProjectExample testCaseReviewProjectExample = new TestCaseReviewProjectExample();
List<TestCaseReviewProject> testCaseReviewProjects = testCaseReviewProjectMapper.selectByExample(testCaseReviewProjectExample); testCaseReviewProjectExample.createCriteria().andProjectIdIn(projectIds);
List<String> reviewIds = testCaseReviewProjects.stream().map(TestCaseReviewProject::getReviewId).collect(Collectors.toList()); List<TestCaseReviewProject> testCaseReviewProjects = testCaseReviewProjectMapper.selectByExample(testCaseReviewProjectExample);
List<String> reviewIds = testCaseReviewProjects.stream().map(TestCaseReviewProject::getReviewId).collect(Collectors.toList());
TestCaseReviewExample testCaseReviewExample = new TestCaseReviewExample(); if (!CollectionUtils.isEmpty(reviewIds)) {
testCaseReviewExample.createCriteria().andIdIn(reviewIds); TestCaseReviewExample testCaseReviewExample = new TestCaseReviewExample();
return testCaseReviewMapper.selectByExample(testCaseReviewExample); testCaseReviewExample.createCriteria().andIdIn(reviewIds);
return testCaseReviewMapper.selectByExample(testCaseReviewExample);
}
}
return new ArrayList<>();
} }
public void testReviewRelevance(ReviewRelevanceRequest request) { public void testReviewRelevance(ReviewRelevanceRequest request) {
@ -340,13 +345,13 @@ public class TestCaseReviewService {
testCaseReview.setId(reviewId); testCaseReview.setId(reviewId);
for (String status : statusList) { for (String status : statusList) {
if (StringUtils.equals(status, TestPlanTestCaseStatus.Prepare.name())) { if (StringUtils.equals(status, TestReviewCaseStatus.Prepare.name())) {
testCaseReview.setStatus(TestPlanStatus.Underway.name()); testCaseReview.setStatus(TestCaseReviewStatus.Underway.name());
testCaseReviewMapper.updateByPrimaryKeySelective(testCaseReview); testCaseReviewMapper.updateByPrimaryKeySelective(testCaseReview);
return; return;
} }
} }
testCaseReview.setStatus(TestPlanStatus.Completed.name()); testCaseReview.setStatus(TestCaseReviewStatus.Completed.name());
SaveTestCaseReviewRequest testCaseReviewRequest = new SaveTestCaseReviewRequest(); SaveTestCaseReviewRequest testCaseReviewRequest = new SaveTestCaseReviewRequest();
TestCaseReview _testCaseReview = testCaseReviewMapper.selectByPrimaryKey(reviewId); TestCaseReview _testCaseReview = testCaseReviewMapper.selectByPrimaryKey(reviewId);
List<String> userIds = new ArrayList<>(); List<String> userIds = new ArrayList<>();
@ -405,16 +410,19 @@ public class TestCaseReviewService {
testReview.setReviewed(0); testReview.setReviewed(0);
testReview.setTotal(0); testReview.setTotal(0);
testReview.setPass(0);
if (testCases != null) { if (testCases != null) {
testReview.setTotal(testCases.size()); testReview.setTotal(testCases.size());
testCases.forEach(testCase -> { testCases.forEach(testCase -> {
if (!StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Prepare.name()) if (!StringUtils.equals(testCase.getReviewStatus(), TestReviewCaseStatus.Prepare.name())) {
&& !StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Underway.name())) {
testReview.setReviewed(testReview.getReviewed() + 1); testReview.setReviewed(testReview.getReviewed() + 1);
} }
if (StringUtils.equals(testCase.getReviewStatus(), TestReviewCaseStatus.Pass.name())) {
testReview.setPass(testReview.getPass() + 1);
}
}); });
} }
testReview.setTestRate(MathUtils.getPercentWithDecimal(testReview.getTotal() == 0 ? 0 : testReview.getReviewed() * 1.0 / testReview.getTotal()));
}); });
} }
return testReviews; return testReviews;

View File

@ -10,6 +10,7 @@ import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper; import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.constants.TestCaseReviewStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
@ -40,7 +41,9 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.*; import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -88,6 +91,7 @@ public class TestCaseService {
testCase.setCreateTime(System.currentTimeMillis()); testCase.setCreateTime(System.currentTimeMillis());
testCase.setUpdateTime(System.currentTimeMillis()); testCase.setUpdateTime(System.currentTimeMillis());
testCase.setNum(getNextNum(testCase.getProjectId())); testCase.setNum(getNextNum(testCase.getProjectId()));
testCase.setReviewStatus(TestCaseReviewStatus.Prepare.name());
testCaseMapper.insert(testCase); testCaseMapper.insert(testCase);
} }
@ -273,6 +277,8 @@ public class TestCaseService {
.map(TestCase::getName) .map(TestCase::getName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
List<ExcelErrData<TestCaseExcelData>> errList = null; List<ExcelErrData<TestCaseExcelData>> errList = null;
if (multipartFile == null)
MSException.throwException(Translator.get("upload_fail"));
if (multipartFile.getOriginalFilename().endsWith(".xmind")) { if (multipartFile.getOriginalFilename().endsWith(".xmind")) {
try { try {
@ -284,6 +290,11 @@ public class TestCaseService {
ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail") + "" + processLog); ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail") + "" + processLog);
errList.add(excelErrData); errList.add(excelErrData);
excelResponse.setErrList(errList); excelResponse.setErrList(errList);
} else if (xmindParser.getNodePaths().isEmpty() && xmindParser.getTestCase().isEmpty()) {
excelResponse.setSuccess(false);
ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail") + "" + Translator.get("upload_content_is_null"));
errList.add(excelErrData);
excelResponse.setErrList(errList);
} else { } else {
if (!xmindParser.getNodePaths().isEmpty()) { if (!xmindParser.getNodePaths().isEmpty()) {
testCaseNodeService.createNodes(xmindParser.getNodePaths(), projectId); testCaseNodeService.createNodes(xmindParser.getNodePaths(), projectId);
@ -295,7 +306,8 @@ public class TestCaseService {
excelResponse.setSuccess(true); excelResponse.setSuccess(true);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
} }
} else { } else {
@ -339,6 +351,7 @@ public class TestCaseService {
testcase.setNodeId(nodePathMap.get(testcase.getNodePath())); testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
testcase.setSort(sort.getAndIncrement()); testcase.setSort(sort.getAndIncrement());
testcase.setNum(num.decrementAndGet()); testcase.setNum(num.decrementAndGet());
testcase.setReviewStatus(TestCaseReviewStatus.Prepare.name());
mapper.insert(testcase); mapper.insert(testcase);
}); });
} }
@ -481,7 +494,7 @@ public class TestCaseService {
if (t.getTestId() != null && t.getTestId().equals("other")) { if (t.getTestId() != null && t.getTestId().equals("other")) {
data.setRemark(t.getOtherTestName()); data.setRemark(t.getOtherTestName());
} else { } else {
data.setRemark(t.getApiName()); data.setRemark("[" + t.getApiName() + "]" + "\n" + t.getRemark());
} }
} else if (t.getMethod().equals("auto") && t.getType().equals("performance")) { } else if (t.getMethod().equals("auto") && t.getType().equals("performance")) {

View File

@ -1,6 +1,7 @@
package io.metersphere.track.service; package io.metersphere.track.service;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseReviewMapper; import io.metersphere.base.mapper.TestCaseReviewMapper;
import io.metersphere.base.mapper.TestCaseReviewTestCaseMapper; import io.metersphere.base.mapper.TestCaseReviewTestCaseMapper;
import io.metersphere.base.mapper.TestCaseReviewUsersMapper; import io.metersphere.base.mapper.TestCaseReviewUsersMapper;
@ -38,6 +39,8 @@ public class TestReviewTestCaseService {
TestCaseReviewMapper testCaseReviewMapper; TestCaseReviewMapper testCaseReviewMapper;
@Resource @Resource
TestCaseReviewService testCaseReviewService; TestCaseReviewService testCaseReviewService;
@Resource
TestCaseMapper testCaseMapper;
public List<TestReviewCaseDTO> list(QueryCaseReviewRequest request) { public List<TestReviewCaseDTO> list(QueryCaseReviewRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -111,9 +114,17 @@ public class TestReviewTestCaseService {
MSException.throwException("此用例评审已到截止时间!"); MSException.throwException("此用例评审已到截止时间!");
} }
// 记录测试用例评审状态变更
testCaseReviewTestCase.setStatus(testCaseReviewTestCase.getStatus()); testCaseReviewTestCase.setStatus(testCaseReviewTestCase.getStatus());
testCaseReviewTestCase.setReviewer(SessionUtils.getUser().getId()); testCaseReviewTestCase.setReviewer(SessionUtils.getUser().getId());
testCaseReviewTestCase.setUpdateTime(System.currentTimeMillis()); testCaseReviewTestCase.setUpdateTime(System.currentTimeMillis());
testCaseReviewTestCaseMapper.updateByPrimaryKeySelective(testCaseReviewTestCase); testCaseReviewTestCaseMapper.updateByPrimaryKeySelective(testCaseReviewTestCase);
// 修改用例评审状态
String caseId = testCaseReviewTestCase.getCaseId();
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
testCase.setId(caseId);
testCase.setReviewStatus(testCaseReviewTestCase.getStatus());
testCaseMapper.updateByPrimaryKeySelective(testCase);
} }
} }

View File

@ -33,13 +33,10 @@ public class XmindCaseParser {
private StringBuffer process; // 过程校验记录 private StringBuffer process; // 过程校验记录
// 已存在用例名称 // 已存在用例名称
private Set<String> testCaseNames; private Set<String> testCaseNames;
// 转换后的案例信息 // 转换后的案例信息
private List<TestCaseWithBLOBs> testCases; private List<TestCaseWithBLOBs> testCases;
// 案例详情重写了hashCode方法去重用 // 案例详情重写了hashCode方法去重用
private List<TestCaseExcelData> compartDatas; private List<TestCaseExcelData> compartDatas;
// 记录没有用例的目录 // 记录没有用例的目录
private List<String> nodePaths; private List<String> nodePaths;
@ -54,7 +51,10 @@ public class XmindCaseParser {
nodePaths = new ArrayList<>(); nodePaths = new ArrayList<>();
} }
// 这里清理是为了 加快jvm 回收 private static final String TC_REGEX = "(?:tc:|tc|tc)";
private static final String PC_REGEX = "(?:pc:|pc|pc)";
private static final String RC_REGEX = "(?:rc:|rc|rc)";
public void clear() { public void clear() {
compartDatas.clear(); compartDatas.clear();
testCases.clear(); testCases.clear();
@ -90,9 +90,9 @@ public class XmindCaseParser {
} }
// 递归处理案例数据 // 递归处理案例数据
private void recursion(StringBuffer processBuffer, Attached parent, int level, List<Attached> attacheds) { private void recursion(Attached parent, int level, List<Attached> attacheds) {
for (Attached item : attacheds) { for (Attached item : attacheds) {
if (isAvailable(item.getTitle(), "(?:tc|tc:|tc)")) { // 用例 if (isAvailable(item.getTitle(), TC_REGEX)) { // 用例
item.setParent(parent); item.setParent(parent);
this.newTestCase(item.getTitle(), parent.getPath(), item.getChildren() != null ? item.getChildren().getAttached() : null); this.newTestCase(item.getTitle(), parent.getPath(), item.getChildren() != null ? item.getChildren().getAttached() : null);
} else { } else {
@ -100,7 +100,7 @@ public class XmindCaseParser {
item.setPath(nodePath); item.setPath(nodePath);
item.setParent(parent); item.setParent(parent);
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) { if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) {
recursion(processBuffer, item, level + 1, item.getChildren().getAttached()); recursion(item, level + 1, item.getChildren().getAttached());
} else { } else {
if (!nodePath.startsWith("/")) { if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath; nodePath = "/" + nodePath;
@ -163,7 +163,7 @@ public class XmindCaseParser {
return; return;
} }
// 用例名称 // 用例名称
testCase.setName(this.replace(tcArr[1], "tc:|tc|tc")); testCase.setName(this.replace(tcArr[1], TC_REGEX));
if (!nodePath.startsWith("/")) { if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath; nodePath = "/" + nodePath;
@ -188,10 +188,10 @@ public class XmindCaseParser {
List<Attached> steps = new LinkedList<>(); List<Attached> steps = new LinkedList<>();
if (attacheds != null && !attacheds.isEmpty()) { if (attacheds != null && !attacheds.isEmpty()) {
attacheds.forEach(item -> { attacheds.forEach(item -> {
if (isAvailable(item.getTitle(), "(?:pc:|pc)")) { if (isAvailable(item.getTitle(), PC_REGEX)) {
testCase.setPrerequisite(replace(item.getTitle(), "(?:pc:|pc)")); testCase.setPrerequisite(replace(item.getTitle(), PC_REGEX));
} else if (isAvailable(item.getTitle(), "(?:rc:|rc)")) { } else if (isAvailable(item.getTitle(), RC_REGEX)) {
testCase.setRemark(replace(item.getTitle(), "(?:rc:|rc)")); testCase.setRemark(replace(item.getTitle(), RC_REGEX));
} else { } else {
steps.add(item); steps.add(item);
} }
@ -267,28 +267,37 @@ public class XmindCaseParser {
// 导入思维导图处理 // 导入思维导图处理
public String parse(MultipartFile multipartFile) { public String parse(MultipartFile multipartFile) {
StringBuffer processBuffer = new StringBuffer();
try { try {
// 获取思维导图内容 // 获取思维导图内容
JsonRootBean root = XmindParser.parseObject(multipartFile); List<JsonRootBean> roots = XmindParser.parseObject(multipartFile);
if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) { for (JsonRootBean root : roots) {
// 判断是模块还是用例 if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) {
for (Attached item : root.getRootTopic().getChildren().getAttached()) { // 判断是模块还是用例
if (isAvailable(item.getTitle(), "(?:tc:|tc|tc)")) { // 用例 for (Attached item : root.getRootTopic().getChildren().getAttached()) {
return replace(item.getTitle(), "(?:tc:|tc|tc)") + "" + Translator.get("test_case_create_module_fail"); if (isAvailable(item.getTitle(), TC_REGEX)) { // 用例
} else { return replace(item.getTitle(), TC_REGEX) + "" + Translator.get("test_case_create_module_fail");
item.setPath(item.getTitle()); } else {
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) { String nodePath = item.getTitle();
recursion(processBuffer, item, 1, item.getChildren().getAttached()); item.setPath(nodePath);
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) {
recursion(item, 1, item.getChildren().getAttached());
} else {
if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath;
}
if (nodePath.endsWith("/")) {
nodePath = nodePath.substring(0, nodePath.length() - 1);
}
nodePaths.add(nodePath); // 没有用例的路径
}
} }
} }
} }
} }
this.validate();
this.validate(); //检查目录合规性
} catch (Exception ex) { } catch (Exception ex) {
processBuffer.append(Translator.get("incorrect_format")); return ex.getMessage();
LogUtil.error(ex.getMessage());
return "xmind "+Translator.get("incorrect_format");
} }
return process.toString(); return process.toString();
} }

View File

@ -5,75 +5,83 @@ import org.json.JSONObject;
import org.json.XML; import org.json.XML;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class XmindLegacy { public class XmindLegacy {
/** /**
* 返回content.xml和comments.xml合并后的json * 返回content.xml和comments.xml合并后的json
* *
* @param xmlContent * @param xmlContent
* @param xmlComments * @param xmlComments
* @return * @return
* @throws IOException * @throws IOException
* @throws DocumentException * @throws DocumentException
*/ */
public static String getContent(String xmlContent, String xmlComments) throws IOException, DocumentException { public static List<String> getContent(String xmlContent, String xmlComments) throws IOException, DocumentException {
// 删除content.xml里面不能识别的字符串 // 删除content.xml里面不能识别的字符串
xmlContent = xmlContent.replace("xmlns=\"urn:xmind:xmap:xmlns:content:2.0\"", ""); xmlContent = xmlContent.replace("xmlns=\"urn:xmind:xmap:xmlns:content:2.0\"", "");
xmlContent = xmlContent.replace("xmlns:fo=\"http://www.w3.org/1999/XSL/Format\"", ""); xmlContent = xmlContent.replace("xmlns:fo=\"http://www.w3.org/1999/XSL/Format\"", "");
// 删除<topic>节点 // 删除<topic>节点
xmlContent = xmlContent.replace("<topics type=\"attached\">", ""); xmlContent = xmlContent.replace("<topics type=\"attached\">", "");
xmlContent = xmlContent.replace("</topics>", ""); xmlContent = xmlContent.replace("</topics>", "");
// 去除title中svg:width属性 // 去除title中svg:width属性
xmlContent = xmlContent.replaceAll("<title svg:width=\"[0-9]*\">", "<title>"); xmlContent = xmlContent.replaceAll("<title svg:width=\"[0-9]*\">", "<title>");
//去除自由风格主题
xmlContent = xmlContent.replaceAll("<topics type=\"detached\">", "");
Document document = DocumentHelper.parseText(xmlContent);// 读取XML文件,获得document对象 Document document = DocumentHelper.parseText(xmlContent);// 读取XML文件,获得document对象
Element root = document.getRootElement(); Element root = document.getRootElement();
List<Node> topics = root.selectNodes("//topic"); List<Node> topics = root.selectNodes("//topic");
if (xmlComments != null) { if (xmlComments != null) {
// 删除comments.xml里面不能识别的字符串 // 删除comments.xml里面不能识别的字符串
xmlComments = xmlComments.replace("xmlns=\"urn:xmind:xmap:xmlns:comments:2.0\"", ""); xmlComments = xmlComments.replace("xmlns=\"urn:xmind:xmap:xmlns:comments:2.0\"", "");
// 添加评论到content中 // 添加评论到content中
Document commentDocument = DocumentHelper.parseText(xmlComments); Document commentDocument = DocumentHelper.parseText(xmlComments);
List<Node> commentsList = commentDocument.selectNodes("//comment"); List<Node> commentsList = commentDocument.selectNodes("//comment");
for (Node topic : topics) { for (Node topic : topics) {
for (Node commentNode : commentsList) { for (Node commentNode : commentsList) {
Element commentElement = (Element) commentNode; Element commentElement = (Element) commentNode;
Element topicElement = (Element) topic; Element topicElement = (Element) topic;
if (topicElement.attribute("id").getValue() if (topicElement.attribute("id").getValue()
.equals(commentElement.attribute("object-id").getValue())) { .equals(commentElement.attribute("object-id").getValue())) {
Element comment = topicElement.addElement("comments"); Element comment = topicElement.addElement("comments");
comment.addAttribute("creationTime", commentElement.attribute("time").getValue()); comment.addAttribute("creationTime", commentElement.attribute("time").getValue());
comment.addAttribute("author", commentElement.attribute("author").getValue()); comment.addAttribute("author", commentElement.attribute("author").getValue());
comment.addAttribute("content", commentElement.element("content").getText()); comment.addAttribute("content", commentElement.element("content").getText());
} }
} }
} }
} }
// 第一个topic转换为json中的rootTopic // 第一个topic转换为json中的rootTopic
Node rootTopic = root.selectSingleNode("/xmap-content/sheet/topic"); List<Node> rootTopics = root.selectNodes("/xmap-content/sheet/topic");
rootTopic.setName("rootTopic"); for (Node rootTopic : rootTopics) {
rootTopic.setName("rootTopic");
// 将xml中topic节点转换为attached节点 // 将xml中topic节点转换为attached节点
List<Node> topicList = rootTopic.selectNodes("//topic"); List<Node> topicList = rootTopic.selectNodes("//topic");
for (Node node : topicList) {
node.setName("attached");
}
for (Node node : topicList) { }
node.setName("attached");
} List<String> sheets = new ArrayList<>();
// 选取第一个sheet for (Element sheet : root.elements("sheet")) {
Element sheet = root.elements("sheet").get(0); String res = sheet.asXML();
String res = sheet.asXML(); // 将xml转为json
// 将xml转为json JSONObject xmlJSONObj = XML.toJSONObject(res);
JSONObject xmlJSONObj = XML.toJSONObject(res); JSONObject jsonObject = xmlJSONObj.getJSONObject("sheet");
JSONObject jsonObject = xmlJSONObj.getJSONObject("sheet"); sheets.add(jsonObject.toString(4));
// 设置缩进 }
return jsonObject.toString(4); // 设置缩进
} return sheets;
}
} }

View File

@ -34,61 +34,68 @@ public class XmindParser {
* @throws ArchiveException * @throws ArchiveException
* @throws DocumentException * @throws DocumentException
*/ */
public static String parseJson(MultipartFile multipartFile) throws IOException, ArchiveException, DocumentException { public static List<String> parseJson(MultipartFile multipartFile) throws IOException, ArchiveException, DocumentException {
File file = FileUtil.multipartFileToFile(multipartFile); File file = FileUtil.multipartFileToFile(multipartFile);
List<String> contents = null;
String res = null;
if (file == null || !file.exists()) if (file == null || !file.exists())
MSException.throwException(Translator.get("incorrect_format")); MSException.throwException(Translator.get("incorrect_format"));
try {
String res = ZipUtils.extract(file); res = ZipUtils.extract(file);
String content = null; if (isXmindZen(res, file)) {
if (isXmindZen(res, file)) { contents = (getXmindZenContent(file, res));
content = getXmindZenContent(file, res); } else {
} else { contents = getXmindLegacyContent(file, res);
content = getXmindLegacyContent(file, res); }
} catch (Exception e) {
MSException.throwException(e.getMessage());
} finally {
// 删除生成的文件夹
if (res != null) {
File dir = new File(res);
FileUtil.deleteDir(dir);
}
// 删除零时文件
if (file != null)
file.delete();
} }
return contents;
// 删除生成的文件夹
File dir = new File(res);
FileUtil.deleteDir(dir);
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
// 删除零时文件
if (file != null)
file.delete();
String json = (JSON.toJSONString(jsonRootBean, false));
if (StringUtils.isEmpty(content) || content.split("(?:tc:|tc|TC:|TC|tc|TC)").length == 1) {
MSException.throwException(Translator.get("import_xmind_not_found"));
}
if (!StringUtils.isEmpty(content) && content.split("(?:tc:|tc|TC:|TC|tc|TC)").length > 500) {
MSException.throwException(Translator.get("import_xmind_count_error"));
}
return json;
} }
public static JsonRootBean parseObject(MultipartFile multipartFile) throws DocumentException, ArchiveException, IOException { public static List<JsonRootBean> parseObject(MultipartFile multipartFile) throws DocumentException, ArchiveException, IOException {
String content = parseJson(multipartFile); List<String> contents = parseJson(multipartFile);
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class); int caseCount = 0;
return jsonRootBean; List<JsonRootBean> jsonRootBeans = new ArrayList<>();
if (contents != null) {
for (String content : contents) {
caseCount += content.split("(?:tc:|tc|TC:|TC|tc|TC)").length;
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
jsonRootBeans.add(jsonRootBean);
}
if (caseCount > 500) {
MSException.throwException(Translator.get("import_xmind_count_error"));
}
}
return jsonRootBeans;
} }
/** /**
* @return * @return
*/ */
public static String getXmindZenContent(File file, String extractFileDir) public static List<String> getXmindZenContent(File file, String extractFileDir)
throws IOException, ArchiveException { throws IOException, ArchiveException {
List<String> keys = new ArrayList<>(); List<String> keys = new ArrayList<>();
keys.add(xmindZenJson); keys.add(xmindZenJson);
Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir); Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir);
String content = map.get(xmindZenJson); String content = map.get(xmindZenJson);
content = XmindZen.getContent(content); return XmindZen.getContent(content);
return content;
} }
/** /**
* @return * @return
*/ */
public static String getXmindLegacyContent(File file, String extractFileDir) public static List<String> getXmindLegacyContent(File file, String extractFileDir)
throws IOException, ArchiveException, DocumentException { throws IOException, ArchiveException, DocumentException {
List<String> keys = new ArrayList<>(); List<String> keys = new ArrayList<>();
keys.add(xmindLegacyContent); keys.add(xmindLegacyContent);
@ -97,7 +104,7 @@ public class XmindParser {
String contentXml = map.get(xmindLegacyContent); String contentXml = map.get(xmindLegacyContent);
String commentsXml = map.get(xmindLegacyComments); String commentsXml = map.get(xmindLegacyComments);
String xmlContent = XmindLegacy.getContent(contentXml, commentsXml); List<String> xmlContent = XmindLegacy.getContent(contentXml, commentsXml);
return xmlContent; return xmlContent;
} }

View File

@ -5,61 +5,68 @@ import com.alibaba.fastjson.JSONObject;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class XmindZen { public class XmindZen {
/** /**
* @param jsonContent * @param jsonContent
* @return * @return
* @throws IOException * @throws IOException
* @throws DocumentException * @throws DocumentException
*/ */
public static String getContent(String jsonContent) { public static List<String> getContent(String jsonContent) {
JSONObject jsonObject = JSONArray.parseArray(jsonContent).getJSONObject(0); JSONArray jsonArray = JSONArray.parseArray(jsonContent);//.getJSONObject(0);
JSONObject rootTopic = jsonObject.getJSONObject("rootTopic"); List<String> contents = new ArrayList<>();
transferNotes(rootTopic); for (int i = 0; i < jsonArray.size(); i++) {
JSONObject children = rootTopic.getJSONObject("children"); JSONObject jsonObject = (JSONObject) jsonArray.get(i);
recursionChildren(children); JSONObject rootTopic = jsonObject.getJSONObject("rootTopic");
return jsonObject.toString(); transferNotes(rootTopic);
} JSONObject children = rootTopic.getJSONObject("children");
recursionChildren(children);
contents.add(jsonObject.toString());
}
return contents;
}
/** /**
* 递归转换children * 递归转换children
* *
* @param children * @param children
*/ */
private static void recursionChildren(JSONObject children) { private static void recursionChildren(JSONObject children) {
if (children == null) { if (children == null) {
return; return;
} }
JSONArray attachedArray = children.getJSONArray("attached"); JSONArray attachedArray = children.getJSONArray("attached");
if (attachedArray == null) { if (attachedArray == null) {
return; return;
} }
for (Object attached : attachedArray) { for (Object attached : attachedArray) {
JSONObject attachedObject = (JSONObject) attached; JSONObject attachedObject = (JSONObject) attached;
transferNotes(attachedObject); transferNotes(attachedObject);
JSONObject childrenObject = attachedObject.getJSONObject("children"); JSONObject childrenObject = attachedObject.getJSONObject("children");
if (childrenObject == null) { if (childrenObject == null) {
continue; continue;
} }
recursionChildren(childrenObject); recursionChildren(childrenObject);
} }
} }
private static void transferNotes(JSONObject object) { private static void transferNotes(JSONObject object) {
JSONObject notes = object.getJSONObject("notes"); JSONObject notes = object.getJSONObject("notes");
if (notes == null) { if (notes == null) {
return; return;
} }
JSONObject plain = notes.getJSONObject("plain"); JSONObject plain = notes.getJSONObject("plain");
if (plain != null) { if (plain != null) {
String content = plain.getString("content"); String content = plain.getString("content");
notes.remove("plain"); notes.remove("plain");
notes.put("content", content); notes.put("content", content);
} else { } else {
notes.put("content", null); notes.put("content", null);
} }
} }
} }

@ -1 +1 @@
Subproject commit c2dacf960cdb1ed35664bdd3432120b1203b73d8 Subproject commit cf6b06526324326a563d933e07118fac014a63b4

View File

@ -0,0 +1,3 @@
alter table test_case add review_status varchar(25) null;
update test_case set review_status = 'Prepare' where review_status is null;

View File

@ -0,0 +1,2 @@
ALTER TABLE test_case
MODIFY maintainer varchar(50) NOT NULL COMMENT 'Test case maintainer';

View File

@ -158,3 +158,10 @@ license_valid_license_error=Authorization authentication failed
timing_task_result_notification=Timing task result notification timing_task_result_notification=Timing task result notification
test_review_task_notice=Test review task notice test_review_task_notice=Test review task notice
test_track.length_less_than=The title is too long, the length must be less than test_track.length_less_than=The title is too long, the length must be less than
# check owner
check_owner_project=The current user does not have permission to operate this project
check_owner_test=The current user does not have permission to operate this test
check_owner_case=The current user does not have permission to operate this use case
check_owner_plan=The current user does not have permission to operate this plan
check_owner_review=The current user does not have permission to operate this review
upload_content_is_null=Imported content is empty

View File

@ -158,4 +158,10 @@ import_xmind_not_found=未找到测试用例
timing_task_result_notification=定时任务结果通知 timing_task_result_notification=定时任务结果通知
test_review_task_notice=测试评审任务通知 test_review_task_notice=测试评审任务通知
test_track.length_less_than=标题过长,字数必须小于 test_track.length_less_than=标题过长,字数必须小于
# check owner
check_owner_project=当前用户没有操作此项目的权限
check_owner_test=当前用户没有操作此测试的权限
check_owner_case=当前用户没有操作此用例的权限
check_owner_plan=当前用户没有操作此计划的权限
check_owner_review=当前用户没有操作此评审的权限
upload_content_is_null=导入内容为空

View File

@ -158,4 +158,11 @@ import_xmind_count_error=思維導圖導入用例數量不能超過 500 條
import_xmind_not_found=未找到测试用例 import_xmind_not_found=未找到测试用例
timing_task_result_notification=定時任務結果通知 timing_task_result_notification=定時任務結果通知
test_review_task_notice=測試評審任務通知 test_review_task_notice=測試評審任務通知
test_track.length_less_than=標題過長,字數必須小於 test_track.length_less_than=標題過長,字數必須小於
# check owner
check_owner_project=當前用戶沒有操作此項目的權限
check_owner_test=當前用戶沒有操作此測試的權限
check_owner_case=當前用戶沒有操作此用例的權限
check_owner_plan=當前用戶沒有操作此計劃的權限
check_owner_review=當前用戶沒有操作此評審的權限
upload_content_is_null=導入內容為空

View File

@ -32,7 +32,8 @@
"js-base64": "^3.4.4", "js-base64": "^3.4.4",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"html2canvas": "^1.0.0-rc.7", "html2canvas": "^1.0.0-rc.7",
"jspdf": "^2.1.1" "jspdf": "^2.1.1",
"yan-progress": "^1.0.3"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0", "@vue/cli-plugin-babel": "^4.1.0",

View File

@ -1,5 +1,5 @@
<template> <template>
<div id="menu-bar"> <div id="menu-bar" v-if="isRouterAlive">
<el-row type="flex"> <el-row type="flex">
<el-col :span="8"> <el-col :span="8">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'> <el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
@ -7,7 +7,7 @@
{{ $t("i18n.home") }} {{ $t("i18n.home") }}
</el-menu-item> </el-menu-item>
<el-submenu v-permission="['test_manager','test_user','test_viewer']" index="3"> <el-submenu :class="{'deactivation':!isProjectActivation}" v-permission="['test_manager','test_user','test_viewer']" index="3">
<template v-slot:title>{{ $t('commons.project') }}</template> <template v-slot:title>{{ $t('commons.project') }}</template>
<ms-recent-list ref="projectRecent" :options="projectRecent"/> <ms-recent-list ref="projectRecent" :options="projectRecent"/>
<el-divider class="menu-divider"/> <el-divider class="menu-divider"/>
@ -21,6 +21,7 @@
<ms-recent-list ref="testRecent" :options="testRecent"/> <ms-recent-list ref="testRecent" :options="testRecent"/>
<el-divider class="menu-divider"/> <el-divider class="menu-divider"/>
<ms-show-all :index="'/api/test/list/all'"/> <ms-show-all :index="'/api/test/list/all'"/>
<el-menu-item :index="apiTestProjectPath" class="blank_item"></el-menu-item>
<ms-create-button v-permission="['test_manager','test_user']" :index="'/api/test/create'" <ms-create-button v-permission="['test_manager','test_user']" :index="'/api/test/create'"
:title="$t('load_test.create')"/> :title="$t('load_test.create')"/>
</el-submenu> </el-submenu>
@ -84,7 +85,15 @@ export default {
index: function (item) { index: function (item) {
return '/api/report/view/' + item.id; return '/api/report/view/' + item.id;
} }
} },
isProjectActivation: true,
isRouterAlive: true,
apiTestProjectPath: '',
}
},
watch: {
'$route'(to) {
this.init();
} }
}, },
methods: { methods: {
@ -98,7 +107,25 @@ export default {
this.$refs.testRecent.recent(); this.$refs.testRecent.recent();
this.$refs.reportRecent.recent(); this.$refs.reportRecent.recent();
}); });
} },
reload() {
this.isRouterAlive = false;
this.$nextTick(function () {
this.isRouterAlive = true;
});
},
init() {
let path = this.$route.path;
if (path.indexOf("/api/test/list") >= 0 && !!this.$route.params.projectId) {
this.apiTestProjectPath = path;
//
this.isProjectActivation = false;
this.reload();
} else {
this.isProjectActivation = true;
}
},
}, },
mounted() { mounted() {
this.registerEvents(); this.registerEvents();
@ -108,12 +135,20 @@ export default {
</script> </script>
<style scoped> <style scoped>
#menu-bar { #menu-bar {
border-bottom: 1px solid #E6E6E6; border-bottom: 1px solid #E6E6E6;
background-color: #FFF; background-color: #FFF;
} }
.menu-divider { .menu-divider {
margin: 0; margin: 0;
} }
.blank_item {
display: none;
}
.deactivation >>> .el-submenu__title {
border-bottom: white !important;
}
</style> </style>

View File

@ -3,7 +3,7 @@
<template v-slot:header> <template v-slot:header>
<span class="title">{{$t('api_report.title')}}</span> <span class="title">{{$t('api_report.title')}}</span>
</template> </template>
<el-table border :data="tableData" class="adjust-table table-content" @row-click="link"> <el-table border :data="tableData" class="adjust-table table-content" @row-click="link" height="300px">
<el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/> <el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/>
<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"> <template v-slot:default="scope">

View File

@ -3,7 +3,7 @@
<template v-slot:header> <template v-slot:header>
<span class="title">{{$t('commons.test')}}</span> <span class="title">{{$t('commons.test')}}</span>
</template> </template>
<el-table border :data="tableData" class="adjust-table table-content" @row-click="link"> <el-table border :data="tableData" class="adjust-table table-content" @row-click="link" height="300px">
<el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/> <el-table-column prop="name" :label="$t('commons.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 prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip/>
<el-table-column width="250" :label="$t('commons.create_time')"> <el-table-column width="250" :label="$t('commons.create_time')">

View File

@ -69,7 +69,6 @@ export default {
}, },
mounted() { mounted() {
console.log(this.response.headers);
if (!this.response.headers) { if (!this.response.headers) {
return; return;
} }

View File

@ -70,348 +70,348 @@
</template> </template>
<script> <script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig"; import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Scenario, Test} from "./model/ScenarioModel" import {Scenario, Test} from "./model/ScenarioModel"
import MsApiReportStatus from "../report/ApiReportStatus"; import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog"; import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils"; import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
import MsScheduleConfig from "../../common/components/MsScheduleConfig"; import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import ApiImport from "./components/import/ApiImport"; import ApiImport from "./components/import/ApiImport";
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent"; import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
import MsContainer from "@/business/components/common/components/MsContainer"; import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer"; import MsMainContainer from "@/business/components/common/components/MsMainContainer";
export default { export default {
name: "MsApiTestConfig", name: "MsApiTestConfig",
components: { components: {
MsMainContainer, MsMainContainer,
MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig
},
props: ["id"],
data() {
return {
reportVisible: false,
create: false,
result: {},
projects: [],
change: false,
test: new Test(),
isReadOnly: false,
debugReportId: ''
}
},
watch: {
'$route': 'init',
test: {
handler: function () {
this.change = true;
},
deep: true
}
},
methods: {
init() {
let projectId;
this.isReadOnly = !checkoutTestManagerOrTestUser();
if (this.id) {
this.create = false;
this.getTest(this.id);
} else {
this.create = true;
this.test = new Test();
if (this.$refs.config) {
this.$refs.config.reset();
}
//
projectId = this.$store.state.common.projectId;
}
this.result = this.$get("/project/listAll", response => {
this.projects = response.data;
//
if (projectId) this.test.projectId = projectId;
})
}, },
updateReference() {
let updateIds = [];
this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) {
updateIds.push(scenario.id.split("#")[0]);
}
})
if (updateIds.length === 0) return; props: ["id"],
//
this.result = this.$post("/api/list/ids", {ids: updateIds}, response => {
let scenarioMap = {};
if (response.data) {
response.data.forEach(test => {
JSON.parse(test.scenarioDefinition).forEach(options => {
let referenceId = test.id + "#" + options.id;
scenarioMap[referenceId] = new Scenario(options);
scenarioMap[referenceId].id = referenceId;
})
})
}
let scenarios = []; data() {
return {
reportVisible: false,
create: false,
result: {},
projects: [],
change: false,
test: new Test(),
isReadOnly: false,
debugReportId: ''
}
},
watch: {
'$route': 'init',
test: {
handler: function () {
this.change = true;
},
deep: true
}
},
methods: {
init() {
let projectId;
this.isReadOnly = !checkoutTestManagerOrTestUser();
if (this.id) {
this.create = false;
this.getTest(this.id);
} else {
this.create = true;
this.test = new Test();
if (this.$refs.config) {
this.$refs.config.reset();
}
//
projectId = this.$store.state.common.projectId;
}
this.result = this.$get("/project/listAll", response => {
this.projects = response.data;
//
if (projectId) this.test.projectId = projectId;
})
},
updateReference() {
let updateIds = [];
this.test.scenarioDefinition.forEach(scenario => { this.test.scenarioDefinition.forEach(scenario => {
if (scenario.isReference()) { if (scenario.isReference()) {
if (scenarioMap[scenario.id]) scenarios.push(scenarioMap[scenario.id]); updateIds.push(scenario.id.split("#")[0]);
} else {
scenarios.push(scenario);
} }
}) })
this.test.scenarioDefinition = scenarios;
})
},
getTest(id) {
this.result = this.$get("/api/get/" + id, response => {
if (response.data) {
let item = response.data;
this.test = new Test({ if (updateIds.length === 0) return;
id: item.id, //
projectId: item.projectId, this.result = this.$post("/api/list/ids", {ids: updateIds}, response => {
name: item.name, let scenarioMap = {};
status: item.status, if (response.data) {
scenarioDefinition: JSON.parse(item.scenarioDefinition), response.data.forEach(test => {
schedule: item.schedule ? item.schedule : {}, JSON.parse(test.scenarioDefinition).forEach(options => {
}); let referenceId = test.id + "#" + options.id;
this.updateReference(); scenarioMap[referenceId] = new Scenario(options);
scenarioMap[referenceId].id = referenceId;
})
})
}
this.$refs.config.reset(); let scenarios = [];
} this.test.scenarioDefinition.forEach(scenario => {
}); if (scenario.isReference()) {
}, if (scenarioMap[scenario.id]) scenarios.push(scenarioMap[scenario.id]);
save(callback) { } else {
let validator = this.test.isValid(); scenarios.push(scenario);
if (!validator.isValid) { }
this.$warning(this.$t(validator.info));
return;
}
this.change = false;
let bodyFiles = this.getBodyUploadFiles();
let url = this.create ? "/api/create" : "/api/update";
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.result = this.$fileUpload(url, file, bodyFiles, this.test, () => {
if (callback) callback();
this.create = false;
this.resetBodyFile();
});
},
saveTest() {
this.save(() => {
this.$success(this.$t('commons.save_success'));
if (this.create) {
this.$router.push({
path: '/api/test/edit?id=' + this.test.id
}) })
} this.test.scenarioDefinition = scenarios;
// 广 head
ApiEvent.$emit(LIST_CHANGE);
})
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('api_test.running'));
this.$router.push({
path: '/api/report/view/' + response.data
}) })
}); },
}, getTest(id) {
saveRunTest() { this.result = this.$get("/api/get/" + id, response => {
this.change = false; if (response.data) {
if (!this.validateEnableTest()) { let item = response.data;
this.$warning(this.$t('api_test.enable_validate_tip'));
return; this.test = new Test({
} id: item.id,
this.save(() => { projectId: item.projectId,
this.$success(this.$t('commons.save_success')); name: item.name,
this.runTest(); status: item.status,
// 广 head scenarioDefinition: JSON.parse(item.scenarioDefinition),
ApiEvent.$emit(LIST_CHANGE); schedule: item.schedule ? item.schedule : {},
})
},
getBodyUploadFiles() {
let bodyUploadFiles = [];
this.test.bodyUploadIds = [];
this.test.scenarioDefinition.forEach(scenario => {
scenario.requests.forEach(request => {
if (request.body) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
this.test.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
}); });
this.updateReference();
this.$refs.config.reset();
} }
}); });
}); },
return bodyUploadFiles; save(callback) {
}, let validator = this.test.isValid();
validateEnableTest() { if (!validator.isValid) {
for (let scenario of this.test.scenarioDefinition) { this.$warning(this.$t(validator.info));
if (scenario.enable) { return;
for (let request of scenario.requests) { }
if (request.enable) { this.change = false;
return true; let bodyFiles = this.getBodyUploadFiles();
let url = this.create ? "/api/create" : "/api/update";
let jmx = this.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.result = this.$fileUpload(url, file, bodyFiles, this.test, () => {
if (callback) callback();
this.create = false;
this.resetBodyFile();
});
},
saveTest() {
this.save(() => {
this.$success(this.$t('commons.save_success'));
if (this.create) {
this.$router.push({
path: '/api/test/edit?id=' + this.test.id
})
}
// 广 head
ApiEvent.$emit(LIST_CHANGE);
})
},
runTest() {
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
this.$success(this.$t('api_test.running'));
this.$router.push({
path: '/api/report/view/' + response.data
})
});
},
saveRunTest() {
this.change = false;
if (!this.validateEnableTest()) {
this.$warning(this.$t('api_test.enable_validate_tip'));
return;
}
this.save(() => {
this.$success(this.$t('commons.save_success'));
this.runTest();
// 广 head
ApiEvent.$emit(LIST_CHANGE);
})
},
getBodyUploadFiles() {
let bodyUploadFiles = [];
this.test.bodyUploadIds = [];
this.test.scenarioDefinition.forEach(scenario => {
scenario.requests.forEach(request => {
if (request.body) {
request.body.kvs.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
this.test.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
}
});
});
return bodyUploadFiles;
},
validateEnableTest() {
for (let scenario of this.test.scenarioDefinition) {
if (scenario.enable) {
for (let request of scenario.requests) {
if (request.enable) {
return true;
}
} }
} }
} }
} return false;
return false; },
}, resetBodyFile() {
resetBodyFile() { //
// this.test.scenarioDefinition.forEach(scenario => {
this.test.scenarioDefinition.forEach(scenario => { scenario.requests.forEach(request => {
scenario.requests.forEach(request => { if (request.body) {
if (request.body) { request.body.kvs.forEach(param => {
request.body.kvs.forEach(param => { if (param.files) {
if (param.files) { param.files.forEach(item => {
param.files.forEach(item => { if (item.file) {
if (item.file) { item.file = undefined;
item.file = undefined; }
} });
}); }
} });
}); }
} });
}); });
}); },
}, cancel() {
cancel() { this.$router.push('/api/test/list/all');
this.$router.push('/api/test/list/all'); },
}, handleCommand(command) {
handleCommand(command) { switch (command) {
switch (command) { case "report":
case "report": this.$refs.reportDialog.open();
this.$refs.reportDialog.open(); break;
break; case "performance":
case "performance": this.$store.commit('setTest', {
this.$store.commit('setTest', { projectId: this.test.projectId,
projectId: this.test.projectId, name: this.test.name,
name: this.test.name, jmx: this.test.toJMX()
jmx: this.test.toJMX() })
}) this.$router.push({
this.$router.push({ path: "/performance/test/create"
path: "/performance/test/create" })
}) break;
break; case "export":
case "export": downloadFile(this.test.name + ".json", this.test.export());
downloadFile(this.test.name + ".json", this.test.export()); break;
break; case "import":
case "import": this.$refs.apiImport.open();
this.$refs.apiImport.open(); break;
break; }
} },
}, saveCronExpression(cronExpression) {
saveCronExpression(cronExpression) { this.test.schedule.enable = true;
this.test.schedule.enable = true; this.test.schedule.value = cronExpression;
this.test.schedule.value = cronExpression; this.saveSchedule();
this.saveSchedule(); },
}, saveSchedule() {
saveSchedule() { this.checkScheduleEdit();
this.checkScheduleEdit(); let param = {};
let param = {}; param = this.test.schedule;
param = this.test.schedule; param.resourceId = this.test.id;
param.resourceId = this.test.id; let url = '/api/schedule/create';
let url = '/api/schedule/create'; if (param.id) {
if (param.id) { url = '/api/schedule/update';
url = '/api/schedule/update'; }
} this.$post(url, param, () => {
this.$post(url, param, () => { this.$success(this.$t('commons.save_success'));
this.$success(this.$t('commons.save_success')); this.getTest(this.test.id);
this.getTest(this.test.id); });
}); },
}, checkScheduleEdit() {
checkScheduleEdit() { if (this.create) {
if (this.create) { this.$message(this.$t('api_test.environment.please_save_test'));
this.$message(this.$t('api_test.environment.please_save_test')); return false;
return false; }
} return true;
return true; },
}, runDebug(scenario) {
runDebug(scenario) { if (this.create) {
if (this.create) { this.$warning(this.$t('api_test.environment.please_save_test'));
this.$warning(this.$t('api_test.environment.please_save_test')); return;
return; }
}
let url = "/api/run/debug"; let url = "/api/run/debug";
let runningTest = new Test(); let runningTest = new Test();
Object.assign(runningTest, this.test); Object.assign(runningTest, this.test);
let bodyFiles = this.getBodyUploadFiles(); let bodyFiles = this.getBodyUploadFiles();
runningTest.scenarioDefinition = []; runningTest.scenarioDefinition = [];
runningTest.scenarioDefinition.push(scenario); runningTest.scenarioDefinition.push(scenario);
let validator = runningTest.isValid(); let validator = runningTest.isValid();
if (!validator.isValid) { if (!validator.isValid) {
this.$warning(this.$t(validator.info)); this.$warning(this.$t(validator.info));
return; return;
} }
let jmx = runningTest.toJMX(); let jmx = runningTest.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"}); let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name); let file = new File([blob], jmx.name);
this.$fileUpload(url, file, bodyFiles, this.test, response => { this.$fileUpload(url, file, bodyFiles, this.test, response => {
this.debugReportId = response.data; this.debugReportId = response.data;
this.resetBodyFile(); this.resetBodyFile();
}); });
},
handleEvent(event) {
if (event.keyCode === 83 && event.ctrlKey) {
console.log('拦截到 ctrl + s');//ctrl+s
this.saveTest();
event.preventDefault();
event.returnValue = false;
return false;
}
},
}, },
handleEvent(event) {
if (event.keyCode === 83 && event.ctrlKey) {
console.log('拦截到 ctrl + s');//ctrl+s
this.saveTest();
event.preventDefault();
event.returnValue = false;
return false;
}
},
},
created() { created() {
this.init(); this.init();
// //
document.addEventListener('keydown', this.handleEvent) document.addEventListener('keydown', this.handleEvent)
}, },
beforeDestroy() { beforeDestroy() {
document.removeEventListener('keydown', this.handleEvent); document.removeEventListener('keydown', this.handleEvent);
}
} }
}
</script> </script>
<style scoped> <style scoped>
.test-container { .test-container {
height: calc(100vh - 155px); height: calc(100vh - 155px);
min-height: 600px; min-height: 600px;
} }
.test-name { .test-name {
width: 600px; width: 600px;
margin-left: -20px; margin-left: -20px;
margin-right: 20px; margin-right: 20px;
} }
.test-project { .test-project {
min-width: 150px; min-width: 150px;
} }
.test-container .more { .test-container .more {
margin-left: 10px; margin-left: 10px;
} }
</style> </style>

View File

@ -103,7 +103,7 @@
}, },
}, },
created() { created() {
if (this.items.length === 0) { if (this.items.length === 0 || this.items[this.items.length - 1].name) {
this.items.push(new KeyValue({enable: true})); this.items.push(new KeyValue({enable: true}));
} }
} }

View File

@ -99,6 +99,8 @@ export default {
clone(row) { clone(row) {
let scenarios = []; let scenarios = [];
row.selected.forEach(options => { row.selected.forEach(options => {
// IDID
options.id = undefined;
scenarios.push(new Scenario(options)); scenarios.push(new Scenario(options));
}) })
this.$emit('select', scenarios); this.$emit('select', scenarios);

View File

@ -168,7 +168,7 @@
} }
}, },
created() { created() {
if (this.parameters.length === 0) { if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
this.parameters.push(new KeyValue( {type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'})); this.parameters.push(new KeyValue( {type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'}));
} }
} }

View File

@ -30,7 +30,7 @@
</el-form-item> </el-form-item>
<el-form-item v-if="useEnvironment || selectedPlatformValue == 'Swagger2'" :label="$t('api_test.environment.environment_config')" prop="environmentId"> <el-form-item v-if="useEnvironment || selectedPlatformValue == 'Swagger2'" :label="$t('api_test.environment.environment_config')" prop="environmentId">
<el-select v-if="showEnvironmentSelect" size="small" v-model="formData.environmentId" class="environment-select" clearable> <el-select v-if="showEnvironmentSelect" size="small" v-model="formData.environmentId" class="environment-select" clearable>
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name + ': ' + environment.protocol + '://' + environment.socket" :value="environment.id"/> <el-option v-for="(environment, index) in environments" :key="index" :label="environment.name" :value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button> <el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
<template v-slot:empty> <template v-slot:empty>
<div class="empty-environment"> <div class="empty-environment">
@ -203,6 +203,15 @@
if (this.formData.projectId) { if (this.formData.projectId) {
this.$get('/api/environment/list/' + this.formData.projectId, response => { this.$get('/api/environment/list/' + this.formData.projectId, response => {
this.environments = response.data; this.environments = response.data;
let hasEnvironmentId = false;
this.environments.forEach(env => {
if (env.id === this.formData.environmentId) {
hasEnvironmentId = true;
}
});
if (!hasEnvironmentId) {
this.formData.environmentId = '';
}
}); });
} else { } else {
this.environments = []; this.environments = [];

View File

@ -1043,11 +1043,8 @@ class JMXGenerator {
this.addScenarioHeaders(threadGroup, scenario); this.addScenarioHeaders(threadGroup, scenario);
this.addScenarioCookieManager(threadGroup, scenario); this.addScenarioCookieManager(threadGroup, scenario);
// 放在计划或线程组中,不建议放具体某个请求中
this.addDNSCacheManager(threadGroup, scenario);
this.addJDBCDataSources(threadGroup, scenario); this.addJDBCDataSources(threadGroup, scenario);
scenario.requests.forEach(request => { scenario.requests.forEach(request => {
if (request.enable) { if (request.enable) {
if (!request.isValid()) return; if (!request.isValid()) return;
@ -1065,6 +1062,8 @@ class JMXGenerator {
sampler = new JDBCSampler(request.name || "", request); sampler = new JDBCSampler(request.name || "", request);
} }
this.addDNSCacheManager(sampler, scenario.environment, request.useEnvironment);
this.addRequestExtractor(sampler, request); this.addRequestExtractor(sampler, request);
this.addRequestAssertion(sampler, request); this.addRequestAssertion(sampler, request);
@ -1126,28 +1125,26 @@ class JMXGenerator {
} }
} }
addDNSCacheManager(threadGroup, scenario) { addDNSCacheManager(httpSamplerProxy, environment, useEnv) {
if (scenario.requests.length < 1) { if (environment && useEnv === true) {
return let commonConfig = environment.config.commonConfig;
}
let request = scenario.requests[0];
if (request.environment) {
let commonConfig = request.environment.config.commonConfig;
let hosts = commonConfig.hosts; let hosts = commonConfig.hosts;
if (commonConfig.enableHost && hosts.length > 0) { if (commonConfig.enableHost && hosts.length > 0) {
let name = request.name + " DNSCacheManager"; let name = " DNSCacheManager";
// 强化判断如果未匹配到合适的host则不开启DNSCache // 强化判断如果未匹配到合适的host则不开启DNSCache
let domain = request.environment.config.httpConfig.domain; let domain = environment.config.httpConfig.domain;
let validHosts = []; let validHosts = [];
hosts.forEach(item => { hosts.forEach(item => {
let d = item.domain.trim().replace("http://", "").replace("https://", ""); if (item.domain != undefined && domain != undefined) {
if (item && d === domain.trim()) { let d = item.domain.trim().replace("http://", "").replace("https://", "");
item.domain = d; // 域名去掉协议 if (d === domain.trim()) {
validHosts.push(item); item.domain = d; // 域名去掉协议
validHosts.push(item);
}
} }
}); });
if (validHosts.length > 0) { if (validHosts.length > 0) {
threadGroup.put(new DNSCacheManager(name, validHosts)); httpSamplerProxy.put(new DNSCacheManager(name, validHosts));
} }
} }
} }
@ -1222,21 +1219,22 @@ class JMXGenerator {
if (request.controller.isValid() && request.controller.enable) { if (request.controller.isValid() && request.controller.enable) {
if (request.controller instanceof IfController) { if (request.controller instanceof IfController) {
let name = request.controller.label(); let name = request.controller.label();
let variable = request.controller.variable; let variable = "\"" + request.controller.variable + "\"";
let operator = request.controller.operator; let operator = request.controller.operator;
let value = request.controller.value; let value = "\"" + request.controller.value + "\"";
if (operator === "=~" || operator === "!~") { if (operator === "=~" || operator === "!~") {
value = "\".*" + value + ".*\""; value = "\".*" + request.controller.value + ".*\"";
} }
if (operator === "is empty") { if (operator === "is empty") {
variable = "empty(\"" + variable + "\")"; variable = "empty(" + variable + ")";
operator = ""; operator = "";
value = ""; value = "";
} }
if (operator === "is not empty") { if (operator === "is not empty") {
variable = "!empty(\"" + variable + "\")"; variable = "!empty(" + variable + ")";
operator = ""; operator = "";
value = ""; value = "";
} }

View File

@ -41,6 +41,13 @@ router.beforeEach((to, from, next) => {
} }
}); });
//重复点击导航路由报错
const routerPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error)
}
// 登入后跳转至原路径 // 登入后跳转至原路径
function redirectLoginPath(originPath) { function redirectLoginPath(originPath) {
let redirectUrl = sessionStorage.getItem('redirectUrl'); let redirectUrl = sessionStorage.getItem('redirectUrl');

View File

@ -3,7 +3,7 @@
<template v-slot:header> <template v-slot:header>
<span class="title">{{$t('api_report.title')}}</span> <span class="title">{{$t('api_report.title')}}</span>
</template> </template>
<el-table border :data="tableData" class="adjust-table table-content" @row-click="link"> <el-table border :data="tableData" class="adjust-table table-content" @row-click="link" height="300px">
<el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/> <el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/>
<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"> <template v-slot:default="scope">

View File

@ -3,7 +3,7 @@
<template v-slot:header> <template v-slot:header>
<span class="title">{{$t('commons.test')}}</span> <span class="title">{{$t('commons.test')}}</span>
</template> </template>
<el-table border :data="tableData" class="adjust-table table-content" @row-click="link"> <el-table border :data="tableData" class="adjust-table table-content" @row-click="link" height="300px">
<el-table-column prop="name" :label="$t('commons.name')" width="150" show-overflow-tooltip/> <el-table-column prop="name" :label="$t('commons.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 prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip/>
<el-table-column width="250" :label="$t('commons.create_time')"> <el-table-column width="250" :label="$t('commons.create_time')">

View File

@ -39,8 +39,7 @@
:disabled="!row.edit || readOnly" :disabled="!row.edit || readOnly"
size="mini" size="mini"
v-model="row.enable" v-model="row.enable"
active-color="#13ce66" inactive-color="#DCDFE6">
inactive-color="#ff4949">
</el-switch> </el-switch>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -37,8 +37,7 @@
<el-table-column prop="status" :label="$t('commons.status')"> <el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-switch v-model="scope.row.status" <el-switch v-model="scope.row.status"
active-color="#13ce66" inactive-color="#DCDFE6"
inactive-color="#ff4949"
active-value="ACTIVE" active-value="ACTIVE"
inactive-value="DISABLED" inactive-value="DISABLED"
@change="changeSwitch(scope.row)" @change="changeSwitch(scope.row)"

View File

@ -16,8 +16,7 @@
<el-table-column prop="status" :label="$t('test_resource_pool.enable_disable')"> <el-table-column prop="status" :label="$t('test_resource_pool.enable_disable')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-switch v-model="scope.row.status" <el-switch v-model="scope.row.status"
active-color="#13ce66" inactive-color="#DCDFE6"
inactive-color="#ff4949"
active-value="VALID" active-value="VALID"
inactive-value="INVALID" inactive-value="INVALID"
@change="changeSwitch(scope.row)" @change="changeSwitch(scope.row)"

View File

@ -19,8 +19,7 @@
<el-table-column prop="status" :label="$t('commons.status')" width="120"> <el-table-column prop="status" :label="$t('commons.status')" width="120">
<template v-slot:default="scope"> <template v-slot:default="scope">
<el-switch v-model="scope.row.status" <el-switch v-model="scope.row.status"
active-color="#13ce66" inactive-color="#DCDFE6"
inactive-color="#ff4949"
active-value="1" active-value="1"
inactive-value="0" inactive-value="0"
@change="changeSwitch(scope.row)" @change="changeSwitch(scope.row)"

View File

@ -35,7 +35,6 @@
@filter-change="filter" @filter-change="filter"
@select-all="handleSelectAll" @select-all="handleSelectAll"
@select="handleSelectionChange" @select="handleSelectionChange"
@row-click="showDetail"
row-key="id" row-key="id"
class="test-content adjust-table"> class="test-content adjust-table">
<el-table-column <el-table-column
@ -54,7 +53,13 @@
<el-table-column <el-table-column
prop="name" prop="name"
:label="$t('commons.name')" :label="$t('commons.name')"
show-overflow-tooltip> show-overflow-tooltip
>
<template v-slot:default="scope">
<div @mouseover="showDetail(scope.row)">
<p>{{ scope.row.name }}</p>
</div>
</template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="priority" prop="priority"
@ -86,6 +91,18 @@
<method-table-item :value="scope.row.method"/> <method-table-item :value="scope.row.method"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column
:filters="statusFilters"
column-key="status"
:label="$t('test_track.case.status')">
<template v-slot:default="scope">
<span class="el-dropdown-link">
<status-table-item :value="scope.row.reviewStatus"/>
</span>
</template>
</el-table-column>
<el-table-column <el-table-column
prop="nodePath" prop="nodePath"
:label="$t('test_track.case.module')" :label="$t('test_track.case.module')"
@ -146,6 +163,7 @@
import BatchEdit from "./BatchEdit"; import BatchEdit from "./BatchEdit";
import {WORKSPACE_ID} from "../../../../../common/js/constants"; import {WORKSPACE_ID} from "../../../../../common/js/constants";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent"; import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
export default { export default {
name: "TestCaseList", name: "TestCaseList",
@ -163,7 +181,8 @@
NodeBreadcrumb, NodeBreadcrumb,
MsTableHeader, MsTableHeader,
ShowMoreBtn, ShowMoreBtn,
BatchEdit BatchEdit,
StatusTableItem
}, },
data() { data() {
return { return {
@ -192,6 +211,11 @@
{text: this.$t('commons.performance'), value: 'performance'}, {text: this.$t('commons.performance'), value: 'performance'},
{text: this.$t('commons.api'), value: 'api'} {text: this.$t('commons.api'), value: 'api'}
], ],
statusFilters: [
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
{text: this.$t('test_track.plan_view.pass'), value: 'Pass'},
{text: '未通过', value: 'UnPass'},
],
showMore: false, showMore: false,
buttons: [ buttons: [
{ {

View File

@ -5,7 +5,7 @@
border border
:data="tableData" :data="tableData"
@row-click="intoPlan" @row-click="intoPlan"
v-loading="result.loading"> v-loading="result.loading" height="300px">
<el-table-column <el-table-column
prop="name" prop="name"
fixed fixed

View File

@ -14,7 +14,7 @@
border border
:data="tableData" :data="tableData"
@row-click="intoPlan" @row-click="intoPlan"
v-loading="result.loading"> v-loading="result.loading" height="300px">
<el-table-column <el-table-column
prop="name" prop="name"
fixed fixed
@ -36,29 +36,19 @@
<el-table-column <el-table-column
prop="status" prop="status"
:label="$t('test_track.plan.plan_status')" :label="$t('test_track.plan.plan_status')">
show-overflow-tooltip>
<template v-slot:default="scope"> <template v-slot:default="scope">
<plan-status-table-item :value="scope.row.status"/> <plan-status-table-item :value="scope.row.status"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="projectName" :label="$t('test_track.review.result_distribution')">
:label="$t('test_track.review.done')"
show-overflow-tooltip>
<template v-slot:default="scope"> <template v-slot:default="scope">
{{scope.row.reviewed}}/{{scope.row.total}} <el-tooltip :content="getResultTip(scope.row.total,scope.row.reviewed,scope.row.pass)"
</template> placement="top" :enterable="false" class="item" effect="dark">
</el-table-column> <yan-progress :total="scope.row.total" :done="scope.row.reviewed" :modify="scope.row.pass" :tip="tip"/>
</el-tooltip>
<el-table-column
prop="projectName"
:label="$t('test_track.home.review_progress')"
min-width="100"
show-overflow-tooltip>
<template v-slot:default="scope">
<el-progress :percentage="scope.row.testRate"></el-progress>
</template> </template>
</el-table-column> </el-table-column>
@ -87,7 +77,12 @@ export default {
return { return {
result: {}, result: {},
tableData: [], tableData: [],
showMyCreator: false showMyCreator: false,
tip: [
{text: "X", fillStyle: '#D3D3D3'},
{text: "X", fillStyle: '#ee4545'},
{text: "X", fillStyle: '#4dcf4d'}
]
} }
}, },
mounted() { mounted() {
@ -115,11 +110,14 @@ export default {
}, },
searchMyCreator() { searchMyCreator() {
this.showMyCreator = !this.showMyCreator; this.showMyCreator = !this.showMyCreator;
if (this.showMyCreator){ if (this.showMyCreator) {
this.initTableData("creator"); this.initTableData("creator");
} else { } else {
this.initTableData("reviewer"); this.initTableData("reviewer");
} }
},
getResultTip(total, reviewed, pass) {
return '通过: ' + pass + '; ' + '未通过: ' + (reviewed - pass) + '; ' + '未评审: ' + (total - reviewed);
} }
} }
} }

View File

@ -8,7 +8,7 @@
class="adjust-table" class="adjust-table"
@row-click="editTestCase" @row-click="editTestCase"
:data="tableData" :data="tableData"
v-loading="result.loading"> v-loading="result.loading" height="300px">
<el-table-column <el-table-column
prop="name" prop="name"

View File

@ -62,6 +62,15 @@
<type-table-item :value="scope.row.type"/> <type-table-item :value="scope.row.type"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column
:filters="statusFilters"
column-key="status"
:label="$t('test_track.case.status')"
show-overflow-tooltip>
<template v-slot:default="scope">
<status-table-item :value="scope.row.reviewStatus"/>
</template>
</el-table-column>
</el-table> </el-table>
<div style="text-align: center"> {{testReviews.length}} </div> <div style="text-align: center"> {{testReviews.length}} </div>
</el-main> </el-main>
@ -91,6 +100,7 @@ import MsTableHeader from "../../../../common/components/MsTableHeader";
import SwitchProject from "../../../case/components/SwitchProject"; import SwitchProject from "../../../case/components/SwitchProject";
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components"; import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
import {_filter} from "../../../../../../common/js/utils"; import {_filter} from "../../../../../../common/js/utils";
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
export default { export default {
name: "TestReviewRelevance", name: "TestReviewRelevance",
@ -102,7 +112,8 @@ export default {
MsTableSearchBar, MsTableSearchBar,
MsTableAdvSearchBar, MsTableAdvSearchBar,
MsTableHeader, MsTableHeader,
SwitchProject SwitchProject,
StatusTableItem
}, },
data() { data() {
return { return {
@ -130,7 +141,12 @@ export default {
{text: this.$t('commons.functional'), value: 'functional'}, {text: this.$t('commons.functional'), value: 'functional'},
{text: this.$t('commons.performance'), value: 'performance'}, {text: this.$t('commons.performance'), value: 'performance'},
{text: this.$t('commons.api'), value: 'api'} {text: this.$t('commons.api'), value: 'api'}
] ],
statusFilters: [
{text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
{text: this.$t('test_track.case.status_pass'), value: 'Pass'},
{text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
],
}; };
}, },
props: { props: {

View File

@ -283,6 +283,7 @@ export default {
saveCase(status) { saveCase(status) {
let param = {}; let param = {};
param.id = this.testCase.id; param.id = this.testCase.id;
param.caseId = this.testCase.caseId;
param.reviewId = this.testCase.reviewId; param.reviewId = this.testCase.reviewId;
param.status = status; param.status = status;
this.$post('/test/review/case/edit', param, () => { this.$post('/test/review/case/edit', param, () => {

View File

@ -104,13 +104,12 @@
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="status"
:filters="statusFilters" :filters="statusFilters"
column-key="status" column-key="status"
:label="$t('test_track.review_view.execute_result')"> :label="$t('test_track.review_view.execute_result')">
<template v-slot:default="scope"> <template v-slot:default="scope">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<status-table-item :value="scope.row.status"/> <status-table-item :value="scope.row.reviewStatus"/>
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
@ -207,9 +206,9 @@ export default {
{text: this.$t('commons.api'), value: 'api'} {text: this.$t('commons.api'), value: 'api'}
], ],
statusFilters: [ statusFilters: [
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'}, {text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
{text: this.$t('test_track.plan_view.pass'), value: 'Pass'}, {text: this.$t('test_track.case.status_pass'), value: 'Pass'},
{text: '未通过', value: 'UnPass'}, {text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
], ],
showMore: false, showMore: false,
buttons: [ buttons: [

@ -1 +1 @@
Subproject commit cc38137a69a0f20fadece9c0f9f50a9468c4ace9 Subproject commit 06d935cd1d22ab36f09763745c2aff8ad3fb08c1

View File

@ -7,6 +7,7 @@ import ajax from "../common/js/ajax";
import App from './App.vue'; import App from './App.vue';
import message from "../common/js/message"; import message from "../common/js/message";
import router from "./components/common/router/router"; import router from "./components/common/router/router";
import YanProgress from 'yan-progress';
import './permission' // permission control import './permission' // permission control
import i18n from "../i18n/i18n"; import i18n from "../i18n/i18n";
import store from "./store"; import store from "./store";
@ -28,6 +29,7 @@ Vue.use(chart);
Vue.use(CalendarHeatmap); Vue.use(CalendarHeatmap);
Vue.use(message); Vue.use(message);
Vue.use(CKEditor); Vue.use(CKEditor);
Vue.use(YanProgress)
// v-permission // v-permission
Vue.directive('permission', permission); Vue.directive('permission', permission);

View File

@ -27,14 +27,14 @@ body {
} }
/*解决高度塌陷和边距重叠*/ /*解决高度塌陷和边距重叠*/
.clearfix:before,.clearfix:after{ .clearfix:before, .clearfix:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
/*解决富文本框中link显示问题*/ /*解决富文本框中link显示问题*/
.ck-rounded-corners .ck.ck-balloon-panel,.ck.ck-balloon-panel.ck-rounded-corners { .ck-rounded-corners .ck.ck-balloon-panel, .ck.ck-balloon-panel.ck-rounded-corners {
z-index: 10055 !important; z-index: 10055 !important;
} }
@ -50,7 +50,7 @@ body {
/* <-- 表格拖拽表头调整宽度,在 t-bable 上添加 border 属性,并添加 adjust-table 类名*/ /* <-- 表格拖拽表头调整宽度,在 t-bable 上添加 border 属性,并添加 adjust-table 类名*/
.adjust-table td { .adjust-table td {
border-right-color: white; border-right: 0;
} }
.adjust-table th { .adjust-table th {
@ -74,12 +74,18 @@ body {
width: 3px; width: 3px;
background-color: #EBEEF5; background-color: #EBEEF5;
} }
.adjust-table tr:hover td {
border-right: 0;
}
/* 表格拖拽表头调整宽度 --> */ /* 表格拖拽表头调整宽度 --> */
/* <-- 表格 input 编辑效果*/ /* <-- 表格 input 编辑效果*/
.table-edit-input .el-textarea__inner { .table-edit-input .el-textarea__inner {
border-style: hidden; border-style: hidden;
} }
.table-edit-input.is-disabled .el-textarea__inner { .table-edit-input.is-disabled .el-textarea__inner {
background-color: white; background-color: white;
color: #606266; color: #606266;
@ -93,6 +99,7 @@ body {
.table-edit-input .el-textarea__inner:focus { .table-edit-input .el-textarea__inner:focus {
border: 1px solid #409EFF; border: 1px solid #409EFF;
} }
/* 表格 input 编辑效果 --> */ /* 表格 input 编辑效果 --> */
.ms-border { .ms-border {

View File

@ -53,6 +53,10 @@ export default {
login(); login();
return; return;
} }
if (error.response && error.response.status === 403) {
window.location.href = "/";
return;
}
result.loading = false; result.loading = false;
window.console.error(error.response || error.message); window.console.error(error.response || error.message);
if (error.response && error.response.data) { if (error.response && error.response.data) {

View File

@ -695,6 +695,10 @@ export default {
batch_delete_case: 'Batch delete', batch_delete_case: 'Batch delete',
batch_unlink: 'Batch Unlink', batch_unlink: 'Batch Unlink',
project_name: "Project", project_name: "Project",
status: 'Status',
status_prepare: 'Prepare',
status_pass: 'Pass',
status_un_pass: 'UnPass',
import: { import: {
import: "Import test case", import: "Import test case",
case_import: "Import test case", case_import: "Import test case",
@ -740,6 +744,10 @@ export default {
plan_status_prepare: "Not started", plan_status_prepare: "Not started",
plan_status_running: "Starting", plan_status_running: "Starting",
plan_status_completed: "Completed", plan_status_completed: "Completed",
planned_start_time: "Scheduled Start Time",
planned_end_time: "Scheduled End Time",
actual_start_time: "Actual Start Time",
actual_end_time: "Actual End Time",
plan_delete_confirm: "All use cases under this plan will be deleted,confirm delete test plan: ", plan_delete_confirm: "All use cases under this plan will be deleted,confirm delete test plan: ",
plan_delete: "Delete test plan", plan_delete: "Delete test plan",
}, },
@ -765,7 +773,8 @@ export default {
my_create: "My Create", my_create: "My Create",
reviewed_by_me: "Review By Me", reviewed_by_me: "Review By Me",
creator: "Creator", creator: "Creator",
done: "Commented use cases" done: "Commented use cases",
result_distribution: "Result Distribution"
}, },
comment: { comment: {
no_comment: "No Comment", no_comment: "No Comment",

View File

@ -697,6 +697,10 @@ export default {
batch_delete_case: '批量删除用例', batch_delete_case: '批量删除用例',
batch_unlink: '批量取消关联', batch_unlink: '批量取消关联',
project_name: '所属项目', project_name: '所属项目',
status: '状态',
status_prepare: '未开始',
status_pass: '通过',
status_un_pass: '未通过',
import: { import: {
import: "导入用例", import: "导入用例",
case_import: "导入测试用例", case_import: "导入测试用例",
@ -771,7 +775,8 @@ export default {
my_create: "我创建的评审", my_create: "我创建的评审",
reviewed_by_me: "待我评审", reviewed_by_me: "待我评审",
creator: "创建人", creator: "创建人",
done: "已评用例" done: "已评用例",
result_distribution: "结果分布"
}, },
comment: { comment: {
no_comment: "暂无评论", no_comment: "暂无评论",
@ -785,7 +790,7 @@ export default {
all_case: "全部用例", all_case: "全部用例",
start_review: "开始评审", start_review: "开始评审",
relevance_case: "关联用例", relevance_case: "关联用例",
execute_result: "执行结果", execute_result: "评审结果",
}, },
module: { module: {
search: "搜索模块", search: "搜索模块",

View File

@ -469,9 +469,9 @@ export default {
input_url: "請輸入請求URL", input_url: "請輸入請求URL",
input_path: "請輸入請求路徑", input_path: "請輸入請求路徑",
name: "請求名稱", name: "請求名稱",
content_type: "請求類型",
method: "請求方法", method: "請求方法",
url: "請求URL", url: "請求URL",
content_type: "請求類型",
path: "請求路徑", path: "請求路徑",
address: "請求地址", address: "請求地址",
refer_to_environment: "引用環境", refer_to_environment: "引用環境",
@ -697,6 +697,10 @@ export default {
batch_delete_case: '批量刪除用例', batch_delete_case: '批量刪除用例',
batch_unlink: '批量取消關聯', batch_unlink: '批量取消關聯',
project_name: '所屬項目', project_name: '所屬項目',
status: '狀態',
status_prepare: '未開始',
status_pass: '通過',
status_un_pass: '未通過',
import: { import: {
import: "導入用例", import: "導入用例",
case_import: "導入測試用例", case_import: "導入測試用例",
@ -742,6 +746,10 @@ export default {
plan_status_prepare: "未開始", plan_status_prepare: "未開始",
plan_status_running: "進行中", plan_status_running: "進行中",
plan_status_completed: "已完成", plan_status_completed: "已完成",
planned_start_time: "計劃開始",
planned_end_time: "計劃結束",
actual_start_time: "實際開始",
actual_end_time: "實際結束",
plan_delete_confirm: "將刪除該測試計劃下所有用例,確認刪除測試計劃: ", plan_delete_confirm: "將刪除該測試計劃下所有用例,確認刪除測試計劃: ",
plan_delete: "刪除計劃", plan_delete: "刪除計劃",
}, },
@ -767,7 +775,8 @@ export default {
my_create: "我創建的評審", my_create: "我創建的評審",
reviewed_by_me: "待我評審", reviewed_by_me: "待我評審",
creator: "創建人", creator: "創建人",
done: "已評用例" done: "已評用例",
result_distribution: "結果分佈"
}, },
comment: { comment: {
no_comment: "暫無評論", no_comment: "暫無評論",
@ -781,7 +790,7 @@ export default {
all_case: "全部用例", all_case: "全部用例",
start_review: "開始評審", start_review: "開始評審",
relevance_case: "關聯用例", relevance_case: "關聯用例",
execute_result: "執行結果", execute_result: "評審結果",
}, },
module: { module: {
search: "搜索模塊", search: "搜索模塊",